一、什么是Django ContentTypes?
Django ContentTypes是由Django框架提供的一個核心功能,它對當前項目中所有基於Django驅動的model提供了更高層次的抽象接口。主要用來創建模型間的通用關系(generic relation)。
進一步了解ContentTypes可以直接查閱以下這兩個鏈接:
- Django official documentation:The contenttypes framework
- stackoverflow: How exactly do Django content types work?
二、Django ContentTypes做了什么?
當創建一個django項目時,可以看到在默認的INSTALL_APPS已經包含了django.contrib.contenttypes。
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', ]
注意:django.contrib.contenttypes是在django.contrib.auth之后,這是因為auth中的permission系統是根據contenttypes來實現的。
導入contenttypes組件:
from django.contrib.contenttypes.models import ContentType
查看django.contrib.contenttypes.models.ContentType類的內容:
class ContentType(models.Model): app_label = models.CharField(max_length=100) model = models.CharField(_('python model class name'), max_length=100) objects = ContentTypeManager() class Meta: verbose_name = _('content type') verbose_name_plural = _('content types') db_table = 'django_content_type' unique_together = (('app_label', 'model'),) def __str__(self): return self.name
可以看到ContentType就是一個簡單的django model,而且它在數據庫中的表的名字為django_content_type。
在第一次對Django的model進行migrate之后,就可以發現在數據庫中出現了一張默認生成的名為django_content_type的表。
如果沒有建立任何的model,默認django_content_type是前六項:
django_content_type記錄了當前的Django項目中所有model所屬的app(即app_label屬性)以及model的名字(即model屬性)。
django_content_type並不只是記錄屬性這么簡單.了contenttypes是對model的一次封裝,因此可以通過contenttypes動態的訪問model類型,而不需要每次import具體的model類型。
1、ContentType實例提供的接口
- ContentType.model_class()
獲取當前ContentType類型所代表的模型類
- ContentType.get_object_for_this_type()
使用當前ContentType類型所代表的模型類做一次get查詢
2、ContentType管理器(manager)提供的j接口
- ContentType.objects.get_for_id()
- 通過id尋找ContentType類型,這個跟傳統的get方法的區別就是它跟get_for_model共享一個緩存,因此更為推薦。
- ContentType.objects.get_for_model()
- 通過model或者model的實例來尋找ContentType類型
三、Django ContentTypes框架使用場景
1、設計模型(創建表結構)
假設我們創建如下模型,里面包含學位課程、專題課程、價格策略。
價格策略既可以是專題課程的價格策略,也可以是學位課程的價格策略。需要在pricepolicy對象里添加非常多的ForeignKey。示例如下所示:
class Food(models.Model): """ id title 1 面包 2 牛奶 """ title = models.CharField(max_length=32) # 不會生成字段 只用於反向查詢 coupons = GenericRelation(to="Coupon") class Fruit(models.Model): """ id title 1 蘋果 2 香蕉 """ title = models.CharField(max_length=32) # 如果有40張表,則每一個都要建立外鍵關系 class Coupon(models.Model): """ id title food_id fruit_id 1 面包九五折 1 null 2 香蕉滿10元減5元 null 2 """ title = models.CharField(max_length=32) food = models.ForeignKey(to="Food") fruit = models.ForeignKey(to="Fruit")
這樣做很傻,會造成代碼重復和字段浪費。有一種優化的方案是:用兩個字段去定位對象不用去創建多個外鍵關系。
# 方法二:用兩個字段去定位對象不用去創建多個外鍵關系 class Coupon(models.Model): """ id title table_id object_id(對應表對應對象的ID) 1 面包九五折 1 1 2 香蕉滿10元減5元 2 2 """ title = models.CharField(max_length=32) table = models.ForeignKey(to="Table") # 與table表建立外鍵關系 object_id = models.IntegerField() # 由object_id定位到表中的某一個對象,但沒有建立外鍵關系 class Table(models.Model): """ id app_name table_name 1 demo food 2 demo fruit """ app_name = models.CharField(max_length=32) table_name = models.CharField(max_length=32)
最好的方式是,只有當你需要對某個對象或模型進行評論時,才創建pricepolicy與那個模型的關系。示例如下所示:
# 方法三:基於ContentTypes創建表結構 class Coupon(models.Model): title = models.CharField(max_length=32) # 優惠券名稱 # 第一步:與ContentType表綁定外鍵關系 content_type = models.ForeignKey(to=ContentType, on_delete=None) # 第二步:建立對象id object_id = models.IntegerField() # 第三步:content_type和object_id綁定外鍵關系 content_object = GenericForeignKey("content_type", "object_id")
學位課程、專題課程、價格策略基於django contenttypes創建表結構如下所示:
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation class DegreeCourse(models.Model): """學位課程""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255, verbose_name="縮略圖") brief = models.TextField(verbose_name="學位課程簡介", ) class Course(models.Model): """專題課程""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255) # 不會在數據庫生成列,只用於幫助你進行查詢 policy_list = GenericRelation("PricePolicy") class PricePolicy(models.Model): """價格策略表""" content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 關聯course or degree_course object_id = models.PositiveIntegerField() # 正整數PositiveInteger # GenericForeignKey不會在數據庫生成列,只用於幫助你進行添加和查詢 content_object = GenericForeignKey('content_type', 'object_id') # 將兩個字段放在這個對象中 # 周期 valid_period_choices = ( (1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1個月'), (60, '2個月'), (90, '3個月'), (180, '6個月'), (210, '12個月'), (540, '18個月'), (720, '24個月'), ) # 價格 valid_period = models.SmallIntegerField(choices=valid_period_choices) price = models.FloatField()
(1)GenericForeignKey
Django ContentType提供了一種GenericForeignKey的類型,通過這種類型可以指定content_object。
GenericForeignKey不會在數據庫生成列,只用於幫助你進行添加和查詢。
(2)GenericRelation
GenericRelation不會在數據庫生成列,只用於幫助你進行查詢。
(3)pricepolicy里有三個重要字段
-
content_type: 內容類型,代表了模型的名字(比如Course,DegreeCourse)
-
object_id: 傳入對象的id
-
content_object: 傳入的實例化對象,其包含兩個屬性content_type和object_id。
2、視圖操作
(1)在價格策略表(pricepolicy)中添加數據
from django.shortcuts import render, HttpResponse from app01 import models from django.contrib.contenttypes.models import ContentType def test(request):
# 方法一: models.PricePolicy.objects.create( valid_period=7, price=6.6, content_type=ContentType.objects.get(model="course"), object_id=1 ) # 方法二: models.PricePolicy.objects.create( valid_period=14, price=9.9, content_object=models.Course.objects.get(id=1) # 'content_type', 'object_id' ) return HttpResponse("...")
訪問http://127.0.0.1:8000/test/ 后,查看價格策略表保存的數據:
(2)根據某個價格策略對象,找到其對應的表和數據
這里以查看管理課程名稱為例:
from django.shortcuts import render, HttpResponse from app01 import models from django.contrib.contenttypes.models import ContentType def test(request): price = models.PricePolicy.objects.get(id=2) print(price.content_object.name) # 21天入門python 即自動幫忙找到對應的對象 return HttpResponse("...")
(3)找到某個課程關聯的所有價格策略
注意這里需要利用到GenericRelation。
from django.shortcuts import render, HttpResponse from app01 import models from django.contrib.contenttypes.models import ContentType def test(request): obj = models.Course.objects.get(id=1) print(obj.policy_list.all()) # <QuerySet [<PricePolicy: PricePolicy object (1)>, <PricePolicy: PricePolicy object (2)>]> return HttpResponse("...")
查詢結果是一個QuerySet對象,如果想讓查詢結果更加清楚:
from django.shortcuts import render, HttpResponse from app01 import models from django.contrib.contenttypes.models import ContentType def test(request): obj = models.Course.objects.get(id=1) for item in obj.policy_list.all(): print(item.id, item.valid_period, item.price) """ 1 7 6.6 2 14 9.9 """ return HttpResponse("...")
四、總結ContentType
如果一張表與N張表動態地要創建Foreign Key關系,如果創建 Foreign key 將生成很多列,這樣很多都是空的,造成嚴重浪費空間。只要是一張表要和多張表建立外鍵關系的情況,都可以考慮使用django的ContentType組件來幫助實現,以簡化表結構的設計。
ContentType組件的作用:可以通過兩個字段(GenericForeignKey, GenericRelation),在保證列數不變的情況下,讓一張表和N張表做Foreign Key關系。