优化上面的两个缺点:

优化缺点一:后台编辑博客可能影响数据

将博客内容和计数字段分开,即按照ACID原则进行设计:将模型重新分类
image.png
具体操作:修改models.py:
image.png
先去掉admin中的read\_num
image.png
python manage.py makemigrations;

python manage.py migrate;

然后admin.py中新建一个阅读数模型并注册
image.png
image.png
image.png
从这里修改read_num并不会影响对应的最后更新时间:
image.png
但是admin中缺少readnum一栏来显示阅读量,处理如下
image.png
改进:因为read\_num的数值已经写入了数据库,只需要读取即可,而因为read\_num不在blog的模型中,而是在另一个模型类readnum中,如果直接在admin中的BlogAdmin来读取另一个类中的方法会读取不到,如果写做readnum.read\_num也是功能限制,所以需要在models.py中的blog类里面新加一个函数:read\_num来引导调用该方法:
image.png
然后修改admin.py中的BlogAdmin类:
image.png
效果如下:
image.png
另外blog\_list的阅读量和blog\_detail中的阅读量数据并没有获取得到!
image.png
代码如下:先修改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

<li>阅读量:{{ blog.get_read_num }}</li>

修改blog\_list.html

阅读量:{{ blog.get_read_num }}

修改命名,修改部分read_num为get_read_num
新问题:现在的detail_list中的阅读量如果没有值会返回空,应该要返回零
image.png
修改如下:处理异常

# views.py
class Blog(models.Model):
    ...
    def get_read_num(self):
    ...
    try:
        return self.readnum.read_num
    except Exception as e:
        return 0
    ...

image.png
为了增强代码健壮性,能够处理各种bug,修改代码如下:

# 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来使用
image.png
为了使用新的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

效果如图:
image.png
image.png
增加object id时发现没有object id,需要自己去找:
image.png
然后发现blog中也没有显示id,所以在blog/admin.py中的BlogAdmin的List\_display中增加id项目:
image.png
效果如图:
image.png
操作如下:
image.png
那么现在如何能够通过上面的计数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
image.png
python manag.py runserver

效果如下:
image.png
但是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

image.png
修改视图函数:blog/views.py

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()
    ......

效果如下:
image.png
image.png
现在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')
    ...

搞定


笨小孩
20 声望3 粉丝

你,要怎样度过这一生?有的人二十岁已经死了,有的人七十岁还在发现生命的可能,有人终其一生,不知道自己要的是什么;有人简单执拗,终其一生;忠于自我未必有结果,坚持努力也不一定换来成功,但有天,回望过...