Ajax、多对多外键的三种创建方式、django内置序列化组件、批量操作数据、分页器推导思路与自定义分页器的使用、form组件
Ajax、多对多外键的三种创建方式、django内置序列化组件、批量操作数据、分页器推导思路与自定义分页器的使用、form组件
目录
Ajax
前端页面想要和后端进行数据交互,可以通过以下方式:
1.将参数添加到url中,后端通过get方式从url中获取数据
GET请求
2.前端页面通过form表单,将数据以get或者post的方式发送给后端
POST请求/GET请求
3.前端通过a标签(和使用url方式参数的方式)
GET请求
# 什么是Ajax
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”。即使用javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。
Ajax不是新的编程语言,而是一种使用现有标准的新方法。其实就是js自带的功能,我们学习的是jQuery封装之后版本。
# Ajax的优点
1.Ajax最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
2.Ajax不需要任何浏览器插件,但需要用户允许javaScript在浏览器上执行。
同步交互:
客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求。
异步交互:
客户端发出一个请求后,无需要等待服务器响应结束,就可以发出第二个请求。
# Ajax用于"局部刷新页面"和"异步提交"的特点
异步提交: 提交完认为不需要原地等待,立马就做其他事
局部刷新: 不在局限于整个页面的刷新,而是在于局部的某一个页面的小块刷新
因此和使用Form表单和后端进行数据交互的方式比较,具有以下优点:
1.Ajax使用JavaScript技术向服务器发送异步请求
2.Ajax请求无须刷新整个页面
因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以Ajax性能高!
Ajax工作原理
1.Ajax相当于在用户和服务器之间加了一个中间层(Ajax引擎),使用户操作与服务器响应异步化。
2.并不是所有用户请求都是提交给服务器,像一些数据验证和数据处理等都交给Ajax引擎自己来做,只有确定需要从服务器读取数据时再由Ajax引擎代为向服务器提交请求。
3.客户端发送请求,请求交给Ajax,Ajax把请求提交给服务器,服务器进行业务处理,服务器响应数据交给Ajax对象,Ajax对象接收数据,由JavaScript把数据写到页面上。
要完整实现一个Ajax异步调用和局部刷新,同窗需要以下几个步骤:
1.创建XMLHttpRequest对象,既创建一个异步调用对象
2.创建一个新的HTTP请求,并指定该HTTP请求的方法,URL及验证信息
3.设置响应HTTP请求状态变化的函数
4.发送HTTP请求
5.获取异步调用返回数据
6.使用JavaSciript和DOM实现局部刷新
"""
注意:
Ajax传输数据为XML(当然,传输数据不只是XML,现在更多使用json数据,基本上web页面都是Ajax)
"""
# 基本语法
$.ajax({
// 发送地址
// 1.不写默认朝当前所在url提交数据
// 2.全写,指名道姓 https://www.baidu.com
// 3.只写后缀/login/
url: '',
type: 'get/post', // 请求方式 默认get
data: {'username':'jason', 'password':123} // 要发送的数据
success:function(args){
// 回调函数(异步回调机制)
}
})
解析(重要):
1.当你在利用ajax进行前后端交互的时候
2.当后端给你返回结果的时候会自动触发 args接受后端的返回结果
Content-Type
Content-Type:
会提前告诉后端,接下来的数据到底是什么格式,能识别到不同的类型分配到不同的编码中。
# 格式1:urlencoded
数据格式: name=jason&pwd=123&hobby=read
django后端统一处理到request.POST中
# 格式2:formdata
数据格式: 无法查阅
django后端自动将文件数据处理到request.FILES 普通数据request.POST
# 格式3:application/json
数据格式: json格式
django后端不会处理 在request.body中存储(bytes类型) 自己处理
"""
向django发送请求后,所携带的数据它会放在request.body中,识别是什么编码。
如果是urlencoded编码,它会将数据解码给request.POST;
如果是formdata那么它会解码以后交给request.FILES;
而现在的数据是JSON格式,针对JSON格式没有一个编码进行比对,所以没人管它
就只能自己处理:
print(request.body)
json_bytes = request.body
import json
json_bytes = json.loads(json_bytes)
print(json_bytes)
" b'{"name":"jason","pwd":123}'
{'name': 'jason', 'pwd': 123} "
"""
# 语法注意事项
data: JSON.stringify({'name':'jason','pwd':123}),
contentType:'application/json',
ajax携带文件数据
$('#d3').click(function () {
// 1.产生内置对象
let formData = new FormData();
// 2.添加普通数据
formData.append('username',$('#d1').val())
// 3.添加文件数据
formData.append('file', $('#d2')[0].files[0])
// 4.发送ajax请求
$.ajax({
url:'',
type:'post',
data:formData,
contentType:false, // 不使用任何编码
processData:false, // 不处理数据对象
success:function (args) {
}
})
})
Ajax补充说明
主要针对回调函数args接收到的响应数据。
1.后端request.is_ajax()
用于判断当前请求是否有ajax发出,有则true,否则是false
2.后端返回的三板斧都会被args接收不再影响整个浏览器页面
3.选择使用ajax做前后端交互的时候,后端一般返回的都是字典数据
user_dict = {'code':10000,'username':'小阳人':'hobby':'哎哟喂'}
# 后端将字典序列化为json格式
1.use_data = json.dumps(user_dict)
2.from django.http import JsonResponse
return JsonResponse(user_dict)
# 前端反序列化
1.前端正常序列化
let userObj = JSON.parse(args);
console.log(userObj)
console.log(userObj.username)
2.直接打印就行,前端认识JsonResponse
console.log(args)
console.log(args)
# ajax自动反序列化后端的json格式的bytes类型数据
dataType:'json', //在ajax里面添加这个参数,无论你怎么序列化字典,都能识别出它是字典。
多对多外键的三种创建方式
1.全自动创建
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
优势:自动创建第三张表,并且提供了add、remove、set、clear四种操作
劣势:第三张表无法创建更多的字段 扩展性较差
2.纯手动创建
class Book(models.Model):
title = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
优势:第三张表完全由自己创建 扩展性强
劣势:编写繁琐,并且不再支持add、remove、set、clear以及正反向概念
3.半自动创建
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
through='Book2Author', through_fields=('book','author')
)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
优势:第三张表完全由自己创建 扩展性强 正反向概念依然清晰可用
劣势:编写繁琐不再支持add、remove、set、clear
django内置序列化组件(drf前身)
前、后端项目要分开做,而后端想发送数据给前端、进行交互一般使用json格式数据。但模板语法是通过django的Queryset对象获取的,在前端页面要进行循环代码获取字段数据显得十分繁琐。为解决这个问题,可使用django的内置序列化组件直接将Queryset对象封装成字典。
'''前后端分离的项目 视图函数只需要返回json格式的数据即可'''
1.第一种方式(繁琐)
from app01 import models
from django.http import JsonResponse
def ab_ser_func(request):
# 1.查询所有的书籍对象
book_queryset = models.Book.objects.all()
# queryset [对象、对象]
# 2.封装成大字典返回
data_dict = {}
for book_obj in book_queryset:
temp_dict = {}
temp_dict['pk'] = book_obj.pk
temp_dict['title'] = book_obj.title
temp_dict['price'] = book_obj.price
temp_dict['info'] = book_obj.info
data_dict[book_obj.pk] = temp_dict
# {1:{},2:{},3:{},4:{}}
return JsonResponse(data_dict)
2.django组件自带的数据序列化组件,后续学更厉害的drf,不同于之前的序列化。
def ab_ser_func(request):
# 1.查询所有的书籍对象
book_queryset = models.Book.objects.all()
# queryset [对象、对象]
# 导入内置序列化模块
from django.core import serializers
# 调用该模块下的方法,第一个参数是想以什么样的方式序列化数据
res = serializers.serialize('json', book_queryset)
return HttpResponse(res)
"""
bejson是前端查看后端代码的一个工具网站,里面有格式化校验的功能。
"""
批量操作数据
浏览器访问一个路由,立刻创建10万条数据并展示到前端页面。
1.常规批量插入数据
def index(request):
# 插入10000条数据
for i in range(10000):
models.Book.objects.create(title= '第%s本书' % i)
book_queryset = models.Book.objects.all()
2.使用orm提供的bulk_create方法批量插入数据(效率高、减少操作时间)
def index(request):
# 插入10000条数据
# for i in range(10000):
# models.Book.objects.create(title='第%s本书' % i)
book_list = []
for i in range(10000):
book_obj = models.Book(title=f'第{i}本书')
book_list.append(book_obj)
# [models.Book(title=f'第{i}本书') for i in range(10000)] # 可以使用列表生成式
models.Book.objects.bulk_create(book_list) # 批量创建
# models.Book.objects.bulk_update() # 批量创建
book_queryset = models.Book.objects.all() # 查询数据
return render(request, 'bookList.html', locals())
涉及到大批量数据的创建,直接使用create可能会造成数据库崩溃。
批量数据创建>>>:bulk_create()
批量数据修改>>>:bulk_update()
# 前端页面展示数据
{% for book_obj in book_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
分页器
分页器着重点在处理逻辑,代码最后很简单。
推导流程:
1.queryset支持切片操作(正数)
2.研究各个参数之间的数学关系
每页固定展示多少条数据、起始位置、终止位置
3.自定义页码参数
current_page = request.GET.get('page',1)
# 如果前端没有就默认当前页为1
4.前端展示分页器样式
5.总页码数问题
divmod方法
6.前端页面页码个数渲染问题
后端产生 前端渲染
# 推导自定义分页器步骤
get请求可以携带参数,所以在向后端发送查看数据的请求的同时,可以携带一个参数告诉后端想看第几页的数据。
current_page = request.GET.get("page",1)
# 获取用户想访问的页码,如果没有,默认展示第一页
try:
# 由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
current_page = int(current_page)
except Exception as e:
current_page = 1
# 还需要定义页面到底展示几条数据
per_page_num = 10 # 一页展示10条数据
# 需要对总数据进行切片操作 需要确定切片起始位置和终止位置
start_page = ?
end_page = ?
"""
下面需要研究current_page、per_page_num、start_page、end_page四个参数之间的数据关系。
per_page_num = 10
current_page start_page end_page
1 0 10
2 10 20
3 20 30
4 30 40
per_page_num = 5
current_page start_page end_page
1 0 5
2 5 10
3 10 15
4 15 20
规律:
start_page = (current_page - 1) * per_page_num
end_page = current_page* per_page_num
"""
# 数据总页面获取
内置方法之divmod
divmod(100,10)
(10, 0) # 10页
divmod(101,10)
(10, 1) # 11页
divmod(99,10)
(9, 9) # 10页
# 余数只要不是0就需要在第一个数字上加一
元组的第二个数字是否为0可用于判断总共需要多少页,不为0则在第一个数字上加一
book_queryset = models.Book.objects.all()
all_count = book_queryset.count() # 数据总条数
all_pager, more = divmod(all_count, per_page_num)
if more:
all_pager += 1
# 有余数则总页数加一
"""
总结:
利用start_page和end_page对总数据进行切片取值再传入前端页面就能够实现分页展示。
后端
book_list = models.Book.objects.all()[start_page:end_page]
return render(request,'booklist.html',locals())
前端
{% for book in book_list %}
<p>{{ book.title }}</p>
{% endfor %}
"""
自定义分页器封装代码
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
# 自定义分页器的使用
django自带的分页器模块使用起来很麻烦,可以自己封装一个,只需要掌握使用方式即可。
# 后端
def ab_pg_func(request):
book_queryset = models.Books01.objects.all()
from app01.utils.mypage import Pagination # 导入
current_page = request.GET.get('page')
#获取当前页
page_obj=Pagination(current_page=current_page, all_count=all_count,per_page_num=10)
# 生成对象
page_queryset = book_queryset[page_obj.start:page_obj.end]
# 切片
return render(request, 'pgPage.html', locals())
# 前端
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for book_obj in page_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
</div>
</div>
</div>
form组件
forms组件介绍
之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签,并且用form标签把它们包起来。
与此同时在很多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确... 若用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息。
Django form组件就实现了上面所述的功能, forms组件的主要功能如下:
自动校验数据
自动生成标签
自动展示信息
在用到forms组件的之前首先需要定义forms组件, 然后才能使用。
# 以用户注册作为示例
Form定义
要定义Form组件, 需要与模型表一一对应, 下面是模型表的定义
from django import forms
class MyForm(forms.Form):
username = forms.CharField(min_length=3, max_length=8)
# username字段最少三个字符最大八个字符
age = forms.IntegerField(min_value=0, max_value=200)
# 年龄最小0 最大200
email = forms.EmailField() # 必须符合邮箱格式
# 校验数据的功能(初识)
小需求:获取用户数据并发送给后端校验,后端返回不符合校验规则的提示信息。
(以下代码都于测试文件中)
form_obj = views.MyForm({'username':'jason','age':18,'email':'123'})
form_obj.is_valid()
# 1.判断数据是否全部符合要求
False # 只要有一个不符合结果都是False
form_obj.cleaned_data
# 2.获取符合校验条件的数据
{'username': 'jason', 'age': 18}
form_obj.errors
# 3.获取不符合校验规则的数据及原因
{'email': ['Enter a valid email address.']}
1.只校验类中定义好的字段对应的数据,多传的根本不做任何操作。
(即多传的数据不 校验功能的判断)
2.默认情况下类中定义好的字段都是必填的