天天看點

部落格閱讀計數優化

為了解決上一篇寫到的計數功能的單一,我們把把計數功能獨立,把部落格内容和計數字段分開,再通過外鍵關聯

class Blog(models.Model):
    title = models.CharField(max_length=50)
    blog_type = models.ForeignKey(BlogType,on_delete=models.CASCADE)
    content = RichTextUploadingField()
    author = models.ForeignKey(User,on_delete=models.CASCADE)

    created_time = models.DateTimeField(auto_now_add=True)
    last_updated_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return '<Blog: %s>' %self.title

    class Meta:
        ordering = ['-created_time']

      
class ReadNum(models.Model):
    read_num = models.IntegerField(default=0)
    blog = models.OneToOneField(Blog,on_delete=models.CASCADE)
把原來Blog模型中的計數字段删除,新增ReadNum類,并于Blog一對一關聯,再同步資料庫
admin中顯示      
@admin.register(ReadNum)
class ReadNumAdmin(admin.ModelAdmin):
    list_display = ('read_num','blog')
此時在admin背景修改部落格時就不會影響到部落格的計數。

為了在背景blogs中看到每篇部落格自己的計數,可以在Blog模型寫一個方法,然後admin中調用這個方法。模型中的方法在admin和前端可以調用。
因為是一對一關聯,此時反向查詢,基于對象,隻需對象.類名小寫得到目标對象
利用錯誤如果不存在記錄則傳回0,這裡是不管什麼錯誤,也可以導入錯誤的集合,錯誤的集合裡有對象不存在的情況。      
def get_read_num(self):
    try:
        return self.readnum.read_num
    except Exception as e:
        return 0

      
from django.db.models.fields import exceptions      
def get_read_num(self):
    try:
        return self.readnum.read_num
    except exceptions.ObjectDoesNotExist:
        return 0      
在前端顯示的需要修改vires和前端頁面,views中計數+1因為部落格和計數的模型分離,需要判斷對應的記錄是否存在      
if ReadNum.objects.filter(blog=blog).count(): #判斷這條部落格對應的計數模型有沒有
    #存在記錄,擷取這個計數對象
    readnum = ReadNum.objects.get(blog=blog)
else:
    #不存在對應的記錄,建立
    readnum = ReadNum(blog=blog)
#計數加1
readnum.read_num +=1
readnum.save()


2.可以對任意模型計數
計數——>部落格、教程、公告、其他等等  都需要進行閱讀的計數,如果我們要對一個新的模型進行計數,此時計數是一對一綁定的,那麼隻能再寫一次代碼,每多一個需要計數的,
就需要多建立一個模型。
可以讓計數模型——>關聯哪個模型、對應主鍵值,這樣一個模型可以統計各種模型的資料——>Django中有ContentType自動把這些東西記錄下來,它記錄了我們這個項目所有的模型,

>>> from django.contrib.contenttypes.models import ContentType>>> ContentType      

<class 'django.contrib.contenttypes.models.ContentType'>

>>> ContentType.objects.all()

<QuerySet [<ContentType: log entry>, <ContentType: group>, <ContentType: permission>, <ContentType: user>, <ContentType: blog>, <ContentType: blog type>, <Conten

tType: read num>, <ContentType: content type>, <ContentType: session>]>

删除原有計數模型,建立一個read_statistics app

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)

    #将您的模型ForeignKey 設為ContentType 通過ContentType找到具體的模型
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField() #記錄對應模型的主鍵值 該字段可以存儲您将要關聯的模型中的主鍵值
    #給您的模型一個 GenericForeignKey,并為其傳遞上述兩個字段的名稱。如果将這些字段分别命名
    # 為“ content_type”和“ object_id”,則可以忽略這些-這些是預設字段名稱 GenericForeignKey。
    content_object = GenericForeignKey('content_type', 'object_id')

在admin中注冊顯示      
from django.contrib import admin
from .models import ReadNum

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

添加一條記錄後,在shell中調試      

>>> from read_statistics.models import ReadNum

>>> from blog.models import Blog

>>> from django.contrib.contenttypes.models import ContentType

>>> ContentType.objects.get_for_model(Blog)

<ContentType: blog>

>>> ct = ContentType.objects.get_for_model(Blog)

>>> blog = Blog.objects.first()

>>> blog

<Blog: <Blog: for 30>>

>>> blog.pk

36

>>> ReadNum.objects.filter(content_type=ct,object_id=blog.pk)

<QuerySet [<ReadNum: ReadNum object (1)>]>

>>> rn = ReadNum.objects.filter(content_type=ct,object_id=blog.pk)[0]

>>> rn

<ReadNum: ReadNum object (1)>

>>> rn.read_num

10

根據shell擷取資料的方法在部落格模型中重寫擷取部落格閱讀數量的方法

def get_read_num(self):
    try:
        ct = ContentType.objects.get_for_model(self) #擷取模型類或模型執行個體,然後傳回ContentType代表該模型的執行個體 <ContentType: blog>
        readnum = ReadNum.objects.get(content_type=ct, object_id=self.pk)
        return readnum.read_num
    except exceptions.ObjectDoesNotExist:
        return 0      
然後修改下views中的計數規則      
def blog_detail(request, blog_pk):
    blog = get_object_or_404(Blog, pk=blog_pk)
    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)
        #計數加1
        readnum.read_num +=1
        readnum.save()
此時就可以了,但是還可以進行通用性處理,把該封裝的代碼封裝到計數app裡面。通過類的繼承,把部落格的擷取閱讀數量方法放到計數模型中,并建立一個類,
之後讓部落格模型繼承這個類。      
class ReadNumExpandMethod():
    def get_read_num(self):
        try:
            ct = ContentType.objects.get_for_model(self) #擷取模型類或模型執行個體,然後傳回ContentType代表該模型的執行個體
            readnum = ReadNum.objects.get(content_type=ct, object_id=self.pk)
            return readnum.read_num
        except exceptions.ObjectDoesNotExist:
            return 0
把views中的計數規則放到計數app的工具檔案中寫成一個方法,讓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)
        # 計數加1
        readnum.read_num +=1
        readnum.save()
    return key

可以傳過來一個對象,通過對象擷取模型類或模型執行個體,然後傳回ContentType代表該模型的執行個體,擷取主鍵,
      

>>> ct

<ContentType: blog>

>>> ct.model

'blog'

最後傳回cookie的鍵

在部落格的視圖中調用該方法

read_cookie_key = read_statistics_once_read(request, blog)