HyでDjangoアプリを実装する

この記事は、LISPっぽいけど一応[Python その2 Advent Calendar 2015](http://qiita.com/advent-calendar/2015/yet_another_python)の5日目の記事です。

[Hy](https://github.com/hylang/hy)のREADMEに["Django + LISP"](https://github.com/paultag/djlisp/tree/master/djlisp)という一文があり、DjangoアプリをHyでも実装できるというコンセプトを示したソースコードが示されていますが、これだけでは何をどうすればいいか分からないので、`startproject`するところからステップを追って解説します。

githubにレポジトリを作り、ステップごとにコミットを分けてpushしたので参考にしてください。

- https://github.com/csakatoku/hellohy

## `startproject`する
ひとまず普通のPythonで`django-admin.py startproject`します。使用しているDjangoのバージョンは1.9です。

```
django-admin.py startproject hellohy
```

`startproject`した段階のコミットがこれです。

- https://github.com/csakatoku/hellohy/commit/d8e579c3199969196f325538299560fd5508cc28

## `*.py`を`*.hy`に書き換える
すべてのプログラミング言語はLISPに至るので、出来る限りPythonコードを排除して、Hyで置き換えたいと思います。

プロジェクトの作成直後のコードをHyに書き換えたコミットがこれです。

- https://github.com/csakatoku/hellohy/commit/9d59e77bb304521910a56add2f6926008da7fb45

少し躓いた点は以下の2つです。

- `manage.py`をHyに置き換えても、`hy manage.hy runserver`が動かなかったので、このファイルだけは諦めました。
- Hyでモジュールを定義するには、起動スクリプト(この場合`manage.py`)に`import hy`を入れる必要があるようです。
- シングルクォートはHy(LISP)では特殊な意味を持つので、文字列はすべてダブルクォートに変更しました。

Pythonの`import x`, `from y import z`のHyの変換の仕方は、[ここ](http://docs.hylang.org/en/latest/language/api.html#import)を見れば一目瞭然です。

- http://docs.hylang.org/en/latest/language/api.html#import

この段階で、`manage.py`以外のすべての`py`ファイルを消しても、`python manage.py runserver`が動くようになり、実際にブラウザでもアクセスできます。

## `app_template`を作成
`startproject`の後は`startapp`でDjangoアプリケーション・モジュールを追加するのがDjangoの定番の流れですが、この段階で`startapp`してもPythonモジュールが追加されるのは分かりきっていたので、先にアプリケーション・テンプレートを追加します。

それが、このコミットです。

- https://github.com/csakatoku/hellohy/commit/91bdbc5bc13231585ef72ea2773d9a45226cb304

この`app_template`を使って`startapp`を実行します。

```
python manage.py startapp --template=hellohy/app_template -ehy myapp
```

ポイントは、

- `--template`で独自のアプリケーション・テンプレートのディレクトリを指定する。
- `-e`でテンプレートとして扱うファイルの拡張子を追加する。

ことです。

上記コマンドで`myapp`というアプリケーションを作り、`myapp.views.top`というごく単純なテンプレートを表示するビューを追加したコミットがこちらです。

- https://github.com/csakatoku/hellohy/commit/ce9c5271d2fd8082ab8b8a3e0a244bf518d1870b

## モデルを定義する
[Hyのチュートリアル](http://docs.hylang.org/en/latest/tutorial.html#hy-is-a-lisp-flavored-python)にDjangoのモデルっぽいサンプルコードがありますが、この段階でそれが擬似コードではなく、実際に動くコードであるというのが確認できます。

モデルを足して、データベース上のデータを表示するようにしたコミットがこちらです。

- https://github.com/csakatoku/hellohy/commit/496d2c4dd27baf5cddfa0c1a2040410711d54fb6

モデルの定義は以下のようになっています。

```
(import [django.db [models]]
        [django.utils.timezone :as timezone])

(defclass Topic [models.Model]
  [title (models.TextField)
   url (models.URLField)
   created_at (models.DateTimeField :default timezone.now)])
```

`Topic`とは何らかのニュースのタイトルをソースのURLを表したオブジェクトと考えてください。重要なのは次の点です。

- `defclass`は最新開発版のHyの構文を使っています。
- クラスプロパティは`defclass`マクロの3番目の引数にリストで与えます
- Pythonのキーワード引数`models.DateTimeField(default=timezone.now)`は`(models.DateTimeField :default timezone.now)`のように表現します。

モデルを定義した後に、`makemigrations`, `migrate`する手順は通常のDjangoと同じです。

## ビューを実装する
上で定義した`Topic`オブジェクトを複数件取得してテンプレートをレンダリングする例です。

```
(import [django.shortcuts [render]]
        [myapp.models [Topic]])

(defn top [req]
  (def topics (-> (Topic.objects.all)
                  (.order_by "-id")))
  (render req "top.html" {"topics" topics}))
```

プライマリキーでオブジェクトを取得してテンプレートをレンダリングする例。

```
(import [django.shortcuts [render]]
        [django.http [Http404]]
        [myapp.models [Topic]])

(defn topic_detial [req topic_id]
  (def topic (try
              (Topic.objects.get :pk topic_id)
              (except [e Topic.DoesNotExist] (raise Http404))))
  (render req "topics/topic_detail.html" {"topic" topic}))
```

以下のコミットで一覧と詳細のビューを追加するところまで実装しています。

- https://github.com/csakatoku/hellohy/commit/e8d254ce09c3fb9fadecfb3bdb8ccf74453230dc

## ユニットテスト
Djangoを使うことのメリットにユニットテストがしやすいということがあると思うので、今回もユニットテストを足しておくことにしましょう。

それが、このコミットです。

- https://github.com/csakatoku/hellohy/commit/1808d0d9d1ca41ecd1aee9e1abd0322307111f42

本当はこの`tests.hy`だけでユニットテストが実行できればよかったのですが、テストランナーが`.hy`を見つけてくれなかったので、裏技のような感じになってしまいますが、同名の空の`.py`ファイルを足しています。

この裏技は何とかしたいと思いますが、とりあえずテストは通っています。

```
$ python manage.py test myapp
Creating test database for alias 'default'...
....
----------------------------------------------------------------------
Ran 4 tests in 0.051s

OK
Destroying test database for alias 'default'...
```

## 最後に
HyでDjangoアプリを実装していく手順を紹介しました。

ご自身でも試してもらえればお分かりになると思いますが、HyでもDjangoのパワーを100%引き出せそうなので、すべてのプログラミング言語はLISPに至るというのを実感できますね。

次回はHyで書いたDjangoアプリを実環境にデプロイする方法を通じて、Hyの可能性を探っていきたいと思います。