Django杂篇(1)

这里我们介绍以下Django常用的一些小工具,分别是:

  1. bulk_create,一种将数据批量插入数据库的方法,效率较高
  2. Pagination,自定义的一种分页器,重点在于其思路和使用
  3. 多对多表关系的创建方法,常见的有三种
  4. form校验组件的应用,其主要作用就是按照我们的要求校验数据的格式,并取到符合条件以及不符合条件的数据和报错

bulk_create

其实批量插入数据的原理非常简单,在日常来看,我们在向数据库插入数据的时候,通常都是一条一条的插入,如果有相似数据,我们通常会用一个循环,来持续插入,实际上这种方法如果在数据量比较大的情况下会非常耗时,所以我们才会引入这种插入方式,下面就用一个非常简单的例子来做一下对比,首先我们创建一个Django项目,命名第一个应用名为app01.

# 我们在models里面创建一个最简单的书籍表
# app01/models.py
class Book(models.Model):
title=models.CharField(max_length=32)
# 然后在Terminal窗口,输入python manage.py makemigrations和python manage.py migrate之后就成功建立了表,然后在urls.py里面加路由 # urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
] # 然后在views.py里面写对应路由的函数,以下这种就是效率最低,也是最常用的插入方式,1000条数据甚至要用两分钟左右,而且对数据库的压力特别大,因为每次写入都要操作数据库
# views.py,常规插入方式
def index(request):
# 往书籍表中插入数据 1000
for i in range(1000): # 这种插入方式 效率极低
models.Book.objects.create(title='第%s本书'%i)
book_queryset = models.Book.objects.all()
return render(request,'index.html',locals()) # index.html
<body>
{% for book_obj in book_queryset %}
<p>{{ book_obj.title }}</p>
{% endfor %}
</body>

换用bulk_create的话,views.py里面就要按以下这种方式写:

# views.py,bulk_create批量插入方式
def index(request):
book_list = []
for i in range(100000):
book_list.append(models.Book(title='第%s本书'%i))
models.Book.objects.bulk_create(book_list) # 批量插入数据
book_queryset = models.Book.objects.all()
return render(request,'index.html',locals())

我们可以很明确的发现,用批量插入十万条数据仅仅用了不到二十秒,差距非常明显.其实原理非常简单

  • 无非就是平时是一条一条插入,用了bulk_create之后我们是把所有需要插入的数据先插入一个列表中,这个操作是在计算机内部完成的,耗时非常低,然后通过bulk_create把这个列表插入到数据库中,从而极大的提升了效率

Pagination

首先我们要知道分页器的概念,因为通常情况下一个网站其页面大小是有限的,不能展示其全部的信息,所以需要分页器来吧这些内容分成不同的页面,以便于我们浏览,那么我们首先用自己的思路来实现一个简单的分页器,我们继续上面的项目写,把插入数据的那几行注释掉就好

# views.py
# 自定义分页器
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)
if more:
page_num += 1
# page_num就决定了需要多少个页码
page_html = ''
xxx = current_page # 这里取出来当前选中的页码赋给xxx,以便于后面判断当前页面
if current_page < 6: # 如果当前页面页码小于6,就不能再往前数五个页面,所以要为其重置为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>' # 这里可以设置当前页面的页码为高亮的形式
else:
page_html+='<li><a href="?page=%s">%s</a></li>'
book_queryset = models.Book.objects.all()[start_page:end_page]
return render(request, 'index.html', locals()) # 然后,在index.html里面,我们如下写,这里分页的格式是来自于bootstrap,我们修改后即可使用,
<body>
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{{ page_html|safe }}{#这里就是我们所写的最关键的一句,|safe的意思是取消转义,即后端传过来的符合前端标签要求的语句可以被表现出其应有的形式,而不只是字符串形式#}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</body>

以上就是我们手动实现的一个简略的分页器,当然还有好多功能没有完善,不过我们了解分页器的思路即可,因为实际生产中我们并不需要手动去写分页器,这里有一个较完整的现成的分页器代码,我们将其记录下来,会调用即可

# 在app01下面新建一个utils文件夹,然后新建一个mypage.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)

下面我们介绍怎样去调用这个已经存在定义好的分页器

# urls.py,写对应路由
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^login/', views.login),
] # views.py
from django.shortcuts import render,HttpResponse,redirect
from app01 import models
from app01.utils.mypage import Pagination # 这里导入刚才粘贴的已经定义好的分页器类
# 使用封装好的分页器代码
def login(request):
book_queryset = models.Book.objects.all() # 这里取到表内所有的数据,生成一个queryset对象
current_page = request.GET.get('page',1) # 从前端发来的get请求里面取出用户点击的page页面,如果没有默认就是1,也就是首页
all_count = book_queryset.count() #这里对queryset对象进行计数,得到一共多少条记录
# 1.实例化产生对象
page_obj = Pagination(current_page=current_page,all_count=all_count)# 我们只需要把记录的总数量和当前页面的page数传进函数里面即可
# 2.对真实数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
return render(request,'login.html',locals()) # login.html,前端的调用也非常简单
<body>
{% for book_obj in page_queryset %} {#这里把后端传来的queryset对象直接循环,显示出来,即可以看到第*本书#}
<p>{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }} {#这里是显示分页器的核心操作,加|safe是取消转义#}
</body>

创建多对多表关系的常用方法

  1. 第一种

    就是我们之前在建表的时候用到的,利用models.ManyToManyField方法来建立表和表之间的多对多关系,Django会自动生成第三张表.

    但是这种方法的弊端在于,第三张表只有主键和两张表的关系字段,且我们不能额外对其添加字段,所以其可扩展性较差,不利于项目后期的维护与开发.实例如下:

    class Book(models.Model):
    ...
    author=models.ManyToManyField(to='Author')
    class Author(models.Model):
    ...
  2. 第二种

    纯手动创建,即我们不依赖于ORM自动帮我们创建表关系,而是自己手动创建,这种方法所建立的表可扩展性好,可以添加很多我们想要额外增加的功能,比如表数据创建的时间等.但是弊端在于很多ORM的查询方法不能用,查询的效率会比较低,而且代码较复杂,开发效率也比较低,实例如下:

    class Book(models.Model):
    ... class Author(models.Models):
    ... class Book2Author(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
    create_time = models.DateField(auto_now_add=True)
    ...
  3. 第三种

    半自动的创建,即多对多的关系表还是我们自己手动创建,但是我们会告诉Django的ORM关系表的名字和位置,这样我们既保留了ORM查询的便利性,又保留了第三张关系表的可扩展性,可谓一举两得,但是唯一的缺点可能就是需要写的代码比较多,不过也无可厚非.

    半自动的创建表关系所需要额外加的两个参数是through='关系表的名字',through_fields=('表名(外键所在的表)','表名(外键不在的那个表)'),注意表名的顺序,不能反

    class Book(models.Model):
    ...
    authors = models.ManyToManyField(to='Author', through='Book_Author', through_fields=('book','author')) class Author(models.Model):
    ...
    books = models.ManyToManyField(to='Book', through='Book2Author', through_fields=('author', 'book')) class Book_Author(models.Model):
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to='Author')
    ...

form校验组件的应用

我们在写一些大大小小的项目的时候,多多少少都会用到校验的功能,比如用户注册账号的时候,验证用户名是否存在,用户密码两次输入是否相同,都需要用到校验.不管是前端还是后端,都有必要加上数据校验的功能,一方面是为了数据库的安全,另外一方面也可以阻止一些非法数据的流入.不过相比之下,后端的校验比前端校验更有必要,因为前端页面是写给大众看的,很多东西都是公开的,可以直接修改,对数据的校验有很大的阻力,所以我们就更要重视在后端对数据的校验.

那么form组件就可以帮助我们来完成数据的校验工作,当然,他不止能完成数据的校验,还可以完成页面的渲染和错误信息的展示,可谓十分强大.

在对form进行说明之前,我们首先要在views.py里面建立一个类,以便于后面的调用和测试

# views.py
from django import forms
class MyRegForm(forms.Form):
sername = forms.CharField(min_length=3,max_length=8)
password = forms.CharField(min_length=3,max_length=8)
email = forms.EmailField()
# 是不是觉得以上定义的类有些眼熟?确实,跟在models.py里面定义表结构十分相似

渲染页面

渲染页面的实际意义是我们在后端用form校验过数据之后把数据发送到前端,然后前端通过一些形式来展示出来,forms组件会自动帮我们渲染用户输入(或者是选择,下拉框等)的标签,但是提交按钮以及一些别的数据并不会自动帮我们渲染,还是需要我们手动去设置.forms常用的三种渲染页面的方式如下:

# formm.html
<body>
<p>
第一种渲染前端页面的方式:封装程度非常高,但是标签样式及参数不方便调整,可扩展性较差
{{ form_obj.as_p }}
{{ form_obj.as_ul }}
</p> <p>第二种渲染页面的方式:扩展性较高,需要我们手写的代码量比较多</p>
<p>
{{ form_obj.username.label }}{{ form_obj.username }}{#.lable可以看到其标签,.username则是显示出其真实的内容,这里是一个input输入框#}
</p>
<p>
{{ form_obj.password.label }}{{ form_obj.password }}
</p>
<p>
{{ form_obj.email.label }}{{ form_obj.email }}
</p> <p>第三种渲染前端页面的方式:代码量和扩展性都很高(推荐使用)</p>
<form action="" method="post" novalidate> {#这里添加novalidate参数可以取消前端帮我们做校验,以便于我们只在后端做校验#}
{% for foo in form_obj %}
<p>
{{ foo.label }}:{{ foo }}
<span style="color: red">{{ foo.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit">
</form>
</body>

展示错误信息

在forms组件里面展示错误信息非常简单,只需要用对象点errors.0就可以了,如下:

#formm.html
<body>
<form action="" method="post" novalidate>
{% for foo in form_obj %}
<p>
{{ foo.label }}:{{ foo }}
<span style="color: red">{{ foo.errors.0 }}</span> {#这里就是真正展示出错误的地方,即对象foo.errors.0 #}
</p>
{% endfor %}
<input type="submit">
</form>
</body>

当然以上的报错都是英文显示的,我们可以手动重写来实现报错用中文来显示,具体就是在之前我们在views.py里定义的类MyRegForm,在里面加参数error_message={}即可,内部我们可以以报错类型来重写报错内容,直接在冒号后面写即可,

from django import forms
class MyRegForm(forms.Form):
sername = forms.CharField(min_length=3,max_length=8,label='用户名',
error_messages={
'min_length':'用户名最短三位', # 这里用户名小于三位的话就会报这个错,报错内容被我们重写之后就会这样报错,以下同理
'max_length':'用户名最长八位',
'required':'用户名不能为空'
},initial='我是初始值',required=False,# required赋值false的话该项不填也不会报错,即允许不填,实际运用就是可以用在非必填项
widget= widgets.TextInput(attrs={'class':'form-control others'}))# widget可以改变该框的type属性
password = forms.CharField(min_length=3,max_length=8)
email = forms.EmailField()

校验数据

手动校验(is_valid,cleaned_data,errors)

手动校验即我们手动调用几个函数来观察数据是否通过校验,比如is_valid,cleaned_data和errors,测试代码如下:

# 这里我们测试的时候在左下角的Python Console里面测试,可以实时看到结果,比较方便
from app01 import views #1. 给自定义的类定义一个字典
obj = views.MyRegForm({'username':'jason','password':'123','email':'12'}) #2. is_vaild()数据全部符校验标准合才会返回True,但凡有不符合标准的都会返回False
obj.is_valid()
Out[5]: False #3. cleaned_data可以查看查看所有符合条件的数据
obj.cleaned_data
Out[9]: {'username': 'jason', 'password': '123'} #4. errors可以查看不符合条件的数据以及报错的原因
obj.errors
Out[10]: {'email': ['Enter a valid email address.']} #5. 校验数据的时候,默认情况下类里面所有的字段都必须传值
obj = views.MyRegForm({'username':'jason','password':'123'})
obj.is_valid()
Out[12]: False
obj.errors
Out[13]: {'email': ['This field is required.']} #6. 默认情况下可以多传,多传后面的数据会舍弃,但是绝对不能少传,少传就会不符合校验标准,出现False
obj=views.MyRegForm({'username':'jason','password':'1233','email':'123@qq.com','xxx':'ooo'})
obj.is_valid()
Out[15]: True

钩子函数的校验

对于数据的字段来说,定义的时候限制其格式是一方面,另外一方面我们可以用钩子函数来对其做额外的校验,这种方式在对于大项目的后期修改,维护和二次开发上有较大的应用.

局部钩子

当我们需要对数据库的表中的某一个字段的数据进行额外的校验的时候,局部钩子就是非常好的选择,示例如下:

class MyRegForm(forms.Form):
def clean_username(self):# 局部钩子要写在MyRegForm总类的下面
username = self.cleaned_data.get('username')
if '黄' in username:# 检测所有的用户姓名,带'黄'字的不符合标准,加入报错信息
self.add_error('username','这本书的名字不符合标准')
return username # 最后要返回我们校验的字段的数据,不然会看不到最后结果
全局钩子

上面局部钩子是针对单个字段进行二次校验,所以全局钩子就是可以根据多个字段进行校验,比如,用户注册的时候我们用来校验其输入的两次密码是否一致,示例如下:

class MyRegForm(forms.Form):
def clean(self):
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

正则校验

我们还有第三种校验数据的方式,即正则校验.

其实正则校验也是在我们定义类的时候就定义好的,与前面手动校验的前提比较相似

# views.py 这里是对于159开头的手机号的校验,RegexValidator即为正则校验的关键字
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开头')],
)

常用字段

以下为几个常用字段,需要使用时直接复制即可.

# 单选框select
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=widgets.RadioSelect()
) # 多选框select
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=widgets.SelectMultiple()
) # 单选heckbox
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()
)

Django杂篇(1)的更多相关文章

  1. Django杂篇(2)

    目录 Django杂篇(2) cookie与session cookie session django中间件 自定义中间件 跨站请求伪造(csrf) CSRF的解决方案 Django杂篇(2) 本文主 ...

  2. 异步任务队列Celery在Django中的使用

    前段时间在Django Web平台开发中,碰到一些请求执行的任务时间较长(几分钟),为了加快用户的响应时间,因此决定采用异步任务的方式在后台执行这些任务.在同事的指引下接触了Celery这个异步任务队 ...

  3. 《Django By Example》第四章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:祝大家新年快乐,这次带来<D ...

  4. django server之间通过remote user 相互调用

    首先,场景是这样的:存在两个django web应用,并且两个应用存在一定的联系.某些情况下彼此需要获取对方的数据. 但是我们的应用肯经都会有对应的鉴权机制.不会让人家随随便便就访问的对吧.好比上车要 ...

  5. Mysql事务探索及其在Django中的实践(二)

    继上一篇<Mysql事务探索及其在Django中的实践(一)>交代完问题的背景和Mysql事务基础后,这一篇主要想介绍一下事务在Django中的使用以及实际应用给我们带来的效率提升. 首先 ...

  6. Mysql事务探索及其在Django中的实践(一)

    前言 很早就有想开始写博客的想法,一方面是对自己近期所学知识的一些总结.沉淀,方便以后对过去的知识进行梳理.追溯,一方面也希望能通过博客来认识更多相同技术圈的朋友.所幸近期通过了博客园的申请,那么今天 ...

  7. 《Django By Example》第三章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:第三章滚烫出炉,大家请不要吐槽文中 ...

  8. 《Django By Example》第二章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:翻译完第一章后,发现翻译第二章的速 ...

  9. 《Django By Example》第一章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:本人目前在杭州某家互联网公司工作, ...

随机推荐

  1. JavaScript ---- 闭包(什么是闭包,为什么使用闭包,闭包的作用)

    经常被问到什么是闭包? 说实话闭包这个概念很难解释.JavaScript权威指南里有这么一段话:“JavaScript函数是将要执行的代码以及执行这些代码作用域构成的一个综合体.在计算机学术语里,这种 ...

  2. Change myself to be better

    发现和改变自己不好的习惯 遇到问题的反应 自己遇到问题的时候,特别是不熟悉的问题的时候就会有点焦虑,这个应该是每个人都会有的,遇到自己不熟悉的或者是没有经历过的东西都会 感觉不舒服,或者是遇到难题或者 ...

  3. NX二次开发-UFUN输入对象tag获得part名字UF_OBJ_ask_owning_part

    NX11+VS2013 #include <uf.h> #include <uf_modl.h> #include <uf_part.h> #include < ...

  4. js Date.parse() format.

    date format android chrome linux chrome Mobile safari ios chrome windows safari linux firefox window ...

  5. python入门 集合(四)

    集合 集合是一个无序的不重复元素序列,可以迭代,也可以修改.集合迭代的时候元素是随机的. 集合通常用来 membership testing, 去重, 也可以用来求交集并集补集. 介绍一下如何创建集合 ...

  6. Rootkit之SSDT hook(通过CR0)

    CR0当中有一个写保护位,是保护内存不可写属性的,为了能够写入内核,只能把它的保护给咔嚓掉了,不过--如果做完了手脚但不还原写保护属性的话,极有可能会BOSD. /================== ...

  7. ionic-CSS:ionic Range

    ylbtech-ionic-CSS:ionic Range 1.返回顶部 1. ionic Range ionic Range 是一个滑块控件,ionic 为 Range 提供了很多种默认的样式.而且 ...

  8. ParameterizedThreadStart task

    using System;using System.Diagnostics;using System.Threading;using System.Threading.Tasks; namespace ...

  9. CodeForces 1166D Cute Sequences

    题目链接:http://codeforces.com/problemset/problem/1166/D 题目大意 给定序列的第一个元素 a 和最后一个元素 b 还有一个限制 m,请构造一个序列,序列 ...

  10. Git 学习(一)安装 Git

    这里写自定义目录标题 这一章介绍怎么安装 Git 大家都是开发老司机,就不简介什么是 Git 了,直接开花. 在 Linux 上安装Git 在 Windows 上安装 Git 初次使用 Git 前的配 ...