Django框架(八)
批量数据插入
# 假设现有一个需求:我们要循环插入10000条数据到数据库中,并将数据返回到前端页面。
urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
# 批量数据操作
path('index', views.index)
]
mdels.py
from django.db import models
# Create your models here.
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
views.py
from django.shortcuts import render
from app01 import models
# Create your views here.
def index(request):
book_list = []
for i in range(10000):
book_obj = models.Book(name=f'第{i}本书')
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list) # 使用orm提供的bulk_create方法可以快速创建数据
book_query = models.Book.objects.all()
return render(request, 'booklist.html', locals())
html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for book_obj in book_query %}
<p>{{ book_obj.name }}</p>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
效果展示
总结
"""
第一种:循环使用create插入100条数据
第二种:循环将数据先统一添加到一个列表中,再统一添加到数据库
很明显第二种方式只操作了一次数据库,速度肯定相比较要快的。
所以:
当你想要批量插入数据的时候 使用orm提供的bulk_create能够大大的减少操作时间
"""
批量数据展示
分页推导
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 = 5current_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就需要在第一个数字上加一
判断需要展示的总页数
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
切片取值
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)
自定义分页器使用前端
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% for book in page_queryset %}
<p>{{ book.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
</div>
</div>
</div>
自定义分页器使用后端
def get_book(request):
book_list = models.Book.objects.all()
current_page = request.GET.get("page",1)
all_count = book_list.count()
page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10)
page_queryset = book_list[page_obj.start:page_obj.end]
return render(request,'booklist.html',locals())
Django之form组件
form组件
前戏
# 我们先来实现一个注册功能来引出什么是form组件
需求:获取用户名和密码:
利用form表单提交数据
在后端判断用户名和密码是否符合一定的格式条件
格式条件:
用户名不能有特殊符号
密码不能少于六位...
# 如果不符合条件需要将提示信息展示到前端页面
这要怎么实现呢? 我们来尝试一下:
views.py
def ab_form(request):
back_dic = {'username':'','password':''} # 定义一个空的字典(用来前端处理GET请求数据)
if request.method == 'POST': # 判断post请求
username = request.POST.get('username') # 取到用户输入的username
password = request.POST.get('password') # 取到用户输入的password
if '傻逼' in username: # 判断是否存在'傻逼'不合法字符
back_dic['username'] = '用户名不合法' # 如果不合法则把提示信息添加到字典中
if len(password) < 6: # 判断密码是否小于6
back_dic['password'] = '密码不能少于六位数'
return render(request,'ab_form.html',locals()) # 返回到前端页面
前端页面
<form action="" method="post"> // 记得修改method属性
<p>username:
<input type="text" name="username">
<span style="color: darkred">
{{ back_dic.username }} 、
// 无论是post请求还是get请求,页面都能够获取到字典 只不过:
// 如果提交的是GET请求这个span标签是没有数据的(字典是空的)
// 如果提交的是post请求才可能有数据
</span>
</p>
<p>password: <input type="password" name="password">
<span style="color: darkred">
{{ back_dic.password }} // 取出后端传的数据(同上)
</span>
</p>
<input type="submit" class="btn btn-success">
</form>
总结
1.手动书写前端获取用户数据的html代码 # 渲染html代码
2.后端对用户数据进行校验 # 校验数据
3.对不符合要求的数据进行前端提示 # 展示提示信息
# 数据的校验必须在后端执行,不能在前端利用js直接完成:
数据校验前端可有可无
但是后端必须要有!
因为前端的校验是弱不禁风的 他人可以直接修改,或者利用爬虫程序绕过前端页面直接朝后端提交数据
# 举例:
购物网站
选取了货物之后 会计算一个价格发送给后端进行结算,如果后端不做价格的校验,用户通过部分手段修改了价格,修改了1元,后端在不校验的话,那么这样可想而知...
# 正规的编写方式
实际是获取到用户选择的所有商品的主键值
然后在后端通过主键值查询出所有商品的价格 再次计算一遍
如果跟前端一致 那么完成支付如果不一致直接拒绝
forms组件
# forms组件
能够完成的事情
1.渲染html代码
2.校验数据
3.展示提示信息
基本使用
from django import forms # 导入forms组件
# 定义一个类继承forms.Form
class MyForm(forms.Form):
# username字符串类型最小三位最长8位
username = forms.CharField(min_length=3,max_length=8)
# password字符串类型最小三位,最长12位
password = forms.CharField(min_length=3,max_length=12)
# email字符必须符合邮箱格式:xxx@xx.com
email = forms.EmailField()
测试环境
# pycharm为我们提供了一个测试django的测试环境:
在这个里面就可以选择性的测试Django里面任意一个py文件
位置:pycharm下方 >> Python Console
# 但是提示功能不是很全:需要自己写(不提示不要认为是错了)
校验数据关键字
# 测试环境下:
from app01 import views
form_obj = views.MyForm({'username':'gary','password':123456,'email':'qwer'})
# 给类传一个字典的形式,那么它会按照类里面写的这些字段去校验这些数据。
# 那么如何查看这些数据是否合法呢?
is_valid()
# 判断是否合法:返回结果为布尔值
# 示例:
form_obj.is_valid()
返回结果:False
# 注意:该方法只有在所有数据全部合法的情况下才会返回True
cleaned_data
# 查看所有校验通过的数据
# 示例:
form_obj.cleaned_data
返回结果:{'username': 'gary', 'password': '123456'}
errors
# 查看所有不符合校验规则以及不符合的原因
# 示例:
form_obj.errors
返回结果:{'email': ['Enter a valid email address.']}
验证多传数据是否校验
form_obj = views.MyForm({'username':'gary','password':'123','email':'123@qq.com','hobby':'study'}) # 多传一个字段
form_obj.is_valid() # 判断是否符合校验规则
True
# 结论:校验数据只校验类中定义过的字段,多传不影响,多传的数据字段直接忽略
验证少传数据是否校验
form_obj = views.MyForm({'username':'gary','password':'123'})
form_obj.is_valid()
False
form_obj.errors
{'email': ['This field is required.']}
总结
也就意味着:校验数据的时候,默认情况下数据可以多传但是不能少传
forms组件渲染标签
准备工作
def index(request):
form_obj = MyForm() # 将上述定义的类实例化先要生成一个空对象
return render(request,'index.html',locals()) # 直接将该空对象传递给html页面
第一种渲染方式
<h3>第一种渲染方式:</h3>
<br>
<!-- 关键字:as_p -->
{{ form_obj.as_p }}
<!-- 关键字:as_table -->
{{ form_obj.as_table }}
<!-- 关键字:as_ul -->
{{ form_obj.as_ul }}
第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用
第二种渲染方式
<h3>第二种渲染方式:</h3>
<br>
<p>{{ form_obj.username.label }}{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}</p>
<p>{{ form_obj.email }}</p>
第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用
第三种渲染方式
<h3>第三种渲染方式:</h3>
{% for form in form_obj %}
<p>{{ form.label }}:{{ form }}</p>
{% endfor %}
第三种渲染方式(推荐使用):使用for循环的方式,代码书写简单 并且扩展性也高
总结
1、
需要在后端类名()实例化生成一个空对象
2、
forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
不能帮你渲染提交按钮
3、
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改 直接给字段对象加label属性即可
4、
# 推荐使用第三种渲染方式渲染标签
forms组件展示信息
html页面
<form action="" method="post">
{% for form in form_obj %}
<p>
{{ form.label }}:{{ form }}
<span style="color: red">{{ form.errors }}</span>
</p>
{% endfor %}
<input type="submit" class="btn btn-info">
</form>
后端数据
def index(request):
form_obj = MyForm()
if request.method == 'POST':
# 获取用户数据并校验
# request.POST可以看成就是一个字典
form_obj = MyForm(request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# 如果合法 操作数据库存储数据
return HttpResponse('OK')
else:
# 不合法怎样将错误信息展示到前端页面呢
pass
return render(request,'index.html',locals())
# 前端浏览器会帮我们校验数据,但是前端的校验是弱不禁风的(如果在'检查'位置修改了前端的代码块他就可以校验通过)
# 如何取消这种浏览器的校验呢
<form action="" method="post" novalidate>
# 只需要在form表单中添加novalidate属性即可
# 我们看到这个提示信息是ul>li的形式
# 因为:errors的结果是一个列表的形式,浏览器针对列表的形式会自动生成一个ul > li标签
# 解决:
<span style="color: red">{{ form.errors.0 }}</span>
# 需要在错误信息后(.)一个0
# 这样就相当于拿到索引[0]的错误信息就不会是一个列表的形式,这样浏览器就不会自动产生ul标签,只会拿到普通文本
异常现象
# 我们看到在我们提交数据的时候页面刷新并没有将页面的数据清空:
# 那么这是怎么实现的呢?
# 这样数据会一直保留到数据彻底合法。
提示信息自定义
# 现在我们的错误提示信息都是forms组件自动帮我们生成的错误信息,并且是英文的,我们就来解决这个问题:
# 参数 :error_messages={约束条件:不成立条件的提示信息}
eg:
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={'min_length':'用户名不能少于3位',
'max_length':'用户名不能大于8位',
'required':'用户名不能为空'
}
)
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确', # 邮箱格式需要这个参数
'required': '邮箱不能为空'
}
)
# 效果:
总结
1.novalidate:取消浏览器校验数据提示信息
2.form.errors.0 :取消自动生成ul标签拿到纯文本
3.必备的条件 get请求和post传给html页面对象变量名必须一样
4.forms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改
更加的人性化
5.error_messages={参数名:参数值} # 自定义错误提示信息
钩子函数(HOOK)
# 在特定的节点自动触发,完成相应的操作
# 钩子函数在forms组件中就类似于第二道管卡,能够让我们自定义校验规则
# 在forms组件中有两类钩子
1.局部钩子
当你需要给某个字段添加校验规则的时候可以使用局部钩子
2.全局钩子
当你需要给多个字段添加校验规则的时候可以使用全局钩子
# 书写位置:
在forms组件类里书写方法即可
示例
# 1.校验用户名中不能含有sb
# 判断:只需要校验username字段 (局部钩子)
# 钩子函数(局部钩子)
# 关键字:clean_局部字段名
def clean_username(self):
username = self.cleaned_data.get('username') # 数据通过了forms组件条件的数据都会存储在cleaned_data里,所以我们拿出这里面(过了第一道关卡)的数据,进行添加第二道关卡
if 'sb' in username: # 如果数据的用户名含有'sb'
# 提示前端展示错误信息(关键字:add_error)
self.add_error('username','不能骂人呀~~')
# 将钩子函数沟取出来的数据再放回去
return username
# 2.校验密码和确认密码两次密码是否一致
# 判断:需要校验password和confirm_password两个字段 (全局钩子)
# 全局钩子
# 关键字:clean
def clean(self): # 定义全局钩子
# 拿到通过forms组件的两次密码
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password: # 如果两次密码不一致
# 告诉前端展示错误信息
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数够出来的数据返回
return self.cleaned_data
整体代码
from django import forms
class MyForm(forms.Form ):
# username字符串类型最小三位最长8位
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={'min_length':'用户名不能少于3位',
'max_length':'用户名不能大于8位',
'required':'用户名不能为空'
}
)
# password字符串类型最小三位,最长12位
password = forms.CharField(min_length=3,max_length=12,label='密码',
error_messages={'min_length': '密码不能少于3位',
'max_length': '密码不能大于8位',
'required': '密码不能为空'
}
)
confirm_password = forms.CharField(min_length=3,max_length=12,label='确认密码',
error_messages={'min_length': '密码不能少于3位',
'max_length': '密码不能大于8位',
'required': '密码不能为空'
})
# email字符必须符合邮箱格式:xxx@xx.com
email = forms.EmailField(label='邮箱',
error_messages={
'invalid':'邮箱格式不正确',
'required': '邮箱不能为空'
}
)
# 钩子函数(局部钩子)
def clean_username(self):
username = self.cleaned_data.get('username')
if 'sb' in username:
# 提示前端展示错误信息
self.add_error('username','不能骂人呀~~')
# 将钩子函数沟取出来的数据再放回去
return username
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password:
self.add_error('confirm_password','两次密码不一致')
# 将钩子函数狗出来的数据返回
return self.cleaned_data
forms组件其他参数
forms组件字段里常见的参数
label # 字段名
error_messages # 自定义报错信息
initial # 默认值
initial默认值
required
required # 控制字段是否必填
required=False # 字段可以为空
样式渲染
# 我们发现我们现在写的标签是没有样式的,因为标签不是我们写的,是forms组件帮助我们完成的,那么我们是不能在前端页面添加样式的。
# 那么这样我们如何给标签添加样式呢?
# 针对不同类型的input如何修改呢?
text password redio date checkbox file
# forms组件默认渲染的都是text类型
控制input标签的type属性数据格式
# 关键字:widgef
widgef = forms.widgefs.TextInput()
widgef = forms.widgets.PasswordInput()
widgef = forms.widgets.CheckboxInput()
widgef = forms.widgets.EmailInput()
widgef = forms.widgets.FileInput()
widgef = forms.widgets.TextInput()
添加样式
# 关键字:attrs={'属性名':'属性值'}
# 多个属性值的话 直接空格隔开即可
字段正则校验
# 第一道关卡里面还支持正则校验
from django.core.validators import RegexValidator
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'), # 第一个正则约束
RegexValidator(r'^159[0-9]+$', '数字必须以159开头') # 第二个正则约束
]
)
# 只有第一个正则通过才走第二个正则
其他类型渲染
# radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3, # 默认是3
widget=forms.widgets.RadioSelect()
)
# select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.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.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
# 总结:
1.widgets.什么就是什么标签类型
2.只要是选择框都是 :ChoiceField
多选框都是:MultipleChoiceField
modelform组件
modelform是form的优化版本 使用更简单 功能更强大
class MyModelForm(forms.ModelForm):
class Meta:
model = models.User
fields = '__all__'
def clean_name(self):
name = self.cleaned_data.get('name')
res = models.User.objects.filter(name=name).first()
if res:
self.add_error('name','用户名已存在')
return name
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/288334.html