I am having trouble submitting a modelformset in my view. For some reason, the formset is not meeting is_valid criteria, whatever I set the actual data to.
Here is the relevant part of the view:
def admin_tools(request):
ElectionFormSet = modelformset_factory(Election, exclude=('Complete',), formset=BaseElectionFormSet, extra=0)
if request.method == 'POST':
if 'edit_elections' in request.POST:
election_formset = ElectionFormSet(request.POST)
# print(formset)
if election_formset.is_valid():
election_formset.save()
messages.add_message(request, messages.SUCCESS, 'Election settings saved')
return redirect(reverse('elections:home'))
else:
messages.add_message(request, messages.ERROR, 'Problem')
return redirect(reverse('elections:home'))
else:
election_formset = ElectionFormSet()
return render(request, 'admin/admin_tools.html',{
'formset': election_formset,
})
Every time I submit the formset I get the problem message indicating the formset is not valid and I don't know why.
Here is form tag in the template:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{% csrf_token %}
<div class='card'>
<div class='card-body w-75 mx-auto'>
<div class='row'>
<div class='col-6 text-center'>
<p>Name<br>{{form.Name}}</p>
</div>
<div class='col-6 text-center'>
<p>Videos<br>{{form.FlipGrid}}</p>
</div>
</div>
<div class='row'>
<div class='col-12 text-center'>
<p>Description<br>{{form.Description}}</p>
</div>
</div>
<div class='row'>
<div class='col-6 text-center'>
<p>Allow registration: {{form.CandidateReg}}</p>
</div>
<div class='col-6 text-center'>
<p>Allow voting: {{form.VotingOpen}}</p>
</div>
</div><p>{{form.id}}</p>
</div>
</div>
{% endfor %}
<div class='text-center'>
<br><button type="submit" class='btn btn-outline-dark' name='edit_elections'>Save</button>
</div>
</form>
I'm not sure what the error would be here. My thought is that maybe each form should be submitted individually and not as a whole formset? I'm also not sure my csrf token is in the right place.
How do I get my formset to submit correctly?
When the formset is invalid, you typically don't redirect. That allows you to re-display the formset along with errors.
In your case, you are rendering the formset manually, so you are not displaying any errors. You could start with the simplest possible template.
<form method="post" action="">
<table>
{{ formset }}
</table>
</form>
Once you're happy the view works, you can customize the template more. See the docs on using formsets in templates for more info.
If you really want to redirect when the formset is invalid, you can check check formset.errors in your view to debug the problem.
Related
How to make it such that if there are errors in the form, all the data I have keyed into the field remains and the error shows for me to edit what I need to edit.
Because it is very user-unfriendly if people press submit, and everything they have previously typed has to be retyped again due to an error that caused them to need to submit the form again.
just like when we post a stackoverflow question, if there are errors in our question eg time limit, whatever we have typed previously remains
Let me know if you require more code.
html
<form class="create-form" method="post" enctype="multipart/form-data">{% csrf_token %}
<div class="form-group">
<label for="id_title">Title</label>
<input class="form-control" type="text" name="title" id="id_title" placeholder="Title" required autofocus>
</div>
<button class="submit-button btn btn-lg btn-primary btn-block" type="submit">Submit</button>
</form>
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
views.py
def create_blog_view(request):
context = {}
user = request.user
if request.method == 'POST':
form = CreateBlogPostForm(request.POST or None, request.FILES or None)
if form.is_valid():
obj.save()
return redirect('HomeFeed:main')
else:
context['form'] = form
return render(request, "HomeFeed/create_blog.html", context)
You are rendering the fields manually by writing the tags. When you render using the form instance Django automatically sets the value attributes with the previous values. You can use {{ form.as_table }}, {{ form.as_p }}, {{ form.as_ul }}. You can also render fields individually using {{ form.title }} where title is the field name.
For my Django project, I am rendering the model formset election_formset = modelformset_factory(Election, exclude=('Complete',), formset=BaseElectionFormSet) in my template:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
<div class='card'>
<div class='card-body w-75 mx-auto'>
<div class='row'>
<div class='col-6 text-center'>
<p>Name<br>{{form.Name}}</p>
</div>
<div class='col-6 text-center'>
<p>Videos<br>{{form.FlipGrid}}</p>
</div>
</div>
<div class='row'>
<div class='col-12 text-center'>
<p>Description<br>{{form.Description}}</p>
</div>
</div>
<div class='row'>
<div class='col-6 text-center'>
<p>Allow registration: {{form.CandidateReg}}</p>
</div>
<div class='col-6 text-center'>
<p>Allow voting: {{form.VotingOpen}}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</form>
When the formset renders, an extra, blank form is shown at the end of the forms. I only want forms to show that are instances of existing records. Why is there an extra blank formset and how can I prevent it?
Try this
election_formset = modelformset_factory(
Election,
exclude=('Complete',),
formset=BaseElectionFormSet,
extra=0
)
The extra keyword defaults to 1 otherwise (see docs):
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
formset=BaseModelFormSet, extra=1, can_delete=False,
can_order=False, max_num=None, fields=None, exclude=None,
widgets=None, validate_max=False, localized_fields=None,
labels=None, help_texts=None, error_messages=None,
min_num=None, validate_min=False, field_classes=None):
Try to use max_num parameter to limit exta if there are some initial data in formset:
election_formset = modelformset_factory(Election, exclude=('Complete',), formset=BaseElectionFormSet, max_num=1)
From the docs:
If the number of items in the initial data exceeds max_num, all initial data forms will be displayed regardless of the value of max_num and no extra forms will be displayed.
When trying to submit my ModelFormSet I am getting a MultiValueDictKeyError. The error message is not very descriptive so I'm not sure why the error is being thrown.
Here is the view:
def admin_tools(request):
ElectionFormSet = modelformset_factory(Election, exclude=('Complete',), formset=BaseElectionFormSet, extra=0)
if request.method == 'POST':
if 'new_election' in request.POST:
new_election = NewElectionForm(request.POST)
if new_election.is_valid():
election = new_election.save(commit=False)
election.save()
messages.add_message(request, messages.SUCCESS, 'Election created')
return redirect(reverse('elections:home'))
elif 'edit_elections' in request.POST:
formset = ElectionFormSet(request.POST)
if formset.is_valid():
formset.save()
messages.add_message(request, messages.SUCCESS, 'Election settings saved')
return redirect(reverse('elections:home'))
else:
new_election_form = NewElectionForm()
formset = ElectionFormSet()
return render(request, 'admin/admin_tools.html',{
'new_election': new_election_form,
'formset': formset,
})
Here is the relevant section of the template:
<div class="card-body">
<h4>Toggle election settings:</h4>
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{% csrf_token %}
<div class='card'>
<div class='card-body w-75 mx-auto'>
<div class='row'>
<div class='col-6 text-center'>
<p>Name<br>{{form.Name}}</p>
</div>
<div class='col-6 text-center'>
<p>Videos<br>{{form.FlipGrid}}</p>
</div>
</div>
<div class='row'>
<div class='col-12 text-center'>
<p>Description<br>{{form.Description}}</p>
</div>
</div>
<div class='row'>
<div class='col-6 text-center'>
<p>Allow registration: {{form.CandidateReg}}</p>
</div>
<div class='col-6 text-center'>
<p>Allow voting: {{form.VotingOpen}}</p>
</div>
</div>
</div>
</div>
{% endfor %}
<div class='text-center'>
<br><button type="submit" class='btn btn-outline-dark' name='edit_elections'>Save</button>
</div>
</form>
</div>
The error is raise MultiValueDictKeyError(repr(key))
django.utils.datastructures.MultiValueDictKeyError: "'form-0-id'"
and it was flagged on the line: if formset.is_valid(): of the view.
How do I resolve this error and get the formset to correctly submit and update the data in the model?
I have a list of multiple objects from my database (named "plp's"), arranged in a table. Next to each "plp" element I have a button "Edit" to modify that particular entry.
Next, I redirect the user to a new url, where I pass the id of that "plp", and show the form to edit it, with a "save" button.
After pressing the "save", which is request.POST, I want to redirect the user back to the first url, with the list of all the "plp" objects in one list. That means to the site, where he first pressed "Edit".
Can I somehow save the url of where the "Edit" was clicked, and pass it to my views.py?
Thank you
listdns.html:
<td>
Uredi
</td>
urls.py:
rl(r'^(?P<plp_id>\d+)/uredi$', plp_list_uredi,name="plpuredi")
views.py:
def plp_list_uredi(request, plp_id=None):
moj_plp=PLPPostavka.objects.get(id=plp_id)
form=PLPPostavkaForm(request.POST or None,request=request,dns=moj_plp.dns, instance=moj_plp)
context ={
'plp':moj_plp,
'form':form,
}
if request.POST:
if form.is_valid():
plp = form.save()
return redirect(request.path)
return render(request, "plp_pos/uredi.html",context)
uredi.html
<form action="" method="POST">
{% csrf_token %}
<div class="box">
<div class="box-header">
<h4 class="box-title">
Urejanje PLP Postavke
</h4>
</div>
<div class="box-body">
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="col-md-2 control-label detail">{{ field.label }}</label>
<div class="col-md-10">
{% if field|field_type == "datefield" %}
{% render_field field class+="form-control dateinput" %}
{% else %}
{% render_field field class+="form-control" %}
{% endif %}
</div>
</div>
{% endfor %}
</div>
<div class="box-footer">
<div class="box-tools pull-right">
<input type="submit" value="Shrani" class="btn btn-primary" />
</div>
</div>
Don't you only have 1 page to edit all the elements? Then you could perhaps hardcode the link e.g.
return HttpResponseRedirect(my_edit_url)
If this doesn't work and you need to go 2 pages back take a look at this post:
How to redirect to previous page in Django after POST request
I've been racking my brain over this problem for the past few days and I've read numerous other questions regarding the same error but they all seem to be different cases (not including management form, forgetting to update TOTAL_FORMS, etc etc) and do not resolve my problem. I have a page which could contain multiple formsets in a single HTML form. When I am posting the data back to the server, it fails on the is_valid() check for the formsets with the error in the title. I am new to web development and Django so please forgive me if I made a silly mistake or am taking an approach that will not work.
def purchase(request):
return generic_form_view(request, "inventory_tracking/add_purchases.html",
"Successfully added purchases for %s.",
PurchaseForm,
[formset_factory(PurchaseForm.LiquorForm),
formset_factory(PurchaseForm.NonLiquorForm)])
def generic_form_view(request, template, success_message, ParentForm, FormSets):
if request.method == 'POST':
request_params = copy(request.POST)
parent_form = ParentForm(request_params)
formsets = list(map(lambda form_set: form_set(request_params), FormSets))
if parent_form.is_valid(): # This works.
for formset in formsets:
if formset.is_valid(): # Fails here.
Here is a snippet from my template:
<form action="{% block form_action %}{% endblock %}" method="post">
{% csrf_token %}
<div class="row">
<div class="row">
<div class=" well well-lg">
<div class="row">
{{ parent_form.management_form }}
{% for field in parent_form %}
<div class="col-lg-6">
<div class="form-group">
<label class="control-label">{{ field.label }}</label>
{{ field }}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="row">
{% for formset in formsets %}
{{ formset.management_form }}
<div class="row">
<div class="well well-lg">
{% for form in formset %}
<div id="{{ form.prefix }}" class="row">
...
I've been trying to debug this and I noticed something a little interesting but since I am not too familiar with Django it could be a red herring. In the POST, I see the management_form data for the formsets I am creating but I do not see the management_form data for the parent formset (in this case PurchaseForm). However the parent_form is passing validation and the other formsets are not.
I expected this to be a silly problem and I turned about to be right! When my generic_form_view method creates the formsets on the GET request I was adding a prefix like the documentation mentioned but I was not adding a prefix when creating the formsets on the POST.