Django で OR を使ったクエリを実行する方法

データベースから「A OR B」っていう条件で絞り込んでデータを取得するのって、Django ではどうやるんだろう。「A AND B」なら filter や exclude でいいんだけど…。

困ったときは Django の公式ドキュメントを見るべし!ってことで調べてみたら、ちゃんと書いてありました。

Q オブジェクトを使えばいいのね。

例えば「user1 または user2 の投稿を取得する」だと、Q オブジェクトを使って

posts = Post.object.filter(Q(author=user1) | Q(author=user2))

と書けます。

OR と AND を組み合わせた、「user1 または user2 の投稿で下書きじゃないものを取得する」の場合

posts = Post.object.filter(Q(author=user1) | Q(author=user2),
                           Q(is_draft=False))

で OK。

filter や exclude といった QuerySet のメソッドと、Q オブジェクトを使えば、たいてい事足りそうです。ホント、Django の Model API って強力だ。

git submodule でサードパーティ製 Django アプリケーションを管理する

Django で新しく Web サービスを作成する場合、タグやユーザー登録といった汎用的な機能は、自分で実装しなくても PyPI や GitHub を探せば Django アプリケーションが見つかります。

ただ、そのままでは使えなくて多少の修正を加えることがあるので、 Django アプリケーションは Python の site-packages には入れたくないです。そこで、git submodule を使って管理することにしました。


プロジェクトのルートで、git submodule コマンドを使ってリポジトリを追加します。

git submodule add リポジトリURL ./submodules/アプリケーション名

例えば django-taggit なら

git submodule add https://github.com/alex/django-taggit.git ./submodule/django-taggit

って感じになります。


これだけだと、デバッグやテストをするとき submodules 下にあるアプリケーションは読み込まれません。submodules 下にあるアプリケーションにパスを通す必要があります。

settings.py に

import os
PROJECT_ROOT = os.path.dirname(__file__)

import sys
# submodules をパスに追加
git_sub_modules = os.path.join(PROJECT_ROOT, "submodules")
for dir in os.listdir(git_sub_modules):
    path = os.path.join(git_sub_modules, dir)
    if not path in sys.path:
        sys.path.append(path)

と書いておけば、デバッグやテストをするとき、submodules 下に置いたアプリケーションのパスがすべて登録されます。


以降、サードパーティ製アプリケーションを追加したくなったら、git submodule add で submodules ディレクトリ下にダウンロードすればいいです。

クラスベースの汎用ビューを使って高品質プログラミング

Django の汎用ビューの使い方を調べるために公式ドキュメントを読んだんですが、今更ながら Django1.3 で汎用ビューが関数からクラスに変わっていることを知りました。1.3 リリースは今年の3月23日だから、8か月近く知らなかったことになるかな。ハズカシイ。

関数版の汎用ビューもまだ使えるみたいですが、ドキュメントを見た感じたと、クラスベースの方が便利そう。


試しにいくつかのビューをクラスベースの汎用ビューを使って実装してみました。現在開発しているサービスのコードから抜粋。

from django.views.generic import ListView

class UserPostListView(ListView):
    # ビューが描画に使うテンプレートを指定
    template_name = "posts/post_user_list.html"

    # テンプレートで扱うときのクエリセットの名前を指定
    context_object_name = "post_list"

    # 1ページに表示するのは20個まで
    paginate_by = 20

    # ユーザーで絞り込んだクエリセットを返す
    def get_queryset(self):
        return Post.get_by_user(self.request.user)

    # テンプレートに渡すコンテキストにデータを追加
    def get_context_data(self, **kwargs):
        ctx = super(UserPostListView, self).get_context_data(**kwargs)
        ctx.update({
            "is_mypage": True,
        })
        return ctx

ListView を使っています。たったこれだけでページネーションに対応したビューが実装できてしまいました。あとはテンプレート書くだけ。


登録用の CreateView も使ってみました。

from django.views.generic import CreateView

class CreatePostView(CreateView):
    # ビューが描画に使うテンプレートを指定
    template_name = "posts/post_new.html"

    # 登録に使うフォームクラスを指定
    form_class = PostForm

    # 登録に成功したときのリダイレクト先を返す
    def get_success_url(self):
        return reverse("list_user_post")

    # ユーザーが入力したデータの検証に成功したら、
    # ユーザーデータを追加して保存。
    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.user = self.request.user
        self.object.save()
        return redirect(self.get_success_url())

    # テンプレートに渡すコンテキストにデータを追加
    def get_context_data(self, **kwargs):
        ctx = super(CreatePostView, self).get_context_data(**kwargs)
        ctx.update({
            "is_mypage": True,
        })
        return ctx

POST かどうか判断する if 文が無くてスッキリ。個人的に、if 文で GET か POST か判断して処理を分岐するやり方は、どうもしっくりきませんでした。Python で最初に使ったフレームワークが AppEngine の webapp だったので、HTTP リクエストをクラスの get や post メソッドに割り当てるやり方のほうが好み。


クラスベースの新しい汎用ビューでは、内部処理をところどころフックできます。汎用ビューをベースに、テンプレートを指定したり、リダイレクト先を指定したり、コンテキストに値を追加したりするだけで、やりたいことが実現できてしまうことが結構あります。Django の汎用ビュー便利すぎ。


「主キーでモデルを取得してテンプレートに表示する」みたいな超単純なビューならともかく、ちょっと複雑になりそうなら、まず汎用ビューが使えるか検討したほうがいいですね。そして、もし汎用ビューが使えなかったら、仕方ないから自分でゴリゴリ書く、と。バグの少ない高品質なサービスを作る一番の方法は、できるだけコードを書かないことですから。

django-taggit と django-taggit-templatetags の組み合わせが便利すぎる

django-taggit を使ってモデルにタグをつけたり、タグでモデルを検索したりが、簡単に実現できました。これだけでも便利なんですけど、django-taggit-templatetags というアプリを使えばさらに便利になります。


というのも、taggit の Tag クラスは、タグが付いているモデルの数を保存するフィールドがありません。タグを人気順に表示したり、タグクラウドを表示する場合、タグが付いているモデルを数える必要があります。面倒ですよね。


django-taggit-templatetags のカスタムタグを使えばそんなの不要。しかも、ビューでタグを取得してテンプレートに渡す必要もありません。


インストールは pip で一発。

pip install django-taggit-templatetags


django-taggit-templatetags も Django アプリなので、settings.py を編集して INSTALLED_APPS に追加しないといけません。

INSTALLED_APPS = (
    ...
    "taggit",
    "taggit_templatetags",
    ...
)

これで準備 OK。


HTML テンプレートに下記コードを書けば、タグを人気順に表示できます。

{% load taggit_extras %}

{% get_taglist as tags %}
<ul>
    {% for tag in tags %}
    <li>{{ tag }} x {{ tag.num_times }}</li>
    {% endfor %}
</ul>


タグクラウドを表示したい場合は、次のように書きます。

{% load taggit_extras %}

{% get_tagcloud as tags %}
<div>
    {% for tag in tags %}
    <font size={{tag.weight|floatformat:0}}>{{tag}}</font> 
    {% endfor %}
</div>


django-taggit と django-taggit-templatetags を使うことで、でやりたいことが超簡単に実現できました。毎回集計するためアクセスが増えるにつれて遅くなりそうなので、いずれはタグの部分をキャッシュするなどの対策が必要ですけどね。

Django で簡単にタグ機能を実装できる django-taggit が便利

Web アプリケーションにタグ機能を実装したくて、いい Django アプリケーションがないか探したみたら、django-tagging っやつが真っ先に挙がってきました。でも、あまり評判よくないっぽい?


さらに調べてみたら django-taggit というのも発見。日本語の情報が見当たらないですけど、海外の評判は良さそうでした。

ドキュメントもしっかり書かれているのが好印象。

今回は taggit を採用することに。


インストールは pip で一発。

pip install django-taggit


django-taggit は Django アプリケーションなので、settings.py の INSTALLED_APPS に追加する必要があります。

INSTALLED_APPS = (
    ...
    
    'taggit',

    ...
)


あとはタグを付けたいモデルに TaggableManager フィールドを追加すれば OK。

from django.db import models
from django.contrib.auth.models import User
from taggit.managers import TaggableManager

class Entry(models.Model):
    user = models.ForienKey(User)
    title = models.CharField(max_length=200)
    content = models.CharField(max_length=800)
    tags = TaggableManager()    # タグ用フィールド
    created = models.DateTimeField(auto_now_add=True)

簡単ですね。


モデルにつけられたタグを取得するには、TaggableManager の all メソッドを使います。

entry = Entry.objects.get(pk=1)
tags = entry.tags.all()

名前に Manager が付いているくせに、Manager クラスを継承しているわけじゃない、というのがややこしいです。名前は TagField に改名した方がいいと思うんですが…。大した問題じゃない?


クエリを使うとき、通常のフィールドと同じように、タグのフィールドにもフィルタを指定できます。

def get_entries_by_tag(tag_name):
    return Entry.objects.filter(tags__name__in=[tag_name])

Django 標準のフィールドと同じように使えるのはポイント高いです。API の使いやすさが、django-taggit を選んだ一番の理由かな。


なんと、django-taggit は ModelForm にも対応しています。

from django.forms import ModelForm
from sample.models import Entry

class EntryForm(ModelForm):
    class Meta:
        model = Entry
        exclude = ("user", "created")
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response, redirect
from sample.forms import EntryForm
from sample.models import Entry

@login_required
def new_entry(request):
    form = EntryForm(request.POST)

    if request.method == "POST" and form.is_valid():
        entry = form.save(commit=False)
        entry.user = request.user
        entry.save()
        form.save_m2m()
        return redirect(reverse("list_entry"))

    return render_to_response("entry_new.html", { "form": form })

ユーザーが入力したタグの書式が正しいかどうか、検証するコードを自分で書かずに済むので助かります。


django-taggit は使いやすい Django アプリケーションだと思います。API が Django っぽい。これ重要。Django っぽいということは、ドキュメントを読まなくても、使い方がだいたい推測できるわけですから。


django-taggig の開発は落ち着いているみたいですけど、今のところ問題には遭遇していません。まだガッツリ使ってないからかもしれませんがね。もし放置されてるバグに遭遇した場合、GitHub で公開してあるので、フォークして修正してプルリクエスト送ればいいですね。最後の手段ですが。

Python と Java で AppEngine アプリを開発してみて

はじめに

PythonJava でそれぞれ AppEngine アプリを何個か作ってきましたが、AppEngine の利用方針を変更したことだし、このタイミングでそれぞれの開発を振り返ってみます。

Python での開発

フレームワークは AppEngine に特化した Kay Framework を使ってきました。Kay はセッションや認証やフォームやなど機能が豊富。認証バックエンドを変更すれば OAuth も使えます。AppEngine テスト用のクラスも提供してあるので、webapp で開発するよりテストしやすいです。ただ、機能が多すぎて使わないのもありますが。REST API とかね。

小〜中規模のサービスを速攻で作るなら、Python + Kay は申し分ないです。ライブラリを足さなくても必要十分な機能があります。

あと、小さいサービスなら、わざわざ Kay を使わなくても Flask で十分かもしれません。

Java での開発

Eclipse の恩恵は大きいです。ソースコードを管理しやすいし、リファクタリングもしやすい。

フレームワークは Slim3 を使ったんですが、テストが異常なくらい書きやすかった。Kay でもテストはサポートしているけど、Slim3 には敵わないですね。

さらに、GWT + Slim3 の組み合わせは強力。今の私の JavaScript スキルでは、Full Ajax なアプリを一か月で実装するなんて無理でした。GWT 様々です。サーバーサイドとクライアントサイドの両方を Java で開発できるスピード感は想像以上でした。

大きいアプリは Java の方が作りやすい印象です。Eclipse と Slim3 の力が大きい。あと GWT も。テストしやすい&リファクタリングしやすいは超重要ですよね。

まとめ

JavaPython 両方使える人は

  • 小規模のとき Python
  • 中規模のとき
    • 開発スピード重視なら Python
    • 作り込みが必要なら Java
  • 大規模のとき Java

という風に使い分けてもいいんじゃないでしょうか。

終わりに

私の場合、C# 歴が長いので、Python よりも Java の方が作り込みやすかったです。「効率とか考えずに速攻で実装→リファクタしまくる」という私の開発スタイルには、静的型付け言語 + IDE が合っているみたい。

まぁ、自分用ツールを作る程度なら、Python の方がサクッと作れるので、今後は Python を使う機会が多くなると思います。HTML5JavaScript のスキルも磨きたいので、GWT を使うことも減るでしょうね。

AppEngine 用の Flask 拡張作ってみた

先日、AppEngine 向けには自分用のツールだけを開発すると宣言しました。

自分用のツールなら Kay みたいなヘビー級のフレームワークを使うまでもないです。なので、Flask を使っています。


ただ、Flask はビューとテンプレートの機能ぐらいしか提供していないので、足りない機能は他のライブラリで補う必要があります。AppEngine なら、モデルは db モジュール、フォームは WTForms を使えばいいですね。


でも、それだけではまだ不便。そこで AppEngine ユーティリティを集めた Flask 拡張を作ってみました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    flask_appengine
    ------------------

    Adds basic App Engine support for Flask.

    :copyright: (c) 2011 by tnakaura.
    :license: BSD, see LICENSE for more details.
"""
from functools import wraps
from google.appengine.api import users
from werkzeug.exceptions import (
    NotFound, Unauthorized, Forbidden,
)
from flask import (
    request, redirect,
)


class AppEngine(object):
    def __init__(self, app=None):
        if app is not None:
            self.app = app
            self.init_app(self.app)
        else:
            self.app = None

    def init_app(self, app):
        self.app = app
        self.app.context_processor(self._inject_users)

    def _inject_users(self):
        return dict(
            create_login_url=self.create_login_url,
            create_logout_url=self.create_logout_url,
            get_current_user=users.get_current_user,
            is_current_user_admin=users.is_current_user_admin)

    def create_login_url(self, url=None):
        """Computes the login URL for redirection.
        """
        if url is None:
            url = request.url
        return users.create_login_url(url)

    def create_logout_url(self, url=None):
        """Computes the logout URL for this request and specified destination URL,
        for both federated login App and Google Accounts App.
        """
        if url is None:
            url = request.url
        return users.create_logout_url(url)


def login_required(func):
    """A decorator to require that a user be logged in to access a view.
    """
    @wraps(func)
    def _login_required(*args, **kwargs):
        user = users.get_current_user()
        if user is None:
            if request.is_xhr:
                raise Unauthorized()
            else:
                return redirect(users.create_login_url(request.url))
        return func(*args, **kwargs)
    return _login_required


def admin_required(func):
    """A decorator to require that a user be admin to access a view.
    """
    @wraps(func)
    def _admin_required(*args, **kwargs):
        if not users.is_current_user_admin():
            user = users.get_current_user()
            if user:
                raise Forbidden()
            elif request.is_xhr:
                raise Unauthorized()
            else:
                return redirect(users.create_login_url(request.url))
        return func(*args, **kwargs)
    return _admin_required


def get_or_404(cls, key):
    model = cls.get(key)
    if not model:
        raise NotFound()
    return model


def get_by_key_name_or_404(cls, key_name):
    model = cls.get_by_key_name(key_name)
    if not model:
        raise NotFound()
    return model


def get_by_id_or_404(cls, id):
    model = cls.get_by_id(id)
    if not model:
        raise NotFound()
    return model

login_required デコレータや、get_or_404 関数のような、Flask での AppEngine 開発がちょっとだけ便利になるユーティリティを集めています。ちりも積もればなんとやら、です。


使い方はこんな感じ。

from flask import Flask
from flask_appengine import (
    AppEngine, login_required,
)

app = Flask(__name__)
gae = AppEngine(app) # 内部で ContextProcessers がセットされる

@app.route("/")
@login_required
def index():
    return "Hello, world!"

あと、AppEngine クラスは内部で ContextProcessers を登録するので

<!DOCTYPE html>
<html>
    <head>
        <title>FlaskAppEngineSample</title>
    </head>
    <body>
        {% if get_current_user() %}
        <a href="{{ create_logout_url() }}">logout</a>
        {% else %}
        <a href="{{ create_login_url() }}">login</a>
        {% endif %}
    </body>
</html>

という風に、テンプレート内で Users API が使えます。これは結構便利かも。


AppEngine の全ての機能をサポートしようとしたら半端ない量になるので、バランスを考えながら機能を追加していく予定。必要最小限の機能を提供するのが Flask っぽいですから。とりあえず、Flask のセッションでデータストアを使うようにはしたいです。あと appstats のサポートも。