views:

282

answers:

3

I'd like the login form (AuthenticationForm from django.contrib.auth) to appear on every page in my site if the user is not logged in. When the user logs in, they will be redirected to the same page. If there is an error, the error will be shown on the same page with the form.

I suppose you'd need a context processor to provide the form to every template. But, then you'd also need every view to handle the posted form? Does this mean you need to create some middleware? I'm a bit lost.

Is there an accepted way of doing this?

+2  A: 

The easiest way is probably to put the form in manually in a base template like so:

{% if user.is_authenticated %}
    <form action="{% url login %}" method="POST">
        <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 %}

and then just write a view hooked up to a URL named "login" to handle the form as you would normally (using a form object that matches the above form). Have the view redirect to request.META['HTTP_REFERER'] to show it on the same page as the one that submitted.

This approach avoids middleware or the need to make a form available to every single template through the context.

Update: There are some problems with this approach; need to think on it a little more. Hopefully it at least gets you going in the right direction.

John Debs
Your form should include `{% csrf_token %}` if you are using a recent version of trunk and if you are posting to the django.contrib.auth view that usually handles login.
Brian Neal
+2  A: 

Using django.contrib.auth, you can put the form code in the base template like so:

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

All you need to do is modify the next value so instead of:

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

It will now be:

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

To access the request object, make sure you include

'django.core.context_processors.request'

in your template context processors. This way you don't have to write any context processors for logins since you are using the Django built-in views.

twampss
Sorry, totally forgot about this question. Thanks for this solution. I wasn't aware of the "next" field. If the form validates, this works perfectly. My problem now is how to handle an error. Basically I want to return to the "next" view, but with the login form in the context.
asciitaxi
+1  A: 

Ok, I eventually found a way of doing this, although I'm sure there are better ways. I created a new middleware class called LoginFormMiddleware. In the process_request method, handle the form more or less the way the auth login view does:

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

Now if you have the request context processor installed, you can access the form with:

{{ request.login_form }}

Note that a hidden field 'is_top_login_form' was added to the form so I could distinguish it from other posted forms on the page. Also, the form action is "." instead of the auth login view.

asciitaxi