Django2.0-驗證和授權(2)-User模型拓展
擴充套件使用者模型
代理模型
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='[email protected]', 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
比較瞭解才推薦使用。
-
建立模型。
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
其中
password
和last_login
是在AbstractBaseUser
中已經新增好了的,直接繼承就可以了。
然後再新增想要的欄位。比如username
、phone
等。這樣就可以實現自己想要的欄位了。但是因為重寫了User
,所以應該儘可能的模擬User
模型:USERNAME_FIELD
:用來描述User
模型名字欄位的字串,作為唯一的標識。如果沒有修改,那麼會使用USERNAME
來作為唯一欄位。REQUIRED_FIELDS
:一個欄位名列表,用於當通過createsuperuser
管理命令建立一個使用者時的提示。is_active
:一個布林值,用於標識使用者當前是否可用。get_full_name()
:獲取完整的名字。get_short_name()
:一個比較簡短的使用者名稱。
-
重新定義
UserManager
:因為預設的UserManager
在建立使用者的時候使用的是username
和password
,這裡需要增加一個phone
引數。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
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")
-
使用2
比如以後有一個Article
模型,需要通過外來鍵引用這個User
模型,那麼可以通過以下兩種方式引用。
第一種就是直接將User
匯入到當前檔案中。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()
函式來讀取該值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
前就先定義好。