转载于这里
说明 Django创建项目后,在settings.py中默认加载了以下几个app:
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', ]
其中有个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名称
实例 使用的例子,现在有四个model,我们自己创建的Post,Picture和Comment,加上django原生的User。
django原生的User model即用户表
Comment为用户提交的评论表
Picture为存储图片信息的表
Post为存储文章信息的表
场景如下:用户可以对图片或者文章提交评论,那么Comment中首先要存储的是,该评论对应的用户,再就是该评论是针对哪张图片或者哪篇文章的评论,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 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中就需要多新增一个外键,扩展性极差。
操作 如果使用contenttypes就不需要再使用多个Foreignkey,因为在django_content_type表已经存储了app名和model名,所以我们只需要将我们创建的模型与django_content_type表关联起来,然后再存储一个实例 id 即可准确定位到某个app中某个模型的具体实例。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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构造函数如下:
1 2 3 4 5 6 7 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
1 2 3 4 5 6 7 8 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实例直接查询属于该实例的所有评论,或者创建一个属于该实例的新评论。
1 2 3 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中具体实例。