web-dev-qa-db-fra.com

Django REST Validation des champs personnalisés du cadre

J'essaie de créer une validation personnalisée pour un modèle, afin de vérifier que son start_date est avant son end_date et cela s'avère presque impossible.

Stuff j'ai essayé:

  • construit Django validateurs: aucun contrôle pour cette

  • écrivant moi-même, comme si:

    def validate_date(self):
       if self.start_date < self.end_date:
            raise serializers.ValidationError("End date must be after start date.")
    

Ce morceau de code que j'ai ajouté à la classe Serializer (puis au modèle), mais il ne semble pas être appelé dans aucun des deux emplacements.

J'ai aussi trouvé ceci peu de code qui pourrait être utile, mais je ne sais pas comment l'intégrer dans ma méthode. Il semble que cela fonctionnerait pour valider un attribut de modèle, mais je dois vérifier entre deux attributs.

Mon modele:

class MyModel(models.Model):

    created = models.DateTimeField(auto_now_add=True)
    relation_model = models.ForeignKey(RelationModel, related_name="mymodels")
    priority = models.IntegerField(
        validators = [validators.MinValueValidator(0), validators.MaxValueValidator(100)])
    start_date = models.DateField()
end_date = models.DateField()

    @property
    def is_active(self):
        today = datetime.date.today()
        return (today >= self.start_date) and (today <= self.end_date)

    def __unicode__(self):
        ...

    class Meta:
        unique_together = ('relation_model', 'priority', 'start_date', 'end_date')

Fyi, toutes les autres validations fonctionnent!

Mon sérialiseur:

class MyModelSerializer(serializers.ModelSerializer):

    relation_model = RelationModelSerializer
    is_active = serializers.Field(source='is_active')

    def validate_date(self):
        if self.start_date > self.end_date:
            raise serializers.ValidationError("End date must be after start date.")   

    class Meta:
        model = MyModel
        fields = (
            'id', 'relation_model', 'priority', 'start_date', 'end_date', 'is_active'
        )

Mon avis:

class MyModelList(generics.ListCreateAPIView):
    permission_classes = (IsAdminUser,)
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    ordering = ('priority')
54
Gabi

Vous devez utiliser une validation à l’échelle de l’objet (validate()), puisque validate_date _ ne sera jamais appelé car date n’est pas un champ du sérialiseur. de la documentation :

class MySerializer(serializers.ModelSerializer):
    def validate(self, data):
        """
        Check that the start is before the stop.
        """
        if data['start_date'] > data['end_date']:
            raise serializers.ValidationError("finish must occur after start")
        return data

Avant DRF 3.0, vous pouvez également l’ajouter à la fonction de nettoyage d’un modèle, mais cela n’appelle plus dans DRF 3.0.

class MyModel(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()
    def clean(self):
        if self.end_date < self.start_date:
            raise ValidationError("End date must be after start date.")
62
jgadelange

la réponse de jgadelange fonctionnait auparavant Django reste 3 probablement. Si quelqu'un utilise la version Django reste framework 3 *, je pense que cela serait utile pour ce folk. un devrait conserver le processus de validation au niveau du modèle et la méthode propre pourrait être la solution. Mais Django) L'annonce du cadre de repos dit ici que, si quelqu'un veut valider le repos-appel dans modèle .clean, il doit redéfinir la méthode de validation du sérialiseur et appeler la méthode de nettoyage de cette classe de sérialiseur de la manière suivante

(car doc dit: la méthode clean () ne sera pas appelée dans le cadre de la validation du sérialiseur)

class MySerializer(serializers.ModelSerializer):

   def validate(self, attrs):
     instance = MyModel(**attrs)
     instance.clean()
     return attrs

et modèle

class MyModel(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()

    def clean(self):
        if self.end_date < self.start_date:
            raise ValidationError("End date must be after start date.")
20
Amir

Une autre réponse ici pourrait être utile, en ce qui concerne la situation si on choisit de remplacer la méthode validate() du sérialiseur.

Concernant la réponse sur Ordre de validation du sérialiseur dans Django REST Framework) , je dois dire que serializer.validate() la méthode est appelée à la fin de la séquence de validation, mais les validateurs de champ sont appelés avant, dans serializer.to_internal_value() , soulevant ValidationError à la fin. .

Cela signifie que les erreurs de validation personnalisées ne se superposent pas avec celles par défaut .

À mon avis, le moyen le plus propre d'obtenir le comportement souhaité consiste à utiliser méthode du champ cible validation dans la classe de sérialiseur:

def validate_end_date(self, value):
    # validation process...
    return value

Si vous avez besoin d’une autre valeur de champ à partir du modèle, telle que start_date _ dans ce cas, vous pouvez les obtenir (non validés car le processus n’est pas terminé) avec:

# `None` here can be replaced with field's default value
start_date = 'start_date' in self.initial_data
    and self.initial_data['start_date'] or None
14
Damaged Organic

Au cas où quiconque aurait du mal à mettre en œuvre ceci en tant que validateur basé sur la classe sur le terrain ...

from rest_framework.serializers import ValidationError

class EndDateValidator:
    def __init__(self, start_date_field):
        self.start_date_field = start_date_field

    def set_context(self, serializer_field):
        self.serializer_field = serializer_field

    def __call__(self, value):
        end_date = value
        serializer = self.serializer_field.parent
        raw_start_date = serializer.initial_data[self.start_date_field]

        try:
            start_date = serializer.fields[self.start_date_field].run_validation(raw_start_date)
        except ValidationError:
            return  # if start_date is incorrect we will omit validating range

        if start_date and end_date and end_date < start_date:
            raise ValidationError('{} cannot be less than {}'.format(self.serializer_field.field_name, self.start_date_field)

En supposant que vous ayez les champs start_date Et end_date Dans votre sérialiseur, vous pouvez ensuite définir le champ end_date Avec validators=[EndDateValidator('start_date')].

4
Konrad Perzyna

Je vais développer la réponse de Konrad. J'aime ça parce que c'est assez explicite, et vous appelez également la validation sur d'autres champs quand nous les utilisons. Donc, il est plus sûr, probablement redondant (certaines validations seront appelées deux fois)

La première chose à noter est que si nous implémentons comme ceci, lorsque nous exécutons run_validator, seules les validations définies dans la variable validators apparaîtront. Donc, si nous validons un champ par exemple avec les méthodes validate_, il ne sera pas exécuté.

En outre, je l'ai rendu héritable, afin que nous puissions réimplémenter la fonction de validation et réutiliser le code.

validators.py

from rest_framework.serializers import ValidationError

class OtherFieldValidator:

    #### This part is the same for all validators ####

    def __init__(self, other_field):
        self.other_field = other_field # name of parameter

    def set_context(self, serializer_field):
        self.serializer_field = serializer_field # name of field where validator is defined

    def make_validation(self,field, other_field):
        pass

    def __call__(self, value):
        field = value
        serializer = self.serializer_field.parent # serializer of model
        raw_other_field = serializer.initial_data[self.other_field] # data del otro campo

        try:
            other_field = serializer.fields[self.other_field].run_validation(raw_other_field)
        except ValidationError:
            return # if date_start is incorrect we will omit validating range

    #### Here is the only part that changes ####

        self.make_validation(field,other_field)

class EndDateValidator(OtherFieldValidator):

    def make_validation(self,field, other_field):
        date_end = field
        date_start = other_field
        if date_start and date_end and date_end < date_start:
            raise ValidationError('date cannot be')

Donc le sérialiseur sera comme ceci: serializers.py

# Other imports
from .validators import EndDateValidator

 def myfoo(value):                                                        
     raise ValidationError("start date error")                             

 class MyModelSerializer(serializers.ModelSerializer):                                        
     class Meta:                                                          
         model = MyModel                                                      
         fields = '__all__'                                                                                       
         extra_kwargs = {                                                 
             'date_end': {'validators': [EndDateValidator('date_start')]},
             'date_start': {'validators': [myfoo]},                       
         }                                                                
0
Gonzalo