Django Model 設計
Django Model設計是Django五項基礎核心設計之一(Model設計,URL配置,View編寫,Template設計,From使用),也是MVC模式中重要的環節。
如果圖片無法訪問,大家可以移步GitHub Django Model 設計查看
Model是數據模型,并不是數據庫,它描述了數據的構成和他們之間的邏輯關系,在Django中,Model設計實質上就是一個類,所以我們可以直接站在類的角度來看Model,這樣可以盡量避免一些晦澀的概念影響理解。
下面是python中一個基本類的構成
class ClassName(object):# 屬性(類屬性)Attribute = ""# 方法def Method():pass
根據上面python中的類,我們依次研究Django Model
- 基類:定義Django Model的類必須繼承自models.Model
- 類名:在Django Model設計中,類名會被當作是這個數據模型的名稱。
- 類屬性:Django Model設計過程中,類屬性有兩方面的作用,一是申明字段,二是申明關系(一對多,多對多,一對一)
- 方法:由于是派生類,所以我們不需要自己定義很多方法,Model基類已經做的足夠完善了,只是在有必要的時候我們需要重寫一些方法,如常見的
__str__
等 - 內嵌類Meta:Django Model設計過程中,內嵌類Meta用來定義元數據,所謂元數據就是不是字段的任何數據,比如定義排序規則等
下面依次介紹字段,關系,Meta
1.字段 Field
都說官方文檔才是學習一門語言最好的教程,訪問官方文檔
1.1 字段類型(Field types)
字面意思,用來申明該字段的類型,比如常見的字符,數字,日期,郵箱等待,下面列舉常見的字段類型
AutoField(主鍵)
這一個字段一般不需要我們手動定義,主要用于ID的自動遞增,Django會默認為我們創建一個id
# 每一個Model Django都會為我們添加這個字段
id = models.AutoField(primary_key=True)
如果想把自己定義的字段設置為主鍵,需要添加參數primary_key,這時(顯式設置主鍵)Django將不會為我們添加ID字段
與之類似還有一個BigAutoField,它支持更大的范圍,最大到9223372036854775807(九百二十億億)足夠絕大多數的使用
IntegerField(整數字段)
字面意思,用來保存整數的字段,支持 -2147483648 到 2147483647 的數字
同樣與之類似有BigIntegerField 支持-9223372036854775808 到 9223372036854775807 的數字
類似還有FloatField,支持浮點數
BooleanField(布爾字段)
用來存儲布爾值,與之類似的還有NullBooleanField,NullBooleanField相當于BooleanField(null=true),不過在Django 2.1 之后不建議使用后者,應為它有可能在未來版本被棄用
CharField(字符字段)
這應該是最常用的一個字段了吧,用于少量的字符串的儲存(大量字符串請使用TextField)這個字段有一個必須參數max_length用來申明允許儲存的最大長度
class CharField(Field):description = _("String (up to %(max_length)s)")# ...def _check_max_length_attribute(self, **kwargs):if self.max_length is None:return [checks.Error("CharFields must define a 'max_length' attribute.",obj=self,id='fields.E120',)]
根據源碼,我們可以發現如果不指定max_length會直接報"CharFields must define a ‘max_length’ attribute."
DateField(時間日期字段)
該字段有兩個常用參數 auto_now 和 auto_now_add
- auto_now :指定這個參數,可以在每次調用save()時將當前時間作為字段的值(會覆蓋默認值或之前的值),但使用QuerySet.update()等方法不會跟新值
- auto_now_add : 在初始化會以當前時間戳作為值給字段賦值,不管你有沒有定義默認值,定義了也會被覆蓋
- 區別:使用auto_now會在每次save()時修改字段值,而auto_now_add只是在首次創建對象時才會把當前時間給字段。
- 如果你想能夠修改這個字段的值,請使用
default=date.today
需要引入from datetime.date.today()
- auto_now,auto_now_add與default是互斥的,任何一種組合都會出錯
- 將auto_now或auto_now_add設置為True的結果與editable = False和blank = True的效果一樣
與DateField類似的還有DateTimeField
- 如果使用DateTimeField想要修改字段的值,需要使用
default=timezone.now
同樣需要引入from django.utils.timezone.now()
- 另外還有TimeField,用來表示時間,接受的參數與DateField一樣
[外鏈圖片轉存失敗(img-w7TfUVCo-1567244122546)(…/image/DjangoModel_01.png)]
EmailField
實質上是CharField,不過使用EmailValidator檢查了字符串是否是有效電子郵件地址而已,默認max_length=254
TextField
實質上也是一個CharField,是一個比較大的文本字段
FileField
class FileField(upload_to=None, max_length=100, **options)
正如文檔所說,這是一個文件上傳字段,有兩個可選參數upload_to和max_length,后者默認100
upload_to
見名知意,upload_to用來申明上傳目錄,如果給定一個字符串類型初值,Django會在他后面添加時間(就是以上傳時間分類文件),用戶上傳文件一般保存到media目錄中,media目錄路徑需要在setting.py里定義
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
這個media文件夾如果我們沒有建立,保存文件時Django會自動創建
class MyModel(models.Model):# file will be uploaded to MEDIA_ROOT/uploadsupload = models.FileField(upload_to='uploads/')# or...# file will be saved to MEDIA_ROOT/uploads/2015/01/30upload = models.FileField(upload_to='uploads/%Y/%m/%d/')
如果需要動態存儲文件,如按用戶存儲,可以把一個函數的返回值作為upload_to的值,這個函數必須接受兩個參數:instance(主鍵,傳給當前文件的唯一實例)和filename(文件名),如:
def user_directory_path(instance, filename):# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>return 'user_{0}/{1}'.format(instance.user.id, filename)class MyModel(models.Model):upload = models.FileField(upload_to=user_directory_path)
這里看著有點復雜,其實也不難,instance是當前FileField的模型實例的模型實例,也就是我們定義的字段,上面的示例是的user是和User表對應的,當然也可以不是user,比如:
def user_directory_path(instance, filename):# 我沒有用user,而是自己定義了一個name字段return 'user_{0}/{1}'.format(instance.name, filename)class Diary(models.Model):name = models.CharField(max_length=10)creat_time = models.DateTimeField(auto_now_add=True)time = models.TimeField(auto_now=True)upload = models.FileField(upload_to=user_directory_path,default="")class Meta:verbose_name = "Diary"verbose_name_plural = "Diarys"
[外鏈圖片轉存失敗(img-SGnfpXgs-1567244122548)(…/image/DjangoModel_02.png)]
保存之后就可以看見項目根目錄下的文件了
[外鏈圖片轉存失敗(img-KUPPj7yc-1567244122552)(…/image/DjangoModel_03.png)]
使用時間也是一樣的
class Diary(models.Model):name = models.CharField(max_length=10)creat_time = models.DateTimeField(auto_now_add=True)time = models.TimeField(auto_now=True)# upload = models.FileField(upload_to=user_directory_path,default="")upload = models.FileField(upload_to='user_123/%Y/%m/%d/',default="")class Meta:verbose_name = "Diary"verbose_name_plural = "Diarys"def __str__(self):return self.name
[外鏈圖片轉存失敗(img-oPc8recT-1567244122558)(…/image/DjangoModel_04.png)]
FileField 和 FieldFile
當您訪問model上的FileField時,會獲得一個FieldFile的實例作為訪問基礎文件的代理。它是繼承自python的File的,有read(),write()等,還自己封裝了一些方法,如url,name等,看官網
ImageField
class ImageField(FileField):attr_class = ImageFieldFiledescriptor_class = ImageFileDescriptordescription = _("Image")def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):self.width_field, self.height_field = width_field, height_fieldsuper().__init__(verbose_name, name, **kwargs)
由源碼可以看到,ImageField是繼承自FileField的一個字段,除了FileField中的特殊屬性外,它額外添加了width_field, height_field兩個屬性用來描述圖片大小,它會檢查一個文件是否是圖片,其他用法與FileField一樣。
其他
除了上面常見的這些,還有一些字段,諸如用作URL的URLField,還有不同大小的IntField,如PositiveSmallIntegerField,PositiveIntegerField,SmallIntegerField,用作IP地址的GenericIPAddressField等待,此外,你也可以自定義字段,看文檔就好。
1.2 參數總結
之前遇到很多參數,如max_length,default等,這里再做總結
null
如果為True,Django將在數據庫中將空值存儲為NULL。 默認值為False。
避免在基于字符串的字段(如CharField和TextField)上使用null。 如果基于字符串的字段具有null = True,則表示它具有“無數據”的兩個可能值:NULL和空字符串。 在大多數情況下,為“無數據”提供兩個可能的值是多余的; Django約定是使用空字符串,而不是NULL。
blank
如果為真,則允許該字段為空。默認是假的。注意,這與null不同。null純粹與數據庫相關,而blank則與驗證相關。如果字段為blank=True,表單驗證將允許輸入空值。如果字段為空=False,則需要該字段。
choices
為字段提供選項,首先他的值應該是一個可迭代對象,如列表或元組,其次每一項中都應該包含兩個元素,第一個是要存儲到數據庫中的真實值,第二個是展示給人的值,比如
class Diary(models.Model):SEX_CHOICES = (('boy', 'BOY'),('girl', 'GIRL'),('else', 'ELSE'))sex = models.CharField(max_length=10,choices=SEX_CHOICES,default="")
這個Model展示在admin頁面是這樣的,請忽略其他字段,那是之前寫的
[外鏈圖片轉存失敗(img-mRp1xKaa-1567244122563)(…/image/DjangoModel_05.png)]
在隨便存儲一個值后,在MySQL數據庫中可以看到是這樣的(最后一項)
[外鏈圖片轉存失敗(img-0JWk72Nk-1567244122565)(…/image/DjangoModel_06.png)]
展示給我們的是后面大寫的,而存儲在數據庫中的則是前面小寫的
除此之外,如果Model中有多個字段需要選項,可以把這些選項分類放在同一個可迭代對象中,如
MEDIA_CHOICES = (('Audio', (('vinyl', 'Vinyl'),('cd', 'CD'),)),('Video', (('vhs', 'VHS Tape'),('dvd', 'DVD'),)),('unknown', 'Unknown'),
)
default
指定默認值,可以是默認字符串也可以是函數返回值
db_column
用于此字段的數據庫列的名稱。 如果沒有給出,Django將使用該字段的名稱。
db_index
如果為True,則將為此字段創建數據庫索引。
db_tablespace
如果此字段已編制索引,則用于此字段索引的數據庫表空間的名稱。 默認值為項目的DEFAULT_INDEX_TABLESPACE設置(如果已設置)或模型的db_tablespace(如果有)。 如果后端不支持索引的表空間,則忽略此選項。
editable
如果為False,則該字段不會顯示在admin或任何其他ModelForm中。 在模型驗證期間也會跳過它們。 默認為True。
error_messages
error_messages參數允許您覆蓋字段將引發的默認消息。傳入一個字典,其中的關鍵字與您要覆蓋的錯誤消息相匹配。
在 表單字段 級別或者 表單 Meta 級別定義的錯誤信息優先級總是高于在 模型字段 級別定義的。
在 模型字段 上定義的錯誤信息只有在 模型驗證 步驟引發 ValidationError 時才會使用,并且沒有在表單級定義相應的錯誤信息。
您可以通過添加 NON_FIELD_ERRORS 鍵到 ModelForm 內部的 Meta 類的 error_messages 中來覆蓋模型驗證引發的 NON_FIELD_ERRORS 錯誤信息。
help_text
使用表單小部件顯示的額外“幫助”文本。 即使您的字段未在表單上使用,它也對文檔很有用。
請注意,此值不會在自動生成的表單中進行HTML轉義。 如果您愿意,這可以讓您在help_text中包含HTML。 例如:
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
primary_key
如果為True,則此字段將作為Model的主鍵
unique
如果需要設置該字段值唯一,需要把unique設置為True,如果嘗試保存一個已經存在的值,將會引發django.db.IntegrityError異常,我們需要捕捉這個異常,用于諸如用戶名等方面
總結
常用的主要這幾個
- blank:允許為空
- choices:提供選擇
- default:提供默認值
- db_column:提供數據表列名
- unique:設置是否唯一
2 關系字段
關系也是一種字段,只是這個字段的值是別的某個或多個Model,只有三種關系字段 ForeignKey,ManyToManyField 和 OneToOneField
2.1 ForeignKey
這是一種多對一的關系。 需要兩個參數:要關聯的模型類和on_delete選項。
如果要創建遞歸關系 ( 與自身具有多對一關系的對象 )請使用
models.ForeignKey('self',on_delete = models.CASCADE)。
如果需要在尚未定義的模型上創建關系,可以使用模型的名稱,而不是模型對象本身:
class Diary(models.Model):Ordinary_user = models.ForeignKey('Demo2',on_delete=models.CASCADE,default="")class Demo2(models.Model):email: str = models.EmailField(blank=True,db_column="郵箱",default="")Tel_Num = models.CharField(max_length=20,blank=True,db_column="聯系電話",default="")address: str = models.CharField(max_length=100,blank=True,db_column="住址",default="")ID_card = models.CharField(max_length=20, db_column="身份證號",default="")
這樣,我們建立了兩張表,Diary表中Ordinary_user字段與Demo2是一個多對一的關系,先看一下兩張表在MySQL數據庫中的樣子
先是diary表,同樣請忽略其他字段,表名是app名_類名
[外鏈圖片轉存失敗(img-j7xvKUZD-1567244122568)(…/image/DjangoModel_07.png)]
其次是Demo2表
[外鏈圖片轉存失敗(img-uHS783Pt-1567244122571)(…/image/DjangoModel_08.png)]
發現Django把ForeignKey字段的列名命名為目標Model名_id
這里之所以是_id
是應為id是這個表的主鍵,而里面存儲的也是對應的id序號,就是通過保存主鍵的方式實現一對多的。
然后再看一下再admin界面的樣子把
[外鏈圖片轉存失敗(img-oGREHt43-1567244122573)(…/image/DjangoModel_09.png)]
點加號會打開一個新窗口讓你添加Dome的數據,應該挺方便
[外鏈圖片轉存失敗(img-BcLglQBH-1567244122575)(…/image/DjangoModel_10.png)]
on_delete
這是必選參數,當刪除ForeignKey引用的對象時,Django將模擬on_delete參數指定的SQL約束的行為。總共有六種選擇
CASCADE,PROTECT,SET_NULL,SET_DEFAULT,SET(),DO_NOTHING
- CASCADE:級聯刪除。 Django模擬SQL約束ON DELETE CASCADE的行為,并刪除包含ForeignKey的對象。
- PROTECT:通過引發ProtectedError(django.db.IntegrityError的子類)來防止刪除引用的對象。
- SET_NULL:將ForeignKey設置為null; 這只有在null為True時才有可能
- SET_DEFAULT:將ForeignKey設置為其默認值; 必須設置ForeignKey的默認值。
- SET():將ForeignKey設置為傳遞給SET()的值,或者如果傳入了回調函數,則調用它的結果。 在大多數情況下,為了避免在導入models.py時執行查詢,必須傳遞一個回調函數。
- DO_NOTHING:不采取行動。 如果數據庫后端強制實施參照完整性,則除非您手動將SQL ON DELETE約束添加到數據庫字段,否則將導致IntegrityError。
2.2 ManyToManyField
多對多的關系。 只有一個必須參數:與模型相關的類,它與ForeignKey的工作方式完全相同,包括遞歸和惰性關系。
2.2.1 參數
(1).symmetrical
只有在和自己建立多對多關系時才有效,為true時建立的是對稱關系,反之為False建立非對稱關系,默認為True,例如:
class Demo2(models.Model):friends = models.ManyToManyField("self",symmetrical=False,)
默認為true的情況下,如果建立A時指定friend為B,C,那么在B,C中,A也會被申明為自己的朋友,反之則不會
(2).through
Django將自動生成一個表來管理多對多關系。 但是,如果要手動指定中間表,可以使用through選項指定表示要使用的中間表的Django模型。
此選項最常見的用途是,您希望將額外數據與多對多關系相關聯。
如果未指定顯式直通模型,則仍可使用隱式直通模型類來直接訪問為保持關聯而創建的表。 它有三個字段來鏈接模型。
如果源模型和目標模型不同,表中的字段如下:
id
: 主鍵<containing_model>_id
: 申明多對多關系的模型id<other_model>_id
: 指向的模型id
[外鏈圖片轉存失敗(img-A9IklSnF-1567244122579)(…/image/DjangoModel_11.png)]
如果原模型與目標Model相同(自己與自己建立多對多關系),表中的字段如下:
id
:關系的主鍵。from_ <model> _id
:源實例to_ <model> _id
:目標模型實例
[外鏈圖片轉存失敗(img-pRlQ5pOm-1567244122581)(…/image/DjangoModel_12.png)]
(3).through_fields
僅在指定自定義中間模型時使用。 Django通常會確定要使用哪個中間模型字段,以便自動建立多對多關系。 但是,請考慮以下情況:
from django.db import modelsclass Person(models.Model):name = models.CharField(max_length=50)class Group(models.Model):name = models.CharField(max_length=128)members = models.ManyToManyField(Person,through='Membership',through_fields=('group', 'person'),)class Membership(models.Model):group = models.ForeignKey(Group, on_delete=models.CASCADE)person = models.ForeignKey(Person, on_delete=models.CASCADE)inviter = models.ForeignKey(Person,on_delete=models.CASCADE,related_name="membership_invites",)invite_reason = models.CharField(max_length=64)
Membership有兩個外鍵(person和inviter),這使得關系模糊不清,Django無法知道使用哪一個。 在這種情況下,您必須使用through_fields明確指定Django應使用哪些外鍵,如上例所示。
through_fields接受2元組(‘field1’,‘field2’),其中field1是定義ManyToManyField的模型的外鍵名稱(在本例中為group),field2是外鍵的名稱。 目標模型(本例中的person)。
如果中間模型上有多個外鍵到參與多對多關系的任何(甚至兩個)模型,則必須指定through_fields。 這也適用于使用中間模型時的遞歸關系,并且模型有兩個以上的外鍵,或者您想要明確指定Django應該使用哪兩個。
使用中間模型的遞歸關系總是被定義為非對稱的 - 也就是說,
symmetrical=False
因此,存在“源”和“目標”的概念。 在這種情況下,'field1’將被視為關系的“源”,'field2’將被視為“目標”。
2.3 OneToOneField
一對一的關系。 從概念上講,這類似于具有unique = True的ForeignKey,但關系的“反向”一側將直接返回單個對象。
這作為模型的主鍵是最有用的,它以某種方式“擴展”另一個模型; 例如,通過從子模型向父模型添加隱式一對一關系來實現多表繼承。
需要一個位置參數:與模型相關的類。 這與ForeignKey完全相同,包括有關遞歸和惰性關系的所有選項。
如果沒有為OneToOneField指定related_name參數,Django將使用當前模型的小寫名稱作為默認值。
3. Meta
用來定義元數據(除字段以外的別的數據),常見的元數據:
- app_label:如果你定義的Model不在app目錄下的models.py中,就需要指定該字段為app名稱
- db_table:自定義數據表名,如果不指定,默認用
app名_model名
- db_tablespace:指定某些數據庫的表空間
- ordering:指定排序集
- verbose_name:可讀的名字
- verbose_name_plural:Model的復數形式