飞仙锅建站日志第5篇-增加文章阅读计数器

关于博客的记数器,最常见的做法是在主表里加一个view_number字段就可以了,每访问一次数据加1就好。

不优雅!而且扩展性不好。

因为以后除了blog以外的其它类型的应用,也需要记数器,又得加一堆boring的代码,所以本文是为整个项目增加一个全局计数器,为今后新增的应用服务。

具体是通过python的装饰器,以及Django的ContentType,tamplate tags来实现的

首先,利用django生成一个通用的阅读计数器app,就叫view_record:

python3 manage.py startapp view_record

创建计数器model

要统计什么时候谁访问了哪篇博文,那么就需要一个明细表记录和总表记录总数。

当然可以不用总表记录阅读总数,为了提高网站的访问效率,每次得到博文的阅读总数如果是直接获取到表中的数据,要比每次都用明细表的数据求和要快很多。

打开view_record应用的models.py文件:

#coding:utf-8
from django.db import models
    
#引用ContentType相关模块
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
    
#引用系统自带的用户模型
#from django.contrib.auth.models import User
from myusers.models import UserProfile  #后续我扩展了系统的User类,后续再讲
    
class Recorder(models.Model):
    """阅读明细记录"""
    #ContentType设置
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey(
        ct_field="content_type",
        fk_field="object_id"
    )
    
    #记录IP地址
    ip_address = models.CharField(max_length=15)
    
    #记录User,这里可能没有登录用户,所以要允许为空
    #user = models.ForeignKey(User, blank=True, null=True,on_delete=models.CASCADE)
    user = models.ForeignKey(UserProfile, blank=True, null=True,on_delete=models.CASCADE)
    
    #阅读的时间
    view_time = models.DateTimeField(auto_now=True)
    
class ViewNum(models.Model):
    """阅读数量记录"""
    #ContentType设置
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey(
        ct_field="content_type",
        fk_field="object_id"
    )
    
    #普通字段,阅读总数量
    view_num = models.IntegerField(default=0)
    
    def __unicode__(self):
        return u'<%s:%s> %s' % (self.content_type, self.object_id, self.view_num)

修改计数器admin

顺手把它加到后台管理中,打开该应用的admin.py:

from django.contrib import admin
from view_record.models import Recorder, ViewNum
    
# Register your models here.
class RecorderAdmin(admin.ModelAdmin):
    """view recorder admin"""
    list_display=('content_type','object_id','ip_address','user','view_time')
    ordering=('-view_time',)
    
class ViewNumAdmin(admin.ModelAdmin):
    """view num admin"""
    list_display=('content_type','object_id','view_num')
    
admin.site.register(Recorder, RecorderAdmin)
admin.site.register(ViewNum, ViewNumAdmin)

同时也打开settings.py文件,加入该应用:

INSTALLED_APPS = (
    #... 其他应用,
    'view_record',
)

记得同步一下数据库,本来这些都是细枝末节(自己应该懂的,注意的事情)。为了避免出现低级错误,还是提一下。

python3 manage.py makemigrations
python3 manage.py migrate

创建计数器的decorator

在view_record应用创建一个decorator.py文件,用于放置装饰器方法:

#coding:utf-8
from django.contrib.contenttypes import models
from django.http import Http404
from django.contrib.contenttypes.models import ContentType
    
from view_record.models import Recorder, ViewNum

# 记录Entry_Detail页面访问量,只在get上生效
def entry_detail_view_counter(func):
    def wrapper(self, request, *args, **kwargs):
        print('自定义装饰器entry_detail_view_counter被调用了 请求路径%s' % request.path)
        #print('request meta =  ' ,request.META)
        #print('request COOKIES =  ' ,request.COOKIES)
        #print('self =  ' ,self.get_object().id)
        #print('kwargs =  ' ,kwargs)
        '''
        for key, value in kwargs.items():
            setattr(self, key, value)
        print(self.year,self.month,self.day,self.slug)
        '''
        #得到view里面的object参数
        obj = self.get_object()
        recorder = Recorder(content_object = obj)
        recorder.ip_address = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR", None))
        #print('in decorator request.user = = = ',request.user)
        recorder.user = request.user if request.user.is_authenticated else None
        recorder.save()

        #总记录+1
        obj_type = ContentType.objects.get_for_model(obj)
        viewers = ViewNum.objects.filter(content_type = obj_type, object_id = obj.id)

        if viewers.count() > 0:
            viewer = viewers[0]
        else:
            viewer = ViewNum(content_type = obj_type, object_id = obj.id)
        viewer.view_num += 1
        viewer.save()

        return func(self, request, *args, **kwargs)
    return wrapper

若你对装饰器不太了解的话,可以看看《我对Python装饰器的理解》。

该装饰器是带一个参数,需要转递模型类进来。

由于zinnia代码框架高度简约,需要在EntryProtectionMixin的get方法上调用装饰器,才能得到每一次访问的计数值

from view_record.decorator import entry_detail_view_counter

class EntryProtectionMixin(LoginMixin, PasswordMixin):
    """
    Mixin returning a login view if the current
    entry need authentication and password view
    if the entry is protected by a password.
    """
    session_key = 'zinnia_entry_%s_password'
    
    @entry_detail_view_counter
    def get(self, request, *args, **kwargs):
        """
        Do the login and password protection.
        """
        response = super(EntryProtectionMixin, self).get(request, *args, **kwargs)
        if self.object.login_required and not request.user.is_authenticated:
            return self.login()
        if (self.object.password and self.object.password !=
                self.request.session.get(self.session_key % self.object.pk)):
            return self.password()
        return response

给装饰器传递博文的模型类,就可以实现对博文阅读计数。

修改博客的admin

为了方便在entry后台管理看到计数器,需要修改models_bases/entry.py文件:

view_num = GenericRelation(ViewNum)

再打开blog应用的admin/entry.py文件:

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

class EntryAdmin(admin.ModelAdmin):
    """blog admin"""
    list_display=('id','view_num_count')

    def view_num_count(self, obj):
        """自定义显示字段"""
        return sum(map(lambda x: x.view_num,obj.view_num.all()))

实现这个代码之后,就可以在后台管理中查看对应博文的阅读次数。

自定义计数器的模板标签

在view_record应用的目录下,创建文件夹templatetags。该文件夹django可以自动识别。

在templatetags文件夹,新建两个文件:__init__.py和view_num.py。打开view_num.py,写入如下代码:

#coding:utf-8
from django import template
from django.contrib.contenttypes.models import ContentType
from view_record.models import ViewNum
    
#得到自定义标签库,用于注册标签
register = template.Library()

#由于view_record使用contentType,所以更利于代码的扩展,自动侦测对象的类型,找到相关的数字
#本方法是专为列表的cell对象写的标签
@register.simple_tag
def get_view_cell_nums(*args, **kwargs):
    obj = kwargs['data_in_cell']
    obj_type = ContentType.objects.get_for_model(obj)
    views = ViewNum.objects.filter(content_type = obj_type, object_id = obj.id)
    view_num_all = sum(map(lambda x: x.view_num, views))
    return str(view_num_all)

这个自定义标签实现的模式和admin差不多,需要处理的类和注册的方法。一般处理的类是返回结果;注册的方法是判断使用自定义标签的格式是否正确。

标签定义好之后,主要在博客列表中使用

{# 导入阅读器标签 #}
{% load view_nums %}

#根据每一条数据去抓取view_nums
{% get_view_cell_nums data_in_cell=object%}

全部完成以后,文章列表就可以显示每个博客的访问数字了。大概效果可以看下图:

结语

至此,这个功能从前端到后端都讲完了,一通疯狂操作以后,发现对django的理解更深了一些。

各位观众老爷,如果觉得对你有用的话。

请把“优雅”打在公屏上!

评论列表

暂无评论,欢迎来抢沙发!

新的评论

清空