9

Hey, I am following this tutorial to learn to make a wiki page with Django. However, it is made in django 0.96 and I use Django 1.3 so there are some things that are different. Some I already fixed myself, however this one I can't seem to make it work.

I made a form that submits data to a view. This is the form:

<form method="post" action"/wikicamp/{{page_name}}/save/">{% csrf_token %}
    <textarea name="content" rows="20" cols="60">{{content}}</textarea><br>
    <input type="submit" value="Save Page"/>
</form>

and the /wikicamp/{{page_name}}/save/ url redirects to the save_page view:

from django.http import HttpResponseRedirect
from django.core.context_processors import csrf

def save_page(request, page_name):
    c = {}
    c.update(csrf(request))
    content = c.POST["content"]
    try:
        page = Page.objects.get(pk=page_name)
        page.content = content
    except Page.DoesNotExist:
        page = Page(name=page_name, content=content)
    page.save()
    return HttpResponseRedirect("wikicamp/" + page_name + "/")

However the problem is that I get this error:

Help

Reason given for failure:

    CSRF token missing or incorrect.


In general, this can occur when there is a genuine Cross Site Request Forgery, or when Django's CSRF mechanism has not been used correctly. For POST forms, you need to ensure:

    The view function uses RequestContext for the template, instead of Context.
    In the template, there is a {% csrf_token %} template tag inside each POST form that targets an internal URL.
    If you are not using CsrfViewMiddleware, then you must use csrf_protect on any views that use the csrf_token template tag, as well as those that accept the POST data.

You're seeing the help section of this page because you have DEBUG = True in your Django settings file. Change that to False, and only the initial error message will be displayed.

You can customize this page using the CSRF_FAILURE_VIEW setting.

So I read through some of the documentation, like http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-to-use-it. I tried to do that however and it still gave the same error.

So: Anyone an idea how to handle form post data well with Django 1.3?

I think it has something to do with: The view function uses RequestContext for the template, instead of Context. but i don't now what it is.

btw, in my terminal which shows the http request of the localhost it says this: A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.

Javaaaa
  • 3,788
  • 7
  • 43
  • 54

4 Answers4

9

You've got to include {% csrf_token %} in your form's template between your <form> tags.

<form method="post" action"/wikicamp/{{page_name}}/save/">
    {% csrf_token %}
    <textarea name="content" rows="20" cols="60">{{content}}</textarea><br>
    <input type="submit" value="Save Page"/>
</form>

If the csrf_token is not rendered into your form make sure you're providing the RequestContext in the view's response:

from django.shortcuts import render_to_response
from django.template import RequestContext

def app_view(request):
    return render_to_response('app_template.html', 
                              app_data_dictionary, 
                              context_instance=RequestContext(request))

Or, use this shortcut method:

from django.views.generic.simple import direct_to_template

def app_view(request):             
    return direct_to_template(request, 'app_template.html', app_data_dictionary)

The RequestContext is always available when you're using generic views.

peterp
  • 3,145
  • 1
  • 20
  • 24
  • that doesn't work, I did that already before asking over here. I did the things described in the last link of the docs. Any idea? – Javaaaa May 16 '11 at 17:27
  • Did you reload the page before submitting it again? You can view the source to verify that the CSRF token is actually rendered in the form. – peterp May 16 '11 at 17:31
  • Taking a closer look, do you have have `django.middleware.csrf.CsrfViewMiddleware` in your `MIDDLEWARE_CLASSES` in `settings.py`? – peterp May 16 '11 at 17:34
  • yes i did, the csrf token isn't renderen in the form. But how could including that token possibly solve all my problems with CSRF? – Javaaaa May 16 '11 at 17:35
  • You could also try and delete your cookies. – peterp May 16 '11 at 17:35
  • already tried in different browsers as well as deleted cookies. – Javaaaa May 16 '11 at 17:37
  • maybe it is the HttpResponseRedirect i use? Or is this wrong: c = {} c.update(csrf(request)) content = c.POST["content"] – Javaaaa May 16 '11 at 17:41
  • Perhaps it's the order of the middleware? – peterp May 16 '11 at 17:41
  • tried both 'django.middleware.csrf.CsrfResponseMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', and the other way around. – Javaaaa May 16 '11 at 17:46
  • What I mean is, the order in which the middleware is loaded: `MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', )` – peterp May 16 '11 at 17:46
  • I tried first with the response before the view, than the view before the response. Is that what you mean? – Javaaaa May 16 '11 at 17:50
  • I updated my view in the question with c.update(csrf(request)) content = c.POST["content"] , is that the okay way to do it? – Javaaaa May 16 '11 at 17:53
  • If you really want to get around CSRF verification you can skip it all together by decorating a view with `@csrf_exempt` – peterp May 16 '11 at 17:55
  • I think the problem is that I am not using RequestContext? As it says that I need to make sure: The view function uses RequestContext for the template, instead of Context. – Javaaaa May 16 '11 at 18:06
  • Ah, yes. Absolutely. Either use `direct_to_template` or inject the request context in your response. – peterp May 17 '11 at 07:02
9

You will need the {% csrf_token %} template tag in between your tags as well as including

   django.middleware.csrf.CsrfViewMiddleware
   django.middleware.csrf.CsrfResponseMiddleware

in your MIDDLEWARE_CLASSES in the applications settings.py

Adding some example post data handling:

This is an example of one of the times I am using POST data in a view. I will generally rely on the form class to do verification via the cleaned_data array.

if request.method == 'POST':
        form = ForgotPassword(data=request.POST)
        if form.is_valid():
            try:
                new_user = backend.forgot_password(request, **form.cleaned_data)
            except IntegrityError:
                context = {'form':form}
                form._errors[''] = ErrorList(['It appears you have already requested a password reset, please \
                check ' + request.POST['email2'] + ' for the reset link.'])
                return render_template(request,'passwordReset/forgot_password.html',context)
            if success_url is None:
                to, args, kwargs = backend.post_forgot_password(request, new_user)
                return redirect(to, *args, **kwargs)
            else:
                return redirect(success_url)
grantk
  • 3,958
  • 4
  • 28
  • 37
  • if i add that one and i click on the form button no error occurs, but the form page just reloads emptying the textarea field while nothing happens. (not the redirect i want to anyway) – Javaaaa May 16 '11 at 17:45
  • I updated my view in the question with c.update(csrf(request)) content = c.POST["content"] , is that the okay way to do it? – Javaaaa May 16 '11 at 17:53
  • is debugging turned on? if so can you post the stack trace? – grantk May 16 '11 at 17:54
  • I do not believe you necessarily have to use the token, I ran into this a few months ago upgrading from 1.1 to 1.3 and all I had to do was add the middleware entries and the csrf_token – grantk May 16 '11 at 17:56
  • I updated my wuestion with the whole error. If this is not a stack trace, please explain me how i could get it. thanks! – Javaaaa May 16 '11 at 17:58
  • btw, in my terminal which shows the http request of the localhost it says this: A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext. – Javaaaa May 16 '11 at 17:59
  • to show the stack trace you would set DEBUG = True in the settings.py . I am trying to get the CSRF error myself again but not having any luck for some reason. Nevermind, just got it and it does not print out anything else of use to us here. – grantk May 16 '11 at 18:10
  • You need to look at http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext . That link will explain how to use RequestContext instead of context in your view. This is the second link in the 403 page you are getting – grantk May 16 '11 at 18:18
  • thanks. I read that, but I don't use the Context class at this moment, or do I? – Javaaaa May 16 '11 at 18:57
2

I guess you've missed the symbol '=' in the form declaration.

action"/wikicamp/{{page_name}}/save/"

action="/wikicamp/{{page_name}}/save/"

Fortunately, it might be not a mistake. So if it is not a solution, try some more easy example:

# settings.py

TEMPLATE_DIRS = (
    # Here comes something like "C:/www/django/templates"
)

MIDDLEWARE_CLASSES = (
    ...
    'django.middleware.csrf.CsrfViewMiddleware',
    ...
)

# urls.py

urlpatterns = patterns('',
    ('^foo', foo),
)


# views.py
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.core.context_processors import csrf

def foo(request):
    d = {}
    d.update(csrf(request))
    if 'output' in request.POST:
        d.update({'output':request.POST['output']})
    return render_to_response('foo.html',d)

# foo.html template
<html>
<h1> Foo </h1>
<form action="/foo" method = "post">
    {% csrf_token %}
    <input type="text" name="output"></input>
    <input type="submit" value="go"></input>
</form>
<p> Output: {{ output }} </p>
</html>

Hope this will work

Павел Тявин
  • 2,529
  • 4
  • 25
  • 32
2

re above use "request.POST" not "c.POST" in the 3rd line

def save_page (request,page_name):
    content = request.POST["content"]

and change in "edit_page"

-   return render_to_response("edit.html",{"page_name":page_name, "content":content})
+   t = get_template('edit.html')
+   html = t.render(Context({"page_name":page_name, "content":content}))
+   return HttpResponse(html)

- :remove 
+ :add
Pete
  • 21
  • 2