Django分页器与forms组件
作者:互联网
一、数量批量操作
# 现在我们需要往数据库中插入100000条数据 class index(request): for i in range(100000): models.Book.objects.create(title=f'第{i}本书') '''但是这样只直接插入的话数据库会很慢还有可能会直接蹦掉 我们可以优化''' class index(request): book_list = [] for i in range(100000): book_obj = models.Book(title=f'第{i}本书') book_list.append(book_obj) # 循环将书籍对象添加到列表中 '''上述四行可以简写为一行>>>:列表生成式(列表表达式) book_list = [models.Book(title=f'第{i}本书')] ''' models.Book.objects.bulk_create(book_list) # 批量添加到数据库中 book_query = models.Book.objects.all() # 将数据提取出来展示到页面上 return render(request,'bookList.html',locals()) # 涉及到大批量数据的创建 直接使用create可能会造成数据库崩溃 批量数据创建>>>:bulk_create() 批量数据修改>>>:bulk_update()
二、分页器推导流程
def index(request): 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) # 批量创建数据 book_data = models.Book.objects.all() # 计算总共的数据条数 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 # 后端提前生成页码标签 因为前端for循环没有range 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 [数据对象 数据对象] return render(request, 'bookList.html', locals()) # 前端: <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> """ per_page_num = 10 current_page start_num end_num 1 0 10 2 10 20 3 20 30 per_page_num = 5 current_page start_num end_num 1 0 5 2 5 10 3 10 15 得出规律: start_num = (current_page - 1) * per_page_num end_num = current_page * per_page_num 总数据 每页展示 总页码 100 10 10 99 10 10 101 10 11 """ # django自带一个分页器组件 但是不太好用 我们自己也写了一个
三、自定义分页器
首先在应用下创建一个空文件夹plugins 然后创建一个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: 最多显示的页码个数 """ 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)
1.使用
# 后端: 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()) # 前端: {% for book_obj in page_query %} <p class="text-center">{{ book_obj.title }}</p> {% endfor %} {{ page_obj.page_html|safe }}
四、forms前戏
# 我们可以编写一个用户登入功能并且校验数据返回提示信息(form表单) def ab_form(request): data_dict = {'username': '', 'password': ''} # 如果没有报错值就是空字符串 if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if username == 'jason': data_dict['username'] = 'jason是你能随便用的吗' # 报错就返回该文本 if password == '123': data_dict['password'] = '密码就设123???' return render(request, 'ab_form.html', locals()) # 前端 <form action="" method="post"> <p>username: <input type="text" name="username"> <span style="color: red">{{ data_dict.username }} # 提示信息 </span> </p> <p>password: <input type="text" name="password"> <span style="color: red">{{ data_dict.password }}</span> </p> <input type="submit"> </form> # 但是如果字段名有很多的话这样会有点麻烦
五、forms组件
'''form组件 1.数据校验 支持提前设置各种校验规则 之后自动校验 2.渲染页面 支持直接渲染获取用户数据的各种标签 3.展示信息 支持针对不同的校验失败展示不同的提示''' # form类型创建 from django import forms class MyForm(forms.Form): name = forms.CharField(max_length=8, min_length=3) # 用户名最长八个字符 最短三个字符 age = forms.IntegerField(max_value=150, min_value=0) # 年龄最小0岁 最大150岁 email = forms.EmailField() # 邮箱必须符合邮箱格式(至少有个@符号) # 测试环境: 1.数据校验功能 from app01 import views 1.1.传递待校验的数据 form_obj = views.MyForm({'name':'jason','age':18,'email':123}) 1.2.判断所有的数据是否符合校验 form_obj.is_valid() 1.3.获取符合校验规则的数据 form_obj.cleaned_data {'name': 'jason', 'age': 18} 1.4.查阅不符合校验规则的数据及错误原因 form_obj.errors {'email': ['Enter a valid email address.']} """ 1.form类中编写的字段默认都是必填的 少传则肯定通不过校验 is_valid 2.校验如果多传了一些字段 则不参与校验 全程忽略 """
1.使用form组件
def func(request): form_obj = MyForm() # 产生一个form对象 if request.method == 'POST': form_obj = MyForm(request.POST) # 将用户输入的数据实例化到forms对象 if form_obj.is_valid(): # 判断用户输入是否正确 print(form_obj.cleaned_data) # 查看正确的数据 return render(request,'func.html',locals()) # 2.渲染标签功能 2.1.方式1(封装程度高 扩展性差) {{ form_obj.as_p }} {{ form_obj.as_table }} {{ form_obj.as_ul }} 2.2.方式2(封装程度低 扩展性好 编写困难) {{ form_obj.name.lable }} {{ form_obj.name }} 2.3.方式3(推荐使用) {% for form in form_obj %} <p>{{ form.label }}{{ form }}</p> {% endfor %} """类中以外的所有标签都不会自动渲染 需要自己编写""" # 3.展示提示信息 <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>
补充:
- forms类中所有的字段数据默认都是必填的,可以添加required=False字段就不必填写
- forms类中额外传入的字段数据不会做任何的校验
- forms组件只负责渲染获取用户数据的标签, form表单标签和提交按钮需要自己写
- 宣染标签中文提示可以使用参数 label指定,不指定默认英文提醒
- forms 类中填写的校验性参数前端浏览器会识别并添加校验操作,但是前端的校验是可有可无的后端必须要再次校验
- form表单可以取消浏览器自动添加校验功能的操作novalidate
- 错误提示信息可以使用参数error_messages修改 不使用默认是英文错误提示
2.form组件重要参数
更多知识点:https://www.cnblogs.com/Dominic-Ji/p/9240365.html
六、钩子函数
1.局部钩子
# 前面的字段都只是第一层检验 都没有链接到数据库校验 那么现在我们在注册功能的时候想要判断用户名是否注册过怎么写? # 可以使用钩子函数 钩子函数也是在form类中编写 from django import forms class MyForm(forms.Form): # 二层校验 def clean_name(self): # 名字是固定的 想要校验那个字段就这样写 clean_xxx name = self.cleaned_data.get('name') # 因为只有结果之前字段的校验才能走到这个函数判断所以肯定有值 res = models.User.objects.filter(name=name).first() # 将拿出来的姓名与数据库中判断 if res: return self.add_error('name','用户名已存在') return name # 钩子函数要求钩出来的数据还要返回回去
2.全局钩子
# 现在我们想要用户输入两次密码并校验两次密码是否一致 from django import forms class MyForm(forms.Form): # 二次校验 def clean(self): pwd = self.cleaned_data.get('pwd') # 使用cleaned_data取值 因为前面的字段已经校验成功了才会来到二次校验 所以肯定有值 confirm_pwd = self.cleaned_data.get('confirm_pwd') if not pwd == confirm_pwd: return self.add_error('confirm_pwd','两次密码不一致') return self.cleaned_data # 不过局部钩子还是全局钩子都需要把取出来的值 返回回去
七、modelform组件
'''我们在编写form组件的时候需要按照模型层中的字段一样编写一样的字段 这样会很浪费效率 这个时候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).first() if res: self.add_error('name','用户名已存在') return name
1.class Meta下常用字段
model = models.Book # 对应的Model中的类 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 帮助提示信息 widgets = None # 自定义插件 error_messages = None # 自定义错误信息
八、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) return render(request, 'func.html', locals()) # 我们可以点击is_valid开始查看 def is_valid(self): return self.is_bound and not self.errors # 我们可以看到返回的是一个self.is_bound and not self.errors # 两者必须是True才能返回我们先看self.is_bound
点击self.is_bound可以发现只要我们传参那么就肯定是True 那么我们查看self.errors
@property def errors(self): if self._errors is None: self.full_clean() return self._errors # 可以看到errors伪装成了属性 然后我们点self.errors它默认就是None 所以肯定会走if分支 那么我们就可以看full_clean # 点击full_clean我们可以知道 它调用了三个方法 self.cleaned_data = {} # 并创建了一个空字典 self._clean_fields() # 局部钩子 self._clean_form() # 全局钩子 self._post_clean() # 内部为pass # 这三个方法就是form是组件最主要的核心功能了
1.self._clean_fields()
# 点击self._clean_fields() # 我们从变量名就可以知道这是不能被我们自己调用的 因为被作者隐藏起来了 只能他自己内部调用 for name, bf in self._bound_items(): field = bf.field field # 拿到了所有的字段对象 value = bf.initial if field.disabled else bf.data # 就是如果字段禁用使用默认值因为我们的字段没有禁用所以点击data查看 返回的是一个 self.form._widget_data_value(self.field.widget, self.html_name) # 其实就是拿该字段 用户输入的值
继续往下看
for name, bf in self._bound_items(): # name就是字段名称 field = bf.field field # 是拿到了所有的字段对象 value = bf.initial if field.disabled else bf.data # try: if isinstance(field, FileField): # 就是判断字段是不是文件字段 value = field.clean(value, bf.initial) # 点击bf.initial其实就是那用另一种取值并校验 else: value = field.clean(value) # 拿用户输入的值判断 self.cleaned_data[name] = value # 往上面的空字典添加键值对 name就是我们写的字段名 if hasattr(self, "clean_%s" % name): # 判断我们有没有写局部钩子 (self, clean_字段名) value = getattr(self, "clean_%s" % name)() # 然后获取局部钩子加括号调用并赋值给value self.cleaned_data[name] = value # 然后将局部钩子执行的值在添加到字典中 所以我们在编写钩子函数的时候需要返回 except ValidationError as e: self.add_error(name, e) # 将报错信息添加字段后面
2.self._clean_form()
def _clean_form(self): try: cleaned_data = self.clean() # clean() 就是全局钩子 加括号执行并赋值 except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data # 其实就是为了执行全局钩子
3.self._post_clean()
# 里面就只有pass 就是留了一个接口
标签:current,obj,name,form,self,Django,forms,组件,page 来源: https://www.cnblogs.com/stephenwzh/p/16670836.html