1.django.contrib.contenttypes
Django创建项目后,在settings.py中默认加载了以下几个app:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
其中有个app为django.contrib.contenttypes,在使用了python manage.py makemigrations 和 python manage.py migrate命令后,这个app会在数据库创建表 django_content_type,数据表字段如下:
django_content_type表存储了用户所提交的所有model名称与model所在的app名称
其中的model字段为用户定义的模型类的名称,app_label为该模型所在app名称
2.实例分析
那么这个表的意义在哪?我们可以用这个表来干嘛?引用网上诸多博文使用的例子,现在有四个model,我们自己创建的Post,Picture和Comment,加上django原生的User。
django原生的User model即用户表
Comment为用户提交的评论表
Picture为存储图片信息的表
Post为存储文章信息的表
场景如下:用户可以对图片或者文章提交评论,那么Comment中首先要存储的是,该评论对应的用户,再就是该评论是针对哪张图片或者哪篇文章的评论,代码如下:
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(blank=True)
class Picture(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(blank=True)
class Comment(models.Model):
author = models.ForeignKey(to=User, on_delete=models.CASCADE)
body = models.TextField(blank=True)
pic = models.ForeignKey(to=Picture, on_delete=models.CASCADE, null=True)
post = models.ForeignKey(to=Post, on_delete=models.CASCADE, null=True)
为了建立Comment与Post或者Picture的关系,我们就需要创建外键,如果该Comment与Picture有关联,那么pic字段是有值的,而其余的关联字段为null。这样实现的缺点是,每增加一种被评论的类型,在Comment model中就需要多新增一个外键,扩展性极差。
3.使用contenttypes来解决问题
如果使用contenttypes就不需要再使用多个Foreignkey,因为在django_content_type表已经存储了app名和model名,所以我们只需要将我们创建的模型与django_content_type表关联起来,然后再存储一个实例 id 即可准确定位到某个app中某个模型的具体实例。代码如下:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(blank=True)
comments = GenericRelation('Comment') # 使用GenericRelation可以建立该类与Comment类的反向关联,
# 那么可以通过该类的实例来创建对应的comment
class Picture(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(blank=True)
comments = GenericRelation('Comment')
class Comment(models.Model):
# user_name = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
author = models.ForeignKey(to=User, on_delete=models.CASCADE)
body = models.TextField(blank=True)
# step1
# ForeignKey为外键,即在Comment类中,content_type对应了django_content_type表中的某个对象
content_type = models.ForeignKey(to=ContentType, on_delete=models.CASCADE) # 与数据库中的django_content_type表关联起来
# step2
object_id = models.PositiveIntegerField() # 正整数类型,在被关联的表中的实例id,以此来定位具体的实例
# step3
content_object = GenericForeignKey() # 根据content_type和object_id来指向一个模型中的具体实例
首先看Comment这个模型,与contenttypes相关的就三个字段:content_tpye object_id content_object
content_type字段为外键,指向ContentType这个模型,也就是上面提到的django_content_type表
object_id为一个整数,存储了实例id,实例id不理解可以看下面的数据表结构分析
content_object为GenericForeignKey类型,主要作用是根据content_type字段和object_id字段来定位某个模型中具体某个实例
所以实际上在使用GenericForeignKey()函数时需要传入两个参数,即content_type和object_id字段的名字,注意是名字不是value。但是默认在该函数的构造函数中已经赋予了默认值,即"content_type"和"object_id",所以如果我们定义的变量名为这两个,那么我们可以省略该参数。该字段不会存储到数据库中,在查询的时候ORM会用到。
GenericForeignKey构造函数如下:
def __init__(self, ct_field='content_type', fk_field='object_id', for_concrete_model=True):
self.ct_field = ct_field
self.fk_field = fk_field
self.for_concrete_model = for_concrete_model
self.editable = False
self.rel = None
self.column = None
将模型同步到数据库,并创建几个测试数据,使用manage.py自带的shell可以轻松的创建测试数据。
python manage.py shell
from app1.models import Post, Picture, Comment # app1即为你自己所创建的app名
from django.contrib.auth.models import User # django原生用户模型
user = User.objects.get(username='admin-x')
post = Post.objects.create(author=user, body='admin-x post test')
picture = Picture.objects.create(author=user, body='admin-x pic test')
pic = Picture.objects.get(id=1) # 获取id为1的pic对象
c1 = Comment.objects.create(author=user, body='test', content_object=pic) # 创建一个评论并与上述的pic对象关联起来
还可以注意到,在Post和Picture模型中我们定义了comments = GenericRelation('Comment')
GenericRelation的作用是建立该model与Comment的反向关联,我们这样就可以通过Post实例或者Picture实例直接查询属于该实例的所有评论,或者创建一个属于该实例的新评论。
pic = Picture.objects.get(id=1) # 获取id为1的pic对象
pic.comments.create(author=user, body='test02') # 创建一个属于pic对象的评论
pic.comments.all() # 获取属于pic对象的所有评论
数据表结构分析,这样应该一目了然了吧
实际上在数据表中,根据content_type_id就可以在django_content_type这个表中定位到哪个app的哪个model,然后根据object_id定位到该model中具体实例。