I'm trying to use a Django Ninja API endpoint (with Django Ninja's ModelSchema
) to update the time zone preference (tz_preference
*) field on my Django app's user model.
*Note: The tz_preference
field has a default value and is limited to a list of choices.
When I test out the API endpoint using /api/docs
, the response keeps returning the tz_preference
field's default value ("America/Denver") even though I give it other valid values in the request body ("Pacific/Honolulu", "America/Chicago", etc.).
I know that my tz_preference
field has a default value of "America/Denver", so this is most likely why the response body is {"tz_preference": "America/Denver"}
, but I'm not sure why it's sticking with the default instead of using the value I give it in the request body.
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"]
Sorry if I included too much of the code for my custom user model. Since I'm wondering if required fields are causing the unexpected behavior, I left them in.
# 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}){"tz_preference": "Pacific/Honolulu"}
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"
}'
{
"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
The image I've included above of the response schemas for this endpoint from the API documentation that Django Ninja automatically generates suggests to me that the default value is automatically set regardless of what the request body is, but I don't have any experience interpreting this automatically generated documentation, so I'm not sure.
And if the problem is due to the default value for this field in my model, I have no idea how to overcome that.
ZoneInfo
objects (I doubt this is the case because plain text data is the only kind of data that can really be submitted through the API, right?)PATCH
method instead of PUT
Turns out I just needed a return statement in the function I wrote to handle the PUT
request:
@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'}
Thanks to @vitalik, the creator of django-ninja, for pointing this out to me.