contenttypes框架Django除了admin、auth、session等我们常用的contrib框架外,还包括一个contenttypes框架,可以跟踪Django项目中安装的所有模型,为我们提供更多高级模型界面。默认情况下,它已经在设置中,如果没有,请手动添加:INSTALLED_APPS=['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes',#看这里!!!!'django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',]通常尝试启用contenttypes框架,因为Django的一些其他框架依赖它:Django的管理框架使用它来记录并添加或更改对象的历史记录。Django的auth认证框架使用它来绑定用户权限到指定的模型。contenttypes不是中间件,不是视图,也不是模板,而是一些“额外的数据表”!因此,在使用它们之前,您需要执行makemigrations和migrate操作来创建内容类型框架保存特定数据所需的数据表。这张表通常叫做django_content_type,我们看看它在数据库中是怎么存在的:表的结构如下图:一共三个字段:id:表的主键,没啥好说的app_label:app模型所属的模型名称模型:对应模型的名称。表中的每条记录其实就是Django项目中一个app下的一个model模型。概述ContentTypes框架的核心是ContentType模型,位于django.contrib.contenttypes.models.ContentType。ContentType实例表示并存储有关安装在Django项目中的所有模型的信息。每当在您的Django项目中创建新模型时,都会自动将新的对应记录添加到ContentType表中。ContentType模型的实例具有一系列方法,用于返回它们记录的模型类以及从这些模型中查询对象。ContentType还有一个自定义管理器,用于与ContentType实例相关的ORM操作。ContentType模型每个ContentType实例有两个字段(除了隐含的主键ID)。app_label:模型所属应用的名称。通过模型的app_label属性自动获取,仅包括Python导入路径的最后一部分。例如对于django.contrib.contenttypes模型,自动获取的app_label就是contenttypes最后的字符串部分。模型:模型类的名称。(小写)此外,ContentType实例还有一个name属性,它保存了ContentType的人类可读名称。从模型的verbose_name属性的值自动获取。例如,对于django.contrib.sites.models.Site模型:app_label将被设置为“sites”(django.contrib.sites的最后一部分)。模型将设置为“站点”(小写)。ContentType的实例方法每个ContentType实例都有一些方法可以让你从ContentType实例中获取其对应的模型,或者从模型中检索对象:ContentType.get_object_for_this_type(**kwargs)提供了一系列合法的参数,在相应的模型,执行一个get()查询操作并返回相应的结果。ContentType.model_class()返回由当前ContentType实例表示的模型类。比如我们可以在ContentType表中查询auth的User模型对应的ContentType记录:>>>fromdjango.contrib.contenttypes.modelsimportContentType>>>user_type=ContentType.objects.get(app_label='auth',model='user')#获取一条记录>>>user_type#注意这里是contenttype的实例对象,不是User表的。然后,您可以使用它来查询特定的用户,或者访问用户模型类:>>>user_type.model_class()#获取用户类>>>user_type.get_object_for_this_type(username='Guido')#获取User表的实例一起使用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):给定应用标签和模型名称,返回唯一匹配的ContentType实例。当您只想使用ContentType,但不想获取模型的元数据来执行手动查找时,get_for_model()方法特别有用:>>>fromdjango.contrib.auth.modelsimportUser>>>ContentType.objects.get_for_model(User)#提供模型名称,查询对应的contenttype实例。反向泛型关系GenericRelation字段既然前面使用的GenericForeignKey字段可以帮助我们正向查询关联对象,那么必然有一个对应的反向关联类型,就是GenericRelation字段类型。使用它可以帮助我们从关联对象中反向查询对象本身,也就是ORM中的反向关联。同样,该字段不会对数据表产生任何影响,它只是用于ORM操作!比如下面的例子,我想从书签中反向查询对应的标签:fromdjango.contrib.contenttypes.fieldsimportGenericRelation#importfromdjango.dbimportmodelsclassBookmark(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()#看这句话!!!!!!!!!!!!,]>上面的操作涉及到一个ORM新手很容易犯的错误,即外键的一对多关系怎么写:首先,todoyou想了解是一个标签可以对应多个书签,还是一个书签可以对应多个标签?这里定义的是一个书签可以对应多个标签。书签是“一”面,标签是“多”面。所以ForeignKey字段应该写在多方,即TaggedItem模型中。那么对于Bookmark模型对象,查询关联的标签对象就是一个从一到多的反向查询。这也是为什么我们最终使用了上面的b.tags.all()查询方式,而不是直接使用b.tags的原因!这里请你自己做一件事,它会帮助你理解为什么推荐在ContentType框架中使用GenericForeignKey和GenericRelation等Association字段:请使用Django原生的ORM方法进行上述正向查询和反向查询操作!并与例子中的操作进行对比!如果你为GenericRelation字段提供了一个related_query_name参数值,比如下面的例子:tags=GenericRelation(TaggedItem,related_query_name='bookmark'),那么你可以从TaggedItem对象中过滤查询关联的BookMark对象,如下:>>>#查找属于特定书签模型的所有标签,这些书签必须包含`django`字符串。>>>TaggedItem.objects.filter(bookmark__url__contains='django'),]>当然如果不添加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),]>对比一下,三行代码和一行代码的区别!注意:GenericForeignKey和GenericRelation字段是匹配的,如果你在定义GenericForeignKey时使用了另一个content-type和object-id名称,那么在GenericRelation定义中,你必须做同样的更改。例如,如果TaggedItem模型使用content_type_fk和object_primary_key创建一个content_object字段,如下所示:...classTaggedItem(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模块用于处理管理后台相关内容。有兴趣的可以自行查阅相关资料。