DjangoでLaravelライクなORM/クエリビルダのOratorを使う方法

この投稿は [「Django Advent Calendar 2019 - Qiita」](https://qiita.com/advent-calendar/2019/django) の0日目の記事です。(勝手に)(怒られそう)(怒られたら消します)

さてみなさん、Pythonでウェブアプリを作った時に、DBアクセスはどのようなライブラリを使っていますか？最もポピュラーなものはDjangoORMだと思います。FlaskやPyramidを使っている人はSQLAlchemyという選択肢が多いしょう。データサイエンス畑の人はPandasのDataFrameを使っているかもしれません。しかし私が最も推しているライブラリは、表題にある「Orator」です。ここQiitaでもOratorについて書かれた記事が既にいくつかあります。

[「Orator」を触ってみたのでメモ](https://qiita.com/subarunari/items/3471f8591991689b4a86)  
[Scrapy でスクレイピングしたデータを ORM で RDB に保存する](https://qiita.com/Chanmoro/items/9cab741c0b8f5a87fee3)


OratorはLaravelのORMの文法にとても強い影響を受けたORM/クエリビルダです。  
[公式サイト](https://orator-orm.com)  
開発を初めたのはsdispater氏。Pythonに深くハマっている人なら気づくかもしれません。最近はPoetryの開発に取り組んでいらっしゃる彼です。  
LaravelのORMに強く影響を受けたというだけあって、マイグレーションはLaravelやRailsのようにマイグレーションファイルを自分で作らなければなりません。ここはテーブルの変更を自動で感知してマイグレーションファイルを作ってくれるDjangoに軍配が上がるでしょう。しかしOratorのその素晴らしさは、クエリの書きやすさにあるのです。

```
students = Student \
    .select('id', 'name', 'email') \
    .join('class', 'class.num', '=', 'students.class_num') \
    .where('class.id', 3) \
    .get()
```

ねっ？SQLライクで直感的でしょ？joinやwhereをメソッドチェーンでいくらでも繋げることができます。どんな複雑なクエリでもドンと来い！

ではこいつをDjangoに取り入れるにはどうすればいいのか？ここから本題に入っていきます。

# アプリケーションの中にorator.pyを作ろう
[Django公式チュートリアル](https://docs.djangoproject.com/ja/2.2/intro/tutorial01/#creating-a-project)で作るディレクトリ構造を元にして、models.pyと同じ階層にorator.pyというファイルを作ります。

```
├── mysite
│   ├── polls
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── migrations
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── orator.py ←←←←←←←NEW!!!
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── templates

```

次にorator.pyの中で、oratorのライブラリにDBへの接続情報を知らせるための情報を設定します。  
別にここで固定値を入れても良いのですが、DBへの接続情報は既にsettings.pyで定義されているので、ここから呼んできた方が良いでしょう。

```mysite/polls/orator.py
import logging

from orator import DatabaseManager
from orator import Model
from django.conf import settings

config = {
    'mysql': {
        'driver': 'mysql',
        'database': settings.DATABASES['default']['NAME'],
        'host': settings.DATABASES['default']['HOST'],
        'user': settings.DATABASES['default']['USER'],
        'password': settings.DATABASES['default']['PASSWORD'],
        'prefix': ''
        'log_queries': True,
    }
}


db = DatabaseManager(config)
Model.set_connection_resolver(db)


logger = logging.getLogger('orator.connection.queries')
logger.setLevel(logging.DEBUG)

formatter = logging.Formatter(
    'It took %(elapsed_time)sms to execute the query %(query)s'
)

handler = logging.StreamHandler()
handler.setFormatter(formatter)

logger.addHandler(handler)
```
`driver`には「sqlite」「mysql」「postgres」の3種類が使えます。完全にこの通りに記述しないと動かないので、Typoに注意。

`from django.conf import settings`でsettings.pyの内容を引っ張ってこれます。あとはDictを辿っていけば望みの値が得られます。

# モデルクラスを定義しよう

OratorのモデルクラスはDjangoやSQL Alchemyとは違い、テーブルのSchemaではありません。純粋に「このクラスはこのテーブルに紐付いているんですよ」というのを定義するだけです。

例として、[Django公式チュートリアル](https://docs.djangoproject.com/ja/2.2/intro/tutorial02/#creating-models)のpollsアプリで使われている`Question`クラスと`Choice`クラスを定義してみましょう。

```mysite/polls/orator.py
class Question(Model):
    __table__ = 'polls_questions'


class Choice(Model):
    __table__ = 'polls_choices'
```

Djangoが作るテーブル名には、先頭にアプリケーション名のプレフィックスが付くので、それを考慮した上で明示的にテーブル名を書いてあげましょう。  
ただ私が普段開発するやり方では、Djangoでテーブルを作る時に、アプリケーション名のプレフィックスが付かないようにしています。

```mysite/polls/models.py
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    class Meta:
        # db_tableを定義するとそれが実際に作られるテーブル名になる
        db_table = 'questions'
```

```mysite/polls/orator.py
class Question(Model):
    __table__ = 'questions'
```
# Viewの中でOratorを呼び出してみよう

[Django公式チュートリアル](https://docs.djangoproject.com/ja/2.2/intro/tutorial03/#a-shortcut-render)のこのコードのDBアクセスしている部分をOratorで書き換えるとどうなるでしょうか？以下のようになります。

```mysite/polls/views.py
from django.shortcuts import render

- from .models import Question
+ from .orator import Question

def index(request):
    - latest_question_list = Question.objects.order_by('-pub_date')[:5]
    + latest_question_list = Question.order_by('pub_date', 'asc').limit(5)
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)
```

なんで簡単！ブラボー！  

# おわりに
DjangoでOratorを取り入れて、テーブル作成・マイグレーションはDjangoモデル、クエリ発行はOratorと役割分担をさせよう！特にウェブアプリ開発では複雑なクエリを発行することが多いので、Oratorは大いに力になってくれるはずです。もっと広まれー！