我想提供一个Django应用程序,该应用程序通过单个数据库但不同的用户集为多个网站提供服务。像博客应用程序一样思考,它将由具有不同主题的多个域使用,但通过向模型添加站点字段来使用相同的数据库。
我使用Django's 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'.
该问题的最佳做法是什么?
答案 0 :(得分:4)
我希望这种方法对你有所帮助:
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>
>>>
答案 1 :(得分:3)
如果您希望将电子邮件保留为USERNAME_FIELD(根据用户模型中的定义必须始终是唯一的),您将无法为每个网站重复该电子邮件。
我能想到的方法不止一种可能有用,但我想我会做以下几点:
首先,我不会扩展AbstractUser模型并制作一个 OneToOne对站点的依赖性。因为实际上允许用户属于 到多个网站。所以在这里,最好的选择imo是创建一个 具有ForeignKey to User和Site字段的成员模型和make 那些unique_together。因此,每个站点只有一个成员,而用户仍然是唯一的。这也是现实生活中更好的代表。
现在,在为网站注册新用户时,如果用户(电子邮件地址)已经存在,则先进行检查,如果存在,只需为该用户分配新成员。如果没有,也可以创建一个新用户。
首先编辑问题,“如果用户想要使用不同的用户名,密码或电子邮件注册到其他网站会怎么样?”
如果根据我的评论,可以共享网站的用户帐户(当然用户也知道这一点)在注册过程中,如果用户已经存在给定的电子邮件,那么他可以被告知,由于该地址的帐户已经存在于该站点-a,该用户帐户将被分配给站点-b的成员资格。然后,可以发送带有验证链接的电子邮件,并在确认后,将创建新成员并将其分配给有效用户。
另一种方法
如果我错误的假设,没关系,甚至希望在网站之间共享用户,那么我想这里需要一种全新的方法:
按照您的方式扩展AbstractUser,但不是将电子邮件用作USERNAME_FIELD,而是使用由<email>_<site_id>
组成的新字段(这将始终是唯一的,因为这两个字段是unique_together)...字段可以称为unique_site_id
左右。在提交登录和登录表单后,可以填写此字段。