Python Web 開發(fā)之 Django Models 詳解

Django 是由 Python 語言編寫的基于 MVC(即 Model View Controller)架構(gòu)的 Web 開發(fā)框架轴踱。
其架構(gòu)中的模型(Model)主要負責處理 Web 應用的數(shù)據(jù)邏輯部分棺聊,包括定義數(shù)據(jù)存儲單位(即數(shù)據(jù)庫表)的字段屬性和行為掸掸、與數(shù)據(jù)庫交互以及其他相關(guān)聯(lián)的操作盆犁。
通常一個模型映射于一個特定的數(shù)據(jù)庫表幽七。

Django 中的模型有以下幾個基本屬性:

  • 每個模型都是繼承自 django.db.models.Model 類的子類
  • 模型類的屬性分別對應于與之相關(guān)聯(lián)的數(shù)據(jù)表中的字段
  • Django 會自動生成用于訪問數(shù)據(jù)庫的 API

一谬运、基本使用

項目初始化

在開始編寫 Web 應用代碼之前爽冕,需要先使用如下命令初始化一個 Django 項目并創(chuàng)建應用:

$ django-admin startproject myproject
$ cd myproject
$ python manage.py startapp myapp

最終生成的項目目錄結(jié)構(gòu)如下:

myproject
    ├─manage.py
    │
    ├─myapp
    │  ├─admin.py
    │  ├─apps.py
    │  ├─models.py
    │  ├─tests.py
    │  ├─views.py
    │  └─migrations
    │
    └─myproject
        ├─settings.py
        ├─urls.py
        └─wsgi.py
定義模型

用于定義模型的代碼通常保存在 myproject/myapp/models.py 文件中仇祭。
下面的代碼即定義了一個簡單的 Person 模型:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

其中的 first_namelast_name 兩個類屬性即對應于數(shù)據(jù)庫表的兩個字段。
Person 模型會以如下的 SQL 語句創(chuàng)建與之關(guān)聯(lián)的數(shù)據(jù)庫表(id 字段默認會自動添加):

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

為了使模型生效颈畸,還需要將 myapp 包含進 settings.py 配置文件中的 INSTALLED_APPS乌奇,編輯 myproject/myproject/settings.py 文件没讲,內(nèi)容如下:

INSTALLED_APPS = [
    #...
    'myapp',
    #...
]

之后即可使用 python manage.py makemigrations myapp 創(chuàng)建數(shù)據(jù)庫遷移文件;
再運行 python manage.py migrate 命令將模型中定義的表結(jié)構(gòu)遷移至數(shù)據(jù)庫中礁苗。

Django Shell 測試

完成數(shù)據(jù)庫遷移后爬凑,可使用 python manage.py shell 命令進入 Django Shell 交互式命令行,通過 Django 提供的模型 API 進行測試(插入數(shù)據(jù)):

>>> from myapp.models import Person
>>> john = Person(first_name='John', last_name='Smith')
>>> john.save()
>>> Person.objects.all()
<QuerySet [<Person: Person object (1)>]>
>>> john.first_name
'John'

訪問 sqlite3 數(shù)據(jù)庫查詢最終結(jié)果试伙,John Smith 已添加至數(shù)據(jù)表中:

>>> import sqlite3
>>> conn = sqlite3.connect('db.sqlite3')
>>> cursor = conn.cursor()
>>> cursor.execute('select * from myapp_person')
<sqlite3.Cursor object at 0x0000022C36ADBD50>
>>> print(cursor.fetchone())
(1, 'John', 'Smith')

二嘁信、字段(Field)

模型中最重要的也是唯一必須存在的項目就是字段,它由模型類的屬性定義疏叨,用來表述與模型相關(guān)聯(lián)的數(shù)據(jù)表的結(jié)構(gòu)潘靖。

字段的類型與選項

模型中的每個字段都是 django.db.models.Field 類的實例,對應于數(shù)據(jù)庫表中的列蚤蔓。
Django 內(nèi)置了大量的字段類型卦溢,如 CharFieldTextFieldDateTimeField 等秀又。具體可查看 模型字段參考单寂。

每個字段都可以接收特定的字段相關(guān)的參數(shù),比如 CharField 需要傳入 max_length 用于定義 VARCHAR 類型的字符長度吐辙。

此外還有一些通用的可選的字段選項宣决。如:

  • null:如為 True,則 Django 會將空值在數(shù)據(jù)庫中存為 NULL袱讹。該選項默認為 False疲扎。
  • blank:如為 True昵时,則該字段允許為空捷雕。與 null 選項不同,blank 是與表單驗證相關(guān)的壹甥,而 null 是數(shù)據(jù)庫相關(guān)的救巷。
  • default:用于設置字段的默認值。
  • primary_key:用于設置模型的主鍵句柠。如未指定任何字段為主鍵浦译,則 Django 會自動添加 IntegerField 字段作為主鍵。
  • unique:設置字段的值是否允許重復溯职。

PS:默認情況下精盅,Django 會給每個模型添加如下字段
id = models.AutoField(primary_key=True)
作為為自增的主鍵。如果想覆蓋此默認行為谜酒,直接手動指定其他字段為主鍵(primary_key=True)即可叹俏。

關(guān)系

額,關(guān)系型數(shù)據(jù)庫的強大之處即在于各數(shù)據(jù)庫表之間的相互關(guān)聯(lián)僻族。Django 支持定義三種最常見的數(shù)據(jù)庫關(guān)系:多對一粘驰、多對多和一對一。

可以通過 django.db.models.ForeignKey 創(chuàng)建多對一關(guān)系,只需要像定義其他字段那樣將它作為類屬性引入即可先舷。如:

from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=20)
    location = models.CharField(max_length=40)

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    price = models.IntegerField()

運行 python manage.py makemigrations 命令創(chuàng)建數(shù)據(jù)庫遷移文件:

$ python manage.py makemigrations myapp
Migrations for 'myapp':
  myapp/migrations/0001_initial.py
    - Create model Manufacturer
    - Create model Car

使用 python manage.py sqlmigrate myapp 0001 命令查看具體會執(zhí)行哪些 SQL 語句(基于 sqlite3):

$ python manage.py sqlmigrate myapp 0001
BEGIN;
--
-- Create model Manufacturer
--
CREATE TABLE "myapp_manufacturer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL, "location" varchar(40) NOT NULL);
--
-- Create model Car
--
CREATE TABLE "myapp_car" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "price" integer NOT NULL, "manufacturer_id" integer NOT NULL REFERENCES "myapp_manufacturer" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "myapp_car_manufacturer_id_2be676ab" ON "myapp_car" ("manufacturer_id");
COMMIT;

多對多和一對一的數(shù)據(jù)庫關(guān)系則分別可以使用 ManyToManyFieldOneToOneField 定義贷祈。

三、模型的屬性與方法

Meta 選項

模型的 Meta 選項在模型類的定義中是可選的顶伞,它基本上包含了除字段以外的所有內(nèi)容饵撑。比如數(shù)據(jù)紀錄的順序(ordering)、關(guān)聯(lián)的數(shù)據(jù)庫表的名稱(db_table)和索引(indexes)等唆貌。

Django 模型支持的所有 Meta 選項可以參考 Model Meta options

示例代碼:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"
自定義模型方法

在模型中創(chuàng)建自定義方法可以為模型對象添加個性化的“底層”功能肄梨。參考如下代碼:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

此時的 Person 模型除了可以從數(shù)據(jù)庫中讀取和寫入數(shù)據(jù)等基本功能外,還可以通過它調(diào)用自定義的 full_name 方法完成額外的需求(返回全名)挠锥。

覆蓋默認的模型方法

有些情況下众羡,還可以通過修改模型內(nèi)置的方法,改變模型與數(shù)據(jù)庫的具體交互方式蓖租。尤其是 save() (向數(shù)據(jù)庫中存入數(shù)據(jù))和 delete() (從數(shù)據(jù)庫中刪除紀錄)等方法粱侣。如:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def save(self, *args, **kwargs):
        self.first_name = self.first_name.capitalize()
        self.last_name = self.last_name.capitalize()
        super().save(*args, **kwargs)

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

重新遷移數(shù)據(jù)庫,進入 Django Shell 測試蓖宦,結(jié)果如下:

>>> from myapp.models import Person
>>> john = Person(first_name='john', last_name='smith')
>>> john.save()
>>> john
<Person: Person object (2)>
>>> john.full_name
'John Smith'

四齐婴、數(shù)據(jù)庫操作

一旦創(chuàng)建了數(shù)據(jù)模型,Django 即會自動生成與數(shù)據(jù)庫交互的 API 供用戶創(chuàng)建稠茂、獲取柠偶、更新和刪除數(shù)據(jù)對象。

此處先創(chuàng)建如下的模型文件供測試使用:

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):
        return self.headline
Insert

可以通過實例化模型類創(chuàng)建一個數(shù)據(jù)對象睬关,并調(diào)用其 save() 方法將對應的記錄插入(執(zhí)行 INSERT SQL 語句)到數(shù)據(jù)庫表中诱担。

>>> from myapp.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news')
>>> b.save()
>>> b.name = 'Beatles Blog All'
>>> b.save()
>>> b
<Blog: Beatles Blog All>
>>> b.tagline
'All the latest Beatles news'

插入 ForeignKey 與 ManyToManyField
更新 ForeignKey 與操作普通字段的方式相同,將正確類型的對象賦值給對應字段并調(diào)用 save() 方法即可电爹。如:

>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

更新 ManyToManyField 的方式稍有不同蔫仙,需要使用 add() 方法:

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
獲取數(shù)據(jù)

從數(shù)據(jù)庫中獲取數(shù)據(jù)會生成一個 QuerySet 對象,它代表從數(shù)據(jù)庫中取出的數(shù)據(jù)對象的集合丐箩。QuerySet 等同于數(shù)據(jù)庫中的 SELECT 語句摇邦,它可以有零個或者多個 filterfilter 對應于數(shù)據(jù)庫中的篩選條件如 WHERELIMIT 等屎勘。

獲取單個對象
>>> one_entry = Entry.objects.get(pk=1)

PS:pkprimary key 施籍。

獲取所有對象
>>> all_entries = Entry.objects.all()

應用篩選器
>>> entry = Entry.objects.filter(pub_date__year=2006)

Limiting
>>> entries = Entry.objects.all()[:5]

排序
>>> entry = Entry.objects.order_by('headline')[0]

字段查詢

字段查詢對應于 SQL 中的 WHERE 語句,可以通過向 QuerySet 對象的方法 filter()概漱、exclude()get() 中傳入特定的參數(shù)來實現(xiàn)丑慎。
基本的查詢參數(shù)語法如下:field__lookuptype=value

如:>>> Entry.objects.filter(pub_date__lte='2006-01-01')
等同于:SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

其中 lteless or equal(小于等于)。其他類似的 lookuptype 還包括 gt(大于)立哑、gte(大于等于)夜惭、lt(小于)、exact铛绰、iexact(忽略大小寫)诈茧、startswithistartswith捂掰、endswith敢会、iendswithcontains这嚣、range(指定范圍)鸥昏、regex(正則表達式)、iregex 等姐帚。

以下為一些常見的使用示例:

  • exact:>>> Entry.objects.get(headline__exact="Cat bites dog")
    等于 SELECT ... WHERE headline = 'Cat bites dog';

  • iexact:>>> Blog.objects.get(name__iexact="beatles blog")
    等于 SELECT ... WHERE name ILIKE 'beatles blog';

  • startswith:>>> Entry.objects.filter(headline__startswith='Lennon')
    等于 SELECT ... WHERE headline LIKE 'Lennon%';

  • contains:>>> Entry.objects.get(headline__contains='Lennon')
    等于 SELECT ... WHERE headline LIKE '%Lennon%';

  • in:>>> Entry.objects.filter(id__in=[1, 3, 4])
    等于 SELECT ... WHERE id IN (1, 3, 4);

  • range:

import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))

等于 SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

Manager

Manager 是提供給 Django 模型吏垮,用于做數(shù)據(jù)庫查詢操作的接口。Django 項目中的每一個模型都需要至少包含一個 Manager 對象罐旗。

默認情況下膳汪,Django 會在每一個模型類中添加一個名為 objects 的 Manager 。通過將 models.Manager() 賦值給除 objects 以外的類屬性九秀,可以覆蓋此默認行為:

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

此時 Person.objects.all() 查詢語句會報出 AttributeError 錯誤遗嗽,而 Person.people.all() 則返回所有的 Person 對象。

自定義 Manager

自定義的 Manager 方法可以向模型中添加表級別的查詢功能鼓蜒。與之對應的紀錄級別的功能則需要使用模型方法痹换。

如:

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

以上面的模型為例,Book.objects.all() 會返回數(shù)據(jù)庫中所有的書籍信息都弹,而 Book.dahl_objects.all() 則會返回所有作者為 Roald Dahl 的書籍娇豫。
Django 允許向模型中添加任意數(shù)量的 Manager() 實例,因此可以用來為模型定義一些通用的篩選器缔杉。如:

class AuthorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role='A')

class EditorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role='E')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
    people = models.Manager()
    authors = AuthorManager()
    editors = EditorManager()

Person.people.all()锤躁、Person.authors.all()Person.editors.all() 都可以作為從模型中獲取數(shù)據(jù)的接口,且 authorseditors 已預先根據(jù) role 對數(shù)據(jù)進行了篩選或详。

參考資料

Django 2.2 官方文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市郭计,隨后出現(xiàn)的幾起案子霸琴,更是在濱河造成了極大的恐慌,老刑警劉巖昭伸,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梧乘,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機选调,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門夹供,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仁堪,你說我怎么就攤上這事哮洽。” “怎么了弦聂?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵鸟辅,是天一觀的道長。 經(jīng)常有香客問我莺葫,道長匪凉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任捺檬,我火速辦了婚禮再层,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘堡纬。我一直安慰自己树绩,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布隐轩。 她就那樣靜靜地躺著饺饭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪职车。 梳的紋絲不亂的頭發(fā)上瘫俊,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音悴灵,去河邊找鬼扛芽。 笑死,一個胖子當著我的面吹牛积瞒,可吹牛的內(nèi)容都是我干的川尖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼茫孔,長吁一口氣:“原來是場噩夢啊……” “哼叮喳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缰贝,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤馍悟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后剩晴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锣咒,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡侵状,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了毅整。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趣兄。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悼嫉,靈堂內(nèi)的尸體忽然破棺而出艇潭,到底是詐尸還是另有隱情,我是刑警寧澤承粤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布暴区,位于F島的核電站,受9級特大地震影響辛臊,放射性物質(zhì)發(fā)生泄漏仙粱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一彻舰、第九天 我趴在偏房一處隱蔽的房頂上張望伐割。 院中可真熱鬧,春花似錦刃唤、人聲如沸隔心。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硬霍。三九已至,卻和暖如春笼裳,著一層夾襖步出監(jiān)牢的瞬間唯卖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工躬柬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拜轨,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓允青,卻偏偏與公主長得像橄碾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颠锉,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容