Django扩展用户模型
一般来说,扩展现有用户模型有四种不同的方法。
1:使用代理模型
什么是代理模型?
它是一种模型的继承,无需在数据库中创建新表。它用于在不影响现有数据库模式的情况下更改现有模型的行为(例如,默认排序、添加新方法等)。
什么时候应该使用代理模型?
当您不需要在数据库中存储额外的信息时,您应该使用代理模型来扩展现有的用户模型,但是我们需要添加额外的方法或更改模型的查询管理器。
2.使用One-To-One与用户模型关联起来。
什么是一对一链接?
它是一个常规的Django模型,它将拥有自己的数据库表,并通过OneToOneField与现有的用户模型保持一对一的关系。
我什么时候应该使用一对一的链接?
当需要存储与身份验证过程无关的现有用户模型的额外信息时,应该使用一对一链接。我们通常称之为用户配置文件。
3.扩展AbstractBaseUser,创建自定义用户模型
扩展AbstractBaseUser的自定义用户模型是什么?
它是从AbstractBaseUser继承的一个全新的用户模型,并通过settings.py引用。需要注意的是,这样的操作,我们应该在项目的开始阶段设计好完,因为它操作将极大地影响数据库模式。执行时要格外小心。
什么时候应该使用AbstractBaseUser的自定义用户模型?
当您的应用程序对身份验证过程有特定的需求时,您应该使用自定义用户模型。例如,在某些情况下,使用电子邮件地址登录,或者是使用手机验证登录的时候。
4.扩展AbstractUser的自定义用户模型
什么是AbstractUser自定义用户模型?
它是一个从AbstractUser继承的新用户模型。并通过settings.py引用。需要注意的是,这样的操作,我们应该在项目的开始阶段设计好完,因为它操作将极大地影响数据库模式。执行时要格外小心。
什么时候应该使用扩展AbstractUser的自定义用户模型?
当您对Django如何处理身份验证过程非常满意时,您应该使用它,并且您不会对它做任何更改。但你希望直接在用户模型中添加一些额外的信息,而不需要创建一个额外的类时(如选项2)。
使用代理模型扩展用户模型
这是扩展现有用户模型的影响较小的方法。 这种策略不会有任何缺点。 但它在很多方面的限制。
代码:
from django.contrib.auth.models import User
from .managers import PersonManager
class Person(User):
objects = PersonManager()
class Meta:
proxy = True
ordering = ('first_name', )
def do_something(self):
...
在上面的示例中,我们定义了一个名为Person的代理模型。我们通过在Meta类中添加以下属性Proxy = True,告诉Django这是一个代理模型。
在本例中,我重新定义了默认排序,为模型分配了一个自定义管理器,并定义了一个新的方法do_something。
值得注意的是,User.objects.all()和Person.objects.all()将查询同一个数据库表。唯一的区别是我们为代理模型定义的行为。
如果这就是你所需要的,那就使用这种方法,简单。
使用One-To-One链接扩展用户模型
很有可能这就是你想要的。就我个人而言,这是我大部分时候使用的方法。我们将创建一个新的Django模型来存储与用户模型相关的额外信息。
请记住,使用此策略会导致额外的查询或连接来检索相关数据。基本上所有访问相关数据的时候,Django都会触发一个附加的查询。但这在大多数情况下是可以避免的。稍后我会再讲这个。
我通常将Django模型命名为Profile:
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
这就是神奇的地方:我们现在将定义信号,这样当我们创建/更新用户实例时,我们的Profile模型将自动创建/更新
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
当发生保存事件时,我们将create_user_profile和save_user_profile方法连接到用户模型。这种信号称为post_save。
这是一个NB的东西。现在,告诉我如何使用它。
在Django模板中查看这个示例:
<h2>{
{ user.get_full_name }}</h2>
<ul>
<li>Username: {
{ user.username }}</li>
<li>Location: {
{ user.profile.location }}</li>
<li>Birth Date: {
{ user.profile.birth_date }}</li>
</ul>
在视图方法中:
ef update_profile(request, user_id):
user = User.objects.get(pk=user_id)
user.profile.bio = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit...'
user.save()
一般来说,您永远不需要调用概要文件的save方法。一切都是通过用户模型完成的。
如果我使用Django表单呢?
你可以同时处理多个表单,看看这个代码片段:
forms.py
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('url', 'location', 'company')
views.py
@login_required
@transaction.atomic
def update_profile(request):
if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
profile_form = ProfileForm(request.POST, instance=request.user.profile)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings:profile')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
profile_form = ProfileForm(instance=request.user.profile)
return render(request, 'profiles/profile.html', {
'user_form': user_form,
'profile_form': profile_form
})
profile.html
<form method="post">
{% csrf_token %}
{
{ user_form.as_p }}
{
{ profile_form.as_p }}
<button type="submit">Save changes</button>
</form>
上面提到的那些额外的数据库查询呢?
Django关系是懒惰的。这意味着Django只会在访问一个相关属性时查询数据库。有时它会产生一些不希望的效果,比如触发数百或数千个查询。可以使用select_related方法缓解这个问题。
事先知道您需要访问相关数据,您可以在单个数据库查询中预取数据:
users = User.objects.all().select_related('profile')
扩展AbstractBaseUser自定义模型扩展用户模型。
老实说,我想尽一切办法避免使用这样的方式。但有时你无法逃避,只能使用这样的方式。如果当你必须使用这个方案的时候,请继续看下去。
当我需要使用电子邮件地址作为auth令牌的这种情况下,用户名对我来说毫无用处。而且不需要is_staff标志,因为我没有使用Django Admin。
以下是我如何定义自己的用户模型:
from __future__ import unicode_literals
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from .managers import UserManager
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
'''
Returns the first_name plus the last_name, with a space in between.
'''
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
'''
Returns the short name for the user.
'''
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
'''
Sends an email to this User.
'''
send_mail(subject, message, from_email, [self.email], **kwargs)
我想让它尽可能接近现有的用户模型。由于我们继承了AbstractBaseUser,我们必须遵循以下规则:
USERNAME_FIELD:描述用户模型上作为唯一标识符的字段名称的字符串。该字段必须是唯一的(即:unique=True);
REQUIRED_FIELDS:通过createsuperesuperuser管理命令创建用户时提示的字段名列表;
is_active:一个布尔属性,指示用户是否被视为“active”;
get_full_name():用户的较长的形式标识符。一种常见的解释是用户的全名,但是可以是标识用户的任何字符串。
get_short_name():用于用户的一个简短的非正式标识符。通常的解释是用户的名字。
我还必须定义我自己的UserManager。这是因为现有的管理器定义了create_user和create_superuser方法。
所以现在我的UserManager是这样的:
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(email, password, **extra_fields)
基本上,我已经清理了现有的UserManager,删除了username和is_staff属性。
现在,我们必须更新我们的settings.py。更具体地说,是AUTH_USER_MODEL属性。
AUTH_USER_MODEL = 'core.User'
这样我们就告诉Django使用我们的自定义模型而不是默认模型。在上面的示例中,我在一个名为core的应用程序中创建了自定义模型。
我应该如何引用这个模型?
我们命名一个叫Course的模型:
from django.db import models
from testapp.core.models import User
class Course(models.Model):
slug = models.SlugField(max_length=100)
name = models.CharField(max_length=100)
tutor = models.ForeignKey(User, on_delete=models.CASCADE)
如果您正在创建一个可重用的应用程序,并希望向公众开放,那么强烈建议您使用以下策略:
from django.db import models
from django.conf import settings
class Course(models.Model):
slug = models.SlugField(max_length=100)
name = models.CharField(max_length=100)
tutor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
使用自定义模型扩展用户模型
这很简单,因为类django.contrib.auth.models.AbstractUser将默认用户作为抽象模型提供完整的实现。
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
更新settings.py,定义AUTH_USER_MODEL
AUTH_USER_MODEL = 'core.User'
与前面的方法类似,应该在项目的开始和额外的关注中完成。它将改变整个数据库模式。此外,我宁愿向用户模型创建外键,从from django.conf import settings导入设置并引用settings.AUTH_USER_MODEL,而不是直接引用自定义用户模型。
总结
我们经历了四种不同的方法来扩展现有的用户模型。我尽力给你提供尽可能多的细节。正如我之前所说的,没有最好的解决方案。这将取决于你需要实现什么。保持简单,明智地选择。
1.Proxy Model:您对Django用户提供的一切都很满意,不需要存储额外的信息。
2.Proxy ModelUser Profile:您对Django处理auth的方式感到满意,并需要向用户添加一些与auth无关的属性。
3.继承AbstractBaseUser的自定义用户模型:Django处理auth的方式不适合您的项目。
4.从AbstractUser定制用户模型:Django处理auth的方式非常适合您的项目,但是您仍然希望添加额外的属性,而不必创建单独的模型