使用ChoiceField的Django Rest框架

aww*_*ter 54 django django-rest-framework

我的用户模型中有一些字段是选择字段,我正在试图找出如何最好地将其实现到Django Rest Framework中.

下面是一些简化的代码来展示我正在做的事情.

# 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 = serializers.CharField(source='get_gender_display')

    class Meta:
        model = User


# viewsets.py
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
Run Code Online (Sandbox Code Playgroud)

基本上我要做的是让get/post/put方法使用选择字段的显示值而不是代码,看起来像下面的JSON.

{
  'username': 'newtestuser',
  'email': 'newuser@email.com',
  'first_name': 'first',
  'last_name': 'last',
  'gender': 'Male'
  // instead of 'gender': 'M'
}
Run Code Online (Sandbox Code Playgroud)

我该怎么做呢?上面的代码不起作用.在我有这样的东西为GET工作之前,但对于POST/PUT它给了我错误.我正在寻找关于如何做到这一点的一般建议,看起来它会是常见的,但我找不到例子.或者我正在做一些非常错误的事情.

lev*_*evi 94

Django提供了Model.get_FOO_display获取字段"人类可读"值的方法:

class UserSerializer(serializers.ModelSerializer):
    gender = serializers.SerializerMethodField()

    class Meta:
        model = User

    def get_gender(self,obj):
        return obj.get_gender_display()
Run Code Online (Sandbox Code Playgroud)

对于最新的DRF(3.6.3) - 最简单的方法是:

gender = serializers.CharField(source='get_gender_display')
Run Code Online (Sandbox Code Playgroud)

  • 如果您想要所有可用选项的显示字符串怎么办? (6认同)
  • 事实证明,如果在ModelViewSet上使用http选项方法,rest-framework会公开选项,因此我不需要自定义序列化程序. (3认同)
  • 不幸的是,这不适用于写作. (3认同)
  • 您只需执行“fuel = serializers.CharField(source='get_fuel_display', read_only=True)”即可仅显示“GET”请求的人类可读名称。“POST”请求仍然适用于代码(在“ModelSerializer”上)。 (3认同)
  • 当我使用drf v3.6.3时,`gender = serializers.CharField(source ='get_gender_display')`运行良好. (2认同)

nic*_*nel 17

我建议将django-models-utils与自定义DRF序列化器字段一起使用

代码变为:

# models.py
from model_utils import Choices

class User(AbstractUser):
    GENDER = Choices(
       ('M', 'Male'),
       ('F', 'Female'),
    )

    gender = models.CharField(max_length=1, choices=GENDER, default=GENDER.M)


# serializers.py 
from rest_framework import serializers

class ChoicesField(serializers.Field):
    def __init__(self, choices, **kwargs):
        self._choices = choices
        super(ChoicesField, self).__init__(**kwargs)

    def to_representation(self, obj):
        return self._choices[obj]

    def to_internal_value(self, data):
        return getattr(self._choices, data)

class UserSerializer(serializers.ModelSerializer):
    gender = ChoicesField(choices=User.GENDER)

    class Meta:
        model = User

# viewsets.py
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
Run Code Online (Sandbox Code Playgroud)

  • 2差异:1)ChoicesField可以重复使用2)它支持不再只读的"性别"字段的版本 (9认同)
  • 很久以前,这个问题得到了回答,内置的解决方案简单得多. (3认同)
  • 我正在寻找一种通过api读取和写入选择字段的方法,这个答案已经确定了.接受的答案没有显示如何更新选择字段,这一点. (2认同)

Kis*_*han 9

顺便说一句,你需要在你的某个地方使用这样的东西,util.py并导入任何序列化ChoiceFields器.

class ChoicesField(serializers.Field):
    """Custom ChoiceField serializer field."""

    def __init__(self, choices, **kwargs):
        """init."""
        self._choices = OrderedDict(choices)
        super(ChoicesField, self).__init__(**kwargs)

    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:
            if self._choices[i] == data:
                return i
        raise serializers.ValidationError("Acceptable values are {0}.".format(list(self._choices.values())))
Run Code Online (Sandbox Code Playgroud)

  • 我更喜欢这个答案,因为这样可以允许用户输入更多的选择键或值。通过简单地将“ if self._choices [i] == data:”更改为“ if i == data或self._choices [i] == data:”。从ChoiceField继承时,不需要重写`to_internal_value()`,而只接受选择键。 (2认同)

lec*_*hup 7

DRF3.1 开始,有一个名为customizing field mapping 的新 API 。我用它来将默认的 ChoiceField 映射更改为 ChoiceDisplayField:

import six
from rest_framework.fields import ChoiceField


class ChoiceDisplayField(ChoiceField):
    def __init__(self, *args, **kwargs):
        super(ChoiceDisplayField, self).__init__(*args, **kwargs)
        self.choice_strings_to_display = {
            six.text_type(key): value for key, value in self.choices.items()
        }

    def to_representation(self, value):
        if value in ('', None):
            return value
        return {
            'value': self.choice_strings_to_values.get(six.text_type(value), value),
            'display': self.choice_strings_to_display.get(six.text_type(value), value),
        }

class DefaultModelSerializer(serializers.ModelSerializer):
    serializer_choice_field = ChoiceDisplayField
Run Code Online (Sandbox Code Playgroud)

如果您使用DefaultModelSerializer

class UserSerializer(DefaultModelSerializer):    
    class Meta:
        model = User
        fields = ('id', 'gender')
Run Code Online (Sandbox Code Playgroud)

你会得到类似的东西:

...

"id": 1,
"gender": {
    "display": "Male",
    "value": "M"
},
...
Run Code Online (Sandbox Code Playgroud)


Mar*_*ndi 6

以下解决方案适用于任何具有选择的字段,无需在序列化程序中为每个字段指定自定义方法:

from rest_framework import serializers

class ChoicesSerializerField(serializers.SerializerMethodField):
    """
    A read-only field that return the representation of a model field with choices.
    """

    def to_representation(self, value):
        # sample: 'get_XXXX_display'
        method_name = 'get_{field_name}_display'.format(field_name=self.field_name)
        # retrieve instance method
        method = getattr(value, method_name)
        # finally use instance method to return result of get_XXXX_display()
        return method()
Run Code Online (Sandbox Code Playgroud)

例:

给定:

class Person(models.Model):
    ...
    GENDER_CHOICES = (
        ('M', 'Male'),
        ('F', 'Female'),
    )
    gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
Run Code Online (Sandbox Code Playgroud)

使用:

class PersonSerializer(serializers.ModelSerializer):
    ...
    gender = ChoicesSerializerField()
Run Code Online (Sandbox Code Playgroud)

受到:

{
    ...
    'gender': 'Male'
}
Run Code Online (Sandbox Code Playgroud)

代替:

{
    ...
    'gender': 'M'
}
Run Code Online (Sandbox Code Playgroud)


Ori*_*uce 6

我迟到了,但我面临着类似的情况并达成了不同的解决方案。

当我尝试以前的解决方案时,我开始想知道 GET 请求返回字段的显示名称是否有意义,但期望用户在 PUT 请求上向我发送字段的值(因为我的应用程序被翻译成多种语言,允许用户输入显示值将导致灾难)。

我总是希望 API 中的选择输出与输入相匹配- 无论业务需求如何(因为这些需求很容易发生变化)

因此,我想出的解决方案(顺便说一句,在 DRF 3.11 上)是创建第二个只读字段,仅用于显示值。

class UserSerializer(serializers.ModelSerializer):
    gender_display_value = serializers.CharField(
        source='get_gender_display', read_only=True
    )

    class Meta:
        model = User
        fields = (
            "username",
            "email",
            "first_name",
            "last_name",
            "gender",
            "gender_display_value",
        )
Run Code Online (Sandbox Code Playgroud)

这样我就可以保持一致的 API 签名,并且不必覆盖 DRF 的字段,也不必冒着将 Django 的内置模型验证与 DRF 的验证混淆的风险。

输出将是:

{
  'username': 'newtestuser',
  'email': 'newuser@email.com',
  'first_name': 'first',
  'last_name': 'last',
  'gender': 'M',
  'gender_display_value': 'Male'
}
Run Code Online (Sandbox Code Playgroud)


loi*_*ser 5

此线程的更新,在最新版本的DRF中实际上有一个ChoiceField

因此,如果要返回,您需要做的display_name就是子类ChoiceField to_representation方法,如下所示:

from django.contrib.auth import get_user_model
from rest_framework import serializers

User = get_user_model()

class ChoiceField(serializers.ChoiceField):

    def to_representation(self, obj):
        return self._choices[obj]

class UserSerializer(serializers.ModelSerializer):
    gender = ChoiceField(choices=User.GENDER_CHOICES)

    class Meta:
        model = User
Run Code Online (Sandbox Code Playgroud)

因此,无需更改__init__方法或添加任何其他程序包。