Package: django-stapler
django-staplerProvides a simple way to combine multiple ModelForm classesDescriptionDjango's ModelForm class lets you create a Form class for a model. This lets you conveniently create and update model instances. In some specific cases it would be desiarble to combine multiple ModelForms so that you can create and update multiple model instances in one view with one form. django-stapler provides this functionalityDependenciesDjango 2.2+Installingpipinstalldjango-staplerBasic usageAdd app to settings.pyINSTALLED_APPS=[...'stapler',]Define Model classes as usualmodels.pyfromdjango.dbimportmodels# Create your models here.classBike(models.Model):name=models.CharField(max_length=10)frame_type=models.CharField(max_length=10,blank=True,null=True)classManufacturer(models.Model):name=models.CharField(max_length=10)chief=models.CharField(max_length=10)Also declare the ModelForm classes as usual, and in addition staple the ModelForms together by creating a new StapleForm class.
In the innerMetaclass set themodelformsattribute to a tuple of to stapleModelForms.forms.pyfromdjango.formsimportModelFormfromstapler.formsimportStaplerFormfrom.modelsimportBike,ManufacturerclassBikeModelForm(ModelForm):classMeta:model=Bikefields=['name','price']classManufacturerModelForm(ModelForm):classMeta:model=Manufacturerfields=['name','revenue']classStapledForm(StaplerForm):classMeta:modelforms=(BikeModelForm,ManufacturerModelForm)#auto_prefix = False, defaults is TrueTheStapledFormyields a form with four fields:bike__name, bike__price, manufacturer__name, manufacturer__revenue.
If you want to keep the original field names, you can set theauto_prefixattribute in the Meta class toFalse.
This may lead to unexpected behaviour when fieldnames between models clash.You can use theStapledFormin views.py to create a newBikeandManufacturerinstance in one go by callingform.save(). This wil return a dictionary with keys resembling the Model class names in lowercase with the_instancesuffix. The keys map to the newly created instances:views.pyfromdjango.views.generic.editimportFormViewfrom.formsimportStapledFormclassSomeView(FormView):template_name='example.html'form_class=StapledFormsuccess_url='/thanks/'defform_valid(self,form):# This method is called when valid form data has been POSTed.# It should return an HttpResponse.result=form.save()nw_bike=result['bike_instance']# the saved bike instancenw_manufacturer=result['manufacturer_instance']# the saved manufacturer instancereturnsuper().form_valid(form)You can also use the form to update existing instances by providing atupleof instances to the namedinstancesargument:from.modelsimportBike,Manufacturerfrom.formsimportStapledForm...# inside view functionbike=Bike.objects.first()manufacturer=Manufacturer.objects.first()form=StapledForm(data=request.POST,instances=(bike,manufacturer))ifform.is_valid():result=form.save()result# this is a dictionary with the updated instancesStapledForm provides thepre_saveandpost_savehooks. These methods
are called before and after the instances are saved when thesave()method is called:classStapledForm(StaplerForm):...defpre_save(self):print("did you get the memo?")defpost_save(self):print("have you seen my stapler?")Extra optionsIn addition to modelforms attribute, you can set a few options inMetaclass that alter the behaviour of theStaplerFormauto_prefix: defaults toTrue. This will append a prefix of the model class name to the appropriate fieldsrequired: A tuple of theModelFormclasses that are required. By default, allModelFormclasses defined inmodelformsare required.
If aModelFormis not in therequiredattribute,  it's errors are ignored andis_valid()can still returnTrue.
Callingsave()in this case will only save the instances ofModelFormsthat did validate. Instead of an instance,Noneis stored in the returned dictionaryLicenseThis project is licensed under the MIT License - see the LICENSE.md file for details