我的网站搭建(第40天) 关键字和标签输入插件

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

这几天琢磨怎么实现智能推荐文章。查阅了一些资料,发现不太适合使用协同算法实现智能推荐功能。

最后准备使用博文的相关关键字进行计算,猜测访客的阅读偏好,进而推荐相关的博文。

那么,需要给博文添加关键字。另外关键字有利于SEO优化。


打开Django中blog应用的models.py文件,添加Keyword模型和Blog添加Keyword多对多外键字段。

class Keyword(models.Model):
    """Keyword关键字模型"""
    keyword = models.CharField(max_length=64)
    create_time = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return u'%s' % (self.keyword)
        
class Blog(models.Model):
    """Blog模型"""
    #其他字段省略不显示
    keywords = models.ManyToManyField(Keyword, blank=True)

修改模型操作需要更新数据库:

>>> python manage.py makegrateions
>>> python manage.py migrate

可以修改admin.py,显示Keyword并新增一些关键字。

#coding:utf-8
from django.contrib import admin
from blog.models import Keyword

@admin.register(Keyword)
class KeywordAdmin(admin.ModelAdmin):
    """keywords admin"""
    list_display=('keyword', 'create_time')

这样我们就可以在后台管理界面给对应的博文新增关键字。关键字如何写这个大家可以网上搜索。

(当然,我是不会在后台管理界面修改。后面会结合我前面写的富文本框新增修改博文的功能处理。)


打开每篇博文时,需要把关键字输出到模版。修改view.py文件对应的方法:

#找到对应的响应打开博文页面的方法
def blog_show(request, id):
    #其他代码不显示
    data = {}
    data["keywords"] = ','.join(map(lambda x:x.keyword, blog.keywords.all()))
    #其他代码不显示

该句代码把该篇博文的关键字用逗号隔开,写到keywords变量中。

再修改对应的模版页面,在head标签部分添加如下代码:

{%if keywords%}<meta name="keywords" content="{{keywords}}">{%endif%}

若没有keywords,就不写该句meta标签。

基础功能到这里为止,接下来结合标签输入插件看看高级炫酷的功能。

------- 手动分割线 -------


前面写了一个页面,用UEditor新增博文

我需要完善该功能,在后面加上关键字编辑。最终效果如下:

20170111/20170111163834122.png

底下部分是一个标签输入插件:bootstrap-tagsinput。该插件是基于bootstrap和jquery。

相关链接:http://bootstrap-tagsinput.github.io/bootstrap-tagsinput/examples/

该链接是一个示例,大家可以下载修改试试。我自己修改整理了一个示例:http://pan.baidu.com/s/1c1RoXiC。效果如下图:

20170111/20170111164713489.png

除了bootstrap-tagsinput自身的功能(只有第一行的关键字),我还加一个推荐关键字的功能。这些推荐的关键字都来自Keyword表中的数据,方便我们输入。

修改该模版页面先添加相关css和js引用。

<link rel="stylesheet" href="/static/css/bootstrap/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap/bootstrap-tagsinput.css">

<script src="/static/js/jquery-1.11.3.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/static/js/bootstrap-tagsinput.min.js"></script>

在页面合适的位置,添加关键字部分的html代码:

<div class="keywords_content">
    <div>
        <label for="keywords">关键字:</label>
        <input id="keywords" name="keywords" type="text" value="" data-role="tagsinput" placeholder="添加关键字 "/>
    </div>
    
    <div>
        <label for="recommend_keywords" style="margin-top:0.5em">推荐词:</label>
        <input type="hidden" id="keywords_index" value="0" tag="关键字页数标记"/>
        <div id="recommend_keywords"></div>
        <a href="javascript:get_keywords();" class="btn btn-default btn-xs">换一批</a>
        <a href="javascript:$('#keywords').tagsinput('removeAll');" class="btn btn-default btn-xs">全部清除</a>
    </div>        
</div>

当然,该标签在我一个form表单中。其中“换一批”按钮的代码那个函数下面再加上。

我需要添加一些推荐的关键字到recommend_keywords中。这里为了避免刷新页面,需要用ajax获取和加载数据。


在script部分,再加入如下设置:

$("#keywords").tagsinput({
    maxTags: 10,    //最多有10个标签
    maxChars: 24,   //每个标签最大长度
    trimValue: true //标签名前后剔除空格
});


接着再写后端代码,获取推荐的关键字。因为关键字随着发表的博文会越来越多。需要分批获取,每次获取不同的关键字。该操作相当于分页操作。打开views.py文件,代码如下:

#coding:utf-8
from django.db.models import Count          #个数统计
from django.core.paginator import Paginator #分页器
from django.views.decorators.csrf import csrf_exempt #排除csrf验证

from blog.models import Blog, Keyword       #相关的模型
from django.http import Http404, HttpResponse
import json

#处理推荐关键字
@csrf_exempt
def ajax_recommend_keywords(request):
    data = {}

    try:
        if request.method != 'POST':
            raise Exception('method error')

        #获取POST的json
        req_json = json.loads(request.body)
        keywords_index = int(req_json.get('keywords_index', '0')) + 1     #得到页数索引
        keywords_each_num = int(req_json.get('keywords_each_num', '20'))  #每页显示多少页

        #个数修正
        if keywords_each_num<5: keywords_each_num = 20
        if keywords_each_num>50: keywords_each_num = 50
        
        #关键字按使用次数排序
        qs = Keyword.objects.annotate(blog_count=Count('blog')).order_by('-blog_count')
        paginator = Paginator(qs, keywords_each_num)
        page_all = paginator.num_pages  #总页数

        if keywords_index>page_all: keywords_index = 1
        objectlist = paginator.page(keywords_index) #获取当前页面的对象集

        data["code"] = 0
        data["message"] = "OK"
        data["keywords_index"] = keywords_index
        data["keywords_list"] = ','.join(map(lambda x:x.keyword, objectlist))
    except Exception as e:
        data["code"] = 1
        data["message"] = e.message
    return HttpResponse(json.dumps(data), content_type = 'application/json')

用ajax访问该方法,其中需要当前页码keywords_index和每页个数keyword_each_num。

其中,关键字需要安装使用频率倒序排序。使用比较多的关键字优先显示。

qs = Keyword.objects.annotate(blog_count=Count('blog')).order_by('-blog_count')

这句代码效果相当与如下的SQL查询:

select blog_keyword.* 
from blog_keyword
left join blog_blog_keywords 
       on blog_keyword.id = blog_blog_keywords.keyword_id
group by blog_keyword.id
order by count(blog_blog_keywords.keyword_id) des

获取之后,在把数据用json返回给前端。格式如下:

{
    "code":0, //状态码:0表示成功,非0表示出错
    "message":"OK", //消息:主要用于出错显示返回的消息
    "keywords_index":2, //关键字页码
    "keywords_list":"python,linux,excel" //推荐的关键字,用逗号隔开
}


再添加url路由设置,就可以访问该方法。打开urls.py,加入如下代码:

url(r'^get_keywords$', blog_views.ajax_recommend_keywords, name='get_keywords'),


前端页面的get_keywords()方法代码如下:

function get_keywords(){
    var json = {};
    $('.tip-text').text('');
    
    //每批关键字的个数
    json['keywords_each_num'] = 10; 
    //获取当前关键字页码
    json['keywords_index'] = $('#keywords_index').val(); 

    //ajax提交json
    $.ajax({
        type: "POST",
        data: JSON.stringify(json),
        url: "{% url 'get_keywords' %}",
        cache: false,
        dataType: "json",
        success: function(json, textStatus) {
            if(json["code"]!=0){
                $('.tip-text').text(json['message']);
            }else{
                //更新列表
                update_list(json['keywords_list'].split(','));
                //更新页码标记
                $('#keywords_index').val(json['keywords_index']);
            }
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            $('.tip-text').text('获取推荐词列表出错,请稍后重试');
        }
    });
}

//更新列表
function update_list(arr){
    $("#recommend_keywords").children().remove(); //移除所有子节点
    //遍历添加子节点
    for (var i = 0; i < arr.length; i++) {
        var str = '<a class="recommend_sub btn btn-info btn-xs">'+arr[i]+'</a>';
        $("#recommend_keywords").append(str);
    };
    //绑定事件
    $("#recommend_keywords a").each(function(){
        $(this).click(function(){$("#keywords").tagsinput('add', $(this).text())});
    });
};

并在页面加载的时候,执行一次该方法。这样可以实现分批次从数据库获取关键字。

到这里,基本逻辑已经完成了。

先获取第1页的关键字。获取之后,页码加1记录在一个hidden标签中。下一次可以获取下一页的关键字。到了最后一页,再重新获取第1页的关键字。

我们可以直接点击推荐关键字列表,添加关键字。


最后,在保存博文的时候,获取这些关键字,并保存添加即可。

提交之前,还需要验证是否有填写关键字。这个简单,前端验证代码如下:

//关键字
if($('#keywords').val().length<=0){
    $('.tip-text').text('尚未添加关键字');
    return false;
}

后端验证代码如下:

#检查和获取博客新增修改POST提交的数据
def _ajax_post_data(request):
    if request.method != 'POST':
        raise Exception('method error')

    #获取数据
    #省略了其他代码,可参考我前面的博文
    blog_keywords = request.POST.get('keywords', '').split(',')

    #省略了其他代码,可参考我前面的博文

    #验证数据
    #省略了其他代码,可参考我前面的博文
    if len(blog_keywords)==0:
        raise Exception(u'尚未添加关键字')
    
    #返回数据
    post_data = {}
    
    #省略了其他代码,可参考我前面的博文
    post_data['blog_keywords'] = blog_keywords
    return post_data

省略的代码可以看我前面的博文:用UEditor新增博文

接着再保存关键字。这里需要判断关键字是否存在,不存在就新增即可。

@check_admin
def add_ajax(request):
    data = {}
    try:
        #检查和获取博客新增POST提交的数据
        post_data = _ajax_post_data(request)

        #新增博文
        blog = Blog()
        blog.caption = post_data['blog_title']
        blog.author = request.user
        blog.content = post_data['blog_content']
        blog.recommend = post_data['blog_recommend']
        blog.save()

        #处理多对多,需要博文保存后才能处理,而新增后无需保存
        #blog.tags.clear()
        for tag in Tag.objects.filter(id__in = post_data['tag_ids']):
            blog.tags.add(tag)        

        #插入关键字
        for keyword_text in post_data['blog_keywords']:
            try:
                keyword = Keyword.objects.get(keyword=keyword_text)
            except:
                keyword = Keyword(keyword=keyword_text)
                keyword.save()
            blog.keywords.add(keyword)

        #返回结果
        data['success'] = True
        data['message'] = reverse('detailblog', args = [blog.id,])
    except Exception as e:
        data['success'] = False
        data['message'] = e.message
    
    return HttpResponse(json.dumps(data), content_type = 'application/json')

哈哈,到这里不知道晕了没有。这些需要大家实践。只是看代码很容易晕。

新增关键字完整的代码和流程已经完成了。


等等,还没结束。

有新增博客,就有修改博客。同样,参考我前面的博文:我的网站搭建(第38天) 用UEditor编辑博文

这里大部分代码和新增博客一样。只需要修改两个地方即可。


1)打开修改页面时,需要写入原有关键字的内容。

这个和给每篇博文写关键字meta标签一样。用逗号隔开,把关键字输出input标签的valu即可。

#后端代码
data["keywords"] = ','.join(map(lambda x:x.keyword, blog.keywords.all()))

#前端代码
<input id="keywords" name="keywords" type="text" value="{{keywords}}" data-role="tagsinput" placeholder="添加关键字 "/>


2)后端保存修改博客的时候,因为是多对多处理,插入关键字之前需要删除原有的关键字。

在插入关键字之前,加入如下代码即可:

blog.keywords.clear()

终于把全部内容说完了,希望我有讲清楚。哈哈~

上一篇:我的网站搭建(第41天) 基于关键字的智能推荐

下一篇:机器学习03:k-近邻算法拓展和总结

评论列表

智慧如你,不想发表一下意见吗?

新的评论

清空