DjangoのManyToManyFieldへの追加と削除

2010-12-22
このエントリーをはてなブックマークに追加

データ設計をするときに、多対多になるケースがあります。たいていのフレームワーク、というかO/Rマッパーは多対多をサポートしており、Djangoも例外ではありません。

が、多対多のモデルへの追加と削除が意外とわかりにくかったので、メモ。

class ServiceCategory(models.Model):

name = models.CharField(max_length=256)

class Service(models.Model):

servicecategory = models.ManyToManyField(ServiceCategory)
name = models.CharField(max_length=256)

というモデルがあったとして、Serviceというモデルへデータを登録する際のservicecategoryへの追加と削除です。

_service = Service()
_service.save()

_service.servicecategory.add(ServiceCategory.objects.get(id=1))

Serviceを保存して、IDが割り振られてからじゃないとManyToManyFieldの保存はできません。Djangoも多対多のリレーションをサポートするために中間テーブルを作成してくれます。この例ですと、「service_servicecategory」というテーブルがこっそり作られています。

その中間テーブルにServiceCategoryとServiceの関連性を保存するために、新しく登録されたServiceのIDが必要なんですね。

で、削除するには

_service.servicecategory.remove(ServiceCategory.objects.get(id=1))

と書きます。関連する多を全部まとめて一気に消す、とかクエリセットを使って消す、とかもできるのかもしれませんが、まだ試してないです。

もうちっとDjangoを使いこなせるようになりたいもんです。

Python2.5、Django1.1

Tags: ,

DjangoでOauth認証を経由してTwitterAPIを叩く方法

2010-09-29
このエントリーをはてなブックマークに追加

先にはっきりいっておきます。tweepyみたいなパッケージ使った方がずっと楽だとおもいます。自分でもそう思っていたのですが、後々Facebook等々への応用もあったのでOAuthの流れを理解しておくためにOauth2だけで書いてみました。以前Railsで書いた「RubyonRailsでOAuth経由でTwitterのタイムラインを取得する」のDjango版です。Djangoというか、Python版ですね。

「Oauth2も使わないで書けよ」というツッコミは覚悟の上です。w

# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
from django.http import *
from xml.etree.ElementTree import *
import urllib
import oauth2 as oauth

request_token_url = 'http://twitter.com/oauth/request_token'
access_token_url = 'http://twitter.com/oauth/access_token'
authorize_url = 'http://twitter.com/oauth/authorize'

timeline_url = "http://api.twitter.com/1/statuses/user_timeline.xml"

consumer_key = 'consumer_key '
consumer_secret = 'consumer_secret '

consumer = oauth.Consumer(key=consumer_key, secret=consumer_secret)

def index(request):

    client = oauth.Client(consumer)
    resp, content = client.request(
                    request_token_url,
                    'POST',
                    body=urllib.urlencode(
                    	{
                    		'oauth_callback':
                    		'http://127.0.0.1:8000/callback/'
                    	}
                    )
               )

    request_token = {}
    for item in content.split('&'):
        _data = item.split('=')
        request_token.update({_data[0]: _data[1]})

    request.session['request_token'] = request_token
    return HttpResponseRedirect("%s?oauth_token=%s"
                                % (authorize_url, request_token['oauth_token']))

def callback(request):
    request_token = request.session['request_token']
    token = oauth.Token(request_token['oauth_token'],
                        request_token['oauth_token_secret'])
    client = oauth.Client(consumer, token)

    _dict = {
            'oauth_token': request.GET['oauth_token'],
            'oauth_verifier': request.GET['oauth_verifier'],
        }

    resp, content = client.request(
    					access_token_url,
    					"POST",
    					body=urllib.urlencode(_dict)
    				)

    access_token = {}
    for item in content.split('&'):
        _data = item.split('=')
        access_token.update({_data[0]: _data[1]})

    request.session['access_token'] = access_token

    return HttpResponseRedirect('/timeline/')

def timeline(request):

    access_token = request.session['access_token']

    token = oauth.Token(access_token['oauth_token'],
    		access_token['oauth_token_secret'])
    client = oauth.Client(consumer, token)
    _result = client.request(timeline_url, 'GET')

    _statuses = _result[1]
    xmlString = urllib.unquote_plus(_statuses.encode('utf-8'))
    elem = fromstring(xmlString)
    for element in elem.findall("status"):
        print element.find('text').text

    return render_to_response('index.html')
DjangoのViewだけ書きました。template側での処理はまったくないです。
流れとしては、
  1. 認証用クライアント作ってリクエストトークンを作る。ここで一度リクエストトークン作成用URLにアクセス。
  2. リクエストトークンに書かれた文字列をくっつけて認証用画面に遷移(ここはTwitter側の画面)。
  3. 「許可」をクリックすると、リクエストトークンで指定したURLに帰って来る。
  4. 帰ってきたときには認証用のベリファイ文字列が着いてくるので、それらを使ってアクセストークン作成。
    ここはアクセストークン作成用のURLにアクセスする。
  5. 帰ってきたアクセストークンに「oauth_token」と「oauth_token_secret」 があり、以降はこれつかってアクセスする。

サンプルはXMLで受信してます。ヘッダとボディに分かれているので、ボディだけXMLとして扱ってパースしました。サンプルはつぶやいた内容だけを書き出しています。

Tags: ,

DjangoでDatetimeのフィールドを年月でGroupByして集計する方法

2010-07-29
このエントリーをはてなブックマークに追加

以前のエントリーで集計クエリを利用する方法を説明しましたが、それだとDatetimeのフィールドを「年月」ごとに、っていう集計はできませんでした。購買履歴データを月別に件数集計したいときなんかは非常に困るんですよね。ので、それを解決する方法です。

select_column = {“d_column”:  “DATE_FORMAT(pub_date, ‘%%Y%%m’)”}
_list = PurchaseRecord.objects.extra(select=select_column)
_list = _list.values(“d_column”).annotate(cnt=Count(‘id’))

上記はMySQLを利用しているケースの書き方です。

あ、横長になると見難いから2行に分けただけで、1行で大丈夫ですからね。

「ORマッパー使ってるのにデータベースに依存ってなによ」

な感じではありますが、extraを使っている部分は直接クエリが流れるみたいですね。
つまり、「DATE_FORMAT(pub_date, ‘%%Y%%m’)」の部分は

select *,DATE_FORMAT(pub_date, ‘%Y%m’) as d_column
from app_purchaserecord

といった風に、追加されるのです。で、追加されたカラムを使って集計、という流れですね。うっかりミスりやすいのが「%」を二つにする点でしょうか。

あと、データベースを変えたら「select_column~」の部分に修正が必要です。使用するデータベースに依存した書き方になります。ここは要注意ですね。

Djangoのmodelで自分自身に対するリレーション(再帰的なリレーション)を張る

2010-07-28
このエントリーをはてなブックマークに追加

例えば組織図なんかのmodelを構築する際に、「属する部門」を表現するには再帰的なリレーションを使うことになると思います。「部門が属する部門」ですね。

class Div(models.Model):

name = models.CharField(max_length=100)

upper = models.ForeignKey(Div)

だと、syncdbの時に「Divなんてありません」と怒られてしまいます。そりゃそうだ。
ので、

class Div(models.Model):

name = models.CharField(max_length=100)

upper = models.ForeignKey(‘self’)

と書きます。

Python2.5、Django1.1.1でのお話でした。

Tags: ,

DjangoでMySQLのストレージエンジンをInnoDBにする

2010-07-27
このエントリーをはてなブックマークに追加

DjangoでMySQLを利用する場合、初期状態だとMyISAMでテーブルが作成されます。まぁ後からalterすればよい話ではあるのですが、なんかそれだと違うかなぁと。

一昔前はInnoDBはMyISAMほどパフォーマンスが優れず、更新系のテーブルと参照系のテーブルでストレージエンジンを使い分けたものですが、今はストレージエンジンによるパフォーマンスの差も少ないため、行レベルロックができるInnoDBを積極的に採用するケースが増えてるみたいですね。まぁ数千万行をレコードから数ミリ秒でレスポンスを返さないといけないケースとかではどうなるのかわかりませんが。

DATABASE_OPTIONS = {
“init_command”: “SET storage_engine=INNODB”,
}

settings.pyに上記を記載するだけでsyncdbするときにInnoDBでテーブル作成してくれます。が、この記述はテーブル作成が終わって運用にはいったら削除するように、とのことです。

MySQLとの接続が確立された後にわざわざストレージエンジンを指定するコマンドを送っているっぽいので、普段は無駄な処理が発生しちゃうっていうことなんですかね。

Djangoでテンプレート出力と同時にcookieを書き込む方法

2010-05-19
このエントリーをはてなブックマークに追加

Djangoでテンプレートを出力するとき、あわせてcookieの書き込みもしたいケースがあります。よくあるサンプルだと

return render_to_response(‘index.html’)

っていう書き方をしているのですが、「コレ、cookieの書き出しどうすんのよ」って悩んでしまいました。その結果、

def  index(request):
_max_age = 45*24*60*60
_expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=_max_age), “%a, %d-%b-%Y %H:%M:%S GMT”)
_response = render_to_response(‘index.html’)
_response.set_cookie(‘hoge’, value=’hogehoge’, max_age=_max_age, expires=_expires, path=’/’)
return _response

こんな書き方をしたらできているようでした。上記の例だと、現在時刻から45日間有効なcookieを書き出しています。

単純なことなんですが、意外と躓いてしまった。。。

Python2.5、Django1.1.1です。

そういえばDjangoの1.2がリリースされてますね。検証したいな。

Tags: ,

RubyとPythonについて考えてみました

2010-04-27
このエントリーをはてなブックマークに追加

ハセテツは複数の言語を状況に応じて使い分けてきましたが、最近のWeb系開発はPython(+Django)がメインになってきています。

ちょっと前までRuby(+Rails)だったのですが、いろいろと考えるところもあり、乗り換えました。その乗り換えた理由をまとめてみようと思います。

まとめてみたら「やっぱりRailsじゃね?」となるかもしれません。w

Ruby(+Rails)

  • Rails便利すぎる。
  • コントローラとモデルが別々のファイルになってくれるのはソースが追いやすい。
  • Railsを通さない画像やCSSはpublicフォルダに置けばよいのはわかりやすい。
  • urlディスパッチャーがいまいち使いにくい。(知らないだけかも)
  • APサーバはmongrel一択?
  • VirtualHost使おうとするとApache+Passenger(mod_rails)だが、これが重い。(チューニングで速くなるのかも)
  • というか、Railsがそもそも重い。
  • ちゅーか、Rubyが重い、遅い。
  • PassengerはWindowsじゃ動かない。
  • mongrelもPassengerより激速軽快かというと、そうでもない。
  • gem便利すぎ。
  • ワンライナで書く人が多くて、Perlとおなじ匂いがする。

ハセテツが使っていたRubyは1.8で、速くなったといわれる1.9には触れていないのでもしかすると古いのかもしれません。でも、Railsって1.9には対応してないですよね?(本日現在)

Python(+Django)

  • Django便利すぎる。
  • 日本語の書籍、情報が少なすぎる。(ハマるとキツい)
  • modelが一枚のファイルなので、大量のmodelがあると可読性が激しくダウン。
  • viewファイルをフォルダで分けたくても、アプリケーションフォルダ直下以外にviewファイル置くなら「sys.path.append」しないといけない。(違う?)
  • urlディスパッチャーのカスタマイズが超便利。
  • mod_pythonが使えるので、Windows環境だろうがLinux環境だろうが気にせずVirtualHostが使える。
  • 速い。
  • 日本語大変。Shift_JIS怖い。
  • easy_installでパッケージのインストールは楽だが、管理が悪夢。

メリットとデメリットが入り乱れました。見難くてすいません。

結局フルスタックのフレームワークということで、DjangoとRailsに関しては一長一短だと思います。個人的にはRailsの方が良くできてたかなぁと。

最終的に、「RubyonRailsは重い」というオチになってしまうのです。Rubyも、Railsも、です。ただ、これはRuby1.9が実は劇的に速くなっていて、1.9に対応したRailsがリリースされたらすべて解決されるのかもしれません。

それでも、いまのところPythonの軽快さ、シンプルさに満足しています。あと、GAEでPythonが動くのもいいですよね。

「Googleが導入した言語」というのがハセテツ的に琴線に触れたのかもしれません。

ミーハーですいません。w

Djangoでパスワード等の暗号化保存を隠蔽する

2010-04-21
このエントリーをはてなブックマークに追加

隠蔽するっていうか、viewにごりごり書くのではなくてModelのメソッドで処理してしまおうという話です。このエントリの応用ですね。

import hashlib

class Member(models.Model):
    name = models.CharField(max_length=32)
    passwd = models.CharField(max_length=256)
    def save(self, force_insert=False, force_update=False):
        self.passwd = hashlib.md5(self.passwd).hexdigest()
        super(Member, self).save(force_insert, force_insert)
Modelのsave()メソッドをオーバーライドしています。これで、登録フォームなどからPOSTされたデータをそのままsave()してもデータベース上には暗号化された状態で保存されます。
adminサイトで作成したユーザ情報なんかのパスワードとかも、暗号化して保存してくれます。便利便利。
Tags: ,

DjangoのフォームでChoiceFieldのchoicesを動的に指定する

2010-04-20
このエントリーをはてなブックマークに追加

コンボボックスの<option>~</option>の値は動的に指定したいものです。が、Djangoでどうしたらよいものか、数時間悩みました。

これが意外と簡単で、こんなことで数時間悩んだ自分が情けなく、それでも発見した自分をほめてやりたく。w

class Form_hoge(forms.Form):
    month = forms.ChoiceField()
_form = Form_hoge()
_form.fields[‘month’].choices = [(1,1), (2,2), (3,3)]
フォームを呼んだ後に指定するだけ。
なんですぐ気づかなかったんだろ。。。
Django1.1.1、Python2.5で動作検証。
Tags: ,

Djangoで集計クエリ(1.1以降でannotateを使用)

2010-04-20
このエントリーをはてなブックマークに追加

Djangoのモデルで集計クエリというと、1.0以降でcount()が使えますが、1.1になってさらに多機能になっていました。

from django.db.models import Sum

_result = Detail.objects.all().values(‘category’).annotate(price=Sum(‘price’))
for item in _result:

    print item

Detailというモデルがあって、それをcategoryというフィールドでグループ化し、priceというフィールドのsum(合計)を求めています。

values(~)は必須ではありません。グループ化しないと全体のsumですね。

annotate(得られる結果の名前=Sum(集計対象フィールド))

です。サンプルの「price」をsum_priceにしても結果は得られます。

また、annotateで利用できるのはsumだけでなく、Avg、Max、Min、Countも使用できます。サンプルコードだとSumしかインポートしていませんが、「*」でまとめてインポートするか、「Avg, Max, Min, Sum, Count」をそれぞれインポートしてあげれば使用できます。

ちなみに、サンプルコードで得られるitemは、Detailモデルのインスタンスではありません。辞書です。最初ここに気づかず、「?」となっていました。w

Tags: ,