Почему конечная точка 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)

screenshot of Responses readout from Django Ninja API documentation

Что, на мой взгляд, идет не так:

Приведенное мною выше изображение схемы ответа для этой конечной точки из документации по 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, за то, что указал мне на это.

Вернуться на верх