代码改变世界

Django 中的 ForeignKey ContentType GenericForeignKey 对应的数据库结构

2011-01-30 12:37  sunblackshine  阅读(2427)  评论(0编辑  收藏  举报

在建立数据库时,通常会把各个对象的属性放在一个一个表中,通过表之间的关系即外键来描述和约束业务逻辑。

Django 的 ORM 模型简化了一些数据库的操作,特别是外键,以及查询等功能,使得我们不用再写复杂的sql语句。

表和关系的逻辑清晰并且可以跨数据库平台使用。

下面主要记录一下 Django 在数据库中是怎样处理 ForeignKey 的。

app 都是 Pinax 中所集成的。

1、ManyToManyField

 

class Url(models.Model):
.........
    class Meta:
        verbose_name = _('url')
        verbose_name_plural = _('url')

class Rule(models.Model):
.........

    allowed = models.ManyToManyField(Url, blank=True, related_name="allowed",
                                     help_text=_("The URLs which are allowed "
                                                 "to be accessed by bots."))

    disallowed = models.ManyToManyField(Url, blank=True, related_name="disallowed",
                                        help_text=_("The URLs which are not "
                                                    "allowed to be accessed "
                                                    "by bots."))
    sites = models.ManyToManyField(Site)

如上是 robots.models

 

在数据库中表现为

图[1]

robots_rule 和 robots_url 两个表对应两个 model

robots_rule_allowed, robots_rule_disallowed, robots_rule_sites 三个表分别对应 ManyToManyField

如果在 rule 中查询 rule.allowed,Django 会自动锁定外键对应的表为 robots_rule_allowed 通过查询这个表得到对应的 url_id(多个),然后去 robots_url 表中提取相应记录。

 

2、ForeignKey

 

class ThreadedComment(models.Model):
.......
    # Generic Foreign Key Fields
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField(_('object ID'))
    content_object = generic.GenericForeignKey()
    
    # Hierarchy Field
    parent = models.ForeignKey('self', null=True, blank=True, default=None, related_name='children')
        
    # User Field
    user = models.ForeignKey(User)

先看 ForeignKey 字段。

在表 threadedcomments_threadedcomment 中的结构如下(注意这个表名是由 <app_label>_<models_model> 组成)

图[2]

parent 是指向自身表的约束(用来表示一组回复,可以呈现 tree 形式的回复),允许为空,字段值为父 item 的 pk 值(即 id )。

查询时,Django 会提取 parent_id 值,然后直接在自身表中搜索到父 id,即被回复的评论。

注,所有的 model 不用给出 id 字段描述,因为 Django 会自动给出,而且做外键关联都是由 id 字段唯一确定的,当然这可以改...看document。

user 是指向另一个表(Django 默认app User,用于登陆后台 admin)的外键,提取 user_id 值,再去 User 表中查询即可。


ForeignKey 的 related_name='children' 标注了反向关系名称。

例如,这个例子中指向的是 parent,而 parent 中没有任何标注,但是我们可以通过 <parent对象>.<children>+<_set> 

parent对象.children_set() 来得到所有的 children 关联记录。


3、GenericForeignKey

这个东西比较好用,在我没有知道这个好东西之前,解决一个表关联多个外键时,是通过定义一个 type 字段来确定当前记录中的哪个外键是有效的。

当然他这个原理也是这样的,不过多加了一个全局的 ContentType 表,使整体结构更加清晰。

例如,我们还是拿上面的评论来做例子。为了使数据表结构清晰,评论都放在一个表中,那么,一个评论就可能是 blog 的, tweets 的,也可能是 shop 的。

那就要定义一个 ContentType GenericForeignKey 来动态存储和得到外键所关联的表信息。

content_type 是关联到 ContentType model 上的一个外键,实际上是一个 integer ,表示所关联的表类别,例如 blog post 表。

object_id 标识的是所关联表中记录的 id 值,例如 post 表中的一个 blog 记录。

content_type, object_id 是默认的值,如果需要更改可以在创建 GenericForeignKey 时传参。

下面看看 django_content_type 表的结构

图[3]

app_label, model 组合是 UNIQUE

app_label 是 app 的名称。

model 是 app models 中的 model 名称。

图[2]中content_type_id 和 object_id 分别对应的就是 model 中声明的 content_type 和 object_id 字段。

通过content_type_id 可以在图[3]django_content_type 表中查询到对应关联的 

名称name(verbose_name) 名称app_label(app名称) model名称(model的小写)


通过 app_label, model 两个字段的拼接就可以得到表名,例如 id=41 blog_post 即为 content_type_id=41 对应的数据表,

说明 comment(id=1-7) 对应的是 blog app 的 post 数据表。


OK就是这样,其实很简单的东西,框架非常清晰,看文档的时候晕晕的。