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