Situation
En travaillant avec la validation dans les Django REST Framework ModelSerializer
, j'ai remarqué que les champs Meta.model
Sont toujours validés, même si cela n'a pas nécessairement de sens. Prenez l'exemple suivant pour la sérialisation d'un modèle User
:
password
et un champ confirm_password
. Si les deux champs ne correspondent pas, l'utilisateur ne peut pas être créé. De même, si le username
demandé existe déjà, l'utilisateur ne peut pas être créé.validate
a été faite dans le sérialiseur (voir ci-dessous), interceptant les champs password
et confirm_password
Qui ne correspondent pasImplémentation de validate
:
def validate(self, data):
if data['password'] != data.pop('confirm_password'):
raise serializers.ValidationError("Passwords do not match")
return data
Problème
Même lorsque le ValidationError
est levé par validate
, le ModelSerializer
interroge toujours la base de données pour vérifier si le username
est déjà utilisé. Cela est évident dans la liste d'erreurs qui est renvoyée par le point de terminaison; les erreurs de modèle et non liées au champ sont présentes.
Par conséquent, je voudrais savoir comment empêcher la validation du modèle jusqu'à la fin de la validation non-terrain, ce qui me permet d'économiser un appel vers ma base de données.
tentative de solution
J'ai essayé de passer par la source du DRF pour comprendre où cela se produit, mais je n'ai pas réussi à trouver ce que je dois contourner pour que cela fonctionne.
Étant donné que votre champ username
a probablement unique=True
Défini, Django REST Framework ajoute automatiquement un validateur qui vérifie pour s'assurer que le nouveau nom d'utilisateur est unique. Vous pouvez le confirmer en faisant repr(serializer())
, qui vous montrera tous les champs générés automatiquement, y compris les validateurs.
La validation est exécutée dans un ordre spécifique et non documenté
serializer.to_internal_value
et field.run_validators
)serializer.validate_[field]
est appelé pour chaque champserializer.run_validation
suivi de serializer.run_validators
)serializer.validate
est appeléLe problème que vous voyez est donc que la validation au niveau du champ est appelée avant votre validation au niveau du sérialiseur. Bien que je ne le recommanderais pas, vous pouvez supprimer le validateur au niveau du champ en définissant extra_kwargs
Dans la méta de votre serilalizer.
class Meta:
extra_kwargs = {
"username": {
"validators": [],
},
}
Vous devrez cependant réimplémenter la vérification unique
dans votre propre validation, ainsi que tous les validateurs supplémentaires qui ont été générés automatiquement.
Je ne crois pas que les solutions ci-dessus fonctionnent plus. Dans mon cas, mon modèle contient les champs "prénom" et "nom", mais l'API ne recevra que "nom".
La définition de 'extra_kwargs' et 'validators' dans la classe Meta semble n'avoir aucun effet, first_name et last_name sont toujours considérés comme requis, et les validateurs sont toujours appelés. Je ne peux pas surcharger les champs de caractères first_name/last_name avec
anotherrepfor_first_name = serializers.CharField(source=first_name, required=False)
comme les noms ont du sens. Après plusieurs heures de frustration, j'ai trouvé que le seul moyen de remplacer les validateurs par une instance de ModelSerializer était de remplacer l'initialiseur de classe comme suit (pardonnez l'indentation incorrecte):
class ContactSerializer(serializers.ModelSerializer):
name = serializers.CharField(required=True)
class Meta:
model = Contact
fields = [ 'name', 'first_name', 'last_name', 'email', 'phone', 'question' ]
def __init__(self, *args, **kwargs):
self.fields['first_name'] = serializers.CharField(required=False, allow_null=True, allow_blank=True)
self.fields['last_name'] = serializers.CharField(required=False, allow_null=True, allow_blank=True)
return super(ContactSerializer, self).__init__(*args, **kwargs)
def create(self, validated_data):
return Contact.objects.create()
def validate(self, data):
"""
Remove name after getting first_name, last_name
"""
missing = []
for k in ['name', 'email', 'question']:
if k not in self.fields:
missing.append(k)
if len(missing):
raise serializers.ValidationError("Ooops! The following fields are required: %s" % ','.join(missing))
from nameparser import HumanName
names = HumanName(data['name'])
names.capitalize()
data['last_name'] = names.last
if re.search(r'\w+', names.middle):
data['first_name'] = ' '.join([names.first, names.middle])
else:
data['first_name'] = names.first
del(data['name'])
return data
Maintenant, le doc dit qu'autoriser les champs vides et nuls avec des champs de caractères est un non non, mais c'est un sérialiseur, pas un modèle, et comme l'API est appelée par toutes sortes de cow-boys, j'ai besoin de couvrir mes bases.
J'essayais également de comprendre comment le contrôle se déroule pendant la validation du sérialiseur et après avoir parcouru attentivement le code source de djangorestframework-3.10.3, j'ai trouvé le diagramme de flux ci-dessous. J'ai décrit le flux et ce qui se passe dans le flux au mieux de ma compréhension sans entrer dans trop de détails car il peut être recherché depuis la source.
Ignorez les signatures de méthode incomplètes. Se concentrer uniquement sur quelles méthodes sont appelées sur quelles classes.
En supposant que vous avez une méthode is_valid
Substituée sur votre classe de sérialiseur (MySerializer(serializers.Serializer)
) lorsque vous appelez my_serializer.is_valid()
, ce qui suit se produit.
MySerializer.is_valid()
est exécutée.BaseSerializer
) is_valid
(Comme: super(MySerializer, self).is_valid(raise_exception)
dans votre méthode MySerializer.is_valid()
, elle sera appelée.MySerializer
étend serializers.Serializer
, La méthode run_validation()
de serializer.Serializers
Est appelée. Ceci valide uniquement les données dictant la première. Nous n'avons donc pas encore commencé les validations au niveau du champ.validate_empty_values
De fields.Field
Est appelé. Cela se produit à nouveau sur l'ensemble du data
et non sur un seul champ.Serializer.to_internal_method
Est appelé.field.run_validation()
. Si le champ a remplacé la méthode Field.run_validation()
, celle-ci sera appelée en premier. Dans le cas d'un CharField
, il est remplacé et appelle la méthode run_validation
De la classe de base Field
. Étape 6-2 de la figure.Field.validate_empty_values()
to_internal_value
Du type de champ est appelé ensuite.Field.run_validators()
. Je suppose que c'est là que les validateurs supplémentaires que nous ajoutons sur le terrain en spécifiant l'option de champ validators = []
Sont exécutés un par unSerializer.to_internal_value()
. Rappelez-vous maintenant que nous faisons ce qui précède pour chaque champ dans cette boucle for. Les validateurs de champs personnalisés que vous avez écrits dans votre sérialiseur (méthodes comme validate_field_name
) Sont maintenant exécutés. Si une exception s'est produite dans l'une des étapes précédentes, vos validateurs personnalisés ne s'exécuteront pas.read_only_defaults()
validate()
sur votre objet est exécutée ici.