本文介绍了Django框架中的contenttypes及其基本使用。

Django contenttypes framework

简介

Django包含一个contenttypes应用程序(app),可以跟踪Django项目中安装的所有模型(Model),提供用于处理模型的高级通用接口。

概述

Contenttypes应用的核心是ContentType模型,位于django.contrib.contenttypes.models.ContentType。 ContentType的实例表示并保存项目中安装的模型的信息,每当有新的模型时会自动创建新的ContentType实例。

只要使用django-admin startproject 命令创建的Django项目(PyCharm创建Django项目同理),默认都会在settings.pyINSTALLED_APPS列表中安装好django.contrib.contenttypesDjango项目默认包含contenttypes

我们执行了数据迁移命令之后,会自动在数据库中创建一个名为django_content_type的表。 Django项目默认contenttypes生成的表

表结构如下图所示: Django项目默认contenttypes表结构

其中,app_label字段存储了APP的名称,model字段存储了APP下的具体的模型类的名称。

contenttypes的应用场景

比如我们现在要实现一个开放的论坛,用户可以在论坛里发布帖子或图片,用户当然还可以对帖子发表评论,或者对图片发表评论。这个时候我们可能会想到按如下方式去设计表结构:

from django.db import models
# 这里使用auth模块的用户表(使用其他用户表同理)
from django.contrib.auth.models import User


class Post(models.Model):
    """帖子表"""
    author = models.ForeignKey(User)
    title = models.CharField(max_length=72)


class Picture(models.Model):
    """图片表"""
    author = models.ForeignKey(User)
    image = models.ImageField()


class Comment(models.Model):
    """评论表"""
    author = models.ForeignKey(User)
    content = models.TextField()
    post = models.ForeignKey(Post, null=True, blank=True, on_delete=models.CASCADE)
    picture = models.ForeignKey(Picture, null=True, blank=True, on_delete=models.CASCADE)

再来个数据示例图: 表结构示例图1 上面的表结构能够实现我们的需求,但是如果以后我们还需要对问答、用户等模块的评论功能时,我们就需要对我们的Comment表进行修改,这个时候就会在Comment表上添加很多的外键字段(这些外键字段通常只有一个有值,其他都是空的)。 这种方式很显然不合适,现在有一个更好的方式是我们可以按照如下方式设计: 表结构示例图2

我们再优化一下: 表结构示例图3

这个时候我们就用上了前面讲到的contenttypes,借助contenttypes我们就能够在创建Comment的时候再决定和Post关联还是和Picture关联。

models.py中使用django.contrib.contenttypes中提供的特殊字段GenericForeignKey来实现:

class Comment(models.Model):
    """评论表"""
    author = models.ForeignKey(User)
    content = models.TextField()

    content_type = models.ForeignKey(ContentType)  # 外键关联django_content_type表
    object_id = models.PositiveIntegerField()  # 关联数据的主键
    content_object = GenericForeignKey('content_type', 'object_id')

看一下表结构:

contenttypes使用

import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_contenttype.settings")

    import django
    django.setup()

    from app01.models import Post, Picture, Comment
    from django.contrib.auth.models import User
    # 准备测试数据
    user_1 = User.objects.create_user(username='aaa', password='123')
    user_2 = User.objects.create_user(username='bbb', password='123')
    user_3 = User.objects.create_user(username='ccc', password='123')

    post_1 = Post.objects.create(author=user_1, title='Python入门教程')
    post_2 = Post.objects.create(author=user_2, title='Python进阶教程')
    post_3 = Post.objects.create(author=user_1, title='Python入土教程')

    picture_1 = Picture.objects.create(author=user_1, image='小姐姐01.jpg')
    picture_2 = Picture.objects.create(author=user_1, image='小姐姐02.jpg')
    picture_3 = Picture.objects.create(author=user_3, image='小哥哥01.jpg')

    # 给帖子创建评论数据
    comment_1 = Comment.objects.create(author=user_1, content='好文!', content_object=post_1)
    # 给图片创建评论数据
    comment_2 = Comment.objects.create(author=user_2, content='好美!', content_object=picture_1)

接下来如果我们想要查看某篇帖子或者某个照片的所有评论,这个时候就可以用上另外一个工具–GenericRelation了。

from django.contrib.contenttypes.fields import GenericRelation

修改models.py中的PostPicture,添加用于反向查询的comments字段:

class Post(models.Model):
    """帖子表"""
    author = models.ForeignKey(User)
    title = models.CharField(max_length=72)

    comments = GenericRelation('Comment')  # 支持反向查找评论数据(不会在数据库中创建字段)


class Picture(models.Model):
    """图片表"""
    author = models.ForeignKey(User)
    image = models.ImageField()

    comments = GenericRelation('Comment')  # 支持反向查找评论数据(不会在数据库中创建字段)

查询示例:

post_1 = Post.objects.filter(id=1).first()
comment_list = post_1.comments.all()