1. 程式人生 > >Django筆記 如何擴充套件User表的欄位

Django筆記 如何擴充套件User表的欄位

django 自帶的許可權框架,其中auth_user表的欄位,很難滿足正常的需求,因此需要擴充套件,至於擴充套件,一般有如下幾種選擇:
1. 直接修改django 原始碼,修改User class 的定義,以及各種方法等,然後把資料庫auth_user表裡的欄位擴充套件到與自己需求一致.(原始碼在:django.contrib.auth.models import User),這種方式,每次升級django都得很小心.
2. 把django 的user以及認證部分的原始碼拷貝到自己的app下面,然後修改,配置,這樣就不需要改動django的程式碼了.但如果你要升級django ,就可能有麻煩
3. 繼承User,做擴充套件.
4. django 官方推薦的方法,profile 方式擴充套件.

方法一

比較一下這幾種方式,發現,1,2種方法很黃很暴力.所以不是在非常特殊的情況下,建議不用。第3中方法,我目前測試下來是可以,但擴充套件之後,輸入密碼的時候,居然是明文的,而且儲存進資料庫是沒有經過加密的密碼,目前還在找原因,所以這裡介紹第4種方法.

在models.py 中增加如下擴充套件user的類:

#==================擴充套件使用者====================================
class UserProfile(models.Model):
    user = models.OneToOneField(User)    
    major = models.TextField(default=''
, blank=True) address = models.CharField(max_length=200,default='',blank=True) def __unicode__(self): return self.user.username def create_user_profile(sender, instance, created, **kwargs): """Create the UserProfile when a new User is saved""" if created: profile = UserProfile() profile.user = instance profile.save() #post_save.connect(create_user_profile, sender=User)
""" 不明白的是,我一定註釋掉上面這一行,才不會出錯,否則會有Duplicate entry '2' for key 'user_id'") ,看意思是,重複了,但不明白為什麼重複,註釋掉上面的之後,一切正常,但與官方文件又有差異了,迷惑中""" #==================擴充套件使用者結束================================

還需要修改admin.py

"""使用者模組擴充套件"""
class ProfileInline(admin.StackedInline):
    model = UserProfile
    #fk_name = 'user'
    max_num = 1
    can_delete = False

class CustomUserAdmin(UserAdmin):
    inlines = [ProfileInline,]

admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
"""使用者模組擴充套件"""

修改settings.py 的配置,增加

"""使用者模組擴充套件部分"""
AUTH_PROFILE_MODULE = 'djangoadmin.myadmin.UserProfile'
"""使用者模組擴充套件完成"""

按照官方的解釋,這裡是app label加上擴充套件類的名字. 應該也就是建立的app的名字,官方推薦的方式就兩個部分用”.”連線起來,我這裡有三個部分,也沒有報錯。
然後執行 python manage.py syncdb ,這是會在資料庫中建立響應的表,並且有user_id這個外來鍵欄位.
最後,我們來執行一下程式,並進入到增加使用者介面中,你會發現,你擴充套件的欄位都顯示出來了
每次增加使用者,都會在擴充套件的表中增加相應的資料,修改的時候,也會修改響相應的資料,通過 user_id 來關聯,這樣就完成了user model的擴充套件。
如果要獲取擴充套件表中的內容,可以通過 request.user.get_profile().address 這種方式來獲取. 得到 User物件後,就能很方便的得到擴充套件的類.

方法二

如果想通過繼承User的方式來做的,可以用如下方法,轉載自網上:
1.建立 user profile 類,直接繼承於 User
程式程式碼 程式程式碼

from django.contrib.auth.models import User, UserManager
class CustomUser(User): 
    timezone = models.CharField(max_length=50, default='Asia/shanghai')   
    objects = UserManager()

用這種方式 可以直接用user = CustomUser.objects.create(…)方式建立新物件. 而且如果用request.user得到的就是你擴充套件的這個物件,可以直接取屬性的。不需要get_profile.

2.擴充套件認證機制
程式程式碼 程式程式碼

from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model

class CustomUserModelBackend(ModelBackend):
    def authenticate(self, username=None, password=None):
        try:
            user = self.user_class.objects.get(username=username)
            if user.check_password(password):
                return user
        except self.user_class.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return self.user_class.objects.get(pk=user_id)
        except self.user_class.DoesNotExist:
            return None

    @property
    def user_class(self):
        if not hasattr(self, '_user_class'):
            self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
            if not self._user_class:
                raise ImproperlyConfigured('Could not get custom user model')
        return self._user_class
  1. 配置settings.py
    程式程式碼 程式程式碼

AUTHENTICATION_BACKENDS = (
   'myproject.auth_backends.CustomUserModelBackend',
)
  1. 執行 python manage.py syncdb
    同樣也會生成一個數據庫,不過表的欄位與前面一種方法有所不用。

然後執行,同樣測試,可以達到同樣的效果,但介面上密碼輸入哪裡我始終不滿意,所以就沒采用。

方法三

Django的使用者模型:
Django和其他很多Web框架不同的一點就是它自帶了一個現成的User Model,因為Web開發中99%會涉及到使用者這個概念,自帶一個User Model,不但解決了每次開發一個新的專案的時候,都不得不為新建一個使用者表所做的重複工作。而且最大的好處是由於Django Model特有的處理方式,只有通過django.contrib.auth.models 匯入 User Model,那麼不管你是來自哪個app,各個app之間引用的都是同一個User Model,而那些域通過ForeignKey,ManyToManyField,OneToOneField指向User Model後在資料庫中所產生的真正外來鍵都是參照 auth_user 表的 id,這對於很多相互獨立但都需要一個使用者模型的app來說是不可缺少的機制,因為如果django不內建User Model,那個每個app就得各自為政,獨立定義User Model,最後的結果就是當想把這些app組合起來使用的時候,由於User Model不統一而得花大量時間來修改這些app的User Model相關的程式碼。
這個優點表現最明顯的例子就是 Pinax Project (http://pinaxproject.com/),這是個將大量開源app組合起來經過簡單配置組裝而成的一個SNS工程。

Django使用者模型的問題:
雖然 Django 自帶了User Model方便了很多app的組合,但是由於每個app肯定都需要再增加一些資訊到使用者模型中,而Django自帶的User Model 中所定義的field都是一些很常規的資訊,肯定無法滿足app本身的需求。為此就需要增加一個profile,放使用者額外的資訊。這主要由以下幾種方式:
1、Django官方指導的方式:
見文件 http://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users
大概過程就是這樣:定義一個Profile Model,裡面包含一個unique=True的ForeignKey ,在settings.py 新增一個選項 AUTH_PROFILE_MODULE 指向該Profile Model,那麼自帶User Model的例項就可以通過get_profile()來獲取相應的profile的資訊了,這個方法雖然看上去很方便,但是有一個很明顯的缺點就是profile是在一張另外的表中,雖然get_profile()快取了第一次取的的profile的結果,但是每個http請求之間都得為此做一個查詢,當用戶數量很多的時候,對效能的影響就比較大了。另外一個問題是當需要定義像friends這樣的指向 ‘self’ 的 ManyToManyField時該怎麼辦?定義在profile中就顯得很不合理了,一個使用者和另一個使用者做朋友,而不是和ta的profile做朋友,更何況定義在profile中的話,當做friends的反向查詢的時候,取得的queryset是profiles,這時候又想取得這些profile指向的user的資訊的時候就得再做大量查詢了。

2、修改Django的原始碼直接在User Model上加欄位了:
這種方法很直接效果也很明顯,如果用得像是1.0.2-final這樣的最終釋出版,並且以後也不打算更新Django版本的話這樣也是可以的。但是如果是用SVN development版的話,這種修改原始碼的方式就顯得有點暴力了。

3、直接捨棄Django的User Model,自己重頭定義一個User Model,包括自己管理auth,寫auth backends:
我見過幾個專案都是這樣做的了,雖然通過繼承原有User Model,ModelBackend等類,可以只需要寫很少的程式碼就可以實現,這種方式其實修改Django原始碼沒什麼大的區別,都是減少了一張profile表,只不過擴充套件後的User Model一個是放在Django原始碼裡,一個是放在自己的原始碼裡,而且這種方式比修改原始碼更糟的是,這破壞了開頭提到的與其他app組合使用的可能性。

不修改django原始碼也不增加profile表的擴充套件使用者模型:
上面提到的各種方式主要都是圍繞修改django原始碼和增加一張profile表之間的權衡利弊展開。那麼有沒有一種兩全其美的方式呢,既不修改原始碼,也不增加profile表。答案是有的,而且在很早的django版本中,這甚至是很常用的方式,只是後來django修改了Model部分的原始碼,阻止了這種方式,因為這看上去太magic了。
以前的django model中的inner Meta中可以定義一個 replaces_module 的引數,用來指定你想將該Model替換另外一個Model,看上去很奇怪也很神奇,但是管理起來也顯得很混亂,所以最後這個特性被取消了。
雖然上面這種方式被阻止了,但是還是可以通過其他方式動態的修改User Model類,說了這麼多,還是直接看原始碼來的更清楚些吧:

  from django.db import models  
from django.contrib.auth.models import User  
from django.contrib.auth.admin import UserAdmin  
import datetime  
class ProfileBase(type):  
    def __new__(cls, name, bases, attrs):  
        module = attrs.pop('__module__')  
        parents = [b for b in bases if isinstance(b, ProfileBase)]  
        if parents:  
            fields = []  
            for obj_name, obj in attrs.items():  
                if isinstance(obj, models.Field): fields.append(obj_name)  
                User.add_to_class(obj_name, obj)  
            UserAdmin.fieldsets = list(UserAdmin.fieldsets)  
            UserAdmin.fieldsets.append((name, {'fields': fields}))  
        return super(ProfileBase, cls).__new__(cls, name, bases, attrs)  

class Profile(object):  
    __metaclass__ = ProfileBase  

class MyProfile(Profile):  
    nickname = models.CharField(max_length = 255)  
    birthday = models.DateTimeField(null = True, blank = True)  
    city = models.CharField(max_length = 30, blank = True)  
    university = models.CharField(max_length = 255, blank = True)  

    def is_today_birthday(self):  
        return self.birthday.date() == datetime.date.today()  

上面的程式碼中定義了一個ProfileBase的元類,然後定義了一個作為Profile基類的Profile類,並且它的元類為ProfileBase。
元類的作用簡單來講,就是建立其他類的類。也就是元類的例項是普通類,而普通類的例項就是普通的例項。

如果你不理解這些也沒關係,只要知道,上面的程式碼中,當直譯器看到你在定義一個Profile類的子類,而Profile類的元類是ProfileBase,所以MyProfilede的元類也是ProfileBase,也就是在定義任何Profile的子類的時候,它就會執行元類ProfileBase中的new中程式碼,並且將正在定義的類的(名字,基類,類屬性)作為引數傳遞給new

MyProfilede中的field採用了和普通Model相同的宣告語法,所以這些field和那個自定義方法is_today_birthday會作為attrs中的屬性傳遞過去,然後元類中將這些資訊提取出來通過
User.add_to_class(obj_name, obj) 加入到User類中,add_to_class也是普通Model中定義field時採用的方式,因為普通Model也有一個元類在做相似的事情,只不過這裡定義的這個元類專門往User Model中加東西。

在新增完這些field和自定義方法後,前面識別出哪些屬性是field,然後加入到UserAdmin.fieldsets中,那樣在admin中就可以和其他User fields 一起編輯這些新加的field了。

如果你有其他app也想往User Model中加field或方法,都只要通過子類Profile類,然後使用宣告語法進行定義即可,所有其他工作都有元類幫你完成。

通過這種方式新增到User Model中的field和方法的效果就和直接修改Django User Model的原始碼一樣,只不過現在這些修改不用再像修改原始碼這麼暴力了,而且想添的時候隨便可以添。

需要注意的地方:
1、以上元類的定義包括MyProfile的定義,最好就和普通Model一樣,放在app的models.py檔案中, 以便django可以方便的碰到這些定義,否則django沒看到這些程式碼也就沒效果了,而且放在其他地方非常容易引起匯入的依賴問題。

2、如果你已經通過syncdb同步了資料庫,再使用此方法擴充套件使用者模型,那麼Django雖然知道你定義了這些field,但是再次執行syncdb時,不會再為這些field自動建立資料庫欄位,因為django的syncdb只負責建立新表的欄位。這與你已經定義了一個Model並執行syncdb,然後往那個Model上又加了fields是同一種情況。

參考: