Django contenttypes的作用

 

1.django.contrib.contenttypes

Django創建項目後,在settings.py中默認加載了以下幾個app:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

其中有個app爲django.contrib.contenttypes,在使用了python manage.py makemigrations 和 python manage.py migrate命令後,這個app會在數據庫創建表 django_content_type,數據表字段如下:

 

django_content_type表存儲了用戶所提交的所有model名稱與model所在的app名稱

其中的model字段爲用戶定義的模型類的名稱,app_label爲該模型所在app名稱

2.實例分析

那麼這個表的意義在哪?我們可以用這個表來幹嘛?引用網上諸多博文使用的例子,現在有四個model,我們自己創建的Post,Picture和Comment,加上django原生的User。

django原生的User model即用戶表

Comment爲用戶提交的評論表

Picture爲存儲圖片信息的表

Post爲存儲文章信息的表

 

場景如下:用戶可以對圖片或者文章提交評論,那麼Comment中首先要存儲的是,該評論對應的用戶,再就是該評論是針對哪張圖片或者哪篇文章的評論,代碼如下:

from django.db import models
from django.contrib.auth.models import User


class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    body = models.TextField(blank=True)

class Picture(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    body = models.TextField(blank=True)

class Comment(models.Model):
    author = models.ForeignKey(to=User, on_delete=models.CASCADE)
    body = models.TextField(blank=True)
    pic = models.ForeignKey(to=Picture, on_delete=models.CASCADE, null=True)
    post = models.ForeignKey(to=Post, on_delete=models.CASCADE, null=True)

爲了建立Comment與Post或者Picture的關係,我們就需要創建外鍵,如果該Comment與Picture有關聯,那麼pic字段是有值的,而其餘的關聯字段爲null。這樣實現的缺點是,每增加一種被評論的類型,在Comment model中就需要多新增一個外鍵,擴展性極差。

3.使用contenttypes來解決問題

如果使用contenttypes就不需要再使用多個Foreignkey,因爲在django_content_type表已經存儲了app名和model名,所以我們只需要將我們創建的模型與django_content_type表關聯起來,然後再存儲一個實例 id 即可準確定位到某個app中某個模型的具體實例。代碼如下:

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation


class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    body = models.TextField(blank=True)
    comments = GenericRelation('Comment')  # 使用GenericRelation可以建立該類與Comment類的反向關聯,
                                           # 那麼可以通過該類的實例來創建對應的comment

class Picture(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    body = models.TextField(blank=True)
    comments = GenericRelation('Comment')

class Comment(models.Model):
    #  user_name = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    author = models.ForeignKey(to=User, on_delete=models.CASCADE)

    body = models.TextField(blank=True)

    # step1
    # ForeignKey爲外鍵,即在Comment類中,content_type對應了django_content_type表中的某個對象
    content_type = models.ForeignKey(to=ContentType, on_delete=models.CASCADE)  # 與數據庫中的django_content_type表關聯起來

    # step2
    object_id = models.PositiveIntegerField()  # 正整數類型,在被關聯的表中的實例id,以此來定位具體的實例

    # step3
    content_object = GenericForeignKey()  # 根據content_type和object_id來指向一個模型中的具體實例

 

首先看Comment這個模型,與contenttypes相關的就三個字段:content_tpye   object_id    content_object

content_type字段爲外鍵,指向ContentType這個模型,也就是上面提到的django_content_type表

object_id爲一個整數,存儲了實例id,實例id不理解可以看下面的數據表結構分析

content_object爲GenericForeignKey類型,主要作用是根據content_type字段和object_id字段來定位某個模型中具體某個實例

所以實際上在使用GenericForeignKey()函數時需要傳入兩個參數,即content_type和object_id字段的名字,注意是名字不是value。但是默認在該函數的構造函數中已經賦予了默認值,即"content_type"和"object_id",所以如果我們定義的變量名爲這兩個,那麼我們可以省略該參數。該字段不會存儲到數據庫中,在查詢的時候ORM會用到。

GenericForeignKey構造函數如下:

def __init__(self, ct_field='content_type', fk_field='object_id', for_concrete_model=True):
    self.ct_field = ct_field
    self.fk_field = fk_field
    self.for_concrete_model = for_concrete_model
    self.editable = False
    self.rel = None
    self.column = None

 

將模型同步到數據庫,並創建幾個測試數據,使用manage.py自帶的shell可以輕鬆的創建測試數據。

python manage.py shell

from app1.models import Post, Picture, Comment  # app1即爲你自己所創建的app名
from django.contrib.auth.models import User  # django原生用戶模型
user = User.objects.get(username='admin-x')
post = Post.objects.create(author=user, body='admin-x post test')
picture = Picture.objects.create(author=user, body='admin-x pic test')

pic = Picture.objects.get(id=1) # 獲取id爲1的pic對象
c1 = Comment.objects.create(author=user, body='test', content_object=pic) # 創建一個評論並與上述的pic對象關聯起來

 

還可以注意到,在Post和Picture模型中我們定義了comments = GenericRelation('Comment')

GenericRelation的作用是建立該model與Comment的反向關聯,我們這樣就可以通過Post實例或者Picture實例直接查詢屬於該實例的所有評論,或者創建一個屬於該實例的新評論。

pic = Picture.objects.get(id=1) # 獲取id爲1的pic對象

pic.comments.create(author=user, body='test02')  # 創建一個屬於pic對象的評論

pic.comments.all()  # 獲取屬於pic對象的所有評論

 

數據表結構分析,這樣應該一目瞭然了吧

實際上在數據表中,根據content_type_id就可以在django_content_type這個表中定位到哪個app的哪個model,然後根據object_id定位到該model中具體實例。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章