Validering av formulär och fält¶
Formulärvalidering sker när data rensas. Om du vill anpassa den här processen finns det olika platser där du kan göra ändringar, var och en med olika syften. Tre typer av rengöringsmetoder körs under formulärbehandlingen. Dessa körs normalt när du anropar metoden is_valid()
på ett formulär. Det finns andra saker som också kan utlösa rengöring och validering (åtkomst till attributet errors
eller direkt anrop av full_clean()
)), men normalt sett behövs de inte.
I allmänhet kan varje rengöringsmetod höja ValidationError
om det finns ett problem med de data den bearbetar, och skicka relevant information till ValidationError
-konstruktören. :ref:Se nedan <raising-validation-error>` för bästa praxis för att höja ``ValidationError
. Om inget ValidationError
uppstår bör metoden returnera de rensade (normaliserade) data som ett Python-objekt.
Den mesta valideringen kan göras med hjälp av validatorer - hjälpare som kan återanvändas. Validerare är funktioner (eller anropsbara) som tar ett enda argument och ger upphov till ValidationError
vid ogiltig indata. Validerare körs efter att fältets metoder to_python
och validate
har anropats.
Valideringen av ett formulär är uppdelad i flera steg, som kan anpassas eller åsidosättas:
Metoden
to_python()
på enField
är det första steget i varje validering. Den tvingar värdet till en korrekt datatyp och ger upphov tillValidationError
om det inte är möjligt. Den här metoden tar emot det råa värdet från widgeten och returnerar det konverterade värdet. Till exempel: kommer enFloatField
att omvandla data till en Pythonfloat
eller ge upphov till ettValidationError
.Metoden
validate()
för enField
hanterar fältspecifik validering som inte lämpar sig för en validerare. Den tar ett värde som har tvingats till en korrekt datatyp och ger upphov tillValidationError
vid eventuella fel. Den här metoden returnerar ingenting och bör inte ändra värdet. Du bör åsidosätta den för att hantera valideringslogik som du inte kan eller vill lägga in i en validator.Metoden
run_validators()
på ettField
kör alla fältets validatorer och aggregerar alla fel till ett endaValidationError
. Du ska inte behöva åsidosätta den här metoden.Metoden
clean()
i en subklass avField
är ansvarig för att körato_python()
,validate()
ochrun_validators()
i rätt ordning och sprida deras fel. Om någon av metoderna vid något tillfälle ger upphov tillValidationError
, stoppas valideringen och det felet anges. Denna metod returnerar de rena uppgifterna, som sedan infogas i formulärets ordbokcleaned_data
.Metoden
clean_<fieldname>()
anropas på en formulärunderklass – där<fieldname>
ersätts med namnet på formulärfältets attribut. Denna metod utför all rengöring som är specifik för det specifika attributet, oberoende av vilken typ av fält det är. Den här metoden får inga parametrar. Du måste leta upp värdet på fältet iself.cleaned_data
och komma ihåg att det kommer att vara ett Python-objekt vid denna tidpunkt, inte den ursprungliga strängen som skickades in i formuläret (det kommer att vara icleaned_data
eftersom den allmänna fältmetodenclean()
ovan redan har rensat data en gång).Om du till exempel vill validera att innehållet i en
CharField
som heterserialnumber
är unikt, skulleclean_serialnumber()
vara rätt plats att göra detta. Du behöver inte ett specifikt fält (det är enCharField
), men du vill ha en formulärfältsspecifik del av valideringen och eventuellt rensa/normera data.Returvärdet för den här metoden ersätter det befintliga värdet i
cleaned_data
, så det måste vara fältets värde fråncleaned_data
(även om den här metoden inte ändrade det) eller ett nytt rensat värde.Metoden
clean()
i formulärunderklassen kan utföra validering som kräver åtkomst till flera formulärfält. Det är här du kan lägga in kontroller som ”om fältA
anges, måste fältB
innehålla en giltig e-postadress”. Den här metoden kan returnera en helt annan ordbok om den vill, som kommer att användas somcleaned_data
.Eftersom fältvalideringsmetoderna har körts när
clean()
anropas, har du också tillgång till formuläretserrors
-attribut som innehåller alla fel som uppstått vid rensning av enskilda fält.Observera att eventuella fel som uppstår genom din
Form.clean()
-överstyrning inte kommer att associeras med något särskilt fält. De hamnar i ett speciellt ”fält” (kallat__all__
), som du kan komma åt via metodennon_field_errors()
om du behöver. Om du vill koppla fel till ett specifikt fält i formuläret måste du anropaadd_error()
.Observera också att det finns särskilda överväganden när du åsidosätter
clean()
-metoden för enModelForm
-underklass. (se :ref:ModelForm dokumentation <overriding-modelform-clean-method>
för mer information)
Dessa metoder körs i den ordning som anges ovan, ett fält i taget. Det vill säga, för varje fält i formuläret (i den ordning de deklareras i formulärdefinitionen) körs metoden Field.clean()
(eller dess åsidosättande), sedan clean_<fieldname>()
. Slutligen, när dessa två metoder har körts för varje fält, körs metoden Form.clean()
, eller dess överstyrning, oavsett om de tidigare metoderna har gett upphov till fel eller inte.
Exempel på var och en av dessa metoder ges nedan.
Som nämnts kan någon av dessa metoder ge upphov till ett ValidationError
. För ett fält, om metoden Field.clean()
ger upphov till ett ValidationError
, anropas inte någon fältspecifik rengöringsmetod. Rengöringsmetoderna för alla återstående fält körs dock fortfarande.
Upphävande av ValidationError
¶
För att felmeddelanden ska vara flexibla och lätta att åsidosätta bör du beakta följande riktlinjer:
Tillhandahålla en beskrivande felkod till konstruktören:
# Good ValidationError(_("Invalid value"), code="invalid") # Bad ValidationError(_("Invalid value"))
Tvinga inte in variabler i meddelandet; använd platshållare och
params
-argumentet i konstruktören:# Good ValidationError( _("Invalid value: %(value)s"), params={"value": "42"}, ) # Bad ValidationError(_("Invalid value: %s") % value)
Använd mappningsnycklar i stället för positionsformatering. Detta gör det möjligt att placera variablerna i vilken ordning som helst eller att utelämna dem helt och hållet när meddelandet skrivs om:
# Good ValidationError( _("Invalid value: %(value)s"), params={"value": "42"}, ) # Bad ValidationError( _("Invalid value: %s"), params=("42",), )
Omslut meddelandet med
gettext
för att möjliggöra översättning:# Good ValidationError(_("Invalid value")) # Bad ValidationError("Invalid value")
Att sätta ihop allt:
raise ValidationError(
_("Invalid value: %(value)s"),
code="invalid",
params={"value": "42"},
)
Det är särskilt viktigt att följa dessa riktlinjer om du skriver återanvändbara formulär, formulärfält och modellfält.
Även om det inte rekommenderas, om du är i slutet av valideringskedjan (dvs. ditt formulär clean()
-metoden) och du vet att du aldrig kommer att behöva åsidosätta ditt felmeddelande kan du fortfarande välja den mindre verbala:
ValidationError(_("Invalid value: %s") % value)
Metoderna Form.errors.as_data()
och Form.errors.as_json()
har stor nytta av fullt utrustade ValidationError
(med ett code
-namn och en params
-ordbok).
Upprepa flera fel¶
Om du upptäcker flera fel under en rensningsmetod och vill signalera dem alla till den som skickar in formuläret, är det möjligt att skicka en lista med fel till konstruktören ValidationError
.
Som ovan rekommenderas det att skicka en lista över ValidationError
-instanser med code
och params
men en lista med strängar fungerar också:
# Good
raise ValidationError(
[
ValidationError(_("Error 1"), code="error1"),
ValidationError(_("Error 2"), code="error2"),
]
)
# Bad
raise ValidationError(
[
_("Error 1"),
_("Error 2"),
]
)
Använda validering i praktiken¶
I de föregående avsnitten förklarades hur validering fungerar i allmänhet för formulär. Eftersom det ibland kan vara lättare att få saker på plats genom att se varje funktion användas, följer här en serie små exempel som använder var och en av de tidigare funktionerna.
Använda validerare¶
Djangos formulär- (och modell-) fält stöder användning av verktygsfunktioner och klasser som kallas validerare. En validator är ett anropbart objekt eller en funktion som tar ett värde och returnerar ingenting om värdet är giltigt eller ger upphov till ett ValidationError
om inte. Dessa kan skickas till ett fälts konstruktör, via fältets validators
argument, eller definieras på Field
-klassen själv med default_validators
-attributet.
Validatorer kan användas för att validera värden i fältet, låt oss ta en titt på Djangos SlugField
:
from django.core import validators
from django.forms import CharField
class SlugField(CharField):
default_validators = [validators.validate_slug]
Som du kan se är SlugField
en CharField
med en anpassad validator som validerar att den inskickade texten följer vissa teckenregler. Detta kan också göras på fältdefinitionen så här:
slug = forms.SlugField()
är likvärdig med:
slug = forms.CharField(validators=[validators.validate_slug])
Vanliga fall som validering mot ett e-postmeddelande eller ett reguljärt uttryck kan hanteras med hjälp av befintliga valideringsklasser som finns i Django. Till exempel: är validators.validate_slug
en instans av en RegexValidator
konstruerad med det första argumentet som är mönstret: ^[-a-zA-Z0-9_]+\Z
. Se avsnittet om Skriva validatorer för att se en lista över vad som redan finns tillgängligt och för ett exempel på hur man skriver en validator.
Standardrengöring av formulärfält¶
Låt oss först skapa ett anpassat formulärfält som validerar att dess inmatning är en sträng som innehåller kommaseparerade e-postadresser. Den fullständiga klassen ser ut så här:
from django import forms
from django.core.validators import validate_email
class MultiEmailField(forms.Field):
def to_python(self, value):
"""Normalize data to a list of strings."""
# Return an empty list if no input was given.
if not value:
return []
return value.split(",")
def validate(self, value):
"""Check if value consists only of valid emails."""
# Use the parent's handling of required fields, etc.
super().validate(value)
for email in value:
validate_email(email)
Varje formulär som använder det här fältet kommer att köra dessa metoder innan något annat kan göras med fältets data. Det här är en rengöring som är specifik för den här typen av fält, oavsett hur det sedan används.
Låt oss skapa en ContactForm
för att visa hur du använder det här fältet:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
recipients = MultiEmailField()
cc_myself = forms.BooleanField(required=False)
Använd MultiEmailField
som vilket annat formulärfält som helst. När metoden is_valid()
anropas på formuläret kommer metoden MultiEmailField.clean()
att köras som en del av rengöringsprocessen och den kommer i sin tur att anropa de anpassade metoderna to_python()
och validate()
.
Rengöring av ett specifikt fältattribut¶
Om vi fortsätter från föregående exempel, anta att vi i vårt ContactForm
vill se till att fältet recipients
alltid innehåller adressen "fred@example.com"
. Det här är validering som är specifik för vårt formulär, så vi vill inte lägga in den i den allmänna klassen MultiEmailField
. Istället skriver vi en rengöringsmetod som fungerar på fältet recipients
, så här:
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data["recipients"]
if "fred@example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data
Rengöring och validering av fält som är beroende av varandra¶
Anta att vi lägger till ett annat krav i vårt kontaktformulär: om fältet cc_myself
är True
måste ubject
innehålla ordet "help"
. Vi utför validering på mer än ett fält i taget, så formulärets clean()
-metod är en bra plats att göra detta på. Observera att vi här talar om clean()
-metoden på formuläret, medan vi tidigare skrev en clean()
-metod på ett fält. Det är viktigt att hålla fält- och formulärskillnaden tydlig när du räknar ut var du ska validera saker. Fält är enskilda datapunkter, formulär är en samling fält.
När formulärets clean()
-metod anropas har alla enskilda fältrengöringsmetoder körts (de två föregående avsnitten), så self.cleaned_data
kommer att fyllas i med alla data som har överlevt hittills. Så du måste också komma ihåg att ta hänsyn till det faktum att de fält du vill validera kanske inte har överlevt de första individuella fältkontrollerna.
Det finns två sätt att rapportera eventuella fel från detta steg. Den vanligaste metoden är förmodligen att visa felet högst upp i formuläret. För att skapa ett sådant fel kan du skapa ett ValidationError
från clean()
-metoden. Till exempel:
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject:
# Only do something if both fields are valid so far.
if "help" not in subject:
raise ValidationError(
"Did not send for 'help' in the subject despite CC'ing yourself."
)
I den här koden visas ett felmeddelande högst upp i formuläret (normalt) som beskriver problemet om valideringsfelet uppstår. Sådana fel är icke-fältfel, som visas i mallen med {{ form.non_field_errors }}
.
Anropet till `super().clean()
i exempelkoden säkerställer att all valideringslogik i överordnade klasser bibehålls. Om ditt formulär ärver ett annat som inte returnerar en cleaned_data
-ordbok i sin clean()
-metod (att göra det är valfritt), tilldela då inte cleaned_data
till resultatet av super()
-anropet och använd self.cleaned_data
istället:
def clean(self):
super().clean()
cc_myself = self.cleaned_data.get("cc_myself")
...
Den andra metoden för att rapportera valideringsfel kan innebära att felmeddelandet tilldelas ett av fälten. I det här fallet tilldelar vi ett felmeddelande till både raderna ”subject” och ”cc_myself” i formulärvisningen. Var försiktig när du gör detta i praktiken, eftersom det kan leda till förvirrande formulärutmatning. Vi visar vad som är möjligt här och överlåter åt dig och dina designers att komma fram till vad som fungerar effektivt i just din situation. Vår nya kod (som ersätter det tidigare exemplet) ser ut så här:
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error("cc_myself", msg)
self.add_error("subject", msg)
Det andra argumentet i add_error()
kan vara en sträng, eller helst en instans av ValidationError
. Se Upphävande av ValidationError för mer information. Notera att add_error()
automatiskt tar bort fältet från cleaned_data
.