django 的 ContentType
Django官网文档 https://docs.djangoproject.com/zh-hans/2.2/ref/contrib/contenttypes/
ContentType model
主要是 app_label
和 model
这两个字段,表示哪个 app
下的 model
, 定位到表, 源码如下
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.app_labeled_name
@property
def name(self):
model = self.model_class()
if not model:
return self.model
return str(model._meta.verbose_name)
@property
def app_labeled_name(self):
model = self.model_class()
if not model:
return self.model
return '%s | %s' % (model._meta.app_label, model._meta.verbose_name)
def model_class(self):
"""Return the model class for this type of content."""
try:
return apps.get_model(self.app_label, self.model)
except LookupError:
return None
def get_object_for_this_type(self, **kwargs):
"""
Return an object of this type for the keyword arguments given.
Basically, this is a proxy around this object_type's get_object() model
method. The ObjectNotExist exception, if thrown, will not be caught,
so code that calls this method should catch it.
"""
return self.model_class()._base_manager.using(self._state.db).get(**kwargs)
def get_all_objects_for_this_type(self, **kwargs):
"""
Return all objects of this type for the keyword arguments given.
"""
return self.model_class()._base_manager.using(self._state.db).filter(**kwargs)
def natural_key(self):
return (self.app_label, self.model)
环境搭建
以用户的收藏为例,这里涉及到的表结构如下
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
# Create your models here.
class User(models.Model):
name = models.CharField(max_length=10, verbose_name='姓名')
desc = models.CharField(max_length=200, verbose_name='简介')
def __str__(self):
return self.name
class Meta:
verbose_name = '姓名'
verbose_name_plural = verbose_name
class Fav(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.IntegerField()
value = GenericForeignKey()
def __str__(self):
return f'{self.user} 收藏了 {self.value}'
class Meta:
verbose_name = '收藏表'
verbose_name_plural = verbose_name
class Org(models.Model):
name = models.CharField(max_length=10, verbose_name='名称')
desc = models.CharField(max_length=200, verbose_name='简介')
fav = GenericRelation(Fav, related_query_name='org')
def __str__(self):
return self.name
class Meta:
verbose_name = '机构'
verbose_name_plural = verbose_name
class Teacher(models.Model):
name = models.CharField(max_length=10, verbose_name='姓名')
desc = models.CharField(max_length=100, verbose_name='简介')
fav = GenericRelation(Fav, related_query_name='teacher')
def __str__(self):
return self.name
class Meta:
verbose_name = '老师'
verbose_name_plural = verbose_name
class Course(models.Model):
name = models.CharField(max_length=10, verbose_name='课程名')
desc = models.CharField(max_length=100, verbose_name='简介')
fav = GenericRelation(Fav, related_query_name='course')
def __str__(self):
return self.name
class Meta:
verbose_name = '课程'
verbose_name_plural = verbose_name
GenericForeignKey
这里看一下收藏表的结构,通过 content_type
来定位到表, 再通过 object_id
来定位到具体的记录值
class Fav(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.IntegerField()
value = GenericForeignKey()
def __str__(self):
return f'{self.user} 收藏了 {self.value}'
class Meta:
verbose_name = '收藏表'
verbose_name_plural = verbose_name
如果不使用 content_type
的话,表结构的设计可能就成下面这个样子了, 也差不多,收藏类型就是 哪张表
,需要事先确定一下字段代表的具体值
TYPE_CHOICES = (
(1, "课程"),
(2, "机构"),
(3, "老师")
)
user = models.ForeignKey(User, verbose_name="用户", on_delete=models.CASCADE)
fav_type = models.IntegerField(choices=TYPE_CHOICES, default=1, verbose_name="收藏类型")
fav_id = models.IntegerField(default=0)
具体操作
添加收藏
前面使用 GenericForeignKey
来关联 content_type
和 object_id
# 获取对象
user = User.objects.first()
course = Course.objects.first()
teacher = Teacher.objects.first()
org = Org.objects.first()
# 空表
Fav.objects.all()
<QuerySet []>
# 添加收藏, value是表结构中定义的,命名有点问题,影响不大
Fav.objects.create(user=user, value=org)
<Fav: 用户1 收藏了 机构1>
Fav.objects.create(user=user, value=teacher)
<Fav: 用户1 收藏了 讲师1>
Fav.objects.create(user=user, value=course)
<Fav: 用户1 收藏了 课程1>
# 表已经有数据了
Fav.objects.all()
<QuerySet [<Fav: 用户1 收藏了 机构1>, <Fav: 用户1 收藏了 讲师1>, <Fav: 用户1 收藏了 课程1>
查询收藏
先再添加一个收藏
user_last = User.objects.last()
user_last
<User: 用户5>
Fav.objects.create(user=user_last, value=course)
<Fav: 用户5 收藏了 课程1>
查询收藏了 课程1
的用户, 刚好是录入的两条记录
Fav.objects.filter(course=course)
<QuerySet [<Fav: 用户1 收藏了 课程1>, <Fav: 用户5 收藏了 课程1>]>
GenericRelation
这里的过滤条件在 fav
这个表结构中并没有出现,能使用的原因是在 Course
表中的 GenericRelation
定义了这个反向查询 related_query_name
前面的表结构中都有一个 fav
字段来关联到 Fav
, 其中定义了 related_query_name
来允许反向查询, 如果没有定义 related_query_name
,就无法进行反向查询, 查询就会报错
这个字段并不会在数据库中生成
如果没有写这个 related_query_name
的话,就要手动查询了
course = Course.objects.first()
value_type = ContentType.objects.get_for_model(Course)
Fav.objects.filter(content_type__pk=value_type.id, object_id=course.id)
<QuerySet [<Fav: 用户1 收藏了 课程1>, <Fav: 用户5 收藏了 课程1>]>
Fav.objects.filter(content_type=value_type, object_id=course.id)
<QuerySet [<Fav: 用户1 收藏了 课程1>, <Fav: 用户5 收藏了 课程1>]>
查询多个的话也可以用这种
courses = Course.objects.all()
Fav.objects.filter(content_type=value_type, object_id__in=courses)
<QuerySet [<Fav: 用户1 收藏了 课程1>, <Fav: 用户5 收藏了 课程1>]>
聚合查询
Course.objects.aggregate(Count('fav'))
{'fav__count': 3}
Fav.objects.all()
<QuerySet [<Fav: 用户1 收藏了 机构1>, <Fav: 用户1 收藏了 讲师1>, <Fav: 用户5 收藏了 课程1>, <Fav: 用户5 收藏了 课程5>, <Fav: 用户5 收藏了 课程5>]>
Org.objects.aggregate(Count('fav'))
{'fav__count': 1}
Course.objects.last()
<Course: 课程5>
Course.objects.last().fav
<django.contrib.contenttypes.fields.create_generic_related_manager.<locals>.GenericRelatedObjectManager object at 0x000001EC8319AD88>
Course.objects.last().fav.count()
2
Course.objects.last().fav.all()
<QuerySet [<Fav: 用户5 收藏了 课程5>, <Fav: 用户5 收藏了 课程5>]>