Ich habe einige Felder in meinem Benutzermodell, die Auswahlfelder sind, und versuche herauszufinden, wie dies am besten in Django Rest Framework implementiert werden kann.
Unten finden Sie einen vereinfachten Code, der zeigt, was ich tue.
# 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
Im Wesentlichen versuche ich, dass die Methoden get / post / put den Anzeigewert des Auswahlfelds anstelle des Codes verwenden und ungefähr so aussehen wie der folgende JSON.
{
'username': 'newtestuser',
'email': '[email protected]',
'first_name': 'first',
'last_name': 'last',
'gender': 'Male'
// instead of 'gender': 'M'
}
Wie würde ich das machen? Der obige Code funktioniert nicht. Bevor ich so etwas für GET hatte, aber für POST / PUT gab es mir Fehler. Ich suche nach allgemeinen Ratschlägen, wie das geht. Es scheint, als wäre es etwas Übliches, aber ich kann keine Beispiele finden. Entweder das oder ich mache etwas schrecklich Falsches.
django
django-rest-framework
awwester
quelle
quelle
Antworten:
Ein Update für diesen Thread, in den neuesten Versionen von DRF gibt es tatsächlich ein ChoiceField .
Alles, was Sie tun müssen, um das zurückzugeben,
display_name
ist eine UnterklassenmethodeChoiceField
to_representation
wie folgt :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
Es ist also nicht erforderlich, die
__init__
Methode zu ändern oder ein zusätzliches Paket hinzuzufügen.quelle
return self.GENDER_CHOICES[obj]
in OPs Fall sein? und ist das Djangos vorzuziehenModel.get_FOO_display
?Django bietet die
Model.get_FOO_display
Methode, um den "lesbaren" Wert eines Feldes zu erhalten:class UserSerializer(serializers.ModelSerializer): gender = serializers.SerializerMethodField() class Meta: model = User def get_gender(self,obj): return obj.get_gender_display()
Für die neueste DRF (3.6.3) ist die einfachste Methode:
gender = serializers.CharField(source='get_gender_display')
quelle
get_render_display
Ihnen erhaltenMale
, wenn Sie auf das Attribut selbst zugreifenobj.gender
, erhalten SieM
gender = serializers.CharField(source='get_gender_display')
funktioniert es gut.fuel = serializers.CharField(source='get_fuel_display', read_only=True)
den für Menschen lesbaren Namen fürGET
Anforderungen anzeigen .POST
Anfragen funktionieren weiterhin mit dem Code (onModelSerializer
).Ich schlage vor, django-models-utils mit einem benutzerdefinierten DRF-Serializer-Feld zu verwenden
Code wird:
# 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
quelle
GENDER = Choices('Male', 'Female')
unddefault=GENDER.Male
, da dies die Notwendigkeit umgeht, ein benutzerdefiniertes Serializer-Feld zu erstellen.Wahrscheinlich brauchen Sie so etwas irgendwo in Ihrem
util.py
und importieren in welche Serializer auchChoiceFields
immer involviert sind.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())))
quelle
if self._choices[i] == data:
inif i == data or self._choices[i] == data:
. Während des Erbens von ChoiceField muss es nicht überschrieben werden,to_internal_value()
sondern akzeptiert nur den Auswahlschlüssel .Die folgende Lösung funktioniert mit jedem Feld mit Auswahlmöglichkeiten, ohne dass im Serializer für jedes Feld eine benutzerdefinierte Methode angegeben werden muss:
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()
Beispiel:
gegeben:
class Person(models.Model): ... GENDER_CHOICES = ( ('M', 'Male'), ('F', 'Female'), ) gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
verwenden:
class PersonSerializer(serializers.ModelSerializer): ... gender = ChoicesSerializerField()
bekommen:
{ ... 'gender': 'Male' }
anstatt:
{ ... 'gender': 'M' }
quelle
Seit
DRF
3.1 gibt es eine neue API namens Customizing Field Mapping . Ich habe es verwendet, um die Standard-ChoiceField-Zuordnung in ChoiceDisplayField zu ändern: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
Wenn Sie verwenden
DefaultModelSerializer
:class UserSerializer(DefaultModelSerializer): class Meta: model = User fields = ('id', 'gender')
Sie erhalten so etwas wie:
... "id": 1, "gender": { "display": "Male", "value": "M" }, ...
quelle
Ich bevorzuge die Antwort von @nicolaspanel, um das Feld beschreibbar zu halten. Wenn Sie diese Definition anstelle seiner verwenden
ChoiceField
, nutzen Sie die gesamte integrierte Infrastruktur,ChoiceField
während Sie die Auswahlmöglichkeiten vonstr
=>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()}
Die Überschreibung von @property ist "hässlich", aber mein Ziel ist es immer, so wenig wie möglich vom Kern zu ändern (um die Vorwärtskompatibilität zu maximieren).
PS Wenn Sie möchten
allow_blank
, gibt es einen Fehler in DRF. Die einfachste Problemumgehung besteht darin, Folgendes hinzuzufügenMappedChoiceField
: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 Wenn Sie eine Reihe von Auswahlfeldern haben, die alle auf diese Weise zugeordnet werden müssen, nutzen Sie die von @lechup angegebene Funktion und fügen Sie Folgendes zu Ihrem
ModelSerializer
( nicht seinemMeta
) hinzu:quelle
Ich fand
soup boy
den Ansatz am besten. Obwohl ich vorschlagen würde,serializers.ChoiceField
eher von als zu erbenserializers.Field
. Auf diese Weise müssen Sie nur dieto_representation
Methode überschreiben und der Rest funktioniert wie ein normales 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]
quelle
super
in Ihrem__init__