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')
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.")
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.")
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
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')]
.
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]},
}