Django form not calling custom validator - django

I'm using Django 2.0. This is my forms.py:
class PostcodeForm(forms.Form):
postcode = forms.CharField(required=True, widget=forms.TextInput(
attrs={
'placeholder': "enter a postcode",
}
))
def clean_postcode(self):
postcode = self.clean_data.get('postcode', '')
print('clean_postcode', postcode)
if postcode != 'something':
raise forms.ValidationError(_("Please enter a valid postcode"), code='invalid')
return data
And my views.py:
def index(request):
form = PostcodeForm()
context = {
'form': form
}
return render(request, 'index.html', context)
And my index.html:
<form class="form-inline" id="lookup_postcode" action="{% url 'lookup_postcode' %}" method="get">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.postcode.errors }}
{{ form.postcode }}
<button type="submit">Submit</button>
</form>
But when I type in any value other than 'something', the form still submits. I also don't see any print statements in the console, so it looks as though the validator just isn't being run.
What am I doing wrong?

At the moment you are always doing form = PostcodeForm(), for GET and POST requests. That means that the form is not bound to any data, so it will never be valid or have any errors.
In Django, a typical view to process a form looks something like this:
from django.shortcuts import redirect
def index(request):
if request.method == 'POST':
form = PostcodeForm(request.POST)
if form.is_valid():
# form is valid. Process form and redirect
...
return redirect('/success-url/')
else:
form = PostcodeForm()
context = {
'form': form
}
return render(request, 'index.html', context)
For this to work, you'll need to change your form method to 'post'.
<form class="form-inline" id="lookup_postcode" action="{% url 'lookup_postcode' %}" method="post">
If you keep the form method as 'get' then you'll need to bind the form to request.GET instead. You might want to add a check, otherwise you'll get errors for required fields when you first access the index view.
if 'postcode' in request.GET:
# bound form
form = PostcodeForm(request.GET)
else:
# unbound, empty form
form = PostcodeForm()

Use your form as below:
class PostcodeForm(forms.Form):
postcode = forms.CharField(required=True, widget=forms.TextInput(
attrs={
'placeholder': "enter a postcode",
}
))
def clean(self):
postcode = self.cleaned_data.get('postcode', '')
print('clean_postcode', postcode)
if postcode != 'something':
raise forms.ValidationError(_("Please enter a valid postcode"), code='invalid')
return super(PostcodeForm, self).clean()

Everytime you deal with the validity of the posted data, make sure to include form.is_valid() condition in your views.py.

Related

Unique=True in Form not throwing any error

I'm entering a duplicate value (already saved in another instance of the same model) in my form to test the unique=True attribute. form.is_valid() returns 'False', as expected, but I don't receive any prompt in the template. Shouldn't I get prompted something like "obj with this value already exists"? The page simply reloads... What am I missing?
forms.py
def update_route(request, pk):
instance = Route.objects.get(id=pk)
if request.method == "POST":
form = RouteForm(request.POST)
if form.is_valid():
data = form.cleaned_data
instance.name = data['name']
instance.priority = data['priority']
instance.url = data['url']
return redirect('campaigns:routes_list')
form = RouteForm(instance=instance)
context= {
'form': form,
}
return render(request, "campaigns/route_form.html", context)
models.py
class Route(models.Model):
name = models.CharField(max_length=48)
priority = models.SmallIntegerField(choices=PRIORITY_LEVEL, default=0, unique=True)
url = models.URLField()
Template
<form method="post" action="">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Submit">
</form>
Your update_route() view handles the condition in which the submitted form is valid (form.is_valid()), but not the condition in which the form is invalid.
The errors you are looking for are stored in the form object that you created with RouteForm(request.POST). The errors are generated when the is_valid() method is called.
This form object needs to be added to the context dict and rerendered to the user for the errors to surface. But your code currently overwrites that object with form = RouteForm(instance=instance), so the POST data and the related errors disappear.
One solution could be to handle it in the conditional statement:
if form.is_valid():
...
else:
context = {'form': form}
return render(request, "campaigns/route_form.html", context)
Another solution could be to create a conditional statement for GET requests, for example:
elif request.method == 'GET':
form = RouteForm(instance=instance)

Django - I'm getting "This field is required" error on UPDATING an object

When I try to use a form to edit an object that includes an image upload I get "This field is required". A similar form works fine to create the object, but when I retrieve the object and attempt to modify other fields, it fails on the image.
#-------models.py:
class Star(models.Model):
firstname = models.CharField(max_length=32)
lastname = models.CharField(max_length=32, blank=True)
portrait = models.ImageField(upload_to='images/')
#------views.py:
class StarForm(forms.ModelForm):
class Meta:
model = Star
fields = ["firstname", "lastname", "portrait"]
def staredit(request, star_id):
instance = Star.objects.get(pk=star_id)
form = StarForm(request.POST or None, instance=instance)
context = {
"form": form,
}
return render(request, "stars/edit.html", context)
def starchange(request):
form = StarForm(request.POST, request.FILES)
if form.is_valid():
newstar.save()
context = {
"message": "The form was posted",
}
return render(request, "stars/edit.html", context)
else:
context = {
"message": form.errors,
}
return render(request, "stars/edit.html", context)
#-----edit.html
<form action="/starchange" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{{message}}
Error message:
portrait
This field is required.
You are not updating the instance, since you never have passed the instance to the view that should update it. When you make a POST requrest, the browser only submits the content of the form elements. There is no data about what has rendered the previous form, that data is lost.
You should specify the instance to update, so:
from django.shortcuts import get_object_or_404
def starchange(request, pk):
obj = get_object_or_404(Star, pk=pk)
form = StarForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
form.save()
context = {
"message": "The form was posted",
}
return render(request, "stars/edit.html", context)
else:
context = {
"message": form.errors,
}
return render(request, "stars/edit.html", context)
in the urls, you thus should specify the primary key of the object to update:
urlpatterns = [
# …,
path('starchange/<int:pk>/', views.starchange, name='starchange')
]
and in the template, you should make a POST request to a view with the given instance:
<form action="{% url 'starchange' pk=form.instance.pk %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
This is one of the main reasons why often the same view is used both for a GET and POST request, since it makes it removes a lot of duplicate logic. Furthermore it is also more clean: you can use GET to retrieve the page, and POST to submit the page.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.

Django form POST resulting in 404

I am attempting to take in a form for update or delete, run the process and either return the updated url, or the updated list of objects. I've got the dynamic url building working, but when I hit submit I get a 404. I am struggling with the how to process the POST, as it doesn't even seem to be hitting that far in the code. Code below:
urls.py
path("customers/", views.customers, name="customers"),
path("customers/customer/<int:id>/", views.customer),
forms.py
class CustomerMaintForm(ModelForm):
class Meta:
model = AppCustomerCst
fields = ('id_cst', 'is_active_cst', 'name_cst', 'address_1_cst', 'address_2_cst', 'address_3_cst',
'city_cst', 'state_cst', 'zip_cst', 'country_cst', 'salesrep_cst', 'type_cst',
'is_allowed_flat_cst', 'iddef_cst', 'date_created_cst', 'date_suspended_cst',
'date_first_tran_cst', 'date_last_tran_cst', 'is_credit_hold_cst',
'old_balance_cst', 'balance_notify_cst', 'balance_statement_cst',
'balance_conversion_cst', 'balance_cst', 'receive_emails_cst',
'contact_domain_cst'
)
labels = {'id_cst': 'Customer ID', 'is_active_cst': 'Active?', 'name_cst': mark_safe('<p>Name'),
'address_1_cst': 'Address 1',
'address_2_cst': 'Address 2', 'address_3_cst': 'Address 3', 'city_cst': 'City', 'state_cst': 'State',
'zip_cst': 'Zip', 'country_cst': 'Country', 'salesrep_cst': 'Sales Rep', 'type_cst': 'Type',
'is_allowed_flat_cst': 'Allowed Flat?', 'iddef_cst': mark_safe('<p>Id'),
'date_created_cst': 'Created Date', 'date_suspended_cst': 'Suspended Date',
'date_first_tran_cst': 'First Tran Date', 'date_last_tran_cst': 'Last Tran Date',
'is_credit_hold_cst': 'Credit Hold?', 'old_balance_cst': 'Old Balance',
'balance_notify_cst': 'Balance Notify', 'balance_statement_cst': 'Balance Statement',
'balance_conversion_cst': 'Balance Conversion', 'balance_cst': 'Current Balance',
'receive_emails_cst': 'Receive Emails?', 'contact_domain_cst': mark_safe('<p>Contact Domain')}
views.py
def customer(request, id):
if request.method == "GET":
obj = AppCustomerCst.objects.get(id_cst=id)
instance = get_object_or_404(AppCustomerCst, id_cst=id)
form = CustomerMaintForm(request.POST or None, instance=instance)
ContactFormSet = modelformset_factory(AppContactCnt, can_delete=True, fields=(
'name_cnt', 'phone_cnt', 'email_cnt', 'note_cnt', 'receives_emails_cnt'))
formset = ContactFormSet(queryset=AppContactCnt.objects.filter(idcst_cnt=id), prefix='contact')
tpList = AppCustomerTpRel.objects.filter(idcst_rel=id).select_related('idcst_rel', 'idtrp_rel').values(
'idtrp_rel__id_trp', 'idtrp_rel__tpid_trp', 'idtrp_rel__name_trp', 'sender_id_rel', 'category_rel',
'cust_vendor_rel')
TPFormSet = formset_factory(TPListForm, can_delete=True)
tp_formset = TPFormSet(initial=tpList, prefix='tp')
doc_list = DocData.objects.document_list(id)
DocFormSet = formset_factory(DocumentListForm)
DocFormSet = formset_factory(DocumentListForm)
doc_formset = DocFormSet(initial=doc_list, prefix='doc')
context = {'form': form, 'formset': formset, 'tp_formset': tp_formset, 'doc_formset': doc_formset, 'id': id}
print(form.errors)
return render(request, 'customer.html', context=context)
elif '_edit' in request.POST:
print(id, request.POST)
cust_name = request.POST['name_cst']
instance = get_object_or_404(AppCustomerCst, name_cst=cust_name)
form = CustomerMaintForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return HttpResponseRedirect("/customers/customer/{id}")
else:
context = {'form': form, 'contact_form': contact_form}
return redirect(request, 'customer.html', context=context)
elif '_delete' in request.POST:
cust_name = request.POST['name_cst']
instance = get_object_or_404(AppCustomerCst, name_cst=cust_name)
form = CustomerMaintForm(request.POST, instance=instance)
if form.is_valid():
AppCustomerCst.objects.filter(id_cst=id).delete()
return render(request, 'customers.html')
else:
pass
customer.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% url customer string %}
{% block content %}
<form method="post" action="/customer">
{% csrf_token %}
<div style="height:300px;overflow:auto;">
{{ form }}
</div>
<input type="submit" value="Edit" name="_edit">
<input type="submit" value="Delete" name="_delete">
</form>
{% endblock %}
The 404 is because the form sends a POST to /customer and your URLs are
/customers/
/customers/customer/<int:id>/
So I'd first add a name to your path so it becomes something like path("customers/customer/<int:id>/", views.customer, name='customer')
Then change your form action to use django URL reversal;
<form method="post" action="{% url 'customer' id=id %}">
By doing this it'll generate the URL for you based on the ID of the customer form you're on, assuming id is in the context which it appears to be from your view code.
That should solve the 404 and improve things a little.

How to differentiate an HTTP Request submitted from a HTML form and a HTTP Request submitted from a client in django?

I have a model in django that is as below:
class Student(Model):
nationality = CharField(max_length=200)
I have a form as below:
class StudentForm(ModelForm):
class Meta:
model = Student
fields = ('nationality', )
my template is as below:
<form method="GET" novalidate id="my_form">
{{ student_form.as_p }}
</form>
<button type="submit" form="my_form" name="my_form">Submit</button>
I have a view as below:
def home(request):
if request.POST:
return HttpResponse('This should never happen')
else:
if request.GET.get('nationality'):
student_form = StudentForm(request.GET)
if student_form.is_valid():
return HttpResponse('get from form submission')
else:
student_form = StudentForm()
print('get from client request')
return render(request, my_template, {'student_form': student_form})
The problem with this method is that if sb submits the form without filling the nationality field, the result would be 'get from client request' that is wrong because the validation error should happen because the request is from submitting a form not direct client get request.
What I can do is that I add a hidden field to my form as below:
<form method="GET" novalidate id="my_form">
{{ student_form.as_p }}
<input type="hidden" id="hidden" name="hidden" value="hidden">
</form>
<button type="submit" form="my_form" name="my_form">Submit</button>
and change my view as below:
def home(request):
if request.POST:
return HttpResponse('This should never happen')
else:
if request.GET.get('hidden'):
student_form = StudentForm(request.GET)
if student_form.is_valid():
return HttpResponse('get from form submission')
else:
student_form = StudentForm()
print('get from client request')
return render(request, my_template, {'student_form': student_form})
However, there should be another method to do this. There should be something in HTTP to tell us the request is fresh get request from client or it is from form submission. I am looking for this.
request.GET is a dictionary. request.GET.get('nationality') is falsy if the dictionary doesn't have the key 'nationality' but also if its value is the empty string (?nationality=). So you should just check the presence of the key, that way you know the form was submitted:
if 'nationality' in request.GET:
# initialise form with `request.GET`
else:
# initial unbound form

Proper way to handle multiple forms on one page in 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()