1. 程式人生 > >Django2.0-驗證和授權(2)-User模型拓展

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沒有其他要求,就是使用usernamepassword即可完成。
但是想要在原來模型的基礎之上新增新的欄位

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方法,那麼就會建立一個ExtraUserUser進行繫結。

# 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模型的基礎之上新增一個phoneaddress欄位。

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比較瞭解才推薦使用。

  1. 建立模型。

    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
    

    其中passwordlast_login是在AbstractBaseUser中已經新增好了的,直接繼承就可以了。
    然後再新增想要的欄位。比如usernamephone等。這樣就可以實現自己想要的欄位了。但是因為重寫了User,所以應該儘可能的模擬User模型:

    • USERNAME_FIELD:用來描述User模型名字欄位的字串,作為唯一的標識。如果沒有修改,那麼會使用USERNAME來作為唯一欄位。
    • REQUIRED_FIELDS:一個欄位名列表,用於當通過createsuperuser管理命令建立一個使用者時的提示。
    • is_active:一個布林值,用於標識使用者當前是否可用。
    • get_full_name():獲取完整的名字。
    • get_short_name():一個比較簡短的使用者名稱。
  2. 重新定義UserManager:因為預設的UserManager在建立使用者的時候使用的是usernamepassword,這裡需要增加一個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)
    
  3. 在建立了新的User模型後,還需要在settings.py中配置好。配置AUTH_USER_MODEL='appname.InheritTwo'

  4. 使用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")
    

    1

  5. 使用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前就先定義好。