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中具體實例。