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.
Related
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 !
I seriously can't figure out how to manage to do this.
I would like to use django forms to validate the following (unknown number of aname):
<input type="hidden" name="aname" value="someJSONdump1"/>
<input type="hidden" name="aname" value="someJSONdump2"/>
<input type="hidden" name="aname" value="someJSONdump3"/>
<input type="hidden" name="aname" value="someJSONdump4"/>
and on the django side, I'm calling:
form = myforms.MyForm(request.POST, request.FILES)
if (form.is_valid()):
# do something
else:
# redisplay the form
How do I define MyForm to allow me to validate each aname and also, when in error, the widget to redisplay the above <input>s?
I can't figure out how to use the MultiValueField or even if it's the right thing to use. It seems to be a solution when you know how many fields you have?
Using clean_aname() in the form is no help as self.cleaned_data.get('aname') is only the last value.
Without the form, I would use something like request.POST.getlist('aname'), but I would like to avoid this if I can do it with django.forms.
Thanks for your help.
EDIT
I've left aside that I was defining more fields from a ModelForm. I think this might have some effects with formset. Here is where I am at... Is this solution considered to be "Django forms" compatible?
class MyField(forms.Field):
widget = MyWidget
def to_python(self, value):
if (isinstance(value, basestring)):
value = [value]
return [json.loads(v) for v in value]
class MyForm(forms.ModelForm):
class Meta:
model = models.MyModel
aname = MyField()
def clean(self):
cleaned_data = super(MyForm, self).clean()
cleaned_data['aname'] = self.fields['aname'].clean(self.data.getlist('aname'))
return cleaned_data
Now, I have to define MyWidget to allow me to display a list of <input type="hidden">, but I would like to know if this solution sound acceptable. Maybe I could have done this in clean_aname() too.
You could try implementing this using a set of forms (called formsets within Django), in which each form would be an instance of the validation form you want. For instance,
class ValidationForm(forms.Form):
aname = forms.CharField()
def clean_aname(self):
aname = self.cleaned_data['aname']
# TODO validate aname
return aname
def save(self, commit=False):
# TODO implement this form's save logic
return 'It works!'
For creating a set of those forms (see formset documentation), do:
from django.forms.formsets import formset_factory
ValidationFormSet = formset_factory(ValidationForm)
On your view, use the ValidationFormSet for receiving the data:
def my_view(request):
if request.method == 'POST':
form = ValidationFormSet(request.POST, request.FILES)
if form.is_valid():
# All anames were validated by clean_aname
results = form.save()
for r in results:
print r # Should print 'It works!'
else:
form = ValidationFormSet()
return <your_result>
You can pass multiple forms to your view using prefix, like this:
jsondumplist = ['jsondump1', 'jsondump2', 'jsondump3', 'jsondump4'....]
if request.method == 'POST':
forms = [YourForm(request.POST, prefix=x) for x in jsondumplist]
for f in forms:
if f.is_valid():
f.save()
else:
forms = [YourForm(prefix=x) for x in jsondumplist]
YourForm could have just the single field you are interested in, or several.
class YourForm(forms.Form):
aname = forms.CharField(widget=forms.HiddenInput())
Then, your template will look something like this:
{% for form in forms %}
{% for field in form %}
{{ field }}
{% endfor %}
{% endfor %}
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.
I've been using CakePHP for a long time now and feel comfortable developing sites with it. I do however like Python more then PHP and would like to move to Django.
EDIT: split in three separate questions
How can I mix multiple models in one form? I know formsets are used for this, but I can't find a decent tutorial on this (views + template). In Cake I can simply to this in my view (template):
echo $this->Form->input('User.title');
echo $this->Form->input('Profile.website');
echo $this->Form->input('Comment.0.title');
echo $this->Form->input('Comment.1.title');
This would mix a User model, a Profile model and add two comments in one form. How to do this with Django?
Thanks!
In regards to part 1, this is much easier than you may think:
forms.py:
class UserForm(forms.ModelForm):
pass ## Using pass so I don't have to write the whole thing.
class ProfileForm(forms.ModelForm):
pass
class CommentForm(forms.ModelForm):
pass
views.py:
def view_forms(request):
userform = UserForm()
profileform = ProfileForm()
comment1 = CommentForm()
comment2 = CommentForm()
if request.method = "POST":
## Process forms here. Yes, I'm lazy.
return render_to_response("template.html",
locals(),
context_instance=RequestContext(request))
template.html:
<form method="POST">
{{ userform.as_p }}
{{ profileform.as_p }}
{{ comment1.as_p }}
{{ comment2.as_p }}
</form>
Sorry for a newbie question but...
Can someone shed some light on what is the use case for inlineformset_factory?
I have followed example from Django documentation:
#Models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
#View
def jojo(request):
BookFormSet = inlineformset_factory(Author, Book)
author = Author.objects.get(name=u'Mike Royko')
formset = BookFormSet(instance=author)
return render_to_response('jojo.html', {
'formset': formset,
})
#jojo.html
<form action="" method="POST">
<table>
{{ formset }}
</table>
<input type="submit" value="Submit" />
</form>
But it only displays book fields.
My understanding was that formset would display Book form with inline Author form just
like Django Admin. On top of that I can't easily pass initial values to formset?
Then how is it better then using two separate AuthorForm and BookForm?
Or am i missing something obvious?
inlineformset_factory only provides multiple forms for the nested elements, you need a separate form at the top if you want a form for the main model.
Here is an example of a working inlineformset_factory with the main form embedded at the top:
views.py
from django.shortcuts import get_object_or_404, render_to_response
from django.forms.models import inlineformset_factory
from django.http import HttpResponseRedirect
from django.template import RequestContext
from App_name.models import * #E.g. Main, Nested, MainForm, etc.
. . .
#login_required
def Some_view(request, main_id=None, redirect_notice=None):
#login stuff . . .
c = {}
c.update(csrf(request))
c.update({'redirect_notice':redirect_notice})#Redirect notice is an optional argument I use to send user certain notifications, unrelated to this inlineformset_factory example, but useful.
#Intialization --- The start of the view specific functions
NestedFormset = inlineformset_factory(Main, Nested, can_delete=False, )
main = None
if main_id :
main = Main.objects.get(id=main_id)#get_object_or_404 is also an option
# Save new/edited Forms
if request.method == 'POST':
main_form = MainForm(request.POST, instance=main, prefix='mains')
formset = NestedFormset(request.POST, request.FILES, instance=main, prefix='nesteds')
if main_form.is_valid() and formset.is_valid():
r = main_form.save(commit=False)
#do stuff, e.g. setting any values excluded in the MainForm
formset.save()
r.save()
return HttpResponseRedirect('/Home_url/')
else:
main_form = MainForm(instance=main, prefix='mains') #initial can be used in the MainForm here like normal.
formset = NestedFormset(instance=main, prefix='nesteds')
c.update({'main_form':main_form, 'formset':formset, 'realm':realm, 'main_id':main_id})
return render_to_response('App_name/Main_nesteds.html', c, context_instance=RequestContext(request))
template.html
{% if main_form %}
<form action="." method="POST">{% csrf_token %}
{{ formset.management_form }}
<table>
{{main_form.as_table}}
{% for form in formset.forms %}
<table>{{ form }}</table>
{% endfor %}
</table>
<p><input type="submit" name="submit" value="Submit" class="button"></p>
</form>
{% endif %}
The beauty of the inlineformset_factory (and modelformset_factory) is the ability to create multiple model instances from a single form. If you were to simply 'use two separate forms' the id's of the form's fields would trample each other.
The formset_factory functions know how many extra form(sets) you need (via the extra argument) and sets the id's of the fields accordingly.
inlineformset_factory creates a list of forms.
This can be used when the same form needs to be repeated at the page, for example:
Upload multiple photo's with a description.
Invite multiple members by email
Fill a calendar grid per hour
Fill a list of books for the author.
With some JavaScript code, you can add a "add another row" functionality as well.