django 的 ContentType

Django官网文档 https://docs.djangoproject.com/zh-hans/2.2/ref/contrib/contenttypes/


ContentType model

主要是 app_labelmodel 这两个字段,表示哪个 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_typeobject_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>]>

posted @ 2020-04-17 15:22  寒菱  阅读(207)  评论(0编辑  收藏  举报