第六十二天 批量操作、分页、form组件
一、批量操作
1.首先我们用以前的方法尝试创建10万条数据
from django.shortcuts import render,redirect,HttpResponse
from app01 import models
# Create your views here.
def index(request):
for i in range(100000):
models.Book.objects.create(name=f'第{i}条数据')
book_query = models.Book.objects.all()
return render(request,'book_list.html',locals())
——————————————————————————————————————
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-offset-md-2">
{% for book_obj in book_query %}
<p>{{ book_obj.name }}</p>
{% endfor %}
</div>
</div>
</div>
</body>
———————————————————————————————————————
"""
1.首先在老师的版本中sqllite3数据库会锁住
2.我们自己现在的版本中创建10万条数据会卡好一会,但是依旧会创建成功
3.我们这里去使用一种对数据库压力小一点的办法
"""
2.bulk_create()批量创建数据
def index(request):
book_list = []
for i in range(100000):
book_obj = models.Book(name=f'第{i}本书')
book_list.append(book_obj)
'''上述四行可以简写为一行>>>:列表生成式(列表表达式)'''
# book_list = [models.Book(name=f'第{i}本书') for i in range(100000)]
models.Book.objects.bulk_create(book_list)
book_query = models.Book.objects.all()
return render(request, 'book_list.html', locals())
————————————————————————————————————————
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-offset-md-2">
{% for book_obj in book_query %}
<p>{{ book_obj.name }}</p>
{% endfor %}
</div>
</div>
</div>
</body>
"""
1.book_obj = models.Book(name=f'第{i}本书'),是先进行对象的创建,不同于上面的直接进行数据库的写入
2.book_list = [models.Book(name=f'第{i}本书') for i in range(100000)] 列表生成式需要进行复习
3.利用models.Book.objects.bulk_create(book_list)中的bulk_create进行批量创建
4.垃圾mac浏览器看不出区别
"""
3.总结
浏览器访问一个django路由 立刻创建10万条数据并展示到前端页面
create()、all()
涉及到大批量数据的创建 直接使用create可能会造成数据库崩溃
批量数据创建>>>:bulk_create()
批量数据修改>>>:bulk_update()
二、数据分页操作
首先思考分页的本质
1.分页本质上是仅展示部分数据,具体操作阶段在book_query = models.Book.objects.all()
2.因为是一个包含很多queryset的对象,所以可以利用[0,10]进行取数据切片
3.那么就思考将开头和结尾设置在[0,10],数据切片的起始位置和结束位置与用户选择页数,以及每页展示数据个数的关系
4.起始位置=(选择页-1)*每页数据个数 结束位置=选择页*每页数据个数
"""
补充知识:关于字典
dic = {'name':'jojo','pwd':123}
print(dic.get('pwd',1)) # 123
print(dic.get('age',1)) # 1
去取字典中的值的时候如果是原来就有的,那么输出值为原来的值,如果是原来没有的就是后面填写的1
"""
def index(request):
current_page = request.GET.get('page', 1)
try:
current_page = int(current_page)
except TypeError:
current_page = 1
per_page_num = 10
start_num = (current_page - 1) * 10
end_num = current_page * 10
book_query = models.Book.objects.all()[start_num:end_num]
return render(request, 'bookList.html', locals())
————————————————————————————————————————————————————————
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-offset-md-2">
{% for book_obj in book_query %}
<p class="text-center">{{ book_obj.name }}</p>
{% endfor %}
<nav aria-label="Page navigation" class="text-center">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li><a href="?page=1">1</a></li>
<li><a href="?page=2">2</a></li>
<li><a href="?page=3">3</a></li>
<li><a href="?page=4">4</a></li>
<li><a href="?page=5">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</body>
"""
1.首先我们要清楚传输具体页面可以使用get可以携带部分不重要的数据的特性
http://127.0.0.1:8000/login/?aaa=123&bbb=456这就传输了两组数据
print(request.GET) # <QueryDict: {'aaa': ['123'], 'bbb': ['456']}>
2.也可以利用上面的特性绑定按钮,前面的网址不用写全可以自动绑定<li><a href="?page=1">1</a></li>
3.下面我们就要对导航条的长度进行要求,以及导航数据的个数
"""
2.我们先把全部的页面数字在导航条上面进行展示
def index(request):
book_data = models.Book.objects.all()
book_count = book_data.count()
per_page_num = 10
page_count, more = divmod(book_count, per_page_num)
if more:
page_count += 1
html_page = ''
for i in range(1, page_count + 1):
html_page += '<li><a href="?page=%s">%s</a></li>' % (i, i)
current_page = request.GET.get('page', 1)
try:
current_page = int(current_page)
except TypeError:
current_page = 1
per_page_num = 10
start_num = (current_page - 1) * 10
end_num = current_page * 10
book_query = book_data[start_num:end_num]
return render(request, 'bookList.html', locals())
——————————————————————————————————————————————————
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-offset-md-2">
{% for book_obj in book_query %}
<p class="text-center">{{ book_obj.name }}</p>
{% endfor %}
<nav aria-label="Page navigation" class="text-center">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{{ html_page|safe }}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</body>
"""
1.第一个难是计算总共有几页divmod(book_count, per_page_num)用以计算除出来的结果和余数
2.如果余数大于一那么计算结果加一即为页数
3.本来想要利用 for 以及 range在前端创建标签,但是前端没法使用range
4.那么可以在后端创建好文字的标签,传输到前段使用|safe进行转换就行了
5.下面我们进行思考页面标签不要全部展示
"""
3.页面标签展示限制
def index(request):
book_data = models.Book.objects.all()
book_count = book_data.count()
per_page_num = 10
page_count, more = divmod(book_count, per_page_num)
if more:
page_count += 1
current_page = request.GET.get('page', 1)
try:
current_page = int(current_page)
except TypeError:
current_page = 1
html_page = ''
xxx = current_page
if current_page < 6:
xxx = 6
for i in range(xxx - 5, xxx + 6):
if i == current_page:
html_page += '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i)
else:
html_page += '<li><a href="?page=%s">%s</a></li>' % (i, i)
start_num = (current_page - 1) * 10
end_num = current_page * 10
book_query = book_data[start_num:end_num]
return render(request, 'bookList.html', locals())
——————————————————————————————————————————————————
html页面同上
"""
1.首先我们本质上只要展示前五页和后五页的标签就行了
2.for i in range(xxx - 5, xxx + 6):其中展示多少自己去设定
3.'<li class="active"><a href="?page=%s">%s</a></li>'中的<li class="active">就是选中给的页面
"""
自定义分页器代码
"""
自定义的模组的代码一般放在app目录下的plugins文件夹下面,文件夹需要自己创建
"""
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=10, 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)
三、自定义分页器
def index(request):
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())
——————————————————————————————————————————
<div class="container">
<div class="row">
<div class="col-md-8 col-offset-md-2">
{% for book_obj in page_query %}
<p class="text-center">{{ book_obj.name}}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
</div>
</div>
</div>
"""
1.主要注意需要传入自定义汉函数的参数
2.page_query = book_query[page_obj.start:page_obj.end]找到了数据的起始与结束位置
3.page_obj.page_html直接数据导航条标签
"""
四、form组件
前戏:编写用户登录功能并且校验数据返回提示信息(form表单)
# 在这里我们不去使用ajax的异步回调
def ab_form(request):
data_dic = {'username': '', 'password': ''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jojo':
data_dic['username'] = '别和我取一样的名字'
if password == '123':
data_dic['password'] = '密码太简单了'
return render(request, 'ab_form.html', locals())
—————————————————————————————————————————————————
<form action="" method="post">
<p>username:
<input type="text" name="username">
<span style="color: brown">{{ data_dic.username }}</span>
</p>
<p>password:
<input type="text" name="password">
<span style="color: brown">{{ data_dic.password }}</span>
</p>
<input type="submit" value="提交">
</form>
"""
这里创建了一个字典去存储数据并且传输到前端span标签没有值是无法显示的
"""
form组件
1.数据校验
支持提前设置各种校验规则 之后自动校验
2.渲染页面
支持直接渲染获取用户数据的各种标签
3.展示信息
支持针对不同的校验失败展示不同的提示
先创建一个对应的表格
class User(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
email = models.EmailField()
之后再在views文件夹中创建一个类,用来限制
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=2)
age = forms.IntegerField(max_value=100, min_value=1)
email = forms.EmailField()
之后再左下角的Python Console进行试运行
1.数据校验功能
1.1.传递待校验的数据
form_obj = views.MyForm({'name':'jason','age':18,'email':123})
1.2.判断所有的数据是否符合校验( is_valid() ) # 是否合适valid
form_obj.is_valid()
1.3.获取符合校验规则的数据( cleaned_data ) # cleaned_data数据清洗
form_obj.cleaned_data
{'name': 'jason', 'age': 18}
1.4.查阅不符合校验规则的数据及错误原因( errors ) # 查找错误
form_obj.errors
{'email': ['Enter a valid email address.']}
"""
1.form类中编写的字段默认都是必填的 少传则肯定通不过校验 is_valid !!!!!!!!!!!!!!!!
2.校验如果多传了一些字段 则不参与校验 全程忽略 !!!!!!!!!!!!!!!!!!!
"""
六、form组件的渲染功能
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=2)
age = forms.IntegerField(max_value=100, min_value=1)
email = forms.EmailField()
# 首先利用上面写的类创建一个对象,并且传输到前端页面
def func(request):
form_obj = MyForm()
return render(request, 'func.html', locals())
—————————————————————————————————————————————————
渲染标签功能
方式1(封装程度高 扩展性差)
<form action="", method="post">
{{ form_obj.as_p }}
{{ form_obj.as_table }}
{{ form_obj.as_ul }}
<input type="submit" value="提交">
</form>
"""
这种方式会直接将对象中的字段自动生成一整个页面,但是因为是一整个,所以无法进行自定义样式的修改
"""
方式2(封装程度低 扩展性好 编写困难)
<form action="", method="post">
{{ form_obj.name.lable }}
{{ form_obj.name }}
<input type="submit" value="提交">
</form>
"""
form_obj.name.lable可以理解为生成name的一个lable,这里也就是生成一个name
form_obj.name可以理解为生成一个需要填入的input框
lable的名字可以自己去进行修改
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=2, lable='用户名')
"""
方式3(推荐使用)
<form action="", method="post">
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p>
{% endfor %}
<input type="submit" value="提交">
</form>
"""
这里的意思其实和上面的一样循环中的form约等于form_obj.name
这个因为for循环所以不用再自己一个个的去写了
"""
六、form的数据校验以及展示功能
from django import forms
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=2)
age = forms.IntegerField(max_value=100, min_value=1)
email = forms.EmailField()
def func(request):
form_obj = MyForm()
if request.method == 'POST':
form_obj = MyForm(request.POST)
if form_obj.is_valid():
print(form_obj.cleaned_data)
return render(request, 'func.html', locals())
————————————————————————————————————————————————
<form action="" method="post" novalidate>
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit" value="提交">
</form>
"""
1.首先要注意前端的校验很不靠谱所以在form上面用novalidate关闭校验
2.form_obj = MyForm(request.POST)因为POST获取到的本来就是字典直接传入就行了没必要一个个request.POST.get !!!!!!!!!!!!!!!!!!!!!!!!
3.检验成功就可以传入数据库了可以用 create(**form_obj.cleaned_data),因为后面出来的是清洗好的字典,拿来用就行了 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4.这里最传神的一点就是,因为post以后form_obj从原来的没有值,被赋值了,就可以传输到前端去调用errors等方法,form.errors.0就可以显示出来值了
"""
# 可以自己指定错误的报错信息
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=2, label='用户名',
error_messages={
'max_length': '用户名最多8个字符',
'min_length': '用户名最少2个字符',
'required': '用户名不为空'
}
)
age = forms.IntegerField(max_value=100, min_value=1)
email = forms.EmailField(error_messages={'invalid':'格式错误'})
# 或者可以在settings文件中将Django中的报错指定为中文!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from django.conf import global_settings # 在其中可以找到该设置什么方法
LANGUAGE_CODE = 'zh-hans'
七、form的数据校验以及展示功能(注意前面import的东西)
from django import forms
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
重要的字段参数
max_length、min_length
max_value、min_value
label 字段注释
error_messages 错误提示 # 前文有提到,类似于超出范围的时候的报错
required 是否为空 # required=True就是不能为空的意思
widget 标签类型、标签属性
initial 默认值 # 标签的默认值比如说摸认选择为男性
validators 正则校验
widget详解
class MyForm(forms.Form):
name = forms.CharField(max_length=8, min_length=2,widget=forms.widgets.TextInput(attrs={'class': 'form-control'})) # class和id都可以去控制
validators详解
phone = forms.CharField(validators=[
RegexValidator(r'^[0-9]+$','请输入数字'),
RegexValidator(r'^159[0-9]+$', '请输入以159为开头数字')
])
以后要用到widget可以查询下面的方法
参考https://blog.csdn.net/ak739105231/article/details/103852585
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
单radio,值为字符串
user = fields.CharField(
initial=2,
widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
)
单radio,值为字符串
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.RadioSelect
)
单select,值为字符串
user = fields.CharField(
initial=2,
widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
)
单select,值为字符串
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
多选select,值为列表
user = fields.MultipleChoiceField(
choices=((1,'上海'),(2,'北京'),),
initial=[1,],
widget=widgets.SelectMultiple
)
单checkbox
user = fields.CharField(
widget=widgets.CheckboxInput()
)
多选checkbox,值为列表
user = fields.MultipleChoiceField(
initial=[2, ],
choices=((1, '上海'), (2, '北京'),),
widget=widgets.CheckboxSelectMultiple
)
八、form组件之对钩函数
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()
'''第二层校验'''
# 1.校验用户名是否已存在
def clean_name(self):
name = self.cleaned_data.get('name')
res = models.User.objects.filter(name=name)
if res:
return self.add_error('name', '用户名已经存在')
return name
# 2.校验两次密码是否一致
def clean(self):
pwd = self.cleaned_data.get('pwd')
confirm_pwd = self.cleaned_data.get('confirm_pwd')
if confirm_pwd != pwd:
return self.add_error('confirm_pwd', '两次密码不一致')
return self.cleaned_data
def func(request):
form_obj = MyForm()
if request.method == 'POST':
form_obj = MyForm(request.POST)
if form_obj.is_valid():
res = form_obj.cleaned_data
res.pop('confirm_pwd')
print(res)
models.User.objects.create(**res)
return render(request, 'func.html', locals())
—————————————————————————————————————————————————
<body>
<form action="" method="post" novalidate>
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
"""
1.钩子函数clean只有在form上面的检验完成之后才运行
2.clean_name是针对name字段进行校验,clean可以对所有字段进行校验
3.return self.add_error('name', '用户名已经存在')是专门针对name进行报错的方法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4.钩子函数必须要把勾的东西还回去,clean_name就return name,clean就把所有东西还回去return self.cleaned_data!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
"""
九、modelform组件(modelform是form的优化版本 使用更简单 功能更强大)
"""modelform最厉害的地方在于不用自己一个个的去创建字段了,它可以自动给你批量生成"""
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)
if res:
self.add_error('name', '用户名已存在')
return name
def md(request):
modelform_obj = MyModelForm()
if request.method == 'POST':
modelform_obj = MyModelForm(request.POST)
# edit_obj = models.User.objects.filter(name='jason').first()
# modelform_obj = MyModelForm(request.POST,instance=edit_obj)
if modelform_obj.is_valid():
modelform_obj.save() # 保存数据
return render(request,'md.html',locals())
—————————————————————————————————————————————————
<body>
<form action="" method="post" novalidate>
{% for modelform in modelform_obj %}
<p>
{{ modelform.label }}{{ modelform }}
<span>{{ modelform.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
"""
1.首先创建一个MyModelForm继承了我们要用的forms.modelform
2.在里面说明要对应的表以及其中的字段class Meta: model = models.User fields = '__all__'
3.接下来的第二步的数据校验就和form的一样了,clean的功能使用方法是一样的
4.modelform_obj.save() 是保存数据的方法!!!!!!!!!!!!!!!!
5.如果想修改数据就得使用
# edit_obj = models.User.objects.filter(name='jason').first()
# modelform_obj = MyModelForm(request.POST,instance=edit_obj)
"""
# 自己试试增删改查的方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现