Django进阶教程:如何扩展Django用户模型
Django的内置身份验证系统非常棒。在大多数情况下,我们可以开箱即用,节省大量开发和测试工作。它适合大多数用例,并且非常安全。但是有时我们需要做一些很好的调整来适应我们的Web应用程序。
通常,我们希望存储更多与用户相关的数据。如果您的Web应用程序具有社会吸引力,您可能希望存储一个简短的个人信息、用户的地址,以及其他类似的用户信息。
在本文中,我将介绍多种不同的策略来扩展默认的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>
在视图方法中:
def 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
,而不是直接引用自定义用户模型。
总结
我们经历了四种不同的方法来扩展现有的用户模型。我尽力给你提供尽可能多的细节。正如我之前所说的,没有最好的解决方案。这将取决于你需要实现什么。保持简单,明智地选择。
Proxy Model:您对Django用户提供的一切都很满意,不需要存储额外的信息。
Proxy ModelUser Profile:您对Django处理auth的方式感到满意,并需要向用户添加一些与auth无关的属性。
继承AbstractBaseUser的自定义用户模型:Django处理auth的方式不适合您的项目。
从AbstractUser定制用户模型:Django处理auth的方式非常适合您的项目,但是您仍然希望添加额外的属性,而不必创建单独的模型。
文章评论 3
当你的才华和能力还不足以支撑你的野心的时候,静下心来,学习!
666,看过,顶一个!
谢谢支持!