ORM 的字段DateField 和DateTimeField 以及TimeField的區(qū)別
存儲的內(nèi)容不同
DateField: date() 日期
DateTimeField: datetime() 日期時間
TimeField: time() 時間

這個三個字段都有兩個關(guān)鍵的屬性:auto_now_add 和 auto_add
auto_now_add: 創(chuàng)建的時候自動填充當(dāng)前時間
auto_add: 每次修改的時候自動把當(dāng)前時間更新
關(guān)系字段的設(shè)計原則
1. ForeignKey(to='類名' ,related_name='xxx) -> 1對多 , 外鍵通常設(shè)置在多的那一個模型類中
2. ManyToMany(to='類名',related_name='xxx') -> 多對多, 外鍵通常設(shè)置在正向查詢比較多的那一個模型類中
related_name的作用:
下面我們定義兩個模型,買主和水果.一個買主對應(yīng)多個水果.
lass Buyer(models.Model):
name = models.CharField(verbose_name='買主名', max_length=10)
Alipay_id = models.CharField(verbose_name='支付寶賬號')
age = models.IntegerField(verbose_name='買主年齡',blank = True)
class Fruit(models.Model):
buyer = models.ForeignKey(Buyer, related_name='buyer_fruit')
fruit_name = models.CharField(verbose_name='水果名', max_length=10)
weight = models.FloatField(verbose_name='水果重量')
如果我們要查詢某個買主都買了哪些水果的時候,我們一般先找到買主,再根據(jù)買主找到它對應(yīng)的水果.
也就是說如果從1找到多.
#首先獲得水果模型中外鍵指向的表中對象:
buyer = Buyer.objects.filter(age = 100).first()
#然后通過‘_set’方法獲得子表中的數(shù)據(jù):
fruits = buyer.fruit_set.all()
"""
django 默認(rèn)每個主表的對象都有一個是外鍵的屬性,可以通過它來查詢到所有屬于主表的子表的信息。
這個屬性的名稱默認(rèn)是以子表的名稱小寫加上_set()來表示,這里我們的主表是buyer,字表是fruit,所以主表外鍵的屬性就是fruit_set
"""
上面的fruit_set是django為對象buyer默認(rèn)創(chuàng)建的外鍵的屬性,個人建議采用自定義的方式定義主表的外鍵,這樣使用時更熟悉一些吧!而related_name就實現(xiàn)這個功能,在字表中定義外鍵時,增加related_name字段指定這個字表在主表中對應(yīng)的外鍵屬性, 如下:
#首先獲得水果模型中外鍵指向的表中對象:
buyer = Buyer.objects.filter(age = 100).first()
#然后通過子表中自定義的外鍵獲取子表的所有信息:
fruits = buyer.buyer_fruit.all()
ORM操作必知必會13條


values()簡單的說,可以理解成展示models指定列的值
單表查詢之神奇的雙下劃線
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 獲取id大于1 且 小于10的值
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 獲取id等于11、22、33的數(shù)據(jù)
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
models.Tb1.objects.filter(name__contains="ven") # 獲取name字段包含"ven"的
models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感
models.Tb1.objects.filter(id__range=[1, 3]) # id范圍是1到3的,等價于SQL的bettwen and
類似的還有:startswith,istartswith, endswith, iendswith
date字段還可以:
models.Class.objects.filter(first_day__year=2017)
ForeignKey操作
正向查找之對象查找(跨表)
book_obj = models.Book.objects.first()
book_obj.publisher.name # 獲取書的對象關(guān)聯(lián)的外鍵對象,然后取到它的屬性名字
正向查找之字段查找(跨表)
publisher_name = models.Book.objects.values('publisher__name')
反向操作之對象查找(跨表)
publisher_obj = models.Publisher.objects().first()
all_books = publisher_obj.book_set.all #找出第一個出版社所有的書籍
titles = all_books.values_list('title') # 找出第一個出版社所有的書籍的所有的書名
基于related_name的查找:
反向操作之字段查找(跨表)
titles = models.Publisher.objects.values_list('book_title')
分組和聚合
聚合
把一些數(shù)據(jù)求和或者求平均值或者求最大值或者求最小值,或者統(tǒng)計一些字段出現(xiàn)的個數(shù).
使用的聚合函數(shù)是aggregate()
aggregate()是QuerySet的一個終止子句,意思就是說,它返回一個包含了一些鍵值對的字典.
其中字典的鍵是聚合值得標(biāo)識符,值時計算出來的聚合值.鍵的名稱是按照字段和聚合函數(shù)的名稱中間加單下劃線自動生成出來的.也可以自己指定.
>>> from django.db.models import Avg, Sum, Max, Min, Count
>>> models.Book.objects.all().aggregate(Avg("price"))
{'price__avg': 13.233333}
>>> models.Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 13.233333}
如果你想不止一個聚合值,可以使用如下方式
>>> models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 13.233333, 'price__max': Decimal('19.90'), 'price__min': Decimal('9.90')}
分組
我們先復(fù)習(xí)一下Sql語句,假設(shè)有一張公司員工表:

我們使用原生的SQL語句,按照部門分組求平均工資
select dept,AVG(salary) from employee group by dept;
ORM查詢
Employee.objects.values("dept").annotate(avg=Avg('salary)).values(dept,'avg')
連表查詢的分組

SQL查詢
select dept.name, AVG(salary) from employee inner join dept on (employee.dept_id=dept.id) group by dept_id;
ORM查詢
models.Dept.objects.all().annotate(avg=Avg("employee__salary")).values('name','avg').
更過示例
統(tǒng)計一本書的作者的個數(shù)
book_list = models.Book.objects().all().annocate(author_num=Count('author'))
for book_obj in book_list:
print(book_obj.author_num)
統(tǒng)計出每個出版社買的最便宜的書的價格
publisher_list = models.Publisher.objects.annocate(min_price=Min("price"))
for publisher_obj in publisher_list:
print(publisher_obj.min_price)
方法二
models.Book.objects.values("publisher_name").annocate(min_price=Min('price'))
統(tǒng)計不止一個作者的圖書
models.Book.objects.annocate(author_num=Count('author')).filter(author_num_gt=1)
根據(jù)一本圖書作者數(shù)量的多少查詢集QuerySet進行排序
models.Book.objects.annocate(author_num=Count('author')).order_by('author_num')
查詢各個作者出的書的總價格
models.Author.objects.annocate(sum_price=Sum('book_price')).values()
F 查詢和 Q查詢
首先簡單點說 F的功能就是取出某個字段的值.
之前我們所有的比較也好,過濾也好都是將一個字段的值與常量做比較.如果我們要比較兩個字段的值,該怎么辦呢?
Django提供了F()模塊,F()可以在查詢中引用字段,來比較同一個model實例中兩個不同字段的值.
示例1 查詢評論數(shù)大于收藏數(shù)的書籍
Book.objects.filter(comment_num_gt=F('keep_num'))
Django 支持F() 對象之間以及F()對象和常數(shù)之間的加減乘除和取模的操作
models.Book.objects.filter(comment_num_lt=F('keep_num')*2)
修改操作也可以使用F函數(shù),比如將每一本書的價格提高30元
models.Book.objects.all().update(F('price')+30)
注意F查詢一定是在查詢的基礎(chǔ)上,也就是它之前必須是一個QuerySet對象
如果要修改char字段咋辦?
如: 把所有的書名后面加上(第一版)
models.Book.objects.all().update(title=Concat(F('title'),Value("(''),Value('第一版'),Value(")")))
Q查詢
Q的作用是包裝查詢的對象,可以進行& 和 | 操作,還可以取反(非),我們進行的一般的filter,多參數(shù)查詢一般都是AND進行操作,如果想要進行or語句操作,就可以使用Q查詢.
示例1: 查詢作者名是小仙女或者小魔女的
models.Book.objects.filter(Q(author_name='小仙女')|Q(author_name='小魔女'))
更加復(fù)雜的操作: 查詢作者名字是小仙女并且不是2018年出版的書的書名
models.Book.objects.filter(Q(author_name='小仙女') & ~Q(publisher_date_year=2018)).values_list('title')
查詢函數(shù)可以混合使用Q 對象和關(guān)鍵字參數(shù)。所有提供給查詢函數(shù)的參數(shù)(關(guān)鍵字參數(shù)或Q 對象)都將"AND”在一起。但是,如果出現(xiàn)Q 對象,它必須位于所有關(guān)鍵字參數(shù)的前面。
例如:查詢出版年份是2017或2018,書名中帶物語的所有書。
models.Book.objects.filter(Q(publisher_date_year=2018) | Q(publisher_date_year=2017),title_icontains='物語')
鎖和事務(wù)
MySql鎖概述
MySql不同的存儲引擎支持不同的鎖機制.例如MyISAM和MEMORY存儲引擎采用的是表級鎖,而BDB存儲引擎采用的是頁面鎖,也支持表級鎖.而InnoDB既支持行級鎖,也支持表級鎖,默認(rèn)使用行級鎖.
Mysql的鎖的性能大致上可以從它的如下方面來考慮性能: 開銷,加鎖速度,死鎖,粒度,并發(fā)性能
表級鎖: 開銷小,加鎖快,不會出現(xiàn)死鎖.鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)最低(因為它鎖定的范圍最大)
行級鎖: 開銷大,加鎖慢,會出現(xiàn)死鎖.鎖定粒度小,發(fā)生鎖沖突的概率最低,并發(fā)最高(因為它鎖定的范圍最小)
頁面鎖: 開銷和加鎖時間都是介于表級鎖和行級鎖之間.
我們的MySQL數(shù)據(jù)庫 ORM中的鎖和事務(wù)
鎖
select_for_update(nowait=False,skip_locked=False)
返回一個鎖住行直到事務(wù)結(jié)束的查詢集,如果數(shù)據(jù)庫支持,它將生成一個SELECT ... FOR UPDATE 語句
entries = Entry.objects.select_for_update().filter(author=request.user)
所有匹配的行將被鎖定,直到事務(wù)結(jié)束.這意味著可以通過鎖防止數(shù)據(jù)被其他事務(wù)修改.
一般情況下如果其他事務(wù)鎖定了相關(guān)行,那么本查詢將被阻塞,直到鎖釋放.
如果這不想要使查詢阻塞的話,使用select_for_update(nowait=True)。 如果其它事務(wù)持有沖突的鎖, 那么查詢將引發(fā) DatabaseError 異常。你也可以使用select_for_update(skip_locked=True)忽略鎖定的行。 nowait和skip_locked是互斥的,同時設(shè)置會導(dǎo)致ValueError。
目前,postgresql,oracle和mysql數(shù)據(jù)庫后端支持select_for_update()。 但是,MySQL不支持nowait和skip_locked參數(shù)。
使用不支持這些選項的數(shù)據(jù)庫后端(如MySQL)將nowait=True或skip_locked=True轉(zhuǎn)換為select_for_update()將導(dǎo)致拋出DatabaseError異常,這可以防止代碼意外終止。
事務(wù)
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/4 10:17'
import os
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE','BMS.settings')
import django
django.setup()
import datetime
try:
from django.db import transaction
with transaction.atomic():
new_publisher = models.Publisher.objcets.create(name='火星出版社')
models.Book.objects.create(title='橘子物語',publisher_date=datetime.date.today(),publisher_id=10)
except Exception as e:
print(str(e))
Django ORM執(zhí)行原生的SQL語句


返回QuerySet的方法大全
##################################################################
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
##################################################################
def all(self)
# 獲取所有的數(shù)據(jù)對象
def filter(self, *args, **kwargs)
# 條件查詢
# 條件可以是:參數(shù),字典,Q
def exclude(self, *args, **kwargs)
# 條件查詢
# 條件可以是:參數(shù),字典,Q
def select_related(self, *fields)
性能相關(guān):表之間進行join連表操作,一次性獲取關(guān)聯(lián)的數(shù)據(jù)。
總結(jié):
1. select_related主要針一對一和多對一關(guān)系進行優(yōu)化。
2. select_related使用SQL的JOIN語句進行優(yōu)化,通過減少SQL查詢的次數(shù)來進行優(yōu)化、提高性能。
def prefetch_related(self, *lookups)
性能相關(guān):多表連表操作時速度會慢,使用其執(zhí)行多次SQL查詢在Python代碼中實現(xiàn)連表操作。
總結(jié):
1. 對于多對多字段(ManyToManyField)和一對多字段,可以使用prefetch_related()來進行優(yōu)化。
2. prefetch_related()的優(yōu)化方式是分別查詢每個表,然后用Python處理他們之間的關(guān)系。
def annotate(self, *args, **kwargs)
# 用于實現(xiàn)聚合group by查詢
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct()
# select distinct nid from userinfo
注:只有在PostgreSQL中才能使用distinct進行去重
def order_by(self, *field_names)
# 用于排序
models.UserInfo.objects.all().order_by('-id','age')
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 構(gòu)造額外的查詢條件或者映射,如:子查詢
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse則是倒序,如果多個排序則一一倒序
def defer(self, *fields):
models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id')
#映射中排除某列數(shù)據(jù)
def only(self, *fields):
#僅取某個表中的數(shù)據(jù)
models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id')
def using(self, alias):
指定使用的數(shù)據(jù)庫,參數(shù)為別名(setting中的設(shè)置)
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 執(zhí)行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表時,必須將名字設(shè)置為當(dāng)前UserInfo對象的主鍵列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 為原生SQL設(shè)置參數(shù)
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 將獲取的到列名轉(zhuǎn)換為指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定數(shù)據(jù)庫
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 獲取每行數(shù)據(jù)為字典格式
def values_list(self, *fields, **kwargs):
# 獲取每行數(shù)據(jù)為元祖
def dates(self, field_name, kind, order='ASC'):
# 根據(jù)時間進行某一部分進行去重查找并截取指定內(nèi)容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC" "DESC"
# 并獲取轉(zhuǎn)換后的時間
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根據(jù)時間進行某一部分進行去重查找并截取指定內(nèi)容,將時間轉(zhuǎn)換為指定時區(qū)時間
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC" "DESC"
# tzinfo時區(qū)對象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet對象
####################################
# METHODS THAT DO DATABASE QUERIES #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函數(shù),獲取字典類型聚合結(jié)果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 獲取個數(shù)
def get(self, *args, **kwargs):
# 獲取單個對象
def create(self, **kwargs):
# 創(chuàng)建對象
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的個數(shù)
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,則獲取,否則,創(chuàng)建
# defaults 指定創(chuàng)建時,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,則更新,否則,創(chuàng)建
# defaults 指定創(chuàng)建時或更新時的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 獲取第一個
def last(self):
# 獲取最后一個
def in_bulk(self, id_list=None):
# 根據(jù)主鍵ID進行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 刪除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有結(jié)果
Django終端打印SQL語句
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
ORM表關(guān)系
一對一
# 作者
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
phone = models.CharField(max_length=11)
detail = models.OneToOneField(to="AuthorDetail")
def __str__(self):
return self.name
# 作者詳情
class AuthorDetail(models.Model):
addr = models.CharField(max_length=64)
email = models.EmailField()
什么時候使用一對一?
當(dāng)一張表的某一些字段查詢的比較頻繁,另外一些字段查詢的不是特別頻繁.把不怎么常用的字段,單獨拿出來做成一張表,然后用一對一關(guān)聯(lián)起來.
優(yōu)勢
既保證了數(shù)據(jù)完整的保存了下來,又能保證大部分的檢索更快.
ORM中的用法
detail = models.OneToOneField(to="AuthorDetail")
# 先獲取Author對象,然后利用detail對象中屬性,獲取detail字段的屬性值.
models.Author.objects().get(id=1).detail.addr
多對多關(guān)聯(lián)關(guān)系的三種方式
方式1: 自行創(chuàng)建第三張表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="書名")
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
# 自己創(chuàng)建第三張表,分別通過外鍵關(guān)聯(lián)書和作者
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
class Meta:
unique_together = ("author", "book")
方法二: 通過ManyToManyField自動創(chuàng)建第三張表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="書名")
# 通過ORM自帶的ManyToManyField自動創(chuàng)建第三張表
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", related_name="authors")
方法三:設(shè)置ManyToManyField并指定自行創(chuàng)建的第三張表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="書名")
# 自己創(chuàng)建第三張表,并通過ManyToManyField指定關(guān)聯(lián)
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
# through_fields接受一個2元組('field1','field2'):
# 其中field1是定義ManyToManyField的模型外鍵的名(author),field2是關(guān)聯(lián)目標(biāo)模型(book)的外鍵名。
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
class Meta:
unique_together = ("author", "book")
注意:
當(dāng)我們需要在第三張關(guān)系表中存儲額外的字段時,就要使用第三種方式。
但是當(dāng)我們使用第三種方式創(chuàng)建多對多關(guān)聯(lián)關(guān)系時,就無法使用set、add、remove、clear方法來管理多對多的關(guān)系了,需要通過第三張表的model來管理多對多關(guān)系。
我們應(yīng)該使用哪一種呢?
看情況:
1. 如果你第三張表沒有額外的字段,就用第一種
2.如果你第三張表有額外的字段,就用第三中或者第一種
元信息

ORM查詢的練習(xí)
查找所有書名里包含番茄的書
models.Book.objects.filter(title_contains='沙河')
查找出版日期是2017年的書
models.Book.objects.filter(publisher_date__year=2018)
查找出版日期是2017年的書名
models.Book.objects.filter(publisher_date_year=2018).values('name')
查找價格大于10元的書
models.Book.objects.filter(price__gt=10)
將所有的書按照價格倒序排序
modes.Book.objcets.all().order_by('-price')
或者
models.Book.objcets.all().order_by('price').reverse()
查找價格大于10元的書名和價格
models.Book.objects.filter(price__gt=10).values('title','price')
查找memo字段是空的書
models.Book.objects.filter(memo=null)
查詢所有的書關(guān)聯(lián)的出版社
ret = models.Book.objects().all().values_list('publisher_name')
print(ret)
print(ret.distinct()) # 對QuerySet去重
查找在北京的出版社
models.Publisher.objects.filter(city='北京')
查找名字以沙河開頭的出版社
models.Publisher.objects.filter(city__startwith='沙河')
查找作者名字里面帶“小”字的作者
查找年齡大于30歲的作者
查找手機號是155開頭的作者
查找手機號是155開頭的作者的姓名和年齡
查找書名是“番茄物語”的書的出版社
查找書名是“番茄物語”的書的出版社所在的城市
models.Book.objects.filter(title='番茄物語').values('publisher_city')
查找書名是“番茄物語”的書的出版社的名稱
models.Book.objects.filter(title='番茄物語').
查找書名是沙河異聞錄的書的作者的愛好(跨兩張表)
models.Book.objects.filter(title='沙河異聞錄').values('authors__detail__hobby')
查找書名是“番茄物語”的書的所有作者
查找書名是“番茄物語”的書的作者的年齡
查找書名是“番茄物語”的書的作者的手機號碼
查找書名是“番茄物語”的書的作者的地址
查找書名是“番茄物語”的書的作者的郵箱
csrf 的簡單用法
什么是csrf?
跨站請求偽造,我們的服務(wù)器的后端對于表單的提交是通過action來判斷的,如果不用csrf_token機制的話,就沒辦法區(qū)分這個提交是不是來自本站.Django 的csrf_token,Django的中間件會在我們每次提交表單的時候,從服務(wù)器那里生成一個csrf_token.在提交的時候,會用這個隱藏的值進行校驗,如果校驗成功,則表示是本站的請求,允許提交,如果沒有,則提交不成功.{%csrf_token%}
分頁
books/?page=1/ 展示相應(yīng)頁數(shù)的數(shù)據(jù).
當(dāng)數(shù)據(jù)庫中數(shù)據(jù)有很多,我們通常會在前端頁面做分頁展示。
分頁的數(shù)據(jù)可以在前端頁面實現(xiàn),也可以在后端實現(xiàn)分頁。
后端實現(xiàn)分頁的原理就是每次只請求一頁數(shù)據(jù)。
準(zhǔn)備工作
我們使用腳本批量創(chuàng)建一些測試數(shù)據(jù)(將下面的代碼保存到bulk_create.py文件中放到Django項目的根目錄,直接執(zhí)行即可。):
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup()
from app01 import models
bulk_obj = (models.Publisher(name='沙河第{}出版社'.format(i)) for i in range(300))
models.Publisher.objects.bulk_create(bulk_obj)
自定義分頁
def publisher_list(request):
# 從URL中取當(dāng)前訪問的頁碼數(shù)
try:
current_page = int(request.GET.get('page'))
except Exception as e:
# 取不到或者頁碼數(shù)不是數(shù)字都默認(rèn)展示第1頁
current_page = 1
# 總數(shù)據(jù)量
total_count = models.Publisher.objects.count()
# 定義每頁顯示多少條數(shù)據(jù)
per_page = 10
# 計算出總頁碼數(shù)
total_page, more = divmod(total_count, per_page)
if more:
total_page += 1
# 定義頁面上最多顯示多少頁碼(為了左右對稱,一般設(shè)為奇數(shù))
max_show = 11
half_show = max_show // 2
# 計算一下頁面顯示的頁碼范圍
if total_page <= max_show: # 總頁碼數(shù)小于最大顯示頁碼數(shù)
page_start = 1
page_end = total_page
elif current_page + half_show >= total_page: # 右邊越界
page_end = total_page
page_start = total_page - max_show
elif current_page - half_show <= 1: # 左邊越界
page_start = 1
page_end = max_show
else: # 正常頁碼區(qū)間
page_start = current_page - half_show
page_end = current_page + half_show
# 數(shù)據(jù)索引起始位置
data_start = (current_page-1) * per_page
data_end = current_page * per_page
publisher_list = models.Publisher.objects.all()[data_start:data_end]
# 生成頁面上顯示的頁碼
page_html_list = []
page_html_list.append('<nav aria-label="Page navigation"><ul class="pagination">')
# 加首頁
first_li = '<li><a href="/publisher_list/?page=1">首頁</a></li>'
page_html_list.append(first_li)
# 加上一頁
if current_page == 1:
prev_li = '<li><a href="#"><span aria-hidden="true">«</span></a></li>'
else:
prev_li = '<li><a href="/publisher_list/?page={}"><span aria-hidden="true">«</span></a></li>'.format(current_page - 1)
page_html_list.append(prev_li)
for i in range(page_start, page_end + 1):
if i == current_page:
li_tag = '<li class="active"><a href="/publisher_list/?page={0}">{0}</a></li>'.format(i)
else:
li_tag = '<li><a href="/publisher_list/?page={0}">{0}</a></li>'.format(i)
page_html_list.append(li_tag)
# 加下一頁
if current_page == total_page:
next_li = '<li><a href="#"><span aria-hidden="true">»</span></a></li>'
else:
next_li = '<li><a href="/publisher_list/?page={}"><span aria-hidden="true">»</span></a></li>'.format(current_page + 1)
page_html_list.append(next_li)
# 加尾頁
page_end_li = '<li><a href="/publisher_list/?page={}">尾頁</a></li>'.format(total_page)
page_html_list.append(page_end_li)
page_html_list.append('</ul></nav>')
page_html = "".join(page_html_list)
return render(request, "publisher_list.html", {"publisher_list": publisher_list, "page_html": page_html})
穩(wěn)扎穩(wěn)打版
封裝保存版本
class Pagination(object):
"""自定義分頁(Bootstrap版)"""
def __init__(self, current_page, total_count, base_url, per_page=10, max_show=11):
"""
:param current_page: 當(dāng)前請求的頁碼
:param total_count: 總數(shù)據(jù)量
:param base_url: 請求的URL
:param per_page: 每頁顯示的數(shù)據(jù)量,默認(rèn)值為10
:param max_show: 頁面上最多顯示多少個頁碼,默認(rèn)值為11
"""
try:
self.current_page = int(current_page)
except Exception as e:
# 取不到或者頁碼數(shù)不是數(shù)字都默認(rèn)展示第1頁
self.current_page = 1
# 定義每頁顯示多少條數(shù)據(jù)
self.per_page = per_page
# 計算出總頁碼數(shù)
total_page, more = divmod(total_count, per_page)
if more:
total_page += 1
self.total_page = total_page
# 定義頁面上最多顯示多少頁碼(為了左右對稱,一般設(shè)為奇數(shù))
self.max_show = max_show
self.half_show = max_show // 2
self.base_url = base_url
@property
def start(self):
return (self.current_page-1) * self.per_page
@property
def end(self):
return self.current_page * self.per_page
def page_html(self):
# 計算一下頁面顯示的頁碼范圍
if self.total_page <= self.max_show: # 總頁碼數(shù)小于最大顯示頁碼數(shù)
page_start = 1
page_end = self.total_page
elif self.current_page + self.half_show >= self.total_page: # 右邊越界
page_end = self.total_page
page_start = self.total_page - self.max_show
elif self.current_page - self.half_show <= 1: # 左邊越界
page_start = 1
page_end = self.max_show
else: # 正常頁碼區(qū)間
page_start = self.current_page - self.half_show
page_end = self.current_page + self.half_show
# 生成頁面上顯示的頁碼
page_html_list = []
page_html_list.append('<nav aria-label="Page navigation"><ul class="pagination">')
# 加首頁
first_li = '<li><a href="{}?page=1">首頁</a></li>'.format(self.base_url)
page_html_list.append(first_li)
# 加上一頁
if self.current_page == 1:
prev_li = '<li><a href="#"><span aria-hidden="true">«</span></a></li>'
else:
prev_li = '<li><a href="{}?page={}"><span aria-hidden="true">«</span></a></li>'.format(
self.base_url, self.current_page - 1)
page_html_list.append(prev_li)
for i in range(page_start, page_end + 1):
if i == self.current_page:
li_tag = '<li class="active"><a href="{0}?page={1}">{1}</a></li>'.format(self.base_url, i)
else:
li_tag = '<li><a href="{0}?page={1}">{1}</a></li>'.format(self.base_url, i)
page_html_list.append(li_tag)
# 加下一頁
if self.current_page == self.total_page:
next_li = '<li><a href="#"><span aria-hidden="true">»</span></a></li>'
else:
next_li = '<li><a href="{}?page={}"><span aria-hidden="true">»</span></a></li>'.format(
self.base_url, self.current_page + 1)
page_html_list.append(next_li)
# 加尾頁
page_end_li = '<li><a href="{}?page={}">尾頁</a></li>'.format(self.base_url, self.total_page)
page_html_list.append(page_end_li)
page_html_list.append('</ul></nav>')
return "".join(page_html_list)
封裝保存版使用示例
def publisher_list(request):
# 從URL中取當(dāng)前訪問的頁碼數(shù)
current_page = int(request.GET.get('page'))
# 比len(models.Publisher.objects.all())更高效
total_count = models.Publisher.objects.count()
page_obj = Pagination(current_page, total_count, request.path_info)
data = models.Publisher.objects.all()[page_obj.start:page_obj.end]
page_html = page_obj.page_html()
return render(request, "publisher_list.html", {"publisher_list": data, "page_html": page_html})
Cookie和Session
為什么需要Cookie?
HTTP協(xié)議是無狀態(tài)的協(xié)議,也就是說服務(wù)器不會記錄上次的請求狀態(tài),它會把每一次的請求當(dāng)成一個全新的請求.
如果我們需要保存用戶的一些狀態(tài),就需要一種機制.然后就引入了Cookie機制,Cookie就是用來保存用戶的請求狀態(tài)的.
Cookie的原理
用戶第一次請求的時候,服務(wù)器會先判斷有沒有session ID,如果沒有在響應(yīng)的時候就給用戶分配一個,然后將一些數(shù)據(jù)保存都服務(wù)器上,然后響應(yīng)的時候會將一些標(biāo)識用戶信息的內(nèi)容響應(yīng)給用戶.用戶收到這些響應(yīng)之后,會將這些信息以鍵值對的形式保存在瀏覽器上,當(dāng)再次訪問的時候,瀏覽器會帶上這些cookie,然后服務(wù)器就知道了具體是誰請求的了.
Django中Cookie的操作
request.COOKIES['key']
request.get_signed_cookie(key,default=RAISE_ERROR,salt='',max_age=None)
- 參數(shù):
default: 默認(rèn)值
salt: 加密鹽
max_age: 后臺控制過期時間

Cookie版本登錄校驗:
def check_login(func):
@wraps(func)
def inner(request, *args, **kwargs):
next_url = request.get_full_path()
if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
# 已經(jīng)登錄的用戶...
return func(request, *args, **kwargs)
else:
# 沒有登錄的用戶,跳轉(zhuǎn)剛到登錄頁面
return redirect("/login/?next={}".format(next_url))
return inner
def login(request):
if request.method == "POST":
username = request.POST.get("username")
passwd = request.POST.get("password")
if username == "xxx" and passwd == "dashabi":
next_url = request.GET.get("next")
if next_url and next_url != "/logout/":
response = redirect(next_url)
else:
response = redirect("/class_list/")
response.set_signed_cookie("login", "yes", salt="SSS")
return response
return render(request, "login.html")
cookie版登錄
Session
Cookie雖然在一定程度上解決了“保持狀態(tài)”的需求,但是由于Cookie本身最大支持4096字節(jié),以及Cookie本身保存在客戶端,可能被攔截或竊取,因此就需要有一種新的東西,它能支持更多的字節(jié),并且他保存在服務(wù)器,有較高的安全性。這就是Session。
問題來了,基于HTTP協(xié)議的無狀態(tài)特征,服務(wù)器根本就不知道訪問者是“誰”。那么上述的Cookie就起到橋接的作用。
我們可以給每個客戶端的Cookie分配一個唯一的id,這樣用戶在訪問時,通過Cookie,服務(wù)器就知道來的人是“誰”。然后我們再根據(jù)不同的Cookie的id,在服務(wù)器上保存一段時間的私密資料,如“賬號密碼”等等。
總結(jié)而言:Cookie彌補了HTTP無狀態(tài)的不足,讓服務(wù)器知道來的人是“誰”;但是Cookie以文本的形式保存在本地,自身安全性較差;所以我們就通過Cookie識別不同的用戶,對應(yīng)的在Session里保存私密的信息以及超過4096字節(jié)的文本。
另外,上述所說的Cookie和Session其實是共通性的東西,不限于語言和框架。
什么是Session?
Session是另一種記錄客戶狀態(tài)的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務(wù)器上。客戶端瀏覽器訪問服務(wù)器的時候,服務(wù)器把客戶端信息以某種形式記錄在服務(wù)器上。這就是Session??蛻舳藶g覽器再次訪問時只需要從該Session中查找該客戶的狀態(tài)就可以了。
如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那么Session機制就是通過檢查服務(wù)器上的“客戶明細(xì)表”來確認(rèn)客戶身份。Session相當(dāng)于程序在服務(wù)器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。
Cookie和Session的區(qū)別?
1. Cookie數(shù)據(jù)存放在瀏覽器客戶端上,而Session是存放在服務(wù)器上
2.Cookie不是很安全,別人可以分析存在的本地的Cookie并進行Cookie欺詐,考慮到安全的話應(yīng)該使用Session
3.session會在一定的時間內(nèi)保存到服務(wù)器上,當(dāng)訪問量增多的時候,會比較占用服務(wù)器的性能,所以考慮到性能方面,應(yīng)該多使用Cookie
4.單個cookie保存的數(shù)據(jù)不得多于4K,很多瀏覽器都限制一個站點最多保存20條cookie
5.所以個人建議:
將登錄信息等重要的信息存放為Session
其他信息不是很私密的如果需要保存,可以存放到Cookie中.
Ajax請求
什么是JSON
- Json是指JavaScript對象表示法(JavaScript Object Notation)
- Json獨立于語言
- Json具有自我描述性,更容易理解
- Json使用JavaScript語法來描述對象,但是Json仍然獨立于語言和平臺.Json解析題和Json庫支持許多不同的編程語言

合格的json對象
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["張三", "李四"] }
[ { "name": "張三"}, {"name": "李四"} ]
不合格的json對象
{ name: "張三", 'age': 32 } // 屬性名必須使用雙引號
[32, 64, 128, 0xFFF] // 不能使用十六進制值
{ "name": "張三", "age": undefined } // 不能使用undefined
{ "name": "張三",
"birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'),
"getName": function() {return this.name;} // 不能使用函數(shù)和日期對象
}
stringify與parse方法
JavaScript中關(guān)于JSON對象和字符串轉(zhuǎn)換的兩個方法:
JSON.parse(): 用于將一個Json字符串轉(zhuǎn)換為JavaScript對象
JSON.parse('{"name":"Q1mi"}');
JSON.parse('{name:"Q1mi"}') ; // 錯誤
JSON.parse('[18,undefined]') ; // 錯誤
JSON.stringify(): 用于將JavaScript值轉(zhuǎn)換為JSON字符串
JSON.stringify({'name':'mengmeng'})
AJAX 簡介
AJAX(Asynchronous Javascript And XML)翻譯成中文就是“異步的Javascript和XML”。即使用Javascript語言與服務(wù)器進行異步交互,傳輸?shù)臄?shù)據(jù)為XML(當(dāng)然,傳輸?shù)臄?shù)據(jù)不只是XML)。
AJAX 不是新的編程語言,而是一種使用現(xiàn)有標(biāo)準(zhǔn)的新方法。
AJAX 最大的優(yōu)點是在不重新加載整個頁面的情況下,可以與服務(wù)器交換數(shù)據(jù)并更新部分網(wǎng)頁內(nèi)容。(這一特點給用戶的感受是在不知不覺中完成請求和響應(yīng)過程)
AJAX 不需要任何瀏覽器插件,但需要用戶允許JavaScript在瀏覽器上執(zhí)行。
同步交互:客戶端發(fā)出一個請求后,需要等待服務(wù)器響應(yīng)結(jié)束后,才能發(fā)出第二個請求;
異步交互:客戶端發(fā)出一個請求后,無需等待服務(wù)器響應(yīng)結(jié)束,就可以發(fā)出第二個請求。
Ajax示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AJAX局部刷新實例</title>
</head>
<body>
<input type="text" id="i1">+
<input type="text" id="i2">=
<input type="text" id="i3">
<input type="button" value="AJAX提交" id="b1">
<script src="/static/jquery-3.2.1.min.js"></script>
<script>
$("#b1").on("click", function () {
$.ajax({
url:"/ajax_add/",
type:"GET",
data:{"i1":$("#i1").val(),"i2":$("#i2").val()},
success:function (data) {
$("#i3").val(data);
}
})
})
</script>
</body>
</html>
HTML部分代碼
def ajax_demo1(request):
return render(request, "ajax_demo1.html")
def ajax_add(request):
i1 = int(request.GET.get("i1"))
i2 = int(request.GET.get("i2"))
ret = i1 + i2
return JsonResponse(ret, safe=False)
views.py
urlpatterns = [
...
url(r'^ajax_add/', views.ajax_add),
url(r'^ajax_demo1/', views.ajax_demo1),
...
]
urls.py
Ajax的常見應(yīng)用場景
搜索引擎根據(jù)用戶輸入的關(guān)鍵字,自動提示檢索關(guān)鍵字。
還有一個很重要的應(yīng)用場景就是注冊時候的用戶名的查重。
其實這里就使用了AJAX技術(shù)!當(dāng)文件框發(fā)生了輸入變化時,使用AJAX技術(shù)向服務(wù)器發(fā)送一個請求,然后服務(wù)器會把查詢到的結(jié)果響應(yīng)給瀏覽器,最后再把后端返回的結(jié)果展示出來。
整個過程中頁面沒有刷新,只是刷新頁面中的局部位置而已!
當(dāng)請求發(fā)出后,瀏覽器還可以進行其他操作,無需等待服務(wù)器的響應(yīng)!

當(dāng)輸入用戶名后,把光標(biāo)移動到其他表單項上時,瀏覽器會使用AJAX技術(shù)向服務(wù)器發(fā)出請求,服務(wù)器會查詢名為lemontree7777777的用戶是否存在,最終服務(wù)器返回true表示名為lemontree7777777的用戶已經(jīng)存在了,瀏覽器在得到結(jié)果后顯示“用戶名已被注冊!”。
整個過程中頁面沒有刷新,只是局部刷新了;
在請求發(fā)出后,瀏覽器不用等待服務(wù)器響應(yīng)結(jié)果就可以進行其他操作;
Ajax的優(yōu)缺點
優(yōu)點:
AJAX使用JavaScript技術(shù)向服務(wù)器發(fā)送異步請求;
AJAX請求無須刷新整個頁面;
因為服務(wù)器響應(yīng)內(nèi)容不再是整個頁面,而是頁面中的部分內(nèi)容,所以AJAX性能高;
AJAX請求如何設(shè)置csrf_token
通過隱藏的input標(biāo)簽中的csrfmiddlewaretoken值,放置在data中發(fā)送
方法1
$.ajax({
url: "/cookie_ajax/",
type: "POST",
data: {
"username": "Q1mi",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})
方法2
通過獲取返回的cookie中的字符串 放置在請求頭中發(fā)送。
注意:需要引入一個jquery.cookie.js插件。
$.ajax({
url: "/cookie_ajax/",
type: "POST",
headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 從Cookie取csrftoken,并設(shè)置到請求頭中
data: {"username": "Q1mi", "password": 123456},
success: function (data) {
console.log(data);
}
})
或者用自己寫一個getCookie方法:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
每一次都這么寫太麻煩了,可以使用$.ajaxSetup()方法為ajax請求統(tǒng)一設(shè)置。
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
注意:
如果使用從cookie中取csrftoken的方式,需要確保cookie存在csrftoken值。
如果你的視圖渲染的HTML文件中沒有包含 {% csrf_token %},Django可能不會設(shè)置CSRFtoken的cookie。
這個時候需要使用ensure_csrf_cookie()裝飾器強制設(shè)置Cookie。
django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def login(request):
pass
Ajax上傳文件
XMLHttpRequest 是一個瀏覽器接口,通過它,我們可以使得 Javascript 進行 HTTP (S) 通信。XMLHttpRequest 在現(xiàn)在瀏覽器中是一種常用的前后臺交互數(shù)據(jù)的方式。2008年 2 月,XMLHttpRequest Level 2 草案提出來了,相對于上一代,它有一些新的特性,其中 FormData 就是 XMLHttpRequest Level 2 新增的一個對象,利用它來提交表單、模擬表單提交,當(dāng)然最大的優(yōu)勢就是可以上傳二進制文件。下面就具體
首先看一下formData的基本用法:FormData對象,可以把所有表單元素的name與value組成一個queryString,提交到后臺。只需要把 form 表單作為參數(shù)傳入 FormData 構(gòu)造函數(shù)即可:
介紹一下如何利用 FormData 來上傳文件。
// 上傳文件示例
$("#b3").click(function () {
var formData = new FormData();
formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
formData.append("f1", $("#f1")[0].files[0]);
$.ajax({
url: "/upload/",
type: "POST",
processData: false, // 告訴jQuery不要去處理發(fā)送的數(shù)據(jù)
contentType: false, // 告訴jQuery不要去設(shè)置Content-Type請求頭
data: formData,
success:function (data) {
console.log(data)
}
})
})
或者使用
var form = document.getElementById("form1");
var fd = new FormData(form);
這樣也可以直接通過ajax的send()方法將fd發(fā)送到后臺
注意:由于FormData是XMLHttpRequest Level2 新增的接口,現(xiàn)在低于IE10 的IE瀏覽器不支持FormData
練習(xí)(用戶名是否已被注冊)
功能介紹
在注冊表單中,當(dāng)用戶填寫了用戶名后,把光標(biāo)移開后,會自動向服務(wù)器發(fā)送異步請求。服務(wù)器返回這個用戶名是否已經(jīng)被注冊過。
案例分析
頁面中給出注冊表單;
在username input標(biāo)簽中綁定onblur事件處理函數(shù)。
當(dāng)input標(biāo)簽失去焦點后獲取 username表單字段的值,向服務(wù)端發(fā)送AJAX請求;
django的視圖函數(shù)中處理該請求,獲取username值,判斷該用戶在數(shù)據(jù)庫中是否被注冊,如果被注冊了就返回“該用戶已被注冊”,否則響應(yīng)“該用戶名可以注冊”。
Form和ModelForm組件
Form介紹
我們之前在HTML頁面中利用form表單向后端提交數(shù)據(jù)時,都會寫一些獲取用戶輸入的標(biāo)簽并且用form標(biāo)簽把它們包起來。
與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如校驗用戶是否輸入,輸入的長度和格式等正不正確。如果用戶輸入的內(nèi)容有錯誤就需要在頁面上相應(yīng)的位置顯示對應(yīng)的錯誤信息.。
Django form組件就實現(xiàn)了上面所述的功能。
總結(jié)一下,其實form組件的主要功能如下:
- 生成頁面可用的HTML標(biāo)簽
- 對用戶提交的數(shù)據(jù)進行校驗
- 保留上次輸入內(nèi)容
普通方式手寫注冊功能
def register(request):
if request.method == 'POST':
# 獲取數(shù)據(jù)
username = request.POST.get('username')
password = request.POST.get('password')
# 判斷數(shù)據(jù)
if not all([username, password, len(username) > 6, len(password) > 5]):
return render(request, 'register.html', {'error_msg': '用戶名或密碼錯誤,請重新輸入'})
else:
return HttpResponse('注冊成功!')
return render(request, 'register.html')
使用Django的Form類,來進行驗證
首先,定義一個Form類,在forms.py中
from django import forms
# 按照Django form組件的要求寫一個自己的類
class RegisterForm(forms.Form):
username = forms.CharField(label='用戶名')
password = forms.CharField(label='密碼')
再寫一個視圖函數(shù)
# 使用form組件實現(xiàn)注冊方式
def register2(request):
form_obj = RegForm()
if request.method == "POST":
# 實例化form對象的時候,把post提交過來的數(shù)據(jù)直接傳進去
form_obj = RegForm(request.POST)
# 調(diào)用form_obj校驗數(shù)據(jù)的方法
if form_obj.is_valid():
return HttpResponse("注冊成功")
return render(request, "register2.html", {"form_obj": form_obj})
Form常用的字段和插件
initial
初始值,input框里面的初始值
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三" # 設(shè)置默認(rèn)值
)
pwd = forms.CharField(min_length=6, label="密碼")
error_messages
重寫錯誤信息
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三",
error_messages={
"required": "不能為空",
"invalid": "格式錯誤",
"min_length": "用戶名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密碼")
password
class LoginForm(forms.Form):
...
pwd = forms.CharField(
min_length=6,
label="密碼",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
)
使用form表單的自動解析方式,在后端使用Form表單書寫字段,然后將form_obj傳遞給前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注冊2</title>
</head>
<body>
<form action="/register2/" method="post">
{% csrf_token %}
{{ form_obj.as_p }}
{{ form_obj.errors }}
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
單radio值為字符串
gender = forms.ChoiceField(
choices=((1, '男'), (2, '女'), (3, '保密')),
label='性別',
initial=3,
widget=widgets.RadioSelect,
)
單選select
hobby = forms.ChoiceField(
choices=((1,'籃球'),(2,'足球'),(3,'雙色球'),),
label='愛好',
initial=3,
widget=forms.widgets.Select,
)
多選select
hobby1 = forms.MultipleChoiceField(
choices=((1,'籃球'),(2,'足球'),(3,'雙色球')),
label='愛好',
initial=[1,3],
widget=forms.widgets.SelectMultiple,
)
單選checkbox
keep = forms.ChoiceField(
label='是否記住密碼',
initial='checked',
widget=forms.widgets.CheckboxInput,
)
多選checkbox
hobby2 = forms.fields.MultipleChoiceField(
choices=((1, '籃球'), (2, '足球'), (3, '雙色球')),
label='愛好',
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple,
)
choice字段注意事項
在使用選擇標(biāo)簽的時候,需要注意choices的選項可以配置從數(shù)據(jù)庫中獲取,但是由于是靜態(tài)字段,獲取的值無法無法實時更新,需要重寫構(gòu)造方法從而實現(xiàn)choice實時更新.
方法一:
class Myform(forms.Form):
user = forms.ChoiceField(
# choices=((1,'上海'),(2,'北京')),
initial=2,
widget=widgets.Select,
)
def __init__(self, *args, **kwargs):
super(Myform,self).__init__(*args,**kwargs)
self.fields['user'].choices = Book.objects.all().values_list('id','caption')
方法二:
class BookForm(forms.Form):
authors = forms.models.ModelMultipleChoiceField(queryset=Book.objects.all()) #多選
authors = forms.models.ModelChoiceField(queryset=Book.objects.all()) # 單選
Django form的所有的內(nèi)置字段
Field
required=True, 是否允許為空
widget=None, HTML插件
label=None, 用于生成Label標(biāo)簽或顯示內(nèi)容
initial=None, 初始值
help_text='', 幫助信息(在標(biāo)簽旁邊顯示)
error_messages=None, 錯誤信息 {'required': '不能為空', 'invalid': '格式錯誤'}
validators=[], 自定義驗證規(guī)則
localize=False, 是否支持本地化
disabled=False, 是否可以編輯
label_suffix=None Label內(nèi)容后綴
CharField(Field)
max_length=None, 最大長度
min_length=None, 最小長度
strip=True 是否移除用戶輸入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 總長度
decimal_places=None, 小數(shù)位長度
BaseTemporalField(Field)
input_formats=None 時間格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 時間間隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正則表達式
max_length=None, 最大長度
min_length=None, 最小長度
error_message=None, 忽略,錯誤信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允許空文件
ImageField(FileField)
...
注:需要PIL模塊,pip3 install Pillow
以上兩個字典使用時,需要注意兩點:
- form表單中 enctype="multipart/form-data"
- view函數(shù)中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 選項,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默認(rèn)select插件
label=None, Label內(nèi)容
initial=None, 初始值
help_text='', 幫助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查詢數(shù)據(jù)庫中的數(shù)據(jù)
empty_label="---------", # 默認(rèn)空顯示內(nèi)容
to_field_name=None, # HTML中value的值對應(yīng)的字段
limit_choices_to=None # ModelForm中對queryset二次篩選
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 對選中的值進行一次轉(zhuǎn)換
empty_value= '' 空值的默認(rèn)值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 對選中的每一個值進行一次轉(zhuǎn)換
empty_value= '' 空值的默認(rèn)值
ComboField(Field)
fields=() 使用多個驗證,如下:即驗證最大長度20,又驗證郵箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象類,子類中可以實現(xiàn)聚合多個字典去匹配一個值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件選項,目錄下文件顯示在頁面中
path, 文件夾路徑
match=None, 正則匹配
recursive=False, 遞歸下面的文件夾
allow_files=True, 允許文件
allow_folders=False, 允許文件夾
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1時候,可解析為192.0.2.1, PS:protocol必須為both才能啟用
SlugField(CharField) 數(shù)字,字母,下劃線,減號(連字符)
...
UUIDField(CharField) uuid類型
Django Form內(nèi)置字段
字段校驗
RegexValidator驗證器
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '請輸入數(shù)字'), RegexValidator(r'^159[0-9]+$', '數(shù)字必須以159開頭')],
)
自定義驗證函數(shù)
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定義驗證規(guī)則
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手機號碼格式錯誤')
class PublishForm(Form):
title = fields.CharField(max_length=20,
min_length=5,
error_messages={'required': '標(biāo)題不能為空',
'min_length': '標(biāo)題最少為5個字符',
'max_length': '標(biāo)題最多為20個字符'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': '標(biāo)題5-20個字符'}))
# 使用自定義驗證規(guī)則
phone = fields.CharField(validators=[mobile_validate, ],
error_messages={'required': '手機不能為空'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': u'手機號碼'}))
email = fields.EmailField(required=False,
error_messages={'required': u'郵箱不能為空','invalid': u'郵箱格式錯誤'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))
Hook方法
除了以上兩種方式外,我們還可以在Form類中定義鉤子函數(shù),來實現(xiàn)自定義的驗證功能.
局部鉤子
我們在Form類中定義clean_字段名()方法,就能夠?qū)崿F(xiàn)對特定字段進行校驗
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三",
error_messages={
"required": "不能為空",
"invalid": "格式錯誤",
"min_length": "用戶名最短8位"
},
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
)
...
# 定義局部鉤子,用來校驗username字段
def clean_username(self):
value = self.cleaned_data.get("username")
if "666" in value:
raise ValidationError("光喊666是不行的")
else:
return value
全局鉤子
我們在Form類中定義clean()方法,就能夠?qū)崿F(xiàn)對字段進行全局校驗
class LoginForm(forms.Form):
...
password = forms.CharField(
min_length=6,
label="密碼",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
re_password = forms.CharField(
min_length=6,
label="確認(rèn)密碼",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
...
# 定義全局的鉤子,用來校驗密碼和確認(rèn)密碼字段是否相同
def clean(self):
password_value = self.cleaned_data.get('password')
re_password_value = self.cleaned_data.get('re_password')
if password_value == re_password_value:
return self.cleaned_data
else:
self.add_error('re_password', '兩次密碼不一致')
raise ValidationError('兩次密碼不一致')
ModelForm
通常在Django項目中,我們編寫的大部分都是與Django 的模型緊密映射的表單。 舉個例子,你也許會有個Book 模型,并且你還想創(chuàng)建一個form表單用來添加和編輯書籍信息到這個模型中。 在這種情況下,在form表單中定義字段將是冗余的,因為我們已經(jīng)在模型中定義了那些字段。
基于這個原因,Django 提供一個輔助類來讓我們可以從Django 的模型創(chuàng)建Form,這就是ModelForm。
modelform的定義
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "書名",
"price": "價格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
}
class Meta下常用參數(shù):
model = models.Book # 對應(yīng)的Model中的類
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 幫助提示信息
widgets = None # 自定義插件
error_messages = None # 自定義錯誤信息
ModelForm的驗證
與普通的Form表單驗證類型類似,ModelForm表單的驗證在調(diào)用is_valid() 或訪問errors 屬性時隱式調(diào)用。
我們可以像使用Form類一樣自定義局部鉤子方法和全局鉤子方法來實現(xiàn)自定義的校驗規(guī)則。
如果我們不重寫具體字段并設(shè)置validators屬性的化,ModelForm是按照模型中字段的validators來校驗的。
save()方法
每個ModelForm還具有一個save()方法。 這個方法根據(jù)表單綁定的數(shù)據(jù)創(chuàng)建并保存數(shù)據(jù)庫對象。 ModelForm的子類可以接受現(xiàn)有的模型實例作為關(guān)鍵字參數(shù)instance;如果提供此功能,則save()將更新該實例。 如果沒有提供,save() 將創(chuàng)建模型的一個新實例:
>>> from myapp.models import Book
>>> from myapp.forms import BookForm
# 根據(jù)POST數(shù)據(jù)創(chuàng)建一個新的form對象
>>> form_obj = BookForm(request.POST)
# 創(chuàng)建書籍對象
>>> new_ book = form_obj.save()
# 基于一個書籍對象創(chuàng)建form對象
>>> edit_obj = Book.objects.get(id=1)
# 使用POST提交的數(shù)據(jù)更新書籍對象
>>> form_obj = BookForm(request.POST, instance=edit_obj)
>>> form_obj.save()