Django Rest Framework với ChoiceField


80

Tôi có một số trường trong mô hình người dùng của mình là trường lựa chọn và đang cố gắng tìm ra cách triển khai tốt nhất điều đó vào Django Rest Framework.

Dưới đây là một số mã đơn giản để hiển thị những gì tôi đang làm.

# 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

Về cơ bản những gì tôi đang cố gắng làm là để các phương thức get / post / put sử dụng giá trị hiển thị của trường lựa chọn thay vì mã, trông giống như JSON bên dưới.

{
  'username': 'newtestuser',
  'email': 'newuser@email.com',
  'first_name': 'first',
  'last_name': 'last',
  'gender': 'Male'
  // instead of 'gender': 'M'
}

Làm thế nào tôi sẽ làm điều đó? Đoạn mã trên không hoạt động. Trước khi tôi có một cái gì đó như thế này hoạt động cho GET, nhưng cho POST / PUT, nó đã cho tôi lỗi. Tôi đang tìm lời khuyên chung về cách thực hiện việc này, có vẻ như nó sẽ là một điều gì đó phổ biến, nhưng tôi không thể tìm thấy ví dụ. Hoặc đó hoặc tôi đang làm điều gì đó sai lầm khủng khiếp.



stackoverflow.com/questions/64619906/… loicgasser vui lòng giúp tôi về điều này
Adnan Rizwee

Câu trả lời:


13

Bản cập nhật cho chủ đề này, trong các phiên bản mới nhất của DRF thực sự có một ChoiceField .

Vì vậy, tất cả những gì bạn cần làm nếu muốn trả về phương thức display_nameis to subclass ChoiceField to_representationnhư sau:

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):
        if obj == '' and self.allow_blank:
            return obj
        return self._choices[obj]

    def to_internal_value(self, data):
        # To support inserts with the value
        if data == '' and self.allow_blank:
            return ''

        for key, val in self._choices.items():
            if val == data:
                return key
        self.fail('invalid_choice', input=data)


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

    class Meta:
        model = User

Vì vậy, không cần phải thay đổi __init__phương pháp hoặc thêm bất kỳ gói bổ sung nào.


đó có phải là return self.GENDER_CHOICES[obj]trong trường hợp OPs không? và điều này có thích hợp với Django Model.get_FOO_displaykhông?
Harry Moreno

Bạn có thể sử dụng SerializerMethodField django-rest-framework.org/api-guide/fields/… của DRF nếu bạn muốn làm điều đó ở cấp bộ nối tiếp. Tôi sẽ tránh trộn xác thực tích hợp django ở cấp mô hình với xác thực DRF.
loicgasser

Khi câu trả lời @ kishan-mehta không hoạt động với tôi. Cái này đã làm
Robert Johnstone

141

Django cung cấp Model.get_FOO_displayphương thức để nhận giá trị "con người có thể đọc được" của một trường:

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

    class Meta:
        model = User

    def get_gender(self,obj):
        return obj.get_gender_display()

cho DRF mới nhất (3.6.3) - phương pháp đơn giản nhất là:

gender = serializers.CharField(source='get_gender_display')

4
Điều gì xảy ra nếu bạn muốn các chuỗi hiển thị của tất cả các lựa chọn có sẵn?
Håken Nắp

4
Hóa ra là khung công tác còn lại hiển thị các lựa chọn nếu bạn sử dụng phương thức tùy chọn http trên ModelViewSet, vì vậy tôi không cần phải tùy chỉnh bộ tuần tự.
Håken Nắp

1
@kishan bằng cách sử dụng get_render_displaybạn sẽ nhận được Male, nếu bạn truy cập vào chính thuộc tính obj.gender, bạn sẽ nhận đượcM
levi

6
như tôi sử dụng drf v3.6.3, gender = serializers.CharField(source='get_gender_display')hoạt động tốt.
dalang

1
Bạn chỉ có thể làm fuel = serializers.CharField(source='get_fuel_display', read_only=True)để chỉ hiển thị tên có thể đọc được của con người cho GETcác yêu cầu. POSTyêu cầu sẽ vẫn hoạt động với mã (bật ModelSerializer).
Pierre Monico

25

Tôi khuyên bạn nên sử dụng django-models-utils với trường bộ tuần tự DRF tùy chỉnh

Mã trở thành:

# 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

3
câu hỏi này đã được trả lời cách đây khá lâu với một giải pháp tích hợp đơn giản hơn nhiều.
awwester,

10
2 sự khác biệt: 1) ChoicesField có thể được tái sử dụng và 2) nó hỗ trợ phiên bản cho lĩnh vực "giới tính" mà không phải là read-only nữa
nicolaspanel

Do bản thân các giá trị cho các lựa chọn quá ngắn nên tôi chỉ sử dụng GENDER = Choices('Male', 'Female')default=GENDER.Malevì điều đó bỏ qua sự cần thiết phải tạo trường tuần tự hóa tùy chỉnh.
ergusto

5
Tôi đang tìm cách đọc và viết trường lựa chọn thông qua api và câu trả lời này đã đóng đinh nó. Câu trả lời được chấp nhận không hiển thị cách cập nhật trường lựa chọn, câu trả lời này có.
JLugao

Chỉ câu trả lời thực sự hỗ trợ việc viết đúng cách!
yspreen

13

Nói một cách rõ ràng rằng bạn cần một cái gì đó như thế này ở đâu đó trong của bạn util.pyvà nhập vào bất kỳ bộ nối tiếp nào ChoiceFieldscó liên quan.

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())))

2
Tôi thích câu trả lời này hơn vì điều này có thể cho phép người dùng nhập khóa hoặc giá trị lựa chọn eihter. Bằng cách đơn giản thay đổi if self._choices[i] == data:thành if i == data or self._choices[i] == data:. Trong khi kế thừa từ ChoiceField không cần ghi đè to_internal_value() nhưng sẽ chỉ chấp nhận khóa lựa chọn.
CK

8

Giải pháp sau hoạt động với bất kỳ trường nào có các lựa chọn, không cần chỉ định trong bộ tuần tự một phương thức tùy chỉnh cho mỗi trường:

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()

Thí dụ:

được:

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

sử dụng:

class PersonSerializer(serializers.ModelSerializer):
    ...
    gender = ChoicesSerializerField()

Nhận:

{
    ...
    'gender': 'Male'
}

thay vì:

{
    ...
    'gender': 'M'
}

4

Kể từ DRF3.1 có API mới được gọi là ánh xạ trường tùy chỉnh . Tôi đã sử dụng nó để thay đổi ánh xạ ChoiceField mặc định thành 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 is 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

Nếu bạn sử dụng DefaultModelSerializer:

class UserSerializer(DefaultModelSerializer):    
    class Meta:
        model = User
        fields = ('id', 'gender')

Bạn sẽ nhận được một cái gì đó như:

...

"id": 1,
"gender": {
    "display": "Male",
    "value": "M"
},
...

0

Tôi thích câu trả lời của @nicolaspanel để giữ trường có thể ghi được. Nếu bạn sử dụng định nghĩa này thay vì định nghĩa của nó ChoiceField, bạn sẽ tận dụng được bất kỳ / tất cả cơ sở hạ tầng có sẵn trong ChoiceFieldkhi ánh xạ các lựa chọn từ str=> int:

class MappedChoiceField(serializers.ChoiceField):

    @serializers.ChoiceField.choices.setter
    def choices(self, choices):
        self.grouped_choices = fields.to_choices_dict(choices)
        self._choices = fields.flatten_choices_dict(self.grouped_choices)
        # in py2 use `iteritems` or `six.iteritems`
        self.choice_strings_to_values = {v: k for k, v in self._choices.items()}

Ghi đè @property là "xấu xí" nhưng mục tiêu của tôi luôn là thay đổi càng ít cốt lõi càng tốt (để tối đa hóa khả năng tương thích về phía trước).

Tái bút nếu bạn muốn allow_blank, có một lỗi trong DRF. Cách giải quyết đơn giản nhất là thêm phần sau vào MappedChoiceField:

def validate_empty_values(self, data):
    if data == '':
        if self.allow_blank:
            return (True, None)
    # for py2 make the super() explicit
    return super().validate_empty_values(data)

PPS Nếu bạn có một loạt các trường lựa chọn mà tất cả đều cần được lập bản đồ này, theo cách này, hãy tận dụng tính năng được @lechup ghi nhận và thêm phần sau vào ModelSerializer( không phải của nó Meta):

serializer_choice_field = MappedChoiceField

-1

Tôi thấy soup boycách tiếp cận của là tốt nhất. Mặc dù tôi khuyên bạn nên kế thừa từ serializers.ChoiceFieldhơn là serializers.Field. Bằng cách này, bạn chỉ cần ghi đè to_representationphương thức và phần còn lại hoạt động như một ChoiceField thông thường.

class DisplayChoiceField(serializers.ChoiceField):

    def __init__(self, *args, **kwargs):
        choices = kwargs.get('choices')
        self._choices = OrderedDict(choices)
        super(DisplayChoiceField, self).__init__(*args, **kwargs)

    def to_representation(self, obj):
        """Used while retrieving value for the field."""
        return self._choices[obj]

Bạn đã một quá nhiều dấu gạch gọi supertrong của bạn__init__
joebeeson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.