'How do I reuse a ModelForm for a through model with an additional field?
I have a Model Example and corresponding ModelForm ExampleModelForm that I use in ModelFormset
ExampleModelFormset = modelformset_factory(Example, form=ExampleModelForm)
I have a many-to-many through Model with an additional property.
class ExampleThrough(models.Model):
batch = models.ForeignKey(Batch, on_delete=models.CASCADE)
example = models.ForeignKey(Example, on_delete=models.CASCADE)
property = models.FloatField()
Is there a way to reuse the ExampleModelForm to create a ExampleThroughModelForm that I can also use in a ExampleThroughModelFormset? I want to have all of the same fields as the ExampleModelForm plus the one new property.
I don't think I can inherit from the ExampleModelForm:
class ExampleThroughModelForm(ExampleModelForm):
class Meta:
model = ExampleThrough
fields = '__all__'
because the documentation has this note:
Normal Python name resolution rules apply. If you have multiple base classes that declare a Meta inner class, only the first one will be used. This means the child’s Meta, if it exists, otherwise the Meta of the first parent, etc.
I can't apply the ModelForm and Meta inheritance used in Django ModelForm inheritance and Meta inheritance because my ExampleThrough model does not inherit from Example.
Solution 1:[1]
What I usually do in situations like this is to create the ExampleTroughModelForm and make the ExampleModelForm its property. Then in HTML I render both of them without form tags and save them together. Remember to override save(), clean(), and is_valid() methods so the ExampleModelForm gets saved and validated properly.
Suggestion code for the ExampleThroughModelForm (not tested):
class ExampleThroughModelForm(ModelForm):
class Meta:
model = ExampleThrough
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add custom prefix to avoid name clashes in case of same
# attribute name in ExampleModelForm and ExampleThroughModelForm
self.example_form = ExampleModelForm(data=kwargs.get('data'), instance=getattr(self.instance, 'example', None), prefix='example')
self.helper = FormHelper()
self.helper.form_tags = False
def save(self, **kwargs):
# The ExampleModelForm needs to be saved first in case you are just
# creating it. We can't have the through Model without this
saved_example_instance = self.example_form.save()
# Set the Example model on the ExampleThrough instance before saving
self.instance.example = saved_example_instance
# Save the ExampleThrough model
saved_example_through_instance = super().save(**kwargs)
return saved_example_through_instance
def clean(self):
# Here we also hook the calls to the clean method of the example form
# This is not necessary but if you have a custom clean method on the
# ExampleModelForm, you will want to call it when this form is cleaned
self.example_form.clean()
super().clean()
return self.cleaned_data
def is_valid(self)
# This is necessary because is_valid method sets the cleaned_data attribute
return self.example_form.is_valid() and super().is_valid()
Remember that in the ExampleForm you also need to remove the form tags.
In HTML with crispy forms used for rendering, it would look something like this:
<form id="example-through-form" method="post" actions="{% url 'save-example-through' %}">
{% crispy form %}
{% crispy form.example_form %}
<!-- Submit input buttons omitted for brevity -->
</form>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
