SQLAlchemyで1つのテーブルに複数種のリレーションを貼る

ある1つのテーブルに複数種のリレーションを貼りたい。

前提

「映画」と「言語」を対象に考えてみると、ある映画と言語には2つの関係が持たせられる:

  1. その映画がどの言語を対象にしたものか
  2. その映画は元々どの言語のものなのか

例えば日本語吹き替え版「バックトゥザフューチャー」は、対象言語が日本語で、 元言語が英語ということになる。

これについてのテーブルを考えて、FilmとLanguageという2つを用意する。 Filmには対象言語へのFKであるlanguage_idと、元言語へのFKであるoriginal_language_id の2つを持たせてやる。もちろんFKの対象はLanguageテーブルである。

こういったテーブルがある場合に SQLAlchemy でどうやって対応するのかという話。

ちなみに議題にあげたテーブルはMySQLの Sakila という サンプル用データベースの、Film、Languageテーブルのこと。

バージョン

  • SQLAlchemy==0.8.0

書く

とりあえずこんなかんじ。nameとかのカラムは下には転記してない。

mport sqlalchemy as sa
from sqlalchemy.dialects import mysql
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class Film(Base):
    __tablename__ = 'film'
    film_id = sa.Column(sa.SmallInteger(unsigned=True),

                        primary_key=True, nullable=False)

    language_id = sa.Column(mysql.TINYINT(unsigned=True),
                            sa.ForeignKey("language.language_id"),
                            nullable=False)
    original_language_id = sa.Column(mysql.TINYINT(unsigned=True),
                                     sa.ForeignKey("language.language_id"))

    language = relationship(
        'Language',
        primaryjoin="Film.language_id==Language.language_id")
    original_language = relationship(
        'Language',
        primaryjoin="Film.original_language_id==Language.language_id")


class Language(Base):
    __tablename__ = 'language'
    language_id = sa.Column(mysql.TINYINT(unsigned=True),
                            nullable=False,
                            primary_key=True)

relationshipしてあげるわけですが、ここでprimaryjoinを指定してあげないと いけなかった。そうしないとlanguage_idとoriginal_language_idのどっちを使って Languageをとってくればいいか分からない。 language、original_languageの2つを用意してあげて、それぞれどういう条件で取ってくる かをprimaryjoinで教えてあげる。

あるけみすとへの道は遠い

Djangoのテストで設定 (settings) を上書きする

Django のユニットテストを書くときに、 設定ファイル (settings.py) を一部変更したいことがある。

そのやり方をメモ。

テストの一部で変えたいとき

ドキュメントに書いてあった。

django.test.TestCase を継承したテストで with self.settings(HOGE=1) とする方法と django.test.utils.override_settings デコレーターを使って @override_settings(HOGE=1) と する方法があるよう。 override_settings デコレーターはクラスにもメソッドにもかけれる とのこと。

使いどころとしては、例えばキャッシュを使ってる場合に、寿命が切れてる場合の挙動を テストするとき。キャッシュが切れるまで待つのはいやなので、設定ファイルに 書いてあるキャッシュの寿命を0にしてやってテストする。 手動で消せるかつ、その方法で十分ならそれでもいいけど。

ちなみにこれは 1.4 からの機能。

1.3 以前で変えたい

setUpで設定ファイル一時保存して、tearDownで戻してあげる。

from django.conf import settings
from django.test import TestCase

class HogeTest(TestCase):
    def setUp(self):
        self.tmp_settings = settings
        settings.HOGE = 1

    def tearDown(self):
        settings = self.tmp_settings

これはちょっと悲しい。

こんなものもあった

これだと 1.2 - 1.4 で、上記のような @override_settings などが使えるらしい。 でも未検証なので気が向いたら試す。

全体で変えたいときは

他の方法としては、テスト用に設定ファイルを分けておくのも良い。 こんなかんじ:

settings
|-- __init__.py
|-- common.py
|-- dev.py
|-- prod.py
`-- test.py

テスト時のDB設定やログファイルの出力先を変えるときなどはこうする。 大抵の場合は開発用の設定ファイルと同じで十分。

開発用の設定ファイルがリポジトリ管理下に無くて、プロジェクトメンバーが 自由に設定できる場合などは、テスト用の設定ファイルがあるといいかもしれない。

でもまぁ結局「このテストのときだけ値を変えたい」という場合には 対応できない。

Django 1.4 以降を使っているなら override_settings を使うのがよさそう。 ただ 「 settings.test で値を変更したのにテストに影響がない?!」 とか言って override_settings 使ってました、とかはありそう。

使うのを最小限にするのと、設定ファイルはテスト内で上書きされ得ると頭の片隅に置いとくと いいかも。

参考: