I want to serve a Django application that serves multiple web sites by single database but different user sets. Think like a blog application, it will be used by several domains with different themes, but use same database by adding a site field to models.
I use Django's SitesFramework for that job. But the problem is, I couldn't separate user models for different sites. I want to use same user model with a site field and email field that unique per site.
I tried to extend AbstractUser model like that:
from django.contrib.auth.models import AbstractUser
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Member(AbstractUser):
username = None
site = models.ForeignKey(Site)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
on_site = CurrentSiteManager()
class Meta:
unique_together = ('site', 'email')
But gives that error: 'Member.email' must be unique because it is named as the 'USERNAME_FIELD'.
What is the best practice for that issue?
I hope this approach helps to you:
1) Compose username before save:
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Member(AbstractUser):
site = models.ForeignKey(Site)
on_site = CurrentSiteManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
class Meta:
unique_together = ('site', 'email')
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=Member)
def compose_username(sender, instance, **kwargs):
instance.username = "{0}__{1}".format( instance.email, instance.site_id )
2) Then overwrite ModelBackend in your custom auth backend:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
class MyModelBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
site = kwargs.get('site')
identifier = "{0}__{1}".format( username, site )
try:
user = UserModel.objects.get(username=identifier)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
3) Remember set your custom backend on settings:
AUTH_USER_MODEL='s1.Member'
SITE_ID = 1
AUTHENTICATION_BACKENDS = ( 'MyApp.MyModule.MyModelBackend',)
4) Include site when authenticate:
>>> from s1.models import Member as M
>>> m1 = M()
>>> m1.site_id = 1
>>> m1.email = 'peter#hello.com'
>>> m1.save()
>>> m1.set_password('hi')
>>> m1.save()
>>>
>>> from django.contrib.auth import authenticate, login
>>> u=authenticate(username='peter#hello.com', password='hi', site=1)
>>> u
<Member: peter#hello.com_at_1>
>>>
Well if you want to keep the email as the USERNAME_FIELD, which by definition in the User-model must be always unique, you won't be able to repeat it for each site.
There are more than one approaches I can think of that would probably work, but I guess I would do the following:
First of all, I wouldn't extend the AbstractUser-model and make a
OneToOne dependency to the Site. Because a User is actually allowed to belong
to more than one site. So here, the best option imo is to create a
Member-model with a ForeignKey to User and a Site field and make
those unique_together. So there is only one Member per Site, and a User remains unique. Which is what represents the case better in real life too.
Now, when registering a new user for a site, just make a check first if the User (email-Address) already exists, and if so, just assign a new Member to that User. If not, create a new User as well.
First Edit to the question, "what if a User want's to register to another site with different username, password or email?"
If according to my comments, it's OK to share a user account for the sites (and of course the user is aware of this) In the register-process, in the case the user already exists for a given email, then he could be informed that, as an account for that address already exists for the site-a, this user account would be assigned to the membership to site-b. Then, an e-mail with a verify link could be sent, and when confirmed, the new member would be created and assigned to the valid user.
Another approach
If I was wrong assuming, it's ok and even desired to share users among sites, then I guess a whole new approach is needed here:
Extend the AbstractUser as you were doing, but instead of using the email as USERNAME_FIELD, use a new field composed from <email>_<site_id> (which would always be unique, as these 2 fields are unique_together)... the field could be called unique_site_id or so. And this field could be populated after submitting the sign-in and login forms.
Related
I know that superusers and regular users are both just django's User objects, but how can I write a custom user class that requires some fields for plain users and doesn't require those fields for superusers?
No structure in the database is tricky. JSONFields for example may prove to be extremely hard to tame when the app grows.
I would go and try to make it "simple" - more maintainable (I imagine if you need to do stuff like that you may want to extend the model in the future). If this is a new project you can easily change the default user model. But that may or may not help you with your case.
You can always make two models:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser
class Mortal(AbstractBaseUser):
is_superuser = False
username = models.CharField(max_length=256)
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
class Admin(AbstractBaseUser):
is_superuser = True
username = models.CharField(max_length=256)
and then make your own authentication backend:
class MyBackend(object):
"""
Danger! A backend to authenticate only via username
"""
def authenticate(self, username=None):
try:
return Mortal.objects.get(username=username)
except Mortal.DoesNotExist:
try:
return Admin.objects.get(username=username)
except Admin.DoesNotExist:
return None
You can have a profile class (say UserProfile) with foreign key to the user that is to be created only when user signs up using the website's registration form. That way, superuser which is created on admin site or through command line wouldn't need an extra profile instance attached to it.
Let's say I already have existing User instances in my database. Then, I just introduced a new model in my app:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
nickname = models.CharField(max_length=30, blank=True)
I want to create a UserProfile instance for every user. I know that signals can handle this upon something like User().save(). However, what do I do with the users already in my database?
Currently I handle it in views.py:
try:
user.profile.nickname = 'my_nickname'
except:
profile = UserProfile()
profile.user = user
profile.nickname = 'my_nickname'
profile.save()
But this makes the view quite long. Is there a better way to do it?
For users already in your database, you can run a script on your django shell.
python manage.py shell
Then:
>>from .models import *
>>from django.contrib.auth.models import User
>>users_without_profile = User.objects.filter(profile__isnull=True)
>>for user in users_without_profile:
....user.profile.nickname = 'your_choice_of_nickname'
....user.save()
Just a side note: doing a wild import like from .models import * is a bad practice, but I did it anyway just for illustration and also I didn't know you appname. Hence, import the appropriate models from the respective app.
Hope this helps you.
I'm trying to implement the Users class from django.contrib.auth.models like this:
from django.db import models
from django.contrib.auth.models import User
class Registration(models.Model):
'''Represents a user registration.'''
user = models.ForeignKey(User)
registration_date = models.DateTimeField(auto_now_add=True, help_text='The date of the registration')
def __str__(self):
return '%s - %s' % (self.user, self.registration_date,)
This user have two attributes enabled by default: username, password
Reading at the code I can see that there are more attributes, like name and email.
How can I enable those hidden (if this is correct) attributes?
First, these attributes are not hidden. Assuming you have "django.contrib.auth" and "django.contrib.contenttypes" in your INSTALLED_APPS, then you have access to the User model as it is defined in your link. See here for the documentation on how to use/access it.
However, since you specified extending the User model, I expect you wanted to add some of your own fields to it (even though your example registration_date exists and is accessible via myuser.date_joined).
The older, more stable and more common way of doing this is similar to what you have. The only difference is to use a OneToOneField(User) instead of a ForeignKey(User). This makes the relationship bidirectional and more convenient by forcing one. You do need to make sure and create a Registration object for every User created.
In fact, there is an example of exactly what you want in the docs for the OneToOneField.
from django.db import models
from django.contrib.auth.models import User
class Registration(models.Model):
user = models.OneToOneField(User)
registration_date = models.DateTimeField(auto_now_add=True)
>>> user = User.objects.get(pk=1)
>>> registration = Registration.objects.create(user=user)
>>> user.registration.registration_date
# Should give the current time
>>> user.get_full_name()
# Should also have all those *hidden* attributes linked above
As of Django 1.5, you can use your own custom User model fairly easily. The documentation for this feature is here. If you are just adding some extra fields, then you can subclass the User model and add your own fields.
from django.db import models
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
# We inherit all those nifty things from AbstractUser
registration_date = models.DateTimeField(auto_now_add=True)
Then enable it in your settings.py by adding AUTH_USER_MODEL = 'myapp.MyUser'. We now have to access the user model a little differently
>>> from django.contrib.auth import get_user_model()
>>> Users = get_user_model()
>>> user = Users.objects.get(pk=1)
>>> user.registration_date
# Should give the current time
>>> user.get_full_name()
# Should have those 'hidden' attributes
All this is available under extending and substituting the User model in the documentation.
I have two models
from django.contrib.auth.models import User
class Vendor(models.MOdel):
user = models.ForeignKey(User,related_name = 'vendor')
......................................................
......................................................
class Customer(models.MOdel):
user = models.ForeignKey(User,related_name = 'customer')
........................................................
.......................................................
what i want to do is enable login for a Vendor and a customer.The login url of vendor is 'vendor/login' and customer is 'customer/login'.While a vendor submit his credentials,i want to check whether the user is a vendor or not and raise a validation error.I couldn't find a way to accomplish this using django.What i basically need is something like a modelform for User which checks the user is a vendor using a queryset.But django auth doesn't have something like this.
The best strategy for me is to write two Authentication Backends, one that enables the vendor authentication and another that does it for the customer.
AUTHENTICATION_BACKENDS is the settings constant giving the authorization backend tuple.
here can you check the documentation about authentication backends.
Declare your implentations with the settings parameter. The order counts django will try all backends untill one is successful.
You could use Django custom user model, add the type field and use it as a factory source to load Vendor/Customer objects. Such solution worked for me very well. Something to start from:
models.py
from django.contrib.auth.models import AbstractUser
TYPES = (
('Vendor', 'Vendor'),
('Customer', 'Customer'),
)
class MyUser(AbstractUser):
type = models.CharField(max_length=10, choices=TYPES, default='Customer')
This approach is very flexible and scales well if you need to add other types in the future. Small example of redirection approach:
class Base():
def __init__(self, auth_user):
self.user = auth_user
def redirect_to(self):
return ""
class Vendor(Base):
def redirect_to(self):
return "/login/vendor"
class Customer(Base):
def redirect_to(self):
return "/login/customer"
Then in the login view you would just dynamically create the user object:
auth_user = form.get_user()
cls = globals()[auth_user.type]
user = cls(auth_user) # This will return either Vendor or Customer object
return HttpResponseRedirect(user.redirect_to())
You can easily create another user types by just adding new class and implementing the needed methods without even touching the rest of the code.
I have an issue with the django.contrib.auth User model where the email max_length is 75.
I am receiving email addresses that are longer than 75 characters from the facebook api, and I need to (would really like to) store them in the user for continuity among users that are from facebook connect and others.
I am able to solve the problem of "Data truncated for column 'email' at row 1" by manually going editing the field in our mySql database, but is there a better way to solve this? preferably one that does not involve me manually editing the database every time I reset it for a schema change?
I am ok with editing the database as long as I can add it to the reset script, or the initial_data.json file.
EmailField 75 chars length is hardcoded in django. You can fix this like that:
from django.db.models.fields import EmailField
def email_field_init(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200)
CharField.__init__(self, *args, **kwargs)
EmailField.__init__ = email_field_init
but this will change ALL EmailField fields lengths, so you could also try:
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from django.db import models
User.email = models.EmailField(_('e-mail address'), blank=True, max_length=200)
both ways it'd be best to put this code in init of any module BEFORE django.contrib.auth in your INSTALLED_APPS
Since Django 1.5 you can use your own custom model based on AbstractUser model, therefore you can use your own fields & lengths.
In your models:
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField(_('e-mail address'), blank=True, max_length=200)
In settings:
AUTH_USER_MODEL = 'your_app.models.User'
There is now a ticket to increase the length of the email field in Django: http://code.djangoproject.com/ticket/11579