Ich versuche, eine benutzerdefinierte Validierung für ein Modell zu erstellen, um zu überprüfen, ob start_date
es vor dem Modell liegt und ob end_date
es nahezu unmöglich ist.
Sachen, die ich versucht habe:
eingebaute Django-Validatoren: Keine Überprüfung
schreibe meine eigenen, so:
def validate_date(self): if self.start_date < self.end_date: raise serializers.ValidationError("End date must be after start date.")
Dieses Stück Code habe ich der Serializer-Klasse (und dann dem Modell) hinzugefügt, aber es scheint an keiner Stelle aufgerufen zu werden.
Ich habe auch diesen Code gefunden, der möglicherweise von Nutzen ist, aber ich weiß nicht, wie ich ihn in meine Methode integrieren soll. Es scheint, dass es funktionieren würde, ein Modellattribut zu validieren, aber ich muss zwischen zwei Attributen prüfen.
Mein Modell:
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')
Zu Ihrer Information, alle anderen Validierungen funktionieren!
Mein Serializer:
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'
)
Meine Sicht:
class MyModelList(generics.ListCreateAPIView):
permission_classes = (IsAdminUser,)
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
ordering = ('priority')
clean
Methode des Modells seit DRF 3.0 nicht mehr aufgerufen wird, wie hier erläutert ... django-rest-framework.org/topics/3.0-announcement/…Jgadelanges Antwort funktionierte wahrscheinlich vor Django Rest 3. Wenn jemand die Django Rest Framework 3 * -Version verwendet, denke ich, dass dies für diese Leute hilfreich wäre. Man sollte den Validierungsprozess auf Modellebene halten und eine saubere Methode könnte die einzige Lösung sein. Die Ankündigung des Django-Rest-Frameworks besagt hier jedoch , dass jemand, der den Restaufruf in der Modell-Clean-Methode validieren möchte, die Serializer-Validierungsmethode überschreiben sollte und die Clean-Methode aus dieser Serializer-Klasse auf folgende Weise aufrufen muss
(weil doc sagt: Die Methode clean () wird im Rahmen der Serializer-Validierung nicht aufgerufen.)
class MySerializer(serializers.ModelSerializer): def validate(self, attrs): instance = MyModel(**attrs) instance.clean() return attrs
und Modell
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.")
quelle
Eine andere Antwort hier könnte nützlich sein, wenn man die
validate()
Methode des Serializers überschreibt .In Bezug auf die Antwort auf die Reihenfolge der Serializer-Validierung im Django REST Framework muss ich sagen, dass die
serializer.validate()
Methode am Ende der Validierungssequenz aufgerufen wird. Die Validatoren des Feldes werden jedoch vorher aufgerufenserializer.to_internal_value()
, umValidationError
am Ende zu erhöhen .Dies bedeutet, dass benutzerdefinierte Validierungsfehler nicht mit Standardfehlern gestapelt werden .
Meiner Meinung nach ist der sauberste Weg, um das gewünschte Verhalten zu erreichen , die Validierung der Zielfeldmethode in der Serializer-Klasse:
def validate_end_date(self, value): # validation process... return value
Wenn Sie einen anderen
start_date
Feldwert aus dem Modell benötigen, wie in diesem Fall, können Sie diese abrufen (jedoch nicht validiert, da ein Prozess nicht abgeschlossen ist) mit:# `None` here can be replaced with the field's default value start_date = self.initial_data.get('start_date')
quelle
Für den Fall, dass jemand Schwierigkeiten hat, dies als klassenbasierten Validator auf dem Feld zu implementieren ...
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)
Angenommen, Sie haben
start_date
undend_date
Felder in Ihrem Serializer, können Sie dann einend_date
Feld mit festlegenvalidators=[EndDateValidator('start_date')]
.quelle
Ich werde Konrad Antwort erweitern. Ich mag es, weil es ziemlich explizit ist und Sie auch die Validierung für andere Felder aufrufen, wenn wir sie verwenden. Es ist also sicherer und wird wahrscheinlich redundant sein (einige Validierungen werden zweimal aufgerufen).
Als erstes ist zu beachten, dass bei einer solchen Implementierung beim Ausführen von run_validator nur die in der Variablen validators festgelegten Validierungen angezeigt werden. Wenn wir also ein Feld beispielsweise mit den Methoden validate_ validieren, wird es nicht ausgeführt.
Außerdem habe ich es vererbbar gemacht, damit wir die Validierungsfunktion erneut implementieren und den Code wiederverwenden können.
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')
Der Serializer sieht also folgendermaßen aus: 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]}, }
quelle
Die Lösungen von jgadelange und Damaged Organic sind ziemlich interessant, wenn Sie eine einfachere Lösung bevorzugen, insbesondere wenn Sie den Validator nicht mehr als einmal wiederverwenden möchten, aber ich würde eine Verbesserung vorschlagen: Ich würde den Validator auf Objektebene verwenden und ein Diktat mit auslösen der Validierungsfehler des Feldes:
def validate(self, data): ... if data["start_date"] > data["end_date"]: raise serializers.ValidationError( {"end_date": "End date must be after start date."} ) ...
Ich nutze den Vorteil, dass die ValidationError-Klasse ein Objekt mit den Fehlerdetails akzeptiert . Auf diese Weise kann ich das gleiche Verhalten einer Validierung auf Feldebene emulieren und die Fehlermeldung mit dem Feld selbst verknüpfen, während ich die Daten nach jeder einzelnen Validierung einzeln vergleichen kann.
Dies ist wichtig, um sicherzustellen, dass Sie nicht mit einem unreinen Startdatum vergleichen, das Sie vor dem Vergleich umsetzen müssten (wie Sie es tun würden, wenn Sie self.initial_data verwenden würden).
quelle
end_date
Feld angezeigt wird. Ich bin damit einverstanden, dass das besser wäre