I have several forms in my application, and like all forms after the users have completed them, I would like to redirect them to a form completion page, which the particulars of the forms are displayed after the submission.
Now, assuming that my forms all have different attributes, how can I create a common view and display template such that I do not have to re-create the them since they are all similar other than the particulars of the forms to be displayed?
According to this Django: How do I redirect a post and pass on the post data you can't redirect with the post data, so you have to either 1) display the success page at the same url as the form or 2) put all the variables into the redirect URL.
If you are ok displaying the success at the same URL as the form, i.e. /my-form/ not /my-form/success/, then you could mutate the canonical form processing view as such:
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# ...
return render_to_response('form_success.html', {'form' : form,} )
else:
form = ContactForm()
return render_to_response('contact.html', {'form': form,})
Then in the template you could do:
<h3>Success! You submitted:</h3>
{% for field in form %}
{{field.label}}: {{field.data}}
{% endfor %}
Related
I am trying to understand how a very regularly used code-form in Django views.py actually works. I see the following (or a variation) used a lot, but I can’t find a line-by-line explanation of how the code works – which I need if I am to use it confidently and modify when needed.
Can you let me know if I have understood how Django processes these various components? If not, please indicate where I have misunderstood.
I will start with the model then introduce urls.py the view and the form. I will go through the relevant parts of the code. I will consider:
The model:
#models.py
class CC_Questions(models.Model):
# defining choices in Model : https://docs.djangoproject.com/en/4.0/ref/models/fields/
personality = [
('IF','IF'),
('IT','IT'),
('EF','EF'),
('ET','ET'),
]
q_text = models.CharField('Question text', max_length=200)
#C1_Type = models.CharField('Choice 1 Type', max_length=2)
C1_Type = models.CharField(choices=personality, max_length=2)
Choice1_text = models.CharField('Choice 1 text', max_length=100)
#C2_Type = models.CharField('Choice 2 Type', max_length=2)
C2_Type = models.CharField(choices=personality, max_length=2)
Choice2_text = models.CharField('Choice 2 text', max_length=100)
#
def __str__(self):
return self.q_text[:20]
The url
#
#urls.py
app_name = ‘polls’
urlpatterns = [
…..
# ex: /polls/p2create
path('p2create/', p2_views.p2create, name='p2create'),
…
The view:
#views.py
from.forms import Anyform
#
def p2create(request):
if request.method == 'POST':
form = AnyForm(request.POST)
if form.is_valid():
form.save()
return redirect('/polls/p2')
else:
form = AnyForm()
context = {'form' : form}
return render(request, 'pollapp2/create.html', context)
#
The form:
#forms.py
#
….
from django import forms
from .models import ….. CC_Questions …
…….
class AnyForm(forms.ModelForm):
class Meta:
model = CC_Questions
fields = ['q_text', 'Choice1_text', 'Choice2_text','C1_Type','C2_Type']
The template:
#
# Create.html
#
…..
{% load widget_tweaks %}
…..
<form method="POST">
{% csrf_token %}
…
<div class="row">
<div class="col-lg-5">
<div class="form-group">
<label for="Choice1_text ">Choice 1</label>
{% render_field form.Choice1_text class="form-control" %}
<label for="C1_type">Type 1</label>
{% render_field form.C1_Type class="form-control" %}
…….
Does the code operate as follows?
The user enters URL in browser: http://localhost:8000/polls/p2create/
The urls.py picks the view to execute
path('p2create/', p2_views.p2create, name='p2create'),
views.py runs the view:
def p2create(request):
Now, as no form has yet been "identified" or "loaded" (??) the following test fails:
if request.method == 'POST':
so the Else clause executes
else:
form = AnyForm()
that "sets" the variable form to "AnyForm()"
The following line creates a dictionary, named context, that creates a key 'form' that is linked with the value form (=Anyform)
context = {'form' : form}
The following line searches for create.html in the template directory, passing the directory context as a parameter
return render(request, 'pollapp2/create.html', context)
Then template, create.html, displays various input boxes (??) from :
<label for="Choice1_text ">Choice 1</label>
{% render_field form.Choice1_text class="form-control" %}
When the submit button is pressed on the displayed page, this "passes back" (??) the {% render_field .. %} values to the view (?????)
<form method="POST">
...
<div class="col-lg-4">
<button type="submit" class="btn btn-info">Submit</button>
</div>
...
</form>
the view is executed again (????) , but this time request method is set to "POST" because of the form method="POST" in the template (?????)
if request.method == 'POST':
Now the same form , AnyForm , is "reloaded" (????) but with the parameter value "POST"
if request.method == 'POST':
form = AnyForm(request.POST)
Now if the form is valid (I have no idea what a "valid" or "invalid" form is)
if form.is_valid():
then all the values "captured" in the template by (???)
<label for="Choice1_text ">Choice 1</label>
{% render_field form.Choice1_text class="form-control" %}
(??????)
are written by the view (?????)
form.save
to the corresponding fields in the ModelForm (?????)
class Meta:
model = CC_Questions
fields = ['q_text', 'Choice1_text', 'Choice2_text','C1_Type','C2_Type']
The view then redirects and loads the home page in the browser
return redirect('/polls/p2')
Ok, So with the help of the references (mentioned below) and the workflow suggested by you, let us first see, the Django MVT workflow and then address the various questions asked in between the post.
WebForms with Django MVT in brief:
Prepare data of several different types for display in a form.
Render data as HTML.
Edit, enter data using a convenient interface.
Validate and clean up the data.
Return data to the server.
Save, delete or pass on for further processing.
The URL:
When a user requests a page from your Django-powered site, this is the algorithm the system follows to determine which Python code to execute. Which is handled by our views.py. From the frontend, if the request is not 'POST', then it is a GET request, hence the else part of the handling function in the views.py executes. This you have mentioned already.
The View: - Form data sent back to a Django website is processed by a view, generally, the same view which published the form. This allows us to reuse some of the same logic. To handle the form we need to instantiate it in the view for the URL where we want it to be published.
If we use request.POST, as in this line:
form = AnyForm(request.POST)
It transforms the form into a bound form (Form bound with data). Read more about it here.
Questioned By You (QBY) - When the submit button is pressed on the displayed page, this "passes back" (??) the {% render_field .. %} values to the view (?????)
So, yes, If the action attribute is not mentioned in the form then the data will be passed to the view responsible for displaying the form.
QBY - the view is executed again (????), but this time request method is set to "POST" because of the form method="POST" in the template (?????)
The button type submit, submits the form and make the request a POST request. The Django template sends that submitted data in the request.POST.
QBY - Now the same form, AnyForm, is "reloaded" (????) but with the parameter value "POST"
Here, if the return method at the end of the POST condition is HttpResponseRedirect it will redirect the user to the mentioned URL page, but if the same HTML is used to be rendered then the form will be displayed as a bound form. (It depends upon the requirements)
QBY - Now if the form is valid (I have no idea what a "valid" or "invalid" form is)
Form.is_valid()
The primary task of a Form object is to validate data. With a bound Form instance, call the is_valid() method to run validation and return a boolean designating whether the data was valid. If yes, then the data is being saved in the model.
QBY - then all the values "captured" in the template by (???)
All the values are sent to views in the request.POST. We can check it by
print(request.POST)
QBY - are written by the view (?????), form.save to the corresponding fields in the ModelForm (?????)
Save method is called on the Django ModelForm instance in order to save the data to the database. Calling save would run the validation check. A ValueError will be raised if the data in the form doesn't validate.
This saved data can now be processed further.
References:
[https://docs.djangoproject.com/en/4.0/topics/forms/][2]
[https://www.tangowithdjango.com/book/chapters/models_templates.html][3]
[https://docs.djangoproject.com/en/4.0/ref/forms/api/][4]
I am rendering a dropdown which displays a list of integers. This is the only field in the form/view. Once that form is submitted, the integer selected should be passed to the URL of the next view which is rendered on submission of the previous form.
I am getting a 404 when I attempt this.
Here is what I am currently trying:
forms.py
#this is the dropdown field
class ManifestDropDown(forms.Form):
reference = forms.ModelChoiceField(queryset=Orders.objects.values_list('reference', flat=True).distinct(),
empty_label=None)
views.py
#this is the view where the dropdown is submitted
def manifest_references(request):
if request.method == 'POST':
form = ManifestDropDown(request.POST)
if form.is_valid():
reference_id = form.cleaned_data.get('reference')
form.save()
return render('manifest', reference_id=reference_id)
query_results = Orders.objects.all()
reference_list = ManifestDropDown()
context = {
'query_results': query_results,
'reference_list': reference_list,
}
return render(request, 'manifest_references.html', context)
#this is the view where the value should be displayed in the url
def manifest(request, reference_id):
form = CreateManifestForm(request.POST)
if request.method == "POST":
....
data = Manifests.objects.all().filter(reference__reference=reference_id)
form = CreateManifestForm(initial={
'reference': Orders.objects.get(reference=reference_id),
})
total_cases = Manifests.objects.filter(reference__reference=reference_id).aggregate(Sum('cases'))
context = {
'reference_id': reference_id,
'form': form,
'data': data,
'total_cases': total_cases['cases__sum'],
}
return render(request, 'manifest_readonly.html', context)
urls.py
#url which displays the manifest view above
url(r'^manifest/(?P<reference_id>\d+)/$', manifest, name='manifest'),
url(r'^references_manifests', manifest_references, name='references_manifests'),
manifest_references.html
<div class="container">
<br>
<br>
<br>
<form method="POST" action="references_manifests">
{% csrf_token %}
{{ reference_list }}
<button type="submit" class="btn btn-primary" name="button">Create Proforma</button>
</form>
</div>
To dynamically change the URL that you're actually submitting to, you would need to use JavaScript.
But an alternative is to submit back to the manifest_references view, then redirect from there to manifest. (Note, you should always be redirecting, not rendering, after a successful submission anyway. And no need to call form.save(), this isn't a modelform so there is nothing to save.)
def manifest_references(request):
if request.method == 'POST':
form = ManifestDropDown(request.POST)
if form.is_valid():
reference_id = form.cleaned_data.get('reference')
return redirect('manifest', reference_id=reference_id)
You can do two things:
Call the manifest view directly.
Redirect the user to the manifest page.
The first one should be done like this:
if form.is_valid():
reference_id = form.cleaned_data.get('reference')
form.save()
return manifest(request, reference_id)
The second one can be done like this:
if form.is_valid():
reference_id = form.cleaned_data.get('reference')
form.save()
return HttpResponseRedirect(reverse('manifest', reference_id = reference_id))
It doesn't really matter which one you do, although I would recomment redirecting the user to the correct page, because then a refresh will not resend the form the user has entered.
I can't get Django to retain entered form values when validation fails. I have created a test page and used the code in the Django guide, and it still doesn't work.
From the Django guide:
We call the form’s is_valid() method; if it’s not True, we go back to the template with the form. This time the form is no longer empty (unbound) so the HTML form will be populated with the data previously submitted, where it can be edited and corrected as required.
And here is my code:
#views.py
def test(request):
if request.method == 'POST':
form = NewAwardForm(request.POST)
if form.is_valid():
return redirect('/test/')
else:
form = NewAwardForm()
print(form)
return render(request, 'test.html', {'form': form})
test.html
<form action="/test/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
forms.py
class NewAwardForm(forms.Form):
recipient_email = forms.EmailField(label='Recipient Email', max_length=100)
Can anyone spot my mistake?
Django renders the email field with type="email". The HTML validation will prevent the browser from submitting an invalid email to the server.
You could test the view by adding a clean method to the form that rejects any emails ending in "example.com".
class NewAwardForm(forms.Form):
recipient_email = forms.EmailField(label='Recipient Email', max_length=100)
def clean_recipient_email(self):
if self.cleaned_data['recipient_email'].endswith('example.com'):
raise forms.ValidationError("No example.org emails allowed")
Now, if you enter hello#example.com and submit the form the HTML validation will allow the request to be submitted, the clean_recipient_email method will raise a validation error, and you'll see that your view and template display the invalid form as expected.
I am trying to create a simple subscription form in the front page of my site. I created the view with a model form (the model contains only name and e-mail as attributes). When I go to the root address (GET) it works fine and loads the form. I then fill it with some data, click the submit button (the form action can either be set to '' or '/', the result is the same) and it redirects to the same root page, but it does not load anything, the page remains blank. In the console I can see it calling through POST method, but not even the first print of the view function gets printed.
Any ideas? I know it must be something silly, but I spent sometime in it and haven't yet found out what it could be.
In urls.py:
url(r'', FrontPage.as_view(template_name='rootsite/frontpage.html')),
In rootsite/views.py
class FrontPage(TemplateView):
'''
Front (index) page of the app, so that users can subscribe to
have create their own instance of the app
'''
template_name = 'rootsite/frontpage.html'
def get_context_data(self,
*args,
**kwargs):
c = {}
c.update(csrf(self.request))
print self.request.method
if self.request.method is 'POST':
print 'OK - POST IT IS, FINALLY'
form = NewUsersForm(self.request.POST)
print form.__dict__
if form.is_valid():
form.save()
return HttpResponseRedirect('/' + '?thanks=1')
else:
form = NewUsersForm()
return {'form':form}
You can't return a redirect from within get_context_data - it's for context data only, hence the name.
You should really be using a proper form view for this, which includes methods for redirecting after form validation.
Did you include a csrf_token in your template (as per the example here: http://www.djangobook.com/en/2.0/chapter07.html)?
<form action="" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<input type="submit" value="Submit">
</form>
I could be wrong, but I thought Django wouldn't accept a POST request without a csrf token?
I must just be overlooking something here, but after stripping everything back - I can't seem to get Django to render either a Form or ModelForm to my template. Code below:
#forms.py
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
#views.py
def index(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid(): # All validation rules pass
return HttpResponseRedirect('/thanks/')
else:
form = ContactForm() # An unbound form
return render_to_response('home/new_website.html',{
'form': form,
})
#new_website_html
<html>
<body>
<form method = "post" action = "">
{{ form.as_p }}
</form>
</body>
</html>
I had the same issue and after many tries this is my solution:
Change the view from this
#views.py
else:
form = ContactForm() # An unbound form
return render_to_response('home/new_website.html',{
'form': form,
})
To that:
#views.py
else:
form = ContactForm() # An unbound form
return render_to_response('home/new_website.html',{
'form': form,
})
Or a simple newline is enough:
#views.py
else:
form = ContactForm() # An unbound form
return render_to_response('home/new_website.html',{
'form': form,
})
Strange thing is, after these changes the original code worked.
Any error page or just blank page? Actually I just try your code and get form rendering correct(I don't know how to insert local result image here)
Please make sure DEBUG=TRUE in settings.py while it's not the problem.
#Burhan I think indent problem only happens because he edits it in stackoverflow.
Btw, your form doesn't have a submit button, maybe add it in html like