Ordre de Validation du Serializer dans le cadre de Django REST

Situation

tout en travaillant avec la validation dans le cadre du REST Framework de Django ModelSerializer, j'ai remarqué que les Meta.model les champs sont toujours validés, même si cela n'a pas nécessairement de sens de le faire. Prenons l'exemple suivant pour un User serialization du model:

  1. j'ai un paramètre qui crée un utilisateur. En tant que tel, il est un password et confirm_password champ. Si les deux champs ne correspondent pas, l'utilisateur ne peut pas être créé. De même, si la demande username existe déjà, l'utilisateur ne peut pas être créé.
  2. L'utilisateur affiche des valeurs inappropriées pour chacun des champs mentionnés ci-dessus
  3. une implémentation de validate a été fait dans le serializer( voir ci-dessous), en attrapant le non-matching password et confirm_password champs

mise en oeuvre 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 quand le ValidationError est augmenté de validate, le ModelSerializer recherche 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 retournée à partir du point final; les erreurs du modèle et les erreurs hors champ sont présentes.

par conséquent, je voudrais savoir comment empêcher la validation du modèle jusqu'à ce que la validation hors champ soit terminée, en m'enregistrant un appel à ma base de données.

Tentative de solution

j'ai essayé de passer par les DRF source pour comprendre où cela se produit, mais j'ai été incapable de trouver ce que je dois outrepasser afin que cela fonctionne.

19
demandé sur ferrangb 2014-12-21 19:52:48

2 réponses

puisque très probablement votre username champ unique=True définir, Django RESTE Cadre ajoute automatiquement un validateur qui vérifie que le nouveau nom d'utilisateur est unique. Vous pouvez confirmer cela en faisant repr(serializer()), qui vous montrera tous les domaines, ce qui inclut les validateurs.

exécution de la Validation de dans un, des sans-papiers

  1. désérialisation du champ appelée (serializer.to_internal_value et field.run_validators)
  2. serializer.validate_[field] est appelé pour chaque champ
  3. les validateurs de niveau de sérialiseur sont appelés (serializer.run_validation suivi de serializer.run_validators)
  4. serializer.validate s'appelle

donc le problème que vous voyez est que la validation au niveau du champ est appelée avant votre validation au niveau du sérialiseur. Bien que je ne le recommande pas, vous pouvez supprimer le validateur de champ par le paramètre extra_kwargs dans le méta de votre sérilaliseur.

class Meta:
    extra_kwargs = {
        "username": {
            "validators": [],
        },
    }

vous aurez besoin de re-implémenter le unique vérifiez votre propre validation, ainsi que tout autre validateur généré automatiquement.

56
répondu Kevin Brown 2015-01-20 03:26:17

Je ne crois pas que les solutions ci-dessus fonctionnent plus. Dans mon cas, mon model a les champs 'first_name' et' last_name', mais l'API ne recevra que'name'.

définir 'extra_kwargs' et' validators ' dans la classe Meta semble n'avoir aucun effet, first_name et last_name sont toujours considérés comme nécessaires, 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 de sens. Après de nombreuses heures de frustration, J'ai trouvé que la seule façon que j'ai pu outrepasser les validateurs avec une instance ModelSerializer était d'outrepasser 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 blancs et les nuls avec des champs de caractères est un non non, mais c'est un serializer, pas un model, et comme L'API est appelée par toutes sortes de cowboys, je dois couvrir mes bases.

1
répondu MagicLAMP 2017-10-18 03:58:10