Как включить отображаемый текст поля (например, "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)
Вернуться на верх