Django: POST form requires CSRF? GET doesn't? - django

Are forms that use the POST method required to have CSRF protection? I'm following a book and the code examples throw 403 errors. I did some searching and it seems as if I need to enable CSRF in all my forms.
My questions are:
Does Django now require that all POST forms be protected from CSRF?
All I need to do to accomplish this is add 'django.middleware.csrf.CsrfViewMiddleware', return render_to_response(template,dictionary,context_instance=RequestContext(request), and add '{% csrf_token %}' in the corresponding form? Am I missing anything here?
When I do this, the form works fine. When any of these pieces are missing, it fails to 403. I just want to make sure I'm doing it RIGHT. :)
Thanks in advance.
edit:
For some reason this code doesn't make sense to me but it doesnt return any error. Please ignore the primitive validation as I haven't gotten to the section of the book where it shows the more efficient way to do it yet.
def contact(request):
errors = []
if request.method == 'POST':
if not request.POST.get('subject',''):
errors.append('Enter a subject')
if not request.POST.get('message',''):
errors.append('Enter a message')
if request.POST.get('email', '') and '#' not in request.POST['email']:
errors.append('Enter a valid email address')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply#example.com'), ['siteownder#example.com'],)
return HttpResponseRedirect('/contact/thanks/')
return render_to_response('contact_form.html', { 'errors': errors }, context_instance=RequestContext(request))
My issue is with the very last line of this view function. It is only called if the request.method != POST. This seems completely wrong to me. Shouldn't I be calling "context_instance=RequestContext(request)" when it's doing a POST?

POST should be used for sensitive information, such as passwords, and django requires securing it with csrf_token; GET should be used for bookmarkable stuff which doesn't need to be secured, like searches. You ARE doing it RIGHT.
EDIT
You shouldn't be calling context_instance=RequestContext(request) when it's doing a POST, you should be calling it regardless of the request type. Look at it like this:
Is it a POST? this means the form was submitted. we validate the form, and redirect the user to another page if the form is OK, or show the form again to the user, with the errors.
Is it a GET? this means the form was not submitted, but other stuff is happening which we don't care about (some referrer link or other stuff). Show the form anyway
Actions in italic are done by the last return, regardless of the if.

Related

Seemingly weird logic of HTTP requests in Django

I have a problem that I solved but the fact that I don't understand how tells me that there is something basic I'm missing. Hope somebody can help me.
So I have a class based update view with inline formsets. I use crispy forms to render the view. The code below was first working, then for some reason it started giving me Management form missing error when just trying to load the update page in my browser.
#views.py:
class CaveUpdateView(UpdateView):
model=Cave
form_class=CaveForm
template_name='caves/cave_form.html'
def get_context_data(self,**kwargs):
context = super(CaveUpdateView, self).get_context_data(**kwargs)
entrance_helper = EntranceFormSetHelper()
context['entrance_helper'] = entrance_helper
if self.request.GET:
context['entrance_formset']=EntranceInlineFormSet(instance=self.object)
else:
context['entrance_formset']=EntranceInlineFormSet(self.request.POST, instance=self.object)
After wrecking my brains out, I changed the last 4 lines of the get_context_data function to this and everything was solved:
if self.request.POST:
context['entrance_formset']=EntranceInlineFormSet(self.request.POST, instance=self.object)
else:
context['entrance_formset']=EntranceInlineFormSet(instance=self.object)
So my question is, how are these two expressions not equivalent? Is there another type of request I somehow make my browser send by refreshing?
if self.request.GET doesn't mean "if the request is a GET" - and if self.request.POST doesn't mean "if the request is a POST". They are using boolean operators on the GET and POST dictionaries respectively - and in Python, dicts are boolean False if they're empty and True otherwise.
So, your calls are actually asking "does the request have some querystring parameters", which may or may not be true whether or not the request is a GET, and "does the request have a body", which will not be true with an empty POST.
If you actually want to check the type of the request,you should explicitly check if request.method == 'GET' (or 'POST').

Django form validation fails (on update)

I'm fighting with the Django form validation for a while now. I'm using pretty basic code. Returning the form data using an ajax request is the only special thing but I don't think its the cause auf my problems here.
I'm trying to update an user entry. I create an instance of the user and I feed that instance into the CreateForm() but I still get the duplicate username error.
here is a part of the view:
def user_update(request):
if request.is_ajax():
print request.POST
user = User.objects.get(pk=int(request.POST['pk']))
print user
form = UserCreateForm(request.POST, instance=user)
print form
if form.is_valid():
form_cleaned = form.cleaned_data
else:
print '### form is invalid ###'
print form.error_messages
The output for request.POST is:
<QueryDict: {u'username': [u'testuser'], u'password1': [u'test'], u'password2': [u'test'], u'pk': [u'27'], u'csrfmiddlewaretoken': [u'wyBoaBAlxLTO952BzWSxR7HMK6W7nsAM'], u'email': [u'soso#soso.so']}>
The output for print user is:
testuser
In print form I always get the duplicate username error, hence the form is always invalid.
Please give me a hint. Many thanks in advance.
For form validation, you have used UserCreateForm.
Apart from the regular regex validation of fields, the UserCreateForm also checks if a given username is available or not. And any existing user will fail this check.
You should use an appropriate form like UserChangeForm or if your application only updates a particular field like password, then choose from the built in forms.
With reference to UserCreationForm in django, it has clean_username() validation method that checks if user with given username already exists, if so raises the validation error that you are getting.
You should better use UserChangeForm if you want to update user fields.
The UserCreationForm's clean_username() has the following code:
User._default_manager.get(username=username)
If this is successful, you get your error. Since your user (admin) already exists, you'll always get the error. As suggested by #sudipta, you're better off with the UserChangeForm.
IMHO, you could define your own custom form either with your own behaviour rather than using a stock one and deviate from its original purpose.

payment processing with django

I am trying to build code that allows people to pay for the services that they get from my website. I have built the form using the forms.Form model available in django. I have also used the following pattern to build my view.
if request.method == 'POST'
form = ContactForm(request.POST)
if form.is_valid(): # All validation rules pass
conn = urllib2.Request(payment_gateway_url,urllib.urlencode(my_dat_in_dict))
f= urrlib2.urlopen(conn)
all_results=f.read()
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render(request, 'contact.html', {
'form': form,
})
The problem i am facing is that my forms get submitted OK and the payment gateway responds back to me with a whole bunch of html that helps the user to choose the credit card details etc in the response to this form POST(details that i am not collecting on my website). I can see this in the all_results (f.read()).
My question is that how do i show the user this page, since i get this as a result of my form POST. Should i save this response in a html file and HTTPResponseredirect to that page. I am assuming that HTTPResponseRedirect is more for a complete transaction and not the intermediate responses.
So, basically how do i handle the situation where the user will get sent to the payment gateway site and then after completing the proceedings come back to my site?
thanks
First off, I would say, if you live in the US, check out Stripe (or something similar). The way you are describing your payment process seems a bit overly complicated.
With that said, if (and I doubt this is the case), the HTML returned from the most is in the correct format for display on your website, you can just stick it in to an HttpResponse (must be a sprint)
return HttpResponse(html)
Otherwise, use something like BeautifulSoup, Scrape.py, or something similar to format it IN RAM, and then use HttpResponse. I would not write it to the file system for modification.

Django Form Error Handling: Wrong url? is_valid()

Hi Stackoverflow people,
I am irritated by the the Django Form handling, if the form submits to new page and the form validation fails. I intended to return to the earlier submitted form, and display the error message for correction.
The error message will be displayed, but the url link is not changing.
How can I change the *else statement of the is_valid() statement*, in order to redirect to the earlier form?
Thank you for your advice!
urls.py
urlpatterns = patterns("",
url(r"^add/$", "remap.views.add_project", name="add_project_location"),
url(r"^add/details/$", "remap.views.add_project_details", name="add_project_details"),
)
views.py
def add_project_details(request):
if request.method == 'POST': # If the form has been submitted...
locationForm = LocationForm(request.POST)
if locationForm.is_valid(): # All validation rules pass
locality = locationForm.cleaned_data['locality']
state = locationForm.cleaned_data['state']
country = locationForm.cleaned_data['country']
projectForm = ProjectForm(initial = {
'locality': locality,
'state': state,
'country': country,
})
return render_to_response('map/add_project_details.html', {
'projectForm': projectForm,
}, context_instance = RequestContext(request))
else:
print locationForm.errors
return render_to_response('map/add_project_location.html', {
'locationForm': locationForm,
}, context_instance = RequestContext(request))
else:
return HttpResponseRedirect('../') # Redirect to the location reg if site is requested w/o proper location
There's no cause to get irritated about Django doing exactly what it is designed to do.
Your redirect is on the else clause corresponding to if request.method == 'POST'. So, it will only take effect if this is not a form submission. In other words, that view will never actually display an empty form.
Then, depending on whether or not the POSTed form is valid, you display one of two templates. You're not doing any redirecting at this point.
However, even if you had asked Django to redirect when the form is invalid, I doubt you would get the result you want. This is because the rules of HTTP (not Django) say that you can't redirect a POST - so your request to the redirected view would be a GET, without any of the invalid data or error messages.
You can't update the url unless you tell the browser to do a redirect. You are returning two different html versions on the same url which can be confusing to the user.
What you should do instead is have two views. The /add url and add_project_location view would accept a post and validate, staying on that url and view until success.
Upon success it would set some session variables, redirect to the add_project_details page. Merging them like this is just not possible because of how browsers work.

How to write good form validation in Django?

I've seen Django's samples and I can see they have decent error handling. However I want to see if there is yet a better approach, a general pattern to handle form validation errors in Django. This is the sample I found here:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
In particular, I was wondering:
How can the view in "/thanks/" be sure that the form was validated? Are there any common ways to pass the successful validation of the form to the next view? Or do I need to do something manually such as setting a flag in request's session?
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
EDIT: With regards to #1 I am referring to cases like user manually entering the '/thanks/' url or going back and forth through history pages and accidentally openning it without any form being validated. (Do we still show the "thanks" page? or we need to somehow re-validate why we are in thanks view).
The view can be sure that the form is validated because it will only be called if the form is valid...
If the page is generated through a post request the browser will always ask you that when hitting refresh... I guess the only way to avoid this would be redirecting to another page!
How can the view in "/thanks/" be sure that the form was validated?
form.is_valid() should thoroughly check any field or - if necessary - any combination, cornercase, etc. That's basically it. The views knows, the form was valid if it renders. There is no need to include redundant information in the session.
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
I am not sure what the point would be. The form contains errors and the user may correct them or leave. To render a page that would not ask for form resubmission, one could use a redirect, just as in the valid case. The error markup would have to be done manually in that case.