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模块用于处理管理后台相关内容,感兴趣的能够自行查阅相关资料。

相关文章
相关标签/搜索