web-dev-qa-db-fra.com

Ordre de validation du sérialiseur dans Django REST Framework

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:

  1. J'ai un point de terminaison qui crée un utilisateur. En tant que tel, il existe un champ 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éé.
  2. L'utilisateur POSTs des valeurs incorrectes pour chacun des champs mentionnés ci-dessus
  3. Une implémentation de validate a été faite dans le sérialiseur (voir ci-dessous), interceptant les champs password et confirm_password Qui ne correspondent pas

Implé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.

27
nmagerko

É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é

  1. Désérialisation de champ appelée ( serializer.to_internal_value et field.run_validators )
  2. serializer.validate_[field] est appelé pour chaque champ
  3. Les validateurs au niveau du sérialiseur sont appelés ( serializer.run_validation suivi de serializer.run_validators )
  4. 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.

65
Kevin Brown

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.

2
MagicLAMP

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.

DRF_Validation_Control_Flow

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.

  1. MySerializer.is_valid() est exécutée.
  2. En supposant que vous appelez la méthode super class (BaseSerializer) is_valid (Comme: super(MySerializer, self).is_valid(raise_exception) dans votre méthode MySerializer.is_valid(), elle sera appelée.
  3. Maintenant que 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.
  4. Ensuite, validate_empty_values De fields.Field Est appelé. Cela se produit à nouveau sur l'ensemble du data et non sur un seul champ.
  5. Ensuite, le Serializer.to_internal_method Est appelé.
  6. Maintenant, nous bouclons sur chaque champ défini sur le sérialiseur. Et pour chaque champ, nous appelons d'abord la méthode 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.
  7. Sur ce champ, nous appelons à nouveau la Field.validate_empty_values()
  8. Le to_internal_value Du type de champ est appelé ensuite.
  9. Maintenant, il y a un appel à la méthode 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 un
  10. Une fois que tout cela est fait, nous revenons à la méthode Serializer.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.
  11. read_only_defaults()
  12. mettre à jour valider les données avec des valeurs par défaut, je pense
  13. exécuter des validateurs de niveau objet. Je pense que la méthode validate() sur votre objet est exécutée ici.
0
railomaya