阅读统计有三个层次实现方法:一是在views方法内统计计数;二是建立独立的class模型实现;三是建立单独的APP,可以通用的进行各类计数统计。

一、实现简单博客阅读计数。

简单计数处理

1.models模型中添加一个显示阅读数量的字段 read_num = models.IntegerField(default=0)

2.同步数据库

3.在admin.py 添加read_num字段,使其在后台可以显示。

4.在views.py的blog_detail中添加计数处理方法。

 1 def blog_detail(request,blog_pk):
 2     blog = get_object_or_404(Blog,pk = blog_pk)
 3     blog.read_num +=1
 4     blog.save()
 5 
 6     context = {}
 7     context['blog'] = blog
 8     context['previous_blog'] = Blog.objects.filter(created_time__gt=blog.created_time).last()  #取得所有大于当前博客创建时间所有博客中的最后一条。
 9     context['next_blog'] = Blog.objects.filter(created_time__lt=blog.created_time).first()   #也可以用[0]或[-1]取得列表的第一条或最后一条。
10     return render_to_response('blog_detail.html',context)

5.页面添加阅读数量 在blog_detail.html中。

<ul class="blog-info-description">
     <li>作者:{{ blog.author }}</li>
     <li>博客分类:<a href="{% url  'blogs_with_type' blog.blog_type.pk %}"> {{ blog.blog_type }}</a></li>
     <li>发表日期:{{ blog.created_time  | date:"Y-m-d H:n:s" }}</li>
     <li>阅读({{ blog.read_num }})</li>

在博客列表中页添加阅读数量。阅读({{ blog.read_num }})

6. 以上计数存在问题,网页每刷新一次就有一个请求request,就会增加一次计数。

自定义计数规则:规定怎么才算阅读1次。

7. 应用cookie保存阅读信息。

cookie是浏览器保存在本地的数据,每次发送请求,浏览器会把cookie提交给服务器。

在cookie中写入信息。在views中。

   response.set_cookie(key,value,) #类似于字典,cookies有一个有效期,过期作废,可以自定义时限。其中key=blog_pk, 用一个名称进行识别,key=‘blog_%s_read’ % blog_pk, value=‘true'。有效期 max_age=60 以秒为单位,另一方法expires=datetime类型的时间点。expires存在时max_age无效。如果没有设置时间的话,默认退出浏览器cookie失效。 完整结构如下:

response.set_cookie(‘blog_%s_read’ % blog_pk,‘true',max_age=60,expires=datetime) 

response = render_to_response('blog_detail.html',context)  #响应,与请request求一一对应。
response.set_cookie(‘blog_%s_read’ % blog_pk,‘true'e) #类似于字典
return response

 网页中查看cookies:F12-->Application-->Cookies.

读出cookies信息。 

if not request.COOKIES.get('blog_ % s_read' % blog_pk):
blog.read_num +=1
blog.save() 
 1 def blog_detail(request,blog_pk):
 2     blog = get_object_or_404(Blog,pk = blog_pk)
 3     if not request.COOKIES.get('blog_%s_read' % blog_pk):
 4         blog.read_num +=1
 5         blog.save()
 6 
 7     context = {}
 8     context['blog'] = blog
 9     context['previous_blog'] = Blog.objects.filter(created_time__gt=blog.created_time).last()  #取得所有大于当前博客创建时间所有博客中的最后一条。
10     context['next_blog'] = Blog.objects.filter(created_time__lt=blog.created_time).first()   #也可以用[0]或[-1]取得列表的第一条或最后一条。
11 
12     response = render_to_response('blog_detail.html', context)  # 响应,与请request求一一对应。
13     response.set_cookie('blog_%s_read' % blog_pk,'true')  #类似于字典
14     return response

以上方法存在问题:该方法缺点,3、同时访问更改最后更新时间,而不是博客实际更改时间。

 二、计数功能独立之一:创建计数模型

1 class Readnum(models.Model):
2     read_num = models.IntegerField(default=0)
3     blog = models.OneToOneField(Blog,on_delete=models.DO_NOTHING)

修改admin.py

1 @admin.register(ReadNum)
2 class ReadNumAdmin(admin.ModelAdmin):
3     list_display = ('id','read_num','blog')

独立之后,后台操作就不影响计数结果了。

 ?这个计数结果在模型ReadNum里面,那么怎么样将计数结果通过关联外键传到模型Blog中呢?

blog_type是关联到BlogType的外键,所以实例blog.blog_type可以查询到实例blog的类型。同样通过实例的类型
也可以查询到这一类型对应的所有博客(这是一对多),blog.blog_type.blog_set.all().

 查询dir(blog) 有一个readnum方法(即ReadNum的小写)。blog.readnum获取了ReadNum中对应的整条记录。然后可以用blog.readnum.read_num获取阅读数量。所以在Blog中写入一个获取数量的方法。

1 def get_read_num(self):
2 return self.readnum.read_num

 修改处理方法:在views的blog_detail中添加获取阅读数量的方法

 1 blog = get_object_or_404(Blog,pk = blog_pk)
 2     if not request.COOKIES.get('blog_%s_read' % blog_pk):
 3         if ReadNum.objects.filter(blog=blog).count():  #判断对应博客的记录是否存在
 4             #存在记录
 5             readnum = ReadNum.objects.get(blog=blog)   #取出数量
 6         else:
 7             #不存在记录
 8             readnum = ReadNum(blog=blog)   #实例化
 9         readnum.read_num += 1
10         readnum.save()

处理class的Blog中取得计数的方法,使得没有记录的显示为0。

from django.db.models.fields import exceptions   #引入错误集合
    def get_read_num(self):
        try:
            return self.readnum.read_num
        except Exception.ObjectDoseNotExist:
            return 0

 三、独立计数之二:创建独立计数APP

可以对任意模型计数。模型记录-->ContentType(属于一个class)

 在官网可以查找文档。contenttype是一个class类,他有连个字段记录APP名称和model的名称。

1.创建app。   reade_statistics

2.创建模型。从https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/、中复制模型代码进行修改。

1 from django.db import models
2 from django.contrib.contenttypes.fields import GenericForeignKey
3 from django.contrib.contenttypes.models import CententType
4 
5 class READNum(models.Model):
6     read_num = models.IntegerField(default=0)
7     content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING)  #外键指向模型
8     object_id = models.PositiveIntegerField()    # 主键
9     content_object = GenericForeignKey('content_type', 'object_id')  #组成通用的外键

3.settings中注册app。数据库迁移。

4.写后台。

from django.contrib import admin
from .models import ReadNum

@admin.register(ReadNum)
class ReadNumAdmin(admin.ModelAdmin):
    list_display = ('read_num','content_object')

5.在Blog中获取阅读数量。

ContentType.objects.filter(model='blog')   通过ContentType的model可以找到contenttype。

ContentType.objects.get_for_model(Blog)   通过ContentType的get_for_model方法也可获取。

主键值:blog_pk    取得contenttype和blog_pk后就可以获取数量。Blog中写代码:

1     def get_read_num(self):
2         ct = ContentType.objects.get_for_model(Blog)
3         readnum = ReadNum.objects.get(content_type=ct,object_id=self.pk)
4         return readnum.read_num

处理get_read_num不存在情况:

    def get_read_num(self):
        try:
            ct = ContentType.objects.get_for_model(self)
            readnum = ReadNum.objects.get(content_type=ct,object_id=self.pk)
            return readnum.read_num
        except exceptions.ObjectDoesNotExist:
            return 0

 6.处理方法修改:

 1 def blog_detail(request,blog_pk):
 2     blog = get_object_or_404(Blog,pk = blog_pk)
 3     if not request.COOKIES.get('blog_%s_read' % blog_pk):
 4         ct = ContentType.objects.get_for_model(Blog)
 5 
 6         if ReadNum.objects.filter(content_type=ct,object_id=blog_pk).count():  #判断对应博客的记录是否存在
 7             #记录存在
 8             readnum = ReadNum.objects.get(content_type=ct,object_id=blog_pk)   #取出数量
 9         else:
10             #不存在记录
11             readnum = ReadNum(content_type=ct,object_id=blog_pk)   #实例化
12         readnum.read_num += 1
13         readnum.save()

7.继续通用化处理 :把models的Blog和views中的计数处理方法通用化,封装到APP中。把通用的东西变成类。然后进行类的继承。

1 class ReadNumExpandMethod():   #ExpandMethod拓展方法
2     def get_read_num(self):
3         try:
4             ct = ContentType.objects.get_for_model(self)
5             readnum = ReadNum.objects.get(content_type=ct,object_id=self.pk)
6             return readnum.read_num
7         except exceptions.ObjectDoesNotExist:
8             return 0

在Blog中引入和继承类

from read_statistics.models import ReadNumExpandMethod

class Blog(models.Model,ReadNumExpandMethod):

在read_statistics中新建文件utils.py(utils工具),把views中处理方法剪切过来。

 1 from django.contrib.contenttypes.models import ContentType
 2 from .models import ReadNum
 3 
 4 def read_statistics_once_read(request,obj):
 5     ct = ContentType.objects.get_for_model(obj)
 6     key = "%s_%s_read" % (ct.model,obj.pk)
 7     if not request.COOKIES.get(key):
 8         if ReadNum.objects.filter(content_type=ct, object_id=obj.pk).count():  # 判断对应博客的记录是否存在
 9             # 记录存在
10             readnum = ReadNum.objects.get(content_type=ct, object_id=obj.pk)  # 取出数量
11         else:
12             # 不存在记录
13             readnum = ReadNum(content_type=ct, object_id=obj.pk)  # 实例化
14         readnum.read_num += 1
15         readnum.save()
16     return key

在views中传入该方法

read_cookie_key = read_statistics_once_read(request,blog)

response.set_cookie(read_cookie_key,'true')  #阅读标记

 四、分日期统计近期阅读数,首页实现折线图显示。

1.read_statistics的models中设计模型class。

1 class ReadDetail(models.Model):
2     date = models.DateField(default=timezone.now)
3     read_num = models.IntegerField(default=0)
4 
5     content_type = models.ForeignKey(ContentType,on_delete=models.DO_NOTHING)
6     object_id = models.PositiveIntegerField()
7     content_object = GenericForeignKey('content_type','object_id')

2.admin.py中注册

1 @admin.register(ReadDetail)
2 class ReadDetailAdmin(admin.ModelAdmin):
3     list_display = ('id','date','read_num','content_object')

3. utils.py设计技术方法(为数据库添加修改记录)。

 1 from django.contrib.contenttypes.models import ContentType
 2 from .models import ReadNum,ReadDetail
 3 from django.utils import timezone
 4 
 5 
 6 def read_statistics_once_read(request,obj):
 7     ct = ContentType.objects.get_for_model(obj)
 8     key = "%s_%s_read" % (ct.model,obj.pk)
 9     if not request.COOKIES.get(key):
10         if ReadNum.objects.filter(content_type=ct, object_id=obj.pk).count():  # 判断对应博客的记录是否存在
11             # 记录存在
12             readnum = ReadNum.objects.get(content_type=ct, object_id=obj.pk)  # 取出数量
13         else:
14             # 不存在记录
15             readnum = ReadNum(content_type=ct, object_id=obj.pk)  # 实例化
16         readnum.read_num += 1
17         readnum.save()
18 
19         date = timezone.now().date()
20         if ReadDetail.objects.filter(content_type=ct,object_id=obj.id,date=date).count():
21             readdetail = ReadDetail.objects.get(content_type=ct,object_id=obj.id,date=date)
22         else:
23             readdetail = ReadDetail(content_type=ct,object_id=obj.id,date=date)
24         readdetail.read_num +=1
25         readdetail.save()
26     return key

利用django的get_or_create()方法代替if...else...方式。created传回是否为创建的布尔值ture或false.

 1 def read_statistics_once_read(request,obj):
 2     ct = ContentType.objects.get_for_model(obj)
 3     key = "%s_%s_read" % (ct.model,obj.pk)
 4     if not request.COOKIES.get(key):
 5        
 6         readnum,created = ReadNum.objects.get_or_create(content_type=ct, object_id=obj.pk)  # 取出数量
 7         readnum.read_num += 1
 8         readnum.save()
 9 
10         date = timezone.now().date()
11        
12         readdetail,created = ReadDetail.objects.get_or_create(content_type=ct, object_id=obj.id, date=date)
13         readdetail.read_num +=1
14         readdetail.save()
15     return key

**1-4步骤为后端设计。

5. 前段步骤。取出前十天阅读总数。计算前十天阅读数的方法,在utils.py中设计方法。

def get_ten_days_read_data(content_type):
    today = timezone.now().date()
    read_nums = []
    for i in range(10,0,-1):
        date = today - datetime.timedelta(days=i)  #迭代前十天
        read_details = ReadDetail.objects.filter(content_type=content_type,date=date) #取出每天记录
        result = read_details.aggregate(read_num_sum=Sum('read_num'))  #对某天阅读数求和。
        read_nums.append(result['read_num_sum'] or 0)   #生成列表
    return read_nums

6.为前端页面home准备数据。在blog.views.py添加内容。

from django.shortcuts import render_to_response
from django.contrib.contenttypes.models import ContentType
from read_statistics.utils import get_ten_days_read_data
from blog.models import Blog

def home(request): blog_content_type = ConetentType.objects.get_for_model(Blog) read_nums = get_ten_days_read_data(blog_content_type) context = {} context['zz'] = "这是我的主页" context['read_nums'] = read_nums return render_to_response('home.html',context)

7.页面home中使用图表显示。后台提供数据,前端使用数据。

使用Highcharts。 https://www.hcharts.cn/

在uitls.py中添加图表需要的dates列表,并在views中传出数据。

dates = []
dates.append(date.strftime('%m.%d'))   #strftime将time变成字符串 图表x轴需要数据
dates,read_nums = get_ten_days_read_data(blog_content_type)
context['dates'] = dates

复制并修改页面  ,修改css使其居中。

{% block content %}
<div class="home-all">
<h2 class="home-title">南飞雁 网站</h2>

<!--图表容器-->
<div id="container" style="width:600px;height:300px;"></div>
<script>
//图表配置
var options = {
chart: { type: 'line' }, // 图表类型
title: { text: '近期阅读量'}, //标题
xAxis: { categories:{{ dates | safe }},tickmarkPlacement:'on',}, //X轴 safe进行转义才能显示
yAxis: { title: { text: '阅读量' },labels:{enabled:true} }, //Y轴
series: [{ name: '阅读量', data: {{ read_nums }} }], //数据列
plotOptions:{line:{dataLabels:{enabled:true}}}, //数据标签
legend:{enabled:false}, //图例不显示
credits:{enabled:false}, //不显示版权
};
var chart = Highcharts.chart('container', options);
</script>
</div>
{% endblock %}

 五、热门博客

1.利用阅读量数据排行。

2.uitls文件写方法

def get_today_hot_data(content_type):
    today = timezone.now().date()     #获取今天日期
    read_detail = ReadDetail.objects.filter (content_type=content_type,date=today).order_by('-read_num')  #获取筛选查询对象, #进行倒叙排序
    return read_details[:5]   
def get_yesterday_hot_data(content_type):
today = timezone.now().date()
yesterday = today - datetime.timedelta(days=1)
read_details = ReadDetail.objects.filter(content_type=content_type,date=yesterday).order_by('-read_num')
return read_details[:5]

3.在views的home中引入上个方法,然后传出给home页面。

def home(request):
blog_content_type = ContentType.objects.get_for_model(Blog)
dates,read_nums = get_ten_days_read_data(blog_content_type)

context = {}
context['zz'] = "这是我的主页"
context['dates'] = dates
context['read_nums'] = read_nums
context['today_hot_data'] = get_today_hot_data(blog_content_type)
context['yesterday_hot_data'] = get_yesterday_hot_data(blog_content_type)
return render_to_response('home.html',context)

4.在页面中显示。

<!-- 今天热门博客 -->
<h3>今天热门博客</h3>
<ul>
    {% for  hot_data in today_hot_data %}
        <li>{{ hot_data.object_id }}({{ hot_data.read_num }})</li>    
<li>{{ hot_data.content_object }}({{ hot_data.read_num }})</li>
<li><a href="{% url 'blog_detail' hot_data.content_object.pk %}" >{{hot_data.content_object.title}}</a> \
({{ hot_data.read_num }})<li>
    {% empty %}
<li>今天暂无热门博客</li>
{% endfor %}
</ul>
<h3>昨天热门博客 </h3>
<ul>
{% for hot_data in yesterday_hot_data %}
<li><a href="{% url 'blog_detail' hot_data.content_type.pk %}">{{ hot_data.content_object.title }}</a>({{ hot_data.read_num }})</li>
{% empty %}
<li>昨天暂时没有热门博客</li>
{% endfor %}
</ul>

5.   七天及更多天的显示

由于多天统计需要进行分组统计,所以引用聚合函数values的annotate.

def get_7_days_hot_data(content_type):
    today = timezone.now().date()
    date = today - datetime.timedelta(days=7)
    read_details = ReadDetail.objects \
                             .filter(content_type=content_type,date__lt=today,date__gte=date) \
                             .values('content_type','object_id') \
                             .annotate(read_num_sum=Sum('read_num')) \
                             .order_by('-read_num_sum')
    return read_details[:5]

上面代码中values将content_type和object_id传递给annotate按read_num进行分组统计。

由于values传递回的是一个字典,所以不能再页面显示title和数量。须进行另外处理。

引入ContentType的GenericRelation在模型 Blog增加字段关联到ReadDetail。

read_details = GenericRelation(ReadDetail)  #该字段关联模型,不需迁移。

在views中增加方法,不在utils中设置。上面的方法是传出detail。而下面的方法是传出blog。

def get_n_days_hot_blogs(n):
    today = timezone.now().date()
    date = today - datetime.timedelta(days=n)
    blogs = Blog.object \
                       .filter(read_detail__date__lt=today, read_detail__date__gte=date) \
                       .values('id','title') \
                       .annotate(read_num_sum=Sum('read_detail__read_num')) \
                       .order_by('-read_num_sum')
    return blogs

在def home中引用上面方法   实际上面方法为通用方法。

context['hot_data_for_7_days'] = get_n_days_hot_blogs(7)

修改页面

<h3>30天热门博客 </h3>
<ul>
     {% for hot_data in hot_data_for_30_days %}
          <li><a href="{% url 'blog_detail' hot_data.id %}">{{ hot_data.title }}</a>({{ hot_data.read_num_sum }})</li>
     {% empty %}
          <li>30天内暂时没有热门博客</li>
     {% endfor %}
</ul>

六、缓存设置

从django官网复制数据库缓存设置到settings。(还有其他缓存,如内存缓存。)

#缓存设置
CACHE = {
    'default':{
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',    #'my_cache_table'缓存表,可以自己定义。
    }
}

缓存基本用法,用set保存,get取出。   cache.set(key,value,timeout)     cache.get(key)   timeout以秒为单位。

创建缓存:  python manage.py createcachetable

网页方法中使用缓存。

 1 from django.core.cache import cache
 2 
 3 def home(request):
 4     blog_content_type = ContentType.objects.get_for_model(Blog)
 5     dates,read_nums = get_ten_days_read_data(blog_content_type)
 6 
 7     #获取缓存数据
 8     hot_date_for_7_days = cache.get('hot_date_for_7_days')
 9     if hot_date_for_7_days is None:
10         hot_date_for_7_days = get_n_days_hot_blogs(7)
11         cache.set('hot_date_for_7_days',hot_date_for_7_days,3600)
12     hot_date_for_30_days = cache.get('hot_date_for_30_days')
13     if hot_date_for_30_days is None:
14         hot_date_for_30_days = get_n_days_hot_blogs(30)
15         cache.set('hot_date_for_30_days', hot_date_for_7_days, 3600)
16 
17     context = {}
18     context['zz'] = "这是我的主页"
19     context['dates'] = dates
20     context['read_nums'] = read_nums
21     context['today_hot_data'] = get_today_hot_data(blog_content_type)
22     context['yesterday_hot_data'] = get_yesterday_hot_data(blog_content_type)
23     context['hot_data_for_7_days'] = hot_date_for_7_days
24     context['hot_data_for_30_days'] = hot_date_for_30_days
25     return render_to_response('home.html',context)

 

posted on 2019-01-02 00:47  南飞雁ht  阅读(385)  评论(0编辑  收藏  举报