1. ORM 簡介
MTV 設計模式中,模型(M)就是對數據庫的操作,在 Web 開發中,使用最頻繁的也是對數據庫的操作,那么該怎么樣去實現呢?
我們不可能自己手動去寫大量的 SQL 語句,因為我們也不是專業 DBA 人員,那么我們就只能奢求有一種能夠使用 Python 程序對數據庫操作的方法了。這就是 ORM(Object Relation Mapping)對象關系映射,以面向對象的方式去操作數據庫。
其實現模式大致是這樣的:
Django 本身給我們提供了強大的 ORM 系統,不需要再額外的
安裝,當然還有一些其他的 ORM ,如:SQLAlch 等。
2. 字段
Model 中的字段 fileds
即數據表中的列,用來存儲數據(記錄),字段類型對應列的數據類型。
2.1 常用字段類型
Django 內置了很多字段類型,都位於 django.db.models
中,以下為常用字段類型:
AutoField(Field)
- int自增列,必須填入參數 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必須填入參數 primary_key=True
注:當model中如果沒有自增列,則自動會創建一個列名為id的列
from django.db import models
class UserInfo(models.Model):
# 自動創建一個列名為id的且為自增的整數列
username = models.CharField(max_length=32)
class Group(models.Model):
# 自定義自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
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
- 參數:
protocol,用於指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定為True,則輸入::ffff:192.0.2.1時候,可解析為192.0.2.1,開啟刺功能,需要protocol="both"
URLField(CharField)
- 字符串類型,Django Admin以及ModelForm中提供驗證 URL
SlugField(CharField)
- 字符串類型,Django Admin以及ModelForm中提供驗證支持 字母、數字、下划線、連接符(減號)
CommaSeparatedIntegerField(CharField)
- 字符串類型,格式必須為逗號分割的數字
UUIDField(Field)
- 字符串類型,Django Admin以及ModelForm中提供對UUID格式的驗證
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 上傳圖片的寬度保存的數據庫字段名(字符串)
DateTimeField(DateField)
- 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
- auto_now_add=True 當在首次創建對象時自動將字段設置為現在。用於創建時間戳
- auto_add=True 每次保存對象時,自動將字段設置為現在。用於“最后修改”的時間戳
TimeField(DateTimeCheckMixin, Field)
- 時間格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 長整數,時間間隔,數據庫中按照bigint存儲,ORM中獲取的值為datetime.timedelta類型
FloatField(Field)
- 浮點型,精確的數不能用 FloatField
DecimalField(Field)
- 10進制小數,對於比較精確的數可以用 DecimalField
- 參數:
max_digits,小數總長度
decimal_places,小數位長度
BinaryField(Field)
- 二進制類型
2.2 字段參數
null 數據庫中字段是否可以為空
db_column 數據庫中字段的列名
default 數據庫中字段的默認值
primary_key 數據庫中字段是否為主鍵
db_index 數據庫中字段是否可以建立索引
unique 數據庫中字段是否可以建立唯一索引
unique_for_date 數據庫中字段【日期】部分是否可以建立唯一索引
unique_for_month 數據庫中字段【月】部分是否可以建立唯一索引
unique_for_year 數據庫中字段【年】部分是否可以建立唯一索引
verbose_name Admin中顯示的字段名稱
blank Admin中是否允許用戶輸入為空
editable Admin中是否可以編輯
help_text Admin中該字段的提示信息
choices Admin中顯示選擇框的內容,用不變動的數據放在內存中從而避免跨表操作
如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)
error_messages 自定義錯誤信息(字典類型),從而定制想要顯示的錯誤信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
如:{'null': "不能為空.", 'invalid': '格式錯誤'}
validators 自定義錯誤驗證(列表類型),從而定制想要的驗證規則
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
max_length=32,
error_messages={
'c1': '優先錯信息1',
'c2': '優先錯信息2',
'c3': '優先錯信息3',
},
validators=[
RegexValidator(regex='root_\d+', message='錯誤了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又錯誤了', code='c2'),
EmailValidator(message='又錯誤了', code='c3'), ]
)
1. null
用來保存空值,默認值 false。對於保存字符串的字段,盡量避免將其設置為 true,否則可能會導致出現是 null 或空字符串
2. blank
表示字段可以為空,默認 false。常用語表單輸入驗證是否為空,與數據庫無關。
3. choices
頁面上選擇框標簽,二維元組格式。兩個參數,第一個為存儲在數據庫中,第二個顯示在頁面上內容。
數據庫存儲的是0 、1 、2,而頁面上顯示的是籃球、足球、羽毛球
hobby_choices = [(0, '籃球'), (1, '足球'), (2, '羽毛球'),]
hobby = models.IntegerField(
choices=hobby_choices
)
4. primary_key
主鍵,如果沒有指定主鍵,那么 Django會自動創建一個 AutoField的自增字段,名為 id,並將其設置為主鍵。
primary_key 相當於 null=False和unique=True,即唯一且不能為空,你也可以自己將其他字段設置為空,若有必要。
示例
from django.db import models
class UserInfo(models.Model):
username = models.CharField(
null=True,
db_column='user',
max_length=32,
db_index=True,
verbose_name='用戶名',
help_text='幫助信息',
default='',
)
hobby_choices = [(0, '籃球'), (1, '足球'), (2, '羽毛球'),]
hobby = models.IntegerField(
choices=hobby_choices
)
def __str__(self):
return self.username
2.3 元類 Meta
模型中的元類是指除了字段外的其他非必須內容,如修改數據表的名字,設置代理等。
使用方式
class User(models.Model):
...
class Meta:
verbose_name = '用戶'
1. abstract
為 true 時,模型會被認為是一個抽象類,長用來作為其他模型的父類被繼承。
2. app_label
如果沒有在 settings 中注冊 app,那么必須在元類中聲名是屬於哪個 app
app_label = 'polls'
3. base_manager_name
自定義模型的 _base_manager 管理器的名字,模型管理器是 Django 為模型提供的 API
4. db_table
指定模型生成數據表時,數據表的表名
db_table = 'user'
5. db_tablespace
自定義數據庫表空間的名字。默認值是工程的DEFAULT_TABLESPACE設置
6. default_manager_name
自定義模型的_default_manager管理器的名字
7. default_related_name
反向查詢時,默認我們使用的 `<model_name>_set` 也就是源模型名字+下划線+set 方法查詢,當我們定義了 default_related_name時,就可以使用它來反向查詢。
8. ordering
指定該模型生成的所有對象的排序方式,接收一個字段名組成的元組或列表。默認按升序排列,如果在字段名前加上字符“-”則表示按降序排列,如果使用字符問號“?”表示隨機排列。
ordering = ['pub_date'] # 表示按'pub_date'字段進行升序排列
ordering = ['-pub_date'] # 表示按'pub_date'字段進行降序排列
ordering = ['-pub_date', 'author'] # 表示先按'pub_date'字段進行降序排列,再按`author`字段進行升序排列。
9. permissions
該元數據用於當創建對象時增加額外的權限。它接收一個所有元素都是二元元組的列表或元組,每個元素都是(權限代碼, 直觀的權限名稱)的格式。比如下面的例子:
permissions = (("can_deliver_pizzas", "可以送披薩"),)
10. default_permissions
Django默認給所有的模型設置('add', 'change', 'delete')的權限,也就是增刪改。你可以自定義這個選項,比如設置為一個空列表,表示你不需要默認的權限,但是這一操作必須在執行migrate命令之前
11. proxy
若為 true,表示使用代理模式的模型繼承方式。
12. required_db_vendor
聲明模型支持的數據庫。Django默認支持sqlite, postgresql, mysql, oracle
13. indexes
接收一個應用在當前模型上的索引列表
from django.db import models
class Customer(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
class Meta:
indexes = [
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['first_name'], name='first_name_idx'),
]
14. unique_together
等同於數據庫的聯合約束,無法應用多對多字段。
比如一張用戶表,保存用戶姓名、出生日期、地址等,現在有兩個叫張偉的人,那么就可以使用聯合唯一。
# 表示 name、birth_day、address 聯合唯一,即不能在同一地方、同一時刻出生且都叫張偉
unique_together = (('name', 'birth_day', 'address'),)
14. verbose_name
給 Admin 提供人性化的名稱,支持中文,如:
verbose_name = '用戶' # 那么 Admin 中顯示的就是 用戶
15. label
只讀元數據,不可修改,相當於 polls.Question
3. 多表關系及參數
- 一對一:Foreignkey 基礎上加一個唯一索引 unique
- 一對多:ForeignKey
- 多對多:ManyToMany(兩個一對多,兩個 ForeignKey 相連)
3.1 一對多模型
一對多模型,如:員工部門表,一個部門可以有多個員工。那么 Django 怎么建立這種關系呢?
其實就是通過外鍵 ForeignKey
進行關聯,在多的一方,字段指定外鍵即可。
ForeignKey 字段參數
ForeignKey(ForeignObject) # ForeignObject(RelatedField)
to, # 要進行關聯的表名
to_field=None, # 要關聯的表中的字段名稱
on_delete=None, # 當刪除關聯表中的數據時,當前表與其關聯的行的行為
- models.CASCADE,刪除關聯數據,與之關聯也刪除
- models.DO_NOTHING,刪除關聯數據,引發錯誤IntegrityError
- models.PROTECT,刪除關聯數據,引發錯誤ProtectedError
- models.SET_NULL,刪除關聯數據,與之關聯的值設置為null(前提FK字段需要設置為可空)
- models.SET_DEFAULT,刪除關聯數據,與之關聯的值設置為默認值(前提FK字段需要設置默認值)
- models.SET,刪除關聯數據,
a. 與之關聯的值設置為指定值,設置:models.SET(值)
b. 與之關聯的值設置為可執行對象的返回值,設置:models.SET(可執行對象)
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User", # 關聯 User 表
to_field="id" # 關聯 User 表 的 id 字段
on_delete=models.SET(func),)
related_name=None, # 反向操作時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作時,使用的連接前綴,用於替換【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中顯示關聯數據時,提供的條件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
db_constraint=True # 是否在數據庫中創建外鍵約束
parent_link=False # 在Admin中是否顯示關聯數據
示例:
class Department(models.Model):
"""部門表"""
name = models.CharField(max_length=32)
create_data = models.DateField(auto_now_add=True) # 創建時間
id_delete = models.BooleanField(default=False) # 是否可刪除
class Meta:
db_table = 'department'
class Employee(models.Model):
"""員工表"""
name = models.CharField(max_length=32)
age = models.IntegerField()
gender = models.IntegerField(default=0)
salary = models.DecimalField(max_digits=8, decimal_places=2) # max_digits 表示八個數字,包括兩位小數,decimal_places 表示保留兩位小數
# null=True 表示可以為空, blank=True 表示 Django admin 后台可以輸入空
comment = models.CharField(max_length=300, null=True, blank=True)
hire_data = models.DateField(auto_now_add=True)
department = models.ForeignKey('Department') # 外鍵字段
class Meta:
db_table = 'employee' # 數據表名字
Tips
在設置外鍵時,需要給子表(有外鍵的表)指定當主表(被連接的表)刪除數據時,從表該如何處理。Django 通過設置 on_delete
屬性來控制,它有三種值:
- 刪除時報錯:
DO_NOTHING/PROTECT
- 級聯刪除:主表刪除數據,從表與之關聯的數據也將被刪除:
CASCADE
- 設置默認值:
SET_NULL/SET_DEFAULT
,set_null
僅在字段可以是 null 時才能使用
3.2 一對一模型
OneToOneField(ForeignKey)
to, # 要進行關聯的表名
to_field=None # 要關聯的表中的字段名稱
on_delete=None, # 當刪除關聯表中的數據時,當前表與其關聯的行的行為
###### 對於一對一 ######
# 1. 一對一其實就是 一對多 + 唯一索引
# 2.當兩個類之間有繼承關系時,默認會創建一個一對一字段
# 如下會在A表中額外增加一個c_ptr_id列且唯一:
class C(models.Model):
nid = models.AutoField(primary_key=True)
part = models.CharField(max_length=12)
class A(C):
id = models.AutoField(primary_key=True)
code = models.CharField(max_length=1)
# 使用 OneToOneField 字段創建一對一模型
class Person(models.Model):
name = models.CharField(max_length=32)
o2o = models.OneToOneField(to='Person_detail', to_field='id', on_delete=models.CASCADE)
class Person_detal(models.Model):
age = models.IntegerField()
gender_choices = [(0, '男'), (1, '女')]
gender = models.IntegerField(
choices=gender_choices,
default=0,
)
height = models.PositiveIntegerField() # 正整數
email = models.EmailField(max_length=64)
3.3 多對多模型
多對多其實就是兩個一對多,通過兩個外鍵將三張表相連,其中第三張表存儲了前面兩張表的對應關系。例如:名字和愛好表,一個人可以有多個愛好,一個愛好也可以是多個人有。
如何創建三張表:
- 自動創建:
ManyToMangField
字段自動創建,不能新增列 - 手動創建:元
Meta
,可以新增列 - 手動加自動:元
Meta
+ManyToMangField
多對多 ManyToManyField
字段參數:
ManyToManyField(RelatedField)
to, # 要進行關聯的表名
related_name=None, # 反向操作時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作時,使用的連接前綴,用於替換【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中顯示關聯數據時,提供的條件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
symmetrical=None, # 僅用於多對多自關聯時,symmetrical用於指定內部是否創建反向操作的字段
# 做如下操作時,不同的symmetrical會有不同的可選字段
models.BB.objects.filter(...)
# 可選字段有:code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=True)
# 可選字段有: bb, code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=False)
through=None, # 自定義第三張表時,使用字段用於指定關系表
through_fields=None, # 自定義第三張表時,使用字段用於指定關系表中那些字段做多對多關系表
from django.db import models
class 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)
db_constraint=True, # 是否在數據庫中創建外鍵約束
db_table=None, # 默認創建第三張表時,數據庫中表的名稱
自動創建
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用戶名')
m = models.ManyToManyField(
to = 'Tag',
related_name = 'bb',
)
class Tag(models.Model):
title = models.CharField(max_length=32)
手動創建
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用戶名')
class Tag(models.Model):
title = models.CharField(max_length=32)
# 手動創建第三張表
class UserToTag(models.Model):
tid = models.AutoField(primary_key=True)
u = models.ForeignKey('UserInfo', on_delete=models.CASCADE) # 通過外鍵相連
t = models.ForeignKey('Tag', on_delete=models.CASCADE)
# 聯合唯一索引
class Meta:
unique_together = [
('u', 't'),
]
手動加自動
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用戶名')
m = models.ManyToManyField(
through = 'UserToTag', # 指定第三張表,不自動創建
through_fields = ['u', 't'] # 指定第三張表的字段
)
class Tag(models.Model):
title = models.CharField(max_length=32)
# 手動創建第三張表
class UserToTag(models.Model):
tid = models.AutoField(primary_key=True)
u = models.ForeignKey('UserInfo', on_delete=models.CASCADE) # 通過外鍵相連
t = models.ForeignKey('Tag', on_delete=models.CASCADE)
# 聯合唯一索引
class Meta:
unique_together = [
('u', 't'),
]
3.4 自關聯模型
自關聯模型,即表中的某列關聯表中另一列。最典型的自關聯模型就是地區表(省市縣三級聯動)省的 pid 為 null,市的 pid 為省的 id,縣的 pid 為市的 id。具體如下:
自關聯表現形式
- 省的
parent_id
為 null - 市的
parent_id
為對應省的id
- 區的
parent_id
為對應市的id
- 數據庫設計
models.py
class Area(models.Model):
name = models.CharField(max_length=32, verbose_name='名稱')
parent = models.ForeignKey('self', # 自關聯字段的外鍵指向自身,也可以是 Area
verbose_name='上級行政區划',
on_delete=models.SET_NULL,
related_name='subs',
null=True,
blank=True
)
class Meta:
db_table = 'tb_areas' # 自定義表名
verbose_name = '行政區划' # admin 中顯示的名稱
verbose_name_plural = '行政區划'
def __str__(self):
return self.name
自關聯模型,最核心的地方就是自己關聯自己 self/Area
,用一張表做兩張表才能做的事。
- 路由系統
app/urls.py
from django.urls import path
from app import views
urlpatterns = [
path('area/', views.area, name='area'),
path('getPro/', views.getPro, name='getArea'),
path('getCity/', views.getCity, name='getCity'),
path('getDis/', views.getDis, name='getDis'),
]
- 視圖函數
views.py
from django.shortcuts import render, HttpResponse
from app import models
from django.http import JsonResponse
# 訪問 http://127.0.0.1:8000/app/area/,返回 area.html
def area(request):
return render(request, 'area.html')
def getPro(request):
"""獲取省份信息"""
pro_list = models.Area.objects.filter(parent_id=None) # 省份的 parent_id 為 None
res = []
for i in pro_list:
res.append([i.id, i.name])
print(res) # [[1, '廣東省'], [7, '湖南省']]
return JsonResponse({'pro_list': res}) # JsonResponse 打包成 json 格式字符串
def getCity(request):
"""獲取市信息"""
city_id = request.GET.get('city_id')
city_list = models.Area.objects.filter(parent_id=int(city_id))
res = []
for i in city_list:
res.append([i.id, i.name])
print('city', res) # [[2, '深圳市'], [3, '廣州市'], [6, '湛江市']]
return JsonResponse({'city_list': res})
def getDis(request):
"""獲取區信息"""
dis_id = request.GET.get('dis_id')
dis_list = models.Area.objects.filter(parent_id=int(dis_id))
res = []
for i in dis_list:
res.append([i.id, i.name])
return JsonResponse({'dis_list': res})
- 模板
area.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<select id="pro">
<option value="">請選擇省份</option>
</select>
<select id="city">
<option value="">請選擇城市</option>
</select>
<select id="dis">
<option value="">請選擇區</option>
</select>
<script src="{% static 'jquery-3.1.1.js' %}"></script>
<script>
$(function () {
var pro = $('#pro');
var city = $('#city');
var dis = $('#dis');
// 查詢省信息,$.get() 為 $.ajax 的簡化版
// 向 /app/getPro/ 發送 ajax 請求,arg 為請求成功返回的信息
$.get('/app/getPro/', function (arg) {
console.log(arg); // {'pro_list': [[1, '廣東省'],[7, '湖南省']]}
$.each(arg.pro_list, function (index, item) {
console.log(item); // [1, '廣東省'] 、[7, '湖南省']
console.log(index); // 0、1
// 將從數據庫中取出的數據添加到 option 標簽中,其中 value 為 id,文本值為 name
var $new = $("<option value="+ item[0] +">" + item[1] + "</option>");
pro.append($new);
});
{# 或者使用 JavaScript for 循環#}
{# console.log(arg.pro_list);#}
{# for (var i = 0, len=arg.pro_list.length; i<len; i++){#}
{# var $new = $('<option value='+ arg.pro_list[i][0] +'>' + arg.pro_list[i][1] + '</option>')#}
{# pro.append($new);#}
{# }#}
});
// 根據省的變化查詢市,改變省時,清空市和區
pro.change(function () {
city.empty().append('<option value="">請選擇市</option>');
dis.empty().append('<option value="">請選擇市</option>');
$.ajax({
url: '/app/getCity/',
type: 'get',
data: {'city_id': $(this).val()},
success: function (arg) {
$.each(arg.city_list, function (index, item) {
var $new = $('<option value='+ item[0]+'>' + item[1] + '</value>');
city.append($new);
})
}
})
});
// 根據市變化查詢區,改變市,清空區
city.change(function () {
dis.empty().append('<option value="">請選擇市</option>');
$.ajax({
url: '/app/getDis/',
type: 'get',
data: {'dis_id': $(this).val()},
success: function (arg) {
console.log('區', arg.dis_list); // [[4, '寶安區'], ]
$.each(arg.dis_list, function (index, item) {
console.log(item[1]);
var $new = $('<option value='+ item[0] +'>' + item[1] + '</value>');
dis.append($new);
})
}
})
})
})
</script>
</body>
</html>
當訪問 http://127.0.0.1:8000/app/area/
時,會向 /app/getPro/
發送 ajax 請求,請求成功獲得包含省份 id、name 的信息 {'pro_list': [[1, '廣東省'],[7, '湖南省']]}
。取出數據遍歷循環,添加到 option
標簽中,從而獲得省份信息。
當我們改變省份時,將上一次省份的市、區信息清空,避免添加重復。獲取當前省份的 id,並向 /app/getDis/
發送 ajax 請求,從而獲得相應市的信息,同理區也是一樣。
- 添加數據
views.py
添加數據,可以用 pycharm 自帶的數據庫添加,也可以手動添加:
def add(request):
models.Area.objects.create(id=7, name='湖南省', parent_id=None)
models.Area.objects.create(id=8, name='長沙市', parent_id=7)
models.Area.objects.create(id=9, name='雨花區', parent_id=8)
return HttpResponse('添加成功')
參考博客:
3.5 知識點補充
Ajax 的 get() 方法
,它可以向后台發送 ajax 請求,請求成功可調用回調函數。如果需要在出錯時執行函數,請使用 $.ajax
,它是 $.ajax
簡化版本。
語法
$(selector).get(url, data, success(response, status, xhr), dataType); // 除了 url 必需,其他都可選,status 為請求狀態,xhr - 包含 XMLHttpRequest 對象
$('button').click(function(){
get('/app/getdata/', function(arg){});
});
3.6 Model 常用設計模型示例及方法
3.6.1 Model 設計
總共四張表,分別是班級、學生、學生個人信息以及老師表,之間的關系如下:
class Class(models.Model):
"""班級表"""
class_name = models.CharField(max_length=32)
create_data = models.DateField()
def __str__(self):
return self.class_name
class Meta:
verbose_name = '班級'
class Student(models.Model):
"""
學生表
一每個學生都有對應的個人信息(一對一),一個班級可以有多個學生(一對多)
"""
student_name = models.CharField(max_length=32)
# 一對多,與班級關聯
sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 一對一,與學生個人信息關聯
detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
class Meta:
verbose_name = '學生'
def __str__(self):
return self.student_name
class StudentDetail(models.Model):
"""學生個人信息表"""
age = models.IntegerField()
gender_choices = [(0, '男'), (1, '女')]
gender = models.IntegerField(
choices=gender_choices,
default=0,
)
height = models.PositiveIntegerField() # 正整數
email = models.EmailField(max_length=64)
class Teacher(models.Model):
"""
老師表
一個老師可以教多個班,一個班也可以有多個老師
"""
teacher_name = models.CharField(max_length=32)
tc = models.ManyToManyField(to='Class', related_name='b')
class Meta:
verbose_name = '老師'
def __str__(self):
return self.teacher_name
- 老師表
app_teacher
:
- 學生個人信息表
app_student_detail
:
- 學生表
app_student
:
- 班級表
app_class
:
- 老師、班級關系表(第三張表)
app_teacher_tc
:
3.6.2 查詢操作
def query(request):
########### 一對一 #####################
# 正向查詢(根據外鍵字段)
# 根據學生名字查詢其個人信息
# obj = models.Student.objects.filter(student_name='rose')[0]
# print(obj)
# print(obj.detail.age, obj.detail.gender, obj.detail.height, obj.detail.email)
# # 17 1 170 456@qq.com
# 反向查詢(根據要查詢的數據表名)
# 根據郵箱查詢學生名字
# obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
# print(obj2) # <QuerySet [<StudentDetail: StudentDetail object (2)>]>
#
# print(obj2.student.student_name) # rose
############## 一對多(班級表和學生表) ##################
# 正向查詢
# 根據學生名查詢所屬班級
# obj3 = models.Student.objects.get(student_name='rose')
# print(obj3.sc_id) # 1
# print(type(obj3.sc)) # <class 'app.models.Class'>
# print(obj3.sc.class_name) # 一班
# 反向查詢
# 二班有哪些學生
# obj4 = models.Class.objects.filter(class_name='二班')[0]
# print(obj4)
# # res = obj4.student_set.all() # 如果外鍵字段沒有設置 related_name 就用 表名_set
# res = obj4.stu.all()
# print(res) # <QuerySet [<Student: john>, <Student: lila>]>
# for i in res:
# print(i.student_name) # john、lila
# 方法二
# ret = models.Student.objects.filter(sc=obj4).values('student_name') # 字典形式
# print(ret) # <QuerySet [{'student_name': 'john'}, {'student_name': 'lila'}]>
# for i in ret:
# print(i['student_name']) # john、lila
# # 雙下划線
# 正向
obj4 = models.Class.objects.filter(class_name='二班').values('stu__student_name')
# 反向
obj5 = models.Student.objects.filter(sc__class_name='二班').values('student_name')
############################### 多對多(老師表與班級表) ############################################
# 查看 孫老師教哪幾個班
# 正向查詢(通過多對多字段)
# obj5 = models.Teacher.objects.filter(teacher_name='孫老師')[0]
# ret = obj5.tc.all()
# print(ret) # <QuerySet [<Class: 一班>, <Class: 二班>]>
# for i in ret:
# print(i.class_name)
# 查看一班有哪幾個老師
# 反向查詢
# obj5 = models.Class.objects.filter(class_name='一班')[0]
# ret = obj5.b.all()
# print(ret) # < QuerySet[ < Teacher: 孫老師 >, < Teacher: 劉老師 >] >
# for i in ret:
# print(i.teacher_name) # 孫老師、劉老師
# # 雙下划線
# # 如果沒有設置 related_name = b,那么就是 values('teacher__name`) 即要查的表的表名__要查詢的字段
# obj6 = models.Class.objects.filter(class_name='一班').values('b__teacher_name')
# print(obj6) # <QuerySet [{'b__teacher_name': '孫老師'}, {'b__teacher_name': '劉老師'}]>
# # 正向 tc = models.ManyToManyField(to='Class', related_name='b')
# obj6 = models.Teacher.objects.filter(tc__class_name='一班').values('teacher_name')
return HttpResponse('查詢成功!')
3.6.3 總結
一對一、一對多、多對多,最好都設置好 related_name
參數,沒有設置時,反向查找用 要查詢的命名.查詢字段 或 _set.all()。
查詢分為:
- 基於對象查詢(相當於 SQL 中子查詢)
- 先獲取相關對象:
models.Models.objects.filter(過濾字段)
- 再通過對象(正向、反向)查詢相關字段
- 先獲取相關對象:
- 基於 QuerySet 和雙下划線查詢(相當於 SQL 中連表查詢)
- 前面是過濾條件,后面跟要顯示(查詢)的字段
models.Model.objects.filter(過濾字段).values(要顯示的字段)
一對一
- 正向查詢:根據
OneToOneField
字段查詢 - 反向查詢:根據 要查詢的命名.查詢字段查詢
# detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
# 正向
obj = models.Student.objects.filter(student_name='rose')[0]
print(obj.detail.age)
# 反向
obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
print(obj2.student.student_name) # 這里沒設置 related_name 因此用表名
一對多
- 正向查詢:根據外鍵字段查詢
- 反向查詢
- 設置了
related_name
,就用這個名字查詢 - 沒有設置,用表名_set 方式查詢
- 設置了
- 雙下划線查詢
- 正向:
filter(條件).values(要查詢的表名__要查詢字段)
- 反向:
filter(外鍵字段__條件).values(要查詢字段)
- 正向:
# sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 正向
obj3 = models.Student.objects.get(student_name='rose')
print(obj3.sc.class_name) # 根據外鍵字段 sc 查詢
# 反向
obj4 = models.Class.objects.filter(class_name='二班')[0]
# res = obj4.student_set.all() # 沒設置 related_name,用表名_set
res = obj4.stu.all() # 用 related_name 名字
# 雙下划線
# 正向
obj5 = models.Class.objects.filter(class_name='二班').values('stu__student_name')
# 反向
obj6 = models.Student.objects.filter(sc__class_name='二班').values('student_name')
多對多
- 正向查詢:
ManyToManyField
字段查詢 - 反向查詢:表名_set、
related_name
名字查詢 - 雙下划線與一對多一樣
# tc = models.ManyToManyField(to='Class', related_name='b')
# 正向
obj5 = models.Teacher.objects.filter(teacher_name='孫老師')[0]
print(obj5.tc.all())
# 反向
obj5 = models.Class.objects.filter(class_name='一班')[0]
print(obj5.b.all())
參考博客:django 一對一、一對多、多對多操作、常用方法
4. 模型繼承
Django中所有的模型都必須繼承 django.db.models.Model
模型,同樣地我們也可以自己創建一個父模型用來保存公共部分,子模型繼承父模型。這樣的好處就是有時會省去很多重復代碼。
同樣地 Django 也支持繼承多個父類。
Django 中三種繼承方式:
- 抽象基類:被用來繼承的模型被稱為
Abstract base classes
,將子類共同的數據抽離,供子類復用,它不會創建實際的數據表 - 多表繼承:
Multi-table inheritance
,每一個模型都有自己的數據庫表 - 代理模型:如果你只想修改模型的Python層面的行為,並不想改動模型的字段,可以使用代理模型
4.1 抽象基類
只需在模型中元類添加 abstract=True
,即可將模型轉換為抽象基類。但是它不會創建實際數據庫表,只能用來被繼承。
from django.db import models
class CommonInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class Meta:
abstract = True
class User(CommonInfo):
email = models.EmailField(max_length=60)
模型 User 將會有 username、password、email
三個字段。
抽象基類元類
如果子類沒有元類,那么它將繼承基類的元類,同樣地子類也可以對基類的元類進行拓展:
class CommonInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class Meta:
abstract = True
ordering = ['username']
class User(CommonInfo):
email = models.EmailField(max_length=60)
# 繼承基類的元類,並進行拓展
class Meta(CommonInfo.Meta):
db_table = 'username'
- 基類有元類,子類也沒有的話,直接繼承
- 基類有元類,子類也有,直接覆蓋
- 子類可以添加額外元數據
- 基類的
abstract=true
不會被繼承 - 基類的
db_table
元數據無效,因為抽象基類不會創建數據表
related_name 和 related_query_name
若抽象基類中存在 Foreignk
和 ManyToManyField
字段,且設置了 related_name
或 related_query_name
參數,其子類也將繼承這兩個參數。但是在查詢時就會出現錯誤。
例如,對於 app01/models.py
中:
class Base(models.Model):
m2m = models.ManyToManyField(User, related_name="%(app_label)s_%(class)s_related", related_query_name="%(app_label)s_%(class)ss")
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
- app01.ChildA.m2m 字段:反向關系名(reverse name)為
app01_childa_related
;反向查詢名(reverse query name)為app01_childas
- app01.ChildB.m2m 字段:分別為:
app01_childb_related
和app01_childbs
- 如果沒有設置 related_name 或 related_query_name 就沒有上述情況
4.2 多表繼承
父類和子類都是可正常使用的模型,且都有自己的數據表,其本質為 一對一 關系。
class UserInfo(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
class PersonalDetail(UserInfo):
email = models.EmailField()
description = models.CharField(max_length=4000)
PersonalDetail
繼承 UserInfo
,它會繼承父類所有的字段,兩個模型內部是一對一關系,如下所示:
>>> from polls.models import UserInfo, PersonalDetail
>>> q = PersonalDetail.objects.all()
>>> for i in q:
... i.name
...
'rose'
'lina'
多表繼承之元類
多表繼承中子類不會繼承父類的元類,但是有兩個元類數據除外: ordering、get_latest_by
,因此若希望不繼承父類的元類的所有元數據,就需要指定或重寫這兩個元數據:
class PersonalDetail(UserInfo):
email = models.EmailField()
description = models.CharField(max_length=4000)
class Meta:
# 重寫 ordering,移除父類元類的影響
ordering = []
4.3 多重繼承
與 Python 一樣,模型也可以繼承多個父類模型,當有多個父類都含有 Meta 類時,那么只有第一個父類的會被繼承,其余將被忽略掉。
Tips
- 盡量避免不用多重繼承
- 當父類有相同 id 主鍵字段時,將會報錯,因此需要給父類顯示地添加
AutoField
字段。
class Base1(models.Model):
base1_id = models.AutoField(primary_key=True)
pass
class Base2(models.Model):
base2_id = models.AutoField(primary_key=True)
pass
class Child(Base1, Base2):
pass
4.4 代理模型
代理模型就是對源模型的一種代理,可以創建、刪除、更新代理模型的實例,與源模型用的是同一張數據表,但是 它對源模型數據沒有影響。
要想將一個模型設置為代理模型,只需在 Meta 類中設置 proxy=True
:
class A(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
class B(A):
# 代理模型
class Meta:
proxy = True
ordering = ['age']
def do_something(self):
pass
只會創建一張數據表,同時代理模型也可以操作數據表:
from .models import A, B
>>> A.objects.create(name='rose', age=18)
>>> q = B.objects.get(name='rose')
>>> q.age
18
# 對代理模型排序,按照年齡從小到大排列,被代理的模型,查詢不會排序
obj_list = B.objects.all()
for obj in obj_list:
print(obj.age) # 17、18 、20
print(obj.name) # lila、rose、tom
Tips
- 代理模型必須繼承自一個非抽象的基類,並且不能同時繼承多個非抽象基類;
- 代理模型可以同時繼承任意多個抽象基類,前提是這些抽象基類沒有定義任何模型字段。
- 代理模型可以同時繼承多個別的代理模型,前提是這些代理模型繼承同一個非抽象基類。
4.5 總結
- 抽象基類不會創建數據表,直接設置
abstract=True
即可設置為抽象基類 - 多表繼承實質是一對一關系,不會繼承基類 Meta,除
ordering 和 get_latest_by
- 多表繼承最好不要用
- 代理模型與被代理模型通用一張數據表,可以對代理數據進行創建、刪除、修改等操作,但對源模型無影響
- 若基類是非抽象基類,基類與子類有相同名字的字段,那么將會覆蓋子類(抽象基類除外)
# A 中的 name 將會覆蓋 B 中 的 name,除非 A 是抽象基類
class A(models.Model):
name = models.CharField(max_length=32)
# class Meta:
# abstract = True
class B(A):
name = models.CharField(max_length=32)
組織模型
當模型有很多時,我們最好將模型分割開來,分類存儲,這樣有利於組織。我們可以利用包來組織模型。
在應用中創建一個名為 models
的包(pycharm 可以直接創建包,不需要手動創建 init.py文件),然后將模型分類到各個 .py 文件中,最后將其導入 __init__.py
文件中:
# app/models/__init__.py
from polls.models.modelone import User
5. 聚合分組
5.1 聚合 aggregate
aggregate()
返回一個字典,鍵為聚合值標識符,值為計算處理的聚合值。
使用時需要先導入內置函數:
from django.db.models import Avg, Sum, Max, Min, Count
示例:
from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age')) # 指定鍵為 avg_age,也可以用默認的
{'avg_age': 18}
多個聚合:
from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'), max_length=Max('height'))
{'avg_age': 18, 'max_length': 182}
5.2 分組 annotate
ORM 中 values()
或 values_list()
選字段,就相當於原生 SQL 中 select
什么字段:
ret = models.Employee.objects.all() # 相當於 select * from employee
"""
SELECT `employee`.`id`, `employee`.`name`, `employee`.`age`, `employee`.`salary`, `employee`.`province`, `employee`.`dept` FROM `employee` LIMIT 21; args=()
"""
ret = models.Employee.objects.all().values("dept", "age")
"""
SELECT `employee`.`dept`, `employee`.`age` FROM `employee` LIMIT 21; args=()
"""
select
選中什么字段,group by
就只能是什么字段:
mysql> select name, Avg(salary) from app_employee group by name;
+------+-------------+
| name | Avg(salary) |
+------+-------------+
| rose | 2000.1 |
| lila | 3000.45 |
| john | 4000.56 |
| tom | 5005.78 |
+------+-------------+
-
連表分組查詢
按照部門分組,查詢每個部門的平均薪水:
# ORM 操作
# 前一個 values() 表示按照什么分組,后一個表示要顯示的字段
>>> employee_list = models.Employee.objects.values('dept__name').annotate(avg=Avg('salary')).values('dept__name', 'avg')
>>> print(employee_list)
<QuerySet [{'dept__name': '財務部', 'avg': 2000.1}, {'dept__name': '事業部', 'avg': 4003.115}, {'dept__name': '人事部', 'avg': 4000.56}]>
# 對應 SQL 語句
SELECT `app_department`.`name`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` INNER JOIN `app_department` ON (`app_employee`.`dept_id` = `app_department`.`id`) GROUP BY `app_employee`.`name`, `app_department`.`name` ORDER BY NULL LIMIT 21; args=()
- ORM 分組查詢
按照國家分組,查詢每個國家的平均薪水:
>>> employee_list = models.Employee.objects.values('addr').annotate(avg=Avg('salary')).values('addr', 'avg')
>>> print(employee_list)
<QuerySet [{'addr': '美國', 'avg': 3502.9399999999996}, {'addr': '英國', 'avg': 3000.45}, {'addr': '德國', 'avg': 4000.56}]>
SELECT `app_employee`.`addr`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` GROUP BY `app_employee`.`addr` ORDER BY NULL LIMIT 21; args=()
Tips
- ORM 中的連表查詢(跨表)相當於 SQL 中的連表(
inner john
)查詢 - ORM 查詢,第一個
values(fields)
為分組字段,第二個values(fileds1, fields2
) 為要顯示的字段 select
選中什么字段,group by
就只能是什么字段
5. F 查詢和 Q 查詢
5.1 F 查詢
上面我們都是在比較字段與某個常亮,要想兩個字段進行比較,那就要用到 F 查詢
- 查詢書的價格大於評論數的書籍
from django.db.models import F
models.Book.objects.filter(price__gt=F('comment_count'))
- 將書的價格整體提升 5 元
models.Book.objects.update(price=F('price')+5)
- F 對象和 F 對象以及常量之間可以進行加減乘除、取模等
models.Book.objects.update(price=F('price')*5)
5.2 Q 查詢
Q 構建搜索條件(在這里解決了或、與的問題)
- Q 對象可以對關鍵字參數進行封裝,從而更好地應用多個查詢
obj1 = models.StudentDetail.objects.filter(Q(age=18)) # 構建過濾條件
- 組合使用 &、| 、~ 操作符
obj2 = models.StudentDetail.objects.filter(Q(age=18) | Q(age=19)) # 解決了 Django API 中沒有或的問題
obj3 = models.StudentDetail.objects.filter(Q(age=18) & Q(age=19)) # 解決了 Django API 中沒有與的問題
obj3 = models.StudentDetail.objects.filter(~ Q(age=19)) # 解決了 Django API 中沒有非的問題
手動創建邏輯關系
q1 = Q() # 創建 Q 對象
q1.connector = 'OR' # q1 內部為 or 關系,即 age=18 或 age =19
q1.children.append(('age', 18))
q1.children.append(('age', 19))
q2 = Q() # 創建 Q 對象
q2.connector = 'AND' # q2 內部為 and 關系
q2.children.append(('gender', 0))
q3 = Q() # 創建 Q 對象
q3.add(q1, 'AND') # 將 q1、q2 都添加到 q3 中,創建總的過濾條件
q3.add(q2, 'AND')
obj = models.StudentDetail.objects.filter(q3)
print(obj) # <QuerySet [<StudentDetail: StudentDetail object (1)>]>
# 原生 SQL
SELECT "app_studentdetail"."id", "app_studentdetail"."age", "app_studentdetail"."gender", "app_studentdetail"."height", "app_studentdetail"."email" FROM "app_studentdetail" WHERE (("app_studentdetail"."age" = 18 OR "app_studentdetail"."age" = 19) AND "app_studentdetail"."gender" = 0) LIMIT 21;
- Q 對象 可以和關鍵字參數一起查詢使用,不管一定要在關鍵字參數前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')
- 應用
import datetime
obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15)))
print(obj)