11

I feel like this is an easy question and I'm just missing 1 small step.

I want to do any number of the following (as the term in the next parameter):

[not signed in] -> profile -> login?next=/accounts/profile/ -> auth -> profile.
[not signed in] -> newsfeed -> login?next=/newsfeed/` -> auth -> newsfeed.

Whereas I am currently going:

[not signed in] -> profile -> login?next=/accounts/profile/ -> auth -> loggedin
[not signed in] -> newsfeed -> login?next=/newsfeed/ -> auth -> loggedin

I am looking to somehow pass the next parameter from a form on login to auth and have auth redirect to this parameter

Currently I am trying in my login.html:

<input type='text' name="next" value="{{ next }}">

however this is not getting the next value. I can see from the debug tool bar:

GET data
Variable    Value
u'next'     [u'/accounts/profile/']

views:

def auth_view(request):
  username = request.POST.get('username', '')
  password = request.POST.get('password', '')
  user = auth.authenticate(username=username, password=password)

  if user is not None:
    auth.login(request, user)
    print request.POST
    return HttpResponseRedirect(request.POST.get('next'),'/accounts/loggedin')
  else:
    return HttpResponseRedirect('/accounts/invalid')

login.html:

{% extends "base.html" %}

{% block content %}

  {% if form.errors %}
  <p class="error"> Sorry, you have entered an incorrect username or password</p>
  {% endif %}
  <form action="/accounts/auth/" method="post">{% csrf_token %}
    <label for="username">User name:</label>
    <input type="text" name="username" value="" id="username">

    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">

    <input type='text' name="next" value="{{ request.GET.next }}">
    <input type="submit" value="login">
  </form>

{% endblock %}

settings:

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:

    url(r'^admin/', include(admin.site.urls)),
    ('^accounts/', include('userprofile.urls')),

    url(r'^accounts/login/$', 'django_yunite.views.login'),
    url(r'^accounts/auth/$', 'django_yunite.views.auth_view'),
    url(r'^accounts/logout/$', 'django_yunite.views.logout'),
    url(r'^accounts/loggedin/$', 'django_yunite.views.loggedin'),
    url(r'^accounts/invalid/$', 'django_yunite.views.invalid_login'),

)

settings:

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

TEMPLATE_DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'debug_toolbar',
    'userprofile',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'django_yunite.urls'

WSGI_APPLICATION = 'django_yunite.wsgi.application'

# Internationalization
# https://docs.djangoproject.com/en/1.6/topics/i18n/

LANGUAGE_CODE = 'en-ca'

TIME_ZONE = 'EST'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.6/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = (
    ('assets', '/home/user/GitHub/venv_yunite/django_yunite/static/'),
    )

TEMPLATE_DIRS = (
    './templates',
    '/article/templates',
)

STATIC_ROOT = "/home/user/Documents/static/"

AUTH_PROFILE_MODULE = 'userprofile.UserProfile'

the print statment shows an empty u'next'

Matt Stokes
  • 4,618
  • 9
  • 33
  • 56
  • Mind your `)`, `return HttpResponseRedirect(request.POST.get('next'),'/accounts/loggedin')` should be `return HttpResponseRedirect(request.POST.get('next','/accounts/loggedin'))` – Burhan Khalid Feb 11 '14 at 05:33
  • what no? why? also that doesn't solve the issue. The issue resides for whatever reason the 'next' parameter is blank. the {{ request.GET.next }} is not returning anything – Matt Stokes Feb 11 '14 at 05:38
  • Update the question with your `settings.py`. – Burhan Khalid Feb 11 '14 at 05:38
  • The reason its not working, as highlighted in my answer is that you are missing the `django.core.context_processors.request` in your `TEMPLATE_CONTEXT_PROCESSORS` setting. I will update my answer to show how to add this. – Burhan Khalid Feb 11 '14 at 05:48

6 Answers6

15

The query string is implicitly passed to any view, without you having to write any special code.

All you have to do is make sure that the next key is passed from the actual login form (in your case, this is the form that is rendered in /accounts/login/), to the /accounts/auth view.

To do that, you need to make sure you have the request template context processor (django.core.context_processors.request) enabled in your settings. To do this, first you need to import the default value for TEMPLATE_CONTEXT_PROCESSORS, then add the request processor to it in your settings.py, like this:

from django.conf import global_settings

TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
    "django.core.context_processors.request",
) 

Then in the form:

<form method="POST" action="/accounts/auth">
    {% csrf_token %}
    <input type="hidden" name="next" value="{{ request.GET.next }}" />
    {{ login_form }}
    <input type="submit">
</form>

Now, in your /accounts/auth view:

def foo(request):
    if request.method == 'POST':
        # .. authenticate your user


        # redirect to the value of next if it is entered, otherwise
        # to /accounts/profile/
        return redirect(request.POST.get('next','/accounts/profile/'))
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • this is exactly what I thought too but my {{ request.GET.next }} value is empty. I will edit and post by code – Matt Stokes Feb 11 '14 at 05:29
  • Its `request.GET`, `request.Get` won't work and you need to make sure you have the template context processor enabled (its not enabled by default). – Burhan Khalid Feb 11 '14 at 05:30
  • my apologies typo in the comments. Code is posted above – Matt Stokes Feb 11 '14 at 05:31
  • im not passing in request.GET.next, simply `{{ next }}` I updated my example. For reference I am testing with django 1.5. What version of django are you using? – Jeff Sheffield Feb 11 '14 at 05:37
  • unfortunately still isn't getting the `{{ request.GET.next }}` – Matt Stokes Feb 11 '14 at 05:56
  • did you try just... `{{ next }}`..? – Jeff Sheffield Feb 11 '14 at 05:59
  • just did and nothing. Ensured to reset the server too. – Matt Stokes Feb 11 '14 at 06:00
  • that's the heart of the issue is getting the `{{ next }}` from the `@login_request` – Matt Stokes Feb 11 '14 at 06:03
  • passing in a value to that field works as expected therefore the issue lies solely with getting the `{{ next }}` – Matt Stokes Feb 11 '14 at 06:28
  • @MattStokes there is no `{{ next }}` in your login form, because it is not passed as part of the context from your `auth` intermediate request. All you have to do is pass the `next` parameter from the **querystring** to the `auth` request. Django will do this automatically, all you have to do is render it in the template. To do that, you need the context processor. – Burhan Khalid Feb 11 '14 at 06:32
  • I know. I just changed the field from hidden to text to test what happens if I am passing the expected value. Which indeed does take me to the correct `/accounts/profile` – Matt Stokes Feb 11 '14 at 06:34
  • Therefore the problem solely lies on `{{ next }}` always returning ' ' – Matt Stokes Feb 11 '14 at 06:35
  • There is no problem, the simple fact is **`{{ next }}` is not passed in from the intermediate view**; simply update your `settings.py` and then _replace_ `{{ next }}` with `{{ request.GET.next }}` and it will work. – Burhan Khalid Feb 11 '14 at 06:37
  • I have made the changes and I'm telling you it still does not work. I have added the line `from django.conf import global_settings TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( "django.core.context_processors.request", )`to the settings and my `login.html` now reads: `` – Matt Stokes Feb 11 '14 at 06:42
  • When i added the ' TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( "django.core.context_processors.request", )' then this code worked for me ''. Now in my view, i get this variable as request.POST.get('next') or request.POST['next'] – Ajeeb.K.P Oct 30 '14 at 05:36
3

What you are looking for is a login decorator.

In your views.py

from django.contrib.auth.decorators import login_required

@login_required(login_url="/accounts/login/")
def profile( request ):
   """your view code here"""
   return HttpResponse("boo ya", "text/html")

Then in your urls.py add the authentication url

(r'^accounts/login/$', 'django.contrib.auth.views.login'),

Finally: Make sure you have django.contrib.auth in your installed apps, and AuthenticationMiddleware installed.

settings.py

INSTALLED_APPS = (
   -- snip --,
   'django.contrib.auth',
   )


MIDDLEWARE_CLASSES = (
   -- snip --,
   'django.contrib.auth.middleware.AuthenticationMiddleware',
   )

your templates/registration/login.html

<form method="POST" action="/accounts/login/">
   {% csrf_token %}
   <input type="hidden" name="next" value="{{ next }}" />
   {{ login_form }}
   <input type="submit">
</form>
Jeff Sheffield
  • 5,768
  • 3
  • 25
  • 32
  • 1
    I already have this. The issue is instead of being routed to (in your example) "boo ya" I want to go to page I was trying to go to before I was forced back to `@login_required` which should be held in the 'next' value somehow – Matt Stokes Feb 11 '14 at 05:14
1

What I have ended up doing is the following. To me this seems like a bit of a hack job. Is there any better way to use login with a csrf?

views:

def login(request):
  c={}
  c.update(csrf(request))
  if 'next' in request.GET:
    c['next'] = request.GET.get('next')
  return render_to_response('login.html', c)

def auth_view(request):
  username = request.POST.get('username', '')
  password = request.POST.get('password', '')
  user = auth.authenticate(username=username, password=password)

  if user is not None:
    auth.login(request, user)

    if request.POST.get('next') != '':
      return HttpResponseRedirect(request.POST.get('next'))
    else:
      return HttpResponseRedirect('/accounts/loggedin')
  else:
    return HttpResponseRedirect('/accounts/invalid')

login.html:

<input type="hidden" name="next" value="{{ next }}"/>
Matt Stokes
  • 4,618
  • 9
  • 33
  • 56
  • `` works! Thank you. To be clear, I already have ?next=... in the URL of /accounts/login, except it was redirecting to `/accounts/profile` per the built-in django auth system. Adding that hidden input overrides the default and redirects after login to ... . This means I didn't have to write my own login view to get this functionality. Thus, if you have ?next=... when you browse to /accounts/login, and ... is already correct, then simply add the hidden input, and it works! – MathCrackExchange Jan 17 '21 at 22:28
1

Faced similar situation sometime back. To solve this I wrote my own decorator -

def validate_request_for_login(f):
    def wrap(request):
        if not request.user.is_authenticated():
            return redirect("/login?next=" + request.path)
        return f(request)
    return wrap

Above decorator checks for user authentication. If user is not authenticated then redirect the user to login page by passing the url from request object.

anuragal
  • 3,024
  • 19
  • 27
0
def login_user(request):
    if request.method=='POST':
        print(request.POST,request.GET.get('next'))
        username=request.POST['email']
        password=request.POST['password']
        user = authenticate(request,username=username,password=password)
        if user:
            login(request,user)
        else:
            print('user not found')
            messages.error(request,'Invalid Credentials')
            return render(request, "login.html")
        return redirect(request.GET.get('next','/'))
    return render(request, "login.html")
  • return redirect(request.GET.get('next','/')) this is the key point use your index page url accordingly – KARTIK CHOUDHARY May 22 '21 at 16:51
  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Tyler2P May 22 '21 at 20:11
0

I found options here for solving this question ... I know you found your answer but here for another one...

urls.py

path('', Home.as_view(), name='home')

after login you need to use redirect like this (don't use render with template name then it gives you problem), then you go to the perfect home URL...

view.py

return redirect('home')
BiswajitPaloi
  • 586
  • 4
  • 16