Django prevent page caching - django

strong textI am trying to prevent a page being cached as I dont want the user to be able to resubmit a form which has already been saved to the db.
I tried the following in views.py
class ServiceCreate(LoginRequiredMixin, CreateView):
model = Service
form_class = ServiceCreateForm
#method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
return super(ServiceCreate, self).dispatch(request, *args, **kwargs)
However, this has no effect as the entire page is shown instantly when the user hits the back button. How can I prevent caching please?
Update
I'm new to Django so thought that the decorator would instruct the browser not to cache - I did not appreciate that there was server caching as well.

What you're seeing has nothing to do with server caching- it is all browser side.
Ultimately you can't 100% guarantee that a form won't be submitted multiple times (users will find a way...), so you'll have to handle that gracefully on the server. However you can greatly reduce the likelihood of it:
Return an HttpResponseRedirect (or use the redirect shortcut) to redirect the browser after a successful form submission. This will prevent a browser refresh from resubmitting the form.
Use javascript to disable the form submit button after the form as been submitted. I recently had some weird errors and data inconsistencies that turned out to be caused by someone double clicking a form's submit button. Disabling the button after the first click resolved the issue (along with doing more validation server-side to recognize a duplicate submission).
Make sure that you use POST (rather than GET) to submit the form. Browsers are less likely to resubmit the form casually and I believe that Django's CSRF protection should also help prevent errant submissions.

Related

Django Form resubmit issue when press back button [duplicate]

strong textI am trying to prevent a page being cached as I dont want the user to be able to resubmit a form which has already been saved to the db.
I tried the following in views.py
class ServiceCreate(LoginRequiredMixin, CreateView):
model = Service
form_class = ServiceCreateForm
#method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
return super(ServiceCreate, self).dispatch(request, *args, **kwargs)
However, this has no effect as the entire page is shown instantly when the user hits the back button. How can I prevent caching please?
Update
I'm new to Django so thought that the decorator would instruct the browser not to cache - I did not appreciate that there was server caching as well.
What you're seeing has nothing to do with server caching- it is all browser side.
Ultimately you can't 100% guarantee that a form won't be submitted multiple times (users will find a way...), so you'll have to handle that gracefully on the server. However you can greatly reduce the likelihood of it:
Return an HttpResponseRedirect (or use the redirect shortcut) to redirect the browser after a successful form submission. This will prevent a browser refresh from resubmitting the form.
Use javascript to disable the form submit button after the form as been submitted. I recently had some weird errors and data inconsistencies that turned out to be caused by someone double clicking a form's submit button. Disabling the button after the first click resolved the issue (along with doing more validation server-side to recognize a duplicate submission).
Make sure that you use POST (rather than GET) to submit the form. Browsers are less likely to resubmit the form casually and I believe that Django's CSRF protection should also help prevent errant submissions.

Refresh Django wizard form after browser's back button

I am using SessionWizardView from django-formtools project.
I've noticed that after successfully passing all form checks and executing done() function, which redirects to completely different view, user can still hit browser Back button and re-fill form again.
Isn't there any way to prevent that? I would assume that it would be some kind of session cleaning mechanism. But I cannot find any in documentation.
After some playing around I've found that it can be achieved in two lines:
def done(self, form_list, form_dict, **kwargs):
#regular form processing
self.instance_dict = None
self.storage.reset()
Now, after pressing Back button and submitting form it fails because no data exists and resets to first screen.

How to add fields to a request?

I have a login page. Upon submission if 'webmail' is selected, the request
should be redirected to the webmail server, with the credentials submitted but
under different keys. Here's what I'm trying now:
if form.validate_on_submit():
if form.destination.data == 'webmail':
form.rc_user.data = form.email.data
form.rc_password.data = form.password.data
return redirect('https://example.com/webmail/', code=307)
This almost works: the POST is redirected to webmail. However the values
submitted are the default values, not the assigned values.
I have some more issues though:
the keys should be _user and _pass, but Flask seems to blow up with
leading-underscore field names.
I do not want to add these fields to the original class. I want to subclass
upon submission, somewhat as follows:
if form.validate_on_submit():
if form.destination.data == 'webmail':
class WebmailLoginForm(LoginForm):
rc_user = EmailField('user', default=form.email.data)
form = WebmailLoginForm(request.form)
return redirect('https://example.com/webmail/', code=307)
When I do this, the added fields show up as UnboundField and are not
submitted.
When issuing a redirect, the browser is simply told to resubmit to another server. I.e. it's too late for the server to influence the request.
So either: start a new request, or use javascript to change the submit target.
Thanks to my colleague Johan for kickstarting my brain.

Why Django 1.4 per-site cache does not work correctly with CACHE_MIDDLEWARE_ANONYMOUS_ONLY?

I am working on a Django 1.4 project and writing one simple application using per-site cache as described here:
https://docs.djangoproject.com/en/dev/topics/cache/#the-per-site-cache
I have correctly setup a local Memcached server and confirmed the pages are being cached.
Then I set CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True because I don't want to cache pages for authenticated users.
I'm testing with a simple view that returns a template with render_to_response and RequestContext to be able to access user information from the template and the caching works well so far, meaning it caches pages just for anonymous users.
And here's my problem. I created another view using a different template that doesn't access user information and noticed that the page was being cached even if the user was authenticated. After testing many things I found that authenticated users were getting a cached page if the template didn't print something from the user context variable. It's very simple to test: print the user on the template and the page won't be cached for an authenticated user, remove the user on the template, refresh the page while authenticated and check the HTTP headers and you will notice you're getting a cached page. You should clear the cache between changes to see the problem more clearly.
I tested a little more and found that I could get rid of the user in the template and print request.user right on the view (which prints to the development server console) and that also fixed the problem of showing a cached page to an authenticated user but that's an ugly hack.
A similar problem was reported here but never got an answer:
https://groups.google.com/d/topic/django-users/FyWmz9csy5g/discussion
I can probably write a conditional decorator to check if user.is_authenticated() and based on that use #never_cache on my view but it seems like that defeats the purpose of using per-site cache, doesn't it?
"""
A decorator to bypass per-site cache if the user is authenticated. Based on django.views.decorators.cache.never_cache.
See: http://stackoverflow.com/questions/12060036/why-django-1-4-per-site-cache-does-not-work-correctly-with-cache-middleware-anon
"""
from django.utils.decorators import available_attrs
from django.utils.cache import add_never_cache_headers
from functools import wraps
def conditional_cache(view_func):
"""
Checks the user and if it's authenticated pass it through never_cache.
This version uses functools.wraps for the wrapper function.
"""
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view_func(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
if request.user.is_authenticated():
add_never_cache_headers(response)
return response
return _wrapped_view_func
Any suggestions to avoid the need of an extra decorator will be appreciated.
Thanks!
Ok, I just confirmed my "problem" was caused by Django lazy loading the User object.
To confirm it, I just added something like this to my view:
test_var = "some text" + request.user
And I got an error message telling me I couldn't concatenate an str to a SimpleLazyObject. At this point the lazy loading logic hasn't got a real User object yet.
To bypass the lazy loading, hence return a non-cache view for authenticated users, I just needed to access some method or attribute to triggers an actual query on the User object. I ended up with this, which I think it's the simplest way:
bypass_lazyload = request.user.is_authenticated()
My conditional_cache decorator is no longer needed, although it was an interesting exercise.
I may not need to do this when I finish working with my views as I'll access some user methods and attributes on my templates anyway but it's good to know what was going on.
Regards.

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.