Django除了咱們常見的admin
、auth
、session
等contrib框架,還包含一個contenttypes
框架,它能夠跟蹤Django項目中安裝的全部模型(model
),爲咱們提供更高級的模型接口。默認狀況下,它已經在settings中了,若是沒有,請手動添加:python
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', # 看這裏!!!! 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
平時仍是儘可能啓用contenttypes框架,由於Django的一些其它框架依賴它:數據庫
contenttypes不是中間件,不是視圖,也不是模板,而是一些"額外的數據表"!因此,在使用它們以前,你須要執行 makemigrations 和 migrate 操做,爲contenttypes框架建立它須要的數據表,用於保存特定的數據。這張表一般叫作django_content_type
,讓咱們看看它在數據庫中的存在方式:django
而表的結構形式則以下圖所示:緩存
一共三個字段:bash
表中的每一條記錄,其實就是Django項目中某個app下面的某個model模型。session
contenttypes框架的核心是 ContentType
模型,它位於django.contrib.contenttypes.models.ContentType
。ContentType實例表示和存儲Django項目中安裝的全部模型的信息。每當你的Django項目中建立了新的模型,會在ContentType
表中自動添加一條新的對應的記錄。app
ContentType
模型的實例具備一系列方法,用於返回它們所記錄的模型類以及從這些模型查詢對象。ContentType
還有一個自定義的管理器,用於進行ContentType
實例相關的ORM操做。框架
每一個 ContentType
實例都有兩個字段(除了隱含的主鍵id)。ui
app_label
: 模型所屬app的名稱。經過模型的app_label
屬性自動獲取,僅包括Python導入路徑的最後部分。例如對於django.contrib.contenttypes
模型,自動獲取的app_label
就是最後的contenttypes
字符串部分。此外,ContentType實例還有一個name
屬性,保存了ContentType
的人類可讀名稱。由模型的verbose_name
屬性值自動獲取。this
例如,對於django.contrib.sites.models.Site
這個模型:
app_label
將被設置爲'sites'(django.contrib.sites
的最後一部分)。model
將被設置爲'site'(小寫)。ContentType
的實例方法每一個ContentType
實例都有一些方法,容許你從ContentType
實例獲取它所對應的模型,或者從該模型中檢索對象:
ContentType.get_object_for_this_type(**kwargs)
提供一系列合法的參數,在對應的模型中,執行一個get()查詢操做,並返回相應的結果。
ContentType.model_class()
返回當前ContentType
實例表示的模型類 。
例如,咱們能夠在 ContentType
表中查詢auth
的 User
模型對應的那條ContentType
記錄:
>>> from django.contrib.contenttypes.models import ContentType >>> user_type = ContentType.objects.get(app_label='auth', model='user') # 獲取到一條記錄 >>> user_type # 注意,這是contenttype的實例對象,不是User表的 <ContentType: user>
而後,就可使用它來查詢特定的 User
,或者訪問User
模型類:
>>> user_type.model_class() # 獲取User類 <class 'django.contrib.auth.models.User'> >>> user_type.get_object_for_this_type(username='Guido') # 獲取某個User表的實例 <User: Guido>
一塊兒使用 get_object_for_this_type()
和model_class()
方法能夠實現兩個特別重要的功能:
app_label
和 model
參數傳入 ContentType
的ORM方法,而後使用model_class()
方法就能夠調用對應模型的ORM操做了。ContentType
關聯起來,做爲將它的實例與特定模型類綁定的方法,並使用這些方法來訪問這些模型類。很差理解,不要緊,日後接着看。
ContentType
還有一個自定義管理器,也就是ContentTypeManager
。它有下面的方法:
clear_cache()
:用於清除內部緩存 。通常不須要手動調用它,Django會在須要時自動調用它。get_for_id(id)
:經過id值查詢一個ContentType
實例。比ContentType.objects.get(pk=id)
的方式更優。get_for_model(model,for_concrete_model = True)
:獲取模型類或模型的實例,並返回表示該模型的ContentType
實例。設置參數for_concrete_model=False
容許獲取代理模型的ContentType。get_for_models(*model,for_concrete_model = True)
: 獲取可變數量的模型類,並返回模型類映射ContentType
實例的字典。get_by_natural_key(app_label, model)
:給定app標籤和模型名稱,返回惟一匹配的ContentType實例。當你只想使用 ContentType
,但不想去獲取模型的元數據以執行手動查找時,get_for_model()
方法特別有用 :
>>> from django.contrib.auth.models import User >>> ContentType.objects.get_for_model(User) # 提供model的名字,查詢出對應的contenttype實例。 <ContentType: user>
既然前面使用GenericForeignKey字段能夠幫咱們正向查詢關聯的對象,那麼就必然有一個對應的反向關聯類型,也就是GenericRelation
字段類型。
使用它能夠幫助咱們從關聯的對象反向查詢對象自己,也就是ORM中的反向關聯。
一樣的,這個字段也不會對數據表產生任何影響,僅僅用於ORM操做!
好比下面的例子,我要從書籤去反向查詢它所對應的標籤:
from django.contrib.contenttypes.fields import GenericRelation # 導入 from django.db import models class Bookmark(models.Model): url = models.URLField() tags = GenericRelation(TaggedItem) # 看這裏!!!!!!!!!!!!!
每一個Bookmark
實例都有一個tags
字段,能夠用來檢索它關聯的TaggedItems
對象:
>>> b = Bookmark(url='https://www.djangoproject.com/') >>> b.save() >>> t1 = TaggedItem(content_object=b, tag='django') >>> t1.save() >>> t2 = TaggedItem(content_object=b, tag='python') >>> t2.save() >>> b.tags.all() # 看這句!!!!!!!!!!!! <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
上面的操做涉及到一個ORM新手很是容易犯的錯誤,那就是外鍵這種一對多的關係,究竟要怎麼寫:
ForeignKey
字段要寫在多的一方,也就是TaggedItem
模型中。tag
對象,就是屬於從一到多的反向查詢。b.tags.all()
這種查詢方法,而不是直接b.tags
!這裏請你本身作一件事,它有助於你理解爲何在ContentType框架中建議使用GenericForeignKey
和GenericRelation
這種關聯字段:
請用Django原生的ORM方法,執行上面的正向查詢和反向查詢操做!並與例子中的操做進行對比!
若是爲GenericRelation
字段提供一個 related_query_name
參數值,好比下面的例子:
tags = GenericRelation(TaggedItem, related_query_name='bookmark')
那麼將能夠從TaggedItem對象過濾查詢關聯的BookMark對象,以下所示:
>>> # 查找全部屬於特定書籤模型的標籤,這些書籤必須包含`django`字符串。 >>> TaggedItem.objects.filter(bookmark__url__contains='django') <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
固然,若是你不添加related_query_name
參數,也能夠手動執行相同類型的查找:
>>> bookmarks = Bookmark.objects.filter(url__contains='django') >>> bookmark_type = ContentType.objects.get_for_model(Bookmark) >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks) <QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
比較一下,三行代碼和一行代碼的區別!
注意:GenericForeignKey
和GenericRelation
字段是匹配的, 若是你在定義GenericForeignKey
的時候使用了另外的content-type
和object-id
名字,那麼在GenericRelation
定義中,你必須作一樣的變化。
例如,若是TaggedItem
模型使用content_type_fk
和 object_primary_key
建立content_object
字段,像下面這樣:
... class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type_fk', 'object_primary_key') #看這裏 ...
那麼在GenericRelation
中,你須要像這樣定義:
tags = GenericRelation( TaggedItem, content_type_field='content_type_fk', object_id_field='object_primary_key', )
另外還要注意:若是你刪除了一個具備GenericRelation
字段的對象,則任何具備GenericForeignKey
字段指向該對象的關聯對象也將被刪除。在上面的示例中,這意味着若是刪除了某個Bookmark對象,則會同時刪除指向該對象的任何TaggedItem對象。
不一樣於普通的ForeignKey
字段, GenericForeignKey
字段不接受on_delete
參數。若是須要,能夠重寫 pre_delete
方法,不細說。
Django的數據庫聚合API可用於 GenericRelation
。例如,你能夠找出全部書籤的標籤數量:
>>> Bookmark.objects.aggregate(Count('tags')) {'tags__count': 3}
除以上內容外,contenttypes框架還提供了django.contrib.contenttypes.forms
模塊用於處理表單相關內容,django.contrib.contenttypes.admin
模塊用於處理管理後臺相關內容,感興趣的能夠自行查閱相關資料。