我的网站搭建(第33天) 页面改版:博文筛选

  • 发布时间:2016年11月9日 11:03
  • 作者:杨仕航
* 该文是基于Python2.7开发的,最新Python3.x和Django2.x视频教程可以前往 >> Django2.0视频教程

近来发现我的网站页面不是很满意,需要改进。但涉及到的代码有点多,分块改版。

改版主要修改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}}&nbsp;({{tag.count}})</label>
        </li>
    {%endfor%}
</ul>

其中,想勾选对应复选框,只需要加上checked属性即可。

到这里,就完成了博文筛选的功能。另外,原本的tag跳转到对应分类标签的链接,可修改为访问博文列表链接,加上对应的tag参数即可,例如?tag=1。


注意:此处的代码只供参考,因为后面我对博文进行排序时,其中的阅读数和评论数排序无法使用QuerySet方法。

因为这两个使用到ContentType,无法直接用QuerySet(就算可以用,也效率很慢)。最后我采用了SQL语句查询的方法。

上一篇:我的网站搭建(第34天) 页面改版:博文排序

下一篇:Django处理同名url参数

评论列表

何小盒

何小盒

😎

2018-09-19 11:52 回复

新的评论

清空