天天看點

Python進階篇(二)-- Django 深入模型

        上一節提到了Django是基于MVC架構的Web架構,MVC架構追求的是“模型”和“視圖”的解耦合。所謂“模型”說得更直白一些就是資料(的表示),是以通常也被稱作“資料模型”。在實際的項目中,資料模型通常通過資料庫實作持久化操作,而關系型資料庫在過去和當下都是持久化的首選方案,下面我們通過完成一個投票項目來講解和模型相關的知識點。投票項目的首頁會展示某線上教育平台所有的學科;點選學科可以檢視到該學科的老師及其資訊;使用者登入後在檢視老師的頁面為老師投票,可以投贊成票和反對票;未登入的使用者可以通過登入頁進行登入;尚未注冊的使用者可以通過注冊頁輸入個人資訊進行注冊。在這個項目中,我們使用MySQL資料庫來實作資料持久化操作。

1 ORM模型

1.1 ORM介紹

對象關系映射(Object Relational Mapping,簡稱ORM)模式是一種為了解決面向對象與關系資料庫存在的互不比對的現象的技術。

        簡單的說,ORM 是通過使用描述對象和資料庫之間映射的中繼資料,将程式中的對象自動持久化到關系資料庫中。ORM 在業務邏輯層和資料庫層之間充當了橋梁的作用。ORM 解決的主要問題是對象和關系的映射。它通常把一個類和一個表一一對應,類的每個執行個體對應表中的一條記錄,類的每個屬性對應表中的每個字段,具體如下圖所示。ORM 提供了對資料庫的映射,不用直接編寫 SQL 代碼,隻需像操作對象一樣從資料庫操作資料。讓軟體開發人員專注于業務邏輯的處理,提高了開發效率。

Python進階篇(二)-- Django 深入模型

        ORM 模式也是有一定缺點的,它會在一定程度上犧牲程式的執行效率。此外,還存在許多複雜場景是 ORM 模式無法解決的,同樣還是需要手動編寫 SQL 語句完成。

1.2 建立 Django 項目

        首先建立Django項目vote,在項目下建立名為polls的應用和儲存模闆頁的檔案夾tempaltes,項目檔案夾的結構如下所示。

Python進階篇(二)-- Django 深入模型
Python進階篇(二)-- Django 深入模型

        根據上面描述的項目需求,這裡準備了四個靜态頁面,分别是展示學科的頁面

subjects.html

,顯示學科老師的頁面

teachers.html

,登入頁面

login.html

,注冊頁面

register.html

,稍後我們會将靜态頁修改為Django項目所需的模闆頁。

1.3 資料庫中生成模型表

        在 Django 中,一個模型(model)會映射到一個資料庫表。每個模型都是一個 Python 類,它是

django.db.models.Model

的子類,模型的每個屬性都代表一個資料庫字段。

(1) 在 polls 中添加資料模型

        在 polls 的

models.py

中添加如下代碼:

from django.db import models        # 引入Django.db.models子產品


class Subject(models.Model):
    """
    編寫Subject模型類,資料模型應該繼承于models.Model或其子類
    """
    no = models.AutoField(primary_key=True, verbose_name='編号')
    name = models.CharField(max_length=50, verbose_name='名稱')
    intro = models.CharField(max_length=1000, verbose_name='介紹')
    is_hot = models.BooleanField(verbose_name='是否熱門')

    def __str__(self):
        return self.name

    class Meta:
        # 通過db_table自定義資料表名
        db_table = 'tb_subject'


class Teacher(models.Model):
    """
    編寫Teacher模型類,資料模型應該繼承于models.Model或其子類
    """
    sex_choices = (
        (0, '女'),
        (1, '男'),
    )
    no = models.AutoField(primary_key=True, verbose_name='編号')
    name = models.CharField(max_length=20, verbose_name='姓名')
    sex = models.BooleanField(default=True, verbose_name='性别', choices=sex_choices)
    birth = models.DateField(verbose_name='出生日期')
    intro = models.CharField(max_length=1000, verbose_name='個人介紹')
    photo = models.ImageField(max_length=255, verbose_name='照片')
    gcount = models.IntegerField(default=0, db_column='gcount', verbose_name='好評數')
    bcount = models.IntegerField(default=0, db_column='bcount', verbose_name='差評數')
    sno = models.ForeignKey(Subject, on_delete=models.CASCADE, db_column='sno')

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'tb_teacher'
           

        Subject 和 Teacher 模型中的每一個屬性都指明了models下面的一個資料類型,代表了資料庫中的一個字段。上面的類在資料庫中會建立如下的表,見1.3節的第二步建立表格的過程。

(2) 遷移模型

        使用 Django 給我們提供的兩個指令來在資料庫中生成 polls 應用下定義的資料模型,第一步生成遷移檔案,第二步将遷移檔案應用到資料庫:

python manage.py makemigrations
python manage.py migrate
           

1.3 自動生成資料模型

1. 配置關系型資料庫MySQL

(1) 在MySQL中建立資料庫,建立使用者,授權使用者通路該資料庫。

create database vote default charset utf8;
create user 'username'@'%' identified by 'yourpassword';
grant all privileges on vote.* to 'username'@'%';
flush privileges;
           

(2) 在 MySQL 中建立儲存學科和老師資訊的二維表(儲存使用者資訊的表稍後處理)。

use vote;

-- 建立學科表
create table `tb_subject`
(
	`no` integer auto_increment comment '學科編号',
    `name` varchar(50) not null comment '學科名稱',
    `intro` varchar(1000) not null default '' comment '學科介紹',
    `is_hot` boolean not null default 0 comment '是不是熱門學科',
    primary key (`no`)
);
-- 建立老師表
create table `tb_teacher`
(
    `no` integer auto_increment comment '老師編号',
    `name` varchar(20) not null comment '老師姓名',
    `sex` boolean not null default 1 comment '老師性别',
    `birth` date not null comment '出生日期',
    `intro` varchar(1000) not null default '' comment '老師介紹',
    `photo` varchar(255) not null default '' comment '老師照片',
    `gcount` integer not null default 0 comment '好評數',
    `bcount` integer not null default 0 comment '差評數',
    `sno` integer not null comment '所屬學科',
    primary key (`no`),
    foreign key (`sno`) references `tb_subject` (`no`)
);
           

(3) 安裝資料庫的驅動,Python 3.x 使用 pymysql 作為 MySQL的驅動,然後在Django項目檔案夾的

__init__.py

中添加如下所示的代碼:

import pymysql
pymysql.install_as_MySQLdb()        # 為了pymysql發揮最大資料庫操作性能
           

         溫馨提示: 如果使用Django 2.2及以上版本,還會遇到PyMySQL跟Django架構的相容性問題,相容性問題會導緻項目無法運作,需要按照GitHub上PyMySQL倉庫Issues中提供的方法進行處理。總體來說,使用pymysql會比較麻煩,強烈建議大家首選安裝mysqlclient,mysqlclient執行效率也比較高。

(4) 修改項目的settings.py檔案,首先将我們建立的應用polls添加已安裝的項目(INSTALLED_APPS)中,然後配置MySQL作為持久化方案。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',     # 把應用檔案加入
]

ATABASES = {
    'default': {
        # 資料庫引擎配置
        'ENGINE': 'django.db.backends.mysql',
        # 資料庫的名字
        'NAME': 'vote',
        # 資料庫伺服器的IP位址(本機可以寫為localhost或127.0.0.1)
        'HOST': 'localhost',
        # 啟動MySQL服務的端口号
        'PORT': 3306,
        # 資料庫使用者名和密碼
        'USER': 'carpediem',
        'PASSWORD': 'carpediem2021',
        # 資料庫使用的字元集
        'CHARSET': 'utf8',
        # 資料庫時間日期的時區設定
        'TIME_ZONE': 'Asia/Chongqing',
    }
}
           

        在配置ENGINE屬性時,常用的可選值包括:

  • 'django.db.backends.sqlite3'

    :SQLite嵌入式資料庫。
  • 'django.db.backends.postgresql'

    :BSD許可證下發行的開源關系型資料庫産品。
  • 'django.db.backends.mysql'

    :甲骨文公司經濟高效的資料庫産品。
  • 'django.db.backends.oracle'

    :甲骨文公司關系型資料庫旗艦産品。

        其他的配置可以參考官方文檔中的資料庫配置部分。

(5) Django架構提供了ORM來解決資料持久化問題,ORM翻譯成中文叫“對象關系映射”。因為Python是面向對象的程式設計語言,我們在Python程式中使用對象模型來儲存資料,而關系型資料庫使用關系模型,用二維表來儲存資料,這兩種模型并不比對。使用ORM是為了實作對象模型到關系模型的雙向轉換,這樣就不用在Python代碼中書寫SQL語句和遊标操作,因為這些都會由ORM自動完成。利用Django的ORM,我們可以直接将剛才建立的學科表和老師表變成Django中的模型類。

python manage.py inspectdb > polls/models.py
           

我們可以對自動生成的模型類稍作調整,代碼如下所示。

from django.db import models


class Subject(models.Model):
    no = models.AutoField(primary_key=True, verbose_name='編号')
    name = models.CharField(max_length=50, verbose_name='名稱')
    intro = models.CharField(max_length=1000, verbose_name='介紹')
    is_hot = models.BooleanField(verbose_name='是否熱門')

    class Meta:
        managed = False
        db_table = 'tb_subject'


class Teacher(models.Model):
    no = models.AutoField(primary_key=True, verbose_name='編号')
    name = models.CharField(max_length=20, verbose_name='姓名')
    sex = models.BooleanField(default=True, verbose_name='性别')
    birth = models.DateField(verbose_name='出生日期')
    intro = models.CharField(max_length=1000, verbose_name='個人介紹')
    photo = models.ImageField(max_length=255, verbose_name='照片')
    gcount = models.IntegerField(default=0, db_column='gcount', verbose_name='好評數')
    bcount = models.IntegerField(default=0, db_column='bcount', verbose_name='差評數')
    sno = models.ForeignKey(Subject, models.DO_NOTHING, db_column='sno')

    class Meta:
        managed = False
        db_table = 'tb_teacher'
           

        若你的确想要允許 Django 管理這些表格的生命周期,你需要将上面的 managed 選項的值改為 True (或者删掉它,因為 True 是預設值)

         溫馨提示: 所有模型都是

django.db.models.Model

類的子類,模型類跟關系型資料庫的二維表對應,模型對象跟表中的記錄對應,模型對象的屬性跟表中的字段對應。每個字段由

django.db.models.Field

子類(内置在Django core)的執行個體表示,它們并将被轉換為資料庫的列。

        該功能僅是一個快捷方式,不是最佳的建立模型的方法。參考 inspectdb 文檔 擷取更多資訊

補充:

1. 通用字段屬性

選項 說明

null

資料庫中對應的字段是否允許為

NULL

,預設為

False

blank

背景模型管理驗證資料時,是否允許為

NULL

,預設為

False

choices

設定字段的選項,各元組中的第一個值是設定在模型上的值,第二值是人類可讀的值

db_column

字段對應到資料庫表中的列名,未指定時直接使用字段的名稱

db_index

設定為

True

時将在該字段建立索引

db_tablespace

為有索引的字段設定使用的表空間,預設為

DEFAULT_INDEX_TABLESPACE

default

字段的預設值

editable

字段在背景模型管理或

ModelForm

中是否顯示,預設為

True

error_messages

設定字段抛出異常時的預設消息的字典,其中的鍵包括

null

blank

invalid

invalid_choice

unique

unique_for_date

help_text

表單小元件旁邊顯示的額外的幫助文本。

primary_key

将字段指定為模型的主鍵,未指定時會自動添加

AutoField

用于主鍵,隻讀。

unique

設定為

True

時,表中字段的值必須是唯一的

verbose_name

字段在背景模型管理顯示的名稱,未指定時使用字段的名稱

2.

ForeignKey

屬性

  1. limit_choices_to

    :值是一個Q對象或傳回一個Q對象,用于限制背景顯示哪些對象。
  2. related_name

    :用于擷取關聯對象的關聯管理器對象(反向查詢),如果不允許反向,該屬性應該被設定為

    '+'

    ,或者以

    '+'

    結尾。
  3. to_field

    :指定關聯的字段,預設關聯對象的主鍵字段。
  4. db_constraint

    :是否為外鍵建立限制,預設值為

    True

  5. on_delete

    :外鍵關聯的對象被删除時對應的動作,可取的值包括

    django.db.models

    中定義的:
    • CASCADE

      :級聯删除。
    • PROTECT

      :抛出

      ProtectedError

      異常,阻止删除引用的對象。
    • SET_NULL

      :把外鍵設定為

      null

      ,當

      null

      屬性被設定為

      True

      時才能這麼做。
    • SET_DEFAULT

      :把外鍵設定為預設值,提供了預設值才能這麼做。

3.

ManyToManyField

屬性

  1. symmetrical

    :是否建立對稱的多對多關系。
  2. through

    :指定維持多對多關系的中間表的Django模型。
  3. throughfields

    :定義了中間模型時可以指定建立多對多關系的字段。
  4. db_table

    :指定維持多對多關系的中間表的表名。

4. 模型中繼資料選項

選項 說明

abstract

設定為True時模型是抽象父類

app_label

如果定義模型的應用不在INSTALLED_APPS中可以用該屬性指定

db_table

模型使用的資料表名稱

db_tablespace

模型使用的資料表空間

default_related_name

關聯對象回指這個模型時預設使用的名稱,預設為<model_name>_set

get_latest_by

模型中可排序字段的名稱。

managed

設定為True時,Django在遷移中建立資料表并在執行flush管理指令時把表移除

order_with_respect_to

标記對象為可排序的

ordering

對象的預設排序

permissions

建立對象時寫入權限表的額外權限

default_permissions

預設為

('add', 'change', 'delete')

unique_together

設定組合在一起時必須獨一無二的字段名

index_together

設定一起建立索引的多個字段名

verbose_name

為對象設定人類可讀的名稱

verbose_name_plural

設定對象的複數名稱

2 使用ORM完成模型的CRUD操作

2.1 基本的增删改查

        有了Django架構的ORM,我們可以直接使用面向對象的方式來實作對資料的CRUD(增删改查)操作。我們可以在PyCharm的終端中輸入下面的指令進入到Django項目的互動式環境,然後嘗試對模型的操作。

python manage.py shell
           

1. 新增

from polls.models import Subject

Subject.objects.create(name='H5前端開發', intro='前段比較熱的學科', is_hot=True)

subject1 = Subject(name='Python全棧開發', intro='當下最熱門的學科', is_hot=True)
subject1.save()
subject2 = Subject(name='全棧軟體測試', intro='學習自動化測試的學科', is_hot=False)
subject2.save()
subject3 = Subject(name='JavaEE分布式開發', intro='基于Java語言的伺服器應用開發', is_hot=True)
           

2. 删除

subject = Subject.objects.get(no=2)
subject.delete()
           

3. 更新

subject = Subject.objects.get(no=1)
subject.name = 'Python全棧+人工智能'
subject.save()
           

4. 查詢

(1) 查詢所有對象

(2) 過濾資料

# 查詢名稱為“Python全棧+人工智能”的學科
Subject.objects.filter(name='Python全棧+人工智能')

# 查詢名稱包含“全棧”的學科(模糊查詢)
Subject.objects.filter(name__contains='全棧')
Subject.objects.filter(name__startswith='全棧')
Subject.objects.filter(name__endswith='全棧')

# 查詢所有熱門學科
Subject.objects.filter(is_hot=True)

# 查詢編号大于3小于10的學科
Subject.objects.filter(no__gt=3).filter(no__lt=10)
Subject.objects.filter(no__gt=3, no__lt=10)

# 查詢編号在3到7之間的學科
Subject.objects.filter(no__gte=3, no__lte=7)
Subject.objects.filter(no__range=(3, 7))
           

(3) 查詢單個對象

# 查詢主鍵為1的學科
Subject.objects.get(pk=1)
Subject.objects.get(no=1)
Subject.objects.filter(no=1).first()
Subject.objects.filter(no=1).last()
           

(4) 排序

# 查詢所有學科按編号升序排列
Subject.objects.order_by('no')
# 查詢所有部門按部門編号降序排列
Subject.objects.order_by('-no')
           

(5) 切片

# 按編号從小到大查詢前3個學科
Subject.objects.order_by('no')[:3]
           

(6) 計數

# 查詢一共有多少個學科
Subject.objects.count()
           

(7) 進階查詢

# 查詢編号為1的學科的老師
Teacher.objects.filter(sno__no=1)
Subject.objects.get(pk=1).teacher_set.all() 

# 查詢學科名稱有“全棧”二字的學科的老師
Teacher.objects.filter(sno__name__contains='全棧') 
           

        上面的 objects 是一個特殊的屬性,通過它來查詢資料庫,它是模型的一個 Manager。在 filter() 方法中還有一些比較神奇的雙下劃線輔助我們進一步過濾結果,詳細了解,請閱讀官方文檔————執行查詢

溫馨提示:

  • 說明1:由于老師與學科之間存在多對一外鍵關聯,是以能通過學科反向查詢到該學科的老師(從一對多關系中“一”的一方查詢“多”的一方),反向查詢屬性預設的名字是類名小寫_set(如上面例子中的teacher_set),當然也可以在建立模型時通過ForeingKey的related_name屬性指定反向查詢屬性的名字。如果不希望執行反向查詢可以将related_name屬性設定為’+‘或者以’+'開頭的字元串。
  • 說明2:ORM查詢多個對象時會傳回QuerySet對象,QuerySet使用了惰性查詢,即在建立QuerySet對象的過程中不涉及任何資料庫活動,等真正用到對象時(對QuerySet求值)才向資料庫發送SQL語句并擷取對應的結果,這一點在實際開發中需要引起注意!
  • 說明3:如果希望更新多條資料,不用先逐一擷取模型對象再修改對象屬性,可以直接使用QuerySet對象的

    update()

    方法一次性更新多條資料。

參考

  • Django 使用原生的 SQL 語句操作 MySQL 資料庫:https://www.imooc.com/wiki/djangolesson/nativesql.html
  • 深入模型:https://gitee.com/zengyujin/Python-100-Days/blob/master/Day41-55/42.深入模型.md
  • Django入門指南-第5章:模型設計:https://www.bookstack.cn/read/django-beginners-guide-zh/Fundamentals-2.md
  • Django v4.0 中文文檔 模型:https://www.bookstack.cn/read/Django-4.0-zh/94d959954f3d0daa.md