4 - django-orm基本使用

1 數據庫與ORM

對象關係映射(英語:(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),是一種程序技術,用於實現面向對象編程語言裏不一樣類型系統的數據之間的轉換 。從效果上說,它實際上是建立了一個可在編程語言裏使用的--「虛擬對象數據庫」。前端

簡單一句話來講:就是把數據庫的表映射爲一個個對象,對對象的操做會被映射成SQL語句,在數據庫執行。
django-ormpython

2 orm的配置

django默認支持sqlite,mysql, oracle,postgresql數據庫。mysql

  • sqllit: django默認使用sqlite的數據庫,默認自帶sqlite的數據庫驅動 , 引擎名稱:django.db.backends.sqlite3
  • mysql: 引擎名稱:django.db.backends.mysql

2.1 引擎和配置

在django的項目中會默認使用sqlite數據庫,在settings裏有以下設置:git

DATABASES = {
    'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

下面是MySQL的配置sql

DATABASES = {
    'default': {
- 'ENGINE': 'django.db.backends.mysql', 
- 'NAME': 'books',    # 你的數據庫名稱
- 'USER': 'root',     # 你的數據庫用戶名
- 'PASSWORD': '',     # 你的數據庫密碼
- 'HOST': '',-  # 你的數據庫主機,留空默認爲localhost
- 'PORT': '3306',     # 你的數據庫端口
    }
}

參數以下:數據庫

  • NAME即數據庫的名字,在mysql鏈接前該數據庫必須已經建立,而上面的sqlite數據庫下的db.sqlite3則是項目自動建立
  • USER和PASSWORD分別是數據庫的用戶名和密碼。

2.2 mysql驅動程序

django可使用以下mysql的驅動程序:django

MySQLdb(不完美支持Python3) 
mysqlclient (官方建議)
PyMySQL(純python的mysql驅動程序)

建議使用:編程

  • mysqlclient
  • PyMySQL

django基礎配置部分已經描述了mysqlclient的安裝,這裏介紹如何使用pymysql,首先須要安裝緩存

pip install pymysql

下一步只須要找到項目名文件下的__init__,在裏面寫入:

import pymysql
pymysql.install_as_MySQLdb()

便可完成配置

3 orm 表模型

咱們所說的ORM主要分爲兩種:

  • DB First 數據庫裏先建立數據庫表結構,根據表結構生成類,根據類操做數據庫
  • Code First 先寫代碼,執行代碼建立數據庫表結構

主流的orm都是code first。django 的orm也是code first,因此本質上分爲兩部分:

  • 根據類自動建立數據庫表
  • 根據類對數據庫表中的數據進行各類操做

3.1 建立表對象

如今有一張表,主要字段以下:
orm-table
對應的models中的映射類爲:

from django.db import models     # 導入models,django提供的對象包含不少建表的方法
 
# Create your models here.
 
class UserInfo(models.Model):    # 須要繼承models.Model
    class Meta:
        db_table = 'userinfo'  # 在數據庫中生成的代表

    id = models.AutoField(primary_key=True,null=False,verbose_name='ID')
    name = models.CharField(max_length=4,null=False,verbose_name='用戶名')
    password = models.CharField(max_length=64,null=False,verbose_name='密碼')
    email = models.EmailField(null=False,verbose_name='郵箱')
 
 
# AutoField : 自增字段,相似於mysql的int字段加auto_increment屬性
# CharField:可變長字段,相似於mysql的varchar類型,須要指定長度
# EmailField:郵件字段,僅僅提供給 django admin進行約束使用,映射到MySQL上,根本上也是字符串類型
# null:是否爲空,通用參數,默認爲否。
# verbose_name:django admin上對錶操做時,顯示的字段名稱
# primary_key:主鍵
# max_length:針對於CharField字段,標示其長度

3.2 Django字段類型

部分字段類型及說明以下:

字段名稱 含義
AutoField(Field) int自增列,必須填入參數 primary_key=True
BigAutoField(AutoField) bigint自增列,必須填入參數 primary_key=True
注:當model中若是沒有自增列,則自動會建立一個列名爲id的列
SmallIntegerField(IntegerField) 小整數 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 正小整數 0 ~ 32767
IntegerField(Field) 整數列(有符號的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 正整數 0 ~ 2147483647
BigIntegerField(IntegerField) 長整型(有符號的) -9223372036854775808 ~ 9223372036854775807
BooleanField(Field) 布爾值類型
NullBooleanField(Field) 能夠爲空的布爾值
CharField(Field) 字符類型,必須提供max_length參數, max_length表示字符長度
TextField(Field) 文本類型
EmailField(CharField) 字符串類型,Django Admin以及ModelForm中提供驗證機制
IPAddressField(Field) 字符串類型,Django Admin以及ModelForm中提供驗證 IPV4 機制
GenericIPAddressField(Field) 字符串類型,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6
URLField(CharField) 字符串類型,Django Admin以及ModelForm中提供驗證 URL
SlugField(CharField) 字符串類型,Django Admin以及ModelForm中提供驗證支持 字母、數字、下劃線、鏈接符(減號)
CommaSeparatedIntegerField(CharField) 字符串類型,格式必須爲逗號分割的數字
UUIDField(Field) 字符串類型,Django Admin以及ModelForm中提供對UUID格式的驗證
FilePathField(Field) 字符串,Django Admin以及ModelForm中提供讀取文件夾下文件的功
FileField(Field) 字符串,路徑保存在數據庫,文件上傳到指定目錄
ImageField(FileField) 字符串,路徑保存在數據庫,文件上傳到指定目錄
DateTimeField(DateField) 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field) 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field) 時間格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field) 長整數,時間間隔,數據庫中按照bigint存儲,ORM中獲取的值爲datetime.timedelta類型
FloatField(Field) 浮點型
DecimalField(Field) 10進制小數
BinaryField(Field) 二進制類型

3.3 經常使用字段參數說明

django中提供了不少參數對字段進行控制

通用類:

屬性 含義
null 是否能夠爲空
default 默認值
primary_key 主鍵
db_column 列名
db_index 索引(bool)
unique 惟一索引

時間日期類:

屬性 含義
unique_for_date 對日期字段來講,表示只對時間作索引
unique_for_month 對日期字段來講,表示只對月份作索引
unique_for_year 對日期字段來講,表示只對年作索引
auto_now 不管是你添加仍是修改對象,時間爲你添加或者修改的時間。
auto_now_add 爲添加時的時間,更新對象時不會有變更。

django admin相關:

屬性 含義
choices 在django admin中顯示下拉框,避免連表查詢(好比用戶類型,能夠在存在內存中)
blank 在django admin中是否能夠爲空
verbose_name 字段在django admin中顯示的名稱
editable 在django admin中是否能夠進行編輯,默認是true
error_message 當在django admin中輸入的信息不匹配時,字段的提示信息
help_text 在django admin中輸入框旁邊進行提示
validators 在django admin中自定義規則限制

django中提供了不少的字段類型,大部分都是提供給django admin 來作驗證的,實際體如今數據庫中的,大部分都是字符串類型。

3.4 特殊類型字段參數說明

GenericIPAddressField(Field):字符串類型,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6
參數:

  • protocol,用於指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
  • unpack_ipv4, 若是指定爲True,則輸入::ffff:192.0.2.1時候,可解析爲192.0.2.1,開啓刺功能,須要protocol="both"

FilePathField(Field):字符串,Django Admin以及ModelForm中提供讀取文件夾下文件的功能
參數:

  • path:文件夾路徑
  • match=None:正則匹配
  • recursive=False:遞歸下面的文件夾
  • allow_files=True:容許文件
  • allow_folders=False:容許文件夾

FileField(Field):字符串,路徑保存在數據庫,文件上傳到指定目錄
參數:

  • upload_to = "":上傳文件的保存路徑
  • storage = None:存儲組件,默認django.core.files.storage.FileSystemStorage

ImageField(FileField):字符串,路徑保存在數據庫,文件上傳到指定目錄
參數:

  • upload_to = "" 上傳文件的保存路徑
  • storage = None 存儲組件,默認django.core.files.storage.FileSystemStorage
  • width_field=None, 上傳圖片的高度保存的數據庫字段名(字符串)
  • height_field=None 上傳圖片的寬度保存的數據庫字段名(字符串)

DecimalField(Field):10進制小數
參數:

  • max_digits,小數總長度
  • decimal_places,小數位長度

3.5 Meta信息

在類內部定義內部類Meta,因爲設置映射的表的元數據信息,

class User(models.Model):
    class Meta:
        db_name = 'user'
    
    id...
    name...

這裏的Meta的類屬性db_name,表示生成的數據庫表名稱爲'user',更多的參數有:

屬性信息 含義
db_tablespace 有些數據庫有數據庫表空間,好比Oracle。你能夠經過db_tablespace來指定這個模型對應的數據庫表放在哪一個數據庫表空間。
managed 默認值爲True,Django能夠對數據庫表進行 migrate或migrations、刪除等操做,若是爲False的時候,不會對數據庫表進行建立、刪除等操做。能夠用於現有表、數據庫視圖等,其餘操做是同樣的。
ordering 告訴Django模型對象返回的記錄結果集是按照哪一個字段排序的
unique_together 設置兩個字段保持惟一性時使用
verbose_name 給模型類起一個更可讀的名字

經常使用的就是db_table,用於指定生成的表的名稱

3.6 生成表

        前面咱們已經編寫了對應數據庫表的類,這裏咱們將進行實例化(建立數據庫對應的表)。利用django提供的命令進行數據庫的初始化工做.(以及其餘對數據庫表進行修改的動做,好比修改表結構,字段屬性等,都須要執行以下步驟)

# 進入項目目錄下執行
python manage.py makemigrations
# 大體含義是:把類轉換成對應格式的sql語句。
 
# 建立表
python manage.py migrate
# 在數據庫中執行生成的sql語句

注意:

  • 執行makemigrations後生成的文件,存放在應用目錄下的migrations目錄下,通常以0001_initial.py爲文件名而且依次遞增。
  • 0001_initial.py存放的是 django 根據咱們寫的 class 生成的相關代碼,執行migrate後,會根據這些代碼生成對應的數據庫表。
  • 若是咱們執行migrate後沒有生成咱們建立的表格,那麼須要確認在django配置文件中,是否加載了咱們的應用(由於django會加載setting裏面的install_app中查找對應的modules.py)
  • 表名默認是:應用名_class類名,能夠在映射類內部定義內部類Meta,設置db_table='表名',來修改在數據庫中生存的表名

3.6.1 註冊app

若是沒有生成對應的表文件,那麼須要在settings.py中,註冊你的app

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    '你的app名稱'       
]

3.6.2 修改表結構遇到的問題

當咱們對已生成的表添加新字段時,會出現以下狀況

ou are trying to add a non-nullable field 'code' to business without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option:

        因爲咱們添加字段會影響以前的數據,因此這時django提醒咱們,不能只是單純的添加一個字段,還須要爲該字段指定對應的值,固然有幾種解決辦法:

  • 設置新增字段default屬性,表示其默認值
  • 設置新增字段null屬性,表示其默承認覺得空
  • 根據上面的提示,選擇1,手動輸入默認值便可(注意默認值須要用引號引發來,否則django認爲輸入的數據是個變量)

4 利用orm完成數據庫的增刪改查

接下來就須要利用orm來對數據庫的表數據進行增刪改查等基本操做了。

4.1 orm之增長

        根據django的MTV架構,業務的處理是在views中完成的,那麼對數據庫的查詢邏輯也應該在views中編寫,而咱們定義的數據庫對象在model.py中,那麼在導入以後,才能進行表操做。

django提供了兩種增長數據的方式:一、create,二、save

#------------------------------------- create -------------------------------------
from app01 import models      # 從本身的應用中導入models模塊
 
username = 'daxin'
password = 'abc.123'
email = 'daixin@qq.com'
models.UserInfo.objects.create(name=username,password=password,email=email)
 
# 表對象.objects.create() 用來新增數據
# name,password,email 表示表的字段
# 等號右邊的表示數據
# 傳遞的key也能夠是字典利用**dic進行傳遞,在後面利用form的時候會常用這種方式。
dict = {'usernane':'daxin','password':'abc.123',email='daxin@qq.com'}
models.UserInfo.objects.create(**dict)
 


#------------------------------------- save -------------------------------------
from app01 import models      # 從本身的應用中導入models模塊
 
username = 'daxin'
password = 'abc.123'
email = 'daixin@qq.com'
userobj = models.UserInfo(name=username,password=password,email=email)
userobj.save()
 
# save的方式是利用對象實例化的傳遞參數後,調用save方法進行保存的
# 利用對象,咱們能夠方便的進行數據修改後,再進行save
# 好比 userobj.name = 'dahuaidan'
# userobj.save()

注意:利用create新增數據時,會默認返回新增長的數據對象,咱們能夠接受該對象來進行其餘的操做,好比添加多對多關係等

比較經常使用的方式是 利用create進行數據的增長

4.2 orm之刪除

想要對數據進行刪除,首先須要匹配到數據,而後執行刪除操做,那麼就涉及兩部分:查找,刪除

models.UserInfo.objects.filter(id=1).delete()
 
# 利用filter對數據惟一的字段進行過濾,匹配到刪除的行,也能夠用all()匹配全部數據,進行刪除
# 利用delete方法進行刪除操做

4.3 orm之修改

總體思路和刪除是相同的,首先查找到數據,而後對字段進行修改

models.UserInfo.objects.filter(id=1).update(name='dachenzi')
 
# 利用filter過濾到要修改的數據,而後更新它的name字段的值

4.4 orm之查詢

查詢會返回結果的集,它是django.db.models.query.QuerySet類型。是惰性求值,和sqlalchemy同樣。結果就是查詢的集。同時它也是一個可迭代對象。

  • 建立查詢集不會帶來任何數據庫的訪問,直到調用方法使用數據時,纔會訪問數據庫。在迭代、序列化、if語句中
    都會當即求值。
  • 每個查詢集都包含一個緩存,來最小化對數據庫的訪問。

4.4.1 查詢過濾方法

返回結果集的方法:

名稱 說明
all() 獲取全部
filter() 過濾,返回知足條件的數據
exclude() 排除,排除知足條件的數據
order_by() 以什麼字段排序(在字段前面加減號,表示倒序)
values() 返回一個對象字典的列表,列表的元素是字典,字典內是字段和值的鍵值對
values_list() 返回一個對象字典的列表,列表的元素是元組,字典內是字段和值的鍵值對
models.Userinfo.object.all().values('id','username')
# 表示只取指定的id和username列
 
# 結果依舊返回的是QuerySet,可是數據不一樣,體現的形式是一個字典
QuerySet({'id':1,'username':'daxin'},{'id':2,'username':'dachenzi'}......)
 
# 使用value_list('id','username'),體現的形式是一個元組
QuerySet((1,'daxin'),(2,'dachenzi')......)

關於filter:

  • filter(k1=v1).filter(k2=v2) 等價於 filter(k1=v1, k2=v2)
  • filter(pk=10) 這裏pk指的就是主鍵, 不用關心主鍵字段名,固然也可使用使用主鍵名filter(std_id=10)

獲取結果集的方法經過適當的組合能夠鏈式編寫。

返回單個值的方法:

名稱 說明
get() 僅返回單個知足條件的對象
若是未能返回對象則拋出DoesNotExist異常;
若是能返回多條拋出MultipleObjectsReturned異常
count() 返回當前查詢的總條數
first() 返回第一個對象
last() 返回最後一個對象
exist() 判斷查詢集中是否有數據,若是有則返回True
# 獲取表裏全部的對象
models.UserInfo.objects.all()    # 結果爲一個QuerySet對象(django提供的),能夠理解爲一個列表
 
# 獲取id爲1的對象
models.UserInfo.objects.filter(id=1)   # 相似於sql中的 where查詢條件,結果也是一個QuerySet
 
# 獲取name字段包含da的記錄
models.UserInfo.objects.filter(name__contains='da') # 這裏涉及了萬能的雙下劃線,在後續會進行說明
 
# get獲取一條數據
models.UserInfo.objects.get(id=1)    # 注意get不到數據,會直接拋出異常
 
# 除了id等於1的其餘數據
models.UserInfo.objects.exclude(id=1)

注意:

  • 查找到的不是想sql語句那樣直接列出一行的各個字段及對應的值,而已把改行表示爲一個行對象。匹配到一行,就表示1個表中的行對象。
  • 每一個對象內才包含了對應的字段和值。
  • 經過 行對象.字段名,來獲取對應的數據。
  • filter中包含多個條件,那麼它們是and的關係。

PS:因爲filter、all取到的數據默認是 QuerySet 格式,在某些場景下咱們只須要驗證是否取到,咱們能夠直接獲取結果中的第一個數據便可,即在最後添加.first()便可,表示取第一個數據,或者使用.count()來統計匹配到的數據的個數。

module.UserInfo.filter(username='daxin').first()

4.4.2 限制查詢集(切片)

查詢集對象能夠直接使用索引下標的方式(不支持負索引),至關於SQL語句中的limit和offset子句。
注意使用索引返回的新的結果集,依然是惰性求值,不會當即查詢。

qs = User.objects.all()[20:40]
# LIMIT 20 OFFSET 20
qs = User.objects.all()[20:30]
# LIMIT 10 OFFSET 20

4.4.2 字段查詢(雙下劃線)

字段查詢表達式能夠做爲filter()、exclude()、get()的參數,實現where子句。
語法:字段名稱__比較運算符=值,屬性名和運算符之間使用雙下劃線

比較運算符以下

名稱 舉例 說明
exact
filter(isdeleted=False)
filter(isdeleted__exact=False) 嚴格等於,可省略不寫
contains exclude(title__contains='天') 是否包含,大小寫敏感,等價於like '%天%'
statswith
endswith
filter(title__startswith='天') 以什麼開頭或結尾,大小寫敏感
isnull
isnotnull
filter(title__isnull=False) 是否爲null
iexact
icontains
istartswith
iendswith
i的意思是忽略大小寫
in filter(pk__in=[1,2,3,100]) 是否在指定範圍數據中
gt、gte
lt、lte
filter(id__gt=3)
filter(pk__lte=6)
filter(pub_date__gt=date(2000,1,1))
year、month、day
week_day、hour
minute、second
filter(pub_date__year=2000) 對日期類型屬性處理

4.4.3 Q對象

        雖然Django提供傳入條件的方式,可是不方便,它還提供了Q對象來解決。Q對象是django.db.models.Q,可使用&(and)、|(or)操做符來組成邏輯表達式。 ~ 表示not。

from django.db.models import Q
User.objects.filter(Q(pk__lt=6))           # 不如直接寫User.objects.filter(pk<6)
User.objects.filter(pk__gt=6).filter(pk_lt=10)  # 與
User.objects.filter(Q(pk_gt=6) & Q(pk_lt=10))   # 與
User.objects.filter(Q(pk=6) | Q(pk=10))         # 或
User.objects.filter(~Q(pk__lt=6))               # 非

可以使用&|和Q對象來構造複雜的邏輯表達式

  • 過濾器函數可使用一個或多個Q對象
  • 若是混用關鍵字參數和Q對象,那麼Q對象必須位於關鍵字參數的前面。全部參數都將and在一塊兒

5 表與表之間的關係

咱們常說的表與表之間的關係有:一對1、一對多、多對多,下面分別說明

5.1 一對多

        表示當前表的某個字段的值,來自於其餘表,好比人員表和部門表,在人員表中利用一對多外鍵關係標明該員工屬於哪一個部門。

user_type = models.ForeignKey('表名',to_field='字段')       # 默認會自動關聯對方表的ID字段(主鍵),手動指定的話必須是惟一列才行。

注意:

  1. 默認狀況下,外鍵字段在數據庫中存儲的名稱爲:字段名_id (上面的例子的話,在數據庫中的字段名:user_type_id)
  2. 外鍵字段(user_type_id)存放的是所關連表的id信息
  3. 同時還存在一個字段名(上面的例子就是user_type)對象,這個對象指代的就是所關聯的表中的數據對象
  4. 咱們能夠經過user_type,來跨表獲取其關聯的信息的某寫字段的值(經過.字段名,便可訪問)

5.1.1 正向查詢

        什麼叫正向查詢?仍是拿人員表和部門表舉例,外鍵關係存在人員表中,那麼咱們經過人員表利用表中的外鍵字段就能夠查詢到該人員的部門信息,我通常稱之爲正向查詢。

一對多跨表查詢例子:

# -------------------- models.py --------------------

from django.db import models

class Business(models.Model):
    class Meta:
        db_table='business'
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=16,default='SA')

class Host(models.Model):
    class Meta:
        db_table='host'
    nid = models.AutoField(primary_key=True)
    hostname = models.CharField(max_length=16)
    ip = models.GenericIPAddressField(protocol='ipv4',db_index=True)
    port = models.IntegerField()
    b = models.ForeignKey(to='Business',to_field='id')    # 外鍵關聯Business表

#  -------------------- views.py --------------------

def host(request):
    v1 = models.Host.objects.all()
    return render(request,'host.html',{'v1':v1})

# -------------------- host.html --------------------

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <p>主機信息</p>
        {% for row in v1 %}
            <p>{{ row.nid }} - {{ row.hostname }} - {{ row.ip }} - {{ row.port }} - {{ row.b_id }} - {{ row.b.caption }} - {{ row.b.code }}</p>   # 經過b對象,進行跨表查詢主機業務線相關信息
        {% endfor %}
    </div>
</body>
</html>

這裏經過row.b_id 和經過b對象來獲取 row.b.id 結果是相同的,區別在於使用b對象,會多一次sql查詢

PS:當多個表進行及聯跨表,那麼均可以經過表的外鍵字段使用點來進行跨表訪問(或者使用雙下劃線)
雙下劃線和點跨表的不一樣之處在於:

  • 雙下劃線通常用於條件查詢(values,values_list)
  • 點則通常用於在結果中利用外鍵對象進行跨表查詢
# -------------- 利用點進行跨表查詢 --------------
 
# 若是我只想獲取主機地址和所屬業務線使用點跨表的話
v2 = models.Host.objects.all()
return render(request,'host.html',{'v2':v2})
# 前端代碼
{% for row in v2 %}
    <p>{{ row.hostname }} - {{ row.b.caption }}</p>
{% endfor %}
# 咱們只用了兩個字段,卻取出了相關的全部數據。
 
# 是否能夠在查詢階段只取出所需字段便可?使用雙下劃線__便可
 
 
# -------------- 利用雙下劃線跨表查詢 --------------
 
v3 = models.Host.object.all().values('hostname','b__caption')
# 咱們能夠看到在做爲value的條件語句,經過b對象配合雙下劃線就進行了一次跨表查詢
 
# 前端代碼
<div>
    {%  for row in v2 %}
        <p>{{ row.hostname }} - {{ row.b__caption }}</p>    # 只須要向字典同樣便可。
    {% endfor  %}
</div>

在django內部,它之因此能識別__,是由於,它在處理values條件時,會使用__做爲分隔符,分隔後再進行相關處理

5.1.2 反向查詢

        不少時候咱們會有另外一種需求,查詢一個部門下的全部員工,這時部門表中沒有外間字段關聯人員表啊,該怎麼查?實際上是能夠的,我把這個查詢方式稱之爲反向查詢。
        在創建外鍵關係時,不只會在外鍵所在表中產生外鍵所關聯表的對象,在所關聯表中也會產生一個關聯表的對象(可能有點繞,沒找到更好的表達方式),這個對象通常有兩種體現方式:

  1. 關聯個人表(小寫):通常用在values,values_list看成條件作字段過濾
  2. 關聯個人表名(小寫)_set:通常看成對象,看成字段處理
def index(request):
    users = models.User.objects.all()
    departments = models.Department.objects.values('user__name')   # 跨到管理本表的user表中,查找對應的user的名稱,利用的是join 格式
    for dep in departments:
        print(dep)

        # {'user__name': 'daxin'}
        # {'user__name': 'dachenzi'}
        # {'user__name': 'hello'}
        # {'user__name': 'xiaoming'}
        # {'user__name': 'xiaochen'}
        
    return HttpResponse('ok')

對應的sql語句爲:

  • SELECT"user"."name"FROM"department"LEFTOUTERJOIN"user"ON("department"."dept_id" = "user"."dept_id");
def index(request):
    departments = models.Department.objects.filter(pk=1)
    for dep in departments:
        for username in dep.user_set.values('name'):  # user_set指代的是關聯的user表對象
            print(username)

            # {'name': 'daxin'}
            # {'name': 'dachenzi'}
            # {'name': 'hello'}

    return HttpResponse('ok')

對應的sql語句爲:

  • SELECT "department"."dept_id", "department"."dept_name" FROM "department" WHERE "department"."dept_id" = 1;
  • SELECT "user"."name" FROM "user" WHERE "user"."dept_id" = 1;

5.2 多對多

        前面說了一對多的狀況,這裏還有一種狀況叫多對對,好比一個主機能夠關聯多個業務線,不一樣的業務線能夠關聯多個主機,因此這裏,業務線和主機的關係爲多對多,在多對多的狀況下,有須要一張額外的表來表示對應關係,這裏有兩種狀況來建立這張關係表。

5.2.1 手動建立

手動建立,故名思議,咱們須要手動的建立一張關係表,而後建立兩個ForeignKey字段(一對多),關聯兩張表便可。

# 業務線表
class Business(models.Model):
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=16,default='SA')
 
# 主機表
class Host(models.Model):
    nid = models.AutoField(primary_key=True)
    hostname = models.CharField(max_length=16)
    ip = models.GenericIPAddressField(protocol='ipv4',db_index=True)
    port = models.IntegerField()
 
# 多對多關係表
class Application(models.Model):
    h = models.ForeignKey(to='Host',to_field='nid')          # 關聯主機id,字段名爲h_id,同時存在對象h,存放對應的Host信息
    b = models.ForeignKey(to='Business',to_field='id')       # 關聯業務線id,字段名爲b_id,同時存在對象b,存放對應的Business信息

PS:一共手動建立三張表,能夠利用建立的Application關係表來直接操做多對多關係。

5.2.2 自動建立

在Django中,還存在一種方式爲自動建立, 經過django提供的ManyToMany關鍵字建立。

# 業務線表
class Business(models.Model):
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=16,default='SA')
 
# 主機表
class Host(models.Model):
    nid = models.AutoField(primary_key=True)
    hostname = models.CharField(max_length=16)
    ip = models.GenericIPAddressField(protocol='ipv4',db_index=True)
    port = models.IntegerField()
    business = models.ManyToManyField('Business')      # 經過manytomany字段建立多對多關係

注意:

  1. 雖然咱們建立了兩張表,可是因爲咱們使用了manytomany關鍵字建立多對對關係,django還會爲咱們建立一張表名爲當前表名加當前字段名的表(上面的例子就是:app01_host_business),看成關係表,不會產生business字段。
  2. 因爲咱們沒有關係表這個class類對象,因此咱們不能直接操做關係表,須要經過Host對象間接經過business字段來操做關係表。
  3. 雖然利用manytomany關鍵字,能幫咱們建立關係表,節省了不少代碼,可是自動生成的表中只會生成兩個字段(兩張表的主鍵id字段),不能定製其餘字段,相反手動生成的表能夠定製,平時能夠根據狀況兩者混用。

5.2.3 操做關係表

手動建立關係表的狀況下,因爲含有第三張表對應的class,那麼咱們能夠直接使用這個class對關係表進行操做,可是多對多的狀況下沒有關係表的class,因此咱們須要經過其餘辦法來操做。

方法 含義
add() 添加關係
remove() 刪除關係
clear() 清除全部關係  
all() 獲取對應的全部關係對象(在查詢時這裏可使用all,filter,get等,就像查找過濾其餘數據同樣)
models.Host.objects.filter(nid=1).first().business.add(1,2)   # 添加兩條多對多關係 1(host) --> 1(business),1(host) --> 2(business)
models.Host.objects.filter(nid=1).first().business.remove(2)  # 刪除一條多對多關係 1 -x-> 2
models.Host.objects.filter(nid=1).first().business.clear()    # 清除nid爲1的全部的多對多關係

下面是一個小栗子:

# models.py
class Bussiness(models.Model):
    class Meta:
        db_table = 'bussiness'
    bus_id = models.AutoField(primary_key=True)
    bus_name = models.CharField(max_length=64, null=False)

class Host(models.Model):
    class Meta:
        db_table = 'host'
    host_id = models.AutoField(primary_key=True)
    host_name = models.CharField(max_length=64, null=False)
    host_ip = models.GenericIPAddressField(protocol='both')
    bus = models.ManyToManyField(Bussiness)

# views.py
def index(request):
    hosts = models.Host.objects.all()
    for host in hosts:
        for bus in host.bus.all():   # 經過bus對象獲取它關聯的全部bussiness實例記錄
            print(host.host_name, host.host_ip, bus.bus_name)
    return HttpResponse('ok')
相關文章
相關標籤/搜索