DjangoのurlresolversでURL以外を振り分ける

リクエストを即時実行したくない

現在作ってるゲームでは諸々の都合で複数のリクエストを一度キューに貯めて後からまとめて実行する仕組みが必要になっている。
そういう時にどうするかってのを、@cactusman と検討したのだけど MQ や Celery をそのままでは上手くいかなそうだったので、結局 Django モデルでキューっぽいテーブルを実装した。

class QueueTask(models.Model):
    """                                                                                                                                                                                                   
    キューに詰むタスク                                                                                                                                                                        
    """
    request = models.CharField(verbose_name=u'操作のリクエスト', max_length=256)
    created_at = models.DateTimeField(verbose_name=u'キューへの追加日時', auto_now_add=True)
    status = models.IntegerField(verbose_name=u'実行ステータス', default=0)

みたいな感じで。

で、この操作のリクエストによって実行内容が異なるのだが、その辺の振り分けを自分で書くのが面倒だったので urlresolvers を流用することにした。


urlresolvers の流用
前述の QueueTask のメソッドとしてタスクの実行部分はこんな感じになっている。

    def exec_task(self):
        resolver = urlresolvers.RegexURLResolver(r'^/', 'queue_manager.task')
        callback, callback_args, callback_kwargs = resolver.resolve(self.request)
        result = callback(*callback_args, **callback_kwargs)
        self.save()

あとは、queue_manager/task.py を urls.py と同じように

urlpatterns = patterns(
    'animal.task',
    url(r'^task/animal/put/(?P<animal_id>\d+)/$', 'put', name='put_animal'),
)

みたいにして、view関数 みたいにタスクの実行部分を書けばOK。
注意点としては、urlpatterns って名前は内部で決め打たれてるので変えちゃダメ。

DjangoのForeinKeyとかキャッシュとか改

毎回キャッシュアクセスはしたくない

前回、DjangoのForeinKeyとかキャッシュとか - ま、そんな日もあるさを書いたんですけど

name = player.cached_community.name
id = player.cached_community.id

みたいに使うと複数回キャッシュを見に行ってしまうので

community = player.cached_community
name = community.name
id = community.id

みたいに変数にコピーして使っていました。
でも、毎回これは面倒!


改良してみた

class Community(AbustractCachedModel):
    name = models.CharField(max_length=50)

class Player(AbustractCachedModel):
    name = models.CharField(max_length=50)
    community = models.ForeinKey(Community)
    community_cache = None
    @property
    def cached_community(self):
        if self.community_cache is None:
            self.community_cache = Community.get(self.community_id)
        return self.community_cache

これで、何も考えずに

name = player.cached_community.name
id = player.cached_community.id

みたいに使えばOK。


気をつけること
communityを更新しても自動ではcached_communityは更新されないので、そこんとこだけ注意。

DjangoのForeinKeyとかキャッシュとか

キャッシュ機能付きモデルの欠点

以前、Djangoでキャッシュ機能付きモデル - ま、そんな日もあるさを書いたんですけど、これって ForeinKey を持つようなモデルだと思ったように上手く動いてくれません。

class Community(AbustractCachedModel):
    name = models.CharField(max_length=50)

class Player(AbustractCachedModel):
    name = models.CharField(max_length=50)
    community = models.ForeinKey(Community)

みたいな、モデルがあった時に

player = Player.get(1)
player.community

みたいにアクセスしてしまうとキャッシュから読むのではなく SQL が発行されてしまいます。


解決策

そういう場合は

class Player(AbustractCachedModel):
    name = models.CharField(max_length=50)
    community = models.ForeinKey(Community)

    @property
    def cached_community(self):
        return Community.get(self.community_id) 

みたいにプロパティを定義して

player = Player.get(1)
player.cached_community

みたいなアクセスをすれば OK です。
ここで使っている community_id は Django で ForeinKey なフィールドを作った時に実際のテーブルに追加されるフィールド名です。(フィールド名が xxx だったら xxx_id になります)


もうちょい詳しい話

player.community

みたいなアクセスの場合(実際には Django 内部のキャッシュとか色々ありますが)簡略化すると以下のような処理が走るので SQL が発行されてしまいます。

Player.objects.get(id=player.community_id)

なので、player を取得した時点でわかっている community_id を使ってキャッシュからアクセスすることで、SQL を発行せずに community を取得することが可能になります。
なお、community の代わりに community_id を使って SQL の発行回数を減らすノウハウは結構使うので覚えておくと良いかもしれません。

GWとかを振り返って

今年のGWは何もしなかった
月曜を休んで7連休だったのだが、30日に服を買いに行ったのと2日に奥様の付き添いで病院に行った以外は近所のスーパーくらいしか行かずに引きこもっていた。
それだけ時間があるなら勉強でもすれば良いのだが、それすらせずに毎日体調の悪い奥様に付き合って寝てたりアニメ見たりしてた。
せめてブログくらい書こうと思ったが、アウトプット出来るようなことは何もない。
なのでBPに転職して2ヶ月でやってたこととかを書いてみようと思う。


ゲームを作るという仕事
BPで何をやっているかというとPC向けのソーシャルゲームブラウザ三国志とかサンシャイン牧場みたいな)に携わっている。
僕は、企画があってプロデューサ的な人(以下Nさん)がいて、大まかなゲームの形が出来てるところから参加した。
まだ実際のゲームに使われるコードは1ミリも書いていない。(一週間くらいだけどプロトタイプ用のコードは書いた。)
ここのところやっているのは、Nさんが作った仕様を見て、「課金が…」とか、「システム的には…」とか、「これ必要ですか?」とか、「この意図なら別の方法のが良くないですか?」とか、ダメ出しをするだけの簡単なお仕事をしている。
役割的にはNさんが仕様を決めるのを手伝ってる感じだ。
何の因果でこんな偉そうなことをしてるのかはわからないが、g社とかMtGの経験とかが関係してるらしい。
こんなことを2月近くやってきてわかったのは、僕は別にゲームを作りたい訳じゃないってことだ。
仕様もほぼ決まり、ようやく本格的にコードが書けそうで今は凄くほっとしている。


プロトタイプのこととかSkypeBotのこととか
技術っぽいことで書こうと思ったこともあるのだけど、眠いのでまた次回。

randomがどれくらい収束するのか試してみた

twitterで50枚のものから1枚を選ぶのを延々と繰り返した場合に、最も選ばれるものと最も選ばれない物とでどれくらい差が出るのかって話をしてたので試してみた。
ソースはこんなの。

import random

count_list = [0] * 50

for i in xrange(10000 * 100):
    count_list[random.randint(0,49)] += 1

結果は

>>> count_list.sort()
>>> count_list[0]
19642
>>> count_list[-1]
20243

試してみる前は最大と最小で2倍程度の差があるのかと思っていたのだが、実際は5%くらいしか差がなかった。

ちなみに最初何も考えずにrange()で1億回くらい実行してメモリが足りなくなったのは秘密。

Google MapsのKMLを触ってみた

Google MapsのマイマップからRSSが無くなっていたので、社内用SkypeBotをRSSからKMLへ変更してみた。
その時いくつかハマったのでメモ代わりに書き残す。
KMLのパースにはElementTreeを使った。
コードはこんな感じ。

import urllib2
from xml.etree import ElementTree

KML_FEED = 'http://maps.google.com/maps/ms?msid=[MSID]&output=kml'
NAMESPACE = 'http://earth.google.com/kml/2.2'

xml = ElementTree.parse(urllib2.urlopen(KML_URL))
entries = xml.findall('.//{%s}Placemark' % NAMESPACE)


ハマった点

  1. KMLにはIDがないので吹き出しを表示するURLを生成出来なくなってしまった。
  2. Google MapsのURLにあるllパラメータは北緯、東経の順なのに、KMLのcoordinatesタグは東経、北緯の順だった。
  3. ネームスペースを指定しないとタグを取って来れなかった。

KMLファイルをエディタで見てもネームスペースは書いてないので

xml.getroot().tag

で根エレメントのネームスペースを確認して、それを指定する必要があった。


結局1は解決出来ていないので、吹き出しを表示するURLの作り方を知ってる人は教えてください。

DjangoテンプレートとGenshiを比較する

これはgumi Engineer's Diaryに書いたものを修正・転載したものです。


はじめに
世の中には、Advent Calendarなるものが存在しているみたいです。
僕は不勉強なのもあって今年になって初めて知りました。
そのうちの一つPython Web フレームワーク アドベントカレンダー にいかがですか?と @terapyonさんに声をかけて頂き、@RyoAbeさんからバトンが回って来たので、何かそれっぽいことを書いてみようかと思います。
とは言え、僕自身はDjango以外のPython Web フレームワークを使った経験があまりないので、今回は前職でちょろっと使っていたGenshiとgumiで使っているDjangoテンプレートの比較をしてみようかなーと考えています。



Djangoテンプレート
そんなわけでDjangoテンプレートです。
Djangoには専用のテンプレートシステムがあります。
詳しくはこの辺を見てください。
Djangoテンプレートだと
変数は

<h1>{{ section.title }}</h1>

条件分岐は

{% if foo %}
  {{ bar }}
{% endif %}

{% for topic in topics %}
<ul>
 <li>{{ topic }}</li>
</ul>
{% endfor %}

みたいに書けます。

書きやすくて便利です!

実際の例はこんな感じ。

{% extends "base_generic.html" %}

{% block title %}{{ section.title }}{% endblock %}

{% block content %}
<h1>Hello, ${w}</h1>

{% if b == True &}
<hr color="#00FF00" />
{% else %}
<hr color="#FF0000" />
{% endif %}

<ul>
{% for link_tag in l %}
 <li>{{link_tag}}</li>
{% endfor %}
</ul>
{% endblock %}

書きやすいのですが、制御文がテキストベースなので、構造が入り組んでいるとごちゃっとしていてわかりにくいですし、ブラウザでテンプレートを見てみてもよくわかりません><
gumiではデザイナさんにhtmlのコーディングを頼んでいるのですが、そのためにはデザイナさんにもDjangoにある程度習熟してもらう必要もあって、結構ハードルが高いです。



Genshi
そんな訳でGenshiです。
詳しくはこっちもUser Guideとかを見てもらうとして、ざっと説明すると
変数は

<h1>${ section.title }</h1>

条件分岐は

<b py:if="foo">${bar}</b>

<ul py:for="topic in topics">
 <li>${topic}</li>
</ul>

みたいに書けます。

制御文がテキストベースではなくタグベースですが、Djangoとあまり変わらないですね!

実際の例はこんな感じ。

<html xmlns="http://www.w3.org/1999/xhtml"   
      xmlns:py="http://genshi.edgewall.org/" py:strip=""  
      xmlns:xi="http://www.w3.org/2001/XInclude"  
      >
<head>
</head>
<body>
<h1>Hello, ${w}</h1>

<py:choose test="b()">
<hr py:when="True"  color="#00FF00" />
<hr py:otherwise="" color="#FF0000" />
</py:choose> 

<xi:include href="testapp/include.html" />

<ul py:for="link_tag in l"> 
  <li>${link_tag}</li> 
</ul> 
</body>  
</html>  

タグに制御文を埋め込むのが最初はキモく見えるかもしれませんが、すぐに慣れると思います。
タグに埋め込んである分シンプルでわかりやすいですし、何よりブラウザで見ても普通のhtmlなので、デザイナさんがそのまま作業が出来るのが良いですね!

ただテンプレートが厳密なxmlでなくてはダメです。
前職で使用していた際によくありましたが、<br /> でなく <br> と書いてしまうとエラーが出て怒られるので気をつけてください。



まとめ
Genshiを思い出しながらDjango上で動かしてみましたが、正直一人で手軽に作るのならDjangoテンプレートの方が楽ですね。
Djangoテンプレートに慣れているのもあって、厳密なxmlかどうかとか考えながらhtmlを書くのは面倒でした。
ですが、デザイナーさんやhtmlコーダーさんにテンプレートを修正してもらう場合は、Genshiテンプレートの方が確認等がしやすくて良いのではないかと思います。
専任のデザイナーさんやhtmlコーダーさんがいるような環境ではGenshiを使ってみるのはどうでしょうか?
その際の環境構築には「djangoで、テンプレートエンジンgenshiを使う」を参考にしてみてください!


ネタもなかったので、Genshiの簡単な紹介みたいになってしまいました。。。
日頃からアンテナを張り巡らせたり色々試したりしていないと、こういう時にあたふたとしてしまって良くないですね><
ハードルは下げておいたので、次は@ransuiさんお願いします!