You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Ninja PUT接口未使用请求体值,返回默认字段值问题

Django Ninja API无法更新用户时区字段问题排查

目标

尝试通过Django Ninja API(结合ModelSchema)更新自定义用户模型中的tz_preference时区偏好字段,该字段设有默认值且限定了可选值列表。

问题

通过/api/docs测试API时,即使传入合法的时区值(如"Pacific/Honolulu"、"America/Chicago"),响应始终返回默认值"America/Denver",接口未使用请求体传入的值。

代码

models.py

from django.db import models
from django.contrib.auth.models import PermissionsMixin, AbstractBaseUser
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,)

    # 待修改的时区字段
    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

from ninja import NinjaAPI, Schema, ModelSchema
from django.contrib.auth import get_user_model

api = NinjaAPI()
User = get_user_model()

# 模式定义
class UserTimezoneSchema(ModelSchema):
    class Meta:
        model = User
        fields = ['tz_preference']

class NotFoundSchema(Schema):
    message: str

# 接口定义
@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})

  • 路径参数:member_id: 1
  • 请求体:{"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文档响应模式来看,似乎默认值会被强制设置,不受请求体影响,但无法确认。若问题源于模型字段默认值,不知如何解决。

怀疑的可能原因

  • 请求提交的是字符串,但字段选项定义为ZoneInfo对象(API仅能传文本数据,存疑)
  • 应该使用PATCH而非PUT方法
  • Django启用了CSRF保护但API未配置支持
  • 仅修改单个字段,未传入其他必填字段

解决方案

1. 修正TZ_CHOICES定义

TimeZoneField的choices参数无需传入ZoneInfo对象,直接使用时区字符串即可,timezone-field库会自动处理字符串到ZoneInfo的转换:

TZ_CHOICES = [
    ('Pacific/Honolulu', 'Pacific/Honolulu'),
    ('America/Anchorage', 'America/Anchorage'),
    ('America/Los_Angeles', 'America/Los_Angeles'),
    ('US/Arizona', 'US/Arizona'),
    ('America/Denver', 'America/Denver'),
    ('America/Chicago', 'America/Chicago'),
    ('America/New_York', 'America/New_York'),
]

2. 明确Schema字段类型

Django Ninja的ModelSchema可能无法自动适配TimeZoneField的序列化/反序列化,手动指定字段为字符串类型:

from ninja import Schema, ModelSchema

class UserTimezoneSchema(ModelSchema):
    tz_preference: str  # 明确接收字符串格式的时区值

    class Meta:
        model = User
        fields = ['tz_preference']

3. 返回更新后的用户对象

当前代码修改后未返回更新后的实例,需在save()后返回member,确保响应为最新值:

@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'}

4. 验证CSRF设置(可选)

API接口默认无需CSRF保护,可在初始化NinjaAPI时显式关闭:

api = NinjaAPI(csrf=False)

5. 可选:改用PATCH方法

若仅更新单个字段,使用PATCH更符合REST规范:

@api.patch("/member/{member_id}", response={200: UserTimezoneSchema, 404: NotFoundSchema})

内容的提问来源于stack exchange,提问作者DevinG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:55:55