I'm working on a Django project using a customized authentication build by me, I run into a problem where users can bypass my login and get into the home page by simply typing the URL
like this: "127.0.0.1/account/Adam" , "127.0.0.1/account/Bob" , "127.0.0.1/account/Alice"
, those people are not registered in the database and yet they receive "welcome Adam", "welcome Bob", "Welcome Alice"
I have been trying different methods from adding a global variable called Auth = False, and once a user is found in the database and the password is matched Auth will receive True, this kinda solved my problem but not as expected because once that variable becomes Auth = True example:
if bob is registred in database and login has been successfully made, with same session Bob can type those urls and manipulating the last url parameter and get Welcome sam, Welcom Alfred....
from django.http import HttpResponse
from django.contrib import messages
from django.contrib.auth.models import auth
from users.models import Composter
from django.core.exceptions import ObjectDoesNotExist
class MyView():
Vuser = None
# Create your views here.
def home(request):
return render(request, 'users/home.html')
#def compost_supplier_register(request):
return render(request, 'users/compost_supplier_register.html')
def composter_register(request):
if request.method == 'POST':
#extracting form data from a POST request and assigning it to variables representing a composter's name, email, password, address, state, city, and zip code.
composterName = request.POST['Composter_Name']
composterEmail = request.POST['Composter_Email']
composterPassword = request.POST['Composter_Password']
composterConfirmationPassword = request.POST['Composter_Confirmation_Password']
composterAddress = request.POST['Composter_Address']
composterState = request.POST['Composter_State']
composterCity = request.POST['Composter_City']
composterZipCode = request.POST['Composter_Zip_Code']
if composterPassword == composterConfirmationPassword:
#checks if the entred composter name exists in the database
if Composter.objects.filter(composterName=composterName).exists():
messages.info(request,'Name is Already taken !')
return redirect('composter_register')
#checks if the entred composter email exists in the database
elif Composter.objects.filter(composterEmail = composterEmail).exists():
messages.info(request,'Email already taken !')
return redirect('composter_register')
else:
#Affect values to composter object instance
composter = Composter(composterName = composterName ,composterEmail = composterEmail , composterPassword = composterPassword ,composterAddress = composterAddress, composterState = composterState, composterCity = composterCity ,composterZipCode = composterZipCode)
#Save the composter instance to the database
composter.save()
return redirect('composter_register')
else:
messages.info(request,'Password is not matching !')
return redirect('composter_register')
else:
return render(request, 'users/composter_register.html')
auth = False
def login(request):
global auth
if request.method == 'POST':
email = request.POST['Email']
password = request.POST['Password']
try:
user = Composter.objects.get(composterEmail = email)
if(user.composterPassword == password):
auth = True
if auth == True:
return redirect('account', composterName = user.composterName)
else:
auth = False
return redirect('login')
except ObjectDoesNotExist:
messages.info(request,'Please enter your username and password to log in')
return redirect('login')
return render(request, 'users/login.html')
def account(request, composterName):
global auth
if auth == False:
return redirect('login')
else:
return render(request, 'users/account.html', {'composterName':composterName})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{% csrf_token %}
<h1>Welcome {{composterName}}</h1>
</body>
</html>
urls.py
from django.urls import path
from . import views
urlpatterns = [ path('', views.home, name='home'),
path('composter_register/', views.composter_register, name='composter_register'),
path('compost_supplier_register/', views.compost_supplier_register, name='compost_supplier_register'),
path('login/', views.login, name='login'),
path('account/<str:composterName>', views.account, name='account')
]
Do not invent the wheel from scratch, better to use the solution which Django officially provided.
You could use #login_required decorator for the protected APIs
If you want to use your own solution then create a new field/column in your User model and use it instead of using the auth variable.
Related
I want to pass a model to a html page as context when I login into an account.
My Home page url comes with user id as a url parameter.
But i cant pass any context in redirect
views.py
from django.contrib.auth import authenticate,login,logout
from django.contrib import messages
from django.shortcuts import redirect, render
from django.http import HttpResponse
from .models import users
from home.models import Posts
def f1(request):
Post = Posts.objects.all()
context = {'Post':Post}
if request.method == "POST":
username = request.POST['username']
password = request.POST['password']
uz = authenticate(request,username=username, password=password)
if uz is not None:
login(request,uz)
id = users.objects.filter(name=username).values('id')[0]['id']
return redirect('home',id) # This Works Fine
return redirect('home',id,context) # This is not Working
else:
messages.error(request,"Wrong Credentials")
return render(request,'login.html')
urls.py
from django.contrib.auth import logout
from django.urls import path
from . import views
urlpatterns=[
path('<str:users_id>/',views.func,name="home"),
]
How can I pass the context?
If I can't tell me an alternative way to do it.
You're redirecting to another view, so you're passing data through your url. You can't put your context in it, so you can directly render your template :
return render(request, "home.html", context)
If you really want to redirect to another url, as your context only contains all Post objects, you can build it in your home view.
Then you have :
f1 :
return redirect('home',id)
func :
context = {'Post': Posts.objects.all()}
return render(request, "home.html", context)
Add the id to your context dictionary and pass to redirect. Something like
context = {'Post':Post, 'Id':id}
return redirect('home', context)
#Balizok is correct: you can't pass context via the URL, without urlencoding it, which would be a bad idea for large amounts of data. If you need the home page to show posts or not show posts depending on whether the user has just logged in, you could do it in a few ways:
Show posts only if the user is logged in:
def home(request, id):
context = {}
if request.user.is_authenticated:
context["Post"] = Posts.objects.all()
return render(request, "home.html", context)
Show posts only if the user has just been redirected from the login page via a query parameter:
from django.url import reverse
def f1(request):
# ... log the user in etc.
url = reverse("home", id) + "?show_posts=true"
return redirect(url)
def func(request, id):
context = {}
if request.GET.get("show_posts") == "true":
context["Post"] = Posts.objects.all()
return render(request, "home.html", context)
Show posts only if the user has just been redirected from the login page via the session:
from django.url import reverse
def f1(request):
# ... log the user in etc.
request.session["show_posts"] = True
return redirect("home", id)
def func(request, id):
context = {}
if request.session.get("show_posts"):
context["Post"] = Posts.objects.all()
return render(request, "home.html", context)
I am creating a book review website for CS50. I am having an issue displaying a query.The problem lies in the /books directory. The books.html contains a form with 3 inputs: isbn,title,author, and a submit button. Once I click the submit button it redirects to the login page. My goal is to output it to the user. If anyone could help me out, it would be really helpful.I get the error:
TypeError: 'ImmutableMultiDict' object is not callable
import os
from flask import Flask, session
from flask_session import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from flask import Flask, render_template, request, redirect
app = Flask(__name__)
# Check for environment variable
if not os.getenv("DATABASE_URL"):
raise RuntimeError("DATABASE_URL is not set")
# Configure session to use filesystem
app.secret_key = 'key'
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
# Set up database
engine = create_engine(os.getenv("DATABASE_URL"))
db = scoped_session(sessionmaker(bind=engine))
#app.route("/")
def index():
return render_template('index.html', navbar=True)
#app.route("/register",methods=['GET','POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
cpassword = request.form.get('cpassword')
if not password == cpassword:
return render_template('error.html', message='Passwords do not match')
avail = db.execute('SELECT username FROM userdetails WHERE username=:username',
{'username': username}).fetchone()
if avail:
return render_template('error.html', message='Username Already Exists')
db.execute('INSERT INTO userdetails(username, password) VALUES(:username, :password)',
{'username': username, 'password': password})
db.commit()
session["username"] = username
return redirect('/')
else:
return render_template('register.html')
#app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = db.execute('SELECT * FROM userdetails WHERE (username=:username AND password=:password)',
{'username': username, 'password': password}).fetchone()
if user is None:
return render_template('error.html', message='Entered credentials not valid!')
session["username"] = username
return redirect('books')
else:
return render_template('login.html', navbar=False)
#app.route("/books", methods=['GET', 'POST'])
def books():
isbn = request.form.get('isbn')
title = request.form.get('title')
author = request.form.get('author')
result = db.execute('SELECT * FROM books WHERE (isbn=:isbn AND title=:title AND author=:author)',
{'isbn': isbn, 'title': title, 'author': author}).fetchall()
return render_template('books.html')
#app.route("/result", methods=['GET', 'POST'])
def results():
if request.method == 'POST':
result = request.form()
return render_template("result.html",result = result)
Books.html
<html>
<head>
<title>Books</title>
</head>
<form method="POST" action="/result">
<input type="number" name="isbn" placeholder="isbn">
<input type="text" name="title" placeholder="title">
<input type="text" name="author" placeholder="author">
<input type="submit" value="Submit">
</form>
</html>
Result.html
<html>
<head>
</head>
<body>
{{result}}
</body>
</html>
One problem is this <form method="POST" action="/login"> in book.html. It is sending user back to the login page. From the spec, it seems the books route is from the search page (with "reviews" not yet implemented). The "book" page should allow user to enter a rating and review, and on submit presumably update the "reviews" table in the database (as suggested by the first Hint). It looks like the book page is not complete.
This line result = request.form() in results could be giving the TypeError since form is a MultiDict as per flask doc. Adding the () to the end looks like a callable function.
form
A MultiDict with the parsed form data from POST or PUT requests. Please keep in mind that file uploads will not end up here, but
instead in the files attribute.
The error message TypeError: 'ImmutableMultiDict' object is not callable should be giving a line number. Likely related to calling login with no username and passsword request form arguments.
I am currently working on a system where the login checking of username and password is checked by a python function. If the login details are correct, it will be redirected to a profile page (which i named dashboard). My problem is that my dahsboard/profile route reutrns a json if it is a POST and has also correct login details. I want this json data to be displayed in the html file. I managed to do it but I have used the variables in my jinja template. Although I have accomplished my goal (display the credentials in the html page), I would want it to be handled by ajax. How do I accomplish that?
Below are the codes I have tried so far (passing the data to the jinja variables)
#app.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
if request.method == 'GET':
#get the username passed along with the redirect
data1= getdatafromdb('getdata1',(request.args.get('uname'),))[0][0]
data2= getdatafromdb('getdata2',(code,))[0]
if 'Error' in str(data2):
return jsonify({'status': 'error', 'message': data2[0][0]})
return render_template('dashboard.html', firstname=data2[1],
middleinitial=data2[2],
lastname=data2[3],
contact=data2[4],
code=data2[5],
affiliation=data2[6],
city=data2[7])
elif request.method == 'POST':
return True
return render_template('dashboard.html')
Currently, it appears that you are running your validation process in your /dashboard route, which is not correct if you wish to redirect your user to that very page once their credentials are validated. Instead, you need to create your separate login method with ajax. First, from the / (home) route, render the template that contains the input boxes with ajax:
home.html:
<html>
<body>
<input type='text' name='username' id='username'>
<div class='username_failed'></div>
<input type='password' name='password' id='password'>
<div class='password_failed'></div>
<button type='button' class='login'>Login</button>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('.login').click(function() {
var username = $('#username').val();
var password = $('#password').val();
$.ajax({
url: "/login",
type: "get",
data: {username: username, password:password},
success: function(response) {
if (!response.status){
$('.'+response.issue+'_failed').html('<p>'+response.message+'</p>')
}
else{
window.location.replace('/dashboard'); //redirect to dashboard
}
},
error: function(xhr) {
//Do Something to handle error
}
});
});
});
</script>
</html>
Then, the login route will valid the input dynamically from the ajax in home.html. Previously, you need to create a function to validate the username and password. A possibility is to first check if they are empty, and then query the database:
import typing
def check_if_valid(username:str, password:str) -> typing.Dict[str, typing.Any]:
if not username or not password:
return {'status':False, 'issue':'username' if not username else 'password', 'message':f'{[username, password][username]} cannot be empty'}
_username = check_valid_username_from_db(username)
_password = check_valid_password_from_db(username, password)
if not _username:
return {'status':False, 'issue':'username', 'message':'Invalid username'}
if not _password:
return {'status':False, 'issue':'password', 'message':'Invalid username or password'}
return {'status':True}
#app.route('/login')
def login():
username = flask.requests.args.get('username')
password = flask.requests.args.get('password')
_r = check_if_valid(username, password)
if _r.status:
data2= getdatafromdb('getdata2',(code,))[0]
for i, a in enumerate(['firstname', 'middleinitial', 'lastname', 'contact', 'code', 'affiliation', 'city']):
flask.session[a] = data2[i]
flask.session['user_validated'] = _r.status
return flask.jsonify(_r)
Now, all your user data, if the user was successfully validated, will be stored as part of the session. Now, you can create your dashboard page, first with the html for the dashboard:
dashboard.html:
<html>
<body>
<h1>Welcome, {{firstname}}</h1>
<h4>Your data:</h4>
{%for d in data%}
<span>{{d.title}}: {{d.value}}</span>
{%endfor%}
</body>
</html>
Then, create the dashboard route with user validator:
def isloggedin(f):
def wrapper(*args):
if not flask.session['user_validated']:
return '<p>Not logged in</p>'
return f(*args)
return wrapper
#app.route('/dashboard', methods=['GET'])
#isloggedin
def dashboard():
from collections import namedtuple
headers = ['firstname', 'middleinitial', 'lastname', 'contact', 'code', 'affiliation', 'city']
data = namedtuple('data', ['title', 'value'])
return flask.render_template('dashboard.html', firstname = flask.session['firstname'], data = [data(a, flask.session[a]) for a in headers[1:]])
Lastly, link all together with the home route:
#app.route('/', methods=['GET'])
def home():
return flask.render_template('home.html')
I have been trying to use the Google reCAPTCHA on a website that I've been making. The captcha loads on the webpage but I've been unable to validate it using several methods. I've tried the recaptcha validation using the method given at
How to use Python plugin reCaptcha client for validation? but I think it's outdated as it no longer works and it is referring to challenges whereas the one I'm trying to use is the new 'checkbox' reCAPTCHA v2 by Google or maybe I need to make changes in my settings after installing recaptcha-client or django-recaptcha.
Please help!
Here is a simple example to verify Google reCAPTCHA v2 within Django view using requests library (http://docs.python-requests.org/en/latest/):
import requests
from django.conf import settings
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def grecaptcha_verify(request):
if request.method == 'POST':
response = {}
data = request.POST
captcha_rs = data.get('g-recaptcha-response')
url = "https://www.google.com/recaptcha/api/siteverify"
params = {
'secret': settings.RECAPTCHA_SECRET_KEY,
'response': captcha_rs,
'remoteip': get_client_ip(request)
}
verify_rs = requests.get(url, params=params, verify=True)
verify_rs = verify_rs.json()
response["status"] = verify_rs.get("success", False)
response['message'] = verify_rs.get('error-codes', None) or "Unspecified error."
return HttpResponse(response)
There is a third-party Django app to implement the new reCAPTCHA v2 here:
https://github.com/ImaginaryLandscape/django-nocaptcha-recaptcha
After installing it, add the following lines to the following files:
# settings.py
NORECAPTCHA_SITE_KEY = <the Google provided site_key>
NORECAPTCHA_SECRET_KEY = <the Google provided secret_key>
INSTALLED_APPS = (
....
'nocaptcha_recaptcha'
)
#forms.py
from nocaptcha_recaptcha.fields import NoReCaptchaField
class YourForm(forms.Form):
.....
captcha = NoReCaptchaField()
# In your template, add the following script tag:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
Google has changed the API around, we need to use a POST request now. Here a re-usable solution in case you need to do the validation in more than one django view:
utils.py
# django imports
from django.conf import settings
from django.views.generic.base import View
from django.http import HttpResponseForbidden
# 3rd-party imports
import requests
from ipware import get_client_ip
def is_recaptcha_valid(request):
"""
Verify if the response for the Google recaptcha is valid.
"""
return requests.post(
settings.GOOGLE_VERIFY_RECAPTCHA_URL,
data={
'secret': settings.RECAPTCHA_SECRET_KEY,
'response': request.POST.get('g-recaptcha-response'),
'remoteip': get_client_ip(request)
},
verify=True
).json().get("success", False)
def human_required(view_func):
"""
This decorator is aimed to verify Google recaptcha in the backend side.
"""
def wrapped(request, *args, **kwargs):
if is_recaptcha_valid(request):
return view_func(request, *args, **kwargs)
else:
return HttpResponseForbidden()
return wrapped
then:
views.py
from utils import human_required
class MyView(View):
#human_required
def post(request, *args, **args):
pass
Note we are using django-ipware in this solution to get the ip address, but this is up to you. Also, don't forget to add GOOGLE_VERIFY_RECAPTCHA_URL and RECAPTCHA_SECRET_KEY to the django settings file!
views.py
def login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = auth.authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
auth.login(request, user)
''' Begin reCAPTCHA validation '''
recaptcha_response = request.POST.get('g-recaptcha-response')
url = 'https://www.google.com/recaptcha/api/siteverify'
values = {
'secret' : settings.GOOGLE_RECAPTCHA_SECRET_KEY,
'response' : recaptcha_response
}
data = urllib.parse.urlencode(values).encode("utf-8")
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
result = json.load(response)
''' End reCAPTCHA validation '''
if result['success']:
return redirect('index')
else:
messages.error(request, 'Invalid reCAPTCHA. Please try again.')
return redirect('login')
else:
messages.info(request, 'Wrong Credentials!!! enter right username or password')
return redirect('login')
else:
return render(request, 'login.html')
login.html
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<div class="body bg-gray">
<div class="form-group">
<input type="text" name="username" class="form-control" placeholder="Username"/>
</div>
<div class="form-group">
<input type="password" name="password" class="form-control" placeholder="Password"/>
</div>
<div class="form-group">
<input type="checkbox" name="remember_me"/> Remember me
</div>
</div>
<div class="footer">
<button type="submit" class="btn bg-olive btn-block">Sign me in</button>
<p>I forgot my password</p>
Register a new membership
</div>
<br><br>
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="g-recaptcha" data-sitekey="(enter your key here that is private or authenticated on google recapthcha)"></div>
</form>
settings.py
INSTALLED_APPS = [
....
....
'captcha'
]
GOOGLE_RECAPTCHA_SECRET_KEY ='6LdXBLAUAMlGYqqyDESeHKI7-'
RECAPTCHA_PUBLIC_KEY = '6LdXBLAUAAAAAP3oI1VPJgA-VHXoj'
RECAPTCHA_PRIVATE_KEY = '6LdXBLAUAAAAAGYqqyDESeHKI7-'
''' you have to register your domain to get the access of these keys. or you can
register your localhost also to test this after uploading on the server you can
register with real domain and change the keys.
don't forget to like if you find it helpful.'''
'https://www.google.com/recaptcha/intro/v3.html' -> 'admin console' where you can
register your domain or localhost and get your key.
Expanding on the answer given by #trinchet, here is a simple modification of the FormView Django class to automatically handle Google's reCAPTCHA v2.
class RecaptchaFormView(FormView):
""" This class handles Google's reCAPTCHA v2. """
recaptcha_ok = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['grecaptcha_site_key'] = settings.RECAPTCHA_SITE_KEY
return context
def get_form(self):
form = super().get_form()
if self.recaptcha_ok == False:
form.add_error(None, "Invalid reCAPTCHA, please try again.")
return form
def post(self, request, *args, **kwargs):
self.recaptcha_ok = is_recaptcha_valid(request)
return super().post(self, request, *args, **kwargs)
Don't forget to include the is_recaptcha_valid function provided by #trinchet (see his answer), reCAPTCHA keys in settings.py and the reCAPTCHA code in the template (use {{ grecaptcha_site_key }} as the site key).
This is how I handle the proposed question:
views.py
from django.contrib.auth.views import LoginView, LogoutView
from django.conf import settings
from authentication.forms import MyAuthenticationForm
class MyLoginView(LoginView):
template_name = 'authentication/login.html'
form_class = MyAuthenticationForm
def get_client_ip(self):
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = self.request.META.get('REMOTE_ADDR')
return ip
def get_form_kwargs(self):
kwargs = super(MyLoginView, self).get_form_kwargs()
if self.request.method in 'POST':
kwargs['g-recaptcha-response'] = self.request.POST.get('g-recaptcha-response')
kwargs['remote_ip'] = self.get_client_ip()
return kwargs
def get_context_data(self, **kwargs):
context = super(MyLoginView, self).get_context_data(**kwargs)
# To use in the template
context['recaptcha_challenge_secret'] = settings.G_RECAPTCHA_CHALLENGE_SECRET
return context
forms.py
import requests
from django.contrib.auth.forms import AuthenticationForm
from django.conf import settings
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
class MyAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
self.g_recaptcha_response = kwargs.pop('g-recaptcha-response', None)
self.remote_ip = kwargs.pop('remote_ip', None)
super(MyAuthenticationForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(MyAuthenticationForm, self).clean()
self.verify_captcha()
return cleaned_data
def verify_captcha(self):
if self.g_recaptcha_response:
data = {
'secret': settings.G_RECAPTCHA_VERIFY_SECRET,
'response': self.g_recaptcha_response,
'remoteip': self.remote_ip
}
response = requests.post(settings.G_RECAPTCHA_VERIFICATION_URL, data=data)
result = response.json()
if result['success']:
return
raise ValidationError(
_('Invalid reCAPTCHA challenge.'),
code='invalid_recaptcha_challenge'
)
My problem in the short version:
I have added the login_required decorator to one of my views. If I enter the URL in the browser which executes this view, the browser correctly redirects to the URL that contains my login form if the user is not authenticated. However, the browser never redirects back to the previous page and I have no idea why this does not work. I have tried hundreds of things.
My problem in the long version:
I have a Django project with a single app, let's call it my_app. All the templates of my project reside in templates/my_app/. I have a template called main.html which contains several forms, among them my login form. With an additional POST parameter called form-type, I check which of the forms has been submitted. The code looks like this:
def process_main_page_forms(request):
if request.method == 'POST':
if request.POST['form-type'] == u'login-form':
template_context = _log_user_in(request)
elif request.POST['form-type'] == u'registration-form':
template_context = _register_user(request)
elif request.POST['form-type'] == u'password-recovery-form':
template_context = _recover_password(request)
else:
template_context = {
'auth_form': AuthenticationForm(),
'registration_form': RegistrationForm(),
'password_recovery_form': EmailBaseForm()
}
return render(request, 'my_app/main.html', template_context)
The function _log_user_in() looks like this:
def _log_user_in(request):
message = ''
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)
else:
message = 'Your account has been disabled. ' \
'Please contact the administrator.'
else:
message = 'Your username and password didn\'t match. Please try again.'
template_context = {
'auth_form': AuthenticationForm(),
'registration_form': RegistrationForm(),
'password_recovery_form': EmailBaseForm(),
'message': message,
}
return template_context
I also include the necessary <input> elements in the template, e.g. for the login form this is:
<input type="hidden" name="form-type" value="login-form" />
<input type="hidden" name="next" value="{{ next }}" />
The URL pattern for this view is:
url(r'^$', process_main_page_forms, name='main-page')
My second view renders two forms for changing email address and password for an authenticated user. It looks like this:
#login_required(login_url='/')
def change_user_credentials(request):
if request.method == 'POST':
if request.POST['form-type'] == u'change-email-form':
template_context = _change_email_address(request)
elif request.POST['form-type'] == u'change-password-form':
template_context = _change_password(request)
else:
template_context = {'change_email_form': ChangeEmailForm()}
return render(request, 'my_app/user.html', template_context)
The URL pattern for this second view is:
url(r'^account/$', change_user_credentials, name='user-page')
Whenever I access /account/ when I'm not authenticated, I'm successfully redirected to the main page that contains the login form. The resulting URL is http://127.0.0.1:8000/?next=/account/ that contains the necessary next parameter. However, when I log in my account, I'm still on the main page. I never get redirected to the user page, although I provided the necessary next parameter in the login form. It seems that this parameter is always empty, but I don't know why. I also don't have any other redirection calls in my code.
Can you help me solving this problem? Thank you very much in advance.
Possibly a trite answer, but the reason that no redirect is happening is because you don't seem to be doing anything with the next query parameter.
In fact, in the event that a user logs in successfully, you show the same page (albeit with a different context dictionary) as if they attempted to do something else:
def process_main_page_forms(request):
if request.method == 'POST':
if request.POST['form-type'] == u'login-form':
template_context = _log_user_in(request)
...
...
return render(request, 'my_app/main.html', template_context)
As the Django docs explain, it's the contrib.auth.views.login() function that processes the next parameter, and you're not using that view (although you are using the confusingly-identically-named contrib.auth.login function).
You should either just use the included view (which, in addition to processing next, also checks for is_active), or add the redirect functionality to your view, in which case it's probably worth bringing the authentication code into process_main_page_forms() unless you're sure you'll need _log_user_in() elsewhere:
from django.http import HttpResponseRedirect
from django.conf import settings
def process_main_page_forms(request):
if request.method == 'POST':
if request.POST['form-type'] == u'login-form':
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)
if request.GET.get('next', False):
return HttpResponseRedirect(request.GET.get('next'))
else:
return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
else:
message = 'Your account has been disabled.'
else:
message = 'Your username and password didn\'t match. Please try again.'
# If we've reached this point then the login failed
template_context = {
'auth_form': AuthenticationForm(),
'registration_form': RegistrationForm(),
'password_recovery_form': EmailBaseForm(),
'message': message,
}
elif ...:
# Do things with other form types
else:
return render(request, 'my_app/main.html', template_context)
The example usage of contrib.auth.login has the following:
def my_view(request):
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)
# Redirect to a success page.
else:
# Return a 'disabled account' error message
else:
# Return an 'invalid login' error message.
Your code is almost there, but you were missing the "redirect to a success page" part, which is where next would come into play.