Skapa formulär från modeller¶
ModelForm
¶
Om du bygger en databasdriven app är chansen stor att du har formulär som är nära kopplade till Django-modeller. Till exempel kan du ha en BlogComment
-modell, och du vill skapa ett formulär som låter människor skicka in kommentarer. I det här fallet skulle det vara överflödigt att definiera fälttyperna i ditt formulär, eftersom du redan har definierat fälten i din modell.
Av denna anledning tillhandahåller Django en hjälpklass som låter dig skapa en Form
-klass från en Django-modell.
Till exempel:
>>> from django.forms import ModelForm
>>> from myapp.models import Article
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ["pub_date", "headline", "content", "reporter"]
...
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
Fälttyper¶
Den genererade klassen Form
kommer att ha ett formulärfält för varje modellfält som anges, i den ordning som anges i attributet fields
.
Varje modellfält har ett motsvarande standardformulärsfält. Till exempel representeras ett CharField
i en modell som ett CharField
i ett formulär. En modells ManyToManyField
representeras som en MultipleChoiceField
. Här är den fullständiga listan över konverteringar:
Modellfält |
Formulärfält |
---|---|
Ej representerad i formuläret |
|
Ej representerad i formuläret |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ej representerad i formuläret |
|
|
|
Som du kanske förväntar dig är modellfälttyperna ForeignKey
och ManyToManyField
specialfall:
ForeignKey
representeras avdjango.forms.ModelChoiceField
, som är enChoiceField
vars val är en modellQuerySet
.ManyToManyField
representeras avdjango.forms.ModelMultipleChoiceField
, som är enMultipleChoiceField
vars val är en modellQuerySet
.
Dessutom har varje genererat formulärfält attribut som är inställda enligt följande:
Om modellfältet har
blank=True
, sättsrequired
tillFalse
på formulärfältet. Annars ärrequired=True
.Formulärfältets
label
sätts till modellfältetsverbose_name
, med det första tecknet med stor bokstav.Formulärfältets
help_text
sätts till modellfältetshelp_text
.Om modellfältet har
choices
inställt, kommer formulärfältetswidget
att vara inställt påSelect
, med val som kommer från modellfältetschoices
. Valmöjligheterna inkluderar normalt det tomma valet som är valt som standard. Om fältet är obligatoriskt tvingar detta användaren att göra ett val. Det tomma valet kommer inte att inkluderas om modellfältet harblank=False
och ett explicitdefault
-värde (default
-värdet kommer att väljas initialt istället).
Observera slutligen att du kan åsidosätta det formulärfält som används för ett visst modellfält. Se Overstyra standardfälten nedan.
Ett fullständigt exempel¶
Tänk på denna uppsättning modeller:
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = {
"MR": "Mr.",
"MRS": "Mrs.",
"MS": "Ms.",
}
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
class BookForm(ModelForm):
class Meta:
model = Book
fields = ["name", "authors"]
Med dessa modeller skulle ModelForm
-underklasserna ovan vara ungefär likvärdiga med detta (den enda skillnaden är save()
-metoden, som vi kommer att diskutera om ett ögonblick.):
from django import forms
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(
max_length=3,
widget=forms.Select(choices=TITLE_CHOICES),
)
birth_date = forms.DateField(required=False)
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
Validering på en ModelForm
¶
Det finns två huvudsakliga steg i valideringen av en ModelForm
:
Precis som normal formulärvalidering utlöses validering av modellformulär implicit vid anrop av is_valid()
eller åtkomst till attributet errors
och explicit vid anrop av full_clean()
, även om du vanligtvis inte kommer att använda den senare metoden i praktiken.
Model
-validering (Model.full_clean()
) utlöses inom valideringssteget för formuläret, direkt efter att formulärets clean()
-metod har anropats.
Varning
Rengöringsprocessen ändrar den modellinstans som skickas till konstruktören ModelForm
på olika sätt. Till exempel konverteras alla datumfält i modellen till faktiska datumobjekt. Misslyckad validering kan lämna den underliggande modellinstansen i ett inkonsekvent tillstånd och därför rekommenderas det inte att återanvända den.
Åsidosättande av metoden clean()
¶
Du kan åsidosätta metoden clean()
på ett modellformulär för att tillhandahålla ytterligare validering på samma sätt som du kan på ett normalt formulär.
En model form-instans som är kopplad till ett model-objekt innehåller ett instance
-attribut som ger dess metoder tillgång till den specifika model-instansen.
Varning
Metoden ModelForm.clean()
anger en flagga som gör att steget modellvalidering validerar unikheten hos modellfält som är markerade som unique
, unique_together
eller unique_for_date|month|year
.
Om du vill åsidosätta metoden clean()
och behålla denna validering måste du anropa den överordnade klassens metod clean()
.
Samverkan med modellvalidering¶
Som en del av valideringsprocessen kommer ModelForm
att anropa clean()
-metoden för varje fält i din modell som har ett motsvarande fält i ditt formulär. Om du har uteslutit några modellfält kommer valideringen inte att köras på dessa fält. Se :doc:``formulärsvalidering </ref/forms/validation>`-dokumentationen för mer information om hur fältrengöring och validering fungerar.
Modellens metod clean()
anropas innan någon unikhetskontroll görs. Se :ref:Validering av objekt <validating-objects>` för mer information om modellens ``clean()
-krok.
Överväganden angående modellens error_messages
¶
Felmeddelanden som definieras på nivån form field
eller på nivån form Meta har alltid företräde framför felmeddelanden som definieras på nivån model field
.
Felmeddelanden som definieras på modellfält
används endast när ValidationError
uppstår under modellvalidering och inga motsvarande felmeddelanden har definierats på formulärnivå.
Du kan åsidosätta felmeddelandena från NON_FIELD_ERRORS
som uppkommer vid modellvalidering genom att lägga till NON_FIELD_ERRORS
-nyckeln i error_messages
-ordlistan i ModelForm
:s inre Meta
-klass:
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
"unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
Metoden ”spara()``¶
Varje ModelForm
har också en save()
-metod. Denna metod skapar och sparar ett databasobjekt från de data som är bundna till formuläret. En subklass av ModelForm
kan acceptera en befintlig modellinstans som nyckelordsargumentet instance
; om detta tillhandahålls kommer save()
att uppdatera den instansen. Om det inte anges kommer save()
att skapa en ny instans av den angivna modellen:
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
Observera att om formuläret inte har validerats, kommer anrop av save()
att göra det genom att kontrollera form.errors
. Ett ValueError
kommer att uppstå om data i formuläret inte valideras – dvs. om form.errors
utvärderas till True
.
Om ett valfritt fält inte visas i formulärets data använder den resulterande modellinstansen modellfältet default
, om det finns ett sådant, för det fältet. Detta beteende gäller inte för fält som använder CheckboxInput
, CheckboxSelectMultiple
, eller SelectMultiple
(eller någon anpassad widget vars value_omitted_from_data()
alltid returnerar False
) eftersom en avmarkerad kryssruta och omarkerad <select multiple>
inte visas i data för en HTML-formulärinlämning. Använd ett anpassat formulärfält eller en widget om du utformar ett API och vill ha ett standardfallbackbeteende för ett fält som använder en av dessa widgetar.
Denna metod save()
accepterar ett valfritt nyckelordsargument commit
, som accepterar antingen True
eller False
. Om du anropar save()
med commit=False
kommer den att returnera ett objekt som ännu inte har sparats i databasen. I det här fallet är det upp till dig att anropa save()
på den resulterande modellinstansen. Detta är användbart om du vill göra anpassad bearbetning på objektet innan du sparar det, eller om du vill använda ett av de specialiserade modellsparingsalternativen. commit
är True
som standard.
En annan bieffekt av att använda commit=False
är när din modell har en många-till-många-relation med en annan modell. Om din modell har en many-to-many-relation och du anger commit=False
när du sparar ett formulär, kan Django inte omedelbart spara formulärdata för many-to-many-relationen. Detta beror på att det inte är möjligt att spara many-to-many-data för en instans förrän instansen finns i databasen.
För att komma runt detta problem lägger Django till en metod save_m2m()
till din underklass ModelForm
varje gång du sparar ett formulär med commit=False
. När du manuellt har sparat den instans som produceras av formuläret kan du anropa save_m2m()
för att spara formulärdata från många till många. Ett exempel:
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = "some_value"
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
Anrop av save_m2m()
krävs bara om du använder save(commit=False)
. När du använder save()
på ett formulär sparas alla data - även många-till-många-data - utan att du behöver göra några ytterligare metodanrop. Ett exempel:
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
Förutom metoderna save()
och save_m2m()
fungerar en ModelForm
på exakt samma sätt som alla andra forms
-formulär. Till exempel används metoden is_valid()
för att kontrollera giltigheten, metoden is_multipart()
används för att avgöra om ett formulär kräver uppladdning av flera filer (och därmed om request.FILES
måste skickas till formuläret), etc. Se Binder uppladdade filer till ett formulär för mer information.
Välja de fält som ska användas¶
Det rekommenderas starkt att du uttryckligen anger alla fält som ska redigeras i formuläret med hjälp av attributet fields
. Om man inte gör det kan det lätt leda till säkerhetsproblem när ett formulär oväntat tillåter en användare att ställa in vissa fält, särskilt när nya fält läggs till i en modell. Beroende på hur formuläret renderas kanske problemet inte ens är synligt på webbsidan.
Det alternativa tillvägagångssättet skulle vara att inkludera alla fält automatiskt, eller bara ta bort vissa. Detta grundläggande tillvägagångssätt är känt för att vara mycket mindre säkert och har lett till allvarliga exploateringar på stora webbplatser (t.ex. GitHub).
Det finns dock två genvägar för de fall där du kan garantera att dessa säkerhetsproblem inte gäller dig:
Sätt attributet
fields
till specialvärdet'__all__'
för att ange att alla fält i modellen ska användas. Till exempel:from django.forms import ModelForm class AuthorForm(ModelForm): class Meta: model = Author fields = "__all__"
Ställ in attributet
exclude
iModelForm
:s inreMeta
-klass till en lista över fält som ska uteslutas från formuläret.Till exempel:
class PartialAuthorForm(ModelForm): class Meta: model = Author exclude = ["title"]
Eftersom modellen
Author
har de 3 fältenname
,title
ochbirth_date
, kommer detta att resultera i att fältenname
ochbirth_date
finns på formuläret.
Om något av dessa alternativ används kommer fälten att visas i formuläret i den ordning som fälten definieras i modellen, med ManyToManyField
-instanser sist.
Dessutom tillämpar Django följande regel: om du ställer in editable=False
på modellfältet, kommer alla formulär som skapas från modellen via ModelForm
inte att innehålla det fältet.
Observera
Alla fält som inte inkluderas i ett formulär med ovanstående logik kommer inte att ställas in med formulärets save()
-metod. Om du manuellt lägger till de uteslutna fälten i formuläret kommer de inte heller att initialiseras från modellinstansen.
Django kommer att förhindra alla försök att spara en ofullständig modell, så om modellen inte tillåter att de saknade fälten är tomma och inte tillhandahåller ett standardvärde för de saknade fälten, kommer alla försök att save()
en ModelForm
med saknade fält att misslyckas. För att undvika detta misslyckande måste du instansiera din modell med initiala värden för de saknade men obligatoriska fälten:
author = Author(title="Mr")
form = PartialAuthorForm(request.POST, instance=author)
form.save()
Alternativt kan du använda save(commit=False)
och manuellt ange eventuella extra obligatoriska fält:
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = "Mr"
author.save()
Se avsnittet om att spara formulär för mer information om hur du använder save(commit=False)
.
Åsidosätta standardfälten¶
Standardfälttyperna, som beskrivs i tabellen Field types ovan, är förnuftiga standardvärden. Om du har ett DateField
i din modell är chansen stor att du vill att det ska representeras som ett DateField
i ditt formulär. Men ModelForm
ger dig flexibiliteten att ändra formulärfältet för en given modell.
Om du vill ange en anpassad widget för ett fält använder du attributet widgets
i den inre klassen Meta
. Detta bör vara en ordbok som mappar fältnamn till widgetklasser eller -instanser.
Om du till exempel vill att CharField
för attributet name
i Author
ska representeras av en <textarea>
i stället för standardvärdet <input type="text">
, kan du åsidosätta fältets widget:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
widgets = {
"name": Textarea(attrs={"cols": 80, "rows": 20}),
}
Ordboken widgets
accepterar antingen widgetinstanser (t.ex. Textarea(...)
) eller klasser (t.ex. Textarea
). Observera att widgets
-dictionary ignoreras för ett modellfält med ett icke-tomt choices
-attribut. I det här fallet måste du åsidosätta formulärfältet för att använda en annan widget.
På samma sätt kan du ange attributen labels
, help_texts
och error_messages
i den inre klassen Meta
om du vill anpassa ett fält ytterligare.
Om du till exempel vill anpassa ordalydelsen i alla strängar för fältet namn
för användare:
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
labels = {
"name": _("Writer"),
}
help_texts = {
"name": _("Some useful help text."),
}
error_messages = {
"name": {
"max_length": _("This writer's name is too long."),
},
}
Du kan också ange field_classes
eller formfield_callback
för att anpassa typen av fält som instansieras av formuläret.
Om du till exempel vill använda MySlugFormField
för fältet slug
kan du göra följande:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
field_classes = {
"slug": MySlugFormField,
}
eller:
from django.forms import ModelForm
from myapp.models import Article
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
formfield_callback = formfield_for_dbfield
Slutligen, om du vill ha fullständig kontroll över ett fält - inklusive dess typ, validerare, obligatoriska osv. – kan du göra detta genom att deklarativt specificera fält som du skulle göra i en vanlig Form
.
Om du vill ange ett fälts validatorer kan du göra det genom att definiera fältet deklarativt och ange parametern validators
:
from django.forms import CharField, ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
Observera
När du uttryckligen instansierar ett formulärfält på det här sättet är det viktigt att förstå hur ModelForm
och vanliga Form
hänger ihop.
ModelForm
är en vanlig Form
som automatiskt kan generera vissa fält. De fält som genereras automatiskt beror på innehållet i klassen Meta
och på vilka fält som redan har definierats deklarativt. I grund och botten kommer ModelForm
endast att generera fält som saknas i formuläret, eller med andra ord, fält som inte definierats deklarativt.
Fält som definieras deklarativt lämnas som de är, och därför ignoreras alla anpassningar som gjorts av Meta
-attribut som widgets
, labels
, help_texts
eller error_messages
; dessa gäller endast för fält som genereras automatiskt.
På samma sätt hämtar fält som definieras deklarativt inte sina attribut som max_length
eller required
från motsvarande modell. Om du vill behålla det beteende som anges i modellen måste du ange de relevanta argumenten explicit när du deklarerar formulärfältet.
Till exempel, om modellen Artikel
ser ut så här:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text="Use puns liberally",
)
content = models.TextField()
och du vill göra en anpassad validering för headline
, samtidigt som du behåller värdena blank
och help_text
som specificerade, kan du definiera ArticleForm
så här:
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text="Use puns liberally",
)
class Meta:
model = Article
fields = ["headline", "content"]
Du måste se till att formulärfältets typ kan användas för att ange innehållet i motsvarande modellfält. Om de inte är kompatibla kommer du att få ett ValueError
eftersom ingen implicit konvertering sker.
Se :doc:``Formulärfältsdokumentation </ref/forms/fields>` för mer information om fält och deras argument.
Möjliggör lokalisering av fält¶
Som standard kommer fälten i ett ModelForm
inte att lokalisera sina data. För att aktivera lokalisering för fält kan du använda attributet localized_fields
på klassen Meta
.
>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
... class Meta:
... model = Author
... localized_fields = ['birth_date']
Om localized_fields
är satt till specialvärdet '__all__'
, kommer alla fält att lokaliseras.
Form av arv¶
Precis som med grundläggande formulär kan du utöka och återanvända ModelForm
-klasser genom att ärva dem. Detta är användbart om du behöver deklarera extra fält eller extra metoder i en överordnad klass som ska användas i ett antal formulär som härrör från modeller. Till exempel genom att använda den tidigare klassen ArticleForm
:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self): ...
...
Detta skapar ett formulär som beter sig identiskt med ArticleForm
, förutom att det finns lite extra validering och rengöring för fältet pub_date
.
Du kan också subklassa förälderns inre klass Meta
om du vill ändra listorna Meta.fields
eller Meta.exclude
:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ["body"]
...
Detta lägger till den extra metoden från EnhancedArticleForm
och ändrar den ursprungliga ArticleForm.Meta
för att ta bort ett fält.
Det finns dock ett par saker att notera.
Normala Python-regler för namnupplösning gäller. Om du har flera basklasser som deklarerar en
Meta
innerklass, kommer endast den första att användas. Detta innebär barnetsMeta
, om det finns, annars den första föräldernsMeta
, etc.Det är möjligt att ärva från både
Form
ochModelForm
samtidigt, men du måste se till attModelForm
visas först i MRO. Detta beror på att dessa klasser förlitar sig på olika metaklasser och en klass kan bara ha en metaklass.Det är möjligt att deklarativt ta bort ett
Field
som ärvts från en överordnad klass genom att ställa in namnet tillNone
i underklassen.Du kan bara använda den här tekniken för att välja bort ett fält som definieras deklarativt av en överordnad klass; det hindrar inte metaklassen
ModelForm
från att generera ett standardfält. För att välja bort standardfält, se Välja de fält som ska användas.
Tillhandahållande av initiala värden¶
Precis som med vanliga formulär går det att ange initiala data för formulär genom att ange en initial
-parameter när formuläret instansieras. Initiala värden som anges på detta sätt åsidosätter både initiala värden från formulärfältet och värden från en ansluten modellinstans. Ett exempel:
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={"headline": "Initial headline"}, instance=article)
>>> form["headline"].value()
'Initial headline'
ModelForm fabriksfunktion¶
Du kan skapa formulär från en given modell med hjälp av den fristående funktionen modelform_factory()
, istället för att använda en klassdefinition. Detta kan vara mer praktiskt om du inte har många anpassningar att göra:
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=["author", "title"])
Detta kan också användas för att göra ändringar i befintliga formulär, t.ex. genom att ange vilka widgets som ska användas för ett visst fält:
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
De fält som ska inkluderas kan anges med hjälp av nyckelordsargumenten fields
och exclude
, eller motsvarande attribut på den inre Meta
-klassen ModelForm
. Se dokumentationen för ModelForm
Välja de fält som ska användas.
… eller aktivera lokalisering för specifika fält:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
Modellformsatser¶
- class models.BaseModelFormSet¶
Liksom regelbundna formulär tillhandahåller Django ett par förbättrade formulärklasser för att göra arbetet med Django-modeller mer bekvämt. Låt oss återanvända modellen Author
från ovan:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
Om du använder fields
begränsas formuläret till att endast använda de angivna fälten. Alternativt kan du välja en ”opt-out”-strategi och ange vilka fält som ska uteslutas:
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
Detta kommer att skapa ett formulär som kan arbeta med data som är associerade med modellen Author
. Det fungerar precis som ett vanligt formulär:
>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div>
<div><label for="id_form-0-title">Title:</label><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></div>
Observera
modelformset_factory()
använder formset_factory()
för att generera formulär. Detta innebär att ett modellformulär är en förlängning av ett grundläggande formulär som vet hur man interagerar med en viss modell.
Observera
När du använder :ref:multi-table inheritance <multi-table-inheritance>
kommer formulär som genereras av en formulärfabrik att innehålla ett fält för föräldralänk (som standard <parent_model_name>_ptr
) i stället för ett id
-fält.
Ändra queryset¶
När du skapar ett formulär från en modell kommer formuläret som standard att använda en queryset som innehåller alla objekt i modellen (t.ex. Author.objects.all()
). Du kan åsidosätta detta beteende genom att använda argumentet queryset
:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
Alternativt kan du skapa en subklass som ställer in self.queryset
i __init__
:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith="O")
Sedan skickar du din BaseAuthorFormSet
-klass till fabriksfunktionen:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=["name", "title"], formset=BaseAuthorFormSet
... )
Om du vill returnera en formuläruppsättning som inte innehåller några redan existerande instanser av modellen kan du ange en tom QuerySet:
>>> AuthorFormSet(queryset=Author.objects.none())
Ändring av formuläret¶
Som standard, när du använder modelformset_factory
, kommer ett modellformulär att skapas med modelform_factory()
. Ofta kan det vara användbart att ange ett anpassat modellformulär. Du kan till exempel skapa ett anpassat modellformulär som har anpassad validering:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
...
Skicka sedan din modellblankett till fabriksfunktionen:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
Det är inte alltid nödvändigt att definiera ett eget modellformulär. Funktionen modelformset_factory
har flera argument som skickas till modelform_factory
och som beskrivs nedan.
Ange widgets som ska användas i formuläret med widgets
¶
Med hjälp av parametern widgets
kan du ange en ordbok med värden för att anpassa ModelForm
:s widgetklass för ett visst fält. Detta fungerar på samma sätt som widgets
-ordlistan på den inre Meta
-klassen i en ModelForm
fungerar:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
... )
Aktivera lokalisering för fält med localized_fields
¶
Med hjälp av parametern localized_fields
kan du aktivera lokalisering för fält i formuläret.
>>> AuthorFormSet = modelformset_factory(
... Author, fields=['name', 'title', 'birth_date'],
... localized_fields=['birth_date'])
Om localized_fields
är satt till specialvärdet '__all__'
, kommer alla fält att lokaliseras.
Tillhandahållande av initiala värden¶
Precis som med vanliga formuläruppsättningar är det möjligt att specificera initialdata för formulär i formuläruppsättningen genom att ange en initial
-parameter när man instansierar klassen model formset som returneras av modelformset_factory()
. Men med modellformsatser gäller de initiala värdena endast för extra formulär, de som inte är kopplade till en befintlig modellinstans. Om längden på initial
överstiger antalet extra formulär ignoreras överskottet av initiala data. Om de extra formulären med initiala data inte ändras av användaren kommer de inte att valideras eller sparas.
Spara objekt i formuläret¶
Precis som med en ModelForm
kan du spara data som ett modellobjekt. Detta görs med formsetets metod save()
:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
Metoden save()
returnerar de instanser som har sparats i databasen. Om data för en viss instans inte har ändrats i de bundna data sparas inte instansen i databasen och ingår inte i returvärdet (instances
, i exemplet ovan).
Om fält saknas i formuläret (t.ex. för att de har uteslutits) kommer dessa fält inte att anges med metoden save()
. Mer information om denna begränsning, som även gäller för vanliga modellformulär, finns i Välja vilka fält som ska användas.
Skicka commit=False
för att returnera de osparade modellinstanserna:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
...
Detta ger dig möjlighet att bifoga data till instanserna innan du sparar dem i databasen. Om ditt formulär innehåller en ManyToManyField
måste du också anropa formset.save_m2m()
för att säkerställa att många-till-många-relationerna sparas korrekt.
När du har anropat save()
kommer din formulärmodell att ha tre nya attribut som innehåller formulärets ändringar:
- models.BaseModelFormSet.changed_objects¶
- models.BaseModelFormSet.deleted_objects¶
- models.BaseModelFormSet.new_objects¶
Begränsa antalet redigerbara objekt¶
Precis som med vanliga formuläruppsättningar kan du använda parametrarna max_num
och extra
till modelformset_factory()
för att begränsa antalet extra formulär som visas.
max_num
förhindrar inte att befintliga objekt visas:
>>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
Dessutom hindrar inte extra=0
skapandet av nya modellinstanser eftersom du kan :ref:lägga till ytterligare formulär med JavaScript <understanding-the-managementform>
eller skicka ytterligare POST-data. Se Förhindrar att nya objekt skapas om hur man gör detta.
Om värdet för max_num
är större än antalet befintliga relaterade objekt, kommer upp till extra
ytterligare tomma formulär att läggas till i formuläruppsättningen, så länge det totala antalet formulär inte överstiger max_num
:
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></div>
<div><label for="id_form-1-name">Name:</label><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></div>
<div><label for="id_form-2-name">Name:</label><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></div>
<div><label for="id_form-3-name">Name:</label><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></div>
Ett värde på max_num
på None
(standard) sätter en hög gräns för antalet formulär som visas (1000). I praktiken är detta likvärdigt med ingen gräns.
Förhindrar att nya objekt skapas¶
Med hjälp av parametern edit_only
kan du förhindra att nya objekt skapas:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... edit_only=True,
... )
Här kommer formuläret endast att redigera befintliga Author
-instanser. Inga andra objekt kommer att skapas eller redigeras.
Använda en formulärmodell i en vy¶
Modellformulären är mycket lika formulären. Låt oss säga att vi vill presentera ett formulär för att redigera Author
modellinstanser:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, "manage_authors.html", {"formset": formset})
Som du kan se är visningslogiken för ett modellformulär inte drastiskt annorlunda än för ett ”vanligt” formulär. Den enda skillnaden är att vi anropar formset.save()
för att spara data i databasen. (Detta beskrevs ovan, i Spara objekt i formuläret.)
Åsidosätta clean()
på en ModelFormSet
¶
Precis som med en ModelForm
kommer som standard clean()
-metoden för en ModelFormSet
att validera att inget av objekten i formuläret bryter mot de unika begränsningarna i din modell (antingen unique
, unique_together
eller unique_for_date|month|year
). Om du vill åsidosätta metoden clean()
på en ModelFormSet
och behålla denna validering måste du anropa den överordnade klassens metod clean
:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
Observera också att när du kommer till det här steget har individuella modellinstanser redan skapats för varje Form
. Att ändra ett värde i form.cleaned_data
är inte tillräckligt för att påverka det sparade värdet. Om du vill ändra ett värde i ModelFormSet.clean()
måste du ändra form.instance
:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
name = form.cleaned_data["name"].upper()
form.cleaned_data["name"] = name
# update the instance value.
form.instance.name = name
Använda en anpassad queryset¶
Som tidigare nämnts kan du åsidosätta den förvalda queryset som används av modellen formset:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
Observera att vi skickar argumentet queryset
i både POST
och GET
-fallen i detta exempel.
Använda formuläret i mallen¶
Det finns tre sätt att rendera en formuläruppsättning i en Django-mall.
För det första kan du låta formuläret göra det mesta av jobbet:
<form method="post">
{{ formset }}
</form>
För det andra kan du manuellt rendera formuläruppsättningen, men låta formuläret sköta sig självt:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
När du manuellt renderar formulären själv, se till att rendera förvaltningsformuläret enligt ovan. Se dokumentation för förvaltningsformulär.
För det tredje kan du göra en manuell rendering av varje fält:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
Om du väljer att använda den här tredje metoden och inte itererar över fälten med en {% feller %}
-loop, måste du rendera fältet för primärnyckeln. Om du till exempel skulle rendera fälten namn
och ålder
i en modell:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
Lägg märke till hur vi uttryckligen måste rendera {{ form.id }}
. Detta säkerställer att modellformuläret, i fallet POST
, kommer att fungera korrekt. (I det här exemplet antas en primärnyckel med namnet id
. Om du uttryckligen har definierat din egen primärnyckel som inte heter id
, se till att den renderas)
Inline-formulär¶
- class models.BaseInlineFormSet¶
Inline-formulär är ett litet abstraktionslager ovanpå modellformulären. Dessa förenklar fallet med att arbeta med relaterade objekt via en främmande nyckel. Anta att du har dessa två modeller:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
Om du vill skapa ett formulär som gör att du kan redigera böcker som tillhör en viss författare kan du göra så här:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=["title"])
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
BookFormSet
s prefix är 'book_set'
(<model name>_set
). Om Book
’s ForeignKey
till Author
har en related_name
, används den istället.
Observera
inlineformset_factory()
använder modelformset_factory()
och markerar can_delete=True
.
Åsidosätta metoder för en InlineFormSet
¶
När du åsidosätter metoder på InlineFormSet
bör du underordna dig BaseInlineFormSet
snarare än BaseModelFormSet
.
Till exempel, om du vill åsidosätta clean()
:
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
Se även Åsidosätta clean() på en ModelFormSet.
När du sedan skapar din inline-formulärsats skickar du in det valfria argumentet formulärsats
:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(
... Author, Book, fields=["title"], formset=CustomInlineFormSet
... )
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
Mer än en främmande nyckel till samma modell¶
Om din modell innehåller mer än en främmande nyckel till samma modell måste du lösa tvetydigheten manuellt med hjälp av fk_name
. Tänk till exempel på följande modell:
class Friendship(models.Model):
from_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="from_friends",
)
to_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="friends",
)
length_in_months = models.IntegerField()
För att lösa detta kan du använda fk_name
till inlineformset_factory()
:
>>> FriendshipFormSet = inlineformset_factory(
... Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
... )
Använda en inline-formulärsats i en vy¶
Du kanske vill tillhandahålla en vy som gör det möjligt för en användare att redigera de relaterade objekten i en modell. Så här kan du göra det:
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"])
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something. Should generally end with a redirect. For example:
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render(request, "manage_books.html", {"formset": formset})
Notera hur vi skickar instance
i både POST
och GET
fallen.
Ange widgetar som ska användas i inline-formuläret¶
inlineformset_factory
använder modelformset_factory
och skickar de flesta av sina argument till modelformset_factory
. Detta innebär att du kan använda parametern widgets
på ungefär samma sätt som när du skickar den till modelformset_factory
. Se Specificera widgets som ska användas i formuläret med widgets ovan.