通过login_required装饰器转发POST参数的建议方法?

时间:2009-02-19 03:17:38

标签: django django-forms

我目前遇到的问题是,当我在任何视图上使用django.contrib.auth.decorators的login_required装饰器时,只要装饰器重定向(到登录页面),我的POST参数就不会到达受保护的视图)并再次返回受保护的视图。关于如何解决这个问题的建议(最好保持login_required装饰器和POST方法的便利性)值得赞赏!

This page似乎是关于此事的有争议的Django门票。虽然错误/增强是以模板与视图逻辑为框架而不仅仅是让视图可以访问参数,这是我的问题。

3 个答案:

答案 0 :(得分:1)

没有简单的方法可以做到这一点,更不用说你想要使用“login_required”装饰器。您可以执行自己的视图来检查is_authenticated方法并执行正确的操作,例如序列化POST数据和传递,但这很容易出错。

简单的解决方法是将表单更改为GET而不是POST。

答案 1 :(得分:1)

我使用我认为可以接受的会话开发了以下解决方案。处理重定向和替换视图是棘手的,并且这种方法似乎是最好的平衡,不是摆弄框架,而是在获得所需功能的同时不与HTTP协议作斗争。此方法的消极方面是每个受保护视图中检查会话变量所需的额外工作。

  1. 创建一个自定义装饰器(下面为login_required2),如果用户通过身份验证,则返回请求的视图,否则返回项目的登录视图。
  2. 登录视图:
    1. 将原始POST参数存储在会话变量中。
    2. 将原始HTTP_REFERER存储在会话变量
    3. 如果用户正确进行身份验证,则返回与请求的路径相对应的视图(请求的路径在整个登录过程中保持相同,并且与用户最初传递登录视图时请求的路径相同。)
  3. 因此,受保护的任何视图必须在使用请求的POSTMETA['HTTP_REFERER']
  4. 之前检查会话变量

    代码如下:

    def login_view(request):    
        from django.conf import settings
        from django.core.urlresolvers import resolve
    
        USERNAME_FIELD_KEY = 'username'
        PASSWORD_FIELD_KEY = 'password'
    
        message = '' #A message to display to the user
        error_message = '' #An error message to display to the user
    
        #If the request's path is not the login URL, the user did not explicitly request 
        # the login page and we assume this view is protecting another.
        protecting_a_view = request.path != settings.LOGIN_URL
    
        post_params_present = bool(request.POST)
    
        #Any POST with username and password is considered a login attempt, regardless off what other POST parameters there may be
        login_attempt = request.POST and request.POST.has_key(USERNAME_FIELD_KEY) and request.POST.has_key(PASSWORD_FIELD_KEY)
    
        if protecting_a_view:
            message = 'You must login for access.'
            if not request.session.has_key(ACTUAL_REFERER_KEY):
                #Store the HTTP_REFERER if not already
                request.session[ACTUAL_REFERER_KEY] = request.META.get(HTTP_REFERER_KEY)
    
        if protecting_a_view and post_params_present and not login_attempt: 
            #Store the POST parameters for the protected view
            request.session[FORWARDED_POST_PARAMS_KEY] = request.POST
    
        if login_attempt:
            form = LoginForm(request.POST)
            if form.is_valid():
                username = form.cleaned_data[USERNAME_FIELD_KEY]
                password = form.cleaned_data[PASSWORD_FIELD_KEY]
                user = auth.authenticate(username=username, password=password)
                if user is not None:
                    if user.is_active:
                        auth.login(request, user)
                        if protecting_a_view:
                            actual_view, actual_args, actual_kwargs = resolve(request.path) #request.path refers to the protected view
                            return actual_view(request, *actual_args, **actual_kwargs)
                        else:
                            HttpResponseRedirect('/')
                    else:
                        message = 'That account is inactive.'
                else:
                    error_message = 'That username or password is incorrect.'
        else:
            form = LoginForm()
    
        context_dict = {
            'form': form,
            'message': message,
            'error_message': error_message,
        }
        return render_to_response2('my_app/login.html', context_dict)
    
    @login_required2
    def protected_view(request):
        post_params = {}
    
        if request.POST:
            post_params = request.POST
        elif request.session.has_key(FORWARDED_POST_PARAMS_KEY):
            post_params = request.session[FORWARDED_POST_PARAMS_KEY]
            del request.session[FORWARDED_POST_PARAMS_KEY]
        if post_params:
            #Process post_params as if it were request.POST here:
            pass
    
        #assuming this view ends with a redirect.  Otherwise could render view normally
        if request.session.has_key(ACTUAL_REFERER_KEY):
            redirect_location = request.session.get(ACTUAL_REFERER_KEY)
        elif request.META.get(HTTP_REFERER_KEY) != request.path:
            redirect_location = request.META.get(HTTP_REFERER_KEY)
        else:
            redirect_location = ROOT_PATH
        return HttpResponseRedirect(redirect_location)
    
    def login_required2(view_func):
        """
        A decorator that checks if the request has an authenticated user.
        If so it passes the request to the view.
        Otherwise, it passes the request to the login view, which is responsible
        for recognizing that the request was originally for another page and forwarding
        state along (GET, POST).
    
        See django.contrib.auth.decorators for how Django's auth decorators mesh 
        using _CheckLogin.  This decorator bypasses that for my ease of creation.
        """
        def login_required_decoration(request, *args, **kwargs):
            if request.user.is_authenticated():
                return view_func(request, *args, **kwargs)
            else:
                from django.conf import settings
                from django.core.urlresolvers import resolve
    
                login_url = settings.LOGIN_URL
                login_view, login_args, login_kwargs = resolve(login_url)
                #Here the user gets a login view instad of the view they requested
                return login_view(request, *login_args, **login_kwargs)
        return login_required_decoration
    

答案 2 :(得分:0)

这是一个很老的问题,但今天仍然存在。

基于先前答案中提供的提示,我对基于类的视图进行了以下混合:

from urllib.parse import urlparse, urlencode

from django.http import QueryDict, HttpRequest
from django.contrib.auth.mixins import AccessMixin

class loginRequiredOnPostMixin (AccessMixin):
    """
    Lets a CBV be freely accessible on GET requests, but requires login on POST.
    On redirect for unauthenticated POST requests, the POST parameters are stored in the session.
    Once the user authenticated and is redirected to the page as GET, the POST parameters are retrieved so the request can complete.
    """
    SESSION_REFERER_KEY = None
    SESSION_POST_PARAMS_KEY = None

    def get_session_referer_key(self):
        return self.SESSION_REFERER_KEY or 'POST_REDIRECT_REFERER'

    def get_session_post_params_key(self):
        return self.SESSION_POST_PARAMS_KEY or 'POST_PARAMS'

    def dispatch(self, request, *args, **kwargs):
        referer_key = self.get_session_referer_key()
        post_params_key = self.get_session_post_params_key()

        if request.method == 'POST' and bool(request.POST) and not request.user.is_authenticated:
            # save the referer url to match it later in case of GET to the same page (after login)
            if not request.session.has_key(referer_key): # so we don't overwrite previous values)
                parsed_url = urlparse(request.META.get('HTTP_REFERER'))
                request.session[referer_key] = parsed_url.path
                # save the POST params in the session
                request.session[post_params_key] = request.POST
            #redirect to login View
            return self.handle_no_permission()

        elif request.method == 'POST' and request.user.is_authenticated:
            # user logged in and rePOSTed... clean up and let the magic work on its on
            if request.session.has_key(referer_key):
                del request.session[referer_key]
            if request.session.has_key(post_params_key):
                del request.session[post_params_key]

        elif request.method == 'GET' and request.user.is_authenticated:
            print ("dispatch: GET authenticated")
            if request.session.has_key(referer_key):
                if request.session[referer_key] == request.get_full_path():
                    newRequest = HttpRequest()
                    newRequest.COOKIES = request.COOKIES
                    newRequest.META = request.META
                    newRequest.FILES = request.FILES
                    newRequest.path = request.path
                    newRequest.path_info = request.path_info
                    newRequest.method = 'POST'
                    newRequest.resolver_match = request.resolver_match
                    newRequest.content_type = request.content_type
                    newRequest.content_params = request.content_params
                    q = QueryDict(urlencode(request.session[post_params_key]))
                    newRequest.POST = q
                    newRequest.session = request.session
                    newRequest.user = request.user
                    self.request = newRequest
                    del request.session[referer_key]
                    del request.session[post_params_key]
                    return super().dispatch(newRequest, *args, **kwargs)
                # we clean up as it seems the user has moved on.
                del request.session[referer_key]
                del request.session[post_params_key]
        return super().dispatch(request, *args, **kwargs)

成功登录后,它实际上会重新创建HttpRequest对象,并将其转换为POST请求,然后再调用super().dispatch(...)

如果在FormView中使用,请确保使用足够的POST数据重新创建表单,例如:

    def post(self, request, *args, **kwargs):
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        return super().post(request, *args, **kwargs)

为我工作。 享受吧!