自定义分页器和form组件

批量操作数据

浏览器访问一个django路由 立刻创建10万条数据并展示到前端页面
	create()、all()
涉及到大批量数据的创建 直接使用create可能会造成数据库崩溃
	批量数据创建>>>:bulk_create() 
	批量数据修改>>>:bulk_update()
def index(request):
    # for i in range(100000):
    #     models.Book.objects.create(title=f'第{i}本书')
    # book_query = models.Book.objects.all()
    book_list = []
    for i in range(100000):
        book_obj = models.Book(title=f'第{i}本书')
        book_list.append(book_obj)
    models.Book.objects.bulk_create(book_list)  # 批量创建
    models.Book.objects.bulk_update()     # 批量删除
    book_query = models.Book.objects.all()
    return render(request,'booList.html',locals())
	
'''上面的for循环可以用列表生成式写成一行'''
 [models.Book(title=f'第{i}本书')for i in range(100000)]

批量数据展示

'''分页器推导流程'''
当数据量比较大的时候 页面展示应该考虑分页
1.QuerySet切片操作 
book_query = models.Book.objects.all()[0:10] 切片操作顾头不顾尾
2.分页样式添加
3.页码展示
如何根据总数据和每页展示的数据得出总页码 divmod()
判断divmod的第二个参数是否有值
4.如何渲染出所有的页码标签
	前端模板语法不支持range 但是后端支持 我们可以在后端创建好html标签然后传递给html页面使用 |safe
5.如何限制住展示的页面标签个数
	页码推荐使用奇数位(对称美)  利用当前页前后固定位数来限制
6.首尾页码展示范围问题

image

# 代码展示
 # 计算总共的数据条数
    all_count = book_data.count()
    # 2.自定义每页展示的数据条数
    per_page_num = 10
    all_page_num, more = divmod(all_count, per_page_num)
    if more:
        all_page_num += 1
    # 1.获取前端想要展示的页码
    current_page = request.GET.get('page', 1)  # 获取用户指定的page 如果没有则默认展示第一页
    try:
        current_page = int(current_page)
    except ValueError:
        current_page = 1
    # 后端提前生成页码标签
    html_page = ''
    xxx = current_page
    if current_page < 6:
        xxx = 6
    for i in range(xxx - 5, xxx + 6):
        if current_page == i:
            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)
    # 3.定义出切片起始位置
    start_num = (current_page - 1) * per_page_num
    # 4.定义出切片终止位置
    end_num = current_page * per_page_num
    book_query = book_data[start_num:end_num]  # QuerySet [数据对象 数据对象]

自定义分页器思路
思路:
1.queryset支持切片操作、(切入点)
2.分页必需的四个参数
3.前端分页器样式展示
4.后端动态分页器相关标签
5.展示的分页页码不能太多 (页码如何处理成常见的类型)
6.首尾有临界值需要处理

自定义分页器

自定义分页器封装代码
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)
自定义分页器使用

后端:

def get_book(request):
   book_list = models.Book.objects.all()
   current_page = request.GET.get("page",1)
   all_count = book_list.count()
   page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10)
   page_queryset = book_list[page_obj.start:page_obj.end]
   return render(request,'booklist.html',locals())

前端

<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            {% for book in page_queryset %}
            <p>{{ book.title }}</p>
            {% endfor %}
            {{ page_obj.page_html|safe }}
        </div>
    </div>
</div>

form组件

# 编写用户登录功能并且校验用户信息
def ab_form(request):
    datadict = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'summer':
            datadict['username'] = '笨蛋!又记错名字啦!!'
        if password == '123':
            datadict['password'] = '密码不可以是123啦!'
    return render(request,'ab_form.html',locals())

前端代码:

  <form action="" method="post">
        <p>
            username: <input type="text" name="username">
            <span style="color: blueviolet">{{ datadict.username }}</span>
        </p>
        <p>
            password: <input type="password" name="password">
            <span style="color: brown">{{ datadict.password }}</span>
        </p>
        <input type="submit" value="提交">
    </form>
form组件主要功能
1.数据校验
  支持提前设置校验规则 之后自动校验
2.渲染页面
  支持直接渲染获取用户数据的各种标签  
3.展示信息
  针对不同的校验失败展示不同的提示信息
用form组件校验用户信息
1.form类的创建
from django import forms

class MyForm(forms.Form):
    name = forms.CharField(max_length=32, min_length=3)  # 最长32位 最短3位
    age = forms.IntegerField(max_value=150, min_value=0)  # 最大年龄150 最小年龄0
    email = forms.EmailField()  # 邮箱格式必须有@符

image

# form组件校验方法:
is_valid() 用于判断用户输入的值是否全部符合!如果是TURE 不是FALSE

cleaned_data 用于校验用户的输入的数据哪些是符合的

errors 用于校验用户输入的数据哪些是不符合条件的 还会返回不符合的原因

1.form类中的字段默认都是必须填的,少传的话is_valid()肯定会报错
2.如果多传了一些字段的数据的话不参与校验
渲染标签
# 渲染标签的三种方式
1.第一种方式  封装程度高 扩展性差
 {{ form_obj.as_p }} 
 {{ form_obj.as_table }}  
 {{ form_obj.as_ul }}  无序列表形式
    
2.第二种方式  
  可自定义lable参数 扩展性强 但是获取的字段多的时候不好用
    {{ form_obj.name.label }}
    {{ form_obj.name }}
    {{ form_obj.age.label }}
    {{ form_obj.age }}
    {{ form_obj.email.label }}
    {{ form_obj.email }}
	
3.第三种方式 兼容性最好 
   {% for form in form_obj %}
       {{ form.label }} {{ form }}
    {% endfor %}
    
'''form渲染只渲染获取用户输入的标签 提交按钮需要自己写'''
展示提示信息
# novalidate 取消前端自动校验提醒
前端代码:
<form action="" method="post" novalidate>
   {% for form in form_obj %}
       <p>{{ form.label }} {{ form }}</p>
        <span style="color: blue"> {{ form.errors }}</span>
    {% endfor %}
    <input type="submit" value="提交">

</form>
后端代码:
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)
        else:
            print(form_obj.errors)
        return render(request,'func.html',locals())
form组件重要字段参数
max_length、min_length
	max_value、min_value
	label				字段注释
	error_messages		       错误提示
	required			  是否为空
	widget				 标签类型、标签属性
	initial				  默认值
	validators			  正则校验
钩子函数
能够提供自定义校验
分为局部钩子和全局钩子
# 1.局部钩子 校验单个字段的

    def clean_name(self):
        name = self.cleaned_data.get('name')
        res = models.User.objects.filter().first()
        if res:
            return self.add_error('name','用户名已存在')
        return name


# 2.全局钩子 校验多个字段
     确认两次密码是否一致
    def clean(self):
        pwd = self.cleaned_data.get('password')
        confirm_pwd = self.cleaned_data.get('confirm_pwd')
        if not pwd == confirm_pwd:
            return self.add_error('confirm_pwd','两次密码不一致')
        return self.cleaned_data  # 固定写法


form组件的源码分析
以 is_vaild( ) 作为突破口, 查看 self.is_bound 和 not self.errors
重点分析 self.errors 是否有值, 有值就是 False
接着分析 errors 里面, 判断 _errors 是否都为空, 如果为空返回 full_clean(),否则返回_errors
查看full_clean(),里面存放_errors和cleaned_data这两个字典, 一个存错误字段, 一个存储正确字段
full_clean()里面还存放着forms组件的核心,比如self._clean_fields(): 用来校验字段
查看_clean_fields()方法发现内部循环每个字段及字段对象, 而对其真正进行校验的是field.clean(value)方法
并且可以知道分别将错误和正确的字段添加到_errors和cleaned_data这两个字典中
后面还设置了局部钩子函数的查找,如果找到则执行该钩子函数, 错误信息也会放入_errors中
字段和局部钩子校验完毕, 最后校验全局钩子


modelform组件

# modelform是form组件的优化版 使用更简单 功能更强大

from django import form

class MyModelForm(forms.ModelForm):
    class Mata:
        model = app01.User  # 想对哪张表做校验
        fields = 'all'   # 想对表的哪个字段做校验
     def clean_name(self):  #局部钩子
        pass
     def clean(self):  # 全局钩子
            pass
        
 obj = MyModelForm(request.POST)  #instance参数
 obj.save()

image

posted @ 2022-09-08 23:07  Hsummer  阅读(20)  评论(0编辑  收藏  举报