Django ContentTypes框架使用场景

Django contenttypes是一个非常有用的框架,主要用来创建模型间的通用关系(generic relation)。不过由于其非常抽象,

理解起来并不容易。当你创建一个django项目的时候,可以看到在默认的INSTALL_APPS已经包含了django.contrib.contenttypes。

今天我们来重点讲下它的使用场景及如何使用django contenttypes。

# Application definition

INSTALLED_APPS = [
    ‘django.contrib.admin‘,
    ‘django.contrib.auth‘,
    ‘django.contrib.contenttypes‘,
    ‘django.contrib.sessions‘,
    ‘django.contrib.messages‘,
    ‘django.contrib.staticfiles‘,
    ‘app01.apps.App01Config‘,
]

Django ContentTypes框架使用场景

假设我们创建了如下模型,里面包含文章Post,Picture和评论Comment模型。

Comment可以是对Post的评论,也可以是对Picture的评论。

如果你还想对其它对象(比如回答,用户) 进行评论, 这样你将需要在comment对象里添加非常多的ForeignKey。

你的直觉会告诉你,这样做很傻,会造成代码重复和字段浪费。一个更好的方式是,只有当你需要对某个对象或模型进行评论时,

才创建comment与那个模型的关系。这时你就需要使用django contenttypes了。

from django.db import models
from django.contrib.auth.models import User
 
# Create your models here.
 
 
class Post(models.Model):
    author = models.ForeignKey(User)
    title = models.CharField(max_length=75)
    body = models.TextField(blank=True)
 
 
class Picture(models.Model):
    author = models.ForeignKey(User)
    image = models.ImageField()
    caption = models.TextField(blank=True)
 
 
class Comment(models.Model):
    author = models.ForeignKey(User)
    body = models.TextField(blank=True)
    post = models.ForeignKey(Post, null=True)
    picture = models.ForeignKey(Picture, null=True)

Django ContentType提供了一种GenericForeignKey的类型,通过这种类型可以指定content_object。修改过的comment模型如下图所示:

import ContentType
 import GenericForeignKey

class Comment(models.Model):
    author = models.ForeignKey(User)
    body = models.TextField(blank=True)    content_type = models.ForeignKey(ContentType)
 
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()

comment里加了3个字段:

  • content_type: 内容类型,代表了模型的名字(比如Post, Picture)

  • object_id: 传入对象的id

  • content_object: 传入的实例化对象,其包含两个属性content_type和object_id。

当你需要对某篇文章或某个图片进行评论时(建立评论关系),

你只需要将实例化的对象user, post或picture传入comment。

这样实现了按需建立评论关系。首先你先需要实例化对象。

user = User.objects.create_user(username=‘user1‘, password=‘2333‘)
post = Post.objects.create(author=user,
                           title=‘title1‘,
                           body=‘‘)
picture = Picture.objects.create(author=user,
                                 image="default.png",
                                 caption=‘picture1‘)

然后在views或者shell里,你可以按如下代码建立评论关系。

 
from foreign.models import Post, Picture, Common
>>> from django.contrib.auth.models import User>>> user = User.objects.get(username=‘user1‘)
>>> post = Post.objects.get(title=‘title1‘)>>> c = Comment.objects.create(author=user, body=‘‘, content_object=post)
>>> picture = Picture.objects.get(caption=‘picuture1‘)
>>> c1 = Comment.objects.create(author=user, body=‘‘, content_object=picture)

然而上述创建评论的方式我们并不推荐

我们更希望直接从模型中获取或创建comment,

我们只需在模型中创建一个与Comment的GenericRelation即可。

注意该字段不会存储于数据库中。

from django.contrib.contenttypes.fields import GenericRelation

class Post(models.Model):
  author = models.ForeignKey(User)
  title = models.CharField(max_length=75)
  body = models.TextField(blank=True)
  comments = GenericRelation(‘Comment‘)

class Picture(models.Model):
  author = models.ForeignKey(User)
  image = models.ImageField()
  caption = models.TextField(blank=True)
  comments = GenericRelation(‘Comment‘)
>>> me = User.objects.get(username=‘myusername‘)
>>> pic = Picture.objects.get(author=me)
>>> pic.comments.create(author=me, body="Man, I‘m cool!")
>>> pic.comments.all()[<Comment: "Man, I‘m cool!">]

值得注意的是,如果在Post中定义了GenericRelation,删除了一个post实例,

在Comment中所有与post相关实例也会被删除。GenericForeignKey不支持设置on_delete参数。 

因此,如果对级联删除不满意的话就不要设置GenericRelation。