39

I have a login page which is working fine with the exception of the redirect to the referrer page. The user gets an email with a direct link within the app, they (in this example) are not logged in already and are redirected to the login page. After successful login the user is redirected to a hardcoded path. See example below.

URL in email: http://localhost:8000/issueapp/1628/view/22

URL of login page: http://localhost:8000/login?next=/issueapp/1628/view/22

Login view (with hardcoded redirect):

def login_user(request):    
    state = "Please log in below..."
    username = password = ''

    if request.POST:
        username = request.POST['username']
        password = request.POST['password']

        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                state = "You're successfully logged in!"
                return HttpResponseRedirect('/issueapp/1628/')
            else:
                state = "Your account is not active, please contact the site admin."
        else:
            state = "Your username and/or password were incorrect."

    return render_to_response(
        'account_login.html',
        {
        'state':state,
        'username': username
        },
        context_instance=RequestContext(request)
    )

Login view (with "next" redirect):

def login_user(request):    
    state = "Please log in below..."
    username = password = ''

    if request.POST:
        username = request.POST['username']
        password = request.POST['password']

        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                state = "You're successfully logged in!"
                return HttpResponseRedirect(request.GET['next'])
            else:
                state = "Your account is not active, please contact the site admin."
        else:
            state = "Your username and/or password were incorrect."

    return render_to_response(
        'account_login.html',
        {
        'state':state,
        'username': username
        },
        context_instance=RequestContext(request)
    )

The above view results in an exception "Key 'next' not found in <QueryDict: {}>" The form does not appear to be posting the "next" variable, even though its there in the url and in the form. I have searched and looked everywhere and cant figure out why its not working. Any ideas?

Additional reference:

Login template:

{% block content %}

    {{ state }}
    <form action="/login/" method="post" >
                {% csrf_token %}
        {% if next %}
        <input type="hidden" name="next" value="{{ next }}" />
        {% endif %}
        username:
        <input type="text" name="username" value="{{ username }}" /><br />
        password:
        <input type="password" name="password" value="" /><br />

        <input type="submit" value="Log In"/>

        {% debug %}
    </form>
{% endblock %}

EDIT: The below is the code which is now working for me (thanks to the help of Paulo Bu)! **

Login View:

def login_user(request):

    state = "Please log in below..."
    username = password = ''

    next = ""

    if request.GET:  
        next = request.GET['next']

    if request.POST:
        username = request.POST['username']
        password = request.POST['password']

        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                state = "You're successfully logged in!"
                if next == "":
                    return HttpResponseRedirect('/issueapp/')
                else:
                    return HttpResponseRedirect(next)
            else:
                state = "Your account is not active, please contact the site admin."
        else:
            state = "Your username and/or password were incorrect."

    return render_to_response(
        'account_login.html',
        {
        'state':state,
        'username': username,
        'next':next,
        },
        context_instance=RequestContext(request)
    )

Login Template:

{{ state }}

{% if next %}
<form action="/login/?next={{next}}" method="post" >
{%else%}
<form action="/login/" method="post" >
{% endif %}

            {% csrf_token %}

    username:
    <input type="text" name="username" value="{{ username }}" /><br />
    password:
    <input type="password" name="password" value="" /><br />

    <input type="submit" value="Log In"/>

    {% debug %}
</form>
Jonas
  • 121,568
  • 97
  • 310
  • 388
James
  • 815
  • 1
  • 13
  • 24
  • The solution in your Edit helped me! Thanks! – Shailen Sep 11 '13 at 13:22
  • I believe there is a problem with your final code, if the login was triggered by a `@login_required` decorator the redirect doesn't work. This can be fixed by putting `request.user = user` before HttpResponseRedirect(next)... – Nicholas Hamilton Mar 05 '14 at 15:26

5 Answers5

43

Your code is fine, the only problem is that in the form you are passing the next attribute as a post because the method is post. In the views you try to get the next parameter within the get dictionary which is obvious not there.

You have to declare the html form action like this in order to your views work.

{% if next %}
<form action="/login/?next={{next}}" method="post" >
{%else%}
<form action="/login/" method="post" >
{% endif %}
        {% csrf_token %}
        username:
        <input type="text" name="username" value="{{ username }}" /><br />
        password:
        <input type="password" name="password" value="" /><br />

        <input type="submit" value="Log In"/>

        {% debug %}
    </form>

There, if there is a next variable then you include in the url for retrieve it as a get parameter. If not, the form doesn't include it.

This is the best approach, but you may also fix this in your views by requesting the next from the POST dictionary like this:

return HttpResponseRedirect(request.POST.get('next'))

Note that this will only work if the template account_login has a variable called next. You should generate it in the views and pass it to the template when you render it.

Normally, in the template you would do something like this:

# this would be hardcoded
next = '/issueapp/1628/view/22'
# you may add some logic to generate it as you need.

and then you do:

return render_to_response(
    'account_login.html',
    {
    'state':state,
    'username': username,
    'next':next
    },
    context_instance=RequestContext(request)
)

Hope this helps!

Paulo Bu
  • 29,294
  • 6
  • 74
  • 73
  • thanks, i have tried your suggestions but it still doesn't work. I think the problem is that '{{ next }}' does not contain a value? i.e. Its empty. To test this i added '{{ next }}' so it would show the value in the template and it shows nothing. Am i on the write track or is what im thinking incorrect? – James May 25 '13 at 15:14
  • You're correct, you just need to pass a variable to the context when you return `render_to_response`. As you can see, there you pass only 2 variables (state and username). Generate another one called `next` in your view and pass it to the variables dictionary. – Paulo Bu May 25 '13 at 15:18
  • I added more code to the answer to see if it can help you a little more. Hope that will be useful. – Paulo Bu May 25 '13 at 15:24
  • thanks Paulo. I have it working now. Had to make a few more tweaks so it worked in all scenario's. Have added the working code to the bottom of my question. Not sure of the etiquette, hope thats ok. Thanks for your help, it was much appreciated! – James May 25 '13 at 15:51
  • You're most welcome :) I'm glad you get it working. – Paulo Bu May 25 '13 at 21:04
  • 1
    Paulo, you are Great! this was my headache for ages, now it is working like anything :)).. hugs hugs.. – doniyor Nov 08 '13 at 19:14
  • 2
    Instead of assigning `next` in your view & passing it to template, isn't it cleaner to use `?next={{request.path}}` in your template. (Remember to enable `django.core.context_processors.request`) – user Apr 06 '14 at 19:38
  • Well yes, it is another way of doing it. – Paulo Bu Apr 06 '14 at 21:15
  • I prefer method of @buffer, so i have posted it as an answer. In my case, it seems to be more suitable and recomended. Here is a related question [Django: Redirect to previous page after login](http://stackoverflow.com/questions/806835/django-redirect-to-previous-page-after-login?rq=1) – Ajeeb.K.P Oct 29 '14 at 05:39
6

In short

I would define in your view function next_page = request.GET['next'] and then redirect to it by return HttpResponseRedirect(next_page) so you never need to change templates; just set @login_required and you are fine.

As example:

User A tries to access - while not logged in - https://www.domain.tld/account/. Django redirects him because @login_required is set to the defined LOGIN_URL in your settings.py. The method UserLogin now tries to GET the next parameter and redirects to it if user A logs in successfully.

settings.py

LOGIN_URL = '/login/'

urls.py

url(r'^account/', account, name='account'),
url(r'^login/$', UserLogin, name='login'),

views.py

@login_required
def account(request):
    return HttpResponseRedirect("https://www.domain.tld/example-redirect/")

def UserLogin(request):
    next_page = request.GET['next']
    if request.user.is_authenticated():
        return HttpResponseRedirect(next_page)
    else:
        if request.method == 'POST':
            if form.is_valid():
                username = form.cleaned_data['username']
                password = form.cleaned_data['password']
                user = authenticate(email=username, password=password)
                if user is not None and user.is_active:
                    login(request, user)
                    return HttpResponseRedirect(next_page)
                else:
                    error_msg = 'There was an error!'
                    return render(request, "login", {'form': form, 'error_msg': error_msg})
            else:
                error_msg = "There was an error!"
                return render(request, "login", {'form':form, 'error_msg':error_msg})
        else:
            form = UserLoginForm()
            return render(request, "login", {'form': form})
rwx
  • 696
  • 8
  • 25
5

Just put

<form action="" method="post" >

Empty action 'what ever current complete url is'

Rizwan Mumtaz
  • 3,875
  • 2
  • 30
  • 31
3

If you want to be more generic, you could just do something like this, which passes any of the GET parameters along when the form is posted:

<form action="/path-to-whatever/{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" method="post">
seddonym
  • 16,304
  • 6
  • 66
  • 71
1

Instead of assigning next in your view & passing it to template, isn't it cleaner to use ?next={{request.path}} in your template. (Remember to enable django.core.context_processors.request in settings.py, which is usually enabled in by default in django 1.6)

Here is the link tells about the same

https://docs.djangoproject.com/en/1.6/topics/auth/default/#the-raw-way

<form action="/login/?next={{request.path}}" method="post" >

is the code required.

Note:

You can get the current url with request.path from view also.

Thanks to buffer. I have just copy and pasted your comment after trying it myself in my own code.

Community
  • 1
  • 1
Ajeeb.K.P
  • 1,013
  • 1
  • 13
  • 21
  • I am not sure i answered your question, but this link might be useful [Django after @login_required redirect to next](http://stackoverflow.com/questions/21693342/django-after-login-required-redirect-to-next/21693784#comment41897184_21693784) – Ajeeb.K.P Oct 30 '14 at 05:58