博客首页|TW首页| 同事录|业界社区
2011-04-18

摘要

本文主要说明在 django 中 schema migration 的两种最常用工具 south 和 evolution, 并且说明二者的差异和为什么要使用 south 作为最主要的 migration 工具。

正文

关于schema migration

无论我们使用何种语言进行web开发,快速开发随之相伴的是需求的不断变动,也就意味着我们要不断 增加或者调整已有的数据库模式(database schema),譬如一个很常见的变动是,我们需要在用户表中 增加一个状态位来标记当前用户是否已经删除,而不是直接从数据库中删除(虽然我不支持这样的保留用户 数据的行为,可是如今大多数的应用即使你要删除自己的账号,其实也不会永久删除的,所以,只要是网上的 信息,大致你可以认为是不会消失的),那么除了应用逻辑的改动外,你需要在数据库上增加一个状态位的 字段。

上面就是一个很常见的应用场景,当然诸如字段的属性更改,增加或者减少字段等等,也都属于这个范畴。

很可惜, django 本身并不支持 schema migration (也就是当你执行 syncdb 时并不会产生任何作用, 增加和删除字段会有效,不过复杂的则不支持,如更改一个字段的属性等),这也就是 evolution 和 south 所要解决的问题。

关于evolution

相比于下面要说明的 south , evolution 出现的比较早,它的主要思路是:在项目初始时会对所有的数据库schema 进行记录(也会存在一个数据库表中),当某个表的schema有更改时,当你执行 syncdb 时, evolution也会与当前记录的 schema进行比较,如果 evolution 认为有更改,则它会进行比较进而生成一个最新schema与上次schema所要做更改的sql,用户可选择执行来进行 schema migration.

相对而言, evolution 很容易集成到自己的项目中,并且也很容易使用,并且 通常 也能很好工作。所以,在我最初的 项目中我基本都是使用 evolution ,但是相比于 south , evolution 的不足有:

  1. 开发并不活跃(写本文时,看到的最近一次更新是2010/11/19)
  2. 没有得到 django 项目核心开发人员的推荐和认可(而 south 是推荐的选项)
  3. 不支持1.2的多数据库
  4. 不支持数据的迁移(只支持表结构本身的迁移)
  5. 不支持rollback到某个schema
  6. 通常需要从项目上线起就开始使用(也就是没有数据时),对于已经有数据的项目则不支持
  7. 跨app的迁移并不支持
  8. migration的code并不能纳入到版本控制工具中(因为 evolution 使用数据库表,而数据库本身是没有状态的)

当然它也有诸如简单易用,学习曲线低,配置较少等优点,当然 south 也并不复杂,并且有更多的优点,请参考下面的说明。

关于south

south 正是因为 evolution 有这么多的问题,作者才开始了这个项目,上面提到的8个问题, south 已经很好 地进行了解决,并且在未来可能加入到 django 的代码库中(其实1.2也差点合并进去,因为 south 作者不建议现在合并 才最终没有成形, 具体可查看 作者的说明 )。

如果你之前没有使用过 south ,那么从现在起开始用 south 会对你受益匪浅; 如果你之前使用的是 evolution ,你会发现 south 更加友好和强大。

那么,不妨从今天起在你的项目中开始使用 south 吧,如何开始,具体可以参考 south的tutorial

另外,你也可以看看 south alternatives 和 south’s design 两篇文章来了解更多。

总结

django 在不断发展,相应的周边的工具也是层出不穷,选择合适高效的工具,对于开发者而言是有很重要的意义的, 而让人头疼的 schema migration 则会因为 south 的出现而得到很好的解决。

下载原文

可从 此处 查看或者下载。

2011-03-21

PureMVC 是大量使用于flash开发的一个框架,它很好地对处理对象进行了 最常见的MVC模式划分,清晰的逻辑和功能使得开发者能够很好地开发 新的功能,阅读旧的功能等,所以很多的flash的前端代码都是基于 PureMVC 开发的.

最近因为工作上的需要,我开始学习 PureMVC ,之前也简单地学习过,也按照 官方的教程完成过一个示例,并且也仔细阅读过 相关的教程 ,但是对于 PureMVC 的理解还是相对粗浅,这次在之前理解的基础上做出了一个最简单的 Hello World式的教程,希望加深自己对于这个框架的理解的同时,也能够帮助到 其他人.

PureMVC Hello World

这个示例的主要场景如下:

  1. 在UI上显示一个登录用的表单
  2. 表单上有name, password,和登录的按钮(代码中使用的还是TextField)
  3. 点击登录后,如果name,password匹配则显示登录成功,否则显示登录失败

基于上面的描述,再根据 PureMVC 的框架,我们大致可分为如下几个部分:

  1. 启动用的单例AppFacade

  2. Proxy:

    • LoginProxy: 用于验证用户提交的表单是否合法
  3. Command:

    • StartUpCommand: 用于启动整个应用的Command, 也兼做一些初始化的工作
    • initMVCCommand: 用于初始化并注册相关的MVC,如mediator, proxy等
    • LoginCommand: 用于处理Login时的逻辑,如调用LoginProxy来进行验证
  4. Mediator:

    • ViewMediator: 用于UI的显示

整个应用的流程为:

AppFacade(register command, send notification to start)-> StartUpCommand(execute) -> initMVCCommand(execute, register MVC)
-> ViewMediator(construtor, init the UI)

ViewMediator(Login Button Clicked) -> Login(Notification) -> LoginCommand(verfiy login using LoginProxy) -> Login_Succ/Login_Fail
-> ViewMediator(show the login result message)

那么下面我们分别看下重要的几个模块.

AppFacade

这是用于启动和关联整个 PureMVC 通信过程的辅助性类,单例的. 在这个类中主要注意,初始时的notification发送,注册初始时的Command等即可.

具体代码见: https://github.com/topman/blog_code/blob/master/HelloPureMVC/src/AppFacade.as

Proxy

Proxy主要功能是处理数据相关的信息,也即Domain logic(域逻辑),通常它只处理数据,保证数据的正确,完整性等. 而与此不同的是Command,它处理的是Business logic(业务逻辑),它更加关注应用所在业务的相关逻辑.

参考 Business Logic和Domain Logic的区别.

在示例中的Proxy, 它的主要功能是提供相关的数据, 因为登录验证是一个非常常见的功能,在实际的登录验证中, 通常是后端进行验证,返回一个成功或者失败即可,所以此例中将验证也放入了Proxy的逻辑中.

具体代码见: https://github.com/topman/blog_code/blob/master/HelloPureMVC/src/model/LoginProxy.as

Command

Command是处理应用的Business Logic(业务逻辑),与Proxy的区别参考上面Proxy的部分. 本例中的Command主要是包括三个,即:

  • StartUpCommand: 应用启动的命令,调用其它的初始化命令即initMVCCommand
  • initMVCCommand: 用于初始化整个MVC,注册MVC各个部分
  • LoginCommand: 用于处理登录逻辑的命令

具体的代码见: https://github.com/topman/blog_code/tree/master/HelloPureMVC/src/controller

注意在 PureMVC 中命令有MacroCommand和SimpleCommand两类,前者调用后者,后者则实现具体的业务逻辑.

当然所有的逻辑都可以使用SimpleCommand来完成,但是通常MacroCommand命令可以简化整个代码结构,使得 逻辑更加清晰易读.

Mediator

由于示例比较简单所以只有一个Mediator来处理相关的UI显示, Mediator通常的功能是显示, 接收UI的事件, 响应来自业务逻辑的UI变动等.

示例中主要是两个功能,一个是初始化时显示登录对话框,一个是对于Login_Succ和Login_Fail事件的响应. 相应的代码参考:https://github.com/topman/blog_code/blob/master/HelloPureMVC/src/view/ViewMediator.as

结语

我个人之前更多的是做后端开发,没有系统地接触过前端的开发,如Javascript, flash等. 第一次使用和了解 PureMVC , 发现其理念与后端的MVC框架是一致的,只是在更细层面进行了MVC的划分, 例如一个项目前端是flash,后端是 DjangoDjango 本身是MVC,而 PureMVC 又是在 Django 的V层上进行了更加细致的MVC划分.

Django 的MVC是简洁,清晰和优美的,了解 PureMVC 后,同样的特征也适用,我想在后续开发中可以比较熟练地加以应用, 也能够更加顺手地控制工作的进展,不至于项目的进展完全驱动于自己不熟悉的前端.

2011-03-09

接着 上文 的内容,本文将基于自己的理解尝试回答 上文 中研究列表中的3和4,即:

  1. 理解和分析Admin系统的设计和代码书写值得学习和注意的问题
  2. 总结

理解和分析Admin系统的设计和代码书写

Admin系统具有多种特征,首先它是一个普通的 Django 应用,其次它又承担 作为各个其它Djago应用的基础应用而存在,所以我们可以从上面两个方面 来加以分析。

作为一个普通的应用

Django 本身是一个基于MVC的web框架,各个层次非常清楚,当然在 Django 中,更多地被 称为MTV,即Model, Template, View,对应于MVC的Model, View, Controller。 其中Template和View的衔接是通过url映射完成,而url映射也是 Django 架构中最为巧妙的 地方。

http://towerjoo.blog.techweb.com.cn/wp-content/blogs.dir/14215/files/blog/django_layers.png对于一个 Django 的应用,我们通常可以从url映射入手,找到url处理的函数,和渲染出的页面, 从而理清整个的应用逻辑。

Django 的Admin应用的url映射大致如下:

# sites.py
urlpatterns = patterns('',
    url(r'^$',
        wrap(self.index),
        name='index'),
    url(r'^logout/$',
        wrap(self.logout),
        name='logout'),
    url(r'^password_change/$',
        wrap(self.password_change, cacheable=True),
        name='password_change'),
    url(r'^password_change/done/$',
        wrap(self.password_change_done, cacheable=True),
        name='password_change_done'),
    url(r'^jsi18n/$',
        wrap(self.i18n_javascript, cacheable=True),
        name='jsi18n'),
    url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
        'django.views.defaults.shortcut'),
    url(r'^(?P<app_label>\w+)/$',
        wrap(self.app_index),
        name='app_list')
    )

# Add in each model's views.
for model, model_admin in self._registry.iteritems():
    urlpatterns += patterns('',
        url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
                include(model_admin.urls))
    )

从上面的代码,我们可以看到,Admin应用的landing page,登出,密码修改等相应的逻辑, 重点查看最后几行代码,将已经注册的应用的model(数据库的表)相应url映射指向了其自身的方法。

我们来看model_admin.urls这个逻辑:

urlpatterns = patterns('',
    url(r'^$',
        wrap(self.changelist_view),
        name='%s_%s_changelist' % info),
    url(r'^add/$',
        wrap(self.add_view),
        name='%s_%s_add' % info),

    # added by Tower Joo At 2011/2/27
    # have to be ahead of change_view unless it will be overrided
    url(r'^export/$',
        wrap(self.export),
        name='%s_%s_export' % info),
    url(r'^aggregate/$',
        wrap(self.aggregate),
        name='%s_%s_aggregate' % info),

    url(r'^(.+)/history/$',
        wrap(self.history_view),
        name='%s_%s_history' % info),
    url(r'^(.+)/delete/$',
        wrap(self.delete_view),
        name='%s_%s_delete' % info),
    url(r'^(.+)/$',
            wrap(self.change_view),
            name='%s_%s_change' % info),
    )

从上面的url映射来看,已经包含了增加记录,删除记录,修改记录等操作,当然上面也包含了 在 上文 中我做的一些url映射的修改。

看过上面的url映射后,心里就会有数,知道整个Admin系统大致有多少个页面,各个页面的作用是什么。

然后,我们可以通过url映射知道处理这个url的函数,例如,处理/add/的self.add_view函数。 通过阅读self.add_view函数的代码,除了相比于我们通常的逻辑更多的边界和条件处理外,其它的 也是相同的,例如数据库的操作,页面渲染所需要的数据准备等。

最后,我们看到的是页面渲染所需要的Template, change_form.html,查看对应的代码,则与通常的
Django 的Template并无二异。

其它的逻辑也相同。相比于我们自己的 Django 应用,这个逻辑中有更多的边界条件的处理和异常的处理 等,你可能会说它过于复杂,这也就引入了我们接下来要说明的第二个问题。

作为其它应用的通用基础应用

作为一个基础的应用,它就需要适应所有基于其上的其它应用的需要。 通用性通常意味着更多的复杂性,我们来看 Django 是如何有效且优美地处理这个问题的。

  1. 充分使用Python的内省,也即model的元数据,如app_label, module_name等,使得动态地构造url映射 和合适的显示成为可能
  2. admin = AdminSite()是一个全局的变量,来维护所有的注册应用的列表
  3. 两级的处理结构:admin site级和table级,分别由两个类来处理,并完成相应的url映射
  4. 可配置性:对于其上层的应用,都提供了完善的可override的属性,如list_display,list_filter等等
  5. 使用类而非 Django 默认推荐的函数作为view的处理,这样就提供了用户基于Admin系统建立自己的Admin系统的可能

总结

Admin系统可谓是 Django 最强大的功能了,它也大大方便了数据库驱动的应用的开发难度,为应用的数据输入,管理等 提供了一个稳定,可靠,方便的管理界面.

从 上文 和本文的介绍中,我们可以从中学习到一些 Django 常用的开发技巧:

  1. 基于类的view实现(当然在 Django 已经完全支持class view了, 参考 Class-based generic views )
  2. 分级的数据处理
  3. 可配置性
  4. 灵活性

会使用 Django 的Admin系统那可以让你的工作时间节省30%以上(基于数据库的应用),如果能够弄清楚 Django Admin 的实现原理并从中学习到可用于自己实际开发的经验与方法,则可运筹帷幄,谈笑风生地写代码了.

2011-02-28

Django 的Admin系统是 Django 几大killer feature之一, 所以对它的一些研究对于熟悉 Django 及其应用都有重要价值(因为Admin系统 是一个完整的 Django 应用示例).

http://towerjoo.blog.techweb.com.cn/files/2011/02/django_logo.png

本次大致的研究过程如下:

  1. 将 Django 的Admin系统拷贝出来,在一个新的 Django 项目中作为独立应用来运行
  2. 对这个独立的应用进行一定的修改
  3. 理解和分析Admin系统的设计和代码书写值得学习和注意的问题
  4. 总结

相关的代码可在 github 查看和检出,也可 下载zip后的代码 。 (techweb会过滤掉一些字符导致无法正确显示和访问,正在协调解决)

将Django的Admin系统作为独立应用来运行

  1. 使用django-admin.py startproject myadmin新建一个新项目
  2. 将django的原文件拷贝到新项目myadmin目录下
  • 找到django的安装目录, 如果不知道,可在python的交互式prompt下输入:
  • import django
  • print django.__file__
  • 那么 Django 的Admin应用就在DJANGO/contrib/admin/
  • 将整个目录拷贝到myadmin目录下
  • 如果需要,则调整相关的文件权限
  1. 修改myadmin项目中的settings.py和urls.py
  2. 同步数据库(syncdb)和运行代码

这时我们的第一个目标得以实现,因为根据Python语言package的导入规则(或者叫module search规则), 相对引入(relative import)会覆盖系统的django module,所以myadmin项目中运行的是myadmin下的 admin项目,而不是系统安装的。

为了检验,我们不妨可做一个简单的修改来证明,我们使用的是myadmin下的admin,我们在myadmin/django/contrib/admin/sites.py 的248行加入一句: import logging; logging.debug(”My Edit here”)

使用浏览器请求http://127.0.0.1:8000/admin/, 则你可在输出的log中看到上面的log.

对Admin系统进行一定的修改

我们打算做以下几点修改:

  1. 在一个具体的table的显示页面,增加导出csv的功能(也就是将当前查看表的数据导出为csv)
  2. 在一个具体的table的显示页面,增加快捷的聚合操作的支持(如sum,average等)

修改1

通过阅读代码,大致的修改步骤如下:

  1. 找到显示页面对应的template(change_list.html),也可使用方便的debug_toolbar来查看
  2. 添加导出为csv的链接
  3. 增加相应的url映射(options.py第254行)
  4. 增加相应的处理(options.py第773行)

相应的csv逻辑还是比较简单的,当然我只实现了将此表中所有记录导出的功能,部分数据导出的功能并未实现。

在实现的过程中,找到相应的url映射和及处理逻辑是比较重要,和相对较难的,大致的过程如下:

  1. 入口是: admin.sites.urls,发现其是一个property
  2. 继而找到get_urls方法,得到相应的url映射,发现对于特定model的url映射是对应各个model的urls属性
  3. 继而找到options.py文件,同样发现其是一个property
  4. 继而找到get_urls方法,及其相应的url映射,这里便是我们要添加我们的url映射的地方
  5. 然后加入相应的url映射的处理逻辑即可

从上面的代码我们会发现django的逻辑很清楚,而且层次分割和明确。

修改2

基本的步骤同修改1,只是在功能和界面上有所调整。

在修改2的完成中,我们使用了

  1. forms
  2. database aggregation

最终的结果可见下图:

http://towerjoo.blog.techweb.com.cn/files/2011/02/django_admin_result1.png

分析与总结

请查看下篇文章