关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
近来发现我的网站页面不是很满意,需要改进。但涉及到的代码有点多,分块改版。
改版主要修改css样式和一些功能。之前我的博客列表页面如下:
摘要可以去掉,而且那些分类标签需要单独页面打开。我想弄成和某宝那些商城一样,可以实现筛选功能。并简化这个页面,展示更多的博文。
为了后续开发不混乱,应该要原型设计。
但我这个原型设计有点麻烦,干脆直接设计页面,先不管功能。最后设计页面如下:
1、去掉多余的摘要和侧边栏;
2、加上一个筛选器(包括排序功能和按分类筛选功能);
3、整理博文列表(只显示标题、类别、发布日期、阅读数和评论数)。
部分组件和样式直接使用Bootstrap的,这里就不多说,前端设计主要看个人功力。主要说说筛选功能的实现。
首先,考虑点击对应的复选框就直接筛选,也就是提交表单,请求数据。
发送请求的方式,我选择GET方式,即可以在url中看到相应的参数。
这些复选框都是input标签,type类型等于checkbox,设置同样的name属性为tag(不包括“推荐”和“全部类别”这两个特殊选项)。
此处Form表单的id为filter_form,action属性写当前页面的地址。模版的html代码如下:
<form id="filter_form" method="GET" action="{%url 'blog_list'%}"> <!--其他内容--> </form>
“推荐”复选框的id为recommmend,“全部类别”的复选框id为check_all。这两个特殊复选框的value属性写的都是true,而那些小类别的value都是对应类别的主键id值。
要实现点击复选框就提交表单,需要给这些复选框绑定事件。在该页面加载时执行如下jQuery代码:
/*绑定复选框事件*/ $("#filter_form input[name=tag]").each(function(){ $(this).change(function(){ //移除“全部类别”的勾选 $('#check_all').removeAttr("checked"); //提交表单 $('#filter_form').submit(); }); }); //“全部类别”的复选框 $("#check_all").change(function(){ //判断是否选中“全部类别”,才去掉其他类别的勾选,并提交表单 if($("#check_all").is(':checked')){ $("#filter_form input[name=tag]").each(function(){ $(this).removeAttr("checked"); }); //提交表单 $('#filter_form').submit(); } }); //“推荐”的复选框 $("#recommend").change(function(){ //提交表单 $('#filter_form').submit(); });
此处为了避免“全部类别”复选框和普通每个类别的复选框逻辑上和操作上的冲突,做了一些处理。
当勾选“全部类别”复选框时,就去掉其他小类别的勾选,再提交表单;当勾选任意小类别的复选框时,去掉“全部类别”复选框的勾选。当然,前端是不安全的(不可信的),在后端我也会做一定的判断处理。
测试点击复选框,可以看到请求链接的GET参数部分如下:
1、勾选“推荐”和“全部类别”:?recommend=true&check_all=true
2、勾选一些小类别:?tag=1&tag=2
3、勾选“推荐”和小类别:?recommend=true&tag=5
这里可以发现,若勾选了多个小类别会出现同样的url参数名。这个参数名在django后端处理需要处理一下,要用request.GET.getlist方法获取。可以参考:Django处理同名参数。
在views.py中处理该请求,我们可以获取到对应的参数。这些复选框勾选实际对应到模型筛选就两种条件:
1、是否选择“推荐”:推荐在我的Blog模型是recommend布尔值字段。若勾选推荐,加上recommend=True条件;若没勾选,则不理会。
2、“全部类别”和小类别是对立关系。若勾选“全部类别”,则无需理会;若勾选小类别,用in查询获取对应的类别。
Django的模型查询是QuerySet类型,可以多次使用filter。代码如下:
#coding:utf-8 #导入我的blog应用,两个模型:Blog和Tag #Blog模型上有个多对多字段tags和Tag模型关联 from blog.models import Blog, Tag def index(request): try: #处理分类标签 tags_checked = request.GET.getlist('tag') #获取同名参数 #判断是否需要勾选(若勾选个数为0或全部,则无需筛选tag) tag_need_length = len(tags_checked) tag_need_check = tag_need_length>0 and tag_need_length<Tag.objects.count() tag_ids = [] for tag in tags_checked: try: #把tag参数值转成int类型,为了避免有非数字的,逐个处理 tag_ids.append(int(tag)) except: pass #博文类别筛选 if tag_need_check: blogs = Blog.objects.filter(tags__in = tag_ids) #in查询 blogs = blogs.distinct() #去重 else: blogs = Blog.objects.all() #返回全部博文 #是否筛选推荐的博文 is_recommend = request.GET.get('recommend','') == 'true' if is_recommend: blogs = blogs.filter(recommend = True) #页码(这个页面处理函数 getPages是我自己定义的) paginator, blogs = getPages(request, blogs) #return data data = {} data["tags"] = Tag.objects.all() data["blogs"] = blogs data["paginator"] = paginator except Exception: raise Http404 return render_to_response('blog/blog_filter.html', data)
上面代码,分别对分类类别和是否推荐进行两次筛选。其中页码的是我之前写的自定义函数,具体可以参考:我的网站搭建(第22天) 分页器优化。
但筛选之后返回的页面没有勾选对应的复选框,以及没有显示对应类别下的博文数量。如下图:
这个是因为我没有把对应的数据写入模版页面,并返回给前端。
利用Python可以动态加属性的特点,给各个分类标签加入博文数量和是否勾选的值。修改上面代码:
#coding:utf-8 #导入我的blog应用,两个模型:Blog和Tag #Blog模型上有个多对多字段tags和Tag模型关联 from blog.models import Blog, Tag def index(request): try: #处理分类标签 tags_checked = request.GET.getlist('tag') #获取同名参数 #判断是否需要勾选(若勾选个数为0或全部,则无需筛选tag) tag_need_length = len(tags_checked) tag_need_check = tag_need_length>0 and tag_need_length<Tag.objects.count() tags = [] tag_ids = [] for tag in Tag.objects.all(): #ManyToMany多对多字段,统计对应博文数量 tag.count = len(tag.blog_set.all()) #判断是否需要勾选(若tag_need_check=False,说明全选。and逻辑关系就变成False) tag.checked = str(tag.id) in tags_checked and tag_need_check #加了两个属性的tag,添加到tags中 tags.append(tag) #重新建一个list避免客户端乱输参数 if tag.checked: tag_ids.append(tag.id) #博文类别筛选 if tag_need_check: blogs = Blog.objects.filter(tags__in = tag_ids) #in查询 blogs = blogs.distinct() #去重 else: blogs = Blog.objects.all() #返回全部博文 #是否筛选推荐的博文 is_recommend = request.GET.get('recommend','') == 'true' if is_recommend: blogs = blogs.filter(recommend = True) #页码(这个页面处理函数 getPages是我自己定义的) paginator, blogs = getPages(request, blogs) #返回的参数 params = {} params['recommend'] = is_recommend #是否勾选推荐 params['check_all'] = not(tag_need_check) #是否勾选全部类别 #return data data = {} data['filter'] = params data["tags"] = tags data["blogs"] = blogs data["paginator"] = paginator except Exception: raise Http404 return render_to_response('blog/blog_filter.html', data)
如上修改代码,给tag绑定两个属性。在模版页面可以遍历tags,读取这两个属性。对应的模版文件部分html代码如下:
<ul> <li class="tag-recommend"> <input id="recommend" type="checkbox" name="recommend" value="true" {%if filter.recommend%}checked{%endif%}/> <label for="recommend">推荐</label> </li> <li class="tag-recommend"> <input id="check_all" type="checkbox" name="check_all" value="true" {%if filter.check_all%}checked{%endif%}/> <label for="check_all">全部类别</label> </li> {%for tag in tags%} <li> <input id="tag_{{tag.id}}" type="checkbox" name="tag" value="{{tag.id}}" {%if tag.checked%}checked{%endif%}/> <label for="tag_{{tag.id}}">{{tag.tag_name}} ({{tag.count}})</label> </li> {%endfor%} </ul>
其中,想勾选对应复选框,只需要加上checked属性即可。
到这里,就完成了博文筛选的功能。另外,原本的tag跳转到对应分类标签的链接,可修改为访问博文列表链接,加上对应的tag参数即可,例如?tag=1。
注意:此处的代码只供参考,因为后面我对博文进行排序时,其中的阅读数和评论数排序无法使用QuerySet方法。
因为这两个使用到ContentType,无法直接用QuerySet(就算可以用,也效率很慢)。最后我采用了SQL语句查询的方法。
何小盒
😎
2018-09-19 11:52 回复