To make a user inactive I usually do:
User.objects.get(pk=X).update(is_active=False)
However, this doesn't log out the user or do anything session-related. Is there a built-in in django to make a user immediately inactive, or what would be the best way to accomplish this?
One similar answer is this: https://stackoverflow.com/a/954318/651174, but that doesn't work great if there are millions of sessions (it's a brute force way iterating over all sessions). Though this is from 2009, so hopefully there's a better way as of today.
As mentioned, you can use django-user-session or django-qsessions. But these include some other metadata such as user agent and ip address, and you may not want these for some reason. Then you need to write your custom session backend
I adjusted the example a bit and created one to the my needs.
session_backend.py:
from django.contrib.auth import get_user_model
from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.contrib.sessions.base_session import AbstractBaseSession
from django.db import models
User = get_user_model()
class QuickSession(AbstractBaseSession):
# Custom session model which stores user foreignkey to asssociate sessions with particular users.
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
#classmethod
def get_session_store_class(cls):
return SessionStore
class SessionStore(DBStore):
#classmethod
def get_model_class(cls):
return QuickSession
def create_model_instance(self, data):
obj = super().create_model_instance(data)
try:
user_id = int(data.get('_auth_user_id'))
user = User.objects.get(pk=user_id)
except (ValueError, TypeError, User.DoesNotExist):
user = None
obj.user = user
return obj
and in settings.py:
SESSION_ENGINE = 'path.to.session_backend'
To delete all session for a user:
from session_backend import QuickSession
QuickSession.objects.filter(user=request.user).delete()
You may write your custom save method for user model to automatically delete all sessions
for the user if the is_active field is set to False.
Keep in mind that, user field for those who are not logged in will be NULL.
Changing the user's password invalidates all the user's sessions since around Django version 2.2. (This works without scanning the whole session table. An HMAC of the password field is saved on login, and on any request where the request.user is accessed, the login session is treated as no-longer-valid if the current HMAC does not match.)
https://docs.djangoproject.com/en/2.2/topics/auth/default/#session-invalidation-on-password-change-1
user = User.objects.get(pk=user_id)
user.is_active = False
user.set_unusable_password()
user.save()
Related
I'm writing a User system that cannot login at the same time.
If the account in login state in somewhere, and someone login the same account in other position. The latter one will be logged in. And the previous will be logged out.
I'm using a model with oneToOneField associated to the User model, And save session ids of this user.
The code is like below.
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from .myModels import JSONField
class Profile(models.Model):
user = models.OneToOneField(User, models.CASCADE)
sessionids = JSONField(null=True)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
The JSONField is a field that using textField to store JSON string.
When a user login, I go to get all session ids of this user and delete all the session ids. Then I add current session id to the Profile. By doing this, I can logout in the previous position. the code is like below.
def login(request):
if request.method == "POST":
if request.user.is_authenticated:
return HttpResponse("the user session is authenticated.")
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:
auth.login(request, user)
#remove cur user all sessions
sessionidsToDelete = request.user.profile.sessionids
if sessionidsToDelete != None:
sessions = Session.objects.filter(session_key__in=sessionidsToDelete)
for session in sessions:
session.delete()
#add cur user sessions
sessionidsToStore = user.profile.sessionids
print("sessionidsToStore = ")
print(sessionidsToStore)
print("sessionidsToDelete = ")
print(sessionidsToDelete)
if sessionidsToStore== None:
sessionidsToStore = []
else:
sessionidsToStore = list(set(sessionidsToStore) - set(sessionidsToDelete))
print("sessionidsToStore = ")
print(sessionidsToStore)
sessionidsToStore.append(request.session.session_key)
user.profile.sessionids = json.dumps(sessionidsToStore)
user.profile.save()
rotate_token(request)
return HttpResponse("login sucessful")
elif user.is_active == False:
userNotActivedHttpresponse = HttpResponse()
userNotActivedHttpresponse.status_code = 605
userNotActivedHttpresponse.reason_phrase = "This user not active"
return userNotActivedHttpresponse
else:
return HttpResponse("Please Input the correct username and password")
else:
return HttpResponseBadRequest("Please use POST to login")
But I think something will happen. When there two people want to login the same account at the same time.
For example, there are two people know the same account.
They login at the same time. It may be happen that B append B's session id to Profile after A remove all other session ids. In this situation, A and B will still in login state, and won't be logout. How could I prevent this problem?
I think you make things very complicated, by storing data in UserProfiles, etc. and then have signals, you introduce a lot of levels, and at each level, things can go wrong.
We basically need two things here: a table that maps Users to their corresponding settings. We can implement this with a UserSession model:
# models.py
from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session
class UserSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
session = models.OneToOneField(Session, on_delete=models.CASCADE)
So the UserSession object makes a link between User and Sessions. Now we can implement a login hook: a signal that is triggered in case a user logs in. In that case we perform two things:
we delete all Sessions (and corresponding UserSessions) of the User that are active; and
we create a new Session and corresponding UserSession that we can remove later. Like:
from django.contrib.auth import user_logged_in
from django.dispatch.dispatcher import receiver
#receiver(user_logged_in)
def remove_other_sessions(sender, user, request, **kwargs):
# remove other sessions
Session.objects.filter(usersession__user=user).delete()
# save current session
request.session.save()
# create a link from the user to the current session (for later removal)
UserSession.objects.get_or_create(
user=user,
session_id=request.session.session_key
)
Update: I wrapped this into a small reusable app [GitHub] that can be installed through pip.
Since you want the user to have only one session at a time, you can call logout before you call login
...
if user is not None and user.is_active:
auth.logout(request)
auth.login(request, user)
Logout documentation: https://docs.djangoproject.com/en/3.0/topics/auth/default/#django.contrib.auth.logout
I am relatively new to Django, and web development in general. I'm trying to build a website with two types of users, customers, and suppliers. I need to be able to show these two types of customers different things on the website. For example, different links in the header section for the suppliers.
I am under the impression the best way to do this is to create two user groups ('suppliers' and 'customers') in my /admin, create two different sign up forms (one for suppliers and one for customers), and send each to their respective user group on sign up form submission. From there I can decide what the user sees based on their user group. Correct?
Unfortunately, I'm nearly at my wits end with this! I've created the user groups, created the different sign-up forms, but for the last 2 days I have been trying to figure out how to send the signups to their respective group and I just can't manage to do it! I've searched high and low and tried every suggested line of code I could find: no luck.
Most of the stuff I have tried is along these lines:
views.py
from __future__ import unicode_literals
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render, get_object_or_404
from .forms import SupplierRegisterForm
def supplier_signup_view(request):
form = SupplierRegisterForm(request.POST or None)
if form.is_valid():
user = form.save(commit=False)
user.groups.add(Group.objects.get(name='suppliers'))
password = form.cleaned_data.get('password')
user.set_password(password)
user.save()
new_user = authenticate(username=user.username, password=password)
login(request, new_user)
if next:
return redirect(next)
return redirect("/")
context = {
"form": form,
"title": title,
}
return render (request, "supplier_signup.html", context)
forms.py
from django import forms
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
User = get_user_model()
class SupplierRegisterForm(forms.ModelForm):
username = forms.CharField()
email = forms.EmailField(label="Email Address")
email2 = forms.EmailField(label="Confirm Email", widget=forms.TextInput(attrs={'autocomplete':'false'}))
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = [
'username',
'email',
'email2',
'password',
]
def clean_email2(self):
email = self.cleaned_data.get('email')
email2 = self.cleaned_data.get('email2')
if email != email2:
raise forms.ValidationError("Emails must match")
email_qs = User.objects.filter(email=email)
if email_qs.exists():
raise forms.ValidationError("This email has already been registered")
return email
It would be much appreciated if anyone could give me a beginners walkthrough of how I would go about automatically sending users to a user group on form submission. Please include all of the code I would need to use, whether in settings, urls.py, models.py, forms.py, views.py, or templates (including imports etc), and any commands I would need to perform and when (eg migrate).
Many thanks!
EDIT: I've changed the answer to make use of Django's API.
Call user.save_m2m() after user.save():
if form.is_valid():
...
user.save()
user.save_m2m()
...
Explanation (from Django docs):
If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you've manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.
I'm writing a server app using Django, Django REST framework, Django-rest-auth and Django-allauth. I have a method that's used to pass messages between users, and this should only happen when the receiver is logged in.
However, it seems that the user object's is_authenticated() method returns True even though the user has logged out (called rest-auth/logout/, which should in turn call Django's logout). What could cause this? Is there something I've missed here?
Here's the code I have:
class SendMessage(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = MessageSerializer
def perform_create(self, serializer):
m = self.request.data['msg']
targetUser = User.objects.get(pk = self.request.data['user'])
if targetUser.is_authenticated():
# Send message
else:
# Don't send message
Unfortunately, the is_authenticated() method always returns true.
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in templates.
"""
return True
It is meant to discern between a User instance and an AnonymousUser instance, which is what the User is set as when they do not pass authentication.
Try fetching all authenticated users, then check if the target user is among them or not:
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
from django.utils import timezone
def get_all_logged_in_users_ids():
# Query all non-expired sessions
# use timezone.now() instead of datetime.now() in latest versions of Django
sessions = Session.objects.filter(expire_date__gte=timezone.now())
uid_list = []
# Build a list of user ids from that query
for session in sessions:
data = session.get_decoded()
uid_list.append(data.get('_auth_user_id', None))
return uid_list
class SendMessage(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = MessageSerializer
def perform_create(self, serializer):
m = self.request.data['msg']
if (self.request.data['user'] in get_all_logged_in_users_ids()):
# Send message
else:
# Don't send message
Reference.
You've misunderstood what is_authenticated is checking.
Remember that the web is a stateless environment. There is no server-level 'logged-in' status: the only record of whether a user is logged in or not is whether the cookie exists on that user's machine.
Since it is always possible for the user to close their browser, or turn of their machine, without notifying the server, there is no reliable way to know whether or not a user is really logged in. The only way you can approach that is to have some kind of repeated Ajax call every few seconds, posting to a view that updates the user's record with the current time. Then you can check that the user did indeed update within the last few seconds. This won't be totally reliable, though.
Alternatively you can look into using some kind of long-polling system to keep a channel open.
I've created a User model for my django app
class User(Model):
"""
The Authentication model. This contains the user type. Both Customer and
Business models refer back to this model.
"""
email = EmailField(unique=True)
name = CharField(max_length=50)
passwd = CharField(max_length=76)
user_type = CharField(max_length=10, choices=USER_TYPES)
created_on = DateTimeField(auto_now_add=True)
last_login = DateTimeField(auto_now=True)
def __unicode__(self):
return self.email
def save(self, *args, **kw):
# If this is a new account then encrypt the password.
# Lets not re-encrypt it everytime we save.
if not self.created_on:
self.passwd = sha256_crypt.encrypt(self.passwd)
super(User, self).save(*args, **kw)
I've also created an authentication middleware to use this model.
from accounts.models import User
from passlib.hash import sha256_crypt
class WaitformeAuthBackend(object):
"""
Authentication backend fo waitforme
"""
def authenticate(self, email=None, password=None):
print 'authenticating : ', email
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
user = None
if user and sha256_crypt.verify(password, user.passwd):
return user
else:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I have ammended the settings.py file correctly and if I add some print statements to this backend I can see the user details print out. I don't recall reading that I need to implement is_authenticated in the django docs. Am I missing something silly?
I'm not quite sure why you have created a new User model instead of using Django's built-in one and adding a linked UserProfile, which is the recommended thing to do (until 1.5 is released, when pluggable user models will be available). However, yes you need to define an is_authenticated method, which always returns True: this is exactly what the built-in model does. The reason is that if you have an actual User, it will always be authenticated: otherwise, you will have an AnonymousUser object, whose is_authenticated method always returns False.
you dont have to reinvent the wheel. Just use Djangos build in authentication system and save yourself a lot of trouble. You can also extend it to your needs or use different authentication backends. Have a read here. HTH.
I have a requirement where I have to register users first via email. So, I went with django-registraton and I managed to integrate tat module into my django project.
After a successful login, the page redirects to 'registration/profile.html'.
I need to get access to the user object which was used in the authentication.
I need this object to make changes to a model which holds custom profile information about my users. I have already defined this in my models.py
Here is the URL I am using to re-direct to my template..
url(r'^profile/$',direct_to_template,{'template':'registration/profile.html'}),
So my question is this... after login, the user has to be taken to a profile page that needs to be filled up.
Any thoughts on how I can achieve this?
I have set up something similar earlier. In my case I defined new users via the admin interface but the basic problem was the same. I needed to show certain page (ie. user settings) on first log in.
I ended up adding a flag (first_log_in, BooleanField) in the UserProfile model. I set up a check for it at the view function of my frontpage that handles the routing. Here's the crude idea.
views.py:
def get_user_profile(request):
# this creates user profile and attaches it to an user
# if one is not found already
try:
user_profile = request.user.get_profile()
except:
user_profile = UserProfile(user=request.user)
user_profile.save()
return user_profile
# route from your urls.py to this view function! rename if needed
def frontpage(request):
# just some auth stuff. it's probably nicer to handle this elsewhere
# (use decorator or some other solution :) )
if not request.user.is_authenticated():
return HttpResponseRedirect('/login/')
user_profile = get_user_profile(request)
if user_profile.first_log_in:
user_profile.first_log_in = False
user_profile.save()
return HttpResponseRedirect('/profile/')
return HttpResponseRedirect('/frontpage'')
models.py:
from django.db import models
class UserProfile(models.Model):
first_log_in = models.BooleanField(default=True, editable=False)
... # add the rest of your user settings here
It is important that you set AUTH_PROFILE_MODULE at your setting.py to point to the model. Ie.
AUTH_PROFILE_MODULE = 'your_app.UserProfile'
should work.
Take a look at this article for further reference about UserProfile. I hope that helps. :)