Django 1.7 multisite User model
Asked Answered
M

2

7

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?

Marybellemarybeth answered 12/11, 2014 at 23:36 Comment(0)
D
6

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 = '[email protected]'
>>> m1.save()
>>> m1.set_password('hi')
>>> m1.save()
>>> 
>>> from django.contrib.auth import authenticate, login
>>> u=authenticate(username='[email protected]', password='hi', site=1)
>>> u
<Member: [email protected]_at_1>
>>> 
Deserve answered 22/11, 2014 at 20:18 Comment(1)
With a few fixes, that worked for me. Here is my latest code: gist.github.com/muratcorlu/1ad61a43d4836d5b1227 Thank you!Thirza
A
3

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.

Alla answered 17/11, 2014 at 20:27 Comment(4)
What if a visitor signs up with same email and username but a different password? Or same email address but different username? Or same username but different email address?Thirza
Well, if the email is correct, it is the same user, so the user wouldn't have to create a new account. ... If the User's personal info is not the same, the user could be asked if he want's to update his data? ... or should the user "think" he's creating a new/separate account for each site? ... I was thinking it could be more like the stackoverflow/-exchange sites... one login/profile for multiple related sites.Alla
... Maybe I misunderstood the point of sharing a database between the sites. I thought the sites are somehow related and will share some data between them, including the users.Alla
My project is not like stackoverflow. It's a news site platform that uses many different and not related news site. All sites use same database but from the visitor side, user don't know anything about relation of these websites. Your approach that you titled "Another approach" seems a good workaround. I'll try it.Thirza

© 2022 - 2024 — McMap. All rights reserved.