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
相关产品推荐
相关产品推荐

