Search code examples
pythondjangodjango-modelsdjango-authenticationdjango-sites

Django 1.7 multisite User model


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?


Solution

  • 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>
    >>>