Как использовать декораторы permission_required в представлениях на основе классов django

#django #django-views #django-authentication #django-class-based-views

#django #django-views #django-аутентификация #представления на основе классов django

Вопрос:

У меня возникли небольшие проблемы с пониманием того, как работают новые CBV. Мой вопрос заключается в следующем: мне нужно требовать входа во все представления, а в некоторых из них — определенных разрешений. В представлениях на основе функций я делаю это с помощью @permission_required() и атрибута login_required в представлении, но я не знаю, как это сделать в новых представлениях. Есть ли какой-нибудь раздел в документах django, объясняющий это? Я ничего не нашел. Что не так в моем коде?

Я попытался использовать @method_decorator, но он отвечает «Ошибка типа в /spaces/prueba/ _wrapped_view() принимает как минимум 1 аргумент (задано 0)«

Вот код (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
  

Ответ №1:

Есть несколько стратегий, перечисленных в документах CBV:

Украсьте представление, когда вы создаете его экземпляр в своих urls.py (документах)

 from django.contrib.auth.decorators import login_required

urlpatterns = [
    path('view/',login_required(ViewSpaceIndex.as_view(..)),
    ...
]
  

Декоратор применяется для каждого экземпляра, поэтому вы можете добавлять его или удалять разными urls.py маршрутами по мере необходимости.

Украсьте свой класс так, чтобы каждый экземпляр вашего представления был обернут (docs)

Это можно сделать двумя способами:

  1. Примените method_decorator к вашему методу отправки CBV, например,

      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'
      

Если вы используете Django < 1.9 (чего делать не следует, он больше не поддерживается), вы не можете использовать method_decorator в классе, поэтому вам придется переопределить dispatch метод вручную:

     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)
  
  1. Используйте смесь, подобную django.contrib.auth.mixins.LoginRequiredMixin хорошо описан в других ответах здесь:

      from django.contrib.auth.mixins import LoginRequiredMixin
    
     class MyView(LoginRequiredMixin, View):
    
         login_url = '/login/'
         redirect_field_name = 'redirect_to'
      

Убедитесь, что вы разместили класс mixin первым в списке наследования (чтобы алгоритм порядка разрешенияметода Python выбрал правильную вещь).

Причина, по которой вы получаете TypeError , объясняется в документах:

Примечание: method_decorator передает *args и ** kwargs в качестве параметров к оформленному методу в классе. Если ваш метод не принимает совместимый набор параметров, это вызовет исключение TypeError.

Комментарии:

1. Упоминается здесь в последних документах docs.djangoproject.com/en/dev/topics/class-based-views/intro

2. как добавить message к нему?

3. Для тех, кто не понял (как и я сначала) — метод ‘dispatch’ должен быть добавлен в класс ViewSpaceIndex

4. Есть ли какая-либо причина отдавать предпочтение одному из этих методов перед другим?

5. @Alistair Я думаю, что это сводится к личным предпочтениям и поддержанию согласованности кодовой базы в вашей команде / организации. Я лично склоняюсь к смешанному подходу, если я создаю представления на основе классов.

Ответ №2:

Вот мой подход, я создаю защищенный mixin (он хранится в моей библиотеке mixin):

 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)
  

Всякий раз, когда вы хотите, чтобы представление было защищено, вы просто добавляете соответствующий микс:

 class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
    template_name = 'index.html'
  

Просто убедитесь, что ваш микс является первым.

Обновление: Я опубликовал это в way еще в 2011 году, начиная с версии 1.9, теперь Django включает этот и другие полезные микшины (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) в стандартную комплектацию!

Комментарии:

1. возможно ли иметь несколько таких миксинов? У меня это не сработало, и я не думаю, что это имеет смысл.

2. Да, должно быть возможно иметь несколько миксинов, поскольку каждый миксин выполняет вызов super, который выбирает следующий класс в соответствии с MRO

3. Я думаю, что это элегантное решение; Мне не нравится иметь смесь декораторов в моем urls.py и микширует в views.py. Это способ обернуть декораторы, которые перенесут всю эту логику в представление.

4. в django-braces есть этот (и не только) mixins — очень полезный пакет для установки

5. Просто замечание для людей в режиме полной задержки, таких как я: убедитесь, что вы не вошли в систему при тестировании функциональности login_required…

Ответ №3:

Вот альтернатива использованию декораторов на основе классов:

 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
  

Затем это можно использовать просто так:

 @class_view_decorator(login_required)
class MyView(View):
    # this view now decorated
  

Комментарии:

1. Вы можете использовать это для создания цепочки декораторов представлений, красиво! 1

2. Это настолько здорово, что его следует рассмотреть для включения в восходящий IMO.

3. Мне это нравится! Мне интересно, возможно ли вообще передать аргументы / kwargs из class_view_decorator в function_decorator??? Было бы здорово, если бы login_decorator мог указать запрос на условное соответствие. МЕТОД, поэтому он применяется только, скажем, к post?

4. Аргументы / kwargs должны быть легко достижимы с помощью class_view_decorator(my_decorator(*args, **kwargs)) . Что касается сопоставления условных методов — вы могли бы изменить class_view_decorator, чтобы он применялся к View.get или View.post вместо View.dispatch .

Ответ №4:

Для тех из вас, кто использует Django >= 1.9, это уже включено в django.contrib.auth.mixins as AccessMixin , LoginRequiredMixin , PermissionRequiredMixin и UserPassesTestMixin .

Итак, чтобы применить LoginRequired к CBV (например, 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'
  

Также полезно иметь в виду порядок смешивания GCBV: микшины должны располагаться с левой стороны, а класс базового представления должен располагаться с правой стороны. Если порядок отличается, вы можете получить сломанные и непредсказуемые результаты.

Комментарии:

1. Это лучший ответ в 2019 году. Кроме того, отличный момент о порядке смешивания.

Ответ №5:

Я понимаю, что эта тема немного устарела, но в любом случае вот мои два цента.

со следующим кодом:

 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
  

теперь у нас есть способ исправить декоратор, чтобы он стал многофункциональным. Это фактически означает, что при применении к обычному декоратору представления, например:

 login_required = patch_view_decorator(login_required)
  

этот декоратор все равно будет работать при использовании так, как он был изначально задуман:

 @login_required
def foo(request):
    return HttpResponse('bar')
  

но также будет работать должным образом при использовании следующим образом:

 @login_required
class FooView(DetailView):
    model = Foo
  

Кажется, это отлично работает в нескольких случаях, с которыми я недавно столкнулся, включая этот реальный пример:

 @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
  

Функция ajax_view написана для изменения представления (на основе функций), так что она выдает ошибку 404 всякий раз, когда это представление посещается не ajax-вызовом. Простым применением функции patch в качестве декоратора этот декоратор полностью настроен для работы и в представлениях на основе классов

Ответ №6:

Используйте фигурные скобки Django. Он предоставляет множество полезных миксинов, которые легко доступны. В нем есть прекрасные документы. Попробуйте.

Вы даже можете создавать свои собственные микшины.

http://django-braces.readthedocs.org/en/v1.4.0/

Пример кода:

 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({})
  

Ответ №7:

В моем коде я написал этот адаптер для адаптации функций-членов к функции, не являющейся членом:

 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
  

Вы можете просто использовать это следующим образом:

 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')
  

Комментарии:

1. Было бы неплохо, чтобы это было встроено в Django (точно так же, как method_decorator есть). Это кажется приятным и читаемым способом достижения этого.

Ответ №8:

Если это сайт, на большинстве страниц которого требуется, чтобы пользователь вошел в систему, вы можете использовать промежуточное программное обеспечение для принудительного входа во все представления, за исключением тех, которые отмечены особо.

До 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
  

Сторонние представления, которые вы не хотите переносить, можно исключить в настройках:

settings.py:

 LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
  

Ответ №9:

Прошло уже некоторое время, и теперь Django так сильно изменился.

Посмотрите здесь, как оформить представление на основе классов.

https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class

В документации не было примера «декораторов, которые принимают любой аргумент». Но декораторы, которые принимают аргументы, выглядят следующим образом:

 def mydec(arg1):
    def decorator(func):
         def decorated(*args, **kwargs):
             return func(*args, **kwargs)   arg1
         return decorated
    return deocrator
  

итак, если мы хотим использовать mydec как «обычный» декоратор без аргументов, мы можем сделать это:

 mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5
  

Аналогично, использовать permission_required с method_decorator

мы можем сделать:

 @method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
    def get(self, request):
        # ...
  

Комментарии:

1. Я думаю, что это самый простой способ получить разрешения в базовых представлениях класса, проверяя другие разрешения, которые являются классическими @login_required … Также вы можете передать более одного пользовательского разрешения следующим образом @permission_required(['polls.can_vote', 'polls.change_vote']) .

Ответ №10:

Я внес это исправление на основе решения Джоша

 class LoginRequiredMixin(object):

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
  

Пример использования:

 class EventsListView(LoginRequiredMixin, ListView):

    template_name = "events/list_events.html"
    model = Event
  

Ответ №11:

Это очень просто, поскольку django > 1.9 поставляется с поддержкой PermissionRequiredMixin и LoginRequiredMixin

Просто импортируйте из auth

views.py

 from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass
  

Для получения более подробной информации прочитайте Авторизация в django

Ответ №12:

Если вы выполняете проект, для которого требуется множество тестов разрешений, вы можете наследовать этот класс.

 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)
  

Ответ №13:

Вот решение для декоратора permission_required:

 class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True