TLDR: <input type="hidden" name="next" value="{{ next }}"> is the line of code I don't understand. With it this system of redirecting works fine, however, I do not understand what it is doing. It's as if is linking the user to the next page but no link is actually clicked?
I understand that if a user is not logged in the next parameter is used to redirect the user to whatever #login_required decorated view they were trying to access after logging in. Though the way this happens seems a bit automagical.
I have the following login related settings:
LOGIN_REDIRECT_URL = 'dashboard' # tells django which url to redirect after login if no 'next' parameter is present in the request
LOGIN_URL = 'login' # url to redirect the user to log in (for example, views using the login_required decorator)
and am using the authentication views fount in django.contrib.auth (only included three for simplicity):
from django.urls import path
from . import views
from django.contrib.auth import views as auth_views
urlpatterns = [
path('', views.dashboard, name = 'dashboard'),
path('login/', auth_views.LoginView.as_view(), name = 'login'),
path('password_change/', auth_views.PasswordChangeView.as_view(), name = 'password_change'),
]
Here is the custom login.html tempate located at account/registration/index.html:
<div class="login-form">
<form action="{% url 'login' %}" method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
<input type="submit" name="" value="Log-in">
</form>
</div>
Now say I am not logged in and try to access /account/password_change, I will be redirected to the login view and the path will be http://127.0.0.1:8000/account/login/?next=/account/password_change/, after logging in I can change my password with no problem. However, If I remove <input type="hidden" name="next" value="{{ next }}"> from index.html:
<div class="login-form">
<form action="{% url 'login' %}" method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" name="" value="Log-in">
</form>
</div>
and again try to access /account/change_password (as a non logged in user), I will be redirected to the login page and the url will be the same as before http://127.0.0.1:8000/account/login/?next=/account/password_change/. However, when I login this time I am redirected to the dashboard view (this makes sense to me, I defined this with LOGIN_REDIRECT_URL = 'dashboard' and did not provide a next parameter.
With all that said, what I don't understand is why after removing <input type="hidden" name="next" value="{{ next }}"> is the path at the login view sill http://127.0.0.1:8000/account/login/?next=/account/password_change/ (even though I will be redirect to dashboard and not password_change)? And why after adding <input type="hidden" name="next" value="{{ next }}"> back to the html does the browser know how to redirect the user to the correct page?
If anyone takes the time to read and respond to this, thanks in advance!
Nice question.
Please read this code segment
https://github.com/django/django/blob/master/django/contrib/auth/views.py#L71
If you observe closely, this line of code is trying to get data from POST as name next. If there is no key named next, it tries to get value from query parameter; I mean GET.
So, if we remove input from form (<input type="hidden" name="next" value="{{ next }}">), it still works as query params are acting as fallback.
Hope, it helps.
Related
I am trying to write a test to see if my login redirects to the correct page. At the moment I am using this code which isn't working:
class TestAuth(TestCase):
def setUp(self):
self.client = Client()
#classmethod
def setUpTestData(cls):
user_login = get_user_model().objects.create(username='admin', email='admin#test.co.uk', password='asdf1234')
cls.user_login = user_login
def test_login_redirect(self):
response = self.client.post(
reverse('udt:login'),
{
'username': 'admin',
'password': 'asdf1234'
}
)
self.assertRedirects(response, reverse('udt:table_list'))
where udt:login equates to '/udt/accounts/login/' and udt:table_list equates to '/udt/table/'.
The login functionality is Django's built-in login with a custom template. When I run the test I get the following error:
AssertionError: 200 != 302 : Response didn't redirect as expected: Response code was 200 (expected 302)
However, when I actually test the login functionality in the app I get this:
[2017/05/30 14:43:22] HTTP POST /udt/accounts/login/ 302 [0.13, 127.0.0.1:60127]
[2017/05/30 14:43:22] HTTP GET /udt/table/ 200 [0.15, 127.0.0.1:60127]
which to me seems like it is in fact redirecting correctly.
So, my question is what is wrong with my test that is causing the assertion error? I am pretty new to testing in Django so it could just be something that I am missing, but it seems like the test should be passing to me.
Any help with this would be much appreciated.
UPDATE
The login template looks like this (just took the standard Django login template and added some bootstrap class names to it):
{% load bootstrap3 %}
<form id="login-form" method="post" action="{% url 'udt:login' %}">
{% csrf_token %}
<table class="table">
<tr>
<td><label for="id_username">Username</label></td>
<td><input id="id_username" name="username" type="text" class="form-control"></td>
</tr>
<tr>
<td><label for="id_password">Password</label></td>
<td><input id="id_password" name="password" type="password" class="form-control"></td>
</tr>
</table>
{% if form.errors %}
<p class=" label label-danger">
Your username and password didn't match.
Please try again.
</p>
{% endif %}
<input type="submit" value="Login" class="btn btn-primary pull-right" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
url for login looks like so:
url(r'^login/$', auth_views.login, {'template_name': 'auth/login.html'}, name='login')
I am using Django v.1.11.1.
You are creating the user incorrectly. You should use create_user instead of create(), so that the password is correctly hashed.
#classmethod
def setUpTestData(cls):
user_login = get_user_model().objects.create_user(username='admin', email='admin#test.co.uk', password='asdf1234')
As an aside, you can remove your setUp method as it is unnecessary. Django's TestCase class takes care of setting up self.client for you.
One more thing - your URL pattern is fine for Django 1.11, but you could update it to use LoginView to be compatible with Django 2.1+:
url(r'^login/$', auth_views.LoginView.as_view(template_name= 'auth/login.html'), name='login'),
I would like to call a function which is in /inscription/views.py since all views (because it's for the login). And I need to pass the username and the password in parameters to log the user.
def login_user(request):
if request.method =='POST':
auth_form=AuthenticationForm(data=request.POST)
if auth_form.is_valid():
username = request.POST.get('username')
password = request.POST.get('password')
uti = authenticate(username = username,password = password)
if uti:
if uti.is_active:
login(request, uti)
return HttpResponseRedirect('/accueil')
else:
return HttpResponse("Your account is disabled.")
else:
return HttpResponse("Invalid login details supplied.")
else:
auth_form=AuthenticationForm()
return render_to_response('authentication.html',
{'auth_form': auth_form}, RequestContext(request))
def logout_user(request):
logout(request)
And In my base.html I would like to add something like :
<label class="form_login">pseudo : </label>
<input type="text" name="username" id="id_username" class="login_input">
<label class="form_login">mot de passe : </label>
<input type="text" name="password" id="id_password" class="login_input">
<input value="login" type="submit"/>
<button>logout</button>
If I understand your question correctly, what you need is to force the user to login if he is not already logged in before he can access your views. To do this, all you need to do is to decorate your views with login_required decorator
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
From the docs:
login_required() does the following:
- If the user isn’t logged in, redirect to settings.LOGIN_URL, passing
the current absolute path in the query string. Example:
/accounts/login/?next=/polls/3/.
- If the user is logged in, execute the view normally. The view code is
free to assume the user is logged in.
Update:
From your comment, now I understand that you need to make a form in all pages for the user to login, or a logout link if he is already logged in. First you need to define your URLs for these views:
url(r'^login/$', 'inscription.views.login', name='auth_login'),
url(r'^logout/$', 'inscription.views.logout', name='auth_logout'),
And in your base.html:
{% if user.is_authenticated %}
Logout
{% else %}
<form method="post" action="{% url 'auth_login' %}">
{% csrf_token %}
<input type="text" name="username" id="id_username">
<input type="text" name="password" id="id_password">
<input type="submit" value="Log in" />
</form>
{% endif %}
As a side note, I highly recommend you to use one of these reusable apps for auth and registration. unless you have strange requirements.
http://django-registration-redux.readthedocs.org/en/latest/
http://django-allauth.readthedocs.org/en/latest/
The problem which you are facing is , that u want the login and logout to work from other pages also, So, for this you need not to go for any extra function. All you need to do is, u just extend your base.html to all other html pages. Then you will surely be able to login and logout from all the pages.
Suppose you have login/logout in base.html
<label class="form_login">pseudo :</label>
<input type="text" name="username" id="id_username" class="login_input">
<label class="form_login">mot de passe : </label>
<input type="text" name="password" id="id_password" class="login_input">
<input value="login" type="submit"/>
<button>logout</button>
Now make some other html say test.html
There at the beginning you write
{% extends 'base.html' %}
followed by your HTML markup.
Don't forget to use
{% block content %} {% endblock %} **template tags**
In base as well as other HTML pages.
In other pages u try to write the complete code in template tags.
For query https://docs.djangoproject.com/en/1.7/topics/templates/
Also try using the concept of decorator.
I'm getting the error
"Reverse for 'recall' with arguments '('',)' and keyword arguments '{}' not found. 1 pattern(s) tried: [u'associate/recall/']"
When I try to submit a form. Here is my html:
<form action="{% url 'associate:recall' ordered_group %}" method="post">
{% csrf_token %}
<div>
<label for="recall">enter as many members of {{ ordered_group }} as you can recall </label>
<input type="text" id="recall" name="recall">
</div>
<div id="enter_button">
<input type="submit" value="enter" name="enter" />
</div>
<div id="done_button">
<input type="submit" value="done" name="done" />
</div>
</form>
"ordered_group" is a model object that is carried over from the 'learn' view:
urls.py:
urlpatterns = patterns('',
url(r'^learn/', "associate.views.learn", name='learn'),
url(r'^recall/', 'associate.views.recall', name='recall'),
url(r'^$', "associate.views.index", name='index'),
)
I am trying to use the ordered_group model object that is submitted in the learn view context to the html, back to the recall view as an argument. Can one do this? It makes sense to me, but what is the correct way of doing this?
views.py
def recall(request, ordered_group):
...
def learn(request):
...
ordered_group = ordered_groups[index]
return render(request, 'associate/learn.html', {'dataset':model, 'ordered_group':ordered_group})
I want to submit the form with
In you HTML, you are doing:
{% url 'associate:recall' ordered_group %}
Django expects that "recall" url is in "associate" namespace, because of the ":". But, you need to declare the namespace in urls.py, like:
url(r'^recall/', 'associate.views.recall', namespace='associate', name='recall')
If you don't want the namespace, just do:
{% url 'recall' ordered_group %}
And, about "ordered_group", you need to declare it in your url, like:
url(r'^recall/(?P<ordered_group>\w+)', 'associate.views.recall', namespace='associate', name='recall')
You are passing ordered_group in HTML, youare expecting this in views.py, but you are not expecting this on you URL.
How to build a login form on the startingpage within a little widget on the top left?
I want to have a stylish web 2.0 like login...on the upper right sliding in (like on dropbox.com)...so far the design part...
when it comes to the views and default login behavior of django (1.4) i can't get myself to the right direction. I get the standart example to work with django.contrib.auth and so forth...
but what if i have my log-in widget on the first site:
(r'^$', 'myproject.views.home')
I tried to integrate the form into my template (home.html) which looks like this:
<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="hidden" name="next" value="{{'something'}}" />
<input type="submit" value="anmelden" />
</form>
my view looks like this:
def home(request):
title='home'
try:
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
# Correct password, and the user is marked "active"
auth.login(request, user)
# Redirect to a success page.
return HttpResponseRedirect("/im_in/")
else:
# Show an error page
return HttpResponseRedirect("/im_not/")
except:
return render_to_response('home.html',locals())
[edit]
my urlconf:
urlpatterns = patterns('',
(r'^$', 'edumin.views.home'),
)
My problem:
after submit django leads me to /login and says:
The current URL, login/, didn't match any of these.
Any help or a good example of having a login on the first page...or a custom login which serves this purpose?
Based on everything I see here, you don't actually have anything at /login/ to catch the login, but instead are trying to log in via the current URL. In that case you should completely remove the action attribute from the login form.
im using the comment system, now, i would like to re-write the segment form the url comment and append a symbol #, i want to move the page seccion to the comment list exactly to the last comment user with <a name=#{{comment.id}}?> username </a>
Im using next for redirect the usen when the comment was posted:
{% get_comment_form for object as form %}
<form action="{% comment_form_target %}" method="POST">
{{ form }}
<input type="hidden" name="next" value="{{ object.get_absolute_url }}" />
<input type="submit" name="preview" class="submit-post" value="Preview"></td>
</form>
But in the Django Doc dont say nothing about rewrite or customizer the comment redirect / url
Any idea?
Thanks
I just stumbled across this little bit of ugliness. After reading the source code I didn't see any nice way to override this behavior. By default you are redirected to the URL in the template's {{ next }} variable and Django appends a ?c=1 to the URL where the 1 is the ID of the comment. I wanted this to instead be #c1 so the user is jumped down the page to the comment they just posted. I did this with a little bit of "monkey patching" as follows:
from django.contrib.comments.views import utils
from django.core import urlresolvers
from django.http import HttpResponseRedirect
def next_redirect(data, default, default_view, **get_kwargs):
next = data.get("next", default)
if next is None:
next = urlresolvers.reverse(default_view)
if get_kwargs:
next += '#c%d' % (get_kwargs['c'],)
return HttpResponseRedirect(next)
# Monkey patch
utils.next_redirect = next_redirect