DRF custom check_permission not being called - django

I'm trying to add a custom permission to a view which extends a generic DetailView:
from django.views.generic import DetailView
from rest_framework.permissions import BasePermission
class MyCustomPermission(BasePermission):
def has_permission(self, request, view):
return False
class MyView(SomeMixin, DetailView):
model = MyObject
template_name = "my_template.html"
permission_classes = [MyCustomPermission]
def get_object(self):
return MyObject.objects.get(id=123)
This should always fail, but it doesn't. I can also put a breakpoint in there (e.g. import pudb; pudb.set_trace()) but that won't ever get hit either.
I know that has_object_permission(...) needs to be explicitely called, but I thought has_permission(...) got called when the view first got called.

You are using Django generic DetailView and expecting to behave as DRF views

Related

Custom view and url for DRF

I'd like to know how to add a custom view and url using DRF.
I currently have a UserDetail(APIView) class that can display a user object using a url like /users/123/ but I'd like to also have the ability to view a users history with a url like /users/123/history/ which would likely call on a new method within the UserDetail class. Is there a way to do this?
I've tried looking through DRFs documentation and it looks like they can achieve this through ViewSets and custom Routers, but I get errors when using ViewSets likes needing to define a queryset.
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class UserDetail(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserCreateSerializer
permission_classes = (IsAuthenticated,)
#detail_route(methods=['GET'])
def history(self, request, pk):
user= self.get_object()
serializer = UserCreateSerializer(user)
return Response(serializer.data)

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'

Django REST Framework - Custom Permissions not Evaluating

I'm trying to set custom permissions on a class that extends viewsets.ModelViewSet and it appears that my permissions are not being evaluated. Here is my view:
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
import models
import serializers
from permissions import IsAdminOrAuthenticatedReadOnly
class KPIViewSet(viewsets.ModelViewSet):
'''
API endpoint that allows KPI metadata to be viewed or edited
'''
authentication_classes = (BasicAuthentication,)
permission_classes = (IsAdminOrAuthenticatedReadOnly,)
queryset = models.KPI.objects.all()
serializer_class = serializers.KPISerializer
And here is my permission class:
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsAdminOrAuthenticatedReadOnly(BasePermission):
def has_permissions(self, request, view):
if request.method in SAFE_METHODS:
return request.user and request.user.is_authenticated()
return request.user and request.user.is_staff()
The problem I'm running into is that IsAdminOrAuthenticatedReadOnly never seems to get evaluated. I tested this both by forcing it to always return "False" and by switching the permission_classes value to "IsAuthenticated" in the view. In the former scenario, a request to the endpoint returns as if there were no authentication requirement. In the later, authentication is enforced as expected.
Any ideas what I'm missing?
The method name is has_permission not has_permissions (no s) ;)

How to make a Django view ONLY accessible to Unauthenticated users?

I'm building a Django API view by extending the rest_framework.views.APIView class.
I have successfully built many APIs that are only callable by an authenticated user. I have done this by adding: permission_classes = [permissions.IsAuthenticated,]
There are some APIs that I only want unauthenticated users to call. Such as "ForgotPassword". Basically, I want to ensure that the API caller doesn't send in the JWT Token in the request header. How can I enforce that? There is no permissions.IsUnAuthenticated.
you can easily create your own IsNotAuthenticated class
something like this:
from rest_framework.permissions import BasePermission
class IsNotAuthenticated(BasePermission):
"""
Allows access only to non authenticated users.
"""
def has_permission(self, request, view):
return not request.user.is_authenticated()
then: permission_classes = (myapp.permissions.IsNotAuthenticated,)
regards.
In case you are using function based view then it would be good if you use the following.
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: not u.is_authenticated())
Or you can do it in permissions.py like this (For who were getting bool object error)
from rest_framework import permissions
class IsNotAuthenticated(permissions.BasePermission):
def has_permission(self, request, view):
return not request.user.is_authenticated
And in the main view
from .permissions import IsNotAuthenticated
permission_classes = [IsNotAuthenticated]
The following answer is for Django not Django REST Framework
For a Class-Based View, make a custom mixin like this
class IsNotAuthenticatedMixin(UserPassesTestMixin):
"""
Allows access only to non authenticated users.
"""
def test_func(self):
return not self.request.user.is_authenticated
def handle_no_permission(self):
return redirect('home')
You can inherit any Class based view from IsNotAuthenticatedMixin

How to use permission_required decorators on django class-based views

I'm having a bit of trouble understanding how the new CBVs work. My question is this, I need to require login in all the views, and in some of them, specific permissions. In function-based views I do that with #permission_required() and the login_required attribute in the view, but I don't know how to do this on the new views. Is there some section in the django docs explaining this? I didn't found anything. What is wrong in my code?
I tried to use the #method_decorator but it replies "TypeError at /spaces/prueba/ _wrapped_view() takes at least 1 argument (0 given)"
Here is the code (GPL):
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required, permission_required
class ViewSpaceIndex(DetailView):
"""
Show the index page of a space. Get various extra contexts to get the
information for that space.
The get_object method searches in the user 'spaces' field if the current
space is allowed, if not, he is redirected to a 'nor allowed' page.
"""
context_object_name = 'get_place'
template_name = 'spaces/space_index.html'
#method_decorator(login_required)
def get_object(self):
space_name = self.kwargs['space_name']
for i in self.request.user.profile.spaces.all():
if i.url == space_name:
return get_object_or_404(Space, url = space_name)
self.template_name = 'not_allowed.html'
return get_object_or_404(Space, url = space_name)
# Get extra context data
def get_context_data(self, **kwargs):
context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
place = get_object_or_404(Space, url=self.kwargs['space_name'])
context['entities'] = Entity.objects.filter(space=place.id)
context['documents'] = Document.objects.filter(space=place.id)
context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
return context
There are a few strategies listed in the CBV docs:
Decorate the view when you instantiate it in your urls.py (docs)
from django.contrib.auth.decorators import login_required
urlpatterns = [
path('view/',login_required(ViewSpaceIndex.as_view(..)),
...
]
The decorator is applied on a per-instance basis, so you can add it or remove it in different urls.py routes as needed.
Decorate your class so every instance of your view is wrapped (docs)
There's two ways to do this:
Apply method_decorator to your CBV dispatch method e.g.,
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
#method_decorator(login_required, name='dispatch')
class ViewSpaceIndex(TemplateView):
template_name = 'secret.html'
If you're using Django < 1.9 (which you shouldn't, it's no longer supported) you can't use method_decorator on the class, so you have to override the dispatch method manually:
from django.contrib.auth.decorators import login_required
class ViewSpaceIndex(TemplateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
Use a mixin like django.contrib.auth.mixins.LoginRequiredMixin outlined well in the other answers here:
from django.contrib.auth.mixins import LoginRequiredMixin
class MyView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
Make sure you place the mixin class first in the inheritance list (so Python's Method Resolution Order algorithm picks the Right Thing).
The reason you're getting a TypeError is explained in the docs:
Note:
method_decorator passes *args and **kwargs as parameters to the decorated method on the class. If your method does not accept a compatible set of parameters it will raise a TypeError exception.
Here is my approach, I create a mixin that is protected (this is kept in my mixin library):
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
class LoginRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
Whenever you want a view to be protected you just add the appropriate mixin:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
Just make sure that your mixin is first.
Update: I posted this in way back in 2011, starting with version 1.9 Django now includes this and other useful mixins (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) as standard!
Here's an alternative using class based decorators:
from django.utils.decorators import method_decorator
def class_view_decorator(function_decorator):
"""Convert a function based decorator into a class based decorator usable
on class based Views.
Can't subclass the `View` as it breaks inheritance (super in particular),
so we monkey-patch instead.
"""
def simple_decorator(View):
View.dispatch = method_decorator(function_decorator)(View.dispatch)
return View
return simple_decorator
This can then be used simply like this:
#class_view_decorator(login_required)
class MyView(View):
# this view now decorated
For those of you who use Django >= 1.9, it's already included in django.contrib.auth.mixins as AccessMixin, LoginRequiredMixin, PermissionRequiredMixin and UserPassesTestMixin.
So to apply LoginRequired to CBV(e.g. DetailView):
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView
class ViewSpaceIndex(LoginRequiredMixin, DetailView):
model = Space
template_name = 'spaces/space_index.html'
login_url = '/login/'
redirect_field_name = 'redirect_to'
It's also good to keep in mind GCBV Mixin order: Mixins must go on the left side, and the base view class must go in the right side. If the order is different you can get broken and unpredictable results.
I realise this thread is a bit dated, but here's my two cents anyway.
with the following code:
from django.utils.decorators import method_decorator
from inspect import isfunction
class _cbv_decorate(object):
def __init__(self, dec):
self.dec = method_decorator(dec)
def __call__(self, obj):
obj.dispatch = self.dec(obj.dispatch)
return obj
def patch_view_decorator(dec):
def _conditional(view):
if isfunction(view):
return dec(view)
return _cbv_decorate(dec)(view)
return _conditional
we now have a way to patch a decorator, so it will become multifunctional. This effectively means that when applied to a regular view decorator, like so:
login_required = patch_view_decorator(login_required)
this decorator will still work when used the way it was originally intended:
#login_required
def foo(request):
return HttpResponse('bar')
but will also work properly when used like so:
#login_required
class FooView(DetailView):
model = Foo
This seems to work fine in several cases i've recently come across, including this real-world example:
#patch_view_decorator
def ajax_view(view):
def _inner(request, *args, **kwargs):
if request.is_ajax():
return view(request, *args, **kwargs)
else:
raise Http404
return _inner
The ajax_view function is written to modify a (function based) view, so that it raises a 404 error whenever this view is visited by a non ajax call. By simply applying the patch function as a decorator, this decorator is all set to work in class based views as well
Use Django Braces. It provides a lot of useful mixins that is easily available.
It has beautiful docs. Try it out.
You can even create your custom mixins.
http://django-braces.readthedocs.org/en/v1.4.0/
Example Code:
from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
class SomeSecretView(LoginRequiredMixin, TemplateView):
template_name = "path/to/template.html"
#optional
login_url = "/signup/"
redirect_field_name = "hollaback"
raise_exception = True
def get(self, request):
return self.render_to_response({})
In my code I have written this adapter to adapt member functions to a non-member function:
from functools import wraps
def method_decorator_adaptor(adapt_to, *decorator_args, **decorator_kwargs):
def decorator_outer(func):
#wraps(func)
def decorator(self, *args, **kwargs):
#adapt_to(*decorator_args, **decorator_kwargs)
def adaptor(*args, **kwargs):
return func(self, *args, **kwargs)
return adaptor(*args, **kwargs)
return decorator
return decorator_outer
You can simply use it like this:
from django.http import HttpResponse
from django.views.generic import View
from django.contrib.auth.decorators import permission_required
from some.where import method_decorator_adaptor
class MyView(View):
#method_decorator_adaptor(permission_required, 'someapp.somepermission')
def get(self, request):
# <view logic>
return HttpResponse('result')
If it's a site where the majority of pages requires the user to be logged in, you can use a middleware to force login on all views except some who are especially marked.
Pre Django 1.10 middleware.py:
from django.contrib.auth.decorators import login_required
from django.conf import settings
EXEMPT_URL_PREFIXES = getattr(settings, 'LOGIN_EXEMPT_URL_PREFIXES', ())
class LoginRequiredMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
path = request.path
for exempt_url_prefix in EXEMPT_URL_PREFIXES:
if path.startswith(exempt_url_prefix):
return None
is_login_required = getattr(view_func, 'login_required', True)
if not is_login_required:
return None
return login_required(view_func)(request, *view_args, **view_kwargs)
views.py:
def public(request, *args, **kwargs):
...
public.login_required = False
class PublicView(View):
...
public_view = PublicView.as_view()
public_view.login_required = False
Third-party views you don't want to wrap can be made excempt in the settings:
settings.py:
LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
It has been a while now and now Django has changed so much.
Check here for how to decorate a class-based view.
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class
The documentation did not include an example of "decorators that takes any argument". But decorators that take arguments are like this:
def mydec(arg1):
def decorator(func):
def decorated(*args, **kwargs):
return func(*args, **kwargs) + arg1
return decorated
return deocrator
so if we are to use mydec as a "normal" decorator without arguments, we can do this:
mydecorator = mydec(10)
#mydecorator
def myfunc():
return 5
So similarly, to use permission_required with method_decorator
we can do:
#method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
def get(self, request):
# ...
I've made that fix based on Josh's solution
class LoginRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
Sample usage:
class EventsListView(LoginRequiredMixin, ListView):
template_name = "events/list_events.html"
model = Event
This is super easy with django > 1.9 coming with support for PermissionRequiredMixin and LoginRequiredMixin
Just import from the auth
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class YourListView(LoginRequiredMixin, Views):
pass
For more details read Authorization in django
If you are doing a project which requires variety of permission tests, you can inherit this class.
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.views.generic import View
from django.utils.decorators import method_decorator
class UserPassesTest(View):
'''
Abstract base class for all views which require permission check.
'''
requires_login = True
requires_superuser = False
login_url = '/login/'
permission_checker = None
# Pass your custom decorator to the 'permission_checker'
# If you have a custom permission test
#method_decorator(self.get_permission())
def dispatch(self, *args, **kwargs):
return super(UserPassesTest, self).dispatch(*args, **kwargs)
def get_permission(self):
'''
Returns the decorator for permission check
'''
if self.permission_checker:
return self.permission_checker
if requires_superuser and not self.requires_login:
raise RuntimeError((
'You have assigned requires_login as False'
'and requires_superuser as True.'
" Don't do that!"
))
elif requires_login and not requires_superuser:
return login_required(login_url=self.login_url)
elif requires_superuser:
return user_passes_test(lambda u:u.is_superuser,
login_url=self.login_url)
else:
return user_passes_test(lambda u:True)
Here the solution for permission_required decorator:
class CustomerDetailView(generics.GenericAPIView):
#method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
def post(self, request):
# code...
return True