Using extra and max_num in a Django formset - django

I have a formset that has no model associated with it and I want to be able to add a form to the formset once all existing forms are valid, so reading the docs, I found: "If the value of max_num is greater than the number of existing objects, up to extra additional blank forms will be added to the formset, so long as the total number of forms does not exceed max_num."(https://docs.djangoproject.com/en/dev/topics/forms/formsets/#limiting-the-maximum-number-of-forms):
So I did this:
FormSet = formset_factory(SomeForm, extra=2, max_num=10)
if request.method == 'POST':
formset = FormSet(data=request.POST)
else:
formset = FormSet()
and this:
<form action="" method="POST">
{{ formset }}
<input type="submit" value="Next" />
</form>
expecting to see 2 empty forms, where I would get extra forms if I filled out one (or 2) forms and pressed "Next". However, only 2 forms are ever shown in the template even if I have 1 or 2 valid forms.
How is this supposed to work? Am I misinterpreting the docs? Is my code wrong?

I found a partial answer to my question: I got it to work, but I find the solution not very Django-like. I would expect this stuff to happen automatically, without the cruft below.
Anyway, I changed my view thus:
if request.method == 'POST':
formset = FormSet(data=request.POST)
if formset.is_valid():
clean_data = formset.cleaned_data
if not any(not(len(f)) for f in clean_data):
formset = FormSet(initial=clean_data)
else:
formset = FormSet()
So I re-instantiated the formset using cleaned_data from the POST data and added some stuff to prevent an extra form popping up if you press "Next" while there is still an empty form.
It works, but I really don't think this should be the way to do this.

Related

Bind dynamic choices to ModelForm in Django

I'm trying to bind a dynamic list of choices to a ModelForm. The form is rendered correctly. However, when using the form with a POST Request, I get an empty form back. My goal is to save that form into the database (form.save()). Any help would be much appreciated.
Model
I'm using a multiple choice select field ( https://github.com/goinnn/django-multiselectfield )
from django.db import models
from multiselectfield import MultiSelectField
class VizInfoModel(models.Model):
tog = MultiSelectField()
vis = MultiSelectField()
Forms
class VizInfoForm(forms.ModelForm):
class Meta:
model = VizInfoModel
fields = '__all__'
def __init__(self,choice,*args,**kwargs):
super(VizInfoForm, self).__init__(*args,**kwargs)
self.fields['tog'].choices = choice
self.fields['vis'].choices = choice
View
Choices are passed from the view when instantiating the form.
def viz_details(request):
options = []
headers = request.session['headers']
for header in headers :
options.append((header, header))
if request.method == 'POST':
form = VizInfoForm(options, request.POST)
#doesnt' get into the if statement since form is empty!
#choices are not bounded to the model although the form is perfectly rendered
if form.is_valid():
form.save()
return HttpResponseRedirect('/upload')
else:
#this works just fine
form = VizInfoForm(options)
return render(request, 'uploads/details.html', {'form': form})
Template
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>Choose variables to toggle between</p>
{{ form.tog }}
<br></br>
<p>Choose variable to be visualized</p>
{{ form.vis }}
<br></br>
<button type="submit">Submit</button>
</form>
You're saying Django doesn't get into your if request.method == 'POST' block.
This tells us that you're not sending your request through the POST method. Your template probably has an error in it, maybe you haven't specified the method on your form, or you made your button to just be a link instead of a submit ?
Show your template so we can say more, unless this was enough to solve your question !

add field to a form if necessary django

I am trying to make a form which has an option of adding multiple objects at a time. I want it to have a button "add another" - when clicked a new form field would appear for adding additional object. If there was an previous not submitted input I want the form to keep it. Is it possible to use templates tags for this(i.e. django template tags and not javascript)?
Your form would have to be constructed based on some variables passed to it from your POST (or blindly check for attributes). The form itself is constructed every time the view is reloaded, errors or not, so the HTML needs to contain information about how many fields there are to construct the correct amount of fields for validation.
I'd look at this problem the way FormSets work: there is a hidden field that contains the number of forms active, and each form name is prepended with the form index. In fact, you could make a one field FormSet.
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#formsets
Here's one made from scratch - it should give you some ideas. It also answers your questions about passing arguments to __init__ - you just pass arguments to an objects constructor: MyForm('arg1', 'arg2', kwarg1='keyword arg')
### Views
class MyForm(forms.Form):
original_field = forms.CharField()
extra_field_count = forms.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
extra_fields = kwargs.pop('extra', 0)
super(MyForm, self).__init__(*args, **kwargs)
self.fields['extra_field_count'].initial = extra_fields
for index in range(int(extra_fields)):
# generate extra fields in the number specified via extra_fields
self.fields['extra_field_{index}'.format(index=index)] = \
forms.CharField()
def myview(request):
if request.method == 'POST':
form = MyForm(request.POST, extra=request.POST.get('extra_field_count')
if form.is_valid():
print "valid!"
else:
form = MyForm()
return render(request, "template", { 'form': form })
### HTML
form_count = Number($("[name=extra_field_count]").val());
// get extra form count so we know what index to use for the next item.
$("#add-another").click(function() {
form_count ++;
element = $('<input type="text"/>');
element.attr('name', 'extra_field_' + form_count);
$("#forms").append(element);
// build element and append it to our forms container
$("[name=extra_field_count]").val(form_count);
// increment form count so our view knows to populate
// that many fields for validation
})
<form>
<div id="forms">
{{ form.as_p }}
</div>
<button id="add-another">add another</button>
<input type="submit" />
</form>

Render Django formset as array

I have a model and i need to create form with multiple instances in it. To be more specific: i need to render my ModelForm inside regular form with square brackets next to it's fields names. Something like this in magicworld:
forms.py
class ManForm(ModelForm):
class Meta:
model = Man
fields = ['name', 'age']
class PeopleForm(forms.Form):
# modelless form
people = ??? # array of ManForm instances or something
form.html
<form action="/people/create/">
{{ form }}
</form>
output
<form action="/people/create/">
<input type="text" name="name[0]"/>
<input type="text" name="age[0]"/>
</form>
To tell you the truth, i don't know how to approach this problem at all. I tried modelformset_factory, but all i've got is <input type="text" name="form-0-name"/>
As discussed in the comments, you need a formset.
def create_people(request):
PeopleFormSet = modelformset_factory(Man, form=ManForm)
if request.method == 'POST':
formset = PeopleFormSet(request.POST)
if formset.is_valid():
for form in formset:
... do something with individual form
else:
formset = PeopleFormSet()
return render(request, template_name, {'formset': formset}
For using formsets in function based views see #Daniel Roseman 's answer or read up here.
For class based views there is no built in generic view for this. According to this ticket they decided to let third-party-packages handle that. You can use django-extra-views for that.

django Formset won't save

I have the below view for a Formset, but when I save the form it doesn't save changes to the database?
def schedule(request, year, month, day):
EntriesFormset = modelformset_factory(Entry, extra = 1, exclude=("creator", "date"),can_delete=True)
if request.method == 'POST':
formset = EntriesFormset(request.POST)
if formset.is_valid():
# add current user and date to each entry & save
entries = formset.save(commit=False)
for entry in entries:
entry.creator = request.user
entry.date = date(int(year), int(month), int(day))
entry.save()
return HttpResponseRedirect(reverse("Pipettes.views.month", args=(year, month)))
else:
# display formset for existing enties and one extra form
formset = EntriesFormset(queryset=Entry.objects.filter(date__year=year,date__month=month, creator=request.user))
return render_to_response("Scheduler.html", add_csrf(request, entries=formset, year=year,
month=month, day=day))
I suspect that the formset is invalid but instead of displaying of the formset with errors you returning the redirect. You should move the redirecting to one level right, into the if statement:
if formset.is_valid():
...
return HttpResponseRedirect(reverse("Pipettes.views.month", args=(year, month)))
UPDATE: If your formset is not validated but you don't see any errors on the page then your formset rendering may be invalid. For testing purposes try to use the simplest possible template:
<table>
{{ formset }}
</table>
Also note that with formset.save(commit=False) deleted objects are not deleted automatically. See the side note in this chapter of the docs.

Validate/clean a FileField on a non-model form in Django?

I'm ultimately trying to validate a FileField by extension type. But I'm having trouble even getting the clean method for this field to pickup the POSTed value.
from django.forms.forms import Form
from django.forms.fields import FileField
from django.forms.util import ValidationError
class TestForm(Form):
file = FileField(required=False)
def clean_file(self):
value = self.cleaned_data["file"]
print "clean_file value: %s" % value
return None
#localhost
def test_forms(request):
form = TestForm()
if request.method == "POST":
form = TestForm(request.POST)
if form.is_valid():
print "form is valid"
return render_to_response("test/form.html", RequestContext(request, locals()))
When I run the code, I'm getting the following output:
clean_file value: None
form is valid
In other words, the clean_file method is not able to get the file data. Likewise, if it returns None, the form is still valid.
Here is my form html:
<form enctype="multipart/form-data" method="post" action="#">
<input type="file" id="id_file" name="file">
<input type="submit" value="Save">
</form>
I have seen a couple snippets with solutions for this problem, but I cannot get them to work with a non-model form. They both declare a custom field type. When I do that, I get the same problem; calling super() returns a None object.
You're not passing request.FILES into the form when you instantiate it in the post.
form = TestForm(request.POST, request.FILES)
See the documentation.
Also note that you're instantiating the form twice on POST, which is unnecessary. Move the first one into an else clause at the end of the function (at the same level as if request.method == 'POST').