Django Model Formset Data Always Persists - django

I feel like I must be missing something obvious but I am having a problem where my model formsets insist on retaining their data after submission. I am creating a page that allows a user to create a project and then add an arbitrary amount of materials to that project. JavaScript is taking care of dynamically adding new instances of the formset as needed. The code works fine the first time through, after that it "remembers" previous data. It happens for the material formset but not for the regular model form above it.
I'm thinking it must have something to do with the way I am creating my model formset. When the page is requested the view seems to be passing back the formset bound to old data rather than an unbound set. I'm new to Django and am trying to teach myself so there are probably things at work I do not quite grasp yet. Below is the code for the view:
def addproject_page(request):
# Define the formset to use to add the materials
MaterialFormSet = modelformset_factory(Material, exclude = ('project',))
# Check to see if there is some POST data from an attempt to fill out the form
if request.method == 'POST':
# Create a form for the project and for the material and use a prefix to separate the POST data for the two
project_form = ProjectForm(request.POST, prefix='project')
# Instantiate the formset to display multiple materials when creating a project
material_formset = MaterialFormSet(request.POST, prefix='material')
# Check both forms with the validators and if both are good process the data
if project_form.is_valid() and material_formset.is_valid():
# Save the data for the newly created project
created_project = project_form.save()
# Tell each material to be associated with the above created project
instances = material_formset.save(commit=False)
for instance in instances:
instance.project = created_project
instance.save()
# After the new project and its materials are created, go back to the main project page
return HttpResponseRedirect('/members/projects/')
# If there is no post data, create and show the blank forms
else:
project_form = ProjectForm(prefix='project')
material_formset = MaterialFormSet(prefix='material')
return render(request, 'goaltracker/addproject.html', {
'project_form': project_form,
'material_formset': material_formset,
})
Edit to add in my template code too in case it helps:
{% extends "base.html" %}
{% block external %}
<script src="{{ static_path }}js/projects.js" type="text/javascript"></script>
{% endblock %}
{% block title %}: Add Project{% endblock %}
{% block content %}
<h1>Add a Project</h1>
<form id="new_project_form" method="post" action="">
{{ project_form.as_p }}
<!-- The management form must be rendered first when iterating manually -->
<div>{{ material_formset.management_form }}</div>
<!-- Show the initial blank form(s) before offering the option to add more via JavaScript -->
{% for material_form in material_formset.forms %}
<div>{{ material_form.as_p }}</div>
{% endfor %}
<input type="button" value="Add Material" id="add_material">
<input type="button" value="Remove Material" id="remove_material">
<input type="submit" value="add" />
</form>
{% endblock %}

I think you need to use a custom queryset, so that your formset is instantiated with an empty queryset. You need to specify the queryset in the POST and GET branches of your if statement.
if request.method == "POST":
...
material_formset = MaterialFormSet(request.POST, prefix='material', queryset=Material.objects.none())
...
else:
material_formset = MaterialFormSet(prefix='material', queryset=Material.objects.none())
At the moment, your formset is using the default queryset, which contains all objects in the model.

Answer of the question the old data is always persists in modelformset is here. https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#changing-the-queryset as it is given in the docs chenge the queryset by overriding the constructor of the basemodelformset
from django.forms.models import BaseModelFormSet
from myapp.models import Author
class CalendarFormset(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(CalendarFormset, self).__init__(*args, **kwargs)
self.queryset = Calendar.objects.none()
A same problem was discussed here
django modelformset_factory sustains the previously submitted data even after successfully created the objects

Related

Django Admin: How to dynamically set list_per_page

I have run into the case where I am managing the codebase for a project that utilizes the django-admin portion of a site. All of the functionality of the django admin exists in normal views as well, but for some reason the client prefers to work on the admin views as opposed to the function based views... Normally, adding in a dropdown and adjusting the pagination/filter would be easy in a function based view, but the only way I can see to modify this is with list_per_page
How do I add a dropdown to the admin page (preferably with the pagination buttons) and then how do I retrieve the results on the server side to alter the list_per_page value dynamically based on what the user has selected? Would adding a form to the template and retrieving a POST in the admin work?
Inspired by plum-0 answer, I've ended up with this :
import django.contrib.admin.views.main
class DynPaginationChangeList(django.contrib.admin.views.main.ChangeList):
def __init__(self, request, model, list_display, list_display_links,
list_filter, date_hierarchy, search_fields, list_select_related,
list_per_page, list_max_show_all, list_editable, model_admin, sortable_by):
page_param = request.GET.get('list_per_page', None)
if page_param is not None:
# Override list_per_page if present in URL
# Need to be before super call to be applied on filters
list_per_page = int(page_param)
super(DynPaginationChangeList, self).__init__(request, model, list_display, list_display_links,
list_filter, date_hierarchy, search_fields, list_select_related,
list_per_page, list_max_show_all, list_editable, model_admin, sortable_by)
def get_filters_params(self, params=None):
"""
Return all params except IGNORED_PARAMS and 'list_per_page'
"""
lookup_params = super(DynPaginationChangeList, self).get_filters_params(params)
if 'list_per_page' in lookup_params:
del lookup_params['list_per_page']
return lookup_params
class AdminDynPaginationMixin:
def get_changelist(self, request, **kwargs):
return DynPaginationChangeList
If you use the javascript code propose in the original answer you just need to use this Mixin in your AdminClass and voilà.
I personally override the pagination.html template like this :
{% load admin_list %}
{% load i18n %}
<p class="paginator">
{% if pagination_required %}
{% for i in page_range %}
{% paginator_number cl i %}
{% endfor %}
{% endif %}
{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %}
{% if show_all_url %}{% translate 'Show all' %}{% endif %}
{% with '5 10 25 50 100 250 500 1000' as list %} — {% translate 'Number of items per page' %}
<select>
{% if cl.list_per_page|slugify not in list.split %}
<option selected>{{ cl.list_per_page }}</option>
{% endif %}
{% for i in list.split %}
<option value="{{ i }}" {% if cl.list_per_page|slugify == i %}selected{% else %}onclick="var p = new URLSearchParams(location.search);p.set('list_per_page', '{{ i }}');window.location.search = p.toString();"{% endif %}>{{ i }}</option>
{% endfor %}
</select>
{% endwith %}
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %}
</p>
DISCLAIMER:
I'm answering my own question, so I am not sure if this is best practice or the best way to achieve this. However all of my searching yielded 0 results for this so I have decided to share it in case someone else needs this functionality. If anyone has any better ways to achieve this, or knows that I am doing something not secure please let me know!
I would try appending a query parameter to the URL then retrieving that parameter through the request on the server side to set the list_per_page. For adding the dropdown as well as the parameter, modifying the Media class for a particular admin result to include some extra javascript should allow you to create the dropdown as well as append the query parameter. We will need to remove this parameter from the request.GET otherwise we will run into an issue inside of get_queryset() since the parameter does not match a field on the model. For that we will need to override the changelist_view() method inside admin.py
admin_paginator_dropdown.js
window.addEventListener('load', function() {
(function($) {
// Jquery should be loaded now
// Table paginator has class paginator. We want to append to this
var paginator = $(".paginator");
var list_per_page = $("<select id='list_per_page_selector'><option value=\"50\">50</option><option value=\"100\" selected>100</option><option value=\"150\">150</option><option value=\"200\">200</option><option value=\"250\">250</option></select>")
var url = new URL(window.location);
// Retrieve the current value for updating the selected dropdown on page refresh
var initial_list_per_page = url.searchParams.get("list_per_page")
paginator.append(list_per_page)
if(initial_list_per_page === null) {
// No choice has been made, set dropdown to default value
$("#list_per_page_selector").val("100")
}
else{
// User has a query parameter with a selection. Update the selected accordingly
$("#list_per_page_selector").val(initial_list_per_page)
}
$("#list_per_page_selector").on("change", function(event) {
// Add the list_per_page parameter to the url to be used in admin.py
url.searchParams.set("list_per_page", event.target.value);
//Take us to the new page.
window.location.href = url.href;
});
})(django.jQuery);
});
admin.py
class someModelAdmin(admin.ModelAdmin)
class Media:
js = ("js/admin_paginator_dropdown.js",)
def changelist_view(self, request, extra_context=None):
# Copy the request.GET so we can modify it (no longer immutable querydict)
request.GET = request.GET.copy()
# Pop the custom non-model parameter off the request (Comes out as an array?)
# Force it to int
page_param = int(request.GET.pop('list_per_page', [100])[0])
# Dynamically set the django admin list size based on query parameter.
self.list_per_page = page_param
return super(someModelAdmin, self).changelist_view(request, extra_context)

Initial Values in Django model form

I am having a difficult time understanding the Django docs on this one. I have also come across some other threads with the same question, but I cannot seem to get the suggested answers to work for me. I think it is because I am posting text, and it is not considered "clean" data?
I want to autofill two form fields, then when the user hits the submit button, it saves. But for some reason only the boolean value is working, not the text value. Any ideas?
You will also see the fields are hidden from my template. When I show these fields, the initial values are set correctly as I expected, but when I hit submit, only the boolean is saved to database correctly.
EDIT
It works fine when I don't hide the form fields using {{ form }} in my template.
It does not work when I hide the fields using {{ form.field.as_hidden }}
Boolean field is accepted, but the text field is not.
I am trying to autofill this field with a text value, hide it, and submit this value when the submit button is pressed...
views.py
class BuildStopView(LoginRequiredMixin,UpdateView):
model = Build
form_class = StopBuild
template_name = 'build_stop.html'
login_url = 'login'
forms.py
class StopBuild(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(StopBuild, self).__init__(*args, **kwargs)
self.initial['buildEndType'] = 'manuallyStopped'
self.initial['buildActive'] = False
class Meta:
model = Build
fields = ['buildEndType','buildActive']
(template) stop_build.html
{% extends 'base.html' %}
{% block body %}
<style>
div.a {
text-align: center;
}
</style>
<div class = "a">
<h3>Are you sure you want to stop this build manually?</h3>
</div>
<form action="" method="post">{% csrf_token %}
{{ form.field.as_hidden }}
<button class="btn btn-danger ml-2" type="submit">Stop Build Manually</button>
</form>
{% endblock %}
form.field.as_hidden does not output the fields as hidden, in fact it does not do anything at all because you don't have a field called field in your form. You need to refer to the actual fields:
{{ form.buildEndType.as_hidden }}
{{ form.buildActive.as_hidden }}
However, if you want these to always be shown as hidden, you should probably do it in your form definition, by declaring them with HiddenInput widgets.

Create Django dropdown form which allows users to select a model to submit

Extreme noob here. I have been trying to create a simple form in Django where the user can select from the models that are present in the database and then click submit (whereupon I will then return a list of objects).
However, I am getting the following error: 'ModelBase' object is not iterable
Note all I am trying to achieve so far is to actually render the form.
Here is the code:
HTML
<form action="." method="">
{% csrf_token %}
{% for field in output %}
{{ output.as_p }}
{% endfor %}
<input type="submit" value="Save" />
</form>
Forms.py
from projectdb.models import *
class TablesForm(forms.Form):
models = models.get_models()
select = forms.ChoiceField(choices=models)
Views.py
def output_form(request):
form = TablesForm()
return render(request, 'projectdb/output.html', {'output': form})
Much obliged for any help!
Some object is not iterable errors will be fixed by adding .all() where you're doing the foreach loop. If the loop is in the template, try .all only
In a view:
for something in array.all():
In a template:
{% for field in output.all %}
And everytime I do a form in Django my method on the form is empty method="". This let's you return to the same view and process your data there. If you have errors on the form you can redirect to the same form but with the pre-saved data that the user has wrote.
Hope it helps.

Retrieving image in django, update/replace image in django

Hi I have saved an image using my django project with the help of models
as
Image = models.ImageField(upload_to="images/profileimages/")
name = models.CharFiled(max_length=20)
#rest of the fileds.
Once I have saved this information I want to change/update it. For this i have used a view as
def Information_change(request):
instance = get_object_or_404(information,pk=request.user.id)
if request.method == 'POST':
iform = informationForm(instance=instance, data=request.POST, files=request.FILES)
if iform.is_valid():
instance = iform.save()
return HttpResponseRedirect('/')
else:
iform = informationForm(instance=instance)
return render_to_response('registration/information_change.html',{'iform':iform}, RequestContext(request))
In my templates am getting all the information in realated fields like name fields contains my name and all the charfields are showing the information, but the image fileds did not show the image/path or any other thing. Rest of the fields can be changed and i am able to edit my name fileds but unable to replace/change the image using this code. how can I fix this. Any help would be greatly appreciated.
My .html file contains
{% block content %}
{% if iform.errors %}
<p style="color: red;">
Please correct the error{{ iform.errors|pluralize }} below.
</p>
{% endif %}
<form method="post" action=".", enctype="multipart/form-data>
{{ iform.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
First of all do what #Rohan has suggested you. In your view you have a bug, what if there was a case when your form is not valid? Then your view will not return an HTTP response because you have not handled that. Just remove the else of if request.Method == 'POST' and indent back following two lines:
iform = informationForm(instance=instance)
return render_to_response('registration/information_change.html',{'iform':iform}, RequestContext(request))
This will fix the bug which I mention. May be that will show some form errors which is related to image field.
I see 2 issues in your form tag:
comma ',' after actions attribute
No closing quote for enctype attribute value

Django form validation with GET

I have a form which allows users to select several parameters to allow faceted querying of data. As there is no data entry going on here I want the form to post to GET and I have a another view with a different template which displays the results.
I want the form to validate as normal so that if a required field is not completed the corresponding errors are displayed. At the moment my process looks like this (simplified):
my search view:
def search(request):
...
context['form'] = GraphForm()
...
return render(request, 'search.html', context)
my results view:
def results(request):
if 'submit' in request.GET:
# process GET variables as query
...
return render(request, 'results.html', context)
my search.html template:
<form action="{% url results %}" method="get">{% csrf_token %}
{% for field in form %}
<div class="field_wrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
<input type="submit" name="submit" value="Query" />
</form>
Given that the form submits to another url with separate view code, what is the best way to go about validating (highlighting errors), and ensuring I have my GET data?
Any help much appreciated.
This might be a little late, but I think the following will work while maintaining similarity to 'POST' workflow:
Instead of having two different views for searching and displaying results, just have one view. The normal codepath described for post forms can then be followed. Instead of using request.method == 'POST' to detect form submission, we instead use 'submit' in request.GET. If using javascript to submit the form, make sure that 'submit' is included in the GET data or use a hidden field to detect form submission.
views.py
def search(request):
context_dict = {}
if 'submit' in request.GET:
form = GraphForm(request.GET)
if form.is_valid():
#do search and add results to context
#If you don't want to use a single view,
# you would redirect to results view here.
results = get_results(**form.cleaned_date)
context_dict['results'] = results
else:
form = GraphForm()
context_dict['form'] = form
return render(request, 'search.html', context_dict)
search.html
<form action="{% url 'search' %}" method="get">
{{form}}
<input type="submit" name="submit" value="Query" />
</form>
{% if results %}
{% include 'results.html' %}
{% endif %}
You should be able to pass request.GET just like request.POST to the form. The form simply accepts a data dictionary. It doesn't care where that comes from. Have you already tried that?
Use JavaScript/jQuery for form validation. All you need to do is add an id to form, and in the corresponding Javascript, do something like
document.getElementById("#form").onsubmit = checkForm();
or using jQuery
$("#form").submit(checkForm);
where checkForm() returns true upon successful validation, and false otherwise. (Note that, if you do not return false, form submission will continue as usual.)
Which fields you check for/validate can also change by using Django's templates.