Django contenttypes框架 詳解

contenttypes框架

Django除了咱們常見的adminauthsession等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的一些其它框架依賴它:數據庫

  • Django的admin框架用它來記錄添加或更改對象的歷史記錄。
  • Django的auth認證框架用它將用戶權限綁定到指定的模型。

contenttypes不是中間件,不是視圖,也不是模板,而是一些"額外的數據表"!因此,在使用它們以前,你須要執行 makemigrations 和 migrate 操做,爲contenttypes框架建立它須要的數據表,用於保存特定的數據。這張表一般叫作django_content_type,讓咱們看看它在數據庫中的存在方式:django

ContentTypes數據表

而表的結構形式則以下圖所示:緩存

ContentTypes數據表內容

一共三個字段:bash

  • id:表的主鍵,沒什麼好說的
  • app_label:模型所屬的app的名字
  • model:具體對應的模型的名字。

表中的每一條記錄,其實就是Django項目中某個app下面的某個model模型。session

概述

contenttypes框架的核心是 ContentType 模型,它位於django.contrib.contenttypes.models.ContentType。ContentType實例表示和存儲Django項目中安裝的全部模型的信息。每當你的Django項目中建立了新的模型,會在ContentType表中自動添加一條新的對應的記錄。app

ContentType模型的實例具備一系列方法,用於返回它們所記錄的模型類以及從這些模型查詢對象。ContentType 還有一個自定義的管理器,用於進行ContentType實例相關的ORM操做。框架

ContentType模型

每一個 ContentType 實例都有兩個字段(除了隱含的主鍵id)。ui

  • app_label: 模型所屬app的名稱。經過模型的app_label屬性自動獲取,僅包括Python導入路徑的最後部分。例如對於django.contrib.contenttypes模型,自動獲取的app_label就是最後的contenttypes字符串部分。
  • model:模型類的名稱。(小寫)

此外,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表中查詢authUser模型對應的那條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_labelmodel參數傳入 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>

反向通用關係GenericRelation字段

既然前面使用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新手很是容易犯的錯誤,那就是外鍵這種一對多的關係,究竟要怎麼寫:

  • 首先,要想明白是一個標籤能夠對應多個書籤,仍是一個書籤能夠對應多個標籤?
  • 這裏定義的是一個書籤bookmark能夠對應多個標籤tag,書籤是‘一’方,標籤是‘多’方。
  • 因此,ForeignKey字段要寫在多的一方,也就是TaggedItem模型中。
  • 那麼對於Bookmark模型對象,去查詢關聯的tag對象,就是屬於從一到多的反向查詢。
  • 這也就是上面咱們最後爲何是使用b.tags.all()這種查詢方法,而不是直接b.tags!

這裏請你本身作一件事,它有助於你理解爲何在ContentType框架中建議使用GenericForeignKeyGenericRelation這種關聯字段:

請用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>]>

比較一下,三行代碼和一行代碼的區別!

注意:GenericForeignKeyGenericRelation字段是匹配的, 若是你在定義GenericForeignKey的時候使用了另外的content-typeobject-id名字,那麼在GenericRelation定義中,你必須作一樣的變化。

例如,若是TaggedItem模型使用content_type_fkobject_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模塊用於處理管理後臺相關內容,感興趣的能夠自行查閱相關資料。

相關文章
相關標籤/搜索