Proper way to handle multiple forms on one page in Django - django
I have a template page expecting two forms. If I just use one form, things are fine as in this typical example:
if request.method == 'POST':
form = AuthorForm(request.POST,)
if form.is_valid():
form.save()
# do something.
else:
form = AuthorForm()
If I want to work with multiple forms however, how do I let the view know that I'm submitting only one of the forms and not the other (i.e. it's still request.POST but I only want to process the form for which the submit happened)?
This is the solution based on the answer where expectedphrase and bannedphrase are the names of the submit buttons for the different forms and expectedphraseform and bannedphraseform are the forms.
if request.method == 'POST':
if 'bannedphrase' in request.POST:
bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
if bannedphraseform.is_valid():
bannedphraseform.save()
expectedphraseform = ExpectedPhraseForm(prefix='expected')
elif 'expectedphrase' in request.POST:
expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
if expectedphraseform.is_valid():
expectedphraseform.save()
bannedphraseform = BannedPhraseForm(prefix='banned')
else:
bannedphraseform = BannedPhraseForm(prefix='banned')
expectedphraseform = ExpectedPhraseForm(prefix='expected')
You have a few options:
Put different URLs in the action for the two forms. Then you'll have two different view functions to deal with the two different forms.
Read the submit button values from the POST data. You can tell which submit button was clicked: How can I build multiple submit buttons django form?
A method for future reference is something like this. bannedphraseform is the first form and expectedphraseform is the second. If the first one is hit, the second one is skipped (which is a reasonable assumption in this case):
if request.method == 'POST':
bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
if bannedphraseform.is_valid():
bannedphraseform.save()
else:
bannedphraseform = BannedPhraseForm(prefix='banned')
if request.method == 'POST' and not bannedphraseform.is_valid():
expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
bannedphraseform = BannedPhraseForm(prefix='banned')
if expectedphraseform.is_valid():
expectedphraseform.save()
else:
expectedphraseform = ExpectedPhraseForm(prefix='expected')
I needed multiple forms that are independently validated on the same page. The key concepts I was missing were 1) using the form prefix for the submit button name and 2) an unbounded form does not trigger validation. If it helps anyone else, here is my simplified example of two forms AForm and BForm using TemplateView based on the answers by #adam-nelson and #daniel-sokolowski and comment by #zeraien (https://stackoverflow.com/a/17303480/2680349):
# views.py
def _get_form(request, formcls, prefix):
data = request.POST if prefix in request.POST else None
return formcls(data, prefix=prefix)
class MyView(TemplateView):
template_name = 'mytemplate.html'
def get(self, request, *args, **kwargs):
return self.render_to_response({'aform': AForm(prefix='aform_pre'), 'bform': BForm(prefix='bform_pre')})
def post(self, request, *args, **kwargs):
aform = _get_form(request, AForm, 'aform_pre')
bform = _get_form(request, BForm, 'bform_pre')
if aform.is_bound and aform.is_valid():
# Process aform and render response
elif bform.is_bound and bform.is_valid():
# Process bform and render response
return self.render_to_response({'aform': aform, 'bform': bform})
# mytemplate.html
<form action="" method="post">
{% csrf_token %}
{{ aform.as_p }}
<input type="submit" name="{{aform.prefix}}" value="Submit" />
{{ bform.as_p }}
<input type="submit" name="{{bform.prefix}}" value="Submit" />
</form>
Wanted to share my solution where Django Forms are not being used.
I have multiple form elements on a single page and I want to use a single view to manage all the POST requests from all the forms.
What I've done is I have introduced an invisible input tag so that I can pass a parameter to the views to check which form has been submitted.
<form method="post" id="formOne">
{% csrf_token %}
<input type="hidden" name="form_type" value="formOne">
.....
</form>
.....
<form method="post" id="formTwo">
{% csrf_token %}
<input type="hidden" name="form_type" value="formTwo">
....
</form>
views.py
def handlemultipleforms(request, template="handle/multiple_forms.html"):
"""
Handle Multiple <form></form> elements
"""
if request.method == 'POST':
if request.POST.get("form_type") == 'formOne':
#Handle Elements from first Form
elif request.POST.get("form_type") == 'formTwo':
#Handle Elements from second Form
Django's class based views provide a generic FormView but for all intents and purposes it is designed to only handle one form.
One way to handle multiple forms with same target action url using Django's generic views is to extend the 'TemplateView' as shown below; I use this approach often enough that I have made it into an Eclipse IDE template.
class NegotiationGroupMultifacetedView(TemplateView):
### TemplateResponseMixin
template_name = 'offers/offer_detail.html'
### ContextMixin
def get_context_data(self, **kwargs):
""" Adds extra content to our template """
context = super(NegotiationGroupDetailView, self).get_context_data(**kwargs)
...
context['negotiation_bid_form'] = NegotiationBidForm(
prefix='NegotiationBidForm',
...
# Multiple 'submit' button paths should be handled in form's .save()/clean()
data = self.request.POST if bool(set(['NegotiationBidForm-submit-counter-bid',
'NegotiationBidForm-submit-approve-bid',
'NegotiationBidForm-submit-decline-further-bids']).intersection(
self.request.POST)) else None,
)
context['offer_attachment_form'] = NegotiationAttachmentForm(
prefix='NegotiationAttachment',
...
data = self.request.POST if 'NegotiationAttachment-submit' in self.request.POST else None,
files = self.request.FILES if 'NegotiationAttachment-submit' in self.request.POST else None
)
context['offer_contact_form'] = NegotiationContactForm()
return context
### NegotiationGroupDetailView
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if context['negotiation_bid_form'].is_valid():
instance = context['negotiation_bid_form'].save()
messages.success(request, 'Your offer bid #{0} has been submitted.'.format(instance.pk))
elif context['offer_attachment_form'].is_valid():
instance = context['offer_attachment_form'].save()
messages.success(request, 'Your offer attachment #{0} has been submitted.'.format(instance.pk))
# advise of any errors
else
messages.error('Error(s) encountered during form processing, please review below and re-submit')
return self.render_to_response(context)
The html template is to the following effect:
...
<form id='offer_negotiation_form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
{% csrf_token %}
{{ negotiation_bid_form.as_p }}
...
<input type="submit" name="{{ negotiation_bid_form.prefix }}-submit-counter-bid"
title="Submit a counter bid"
value="Counter Bid" />
</form>
...
<form id='offer-attachment-form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
{% csrf_token %}
{{ offer_attachment_form.as_p }}
<input name="{{ offer_attachment_form.prefix }}-submit" type="submit" value="Submit" />
</form>
...
This is a bit late, but this is the best solution I found. You make a look-up dictionary for the form name and its class, you also have to add an attribute to identify the form, and in your views you have to add it as a hidden field, with the form.formlabel.
# form holder
form_holder = {
'majeur': {
'class': FormClass1,
},
'majsoft': {
'class': FormClass2,
},
'tiers1': {
'class': FormClass3,
},
'tiers2': {
'class': FormClass4,
},
'tiers3': {
'class': FormClass5,
},
'tiers4': {
'class': FormClass6,
},
}
for key in form_holder.keys():
# If the key is the same as the formlabel, we should use the posted data
if request.POST.get('formlabel', None) == key:
# Get the form and initate it with the sent data
form = form_holder.get(key).get('class')(
data=request.POST
)
# Validate the form
if form.is_valid():
# Correct data entries
messages.info(request, _(u"Configuration validée."))
if form.save():
# Save succeeded
messages.success(
request,
_(u"Données enregistrées avec succès.")
)
else:
# Save failed
messages.warning(
request,
_(u"Un problème est survenu pendant l'enregistrement "
u"des données, merci de réessayer plus tard.")
)
else:
# Form is not valid, show feedback to the user
messages.error(
request,
_(u"Merci de corriger les erreurs suivantes.")
)
else:
# Just initiate the form without data
form = form_holder.get(key).get('class')(key)()
# Add the attribute for the name
setattr(form, 'formlabel', key)
# Append it to the tempalte variable that will hold all the forms
forms.append(form)
I hope this will help in the future.
view:
class AddProductView(generic.TemplateView):
template_name = 'manager/add_product.html'
def get(self, request, *args, **kwargs):
form = ProductForm(self.request.GET or None, prefix="sch")
sub_form = ImageForm(self.request.GET or None, prefix="loc")
context = super(AddProductView, self).get_context_data(**kwargs)
context['form'] = form
context['sub_form'] = sub_form
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
form = ProductForm(request.POST, prefix="sch")
sub_form = ImageForm(request.POST, prefix="loc")
...
template:
{% block container %}
<div class="container">
<br/>
<form action="{% url 'manager:add_product' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
{{ sub_form.as_p }}
<p>
<button type="submit">Submit</button>
</p>
</form>
</div>
{% endblock %}
Based on this answer by #ybendana:
Again, we use is_bound to check if the form is capable of validation. See this section of the documentation:
Bound and unbound forms
A Form instance is either bound to a set of data, or unbound.
If it’s bound to a set of data, it’s capable of validating that data and rendering the form as HTML with the data displayed in the HTML.
If it’s unbound, it cannot do validation (because there’s no data to validate!), but it can still render the blank form as HTML.
We use a list of tuples for form objects and their details allowing for more extensibility and less repetition.
However, instead of overriding get(), we override get_context_data() to make inserting a new, blank instance of the form (with prefix) into the response the default action for any request. In the context of a POST request, we override the post() method to:
Use the prefix to check if each form has been submitted
Validate the forms that have been submitted
Process the valid forms using the cleaned_data
Return any invalid forms to the response by overwriting the context data
# views.py
class MultipleForms(TemplateResponseMixin, ContextMixin, View):
form_list = [ # (context_key, formcls, prefix)
("form_a", FormA, "prefix_a"),
("form_b", FormB, "prefix_b"),
("form_c", FormC, "prefix_c"),
...
("form_x", FormX, "prefix_x"),
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add blank forms to context with prefixes
for context_key, formcls, prefix in self.form_list:
context[context_key] = formcls(prefix=prefix)
return context
def post(self, request, *args, **kwargs):
# Get object and context
self.object = self.get_object()
context = self.get_context_data(object=self.object)
# Process forms
for context_key, formcls, prefix in self.form_list:
if prefix in request.POST:
# Get the form object with prefix and pass it the POST data to \
# validate and clean etc.
form = formcls(request.POST, prefix=prefix)
if form.is_bound:
# If the form is bound (i.e. it is capable of validation) \
# check the validation
if form.is_valid():
# call the form's save() method or do whatever you \
# want with form.cleaned_data
form.save()
else:
# overwrite context data for this form so that it is \
# returned to the page with validation errors
context[context_key] = form
# Pass context back to render_to_response() including any invalid forms
return self.render_to_response(context)
This method allows repeated form entries on the same page, something I found did not work with #ybendana's answer.
I believe it wouldn't be masses more work to fold this method into a Mixin class, taking the form_list object as an attribute and hooking get_context_data() and post() as above.
Edit: This already exists. See this repository.
NB:
This method required TemplateResponseMixin for render_to_response() and ContextMixin for get_context_data() to work. Either use these Mixins or a CBV that descends from them.
If you are using approach with class-based views and different 'action' attrs i mean
Put different URLs in the action for the two forms. Then you'll have two different view functions to deal with the two different forms.
You can easily handle errors from different forms using overloaded get_context_data method, e.x:
views.py:
class LoginView(FormView):
form_class = AuthFormEdited
success_url = '/'
template_name = 'main/index.html'
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
....
def get_context_data(self, **kwargs):
context = super(LoginView, self).get_context_data(**kwargs)
context['login_view_in_action'] = True
return context
class SignInView(FormView):
form_class = SignInForm
success_url = '/'
template_name = 'main/index.html'
def dispatch(self, request, *args, **kwargs):
return super(SignInView, self).dispatch(request, *args, **kwargs)
.....
def get_context_data(self, **kwargs):
context = super(SignInView, self).get_context_data(**kwargs)
context['login_view_in_action'] = False
return context
template:
<div class="login-form">
<form action="/login/" method="post" role="form">
{% csrf_token %}
{% if login_view_in_action %}
{% for e in form.non_field_errors %}
<div class="alert alert-danger alert-dismissable">
{{ e }}
<a class="panel-close close" data-dismiss="alert">×</a>
</div>
{% endfor %}
{% endif %}
.....
</form>
</div>
<div class="signin-form">
<form action="/registration/" method="post" role="form">
{% csrf_token %}
{% if not login_view_in_action %}
{% for e in form.non_field_errors %}
<div class="alert alert-danger alert-dismissable">
{{ e }}
<a class="panel-close close" data-dismiss="alert">×</a>
</div>
{% endfor %}
{% endif %}
....
</form>
</div>
Here is simple way to handle the above.
In Html Template we put Post
<form action="/useradd/addnewroute/" method="post" id="login-form">{% csrf_token %}
<!-- add details of form here-->
<form>
<form action="/useradd/addarea/" method="post" id="login-form">{% csrf_token %}
<!-- add details of form here-->
<form>
In View
def addnewroute(request):
if request.method == "POST":
# do something
def addarea(request):
if request.method == "POST":
# do something
In URL
Give needed info like
urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
if request.method == 'POST':
expectedphraseform = ExpectedphraseForm(request.POST)
bannedphraseform = BannedphraseForm(request.POST)
if expectedphraseform.is_valid():
expectedphraseform.save()
return HttpResponse("Success")
if bannedphraseform.is_valid():
bannedphraseform.save()
return HttpResponse("Success")
else:
bannedphraseform = BannedphraseForm()
expectedphraseform = ExpectedphraseForm()
return render(request, 'some.html',{'bannedphraseform':bannedphraseform, 'expectedphraseform':expectedphraseform})
This worked for me accurately as I wanted. This Approach has a single problem that it validates both the form's errors. But works Totally fine.
I discovered a pretty interesting way to send TWO Forms from a single page using the same view. I tried many options but just wanted something that can just work. So here is something that I discovered. But it only works when there are just TWO Forms on a page.
I am using just try and except method to first try first form and if that doesnt works than try second form. This is quiet interesting to know that it works absolutely fine. Don't use it on scalable application as it can create troublesome or may risk the security of the application, else use Class based view to submit mutiple forms or create seperate views for each form.
def create_profile(request):
if request.method=='POST':
try:
biograph = Biography(name=name, email=email, full_name=full_name, slug_name=slug_name, short_bio=short_bio)
biograph.save()
except:
social = SocialMedia(twitter=twitter, instagram=instagram, facebook=facebook, linkedin=linkedin, github=github)
social.save()
Related
how to make a post request and get the radio button values in django
I'm doing a website in django but this is the first time i use this framework so i'm not so used to it. I need to save some information on a DB, and i need to take these information from some radio buttons. I tryied so many ways to get the data but nothing worked. So i'd like to ask how to get these data in models.py from a template.html. This is the code in views.py: def question1(request): form = CHOICES(request.POST) if request.method == 'POST': form = CHOICES(request.POST) if form.is_valid(): selected = form.cleaned_data.get("NUMS") return render(request, 'q1.html', {'form': form}) This is the template question1.html: <form class="form-inline" method='POST' action="" enctype='multipart/form-data'>{% csrf_token %} {% csrf_token %} {{form.path}} </form> Then there is the form in forms.py: NUMS = [ ('one', 'one'), ('two', 'two'), ('three', 'three'), ('four', 'four'), ('five', 'fives'), ] class CHOICES(forms.Form): NUMS = forms.ChoiceField(choices=NUMS, widget=forms.RadioSelect) I checked and I think that the problem could be the request.method that is GET insted of POST. So how can I make a POST request? Thank you
Django file upload with model form
there are tons of relevant posts: https://docs.djangoproject.com/en/3.2/topics/http/file-uploads/ Uploading A file in django with ModelForms https://github.com/axelpale/minimal-django-file-upload-example I am creating a simple app that allow user to upload css file: First, validate field field to make sure the file is css and the max size does not exceed 5MB. fields.py from django.db import models from django import forms from django.template.defaultfilters import filesizeformat def mb(n): return n * 1048576 class FileField(models.FileField): def __init__(self, *args, **kwargs): self.content_types = kwargs.pop('content_types', []) self.max_upload_size = kwargs.pop('max_upload_size', []) super().__init__(*args, **kwargs) def clean(self, *args, **kwargs): data = super().clean(*args, **kwargs) file = data.file try: content_type = file.content_type if content_type in self.content_types: if file.size > self.max_upload_size: raise forms.ValidationError('Please keep filesize under {}. Current filesize {}' .format(filesizeformat(self.max_upload_size), filesizeformat(file.size))) else: raise forms.ValidationError('File type rejected') except AttributeError: pass return data second, implement validation into model models.py # Create your models here. class Css(models.Model): file = FileField( upload_to='css', content_types=['text/css'], max_upload_size=mb(5), ) third, create form for model creation forms.py class CssCreateForm(forms.ModelForm): class Meta: model = Css fields = ['file'] last, write a callable view views.py # Create your views here. def cssUploadView(request): if request.method == 'POST': print(request.POST['file']) print(type(request.POST['file'])) print(request.FILES) form = forms.CssCreateForm(request.POST, request.FILES) if form.is_valid(): print('---------') print(form.cleaned_data['file']) else: print('not valid') else: form = forms.CssCreateForm() return render(request, 'css/css_create.html', { 'form': form, }) template <form method="POST"> {% csrf_token %} <div class="form-title">Upload a new CSS file</div> {{ form.as_p }} <div class="button-area"> <button id="go" type="submit">Go</button> </div> </form> The expected result is that when uploading html file, or css file larger than 5MB, ValidationError will be raised However, in view Both request.FILES and form.cleaned_data['file'] fails to retrieve uploaded file. form.is_valid always returns True for example, when I upload clean.py: clean.py <class 'str'> <MultiValueDict: {}> --------- None I wonder why file fails to upload and validation does not work. Any suggestion will be appreciated.
You have to specify the data encoding method in your html form. <form method="POST" enctype="multipart/form-data"> {% csrf_token %} <div class="form-title">Upload a new CSS file</div> {{ form.as_p }} <div class="button-area"> <button id="go" type="submit">Go</button> </div> </form>
The "post" method in Django
I created three files: 2- view.py : class AddTeamView(View): def get (self, request): form = TeamForm() context = {'form': form} return render(request, 'add_team.html', context) 1-forms.py: class TeamForm(forms.Form): name = forms.CharField( max_length='100') details = forms.CharField(max_length='250') 3-add_team.html: -here there is another file called "base.html" {% extends 'base.html' %} {% block title %} add team {% endblock %} {% block content %} <form action="/add_team/" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="Submit"> </form> {% endblock %} and i went to cmd and entered the server "python manage.py runserver" it appeared on the browser: "This page isn’t working If the problem continues, contact the site owner. HTTP ERROR 405"
A view can support methods like GET, POST, PUT, etc. given the corresponding method exists, so the view should have a .get(..), .post(..), .put(..), etc. function. Here you only implemented a def get(self, request), and so POST requests are not allowed. Based on the data you however show, this looks like the typical usecase of a CreateView [Django-doc]. The idea of these views is to encapsulate common scenario's such that by overriding a few attributes, one creates a view that is tailored towards a specific case, like: class AddTeamView(CreateView): form_class = TeamForm template_name = 'add_team.html' success_url = '/some/success_url' The TeamForm should probably be a ModelForm, or at least a Form where you override the .save(..) function to properly save your data to the database, since right now, the form is not doing anything (well it receives the data, but after validation, it throws it away). You might want to override the form_valid(..) function in case you do not want to redirect to the success_url. Furthermore it is very common that the success_url is resolved lazily from a given view name, like: class AddTeamView(CreateView): form_class = TeamForm template_name = 'add_team.html' success_url = reverse_lazy('view_name')
So, we don’t need to do a conditional to check if the request is a POST or if it’s a GET: Your views.py: from django.views.generic import View class AddTeamView(View): def post(self, request): form = TeamForm(request.POST) if form.is_valid(): new_tm = TeamModel(name=form.cleaned_data['name'], details=form.cleaned_data['details']) new_tm.save() return redirect('team_list') return render(request, 'add_team.html', {'form': form}) def get(self, request): form = TeamForm() return render(request, 'add_team.html', {'form': form}) Hope this help you...
Why django form.as_p call form.clean method when template renders?
I have this sample of django code: # views.py def test_view(request): form = TestForm( request.POST or { 'text': 'some text'}, ) data = { 'form': form, } print 'before rendering' return render(request, 'test.html', data) # forms.py class TestForm(forms.Form): text = forms.CharField() def __init__(self, *args, **kwargs): print 'init' super(TestForm, self).__init__(*args, **kwargs) def clean(self): print 'in clean' and this template: #test.html <form id='test-form' method="post" action="some url" enctype="multipart/form-data"> {{ form.as_p }} <input type="submit" value="Save"/> </form> when i send get request to this file i have this output in console: before renderinginit in clean when I write {{ form.text }} instead of {{ form.as_p }} I have only: before renderinginit It seams to me that as_p method calls clean() internally in process of rendering template. Before that I mentioned that as_p method only is some kind of shortcut(I understand that it's a method of Form class) and doesn't realize logic. Why does it happen? Is it a bug or some usefull feature? Version of Django==1.5.1
As far as I can see in the source django has a _html_output helper function to return the function form.as_p(). If there is data bound to the form (like yours), then the BaseForm class property errors is called. This function calls the forms full clean. So I think this behaviour is intentionally to render form errors.
Change your view like this: # views.py def test_view(request): if request.POST: form = TestForm(request.POST) # this is usually used when there's an actual post request # and in this block you do validation else: form = TestForm(initial={'somekey': 'somevalue'}) data = { 'form': form, } print 'before rendering' return render(request, 'test.html', data) and clean() won't be called anymore
The problem is that I unproper initialize form, I should use Form(initial={#something#})
Django: Can class-based views accept two forms at a time?
If I have two forms: class ContactForm(forms.Form): name = forms.CharField() message = forms.CharField(widget=forms.Textarea) class SocialForm(forms.Form): name = forms.CharField() message = forms.CharField(widget=forms.Textarea) and wanted to use a class based view, and send both forms to the template, is that even possible? class TestView(FormView): template_name = 'contact.html' form_class = ContactForm It seems the FormView can only accept one form at a time. In function based view though I can easily send two forms to my template and retrieve the content of both within the request.POST back. variables = {'contact_form':contact_form, 'social_form':social_form } return render(request, 'discussion.html', variables) Is this a limitation of using class based view (generic views)? Many Thanks
Here's a scaleable solution. My starting point was this gist, https://gist.github.com/michelts/1029336 i've enhanced that solution so that multiple forms can be displayed, but either all or an individual can be submitted https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f and this is an example usage class SignupLoginView(MultiFormsView): template_name = 'public/my_login_signup_template.html' form_classes = {'login': LoginForm, 'signup': SignupForm} success_url = 'my/success/url' def get_login_initial(self): return {'email':'dave#dave.com'} def get_signup_initial(self): return {'email':'dave#dave.com'} def get_context_data(self, **kwargs): context = super(SignupLoginView, self).get_context_data(**kwargs) context.update({"some_context_value": 'blah blah blah', "some_other_context_value": 'blah'}) return context def login_form_valid(self, form): return form.login(self.request, redirect_url=self.get_success_url()) def signup_form_valid(self, form): user = form.save(self.request) return form.signup(self.request, user, self.get_success_url()) and the template looks like this <form class="login" method="POST" action="{% url 'my_view' %}"> {% csrf_token %} {{ forms.login.as_p }} <button name='action' value='login' type="submit">Sign in</button> </form> <form class="signup" method="POST" action="{% url 'my_view' %}"> {% csrf_token %} {{ forms.signup.as_p }} <button name='action' value='signup' type="submit">Sign up</button> </form> An important thing to note on the template are the submit buttons. They have to have their 'name' attribute set to 'action' and their 'value' attribute must match the name given to the form in the 'form_classes' dict. This is used to determine which individual form has been submitted.
By default, class-based views only support a single form per view. But there are other ways to accomplish what you need. But again, this cannot handle both forms at the same time. This will also work with most of the class-based views as well as regular forms. views.py class MyClassView(UpdateView): template_name = 'page.html' form_class = myform1 second_form_class = myform2 success_url = '/' def get_context_data(self, **kwargs): context = super(MyClassView, self).get_context_data(**kwargs) if 'form' not in context: context['form'] = self.form_class(request=self.request) if 'form2' not in context: context['form2'] = self.second_form_class(request=self.request) return context def get_object(self): return get_object_or_404(Model, pk=self.request.session['value_here']) def form_invalid(self, **kwargs): return self.render_to_response(self.get_context_data(**kwargs)) def post(self, request, *args, **kwargs): self.object = self.get_object() if 'form' in request.POST: form_class = self.get_form_class() form_name = 'form' else: form_class = self.second_form_class form_name = 'form2' form = self.get_form(form_class) if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(**{form_name: form}) template <form method="post"> {% csrf_token %} ......... <input type="submit" name="form" value="Submit" /> </form> <form method="post"> {% csrf_token %} ......... <input type="submit" name="form2" value="Submit" /> </form>
Its is possible for one class-based view to accept two forms at a time. view.py class TestView(FormView): template_name = 'contact.html' def get(self, request, *args, **kwargs): contact_form = ContactForm() contact_form.prefix = 'contact_form' social_form = SocialForm() social_form.prefix = 'social_form' # Use RequestContext instead of render_to_response from 3.0 return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form})) def post(self, request, *args, **kwargs): contact_form = ContactForm(self.request.POST, prefix='contact_form') social_form = SocialForm(self.request.POST, prefix='social_form ') if contact_form.is_valid() and social_form.is_valid(): ### do something return HttpResponseRedirect(>>> redirect url <<<) else: return self.form_invalid(contact_form,social_form , **kwargs) def form_invalid(self, contact_form, social_form, **kwargs): contact_form.prefix='contact_form' social_form.prefix='social_form' return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form})) forms.py from django import forms from models import Social, Contact from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit, Button, Layout, Field, Div from crispy_forms.bootstrap import (FormActions) class ContactForm(forms.ModelForm): class Meta: model = Contact helper = FormHelper() helper.form_tag = False class SocialForm(forms.Form): class Meta: model = Social helper = FormHelper() helper.form_tag = False HTML Take one outer form class and set action as TestView Url {% load crispy_forms_tags %} <form action="/testview/" method="post"> <!----- render your forms here --> {% crispy contact_form %} {% crispy social_form%} <input type='submit' value="Save" /> </form> Good Luck
I have used a following generic view based on TemplateView: def merge_dicts(x, y): """ Given two dicts, merge them into a new dict as a shallow copy. """ z = x.copy() z.update(y) return z class MultipleFormView(TemplateView): """ View mixin that handles multiple forms / formsets. After the successful data is inserted ``self.process_forms`` is called. """ form_classes = {} def get_context_data(self, **kwargs): context = super(MultipleFormView, self).get_context_data(**kwargs) forms_initialized = {name: form(prefix=name) for name, form in self.form_classes.items()} return merge_dicts(context, forms_initialized) def post(self, request): forms_initialized = { name: form(prefix=name, data=request.POST) for name, form in self.form_classes.items()} valid = all([form_class.is_valid() for form_class in forms_initialized.values()]) if valid: return self.process_forms(forms_initialized) else: context = merge_dicts(self.get_context_data(), forms_initialized) return self.render_to_response(context) def process_forms(self, form_instances): raise NotImplemented This has the advantage that it is reusable and all the validation is done on the forms themselves. It is then used as follows: class AddSource(MultipleFormView): """ Custom view for processing source form and seed formset """ template_name = 'add_source.html' form_classes = { 'source_form': forms.SourceForm, 'seed_formset': forms.SeedFormset, } def process_forms(self, form_instances): pass # saving forms etc
It is not a limitation of class-based views. Generic FormView just is not designed to accept two forms (well, it's generic). You can subclass it or write your own class-based view to accept two forms.
Use django-superform This is a pretty neat way to thread a composed form as a single object to outside callers, such as the Django class based views. from django_superform import FormField, SuperForm class MyClassForm(SuperForm): form1 = FormField(FormClass1) form2 = FormField(FormClass2) In the view, you can use form_class = MyClassForm In the form __init__() method, you can access the forms using: self.forms['form1'] There is also a SuperModelForm and ModelFormField for model-forms. In the template, you can access the form fields using: {{ form.form1.field }}. I would recommend aliasing the form using {% with form1=form.form1 %} to avoid rereading/reconstructing the form all the time.
Resembles #james answer (I had a similar starting point), but it doesn't need to receive a form name via POST data. Instead, it uses autogenerated prefixes to determine which form(s) received POST data, assign the data, validate these forms, and finally send them to the appropriate form_valid method. If there is only 1 bound form it sends that single form, else it sends a {"name": bound_form_instance} dictionary. It is compatible with forms.Form or other "form behaving" classes that can be assigned a prefix (ex. django formsets), but haven't made a ModelForm variant yet, tho you could use a model form with this View (see edit below). It can handle forms in different tags, multiple forms in one tag, or a combination of both. The code is hosted on github (https://github.com/AlexECX/django_MultiFormView). There are some usage guidelines and a little demo covering some use cases. The goal was to have a class that feels as close as possible like the FormView. Here is an example with a simple use case: views.py class MultipleFormsDemoView(MultiFormView): template_name = "app_name/demo.html" initials = { "contactform": {"message": "some initial data"} } form_classes = [ ContactForm, ("better_name", SubscriptionForm), ] # The order is important! and you need to provide an # url for every form_class. success_urls = [ reverse_lazy("app_name:contact_view"), reverse_lazy("app_name:subcribe_view"), ] # Or, if it is the same url: #success_url = reverse_lazy("app_name:some_view") def get_contactform_initial(self, form_name): initial = super().get_initial(form_name) # Some logic here? I just wanted to show it could be done, # initial data is assigned automatically from self.initials anyway return initial def contactform_form_valid(self, form): title = form.cleaned_data.get('title') print(title) return super().form_valid(form) def better_name_form_valid(self, form): email = form.cleaned_data.get('email') print(email) if "Somebody once told me the world" is "gonna roll me": return super().form_valid(form) else: return HttpResponse("Somebody once told me the world is gonna roll me") template.html {% extends "base.html" %} {% block content %} <form method="post"> {% csrf_token %} {{ forms.better_name }} <input type="submit" value="Subscribe"> </form> <form method="post"> {% csrf_token %} {{ forms.contactform }} <input type="submit" value="Send"> </form> {% endblock content %} EDIT - about ModelForms Welp, after looking into ModelFormView I realised it wouldn't be that easy to create a MultiModelFormView, I would probably need to rewrite SingleObjectMixin as well. In the mean time, you can use a ModelForm as long as you add an 'instance' keyword argument with a model instance. def get_bookform_form_kwargs(self, form_name): kwargs = super().get_form_kwargs(form_name) kwargs['instance'] = Book.objects.get(title="I'm Batman") return kwargs