Django杂篇(1)

Django杂篇(1)

这里咱们介绍如下Django经常使用的一些小工具,分别是:前端

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

bulk_create

其实批量插入数据的原理很是简单,在平常来看,咱们在向数据库插入数据的时候,一般都是一条一条的插入,若是有类似数据,咱们一般会用一个循环,来持续插入,实际上这种方法若是在数据量比较大的状况下会很是耗时,因此咱们才会引入这种插入方式,下面就用一个很是简单的例子来作一下对比,首先咱们建立一个Django项目,命名第一个应用名为app01.python

# 咱们在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())

咱们能够很明确的发现,用批量插入十万条数据仅仅用了不到二十秒,差距很是明显.其实原理很是简单django

  • 无非就是平时是一条一条插入,用了bulk_create以后咱们是把全部须要插入的数据先插入一个列表中,这个操做是在计算机内部完成的,耗时很是低,而后经过bulk_create把这个列表插入到数据库中,从而极大的提高了效率

Pagination

首先咱们要知道分页器的概念,由于一般状况下一个网站其页面大小是有限的,不能展现其所有的信息,因此须要分页器来吧这些内容分红不一样的页面,以便于咱们浏览,那么咱们首先用本身的思路来实现一个简单的分页器,咱们继续上面的项目写,把插入数据的那几行注释掉就好bootstrap

# 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. 第一种app

    就是咱们以前在建表的时候用到的,利用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()
)
相关文章
相关标签/搜索