Cara menggunakan dekorator permit_required pada pandangan berbasis kelas Django

161

Saya mengalami sedikit kesulitan memahami cara kerja CBV baru. Pertanyaan saya adalah ini, saya perlu meminta login di semua tampilan, dan di beberapa dari mereka, izin khusus. Dalam tampilan berbasis fungsi saya melakukannya dengan @permission_required () dan atribut login_required dalam tampilan, tapi saya tidak tahu bagaimana melakukan ini pada tampilan baru. Apakah ada bagian dalam dokumen django yang menjelaskan hal ini? Saya tidak menemukan apa pun. Apa yang salah dalam kode saya?

Saya mencoba menggunakan @method_decorator tetapi menjawab " TypeError at / spasi / prueba / _wrapped_view () membutuhkan setidaknya 1 argumen (0 diberikan) "

Berikut adalah kode (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
Oscar Carballal
sumber

Jawaban:

211

Ada beberapa strategi yang tercantum dalam dokumen CBV :

Hiasi tampilan berdasarkan per-instance, di urls.pysaat Anda membuat tampilan ( docs ) Anda

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

Dekorator diterapkan berdasarkan per instance, sehingga Anda dapat menambahkan atau menghapusnya dalam urls.pyrute yang berbeda sesuai kebutuhan.

Hiasi kelas Anda sehingga setiap instance tampilan Anda akan dibungkus oleh dekorator ( dokumen )

Ada dua cara Anda bisa melakukan ini:

  1. Menerapkan method_decoratormetode pengiriman CBV Anda misalnya,

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

Jika Anda menggunakan Django <1.9 (yang tidak seharusnya, tidak lagi didukung) yang tidak dapat Anda gunakan method_decoratordi kelas, jadi Anda harus mengganti dispatchmetode:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Praktik umum dalam Django modern (2.2+) adalah menggunakan akses mixin seperti django.contrib.auth.mixins.LoginRequiredMixin tersedia di Django 1.9+ dan diuraikan dengan baik dalam jawaban lain di sini:

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

Pastikan Anda menempatkan Mixin sebagai yang pertama dalam daftar warisan (jadi Metode Resolusi Resolusi memilih Hal yang Benar).

Alasan Anda mendapatkan TypeErrordijelaskan dalam dokumen:

Catatan: method_decorator meneruskan * args dan ** kwargs sebagai parameter ke metode yang didekorasi di kelas. Jika metode Anda tidak menerima set parameter yang kompatibel, itu akan memunculkan pengecualian TypeError.

A Lee
sumber
3
Disebutkan di sini dalam dokumen terbaru docs.djangoproject.com/en/dev/topics/class-based-views/intro
Bharathwaaj
bagaimana cara menambahkannya message?
andilabs
Bagi mereka yang tidak mengerti (seperti yang saya lakukan, pada awalnya) - metode 'pengiriman' harus ditambahkan ke kelas
ViewSpaceIndex
Apakah ada alasan untuk lebih menyukai salah satu dari metode ini?
Alistair
@ Alairair saya pikir itu bermuara pada preferensi pribadi dan mempertahankan konsistensi basis kode dalam tim / organisasi Anda. Saya pribadi cenderung ke arah pendekatan mixin jika saya membangun pandangan berbasis kelas sekalipun.
A Lee
118

Inilah pendekatan saya, saya membuat mixin yang dilindungi (ini disimpan di perpustakaan mixin saya):

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)

Kapan pun Anda ingin tampilan dilindungi, tambahkan saja mixin yang sesuai:

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

Pastikan mixin Anda yang pertama.

Pembaruan: Saya memposting ini pada tahun 2011, dimulai dengan versi 1.9. Django sekarang menyertakan ini dan mixin lain yang berguna (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) sebagai standar!

Gert Steyn
sumber
apakah mungkin untuk memiliki beberapa jenis campuran ini? Itu tidak berhasil untuk saya dan saya pikir itu tidak masuk akal.
Pykler
Ya, itu mungkin untuk memiliki beberapa mixin karena setiap mixin melakukan panggilan ke super yang memilih kelas berikutnya sesuai dengan MRO
Hobblin
Saya pikir ini adalah solusi yang elegan; Saya tidak suka memiliki campuran dekorator di urls.py saya dan mixins di views.py. Ini adalah cara untuk membungkus dekorator yang akan memindahkan semua logika itu ke tampilan.
dhackner
1
django-braces memiliki ini (dan banyak lagi) mixin - paket yang sangat berguna untuk diinstal
askvictor
Hanya catatan untuk orang-orang dalam mode perlambatan penuh seperti saya: pastikan bahwa Anda tidak masuk saat menguji fungsi login_required ...
Visgean Skeloru
46

Berikut ini alternatif menggunakan dekorator berbasis kelas:

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

Ini kemudian dapat digunakan seperti ini:

@class_view_decorator(login_required)
class MyView(View):
    # this view now decorated
mjtamlyn
sumber
3
Anda dapat menggunakan ini untuk dekorator tampilan rantai, baik! +1
Pykler
9
Ini sangat bagus sehingga harus dipertimbangkan untuk dimasukkannya IMO hulu.
koniiiik
Aku suka ini! Saya bertanya-tanya apakah mungkin untuk melewatkan args / kwargs turun dari class_view_decorator ke function_decorator ??? Akan lebih bagus jika login_decorator dapat mengatakan sesuai dengan persyaratan. Apakah itu hanya berlaku untuk pos kirim?
Mike Waites
1
Args / kwargs harus mudah dicapai dengan menggunakan class_view_decorator(my_decorator(*args, **kwargs)). Adapun pencocokan metode kondisional - Anda dapat memodifikasi class_view_decorator untuk diterapkan sendiri View.getatau View.postbukan View.dispatch.
mjtamlyn
14

Saya menyadari bahwa utas ini agak ketinggalan jaman, tapi ini dua sen saya.

dengan kode berikut:

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

kita sekarang memiliki cara untuk menambal dekorator, jadi itu akan menjadi multifungsi. Ini secara efektif berarti bahwa ketika diterapkan pada dekorator tampilan biasa, seperti:

login_required = patch_view_decorator(login_required)

dekorator ini akan tetap berfungsi saat digunakan seperti yang seharusnya:

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

tetapi juga akan berfungsi dengan baik bila digunakan seperti itu:

@login_required
class FooView(DetailView):
    model = Foo

Ini tampaknya berfungsi dengan baik dalam beberapa kasus yang baru-baru ini saya temui, termasuk contoh dunia nyata ini:

@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

Fungsi ajax_view ditulis untuk mengubah tampilan (berbasis fungsi), sehingga menimbulkan kesalahan 404 setiap kali tampilan ini dikunjungi oleh panggilan non ajax. Dengan hanya menerapkan fungsi tambalan sebagai dekorator, dekorator ini siap untuk bekerja dalam tampilan berbasis kelas juga

mephisto
sumber
14

Bagi Anda yang menggunakan Django> = 1,9 , itu sudah termasuk dalam django.contrib.auth.mixinssebagai AccessMixin, LoginRequiredMixin, PermissionRequiredMixindanUserPassesTestMixin .

Jadi untuk menerapkan LoginRequired ke CBV (mis 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'

Ini juga baik untuk diingat GCBV Mixin rangka: mixin harus pergi di kiri sisi, dan dasar pandangan kelas harus pergi di kanan sisi. Jika urutannya berbeda, Anda bisa mendapatkan hasil yang rusak dan tidak terduga.

vishes_shell
sumber
2
Ini adalah jawaban terbaik di 2019. Juga, poin bagus tentang pesanan mixin.
Christian Long
5

Gunakan Kawat Gigi Django. Ini menyediakan banyak mixin berguna yang mudah tersedia. Itu memiliki dokumen yang indah. Cobalah.

Anda bahkan dapat membuat mixin khusus Anda.

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

Kode Contoh:

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

Jika ini adalah situs tempat mayoritas halaman mengharuskan pengguna untuk login, Anda dapat menggunakan middleware untuk memaksa login pada semua tampilan kecuali beberapa yang ditandai secara khusus.

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

Tampilan pihak ketiga yang tidak ingin Anda bungkus dapat dikecualikan dalam pengaturan:

settings.py:

LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
kaleissin
sumber
3

Dalam kode saya, saya telah menulis adaptor ini untuk mengadaptasi fungsi anggota ke fungsi non-anggota:

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

Anda cukup menggunakannya seperti ini:

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
sumber
Akan menyenangkan bahwa ini adalah built-in pada Django (sama seperti method_decorator). Tampaknya cara yang bagus dan mudah dibaca untuk mencapai ini.
MariusSiuram
1

Ini sangat mudah dengan Django> 1.9 datang dengan dukungan untuk PermissionRequiredMixindanLoginRequiredMixin

Impor saja dari auth

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Untuk lebih jelasnya baca Otorisasi di Django

Amar
sumber
1

Sudah lama sekarang dan sekarang Django telah banyak berubah.

Periksa di sini untuk cara menghias tampilan berbasis kelas.

https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class

Dokumentasi tidak termasuk contoh "dekorator yang mengambil argumen apa pun". Tapi dekorator yang mengambil argumen seperti ini:

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

jadi jika kita ingin menggunakan mydec sebagai dekorator "normal" tanpa argumen, kita dapat melakukan ini:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

Demikian pula, untuk digunakan permission_requireddenganmethod_decorator

kita bisa:

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

Jika Anda melakukan proyek yang memerlukan berbagai tes izin, Anda bisa mewarisi kelas ini.

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)
Vinayak Kaniyarakkal
sumber
0

Saya telah melakukan perbaikan berdasarkan solusi Josh

class LoginRequiredMixin(object):

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

Penggunaan sampel:

class EventsListView(LoginRequiredMixin, ListView):

    template_name = "events/list_events.html"
    model = Event
Ramast
sumber
0

Di sini solusi untuk penghias permit_required:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Ashish Sondagar
sumber