Say I have a form like:
class GeneralForm(forms.Form):
field1 = forms.IntegerField(required=False)
field2 = forms. IntegerField(required=False)
And I want to show it twice on a page within one form tag each time with a different prefix e.g.,:
rest of page ...
<form ..>
GeneralForm(data,prefix="form1").as_table()
GeneralForm(data,prefix="form2").as_table()
<input type="submit" />
</form>
rest of page ...
When the user submits this, how do I get the submitted form back into two separate forms to do validation, and redisplay it?
This was the only documentation I could find and it's peckish.
You process each form as you normally would, ensuring that you create instances which have the same prefixes as those used to generate the form initially.
Here's a slightly awkward example using the form you've given, as I don't know what the exact use case is:
def some_view(request):
if request.method == 'POST':
form1 = GeneralForm(request.POST, prefix='form1')
form2 = GeneralForm(request.POST, prefix='form2')
if all([form1.is_valid(), form2.is_valid()]):
pass # Do stuff with the forms
else:
form1 = GeneralForm(prefix='form1')
form2 = GeneralForm(prefix='form2')
return render_to_response('some_template.html', {
'form1': form1,
'form2': form2,
})
Here's some real-world sample code which demonstrates processing forms using the prefix:
http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/
Even better, I think formsets is exactly what you're looking for.
class GeneralForm(forms.Form):
field1 = forms.IntegerField(required=False)
field2 = forms. IntegerField(required=False)
from django.forms.formsets import formset_factory
# GeneralSet is a formset with 2 occurrences of GeneralForm
# ( as a formset allows the user to add new items, this enforces
# 2 fixed items, no less, no more )
GeneralSet = formset_factory(GeneralForm, extra=2, max_num=2)
# example view
def someview(request):
general_set = GeneralSet(request.POST)
if general_set.is_valid():
for form in general_set.forms:
# do something with data
return render_to_response("template.html", {'form': general_set}, RequestContext(request))
You can even have a formset automatically generated from a model with modelformset_factory , which are used by the automated django admin. FormSet handle even more stuff than simple forms, like adding, removing and sorting items.
Related
MODELS.PY
class Campaign(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
campaign_image = models.ImageField(default="profilepic.jpg",upload_to="campaign_pictures")
FORMS.PY
class RaiseFundsFrom3(forms.ModelForm):
class Meta:
model = Campaign
fields = ['campaign_image']
VIEWS.PY
#login_required
def raise_funds_medical_3(request):
if request.method == 'POST':
form = RaiseFundsFrom3(request.POST, request.FILES or None, instance=request.user)
if form.is_valid():
check = form.save(commit=False)
check.save()
return HttpResponse('form worked')
else:
form = RaiseFundsFrom3()
return render(request,'funds/raise_funds_medical_3.html',{'form':form})
URLS.PY
path('raise/medical/photo', views.raise_funds_medical_3, name="raise_funds_medical_3"),
raise_funds_medical_3.html
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group pt-2">
<small>Photo formats must be PNG / JPG / JPEG</small>
<input type="file" name="campaign_image" />
</div>
<button class="btn btn-lg button_bfg_blue" type="submit"> <small><b> NEXT </b></small> </button>
</form>
on form submit, i do not get any error, but image is not uploaded to the required folder.
however, in the raise_funds_medical_3 function within views.py, if i remove instance=request.user, the image gets uploaded but i get following error : NOT NULL constraint failed: funds_campaign.user_id
Your form is a ModelForm for a Campaign, so its instance needs to be a Campaign. Don't assign request.user as its instance!
Now, your form isn't including the user field which is required to save a Campaign, so you should assign that yourself in the view before saving to the database:
campaign = form.save(commit=False) # this gives your the form's instance
campaign.user = request.user # this assigns the user
campaign.save() # this commits to the database
Also you should handle the case where the form isn't valid. This is quite simple, just un-indent the last return in your view function, so that return render(...) is also called in case the form isn't valid.
Finally, instead of returning a response when the form is valid, it's good practice to redirect to another view. This way, when the user refreshes the page, the form isn't submitted again. Your final code should look like this:
#login_required
def raise_funds_medical_3(request):
if request.method == 'POST':
form = RaiseFundsFrom3(request.POST, request.FILES or None)
if form.is_valid():
check = form.save(commit=False)
check.user = request.user
check.save()
return redirect(<url_pattern>)
else:
form = RaiseFundsFrom3()
return render(request,'funds/raise_funds_medical_3.html',{'form':form})
Supplementary answer to dirkgroten's one
I have come to completely hate the conventional structuring of a Django Function-based View. They can be re-factored by inverting the validity test and adding one line so that one and only one instantiation of a form is present. The result is IMO far easier to read, and easily generalizes for a view displaying two or more forms.
def raise_funds_medical_3(request):
args = [request.POST, request.FILES or None] if request.method == "POST" else []
form = RaiseFundsFrom3(*args)
if request.method != "POST" or not form.is_valid():
# unbound form or form not valid
return render(request,'funds/raise_funds_medical_3.html',{'form':form})
# form is valid so do the processing and redirect
check = form.save(commit=False)
check.user = request.user
check.save()
return redirect(<url_pattern>)
If you want to process >1 form, the test becomes
if request.method != "POST" or any(
[ not form.is_valid(), not form2.is_valid(), ...]):
which forces evaluation of .is_valid() for all forms, even if the first was not valid, so that all the error messages are shown to the user.
In a complex business application, the processing of a successful form submission may be quite a few more lines of code than this simple example. Having it at the end, not indented, isolated from all the boilerplate save the return redirect(...), makes things much easier!
I looked at other similar questions on Stackoverflow, but those situations do not apply to me.
I have a form with a Queue field that is a required field. This form is used in multiple places and in one such instance, I don't want the Queue field to be shown to the user. So, I simply did not render it on the template. But because this a required field, the form won't submit. How do I pre-populate this field while at the same time hiding it from the user?
I cannot make changes to the model or the form's save methods because this form is also used at other places.
forms.py
class PublicTicketForm(CustomFieldMixin, forms.Form):
queue = forms.ChoiceField(
widget=forms.Select(attrs={'class': 'form-control'}),
label=_('Queue'),
required=True,
choices=()
)
views.py:
def no_queue(request):
if request.method == 'POST':
form = PublicTicketForm(request.POST, request.FILES)
form['queue'] = 9 # Tried to assign queue value to field, did not work
if form.is_valid():
if text_is_spam(form.cleaned_data['body'], request):
# This submission is spam. Let's not save it.
return render(request, template_name='helpdesk/public_spam.html')
else:
form.save()
else:
form = PublicTicketForm(initial={'queue': 9}) # tried this one too, did not work either
return render(request, 'helpdesk/no_queue.html', {'form': form})
The choices for this form were populated in the views, but because I'm not rendering it in the template, I did not do it.
You can use formsets to be able to assign a specific value and show specific inputs to the user like this
Don’t make a hidden field. Create another form class that doesn’t contain the field (subclassing will prevent repetition), then set the value in the view.
instance = form.save(commit=False)
instance.queue = 9
instance.save()
You can override POST data.
if request.method in ('POST', 'PUT'):
data = request.POST.copy()
data['queue'] = 9
form = PublicTicketForm(data, request.FILES)
I was able to do something like this in the template and that worked!
<input type="hidden" name="queue" value="9" />
I just did this last night!
In forms.py declare the field with the HiddenInput widget (be sure to render it):
scope = CharField(max_length=60,widget=HiddenInput())
Then, in views.py you can apply the initial value:
form = MyForm(initial={'scope': 'public'})
Or using a CBV:
initial = {'scope':'public'}
I have a form, "results", where one of the fields, "subjectID", is many-to-many because there's more than one result for each subject. I want one of the submit buttons to let me save what I've entered, then redirect to the same form, now unbound except that the many-to-many "subjectID" field stays the same so I can enter more results for that subject.
Edit: I should have made it clear that I wanted the instance that I had selected in the subjectID field to stay the same. I posted the code below that actually seems to be working for me
from models.py
class ResultsForm(forms.Modelform):
class Meta:
model = models.Results
fields = ['subjectID', # this is the field want
# to populate the form with when I "save and add another"
'slideNum', # IntegerField
'resultType' ] # ForeignKey
from views.py
def addResults(request):
if request.method == 'POST'
form = ResultsForm(request.POST)
if form.is_valid():
form.save()
if 'Save_and_add_another' in request.POST:
subjectID = form.fields['subjectID']
prepop = {'subjectID' : subjectID}
form = ResultsForm(initial=prepop)
return render(request, 'slideAdmin/addResults.html', {'form': form})
elif 'Save_and_return' in request.POST:
return HttpResponseRedirect('/home/')
else:
form = ResultsForm()
return render(request, 'slideAdmin/addResults.html', {'form': form})
Right now when I click on "save and add another" from my addResults form, I get this error:
TypeError at /slidebox/addResults
'ModelMultipleChoiceField' object is not iterable
which happens when rendering {{ form.as_p }} in the template.
Edit: Changes I made to views.py
if 'Save_and_add_another' in request.POST:
subjectID = form.cleaned_data.get('subjectID')
form = ResultsForm(initial={'subjectID': subjectID})
return render(request, 'slideAdmin/addResults.html', {'form': form})
As far as I can tell, this change works. Thanks again
You should always use form.cleaned_data.get('subjectID') versus pulling the field directly from the post data. You need to pass in a list of the pk's for the M2M field.
Your view can also use a touch of cleanup:
from django.core.urlresolvers import reverse
def addResults(request):
form = ResultsForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
if 'Save_and_add_another' in request.POST:
subjectID = form.cleaned_data.get('subjectID', [])
if subjectID:
subjectID = subjectIDs.split(',')
form = ResultsForm(initial={'subjectID': subjectID})
elif 'Save_and_return' in request.POST:
return HttpResponseRedirect(reverse('home')) # don't hard code
return render(request, 'slideAdmin/addResults.html', {'form': form})
I'm not sure if you will be able to keep the form unbound when initialized.
Your form.fields is an ordered dict of django.forms.fields objects. You just want the ids, and not all the other info that comes across it.
Get the data straight from the POST dictionary.
subjectID = request.POST.get('subjectID', '')
If this is a true many to many model. You need to make sure the data is setup correctly for the initialization.
# We have to special-case M2Ms as a list of comma-separated PKs.
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
Here is the initialization method from the django source code for Admin (or as I call it my super detailed and complicated Django cheat sheet, I am pedantic)
def get_changeform_initial_data(self, request):
"""
Get the initial form data.
Unless overridden, this populates from the GET params.
"""
initial = dict(request.GET.items())
for k in initial:
try:
f = self.model._meta.get_field(k)
except FieldDoesNotExist:
continue
# We have to special-case M2Ms as a list of comma-separated PKs.
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
return initial
Some PEP8 nonsense as well
classes are camel case ex: class MyAwesomeClass(object):
everything else is lower with underscores. ex: awesome_id = awesome1245
Good Luck!!
I've recently learned Django forms by subclassing FormView, where the desired form is assigned to the FormView.form_class attribute. When the form validates, the form_valid() method is invoked (for that one form). For example:
from accounts.forms import SignUpForm, UpdateAccountForm, UpdateBillingForm
class SignUpView(FormView):
form_class = SignUpForm
def form_valid(self, form):
# code when form validates...
However, I now have a situation where I need three unique forms on one page (with only one form visible to the user at a time). So, I'd like to handle them all in the same View.
Are multi-form pages possible using FormView? I'm not sure how to handle it, both in terms of passing multiple forms to the View (e.g. the other UpdateAccountForm and UpdateBillingForm), as well as distinguishing which one was submitted/validated? What would be the best way?
Well, for what it's worth here's what ultimately worked for me, using a generic View.
1) I added a hidden input field (named 'action') to each individual form on the page. For example, this is the form for updating user's info, which is pulling in UserForm:
<form action='/account/' method='post'>{% csrf_token %}
<input type='hidden' name='action' value='edit_user'>
{{ user_form.as_p }}
<input type='submit' value='Update'>
</form>
2) In my View logic, I can distinguish the forms by applying a prefix (per other SO posts and Django docs). Then, depending on the incoming 'action', I only bind the applicable form to the POST request (so validations aren't applied across all of them). In my case, I had two forms defined in forms.py, UserForm and BillingForm:
from django.views.generic.edit import View
from django.shortcuts import render
from django.http import HttpResponse
from accounts.forms import UserForm, BillingForm
class AccountView(View):
def get(self, request):
# code for GET request...
def post(self, request):
#instantiate all unique forms (using prefix) as unbound
user_form = UserForm(prefix='user_form')
billing_form = BillingForm(prefix='billing_form')
# determine which form is submitting (based on hidden input called 'action')
action = self.request.POST['action']
# bind to POST and process the correct form
if (action == 'edit_user'):
user_form = UserForm(request.POST, prefix='user_form')
if user_form.is_valid():
# user form validated, code away..
elif (action == 'edit_billing'):
billing_form = BillingForm(request.POST, prefix='billing_form')
if billing_form.is_valid():
# billing form validated, code away..
# prep context
context = {
'user_form': user_form,
'billing_form': billing_form,
}
return render(request, 'accounts/account.html', context)
Seems to work well, hopefully this is the right approach (?)
You can write a plain python class mimicking the Form API (at least the useful parts) and wrapping your three forms. Detecting which form has been submitted is just a matter of adding a hidden input with the form's identifier in each form (hint : use prefixes for your forms and use that same prefix as identifier).
The other solution is to use a simple function-based view instead, but even there I'd still use the same "form wrapper" pattern as far as I'm concerned.
All,
I have a template page say x.html
i have 3 text fields name(varchar2) ,age(int),school(varchar2) in it.
If the users enters values in the form in x.html(say values name="a" ,age="2" ,school="a") and submit it.I need to return the same values back to x.html indicating an error.
My question is how to return the same values to x.html.
Thanks.....
from docs:
The standard pattern for processing a form in a view looks like this:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
Django will write the submitted values back as long as you provide the form object to the rendered template. For example, in your view, something like:
# handle POST
form = MyForm(request.POST)
if form.is_valid():
# do something and redirect
else:
# render the template with the invalid form
return render_to_response('mytemplate.html', {'form': form})
and in your template, something like:
{{ form.myfield.label_tag }}
{% if form.myfield.errors %} indicate error message/icon here {% endif %}
{{ form.myfield }}
Note that {{ form.myfield }} will show an HTML widget for myfield with the previous submitted values based on the view code above. And it will be blank when you render it with a blank form in response to a GET (e.g. form = MyForm()).
If you are using django forms, it would do validation itself and then return the values you need. Here you can read about using forms and how they validate values. Basically, when you pass some values into the form and it's not valid, you just render the site again, but django will automagically fill fields. Don't bother writing your own forms, unless you really, really need them. And in your example you really don't ;-)
I'm not sure how you are processing your form information. However if you use the Form API built into Django, it takes care of much of this for you. For details take a look at the Django Docs for Forms http://docs.djangoproject.com/en/dev/topics/forms/#topics-forms-index
If you use the Form API and the submission is not valid, Django provides the template with a bound copy of the form with the user supplied data already in it. Again you will have to read the details of the API for how to implement it in your situation.