第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

全部開發(fā)者教程

Django 入門教程

課程導(dǎo)學(xué)
Django 慕課教程使用指南
Django開發(fā)實戰(zhàn)
35 開發(fā)實戰(zhàn)

Django 中 ORM 外鍵使用

外鍵 (Foreign Key)是用于建立和加強(qiáng)兩個表數(shù)據(jù)之間的鏈接的一列或多列。通過將保存表中主鍵值的一列或多列添加到另一個表中,可創(chuàng)建兩個表之間的連接,這個列就成為第二個表的外鍵。外鍵的作用如下:

保持?jǐn)?shù)據(jù)一致性,完整性,主要目的是控制存儲在外鍵表中的數(shù)據(jù)。 使兩張表形成關(guān)聯(lián),就是當(dāng)你對一個表的數(shù)據(jù)進(jìn)行操作,和他有關(guān)聯(lián)的一個或更多表的數(shù)據(jù)能夠同時發(fā)生改變。

外鍵可以是一對一的,一個表的記錄只能與另一個表的一條記錄連接,或者是一對多的,一個表的記錄與另一個表的多條記錄連接。

在 MySQL 種想使用外鍵需要具備一定條件的:

  • MySQL 重需要關(guān)聯(lián)的表必須都使用 InnoDB 引擎創(chuàng)建,MyISAM 表暫時不支持外鍵;
  • 外鍵列必須建立了索引,MySQL 4.1.2 以后的版本在建立外鍵時會自動創(chuàng)建索引,但如果在較早的版本則需要顯式建立;
  • 外鍵關(guān)系的兩個表的列必須是數(shù)據(jù)類型相似,也就是可以相互轉(zhuǎn)換類型的列,比如 int 和 tinyint 可以,而 int和char 則不可以。

最后我們來了解下在 MySQL 中創(chuàng)建外鍵的用法,如下:

[CONSTRAINT symbol] FOREIGN KEY [id] (index_col_name, ...)
REFERENCES tbl_name (index_col_name, ...)
[ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT}]
[ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT}]

該語法可以在 CREATE TABLE 和 ALTER TABLE 時使用,如果不指定 CONSTRAINT symbol,MySQL 會自動生成一個名字。其中 ON DELETE、ON UPDATE 表示事件觸發(fā)限制,可設(shè)參數(shù):

  • RESTRICT:限制外表中的外鍵改動,默認(rèn)值;
  • CASCADE:跟隨外鍵改動;
  • SET NULL:設(shè)空值;
  • SET DEFAULT:設(shè)默認(rèn)值;
  • NO ACTION:無動作,默認(rèn)的。

例如下面的 SQL 語句是由 Django 來幫我們自動生成 nember 和 vip_level 的:

CREATE TABLE `member` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `age` varchar(30) NOT NULL,
  `sex` smallint(6) NOT NULL,
  `occupation` varchar(30) NOT NULL,
  `phone_num` varchar(14) NOT NULL,
  `email` varchar(254) NOT NULL,
  `city` varchar(30) NOT NULL,
  `register_date` datetime(6) NOT NULL,
  `vip_level_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `member_vip_level_id_44ba3146_fk_vip_level_id` (`vip_level_id`),
  CONSTRAINT `member_vip_level_id_44ba3146_fk_vip_level_id` FOREIGN KEY (`vip_level_id`) REFERENCES `vip_level` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

1. Django ORM 中外鍵的使用

為了能演示 ORM 中外鍵的使用,我們在前面的會員 Member 的基礎(chǔ)上新增一個關(guān)聯(lián)表:會員等級表(vip_level)。這個會員等級有 VIP、VVIP 以及超級 VIP 的 VVVIP 三個等級,我們在 models.py 中添加如下模型類,并在會員表中添加對應(yīng)的外鍵字段,連接到會員等級表中:

# hello_app/models.py
# ...

class VIPLevel(models.Model):
    name = models.CharField('會員等級名稱', max_length=20)
    price = models.IntegerField('會員價格,元/月', default=10)
    remark = models.TextField('說明', default="暫無信息")

    def __str__(self):
        return "<%s>" % (self.name)

    class Meta:
        db_table = 'vip_level'
    
class Member(models.Model):
    # ...

    # 添加外鍵字段
    vip_level = models.ForeignKey('VIPLevel', on_delete=models.CASCADE, verbose_name='vip level')

    # ...

# ...

首先,我們需要把前面生成的 Member 表刪除,同時刪除遷移記錄文件,操作如下:

(django-manual) [root@server first_django_app]# pwd
/root/django-manual/first_django_app
# 刪除遷移記錄表
(django-manual) [root@server first_django_app]# rm -f hello_app/migrations/0001_initial.py 

此外,還需要將數(shù)據(jù)庫中的原 member 表、django_migrations 表刪除,即還原到最初狀態(tài)。接下來,我們使用數(shù)據(jù)庫遷移命令:

(django-manual) [root@server first_django_app]# python manage.py makemigrations
Migrations for 'hello_app':
  hello_app/migrations/0001_initial.py
    - Create model VIPLevel
    - Create model Member
(django-manual) [root@server first_django_app]# python manage.py migrate hello_app
Operations to perform:
  Apply all migrations: hello_app
Running migrations:
  Applying hello_app.0001_initial... OK

注意: 如果 migrate 后面不帶應(yīng)用會生成許多 Django 內(nèi)置應(yīng)用的表,比如權(quán)限表、用戶表、Session表等。

圖片描述

生成的 member 表

上面我們可以看到,我們生成的會員表中相比之前對了一個 vip_level_id 字段,這個字段關(guān)聯(lián)的是 vip_level 表的 id 字段?,F(xiàn)在我們首先在 vip_level 中新建三條記錄,分別表示 VIP、VVIP 以及 VVVIP:

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from hello_app.models import VIPLevel
>>> vip = VIPLevel(name='vip', remark='普通vip', price=10)
>>> vip.save()
>>> vvip = VIPLevel(name='vvip', remark='高級vip', price=20)
>>> vvip.save()
>>> vvvip = VIPLevel(name='vvvip', remark='超級vip', price=30)
>>> vvvip.save()
>>> VIPLevel.objects.all()
<QuerySet [<VIPLevel: <vip>>, <VIPLevel: <vvip>>, <VIPLevel: <vvvip>>]>

接下來,我們操作 member 表,生成幾條記錄并關(guān)聯(lián)到 vip_level 表:

>>> from hello_app.models import Member
>>> m1 = Member(name='會員1', age=29, sex=0, occupation='python', phone_num='18054299999', city='guangzhou')
>>> m1.vip_level = vip
>>> m1.save()
>>> m2 = Member(name='會員2', age=30, sex=1, occupation='java', phone_num='18054299991', city='shanghai')
>>> m2.vip_level = vvip
>>> m2.save()
>>> m3 = Member(name='會員3', age=35, sex=0, occupation='c/c++', phone_num='18054299992', city='beijing')
>>> m3.vip_level = vvvip
>>> m3.save()

查看會員表中生成的數(shù)據(jù)如下:

圖片描述

會員表

可以看到,這里我們并沒有直接寫 vip_level_id 值,而是將 Member 的 vip_level 屬性值直接賦值,然后保存。最后 Django 的 ORM 模型在這里會自動幫我們處理這個關(guān)聯(lián)字段的值,找到關(guān)聯(lián)記錄的 id 值,并賦值給該字段。接下來,我們看下外鍵關(guān)聯(lián)的查詢操作:

>>> Member.objects.get(age=29).vip_level
<VIPLevel: <vip>>
>>> type(Member.objects.get(age=29).vip_level)
<class 'hello_app.models.VIPLevel'>

>>> vip = VIPLevel.objects.get(name='vip')
>>> vip.member_set.all()
<QuerySet [<Member: <會員1, 18054299999>>]>
>>> type(vip.member_set)
<class 'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager'>

上面的操作示例中我們給出了關(guān)聯(lián)表 vip_level (往往成為主表) 和 member (往往成為子表) 之間的正向和反向查詢。在 Django 默認(rèn)每個主表都有一個外鍵屬性,這個屬性值為:從表_set,通過這個屬性值我們可以查到對應(yīng)的從表記錄,比如上面的 vip.member_set.all() 語句就是查詢所有 vip 會員。當(dāng)然這個外鍵屬性是可以修改的,我們需要在 member 表中的外鍵字段那里加上一個屬性值:

class Member(models.Model):
    ...

    vip_level = models.ForeignKey('VIPLevel', related_name="new_name", on_delete=models.CASCADE, verbose_name='vip level')

    ...

這樣我們想再次通過主表查詢子表時,就要變成如下方式了:

>>> from hello_app.models import VIPLevel
>>> from hello_app.models import Member
>>> vip = VIPLevel.objects.get(name='vip')
>>> vip.member_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'VIPLevel' object has no attribute 'member_set'
>>> vip.new_name.all()
<QuerySet [<Member: <會員1, 18054299999>>]>
>>>

前面在定義外鍵時,我們添加了一個 on_delete 屬性,這個屬性控制著在刪除子表外鍵連接的記錄時,對應(yīng)字表的記錄會如何處理,它有如下屬性值:

CASCADE:級聯(lián)操作。如果外鍵對應(yīng)的那條記錄被刪除了,那么子表中所有外鍵為那個記錄的數(shù)據(jù)都會被刪除。對于例中,就是如果我們將會員等級 vip 的記錄刪除,那么所有 vip 會員會被一并刪除;

# 前面使用的正是CASCADE
>>> from hello_app.models import VIPLevel
>>> from hello_app.models import Member
>>> VIPLevel.objects.get(name='vip')
<VIPLevel: <vip>>
>>> VIPLevel.objects.get(name='vip').delete()
(2, {'hello_app.Member': 1, 'hello_app.VIPLevel': 1})
>>> Member.objects.all()
<QuerySet [<Member: <會員2, 18054299991>>, <Member: <會員3, 18054299992>>]>

PROTECT:受保護(hù)。即只要子表中有記錄引用了外鍵的那條記錄,那么就不能刪除外鍵的那條記錄。如果我們強(qiáng)行刪除,Django 就會報 ProtectedError 異常;

# 修改外鍵連接的 on_delete 屬性值為 PROTECT
>>> from hello_app.models import VIPLevel
>>> from hello_app.models import Member
>>> VIPLevel.objects.get(name='vvip').delete()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/db/models/base.py", line 918, in delete
    collector.collect([self], keep_parents=keep_parents)
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/db/models/deletion.py", line 224, in collect
    field.remote_field.on_delete(self, field, sub_objs, self.using)
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/db/models/deletion.py", line 22, in PROTECT
    raise ProtectedError(
django.db.models.deletion.ProtectedError: ("Cannot delete some instances of model 'VIPLevel' because they are referenced through a protected foreign key: 'Member.vip_level'", <QuerySet [<Member: <會員2, 18054299991>>]>)

SET_NULL:設(shè)置為空。如果外鍵的那條數(shù)據(jù)被刪除了,那么子表中所有外鍵為該條記錄的對應(yīng)字段值會被設(shè)置為 NULL,前提是要指定這個字段可以為空,否則也會報錯;

# hello_app/models.py
vip_level = models.ForeignKey('VIPLevel', related_name="new_name", on_delete=models.SET_NULL, verbose_name='vip level', null=True)

>>> from hello_app.models import VIPLevel
>>> from hello_app.models import Member
>>> VIPLevel.objects.get(name='vvip').delete()
>>> Member.objects.get(name='會員2').vip_level_id is None
True

注意:注意加上null=True是不夠的,因為數(shù)據(jù)庫在使用遷移命令時候已經(jīng)默認(rèn)是不可為空,這里測試時還需要手動調(diào)整下表 vip_level 字段屬性,允許為 null。

圖片描述

允許 vip_level_id 為 null

SET_DEFAULT:設(shè)置默認(rèn)值。和上面類似,前提是字表的這個字段有默認(rèn)值;

SET():如果外鍵的那條數(shù)據(jù)被刪除了。那么將會獲取SET函數(shù)中的值來作為這個外鍵的值。SET函數(shù)可以接收一個可以調(diào)用的對象(比如函數(shù)或者方法),如果是可以調(diào)用的對象,那么會將這個對象調(diào)用后的結(jié)果作為值返回回去;

# hello_app/models.py

# 新增一個設(shè)置默認(rèn)值函數(shù)
def default_value():
    # 刪除記錄時會調(diào)用,在這里可以做一些動作
    # ...
    # 返回臨時指向一條記錄的id,返回不存在的id時會報錯;返回數(shù)字也會報錯,要注意
    return '4'

# ...
class Member(models.Model):
    # ...
    vip_level = models.ForeignKey('VIPLevel', related_name="new_name", on_delete=models.SET(default_value), verbose_name='vip level', null=True)
    # ...
>>> from hello_app.models import VIPLevel
>>> from hello_app.models import Member
>>> VIPLevel.objetcs.get(name='會員3').vip_level_id
3

# 新建一個臨時過渡vip記錄
>>> tmp_vip=VIPLevel(name='等待升級vip', price=30, remark='臨時升級過渡')
>>> tmp_vip.save()
>>> tmp_vip.id
4

# 刪除vvvip記錄
>>> VIPLevel.objects.all().get(name='vvvip').delete()
(1, {'hello_app.VIPLevel': 1}
 # 可以看到,會員表中曾經(jīng)指向為vvvip的記錄被重新指向了臨時過渡vip
>>> Member.objects.get(name='會員3').vip_level_id
4

DO_NOTHING:什么也不做,你刪除你的,我保留我的,一切全看數(shù)據(jù)庫級別的約束。在 MySQL 中,這種情況下無法執(zhí)行刪除動作。

2. 小結(jié)

本小節(jié)中我們描述了外鍵的相關(guān)概念,然后在 Django 的 shell 模式下使用會員表和會員等級表來進(jìn)行外鍵的操作,重點演示了關(guān)聯(lián)表之間的創(chuàng)建、相互查詢以及刪除等相關(guān)的操作。