Py006-02-04forms局部和全局钩子使用

钩子函数

定义你特殊的校验规则

局部钩子和全局钩子

  • clean_字段 校验单个字段
  • clean()全局钩子函数 如校验多个字段的时候 如两次密码是否一致
  • 需要抛出指定异常ValidationError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# forms组件
from django.forms import widgets

wid_01=widgets.TextInput(attrs={"class":"form-control"})
wid_02=widgets.PasswordInput(attrs={"class":"form-control"})

from django.core.exceptions import ValidationError
class UserForm(forms.Form):
name=forms.CharField(max_length=32,
widget=wid_01
)
pwd=forms.CharField(max_length=32,widget=wid_02)
r_pwd=forms.CharField(max_length=32,widget=wid_02)
email=forms.EmailField(widget=wid_01)
tel=forms.CharField(max_length=32,widget=wid_01)

# 局部钩子
def clean_name(self):
val=self.cleaned_data.get("name")
if not val.isdigit():
return val
else:
raise ValidationError("用户名不能是纯数字!")

# 全局钩子

def clean(self):
pwd=self.cleaned_data.get("pwd")
r_pwd=self.cleaned_data.get("r_pwd")

if pwd==r_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致!')

def register(request):

if request.method=="POST":
form=UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data) # 所有干净的字段以及对应的值
else:
# 全局
clean_error=form.errors.get("__all__")

return render(request,"register.html",locals())
form=UserForm()
return render(request,"register.html",locals())

reg.html

  • 全局钩子的错误信息获取
1
{{errors.0}}

思考 如果第一次密码输入123 第二次密码输入12345会提示几个错误?

两个,具体请参考源码

如何处理第一次密码校验失败(长度不够) 就不校验 两次密码一致

1
2
3
4
5
6
7
8
9
10
11
def clean(self):
pwd=self.cleaned_data.get("pwd")
r_pwd=self.cleaned_data.get("r_pwd")

if pwd and r_pwd:
if pwd==r_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致!')
else:
return self.cleaned_data

Py006-02-03forms表单验证

forms组件

校验字段

针对一个实例:注册用户讲解。

  • 前置准备

创建项目,创建一个orm表,一个注册页面

模型:models.py

1
2
3
4
5
6
7
8
9
10
11
12
class UserInfo(models.Model):
name=models.CharField(max_length=32)
pwd=models.CharField(max_length=32)
email=models.EmailField()
tel=models.CharField(max_length=32)

'''
# 别忘了settings.py里 注册 应用如 app01
# 数据库迁徙
python3 manage.py makemigrations
python3 manage.py migrate
'''

注册路由配置

1
2
3
4
5
6
from app01 import views

urlpatterns = [
path('admin/', admin.site.urls),
path('reg/',views.reg) # 注册页
]

reg.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<form action="" method="post">
{% csrf_token %}
<div>
<label for="user">用户名</label>
<p><input type="text" name="name" id="name"></p>
</div>
<div>
<label for="pwd">密码</label>
<p><input type="password" name="pwd" id="pwd"></p>
</div>
<div>
<label for="r_pwd">确认密码</label>
<p><input type="password" name="r_pwd" id="r_pwd"></p>
</div>
<div>
<label for="email">邮箱</label>
<p><input type="text" name="email" id="email"></p>
</div>
<div>
<label for="tel">手机号</label>
<p><input type="text" name="tel" id="tel"></p>
</div>
<input type="submit">
</form>

views.py

1
2
3
4
5
6
7
def reg(request):
if request.method=='POST':

print(request.POST)
return HttpResponse('ok')

return render(request,'reg.html')
  • 至此,准备工作完毕,
1
2
3
# 依次填写表单1,2,3,4,5。
# 打印如下信息
<QueryDict: {'csrfmiddlewaretoken': ['dTj3JNHHoZKrLZEXAJsoty9UxZ8vCJ3zqZH3gvIor4Lp1bKp8GFfmYS1Q4A58fji'], 'name': ['1'], 'pwd': ['2'], 'r_pwd': ['3'], 'email': ['4'],'tel':['5']}>

forms简单使用

  • 引入forms
  • 定义一个校验类如UserForm继承forms.Form
  • 自定义校验规则name = forms.CharField(min_length=4)
  • 根据校验字段生成UserForm初始化对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 引入forms
from django import forms

# 自定义表单校验
class UserForm(forms.Form):
# 定义校验字段的规则
name = forms.CharField(min_length=4) # 最少四个字符
email = forms.EmailField() # 邮箱校验


def reg(request):
if request.method=='POST':

form = UserForm({"name":'abcd','email':'123'})
# 所有值校验字段通过返回 true 10个字段其中一个失败返回false
print(form.is_valid()) # 返回布尔值 返回False email没有通过校验

return HttpResponse('ok')

return render(request,'reg.html')

'''
form = UserForm({"name":'abcd','email':'123@qq.com'})
form.is_valid() # True
'''

错写校验字段会如何?

1
2
3
4
5
form = UserForm({"names":'abcd','email':'123@qq.com'})
# 定义的UserForm里 为name 你传递names 显然不匹配
form.is_valid() # False

# 校验字段必须和定义里的字段相同

多出校验字段会咋样?

多了无所谓,只要定义的字段全匹配就ok

1
2
3
form = UserForm({"name":'abcd','email':'123@qq.com','age':22})
# 定义的UserForm里 为name,email 你多传递的key会被忽略
form.is_valid() # True

form.cleaned_data

这里是校验合法字段的字典

1
2
3
4
5
6
7
8
9
10
11
# 部分校验通过
form = UserForm({"names":'abcd','email':'123@qq.com','age':111})
print(form.cleaned_data)
# {'email': '123@qq.com'}


# -------------------
# 全部校验通过
form = UserForm({"name":'abcd','email':'123@qq.com','age':111})
print(form.cleaned_data)
# {'name': 'abcd', 'email': '123@qq.com'}

form.errors

校验失败字段对应的错误信息

1
2
3
form = UserForm({"name":'abc','email':'123@qq.com','age':111})
print(form.errors)
# <ul class="errorlist"><li>name<ul class="errorlist"><li>Ensure this value has at least 4 characters (it has 3).</li></ul></li></ul>

别看返回的是个ul实际是个字典类型

重写我们的UserForm对应实际的表单

1
2
3
4
5
6
7
class UserForm(forms.Form):
# 定义校验字段的规则
name = forms.CharField(min_length=4) # 最少四个字符
pwd = forms.CharField(min_length=4) # 最少四个字符
r_pwd = forms.CharField(min_length=4) # 最少四个字符
email = forms.EmailField() # 邮箱校验
tel = forms.CharField()

重写views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def reg(request):
if request.method=='POST':
# 表单会携带csrf_token字段 但是未在UserForm里定义所以会忽略
form = UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
else:
print(form.cleaned_data)
print(form.errors) # ErrorDict 字典
print(form.errors.get('name')) # ErrorList 列表
print(form.errors.get('name')[0]) # 具体的错误信息
return HttpResponse('ok')

return render(request,'reg.html')

# reg页面表单什么都不填写直接提交
'''
{}
<ul class="errorlist"><li>name<ul class="errorlist"><li>This field is required.</li></ul></li><li>pwd<ul class="errorlist"><li>This field is required.</li></ul></li><li>r_pwd<ul class="errorlist"><li>This field is required.</li></ul></li><li>email<ul class="errorlist"><li>This field is required.</li></ul></li><li>tel<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
<ul class="errorlist"><li>This field is required.</li></ul>
This field is required.
'''

forms渲染标签方式一

修改views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from django.shortcuts import render,HttpResponse

from django import forms

# 自定义表单校验
class UserForm(forms.Form):
# 定义校验字段的规则
name = forms.CharField(min_length=4) # 最少四个字符
pwd = forms.CharField(min_length=4) # 最少四个字符
r_pwd = forms.CharField(min_length=4) # 最少四个字符
email = forms.EmailField() # 邮箱校验
tel = forms.CharField()

def reg(request):
if request.method=='POST':

form = UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
else:
print(form.cleaned_data)
print(form.errors) # ErrorDict
print(form.errors.get('name')) # ErrorList
print(form.errors.get('name')[0]) # 具体的错误信息
return HttpResponse('ok')

# get请求的时候
form = UserForm()

return render(request,'reg.html',locals())

不传递参数初始化表单校验对象form = UserForm() 传递到模版里就以定义的字段可以生成表单标签

reg.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<form action="" method="post">
{% csrf_token %}
<div>
<label for="">用户名</label>
{{ form.name }}
</div>
<div>
<label for="">密码</label>
{{ form.pwd }}
</div>
<div>
<label for="">确认密码</label>
{{ form.r_pwd }}
</div>
<div>
<label for=""> 邮箱</label>
{{ form.email }}
</div>
<div>
<label for=""> 手机</label>
{{ form.tel }}
</div>

<input type="submit" class="btn btn-default pull-right">
</form>

forms渲染方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
<form action="" method="post">
{% csrf_token %}

{% for field in form %}
<div>
<label for="">{{ field.label }}</label>
{{ field }}
</div>
{% endfor %}
<input type="submit">
</form>

# 这样渲染的label名是首字母大写的字段

我就要显示中文

设置label值

1
2
3
4
5
6
7
class UserForm(forms.Form):
# 定义校验字段的规则
name = forms.CharField(min_length=4,label='用户名') # 最少四个字符
pwd = forms.CharField(min_length=4,label='密码') # 最少四个字符
r_pwd = forms.CharField(min_length=4,label='密码') # 最少四个字符
email = forms.EmailField(label='邮箱') # 邮箱校验
tel = forms.CharField(label='电话')

forms渲染方式三

不建议使用,因为dom结构是定义死的。不够灵活

1
2
3
4
5
6
<form action="" method="post">
{% csrf_token %}

{{ form.as_p }}
<input type="submit">
</form>

forms渲染错误信息

在校验失败的地方 返回 HttpResponse(request,’reg.html’,locals())

为啥?

  • 你已经填写了表单,不管校验成功还是失败
  • 此时已经提交,页面会重刷,但是UserForm对象里有你传递的值,所以通过UserForm对象渲染标签的时候同时会把传递的值渲染到里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def reg(request):

if request.method=='POST':
form = UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
else:
print(form.cleaned_data)
print(form.errors) # ErrorDict
print(form.errors.get('name')) # ErrorList
print(form.errors.get('name')[0]) # 具体的错误信息

return render(request,'reg.html',locals())

form = UserForm()

return render(request,'reg.html',locals())

错误信息form.字段.errors.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<form action="" method="post">
{% csrf_token %}
<div>
<label for="">用户名</label>
{{ form.name }} <span>{{ form.name.errors.0 }}</span>
</div>
<div>
<label for="">密码</label>
{{ form.pwd }} <span>{{ form.pwd.errors.0 }}</span>
</div>
<div>
<label for="">确认密码</label>
{{ form.r_pwd }} <span>{{ form.r_pwd.errors.0 }}</span>
</div>
<div>
<label for=""> 邮箱</label>
{{ form.email }} <span>{{ form.email.errors.0 }}</span>
</div>
<div>
<label for=""> 手机</label>
{{ form.tel }} <span>{{ form.tel.errors.0 }}</span>
</div>
<input type="submit" class="btn">
</form>

或者

1
2
3
4
5
6
7
8
9
10
11
<form action="" method="post" novalidate>
{% csrf_token %}

{% for field in form %}
<div>
<label for="">{{ field.label }}</label>
{{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<input type="submit" class="btn btn-default">
</form>

forms参数配置

  • 错误信息定义为自己想要的
  • 渲染不同类型的表单select/password

渲染其他类型标签要引入widgets

定义错误信息要设置error_messages={“选项”:”错误信息”} required代表必填

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserForm(forms.Form):
# 定义校验字段的规则
name = forms.CharField(min_length=4,label='用户名',error_messages={"required":'该字段不能为空'},
widgets = widgets.TextInput(attrs={"class":'form-control'}))
pwd = forms.CharField(min_length=4,label='密码',
widgets=widgets.PasswordInput())
r_pwd = forms.CharField(min_length=4,label='密码') # 最少四个字符
email = forms.EmailField(label='邮箱',error_messages={"required":'该字段不能为空',"invalid":"格式错误"}) # 邮箱校验
tel = forms.CharField(label='电话')

'''
widgets = widgets.TextInput(attrs={"class":'form-control'}))
给标签设置属性
'''

Py006-02-02分页器Paginator

前置准备

新建django项目应用app01

models.py

1
2
3
4
5
6
from django.db import models

# Create your models here.
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(decimal_places=2,max_digits=8)

urls.py

1
2
3
4
5
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/',views.index)
]

数据库迁徙

1
2
python3 manage.py makemigrations
python3 manage.py migrate

views.py

批量录入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 录入初识数据100条
def index(request):

# 缺陷 循环一次插入一条
'''
for i in range(100):
Book.objects.create(title="book%s"%i,price=i*i)
'''
# 改善批量插入
'''
book_list = []
for i in range(100):
book = Book(title="book%s" % i, price=i * i)
book_list.append(book)

Book.objects.bulk_create(book_list) # 批量插入

return render(request,'index.html',locals())

index.html

1
2
3
4
5
<ul>
{% for book in book_list %}
<li>{{ book.title }}:{{ book.price }}</li>
{% endfor %}
</ul>

如上我们有了100条数据

分页器 Paginator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 引入分页器
from django.core.paginator import Paginator

def index(request):
book_list = Book.objects.all()
# 分页器 Paginator(列表,分页条数)
paginator = Paginator(book_list,10)

# 总数据条数
print("count:",paginator.count)
# 总页数
print("num_pages:", paginator.num_pages)
# 页码对应的列表
print("page_range:", paginator.page_range)
# 第x页数据
page3 = paginator.page(3)
'''
或者迭代取第x页数据
for i in page3
print(i)
'''
print("第三页数据",page3.object_list)
return render(request,'index.html',locals())

'''
访问 http://127.0.0.1:8000/index/
count: 100
num_pages: 10
page_range: range(1, 11)
第三页数据 <QuerySet [<Book: Book object (21)>, <Book: Book object (22)>, <Book: Book object (23)>, <Book: Book object (24)>, <Book: Book object (25)>, <Book: Book object (26)>, <Book: Book object (27)>, <Book: Book object (28)>, <Book: Book object (29)>, <Book: Book object (30)>]>
'''

> 通过url的get参数模拟动态页码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from django.shortcuts import render
from app01.models import Book

# 引入分页组件,和边界值异常类型处理边界值
from django.core.paginator import Paginator,EmptyPage

def index(request):

#分页组件 初始化一定数量的数据

# 缺陷 循环一次插入一条
'''
for i in range(100):
Book.objects.create(title="book%s"%i,price=i*i)
'''
# 改善批量插入
'''
book_list = []
for i in range(100):
book = Book(title="book%s" % i, price=i * i)
book_list.append(book)

Book.objects.bulk_create(book_list) # 批量插入
'''

book_list = Book.objects.all()
# 分页器 Paginator(列表,分页条数)
paginator = Paginator(book_list,10)

# 总数据条数
# print("count:",paginator.count)
# 总页数
# print("num_pages:", paginator.num_pages)
# 页码对应的列表
# print("page_range:", paginator.page_range)
# 第x页数据
# page3 = paginator.page(3)
# print("第三页数据",page3.object_list)

try:
current_page = int(request.GET.get('page', 1))
current_page_data = paginator.page(current_page)
# 处理边界值 <1,>10
except EmptyPage as e:
current_page_data = paginator.page(1)


return render(request,'index.html',locals())

index.html

1
2
3
4
5
<ul>
{% for book in current_page_data %}
<li>{{ book.title }}:{{ book.price }}</li>
{% endfor %}
</ul>

访问

1
2
3
4
5
6
http://127.0.0.1:8000/index/
http://127.0.0.1:8000/index/?page=1
http://127.0.0.1:8000/index/?page=2
http://127.0.0.1:8000/index/?page=10
http://127.0.0.1:8000/index/?page=2000
http://127.0.0.1:8000/index/?page=-1

引入bootstrap分页组件

  • paginator.page_range 分页列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">


<ul>
{% for book in current_page_data %}
<li>{{ book.title }}:{{ book.price }}</li>
{% endfor %}
</ul>

<nav aria-label="Page navigation">
<ul class="pagination">
<li><a href="#" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>
{% for index in paginator.page_range %}
<li><a href="#">{{ index }}</a></li>
{% endfor %}
<li><a href="#" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
</ul>
</nav>

点击页码a标签跳转url设置当前页码数据

1
2
3
4
5
6
7
{% for index in  paginator.page_range %}
{% if current_page == index %}
<li class="active"><a href="?page={{ index }}">{{ index }}</a></li>
{% else %}
<li><a href="?page={{ index }}">{{ index }}</a></li>
{% endif %}
{% endfor %}

上一页下一页处理

1
2
3
<li><a href="?page={{ current_page|add:-1 }}" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>

<li><a href="?page={{ current_page|add:1" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>

通过分页器的上一页下一页api接口处理

  • current_page_data.previous_page_number
  • current_page_data.next_page_number
1
2
3
4
<li><a href="?page={{ current_page_data.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>

<li><a href="?page={{ current_page_data.next_page_number }}" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
# 仍然有bug 在第2~9是好的 1和10是坏的

> 边界值处理

  • current_page_data.has_previous
  • current_page_data.has_next
1
2
3
4
5
6
7
8
9
10
11
12
{% if current_page_data.has_previous %}
<li><a href="?page={{ current_page_data.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>
{% else %}
<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>
{% endif %}
...

{% if current_page_data.has_next %}
<li><a href="?page={{ current_page_data.next_page_number }}" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
{% else %}
<li class="disabled"><a href="#" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
{% endif %}

页码过多时的处理

  • 左5右5

总不能分页100页全显示出来吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 特殊处理
from django.shortcuts import render
from app01.models import Book

# 引入分页组件
from django.core.paginator import Paginator,EmptyPage


def index(request):

book_list = Book.objects.all()
# 分页器 Paginator(列表,分页条数)
paginator = Paginator(book_list,2)

# 总数据条数
print("count:",paginator.count)
# 总页数
print("num_pages:", paginator.num_pages)
# 页码对应的列表
print("page_range:", paginator.page_range)
# 第x页数据
# page3 = paginator.page(3)
# print("第三页数据",page3.object_list)

# 左五右五
current_page = int(request.GET.get('page', 1))

if paginator.num_pages > 11:

if current_page - 5 < 1:
page_range = range(1, 12)
elif current_page + 5 > paginator.num_pages:
page_range = range(paginator.num_pages - 10, paginator.num_pages + 1)

else:
page_range = range(current_page - 5, current_page + 6)
else:
page_range = paginator.page_range

try:

current_page_data = paginator.page(current_page)

except EmptyPage as e:
current_page_data = paginator.page(1)

return render(request,'index.html',locals())

index.html里的页码循环部分替换页码列表page_range

1
2
3
4
5
6
7
{% for index in  page_range %}
{% if current_page == index %}
<li class="active"><a href="?page={{ index }}">{{ index }}</a></li>
{% else %}
<li><a href="?page={{ index }}">{{ index }}</a></li>
{% endif %}
{% endfor %}

Py006-02-01ajax

什么是ajax

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。使用Javascript语言与服务器进行异步交互,传输的数据为XML。但现在广泛使用的是JSON格式.

  • 同步交互:客户端form表单提交,需等待服务器响应结果,页面是停滞的。
  • 异步交互:客户端发出一个ajax请求后,无需等待服务器响应结束,就可以发出第二个请求。

AJAX除了异步的特点外,还有一个就是:浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程)

前端的起源就是ajax的出现,ajax之前的程序员们绞尽脑汁想出jsonp的形式,来提升用户体验(也就是局部更新,但jsonp只支持get请求)。

AJAX优点

  • AJAX使用XMLHttpRequest对象向服务器发送异步请求;
  • AJAX请求无须刷新整个页面;

ajax的技术实例

  • 注册账号时,账号是否注册的检查提升
  • 软件搜索栏里信息热词(用jsonp也是可以的如百度)

ajax实战

请自行创建django实例,同时注释掉settings里的csrf限制

urls.py

1
2
3
4
5
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path(r'index/',views.index)
]

views.py

1
2
3
4
5
6
7
8
9
10
11
from django.shortcuts import render,HttpResponse

def index(request):
print(request.method)
if request.method=='POST':
print(request.POST)
aa = request.POST.get('aa')
bb = request.POST.get('bb')
return HttpResponse('{"status":0,"msg":"hahaha"}')
else:
return render(request,'index.html')

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"></script>
</head>
<body>
<div>
<form action="">
<span>aa:</span><input type="text" name="aa" value="aa" id="aa"> <br>
<span>bb:</span><input type="text" name="bb" value="bb" id="bb"> <br>
<input type="submit" value="提交">
</form>
<button id="btn1">ajax1</button>
</div>
<script>
$(function(){
$('#btn1').on('click',function(){
$.ajax({
url:'',
type:'post',
data:{
aa:$('#aa').val(),
bb:$('#bb').val(),
},
success:function(res){
console.log(res)
},
error:function(err){
console.log(err)
}
})
})
})
</script>
</body>
</html>

文件上传

一、请求头ContentType

ContentType指的是请求体的编码类型,常见的类型共有3种:

  1. application/x-www-form-urlencoded
    这应该是最常见的 POST 提交数据的方式了。浏览器的原生

表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了):

1
2
3
4
5
6
7
8
9
10
11
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

'''
使用chrome开发模式打开Network标签
点击view source!!!
点击view source!!!
点击view source!!!
格式如下
user=aa&age=18
'''

  1. multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让

表单的 enctype 等于 multipart/form-data。直接来看一个请求示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="user"

yuan
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 –boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 –boundary– 标示结束。关于 multipart/form-data 的详细定义,请前往 rfc1867 查看。

这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。

上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段标准中原生

表单也只支持这两种方式(通过 元素的 enctype 属性指定,默认为 application/x-www-form-urlencoded。其实 enctype 还支持 text/plain,不过用得非常少)。

随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,给开发带来更多便利。

1
2
3
4
该类型的文件服务端获取在
request.FILES.get('文件字段')
非文件字段依然在 
request.POST里
  1. application/json(仅ajax支持)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$.ajax({
url:'',
type:'post',
contentType:'application/json',
data:{
a:1,
b:2
},
success:function(){}
})

contentType:'application/json'
实际是把data数据序列号传递给服务器
JSON.stringify({a:1,b:2})
请求体如下:
{"a":1,"b":2}
此时服务端的request.POST是个字典{"a":1,"b":2}

如果不设置contentType默认是 urlencode
数据给服务器的格式如下
a=1&b=2
此时服务端的request.POST是个空字典
requets.body 里内容是{"a":1,"b":2}字符串 需要你自己进行反序列化

application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

JSON 格式支持比键值对复杂得多的结构化数据,这一点也很有用。记得我几年前做一个项目时,需要提交的数据层次非常深,我就是把数据 JSON 序列化之后来提交的。不过当时我是把 JSON 字符串作为 val,仍然放在键值对里,以 x-www-form-urlencoded 方式提交。

基于form表单的文件上传

模板部分

1
2
3
4
5
<form action="" method="post" enctype="multipart/form-data">
用户名 <input type="text" name="user">
头像 <input type="file" name="avatar">
<input type="submit">
</form>

视图部分

1
2
3
4
5
6
def index(request):
print(request.body) # 原始的请求体数据
print(request.GET) # GET请求数据
print(request.POST) # POST请求数据
print(request.FILES) # 上传的文件数据
return render(request,"index.html")

基于Ajax的文件上传

模板

如果上传文件的内容需要设置两个参数和使用一个FormData对象提交

  • processData: false // 不处理数据
  • contentType: false // 不设置内容类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<form>
用户名 <input type="text" id="user">
头像 <input type="file" id="avatar">
<input type="button" id="ajax-submit" value="ajax-submit">
</form>
<script>

$("#ajax-submit").click(function(){
var formdata=new FormData();
formdata.append("user",$("#user").val());
formdata.append("avatar_img",$("#avatar")[0].files[0]);
$.ajax({

url:"",
type:"post",
data:formdata,
processData: false , // 不处理数据
contentType: false, // 不设置内容类型

success:function(data){
console.log(data)
}
})
})
</script>

视图

1
2
3
4
5
6
7
8
9
def index(request):
if request.is_ajax():
print(request.body) # 原始的请求体数据
print(request.GET) # GET请求数据
print(request.POST) # POST请求数据
print(request.FILES) # 上传的文件数据
file_obj=request.FILES.get("avatar") # 获取指定上传文件内容
return HttpResponse("ok")
return render(request,"index.html")

检查浏览器的请求头:

1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaWl9k5ZMiTAzx3FT

$.ajax()参数

$.ajax()方法是jQuery最底层的Ajax实现。它的结构为:$.ajax(options)

该方法只有一个参数,但是这个对象里包含了$.ajax()方法所需要的请求设置以及回调函数等信息,参数以key/value的形式存在,所有的参数都是可选的。常用参数如下:

1.url

要求为String类型的参数,(默认为当前地址)发送请求的页面。

2.type

要求为String类型的参数,请求方式(post或get)默认为get。注意其他http请求方法,例如put和delete也可以使用,但仅部分浏览器支持。

3.timeout

要求为Number类型的参数,设置请求超时时间(毫秒)。此设置将覆盖$.ajaxSetup()方法的全局设置。

4.async

要求为Boolean类型的参数,默认设置为true,所有请求均为异步请求。如果需要同步请求,请将此选项设置为false。注意,同步请求将锁住浏览器,用户其他操作必须等待请求完成才可以执行。

5.cache

要求为Boolean类型的参数,默认为true(当dataType为Script时,默认为false),设置false将不会从浏览器缓存中加载请求信息。

6.data

要求为Object或String类型的参数,发送到服务器的数据。如果不是字符串,将自动转换为字符串格式。get请求中将附加在URL后。防止这种自动转换,

可以查看processData选项。对象必须为key/value格式,例如{foo1:”bar1”,foo2:”bar2”}转换为&foo1=bar1&foo2=bar2。如果是数组,JQuery将自动为不同值对应同一个名称。例如:

1
{foo:["bar1","bar2"]}转换为&foo=bar1&foo=bar2。

7.dataType

要求为String类型的参数,预期服务器返回的数据类型。如果不指定,jQuery将自动根据HTTP包的mine信息返回responseXML或responseText,并作为回调函数参数传递。可用的类型如下:

1
2
3
4
5
6
7
8
9
10
11
xml:返回XML文档,可用jQuery处理。

  html:返回纯文本HTML信息;包含的script标签会在插入DOM时执行。

  script:返回纯文本javascript代码。不会自动缓存结果,除非设置了cache参数。注意在远程请求时(不在同一个域下),所有post请求都将转为get请求。

  json:返回JSON数据。

  jsonp:JSON格式。使用JSONP形式调用函数时,例如myurl?callback=?,JQuery将自动替换后一个“?”为正确的函数名,以执行回调函数。

  text:返回纯文本字符串。

8.beforeSend

要求为Function类型的参数,发送请求前可以修改XMLHttpRequest对象的函数,例如添加自定义HTTP头。在beforeSend中如果返回false可以取消本次ajax请求。XMLHttpRequest对象是唯一的参数。

1
2
3
4
5
function(XMLHttpRequest){

    this;//调用本次ajax请求时传递的options参数

  }

9.complete

要求为Function类型的参数,请求完成后调用的回调函数(请求成功或失败均调用)。参数:XMLHttpRequest对象和一个描述成功请求类型的字符串。

1
2
3
4
5
function(XMLHttpRequest,textStatus){

    this; //调用本次ajax请求时传递的options参数

  }

10.success

要求为Function类型的参数,请求成功后调用的回调函数

11.error

要求为Function类型的参数,请求失败时被调用的函数。该函数有3个参数,即XMLHttpRequest对象、错误信息、捕获的错误对象(可选)。ajax事件函数如下:

12.contentType

要求为String类型的参数,当发送信息至服务器时。内容编码类型默认为”application/x-www-form-urlencoded”。该默认值适合大多数应用场合。

13.dataFilter
要求为Function类型的参数,给Ajax返回的原始数据进行预处理的函数。提供data和type两个参数。data是Ajax返回的原始数据,type是调用jQuery.ajax时提供的dataTYpe参数。函数返回的值将由jQuery进一步处理。

1
2
3
4
5
6
7
function(data,type){

    //返回处理后的数据

    return data;

  }

14.global

要求为Boolean类型的参数,默认为true。表示是否触发全局ajax事件。设置为false将不会触发全局ajax事件,ajaxStart和ajaxStop可用于控制各种ajax事件。

15.ifModified

要求为Boolean类型的参数,默认为false。仅在服务器数据改变时获取新数据。服务器数据改变判断的依据是Last-Modified头信息。默认值是false,即忽略头信息。

16.jsonp

要求为String类型的参数,在一个jsonp请求中重写回调函数的名字。该值用来替代在”callback=?”这种GET或POST请求中URL参数里的”callback”部分,例如{jsonp:’onJsonPLoad’}会导致将”onJsonPLoad=?”传给服务器。

17.username

要求为String类型的参数,用于响应HTTP访问认证请求的用户。

18.password

要求为String类型的参数,用于响应HTTP访问认证请求的密码。

19.processData

要求为Boolean类型的参数,默认为true。默认情况下,发送的数据将被转换为对象(从技术角度来讲而非字符串)以配合默认内容类型”application/x-www-form-urlencoded”。如果要发送DOM树信息或者其他不希望转换的信息,请设置为false。

20.scriptCharset

要求为String类型的参数,只有当请求时dataType为”jsonp”或者”script”,并且type是GET时才会用于强制修改字符集(charset)。通常在本地和远程的内容编码不同时使用。

AJAX请求如何设置csrf_token

方式1

通过获取隐藏的input标签中的csrfmiddlewaretoken值,放置在data中发送。

1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
url: "/cookie_ajax/",
type: "POST",
data: {
"username": "fuyong",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用JQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})

  

方式2

通过获取返回的cookie中的字符串 放置在请求头中发送。

注意:需要引入一个jquery.cookie.js插件。(插件下载)

1
2
3
4
5
6
7
8
9
$.ajax({
url: "/cookie_ajax/",
type: "POST",
headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 从Cookie取csrf_token,并设置ajax请求头
data: {"username": "fuyong", "password": 123456},
success: function (data) {
console.log(data);
}
})

 

或者用自己写一个getCookie方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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请求统一设置。

1
2
3
4
5
6
7
8
9
10
11
12
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);
}
}
});

更多细节详见:Djagno官方文档中关于CSRF的内容

axios

由于现在前后端分离所以新项目基本都是vue/react/angular

所以几乎项目里使用的ajax库是—— axios

axios具体配置请参考

参考链接

Py006-01-21F查询和Q查询

其他查询

  • F查询
  • Q查询

我们为book表新增两个字段

  • zan 赞的次数
  • cai 踩的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Book

class Book(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=32)
publishDate = models.DateField()
price = models.DecimalField(max_digits=5,decimal_places=2)

zan = models.IntegerField()
cai = models.IntegerField()


publish = models.ForeignKey(to="Publish",to_field='nid',on_delete=models.CASCADE)

authors = models.ManyToManyField(to="Author");

# 此时执行数据库迁徙操作
python3 manage.py makemigrations
# 警告如下信息
'''
You are trying to add a non-nullable field 'zan' to book 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
意思是新增的两个字段 没有设置默认值
'''

# 修改代码如下 设置初始默认值
zan = models.IntegerField(default=0)
cai = models.IntegerField(default=0)

# 继续数据库迁徙
python3 manage.py makemigrations
python3 manage.py migrate

F查询

在之前所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较

如果我们要对两个字段的值做比较,那该怎么做呢?

1
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

查询点赞数大于踩数的书籍

1
2
3
from django.db.models import F

res = Book.objects.filter(zan__gt=F('cai'))

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。

1
2
# 查询点赞数大于踩数2倍的书籍
Book.objects.filter(zan__lt=F('cai')*2)

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

1
Book.objects.all().update(price=F("price")+30)

Q查询

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。

1
2
from django.db.models import Q
Q(title__startswith='Py')

Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

1
bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))

等同于下面的SQL WHERE 子句:

1
WHERE name ="yuan" OR name ="egon"

你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:

1
bookList=Book.objects.filter(Q(authors__name="alex") & ~Q(publishDate__year=2017)).values_list("title")

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将”AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:

1
2
3
bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),
title__icontains="python"
)

Py006-01-20多表分组查询

多表分组查询

表结构参考之前内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from django.db import models

# Create your models here.
from django.db import models
'''
Book ---- Publish 一对多

'''

class Author(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
age=models.IntegerField()
# 一对一
authordetail=models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE)
def __str__(self):
return self.name

# 作者详情表
class AuthorDetail(models.Model):

nid = models.AutoField(primary_key=True)
birthday=models.DateField()
telephone=models.BigIntegerField()
addr=models.CharField( max_length=64)

# 出版社表
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
city=models.CharField( max_length=32)
email=models.EmailField()


def __str__(self):
return self.name


class Book(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField( max_length=32)
publishDate=models.DateField()
price=models.DecimalField(max_digits=5,decimal_places=2)



# 一对多关系
publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE,)
'''
publish_id INT ,
FOREIGN KEY (publish_id) REFERENCES publish(id)

'''

#多对多
authors =models.ManyToManyField(to="Author")

'''
CREATE TABLE book_authors(
id INT PRIMARY KEY auto_increment ,
book_id INT ,
author_id INT ,
FOREIGN KEY (book_id) REFERENCES book(id),
FOREIGN KEY (author_id) REFERENCES author(id)
)
'''


# class Book2Author(models.Model):
#
# nid = models.AutoField(primary_key=True)
# book=models.ForeignKey(to="Book")
# author=models.ForeignKey(to="Author")

def __str__(self):
return self.title





初识数据

1
2
3
4
5
6
7
8
9
10
11
12
Book表

id title date price publish_id
1 红楼梦 2012-12-12 101 1
2 西游记 2012-12-12 101 1
3 三国演绎 2012-12-12 101 1
4 金瓶梅 2012-12-12 301 2

Publish表
id name addr email
1 人民出版社 北京 123@qq.com
2 南京出版社 南京 345@163.com
1. 查询每一个出版社出版的书籍个数
1
2
# book表外键关联了publish表的id所以可以直接拿出版社id分组统计书籍数量
Book.objects.values("publish_id").annotate(Count("id"))
2. 示例1 查询每一个出版社的名称以及出版的书籍个数

join连表操作

1
2
3
4
5
6
7
8
9
10
11
    join sql : select * from Book inner join Publish on book.publish_id=publish.id

id title date price publish_id publish.id publish.name publish.addr publish.email
1 红楼梦 2012-12-12 101 1 1 人民出版社 北京 123@qq.com
2 西游记 2012-12-12 101 1 1 人民出版社 北京 123@qq.com
3 三国演绎 2012-12-12 101 1 1 人民出版社 北京 123@qq.com
4 金瓶梅 2012-12-12 301 2 2 南京出版社 南京 345@163.com

分组查询sql: 统计结果: 出版社名字,书籍数量
select publish.name,Count("title") from Book inner join Publish on book.publish_id=publish.id
group by publish.id
思考:如何用ORM语法进行跨表分组查询

参考单表的模型是: xx模型.objects.values(‘group by的字段’).annotate(聚合函数(‘统计字段’))

1
2
3
4
5
6
7
8
9
10
11
12
13
# 通过Publish找book 是反向查询  所以表名小写__加字段 book__title
res=Publish.objects.values("nid").annotate(c=Count("book__title"))
# <QuerySet [{'nid': 1, 'c': 3}, {'nid': 2, 'c': 1}]>

# 出版社的名称,和对应书籍的个数
res=Publish.objects.values("name").annotate(c=Count("book__title"))
# <QuerySet [{'name': '人民出版社', 'c': 3}, {'name': '南京出版社', 'c': 1}]>

# 其他玩法--》分组已经完毕了,你可以通过values取最后你要的列
res=Publish.objects.values("nid").annotate(c=Count("book__title")).values("name","c")
# <QuerySet [{'name': '人民出版社', 'c': 3}, {'name': '南京出版社', 'c': 1}]>

在以nid分组后annotate里一个聚合函数就会返回两列 如果不够用就在values里写你需要的列
3. 示例2 查询每一个作者的名字以及出版过的书籍的最高价格
1
ret=Author.objects.values("pk").annotate(max_price=Max("book__price")).values("name","max_price")
4. 示例3 查询每一个书籍的名称以及对应的作者个数
1
2
ret=Book.objects.values("pk").annotate(c=Count("authors__name")).values("title","c")
print(ret)
5. 总结:
1
2
3
# 总结 跨表的分组查询的模型:
# 每一个后的表模型.objects.values("pk").annotate(聚合函数(关联表__统计字段)).values("表模型的所有字段以及统计字段")
# 每一个后的表模型.objects.annotate(聚合函数(关联表__统计字段)).values("表模型的所有字段以及统计字段")

Py006-01-19聚合查询和分组查询

聚合

  • aggregate聚合:返回值是字典,不再是queryset
  • 导入模块from django.db.models import Avg,Max,Min,Count
1
2
3
4
5
6
7
8
查询:所有书籍平均价格
res = Book.objects.all().argregate(Avg('price')) # {"price_avg":111.00} 这个key是默认生成的,你也可以自己指定

res = Book.objects.all().argregate(avg_price=Avg('price')) # {"avg_price":111.00}


# 查avg max min
res = Book.objects.all().argregate(Avg('price'),Max('price'),Min('price'))

分组查询

单表分组查询

  • annotate(聚合函数)
1
2
3
4
5
6
7
8
9
emp表

id name age salary dep
1 alex 12 2000 销售部
2 egin 22 3000 人事部
3 wen 22 5000 人事部

查询每个部门对应员工人数
sql:select count(id) from emp group by dep;

如何用orm查询分组数据

1
2
3
4
5
6
7
# orm模型 (单表)
class Emp(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
salary = models.DecimalField(max_digits=8,decimal_places=2)
dep = models.CharField(max_length=32)
province = models.CharField(max_length=32) # 省份
1
2
3
4
# 先录入一些数据
e1 = Emp.objects.create(name='alex',age=22,salary=2000,dep='教学部',province='山东省')
e2 = Emp.objects.create(name='egin', age=33, salary=5000, dep='保安部', province='山东省')
e3 = Emp.objects.create(name='yuan', age=21, salary=100000, dep='教学部', province='河北省')

示例1

1
2
3
4
5
6
7
8
# 查询每一个部门平均薪水
# select dep,avg(salary) from emp group by dep;
res = Emp.objects.values('dep').annotate(Avg('salary'))
print(res)

'''
<QuerySet [{'dep': '保安部', 'salary__avg': 5000.0}, {'dep': '教学部', 'salary__avg': 51000.0}]>
'''

单表分组查询ORM语法:

1
单表模型.objects.values('group by的字段').annotate(聚合函数)

示例2

1
2
3
4
5
6
# 查询每一个省份的名称以及员工数
res2 = Emp.objects.values('province').annotate(Count('id'))
print(res2)
'''
<QuerySet [{'province': '山东省', 'id__count': 2}, {'province': '河北省', 'id__count': 1}]>
'''

补充知识点

1
2
3
4
5
6
7
8
9
10
11
12
13
# 补充
res3 = Emp.objects.all() # select * from emp;
res4 = Emp.objects.all().values('name') # select name from emp;

res5 = Emp.objects.all().values('id') # select id from emp;

# annotate之前的内容是以xxx分组(group by) 但是如果以id分组完全没意义
res6 = Emp.objects.all().values('id').annotate(Avg('salary'))

# 同理 all()代表所有数据分组 select * from emp就是 以id做分组 也是没意义的
res7 = Emp.objects.all().annotate(Avg('salary'))

# 在单表分组下 按主键进行分组是没任何意义的

在单表分组下 按主键进行分组是没任何意义的

Py006-01-18基于双下划线的跨表查询

基于双下划线的跨表查询(join查询)

  • key:正向查询按字段,反向查询按表名小写

orm表如下:





一对多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 一对多 正向查询:查询金瓶梅这本书的出版社名字
# 基于双下划线的跨表查询(join)
# 一对多 正向查询:查询金瓶梅这本书的出版社名字
'''
select app01_publish.name from app01_book inner join app01_publish
on app01_book.publish_id = app01_publish.nid
where app01_book.title = '金瓶梅'

正向查询按字段,
反向查询按表名小写
用来告诉orm引擎join那个表
'''
# 方式一
res = Book.objects.filter(title='金瓶梅').values("publish__name") # 正向查询按字段 关联的字段也就是---publish 查指定字段就双下划线+字段

# 方式二
res2 = Publish.objects.filter(book__title='金瓶梅').values('name')
多对多查询

查询金瓶梅这本书所有作者的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''
select app01_author.name from app01_book inner join app01_book_authors
on app01_book.nid = app01_book_authors.book_id
inner join app01_author
on app01_book_authors.authors_id = app01_author.nid
where app01_book.title = '金瓶梅'
'''
# 方式一:正向
#需求: 通过Book表join与其关联的Author表,按字段
Book.objects.filter(title='金瓶梅').values("authors__name")

# 方式二:反向
#需求: 通过Author表join与其关联的Book表,按表名小写
Author.objects.filter(book__title='金瓶梅').values("name")
一对一
1
2
3
4
5
6
# 查询alex手机号
# 方式一 正向查询
Author.objects.filter(name='alex').values("authordetail__telephone")

# 方式二
AuthorDetail.objects.filter(author_name="alex").values("telephone")
进阶练习:(连续跨表)
1
2
3
4
5
6
7
# 查询 手机号以110开头的作者出版过的所有书籍名称以及出版社名称
# 方式一:
# 需求:通过Book表join AuthorDetail表,Book和AuthorDetail无关联,所有必须连续跨表
res = Book.objects.filter(authors__authordetail__telephone__startswith='110').values('title','publish__name')

# 方式二:
res = Author.objects.filter(authordetail__telephone__startswith='110').values("book__title","book__publish__name")

Py006-01-17基于对象的跨表查询

核心知识(重点)

跨表查询

  • 基于对象查询
  • 基于双下滑线查询
  • 聚合和分组查询
  • F 和 Q 查询

orm模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from django.db import models

# 作者表
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
age=models.IntegerField()
# 一对一 OneToOneField相当于指定 外间关联时设置 unique
'''
ad_id int unique,
foreign key(ad_id) references authorDetail(nid)
'''
authordetail=models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE)

# 作者详情表
class AuthorDetail(models.Model):
nid = models.AutoField(primary_key=True)
birthday=models.DateField()
telephone=models.BigIntegerField()
addr=models.CharField( max_length=64)

# 出版社表
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name=models.CharField( max_length=32)
city=models.CharField( max_length=32)
email=models.EmailField()

'''
Book ---- Publish 一对多
'''
class Book(models.Model):
nid = models.AutoField(primary_key=True)
title = models.CharField( max_length=32)
publishDate=models.DateField()
price=models.DecimalField(max_digits=5,decimal_places=2)



# 一对多关系 一本书对应多个出版社
publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE,)
'''
publish_id int,
foreign key(publish_id) references publish(nid)

'''

#多对多
authors =models.ManyToManyField(to="Author")

'''
# 书籍作者关系表
create table book_authors(
nid int primary key auto_increment,
book_id int,
author_id int,
foreign key(book_id) references book(nid),
foreign key(author_id) references author(nid)
);
'''

# 多对多关系时 不需要这个模型类 django会依据 authors =models.ManyToManyField(to="Author")帮我们生成这张表
# class Book2Author(models.Model):
#
# nid = models.AutoField(primary_key=True)
# book=models.ForeignKey(to="Book")
# author=models.ForeignKey(to="Author")

生成表如下:





基于对象跨表查询(子查询)

1
2
3
4
5
6
7
A-B
关联属性在A表中

正向查询: A------>B
反向查询: B------>A

基于对象的跨表查询(子查询)

一对多查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
正向查询:按字段
反向查询:表名小写_set.all()


book_obj.publish
Book(关联属性:publish)对象 --------------> Publish对象
<--------------
publish_obj.book_set.all() # queryset

# 一对多查询:正向查询 ==》查询金瓶梅这本书的出版社的名字
book_obj = Book.objects.filter(title='金瓶梅').first()
book_obj.publish # 与这本书关联的出版社对象
book_obj.publish.name


# 一对多查询:反向查询 ==》查询人民出版社出版的书籍名称
publish_obj = Publish.objects.filter(name='人民出版社').first()
publish_obj.book_set.all() # queryset 与出版社关联的书籍集合

多对多查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
正向查询:按字段
反向查询:表名小写_set.all()

book_obj.authors.all()
Book(关联属性:authors)对象 ------------------------> Author对象
<------------------------
author_obj.book_set.all() # queryset

# 多对多查询:正向查询 ==》查询金瓶梅这本书的所有作者的名字
book_obj = Book.objects.filter(title='金瓶梅').first()
author_list = book_obj.authors.all() # queryset 对象 [au1,au2,....]
for author in author_list:
print(author.name)

# 多对多查询:反向查询 ==》查询alex作者出版的所有书籍名称
author_obj = Author.objects.filter(name='alex').first()
book_list = author_obj.book_set.all() # queryset 与作者关联的书籍集合
for book in book_list:
print(book.name)

一对一查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
正向查询:按字段
反向查询:表名小写(不带_set了因为一对一了)

author.authordetail
Author(关联属性:authordetail)对象 ------------------------>AuthorDetail对象
<------------------------
authordetail.author

# 一对一查询:正向查询 ==》查询作者alex的手机号
alex = Author.objects.filter(title='alex').first()
alex.authordetail.telephone

# 多对多查询:反向查询 ==》查询手机号为110的作者姓名和年龄
ad = AuthorDetail.objects.filter(telephone=110).first()
ad.author.name
ad.author.age