Skip to content

ねたみまん

Webサイト運営の裏側で必要な技術、テクニック、マーケティング、考え方などのメモを記事にしたサイトです

Archive

Category: Python

Djangoで顧客管理システムを作るテストをするのに、ダミーの顧客情報を作る方法を以前紹介した。
個人情報のダミーデータを作ってくれるサイト

そこで作ったダミーの顧客データをDjangoに入力するのに、どのようにしたらいいのか調べてみると・・・
manage.pyにloaddata というFixtureを入力するコマンドがあることを知った。

> manage.py loaddata data.json

Fixture は XML, JSON, YAML形式で書いていたらOKらしい。
manage.py dumpdata appname
で出力したデータがそのままFixtureに使えるそうなので、それを参考にCSVファイルからJSON形式のFixtureを作るスクリプトを作ってみた。

JSON形式のFixtueの構造

まず始めにJSON形式のFixtureの構造は以下のようになっている。

[
    {"pk": 1,
     "model": "customer.item",
     "fields":
        {"category": "\u30af\u30ed\u30b9",
         "code": "a-silvercloth",
         "name": "\u9280\u307f\u304c\u304d\u30af\u30ed\u30b9\uff082\u679a\u5165\uff09",
         "price": "525",
         "memo": "\u9280\u78e8\u304d\u30af\u30ed\u30b9\u30022\u679a\u5165\u308a\u3002",
         "unit": "\u500b"}
     },
     {"pk": 2,
      "model": "customer.item",
      "fields":
          {"category": "\u30d0\u30f3\u30b0\u30eb",
           "code": "ba-ki-l0001",
           "name": "\u30ec\u30c7\u30a3\u30b9\u30d0\u30f3\u30b0\u30eb\u3000\u300e\u83ca\u6c34\u300f",
           "price": "70350",
           "memo": "",
           "unit": "\u500b"}
     }
]

Objectで書かれた各データの配列になっていて、各データのObjectは
pk — PrimaryKeyの値
model — Djangoのモデル名 アプリ名.モデルクラス名 の形式で表記
fields — 各フィールドのフィールド名と値のObject

となっている。
ちなみにJSONの構文は以下のページを見れば良く分かる。
JSONの紹介

手書きでも書けるので、初期データを放り込むのにカンタンなものなら手書きで書いてもいいんだけど、顧客ダミーデータは5000件あるのでそれを手書きで変換するのはめんどくさい。

そこで、カンタンに変換できるツールを作ってみた。

CSVデータをJSONのFixtureに変換するツール(Pythonスクリプト)

ツールの構成は
1.変換用スクリプト make_fixture.py
2.変換設定ファイル(JSON形式)
からなっていて、コマンドラインで利用する。

使い方は、変換設定ファイルを書いて、コマンドラインから
make_fixture.py -s 設定ファイル -i 変換元CSVファイル -o 変換後の出力ファイル
とするだけ。
各ファイルはUTF-8で使うことを前提にしている(UTF-8でしかテストしていない)

例として前回作ったダミーの顧客情報を変換する設定ファイルを載せてみる。

変換元のCSVデータはこんな感じ

姓,名,姓(カタカナ),名(カタカナ),性別,電話番号,携帯電話,メールアドレス,郵便番号,住所,,,,,生年月日,年齢
早川,順,ハヤカワ,ジュン,M,03-0043-5967,080-1129-0749,srkpfajun112@ftapigbq.uh,229-0032,神奈川県,相模原市,矢部,2-8,グランド矢部403,1980/03/09,29
都築,武雄,ツズキ,タケオ,M,03-8521-5608,080-4747-0980,takeotsuzuki@cukt.npkbr.fi,393-0046,長野県,諏訪郡下諏訪町,東赤砂,4-5-15,,1988/05/27,21
内藤,果歩,ナイトウ,カホ,F,03-7080-8426,090-0443-2574,jyqydqafb-kaho64604@pljimta.qc,794-0015,愛媛県,今治市,常盤町,1-3-18,,1979/07/05,30
岩永,紅葉,イワナガ,クレハ,F,03-7063-2531,080-1587-4004,dyjkpsnkureha28732@yoiyr.ks,382-0806,長野県,上高井郡高山村,なかひら,3-11-16,,1944/03/13,65
永野,志保,ナガノ,シホ,F,03-7739-4442,,shiho5190@voyifkg.pd,297-0075,千葉県,茂原市,押日,2-11-11,,1944/08/21,65

※これは、完全にダミーのデータなのでこの世に存在しない人の情報ですが、偶然にも存在してしまった場合はごめんなさい。

この、CSVデータをDjangoの以下のモデル用のFixtureに変換する。

class Customer(models.Model):
    SEX_CHOICES = (
        ('M', u'男性'),
        ('F', u'女性'),
    )
    AGE_CHOICES = (
        ('-', u'---'),
        ('-20', u'20最未満'),
        ('20-29', u'20歳-29歳'),
        ('30-39', u'30歳-39歳'),
        ('40-49', u'40歳-49歳'),
        ('50-',   u'50歳以上'),
    )
    JOB_CHOICES = (
        ('-', u'---'),
        (u'会社員', u'会社員'),
        (u'自営業', u'自営業'),
        (u'公務員', u'公務員'),
        (u'団体職員', u'団体職員'),
        (u'主婦', u'主婦'),
        (u'学生', u'学生'),
        (u'その他', u'その他'),
    )
    sei     = models.CharField(u'姓', max_length=100)
    mei     = models.CharField(u'名', max_length=100)
    kanasei = models.CharField(u'かな姓', max_length=100)
    kanamei = models.CharField(u'かな名', max_length=100)
    zip     = models.CharField(u'郵便番号', max_length=10)
    pref    = models.CharField(u'都道府県', max_length=20)
    add1    = models.CharField(u'住所1', max_length=256)
    add2    = models.CharField(u'住所2', max_length=256, blank=True)
    tel1    = models.CharField(u'TEL1', max_length= 20)
    tel1_is_mobile = models.BooleanField(u'TEL1は携帯電話', default= False)
    tel2    = models.CharField(u'TEL2', max_length = 20, blank=True)
    tel2_is_mobile = models.BooleanField(u'TEL2は携帯電話', default= False)
    fax     = models.CharField(u'FAX', max_length = 20, blank=True)
    email1  = models.CharField(u'email1', max_length=100)
    email1_is_mobile = models.BooleanField(u'email1は携帯メール', default= False)
    email2  = models.CharField(u'email2', max_length=100, blank=True)
    email2_is_mobile = models.BooleanField(u'email2は携帯メール', default= False)
    is_inform = models.BooleanField(u'お知らせメール許可', default=True)
    sex  = models.CharField(u'性別', max_length=1, choices=SEX_CHOICES)
    birthday = models.DateField(u'生年月日', null=True, blank=True)
    age  = models.IntegerField(u'年齢', null=True, blank=True)
    ages = models.CharField(u'年代', max_length=10, choices=AGE_CHOICES, blank=True)
    jobclass = models.CharField(u'職業区分', max_length=20, choices=JOB_CHOICES)
    occupation = models.CharField(u'職業', max_length=100, blank=True)
    first_contact = models.DateField(u'初回接触', null=True, blank=True)
    first_buy = models.DateField(u'初回購入日', null=True, blank=True)

モデルと元データを比べてみたら分かると思うが、元データにはモデルの全てのフィールドが定義されているわけではなく、また、
元データのフィールドの値を加工しなければモデルの合わないデータも存在する。

住所の項目などでは、CSVのいくつかのフィールドを結合しなければモデルの住所データに合わないし、
誕生日の日付の形式はPythonで使っているyyyy-mm-dd 形式に変換しなければいけない。
(Python側で日付のフォーマット形式を変更することも出来そうだけど、今回はデータを変換して使う)
また、携帯電話項目には tel2_is_mobile を常に True としたデータを作りたい。

ということで、以下のような設定ファイルで、モデルにあるJSON形式のFixtureを作れるようにした。

{"model": "customer.customer",
 "header": true,
 "fields": [{"field": "sei",     "col": [0]},
            {"field": "mei",     "col": [1]},
            {"field": "kanasei", "col": [2]},
            {"field": "kanamei", "col": [3]},
            {"field": "sex" ,    "col": [4]},
            {"field": "tel1",    "col": [5]},
            {"field": "tel2",    "col": [6]},
            {"field": "tel2_is_mobile", "static": true},
            {"field": "email1",  "col": [7]},
            {"field": "zip",     "col": [8]},
            {"field": "pref",    "col": [9]},
            {"field": "add1",    "col": [10, 11, 12]},
            {"field": "add2",    "col": [13]},
            {"field": "is_inform", "static": true},
            {"field": "birthday", "col": [14], "replace": ["/", "-"]},
            {"field": "age",      "col": [15]}
           ]
}

設定の意味は以下の通り
model — Djangoのモデル名 アプリ名.モデルクラス名 を小文字で
header — CSVファイルにヘッダー行があるか? ある場合(1行目がデータでない場合) true
fields — データのフィールドの定義をObjectの配列で設定
—- fields 内の意味 —-
ひとつのフィールドはひとつのObject
field — モデルのフィールド名 Django のモデルのフィールド名
col — CSVの何カラム目を当てはめるのかの配列
複数のカラムを指定するとそれらを結合したものをそのフィールドのデータとする
例 {“field”: “add1″, “col”: [10, 11, 12]}
CSVの10カラム目、11カラム目、12カラム目を結合したデータをadd1に入力する。
static — 常に一定のデータを入力したいときに利用
例 {“field”: “is_inform”, “static”: true}
is_inform は常にTrueを入力する。
replace — CSVのカラム内のデータの一部を置換したデータにして入力する
例  {“field”: “birthday”, “col”: [14], “replace”: ["/", "-"]}
birthday フィールドにはCSVの14カラム目のデータの / を - に置換して入力
2010/02/12 を 2010-02-12 に変換する
replace は re.subを使っているので、正規表現が使える(と思う)
select — 選択データを入力する
例  {“field”: “is_inform”, “col”: [16], “select”: {“Y”: true, “N”: false}}
is_inform にはCSVの16カラム目のデータがYの場合は True、 Nの場合はFalseを入力する。

上のデータを変換するとこんな感じになる。

[{"pk": 1, "model": "customer.customer", "fields": {"tel1": "03-0043-5967", "is_inform": true, "add2": "\u30b0\u30e9\u30f3\u30c9\u77e2\u90e8403", "add1": "\u76f8\u6a21\u539f\u5e02\u77e2\u90e82-8", "zip": "229-0032", "age": "29", "pref": "\u795e\u5948\u5ddd\u770c", "mei": "\u9806", "kanamei": "\u30b8\u30e5\u30f3", "birthday": "1980-03-09", "sex": "M", "tel2": "080-1129-0749", "kanasei": "\u30cf\u30e4\u30ab\u30ef", "tel2_is_mobile": true, "sei": "\u65e9\u5ddd", "email1": "srkpfajun112@ftapigbq.uh"}}, {"pk": 2, "model": "customer.customer", "fields": {"tel1": "03-8521-5608", "is_inform": true, "add2": "", "add1": "\u8acf\u8a2a\u90e1\u4e0b\u8acf\u8a2a\u753a\u6771\u8d64\u78024-5-15", "zip": "393-0046", "age": "21", "pref": "\u9577\u91ce\u770c", "mei": "\u6b66\u96c4", "kanamei": "\u30bf\u30b1\u30aa", "birthday": "1988-05-27", "sex": "M", "tel2": "080-4747-0980", "kanasei": "\u30c4\u30ba\u30ad", "tel2_is_mobile": true, "sei": "\u90fd\u7bc9", "email1": "takeotsuzuki@cukt.npkbr.fi"}}, {"pk": 3, 以下省略

さて、肝心のツールのコードは以下

<追記 2010/02/13>

以前のコードでUTF-8回りでうまく動かないところがあった(select がうまく動いていなかった)ので、修正したソースに変更。
追加機能として

type -- 数値型の型指定をする場合に使う。type指定していない場合は文字列としてデータを扱う。
例 {"field": "price",    "col": [2], "type": "integer"}
priceフィールドには2番目のカラムのデータをinteger型で入力。
型指定は現在のところ integer とfloat だけ存在している。

< 追記 2010/02/15 >
余計なデバッグ用print文があったので削除。

make_fixture.py

# -*- coding: utf-8 -*-

import csv
import cStringIO
import json
import codecs
import re
import sys
from optparse import OptionParser

# PythonのリファレンスマニュアルよりUnicode対応csv.reader csv.writerラッパークラス
# http://pythonjp.sourceforge.jp/dev/library/csv.html#csv-examples
class UTF8Recoder:
    """
    Iterator that reads an encoded stream and reencodes the input to UTF-8
    """
    def __init__(self, f, encoding):
        self.reader = codecs.getreader(encoding)(f)

    def __iter__(self):
        return self

    def next(self):
        return self.reader.next().encode("utf-8")

class UnicodeReader:
    """
    A CSV reader which will iterate over lines in the CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
        f = UTF8Recoder(f, encoding)
        self.reader = csv.reader(f, dialect=dialect, **kwds)

    def next(self):
        row = self.reader.next()
        return [unicode(s, "utf-8") for s in row]

    def __iter__(self):
        return self

class UnicodeWriter:
    """
    A CSV writer which will write rows to CSV file "f",
    which is encoded in the given encoding.
    """

    def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
        # Redirect output to a queue
        self.queue = cStringIO.StringIO()
        self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
        self.stream = f
        self.encoder = codecs.getincrementalencoder(encoding)()

    def writerow(self, row):
        self.writer.writerow([s.encode("utf-8") for s in row])
        # Fetch UTF-8 output from the queue ...
        data = self.queue.getvalue()
        data = data.decode("utf-8")
        # ... and reencode it into the target encoding
        data = self.encoder.encode(data)
        # write to the target stream
        self.stream.write(data)
        # empty queue
        self.queue.truncate(0)

    def writerows(self, rows):
        for row in rows:
            self.writerow(row)

# CSVをDjango用のFixtureに変換するクラス
class CSV2Fixture:
    def __init__(self, settingf, inputf, outputf):
        self.setting_file = settingf
        self.data_file = inputf
        self.fixture = outputf
        self.setting = json.load(open(self.setting_file, 'r'))
        self.csvdata = UnicodeReader(open(self.data_file, 'r'))
        self.data = []
        self.model_name = self.setting['model']
        self.index = 1

    def load_one_line(self, line):
        fields = {}
        for field in self.setting['fields']:
            fieldname = field['field']
            data = ''
            #-- カラムからデータを取り出す場合
            try:
                if field['col']:
                    for col in field['col']:
                        data += line[col]
                    try:
                        #-- 選択値データの場合 --
                        if field['select']:
                            data = field['select'][data]
                    except KeyError:
                        pass

                    try:
                        #-- データの変換がある場合 --
                        if field['replace']:
                            data = re.sub(field['replace'][0], field['replace'][1], data)
                    except KeyError:
                        pass

                    try:
                        #-- データが数値の場合 --
                        if field['type']:
                            if field['type'] == 'integer':
                                data = int(data)
                            elif field['type'] == 'float':
                                data = float(data)
                    except KeyError:
                        pass
            except:
                pass

            #-- 固定値データの場合 --
            try:
                if field['static']:
                    data = field['static']
            except:
                pass

            fields[fieldname] = data
        return fields

    def execute(self):
        #ヘッダー処理 settingファイル内で "header": true の場合、1行目をフィールド名とみなして1行目を取り込まない
        try:
            if self.setting['header']:
                self.csvdata.next()
        except:
            pass

        #データの処理
        for line in self.csvdata:
            a=self.load_one_line(line)
            if 'pk' in a:
                self.data.append({'model': self.model_name,
                                  'fields': a})
            else:
                self.data.append({'pk': self.index,
                                  'model': self.model_name,
                                  'fields': a})
            self.index += 1

        json_data = json.JSONEncoder().encode(self.data)
        outf = codecs.open(self.fixture, 'w', encoding='utf-8')
        outf.write(json_data)
        outf.close()

if __name__ == '__main__':
    parser = OptionParser()
    parser.add_option("-s", "--setting", dest="settingfile", default="setting.json", help="Setting File (JSON)")
    parser.add_option("-i", "--input", dest="inputfile", default="input.csv", help="Input File (CSV)")
    parser.add_option("-o", "--output", dest="outputfile", default="output.json", help="Output File (JSON): This file is Fixture for Django apprication model.")

    (options, args) = parser.parse_args(sys.argv)
    x = CSV2Fixture(options.settingfile, options.inputfile, options.outputfile)
    x.execute()

カンタンなコードの割にはかなり便利である。
よければ自由に使ってみてください。
もう少し発展させて、リレーションのあるモデル用のCSV-JSON Fixtureの変換も出来るようにしたいところです。

Corazón loco / Crazy heart
Creative Commons License photo credit: lepiaf.geo (back mid February)

Djangoをもう少し理解しようと、Djangoドキュメントサイトのチュートリアルをやっていた。
チュートリアル4の前半までは、あまり悩むことも無く、うまくいっていたのだが、最後の汎用ビューで詰まった。

というか、汎用ビューのチュートリアルはちょっとはしょりすぎで良く分からない。

urls.py を書き換えるところまでは問題ない。
で、その後テンプレートも書き換えないといけないんだが、それがかなりはしょられているので分かりにくい。

分かったことをまとめておく。

urls.py は以下の通り

from django.conf.urls.defaults import *
from mysite.polls.models import Poll

info_dict = {
    'queryset': Poll.objects.all()
}

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
    (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
    url(r'^(?P<object_id>\d+)/results/$',
     'django.views.generic.list_detail.object_detail',
     dict(info_dict, template_name='polls/results.html'), 'poll_results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)

ここで、info_dict で設定された queryset の結果はテンプレートではobject_list として参照できる。
これをチュートリアル中に書いておいて欲しかった。
これが分からないためかなり悩んだんです。

なので、チュートリアルで作っていたテンプレート /mysite/polls/index.html は以下のように書き換えなければいけない。

{% if object_list %}
    <ul>
    {% for poll in object_list %}
        <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

ついでだから、detailへのリンクを追加しておいた。
こんなもっといい書き方があるかもしれないが、今のところはこの形で。
さらに、汎用ビューのdjango.views.generic.list_detail.object_list は モデル名/モデル名_list.html というテンプレートを呼び出すので、/polls/index.html のファイル名を /polls/poll_list.html に変更しなければならない。
同様に、django.views.generic.list_detail.object_detail は モデル名/モデル名_detail.html というテンプレートを呼び出すので、/polls/detail.html は/polls/poll_list.html にファイル名を変更しておく。

次に、detail のテンプレートは以下のように変更が必要。

<h1>{{ object.question }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/polls/{{ object.id }}/vote/" method="post">
{% for choice in object.choice_set.all %}
	<input type="radio" name="choice" id="choice{{ forloop.counter }}"
	 value="{{ choice.id }}" />
	<label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="投票する" />
</form>

poll で渡されていたオブジェクトが object という汎用名で渡されているので、poll を objectに変更した。

結果を表示するテンプレート /polls/results.html は以下のようになる。

<h1>{{ object.question }}</h1>

<ul>
{% for choice in object.choice_set.all %}
  <li>{{ choice.choice }} -- {{ choice.votes }} 票</li>
{% endfor %}
</ul>
<p><a href="/polls/">投票一覧に戻る</a></p>

これは、urls.py 内でパターンをurl()で設定していて、テンプレートを明示的に設定しているので、テンプレートのファイル名は元のままでOK。

vote部分は汎用ビューを使わないので、views.py 中のvote() が使われる。
vote(request, poll_id)関数内を、汎用ビューにあわせて以下のように変更する必要がある。

def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Poll 投票フォームを再表示します。
        return render_to_response('polls/poll_detail.html', {
            'object': p,
            'error_message': "選択肢を選んでいません。",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # ユーザーがBackボタンを押して同じフォームを提出するのを防ぐため
        # POST データを処理できた場合には必ず
        # HttpResponseRedirect を返すようにします。
        return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

変更点は選択肢が選ばれていないときの render_to_response 部分

        return render_to_response('polls/poll_detail.html', {
            'poll': p,
            'error_message': "選択肢を選んでいません。",
        })

のpoll を onject に変更。(汎用ビュー用にテンプレートをobject で参照するように変更したため)
さらに、reverseするための名前をulrs.py のurl()関数で名前をつけた poll_results に変更している。

以上。

汎用ビューを使おうとしてかなり悩んだ部分をカンタンにまとめておいた。

昨日の「Djangoで郵便番号を一気に取り込む」を参考に自分で作っていた方のスクリプトを改変したものが以下です。

こちらはcsvもsplitとstripで処理。半角カナは使わないので取り込むのをあきらめています。

郵便局の郵便番号辞書はこのスプリクトと同じディレクトリ(フォルダ)においてあります。

取り込む時間は約56秒。前は終わるまで待てなかったので、かなりのスピードアップです。

モデルの定義 プロジェクト/ziplist/models.py

# -*- coding: utf-8 -*-
from django.db import models

# Create your models here.

class Ziplist(models.Model):
    cityno = models.CharField(u'全国地方公共団体コード', max_length = 10)
    oldzip = models.CharField(u'旧郵便番号', max_length=5)
    zipcode = models.CharField(u'郵便番号', max_length=7)
    pref = models.CharField(u'都道府県', max_length=20)
    city = models.CharField(u'市区町村名', max_length=100)
    town = models.CharField(u'町域名', max_length= 100)
    two_code = models.BooleanField(u'一町域が二以上の郵便番号で表される場合の表示')
    banchi = models.BooleanField(u'小字毎に番地が起番されている町域の表示')
    chome =models.BooleanField(u'丁目を有する町域の場合の表示')
    two_town = models.BooleanField(u'一つの郵便番号で二以上の町域を表す場合の表示')

    def __unicode__(self):
        return self.zipcode + ' ' + self.pref + self.city + self.town

    class Meta:
        db_table = 'ziplist'

郵便番号取り込みスクリプト import_zipcode.py :shell(コマンドプロンプト)で実行用。プロダクションディレクトリに置いてある

# -*- coding: utf-8 -*-
from django.core.management import setup_environ
try:
    import settings # Assumed to be in the same directory.
except ImportError:
    import sys
    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
    sys.exit(1)

setup_environ(settings)

import codecs, time
from django.db import transaction
from ziplist.models import Ziplist

@transaction.commit_manually
def import_zipcode():
    fin = codecs.open('KEN_ALL.CSV', 'r', 'cp932')
    li = 1
    start = time.clock()

    for line in fin:
        datas = line.split(',')
        for idx in range(len(datas)):
            datas[idx] = datas[idx].strip('" ')
        zip = Ziplist()
        zip.cityno = datas[0]
        zip.oldzip = datas[1]
        zip.zipcode = datas[2]
        zip.pref = datas[6]
        zip.city = datas[7]
        zip.town = datas[8]
        zip.tow_code = '1' == datas[9]
        zip.banchi = '1' == datas[10]
        zip.chome  = '1' == datas[11]
        zip.tow_town = '1' == datas[12]
        zip.save()

        li+=1
        if li%1000 == 0:
            print '%d: %.2f' % (li, time.clock() - start)

    transaction.commit()
    print 'end: %.2f' % (time.clock() - start)

    fin.close

import_zipcode()

取り込んだ結果を Django のShellで試してみる

C:\_work\python\zipcode>manage.py shell
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from ziplist.models import Ziplist
>>> adds = Ziplist.objects.filter(zipcode__startswith = '102')
>>> len(adds)
15
>>> for add in adds:
...     print add.__unicode__().encode('cp932')
...
1020072 東京都千代田区飯田橋
1020082 東京都千代田区一番町
1020094 東京都千代田区紀尾井町
1020091 東京都千代田区北の丸公園
1020074 東京都千代田区九段南
1020073 東京都千代田区九段北
1020083 東京都千代田区麹町
1020076 東京都千代田区五番町
1020075 東京都千代田区三番町
1020084 東京都千代田区二番町
1020092 東京都千代田区隼町
1020093 東京都千代田区平河町
1020071 東京都千代田区富士見
1020081 東京都千代田区四番町
1020085 東京都千代田区六番町
>>>

\は日本語環境では¥です。なぜかWordPressに貼り付けると半角円記号はバックスラッシュになってしまいます??

Djangoで前方一致検索するときは モデル名.objects.filter( フィールド名__startswith = “検索値”) と書く。

詳しくは Django ドキュメント クエリを生成する フィールドの照合を参照してください。

顧客管理で住所の入力は出来るだけカンタンにしたい。

ということで、郵便番号辞書は必須。

郵便番号を読み込むプログラムを試してみた。
始めは自分で作ってたんだけど、読み込みに時間がかかるのでまたもやネットを探してみた。

TSUYUKI.MAKOTO にあった記事を元に、ワントランザクションでのプログラムにしてみた。

データモデル

from django.db import models

# Create your models here.

class ZipCode(models.Model):
zipcode = models.CharField(‘Zip Code’, max_length=7, db_index=True)
prefecture_kana = models.CharField(‘Prefecture Kana’, max_length=20)
city_kana = models.CharField(‘City Kana’, max_length=100)
town_kana = models.CharField(‘Town Kana’, max_length=100)
prefecture = models.CharField(‘Prefecture’, max_length=20)
city = models.CharField(‘City’, max_length=100)
town = models.CharField(‘Town’, max_length=100)

def __unicode__(self):
return ‘%s: %s %s %s’ % (self.zipcode, self.prefecture, self.city, self.town)

読み込みようプログラム

import sys, os, csv, time
from django.core.management import setup_environ
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write(“Error: Can’t find the file ‘settings.py’ in the directory containing %r. It appears you’ve customized things.\nYou’ll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it’s causing an ImportError somehow.)\n” % __file__)
sys.exit(1)

setup_environ(settings)

from django.conf import settings
from django.db import IntegrityError, transaction
from zipcode.models import ZipCode

try:
import zenhan
except ImportError:
print ”’You need zenhan.py .
Download zenhan.py and install it.

http://matatabi.homeip.net/blog/setomits/877

”’
sys.exit()

#
#from django.db import connection
#cursor = connection.cursor()
#cursor.execute(‘PRAGMA temp_store = MEMORY;’)
#cursor.execute(‘PRAGMA synchronous=OFF’)

from django.core.management import call_command
call_command(‘syncdb’)

reader = csv.reader(open(‘KEN_ALL.CSV’, ‘rb’))

@transaction.commit_manually
def import_zip(reader):
counter = 0
start = time.clock()
for data in reader:
try:
zipcode = ZipCode(zipcode=data[2],
prefecture_kana=zenhan.h2z(unicode(data[3], ‘shift_jis’), zenhan.KANA),
city_kana=zenhan.h2z(unicode(data[4], ‘shift_jis’), zenhan.KANA),
town_kana=zenhan.h2z(unicode(data[5], ‘shift_jis’), zenhan.KANA),
prefecture=unicode(data[6], ‘shift_jis’),
city=unicode(data[7], ‘shift_jis’),
town=unicode(data[8], ‘shift_jis’))
zipcode.save()
except IntegrityError: pass
counter += 1
if counter%1000 == 0:
print ‘%d: %.2f’ % (counter, time.clock() – start)

transaction.commit()
print ‘end: %.2f’ % (time.clock() – start)

import_zip(reader)

トランザクションをマニュアルにするには

from django.db import transaction

でdjango.dbのトランザクションをロードしておいて
@transaction.comit_manually
def トランザクションをマニュアル処理する関数の定義

としなければならない。
@transaction.commit_manually の意味が始めはよく分からなくて(後ろに続く関数が引数になっていると分かっていなくて)、SyntaxErrorが出ていたのでかなり悩んだ。

ワントランザクションにすると、延々と終わらなかった約12万件の郵便番号データが 59.51秒(約1分)で読み込み完了した。

データベースはwindowsXP上のsqlite3.6.16

<追記>

ソースコードがインデントされていない・・・orz

Python はインデントで構造を作っているので、これは問題だなー。

ソースコードをWebに載せる方法を考えなくては。

とりあえず、このソースコードはインデントされていないので、注意

Rails感覚でモデルを作って試していると、モデルにフィールドを追加したり、削除したり、変更したりしたくなってくる。

で、単純にモデルのクラスにフィールドを追加して manage.py syncdb ってやればいいのかなと思ったら・・・

駄目だった。

どうすればいいのかネットを探しているとこんな記事を見つけた
continue reading…