Як використовувати декоратори, які вимагають дозвіл, на представленнях класів, що базуються на 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.