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

引言
=====

上一讲的确很长，但如果看代码你会发现，代码主要在 model 的调整中，``urls.py`` 的工作不多，而连一行 view 的代码都没有写。是不是非常方便呢！

那么让我们来继续完善这个通讯录吧。

现在我想完成的是：

    增加批量导入和导出功能

为什么要批量导入呢？因为一般情况下，我一定是已经有了一个通讯录文件(象以前我说过的Excel文件)，那么现在需要转到 web 上来，难道要我一条条全部手工录入吗？能不能上传文件，自动插入到数据库中去呢？那么就让我们实现一个文件上传的处理吧。

为了简化，我采用csv格式文本文件(这个文件在svn中有一个例子 ``data.csv`` ，不然就自行生成好了)。

::

    abc,M,11,11,11,
    bcd,M,11,11,11,
    ass,M,11,11,11,
    dfsdf,F,11,11,11,
    sfas,F,11,11,11,
    ...
    
修改 templates/address/address_list.html
===========================================

::

    <h1 id="title">通讯录</h1>
    <hr>
    <form enctype="multipart/form-data" method="POST" action="/address/upload/">
    上传通讯录文件： <input type="file" name="file"/><br/>
    <input type="submit" value="上传文件"/>
    </form>
    <table border="1">
    <tr>
      <th>姓名</th>
      <th>性别</th>
      <th>电话</th>
      <th>手机</th>
      <th>房间</th>
    </tr>
    {% for person in object_list %}
    <tr>
      <td>{{ person.name }}</td>
      <td>{{ person.gender }}</td>
      <td>{{ person.telphone }}</td>
      <td>{{ person.mobile }}</td>
      <td>{{ person.room }}</td>
    </tr>
    {% endfor %}
    </table>

修改 address/views.py
============================

::

    #coding=utf-8
    # Create your views here.
    from newtest.address.models import Address
    from django.http import HttpResponseRedirect
    from django.shortcuts import render_to_response
    
    def upload(request):
        file_obj = request.FILES.get('file', None)
        if file_obj:
            import csv
            import StringIO
            buf = StringIO.StringIO(file_obj['content'])
            try:
                reader = csv.reader(buf)
            except:
                return render_to_response('address/error.html', 
                    {'message':'你需要上传一个csv格式的文件！'})
            for row in reader:
    #            objs = Address.objects.get_list(name__exact=row[0])
                objs = Address.objects.filter(name=row[0])
                if not objs:
                    obj = Address(name=row[0], gender=row[1], 
                        telphone=row[2], mobile=row[3], room=row[4])
                else:
                    obj = objs[0]
                    obj.gender = row[1]
                    obj.telphone = row[2]
                    obj.mobile = row[3]
                    obj.room = row[4]
                obj.save()
               
            return HttpResponseRedirect('/address/')
        else:
            return render_to_response('address/error.html', 
                {'message':'你需要上传一个文件！'})

这里有一个 ``upload()`` 方法，它将使用 csv 模块来处理上传的 csv 文件。首先查找姓名是否存在于数据库中，如果不存在则创建新记录。如果存在则进行替换。如果没有指定文件直接上传，则报告一个错误。如果解析 csv 文件出错，则也报告一个错误。

报造错误使用了一个名为 error 的模板，我们马上要创建。

创建 templates/error.html
============================

::

    <h2>出错</h2>
    <p>{{ message }}</p>
    <hr>
    <p><a href="/address/">返回</a></p>

很简单。

修改 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),
        (r'^upload/$', 'address.views.upload'),
    )

增加一个 upload 的 url 映射。

启动 server 测试
=================

这样导入功能就做完了。那导出呢？很简单了，参考 csv 的例子去做就可以了。不过，并不全是这样，仍然有要修改的地方，比如 csv.html 模板，它因为写死了处理几个元素，因此需要改成一个循环处理。

修改 templates/csv.html
=========================

::

    {% for row in data %}{% for i in row %}"{{ i|addslashes }}",{% endfor %}
    {% endfor %}

将原来固定个数的输出改为循环处理。

修改 templates/address/address_list.html
===========================================

增加一个生成导出的 csv 文件的链接

::

    <h1 id="title">通讯录</h1>
    <hr>
    <form enctype="multipart/form-data" method="POST" action="/address/upload/">
    上传通讯录文件： <input type="file" name="file"/><br/>
    <input type="submit" value="上传文件"/>
    </form>
    <hr>
    <p><a href="/address/output/">导出为csv格式文件</a></p>
    <table border="1">
    <tr>
      <th>姓名</th>
      <th>性别</th>
      <th>电话</th>
      <th>手机</th>
      <th>房间</th>
    </tr>
    {% for person in object_list %}
    <tr>
      <td>{{ person.name }}</td>
      <td>{{ person.gender }}</td>
      <td>{{ person.telphone }}</td>
      <td>{{ person.mobile }}</td>
      <td>{{ person.room }}</td>
    </tr>
    {% endfor %}
    </table>

修改 apps/address/views.py
============================

::

    #coding=utf-8
    # Create your views here.
    from newtest.address.models import Address
    from django.http import HttpResponse, HttpResponseRedirect
    from django.shortcuts import render_to_response
    from django.template import loader, Context
    
    def upload(request):
        file_obj = request.FILES.get('file', None)
        if file_obj:
            import csv
            import StringIO
            buf = StringIO.StringIO(file_obj['content'])
            try:
                reader = csv.reader(buf)
            except:
                return render_to_response('address/error.html', 
                    {'message':'你需要上传一个csv格式的文件！'})
            for row in reader:
    #            objs = Address.objects.get_list(name__exact=row[0])
                objs = Address.objects.filter(name=row[0])
                if not objs:
                    obj = Address(name=row[0], gender=row[1], 
                        telphone=row[2], mobile=row[3], room=row[4])
                else:
                    obj = objs[0]
                    obj.gender = row[1]
                    obj.telphone = row[2]
                    obj.mobile = row[3]
                    obj.room = row[4]
                obj.save()
               
            return HttpResponseRedirect('/address/')
        else:
            return render_to_response('address/error.html', 
                {'message':'你需要上传一个文件！'})
                
    def output(request):
        response = HttpResponse(mimetype='text/csv')
        response['Content-Disposition'] = 'attachment; filename=%s' % 'address.csv'
        t = loader.get_template('csv.html')
    #    objs = Address.objects.get_list()
        objs = Address.objects.all()
        d = []
        for o in objs:
            d.append((o.name, o.gender, o.telphone, o.mobile, o.room))
        c = Context({
            'data': d,
        })
        response.write(t.render(c))
        return response
    
在开始处增加了对 ``HttpResponse``, ``loader``, ``Context`` 的导入。然后增加了用于输出处理的 ``output()`` 方法。

修改 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),
        (r'^upload/$', 'address.views.upload'),
        (r'^output/$', 'address.views.output'),
    )

增加了对 output 方法的 url 映射。

启动 server 测试
===================