上一篇blog介绍到了那个 repository ,其中 trunk/libs.common/src/common/mptt/models.py 里有一个很有意思的叫做 Node 的 model。实现了一个叫 Modified Preorder Tree Traversal 的算法,相关内容还可以参考 这个 页面。 算法细节刚才链接到的文章都讲得很详细了,吸引我注意的是代码中两个陌生的词语:ContentType, GenericForeignKey。django 文档中 model-api 中对 GenericForeignKey 完全没有涉及,add_ons 中对 contenttype 的描述也只有简单的一句:
A light framework for hooking into "types" of content, where each installed Django model is a separate content type. This is not yet documented.通过 google 也只搜到了这么 一篇文章 ,也只是泛泛而谈而已。经过研究,越发地感觉有意思了,于是写下心得,这么有意思的东西被埋没了可真可惜。 简单得说,ContentType 就是一个 model,也就是一张数据表,其中保存着当前 project 中所有 models 的元数据,具体就是 name、app_label 和 model 三个字段,其中 app_label 和 model 这两个字符串组合起来便可以唯一标识一个 model 。通过调用 django.db.models.get_model(app_label, model) 就可以获得该 model 类。 这样一个奇怪的 model 会有什么用处呢?可以设想一下,如果你需要一个和任意 model 都建立有关系的 model 时,你会怎么做?比如:用户评论! 假设你的 project 中有电影、有文章、有音乐等等内容,它们分别对应不同的 model ,而用户对它们每一种内容都可以进行评论,那么最简单的做法就是为每一种内容建立相应的评论表,比如:movie_comments, article_comments 等。不过这种做法的弊端是很明显的:首先是增加了 model 的数量也增加了代码的复杂度;而且没有扩展性,增加其他内容的话还需要增加相应的 comments 表;还有就是统计用户所有评论的时候比较麻烦,需要在多个表中进行查询。 要是我们有了一个记录了项目中所有 model 的元数据的表,表中一条记录便对应着一个 model ,那么我们只要通过一个元数据表的 id 和 一个具体数据表中的 id ,便可以找到任何 model 中的任何记录。ContentType 正是这个表(不过有个前提就是:相关 model 的主键类型必须是相同的,使用django默认的主键就ok了)。 有了 ContentType ,我们的用户评论就只需要一个 model 就可以搞定! 下面开始介绍具体做法吧,首先通过执行以下命令 >django-admin.py startproject ContentType 创建一个 project。 然后修改 settings.py ,配置合适的数据库后端。 然后通过 >cd ContentType >manage.py startapp contents 创建一个 app,修改 contents/models.py 如下:
from django.db import models from django.contrib.contenttypes.models import ContentType class Movie(models.Model): title = models.CharField(maxlength=100) class Article(models.Model): title = models.CharField(maxlength=100) class Music(models.Model): title = models.CharField(maxlength=100) class Comment(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.IntegerField() content_object = models.GenericForeignKey() title = models.CharField(maxlength=100)然后在 settings.py 的 INSTALLED_APPS 中加入: "ContentType.contents", 执行命令: >manage.py syncdb 然后执行: >manage.py shell 现在就可以好好地享受享受劳动果实了。
>>> from ContentType.contents.models import * >>> a = Article() >>> a.title = 'article1' >>> a.save() >>> m = Movie() >>> m.title = 'movie1' >>> m.save() >>> mu = Music() >>> mu.title = 'music1' >>> mu.save() >>> c = Comment() >>> c.content_object = a >>> c.title = 'comment1' >>> c.save() >>> c = Comment() >>> c.content_object = m >>> c.title = 'comment2' >>> c.save() >>> c = Comment() >>> c.content_object = mu >>> c.title = 'comment3' >>> c.save() >>> for c in Comment.objects.all(): ... print c.content_type,c.object_id ... article 1 movie 1 music 1 >>> c.content_object.title 'music1'还有一个值得提一下的地方就是 Comment 的 content_object 字段。实际上根据上面的解释它只要有 content_type 和 object_id 两个字段就够了,不过你总是需要亲自指定两个字段的值。而 GenericForeignKey 出现的目的就是要把这个过程给自动化了,只要给 content_object 赋一个对象,就会自动得根据这个对象的元数据 ,给 content_type 和 object_id 赋值了。 GenericForeignKey 的构造函数接受两个可选参数: def __init__(self, ct_field="content_type", fk_field="object_id"): 你可以在构造 GenericForeignKey 时指定另外的字段名称。 另外还有值得注意的一点就是:contenttype 的表是在 syncdb 时创建的,不过一开始其中并没有元数据,其中的数据是在需要的时候才添加上去的,正如你所想的,它使用的是get_or_create方法。
0 评论:
发表评论