with framework example ejemplo create django django-rest-framework

example - Django Rest Framework con ChoiceField



django rest framework serializer (8)

Desde DRF 3.1 hay una nueva API llamada personalización de mapeo de campo . Lo usé para cambiar la asignación predeterminada de ChoiceField a ChoiceDisplayField:

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

Si usa DefaultModelSerializer :

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

Obtendrás algo como:

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

Tengo algunos campos en mi modelo de usuario que son campos de elección y estoy tratando de descubrir cómo implementarlo mejor en Django Rest Framework.

A continuación hay un código simplificado para mostrar lo que estoy haciendo.

# 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

Esencialmente, lo que intento hacer es hacer que los métodos get / post / put utilicen el valor de visualización del campo de elección en lugar del código, con un aspecto similar al JSON a continuación.

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

¿Cómo voy a hacer eso? El código anterior no funciona. Antes tenía algo como esto trabajando para GET, pero para POST / PUT me daba errores. Estoy buscando consejos generales sobre cómo hacer esto, parece que sería algo común, pero no puedo encontrar ejemplos. O eso o estoy haciendo algo terriblemente mal.


Django proporciona el método Model.get_FOO_display para obtener el valor "legible para humanos" de un campo:

class UserSerializer(serializers.ModelSerializer): gender = serializers.SerializerMethodField() class Meta: model = User def get_gender(self,obj): return obj.get_gender_display()

para el último DRF (3.6.3) - el método más sencillo es:

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


Encontré soup boy el enfoque del soup boy es el mejor. Aunque sugeriría heredar de serializers.ChoiceField lugar de serializers.Field . De esta manera, solo necesita anular el método to_representation y el resto funciona como un ChoiceField normal.

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]


La siguiente solución funciona con cualquier campo con opciones, sin necesidad de especificar en el serializador un método personalizado para cada uno:

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

Ejemplo:

dado:

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

utilizar:

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

para recibir:

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

en lugar de:

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


Prefiero la respuesta de @nicolaspanel para mantener el campo de escritura. Si usa esta definición en lugar de su ChoiceField , aprovechará toda / toda la infraestructura en el ChoiceField mientras ChoiceField las opciones de 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()}

La anulación @property es "fea", pero mi objetivo siempre es cambiar la menor cantidad posible del núcleo (para maximizar la compatibilidad hacia adelante).

PD: si quieres allow_blank , hay un bug en DRF. La solución más simple es agregar lo siguiente a 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 Si tiene un montón de campos de elección que todos necesitan ser mapeados, aproveche la característica anotada por @lechup y agregue lo siguiente a su ModelSerializer ( no su Meta ):

serializer_choice_field = MappedChoiceField


Probablemente necesite algo como esto en algún lugar de su util.py e importar en cualquier serializador que 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())))


Sugiero usar django-models-utils con un campo de serializador DRF personalizado

El código se convierte en:

# 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


Una actualización para este hilo, en las últimas versiones de DRF, en realidad hay un ChoiceField .

Por lo tanto, todo lo que debe hacer si desea devolver el display_name es subclasificar ChoiceField to_representation método de presentación como este:

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

Por lo tanto, no es necesario cambiar el método __init__ ni agregar ningún paquete adicional.