代理模型
Django内置的User模型虽然已经足够强大了。但是有时候还是不能满足的需求。
比如在验证用户登录的时候,默认使用的是用户名作为验证,但一般通过手机号码或者邮箱来进行验证。或者需要增加一些新的字段。那么这时候就需要扩展用户模型了。
如果只是需要在默认的基础之上增加一些操作的方法。则使用代理模型的方法
# models.pyfrom django.db import modelsfrom django.contrib.auth.models import User# 如果模型是一个代码模型,那么就不能在这里模型中添加新的Field!!!!!!!!!!!class ProxyUser(User):class Meta:proxy = True@classmethoddef get_blacklist(cls):# seek that is_active = Falsereturn cls.objects.filter(is_active=False)
定义了一个ProxyUser类继承自User,并且在Meta中设置proxy=True,说明这个只是User的一个代理模型。并不会影响原来User模型在数据库中表的结构。
以后如果想方便的获取所有黑名单的人,那么就可以通过ProxyUser.get_blacklist()就可以获取到。
并且User.objects.all()和Person.objects.all()其实是等价的。因为它们都是从User这个模型中获取所有的数据。
from django.http import HttpResponsefrom .models import ProxyUserdef proxy(reqeust):blacklist = ProxyUser.get_blacklist() # 代理模型的类方法(查找is_active=False的对象)for user in blacklist:print(user.username)# 再次注意ProxyUser.object.all() == User.object.all()return HttpResponse("proxy")
2. 一对一外键
如果你对用户验证方法authenticate没有其他要求,就是使用username和password即可完成。
但是想要在原来模型的基础之上添加新的字段
from django.contrib.auth.models import Userfrom django.db import modelsfrom django.dispatch import receiverfrom django.db.models.signals import post_saveclass ExtraUser(models.Model): # 一对一方式扩展all_user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="extra") # 注意这里更改User中自添加的extrauser字段改名为extraphone = models.CharField(max_length=11) # 新添加的字段address = models.CharField(max_length=100) # 新添加的字段# 将User和一对一扩展模型进行绑定# 原型是: receiver(signal, **kwargs)@receiver(post_save, sender=User) # 当User产生post_save信号时def handler_user_extra_content(sender, instance, created, **kwargs):if created: # 如果第一次创建ExtraUser.objects.create(all_user=instance) # 绑定User实例到ExtraUser的all_user字段else:instance.extra.save() # 保存ExtraUser的内容 ,注意extra是ExtraUser的all_user字段外键的related_name名
定义一个ExtraUser的模型,并且让它和User模型进行一对一的绑定,以后新增的字段,就添加到ExtraUser上。
还写了一个接受保存模型的信号处理方法,只要是User调用了save方法,那么就会创建一个ExtraUser和User进行绑定。
# views.py 使用该模型fron django.http import HttpResponsedef one_to_one_authenticate(phone, password): # 自定义的authenticateuser = User.objects.filter(extra__phone=phone).first()# User由于被引用,其下默认生成一个字段,且被改名为extraif user:is_correct = user.check_password(password)if is_correct:return userelse:return Noneelse:return Nonedef one_to_one(request):user = User.objects.create_user(username="lee", email='lee@163.com', password=111111)user = User.objects.get(username="lee")user.extra.phone = 10086100861 # 更改ExtraUser里的字段信息user.extra.address = "china"user.save()# 假设下面是用户输入phone = 10086100861password = 111111user = one_to_one_authenticate(phone=phone, password=password) # 使用自己的authenticateif user:print(user.username)else:print("no such user")return HttpResponse("one to one successful")
3. 继承自AbstractUser
对authenticate不满意,并且不想要修改原来User对象上的一些字段,且想要增加一些字段,那么这时候可以直接继承自django.contrib.auth.models.AbstractUser,这个类也是django.contrib.auth.models.User的父类。
比如想要在原来User模型的基础之上添加一个phone和address字段。
from django.contrib.auth.models import AbstractUser, BaseUserManager# 前者是User的父类class UserManager(BaseUserManager):def _create_user(self, phone, username, password, **kwargs):# 这是一个受保护函数,只能被类自己中调用# 作为create_user和create_superuser的被调用函数if not phone:raise ValueError("必须传递手机号码")if not password:raise ValueError("必须传递密码")user = self.model(phone=phone, username=username, **kwargs) # self.model表示当前模型user.set_password(password) # password只能这样设置user.save()return userdef create_user(self, phone, username, password, **kwargs):kwargs["is_superuser"] = False # 添加is_superuser键值对return self._create_user(phone=phone, username=username, password=password, **kwargs)def create_superuser(self, phone, username, password, **kwargs):kwargs["is_superuser"] = Truereturn self._create_user(phone=phone, username=username, password=password, **kwargs)class InheritOne(AbstractUser): # 自定义的User类phone = models.CharField(max_length=11, unique=True)address = models.CharField(max_length=100)# 指定phone作为USERNAME_FIFLE,使用authenticate函数验证的时候,就可以用phone的值来验证而不是usernameUSERNAME_FIELD = 'phone' # 到时候用的时候是username = phone's valueREQUIRED_FIELDS = [] # 命令行创建超级用户的时候系统提示要添加的内容# 重新指定Manager对象,为了在使用object.create_user和object.create_superuser的时候# 使用phone和password而不是username和passwordobjects = UserManager()
然后再在settings中配置好AUTH_USER_MODEL=app_name.InheritOne。
这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好(如果已经migrate,练习时需要删除所有表和app的migrations包内的迁移文件)。
# 使用from django.http import HttpResponsefrom .models import InheritOnedef inherit_one(request):InheritOne.objects.create_user(phone=100861008611, username="jack", password=111111)InheritOne.objects.create_superuser(phone=13100001111, username='superJack', password=222222)user = authenticate(request, username=100861008611, password=111111) # 这里的username是phone's value, 在InheritOne中的USERNAME_FIELD定义的字段代表的值,即'phone'字段的值if user:print("存在")else:print("不存在")return HttpResponse("inherit from AbstractUser")
4. 继承自AbstractBaseUser模型
如果想修改默认的验证方式,并且对于原来User模型上的一些字段不想要,那么可以自定义一个模型,然后继承自AbstractBaseUser,再添加想要的字段。这种方式会比较麻烦,最好是确定自己对Django比较了解才推荐使用。
- 创建模型。 ```python from django.contrib.auth.models import AbstractBaseUser, BaseUserManager from django.contrib.auth.models import PermissionsMixin
class InheritTwo(AbstractBaseUser, PermissionsMixin): phone = models.CharField(max_length=11, unique=True) username = models.CharField(max_length=20) password = models.CharField(max_length=20) address = models.CharField(max_length=100) is_active = models.BooleanField(default=True) # 这个要加
USERNAME_FIELD = 'phone' # authenticate函数的usernma参数指定为phone字段的值REQUIRED_FIELDS = []objects = UserManager()def get_full_name(self): # 可以参考AbstractUser的函数return self.usernamedef get_short_name(self):return self.username
1. <br />其中`password`和`last_login`是在`AbstractBaseUser`中已经添加好了的,直接继承就可以了。<br />然后再添加想要的字段。比如`username`、`phone`等。这样就可以实现自己想要的字段了。但是因为重写了`User`,所以应该尽可能的模拟`User`模型:- `USERNAME_FIELD`:用来描述`User`模型名字字段的字符串,作为唯一的标识。如果没有修改,那么会使用`USERNAME`来作为唯一字段。- `REQUIRED_FIELDS`:一个字段名列表,用于当通过`createsuperuser`管理命令创建一个用户时的提示。- `is_active`:一个布尔值,用于标识用户当前是否可用。- `get_full_name()`:获取完整的名字。- `get_short_name()`:一个比较简短的用户名。2. 重新定义`UserManager`:因为默认的`UserManager`在创建用户的时候使用的是`username`和`password`,这里需要增加一个`phone`参数。```shellclass UserManager(BaseUserManager):def _create_user(self, phone, username, password, **kwargs):# 这是一个受保护函数,只能被类自己中调用# 作为create_user和create_superuser的被调用函数if not phone:raise ValueError("必须传递手机号码")if not password:raise ValueError("必须传递密码")user = self.model(phone=phone, username=username, **kwargs) # self.model表示当前模型user.set_password(password) # password只能这样设置user.save()return userdef create_user(self, phone, username, password, **kwargs):kwargs["is_superuser"] = False # 添加is_superuser键值对return self._create_user(phone=phone, username=username, password=password, **kwargs)def create_superuser(self, phone, username, password, **kwargs):kwargs["is_superuser"] = Truereturn self._create_user(phone=phone, username=username, password=password, **kwargs)
- 在创建了新的
User模型后,还需要在settings.py中配置好。配置AUTH_USER_MODEL='appname.InheritTwo'。 - 使用1 ```python from django.http import HttpResponse from .models import InheritTwo
def inherit_two(request): InheritTwo.objects.create_user(phone=10086100861, username=’jack’, password=11111) InheritTwo.objects.create_superuser(phone=10086100862, username=’lee’, password=222222) user = authenticate(request, username=10086100862, password=222222) if user: print(‘存在’) else: print(‘不存在’) return HttpResponse(“inherit two”)
4. <br />5. 使用2<br />比如以后有一个`Article`模型,需要通过外键引用这个`User`模型,那么可以通过以下两种方式引用。<br />第一种就是直接将`User`导入到当前文件中。```pythonfrom django.db import modelsfrom otherapp.models import InheritTwoclass Article(models.Model):title = models.CharField(max_length=100)content = models.TextField()author = models.ForeignKey(User, on_delete=models.CASCADE)
为了更好的使用性,建议还是将User抽象出来,使用settings.py中的AUTH_USER_MODEL来表示。可以使用get_user_model()函数来读取该值 ```python from django.db import models from django.conf import settings from django.contrib.auth import get_user_model
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) ```
这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好。
