Accessing user in Class-based generic views - django

I'm trying to check if user.is_authenticated() or if user.has_perm() but it seems impossible extending django Class-based genering views. The only method I found where the request appears is get().
class MyDetailView(DetailView):
def get(self, request, **kwargs):
import pdb
pdb.set_trace()
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
there I found that request.user is instance of AnonymusClass no matter if I'm logged in or not.
(Pdb) request.user.__class__
<class 'django.contrib.auth.models.AnonymousUser'>
so checking for authentification or perms will always fail:
(Pdb) self.request.user.is_authenticated()
False
I've tried overriding other methods such as get_object(), get_context_data() adn others. I each of them there is self.request attribute available, but user is still Anonymus.
So my question is: How on Earth am I supposed to check if user is logged in using Class-based views!?
Does it mean I have to (go back and) use function-based views?
I'm using Python 2.7.1+ and Django version 1.4 pre-alpha SVN-16627
In response to EVIAAC post:
Using login_required or permissions_required decorators is not an option. I need to check for permissions/logon after I retrieve object: if object has boolean field registration_required set to True only reqistered users will be able to see the page, others will get redirected to logon page (example behavior borrowed from django.contrib.flatpages).

Works properly in 1.3:
class TestView(DetailView):
def get(self, request, **kwargs):
import ipdb; ipdb.set_trace()
ipdb> request.user
<User: zk>
ipdb> request.user.is_authenticated()
True
Possibly a bug?

Try using the decorators from django.contrib.auth.decorators. In your urls.py, you can do something like:
from django.contrib.auth.decorators import login_required
...
url(r'^something/?$', login_required(MyDetailView.as_view()))
...
For checking permissions, you can use the premissions_required decorator. For more info, check out the docs: https://docs.djangoproject.com/en/dev/topics/auth/#the-login-required-decorator

I use mixins for class based views. In this case, you can do it like this:
Django Class-Based Generic Views and Authentication

Related

Redirect anonymous users to log in (don't show them anything)

Django 1.9.6.
I want to absolutely disable the whole website from viewing by anonymous users. Anonymous users will always be redirected to login page.
I have created a general view. The problem is that subclasses of GeneralView may not just render a template but perform some calculations or just be of different kinds: DetailView, ListView etc.
class GeneralView(View):
def get(self, request, template):
if not request.user.is_authenticated() and request.user.is_active:
return redirect("auth_login")
else:
return render(request, template)
If I try to inherit, the code gets clumsy:
class HomePageView(GeneralView):
def get(self, request, template):
super().get(self, request)
Well, what can I do here? I get error message that my get method doesn't return HttpResponse.
I can rewrite get method of the superclass to return status code. Then check it in the subclass. But this seems to be garbage.
In other words I'm lost in clouds of inheritance. Could you give me a kick here how always to redirect anonymous users to login page, whereas let logged in users see everything.
Thank you in advance.
You could use the UserPassesTestMixin for this.
from django.core.urlresolvers import reverse_lazy
class GeneralView(UserPassesTestMixin, View):
def test_func(self):
# I assume you missed out the brackets in your question and actually wanted 'if not (request.user.is_authenticated() and request.user.is_active)'
return request.user.is_authenticated() and request.user.is_active
login_url = reverse_lazy('auth_login')
The mixin overrides dispatch, so you won't have to call super() when you override get() in your view.
class HomePageView(GeneralView):
def get(self, request):
...
I think your error for get method not returning belongs to not putting a return statement. in fact, in get method of the child class you should do:
class HomePageView(GeneralView):
def get(self, request, template):
return super().get(self, request)
That should solve the error
If you have lots of views and you do not want to touch any one you can just use Middleware for this issue. Try code below:
import traceback
from django.contrib.auth.decorators import login_required
class RejectAnonymousUsersMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
current_route_name = resolve(request.path_info).url_name
if current_route_name in settings.AUTH_EXEMPT_ROUTES:
return
if request.user.is_authenticated:
return
return login_required(view_func)(request, *view_args, **view_kwargs)
Cautions:
You must add this middleware to the bottommost of middleware section
of settings.py
You should put this variable in settings.py
AUTH_EXEMPT_ROUTES = ('register', 'login', 'forgot-password')
New versions of Django provides the #login_required decorator. If an anonymous user tries to access the view, the system redirects to the login page.
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
It can be used in function views, as shown above, or generic views (using #method_decorator, usually in dispatch method)
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
#method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'

Can PermissionRequiredMixin and LoginRequiredMixin be combined?

I have some users that are allowed to see a certain view.
To allow users to login and complain with a 403 Forbidden for those users that cannot see that login, I can use the following (as explained here):
#permission_required('polls.can_vote', raise_exception=True)
#login_required
def my_view(request):
...
This indeed works as expected. But all my views are class-based views. Since Django 1.9 (finally!) there are a bunch of pretty mixins for doing things that were only possible through the decorators. However...
class MyClassView(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
raise_exception = <???>
permission_required = 'polls.can_vote'
template_name = 'poll_vote.html'
this doesn't work. Because the raise_exception flag is used by both LoginRequiredMixin and PermissionRequiredMixin, I cannot set it to anything.
if raise_exception is True, a user that is not logged in receives a 403 Forbidden (which I do not want).
if raise_exception is False, a user that is not allowed to see the view, will be redirected to the login page which, because the user is logged in, will redirect again to the page. Creating a not-at-all fancy redirect loop.
Of course I could implement my own mixin that behaves I expected, but is there any Django-way of doing this in the view itself? (not in the urls.py)
For many cases raising 403 for unauthenticated users is the expected behaviour. So yes, you need a custom mixin:
class LoggedInPermissionsMixin(PermissionRequiredMixin):
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated():
return redirect_to_login(self.request.get_full_path(),
self.get_login_url(), self.get_redirect_field_name())
if not self.has_permission():
# We could also use "return self.handle_no_permission()" here
raise PermissionDenied(self.get_permission_denied_message())
return super(LoggedInPermissionsMixin, self).dispatch(request, *args, **kwargs)
The desired behavior is the default since 2.1, so the other answers are obsolete:
Changed in 2.1: In older versions, authenticated users who lacked permissions were redirected to the login page (which resulted in a loop) instead of receiving an HTTP 403 Forbidden response. [src]
I wanted to add a comment, but my reputation does not allow. How about the following? I feel the below is more readable?
Updated after comments
My reasoning is: You basically write modified dispatch from LoginRequiredMixin and just set raise_exception = True. PermissionRequiredMixin will raise PermissionDenied when correct permissions are not met
class LoggedInPermissionsMixin(PermissionRequiredMixin):
raise_exception = True
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated():
return redirect_to_login(self.request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name())
return super(LoggedInPermissionsMixin, self).dispatch(request, *args, **kwargs)
Simplest solution seems to be a custom view mixin.
Something like that:
class PermissionsMixin(PermissionRequiredMixin):
def handle_no_permission(self):
self.raise_exception = self.request.user.is_authenticated()
return super(PermissionsMixin, self).handle_no_permission()
Or, just use PermissionRequiredMixin as usual and put this handle_no_premission to every CBV.

Django - allauth - Prevent an unauthenticated user to view pages meant for authenticated users

I am trying to use Django with allauth trying to create a login page.
I am able to login and redirect the user to LOGIN_REDIRECT_URL (= "/users/{id}/mypage") page successfully. I have a separate view for /users/{id}/mypage called the UserHomePageView as defined in views.py:
from django.views.generic import TemplateView
class UserHomePageView(TemplateView):
template_name = "user_homepage.html"
I want a security measure to prevent anyone (basically non-loggedin-users) to navigating to "/users/{id}/mypage" (e.g /users/5/mypage). e.i If unauthenticated user tries to navigate to /users/5/mypage, he/she should get redirected to signin page/homepage.
Basically, how do i present a unauthenticated user to view pages that are meant for authenticated users (I don't want to use template tags users.authenitcated, but wish to override the TemplateView)
Another solution which is generally used is to decorate the dispatch method with the login_required decorator:
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
class UserHomePageView(TemplateView):
template_name = "user_homepage.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UserHomePageView, self).dispatch(*args, **kwargs)
This has the advantage that it works with all methods (get, post, put, etc.), and that using the login_required decorator is more DRY than explicitly checking request.user.is_authenticated and redirecting.
I found the answer...
By overriding get() and using self.request.user.is_authenticated()
class UserHomePageView(TemplateView):
template_name = "user_homepage.html"
redirect_field_name = "next"
def get(self, request, *args, **kwargs):
'''
Overriding TemplateView.get() in order to
prevent unauthorized user to view UserHomePageView
'''
# Redirect to Homepage if user is not signed in
if not self.request.user.is_authenticated():
return redirect(self.get_redirect_url())
# Process normally if User is signed in
context = self.get_context_data(**kwargs)
return self.render_to_response(context)

How to limit a view to superuser only?

view.py
#login_required
#permission_required('is_superuser')
def score_reset(request):
pass
url.py
url(r'^score-reset/$', score_reset, name='score-reset'),
I have the following code and to my surprise I still hit the function, despite being logged in with a non superuser. I was expecting to get a permission denied.
What am I missing?
is_superuser isn't a permission, it's an attribute on the user model. Django already has another decorator you can make use of called user_passes_test to perform this check:
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: u.is_superuser)
def score_reset(self,...):
...
allowing only super user login
Django is_staff permission decorator
Above answers seems to be for very early versions of django.
They are bit complicated than for the more later version
for django 1.11 here is a bit similar but simpler strategy.
views.py
from django.contrib.auth.decorators import login_required
#login_required
def some_view(request):
if request.user.is_superuser:
//allow access only to superuser
return render(request, 'app/template1.html', args)
else:
//allow access only to user
return render(request, 'app/template2.html', args)
Make use of Django's UserPassesTestMixin
Create a custom mixin SuperuserRequiredMixin
#mixins.py
from django.contrib.auth.mixins import UserPassesTestMixin
class SuperuserRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_superuser
Usage
class SomeSuperUserOnlyView(SuperuserRequiredMixin, ListView):
form_class = ExamForm
template_name = 'exam/newexam.html'
#user_passes_test is not an elegant solution if you want to perform this check on many views.
You can easily write your own decorathor having for example #staff_member_require.
Here you can see one of the possible solutions.
You can use the user passes test decorator to restrict access any way you want. Here is a restriction based on user email example:
from django.contrib.auth.decorators import user_passes_test
def email_check(user):
x = False
if user.email == 'anyemailhere':
x = True
return x
# Create your views here.
#user_passes_test(email_check)
def dash_index(request):
...
More here https://docs.djangoproject.com/en/2.1/topics/auth/default/#the-permission-required-decorator
SuperuserRequiredMixin
Another permission-based mixin. This is specifically for requiring a user to be a superuser. Comes in handy for tools that only privileged users should have access to.
First install: pip install django-braces
views.py
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
class SomeSuperuserView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
template_name = "path/to/template.html"

Set all pages to require login, globally?

I want to redirect access of unauthenticated users to the login page, after which the logged-in user should be redirected to the originally requested page.
According to documentation, this is easily achieved using the #user_passes_test decorator. But it seems I'd have to decorate every view, which is crazy, there are too many and it's error-prone.
What is a good way to turn on this functionality globally (except for a small fixed set of views, such as login)? That is, default everything to logged-in-only + handle anonymous viewing explicitly, where needed.
from django.shortcuts import redirect
from django.conf import settings
class LoginRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.login_url = settings.LOGIN_URL
self.open_urls = [self.login_url] + \
getattr(settings, 'OPEN_URLS', [])
def __call__(self, request):
if not request.user.is_authenticated \
and not request.path_info in self.open_urls:
return redirect(self.login_url+'?next='+request.path)
return self.get_response(request)
have a look at middleware. these are functions run at various points in the request cycle, e.g. before each view is called.
since you may want to exclude certain views from this, i'd look at e.g. how the csrf middleware works, together with the csrf_exempt decorator.
see [SOURCE]/django/views/decorators/csrf.py and [SOURCE]/django/middleware/csrf.py
The way I solved this, was to have mixin class, with the decorator (or whatever code you need). Although you have to remember to call the super(Class, self).get(...) function, so I guess it's not so different after all.
On the other hand, having a set of mixins that does different things I found was quite good at getting a very simple view to do a lot without much code.
Edit
This is how I did in my last project:
class BaseAuthMixin(object):
def auth_check(self, user):
return True
def dispatch(self, request, *args, **kwargs):
if not self.auth_check(request.user):
from django.http import HttpResponseRedirect
from django.contrib.auth import logout
is_web = False
is_live = False
if hasattr(self, 'get_url_name'):
from django.core.urlresolvers import reverse
from django.core.urlresolvers import NoReverseMatch
try:
next = reverse(self.get_url_name(), kwargs=kwargs)
except NoReverseMatch:
next = ''
else:
next= '?next=' + next
logout(request)
redirect_url = settings.LOGIN_URL
redirect_url += next
return HttpResponseRedirect(redirect_url)
else:
return super(BaseAuthMixin, self).dispatch(request, *args, **kwargs)
class LoginRequiredMixin(BaseAuthMixin):
"""
Check if the view needs the user to be logged in.
"""
def auth_check(self, user):
if not super(LoginRequiredMixin, self).auth_check(user):
return False
else:
if hasattr(self, 'login_required'):
if self.login_required and not user.is_authenticated():
return False
return True
class MyDefaultMixin(LoginRequiredMixin):
"""
Mixin that inherits from all common view mixins.
"""
pass
The above is then used by the view-classes (I used Django 1.3 with class-based views):
from django.views.generic import TemplateView
class SomeViewClass(TemplateView, MyDefaultMixin):
# Used by 'LoginRequiredMixin' to check if a user has to be logged in
login_required = True
# Template for the Django TemplateView
template_name = "some_view_template.html"
You need a view to handle the login (with URL in settings.LOGIN_URL), containing a form with a hidden field called next. This field has to be set by a context variable to the page to go to after successful login.
If all views inherit from the base mixin (MyDefaultMixin in my code above), it will automatically check that the user is logged in iv the view contain an attribute called login_required and that is set to True.
There might be better ways to do this, but this is what I did and it worked very well.