瀑布流&分类筛选&分页
1. 瀑布流
瀑布流,又称瀑布流式布局。整版以图片为主,大小不一的图片按照一定的规律排列。
是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
1.1 models表结构
class Img(models.Model): # src = models.CharField(max_length=32, verbose_name='图片路径') src = models.FileField(max_length=32, verbose_name='图片路径', upload_to='static/upload') title = models.CharField(max_length=16, verbose_name='标题') summary = models.CharField(max_length=128, verbose_name='简介') class Meta: verbose_name_plural = '图片' def __str__(self): # 在admin中显示每个img对象对应的title,否则每个对象都会显示Img object return self.title
1.2 views视图函数
from django.http import JsonResponse def imgs(request): if request.method == 'GET': return render(request, 'img.html') elif request.method == 'POST': nid = request.POST.get('nid') img_list = models.Img.objects.filter(id__gt=nid).values('id', 'src', 'title') img_list = list(img_list) ret = { 'status': True, 'data': img_list, } # return HttpResponse(json.dumps(ret)) # 这样也行 return JsonResponse(ret)
1.3 img.html模板文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .w{ width: 1000px; margin: 0 auto; } .item{ width: 25%; float: left; } .item img{ width: 100%; } </style> </head> <body> <div>瀑布流</div> <div class="w" id="container"> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div class="item"></div> </div> <script src="/static/jquery-3.1.1.js"></script> <script> $(function () { var obj = new ScrollImg(); // 实例化一个ScrollImg对象, 专门负责瀑布流的执行 obj.initImg(); // 进行图片的获取和在页面的插入 obj.scrollEvent(); // 探测滑轮是否滑到底部(当前最后一张图片) }); function ScrollImg() { // nid和lastPosition这两个变量被模拟成了类似py类中的静态变量 this.nid = 0; // 保存最后一张图片的id号 this.lastPosition = 3; // 保存最后一张图片在div中的位置编号(0到3,一共四个div) this.initImg = function () { var that = this; // 这里的this指的就是这个实例化的对象(本身) ,// this = obj console.log(that.nid); $.ajax({ url: '/imgs.html', type: 'POST', data: {nid: that.nid, }, dataType: 'JSON', success:function (arg) { var img_list = arg.data; $.each(img_list, function (index, v) { var eqv = (index+that.lastPosition+1) % 4; var tag = document.createElement('img'); tag.src = v.src; $('#container').children().eq(eqv).append(tag); // 当循环到最后一个图片时,将图片的ID赋值给NID if(index+1==img_list.length){ that.nid = v.id; // that.nid = 0; that.lastPosition = eqv; } }) } }) }; this.scrollEvent = function () { var that = this; $(window).scroll(function () { // 什么时候到达最底部 // 文档高度 var docHeight = $(document).height(); // 窗口高度 var winHeight = $(window).height(); // 滚动条滑动高度 var scrollTop = $(window).scrollTop(); if(winHeight+scrollTop == docHeight){ // 一旦窗口的高度加上滑轮滑动的距离等于整个图片文档的高度,就再执行一次initImg that.initImg(); } }) } } </script> </body> </html>
1.4 效果展示
2. 分类筛选
2.1 实现的效果
- 在页面上点击的标签序号将作为URL的参数传递给后端进行处理
1)简单版的实现效果
2)略微复杂的多条件筛选
2.2 models表结构设计
1)设计四张表
- Direction:方向表,可以有自动化、测试、运维、前端等IT大方向
- Classification:分类表,可以有Python、Linux、JavaScript、C++等技术
- Level:等级表,给技术划分具体的级别,可以有初、中、高、骨灰等级别
- Video:视频表,通过以上等分类选择,筛选出具体的视频内容
2)表与表之间的关系
Direction表和Classification表之间是多对多的关系:某个方向可以对应多种技术,而某个技术也可以包含在多个方向中。
Video表中要定义两个外键,分别与Classification和Level表建立外键关联。
综上,Video表和Classification、Level建立了外键关联,Classification表和Direction表建立了多对多关联。
3)具体在models中的实现
class Direction(models.Model): """方向表:自动化,测试,运维,前端""" # weight = models.IntegerField(verbose_name="权重(按从大到小排列)", default=0) name = models.CharField(verbose_name='名称', max_length=32) classification = models.ManyToManyField('Classification') class Meta: db_table = 'Direction' # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 verbose_name_plural = '方向(视频方向)' # 设置admin中的表名 def __str__(self): return self.name class Classification(models.Model): """分类表:Python,Linux,JavaScript,OpenStack,Node.js""" # weight = models.IntegerField(verbose_name='权重(按从大到小排列)', default=0) name = models.CharField(verbose_name='名称', max_length=32) class Meta: db_table = 'Classification' verbose_name_plural = '分类(视频分类)' def __str__(self): return self.name class Level(models.Model): """级别表""" title = models.CharField(max_length=32) class Meta: verbose_name_plural = '难度级别' def __str__(self): return self.title class Video(models.Model): """视频表,保存视频的各种信息""" status_choice = ( (1, '下线'), (2, '上线'), ) # 视频上下线状态,默认是1, 也就是下线状态 status = models.IntegerField(verbose_name='状态', choices=status_choice, default=1) level = models.ForeignKey(Level, on_delete=models.CASCADE) # level = models.IntegerField(verbose_name='级别', choices=level_choice, default=1) classification = models.ForeignKey('Classification', null=True, blank=True, on_delete=models.CASCADE) weight = models.IntegerField(verbose_name='权重(按从大到小排列)', default=0) title = models.CharField(verbose_name='标题', max_length=32) summary = models.CharField(verbose_name='简介', max_length=32) # img = models.ImageField(verbose_name='图片', upload_to='./static/images/Video/') img = models.CharField(verbose_name='图片', max_length=32) href = models.CharField(verbose_name='视频地址', max_length=256) create_date = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'Video' verbose_name_plural = '视频' def __str__(self): return self.title
4)在admin中注册表结构
from django.contrib import admin from app01 import models admin.site.register(models.Direction) admin.site.register(models.Classification) admin.site.register(models.Level) admin.site.register(models.Video)
2.3 urls路由设置
- url中后缀的每个数字都分别对应于一个分类类型
from django.contrib import admin from django.urls import path from django.conf.urls import url from app01 import views urlpatterns = [ path('admin/', admin.site.urls), url(r'video-(?P<classification_id>(\d+))-(?P<level_id>(\d+))-(?P<status>(\d+)).html$', views.video), # 技术类型、等级、上下线状态 url(r'video2-(?P<direction_id>(\d+))-(?P<classification_id>(\d+))-(?P<level_id>(\d+)).html$', views.video2, name="video2"), # 方向、技术类型、等级 ]
2.4 views视图函数中的处理
1)简易版的实现
from django.shortcuts import render, HttpResponse from app01 import modelsdef video(request, *args, **kwargs): condition = {} # 构造一个查询字典,这里面将保存查询条件 for k, v in kwargs.items(): # 获取url后面的每个键值对参数的值,就是拿到每个数字 temp = int(v) # url中的字符类型,要转换为int类型 kwargs[k] = temp if temp: condition[k] = temp # 从数据库取出所有的类型、等级、状态 class_list = models.Classification.objects.all() level_list = models.Level.objects.all() status_list = list(map(lambda x: {'id': x[0], 'name': x[1]}, models.Video.status_choice)) # 视频的上下线状态保存的形式:[(1, '下线'), (2, '上线'), ],这里用map函数将其迭代改造成字典的形式 # 直接传入包含查询条件的字典,这个字典中保存的就是url中的参数键值对 video_list = models.Video.objects.filter(**condition) return render( request, 'video.html', { 'class_list': class_list, 'level_list': level_list, 'status_list': status_list, 'kwargs': kwargs, # 将url中的参数也传递给模板 'video_list': video_list, } )
2)略微复杂的多条件筛选
from django.shortcuts import render, HttpResponse from app01 import models def video2(request, *args, **kwargs): condition = {} # 构造一个查询字典 for k, v in kwargs.items(): # 将url传过来的键值对中的数字转换为int类型 temp = int(v) kwargs[k] = temp # 拿到url中对应各个键的值 direction_id = kwargs.get('direction_id') classification_id = kwargs.get('classification_id') level_id = kwargs.get('level_id') level_list = models.Level.objects.all() # 从数据库中拿到所有的方向,因为方向会一直在页面上显示 direction_list = models.Direction.objects.all() # 方向若选了0,也就是全部,则会显示所有的技术类型 if direction_id == 0: class_list = models.Classification.objects.all() # 进入技术类型判断的逻辑 # 如果是0显示全部,本来上上一层逻辑中就是显示全部技术类型,则可以忽略, # 如果显示指定的技术类型,则在condition中写入一个查询键值对 if classification_id == 0: pass else: condition['classification_id'] = classification_id # 若用户选择了某个方向(非0),则进入以下逻辑 else: direction_obj = models.Direction.objects.filter(id=direction_id).first() class_list = direction_obj.classification.all() # 拿到指定某个方向下的所有技术类型的id,以键值对的形式返回 vlist = direction_obj.classification.all().values_list('id') # 构造一个包含某个方向下的所有技术类型id值的列表,若该方向下无技术类型,则该技术类型列表留空 if not vlist: classification_id_list = [] else: classification_id_list = list(zip(*vlist))[0] # 若技术类型选择了0(全部),则构造一个包含in关键字的查询条件,查找所有符合条件的技术类型 if classification_id == 0: condition['classification_id__in'] = classification_id_list # 若选择了某技术类型,且当前选中的技术类型在某方向的技术类型列表中,则查询条件为查找指定技术类型 # 否则进入查询全部技术类型的逻辑(也就是选中了某个技术类型,而下一次选择的方向中不包含这个技术类型,) else: if classification_id in classification_id_list: condition['classification_id'] = classification_id else: # 例如: 指定方向[1,2,3] , 而分类为5时, 就走这里的条件 kwargs['classification_id'] = 0 condition['classification_id__in'] = classification_id_list # 等级的筛选,选中全部则忽略,否则就加一个查询条件 if level_id == 0: pass else: condition['level_id'] = level_id # 传入构造好的查询字典,到数据库进行筛选,得到包含全部符合条件video的列表 video_list = models.Video.objects.filter(**condition) return render( request, 'video2.html', { 'direction_list': direction_list, # 方向列表 'class_list': class_list, # 类型列表 'level_list': level_list, # 等级列表 'video_list': video_list, # 视频列表 'kwargs': kwargs, # 包含当前url传入参数的列表 } )
2.5 模板文件
1)简易版的video.html模板文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .condition a{ display: inline-block; padding: 5px 8px; border: 1px solid #dddddd; } .condition a.active{ background-color: coral; color: white; } </style> </head> <body> <div class="condition"> <h1>筛选</h1> {# 方向的筛选 #} <div> {# 如果url传入的classification的值等于0,就让改方向类型被选中 #} {% if kwargs.classification_id == 0 %} <a href="/video-0-{{ kwargs.level_id }}-{{ kwargs.status }}.html" class="active">全部</a> {% else %} <a href="/video-0-{{ kwargs.level_id }}-{{ kwargs.status }}.html">全部</a> {% endif %} {# 对技术类型进行循环取值,若取出项的值和url中的classification_id的值相等,则让该项被选中 #} {% for item in class_list %} {% if item.id == kwargs.classification_id %} <a href="/video-{{ item.id }}-{{ kwargs.level_id }}-{{ kwargs.status }}.html" class="active">{{ item.name }}</a> {% else %} <a href="/video-{{ item.id }}-{{ kwargs.level_id }}-{{ kwargs.status }}.html">{{ item.name }}</a> {% endif %} {% endfor %} </div> {# 技术类型的筛选 #} <div> {% if kwargs.level_id == 0 %} <a href="/video-{{ kwargs.classification_id }}-0-{{ kwargs.status }}.html" class="active">全部</a> {% else %} <a href="/video-{{ kwargs.classification_id }}-0-{{ kwargs.status }}.html">全部</a> {% endif %} {% for item in level_list %} {% if item.id == kwargs.level_id %} <a href="/video-{{ kwargs.classification_id }}-{{ item.id }}-{{ kwargs.status }}.html" class="active">{{ item.title }}</a> {% else %} <a href="/video-{{ kwargs.classification_id }}-{{ item.id }}-{{ kwargs.status }}.html">{{ item.title }}</a> {% endif %} {% endfor %} </div> {# 上下线状态的筛选 #} <div> {% if kwargs.status == 0 %} <a href="/video-{{ kwargs.classification_id }}-{{ kwargs.level_id }}-0.html" class="active">全部</a> {% else %} <a href="/video-{{ kwargs.classification_id }}-{{ kwargs.level_id }}-0.html">全部</a> {% endif %} {% for item in status_list %} {% if item.id == kwargs.status %} <a class="active" href="/video-{{ kwargs.classification_id }}-{{ kwargs.level_id }}-{{ item.id }}.html">{{ item.name }}</a> {% else %} <a href="/video-{{ kwargs.classification_id }}-{{ kwargs.level_id }}-{{ item.id }}.html">{{ item.name }}</a> {% endif %} {% endfor %} </div> </div> <div> <h1>结果</h1> <div> {% for row in video_list %} <div>{{ row.title }}</div> {% endfor %} </div> </div> </body> </html>
2)略微复杂多条件筛选的video2.html模板文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .condition a{ display: inline-block; padding: 5px 8px; border: 1px solid #dddddd; } .condition a.active{ background-color: coral; color: white; } </style> </head> <body> <div class="condition"> <h1>筛选</h1> {# 方向的筛选 #} <div> {% if kwargs.direction_id == 0 %} <a href="/video2-0-{{ kwargs.classification_id }}-{{ kwargs.level_id }}.html" class="active">全部</a> {% else %} <a href="/video2-0-{{ kwargs.classification_id }}-{{ kwargs.level_id }}.html">全部</a> {% endif %} {% for item in direction_list %} {% if item.id == kwargs.direction_id %} <a href="{% url "video2" direction_id=item.id classification_id=kwargs.classification_id level_id=kwargs.level_id %}" class="active">{{ item.name }}</a> {% else %} <a href="{% url "video2" direction_id=item.id classification_id=kwargs.classification_id level_id=kwargs.level_id %}">{{ item.name }}</a> {% endif %} {% endfor %} </div> {# 技术类型的筛选 #} <div> {% if kwargs.classification_id == 0 %} <a href="/video2-{{ kwargs.direction_id }}-0-{{ kwargs.level_id }}.html" class="active">全部</a> {% else %} <a href="/video2-{{ kwargs.direction_id }}-0-{{ kwargs.level_id }}.html">全部</a> {% endif %} {% for item in class_list %} {% if item.id == kwargs.classification_id %} <a href="/video2-{{ kwargs.direction_id }}-{{ item.id }}-{{ kwargs.level_id }}.html" class="active">{{ item.name }}</a> {% else %} <a href="/video2-{{ kwargs.direction_id }}-{{ item.id }}-{{ kwargs.level_id }}.html">{{ item.name }}</a> {% endif %} {% endfor %} </div> {# 等级的筛选 #} <div> {% if kwargs.level_id == 0 %} <a href="/video2-{{ kwargs.direction_id }}-{{ kwargs.classification_id }}-0.html" class="active">全部</a> {% else %} <a href="/video2-{{ kwargs.direction_id }}-{{ kwargs.classification_id }}-0.html">全部</a> {% endif %} {% for item in level_list %} {% if item.id == kwargs.level_id %} <a href="/video2-{{ kwargs.direction_id }}-{{ kwargs.classification_id }}-{{ item.id }}.html" class="active">{{ item.title }}</a> {% else %} <a href="/video2-{{ kwargs.direction_id }}-{{ kwargs.classification_id }}-{{ item.id }}.html">{{ item.title }}</a> {% endif %} {% endfor %} </div> </div> <div> <h1>结果</h1> <div> {% for row in video_list %} <div>{{ row.title }}</div> {% endfor %} </div> </div> </body> </html>
3. Django的内置分页
默认的Django内置分页只能实现 【上一页】【下一页】的效果,无法实现显示页码的功能。
实现的效果:
3.1 views.py
from django.shortcuts import render from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger L = [] for i in range(999): L.append(i) def index(request): current_page = request.GET.get('p') # 拿到当前页码 paginator = Paginator(L, 10) # per_page: 每页显示条目数量 # count: 数据总个数 # num_pages:总页数 # page_range:总页数的索引范围,如: (1,10),(1,200) # page: page对象 try: posts = paginator.page(current_page) # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # paginator paginator对象 except PageNotAnInteger: posts = paginator.page(1) except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request, 'index.html', {'posts': posts})
3.2 index.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <ul> {% for item in posts %} <li>{{ item }}</li> {% endfor %} </ul> <div class="pagination"> <span class="step-links"> {% if posts.has_previous %} <a href="?p={{ posts.previous_page_number }}">Previous</a> {% endif %} <span class="current"> Page {{ posts.number }} of {{ posts.paginator.num_pages }}. </span> {% if posts.has_next %} <a href="?p={{ posts.next_page_number }}">Next</a> {% endif %} </span> </div> </body> </html>
4. 扩展Django内置分页
将默认的Django内置分页扩展后,就能实现显示页码的效果了。
实现的效果:
4.1 views.py
from django.shortcuts import render from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger class CustomPaginator(Paginator): def __init__(self, current_page, max_pager_num, *args, **kwargs): self.current_page = int(current_page) self.max_pager_num = max_pager_num super(CustomPaginator, self).__init__(*args, **kwargs) def page_num_range(self): # 当前页面 # self.current_page # 总页数 # self.num_pages # 最多显示的页码个数 # self.max_pager_num print(1) if self.num_pages < self.max_pager_num: return range(1, self.num_pages + 1) print(2) part = int(self.max_pager_num / 2) if self.current_page - part < 1: return range(1, self.max_pager_num + 1) print(3) if self.current_page + part > self.num_pages: return range(self.num_pages + 1 - self.max_pager_num, self.num_pages + 1) print(4) return range(self.current_page - part, self.current_page + part + 1) L = [] for i in range(999): L.append(i) def index(request): current_page = request.GET.get('p') paginator = CustomPaginator(current_page, 11, L, 10) # per_page: 每页显示条目数量 # count: 数据总个数 # num_pages:总页数 # page_range:总页数的索引范围,如: (1,10),(1,200) # page: page对象 try: posts = paginator.page(current_page) # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # paginator paginator对象 except PageNotAnInteger: posts = paginator.page(1) except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request, 'index.html', {'posts': posts})
4.2 index.html
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <ul> {% for item in posts %} <li>{{ item }}</li> {% endfor %} </ul> <div class="pagination"> <span class="step-links"> {% if posts.has_previous %} <a href="?p={{ posts.previous_page_number }}">Previous</a> {% endif %} {% for i in posts.paginator.page_num_range %} <a href="?p={{ i }}">{{ i }}</a> {% endfor %} {% if posts.has_next %} <a href="?p={{ posts.next_page_number }}">Next</a> {% endif %} </span> </div> </body> </html>
5. 自定义分页
实现的效果:
5.1 自定义分页插件
- pager.py
class Paginator(object): def __init__(self, totalCount, currentPage, perPageItemNum=10, maxPageNum=11): """ totalCount:数据总个数 currentPage:当前页 perPageItemNum: 每页显示的行数 maxPageNum:最多显示的页码个数 """ self.total_count = totalCount try: v = int(currentPage) if v <= 0: v = 1 self.current_page = v except Exception as e: self.current_page = 1 self.per_page_item_num = perPageItemNum self.max_page_num = maxPageNum @property def start(self): """数据切片的起始位置""" return (self.current_page-1) * self.per_page_item_num @property def end(self): """数据切片的结束位置""" return self.current_page * self.per_page_item_num @property def num_pages(self): """总的页数""" a, b = divmod(self.total_count, self.per_page_item_num) if b == 0: return a return a + 1 def page_num_range(self): """页码范围""" part = int(self.max_page_num / 2) # 总的页数少于默认要显示的页码数 if self.num_pages < self.max_page_num: return range(1, self.num_pages + 1) # 当前页码处于第一页的前一半位置 if self.current_page - part < 1: return range(1, self.max_page_num + 1) # 当前页码处于最后一页的后一半位置 if self.current_page + part > self.num_pages: return range(self.num_pages - self.max_page_num + 1, self.num_pages + 1) return range(self.current_page - part, self.current_page + part + 1) def page_str(self): """生成所有的页码""" # 创建一个保存所有页码的容器 page_list = [] # 生成首页和上一页的页码 first = "<li><a href='?p=1'>首页</a></li>" page_list.append(first) if self.current_page == 1: the_prev = "<li><a href='#'>上一页</a></li>" else: the_prev = "<li><a href='?p=%s'>上一页</a></li>" % (self.current_page - 1, ) page_list.append(the_prev) # 生成中间所有页的页码 for i in self.page_num_range(): if i == self.current_page: temp = "<li class='active'><a href='?p=%s'>%s</a></li>" % (i, i, ) else: temp = "<li><a href='?p=%s'>%s</a></li>" % (i, i, ) page_list.append(temp) # 生成下一页和尾页的页码 if self.current_page == self.num_pages: the_next = "<li><a href='#'>下一页</a></li>" else: the_next = "<li><a href='?p=%s'>下一页</a></li>" % (self.current_page + 1, ) page_list.append(the_next) last = "<li><a href='?p=%s'>尾页</a></li>" % (self.num_pages, ) page_list.append(last) # 列表容器中的各个页码转换为字符串 result = ''.join(page_list) return result
5.2 在后台函数中调用自定义分页插件
- views.py
user_list = [] for i in range(1, 999): temp = {'username': 'root' + str(i), 'age': i, } user_list.append(temp) def index(request): # 获取当前页码号 current_page = int(request.GET.get('p')) # 导入自定义分页插件 from app01.pager import Paginator # 参数依次为: 数据总个数, 当前页码号, 每页显示的行数, 最多显示的页码个数 # 后两个参数默认为: 10, 11 obj = Paginator(999, current_page,) # obj对象中可调用的属性(方法): # start 当前页的起始条目索引 # end 当前页的结束条目索引 # page_str() 生成的所有页码结构和样式 data_list = user_list[obj.start:obj.end] return render(request, 'index.html', {'data': data_list, 'page_obj': obj, })
5.3 模板文件index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7-dist/css/bootstrap.css"> </head> <body> {# 数据内容 #} <ul> {% for row in data %} <li>{{ row.username }} -- {{ row.age }}</li> {% endfor %} </ul> {# 分页的内容 #} <ul class="pagination"> {{ page_obj.page_str|safe }} </ul> </body> </html>