阅读统计有三个层次实现方法:一是在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)