2015-03-09 27 views
28

Mam kilka pól w moim modelu użytkownika, które są polami wyboru i staram się dowiedzieć, jak najlepiej to wdrożyć w Django Rest Framework.Django Rest Framework z ChoiceField

Poniżej znajduje się uproszczony kod pokazujący, co robię.

# 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 

Zasadniczo co próbuję zrobić, to mieć metody get/post/put użyć wartości wyświetlania pola wyboru zamiast kodu, patrząc coś jak poniżej JSON.

{ 
    'username': 'newtestuser', 
    'email': '[email protected]', 
    'first_name': 'first', 
    'last_name': 'last', 
    'gender': 'Male' 
    // instead of 'gender': 'M' 
} 

Co mam zrobić? Powyższy kod nie działa. Zanim miałem coś takiego, pracując dla GET, ale dla POST/PUT dawało mi to błędy. Szukam ogólnej porady, jak to zrobić, wydaje się, że jest to coś powszechnego, ale nie mogę znaleźć przykładów. Albo to, albo robię coś okropnie nie tak.

+0

Próbowałaś http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield – DAKZH

Odpowiedz

52

Django zapewnia metodę Model.get_FOO_display aby uzyskać "czytelny dla człowieka" wartość pola:

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

    class Meta: 
     model = User 

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

do najnowszej DRF (3.6.3) - najprostszy sposób to:

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

Co jeśli chcesz sznurki wyświetlanie wszystkich dostępnych opcji? –

+0

@ HåkenLid należy wywoływać pojedynczo w nowej metodzie. – levi

+3

Okazuje się, że reszta-ramka udostępnia opcje, jeśli korzystasz z metody opcji http w module ModelViewSet, więc nie trzeba dostosowywać serializera. –

11

proponuję użyć django-models-utils ze zwyczajem DRF serializer field

Kod staje:

# 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 
+1

na to pytanie odpowiedziano dawno temu dzięki znacznie prostszemu wbudowanemu rozwiązaniu. – awwester

+8

2 różnice: 1) ChoicesField może być ponownie użyty i 2) obsługuje edycje dla pola "gender", które nie jest już tylko do odczytu – nicolaspanel

+0

Biorąc pod uwagę, że wartości dla wyborów same są tak krótkie, po prostu użyłbym 'GENDER = Choices ("Male", "Female") 'i' default = GENDER.Male', ponieważ omija to potrzebę utworzenia niestandardowego pola serializera. – ergusto

5

następujące rozwiązanie działa z każdym polu z wyborów, bez konieczności określenia w serializatora niestandardową metodę dla każdego:

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

Przykład:

Dane:

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

użycie:

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

otrzymywać:

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

zamiast:

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

Probalbly trzeba coś takiego gdzieś w util.py i importu w zależności od tego serializers ChoiceFields są zaangażowane.

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

Od DRF 3.1 jest nowy API o nazwie customizing field mapping.Używałem go zmienić domyślne mapowanie ChoiceField do 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 

Jeśli użyć DefaultModelSerializer:

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

Dostaniesz coś takiego:

... 

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

znalazłem podejście soup boy „s do być najlepszym. Chociaż sugeruję dziedziczyć po serializers.ChoiceField zamiast serializers.Field. W ten sposób wystarczy zastąpić metodę to_representation, a reszta działa jak zwykły ChoiceField.

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] 
+0

Masz jeden zbyt wiele podkreśleń nazywających '' super'' w twoim '__init__'' – joeb

Powiązane problemy