Как включить отображаемый текст поля (например, "get_foo_display") в ModelSchema в django ninja?
Проблема
Допустим, у меня есть модель django с IntegerField
с определенным набором вариантов выбора, а затем схема django ninja и конечная точка для обновления этой модели. Как я могу получить доступ к отображаемому тексту для IntegerField
(т. е. get_foo_display
)?
Другими словами, моя текущая схема возвращает 1, 2 или 3 для поля "рейтинг". Как сделать так, чтобы она возвращала и текст отображения?
Код примера
models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
class PracticeSession(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=False,related_name="practice_sessions", db_index=True,
)
class RATING_CHOICES(models.IntegerChoices):
UNHELP = 1, _('unhelpful')
NEITHER = 2, _('neither helpful nor unhelpful')
HELP = 3, _('helpful')
rating = models.IntegerField(choices=RATING_CHOICES.choices)
is_practice_done = models.BooleanField(default=False)
api.py
from ninja import NinjaAPI, Schema, ModelSchema
from ninja.security import django_auth
from practice.models import PracticeSession
api = NinjaAPI(csrf=True, auth=django_auth)
class UpdatePracticeSessionSchema(ModelSchema):
class Meta:
model = PracticeSession
fields = ['is_practice_done', 'rating']
@api.put(
"member/{member_id}/practice_session/{sesh_id}/update/",
response={200: UpdatePracticeSessionSchema, 404: NotFoundSchema}
)
def update_practice_sesh(request, member_id: int, sesh_id: int, data: UpdatePracticeSessionSchema):
try:
practice_sesh = PracticeSession.objects.get(pk=sesh_id)
practice_sesh.is_practice_done = data.is_practice_done
practice_sesh.rating = data.rating
practice_sesh.save()
return practice_sesh
except Exception as e:
print(f"Error in update_practice_sesh: {str(e)}")
return 404, {'message': f'Error: {str(e)}'}
То, что я пробовал
Я пробовал добавить rating_choices = PracticeSession.rating.field.choices
к моему UpdatePracticeSessionSchema
перед class: Meta
, но это вызвало пидантическую ошибку (см. ниже), и, кроме того, это дополнительное поле в моей схеме только приблизило бы меня на шаг к созданию какого-то объекта отображения (что потребовало бы написания дополнительного javascript для извлечения текста отображения для любого целочисленного значения, возвращаемого схемой), но я бы предпочел, чтобы моя схема просто возвращала текст отображения именно для того целочисленного значения, которое она возвращает.
Пидантическая ошибка
pydantic.errors.PydanticUserError: Обнаружен неаннотированный атрибут: rating_choices = [(1, 'unhelpful'), (2, 'neither helpful nor unhelpful'), (3, 'helpful')]
. Все поля модели требуют аннотации типа; если rating_choices
не является полем, вы можете решить эту ошибку, аннотировав его как ClassVar
или обновив model_config['ignored_types']
.
Вы должны установить тип рейтинга, а не установить переменную.
class UpdatePracticeSessionSchema(ModelSchema):
rating: PracticeSession.RATING_CHOICES = Field(PracticeSession.RATING_CHOICES.UNHELP.value)
class Meta:
model = PracticeSession
fields = ['is_practice_done', 'rating']
Поскольку вы хотите переопределить поведение сериализации поля, вам потребуется использовать пользовательскую логику сериализации. Как ни странно, это следует делать с помощью валидаторов полей, а не сериализаторов полей. Подробнее о том, почему так происходит, вы можете прочитать в соответствующем вопросе StackOverflow.
Сначала определите простое Schema
для поля рейтинга:
from ninja import Schema
class RatingSchema(Schema):
# You can choose the actual names you want for these fields
value: id
label: str
А затем обновите схему модели, чтобы использовать эту схему полей, и "подтвердите" ее с помощью field_validator
:
from ninja import ModelSchema
from pydantic import field_validator
from practice.models import PracticeSession
class UpdatePracticeSessionSchema(ModelSchema):
# rating should use your custom schema
rating: RatingSchema
class Meta:
model = PracticeSession
fields = ['is_practice_done', 'rating']
# mode='before' ensures that this runs before other validators
# Note that the value is an int since this is what the PracticeSession.rating returns
@field_validator('rating', mode='before')
@classmethod
def validate_rating(cls, value: int) -> RatingSchema:
# Turn the value from the model into a RATING_CHOICES
# This will throw a ValueError if it's an invalid value
# which works in our favor since Pydantic will catch this
# and throw a ValidationError
rating = PracticeSession.RATING_CHOICES(value)
return RatingSchema(value=rating.value, label=rating.label)