GET API 返回选项的字符串而不是ID
Django 中常常要用到选项字段
只读显示字符串
为了提高数据库效率和用户可读性,我们实际存储的是整数,显示的时候以字符串显示。
这个属性在 Django Admin 得到了很好的处理,但到了 Django Rest Framework 就不会自动转化了。
下面的在GET的时候显示字符串,但这个字段就变成只读但了。
# models.py
class User(AbstractUser):
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
# serializers.py
class UserSerializer(serializers.ModelSerializer):
# 自定义了gender 字段,该字段变成只读的了。
gender = serializers.CharField(source='get_gender_display')
class Meta:
model = User
# viewsets.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
这里涉及到一个有趣的实例方法: get_FOO_display
对于模型中含有 ++choices++ 参数的字段, FOO 是字段的名字, get_FOO_display() 返回选项的可读字符串
【重点】可读写:GET-显示选项名字 POST-接受数字
要实现 model 中的 Choice Field, 在 GET 的时候显示选项名字,在POST的时候既能字符串又能接受ID
接受下面singo评论的建议:参考 stackoverflow 最佳方法如下:
class CommonInfoSerializer(serializers.ModelSerializer):
....
def to_representation(self, instance):
data = super().to_representation(instance)
# 返回 data 是一个字典,更新里面的 status 值
data.update(status=instance.get_status_display())
return data
get_<field_name>_display()
是一个自带的方法,返回选项字段的可读值(字符串)
完美解决 🔚
以下其他方法在更复杂的情况下才考虑,可以忽略
方法一
比如模型中有个status
# models.py
class CommonInfo(models.Model):
INACTIVE = 0
PUBLISHED = 1
PENDING = -1
DRAFT = -2
REPORTED = -3
DELETED = -4
STATUS_CHOICES = (
(INACTIVE, 'INACTIVE'),
(PUBLISHED, 'PUBLISHED'),
(PENDING, 'PENDING'),
(DRAFT, 'DRAFT'),
(REPORTED, 'REPORTED'),
(DELETED, 'DELETED'),
)
status = models.SmallIntegerField(
choices=STATUS_CHOICES, default=PUBLISHED)
# utils.py
from rest_framework import serializers
from collections import OrderedDict
class ChoiceDisplayField(serializers.Field):
"""Custom ChoiceField serializer field."""
def __init__(self, choices, **kwargs):
"""init."""
self._choices = OrderedDict(choices)
super(ChoiceDisplayField, self).__init__(**kwargs)
# 返回可读性良好的字符串而不是 1,-1 这样的数字
def to_representation(self, obj):
"""Used while retrieving value for the field."""
return self._choices[obj]
def to_internal_value(self, data):
"""Used while storing value for the field."""
for i in self._choices:
# 这样无论用户POST上来但是CHOICES的 Key 还是Value 都能被接受
if i == data or self._choices[i] == data:
return i
raise serializers.ValidationError("Acceptable values are {0}.".format(list(self._choices.values())))
# serializers.py
from utils import ChoiceDisplayField
class CommonInfoSerializer(serializers.ModelSerializer):
INACTIVE = 0
PUBLISHED = 1
PENDING = -1
DRAFT = -2
REPORTED = -3
DELETED = -4
STATUS_CHOICES = (
(INACTIVE, 'INACTIVE'),
(PUBLISHED, 'PUBLISHED'),
(PENDING, 'PENDING'),
(DRAFT, 'DRAFT'),
(REPORTED, 'REPORTED'),
(DELETED, 'DELETED'),
)
status = ChoiceDisplayField(choices=STATUS_CHOICES)
方法二
- 参考页面提到了更简单的方法,虽然我没有试过。
- 即:继承 ChoiceField 而不是 Field,就样就不用写
to_internal_value()
了。 - 但 POST 只能接受 ID, 不能接受字符串,如下
from rest_framework import serializers
from collections import OrderedDict
class ChoiceDisplayField(serializers.ChoiceField):
"""Custom ChoiceField serializer field."""
def __init__(self, choices, **kwargs):
"""init."""
self._choices = OrderedDict(choices)
super(ChoiceDisplayField, self).__init__(**kwargs)
# 返回可读性良好的字符串而不是 1,-1 这样的数字
def to_representation(self, obj):
"""Used while retrieving value for the field."""
return self._choices[obj]