Django と South と テーブル名変更

South でテーブル名を変更したい

class Hoge(models.Model):
    text = models.TextField()

hogeアプリケーションにこんなモデルを作るとデータベースのテーブル名はhoge_hogeになります。
Djangoの外に出ないならこのままでも良いんですけど、データベースを直接見たりSQLを書くにはちょっと格好悪いですよね><
そんな時は

class hoge(models.Model):
    text = models.TextField()
    class Meta:
        db_table = 'hoge'

こんな風にdb_tableを指定してあげると、hogeというテーブルになります。
ただし、Southを使っているとこういう変更もmigrationファイルを作る必要があります。

datamigrationを利用して

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('hoge_hoge', 'hoge')

    # backwards以下省略

rename_tableを実行するmigrationファイルを作るとテーブル名が変更出来ます。
簡単ですね!


落とし穴あるよー

名前を変えたテーブルが他のテーブルと関連していない場合はこれで大丈夫ですが、関連しているテーブルがあると問題が起こる場合があります。

例えば、hugaアプリケーションにこんなモデルがあるとします。

class Huga(models.Model):
    hoge = models.ForeignKey(Hoge)

そして、settingsのINSTALLED_APPSが

INSTALLED_APPS = (
   'south',

   'hoge',
   'huga'
)

こんな風になってるとエラーが発生する場合があります。

migrationをする時はINSTALLED_APPSの上から順に、そのアプリにある全てのmigrationが実行されるので
1. hogeアプリ:hoge_hoge の create
2. hogeアプリ:hoge_hoge から hoge への rename
3. hugaアプリ:hoge_hoge への ForeignKey を持つ huga の create
となって、hugaにアクセスすると、hoge_hogeなんてテーブルないよ!とエラーが発生するのです。


当たり前な解決策

INSTALLED_APPSの順番をhugaを上にするだけで、この問題は解決します。
migrationの実行順が
1. hugaアプリ:hoge_hoge への ForeignKey を持つ huga の create
2. hogeアプリ:hoge_hoge の create
3. hogeアプリ:hoge_hoge から hoge への rename
となって、hoge_hoge から hoge への rename で、huga の ForeignKey も hoge にちゃんと変更されます。

普通に書いてるとForeignKeyを張られる方を上に書きがちなのですが、普段からForeignKeyを張る方を上に書くようにしてるとこーゆー無駄なのに引っかからずに良いのではないでしょうか。

Django と South と カラム名変更

South でカラム名を変更したい

class Example(models.Model):
    hoge = models.TextField()

こんなモデルの hoge を huga に変えたいとします。

class Example(models.Model):
    huga = models.TextField()  # hoge → huga

こういう場合普通に schemamigration をすると South は add して delete します。

class Migration(SchemaMigration):
    def forwards(self, orm):
        # Adding field 'Example.huga'
        db.add_column('example', 'huga', self.gf('django.db.models.fields.TextField')())

        # Deleting field 'Exsample.hoge'
        db.delete_column(u'example', 'hoge')

    # backwards以下省略

自動ではカラム名を変更すべきかどうかわからないので、しょうがないですね。
hoge にデータがない場合はこのままでも良いのですが、データがある場合は delete しちゃうと困ります。
こんな時はモデルを変更した後に schemamigration じゃなくて datamigration をします。
そうすると、こんなファイルが出来るので

class Migration(SchemaMigration):
    def forwards(self, orm):
        "Write your forwards methods here."

    # backwards以下省略

db.rename_column でカラム名を変更します。

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_column('example', 'hoge', 'huga')

    # backwards以下省略

カラム名の変更以外にも schemamigration で上手く行かなかった場合は、datamigration で Database API を使って自前で migration を書くことを考えると良いと思います。

こんなの書いたけど
とうとう South が Django に取込まれるみたいです。
https://speakerdeck.com/andrewgodwin/migrating-the-future
なので、すぐに使えなくなる知識かもしれません。
ですが、South なしの Django とか使う気にならないので、取込まれるのが楽しみです!

僕とPythonとBeProud

はじめにというか注意書き

エイプリルフールだし先月転職2周年で公開しようと思いつつ躊躇してたものを一部嘘を交えて書き直しました。
そんな訳でこの内容をそのまま信じたり何が嘘なのか聞くのは止めてください。


前職の話

こう言って(言わされて)現職に転職してから2年が経ちました。
まさかそんな前職が持ち直して急成長するとは思わなかったです。
というか、担当してた持ち直したきっかけになったプロジェクトがあんなに当たるとは夢にも思ってませんでした。
出来るだけ良いものにしようと頑張ったけど、経緯が経緯だったので…
その辺り忘れちゃうから「なんで17歳以上推奨だったのか?」とか赤裸々に書き残したいなーと思いつつ方々から色々言われそうなので書けないでいます。


転職とPython

僕はPythonが好きです。前々職からだから、もう6年くらい使ってるのかな?
もともと転職したきっかけは、会社が潰れそうな時に知人にD社を紹介されて、どうせ受けるなら別の会社も受けてみようと思ってPython使えるってだけでBeProudにも応募しました。
結局BeProudの面接が先で受けたら受かったので、そのまま入社した感じです。
Pythonで仕事したかったし。
あ、ちなみにD社の面接はBeProudで良いかなって思ってたのもあって酷かったみたいです。
後で紹介してくれた人に聞いたのですが「なんか悲しいことがあったらしく、やる気なさそうだった」って評価だったとか。
何があったか覚えてないんですけど、きっと前日に奥さんと喧嘩でもしたんでしょう。


BeProudで働くということ

BeProudの強みは働いている人の質の高さです。
全員が全員という訳にはいかないのですが、技術力は勿論、仕事をすることの責任感とか金感覚とかそういうものを含めての質の高さは群を抜いていると思います。
まぁそれだけなんですけど、でもとても大切なことです。
ただ、どれだけ技術者の質が高かろうが、単純な受託をやっている限り技術者の楽園なんてものはありません。
僕はそんな環境にいたことないので想像でしかないのですが、あるとしたらそんなものは凄く儲かってる自社開発くらいにしかないんじゃないかなー。
そんな訳で技術者の楽園に行きたいって人ではなく、仕事ができるようになりたい or 仕事ができる人と仕事をしたい って人にはBeProudは良い会社なんだと思います。
あ、それと僕みたいな社会不適合者でも働けてるんだから懐は深いですね。
社員募集してるみたいなんで興味がある人は応募すると良いんじゃないでしょうか。
2年も働いてると色々ありますが、社内に素晴らしい人が沢山いるので僕はBeProudに転職して良かったと思ってます。

人生を豊かにする

はじめに
この記事はPySpa Advent Calendar21日目の記事です。

人生を豊かにするものは幾つもあります。
例えば、魅力的な恋人。
例えば、心を許せる友人。
例えば、知的な語らい。
例えば、力を発揮出来る仕事。
…etc

今日までのAdvent Calendarを読んでいれば自明なように、PySpaに参加すれば上記のうち魅力的な恋人以外は手に入ることでしょう。
運が良ければお見合いおじさん の計らいで恋人さえも手に入るかもしれません。
しかし、PySpaに参加することによって失われるものもあるのです。
あらかじめ坊主にしたことだし、みんなが「PySpaすげーよ」とか「こんなに良いんだよ」とか言ってる中で、PySpaに参加することで失われる素晴らしいものについて語りたいと思います。


美味い食事は人生を豊かにする
これ僕は割と本気で信じています。
美味いものを食べるために人より時間や金を使っていますし、美味くないものを食べるくらいだったら何も食べない方がマシだと真剣に思っています。
最近のお気に入りは目黒にある和創作 太
美味しい物を食べるのが好きな方は是非一緒に行きましょう。
とは言え、金がかかったものじゃなきゃ嫌だとかって訳ではなく、吉牛でもマックでもそれなりに美味しく食べられるので、日々食べるものに困ったり食費で困窮したりはしていないのですが。


しかし、PySpaのというか旅館の食事はダメです。
ここ十何年で下から何番目かってくらいに圧倒的にダメ。
参加したことない人のために説明すると、20年前の最低な修学旅行の食事って感じです。
その辺にあるしょぼい旅館の食事を3ランクくらいダウンさせたようなレベル。
僕は頑張っても半分も食べることが出来ませんでした。
30人分足らずの食事なのに、温かいものは温かくという美味しい食事の基本すら全く守る気がないとか、会場の旅館は飲食施設としては終わってます。
人生で約9万食しか食べられない食事のうちの何食もをそんなところで費やすなんて圧倒的な損失であり、人生が貧しくなること請け合いです。
こんな食事でも美味しいとか書いてしまう人とは友達にはなれる気がしません。
2012.10のPySpaに僕は隣の席の人ソウルジェムの都合で金曜の夕食後から参加したのですが、翌日の朝食夕食を見て金曜の夕食に参加しないで本当に良かったと思いました。


PySpaは一泊一万円なのですが、正直この食事でこの金額は高いです。
なんで一万円もかけて不味い食事をしなきゃならないのかと。
同じ金額のPython mini hackathon合宿の食事が非常に美味しいことを考えると「なんかもっとどうにかならないのかなー」って思っちゃいますね。
温泉旅行の楽しみの半分である食事(ちなみに、残り半分は温泉で、温泉はまぁ普通です。)で圧倒的な敗北感を感じさせる辺り、PySpaは単純な温泉旅行としては失格です。
もっともPySpaに限って言えば単純な温泉旅行以外の側面に価値があるので困ったものなのですが。


おわりに
人生を豊かにするものは色々あります。
PySpaは人生を豊かにするかもしれないもので溢れていますが、人生を貧しくするかもしれないものでも溢れています。
PySpaに参加することであなたの人生が豊かになるのであれば参加すると良いのではないでしょうか。
僕は会場が変わらない限り積極的には参加したくないかなーというのが正直なところです。

次回は@mopemopeさんです。
僕と違ってもっとまともな記事を書いてくれることでしょう。楽しみですね!

South のハマりどころ -同じ番号のmigrationファイルが出来た場合-

はじめに
この記事は 2012 Pythonアドベントカレンダー 12日目の記事です。
South を使ってると幾つかハマるポイントがあります。
Pythonプロフェッショナルプログラミングにも少し書いているのですが、書ききれなかったものとか、それ以降で感じたことなどを記しておきたいなーと思ってたところアドベントカレンダーの募集をしていたのでちょうど良い機会かと筆を取っています。


同じ番号のmigrationファイルが出来る
複数人で同じDjangoアプリに対してモデルの変更作業を行うと同じ番号のmigrationファイルが出来ることがあります。

具体的には、こんなモデルに

class Example(models.Model):
    text = models.TextField()

以下のような修正をしてschemamigrationを行うと

class Example(models.Model):
    text = models.TextField()
    int = models.IntegerField(default=0) # 追加

このようなmigrationファイルが出来るのですが

# 0002_auto__add_field_example_int.py
class Migration(SchemaMigration):                                                                                                                                                   
    def forwards(self, orm):                                                                                                                                                                
        # Adding field 'Example.int'
        db.add_column('example_example', 'int', self.gf('django.db.models.fields.IntegerField')(default=0))

    # backwardsは省略

    models = {
        'example.example': {
            'Meta': {'object_name': 'Example'},
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'int': ('django.db.models.fields.IntegerField', [], {'default':0}),
            'text': ('django.db.models.fields.TextField', [], {})
        },
    }

別の人が同じモデルに別の修正を加えてschemamigrationを行うと

class Example(models.Model):
    text = models.TextField()
    time = models.DateTimeField(null=True) # 追加

このような別のmigrationファイルが出来ます

# 0002_auto__add_field_example_time.py
class Migration(SchemaMigration):                                                                                                                                                   
    def forwards(self, orm):                                                                                                                                                                
        # Adding field 'Example.time'
        db.add_column('example_example', 'time', self.gf('django.db.models.fields.DateTimeField')(null=True))

    # backwardsは省略

    models = {
        'example.example': {
            'Meta': {'object_name': 'Example'},
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'time': ('django.db.models.fields.DateTimeField', [], {'null':'True'}),
            'text': ('django.db.models.fields.TextField', [], {})
        },
    }


同じ番号のmigrationファイルが出来たら何が困るのか
同じ番号のmigrationファイルが存在してもmigrateする上では--mergeオプションの使用を示唆されることがあるくらいで何も問題ないのですが、その後のschemamigrationで問題が発生します。

intの修正とtimeの修正とをマージして再度修正をしてschemamigrationをしてみると

class Example(models.Model):
    text = models.TextField()
    int = models.IntegerField(default=0)
    time = models.DateTimeField(null=True) # timeの修正はマージされているものとする
    bool = models.BooleanField(default=True) # 追加

このようなmigrationファイルが出来ます。

# 0003_auto__add_field_example_bool.py
class Migration(SchemaMigration):                                                                                                                                                   
    def forwards(self, orm):                                                                                                                                                                
        # Adding field 'Example.bool'
        db.add_column('example_example', 'bool', self.gf('django.db.models.fields.BooleanField')(default=True))

        # Adding field 'Example.int'
        # 0002_auto__add_field_example_int.py で追加したはずなのに再度出てくる
        db.add_column('example_example', 'int', self.gf('django.db.models.fields.IntegerField')(default=0)) 

    # backwardsは省略

    models = {
        'example.example': {
            'Meta': {'object_name': 'Example'},
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'int': ('django.db.models.fields.IntegerField', [], {'default':0}),
            'text': ('django.db.models.fields.TextField', [], {})
        },
    }

このmigrationファイルだと追加したはずのintが更に追加されているので、migrateが出来ません。
何故このようなことが起こるかというと、schemamigrationでは最新のmigrationファイル(数字が大きい場合はソート順が下のもの)のmodelsの内容とモデルの構造とを比べて差分をmigrationファイルとしているためです。
今回は 0002_auto__add_field_example_time.py のmodels に int がないため、schemamigrationでintが再度追加されてしまいました。


こういう時はどうすれば良いか
解決策は2通りあります。
一つは同じ番号のmigrationファイルがあった場合はschemamigration前にmigrationファイルのmodelsを修正してモデルの内容と合わせる方法です。

# 0002_auto__add_field_example_time.py
class Migration(SchemaMigration):                                                                                                                                                   

    # forwards, backwardsは省略

    models = {
        'example.example': {
            'Meta': {'object_name': 'Example'},
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'int': ('django.db.models.fields.IntegerField', [], {'default':0}), # これを追加する
            'time': ('django.db.models.fields.DateTimeField', [], {'null':'True'}),
            'text': ('django.db.models.fields.TextField', [], {})
        },
    }

もう一つは、schemamigration後にmigrationファイルのforwardsとbackwardsから重複した処理を削除するという方法です。

# 0003_auto__add_field_example_bool.py
class Migration(SchemaMigration):                                                                                                                                                   
    def forwards(self, orm):                                                                                                                                                                
        # Adding field 'Example.bool'
        db.add_column('example_example', 'bool', self.gf('django.db.models.fields.BooleanField')(default=True))

        # Adding field 'Example.int'
        # これを削除する
        # db.add_column('example_example', 'int', self.gf('django.db.models.fields.IntegerField')(default=0)) 

    # backwards, modelsは省略

どっちでも良いのですが、schemamigration前には気付けないことが多いので僕はもっぱら後者で対応しています。


さいごに
複数人で作業する場合に一番ハマりやすいのはこれだと思います。
他にも幾つかそういうのあるので気が向いたら書いていこうと思いますが期待しないでください。
明日は @takanory です。

Django と South と custom sql

自前で create table したい
Django を使っていて Django の ORM はそのまま使いたいんだけど、create table は自前で行いたい場合があります。
良くあるのは、partition 切りたい場合とか。
MySQL の range partition を使うには Primary Key を 複合Primary Key にする必要があるのですが、Djangoは 複合Primary Key を許可していません。
なので、そのような場合は自前で create table文 を書いて実行する必要があります。

class PartitionExample(models.Model):
    log = models.TextField(verbose_name=u'ログ')
    created_at = models.DateTimeField(verbose_name=u'作成日時', auto_now_add=True)

    class Meta:
        db_table = 'partition_example'
        managed = False # こうすると syncdb でモデルが作られない

こんなモデルを書いて

CREATE TABLE partition_example (                                                                                                                                                                               
    id int AUTO_INCREMENT,                                                                                                                                                                                  
    log TEXT,                                                                                                                                                                                             
    created_at DATETIME,                                                                                                                                                                                         
    CONSTRAINT PRIMARY KEY (id, created_at)                                                                                                                                                                      
)                                                                                                                                                                                                           
PARTITION BY RANGE COLUMNS (created_at) (                                                                                                                                                                        
  PARTITION pfuture VALUES LESS THAN (maxvalue)                                                                                                                                                             
);   

それと構造が等しい sql を書く。
その sql を appname/sql/partitionexample.sql に置くと、syncdb する時に自動的に sql を実行してモデルを作成してくれるという案配です。
いやー Django って良く出来てますね。
ところが、このままだと South 環境では上手く行きません。


South の場合
South を入れていると syncdb を上書いてしまっているので、sql を実行してくれないのです。
そこで、どうするかと言うと、schemamigration --init を実行すると managed = False のモデルを無視した migration ファイルが出来ます。

class Migration(SchemaMigration):
    def forwards(self, orm):
        pass
    def backwards(self, orm):
        pass

他にモデルがない場合はこんなの。
これを自前で sql を実行するように修正します。

class Migration(SchemaMigration):
    def forwards(self, orm):
        with open(sql_file_path) as f:
            sql = f.read()
        db.execute(sql)
    def backwards(self, orm):
        pass

これでOK。あとは普通に migrate するだけです。
これ以降の変更は schemamigration --auto で自動的に migration ファイルを作ってくれるので、普通に South を使う時と何も変わりません。

PyCon JP 2012 終わりました

初日
誘導を手伝った後、発表者用受付にほぼずっといました。
発表は何も見ていません。

夕方からはPartyの準備とか受付とかをしてました。
参加者が少ないのではないかと心配していたのですが、思ったよりずっと当日参加が多かったのと、会場が素晴らしかったおかげで参加した皆様に楽しんで頂けたみたいで良かったです。

二日目
初日頑張り過ぎたので寝坊して開場前の準備はパス。
午前中は初日と同じく発表者用受付にいました。
二日間とも受付をお願いしちゃった上に僕よりちゃんと対応してる@RicoImazu には頭が上がりません。

午後は基調講演だけ聞いて、後はなんか色々雑用してました。
@dankogaiさんの基調講演は賛否両論ありましたが、そもそもPython第一言語でない人にPyConに興味を持ってもらおうと「Pythonをdisって頂いても構いません」ってオファーをしているので…
不快な方もいたでしょうし、そういう方には申し訳ないと思いますが、プロレスで盛り上がった面もあると思うので個人的にはあれはあれで良かったかな、と思っています。
企画したパネルディスカッションが司会の@ymotongpoo と登壇者の皆様のおかげで盛り上がっていて一安心。
流石に疲れていたので、打ち上げにも参加せず帰宅。

三日目
Sprintにも参加せずに寝てました。
それなのに、翌日も寝坊したのは秘密です。

感想とか
「楽しかったー」って言うよりは「終わったー」「疲れたー」って感じでした。
今年はちょっと頑張り過ぎた。
発表見ないで働きっぱなしとかないわ。
来年は関わるにしても、もうちょっと力を抜こう。
そう思いつつ、来年の会場を探したり反省会の幹事をやってたりする辺り救われません。
あ、来年はAPACなのでスタッフしてくれる方を募集しています。
スタッフが増えれば今年の僕みたいなことにもならないと思うので、是非お願いします!