[python] Django의 login_required를 기본값으로 만드는 가장 좋은 방법

저는 큰 Django 앱을 작업 중이며 대부분의 경우 액세스하려면 로그인이 필요합니다. 즉, 앱 전체에 다음과 같이 뿌렸습니다.

@login_required
def view(...):

괜찮 습니다. 모든 곳에 추가하는 것을 기억하는 한 훌륭하게 작동합니다 ! 슬프게도 때때로 우리는 잊어 버리고 실패는 종종 그다지 분명하지 않습니다. 보기에 대한 유일한 링크가 @login_required 페이지에있는 경우 로그인하지 않고도 해당보기에 실제로 도달 할 수 있다는 사실을 눈치 채지 못할 것입니다.하지만 악의적 인 사용자는 이것이 문제임을 알아 차릴 수 있습니다.

내 생각은 시스템을 뒤집는 것이었다. 모든 곳에 @login_required를 입력하는 대신 다음과 같은 내용이 있습니다.

@public
def public_view(...):

공공 장소를 위해서. 나는 이것을 미들웨어로 구현하려고 시도했지만 작동하지 않는 것 같습니다. 내가 시도한 모든 것이 우리가 사용하는 다른 미들웨어와 나쁘게 상호 작용했다고 생각합니다. 다음으로 URL 패턴을 탐색하여 @public이 아닌 모든 항목이 @login_required로 표시되었는지 확인하기 위해 무언가를 작성하려고했습니다. 적어도 무언가를 잊으면 빠른 오류가 발생합니다. 하지만 @login_required가 뷰에 적용되었는지 확인하는 방법을 알 수 없었습니다 …

그렇다면이를 수행하는 올바른 방법은 무엇입니까? 도와 주셔서 감사합니다!



답변

미들웨어가 최선의 선택 일 수 있습니다. 나는 과거에 다른 곳에서 발견 된 스 니펫에서 수정 된이 코드를 사용했다.

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()데코레이터를 urls.py파일 에 넣을 수도 있습니다. 이 작업은 여전히 ​​수동 작업이지만 최소한 모든 작업을 한곳에 모아서 감사하기가 더 쉽습니다.

예 :

    my_views에서 home_view 가져 오기

    urlpatterns = 패턴 ( '',
        # "집":
        (r '^ $', login_required (home_view), dict (template_name = 'my_site / home.html', items_per_page = 20)),
    )

뷰 함수는 이름이 지정되고 문자열이 아니라 직접 가져옵니다.

또한 이것은 클래스를 포함하여 호출 가능한 모든 뷰 객체에서 작동합니다.


답변

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/'당신에 settings.py


답변

URL이 함수를보기 위해 전달되는 방식을 재 작업하지 않고 장고의 내장 가정을 변경하는 것은 어렵습니다.

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적절한 데코레이터없이 s에 대한 출력을 검사하십시오 .


답변

다음은 django 1.10+ 용 미들웨어 솔루션입니다.

의 미들웨어는 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. 코드를 프로젝트 폴더에 복사하고 middleware.py로 저장하십시오.
  2. MIDDLEWARE에 추가

    MIDDLEWARE = ​​[… ‘.middleware.RequireLoginMiddleware’, # 로그인 필요]

  3. settings.py에 추가하십시오.
LOGIN_REQUIRED_URLS = (
    r'(.*)',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
    r'/admin(.*)$',
)
LOGIN_URL = '/admin'

출처 :

  1. Daniel Naab의 답변

  2. Max Goodridge의 Django 미들웨어 튜토리얼

  3. Django 미들웨어 문서


답변

Ber의 답변에 영감을 받아 patterns모든 URL 콜백을 login_required데코레이터 로 래핑 하여 함수 를 대체하는 작은 스 니펫을 작성했습니다 . 이것은 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)))


답변

당신은 이길 수 없습니다. 당신은 단순히 해야한다 인증 요구 사항의 선언을합니다. 뷰 함수에 의한 권리를 제외하고이 선언을 어디에 두겠습니까?

뷰 함수를 호출 가능한 객체로 바꾸는 것을 고려하십시오.

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

코드 줄을 저장하지 않습니다. 그리고 그것은 “우리가 잊었다”문제에 도움이되지 않습니다. 할 수있는 일은 코드를 검사하여 뷰 함수가 객체인지 확인하는 것입니다. 올바른 클래스.

그러나 그럼에도 불구 하고 단위 테스트 스위트가 없으면 모든 뷰 기능이 정확 하다는 것을 결코 알 수 없습니다 .