从零开始的django开发生活之博客阅读计数优化(13)
十三、博客阅读计数优化
1、计数功能独立
博客内容与计数字段分离
- 方法一
在models中专门创建一个ReadNum模型,将之前Blog模型中的read_num字段删除,admin中也要删除
class ReadNum(models.Model):
read_num = models.IntegerField(default=0)
blog = models.OneToOneField(Blog, on_delete=models.DO_NOTHING)
在admin中作如下修改:
from .models import BlogType, Blog, ReadNum
@admin.register(ReadNum)
class ReadNumAdmin(admin.ModelAdmin):
list_display = ('read_num', 'blog')
打开shell,查看blog中的方法,发现了readnum,发现可以直接调用readnum方法,这样会显示出ReadNum的一个对象,再对admin修改
@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
list_display = ('title', 'blog_type', 'author', 'readnum', 'created_time', 'last_updated_time')
我们想要显示的并不是对象,但是直接在admin中写readnum.read_num是不会识别的,所以在Blog模型中再加一个方法
def get_read_num(self):
return self.readnum.read_num
admin中readnum改为get_read_num,后台管理界面就能显示出数量,而非对象了
下面在views中写自增算法
将原先的自增改为
if not request.COOKIES.get('blog_%s_readed' % blog_pk):
if ReadNum.objects.filter(blog=blog).count():
readnum = ReadNum.objects.get(blog=blog)
else:
readnum = ReadNum(blog=blog)
readnum.read_num += 1
readnum.save()
前端页面相应部分调用Blog模型中的get_read_num方法即可。
在没有点到的博客部分阅读数部分是空白的,并没有显示0,只有点击一次之后才会显示1,怎么让他默认显示0呢,我们需要对get_read_num方法改进
先在shell模式试验
可以看到之所以没有任何显示,是因为没有点击到的对象,Blog和ReadNum没有关联到一起,抛出ObjectDoesNotExist错误,下面对get_read_num进行改进:
from django.db.models.fields import exceptions
def get_read_num(self):
try:
return self.readnum.read_num
except exceptions.ObjectDoesNotExist as e:
return 0
这样就能默认显示0了。
2、对任意模型计数
单独创建一个app用于各种计数,django中有一个框架ContentType用于记录所有模型,它是一个模型。先在shell模式下进行试验。
可以看到里面的确有我们先前创建的模型
下面删除之前所写的ReadNum模型以及与之相关的方法,转而采用现在的方法,首先创建一个app read_statistics,在models中写入
参考文档中有contentype的用法,我们找到Generic relations,里面的代码直接可以引用
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class ReadNum(models.Model):
read_num = models.IntegerField(default=0)
content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
content_type通过外键指向一个模型,object_id记录指向模型主键值,content_object将前面两个集成为一个通用外键
在settings中注册app,然后进行数据库迁移
admin定制后台:
from django.contrib import admin
from .models import ReadNum
@admin.register(ReadNum)
class ReadNumAdmin(admin.ModelAdmin):
list_display = ('read_num', 'content_object')
查看后台,添加一条记录
接下来使得后台blog中也能看到read_num
先在shell模式下实验:
这里已经取出blog类型,id=36的1条ReadNum查询,下面将取出查询中的第一条记录也是目前为止的仅有记录
在blog/models写入方法:
def get_read_num(self):
ct = ContentType.objects.get_for_model(self)
try:
readnum = ReadNum.objects.get(content_type=ct, object_id=self.pk)
return readnum.read_num
except exceptions.ObjectDoesNotExist as e:
return 0
blog/views,blog_detail方法中写入:
if not request.COOKIES.get('blog_%s_readed' % blog_pk):
ct = ContentType.objects.get_for_model(Blog)
if ReadNum.objects.filter(content_type=ct, object_id=blog.pk).count():
#存在记录就获取记录
readnum = ReadNum.objects.get(blog=blog)
else:
#不存在记录则创建记录
readnum = ReadNum(content_type=ct, object_id=blog.pk)
readnum.read_num += 1
readnum.save()
以上方法是为了通用性的要求,所以我们对其进行封装,以后可以直接调用,先对模型中的计数方法get_read_num进行封装
将此方法封装为read_statistics/models中的一个类
class ReadNumExpand():
def get_read_num(self):
ct = ContentType.objects.get_for_model(self)
try:
readnum = ReadNum.objects.get(content_type=ct, object_id=self.pk)
return readnum.read_num
except exceptions.ObjectDoesNotExist as e:
return 0
再导入相关的库,blog/models中导入ReadNumExpand类,Blog继承ReadNumExpand类,封装完成
再对views中的方法封装
在read_statistic新建python文件utils.py,将views中的计数方法剪切到这个文件,并做适当修改
from django.contrib.contenttypes.models import ContentType
from .models import ReadNum
def read_statistics_once_read(request, obj):
ct = ContentType.objects.get_for_model(obj)
key = "%s_%s_read" % (ct.model, obj.pk)
if not request.COOKIES.get(key):
if ReadNum.objects.filter(content_type=ct, object_id=obj.pk).count():
#存在记录就获取记录
readnum = ReadNum.objects.get(content_type=ct, object_id=obj.pk)
else:
#不存在记录则创建记录
readnum = ReadNum(content_type=ct, object_id=obj.pk)
readnum.read_num += 1
readnum.save()
return key
因为我们需要cookie的键名,所以返回key。ct.models应用了contenttype模型的一个属性,可以看文档有说明
在blog/views中导入Python文件 from read_statistics.utils import read_statistics_once_read
获取键名 read_cookie_key = read_statistics_once_read(request, blog)
替换原参数 response.set_cookie(read_cookie_key, 'true')
大功告成!