contenttypes框架
Django除了我们常见的admin
、auth
、session
等contrib框架,还包含一个contenttypes
框架,它可以跟踪Django项目中安装的所有模型(model
),为我们提供更高级的模型接口。默认情况下,它已经在settings中了,如果没有,请手动添加:
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
,让我们看看它在数据库中的存在方式:
而表的结构形式则如下图所示:
一共三个字段:
- id:表的主键,没什么好说的
- app_label:模型所属的app的名字
- model:具体对应的模型的名字。
表中的每一条记录,其实就是Django项目中某个app下面的某个model模型。
概述
contenttypes框架的核心是 ContentType
模型,它位于django.contrib.contenttypes.models.ContentType
。ContentType实例表示和存储Django项目中安装的所有模型的信息。每当你的Django项目中创建了新的模型,会在ContentType
表中自动添加一条新的对应的记录。
ContentType
模型的实例具有一系列方法,用于返回它们所记录的模型类以及从这些模型查询对象。ContentType
还有一个自定义的管理器,用于进行ContentType
实例相关的ORM操作。
ContentType模型
每个 ContentType
实例都有两个字段(除了隐含的主键id)。
-
app_label
: 模型所属app的名称。通过模型的app_label
属性自动获取,仅包括Python导入路径的最后部分。例如对于django.contrib.contenttypes
模型,自动获取的app_label
就是最后的contenttypes
字符串部分。 - model:模型类的名称。(小写)
此外,ContentType实例还有一个name
属性,保存了ContentType
的人类可读名称。由模型的verbose_name
属性值自动获取。
例如,对于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>
反向通用关系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框架中建议使用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
模块用于处理管理后台相关内容,感兴趣的可以自行查阅相关资料。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。