代理模型
Django
内置的User
模型虽然已经足够强大了。但是有时候还是不能满足的需求。
比如在验证用户登录的时候,默认使用的是用户名作为验证,但一般通过手机号码或者邮箱来进行验证。或者需要增加一些新的字段。那么这时候就需要扩展用户模型了。
如果只是需要在默认的基础之上增加一些操作的方法。则使用代理模型的方法
# models.py
from django.db import models
from django.contrib.auth.models import User
# 如果模型是一个代码模型,那么就不能在这里模型中添加新的Field!!!!!!!!!!!
class ProxyUser(User):
class Meta:
proxy = True
@classmethod
def get_blacklist(cls):
# seek that is_active = False
return 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 HttpResponse
from .models import ProxyUser
def 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 User
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import post_save
class ExtraUser(models.Model): # 一对一方式扩展
all_user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="extra") # 注意这里更改User中自添加的extrauser字段改名为extra
phone = 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 HttpResponse
def one_to_one_authenticate(phone, password): # 自定义的authenticate
user = User.objects.filter(extra__phone=phone).first()
# User由于被引用,其下默认生成一个字段,且被改名为extra
if user:
is_correct = user.check_password(password)
if is_correct:
return user
else:
return None
else:
return None
def 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 = 10086100861
password = 111111
user = one_to_one_authenticate(phone=phone, password=password) # 使用自己的authenticate
if 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 user
def 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"] = True
return 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的值来验证而不是username
USERNAME_FIELD = 'phone' # 到时候用的时候是username = phone's value
REQUIRED_FIELDS = [] # 命令行创建超级用户的时候系统提示要添加的内容
# 重新指定Manager对象,为了在使用object.create_user和object.create_superuser的时候
# 使用phone和password而不是username和password
objects = UserManager()
然后再在settings
中配置好AUTH_USER_MODEL=app_name.InheritOne
。
这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate
前就先定义好(如果已经migrate,练习时需要删除所有表和app的migrations包内的迁移文件)。
# 使用
from django.http import HttpResponse
from .models import InheritOne
def 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.username
def 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`参数。
```shell
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 user
def 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"] = True
return 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 />![](https://cdn.nlark.com/yuque/0/2019/png/367873/1559645165747-ccd36e7d-a810-4502-9b64-53fb4feae2a3.png#align=left&display=inline&height=280&originHeight=280&originWidth=1005&size=0&status=done&width=1005)
5. 使用2<br />
比如以后有一个`Article`模型,需要通过外键引用这个`User`模型,那么可以通过以下两种方式引用。<br />
第一种就是直接将`User`导入到当前文件中。
```python
from django.db import models
from otherapp.models import InheritTwo
class 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
前就先定义好。