Reputation: 11
I have a Django app with django-extra-views to create and maintain a pair of related models. The set up is as follows:
# models.py
class ModelA(models.Model):
# fields
def get_absolute_url(self):
return reverse('model_a:detail', kwargs={'pk': self.id})
class ModelB(models.Model):
model_a = models.OneToOneField(ModelA, on_delete=models.CASCADE)
# other fields
I have two corresponding views and forms:
# views.py
class ModelBView(InlineFormSetFactory):
model = ModelB
form_class = ModelBForm
prefix = 'model_b'
class ModelACreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateWithInlinesView):
model = ModelA
inlines = [ModelBView, ]
permission_required = ('add_model_a')
template_name = 'apps/model_a/create.html'
form = ModelAForm
fields = '__all__'
def get_success_url(self):
return self.object.get_absolute_url()
class ModelAUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateWithInlinesView):
model = ModelA
inlines = [ModelBView, ]
permission_required = ('change_model_a')
template_name = 'apps/model_a/update.html'
form = ModelAForm
fields = '__all__'
def get_success_url(self):
return self.object.get_absolute_url()
# forms.py
class ModelAForm(forms.ModelForm):
class Meta:
model = ModelA
fields = [
# some fields
]
initial = [...]
class ModelBForm(forms.ModelForm):
class Meta:
model = ModelB
fields = [
# some fields
]
The trouble happens in the template files. The CreateWithInlinesView works, while UpdateWithInlinesView does not, when paired with crispy_forms. Following is the code that DOES NOT work. When I press the 'Save' button, it brings me back to the update page and when I check the details page, things are unchanged:
<form class="form-horizontal" method="POST">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-8 mb-0">
{{ form.name|as_crispy_field }}
</div>
<div class="form-group col-md-4 mb-0">
{{ form.phone|as_crispy_field }}
</div>
</div>
{% for formset in inlines %} {% for addr in formset %}
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ addr.field_1|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ addr.field_2|as_crispy_field }}
</div>
</div>
{% endfor %} {{ formset.management_form }} {% endfor %}
<div class="control-group text-right">
<div class="controls">
<button type="submit" class="btn btn-default btn-person"><i class="fas fa-save"></i> Save</button>
</div>
</div>
</form>
There is a version that DOES WORK. But it involves me not using crispy_forms and is aesthically unappealing. When I press the 'Save' button, it updates and redirects to the "details" page. Below is the code (the difference is the bottom half):
<form class="form-horizontal" method="POST">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-8 mb-0">
{{ form.name|as_crispy_field }}
</div>
<div class="form-group col-md-4 mb-0">
{{ form.phone|as_crispy_field }}
</div>
</div>
{% for formset in inlines %} {{ formset }} {% endfor %}
<div class="control-group text-right">
<div class="controls">
<button type="submit" class="btn btn-default btn-person"><i class="fas fa-save"></i> Save</button>
</div>
</div>
</form>
I don't know how to proceed on this one and would love someone's help. Thank you.
Upvotes: 1
Views: 857
Reputation: 11
This is basically happening because you didn't include id
(hidden field) in the formset.
To check error you can write the below method in UpdateWithInlinesView.
def forms_invalid(self, form, inlines):
for formset in inlines:
for errors in formset.errors:
for _, error in errors.items():
print(error[0])
return self.render_to_response(
self.get_context_data(request=self.request, form=form, inlines=inlines))
This will print
id: 'This field is required'
To get rid of this, you have to include hidden fields as follows:
{% for formset in inlines %}
{{ formset.management_form|crispy }}
{{ formset|as_crispy_errors }}
{% for addr in formset %}
{% for hidden in addr.hidden_fields %}
{{ hidden }}
{% endfor %}
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ addr.field_1|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ addr.field_2|as_crispy_field }}
</div>
</div>
{% endfor %}
{% endfor %}
Or you can try below one also. In this we are directly passing the id field (hidden by default). I am using this one
{% for formset in inlines %}
{{ formset.management_form|crispy }}
{{ formset|as_crispy_errors }}
{% for addr in formset %}
{{ addr.id }}
<div class="form-row">
<div class="form-group col-md-6 mb-0">
{{ addr.field_1|as_crispy_field }}
</div>
<div class="form-group col-md-6 mb-0">
{{ addr.field_2|as_crispy_field }}
</div>
</div>
{% endfor %}
{% endfor %}
Upvotes: 1