WTForms: passing form values back to the user on post - python

When both the GET and POST methods are in the same handler class, and I want to populate form fields with user input after failed form validation, I do this...
Class CommentHandler(BaseHandler):
def get(self, form=None):
if form is None: # create new form unless populated form is passed in
form = CommentForm()
# query DB and create template context
self.render('page.html', **context)
def post(self):
form = CommentForm(self.request.POST)
if form.validate():
# populate entity with form data and save to DB
return self.redirect_to('page')
self.get(form=form) # pass populated form back to user for editing
I don't know if this is the best way to get form data back to the user, but it seems to work. My question is: how do I pass that data back into the form if the GET and POST methods are in different handler classes?
class PageHandler(BaseHandler):
def get(self):
# displays form to user
class CommentHandler(BaseHandler):
def post(self):
# processes POSTed form data...
# but if form.validate() fails,
# how can I pass the form data back to the user
# so they can edit their form input?

When you post and call validate() WTForms binds the form encoded data to the Form instance. An http POST can return a response just like a GET this is why you sometimes get those funny messages in your browser when a server application has failed validation and you try to refresh. Its because the refresh action is going to invoke the GET processing pipeline and you will lose your POST data.
What you need to do instead of delegating back to the get implementation you just need to render a response from your post implementation that passes Form instance with the data bound to it back in the response. If you have set your template up in the recommended way the data will automatically show up in the appropriate fields. Below is a snippet of what your Handler might look like.
def post(self):
form = CommentForm(self.request.POST)
if form.validate():
# populate entity with form data and save to DB
return self.redirect_to('page')
# If we are here it means we failed validation
# We need to send back the data the use supplied
# with error messages so we can re-render the form
# with their data and error messages indicating why
# it was rejected.
self.render('page.html', form=form) # pass populated form back to user for editing
This of course assumes that your page.html knows what to do with the Form instance.

Here's one workflow that may help to solve your problem. I chose to provide a workflow instead of a specific code snippet in order to avoid being too prescriptive in the technology you use to solve the problem.
GET
if session contains form data:
add form data from session to template context
display form
POST
if form validates:
clear this form data from session
save to db
else:
save form data to session
redirect to GET handler

Related

Pass data between different views in Django

I have a basic view that retrieves some data, renders my page and sends some data to this page:
def myview(request)
one = values.objects.get(user=request.user).address
two = values.objects.get(user=request.user).number
return render(request, "main/mytemplate.html",
context={'address': one, 'numbers': two})
So the values retrieved by those two queries are shown on my page.
Now, on the same page, called mytemplate.html, i'm using another view, which is supposed to handle a form and some other operations:
def secondview(request):
if request.method == 'POST':
if 'button1' in request.POST:
form = MyForm(request.POST)
# check whether it's valid:
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
return HttpResponseRedirect(request.path_info)
else:
form = MyForm()
return HttpResponse('it works!')
How can i use the data retrieved by those two queries in the second view? The queries are executed when the page is loaded by the first view. Then, in the same page the second view is used. I want to use the two variables one and two in the second view. Is there a way to do this in Django?
Why don't you make the same queries in the second view? Because i would like the second form to be as fast as possible in terms of reload, without having to do a DB query each time that view is used. Also, since i already retrieved those values when the page is opened, it would be a waste to do that again.
I don't know if this question is clear enough, but the core of it is: can i pass variables/data between two views in django?
You have few options:
Simplest way: include this data in request to the second view (as part of the form data, see an example below). You might even use a single view: if POST was send - store data else do request and show it on a page.
Use cache for that (see an example below) - But I'd recommend to use Django built-in package. Here is a basic example how to use it
Use Django Sessions (see an example below) - it is working option despite of that they have another purpose. When customer is loaded Django will load full session record, so you'll have all data in request.session variable. But that is bad practice: you can get a lot of data duplication and increased database memory consumption.
Use API (e.g. using DjangoRestFramework) together with usual Django app. So you'll just get data you need, and when you need. These API requests can also be cached so it is fast solution.
Yes, you can use session to pass data across views. A session works like a temporary server storage and keeps the needed data in a dictionary form.
For instance, add the following lines to myview:
request.session['one'] = one
request.session['two'] = two
Then, retrieve the data in secondview by referring to the session:
one = request.session['one']
two = request.session['two']
you can use cookies. but if you want more secure your request i suggest to you using redis and the python client for redis
file settings.py
redis = redis.Redis(host='localhost', port=6379, db=0)
file views.py
def view1(request):
redis.set("foo", "boo")
def view2(request):
boo = redis.get("foo")
Why not just saving the results of the two queries as hidden fields in the form rendered by the first template ?
<form ...>
<input type="hidden" id="address" name="address" value="{{address}}">
<input type="hidden" id="numbers" name="numbers" value="{{numbers}}">
...
Then, you can either add 'address' and 'numbers' form fields to MyForm
address = forms.CharField(widget=forms.HiddenInput(), required=False)
...
or just retrieve the values from request.POST

django - how to implement a 2-step publish mechanism

I'm new to both web development and django so maybe that's a noob question.
I want to do the following:
Ask user to fill some form and submit it.
Then, parse and format the content and display it back to the user to let him verify it.
User can accept the result or go back to the previous view, update data and resend.
This is as far as I can think:
views.py
def add_content(request):
if request.method == 'POST':
form = AddContentForm(request.POST)
if form.is_valid():
content = form.save(commit=False)
return verify_content(request, content)
else:
form = AddContentForm()
return render(request, 'myapp/add_content.html', {'form' : form})
def verify_content(request, content):
return render(request, 'myapp/verify_content.html', {'content' : content})
The verify_content template will obviously contain two buttons ('back', 'ok'), but I don't know how to pass the content object to a view for saving it in the db, or send it back to the previous view from there. Should I use js? Can i do it with just server side code?
Maybe my whole logic is wrong. Should I save the object in the db before verification and then delete it if needed (sounds ugly)? What is a good way to implement this?
Thanks in advance for your time.
You could use the users session for this:
request.session['content'] = content
and in the view where the user should verify his input do:
content = request.session['content']
and voilá you got the content between 2 views.
Django also secures that users can't tinker with its data by either saving it server side, or in a signed cookie.
I would save the form with commit=True in the add_content view, and would add a verified field or something to the model. Then you can append the pk as GET parameter to the link which will get you back to add_content view from verify. You can extract the parameter from request.GET dict.

Conditional redirecting in Pyramid

I am looking for a way to redirect users to different routes/templates that is compatible with using #view_config.
I have a function that reads in an uploaded file and attempts to create a new model based on the file content. I was wondering if there was a clean way that I can redirect the user to one of two urls, based on whether the creation of the new model succeeds or there is an error.
If the model creation is successful, I want to redirect the user to the model page. If there is an error, I want to redirect the user to an error page. However, am having trouble breaking out the original function (load_model)'s view_config when rendering the error page.
#view_config(renderer="error.mak")
#view_config(renderer="model.mak",
route_name='load_model_route')
def load_model(self):
...
model = Model.find_model(model_name)
if model:
#redirect to model_route
else:
#redirect to model_error_route
Each route would have a #view_config that binds it to a function.
What you are asking is not "How to redirect" but "How to change renderer in the view function". To answer quickly, I think you could use:
request.override_renderer = 'other-renderer.mak'
But I don't think it's a good idea. Here's the usual pattern that is used most of the time to handle form submission:
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.url import route_url
from your_app import Model, some_stuff, save_to_db
#view_config(route_name='new_model',
renderer='model/new.mak', request_method='GET')
def new(request):
"""Shows the empty form."""
return {'model': Model(), 'errors': {}}
#view_config(route_name='create_model',
renderer='model/new.mak', request_method='POST')
def create(request):
"""Receives submitted form."""
model = some_stuff()
if len(model.errors) = 0: # is valid
# do your stuff, then redirect
save_to_db(model)
return HTTPFound(route_url('show_model', request, model_id=model.id))
# is invalid
return {'model': model, 'errors': model.errors}
#view_config(route_name='show_model',
renderer='model/show.mak', request_method='GET')
def show(request):
"""Shows details of one model."""
model = Model.find(request.matchdict['model_id'])
if model is None:
return HTTPNotFound()
return {'model': model}
In short:
You show an empty form when you have a GET on the route for a new model.
You handle the form submission (POST) in a different view function
If the data is valid, you do your stuff, then you redirect with HTTPFound
If the data is invalid, you return a dict to show the form again, with errors this time
You use the same renderer in the GET and POST, but the one in the POST is only used in case of invalid data (otherwise, you redirect).
You have another view function to show the created model.
Antoine showed a more general solution, but here's an attempt to stick to your basic format.
The idea is that you want to do some processing and then redirect the user to either a success or a failure page. You can redirect to a failure page if you want to just the same way you redirect to a success page, but I'll show a different version where you just show the error page in load, but if load works you redirect to the model.
config.add_route('show_model', '/models/{id}')
config.add_route('load_model', '/load_model')
#view_config(route_name='load_model', renderer='error.mak')
def load_model(self):
# ...
model = Model.find_model(model_name)
if model:
return HTTPFound(self.request.route_url('show_model', id=model.id))
return {} # some dict of stuff required to render 'error.mak'
#view_config(route_name='show_model', renderer='model.mak')
def show_model(self):
id = request.matchdict['id']
model = Model.get_model_by_id(id)
# be aware model could fail to load in this new request
return {'model': model} # a dict of stuff required to render 'model.mak'

How do I forcefully clean a field and redisplay it in Django?

How can I clean the data in a form and have the cleaned data redisplayed instead of the submitted data?
There are several fields in my form, and every time the user submits it, it should be redisplayed with the values the user entered. However, some of the fields I would like to clean and update for the user. More specifically, I have a field FriendlyIntegerField(forms.CharField) in which I override to_python to not only call int(str(value)), but also set any negative number to 0 etc. I do not want to redisplay the form with the invalid data and have the user fix it himself (which is how Django wants me to do it).
I don't have a problem cleaning the data and use it for the rest of my view-function, but how can I update the actual form with this data?
By the way, the form does not reflect a structure in my data model, and so inherits from Form, not ModelForm.
Edit:
My Field (in a stripped down version) looks like this:
class FriendlyIntegerField(forms.CharField):
def to_python(self, value):
try:
return str(int(str(value).replace(' ','')))
except:
raise forms.ValidationError('some error msg')
My Form (in a stripped down version) looks like this:
class SearchForm(forms.Form):
price_from = FriendlyIntegerField()
price_to = FriendlyIntegerField()
And my view:
def search(request, key):
if request.method == 'POST':
form = SearchForm(request.REQUEST)
if not form.is_valid():
print "Form not valid"
else:
form = SearchForm()
return render_to_response('path_to_template', {'form' : form}
If, after you've cleaned your form with is_valid(), you render that cleaned form with your view, rather than redirect to a new page, you'll see the cleaned data in your page.
(If you wanted the user to see this cleaned data and then properly submit it, you could use a hidden field to track whether the form data has already been cleaned, but this isn't without complications...)

When should I submit my Django form's results?

The contents of my form must be submitted to another application server for validation and execution (specifically, I call a RESTful web service with the posted values in the form). The service will either return a 200 SUCCESS or a 400/409 error with a body that describes the exact field errors.
When should I do this submission? Should I do it in the view:
if form.is_valid:
result = submit_to_service(POST)
if result.code in (400, 409):
somehow_set_errors_on_the_form(form)
else:
go_on...
Or in the Form.clean method?
def clean(self):
result = submit_to_service(POST)
if result.code in (400, 409):
for field in result.errors:
self._errors[field].append(result.errors[field])
else:
pass
Which of these is clearer?
validation and execution
No execution or stateful changes in the form clean(). Please. The form's clean() should only mess with data on the form, not anywhere else.
If there is a stateful change, it must be in a view function inside a non-GET request handler.
I usually encapsulate these type of logic in the form. Since you use the form to validate the data you also use it to send the data. This makes sense because the form already knows about the data and its types etc (it has the cleaned_data dictionary).
But processing data and changing state of your application should not live directly inside your validation logic (e.g. in your clean method). You should put it in an extra method of your form - like ModelForm is doing it with the save() method.
So my suggestion is to have an extra method named save() (if the method actually saves your processing to the REST service) or post_result() or something similar that fits better.
Here is an example:
# forms.py
class ValidateDataForm(forms.Form):
...
def clean(self):
# validation logic
def save(self):
post_results_to_service(self.cleaned_data)
# views.py
def view(request):
if request.method == 'POST':
form = ValidateDataForm(request.POST)
if form.is_valid():
form.save()
else:
form = ValidateDataForm()
The above assumes that the REST service is changing state for your
application, e.g. it implements some business logic. If this is not the case
and you only use the service as validation for the input data on your form -
and use the form data then for something different - I would suggest something
different.
In this case the code should go into the clean() method like you suggested
in your second code example.

Categories

Resources