django8/分页器/form组件
批量操作数据
批量数据展示
自定义分页器
form组件
modelform组件
分页器组件
分页器,简单明了,将很多页面进行分页展示。美观且方便
批量操作数据
1.新建一个django项目
2.默认使用sqlite3数据库
urls.py
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
]
models.py
创建字段
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name='名字')
#-----------------------
模型迁移数据库
点击tools->点击run manage.Task ->makemigration->migrate
views.py
批量插入有两种方式
【了解】方式一:for 循环+create( ) 弊端,容易导致数据库崩溃
【老铁良心推荐】方式二: bulk_create() 函数方法,批量创建,自带分页。不回导致数据库崩溃
-------------------方式一------------------------------------------------
from django.shortcuts import render
from app01 import models
# Create your views here.
def index(request):
#for循环查询
for i in range(1000):
models.Book.objects.create(title=f'第{i}本书')
# 然后返回查询
book_query = models.Book.objects.all()
return render(request, 'bookList.html', locals())
----------------方式二----------------------------------------
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(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) #批量创建,自带分页,不会崩掉
book_query = models.Book.objects.all()
return render(request, 'bookList.html', locals())
运行django
数据插入完成;下面写一个页面进行前端展示
BookList.py
前端展示块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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.title }}</p>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
效果图
数据量较大时候,页面应该考虑分页
分页的推导流程
1.QuerySet切片操作
[0:10] 只展示切片的一部分
2.分页样式添加;
分页的数据不规范(比如9332112条数据),划分难
3.页码展示 根据总数据和每页展示的数据得出总页码
使用内置函数divmod()
eg: divmod(总页码数,分页数)
100条分10页
divmod() 方法就是整除取余
如:
divmod(100,10)
(10,0) # 10页
divmod(101,10)
(10,1) # 11页
divmod(99,10)
(9,9) # 10页
余数只要不是0就需要在第一个数字上加一
4.后端分成页码后 前端如何展示总页数呢。
前端模板语法不支持range 但是后端支持 我们可以在后端创建好html标签然后传递给html页面使用
5.如何限制住展示的页面标签个数
页面推荐使用奇数位(对称美)利用当前页前后固定位数来限制,如何想展示当前是那页,可以用css样式的激活码样式
6.首尾页码展示范围问题 此时就结束。
设置页码数限制,不然出现负数
django自带的jango分页不好用。
自定义分页器
封装好的,直接使用
在项目里我们一般新建一个plugins,或者utils存放第三方模块。
将以下代码自定义到plugins文件夹下面,文件名称自定义。
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)
自定义分页器使用
后端 views.py
from app01.plugins import mypage
book_query = models.Book.objects.all()
page_obj = mypage.Pagination(current_page=request.GET.get('page'),
all_count=book_query.count()
)
page_query = book_query[page_obj.start:page_obj.end]
return render(request, 'bookList.html', locals())
{% for book_obj in page_query %}
<p class="text-center">{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
前端
{% for book_obj in page_query %}
<p class="text-center">{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
form组件
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户数据的标签,并且用form标签把它们包起来
与此同时我们在好多场景下逗需要对用户的输入做校验,比如校验用户输入长度和格式等正不正确,如果用户输入的内容有错误就需要在页面上相应的位置显示错误信息,保留上次输入记录
django form组件就实现了上述对对功能
前戏
编写用户登录功能并且校验数据返回提示信息(form表单)
def ab_form(request):
data_dict = {'username':'','password':''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason':
data_dict['username'] = 'jason是你能随便用的吗'
if password == '123':
data_dict['password'] = '密码就设123???'
return render(request,'ab_form.html',locals())
form组件功能
1.数据校验
支持提前设置各种校验规则,之后自动校验
2.渲染页面
支持直接渲染获取用户数据的各种标签
3.展示信息
支持针对不同的校验失败展示不同的提示
form类型创建
from django import forms
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=3) # 用户名最长八个字符 最短三个字符
age = forms.IntegerField(max_value=150, min_value=0) # 年龄最小0岁 最大150岁
email = forms.EmailField() # 邮箱必须符合邮箱格式(至少有个@符号)
1.数据校验功能
1.1.传递待校验的数据
form_obj = views.MyForm({'name':'jason','age':18,'email':123})
1.2.判断所有的数据是否符合校验
form_obj.is_valid()
1.3.获取符合校验规则的数据
form_obj.cleaned_data
{'name': 'jason', 'age': 18}
1.4.查阅不符合校验规则的数据及错误原因
form_obj.errors
{'email': ['Enter a valid email address.']}
方法 | 作用 |
---|---|
.is_valid() | 判断所有数据是否符合校验 |
.cleaned_data | 获取符合校验规则的数据 |
.errors | 查阅不符合校验规则的数据及错误原因 |
2.渲染标签功能
后端
class MyForm(forms.Form):
'''第一层校验'''
name = forms.CharField(max_length=8, min_length=3, label='用户名')
pwd = forms.IntegerField(label='密码')
confirm_pwd = forms.IntegerField(label='确认密码')
age = forms.IntegerField()
email = forms.EmailField()
前端
渲染方式一:封装程度高,扩展性差 自动生成label,和p标签,扩展性差,如果不想使用p标签等,无法拆除
<form action="">
{{ form_obj.as_p }}
{{ form_obj.as_table }}
{{ form_obj.as_ul }}
</form>
-------------------------------------------------------------------------
渲染方式二:{封装程度低,扩展性好,编写困难,后端字段多的情况下,需要写很多。}
<form action="">
{{form_obj.name.lable}}
{{form_obj.name}}
</form>
-------------------------------------------------------------------------
【推荐使用】渲染方式三
<form action="">
{% for form in form_obj %}
<p>{{form.label}}{{form}}</p>
{% end for %}
</form>
--------------------------------------总结-------------------------------
类中以外的所有标签都不会自动渲染 需要自己编写
forms组件只负责渲染获取用户数据的标签
form表单标签和提交按钮需要自己写
渲染标签中文提示 可以使用参数 label指定 不指定默认使用字段名首字母大写
渲染方式一
渲染方式二展示效果
渲染方式三
forms组件钩子函数
钩子函数的含义其实就是在程序的执行过程中穿插额外的逻辑,比如说在判断用户名是否符合格式的同时校验用户名是否已存在,校验校验密码和确认密码是否一致等。
校验用户名是否已存在,钩子函数之局部钩子(校验单个字段)。
# 局部钩子:校验用户名是否已存在(一次性只能勾一个人)
'''钩子函数是数据经过了字段第一层参数校验之后才会执行'''
def clean_name(self): # 自动生成的函数名 专门用于对name字段添加额外的校验规则
# 1.先获取用户名
name = self.cleaned_data.get('name')
# 2.判断用户名是否已存在
is_exist = models.User.objects.filter(name=name)
if is_exist:
# 3.提示信息
self.add_error('name', '用户名已存在')
# 4.最后将你勾上来的name返回回去
return name
校验校验密码和确认密码是否一致,钩子函数之全局钩子(校验多个字段)。
# 全局钩子:校验密码与确认密码是否一致(一次性可以勾多个人)
def clean(self):
# 1.获取多个字段数据
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
self.add_error('confirm_password', '两次密码不一致 你个傻帽!!!')
# 最后将整个数据返回
return self.cleaned_data
forms组件字段参数
参数 | 说明 |
---|---|
min_length | 最小长度 |
max_length | 最大长度 |
label | 字段名称 |
error_messages | 错误提示 |
min_value | 最小值 |
max_value | 最大值 |
initial | 默认值 |
validators | 正则校验器 |
widget | 控制渲染出来的标签各项属性 |
choices | 选择类型的标签内部对应关系 |
validators案例
from django.core.validators import RegexValidator
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
widget案例
# forms.widgets.控制type的类型(attrs=控制各项属性:class id ...)
password = forms.CharField( widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
)
choices案例
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
self.fields['hobby'].choices = models.Classes.objects.all().values_list('id','caption')
forms组件字段类型
password---密码
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
)
radioSelect--单选框
gender = forms.fields.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
单选Select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
多选Select
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
单选checkbox
class LoginForm(forms.Form):
...
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
forms组件字段类型了解即可,其他的可以参考Django Form表单组件 - JasonJi - 博客园 (cnblogs.com)
forms组件源码分析
我们从is_valid如何校验数据进行分析。
第一步:查看类源码发现只要给类传参self.is_bound肯定是True,重点看self.errors
def is_valid(self):
return self.is_bound and not self.errors
第二步:查看了源码发现self._errors初始化就是None 所以肯定看full_clean方法
@property
def errors(self):
if self._errors is None:
self.full_clean()
return self._errors
第三步:查看full_clean方法,发现主要有3个方法。
def full_clean(self):
self.cleaned_data = {} # 创建了一个存储检查没问题的数据的空字典
self._clean_fields() # 校验数据
self._clean_form() # 封装
self._post_clean() # 返回
第四步:查看self._clean_fields方法
def _clean_fields(self):
for name, field in self.fields.items():# 循环获取字段名和字段对象
if field.disabled: # 字段是否是禁用的
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) # 如果不是禁用,获取字段对应的用户数据,进行校验
try: # 异常捕获
if isinstance(field, FileField): # 文件数据
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value) # clean校验的方法
self.cleaned_data[name] = value # 以上校验没有问题,就上传到cleaned_data里
if hasattr(self, 'clean_%s' % name): # 利用反射判断是否拥有clean_%s的方法,就是在获取钩子函数执行,
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value # 钩子函数不报错,添加到cleaned_data里
except ValidationError as e: # 钩子函数报错,添加提示保存信息
self.add_error(name, e)
查看源码发现校验数据的整个过程内部都有异常处理机制
from django.core.exceptions import ValidationError
raise ValidationError('用户名不存在 你个DSB')
# 自己在clean_%s钩子函数主动抛出一个异常,也不会报错,因为钩子函数是交给_clean_fields这个方法里执行的,就算clean_%s报错也是交给了_clean_fields方法里的异常捕获处理。
ModelForm简介
forms组件主要配合models里面的默写类一起使用,但是默写类里面的字段需要在forms类中相当于重写一遍, 代码冗余。为了更好的结合forms与models的关系,有了一个ModelForm(基于forms组件)
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "书名",
"price": "价格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
}
class Meta下常用参数
model = models.Book # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
save()方法
每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:
from myapp.models import Book
from myapp.forms import BookForm
# 根据POST数据创建一个新的form对象
form_obj = BookForm(request.POST)
# 创建书籍对象
new_ book = form_obj.save()
# 基于一个书籍对象创建form对象
edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象
form_obj = BookForm(request.POST, instance=edit_obj)
form_obj.save()