最近在作帮@Brian作一个小项目,借助了 Mezzanine 这个Django的CMS系统。在 CMS 里大量用到了 Django 里的 Content Type 和 Generic Relations。虽然在Django的官方文档里有对此有还算详细的描述,可是相关的中文资料比较少。因此打算汇总一下,结合个人经验写一个小教程,重要的是给出一些例子,而不是干巴巴看说明。django
假使咱们要写一个 Django 版本的 WordPress ,要定义的 Model 会有 Post,Page和URL,在是在WP里最典型的3种内容形式。ide
from django.db import models class Post(models.Model): title = models.CharField(max_length=100) pub_date = model.DateTimeField(auto_now_add=True) content = models.TextField() class Url(models.Model): title = models.CharField(max_length=100) pub_date = models.DateTimeField(auto_now_add=True) url = models.URLField(blank=True, verify_exists=True)
这个时候我想在写一个 Comment 的 Model,由于无论是 Post 或者 URL 均可以容许被评论。若是以前没有接触过Generic Relations,真的无所适从。有可能会写两个Model,一个Post_comments和一个URL_comments,或者是在Comment的model里加入两组Foreign Key。函数
class Comment(models.Model): title = models.CharField(max_length=100) post = models.ForeignKey(Post, black=True, null=True) url = models.ForeignKey(Url, black=True, null=True)
估计写出这样的Model的话,想自杀的心都有了。引入正题,Generic Relation。咱们但愿建立一个Comment的Model适用于全部内容类型,无论是Post,URL或者Page。Generic Relation能帮咱们实现这样的Model。post
from django.db import models from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType class Comment(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') content = models.CharField(max_length=1000)
在 Django 中,有一个记录了项目中全部 model 元数据的表,就是 ContentType。表中一条记录便对应着一个存在的model ,那么咱们只要经过一个元数据表的 id 和一个具体数据表中的 id,即可以找到任何model中的任何记录。this
Comment 中使用 GenericForeignKey() 来指向其它的 Model 实例。为了使用它,还须要在 Model 中定义content_type 和 object_id 才能够。其中 content_typ e来自 ContentType 这个 Model,记录 Comment 所指向的其余 Model 实例的名字。 object_id则是表示所指向的Model实例的id。url
实际上根据上面的解释它只要有 content_type 和 object_id 两个字段就够了,不过咱们老是须要亲自指定两个字段的值。而 GenericForeignKey 出现的目的就是要把这个过程给自动化了,只要给 content_object 赋一个对象,就会自动得根据这个对象的元数据 ,给 content_type 和 object_id 赋值了。
GenericForeignKey 的构造函数接受两个可选参数:
def __init__(self, ct_field=”content_type”, fk_field=”object_id”):
你能够在构造 GenericForeignKey 时指定另外的字段名称。spa
a = Post(title='post1') a.save() b = Url(title='url1') b.save() c = Comment(content_object=a, content="test1") c.save() c.content_object d = Comment(content_object=b, content="test2") d.save() d.content_object Comment.objects.all()
结果是[<Comment: test1>, <Comment: test2>]翻译
因为GenericForeignKey()不是普通的外键,若是咱们想查找一个Post下的全部评论,无法用下面的这种方式对象
# This will fail Comment.objects.filter(content_object=a) # This will also fail Comment.objects.get(content_object=a)
须要绕一步,略微复杂blog
a_type = ContentType.objects.get_for_model(a) Comment.objects.filter(content_type__pk=a_type.id, object_id=a.id)
结果是[<Comment: test1>]
实际上是有办法让这个很正常的查询变得简单一些,Django 提供了 Reverse generic relations 的机制。
从新改一下 Post 这个 Model
class Post(models.Model): title = models.CharField(max_length=100) pub_date = model.DateTimeField(auto_now_add=True) content = models.TextField() comments = generic.GenericRelation(Comment)
这样咱们就给 Post 这个 Model 添加了一个“逆向”的 generic relationship。每一个 Post 的实例都有了个 comments 的属性,用于检索与之有关的 comments。
a = Post(title='test2') a.save() c1 = Comment(content_object=a, content='comment1') c1.save() c2= Comment(content_object=a, content='comment2') c2.save() a.comments.all()
[<Comment: comment1>, <Comment: comment2>]
这里有有一段总结,写的不错,我就不翻译了,直接粘贴过来。
A generic relationship is defined by two elements: a foreign key to the ContentType table, to determine the type of the related object, and an ID field, to identify the specific object to link to. Django uses these two elements to provide a content_object pseudo-field which, to the user, works similarly to a real ForeignKey field. And, again just like a ForeignKey, Django can helpfully provide a reverse relationship from the linked model back to the generic one, although you do need to explicitly define this using generic.GenericRelation to make Django aware of it.
Django 官方文档 The contenttypes framework
efficient generic relations