好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Django contenttypes框架

什么是Django ContentTypes?

Django ContentTypes是由Django框架提供的一个核心功能,它对当前项目中所有基于Django驱动的model提供了更高层次的抽象接口。

然而,对于Django ContentTypes不熟悉的人来说,上面这句话说了跟没说一样,因此,笔者将一步一步解释Django ContentTypes在Django框架中做了什么,以及如何使用Django ContentTypes。

当然,如果对于ContentTypes有了初步了解而只是不了解它的应用场景,可以直接查阅以下这两个链接:

Django official documentation:The contenttypes framework

stackoverflow: How exactly do Django content types work?

Django ContentTypes做了什么?

当使用django-admin初始化一个django项目的时候,可以看到在默认的INSTALL_APPS已经包含了django.contrib.contenttypes:

INSTALLED_APPS?=?[
????'django.contrib.admin',
????'django.contrib.auth',
????'django.contrib.contenttypes',
????'django.contrib.sessions',
????'django.contrib.messages',
????'django.contrib.staticfiles',
]

而且注意django.contrib.contenttypes是在django.contrib.auth之后,这是因为auth中的permission系统是根据contenttypes来实现的。

笔者紧接着查阅了一下django.contrib.contenttypes.models文件:

class?ContentType(models.Model):
????app_label?=?models.CharField(max_length=100)
????model?=?models.CharField(_('python?model?class?name'),?max_length=100)
????objects?=?ContentTypeManager()
????class?Meta:
????????verbose_name?=?_('content?type')
????????verbose_name_plural?=?_('content?types')
????????db_table?=?'django_content_type'
????????unique_together?=?(('app_label',?'model'),)
????def?__str__(self):
????????return?self.name

大家可以看到ContentType就是一个简单的django model,而且它在数据库中的表的名字为django_content_type。

有经验的Django开发者对于这个表的名字一般都不会陌生,在第一次对Django的model进行migrate之后,就可以发现在数据库中出现了一张默认生成的名为django_content_type的表。

如果没有建立任何的model,默认django_content_type是这样的:

sqlite>?select?*?from?django_content_type;
1|admin|logentry
2|auth|group
3|auth|user
4|auth|permission
5|contenttypes|contenttype
6|sessions|session

因此,django_content_type记录了当前的Django项目中所有model所属的app(即app_label属性)以及model的名字(即model属性)。

当然,django_content_type并不只是记录属性这么简单,在一开始的时候笔者就提及了contenttypes是对model的一次封装,因此可以通过contenttypes动态的访问model类型,而不需要每次import具体的model类型。

·ContentType实例提供的接口

·ContentType.model_class()

·获取当前ContentType类型所代表的模型类

·ContentType.get_object_for_this_type()

·使用当前ContentType类型所代表的模型类做一次get查询

·ContentType管理器(manager)提供的接口

·ContentType.objects.get_for_id()

·通过id寻找ContentType类型,这个跟传统的get方法的区别就是它跟get_for_model共享一个缓存,因此更为推荐。

·ContentType.objects.get_for_model()

·通过model或者model的实例来寻找ContentType类型

Django ContentTypes的使用场景

Permission对ContentType的使用

在之前,笔者简单地提及了auth中Permission有涉及到对ContentType的使用,下面来看一下Permission的model源码:

class?Permission(models.Model):
????"""
????The?permissions?system?provides?a?way?to?assign?permissions?to?specific
????users?and?groups?of?users.
????The?permission?system?is?used?by?the?Django?admin?site,?but?may?also?be
????useful?in?your?own?code.?The?Django?admin?site?uses?permissions?as?follows:
????????-?The?"add"?permission?limits?the?user's?ability?to?view?the?"add"?form
??????????and?add?an?object.
????????-?The?"change"?permission?limits?a?user's?ability?to?view?the?change
??????????list,?view?the?"change"?form?and?change?an?object.
????????-?The?"delete"?permission?limits?the?ability?to?delete?an?object.
????Permissions?are?set?globally?per?type?of?object,?not?per?specific?object
????instance.?It?is?possible?to?say?"Mary?may?change?news?stories,"?but?it's
????not?currently?possible?to?say?"Mary?may?change?news?stories,?but?only?the
????ones?she?created?herself"?or?"Mary?may?only?change?news?stories?that?have?a
????certain?status?or?publication?date."
????Three?basic?permissions?--?add,?change?and?delete?--?are?automatically
????created?for?each?Django?model.
????"""
????name?=?models.CharField(_('name'),?max_length=255)
????content_type?=?models.ForeignKey(ContentType,models.CASCADE,verbose_name=_('content?type'),)
????codename?=?models.CharField(_('codename'),?max_length=100)
????objects?=?PermissionManager()
????class?Meta:
????????verbose_name?=?_('permission')
????????verbose_name_plural?=?_('permissions')
????????unique_together?=?(('content_type',?'codename'),)
????????ordering?=?('content_type__app_label',?'content_type__model','codename')

大家可以看到Permission模型中设置了一个对ContentType的外键,这意味着每一个Permission的实例都具有关于一个ContentType的id作为外键,而ContentType的id恰恰代表着一个Model。

回想Permission模型在初始化的时候发生了什么,它为每个模型设置了三个权限,分别是add,change以及delete,那么它是如何跟每个模型联系起来的呢?就是通过一个到ContentType的外键。

大家可以看一下Permission表,其中第二行就是content_type,然后将主键于django_content_type对比:

sqlite>?select?*?from?auth_permission;
1|1|add_logentry|Can?add?log?entry
2|1|change_logentry|Can?change?log?entry
3|1|delete_logentry|Can?delete?log?entry
4|2|add_group|Can?add?group
5|2|change_group|Can?change?group
6|2|delete_group|Can?delete?group
7|3|add_user|Can?add?user
8|3|change_user|Can?change?user
9|3|delete_user|Can?delete?user
10|4|add_permission|Can?add?permission
11|4|change_permission|Can?change?permission
12|4|delete_permission|Can?delete?permission
13|5|add_contenttype|Can?add?content?type
14|5|change_contenttype|Can?change?content?type
15|5|delete_contenttype|Can?delete?content?type
16|6|add_session|Can?add?session
17|6|change_session|Can?change?session
18|6|delete_session|Can?delete?session

如此,Permission模型借助ContentType表达了对一个model的权限操作。

ContentType的通用类型

笔者将引用在顶部的stackoverflow中回答的例子讲述对通用类型的理解。

假设以下的应用场景:

from?django.db?import?models
from?django.contrib.auth.models?import?User
#?Create?your?models?here.
class?Post(models.Model):
????author?=?models.ForeignKey(User)
????title?=?models.CharField(max_length=75)
????slug?=?models.SlugField(unique=True)
????body?=?models.TextField(blank=True)
class?Picture(models.Model):
????author?=?models.ForeignKey(User)
????image?=?models.ImageField()
????caption?=?models.TextField(blank=True)
class?Comment(models.Model):
????author?=?models.ForeignKey(User)
????body?=?models.TextField(blank=True)
????post?=?models.ForeignKey(Post,?null=True)
????picture?=?models.ForeignKey(Picture,?null=True)

注意笔者这里跟原回答做了一些更改,在原回答中Comment中没有null的选项,笔者觉得回答者真正要表达的是Comment是分别和Picture或者Post中其中一个对应即可,一个Comment并不既需要Post又需要Picture才能建立,可能是回答者写错没注意的缘故。

当笔者对以上model进行migrate之后,发现Comment表中的foreignkey是可以被设置为null的。

那么,如何通过Contenttype框架对以上代码进行改进呢?

ContentType提供了一种GenericForeignKey的类型,通过这种类型可以实现在Comment对其余所有model的外键关系。

修改后的Comment模型如下:

class?Comment(models.Model):
????author?=?models.ForeignKey(User)
????body?=?models.TextField(blank=True)
????content_type?=?models.ForeignKey(ContentType)
????object_id?=?models.PositiveIntegerField()
????content_object?=?fields.GenericForeignKey()

在这里,通过使用一个content_type属性代替了实际的model(如Post,Picture),而object_id则代表了实际model中的一个实例的主键,其中,content_type和object_id的字段命名都是作为字符串参数传进content_object的。即:

content_object?=?fields.GenericForeignKey('content_type',?'object_id')

笔者先准备一些测试用的数据:

user?=?User.objects.create_user(username='user1',?password='2333')
post?=?Post.objects.create(author=user,title='title1',slug=slugify('title1'),body='')
picture?=?Picture.objects.create(author=user,image="http://HdhCmsTestpicture1测试数据",caption='picture1')

接着在shell中创建Comment:

>>>?from?foreign.models?import?Post,?Picture,?Common
>>>?from?django.contrib.auth.models?import?User
>>>?user?=?User.objects.get(username='user1')
>>>?post?=?Post.objects.get(title='title1')
>>>?c?=?Comment.objects.create(author=user,?body='',?content_object=post)
>>>?c
<Comment:?Comment?object>
>>>?c.content_type
<ContentType:?post>
>>>?c.object_id
1
>>>?picture?=?Picture.objects.get(caption='picuture1')
>>>?c?=?Comment.objects.create(author=user,?body='',?content_object=picture)
>>>?c.content_type
<ContentType:?picture>
>>>?c.object_id
1

在django中,也提供从诸如Post,Picture访问Comment的查询,通过GenericRelation类型。如:

class?Post(models.Model):
????author?=?models.ForeignKey(User)
????title?=?models.CharField(max_length=75)
????slug?=?models.SlugField(unique=True)
????body?=?models.TextField(blank=True)
????comment?=?GenericRelation('Comment')

值得注意的是,如果在Post中定义了GenericRelation,删除了一个实例,在Comment中所有的相关实例也会被删除,GenericForeignKey不支持设置on_delete参数。

因此,如果对级联删除不满意的话就不要设置GenericRelation。

查看更多关于Django contenttypes框架的详细内容...

  阅读:20次