Django ContentType组件

什么是content type:django内置的一个组件,这个组件帮忙做连表的操作。(混搭连表)

适用场景:适用于一张表与多张表同时做关联的时候。直接导入就可以使用了。

什么是Django ContentTypes

Django ContentTypes是由Django框架提供的一个核心功能,它对当前项目中所有基于Django驱动的model提供了更高层次的抽象接口。 当然我们不是说的是http中的content-type!完全没有任何关系!

下面将一步一步解释Django ContentTypesDjango框架中做了什么,以及如何使用Django ContentTypes

Django ContentTypes做了什么?

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

1
2
3
4
5
6
7
8
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文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

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

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

1564566517195

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

当然,django_content_type并不只是记录属性这么简单,contenttypes是对model的一次封装,

因此可以通过contenttypes动态的访问model类型,而不需要每次import具体的model类型。

Django ContentTypes的使用场景

ContentType组件应用:

  • model中定义ForeignKey字段,并关联到ContentType表,通常这个字段命名为content-type
  • model中定义PositiveIntergerField字段, 用来存储关联表中的主键,通常我们用object_id
  • model中定义GenericForeignKey字段,传入上面两个字段的名字
  • 方便反向查询可以定义GenericRelation字段

DEMO需求

现在我们有这样一个需求~我们的商城里有很多的商品,节日要来了,我们要搞活动。那么我们就要设计优惠券,优惠券都有什么类型呢,满减的、折扣的、立减的。
我们对应着我们活动类型,对我们的某类商品设计优惠券,比如:家电是一类商品、食物是一类商品。那么我们可以设计家电折扣优惠券,以及食物满减优惠券等……
那么我们看表结构怎么设计???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Coupon(models.Model):
'''
优惠券表
id name appliance_id food_id fruit_id
1 通用优惠券 null null null
2 冰箱折扣券 1 null null
3 电视折扣券 2 null null
4 苹果满减卷 null null 1
我每增加一张表就要多增加一个字段
'''
name = models.CharField(verbose_name='优惠券名', max_length=50)
def __str__(self):
return self.name


class Appliance(models.Model):
'''
家用电器表
id name
1 电视
2 冰箱
3 空调
'''
name = models.CharField(verbose_name='电器名称', max_length=50)
def __str__(self):
return self.name

class Food(models.Model):
'''
食物表
id name
1 面包
2 牛奶
3 零食
'''
name = models.CharField(verbose_name='食物名称', max_length=50)
def __str__(self):
return self.name

class Fruit(models.Model):
'''
水果表
id name
1 苹果
2 香蕉
3 西瓜
'''
name = models.CharField(verbose_name='水果名', max_length=50)
def __str__(self):
return self.name

实际上我们商品的种类会特别的多,导致我们这张表外键越来越多
遇到像Coupon这种一张表要跟多张表进行外键关联的时候~我们Django提供了ContentType组件

使用ContentType重置数据表

model数据表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation


class Coupon(models.Model):
'''
优惠券表
'''
name = models.CharField(verbose_name='优惠券名', max_length=50)
# 关联到ContentType表
content_type = models.ForeignKey(to=ContentType, on_delete=models.CASCADE)
# 用来存储关联表中的主键
object_id = models.PositiveIntegerField()
# 定义GenericForeignKey字段,传入上面两个字段的名字
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.name


class Appliance(models.Model):
'''
家用电器表
id name
1 电视
2 冰箱
3 空调
'''
name = models.CharField(verbose_name='电器名称', max_length=50)
# 用于商品查对应的优惠券的反向查询,数据表不会创建此字段
coupons = GenericRelation(to=Coupon)
def __str__(self):
return self.name

class Food(models.Model):
'''
食物表
id name
1 面包
2 牛奶
3 零食
'''
name = models.CharField(verbose_name='食物名称', max_length=50)
def __str__(self):
return self.name

class Fruit(models.Model):
'''
水果表
id name
1 苹果
2 香蕉
3 西瓜
'''
name = models.CharField(verbose_name='水果名', max_length=50)
def __str__(self):
return self.name

view视图文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from django.shortcuts import HttpResponse
from app import models
def index(request):
# 通过ContentType获得表名
content = models.ContentType.objects.filter(app_label='app', model='appliance').first()
# 获得表model对象 相当于models.Applicance
model_class = content.model_class()
# 使用上面获取的家电对象模型查询表中所有家电
obj_list = model_class.objects.all()

# 查询主键为3的家电对象:洗衣机
obj = models.Appliance.objects.filter(pk=3).first()
# 为洗衣机添加优惠券
models.Coupon.objects.create(name='618洗衣机优惠券', content_object=obj)

# 正向查询,查询指定优惠券对应的商品
# 查询主键为1的优惠券对象
coupon_obj = models.Coupon.objects.filter(pk=1).first()
# 查询该优惠券对应的商品对象
good = coupon_obj.content_object

# 反向查询,查询家电对应的优惠券
# 查询主键为3的家电对象:洗衣机
good_obj = models.Appliance.objects.filter(pk=3).first()
# 根据商品对象,使用模型中定义的GenericRelation进行反向查询对应的优惠券
coupon_objs = good_obj.coupons.all()

# 如果模型中未定义GenericRelation,如何查询指定商品对应的优惠券
# content_type对应上方我们获取的表名,object_id对应商品id
coupon_objs = models.Coupon.objects.filter(content_type=content, object_id=3)

return HttpResponse('ok')