优化上面的两个缺点:
优化缺点一:后台编辑博客可能影响数据
将博客内容和计数字段分开,即按照ACID原则进行设计:将模型重新分类
python manage.py makemigrations;
python manage.py migrate;
然后admin.py中新建一个阅读数模型并注册
代码如下:先修改views.py:
...
from .models import Blog, BlogType,ReadNum
...
...
def blog_detail(request, blog_pk):
"""
打开某一个博文后的显示内容
"""
blog = get_object_or_404(Blog, pk=blog_pk)
# 因为没有设置set_cookie的过期时间,所以默认是当关闭浏览器时删除cookie,所以刷新失效,只有重新打开才有效!
if not request.COOKIES.get('blog_%s_readed' % blog_pk):
# blog.readed_num += 1
# blog.save()
if ReadNum.objects.filter(blog=blog).count(): # 如果有阅读量则获取数量
readnum = ReadNum.objects.get(blog=blog)
else: # 如果没有阅读量则创建对象,处理异常
readnum = ReadNum()
readnum.blog = blog
# 计数加一
readnum.read_num += 1
readnum.save()
...
修改blog_detail.html
阅读量:{{ blog.get_read_num }}
修改blog_list.html
阅读量:{{ blog.get_read_num }}
修改命名,修改部分read_num为get_read_num
新问题:现在的detail_list中的阅读量如果没有值会返回空,应该要返回零
修改如下:处理异常
# views.py
class Blog(models.Model):
...
def get_read_num(self):
...
try:
return self.readnum.read_num
except Exception as e:
return 0
...
# views.py
from django.db.models.fields import exceptions
...
class Blog(models.Model):
def get_read_num(self):
"""
引导调用ReadNum中的read_num方法
"""
try:
return self.readnum.read_num
except exceptions.ObjectDoesNotExist:
return 0
功能要求:我们封装的计数功能呢可以被调用从而对任何模型进行计数:使用ContentType,并将此模型封装成一个app来使用
为了使用新的contenttype来代替自己定义的计数器,先将该模型类以及相关引用注释起来
models.py
class Blog(models.Model):
......
'''
# models.py中的get_read_num方法去掉
def get_read_num(self):
"""
引导调用ReadNum中的read_num方法
"""
try:
return self.readnum.read_num
except exceptions.ObjectDoesNotExist:
return 0
'''
......
'''
# 为了使用新的contenttype来代替自己定义的计数器,先将该模型类注释起来
class ReadNum(models.Model):
"""
计数字段,优化后台编辑博客可能影响数据缺点
"""
read_num = models.IntegerField(default=0)
# 将计数字段与博客模型相关联,当删除博文时不要对博客的计数字段做任何事情
# 外键:一对多或者多对一使用,如果是一对一使用OneToOneField;这里因为是一个字段对应一个博文,所以是一对一
blog = models.OneToOneField(Blog,on_delete=models.DO_NOTHING)
'''
然后admin中的
from django.contrib import admin
from .models import BlogType,Blog # 去掉ReadNum
......
@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
list_display = ('title','blog_type','author','created_time','last_updated_time')
'''
@admin.register(ReadNum)
class ReadNumAdmin(admin.ModelAdmin):
list_display = ('read_num','blog')
'''
views.py中
from .models import Blog, BlogType # 去掉ReadNum
...
'''
def get_blogs_list_common_data(request, blogs_all_list):
# 加上省略页码标记
if page_range[0] - 1 >= 2:
page_range.insert(0, '...') # 0表示位置,后面的省略号表示要插入的内容
if paginator.num_pages - page_range[-1] >= 2:
page_range.append('...') # 使用append来将省略号插入到页码的最后
# 加上首页和尾页
if page_range[0] != 1:
page_range.insert(0, 1)
if page_range[-1] != paginator.num_pages:
page_range.append(paginator.num_pages)
'''
......
def blog_detail(request, blog_pk):
"""
打开某一个博文后的显示内容
"""
blog = get_object_or_404(Blog, pk=blog_pk)
# 因为没有设置set_cookie的过期时间,所以默认是当关闭浏览器时删除cookie,所以刷新失效,只有重新打开才有效!
if not request.COOKIES.get('blog_%s_readed' % blog_pk):
"""
# blog.readed_num += 1
# blog.save()
if ReadNum.objects.filter(blog=blog).count(): # 如果有阅读量则获取数量
readnum = ReadNum.objects.get(blog=blog)
else: # 如果没有阅读量则创建对象,处理异常
readnum = ReadNum()
readnum.blog = blog
# 计数加一
readnum.read_num += 1
readnum.save()
"""
pass
......
然后python manage.py runserver来运行服务器检查是否有漏删错删的,如果正常则正式开始创建app:
创建一个app:python manage.py startapp read_statistics
read_statistics中的models中新建一个模型,然后关联内置模块ContentType:
# read_statistics/models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
# Create your models here.
class ReadNum(models.Model):
read_num = models.IntegerField(default=0) # 规定read_num的类型为整型,如果不指定默认为0
# 创建两个属性分别为content_type和object_id,前者外键关联ContentType app(django自带)
content_type = models.ForeignKey(ContentType,on_delete=models.DO_NOTHING)
object_id = models.PositiveIntegerField() # object_id类型为自动增加的整型数字类型
content_object = GenericForeignKey('content_type','object_id') # 然后将上面两个属性变成一个通用的外键
注册应用:setting
# mysite/settings.py
......
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'ckeditor',
'ckeditor_uploader',
'blog',
'read_statistics',
]
......
python manage.py makemigrations
python manage.py migrate
admin中的写要显示的内容:
# read_statistics/admin.py
from django.contrib import admin
from .models import ReadNum
# Register your models here.
@admin.register(ReadNum)
class ReadNumAdmin(admin.ModelAdmin):
"""
计数app功能
"""
list_display = ('read_num','content_object')
python manage.py runserver
效果如图:
那么现在如何能够通过上面的计数app中的ReadNum模型来获取里面的Read num数量然后展示在blog_list.html和blog_detail.html中呢?方法如下:
先尝试通过shell模式来获取ContentType内容
python manage.py shell
from read_statistics.models import ReadNum
from blog.models import Blog
from django.contrib.contenttypes.models import ContentType
ContentType.objects.filter(model='blog')
ContentType.objects.get_for_model(Blog)
ct = ContentType.objects.get_for_model(Blog)
ct
# 此时的ct就是要提取的ContentType数据
# 在提取主键值:
blog = Blog.objects.first()
blog.pk
# 最后通过上面两个参数拿取对应的数据
ReadNum.objects.filter(content_type=ct,object_id=blog.pk)
# 具体查询其里面的内容:
rn = ReadNum.objects.filter(content_type=ct,object_id=blog.pk)[0]
rn
rn.read_num
quit()
blog/models.py
from django.contrib.contenttypes.models import ContentType
from read_statistics.models import ReadNum
...
class Blog(models.Model):
...
def get_read_num(self):
"""
用来获取read_statistics中的计数数据的方法
"""
ct = ContentType.objects.get_for_model(Blog) # 获取contenttype:blog|blog
# 获取对应的计数值
readnum = ReadNum.objects.get(content_type=ct,object_id=self.pk)
return readnum.read_num
...
blog/admin.py
python manag.py runserver
效果如下:
但是10下面不存在显示的是-而不是0,使用try-except来处理异常
# blog/models.py
...
class Blog(models.Model):
...
def get_read_num(self):
"""
用来获取read_statistics中的计数数据的方法
"""
try:
ct = ContentType.objects.get_for_model(Blog)
readnum = ReadNum.objects.get(content_type=ct, object_id=self.pk)
return readnum.read_num
except exceptions.ObjectDoesNotExist:
return 0
from django.contrib.contenttypes.models import ContentType
from read_statistics.models import ReadNum
......
def blog_detail(request, blog_pk):
"""
打开某一个博文后的显示内容
"""
blog = get_object_or_404(Blog, pk=blog_pk)
# 因为没有设置set_cookie的过期时间,所以默认是当关闭浏览器时删除cookie,所以刷新失效,只有重新打开才有效!
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(content_type=ct, object_id=blog.pk)
else: # 如果没有阅读量则创建对象,处理异常
readnum = ReadNum(content_type=ct, object_id=blog.pk)
# 计数加一
readnum.read_num += 1
readnum.save()
......
效果如下:
现在blog/models.py中的Blog还是太臃肿了,想要把get_read_num封装出来做成一个父类,然后让Blog类来继承get_read_num,就可以精简,代码如下:
# read_statistics/models.py
from django.db.models.fields import exceptions # 里面包含各种错误
......
class ReadNumExpandMethod():
"""
用来封装read_statistics中的计数方法
"""
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
class Blog(models.Model,Test):
......
# blog/models.py
from read_statistics.models import ReadNumExpandMethod
......
class Blog(models.Model,ReadNumExpandMethod):
......
没有bug,真开心
然后同样将blog/views.py中的blog_detail中的判断内容封装在read_statistics/utils.py中:
新建utils.py
# read_statistics/utils.py
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)
# 因为没有设置set_cookie的过期时间,所以默认是当关闭浏览器时删除cookie,所以刷新失效,只有重新打开才有效!
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
# blog/views.py
from read_statistics.utils import read_statistics_once_read
......
def blog_detail(request, blog_pk):
read_cookie_key = read_statistics_once_read(request, blog)
......
response.set_cookie(read_cookie_key,'true')
...
搞定