Django - Force a user to reset initial password - django

I am implementing a functionality where a user is forced to change a password (i.e. if the admin creates an initial password, the user is required to change it on the next login). I am using the Django Contrib Auth package.
For this, I have a extended the user profile by a boolean parameter force_password_change:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
force_password_change = models.BooleanField(default=True)
In my view, I am extending the standard LoginView:
class MyLoginView(LoginView):
def form_valid(self, form):
# form is valid (= correct password), now check if user requires to set own password
if form.get_user().profile.force_password_change:
return HttpResponseRedirect('password_change')
else:
auth_login(self.request, form.get_user())
return HttpResponseRedirect(self.get_success_url())
The system renders a 404 page not found error after I click the login button: The current path, accounts/login/, didn't match any of these.
I noticed that when I add auth_login(self.request, form.get_user()) just before the HttpResponseRedirect('password_change') it works fine. But this also means that the user is (incorrectly) authenticated.
urls.py:
path('', myapp.MyLoginView.as_view(), name='login'),
path('password_change/', myapp.MyPasswordChangeView.as_view(), name='password_change'),
Why is this the case and why is the 404 error refering to accounts/login/?

You need to use reverse for resolving the url name:
from django.urls import reverse
class MyLoginView(LoginView):
def form_valid(self, form):
# form is valid (= correct password), now check if user requires to set own password
if form.get_user().profile.force_password_change:
return HttpResponseRedirect(reverse('password_change'))
else:
auth_login(self.request, form.get_user())
return HttpResponseRedirect(self.get_success_url())

Use User.set_unusable_password, then assuming your password change view is called 'change-password':
Add this to your login view:
from django.http import HttpResponseRedirect
from django.urls import revese
if not user.has_usable_password():
return HttpResponseRedirect(reverse('change-password')

Related

Override Django LoginView Error Message With Custom AuthenticationForm

I have a class-based view that subclasses LoginView.
from django.contrib.auth.views import LoginView
class CustomLoginView(LoginView):
def get_success_url(self):
url = self.get_redirect_url()
return url or reverse_lazy('knowledgebase:user_home', kwargs={
'username':self.request.user.username,
})
I want to override the error message if a user's email is not yet active because they have to click a link sent to their email address. The current default message looks like this:
Instead of saying:
Please enter a correct email address and password. Note that both
fields may be case-sensitive.
I want to say something to the effect of:
Please confirm your email so you can log in.
I tried:
accounts/forms.py
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import gettext as _
class PickyAuthenticationForm(AuthenticationForm):
def confirm_login_allowed(self, user):
if not user.is_active:
raise forms.ValidationError(
_("Please confirm your email so you can log in."),
code='inactive',
)
accounts/views.py
class CustomLoginView(LoginView): # 1. <--- note: this is a class-based view
form_class = PickyAuthenticationForm # 2. <--- note: define form here?
def get_success_url(self):
url = self.get_redirect_url()
return url or reverse_lazy('knowledgebase:user_home', kwargs={
'username':self.request.user.username,
})
The result is absolutely no effect when I try to log in with a user that does exist, but hasn't verified their email address yet.
AuthenticationForm docs.
Method - 1
Django uses ModelBackend as default AUTHENTICATION_BACKENDS and which does not authenticate the inactive users.
This is also stated in Authorization for inactive users sections,
An inactive user is one that has its is_active field set to False. The
ModelBackend and RemoteUserBackend authentication backends prohibits
these users from authenticating. If a custom user model doesn’t have
an is_active field, all users will be allowed to authenticate.
So, set AllowAllUsersModelBackend as your AUTHENTICATION_BACKENDS in settings.py
# settings.py
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
How much does it affect my Django app?
It doesn't affect anything other than the authentication. If we look into the source code of AllowAllUsersModelBackend class we can see it just allowing the inactive users to authenticate.
Method - 2
Personally, I don't recommend this method since method-1 is the Django way of tackling this issue.
Override the clean(...) method of PickyAuthenticationForm class and call the AllowAllUsersModelBackend backend as,
from django.contrib.auth.backends import AllowAllUsersModelBackend
class PickyAuthenticationForm(AuthenticationForm):
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
backend = AllowAllUsersModelBackend()
self.user_cache = backend.authenticate(self.request, username=username, password=password)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
if not user.is_active:
raise forms.ValidationError(
"Please confirm your email so you can log in.",
code='inactive',
)
Result Screenshot
You need to use AllowAllUsersModelBackend
https://docs.djangoproject.com/en/3.0/ref/contrib/auth/#django.contrib.auth.backends.AllowAllUsersModelBackend
Here you will get instruction for setting custom backend
https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#specifying-authentication-backends
Hope it helps.
I'm not convinced setting up a custom backend is the solution when I simply want to override a message. I did a temporary fix by defining form_invalid. Yes it's hacky but for now, it'll do the trick. Doubt this will help anyone but it was interesting to discover form.errors. Maybe someone can build off this to solve their specific problem.
def form_invalid(self, form):
"""If the form is invalid, render the invalid form."""
#TODO: This is EXTREMELY HACKY!
if form.errors:
email = form.cleaned_data.get('username')
if User.objects.filter(email=email, username=None).exists():
if len(form.errors['__all__']) == 1:
form.errors['__all__'][0] = 'Please confirm your email to log in.'
return self.render_to_response(self.get_context_data(form=form))

python - Django redirect to next after login (Generic Form View)

I am using generic form view for authentication, I am getting next parameter in url but unfortunately I don't know how to redirect it to next, after successful login for Generic Form View, here is my view
class LoginView(
views.AnonymousRequiredMixin,
generic.FormView):
form_class = LoginForm
success_url = reverse_lazy('home')
template_name = 'accounts/registered/login.html'
def form_valid(self, form):
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = authenticate(username=username, password=password)
if user is not None and user.is_active and user.is_seller:
login(self.request, user)
return super(LoginView, self).form_valid(form)
else:
return self.form_invalid(form)
I am getting this
http://127.0.0.1:8000/accounts/login/?next=/accounts/dashboard/
help me out!
So essentially, what the url that you are getting means is that it's trying to go to 127.0.0.1:8000/accounts/dashboard/, but because the user needs to be logged in, it's going to the login page first. Essentially, this means that your view is not logging the user in for some reason.
Try using (or extending) Django's built in LoginForm class (https://docs.djangoproject.com/en/2.0/topics/auth/default/#django.contrib.auth.views.LoginView)
Alternatively, go with a broader solution suite, such as django allauth (https://github.com/pennersr/django-allauth/blob/master/docs/index.rst)
You should use HttpRedirectResponse:
views.py
from django.http import HttpResponseRedirect
def login(request):
# You logic goes here
return HttpResponseRedirect('dashboard')
def dashboard(request):
context = {
# User information goes here
}
return render(request, 'dashboard', context)
Do not forget to add this call to the login method in your urls.py:
path('login', views.login, name='login'),
path('dashboard', views.dashboard, name='dashboard'),
You should also take a look at https://docs.djangoproject.com/en/2.0/ref/request-response/ for a better understanding of how request and response work.
You should also be familiar with https://docs.djangoproject.com/en/2.0/intro/tutorial04/ so that you could understand an example of HttpResponseRedirect.

Building a login form

I'm building a login form in Django and as far as my research about it goes is seems like I can't create login form that is "independent" from admin interface. What I mean by that is visitor doesn't have to be registered user in admin. Much like in PHP and MySQL. I can do what I need with PHP but I've been learning Python for some time now and would like to build it in Python. Is Django really gives me this only option ?
Makes me wonder how Pinterest did this. I know vast majority of the app is built in Django and people are presented with a login form on front page.
You might find this helpful. I didn't write all the explanations, so please search for the attributes or methods you're not familiar with. Also, this implementation lacks many options like confirming the email address, etc. (these are secondary priority at the moment for me).
'password' and 'username' are model attributes in this case.
Here, after the user creates an account with a username and password, it is authenticated and the user is logged in then redirected to the specified url.
For html/bootstrap login forms, have a look at these:
https://bootsnipp.com/search?q=login
For the django implementation you can follow these steps:
1) After making the html form, add proper url line the urls.py
2) Make a new file in your project and name it forms.py, then:
from django import forms
class UserForm(forms.ModelsForm):
email = form.CharField(widget=forms.EmailInput)
password = form.CharField(widget=forms.PasswordInput)
class Meta:
model = *write your model name here*
fields = *fields from your model. The model design should include
username email password and other attributes*
3) In views.py:
from django.views.generic import View
from django.shortcuts import render, redirect
class UserFormView(View):
form_class = UserForm
template_name = *your html template*
def get(self, request):
form = self.form_class(None)
return render(request, self.template_name, {'form':form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
user = form.save(commit=False)
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user.set_password(password)
user.save()
user = authenticate(username=username, password=password)
if user is not None and user.is_active:
login(request, user)
return redirect(*your redirecting url*)
return render(request, self.template_name, {'form':form})
There are in fact login views that are independent from the admin login, as per the docs:
Add the following to your urls.py:
urlpatterns = [
url('^', include('django.contrib.auth.urls')),
]
This will include the following URL patterns:
^login/$ [name='login']
^logout/$ [name='logout']
^password_change/$ [name='password_change']
^password_change/done/$ [name='password_change_done']
^password_reset/$ [name='password_reset']
^password_reset/done/$ [name='password_reset_done']
^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ [name='password_reset_confirm']
^reset/done/$ [name='password_reset_complete']
These provide views to login/change and reset passwords/etc for non-admin users. As a safety precaution, they do not include a sign-up view by default (since many implementations may want to not allow random people to sign up).

How can I redirect from my login page to :8000/username in Django

i have my login form in the homepage itself i.e. "/". now from there i want to redirect a user to 0.0.0.0:8000/username where 'username' is not static, it i different for different users.
I'm a beginner to Django. Pls explain in dept. Thanks in advance
what you could do is define a home url and a profile url in your urls.py like this.
#urls.py
url(r'^$', 'app.views.home'),
url(r'^(?P<username>\w+)/$', 'app.views.profile'),
now under views.py define 2 views one to render the home page and second to render the profile page
# views.py
import models
from django.shortcuts import render_to_response
from django.templates import RequestContext
from django.contrib.auth import authenticate, login
def home(request):
"""
this is the landing page for your application.
"""
if request.method == 'POST':
username, password = request.POST['username'], request.POST['password']
user = authenticate(username=username, password=password)
if not user is None:
login(request, user)
# send a successful login message here
else:
# Send an Invalid Username or password message here
if request.user.is_authenticated():
# Redirect to profile page
redirect('/%s/' % request.user.username)
else:
# Show the homepage with login form
return render_to_response('home.html', context_instance=RequestContext(request))
def profile(request, username):
"""
This view renders a user's profile
"""
user = user.objects.get(username=username)
render_to_response('profile.html', { 'user' : user})
Now when the first url / is requested it forwards the request to app.views.home which means the home view ===within===> views.py ===within===> app application.
the home view checks if a user is authenticated or not. if a user is authenticated it calls the url /username otherwise it simply renders a template called home.html in your templates directory.
The profile view accepts 2 arguments, 1. request and 2. username. Now, when the profile view is called with the above mentioned arguments it gets the user instance for the username provided and stores it in a user variable and later passes it to the profile.html template.
also please do read through the very easy Poll Application Tutorial on Django Project to get familiar with the power of django.
:)

django LOGIN_REDIRECT_URL with dynamic value

I'm trying to redirect a user to a url containing his username (like http://domain/username/), and trying to figure out how to do this. I'm using django.contrib.auth for my user management, so I've tried using LOGIN_REDIRECT_URL in the settings:
LOGIN_REDIRECT_URL = '/%s/' % request.user.username # <--- fail..
but it only seems to accept fixed strings, rather than something that'll be determined after the user is logged in. How can I still accomplish this?
A solution, is to redirect to a static route like '/userpage/' and have that redirect to the final dynamic page.
But I think the real solution is to make a new view that does what you really want.
from django.contrib.auth import authenticate, login
from django.http import HttpResponseRedirect
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)
HttpResponseRedirect('/%s/'%username)
else:
# Return a 'disabled account' error message
else:
# Return an 'invalid login' error message.
http://docs.djangoproject.com/en/dev/topics/auth/#authentication-in-web-requests
for more information about rewriting the view. This is how the docs say to override this kind of thing.
With the class-based django.contrib.auth.views.LoginView, you can now simply override get_success_url:
urls.py:
url(r'^login$', MyLoginView.as_view(), name='login'),
url(r'^users/(?P<username>[a-zA-Z0-9]+)$', MyAccountView.as_view(), name='my_account'),
views.py
class MyLoginView(LoginView):
def get_success_url(self):
return reverse('my_account', args=[self.request.user.username])
Wrap the auth view in your own custom view and redirect to wherever you want if authentication succeeded.
from django.http import HttpResponseRedirect
from django.contrib import auth
from django.core.urlresolvers import reverse
def login(request):
template_response = auth.views.login(request)
if isinstance(template_response, HttpResponseRedirect) and template_response.url == '/accounts/profile/':
return HttpResponseRedirect(reverse('user', args=(request.user.username,)))
return template_response
Another alternative is to use the query param next to indicate where to redirect to after the login.
sign in
I use django-two-factor-auth login view which provides OTP features for login.
Hence I extend from two_factor's LoginView.
In main urls.py:
from two_factor.urls import urlpatterns as tf_urls
urlpatterns = [
# make sure login is before default two_factor (tf_urls) urls
# coz first url has higher preference
path('account/login/', MyLoginView.as_view(), name='login'),
path('', include(tf_urls)),
]
In views.py:
from two_factor.views.core import LoginView
from two_factor.utils import default_device
class MyLoginView(LoginView):
def get_success_url(self):
if self.is_otp_setup() is True:
return reverse('homepage:homepage')
# otp not setup. redirect to OTP setup page
else:
return reverse('two_factor:setup')
def is_otp_setup(self):
if self.request.user and \
self.request.user.is_authenticated and \
default_device(self.request.user):
return True
else:
return False