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

如何在Django Ninja的ModelSchema中包含字段显示文本(如get_foo_display)

问题

如何在Django Ninja接口的返回结果中,同时展示IntegerField的数值和对应的choices显示文本(即Django模型的get_foo_display()方法返回值)?

当前场景是:Django模型中的IntegerField定义了choices集合,使用Django Ninja的ModelSchema更新模型后,接口仅返回数值(如1、2、3),希望同时返回对应的显示文本(如'unhelpful')。

示例代码

models.py

from django.db import models
from django.utils.translation import gettext_lazy as _
from django.conf import settings

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: Schema}
)
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)}'}

已尝试的方法

曾在UpdatePracticeSessionSchema的Meta类前添加rating_choices = PracticeSession.rating.field.choices,但触发Pydantic错误:

pydantic.errors.PydanticUserError: A non-annotated attribute was detected: rating_choices = [(1, 'unhelpful'), (2, 'neither helpful nor unhelpful'), (3, 'helpful')]. All model fields require a type annotation; if rating_choices is not meant to be a field, you may be able to resolve this error by annotating it as a ClassVar or updating model_config['ignored_types'].

这种方式仅返回所有选项的映射,还需前端额外处理提取对应文本,不符合需求——希望接口直接返回当前数值对应的显示文本。

解决方案

方法1:拆分更新与响应Schema(推荐)

更新操作只需接收数值,返回数据需包含显示文本,分开定义两个Schema更清晰:

from ninja import NinjaAPI, Schema, ModelSchema
from ninja.security import django_auth
from practice.models import PracticeSession
from pydantic import Field

api = NinjaAPI(csrf=True, auth=django_auth)

# 用于接收更新请求的Schema
class UpdatePracticeSessionSchema(ModelSchema):
    class Meta:
        model = PracticeSession
        fields = ['is_practice_done', 'rating']

# 用于返回响应数据的Schema,新增显示文本字段
class PracticeSessionResponseSchema(ModelSchema):
    rating_display: str = Field(..., description="评分对应的显示文本")
    
    class Meta:
        model = PracticeSession
        fields = ['is_practice_done', 'rating']
    
    @classmethod
    def from_orm(cls, obj):
        # 调用模型的get_rating_display()方法获取显示文本
        return cls(
            **super().from_orm(obj).dict(),
            rating_display=obj.get_rating_display()
        )

@api.put(
    "member/{member_id}/practice_session/{sesh_id}/update/",
    response={200: PracticeSessionResponseSchema, 404: Schema}
)
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)}'}

方法2:在单Schema中添加计算字段

如果不想拆分Schema,可以直接在原Schema中新增字段,通过验证器填充显示文本:

from ninja import NinjaAPI, Schema, ModelSchema
from ninja.security import django_auth
from practice.models import PracticeSession
from pydantic import Field, field_validator
from typing import Any

api = NinjaAPI(csrf=True, auth=django_auth)

class UpdatePracticeSessionSchema(ModelSchema):
    rating_display: str = Field(None, description="评分对应的显示文本")
    
    class Meta:
        model = PracticeSession
        fields = ['is_practice_done', 'rating']
    
    @field_validator('rating_display', mode='before')
    def populate_rating_display(cls, v, values: dict[str, Any]):
        # 从模型实例转换时(响应阶段)填充显示文本,接收请求时忽略
        if isinstance(values.get('__object__'), PracticeSession):
            return values['__object__'].get_rating_display()
        return v

@api.put(
    "member/{member_id}/practice_session/{sesh_id}/update/",
    response={200: UpdatePracticeSessionSchema, 404: Schema}
)
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)}'}

方法3:封装SerializerExtension(批量场景)

如果多个模型都需要返回choices显示文本,可以封装成Django Ninja扩展实现批量处理:

from ninja import NinjaAPI, Schema, ModelSchema
from ninja.security import django_auth
from practice.models import PracticeSession
from ninja.extensions import SerializerExtension

class ChoiceDisplayExtension(SerializerExtension):
    def serialize_field(self, field, value, obj):
        # 自动检测模型是否有get_xxx_display方法,有则添加xxx_display字段
        if hasattr(obj, f'get_{field.name}_display'):
            self.context['result'][f'{field.name}_display'] = getattr(obj, f'get_{field.name}_display')()
        return value

# 注册扩展到API实例
api = NinjaAPI(csrf=True, auth=django_auth, extensions=[ChoiceDisplayExtension])

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: Schema}
)
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)}'}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 16:40:57