批量插入数据(bulk_create)
视图层
def index(request):
# 1.往书籍表中插入数据 1000
# 方式一: 这种插入方式,效率极低,每添加一条数据就要走一次数据库(不使用)
# for i in range(1000):
# models.Book.objects.create(title='第%s本书' %i)
# book_query = models.Book.objects.all()
# 方式二:批量插入(bulk_create),效率高
book_list = [] # 先定义一个空列表
for i in range(10000):
book_obj = models.Book(title='第%s本书' %i)
book_list.append(book_obj) # 把循环获得的对象添加到列表中
models.Book.objects.bulk_create(book_list) # 通过 bulk_create批量添加数据
return render(request, 'index.html', locals())
模型层
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{#普通插入#}
{#{% for foo in book_query %}#}
{# {{ foo.title }}#}
{#{% endfor %}#}
{#批量插入#}
{% for book in book_list %}
{{ book.title }}
{% endfor %}
</body>
</html>
自定义分页器
手动推到分页器(了解)
视图层
def index(request):
# 1.获取用户想要访问的页码数
current_page = request.GET.get('page', 1) # 如果没有page参数 默认就展示第一页
# 转成整型
current_page = int(current_page)
# 2.每页展示10条数据
per_page_num = 10
# 3.定义起始位置和终止位置
start_page = (current_page - 1) * per_page_num
end_page = current_page * per_page_num
# 4.统计数据的总条数
book_queryset = models.Book.objects.all()
all_count = book_queryset.count()
# 5.求数据到底需要多少页才能展示完
page_num, more = divmod(all_count, per_page_num) # divmod(100,10)
if more:
page_num += 1
# page_num就觉得了 需要多少个页码
page_html = ''
xxx = current_page # xxx就是用户点击的数字
if current_page < 6:
current_page = 6
for i in range(current_page - 5, current_page + 6):
if xxx == i:
page_html += '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i)
else:
page_html += '<li><a href="?page=%s">%s</a></li>' % (i, i)
book_queryset = book_queryset[start_page:end_page]
return render(request, 'index.html', locals())
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
{% for book_obj in book_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{{ page_html|safe }}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</body>
</html>
自定义分页器(后期使用直接复制)
自定义分页器的使用
拷贝下面的分页器代码,放在某个py文件里
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: 最多显示的页码个数
用法:
queryset = model.objects.all()
page_obj = Pagination(current_page,all_count)
page_data = queryset[page_obj.start:page_obj.end]
获取数据用page_data而不再使用原始的queryset
获取前端分页样式用page_obj.page_html
"""
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)
后端代码
from app01.utils.mypage import Pagination //导入分页器代码
# 使用封装好的分页器代码
def login(request):
book_queryset = models.Book.objects.all() //查询所有数据
current_page = request.GET.get('page',1)
all_count = book_queryset.count() //获取数据总条数
# 1.实例化产生对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
# 2.对真实数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
return render(request,'login.html',locals())
前端
{% for book_obj in page_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
创建多对多表关系的三种方式
- 全自动(就是平常我们创建表多对多关系的方式)
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
authors = models.ManyToManyField(to='Author') //建立表多对多关系字段
class Author(models.Model):
name = models.CharField(max_length=32)
优势:不需要手动创建第张表
缺点:由于第三张表不是自己手动创建,也就意味着第三张表字段是固定的,无法做扩展
- 纯手动(不推荐)
class Book(models.Model):
title = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book_Author(models.Model):
book = models.ForeignKey(to='Book') # ForeignKey 会自定给字段名添加_id
author = models.ForeignKey(to='Author') # ForeignKey 会自定给字段名添加_id
create_time = models.DateField(auto_now_add=True)
# auto_now=True, 每次对数据进行修改或保存都会触发获得最新的时间
# auto_now_add, 只有在第一次创建时会保存当前的时间,之后时间都不会改变
优点:第三张表可以任意扩展字段,扩展性高
缺点:orm查询数据麻烦,很多方法都不支持,不方便
半自动(扩展性强,推荐使用)
手动建表,但是要告诉orm 第三张表示是你自己建的
orm 只需要给我们提供方便的查询数据方法
但是,这种方式不支持使用 '''多对多字段的 add()、set()、 remove()、clear()''' 这四种方式
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
authors = models.ManyToManyField(to='Author', through='Book_Author', through_fields=('book', 'author'))
'''半自动 一定要加两个额外参数
through='Book_Author': 告诉django orm 书籍表和作者表的多对多关系是通过Book_Author表来记录的
through_fields=('book','author'): 告诉django orm记录关系时通过 Book_Author表中的 book字段和author字段来记录
后面字段的顺序由第三张表通过哪个字段查询单表就把哪个字段放前面'''
class Author(models.Model):
name = models.CharField(max_length=32)
class Book_Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
create_time = models.DateField(auto_now_add=True)
form组件
什么是form组件,可以干什么
1.forms组件就是一个类,可以检测前端传过来的数据,是否合法。
例如:前端传给来的邮箱数据,判断邮件格式对不对,用户名不能以什么开头,等等 》》》校验数据
2.还可以前端页面搭建 》》》 渲染页面
3.展示错误信息 》》》 展示错误信息
1.注册功能
用户输入的用户名中 不能包含hlm
如果包含了 就提示用户 输入的内容不符合社会主义核心价值观
用户输入的密码 不能小于三位
如果密码少于三位 提示用户 密码太短了
校验数据的普通代码逻辑
后端
def reg(request):
back_dic = {'username': '', 'password': ''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if 'jpm' in username:
back_dic['username'] = '不符合社会主义核心价值观'
if len(password) < 3:
back_dic['password'] = '密码不能少于三位'
return render(request, 'reg.html', locals())
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<form action="" method="post">
<p><input type="text" name="username">
<span>{{ back_dic.username }}</span></p>
<p><input type="password" name="password">
<apan>{{ back_dic.password }}</apan></p>
<p><input type="submit" class="btn-danger"></p>
</form>
</body>
</html>
通过 form组件操作,form组件能够自动帮你完成上面三件事
1.渲染页面
2.校验数据
3.展示错误信息
form组件的使用
1.使用语法
from django.shortcuts import render, HttpResponse
from django import forms
#1.先写一个类,继承Form
class MyForm(forms.Form):
#定义属性,用来校验
#限制最大长度为8,最小长度为3
username = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={
'min_length':'用户名最短三位',
'max_length':'用户名最长八位',
'required':'用户名不能为空'
},initial='我是初始值',required=False,
widget= widgets.TextInput(attrs={'class':'form-control others'})
)
password = forms.CharField(min_length=3,max_length=8,label='密码',error_messages={
'min_length':'密码最短三位',
'max_length':'密码最长八位',
'required':'密码不能为空'
},widget=widgets.PasswordInput())
#校验邮箱
email = forms.EmailField(label='邮箱',error_messages={
'required':'邮箱不能为空',
'invalid':'邮箱格式不正确' #(记住)
},required=False,widget=widgets.EmailInput(attrs={'class':'form-control'}))
2.在视图函数中使用MyFrom来校验数据
def formmm(request):
#实例化产生对象,将要校验的数据传入(可以传字典,也可以不传),这个数据是前端传递过来的
form_obj = Myforms()
if request.method == 'POST':
form_obj = Myforms(request.POST)
3.校验,is_valid如果是true表示校验成功,(表示满足所有条件),否则验证失败
if form_obj.is_valid():
#打印校验通过的数据
print(form_obj.cleaned_data) #{'username': 'chen', 'password': '111', 'email': '[email protected]'}
return HttpResponse('你上传的数据没问题')
else:
#校验失败的信息 是一个字典,它是所有错误信息 {错误字段名:[错误原因]}
print(form_obj.errors) # errors是一个字典,key:字段名,value:是一个列表,里面内容表示每个字段的错误信息
return HttpResponse('校验失败')
return render(request, 'formmm.html', locals())
组件的参数及其他操作方式
max_length #代表该字段最长可以为多少
min_length #代表该字段最短可以为多少
error_messages #设置错误信息的属性
required #默认值为True,意思是你前端传来的字段必须有它,没有的话校验失败,意思就是输入框必填,不能为空; 可改为False.就无需填写
label #注释信息label ='用户名' 在前端渲染可以直接对象.label获取值
initial #设置默认值
widget #控制标签属性和样式
widget = widgets.PasswordInput() #你在模板渲染的时候,就会渲染成Input框,password样式
控制标签属性
widget = widgets.PasswordInput(attrs={'class':'form-control c1 c2','username':'jason'})
#例子
pwd = forms.CharField(max_length=8,min_length=3,required=True,label='密码',
error_messages = {'max_length':'最长是8','min_length':'最短是3','required':'这个必须填'}
其他操作方式
# 单选的radio框
gender = forms.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框
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()
)
方法总结:
clean_data 验证通过的数据
errors 错误数据的对象
errors.as_data 错误数据的信息
注意事项
1.自定义的类中所有的字段默认都是必须要传值的
2.可以额外传入类中没有定义的字段名,forms组件不会去校验,也就是多传没有关系3.实例化时,传入必须是字典,也可以不传
form_obj = views.LoginForm({'username':'jason','password':'123456','email':'[email protected]'})
form_obj.is_valid()
True
form_obj = views.LoginForm({'username':'jason','password':'123456'})
form_obj.is_valid() # 少传就是 False
False
form_obj = views.LoginForm({'username':'jason','password':'123456','email':'[email protected]','hobby':'read'})
form_obj.is_valid() # 多传默认是没关系的
True
渲染页面、渲染错误信息
form组件即可在模板使用也可在视图层使用
模板层
1.forms组件只会帮你渲染获取用户输入(输入,选择,下拉框...)的标签 提交按钮需要你自己手动写
#第一种渲染方式(封装程度太高,一般只用于本地测试,通常不适用)
<form action='' method='post'>
{{ myform.as_p }}
{{ myform.as_ul }}
{{ myform.as_table}}
<input type='submit' value = '提交'></input>
</form>
#第二种渲染页面的方式(可扩展性高,书写麻烦)
<form action='' method='post'>
{{myform.name.label}}:{{myform:name}}
{{myform.pwd.label}}:{{myform:pwd}}
{{myform.email.label}}:{{myform:email}}
<input type='submit' value = '提交'></input>
</form>
#第三种渲染方式(推荐)
<form action='' method='post'>
{% for foo in myform %}
<p> {{ foo.lable }} : {{ foo }}
<span>{{ foo.errors.0 }}</span> //注意后面加0 错误信息
</p>
<input type='submit' value = '提交'></input>
</form>
视图层
def reg(request):
myform = MyForm()
if request.method == 'POST':
myform = MyForm(request.POST)
print(request.POST) #<QueryDict: {'name': ['小张'], 'pwd': ['123123'], 'email': ['[email protected]']}>
return render(request,'reg.html',locals()) #把myform对象传到前端页面了
注意事项:
1.forms组件在帮你渲染页面的时候 只会渲染获取用户输入的标签 提交按钮需要你手动添加
2.input框的label注释 不指定的情况下 默认用的类中字段的首字母大写
钩子函数(HOOK)
forms组件暴露给用户,可以自定义的校验规则 用法:在自定义的form类中定义一个函数即可
1.局部钩子(针对某一个字段做额外的校验)
名字叫:clean_字段名,内部,取出该字段进行校验,如果通过,将该字段返回,如果失败,抛异常
#函数名:clean_字段名字
def clean_name(self): #self是当前form对象
name = self.cleaned_data.get('name') #从验证通过的数据中再次筛选
if '666' in username:
#失败,抛异常,把异常信息以('key','value')形式写入add_error中
self.add_error('name','不能包含666')
#正常,把name返回到clean_data,将name写入clean_data字典中
return name
注意点:
- 这些函数是写在自定义类form里面的,需要校验哪个字段就以 clean_字段名写一个校验函数
- 校验失败,通过add_error('字段名','错误信息'),以{key:value}形式写入到errors字典中
- 校验成功,返回name到clean_data,写入clean_data字典中
2.全局钩子(针对多个字段做额外的校验)
#重写clean方法
def clean(self):
#能走到这步,说明前面的校验已经通过了,下面校验两次两次密码是否输入一致
pwd = self.clean_data.get('pwd')
re_pwd = self.clean_data.get('re_pwd')
if pwd == re_pwd:
#校验成功,直接返回clean_data
return self.clean_data
else:
#校验失败,把错误信息放入errors中
self.add_error('re_pwd','两次密码输入不一致')
或者
#抛出异常
raise ValidationError('两次密码不一致')
注意点:
- 校验失败,有两种写法,抛异常,将异常信息以{'all':[value,]}写入errors字典中
- 校验成功,返回clean_data字典
- 抛出异常类型为 ValidationError, from django.core.exceptions import ValidationError导入
钩子错误信息渲染注意点:
- 局部钩子抛出的异常会添加到该字段中的错误信息中,获取错误信息
前端: for循环对象 {{ foo.errors.0}}
- 全局钩子抛出的异常会添加到__all__中,获取错误信息:
后端: myform.errors.get('all')[0] 注意先判断myform.errors.get('all')是否存在
前端:{{ myform.errors.__all__.0}}
- 能走到这些函数,说明前面的form组件的那些校验都成功了,此时就可以从clean_data中取数据,因为此时clean_data中的数据全部符合,都找的到,而且clean_data是字典比较好取值。
完整的form组件校验
视图层
from django.shortcuts import render, HttpResponse, redirect
# forms组件数据校验的功能
# 第一步:先要继承Form
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
# 写一个类
class MyForm(forms.Form):
# 定义一个属性,可以用来校验字符串类型
# 限制最大长度是8,最小长度是3
name = forms.CharField(max_length=8, min_length=3, label='用户名'
error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'},
widget=widgets.TextInput(attrs={'class': 'form-control'}))
pwd = forms.CharField(max_length=8, min_length=3, required=True, label='密码',
error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'},
widget=widgets.PasswordInput())
re_pwd = forms.CharField(max_length=8, min_length=3, required=True, label='确认密码',
error_messages={'max_length': '最长是8', 'min_length': '最短是3', 'required': '这个必须填'},
widget=widgets.PasswordInput())
# 校验是否是邮箱格式
email = forms.EmailField(label='邮箱', error_messages={'required': '这个必须填', 'invalid': '不符合邮箱格式'})
# aa = forms.CharField(label='选择', error_messages={'required': '这个必须填', 'invalid': '不符合邮箱格式'},widget=widgets.CheckboxInput())
def clean_name(self):
# self:当前form对象
name = self.cleaned_data.get('name')
if name.startswith('sb'):
# 失败,抛异常
raise ValidationError('不能以傻逼开头')
# 正常,把name返回
return name
def clean(self):
pwd = self.cleaned_data.get('pwd')
re_pwd = self.cleaned_data.get('re_pwd')
if pwd == re_pwd:
return self.cleaned_data
else:
raise ValidationError('两次密码不一致')
def index_form(request):
# 生成对象时(实例化),需要传入要校验的数据(字典)
if request.method == 'GET':
myform = MyForm()
return render(request,'indxe2.html',locals())
elif request.method == 'POST':
myform = MyForm(request.POST)
if myform.is_valid():
# print(myform.cleaned_data) # 验证通过的数据
# models.User.objects.create(name='lqz',pwd='123',re_pwd='123)
myform.cleaned_data.pop('re_pwd')
models.User.objects.create(**myform.cleaned_data)
return redirect('http://www.baidu.com')
else:
all_error = myform.errors.get('__all__')
if all_error:
all_error = all_error[0]
# print(myform.errors.as_data)
return render(request, 'indxe3.html', locals())
模板层
<form action="" method="post" novalidate>
{% for foo in myform %}
<p>{{ foo.label }}:{{ foo }} <span>{{ foo.errors.0 }}</span></p>
{% endfor %}
<input type="submit" value="提交"><span>{{ all_error }}</span>
</form>
其它注意事项
如何展示错误信息
如何取消前端帮我们做的校验 form表单中添加一个参数即可:novalidate
<form action="" method="post" novalidate>
展示错误信息 用对象点errors.0
<form action="" method="post" novalidate>
{% for foo in form_obj %}
<p>
{{ foo.label }}:{{ foo }}
<span style="color: red">{{ foo.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit">
</form>
如何改变input框的type属性值
widget= widgets.TextInput()
widget=widgets.PasswordInput()
如何让forms组件渲染出来的input框有form-control类属性
widget= widgets.TextInput(attrs={'class':'form-control others'}) # 如果有多个类属性 空格隔开
widget=widgets.PasswordInput(attrs={'class':'form-control others'})
每个字段 还支持正则校验
from django import forms
from django.forms import Form
from django.core.validators import RegexValidator
class MyForm(Form):
user = forms.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)