Як використовувати декоратори, які вимагають дозвіл, на представленнях класів, що базуються на django


161

У мене є проблеми з розумінням того, як працюють нові КБС. Моє запитання таке: мені потрібно вимагати входу в усі перегляди, а в деяких із них - конкретні дозволи. У представленнях на основі функцій я це роблю з @permission_required () та атрибутом login_required у представленні, але я не знаю, як це зробити для нових представлень. Чи є якийсь розділ у документах про джанго, що пояснює це? Я нічого не знайшов. Що не так у моєму коді?

Я спробував використати @method_decorator, але він відповідає " TypeError в / space / 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'

Переконайтеся, що ви вперше помістили Міксин у список спадкування (тому Порядок вирішення методу вибирає правильну річ).

Причина, яку ви отримуєте TypeError, пояснюється в документах:

Примітка: method_decorator передає * args та ** kwargs як параметри декорованому методу на класі. Якщо ваш метод не приймає сумісний набір параметрів, він призведе до виключення TypeError.



як до неї додати 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 є суміш декораторів і міксін в view.py. Це спосіб обгортати декораторів, які перенесуть всю цю логіку на вигляд.
dhackner

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

3
Ви можете використати це для створення декораторів виду ланцюга, прекрасно! +1
Pykler

9
Це так чудово, що його слід враховувати для включення в ІМО вище.
koniiiik

Я обожнюю це! Мені цікаво, чи взагалі можна передавати args / kwargs вниз від class_view_decorator до function_decorator ??? Було б чудово, якби login_decorator міг сказати умовно відповідність запиту.
Майк Уейтс

1
Args / 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.

Отже, застосувати Потрібне вхід до 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 Mixin: Mixins повинен переходити з лівого боку, а базовий клас подання повинен знаходитись у правій частині. Якщо порядок інший, ви можете отримати зламані та непередбачувані результати.


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

4

Якщо це веб-сайт, на якому більшість сторінок вимагає входу користувача, ви можете використовувати проміжне програмне забезпечення, щоб примусити ввійти у всі перегляди, за винятком тих, які особливо позначені.

Попередній 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/')

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

Було б добре, що це був вбудований на Джанго (як і method_decoratorє). Здається, приємний і читабельний спосіб досягти цього.
МаріусСіурам

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):
        # ...

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

0

Ось рішення для дозволу_потрібного декоратора:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.