I need to set language for user in 2 cases:
User logs in (django checks his UserProfile field for language field value and sets the proper lang)
Users chenges lang in "user preferences" page.
I've tried with "user_logged_in" signal receiver
from django.utils import translation
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
#receiver(user_logged_in)
def setlang(sender, **kwargs):
translation.activate(kwargs['user'].get_profile().language)
kwargs['request'].session['django_language'] = translation.get_language()
This works fine until I restart my django instance. Though session is alive (no need to log in again) website is being displayed in language specified in settings.LANGUAGE
I think the same applies to situation #2 (on user preferences form save())
Following up to Timmy's answer you can find an already published middleware that does the job for you!
The translation of a Django app with the preferred language selected by a registered user can be done with the middleware django-user-language-middleware. This allows easy translation of your Django app by looking at the selected language in the user.language field.
Usage:
Add a language field to your user model:
class User(auth_base.AbstractBaseUser, auth.PermissionsMixin):
# ...
language = models.CharField(max_length=10,
choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE)
Install the middleware from pip:
pip install django-user-language-middleware
Add it to your middleware class list in settings to listen to requests:
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'user_language_middleware.UserLanguageMiddleware',
...
]
I hope this may help people landing on this question in the future.
I think it's better if you use middleware to achieve this. Django provides a way to add in hooks at various places throughout a request so that you can add custom code.
In your case: check on every request if there is a language variable in the session, if not, fetch the users language preference from the database and save it to the session variable. Check on a save signal whether or not the user has changed their preference; if so, update the session variable (you might have problems getting access to the request from the model level so it might be better to do this in the view).
I've used this snippet, but it conflicts with the Regular LocaleMiddleware, and thus I can't add a multilingual content farm.
http://django-hotclub.googlecode.com/svn-history/r708/trunk/pinax/profiles/middleware.py
Related
I am trying to work with Sites Model of Django.
I dont quite understand why SITE_ID should be SITE_ID = 1.
in the docs:
The ID, as an integer, of the current site in the django_site database
table. This is used so that application data can hook into specific
sites and a single database can manage content for multiple sites.
why 1? what is the current site? this is not clearly explained in the docs.
lets say, I have www.coolsite.com and some other subdomains like www.wow.coolsite.com and www.awesome.coolsite.com
I want to render different content depending on domain name.
my question is, or better, are:
Do I have to add all those domains into Sites Table in DB?
if so, how should I set SITE_ID in settings? Do I have to set all ids like SITE_ID = 1, SITE_ID = 2.. etc?
what does current site has to do with SITE_ID = 1?
I am a bit confused here.
I thought, each Site (e.g. www.wow.coolsite.com) should be a separate django project so that they can have their own settings.py? and in each of those settings.py's, I will set the id of that page from Sites table? but then there are many django projects which also doesnot make sense to me.
Django was created from a set of scripts developed at a newspaper to publish content on multiple domains; using one single content base.
This is where the "sites" module comes in. Its purpose is to mark content to be displayed for different domains.
In previous versions of django, the startproject script automatically added the django.contrib.sites application to INSTALLED_APPS, and when you did syncdb, a default site with the URL example.com was added to your database, and since this was the first site, its ID was 1 and that's where the setting comes from.
Keep in mind that starting from 1.6, this framework is not enabled by default. So if you need it, you must enable it
The SITE_ID setting sets the default site for your project. So, if you don't specify a site, this is the one it will use.
So to configure your application for different domains:
Enable the sites framework
Change the default site from example.com to whatever your default domain is. You can do this from the django shell, or from the admin.
Add your other sites for which you want to publish content to the sites application. Again, you can do this from the django shell just like any other application or from the admin.
Add a foreign key to the Site model in your object site = models.ForeignKey(Site)
Add the site manager on_site = CurrentSiteManager()
Now, when you want to filter content for the default site, or a particular site:
foo = MyObj.on_site.all() # Filters site to whatever is `SITE_ID`
foo = MyObj.objects.all() # Get all objects, irrespective of what site
# they belong to
The documentation has a full set of examples.
Things would be much easier to understand if Django's default SiteAdmin included the id field in the list_display fields.
To do this, you can redefine SiteAdmin (anywhere in your app, but I'd recommend your admin.py or maybe your urls.py) like this:
from django.contrib import admin
from django.contrib.sites.models import Site
admin.site.unregister(Site)
class SiteAdmin(admin.ModelAdmin):
fields = ('id', 'name', 'domain')
readonly_fields = ('id',)
list_display = ('id', 'name', 'domain')
list_display_links = ('name',)
search_fields = ('name', 'domain')
admin.site.register(Site, SiteAdmin)
After including this code snippet, the ID for each "Site" will be shown in the first column of the admin list and inside the form as a read only field. These 'id' fields are what you need to use as SITE_ID:
The concept is that each different site runs in a different application server instance, launched using a different yourdomain_settings.py that then includes a base_settings.py with the rest of the common configuration.
Each of these yourdomain_settings.py will define its own SITE_ID and all other different settings.py parameters that they need to look and be different from each other (static resources, templates, etc.) then you'll define a DJANGO_SETTINGS_MODULE environment variable pointing to that specific yourdomain_settings.py file when launching the application server instance for that domain.
A further note: get_current_site(request) does need request to be available for it to work. If your code doesn't have one, you can use Site.objects.get_current() that however will need a SITE_ID properly defined in the running application server's settings.
This is a late answer but for anyone else having SITE_ID issues and Site problems.
Inside the database, django has a django_site table with(id, domain, name). This is where django stores the SITE_IDs. Mine was actually 5 in the database but i had it set to SITE_ID=1 in the settings.
Knowing that, i can now go back to the database and clear it to get back to zero or use the actual id in the database.
This is covered in the documentation for the Sites framework:
In order to serve different sites in production, you’d create a
separate settings file with each SITE_ID (perhaps importing from a
common settings file to avoid duplicating shared settings) and then
specify the appropriate DJANGO_SETTINGS_MODULE for each site.
But if you didn't want to do it that way, you can not set SITE_ID at all and just look up the current site based on the domain name in your views using get_current_site:
from django.contrib.sites.shortcuts import get_current_site
def my_view(request):
current_site = get_current_site(request)
if current_site.domain == 'foo.com':
# Do something
pass
else:
# Do something else.
pass
This link explains it:
You’ll want to create separate settings files for each domain you’re adding; each one will need its own MEDIA_URL and other settings. You’ll also want to do two things to make sure everything works out properly for administering the different sites:
Create a new Site object in your admin for each domain, and put the id of that Site into its settings file as SITE_ID so Django knows which site in the database corresponds to this settings file.
In the settings file for your original site (the one with id 1), add the other sites’ settings files to the ADMIN_FOR setting, to let Django know that this one instance of the admin application will handle all of the sites.
Also, if you wanna figure out how to modify models and set the views You may take a look at this link:
https://django.cowhite.com/blog/managing-multiple-websites-with-a-common-database-in-django-the-sites-framework/
I want to add (already existing, defined via permissions in the meta tag) permissions to groups and looking for the correct place to do so. I have to assure that after the sync_db command all groups have the correct permissions assigned to.
I was thinking to hook up a post_syncdb signal, but the signal docs say:
It is important that handlers of this signal perform idempotent
changes (e.g. no database alterations) as this may cause the flush
management command to fail if it also ran during the syncdb command.
Thus it is probably not advisable that someone adds permissions to a group in a management command called via the sync_db_post signal.
I planned to setup the permissions like this:
class UserProfile(models.Model):
user = models.OneToOneField(User)
company = models.ForeignKey(Company, related_name='user_profiles')
department = models.ForeignKey(Department, related_name='user_profiles')
class Meta:
permissions = (
('admin_section_access', 'Access to front-end admin section'),
)
in the management command:
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
class Command(BaseCommand):
args = 'none'
help = 'Sets the defined group permissions'
def handle(self, *args, **options):
front_end_admin_group, created = Group.objects.get_or_create(name="Front End Admin")
#get the content type for the UserProfile model
user_profile_ct = ContentType.objects.get(app_label='organization', model='userprofile')
admin_section_access_perm = Permission.objects.get(content_type=user_profile_ct, codename="admin_section_access")
front_end_admin_group.permissions.add(admin_section_access_perm)
self.stdout.write('Successfully setup group permissions.')
You have two main options here. The first is to create a fixture for your database, as described here in the Django docs. That's the oldest way to seed data (for you, permissions xref records) into your database.
The second option, and my preferred over the first, is to use South to programmatically track and maintain changes to your database. While most commonly used for schema changes, its data migrations feature is the perfect place to handle what you want. You'd run this in your command line: ./manage.py datamigration my_app seed_permissions and then simply write the Django code required to get your permissions into place within the provided forwards function.
The main gotcha to the second option is that it requires you to run the additional command of migrate after sync_db, but this is probably unavoidable anyway as South is the truth (and slated to become core Django in 1.7).
I need to increase the max username size in auth to exceed the 30 chars defined in the model.
How can it be done? I' m not sure that just customizing the model is the right or safe way.
This answer here is an interesting approach:
Can django's auth_user.username be varchar(75)? How could that be done?
A small app that overrides the max_length attribute on the User model, but note your DB column needs to be modified if the tables are not being syncdbed
I personally use a trimmed hash as my username which would notify me if there was ever a super unlikely collision, with the subject "You just won the lottery!"
I encountered this need in our existing infrastructure.
Our whole backend was relying on the Django default user model, but we had the need to change this max_len to make it match the email max_len.
From most StackOverflow posts I have seen, people mostly recommend creating a custom User model.
This was definitely something we needed to avoid in our case. Changing from the default User model to a custom one is a serious and complex operation when performed on hundreds of thousands of Users in production.
So, instead, we just wanted to directly apply the change to the database schema itself.
To do it properly, the best way is to perform the change from a migration.
However, I could not see a way to directly generate a migration for the User model.
One way I could see was to generate an empty migration, then to use raw SQL to perform the migration.
Generate empty migration:
python manage.py makemigrations YOUR_APP --empty
Edit migration:
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2019-02-11 09:39
from __future__ import unicode_literals
from django.db import migrations, models
from django.db.models import F
from django.db.models.functions import Length
from pittsburgh.models import User
#
# This function fixes thee existing user records by applying setting their username to be their email
#
def forwards_func_username(apps, schema_editor):
User.objects.annotate(email_len=Length('email')).filter(email_len__gte=30).update(username=F('email'))
#
# This function reverts back to the original state
# Users with email > 30 chars have their username being a truncated email
#
def reverse_func_username(apps, schema_editor):
users = User.objects.annotate(email_len=Length('email')).filter(email_len__gte=30)
for user in users:
user.username = user.email[:30]
user.save()
class Migration(migrations.Migration):
dependencies = [
('pittsburgh', '0076_auto_20190205_1623'),
]
operations = [
# change username max_length from 30 to 75 to match email max max_length
migrations.RunSQL(sql="ALTER TABLE auth_user MODIFY COLUMN username VARCHAR(75) NOT NULL;",
reverse_sql="ALTER TABLE auth_user MODIFY COLUMN username VARCHAR(30) NOT NULL;"),
# update username to match email
migrations.RunPython(forwards_func_username, reverse_func_username),
]
The forwards_func_username and reverse_func_username of the RunPython are optional, depends on what you are willing to do.
Note that the RunSQL requires the sqlparse dependency, so don't forget to add that to your requirements.txt file.
sqlparse==0.2.4 # used implicitly by Django Migrations engine when using RunSQL operation
Hopes that help, I spent a couple hour browsing the web for some good solution, but this part is seriously poorly designed by Django (really misses the clean and easy design available on Ruby on Rails for example).
for future needs this is the best way i found out:
https://github.com/GoodCloud/django-longer-username
AFAIK, you need to subclass auth.user if you want exactly that. A simpler and less adventurous solution might be implementing a user profile model with a longer username field. To avoid redundancy you may for example populate the actual username field with randomly-generated numbers and quit using it.
How would you go about making a pluggable django app, that can accept data from any model and then perfom some action with that data (i.e Save to DB, Send email). This functionality should be generic and should not be tied to a specific model.
It depends on what functionality your app would provide and in what way you'd expect users of your app to use it's api. For interacting with other models you don't know about there are a few ways, depending on what your reusable app does. You can make forms, views etc that would accept a model class or instance as a property or parameter. The other way would be for the users of your app to specify their relevant models in settings.py much like auth deals with user profiles. For example if your app needs to know about a model class that provides info about gadgets the user would specify:
#user's settings.py
GADGETS_MODEL='myapp.CustomSuperFunGadgets'
To get the class for the user specified model you would do:
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
if not getattr(settings, 'GADGETS_MODEL', False):
raise ImproperlyConfigured('You need to set GADGETS_MODEL'
'in your project settings')
try:
app_label, model_name = settings.GADGETS_MODEL.split('.')
except ValueError:
raise ImproperlyConfigured('app_label and model_name should'
' be separated by a dot in the GADGETS_MODEL set'
'ting')
try:
model = models.get_model(app_label, model_name)
if model is None:
raise ImproperlyConfigured('Unable to load the gadgets '
'model, check GADGETS_MODEL in your project sett'
'ings')
except (ImportError):
raise ImproperlyConfigured('Unable to load the gadgets model')
#at this poing model will hold a reference to the specified model class
Another way of interacting with models you don't know about is your app to provide custom model fields or managers or special properties that would just attach signal handlers to the model they are attached to.
As I said it all depends on what problem your reusable app is trying to solve, and the approach you should take is always based on that.
In my Django app under certain conditions I want to be able to force users to log out by a username. Not necessarily the current user who is logged in, but another user. So, the request method in my view doesn't have any session information about the user that I want to logout.
I am familiar with django.auth and with auth. logout method, but it takes request as an argument. Is there a "Django-way" to log the user out if all I have is the username? Or do I have to roll my own logout SQL?
Update:
Since Django 1.7, users are automatically logged-out when their password changes. On each request, the current password hash is compared to the value saved in their session and if doesn't match, the user is logged-out.
So, a simple password update has the effect of logging the user out. You can then disable the account for login, or advise them to use the password reset feature to set a new password and log in again.
Original:
I don't think there is a sanctioned way to do this in Django yet.
The user id is stored in the session object, but it is encoded. Unfortunately, that means you'll have to iterate through all sessions, decode and compare...
Two steps:
First delete the session objects for your target user. If they log in from multiple computers they will have multiple session objects.
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
# grab the user in question
user = User.objects.get(username='johndoe')
[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_id') == user.id]
Then, if you need to, lock them out....
user.is_active = False
user.save()
Although Harold's answer works in this specific case, I can see at least two important issues with it:
This solution can only be used with a database session engine. In other situations (cache, file, cookie) the Session model would not be used.
When the number of sessions and users in database grows, this becomes quite inefficient.
To solve those issues, I suggest you take another approach at the problem. The idea is to store somewhere the date when the user was logged in for a given session, and the last time you requested a user to be logged out.
Then whenever someone access your site, if the logged in date is lower than the log out date, you can force-logout the user. As dan said, there's no practical difference between logging out a user immediately or on his next request to your site.
Now, let's see a possible implementation of this solution, for django 1.3b1. In three steps:
1. store in the session the last login date
Fortunately, Django auth system exposes a signal called user_logged_in. You just have to register that signals, and save the current date in the session. At the bottom of your models.py :
from django.contrib.auth.signals import user_logged_in
from datetime import datetime
def update_session_last_login(sender, user=user, request=request, **kwargs):
if request:
request.session['LAST_LOGIN_DATE'] = datetime.now()
user_logged_in.connect(update_session_last_login)
2. request a force logout for a user
We just need to add a field and a method to the User model. There's multiple ways to achieve that (user profiles, model inheritance, etc.) each with pros and cons.
For the sake of simplicity, I'm gonna use model inheritance here, if you go for this solution, don't forget to write a custom authentication backend.
from django.contrib.auth.models import User
from django.db import models
from datetime import datetime
class MyUser(User):
force_logout_date = models.DateTimeField(null=True, blank=True)
def force_logout(self):
self.force_logout_date = datetime.now()
self.save()
Then, if you want to force logout for user johndoe, you just have to:
from myapp.models import MyUser
MyUser.objects.get(username='johndoe').force_logout()
3. implement the check on access
Best way here is to use a middleware as dan suggested. This middleware will access request.user, so you need to put it after 'django.contrib.auth.middleware.AuthenticationMiddleware' in your MIDDLEWARE_CLASSES setting.
from django.contrib.auth import logout
class ForceLogoutMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated() and request.user.force_logout_date and \
request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date:
logout(request)
That should do it.
Notes
Be aware of the performance implication of storing an extra field for your users. Using model inheritance will add an extra JOIN. Using user profiles will add an extra query. Modifying directly the User is the best way performance wise, but it is still a hairy topic.
If you deploy that solution on an existing site, you will probably have some trouble with existing sessions, which won't have the 'LAST_LOGIN_DATE' key. You can adapt a bit the middleware code to deal with that case :
from django.contrib.auth import logout
class ForceLogoutMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated() and request.user.force_logout_date and \
( 'LAST_LOGIN_DATE' not in request.session or \
request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date ):
logout(request)
In django 1.2.x, there is no user_logged_in signal. Fall back to overriding the login function:
from django.contrib.auth import login as dj_login
from datetime import datetime
def login(request, user):
dj_login(request, user)
request.session['LAST_LOGIN_DATE'] = datetime.now()
I needed something similar in my app. In my case, if a user was set to inactive, I wanted to make sure if the user was already logged in that they will be logged out and not able to continue to use the site. After reading this post, I came to the following solution:
from django.contrib.auth import logout
class ActiveUserMiddleware(object):
def process_request(self, request):
if not request.user.is_authenticated:
return
if not request.user.is_active:
logout(request)
Just add this middleware in your settings and off you go. In the case of changing passwords, you could introduce a new field in the userprofile model that forces a user to logout, check for the value of the field instead of is_active above, and also unset the field when a user logs in. The latter can be done with Django's user_logged_in signal.
Perhaps, a bit of middleware that references a list of users who have been forced to log out. Next time the user tries to do anything, log them out then, redirects them, etc.
Unless of course, they need to be logged out immediately. But then again, they wouldn't notice until they next tried to make a request anyway, so the above solution may just work.
This is in response to Balon's query:
Yes, with around 140k sessions to iterate through I can see why Harold's answer may not be as fast as you may like!
The way I would recommend is to add a model whose only two properties are foreign keys to User and Session objects. Then add some middleware that keeps this model up-to-date with current user sessions. I have used this sort of setup before; in my case, I borrowed the sessionprofile module from this Single Sign-On system for phpBB (see the source code in the "django/sessionprofile" folder) and this (I think) would suit your needs.
What you would end up with is some management function somewhere in your code like this (assuming the same code names and layout as in the sessionprofile module linked above):
from sessionprofile.models import SessionProfile
from django.contrib.auth.models import User
# Find all SessionProfile objects corresponding to a given username
sessionProfiles = SessionProfile.objects.filter(user__username__exact='johndoe')
# Delete all corresponding sessions
[sp.session.delete() for sp in sessionProfiles]
(I think this will also delete the SessionProfile objects, as from what I recall, Django's default behaviour when an object referenced by a ForeignKey is deleted is to cascade it and also delete the object containing the ForeignKey, but if not then it is trivial enough to delete the contents of sessionProfiles when you are done.)
You can also use direct django function to do that, it will update and logs out all other sessions for the user, except the current one.
from django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)
Docs for update_session_auth_hash here.
As Tony Abou-Assaleh, I also needed to log out users who were set to inactive, so I started by implementing his solution. After some time I found out that the middleware is forcing a DB query on all requests (to check if the user was blocked), and thus hurts performance on pages that doesn't require login.
I have a custom user object and Django >= 1.7, so what I ended up doing is overriding its get_session_auth_hash function to invalidate the session when the user is inactive. A possible implementation is:
def get_session_auth_hash(self):
if not self.is_active:
return "inactive"
return super(MyCustomUser, self).get_session_auth_hash()
For this to work, django.contrib.auth.middleware.SessionAuthenticationMiddleware should be in settings.MIDDLEWARE_CLASSES
As others stated, you can iterate over all sessions in DB, decode all of them, and delete those belonging to that user. But it's slow, particularly if your site has high traffic and there are lots of sessions.
If you need a faster solution, you can use a session backend that lets you query and get the sessions of a specific user. In these session backends, Session has a foreign key to User, so you don't need to iterate over all session objects:
django-qsessions (based on django's db, cached_db session backends)
django-user-sessions (based on django's db session backend)
Using these backends, deleting all sessions of a user can be done in a single line of code:
user.session_set.all().delete()
Disclaimer: I am the author of django-qsessions.
from django.contrib.sessions.models import Session
deleting user session
[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_hash') == user.get_session_auth_hash()]
Even I faced this issue. Few spammers from India keep posting about those Baba and Molvi for love solutions.
What I did is at the time of posting just inserted this code:
if request.user.is_active==False:
return HttpResponse('You are banned on the site for spaming.')