190

I'm trying to build a simple website with login functionality very similar to the one here on SO. The user should be able to browse the site as an anonymous user and there will be a login link on every page. When clicking on the login link the user will be taken to the login form. After a successful login the user should be taken back to the page from where he clicked the login link in the first place. I'm guessing that I have to somehow pass the url of the current page to the view that handles the login form but I can't really get it to work.

EDIT: I figured it out. I linked to the login form by passing the current page as a GET parameter and then used 'next' to redirect to that page. Thanks!

EDIT 2: My explanation did not seem to be clear so as requested here is my code: Lets say we are on a page foo.html and we are not logged in. Now we would like to have a link on foo.html that links to login.html. There we can login and are then redirected back to foo.html. The link on foo.html looks like this:

      <a href='/login/?next={{ request.path }}'>Login</a> 

Now I wrote a custom login view that looks somewhat like this:

def login_view(request):
   redirect_to = request.REQUEST.get('next', '')
   if request.method=='POST':
      #create login form...
      if valid login credentials have been entered:
         return HttpResponseRedirect(redirect_to)  
   #...
   return render_to_response('login.html', locals())

And the important line in login.html:

<form method="post" action="./?next={{ redirect_to }}">

So yeah thats pretty much it, hope that makes it clear.

Wipqozn
  • 1,282
  • 2
  • 17
  • 30
jörg
  • 3,221
  • 3
  • 20
  • 12

9 Answers9

169

You do not need to make an extra view for this, the functionality is already built in.

First each page with a login link needs to know the current path, and the easiest way is to add the request context preprosessor to settings.py (the 4 first are default), then the request object will be available in each request:

settings.py:

TEMPLATE_CONTEXT_PROCESSORS = (
    "django.core.context_processors.auth",
    "django.core.context_processors.debug",
    "django.core.context_processors.i18n",
    "django.core.context_processors.media",
    "django.core.context_processors.request",
)

Then add in the template you want the Login link:

base.html:

<a href="{% url django.contrib.auth.views.login %}?next={{request.path}}">Login</a>

This will add a GET argument to the login page that points back to the current page.

The login template can then be as simple as this:

registration/login.html:

{% block content %}
<form method="post" action="">
  {{form.as_p}}
<input type="submit" value="Login">
</form>
{% endblock %}
sverrejoh
  • 16,464
  • 13
  • 41
  • 29
  • 52
    I personally use this solution, but the one modification I would make is that it fails if `request.path` is null, so I make it `{% firstof request.path '/' %}`, that way if the request path isn't available for some reason the user gets sent to the home page. – jorelli Oct 12 '11 at 16:40
  • 1
    I am trying to implement your solution but, when django.contrib.auth.views.login redirects (after authenticates succesfully) the instance of User is not in the request, so user.is_authenticated() always returns False. Any help? – juankysmith Mar 07 '12 at 12:14
  • 3
    Adding on top of @jorelli, you can use named URL and do `Login` instead. – hobbes3 Mar 29 '12 at 05:58
  • 15
    You also have to watch out for if a user logs in after just logging out. In other words, the `next` would be something like `/accounts/logout/` and after the user log ins, he will immediately be logged back out lol, and the loop continues. – hobbes3 Mar 29 '12 at 06:04
  • @jorelli request.path won't be null at all, I don't see the sense of this. – dspjm Apr 04 '14 at 10:29
  • 3
    I found that this only worked when I added the following to the login form on Django 1.5: – Ellis Percival Sep 14 '14 at 10:51
  • Could this be used for a package such as `django-allauth`? – Newtt Feb 26 '15 at 11:10
  • @hobbes3 How could I handle this loop then? I mean, if the previous page is logout page... – Doo Hyun Shin Mar 06 '20 at 15:59
  • **NOTE:** If you get a error something like `ModuleNotFoundError: No module named 'django.core.context_processors'` Do NOT add this line `"django.core.context_processors.request",` in *settings.py* . Because in newer version this following line is given by default by Django and it will handle this task. `'django.template.context_processors.request'` – ajinzrathod Jan 20 '21 at 11:32
32

To support full urls with param/values you'd need:

?next={{ request.get_full_path|urlencode }}

instead of just:

?next={{ request.path }}
arntg
  • 1,557
  • 14
  • 12
  • Actually this is not a good idea, you will find the query string very very long if you have too many clicks – dspjm Jun 21 '14 at 12:31
30

This may not be a "best practice", but I've successfully used this before:

return HttpResponseRedirect(request.META.get('HTTP_REFERER','/'))
Grant
  • 2,838
  • 1
  • 19
  • 17
  • 11
    This has a weak point - some users/browsers have passing referer turned off – Tomasz Zieliński Jul 19 '10 at 21:36
  • 4
    There's also the problem in the case where a user arrives at a form, fills in the information and then submits. The HTTP_REFERER is then the form page's URL, and not the URL of the page the user came from in the first place. – Mathieu Dhondt Oct 27 '11 at 10:06
14

Django's built-in authentication works the way you want.

Their login pages include a next query string which is the page to return to after login.

Look at http://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.decorators.login_required

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 4
    Well, the login_required decorator would work just fine as it sets 'next' to whatever page I come from, but when using the decorator anonymous users cant view the site because they will be redirect to the login form right away. I think I just need to figure out how to set the value of 'next' to the url of the page the user comes from. – jörg Apr 30 '09 at 13:49
1

I encountered the same problem. This solution allows me to keep using the generic login view:

urlpatterns += patterns('django.views.generic.simple',
    (r'^accounts/profile/$', 'redirect_to', {'url': 'generic_account_url'}),
)
moo
  • 7,619
  • 9
  • 42
  • 40
1

I linked to the login form by passing the current page as a GET parameter and then used 'next' to redirect to that page. Thanks!

jörg
  • 3,221
  • 3
  • 20
  • 12
1

In registration/login.html (nested within templates folder) if you insert the following line, the page will render like Django's original admin login page:

{% include "admin/login.html" %}

Note: The file should contain above lines only.

Harshith J.V.
  • 877
  • 1
  • 8
  • 21
-1

See django docs for views.login(), you supply a 'next' value (as a hidden field) on the input form to redirect to after a successful login.

YHVH
  • 585
  • 4
  • 12
-3

You can also do this

<input type="hidden" name="text" value="{% url 'dashboard' %}" />
JREAM
  • 5,741
  • 11
  • 46
  • 84