Làm thế nào để sử dụng trang trí cấp phép cho các khung nhìn dựa trên lớp django


161

Tôi gặp một chút khó khăn khi hiểu cách thức hoạt động của CBV mới. Câu hỏi của tôi là này, tôi cần yêu cầu đăng nhập trong tất cả các chế độ xem và trong một số trong số đó, các quyền cụ thể. Trong các chế độ xem dựa trên chức năng, tôi thực hiện điều đó với @ allow_Vquired () và thuộc tính login_Vquired trong chế độ xem, nhưng tôi không biết cách thực hiện điều này trên các chế độ xem mới. Có một số phần trong tài liệu django giải thích điều này? Tôi không tìm thấy gì cả. Có gì sai trong mã của tôi?

Tôi đã thử sử dụng @method_decorator nhưng nó trả lời " TypeError at / space / prueba / _wrapping_view () mất ít nhất 1 đối số (0 đã cho) "

Đây là mã (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

Câu trả lời:


211

Có một vài chiến lược được liệt kê trong tài liệu CBV :

Trang trí chế độ xem trên cơ sở từng trường hợp, urls.pykhi bạn khởi tạo chế độ xem ( tài liệu ) của mình

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

Trình trang trí được áp dụng trên cơ sở từng trường hợp, vì vậy bạn có thể thêm hoặc xóa nó trong các urls.pytuyến đường khác nhau khi cần thiết.

Trang trí lớp học của bạn để mọi trường hợp của chế độ xem của bạn sẽ được bao bọc bởi trình trang trí ( tài liệu )

Có hai cách bạn có thể làm điều này:

  1. Áp dụng một method_decoratorphương pháp điều phối CBV của bạn, vd

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

Nếu bạn đang sử dụng Django <1.9 (mà bạn không nên sử dụng, nó không còn được hỗ trợ nữa), bạn không thể sử dụng method_decoratortrên lớp, vì vậy bạn phải ghi đè dispatchphương thức:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Một thực tế phổ biến trong Django hiện đại (2.2+) là sử dụng các mixin truy cập như django.contrib.auth.mixins.LoginRequiredMixin có sẵn trong Django 1.9+ và được nêu rõ trong các câu trả lời khác tại đây:

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

Hãy chắc chắn rằng bạn đặt Mixin đầu tiên trong danh sách thừa kế (để Thứ tự giải quyết phương pháp chọn điều đúng).

Lý do bạn nhận được một TypeErrorđược giải thích trong các tài liệu:

Lưu ý: method_decorator truyền * args và ** kwargs làm tham số cho phương thức được trang trí trên lớp. Nếu phương thức của bạn không chấp nhận một bộ tham số tương thích, nó sẽ đưa ra một ngoại lệ TypeError.


3
Được đề cập ở đây trong tài liệu mới nhất docs.djangoproject.com/en/dev/topics/ class
basing -views / intro

Làm thế nào để nối messagenó?
andilabs

Đối với những người không hiểu (như tôi đã làm lúc đầu) - nên thêm phương thức 'công văn' vào lớp
ViewSpace Index

Có bất kỳ lý do để ủng hộ một trong những phương pháp này hơn các phương pháp khác?
Alistair

@Alistair Tôi nghĩ rằng nó tập trung vào sở thích cá nhân và duy trì tính nhất quán của cơ sở mã trong nhóm / tổ chức của bạn. Cá nhân tôi có xu hướng hướng tới cách tiếp cận mixin nếu tôi đang xây dựng các quan điểm dựa trên lớp.
Một Lee

118

Đây là cách tiếp cận của tôi, tôi tạo một mixin được bảo vệ (cái này được giữ trong thư viện mixin của tôi):

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)

Bất cứ khi nào bạn muốn một chế độ xem được bảo vệ, bạn chỉ cần thêm mixin thích hợp:

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

Chỉ cần chắc chắn rằng mixin của bạn là đầu tiên.

Cập nhật: Tôi đã đăng bài này theo cách trở lại vào năm 2011, bắt đầu với phiên bản 1.9 Django hiện bao gồm các mixin hữu ích này (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) làm tiêu chuẩn!


Có thể có nhiều loại mixin này không? Nó không làm việc cho tôi và tôi không nghĩ nó có ý nghĩa.
Pykler

Có, có thể có nhiều mixin vì mỗi mixin thực hiện một cuộc gọi đến siêu chọn lớp tiếp theo theo MRO
Hobblin

Tôi nghĩ rằng đây là một giải pháp tao nhã; Tôi không thích có một hỗn hợp các công cụ trang trí trong url của mình và mixins trong lượt xem. Đây là một cách để bọc các nhà trang trí sẽ di chuyển tất cả logic đó vào chế độ xem.
dhackner

1
django-niềng răng có mixins này (và hơn thế nữa) - một gói rất hữu ích để cài đặt
Askvictor

Chỉ là một lưu ý cho những người ở chế độ chậm hoàn toàn như tôi: đảm bảo rằng bạn chưa đăng nhập khi kiểm tra chức năng đăng nhập yêu cầu ...
Visgean Sk Bacheloru 17/03/2015

46

Đây là một thay thế sử dụng trang trí dựa trên lớp:

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

Điều này sau đó có thể được sử dụng đơn giản như thế này:

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

3
Bạn có thể sử dụng điều này để trang trí xem chuỗi, độc đáo! +1
Pykler

9
Điều này thật tuyệt vời nên được xem xét để đưa vào IMO ngược dòng.
koniiiik

Tôi thích điều này! Tôi tự hỏi liệu có thể chuyển args / kwargs từ class_view_decorator sang function_decorator không? Sẽ thật tuyệt nếu login_decorator có thể nói yêu cầu khớp có điều kiện.METHOD để nó chỉ áp dụng cho bài nói?
Mike Chờ

1
Các args / kwargs có thể dễ dàng đạt được bằng cách sử dụng class_view_decorator(my_decorator(*args, **kwargs)). Đối với phương thức khớp có điều kiện - bạn có thể sửa đổi class_view_decorator để áp dụng chính nó View.gethoặc View.postthay vì View.dispatch.
mjtamlyn

14

Tôi nhận ra chủ đề này là một chút ngày, nhưng dù sao đây cũng là hai xu của tôi.

với đoạn mã sau:

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

bây giờ chúng ta có một cách để vá một trang trí, vì vậy nó sẽ trở thành đa chức năng. Điều này có nghĩa là khi áp dụng cho một trang trí xem thông thường, như vậy:

login_required = patch_view_decorator(login_required)

trang trí này sẽ vẫn hoạt động khi được sử dụng theo cách nó được dự định ban đầu:

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

nhưng cũng sẽ hoạt động đúng khi được sử dụng như vậy:

@login_required
class FooView(DetailView):
    model = Foo

Điều này dường như hoạt động tốt trong một số trường hợp tôi đã gặp gần đây, bao gồm cả ví dụ thực tế này:

@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

Hàm ajax_view được viết để sửa đổi chế độ xem (dựa trên chức năng), do đó, nó sẽ gây ra lỗi 404 bất cứ khi nào chế độ xem này được truy cập bởi một cuộc gọi không phải là ajax. Chỉ cần áp dụng chức năng vá như một trình trang trí, trình trang trí này hoàn toàn được thiết lập để hoạt động trong các khung nhìn dựa trên lớp.


14

Đối với những người bạn của những người sử dụng Django> = 1.9 , nó đã được bao gồm trong django.contrib.auth.mixinsnhư AccessMixin, LoginRequiredMixin, PermissionRequiredMixinUserPassesTestMixin.

Vì vậy, để áp dụng LoginRequired cho CBV (ví dụ 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'

Nó cũng tốt để giữ trong tâm trí GCBV Mixin theo thứ tự: mixins phải đi trên trái bên, và xem cơ sở lớp phải đi theo đúng phụ. Nếu thứ tự khác nhau, bạn có thể nhận được kết quả bị hỏng và không thể đoán trước.


2
Đây là câu trả lời tốt nhất trong năm 2019. Ngoài ra, điểm tuyệt vời về thứ tự mixin.
Christian Long

5

Sử dụng niềng răng Django. Nó cung cấp rất nhiều mixin hữu ích dễ dàng có sẵn. Nó có tài liệu đẹp. Hãy thử nó.

Bạn thậm chí có thể tạo mixins tùy chỉnh của bạn.

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

Mã ví dụ:

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

Nếu đó là một trang web nơi phần lớn các trang yêu cầu người dùng đăng nhập, bạn có thể sử dụng phần mềm trung gian để buộc đăng nhập trên tất cả các chế độ xem trừ một số người được đánh dấu đặc biệt.

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) 

lượt xem:

def public(request, *args, **kwargs):
    ...
public.login_required = False

class PublicView(View):
    ...
public_view = PublicView.as_view()
public_view.login_required = False

Chế độ xem của bên thứ ba mà bạn không muốn bọc có thể được miễn trừ trong cài đặt:

cài đặt:

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

3

Trong mã của tôi, tôi đã viết bộ điều hợp này để điều chỉnh các chức năng thành viên thành chức năng không phải thành viên:

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

Bạn chỉ có thể sử dụng nó như thế này:

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

Thật tuyệt khi đây là một Django tích hợp (giống như vậy method_decorator). Có vẻ như một cách tốt đẹp và dễ đọc để đạt được điều này.
MariusSiuram

1

Điều này cực dễ với django> 1.9 đi kèm với sự hỗ trợ cho PermissionRequiredMixinLoginRequiredMixin

Chỉ cần nhập từ auth

lượt xem

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Để biết thêm chi tiết đọc Ủy quyền trong django


1

Đã được một thời gian và bây giờ Django đã thay đổi rất nhiều.

Kiểm tra ở đây để làm thế nào để trang trí một cái nhìn dựa trên lớp.

https://docs.djangoproject.com/en/2.2/topics/group-basing-view/intro/#decorating-the- class

Các tài liệu không bao gồm một ví dụ về "trang trí có bất kỳ đối số". Nhưng các nhà trang trí có lập luận như thế này:

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

Vì vậy, nếu chúng ta sử dụng mydec như một trang trí "bình thường" mà không cần đối số, chúng ta có thể làm điều này:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

Tương tự như vậy, để sử dụng permission_requiredvớimethod_decorator

chúng tôi có thể làm:

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

0

Nếu bạn đang làm một dự án đòi hỏi nhiều bài kiểm tra cấp phép, bạn có thể kế thừa lớp này.

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

Tôi đã thực hiện sửa lỗi đó dựa trên giải pháp của Josh

class LoginRequiredMixin(object):

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

Sử dụng mẫu:

class EventsListView(LoginRequiredMixin, ListView):

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

0

Đây là giải pháp cho phép trang trí cho phép:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.