csrf_exempt not working with django auth - django

I am making the backend for a mobile app and using Django with Userena for the user management. I made the sign in and sign up using Django REST framework and everything works fine. The only thing I need to do now is to implement the "forget password" functionality. I wanted to use the already implemented one from Userena, but I cannot get rid of the error "CSRF token missing or incorrect" even after using the csrf_exempt dectorator. What am I doing worng?
urls.py
from django.contrib.auth.views import password_reset
from django.views.decorators.csrf import csrf_exempt
...
urlpatterns = patterns(
'',
url(r'^password/mobile/reset/$',
csrf_exempt(password_reset),
{'template_name': 'userena/password_reset_form.html',
'email_template_name': 'userena/emails/password_reset_message.txt',
'extra_context': {'without_usernames': userena_settings.USERENA_WITHOUT_USERNAMES}
},
name='userena_password_mobile_reset'),
)
passowrd_reset_form.html
{% extends 'userena/base_userena.html' %}
{% load i18n %}
{% block title %}{% trans "Reset password" %}{% endblock %}
{% block content %}
<form action="" method="post">
<fieldset>
<legend>{% trans "Reset Password" %}</legend>
{% csrf_token %}
{{ form.as_p }}
</fieldset>
<input type="submit" value="{% trans "Send password" %}" />
</form>
{% endblock %}

If you do a GET request before POSTing to the password reset view, you get the CSRF token in a cookie, which you can then send in your POST request.
If you insist on exempting the view: I think the problem lies in the way the CSRF protection is applied to the password_reset view. It is explicitly decorated by csrf_protect.
To have a closer look at the problem, lets assume original_password_reset_view is password_reset without the csrf_protect. Basically, you are doing this:
csrf_exempt(csrf_protect(original_password_reset_view))
# ^^ your code
# ^^ the decorator in django.contrib.auth.views
And adding in the effect of the CsrfViewMiddleware, we get the equivalent of
csrf_protect(csrf_exempt(csrf_protect(original_password_reset_view)))
csrf_protect is just a middleware-turned-decorator from CsrfViewMiddleware. csrf_exempt on the other hand simply sets csrf_exempt=True on its argument. So the middleware, represented by the outer csrf_protect, sees the csrf_exempt=True value on the view and disables its CSRF projection. It negates the outer csrf_protect. So we have:
csrf_protect(original_password_reset_view)
The view is still protected. Basically, there is no sane way around. (An insane way: write a middleware that sets request.csrf_processing_done = True for that specific URL. Don't do that...)

Related

Password reset in Django-registration can't find site.domain

My password_reset_email.html in django-registration looks like this:
{% blocktrans %}
To reset your password, please click the following link:
{% endblocktrans %}
<body>
<p>
<a href="http://{{ site.domain }}{% url 'auth_password_reset_confirm' uid token %}">
Reset password
</a>
</p>
</body>
{% blocktrans %}
Django is picking up the url but not {{site.domain}}. Yet, when I have the same code in the registration process {{site.domain}} was valid. What makes the password_reset_email.html different than the registration process?
Thanks.
django-registration (or rather django.contrib.auth) doesn't use context processors for rendering emails. You will have to add {{ site }} to the context manually during rendering. Basically you'll have to customize django-registration's urls. Something along those line:
from django.contrib.sites.models import Site
from django.contrib.auth import views as auth_views
....
url(r'^password/reset/$', auth_views.password_reset,
{'post_reset_redirect': reverse_lazy('auth_password_reset_done'),
'extra_email_context': {'site': Site.objects.get_current()}},
name='auth_password_reset'),
....

Django password_change view & PasswordChangeForm not working

I'm trying to get password_change view and PasswordChangeForm working, but I get an html where the form renders correctly but nothing happens when I fill the form and click enter and there isn't a "send" button for the form. I'm probably missing something simple here, but the docs doesn't seem very helpful.
So, this is the urls.py:
from django.contrib.auth.views import password_change
from sisacademico import views
url(r'^password_changed/$', views.password_changed, name='password_changed'),
url(r'^change_password/$', password_change,
{'template_name': 'sisacademico/change_password.html',
'post_change_redirect': sisacademico/password_changed/'}),
this is the html, change_password.html:
{% extends 'base_sisacademico.html' %}
{% block content %}
{{form.as_ul}}
{% endblock content %}
What I get is this form with no submit button that doesn't work:
The Django template form tags {{ form }} don't output a submit button. You're going to need to put that in your HTML. You'll have something like:
<form method="post">
{% csrf_token %}
{{ form.as_ul }}
<input type="submit" value="Submit" />
</form>
take a look at the form docs for other examples

Proper way to handle multiple Django forms in one page with two views?

I've struggled with this problem for the last two days and could use some help. The home page for my Django 1.6 application will include two forms, one that a user can use to sign in to the site and one they can use to sign up (create a login) for the site:
# templates/home/home_page.html
<div class="sign-in-form">
<form action="{% url 'apps.home.views.sign_in' %}" method="post">
{% csrf_token %}
{{ sign_in_form.as_p }}
{% if next %}
<input type="hidden" name="next" value="{{ next }}">
{% else %}
<input type="hidden" name="next" value="{% url 'view-members' %}">
{% endif %}
<input type="submit" value="Sign in">
</form>
</div>
<div class="sign-up-form">
<fieldset>
<legend>Sign up</legend>
<form action="{% url 'apps.home.views.sign_up' %}" method="post">
{% csrf_token %}
{{ sign_up_form.as_p}}
<p><input type="submit" value="Sign up" /></p>
</form>
</fieldset>
</div>
If the user submits, the sign_in form, they'll be taken to a page where they can view other site members. If they submit the sign_up form, they'll be taken to a second signup page where they'll create a user profile, etc.
Originally, I was going to use the technique shown in this question and use one view to handle the homepage. However, I decided to try to use two views because I'm using the Django's actual login view (django.contrib.auth.views.login) so that I can add code to it to detect the user's device (phone, tablet, or computer), and merging that view with my sign_up view would create a very long and complicated view to maintain. I'd prefer to keep the views for both forms separate.
Here's the home page and sign_in views:
# apps/home/views:
def home_page(request, template):
sign_in_form = SignInAuthenticationForm()
sign_up_form = CreateAccountForm()
return render(request, template, {"sign_in_form": sign_in_form,
"sign_up_form": sign_up_form})
#sensitive_post_parameters()
#csrf_protect
#never_cache
def sign_in(request,
template='home_page.html',
redirect_field_name=REDIRECT_FIELD_NAME,
# authentication_form=AuthenticationForm,
authentication_form=SignInAuthenticationForm,
current_app=None, extra_context=None):
# Do device detection here...
# django.contrib.auth.views code goes here...
return response
The signup view will just be your typical, function-based view for processing a form as described in the Django documentation.
What I'm struggling with is my URLconf files. Here's my main and "home" URLconf files:
# conf/urls.py
urlpatterns = patterns('',
url(r'^$', include('apps.home.urls')),
# Other url patterns...
)
# apps/home/urls.py
urlpatterns = patterns('apps.home.views',
url(r'^$',
'home_page',
{'template': 'home/home_page.html'},
name='home-page'),
url(r'^sign_in/$',
'sign_in',
{'template': 'home/home_page.html'},
name='sign-in'),
url(r'^sign_up/$',
'sign_up',
{'template': 'home/home_page.html'},
name='sign-up'),
)
The problem is that I get this error during template rendering:
NoReverseMatch at /
Reverse for 'apps.home.views.sign_in' with arguments '()' and keyword arguments '{}' not found. 1 pattern(s) tried: ['$sign_in/$']
Request Method: GET
Request URL: http://localhost:8000/
Django Version: 1.6.2
Exception Type: NoReverseMatch
Exception Value:
Reverse for 'apps.home.views.sign_in' with arguments '()' and keyword arguments '{}' not found. 1 pattern(s) tried: ['$sign_in/$']
Exception Location: /Users/smith/venv/swing/lib/python2.7/site-packages/django/core/urlresolvers.py in _reverse_with_prefix, line 429
Python Executable: /Users/smith/venv/swing/bin/python
Python Version: 2.7.5
Python Path:
['/Users/smith/Dropbox/www/swing',
'/Users/smith/venv/swing/lib/python2.7/site-packages/wurfl_cloud-1.0.1-py2.7.egg',
'/Users/smith/venv/swing/lib/python27.zip',
'/Users/smith/venv/swing/lib/python2.7',
'/Users/smith/venv/swing/lib/python2.7/plat-darwin',
'/Users/smith/venv/swing/lib/python2.7/plat-mac',
'/Users/smith/venv/swing/lib/python2.7/plat-mac/lib-scriptpackages',
'/Users/smith/venv/swing/Extras/lib/python',
'/Users/smith/venv/swing/lib/python2.7/lib-tk',
'/Users/smith/venv/swing/lib/python2.7/lib-old',
'/Users/smith/venv/swing/lib/python2.7/lib-dynload',
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7',
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin',
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk',
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac',
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages',
'/Users/smith/venv/swing/lib/python2.7/site-packages']
At first I started to think that maybe it's telling me that it can's find the correct URL pattern in my home/urls.py file because the URL signature in my form is incorrect. Maybe I needed to do this to match the arguments in the sign_in view:
<form action="{% url 'apps.home.views.sign_in' 'home/home_page.html' %}" method="post">
But I'm already showing the template name in the home URLconf. And I don't think I need to pass the other view arguments in the form action (e.g. redirect_field_name) because their optional. In any case, adding this argument to the form action didn't fix it.
One of the things that confuses me is how to set the first url argument. I've set them to r'^sign_in/$' and r'^sign_up/$' because if I set them both to r'^$', the page will render properly but when I submit either form, it justs posts back to the home page. You can see this will happen by doing a "view source" on the page. It shows each form's action will be "/". On the other hand, the way I have it now seems incorrect to me because the site won't actually have a "/sign_in/" and "/sign_up/" URL since both forms are on the home page. Also, is there going to be a problem in which if the user submits one for or the other improperly, errors for both forms will be rendered on the page?
The Django documentation, to the best of my knowledge, doesn't really describe a standard approach for doing what I'm trying to do. It describes how to render multiple versions of the same form. Can anyone tell me what I'm doing wrong?
Thanks.
Your form names are 'sign_in_form' and 'sign_up_form', but in your html you wrote them 'form.as_p' instead of 'sign_in_form.as_p' and 'sign_up_form.as_p' this is the first bug a saw in your code.
The real problem is in your urls configuration. In your main urls.py you have
url(r'^$', include('apps.home.urls')),
Other ...
Though you will not be able to get to localhost:8000/sign_in/ because initially it does not satisfy to ^$ .
Try to change it by
url(r'', include('apps.home.urls')),
and put it to the end of urls.py.
i test this see if this what you want:
view.py
def loginUser(request,**Kargs):
LoginFormSet = formset_factory(LoginForm)
SignFormSet = formset_factory(SignForm)
if request.method == 'POST':
login_formset = LoginFormSet(request.POST, prefix='login')
sign_formset = SignFormSet(request.POST ,prefix='sign')
if login_formset.is_valid():
#do somthing
elif sign_formset.is_valid():
#do somthing
return render(request, 'reservetion/login.html',{'login_formset': login_formset,'sign_formset':sign_formset})
else:
login_formset = LoginFormSet(prefix='login')
sign_formset = SignFormSet(prefix='sign')
return render(request, 'reservetion/login.html',{'login_formset': login_formset,'sign_formset':sign_formset})
page.html:
<form action="{% url 'loginUser' %}" method="post">
{% csrf_token %}
{{ login_formset.management_form }}
{% for form in login_formset %}
{{ form }}
{% endfor %}
{{ sign_formset.management_form }}
{% for form in sign_formset %}
{{ form }}
{% endfor %}

Use django comments on article's comment page (/a/2/comments/)

I have made a blog with Django with articles (like so: mysite.com/a/article_id/) and would like users to be able to comment on the article's comment page (i.e: mysite.com/a/article_id/comments/)
So far I haven't had much success. It seems that the article_id in the url is blocking somehow the comments app.
This is my url.py:
from django.conf.urls import patterns, include, url
from django.contrib.auth.views import login, logout
urlpatterns = patterns('blogengine.views',
url(r'^$', 'get_posts', name='index'),
url(r'^write/', 'write_post', name='write'),
url(r'^a/(?P<post_id>\d+)/$', 'detail'),
url(r'^a/(?P<post_id>\d+)/comments/$', 'detail_comments'),
url(r'^a/(?P<post_id>\d+)/comments/', include('django.contrib.comments.urls')),
)
These are my views - views.py:
def detail_comments(request, post_id):
p = get_object_or_404(Post, pk=post_id)
return render_to_response('blogengine/detail_comments.html', {'post': p},
context_instance=RequestContext(request))
And this is my template detail_comments.html
{% block content %}
{% load comments %}
{% get_comment_form for post as form %}
<form action="/a/{{ post.id }}/comments/post/" method="post">
{% csrf_token %}
{{ form.content_type }}
{{ form.object_pk }}
{{ form.timestamp }}
{{ form.security_hash }}
<p style="display:none"><label for="id_honeypot">Leave blank</label>{{ form.honeypot }}</p>
<p>
<label for="id_comment">Comment</label>
{{ form.comment }}
</p>
<p><input type="submit" name="post" value="Post →" /></p>
</form>
{% endblock %}
(Oh and this is kind of obvious but the comments app is installed in settings.py)
If the form action is set to {% comment_form_target %}, like suggested in the docs, django throws this error:
NoReverseMatch at /a/2/comments/
Reverse for 'django.contrib.comments.views.comments.post_comment' with arguments '()' and keyword arguments '{}' not found.
I tried "hacking" my way out by replacing it with this /a/{{ post.id }}/comments/post/ which works to display the page but then if I try to post a comment, django throws a different error:
TypeError at /a/2/comments/post/
post_comment() got an unexpected keyword argument 'post_id'
Is there a way to get the comments app to ignore the id_post? Or another way to do this?
Thanks.
The error message is pretty unambiguous: django.contrib.comments.views.post_comment does not take a post_id argument, so it throws.
As the comments views do not need nor want the argument, why not just leave it out?
You should be able to modify the URL route not to capture the post_id at all (although at the cost of consistency) like so:
url(r'^a/(?:\d+)/comments/', include('django.contrib.comments.urls')),
or simply
url(r'^a/\d+/comments/', include('django.contrib.comments.urls')),
Note that there's really no point in having this kind of nesting at this point anyway if it's just going to be ignored, so you could simplify it to:
url(r'^comments/', include('django.contrib.comments.urls')),
Granted, this doesn't look as pretty and pseudo-RESTful without the vestigial prefix, but there's really no point in having it there if you're just going to ignore it.
The other thing you could do would be to wrap all the views the comments app provides so they throw a 404 if the post_id is invalid, but that seems overkill.
Ok so I solved my problem by simply doing what the docs say. I imported the comments like so:
url(r'^comments/', include('django.contrib.comments.urls')),
And kept this url pointing to my detail_comments view which displays the comment list and form:
url(r'^a/(?P<post_id>\d+)/comments/$', 'detail_comments'),
So basically the processing happens at /comments/ but the user interacts with this page: /a/post_id/comments/
The only problem I had was that the Django comments app automatically redirected the user to a success page after posting a comment.
I solved this by setting a "next" hidden field in the form indicating the current page.

Can't make class-based RedirectView work

I'm currently trying to migrate my function based views for the new django 1.3 class-based views. To start, I changed a simple RedirectView I had, but I cant get it to work, even worst, I can't understand how the class view works. The mechanism is simple, I have a select field in the index page, the user select an option and clicks "go". The view must get the url corresdponding to that name and redirect there.
When sending the POST signal, django doesn't return anything, just a 405 error in the terminal.
UPDATED code:
index.html
[...]
<div id="widget">
<h2>{% trans "Spaces list" %}</h2><br />
<form method="post" action="/spaces/go/">{% csrf_token %}
<select name="spaces">
{% for space in spaces %}
<option>{{ space.name }}</option>
{% empty %}
<option>{% trans "No spaces" %}</option>
{% endfor %}
</select>
<input type="submit" value="{% trans 'Go' %}" />
</form>
</div>
[...]
views.py
class GoToSpace(RedirectView):
url = "/spaces/"
def get_redirect_url(self, **kwargs):
self.place = get_object_or_404(Space, name = self.request.POST['spaces'])
return self.place.url
urls.py
from django.conf.urls.defaults import *
from e_cidadania.apps.spaces.views import GoToSpace
urlpatterns = patterns('',
(r'^go/', GoToSpace.as_view()),
)
What I am doing wrong?
You can't refer to a class-based view in urls.py just by referencing the name of the class. As is well documented, you need to call the classmethod as_view:
(r'^go/', go_to_space.as_view()),
You should really follow PEP8 and make your class name GoToSpace, which would make the difference from a function more obvious.
Also, get_redirect_url is a method, so it should have self as the first positional argument.
SOLVED:
RedirectView in django 1.3 only accepts GET requests, I was doing a POST (as recommended in django 1.2.x)
This issue was fixed for django 1.3.x (ticket #15739)