在每个页面上放置Django登录表单

46
如果用户未登录,我希望在我的网站上的每个页面上显示登录表单(使用django.contrib.auth中的AuthenticationForm)。当用户登录后,将重定向到同一页。如果存在错误,则在同一页上显示表单和错误。
我认为您需要一个上下文处理器来为每个模板提供表单。但是,您还需要每个视图处理提交的表单吗?这是否意味着您需要创建一些中间件?我有点迷失。
是否有一种被接受的方法来做到这一点?
4个回答

33

好的,我最终找到了一种方法来做到这一点,尽管我相信还有更好的方法。我创建了一个名为LoginFormMiddleware的新中间件类。在process_request方法中,处理表单的方式与auth登录视图几乎相同:

class LoginFormMiddleware(object):

    def process_request(self, request):

        # if the top login form has been posted
        if request.method == 'POST' and 'is_top_login_form' in request.POST:

            # validate the form
            form = AuthenticationForm(data=request.POST)
            if form.is_valid():

                # log the user in
                from django.contrib.auth import login
                login(request, form.get_user())

                # if this is the logout page, then redirect to /
                # so we don't get logged out just after logging in
                if '/account/logout/' in request.get_full_path():
                    return HttpResponseRedirect('/')

        else:
            form = AuthenticationForm(request)

        # attach the form to the request so it can be accessed within the templates
        request.login_form = form

如果您已安装请求上下文处理器,则可以使用以下方式访问表单:

{{ request.login_form }}

请注意,表单中添加了一个隐藏字段“is_top_login_form”,以便我可以将其与页面上的其他已发布表单区分开来。此外,表单操作是“.”而不是认证登录视图。


谢谢,我曾经试图通过上下文处理器来实现类似的功能,但是一直很困扰。 - Mathieu Dhondt
真的很干净的解决方案 - 但是您是否必须检查视图中发布的每个其他表单,以确保在尝试验证表单之前它不是is_top_login_form,还是我错过了某些部分?我的意思是,在稍后的视图中,如果您有另一个表单,它是否必须是if reqeust.POST而不是'request.POST'中的'is_top_login_form'? - niklasdstrom
5
Django表单接受一个prefix参数,您可以使用它代替隐藏字段,例如form=AuthenticationForm(prefix="login", data=request.POST)。注意不要改变原文意思。 - Alasdair

26
使用django.contrib.auth,您可以将表单代码放在基本模板中,代码如下:
<form method="post" action="{% url auth_login %}">
    {% csrf_token %}
    <p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="30" /></p>
    <p><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></p>

    <input type="submit" value="Log in" />
    <input type="hidden" name="next" value="" />
</form>

您需要做的就是修改下一个值,使其不是:

<input type="hidden" name="next" value="" />

现在它将是:

<input type="hidden" name="next" value="{{ request.get_full_path }}" />

要访问请求对象,请确保包含

'django.core.context_processors.request'

在你的模板上下文处理器中添加代码。这样一来,由于使用了Django内置的视图,你就不必编写任何用于登录的上下文处理器。


5
抱歉,完全忘记了这个问题。感谢您提供的解决方案。我之前不知道“next”字段。如果表单验证成功,这个方法就很完美。我现在的问题是如何处理错误。基本上我想返回到“next”视图,但上下文里需要包含登录表单。 - asciitaxi

6
最简单的方法可能是在基本模板中手动添加表单,如下所示:
{% if user.is_authenticated %}
    <form action="{% url login %}" method="POST">{% csrf_token %}
        <input id="username-field" name="username" type="text" />
        <input id="password-field" name="password" type="password" />
        <button type="submit">Login</button>
    </form>
{% else %}
    {# display something else here... #}
{% endif %}

然后编写一个与名为“login”的URL相关联的视图,以处理表单(使用与上述表单匹配的表单对象)。使视图重定向到request.META['HTTP_REFERER'],以在提交页面上显示它。
这种方法避免了中间件或通过上下文使每个模板都可用的表单的需要。
更新:这种方法存在一些问题;需要再考虑一下。希望至少能让您朝正确的方向前进。

4
如果你正在使用最新版本的trunk,并且要发布到通常处理登录的django.contrib.auth视图,那么你的表单应该包含{% csrf_token %} - Brian Neal
有人知道如何在使用默认的“AuthenticationForm”时在模板中显示错误消息吗?(我没有使用“form.as_p”。相反,我使用诸如“form.username”之类的标记来更好地控制自定义。) - Sahas Katta
2
{{ form.errors }}、{{ form.non_field_errors }} 和 {{ form.<field>.errors }} - John Debs
如果用户已经被授权且不需要时,显示表单是没有意义的,所以应该写成{% if not user.is_authenticated %} - cirrusio

2

根据 asciitaxi 的回答,我使用这些中间件类来登录和注销:

class LoginFormMiddleware(object):
    def process_request(self, request):
        from django.contrib.auth.forms import AuthenticationForm
        if request.method == 'POST' and request.POST.has_key('base-account') and request.POST['base-account'] == 'Login':
            form = AuthenticationForm(data=request.POST, prefix="login")
            if form.is_valid():
                from django.contrib.auth import login
                login(request, form.get_user())
            request.method = 'GET'
        else:
            form = AuthenticationForm(request, prefix="login")
        request.login_form = form

class LogoutFormMiddleware(object):
    def process_request(self, request):
        if request.method == 'POST' and request.POST.has_key('base-account') and request.POST['base-account'] == 'Logout':
            from django.contrib.auth import logout
            logout(request)
            request.method = 'GET'

这是我的基础模板中的内容:

{% if not request.user.is_authenticated %}
    <form action="" method="post">
        {% csrf_token %}
        <p id="login">
            {{ request.login_form.non_field_errors }}
            {% for field in request.login_form %}
                {{ field.errors }}
                {{ field.label_tag}}: {{ field }}
            {% endfor %}
            <input type="submit" name="base-account" value="Login" />
        </p>
    </form>
{% else %}
    <form action="" method="post">
        {% csrf_token %}
        <p id="logout">Logged in as <b>{{ request.user.username }}</b>.
            <input type="submit" name="base-account" value="Logout" />
        </p>
    </form>
{% endif %}

备注:

  • 对于其他表单的网站,需要添加request.method = 'GET'行。看起来有点奇怪,但它可以正常工作。
  • 由于这将在每个页面上显示,我不再需要注销特殊情况,因为我根本不需要单独的注销页面。
  • 在检查表单是否有效(调用AuthenticationForm类)之前,我需要区分我的登录/注销表单。否则,在具有更多表单的页面上会出现错误。因此,我使用提交按钮的值来挑选相关的情况。

这个对于基于类的视图是否适用于您?我一直在使用asciitaxi的代码进行开发,但现在我正在切换到基于类的视图时,中间件似乎会出问题。实际上,上面的中间件工作正常,但是请求的其余部分被破坏,并返回一个空的httpresponse(没有错误,没有呈现的视图)。基本上,我必须点击“登录”,然后得到一个白色页面,然后可以手动重新请求相关的URL。详细问题请参见:[http://stackoverflow.com/questions/16156899/django-loginformmiddleware-breaks-with-class-based-views] - Alex Whittemore
你从哪里获取表单数据?是从表单还是模型中获取的?我没有看到有解释。 - Jam1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接