Formsatser¶
En formset är ett abstraktionslager som gör det möjligt att arbeta med flera formulär på samma sida. Det kan bäst jämföras med ett datagalleri. Låt oss säga att du har följande formulär:
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
...
Du kanske vill tillåta användaren att skapa flera artiklar på en gång. För att skapa en formuläruppsättning av en ArticleForm
gör du så här:
>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
Du har nu skapat en formulärklass med namnet ArticleFormSet
. Genom att instansiera formuläruppsättningen kan du iterera över formulären i formuläruppsättningen och visa dem som du skulle göra med ett vanligt formulär:
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
Som du kan se visade den bara ett tomt formulär. Antalet tomma formulär som visas styrs av parametern extra
. Som standard definierar formset_factory()
ett extra formulär; följande exempel kommer att skapa en formset-klass för att visa två tomma formulär:
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
Formuläruppsättningar kan itereras och indexeras, vilket ger åtkomst till formulären i den ordning de skapades. Du kan ändra ordningen på formulären genom att åsidosätta standardbeteendet för iteration
och indexing
om det behövs.
Använda initiala data med en formulärset¶
Initiala data är det som styr den huvudsakliga användbarheten för en formuläruppsättning. Som visas ovan kan du definiera antalet extra formulär. Det innebär att du talar om för formuläret hur många ytterligare formulär som ska visas utöver det antal formulär som genereras utifrån de ursprungliga uppgifterna. Låt oss ta en titt på ett exempel:
>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(
... initial=[
... {
... "title": "Django is now open source",
... "pub_date": datetime.date.today(),
... }
... ]
... )
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2023-02-11" id="id_form-0-pub_date"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
Det finns nu totalt tre formulär som visas ovan. Ett för de initiala data som skickades in och två extra formulär. Observera också att vi skickar in en lista med ordböcker som startdata.
Om du använder en initial
för att visa ett formulär bör du skicka samma initial
när du behandlar formulärets inlämning så att formuläret kan upptäcka vilka formulär som har ändrats av användaren. Till exempel kan du ha något liknande: ArticleFormSet(request.POST, initial=[...])
.
Begränsa det maximala antalet formulär¶
Parametern max_num
till formset_factory()
ger dig möjlighet att begränsa antalet formulär som formuläret ska visa:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
Om värdet för max_num
är större än antalet befintliga objekt i de ursprungliga uppgifterna, kommer upp till extra
ytterligare blanketter att läggas till i formuläruppsättningen, så länge det totala antalet blanketter inte överstiger max_num
. Om t.ex. extra=2
och max_num=2
och formuläruppsättningen initieras med ett initial
objekt, kommer ett formulär för det första objektet och ett tomt formulär att visas.
Om antalet objekt i startdata överstiger max_num
visas alla formulär med startdata oavsett värdet på max_num
och inga extra formulär visas. Om t.ex. extra=3
och max_num=1
och formuläruppsättningen initieras med två initiala objekt, visas två formulär med de initiala uppgifterna.
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.
Som standard påverkar max_num
endast hur många formulär som visas och påverkar inte valideringen. Om validate_max=True
skickas till formset_factory()
, så kommer max_num
att påverka valideringen. Se validera_max.
Begränsning av det maximala antalet instansierade formulär¶
Parametern absolute_max
till formset_factory()
gör det möjligt att begränsa antalet formulär som kan instansieras när POST
-data levereras. Detta skyddar mot minnesutmattningsattacker med hjälp av förfalskade POST
-förfrågningar:
>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
>>> data = {
... "form-TOTAL_FORMS": "1501",
... "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> len(formset.forms)
1500
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Please submit at most 1000 forms.']
När absolute_max
är None
, är standardvärdet max_num + 1000
. (Om max_num
är None
, är standardvärdet 2000
).
Om absolute_max
är mindre än max_num
, kommer ett ValueError
att uppstå.
Validering av formulär¶
Validering med en formset är nästan identisk med en vanlig Form
. Det finns en is_valid
-metod på formuläret för att tillhandahålla ett bekvämt sätt att validera alla formulär i formuläret:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
... "form-TOTAL_FORMS": "1",
... "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True
Vi har inte skickat in några data till formuläret, vilket resulterar i ett giltigt formulär. Formuläret är tillräckligt smart för att ignorera extra formulär som inte har ändrats. Om vi tillhandahåller en ogiltig artikel:
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test",
... "form-1-pub_date": "", # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
Som vi kan se är formset.errors
en lista vars poster motsvarar formulären i formuläruppsättningen. Validering utfördes för vart och ett av de två formulären, och det förväntade felmeddelandet visas för det andra objektet.
Precis som när du använder ett vanligt Form
kan varje fält i ett formulär innehålla HTML-attribut som maxlength
för webbläsarvalidering. Formulärfält i formuläruppsättningar kommer dock inte att innehålla attributet required
eftersom den valideringen kan vara felaktig när formulär läggs till och tas bort.
För att kontrollera hur många fel som finns i formuläret kan vi använda metoden total_error_count
:
>>> # Using the previous example
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> len(formset.errors)
2
>>> formset.total_error_count()
1
Vi kan också kontrollera om uppgifterna i formuläret skiljer sig från de ursprungliga uppgifterna (dvs. om formuläret skickades utan några uppgifter):
>>> data = {
... "form-TOTAL_FORMS": "1",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "",
... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False
Förståelse för ”Managementformuläret¶
Du kanske har lagt märke till de ytterligare data (form-TOTAL_FORMS
, form-INITIAL_FORMS
) som krävdes i formulärets data ovan. Dessa data krävs för ManagementForm
. Detta formulär används av formuläruppsättningen för att hantera den samling formulär som finns i formuläruppsättningen. Om du inte tillhandahåller dessa hanteringsdata kommer formuläret att vara ogiltigt:
>>> data = {
... "form-0-title": "Test",
... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
Det används för att hålla reda på hur många formulärinstanser som visas. Om du lägger till nya formulär via JavaScript bör du inkrementera count-fälten även i detta formulär. Om du å andra sidan använder JavaScript för att tillåta borttagning av befintliga objekt, måste du se till att de som tas bort är korrekt markerade för borttagning genom att inkludera form-#-DELETE
i POST
-data. Det förväntas att alla formulär finns i POST
-datan oavsett.
Hanteringsformuläret är tillgängligt som ett attribut för själva formuläret. När du renderar en formset i en mall kan du inkludera alla hanteringsdata genom att rendera {{ my_formset.management_form }}
(med namnet på din formset som ersättning).
Observera
Förutom fälten form-TOTAL_FORMS
och form-INITIAL_FORMS
som visas i exemplen här, innehåller hanteringsformuläret också fälten form-MIN_NUM_FORMS
och form-MAX_NUM_FORMS
. De matas ut tillsammans med resten av hanteringsformuläret, men endast för att underlätta för koden på klientsidan. Dessa fält är inte obligatoriska och visas därför inte i exemplet med POST
-data.
total_form_count
och initial_form_count
¶
BaseFormSet
har ett par metoder som är nära relaterade till ManagementForm
, total_form_count
och initial_form_count
.
total_form_count
returnerar det totala antalet formulär i denna formuläruppsättning. initial_form_count
returnerar antalet formulär i formuläruppsättningen som var förifyllda, och används också för att avgöra hur många formulär som krävs. Du kommer förmodligen aldrig att behöva åsidosätta någon av dessa metoder, så se till att du förstår vad de gör innan du gör det.
tomt_formulär
¶
BaseFormSet
tillhandahåller ett ytterligare attribut empty_form
som returnerar en formulärinstans med prefixet __prefix__
för enklare användning i dynamiska formulär med JavaScript.
Felmeddelanden
¶
Med argumentet error_messages
kan du åsidosätta de standardmeddelanden som formuläret kommer att ge upphov till. Skicka in en ordbok med nycklar som matchar de felmeddelanden som du vill åsidosätta. Felmeddelanden är bland annat 'too_few_forms'
, 'too_many_forms'
och 'missing_management_form'
. Felmeddelandena 'too_few_forms'
och 'too_many_forms'
kan innehålla %(num)d
, som kommer att ersättas med min_num
respektive max_num
.
Här är t.ex. standardfelmeddelandet när förvaltningsformuläret saknas:
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. You may need to file a bug report if the issue persists.']
Och här är ett anpassat felmeddelande:
>>> formset = ArticleFormSet(
... {}, error_messages={"missing_management_form": "Sorry, something went wrong."}
... )
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Sorry, something went wrong.']
Anpassad validering av formulär¶
Ett formulär har en clean
-metod som liknar den som finns i klassen Form
. Det är här du definierar din egen validering som fungerar på formulärnivå:
>>> from django.core.exceptions import ValidationError
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def clean(self):
... """Checks that no two articles have the same title."""
... if any(self.errors):
... # Don't bother validating the formset unless each form is valid on its own
... return
... titles = set()
... for form in self.forms:
... if self.can_delete and self._should_delete_form(form):
... continue
... title = form.cleaned_data.get("title")
... if title in titles:
... raise ValidationError("Articles in a set must have distinct titles.")
... titles.add(title)
...
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test",
... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Articles in a set must have distinct titles.']
Metoden clean
i formuläret anropas efter att alla Form.clean
-metoder har anropats. Felen kommer att hittas med hjälp av metoden non_form_errors()
på formuläret.
Icke-formulärfel återges med en extra klass nonform
för att skilja dem från formulärspecifika fel. Till exempel skulle {{ formset.non_form_errors }}
se ut så här:
<ul class="errorlist nonform">
<li>Articles in a set must have distinct titles.</li>
</ul>
Validering av antalet formulär i en formuläruppsättning¶
Django tillhandahåller ett par sätt att validera det lägsta eller högsta antalet inlämnade formulär. Applikationer som behöver mer anpassningsbar validering av antalet formulär bör använda anpassad validering av formuläruppsättningar.
validera_max
¶
Om validate_max=True
skickas till formset_factory()
, kommer valideringen också att kontrollera att antalet formulär i datauppsättningen, minus de som är markerade för borttagning, är mindre än eller lika med max_num
.
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test 2",
... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at most 1 form.']
validate_max=True
validerar mot max_num
strikt även om max_num
överskreds på grund av att mängden initiala data som levererades var för stor.
Felmeddelandet kan anpassas genom att skicka meddelandet 'too_many_forms'
till argumentet Felmeddelanden.
Observera
Oavsett validate_max
, om antalet formulär i en datauppsättning överstiger absolute_max
, kommer formuläret inte att valideras som om validate_max
hade angetts, och dessutom kommer endast de första absolute_max
formulären att valideras. Resten kommer att avkortas helt och hållet. Detta är för att skydda mot minnesutmattningsattacker med hjälp av förfalskade POST-begäranden. Se formuläruppsättningar-absolute-max.
validera_min
¶
Om validate_min=True
skickas till formset_factory()
, kommer valideringen också att kontrollera att antalet formulär i datauppsättningen, minus de som är markerade för borttagning, är större än eller lika med min_num
.
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test 2",
... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at least 3 forms.']
Felmeddelandet kan anpassas genom att skicka meddelandet 'too_few_forms'
till argumentet Felmeddelanden.
Observera
Oavsett validate_min
, om en formuläruppsättning inte innehåller några data, kommer extra + min_num
tomma formulär att visas.
Hantering av beställning och borttagning av formulär¶
formset_factory()
tillhandahåller två valfria parametrar can_order
och can_delete
för att hjälpa till med ordning av formulär i formulär och borttagning av formulär från ett formulär.
kan beställa
¶
- BaseFormSet.can_order¶
Standard: False
Låter dig skapa ett formulär med möjlighet att beställa:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ]
... )
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-ORDER">Order:</label><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-ORDER">Order:</label><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-ORDER">Order:</label><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></div>
Detta lägger till ett extra fält i varje formulär. Det nya fältet heter ORDER
och är ett forms.IntegerField
. För de formulär som kom från de ursprungliga uppgifterna tilldelades de automatiskt ett numeriskt värde. Låt oss titta på vad som kommer att hända när användaren ändrar dessa värden:
>>> data = {
... "form-TOTAL_FORMS": "3",
... "form-INITIAL_FORMS": "2",
... "form-0-title": "Article #1",
... "form-0-pub_date": "2008-05-10",
... "form-0-ORDER": "2",
... "form-1-title": "Article #2",
... "form-1-pub_date": "2008-05-11",
... "form-1-ORDER": "1",
... "form-2-title": "Article #3",
... "form-2-pub_date": "2008-05-01",
... "form-2-ORDER": "0",
... }
>>> formset = ArticleFormSet(
... data,
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ],
... )
>>> for form in formset.ordered_forms:
... print(form.cleaned_data)
...
{'title': 'Article #3', 'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0}
{'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1}
{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2}
BaseFormSet
tillhandahåller också attributet ordering_widget
och metoden get_ordering_widget()
som styr widgeten som används med can_order
.
beställning_widget
¶
- BaseFormSet.ordering_widget¶
Standard: NumberInput
Ange ordering_widget
för att specificera widgetklassen som ska användas med can_order
:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... ordering_widget = HiddenInput
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_order=True
... )
hämta beställningswidget¶
Åsidosätt get_ordering_widget()
om du behöver tillhandahålla en widgetinstans för användning med can_order
:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_ordering_widget(self):
... return HiddenInput(attrs={"class": "ordering"})
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_order=True
... )
kan radera
¶
- BaseFormSet.can_delete¶
Standard: False
Låter dig skapa en formuläruppsättning med möjlighet att välja formulär för borttagning:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ]
... )
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-DELETE">Delete:</label><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-DELETE">Delete:</label><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-DELETE">Delete:</label><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></div>
I likhet med can_order
lägger detta till ett nytt fält till varje formulär som heter DELETE
och är ett forms.BooleanField
. När data kommer genom att markera något av delete-fälten kan du komma åt dem med deleted_forms
:
>>> data = {
... "form-TOTAL_FORMS": "3",
... "form-INITIAL_FORMS": "2",
... "form-0-title": "Article #1",
... "form-0-pub_date": "2008-05-10",
... "form-0-DELETE": "on",
... "form-1-title": "Article #2",
... "form-1-pub_date": "2008-05-11",
... "form-1-DELETE": "",
... "form-2-title": "",
... "form-2-pub_date": "",
... "form-2-DELETE": "",
... }
>>> formset = ArticleFormSet(
... data,
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ],
... )
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'DELETE': True}]
Om du använder en ModelFormSet
, kommer modellinstanser för borttagna formulär att raderas när du anropar formset.save()
.
Om du anropar formset.save(commit=False)
kommer objekten inte att raderas automatiskt. Du måste anropa delete()
på var och en av formset.deleted_objects
för att faktiskt radera dem:
>>> instances = formset.save(commit=False)
>>> for obj in formset.deleted_objects:
... obj.delete()
...
Om du å andra sidan använder ett vanligt FormSet
är det upp till dig att hantera formset.deleted_forms
, kanske i ditt formsets save()
-metod, eftersom det inte finns någon allmän uppfattning om vad det innebär att ta bort ett formulär.
BaseFormSet
tillhandahåller också attributet deletion_widget
och metoden get_deletion_widget()
som styr widgeten som används med can_delete
.
deletion_widget
¶
- BaseFormSet.deletion_widget¶
Standard: CheckboxInput
Ange deletion_widget
för att specificera widgetklassen som ska användas med can_delete
:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... deletion_widget = HiddenInput
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_delete=True
... )
get_deletion_widget
¶
Åsidosätt get_deletion_widget()
om du behöver tillhandahålla en widgetinstans för användning med can_delete
:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_deletion_widget(self):
... return HiddenInput(attrs={"class": "deletion"})
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_delete=True
... )
kan_radera_extra
¶
- BaseFormSet.can_delete_extra¶
Standard: True
När du anger can_delete=True
och specificerar can_delete_extra=False
tas möjligheten att radera extra formulär bort.
Lägga till ytterligare fält i en formuläruppsättning¶
Om du behöver lägga till ytterligare fält i formuläret kan detta enkelt göras. Basklassen för formuläret innehåller metoden add_fields
. Du kan åsidosätta denna metod för att lägga till dina egna fält eller till och med omdefiniera standardfälten/attributen för order- och borttagningsfälten:
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def add_fields(self, form, index):
... super().add_fields(form, index)
... form.fields["my_field"] = forms.CharField()
...
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-my_field">My field:</label><input type="text" name="form-0-my_field" id="id_form-0-my_field"></div>
Överföra anpassade parametrar till formulär¶
Ibland tar din formulärklass anpassade parametrar, till exempel MyArticleForm
. Du kan skicka den här parametern när du instansierar formuläret:
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class MyArticleForm(ArticleForm):
... def __init__(self, *args, user, **kwargs):
... self.user = user
... super().__init__(*args, **kwargs)
...
>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={"user": request.user})
`form_kwargs
kan också bero på den specifika formulärinstansen. Basklassen formset tillhandahåller metoden get_form_kwargs
. Metoden tar ett enda argument - indexet för formuläret i formuläruppsättningen. Indexet är None
för tomt_formulär:
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> class BaseArticleFormSet(BaseFormSet):
... def get_form_kwargs(self, index):
... kwargs = super().get_form_kwargs(index)
... kwargs["custom_kwarg"] = index
... return kwargs
...
>>> ArticleFormSet = formset_factory(MyArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
Anpassa prefixet för ett formulär¶
I den renderade HTML-filen inkluderar formulär ett prefix på varje fältnamn. Som standard är prefixet 'form'
, men det kan anpassas med hjälp av formulärets prefix
-argument.
I standardfallet kan du till exempel se:
<label for="id_form-0-title">Title:</label>
<input type="text" name="form-0-title" id="id_form-0-title">
Men med ArticleFormset(prefix='article')
blir det så:
<label for="id_article-0-title">Title:</label>
<input type="text" name="article-0-title" id="id_article-0-title">
Detta är användbart om du vill använda mer än ett formulär i en vy.
Använda en formulärsats i vyer och mallar¶
Formset har följande attribut och metoder som är associerade med rendering:
- BaseFormSet.renderer¶
Anger vilken renderer som ska användas för formuläret. Standard är den rendering som anges av inställningen
FORM_RENDERER
.
- BaseFormSet.template_name[source]¶
Namnet på den mall som återges om formuläret omvandlas till en sträng, t.ex. via
print(formset)
eller i en mall via{{ formset }}
.Som standard en egenskap som returnerar värdet av renderingsprogrammets
formset_template_name
. Du kan ange det som ett mallnamn i form av en sträng för att åsidosätta det för en viss formulärklass.Denna mall kommer att användas för att rendera formuläruppsättningens hanteringsformulär och sedan varje formulär i formuläruppsättningen enligt den mall som definieras av formulärets
template_name
.
- BaseFormSet.template_name_div¶
Namnet på den mall som används vid anrop av
as_div()
. Som standard är detta"django/forms/formsets/div.html"
. Den här mallen renderar formuläruppsättningens hanteringsformulär och sedan varje formulär i formuläruppsättningen enligt formuläretsas_div()
-metod.
- BaseFormSet.template_name_p¶
Namnet på den mall som används vid anrop av
as_p()
. Som standard är detta"django/forms/formsets/p.html"
. Den här mallen renderar formuläruppsättningens hanteringsformulär och sedan varje formulär i formuläruppsättningen enligt formuläretsas_p()
-metod.
- BaseFormSet.template_name_table¶
Namnet på den mall som används vid anrop av
as_table()
. Som standard är detta"django/forms/formsets/table.html"
. Den här mallen renderar formuläruppsättningens hanteringsformulär och sedan varje formulär i formuläruppsättningen enligt formuläretsas_table()
-metod.
- BaseFormSet.template_name_ul¶
Namnet på den mall som används vid anrop av
as_ul()
. Som standard är detta"django/forms/formsets/ul.html"
. Den här mallen renderar formuläruppsättningens hanteringsformulär och sedan varje formulär i formuläruppsättningen enligt formuläretsas_ul()
-metod.
- BaseFormSet.get_context()[source]¶
Returnerar kontexten för rendering av en formulärsats i en mall.
Den tillgängliga kontexten är:
formset
: Instansen av formuläret.
- BaseFormSet.render(template_name=None, context=None, renderer=None)¶
Renderingsmetoden anropas av
__str__
samt metodernaas_div()
,as_p()
,as_ul()
ochas_table()
. Alla argument är valfria och kommer som standard att vara:template_name
:template_name
context
: Värde som returneras avget_context()
renderer
: Värde som returneras avrenderer
- BaseFormSet.as_div()¶
Renderar formuläret med mallen
template_name_div
.
- BaseFormSet.as_p()¶
Renderar formuläret med mallen
template_name_p
.
- BaseFormSet.as_table()¶
Renderar formuläret med mallen
template_name_table
.
- BaseFormSet.as_ul()¶
Renderar formuläret med mallen
template_name_ul
.
Att använda en formset i en vy skiljer sig inte mycket från att använda en vanlig Form
-klass. Det enda du vill vara medveten om är att se till att använda hanteringsformuläret inuti mallen. Låt oss titta på ett exempel på en vy:
from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
if request.method == "POST":
formset = ArticleFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
pass
else:
formset = ArticleFormSet()
return render(request, "manage_articles.html", {"formset": formset})
Mallen manage_articles.html
kan se ut så här:
<form method="post">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
Det finns dock en liten genväg till ovanstående genom att låta själva formuläret hantera hanteringsformuläret:
<form method="post">
<table>
{{ formset }}
</table>
</form>
Ovanstående anropar metoden BaseFormSet.render()
på klassen formset. Detta renderar formuläret med hjälp av den mall som anges av attributet template_name
. På samma sätt som för formulär, kommer formuläret som standard att renderas as_div
, med andra hjälpmetoder för as_p
, as_ul
och as_table
tillgängliga. Renderingen av formuläret kan anpassas genom att ange attributet template_name
, eller mer allmänt genom :ref:overriding the default template <overriding-built-in-formset-templates>
.
Manuellt återgivna can_delete
och can_order
¶
Om du manuellt renderar fält i mallen kan du rendera parametern can_delete
med {{ form.DELETE }}
:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
<ul>
<li>{{ form.title }}</li>
<li>{{ form.pub_date }}</li>
{% if formset.can_delete %}
<li>{{ form.DELETE }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
På samma sätt, om formuläret har möjlighet att beställa (can_order=True
), är det möjligt att rendera det med {{ form.ORDER }}
.
Använda mer än en formuläruppsättning i en vy¶
Du kan använda mer än en formuläruppsättning i en vy om du vill. Formsets lånar mycket av sitt beteende från formulär. Med detta sagt kan du använda prefix
för att prefixera formulärfältsnamn med ett givet värde så att mer än ett formulär kan skickas till en vy utan att namnen krockar. Låt oss ta en titt på hur detta kan åstadkommas:
from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
BookFormSet = formset_factory(BookForm)
if request.method == "POST":
article_formset = ArticleFormSet(request.POST, request.FILES, prefix="articles")
book_formset = BookFormSet(request.POST, request.FILES, prefix="books")
if article_formset.is_valid() and book_formset.is_valid():
# do something with the cleaned_data on the formsets.
pass
else:
article_formset = ArticleFormSet(prefix="articles")
book_formset = BookFormSet(prefix="books")
return render(
request,
"manage_articles.html",
{
"article_formset": article_formset,
"book_formset": book_formset,
},
)
Du skulle sedan rendera formuläruppsättningarna som normalt. Det är viktigt att påpeka att du måste skicka prefix
i både POST- och icke-POST-fallen så att det renderas och bearbetas korrekt.
Varje formuläruppsättnings prefix ersätter standardprefixet form
som läggs till i varje fälts HTML-attribut name
och id
.