Skip to content

Instantly share code, notes, and snippets.

@cwurld
Last active April 22, 2022 01:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cwurld/c2381290d60d770ba09d01e1132ca565 to your computer and use it in GitHub Desktop.
Save cwurld/c2381290d60d770ba09d01e1132ca565 to your computer and use it in GitHub Desktop.
Django model formset example
from django.forms.models import inlineformset_factory
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django import forms
from crispy_forms.helper import FormHelper
# The forms ---------------------------------------------------------------------------------------------------------
class ParentForm(forms.ModelForm):
# For crispy
helper = FormHelper()
helper.form_class = 'form-horizontal'
helper.label_class = 'col-md-2'
helper.field_class = 'col-md-10'
helper.include_media = False
helper.form_tag = False
class Meta:
model = models.Parent
fields = ('field1', 'field2')
class ItemForm(forms.ModelForm):
class Meta:
model = models.Item
fields = ('field1', 'field2')
class BaseItemFormSet(forms.models.BaseInlineFormSet):
"""
Use this to do cross form validation. This is commonly needed. Although sometimes a better solution is to
use model constraints such as "unique_together"
"""
def clean(self):
super(BaseItemFormSet, self).clean()
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
# Do error cross form checking here
for form in self.forms:
if error:
raise forms.ValidationError("Opps. There was an error")
# For using django crispy
class ItemFormSetFormHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(ItemFormSetFormHelper, self).__init__(*args, **kwargs)
self.template = 'bootstrap/table_inline_formset.html'
self.include_media = False
self.form_tag = False
self.form_show_errors = True
# The view ----------------------------------------------------------------------------------------------------------
def model_w_items_view(request, parent_id=None):
if parent_id:
parent_instance = models.Parent.objects.get(id=int(parent_id))
headline = 'Update'
extra_lines = 1
else:
parent_instance = None
headline = 'Create'
extra_lines = 5
# fk_name = the name of the foreign key field in models.Item
ItemFormSet = inlineformset_factory(
models.Parent, models.Item, form=ItemForm, formset=BaseItemFormSet,
fk_name='parent', extra=int(extra_lines)
)
# Use this if you are using crispy to layout the Item forms
formset_helper = ItemFormSetFormHelper()
if request.method == "POST":
main_form = ItemForm(request.POST, instance=parent_instance)
if main_form.is_valid():
parent_instance = main_form.save()
formset = ItemFormSet(request.POST, request.FILES, instance=parent_instance)
if main_form.is_valid() and formset.is_valid():
main_form.save()
formset.save()
return HttpResponseRedirect('/')
else:
formset = ItemFormSet(instance=parent_instance)
main_form = ParentForm(instance=parent_instance)
return render(request, "my_template.html", {
'main_form': main_form,
'item_formset': formset,
'formset_helper': formset_helper,
'parent_instance': parent_instance,
'headline': headline,
})
# The template ------------------------------------------------------------------------------------------------------
# This template uses Bootstrap panels
'''
{% load crispy_forms_tags %}
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-heading">{{ headline }}</h4>
</div>
<div class="panel-body">
<form class="form-horizontal" method="post">
{% if main_form.non_form_errors %}
{{ main_form.non_form_errors }}
{% endif %}
{% crispy main_form %}
{% if item_formset.non_form_errors %}
{{ item_formset.non_form_errors }}
{% endif %}
{% crispy item_formset helper %}
<div>
<input type="submit" name="save" value="Save" class="btn btn-primary">
<a onclick='window.history.back();' class="btn btn-default">Cancel</a>
</div>
</form>
</div>
</div>
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment