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

161

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

Я пытался использовать @method_decorator, но он отвечает: « Ошибка TypeError в / 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
Оскар Карбалл
источник

Ответы:

211

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

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

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

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

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

Есть два способа сделать это:

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

    from django.utils.decorators import method_decorator
    
    @method_decorator(login_required, name='dispatch')
    class ViewSpaceIndex(TemplateView):
        template_name = 'secret.html'

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

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Обычная практика в современном Django (2.2+) - использовать миксины доступа, такие как django.contrib.auth.mixins.LoginRequiredMixin, доступные в Django 1.9+ и хорошо изложенные в других ответах здесь:

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

Убедитесь, что вы поместили Mixin на первое место в списке наследования (чтобы в Порядке разрешения методов была выбрана правильная вещь).

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

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

Ли
источник
3
Упоминается здесь в последних документах docs.djangoproject.com/en/dev/topics/class-based-views/intro
Bharathwaaj
как к нему добавить message?
andilabs
Для тех, кто не понимает (как я сначала понял), метод «диспетчеризация» должен быть добавлен в класс
ViewSpaceIndex
Есть ли основания отдавать предпочтение одному из этих методов другому?
Алистер
@Alistair Я думаю, что это сводится к личным предпочтениям и поддержанию согласованности кодовой базы в вашей команде / организации. Я лично склоняюсь к смешанному подходу, если я строю классовые представления.
Ли
118

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

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'

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

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

Герт Стейн
источник
можно ли иметь несколько таких миксинов? Это не сработало для меня, и я не думаю, что это имеет смысл.
Пиклер
Да, должно быть возможно иметь несколько миксинов, так как каждый миксин вызывает супер, который выбирает следующий класс в соответствии с MRO
Хобблин
Я думаю, что это элегантное решение; Мне не нравится иметь смесь декораторов в моем urls.py и mixins в views.py. Это способ обернуть декораторы, которые переместят всю эту логику в представление.
Дакнер
1
У django-braces есть этот (и более) миксин - очень полезный пакет для установки
askvictor
Просто примечание для людей в режиме полной задержки, таких как я: убедитесь, что вы не вошли в систему при тестировании функциональности login_required ...
Visgean Skeloru
46

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

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
mjtamlyn
источник
3
Вы можете использовать это, чтобы красиво оформить цепочку! +1
Пиклер
9
Это так здорово, что следует рассмотреть вопрос о включении восходящей ИМО.
koniiiik
Мне это нравится! Мне интересно, можно ли вообще передать args / kwargs из class_view_decorator в function_decorator ??? Было бы здорово, если бы login_decorator мог сказать условно совпадающий запрос. МЕТОД, чтобы он действовал только для, скажем, сообщения?
Майк Уэйтс
1
Арги / kwargs должны быть легко достижимы при использовании class_view_decorator(my_decorator(*args, **kwargs)). Что касается условного сопоставления методов - вы можете изменить class_view_decorator, чтобы применить себя к View.getили View.postвместо View.dispatch.
mjtamlyn
14

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

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

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. Просто применяя функцию исправления в качестве декоратора, этот декоратор также может работать в представлениях на основе классов.

Мефисто
источник
14

Для тех из вас , кто использует Django> = 1.9 , она уже включена в django.contrib.auth.mixinsкачестве 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: Mixins должен идти с левой стороны, а базовый класс просмотра должен идти с правой стороны. Если порядок отличается, вы можете получить непредсказуемые результаты.

vishes_shell
источник
2
Это лучший ответ в 2019 году. Кроме того, большое замечание по поводу порядка смешивания.
Кристиан Лонг
5

Используйте брекеты 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({})
shap4th
источник
4

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

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

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

settings.py:

LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
kaleissin
источник
3

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

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')
rabbit.aaron
источник
Было бы неплохо, чтобы это было встроено в Django (так же, как method_decoratorесть). Кажется, хороший и читаемый способ достижения этого.
MariusSiuram
1

Это очень легко с Django> 1,9 с поддержкой PermissionRequiredMixinиLoginRequiredMixin

Просто импортируй из аутентификации

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

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

Амар
источник
1

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

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

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):
        # ...
rabbit.aaron
источник
0

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

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)
Винаяк Канияраккал
источник
0

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

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
Ramast
источник
0

Вот решение для allow_required decorator:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Ашиш Сондагар
источник