When should I submit my Django form's results? - python

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.

Related

Avoid recreating the GET logic in the POST part of a view

How can I avoid duplicating the logic code from the GET block in the view below?
The logic is view-specific enough that I don't feel it makes sense to put a helper function in a separate utils.py.
'''
show_stuff view shows different stuff depending on how many other
POST requests have been submitted to view and saved to the DB.
All the users access the same URL randomly, so I don't believe it's possible to
split things up like "view_for_template1", "view_for_template2" in urls.py
'''
def show_stuff(request, url_dispatch_var1, url_dispatch_var2=None):
if request.method == "GET":
#30 lines of logic determining which Template and Context to return
if request.method =="POST":
#10 lines of logic determining which Form type to save
#then, the same 30 lines of logic as the GET block to determine
#which Template and Context to return
You can usually do something like the following:
def show_stuff(request, url_dispatch_var1, url_dispatch_var2=None):
if request.method =="POST":
#10 lines of logic determining which Form type to save
# redirect if form is valid
else:
# this is a GET request
form = MyForm() # unbound form for get request
# 30 lines of logic to determine which Template and Context to return
return render(request, template, context)
Note that after a successful post request, the usual approach is to redirect to prevent duplicate submissions.
This might be a case where class based views are useful. You could subclass FormView, then override get_context_data, get_template_names and so on.
Maybe instead of returning body for POST request you could redirect user to your GET view ?

WTForms: passing form values back to the user on post

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

Is there a way to pass extra fields to a form with WTForms?

I am writing a change password form, and I would like to pass the original password to the form for validation purposes, but the only way to do that is to make it a hidden field and pass it in with the rest of the data. I obviously don't want to do that, I'd rather just pass it to the form constructor from within the view, but if it's not passed in with the formdata then that won't work.
class MyForm(Form):
...
original_password = HiddenField()
...
def validate_current_password(form, field):
if field.data != form.original_password.data:
ERROR
form = MyForm(request.POST, original_password=password) Does not work unless request.POST is empty, or unless I actually render and submit the original password with the form. form.original_password.data is empty otherwise.
For those of you familiar with formencode's "state" variable, I solved this by subclassing Form and adding a state variable with information to assist in validation.

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 associate input to a Form with a Model in Django?

In Django, how do I associate a Form with a Model so that data entered into the form are inserted into the database table associated with the Model? How do I save that user input to that database table?
For example:
class PhoneNumber(models.Model):
FirstName = models.CharField(max_length=30)
LastName = models.CharField(max_length=30)
PhoneNumber = models.CharField(max_length=20)
class PhoneNumber(forms.Form):
FirstName = forms.CharField(max_length=30)
LastName = forms.CharField(max_length=30)
PhoneNumber = forms.CharField(max_length=20)
I know there is a class for creating a form from the the model, but even there I'm unclear on how the data actually gets to the database. And I'd like to understand the inner workings before I move on to the time-savers. If there is a simple example of how this works in the docs, I've missed it.
Thanks.
UPDATED:
To be clear -- I do know about the ModelForm tool, I'm trying to figure out how to do this without that -- in part so I can better understand what it's doing in the first place.
ANSWERED:
With the help of the anwers, I arrived at this solution:
Form definition:
class ThisForm(forms.Form)
[various Field assignments]
model = ThisModel()
Code in views to save entered data to database:
if request_method == 'POST':
form = ThisForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.items():
setattr(form.model, key, value)
form.model.save(form.model)
After this the data entered in the browser form was in the database table.
Note that the call of the model's save() method required passage of the model itself as an argument. I have no idea why.
CAVEAT: I'm a newbie. This succeeded in getting data from a browser to a database table, but God only knows what I've neglected or missed or outright broken along the way. ModelForm definitely seems like a much cleaner solution.
Back when I first used Forms and Models (without using ModelForm), what I remember doing was checking if the form was valid, which would set your cleaned data, manually moving the data from the form to the model (or whatever other processing you want to do), and then saving the model. As you can tell, this was extremely tedious when your form exactly (or even closely) matches your model. By using the ModelForm (since you said you weren't quite sure how it worked), when you save the ModelForm, it instantiates an object with the form data according to the model spec and then saves that model for you. So all-in-all, the flow of data goes from the HTML form, to the Django Form, to the Django Model, to the DB.
Some actual code for your questions:
To get the browser form data into the form object:
if request.method == 'POST':
form = SomeForm(request.POST)
if form.is_valid():
model.attr = form.cleaned_data['attr']
model.attr2 = form.cleaned_data['attr2']
model.save()
else:
form = SomeForm()
return render_to_response('page.html', {'form': form, })
In the template page you can do things like this with the form:
<form method="POST">
{{ form.as_p }}
<input type="submit"/>
</form>
That's just one example that I pulled from here.
I'm not sure which class do you mean. I know that there were a helper, something like form_for_model (don't really remember the exact name; that was way before 1.0 version was released). Right now I'd it that way:
import myproject.myapp.models as models
class PhoneNumberForm(forms.ModelForm):
class Meta:
model = models.PhoneNumber
To see the metaclass magic behind, you'd have to look into the code as there is a lot to explain :]. The constructor of the form can take instance argument. Passing it will make the form operate on an existing record rather than creating a new one. More info here.
I think ModelForm.save documentation should explain it. With its base class (Form) you would need to use the Form.cleaned_data() to get the field values and set them to appropriate Model fields "by hand". ModelForm does all that for you.
The Django documentation is pretty clear on this subject. However, here is a rough guide for you to get started: You can either override the form's save method or implement that functionality in the view.
if form.is_valid() # validation - first the fields, then the form itself is validated
form.save()
inside the form:
def save(self, *args, **kwargs):
foo = Foo()
foo.somefield = self.cleaned_data['somefield']
foo.otherfield = self.cleaned_data['otherfield']
...
return foo.save()

Categories

Resources