วิธีใช้เครื่องมือตกแต่ง _ สิทธิ์ที่ต้องใช้บนมุมมองระดับ django


161

ฉันมีปัญหาเล็กน้อยในการทำความเข้าใจวิธีการทำงานของ CBV ใหม่ คำถามของฉันคือสิ่งนี้ฉันต้องมีการเข้าสู่ระบบในทุกมุมมองและในบางส่วนของพวกเขาสิทธิ์เฉพาะ ในมุมมองที่อิงกับฟังก์ชั่นฉันทำอย่างนั้นกับ @permission_required () และแอตทริบิวต์ login_required ในมุมมอง แต่ฉันไม่รู้วิธีการทำสิ่งนี้ในมุมมองใหม่ มีบางส่วนในเอกสาร django อธิบายเรื่องนี้หรือไม่? ฉันไม่พบอะไรเลย มีอะไรผิดปกติในรหัสของฉัน

ฉันพยายามใช้ @method_decorator แต่มันตอบว่า " TypeError at / 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. ใช้ a 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+) คือการใช้การเข้าถึง mixins เช่น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

มีเหตุผลใดที่จะสนับสนุนวิธีใดวิธีหนึ่งต่อไปนี้?
อลิสแตร์

@ รายการบัญชีฉันคิดว่ามันจะเพิ่มความชอบส่วนตัวและรักษาความสอดคล้องของรหัสฐานภายในทีม / องค์กรของคุณ ฉันมักจะเข้าหาแนวทาง mixin โดยส่วนตัวถ้าฉันสร้างมุมมองตามระดับ
ลี

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) เป็นมาตรฐาน!


เป็นไปได้ไหมที่จะมีมิกซ์อินหลายชนิดนี้? มันไม่ได้ผลสำหรับฉันและฉันไม่คิดว่ามันจะสมเหตุสมผล
Pykler

ใช่มันน่าจะเป็นไปได้ที่จะมีมิกซ์อินหลายอันเนื่องจากมิกซ์แต่ละตัวจะเรียกซูเปอร์ที่เลือกระดับถัดไปตาม MRO
Hobblin

ฉันคิดว่านี่เป็นทางออกที่สง่างาม ฉันไม่ชอบให้มีการผสมผสานของนักตกแต่งใน URL ของฉันและผสมใน views.py นี่เป็นวิธีในการห่อเครื่องมือตกแต่งที่จะย้ายตรรกะทั้งหมดนั้นไปยังมุมมอง
dhackner

1
django-braces มี mixins (และอื่น ๆ ) นี้ - แพ็คเกจที่มีประโยชน์มากในการติดตั้ง
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
นี่ยอดเยี่ยมมากจึงควรพิจารณาการรวม IMO ต้นน้ำ
koniiiik

ฉันรักสิ่งนี้! ฉันสงสัยว่าเป็นไปได้หรือไม่ที่จะส่ง args / kwargs ลงจาก class_view_decorator ไปยัง function_decorator มันจะดีถ้า login_decorator สามารถพูดคำขอตรงตามเงื่อนไข METHOD ดังนั้นมันใช้เฉพาะสำหรับพูดโพสต์?
Mike Waites

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, และPermissionRequiredMixinUserPassesTestMixin

ดังนั้นในการใช้ 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 Mixin: Mixinsจะต้องอยู่ทางด้านซ้ายและคลาสมุมมองพื้นฐานจะต้องอยู่ทางด้านขวา หากคำสั่งซื้อนั้นแตกต่างกันคุณจะได้รับผลลัพธ์ที่ไม่แน่นอนและไม่แน่นอน


2
นี่คือคำตอบที่ดีที่สุดในปี 2562 นอกจากนี้ยังมีจุดที่ดีเกี่ยวกับการสั่งซื้อ mixin
Christian Long

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

หากเป็นเว็บไซต์ที่หน้าเว็บส่วนใหญ่ต้องการให้ผู้ใช้ลงชื่อเข้าใช้คุณสามารถใช้มิดเดิลแวร์เพื่อบังคับให้เข้าสู่ระบบในทุกมุมมองยกเว้นบางคนที่ถูกทำเครื่องหมายเป็นพิเศษ

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

มุมมองบุคคลที่สามที่คุณไม่ต้องการห่อสามารถทำได้ excempt ในการตั้งค่า:

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

คงจะดีที่นี่เป็นแบบในตัวบน 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

ไม่นานมานี้และตอนนี้ 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):
        # ...

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

ฉันได้ทำการแก้ไขนั้นตามวิธีของ Josh

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

ต่อไปนี้เป็นวิธีแก้ปัญหาสำหรับมัณฑนากร decor_required:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.