============================
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::

引言
=====

经过前几节的学习，我想大家应该比较熟悉 Django 的大致开发流程：

    * 增加 view 方法
    * 增加模板
    * 修改 ``urls.py``

.. _Django: http://www.djangoproject.com/

就是这样。剩下的就是挖掘 Django 提供的其它的能力。在我们还没有进入模型(model)之前还是再看一看外围的东西，再更进一步体验 Django 吧。

在 Django 中我看到了一个生成 csv 格式的文档(`Outputting CSV dynamically`_)，非常好，它没有数据库，正好用来做演示。

.. _`Outputting CSV dynamically`: http://www.djangoproject.com/documentation/outputting_csv/

现在我的需求就是提供 csv 格式文件的下载。

我们会在原来 list(表格) 例子基础上进行演示，步骤就是上面的流程。

修改 templates/list.html
==========================

在文件最后增加::

    <p><a href="/csv/address/">csv格式下载</a></p>

它将显示为一个链接，它所指向的链接将用来生成 csv 文件。

在newtest下增加 csv_test.py
=============================

::

    #coding=utf-8
    from django.http import HttpResponse
    from django.template import loader, Context
    
    address = [
        ('张三', '地址一'), 
        ('李四', '地址二')
        ]
    
    def output(request, filename):
        response = HttpResponse(mimetype='text/csv')
        response['Content-Disposition'] = 'attachment; filename=%s.csv' % filename
    
        t = loader.get_template('csv.html')
        c = Context({
            'data': address,
        })
        response.write(t.render(c))
        return response
    
.. note:: 此文件原来为 ``csv.py`` ，但后来发现与系统的 csv 模块重名，因此改名

.. note:: 在 0.91 版中 ``HttpResponse`` 是从 ``django.utils.httpwrappers`` 导入的，而 0.92 为 ``django.http`` 。 ``loader, Context`` 是从 ``django.core.template`` 导入的，而 0.92 为 ``django.template`` 。具体可以参考： NamespaceSimplification_ 文档。以后类似的地方不再详述。

.. _NamespaceSimplification: http://code.djangoproject.com/wiki/NamespaceSimplification

这里使用的东西多了一些。这里没有 ``render_to_response`` 了，而是演示了一个完整的从头进行模板解析的处理过程。为什么需要这样，因为我们需要修改 ``response`` 对象的值，而 ``render_to_response`` 封装了它使得我们无法修改。从这里我们也可以看到，在调用一个方法时， Django 会传入一个 ``request`` 对象，在返回时，你需要将内容写入 ``response`` ，必要时修改它的某些属性。更详细的建议你参考 django 所带的 request_response 文档，里面详细描述了两个对象的内容，并且还可以在交互环境下进行测试，学习非常方便。

.. note:: response 对象也是由 Django 自动维护的。具体的内容参见 `Request and response objects`_ 文档。

.. _`Request and response objects`: http://www.djangoproject.com/documentation/request_response/

这里 ``address`` 不再是字典的列表，而是 tuple 的列表。让人高兴的是， Django 的模板除了可以处理字典，还可以处理序列，而且可以处理序列中的元素。一会在模板定义中我们会看到。

这里 ``output()`` 是我们希望 Django 调用的方法，不再是 ``index()`` 了。(不能老是一样的呀。)而且它与前面的 ``index()`` 不同，它带了一个参数。这里主要是想演示 url 的参数解析。因此你要注意，这个参数一定是放在 url 上的。它表示输出文件名。

::

    response = HttpResponse(mimetype='text/csv')
    response['Content-Disposition'] = 'attachment; filename=%s.csv' % filename

这两行是用来处理输出类型和附件的，以前我也没有用过，这回也学到了。它表明返回的是一个 csv 格式的文件。

::

    t = loader.get_template('csv.html')
    c = Context({
        'data': address,
    })
    response.write(t.render(c))

这几行就是最原始的模板使用方法。先通过 ``loader`` 来找到需要的模板，然后生成一个 template 对象，再生成一个 ``Context`` 对象，它就是一个字典集。然后 ``t.render(c)`` 这个用来对模板和提供的变量进行合并处理，生成最终的结果。最后调用 ``response.write()`` 将内容写入。

增加 templates/csv.html
==========================

::

    {% for row in data %}"{{ row.0|addslashes}}", "{{ row.1|addslashes}}",
    {% endfor %}
    
使用了一个 for 循环。这里 ``data`` 与上面的 ``Context`` 的 ``data`` 相对应。因为 ``data`` 是一个列表，它的每行是一个 tuple ，因此 ``row.0, row.1`` 就是取 tuple 的第一个和第二个元素。 ``|`` 是一个过滤符，它表示将前一个的处理结果作为输入传入下一个处理。因此 Django 的模板很强大，使用起来也非常直观和方便。 ``addslashes`` 是 Django 模板内置的过滤 Tag ，它用来将结果中的特殊字符加上反斜线。

同时我们注意到，每个 ``{{}}`` 前后都有一个双引号，这样就保证每个字符串使用双引号引起来。然后在第一个与第二个元素之间还使用了逗号分隔。最后 ``endfor`` 在下一行，表示上面每行模板后有一个回车。

Django 还允许你自定义 Tag ，在 `The Django template language: For Python programmers`_ 文档中有描述，其实是很简单的。

.. _`The Django template language: For Python programmers`: http://www.djangoproject.com/documentation/templates_python/

修改 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<filename>\w+)/$', 'newtest.csv_test.output'),
    
        # Uncomment this for admin:
    #     (r'^admin/', include('django.contrib.admin.urls')),
    )

增加了 csv 的 url 映射。

上面的正则表达式有些复杂了，因为有参数的处理在里面。 ``(?P<filename>\w+)`` 这是一个将解析结果起名为 ``filename`` 的正则表达式，它完全符合 Python 正则表达式的用法。在最新的 Django 中，还可以简化一下： ``(\w+)`` 。但这样需要你的参数是按顺序传入的，在一个方法有多个参数时一定要注意顺序。

.. note:: 关于 url 的配置问题在 `URL configuration`_ 文档中有说明。

.. _`URL configuration`: http://www.djangoproject.com/documentation/url_dispatch/

还记得吗？我们的链接是写成 ``/csv/address/`` ，因此上面实际上会变成对 ``csv.output(filename='address')`` 的调用。

启动 server
=============

看一下结果吧。点击链接，浏览器会提示你保存文件的。

很简单吧。但这里面的内容其实也不少，而且许多地方都有很大的扩展空间。