Django के login_required को डिफ़ॉल्ट बनाने का सबसे अच्छा तरीका है


103

मैं एक बड़े Django ऐप पर काम कर रहा हूं, जिसमें से अधिकांश को एक्सेस करने के लिए एक लॉगिन की आवश्यकता होती है। इसका मतलब यह है कि हमारे सारे ऐप में हम छिड़क चुके हैं:

@login_required
def view(...):

यह ठीक है, और जब तक हम इसे हर जगह जोड़ना याद करते हैं तब तक यह बहुत अच्छा काम करता है ! अफसोस की बात है कि कभी-कभी हम भूल जाते हैं, और विफलता अक्सर स्पष्ट रूप से स्पष्ट नहीं होती है। यदि किसी दृश्य का एकमात्र लिंक @login_required पृष्ठ पर है, तो आपको यह नोटिस करने की संभावना नहीं है कि आप वास्तव में लॉग इन किए बिना उस दृश्य तक पहुंच सकते हैं। लेकिन बुरे लोग नोटिस कर सकते हैं, जो एक समस्या है।

मेरा विचार व्यवस्था को उलटने का था। हर जगह @login_required टाइप करने के बजाय, मुझे कुछ ऐसा चाहिए:

@public
def public_view(...):

सिर्फ जनता के सामान के लिए। मैंने कुछ मिडलवेयर के साथ इसे लागू करने की कोशिश की और मुझे यह काम करने के लिए नहीं मिला। मेरे द्वारा उपयोग किए जा रहे अन्य मिडलवेयर के साथ मैंने जो कुछ भी बुरी तरह से बातचीत करने की कोशिश की, मुझे लगता है। अगली बार मैंने URL पैटर्न को ट्रेस करने के लिए कुछ लिखने की कोशिश की कि यह जांचने के लिए कि @public @login_required को चिह्नित नहीं किया गया है - कम से कम तब हम एक त्वरित त्रुटि प्राप्त करेंगे यदि हम कुछ भूल गए। लेकिन तब मैं यह नहीं बता पाया कि अगर @login_required को किसी दृश्य पर लागू किया गया हो तो कैसे बताऊं ...

तो, ऐसा करने का सही तरीका क्या है? सहायता के लिए धन्यवाद!


2
बहुत बढ़िया सवाल। मैं बिल्कुल उसी स्थिति में हूं। हमारे पास पूरी साइट को login_required बनाने के लिए मिडलवेयर है , और हमारे पास अलग-अलग लोगों / भूमिकाओं के लिए अलग-अलग विचार / टेम्प्लेट-टुकड़े दिखाने के लिए एक प्रकार का घर-निर्मित ACL है, लेकिन यह उन दोनों में से अलग है।
पीटर रौयल

जवाबों:


99

मिडलवेयर आपकी सबसे अच्छी शर्त हो सकती है। मैंने अतीत में कोड के इस टुकड़े का उपयोग किया है, एक स्निपेट से कहीं और संशोधित किया गया है:

import re

from django.conf import settings
from django.contrib.auth.decorators import login_required


class RequireLoginMiddleware(object):
    """
    Middleware component that wraps the login_required decorator around
    matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and
    define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your
    settings.py. For example:
    ------
    LOGIN_REQUIRED_URLS = (
        r'/topsecret/(.*)$',
    )
    LOGIN_REQUIRED_URLS_EXCEPTIONS = (
        r'/topsecret/login(.*)$',
        r'/topsecret/logout(.*)$',
    )
    ------
    LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must
    be a valid regex.

    LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly
    define any exceptions (like login and logout URLs).
    """
    def __init__(self):
        self.required = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS)
        self.exceptions = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)

    def process_view(self, request, view_func, view_args, view_kwargs):
        # No need to process URLs if user already logged in
        if request.user.is_authenticated():
            return None

        # An exception match should immediately return None
        for url in self.exceptions:
            if url.match(request.path):
                return None

        # Requests matching a restricted URL pattern are returned
        # wrapped with the login_required decorator
        for url in self.required:
            if url.match(request.path):
                return login_required(view_func)(request, *view_args, **view_kwargs)

        # Explicitly return None for all non-matching requests
        return None

उसके बाद settings.py, उन आधार URL को सूचीबद्ध करें जिन्हें आप सुरक्षित करना चाहते हैं:

LOGIN_REQUIRED_URLS = (
    r'/private_stuff/(.*)$',
    r'/login_required/(.*)$',
)

जब तक आपकी साइट प्रमाणीकरण की आवश्यकता वाले पृष्ठों के लिए URL सम्मेलनों का अनुसरण करती है, तब तक यह मॉडल काम करेगा। यदि यह एक-से-एक फिट नहीं है, तो आप अपनी परिस्थितियों के अधिक निकटता के लिए मिडलवेयर को संशोधित करने का विकल्प चुन सकते हैं।

मुझे इस दृष्टिकोण के बारे में क्या पसंद है - @login_requiredडेकोरेटर के साथ कोडबेस को कूड़े की आवश्यकता को दूर करने के अलावा - यह है कि यदि प्रमाणीकरण योजना बदलती है, तो आपके पास वैश्विक परिवर्तन करने के लिए एक जगह है।


धन्यवाद, यह बहुत अच्छा लग रहा है! यह वास्तव में मेरे मिडलवेयर में login_required () का उपयोग करने के लिए मेरे साथ नहीं हुआ। मुझे लगता है कि यह उस समस्या के आसपास काम करने में मदद करेगा जो मुझे हमारे मिडलवेयर स्टैक के साथ अच्छा खेल रहा था।
19 मई को samtregar

रवींद्र! यह लगभग वैसा ही पैटर्न है जैसा कि हमने पृष्ठों के एक समूह के लिए उपयोग किया था जो HTTPS होना चाहिए था , और बाकी सब HTTPS नहीं होना चाहिए । यह 2.5 साल पहले था और मैं इसके बारे में पूरी तरह से भूल गया था। थैंक्स, डैनियल!
पीटर रोवेल

4
मिडिलवेयर आवश्यकताLoginMiddleware वर्ग कहाँ रखा जाना चाहिए? विचारों
यासीन

1
@richard डेकोरेटर संकलन के समय चलते हैं, और इस मामले में मैंने जो कुछ किया वह था: function.public = True। तब जब मिडलवेयर चलता है, तो यह तय करने के लिए कि क्या एक्सेस की अनुमति दी जानी है या नहीं, फंक्शन पर .public का झंडा लग सकता है। अगर इससे कोई मतलब नहीं है तो मैं आपको पूरा कोड भेज सकता हूं।
samtregar

1
मुझे लगता है कि सबसे अच्छा तरीका @publicडेकोरेटर बनाना है , जो _publicदृश्य पर विशेषता सेट करता है , और मिडिलवेयर फिर उन विचारों को छोड़ देता है। Django के csrf_exempt डेकोरेटर उसी तरह से काम करते हैं
इवान विराबीयन

31

प्रत्येक दृश्य फ़ंक्शन पर डेकोरेटर लगाने का विकल्प है। आप login_required()डेकोरेटर को urls.pyफाइल में भी रख सकते हैं । हालांकि यह अभी भी एक मैनुअल काम है, कम से कम आपके पास यह सब एक ही जगह है, जिससे ऑडिट करना आसान हो जाता है।

जैसे,

    my_ साक्षात्कार से home_view आयात करें

    urlpatterns = पैटर्न ('',)
        # "घर":
        (r '^ $', login_required (home_view), तानाशाही (template_name = 'my_site / home.html', items_per_page = 20)),
    )

ध्यान दें कि फ़ंक्शन का नाम सीधे और आयात किया गया है, न कि स्ट्रिंग्स के रूप में।

यह भी ध्यान दें कि यह कक्षाओं सहित किसी भी कॉल करने योग्य दृश्य ऑब्जेक्ट के साथ काम करता है।


3

Django 2.1 में, हम एक कक्षा में सभी तरीकों को सजा सकते हैं :

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

अद्यतन: मैंने काम करने के लिए निम्नलिखित भी पाया है:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

class ProtectedView(LoginRequiredMixin, TemplateView):
    template_name = 'secret.html'

और LOGIN_URL = '/accounts/login/'अपनी सेटिंग में सेट करें


1
इस नए उत्तर के लिए धन्यवाद। लेकिन कृपया इसके बारे में थोड़ा सा समझाएं, मैं इसे प्राप्त नहीं कर सका, भले ही मैंने आधिकारिक डॉक्टर को पढ़ा हो। अग्रिम में हां मदद के लिए धन्यवाद
तियान लूऑन

@TianLoon कृपया मेरे अपडेट किए गए उत्तर को देखें, यह मदद कर सकता है।
andyandy

2

यूआरएल के कार्यों को देखने के लिए जिस तरह से प्रस्तुत किया गया है, उसके बिना Django में निर्मित मान्यताओं को बदलना मुश्किल है।

Django के इंटर्ल्स के बारे में जानकारी देने के बजाय, यहां एक ऑडिट है जिसका आप उपयोग कर सकते हैं। बस प्रत्येक दृश्य फ़ंक्शन की जाँच करें।

import os
import re

def view_modules( root ):
    for path, dirs, files in os.walk( root ):
        for d in dirs[:]:
            if d.startswith("."):
                dirs.remove(d)
        for f in files:
            name, ext = os.path.splitext(f)
            if ext == ".py":
                if name == "views":
                    yield os.path.join( path, f )

def def_lines( root ):
    def_pat= re.compile( "\n(\S.*)\n+(^def\s+.*:$)", re.MULTILINE )
    for v in view_modules( root ):
        with open(v,"r") as source:
            text= source.read()
            for p in def_pat.findall( text ):
                yield p

def report( root ):
    for decorator, definition in def_lines( root ):
        print decorator, definition

इसे चलाएं और defउपयुक्त डेकोरेटर के बिना आउटपुट की जांच करें ।


2

यहाँ django 1.10+ के लिए एक मिडलवेयर समाधान है

में middlewares Django 1.10+ में एक नए तरीके से लिखा जा करने के लिए है

कोड

import re

from django.conf import settings
from django.contrib.auth.decorators import login_required


class RequireLoginMiddleware(object):

    def __init__(self, get_response):
         # One-time configuration and initialization.
        self.get_response = get_response

        self.required = tuple(re.compile(url)
                              for url in settings.LOGIN_REQUIRED_URLS)
        self.exceptions = tuple(re.compile(url)
                                for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)

    def __call__(self, request):

        response = self.get_response(request)
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):

        # No need to process URLs if user already logged in
        if request.user.is_authenticated:
            return None

        # An exception match should immediately return None
        for url in self.exceptions:
            if url.match(request.path):
                return None

        # Requests matching a restricted URL pattern are returned
        # wrapped with the login_required decorator
        for url in self.required:
            if url.match(request.path):
                return login_required(view_func)(request, *view_args, **view_kwargs)

        # Explicitly return None for all non-matching requests
        return None

स्थापना

  1. कोड को अपने प्रोजेक्ट फ़ोल्डर में कॉपी करें, और मिडलवेयर एरो के रूप में सहेजें
  2. MIDDLEWARE में जोड़ें

    MIDDLEWARE = ​​[... '.middleware.RequireLoginMiddleware', # लॉगिन की आवश्यकता]

  3. अपनी सेटिंग्स में जोड़ें:
LOGIN_REQUIRED_URLS = (
    r'(.*)',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
    r'/admin(.*)$',
)
LOGIN_URL = '/admin'

सूत्रों का कहना है:

  1. डेनियल नाब का यह जवाब

  2. मैक्स Goodridge द्वारा Django मिडिलवेयर ट्यूटोरियल

  3. Django मिडिलवेयर डॉक्स


ध्यान दें कि हालांकि इसमें कुछ नहीं होता है __call__, फिर process_viewभी हुक का उपयोग किया जाता है [संपादित करें]
साइमन कोह्लमीयर

1

बेर के जवाब से प्रेरित होकर मैंने थोड़ा स्निपेट लिखा जो कि patternsफ़ंक्शन की जगह , login_requiredडेकोरेटर के साथ सभी URL कॉलबैक लपेटकर । यह Django 1.6 में काम करता है।

def login_required_patterns(*args, **kw):
    for pattern in patterns(*args, **kw):
        # This is a property that should return a callable, even if a string view name is given.
        callback = pattern.callback

        # No property setter is provided, so this will have to do.
        pattern._callback = login_required(callback)

        yield pattern

इसका उपयोग करना इस तरह काम करता है (क्योंकि कॉल listकी आवश्यकता है yield)।

urlpatterns = list(login_required_patterns('', url(r'^$', home_view)))

0

आप वास्तव में इसे नहीं जीत सकते। आपको बस प्राधिकरण आवश्यकताओं की घोषणा करनी चाहिए । दृश्य फ़ंक्शन द्वारा अधिकार के अलावा आप इस घोषणा को कहां रखेंगे?

कॉल करने योग्य वस्तुओं के साथ अपने दृश्य कार्यों को बदलने पर विचार करें।

class LoginViewFunction( object ):
    def __call__( self, request, *args, **kw ):
        p1 = self.login( request, *args, **kw )
        if p1 is not None:
            return p1
        return self.view( request, *args, **kw )
    def login( self, request )
        if not request.user.is_authenticated():
            return HttpResponseRedirect('/login/?next=%s' % request.path)
    def view( self, request, *args, **kw ):
        raise NotImplementedError

आप तब अपने दृश्य फ़ंक्शंस को उप-वर्ग बनाते हैं LoginViewFunction

class MyRealView( LoginViewFunction ):
    def view( self, request, *args, **kw ):
        .... the real work ...

my_real_view = MyRealView()  

यह कोड की किसी भी लाइन को नहीं बचाता है। और यह "हम भूल गए" समस्या में मदद नहीं करता है। आप केवल इतना कर सकते हैं कि यह सुनिश्चित करने के लिए कोड की जांच करें कि दृश्य फ़ंक्शन ऑब्जेक्ट हैं। सही वर्ग का।

लेकिन फिर भी, आप वास्तव में कभी नहीं जान पाएंगे प्रत्येक दृश्य फ़ंक्शन एक यूनिट टेस्ट सूट के बिना सही है।


5
मैं जीत नहीं सकता? लेकिन मुझे जीतना है! हारना एक विकल्प नहीं है! लेकिन गंभीरता से, मैं अपनी सामान्य आवश्यकताओं को घोषित करने से बचने की कोशिश नहीं कर रहा हूं। मैं सिर्फ यह घोषित करना चाहता हूं कि क्या घोषित किया जाना चाहिए। सभी निजी विचारों को घोषित करने और सार्वजनिक लोगों के बारे में कुछ नहीं कहने के बजाय, मैं सभी सार्वजनिक विचारों को घोषित करना चाहता हूं और डिफ़ॉल्ट रूप से निजी होना चाहिए।
samtregar

इसके अलावा, विचार-के-वर्गों के लिए स्वच्छ विचार ... लेकिन मुझे लगता है कि इस बिंदु पर मेरे ऐप में सैकड़ों विचारों को फिर से लिखना एक गैर-स्टार्टर है।
samtregar

@samtregar: आपको जीतना है? मेरे पास एक नई बेंटले होनी चाहिए। गंभीरता से। आप के लिए grep कर सकते हैं def। आप defसभी दृश्य मॉड्यूल में सभी स्कैन करने के लिए बहुत कम स्क्रिप्ट लिख सकते हैं और यह निर्धारित कर सकते हैं कि कोई @login_required भूल गया था या नहीं।
एस.लॉट

8
@ S.Lott यह ऐसा करने के लिए सबसे संभव तरीका है, लेकिन हाँ, मुझे लगता है कि यह काम करेगा। सिवाय आपको कैसे पता चलेगा कि कौन से दृश्य हैं? बस दृश्यों में कार्यों को देखने से काम नहीं चलेगा, सहायक कार्यों को साझा करने के लिए @login_required की आवश्यकता नहीं है।
19

हाँ, यह लंगड़ा है। लगभग सबसे मुश्किल मैं सोच सकता था। आपको नहीं पता कि परीक्षा को छोड़कर कौन से व्यूज हैं urls.py
एस.लॉट

0

सभी urlsप्रकार के शामिल के लिए एक ही प्रारंभिक बिंदु होना संभव होगा और इस पैकेज https://github.com/vorujack/decorate_url का उपयोग करके इसे सजाने के लिए ।


0

वहाँ एक app है कि इस के लिए एक प्लग-एंड-प्ले समाधान प्रदान करता है:

https://github.com/mgrouchy/django-stronghold

pip install django-stronghold
# settings.py

INSTALLED_APPS = (
    #...
    'stronghold',
)

MIDDLEWARE_CLASSES = (
    #...
    'stronghold.middleware.LoginRequiredMiddleware',
)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.