============================ Django Step by Step (七) ============================ :作者: limodou :联系: limodou@gmail.com :版本: 0.1 :主页: http://wiki.woodpecker.org.cn/moin/NewEdit :BLOG: http://www.donews.net/limodou :版权: FDL .. contents:: 目录 .. sectnum:: 引言 ===== 敢问路在何方,路在脚下。如果你坚持下来,一定会有收获的。 直到目前我们已经学了: * settings.py的设置 * url dispatcher * 模板 * session * app * model 其实在某些方面,使用 Django_ 还可以更加方便。而且我们还有许多东西没有学,一点点跟着我学吧。 .. _Django: http://www.djangoproject.com/ 我有一个通讯录,它是保存在 Excel 文件中的,我不想每次到目录下去打开它,我希望用 Django 做一个web上的简单应用,如何做呢? 创建 address app =================== :: manage.py startapp address 这样就创建好了 address 相关的目录了。 修改 address/models.py ========================= :: #coding=utf-8 from django.db import models # Create your models here. class Address(models.Model): name = models.CharField('姓名', maxlength=6, unique=True) gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), maxlength=1, radio_admin=True) telphone = models.CharField('电话', maxlength=20) mobile = models.CharField('手机', maxlength=11) 这回 model 复杂多了。在上面你可以看到我定义了四个字段: ``name`` , ``gender`` , ``telpnone`` , ``mobile`` 。其中 ``gender`` 表示性别,它可以从一个 tuple 数据中进行选取。并且在后面的 ``radio_admin=True`` 表示在 admin 的管理界面中将使用 radio 按钮来处理。 .. note:: Django 提供了许多的字段类型,有些字段类型从数据的取值范围来讲没有什么区别,但之所以有这种区别,是因为:Django 的数据类型不仅仅用于创建数据库,进行 ORM 处理,还用于 admin 的处理。一方面将用来对应不同的 UI 控件,另一方面提供对不同的数据类型将进行不同的数据校验的功能。 在 Django 中每个字段都可以有一个提示文本,它是第一个参数,如果没有则会使用字段名。因此我定义的每个字段为了方便都有一个对应的汉字提示文本。 .. note:: 在 0.91 版,如果想要使用 admin 来编辑一个字段,还需要向 model 中的字段加入 ``core=True`` 参数。但 0.95 不再需要了。 因为本节主要是讲 admin 的使用。admin 是 Django 提供的一个核心 app(既然是 app 就需要安装,一会就看到了),它可以根据你的 model 来自动生成管理界面。我为什么要用它,因为有了这个管理界面,对于通讯录的增加、删除、修改的处理界面完全可以通过 admin 来自动生成,我不用自已写。不相信吗?我们就会看到了。 那么 admin 到底可以带来些什么好处呢?它的功能很强大,不仅界面漂亮,还能对数据提供操作记录,提供搜索。特别是它是在用户权限控制之下,你都可以不用考虑安全的东西了。并且它本身就是一个非常好的学习的东西,特别是界面自动生成方面,学习它的代码可以用在我们自已的定制之中。当然,你许你用不上 admin ,它的确有一定的适应范围,不过对于大部分工作来说它可能足够了。对于那些交互性强的功能,你可能要自已实现许多东西,对于管理集中,主要以发布为主的东西,使用它可以节省你大量的时间。至于怎么使用,你要自已去权衡。但这一点对于快速实现一个 web 应用,作用非常大,这是 Django 中的一个亮点。 修改 settings.py ==================== :: INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'newtest.wiki', 'newtest.address', ) 这里我们加入了两个 app ,一个是 ``address`` ,还有一个是 ``django.contrib.admin`` 。 admin 也是一个应用,需要加入才行,后面还要按添加 app 的方式来修改 url 映射和安装 admin app。这些与标准的app的安装没有什么不同。 安装 admin app ================ :: manage.py syncdb 这样将在数据库中创建 admin 相关的表。 修改 urls.py ============== :: from django.conf.urls.defaults import * urlpatterns = patterns('', # Example: # (r'^testit/', include('newtest.apps.foo.urls.foo')), (r'^$', 'newtest.helloworld.index'), (r'^add/$', 'newtest.add.index'), (r'^list/$', 'newtest.list.index'), (r'^csv/(?P\w+)/$', 'newtest.csv_test.output'), (r'^login/$', 'newtest.login.login'), (r'^logout/$', 'newtest.login.logout'), (r'^wiki/$', 'newtest.wiki.views.index'), (r'^wiki/(?P\w+)/$', 'newtest.wiki.views.index'), (r'^wiki/(?P\w+)/edit/$', 'newtest.wiki.views.edit'), (r'^wiki/(?P\w+)/save/$', 'newtest.wiki.views.save'), # Uncomment this for admin: (r'^admin/', include('django.contrib.admin.urls')), ) 缺省的 ``urls.py`` 中在最后已经加上了 admin 的映射,不过是一个注释,把注释去掉就好了。这里要注意,它使用了一个 ``include`` 方式。对于这种URL的解析 Django 是分段的,先按 ``r'^admin/'`` 解析(这里没有 ``$``),匹配了则把剩下的部分丢给 ``django.contrib.admin.urls.admin`` 去进行进一步的解析。使用 ``include`` 可以方便移植,每个 app 都可以有独立的 ``urls.py`` ,然后可以与主 ``urls.py`` 合在一起使用。配置起来相对简单。而且可以自由地在主 ``urls.py`` 中修改应用URL的前缀,很方便。 增加超级用户 ============== 进入 http://localhost:8000/admin .. image:: tut07_01.jpg 进入看一看吧。咦,要用户。对,admin 功能是有用户权限管理的,因此一个 admin 替你完成了大量的工作:用户的管理和信息的增加、删除、修改这类功能类似,开发繁琐的东西。那么我们目前还没有一个用户,因此可以在命令下创建一个超级用户,有了这个用户,以后就可以直接在 admin 界面中去管理了。 :: manage.py shell >>> from django.contrib.auth.create_superuser import createsuperuser >>> createsuperuser() 它会让你输入用户名,邮件地址和口令。 .. note:: 如果你使用了 syncdb 的话,应该在运行的最后,当没有超级用户时会提示你创建的。因此这一步可能会省略掉。如果想直接创建可以使用这种方法。 .. note:: 这种方法与 authentication 所讲述的不完全一致,原因就是这种方法不用设置 ``PYTHONPATH`` 和 ``DJANGO_SETTING_MODULE`` 环境变量,所以要简单一些。 这回再进去看一下吧。 .. image:: tut07_02.jpg 上面已经有一些东西了,其中就有用户管理。但如何通过 admin 增加通讯录呢?别急,我们需要在 model 文件中增加一些与 admin 相关的东西才可以使用 admin 来管理我们的 app 。 .. note:: 因此是否启用 admin 管理取决于你。只要在 model 中增加 admin 相关的部分,我们的应用才可以在 admin 中被管理。 修改 address/models.py ========================= :: #coding=utf-8 from django.db import models # Create your models here. class Address(models.Model): name = models.CharField('姓名', maxlength=6, unique=True) gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), maxlength=1, radio_admin=True) telphone = models.CharField('电话', maxlength=20) mobile = models.CharField('手机', maxlength=11) class Admin: pass .. note:: 在 0.91 版 Admin 内部类需要写成 :: class META: admin = meta.Admin() 在 0.95 版发生了变化。 有了这个东西,你就可以在 admin 中看到 adress 这个 app 了。再到浏览器中看一下是什么样子了。 .. image:: tut07_03.jpg 看见了吧。上面有增加和删除的按钮,先让我们点击一下增加吧。 .. image:: tut07_04.jpg 这个自动生成的界面是不是很不错。增加一条保存起来了。不过我发现当我输入 ``limodou`` 时,只能输入 ``limodo`` 好象 ``u`` 输不进去。为什么?因为我把姓名按汉字算最多6个就够了,一旦我使用英文的名字可能就不够。因此这是一个问题,一会要改掉。 .. image:: tut07_05.jpg 怎么新增的记录叫 ``
`` 这样看上去很别扭。为什么会这样,因为没有定义特殊的方法。下面就让我们定义一下。 .. note:: 在 0.91 版,我发现是看不到(其实还是可以点的。只是不容易)。 修改 address/models.py ========================= :: #coding=utf-8 from django.db import models # Create your models here. class Address(models.Model): name = models.CharField('姓名', maxlength=6, unique=True) gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), maxlength=1, radio_admin=True) telphone = models.CharField('电话', maxlength=20) mobile = models.CharField('手机', maxlength=11) def __str__(self): return self.name class Admin: pass 改好了,再刷新下页面。这次看见了吗?增加了一个 ``__str__`` 方法。这个方法将在显示 Address 实例的时候起作用。我们就使用某个联系人的姓名就行了。 .. note:: 曾经在 0.91 使用 ``__repr__`` 也可以,但后来只能使用 ``__str__`` 了。要注意。 .. image:: tut07_06.jpg 你记得吗?Model 是与数据库中的表对应的,为什么我们改了 model 代码,不需要重新对数据库进行处理呢?因为只要不涉及到表结构的调整是不用对表进行特殊处理的。不过,我们马上要修改表结构了。 修改 address/models.py ========================= 姓名留短了真是不方便,另外我突然发现需要再增加一个房间字段。 :: #coding=utf-8 from django.db import models # Create your models here. class Address(models.Model): name = models.CharField('姓名', maxlength=20, unique=True) gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), maxlength=1, radio_admin=True) telphone = models.CharField('电话', maxlength=20) mobile = models.CharField('手机', maxlength=11) room = models.CharField('房间', maxlength=10) def __repr__(self): return self.name class Admin: pass 这回表结构要改变了,怎么做呢? 修改表结构 =========== 目前 Django 没有一个特别的命令可以直接更新表结构。为什么呢?在 Django 看来修改表结构并不是件很容易的事情,主要的问题是数据库中现有的数据怎么办,因此为了使旧的数据可以平滑迁移到新的表结构中,这步操作还是手工来做好一些。但现在我们正在开发中,因此很有可能表结构要经常发生变化,每次手工做多麻烦呀。 Django 有一个命令行命令: ``sqlreset`` 可以生成 drop 表,然后创建新表的 SQL 语句,因此我们可以先调用这个命令,然后通过管道直接导入数据库的命令行工具中。这里我使用的是 sqlite3 ,因此我这样做:: manage.py sqlreset address|sqlite3 data.db ``sqlreset`` 后面是要处理的 app 的名字,因此它只会对指定的 app 有影响。但这样,这个 app 的所有数据都丢失了。如果想保留原有数据,你需要手工做数据切换的工作。 .. note:: 另外 ``django-amdin.py`` 还提供了更为简单的命令 ``manage.py reset address`` ,效果同上面是一样的。 对于其它的数据库,在数据库命令行可能是不同的,这个你自已去掌握吧。同时对于 sqlite3 ,有人可能想:直接把数据库文件删除了不就行了。但是你一定要清楚,如果存在其它的 app 的话,它们的数据是否还有用,如果没用删除当然可以,不过相应的 app 都要再重新 install 一遍以便初始化相应的表。如果数据有用,这样做是非常危险的,因此还是象上面的处理为好,只影响当前的 app 。 进入 admin =========== 我们可以再次进入 admin 了,增加,删除,修改数据了。 用了一会,也许你会希望:能不能有汉化版本的界面呢?答案是肯定的,而且已做好了。 修改 settings.py ================== 把 ``LANGUAGE_CODE`` 由 ``'en'`` 改为 ``'zh-cn'`` , ``TIME_ZONE`` 建议改为 ``'CCT'`` 刷新下界面,是不是变成汉字了。 国际化支持在 Django 中做得是非常的出色,程序可以国际化,模板可以国际化,甚至js都可以国际化。这一点其它的类似框架都还做不到。而国际化的支持更是 RoR 的一个弱项,甚至在 `Snakes and Rubies`_ 的会议上,RoR 的作者都不想支持国际化。但 Django 却做得非常出色,目前已经有二十多种语言译文。 .. _`Snakes and Rubies`: http://snakesandrubies.com/event 在增加,删除,修改都做完了,其实还剩下什么呢?显示和查询。那么实现它则需要写 view 和使用模板了。这个其实也没什么,最简单的,从数据库里查询出所有的数据,然后调用模板,通过循环一条条地显示。不错是简单。但是在做之前,先让我们想一想,这种处理是不是最常见的处理方法呢?也许我们换成其它的应用也是相似的处理。如果很多这样的处理,是不是我们需要每次都做一遍呢?有没有通用的方便的方法。答案是:有! Django 已经为我们想到了,这就是 `Generic views`_ 所做的。它把最常见的显示列表,显示详细信息,增加,修改,删除对象这些处理都已经做好了一个通用的方法,一旦有类似的处理,可以直接使用,不用再重新开发了。但在配置上有特殊的要求。具体的可以看 Generic views 文档。 .. _`Generic views`: http://www.djangoproject.com/documentation/generic_views/ 从这里我有一点想法,我认为 view 这个名称特别容易让人产生误解,为什么呢?因为 view 可以译为视图,给人一种与展示有关的什么东西。但实际上 Django 中的 view 相当于一个 Controller 的作用,它是用来收集数据,调用模板,真正的显示是在模板中处理的。因此我倒认为使用 Controller 可能更合适,这样就称为 MTC 了。呵呵,只是个人想法。 另外, Generic views 产生的意义在于 Django 的哲学理含 DRY (Don't repeat yourself, 不要自已重复),目的是重用,减少重复劳动。还有其它的哲学理含参见 `Design philosophies`_ 文档。 .. _`Design philosophies`: http://www.djangoproject.com/documentation/design_philosophies/ 因此可以知道 view 可以省掉,但模板却不能省, Django 在这点上认为:每个应用的显示都可能是不同的,因此这件事需要用户来处理。但如果有最简单的封装,对于开发人员在测试时会更方便,但目前没有,因此模板我们还是要准备,而且还有特殊的要求,一会就看到了。 对于目前我这个简单的应用来说,我只需要一个简单的列表显示功能即可,好在联系人的信息并不多可以在一行显示下。因此我要使用 ``django.views.generic.list_detail`` 模块来处理。 增加 address/urls.py =========================== 对,我们为 address 应用增加了自已的 urls.py。 :: from django.conf.urls.defaults import * from newtest.address.models import Address info_dict = { # 'model': Address, 'queryset': Address.objects.all(), } urlpatterns = patterns('', (r'^/?$', 'django.views.generic.list_detail.object_list', info_dict), ) ``info_dict`` 存放着 ``object_list`` 需要的参数,它是一个字典。不同的 generic view 方法需要不同的 ``info_dict`` 字典(这个变量你可以随便起名)。对于我们要调用的 ``object_list`` 它只要一个 queryset 值即可。但这个值需要是一个 queryset 对象。因此在第二句我们从 ``newtest.address.models`` 中导入了 ``Address`` 。并且使用 ``Address.objects.all()`` 来得到一个全部记录的 queryset 。 .. note:: 在 0.91 版,需要两个参数 ``app_name`` 和 ``module_name`` 。但在 0.95 版之后, ``module_name`` 取消了。代替为 ``model_name`` 的小写形式。而 ``info_dict`` 也变成了一个 model 值了。但最新的变化是 model 也不要了,取而代之的是 queryset 。这样会更方便。只是我的代码要改来改去的。 前面已经谈到:使用 generic view 只是减少了 view 的代码量,但对于模板仍然是必不可少的。因此要创建符合 generic view 要求的模板。主要是模板存放的位置和模板文件的名字。 使用 ``object_list()`` 需要的模板文件名为: ``app_label/model_name_list.html`` ,这是缺省要查找的模板名。 创建 templates/address 目录 ============================= 创建 templates/address/address_list.html ============================================ ::

通讯录


{% for person in object_list %} {% endfor %}
姓名 性别 电话 手机 房间
{{ person.name }} {{ person.gender }} {{ person.telphone }} {{ person.mobile }} {{ person.room }}
修改 urls.py ============== 将我们的应用的 urls.py include 进去。 :: from django.conf.urls.defaults import * urlpatterns = patterns('', # Example: # (r'^testit/', include('newtest.apps.foo.urls.foo')), (r'^$', 'newtest.helloworld.index'), (r'^add/$', 'newtest.add.index'), (r'^list/$', 'newtest.list.index'), (r'^csv/(?P\w+)/$', 'newtest.csv_test.output'), (r'^login/$', 'newtest.login.login'), (r'^logout/$', 'newtest.login.logout'), (r'^wiki/$', 'newtest.wiki.views.index'), (r'^wiki/(?P\w+)/$', 'newtest.wiki.views.index'), (r'^wiki/(?P\w+)/edit/$', 'newtest.wiki.views.edit'), (r'^wiki/(?P\w+)/save/$', 'newtest.wiki.views.save'), (r'^address/', include('newtest.address.urls')), # Uncomment this for admin: (r'^admin/', include('django.contrib.admin.urls')), ) 可以看到 ``r'^address/'`` 没有使用 ``$`` ,因为它只匹配前部分,后面的留给 address 中的 ``urls.py`` 来处理。 启动 server 看效果 =================== .. image:: tut07_07.jpg