7

我想为一个 Django 应用程序提供服务,该应用程序通过单个数据库但不同的用户集为多个网站提供服务。像博客应用程序一样思考,它将被具有不同主题的多个域使用,但通过向模型添加站点字段来使用相同的数据库。

我使用Django 的 SitesFramework来完成这项工作。但问题是,我无法为不同站点分离用户模型。我想将相同的用户模型与每个站点唯一的站点字段和电子邮件字段一起使用。

我试图AbstractUser像这样扩展模型:

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')

但是给出了那个错误:'Member.email' must be unique because it is named as the 'USERNAME_FIELD'.

该问题的最佳做法是什么?

4

2 回答 2

6

我希望这种方法对您有所帮助:

1)在保存前编写用户名:

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)然后在您的自定义身份验证后端覆盖ModelBackend

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)记住在设置中设置您的自定义后端:

AUTH_USER_MODEL='s1.Member'
SITE_ID = 1
AUTHENTICATION_BACKENDS = ( 'MyApp.MyModule.MyModelBackend',)

4) 认证时包括站点:

>>> 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>
>>> 
于 2014-11-22T20:18:29.283 回答
3

好吧,如果您想将电子邮件保留为 USERNAME_FIELD,根据用户模型中的定义,它必须始终是唯一的,您将无法为每个站点重复它。

我能想到的方法不止一种可能会奏效,但我想我会做以下事情:

  • 首先,我不会扩展 AbstractUser 模型并对站点建立 OneToOne 依赖项。因为实际上允许用户属于多个站点。所以在这里,最好的选择 imo 是创建一个具有用户外键和站点字段的成员模型,并使这些 un​​ique_together。因此,每个站点只有一个成员,并且用户保持唯一。这也是在现实生活中更好地代表案例的原因。

  • 现在,在为站点注册新用户时,只需先检查用户(电子邮件地址)是否已经存在,如果存在,只需为该用户分配一个新成员。如果没有,也创建一个新用户。

首先编辑问题,“如果用户想用不同的用户名、密码或电子邮件注册到另一个站点怎么办?”

如果根据我的评论,可以共享站点的用户帐户(当然用户知道这一点)在注册过程中,如果用户已经存在给定电子邮件,那么可以通知他即,由于站点-a 已经存在该地址的帐户,因此该用户帐户将被分配给站点-b 的成员资格。然后,可以发送一封带有验证链接的电子邮件,确认后,将创建新成员并将其分配给有效用户。

另一种方法

如果我的假设是错误的,可以甚至希望在站点之间共享用户,那么我想这里需要一种全新的方法:

正如您所做的那样扩展 AbstractUser,但不要使用电子邮件作为 USERNAME_FIELD,而是使用由以下组成的新字段<email>_<site_id>(它始终是唯一的,因为这两个字段是唯一的)......该字段可以被调用unique_site_id左右。提交登录和登录表单后,可以填充此字段。

于 2014-11-17T20:27:39.843 回答