Почему конечная точка django-ninja PUT не использует значение из тела запроса, а возвращает значение по умолчанию для обновляемого поля?
Гол
Я пытаюсь использовать конечную точку API Django Ninja (с помощью ModelSchema
) для обновления поля предпочтения часового пояса (tz_preference
*) в пользовательской модели моего Django-приложения.
* Примечание: Поле tz_preference
имеет значение по умолчанию и ограничено списком вариантов выбора.
Проблема
Когда я тестирую конечную точку API с помощью /api/docs
, ответ продолжает возвращать значение поля tz_preference
по умолчанию ("Америка/Денвер"), даже если я задаю ему другие допустимые значения в теле запроса ("Тихоокеанский/Гонолулу", "Америка/Чикаго" и т. д.).
Я знаю, что мое поле tz_preference
имеет значение по умолчанию "Америка/Денвер", так что, скорее всего, именно поэтому тело ответа имеет значение {"tz_preference": "America/Denver"}
, но я не уверен, почему оно придерживается значения по умолчанию, а не использует значение, которое я дал ему в теле запроса.
Код
models.py
from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.validators import ASCIIUsernameValidator
from django.utils.translation import gettext_lazy as _
from timezone_field import TimeZoneField
from zoneinfo import ZoneInfo
TZ_CHOICES = [
(ZoneInfo('Pacific/Honolulu'), 'Pacific/Honolulu'),
(ZoneInfo('America/Anchorage'), 'America/Anchorage'),
(ZoneInfo('America/Los_Angeles'), 'America/Los_Angeles'),
(ZoneInfo('US/Arizona'), 'US/Arizona'),
(ZoneInfo('America/Denver'), 'America/Denver'),
(ZoneInfo('America/Chicago'), 'America/Chicago'),
(ZoneInfo('America/New_York'), 'America/New_York'),
]
class CustomUser(AbstractBaseUser, PermissionsMixin):
username_validator = ASCIIUsernameValidator()
username = models.CharField(
_("username"),
max_length=150,
unique=True,
db_index=True,
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
email = models.EmailField(
_("email address"),
unique=True,
db_index=True,
help_text=_(
"Required."
),
error_messages={
"unique": _("A user with that email address already exists."),
},
)
is_staff = models.BooleanField(_("staff status"), default=False,)
is_active = models.BooleanField(_("active"), default=True,)
""" -----------------FIELD I'M TRYING TO CHANGE------------------- """
tz_preference = TimeZoneField(
use_pytz=False,
choices=TZ_CHOICES,
default="America/Denver",
choices_display="STANDARD",
)
""" -------------------------------------------------------------- """
class CAT_CHOICES(models.TextChoices):
S = 'STUDENT', _('Student')
I = 'INSTRUCTOR', _('Instructor')
category = models.CharField(max_length=10, choices=CAT_CHOICES.choices)
objects = TerpUserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email", "category"]
Извините, если я включил слишком много кода для моей пользовательской модели пользователя. Поскольку я задаюсь вопросом, не являются ли обязательные поля причиной неожиданного поведения, я оставил их.
api.py
# do ye django ninja stuff
from ninja import NinjaAPI
api = NinjaAPI()
# django models stuff (to get my custom user model)
from django.contrib.auth import get_user_model
User = get_user_model()
# ninja schema stuff
from ninja import Schema, ModelSchema
"""------------------------ SCHEMAS ------------------------------"""
class UserTimezoneSchema(ModelSchema):
class Meta:
model = User
fields = ['tz_preference']
class NotFoundSchema(Schema):
message: str
"""----------------------- ENDPOINTS -----------------------------"""
@api.put("/member/{member_id}", response={200: UserTimezoneSchema, 404: NotFoundSchema})
def change_tz(request, member_id: int, data: UserTimezoneSchema):
try:
member = User.objects.get(pk=member_id)
member.tz_preference = data.tz_preference
member.save()
except User.DoesNotExist as e:
return 404, {'message': 'User does not exist'}
Информация о запросе и ответе
Запрос (PUT
/api/member/{member_id})
- path: member_id: 1
- body:
{"tz_preference": "Pacific/Honolulu"}
CURL
curl -X 'PUT' \
'http://127.0.0.1:8000/api/member/1' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"tz_preference": "Pacific/Honolulu"
}'
Ответ
- Код: 200
Тело
{
"tz_preference": "America/Denver"
}
Заголовки
content-length: 35
content-type: application/json; charset=utf-8
cross-origin-opener-policy: same-origin
date: Mon,01 Jul 2024 18:02:59 GMT
referrer-policy: same-origin
server: WSGIServer/0.2 CPython/3.10.14
x-content-type-options: nosniff
x-frame-options: DENY
Ответы (нижний раздел чтения автодокументации Django Ninja API)
Что, на мой взгляд, идет не так:
Приведенное мною выше изображение схемы ответа для этой конечной точки из документации по API, которую автоматически генерирует Django Ninja, наводит меня на мысль, что значение по умолчанию устанавливается автоматически, независимо от тела запроса, но у меня нет опыта интерпретации этой автоматически генерируемой документации, поэтому я не уверен.
А если проблема связана со значением по умолчанию для этого поля в моей модели, то я понятия не имею, как это преодолеть.
Другие возможности (хотя они кажутся мне менее вероятными)
- Может быть, это потому, что я отправляю на сервер обычные строки, а варианты выбора для этого поля определены как
ZoneInfo
объекты (сомневаюсь, что дело в этом, потому что обычные текстовые данные - единственный вид данных, который действительно можно отправить через API, верно?) - Может быть, мне следует использовать метод
PATCH
вместоPUT
- Возможно, это не работает потому, что мой Django-проект использует защиту csrf, но я не включил ее в API, который я создаю с помощью Django Ninja .
- Возможно, дело в том, что я пытаюсь изменить только одно поле, но есть и другие обязательные поля, которые я не включаю в запрос .
Операторы возврата очень важны
Оказалось, что мне просто нужен оператор возврата в функции, которую я написал для обработки запроса PUT
:
api.py
@api.put("/member/{member_id}", response={200: UserTimezoneSchema, 404: NotFoundSchema})
def change_tz(request, member_id: int, data: UserTimezoneSchema):
try:
member = User.objects.get(pk=member_id)
member.tz_preference = data.tz_preference
member.save()
return member
except User.DoesNotExist as e:
return 404, {'message': 'User does not exist'}
Спасибо @vitalik, создателю django-ninja, за то, что указал мне на это.