Djangoのデータベースルーターのallow_migrateの挙動について

Djangoフレームワークでは、データベースルータークラスを作成して、 settings.DATABASE_ROUTERS に設定することで、モデル毎に使用するデータベースを変えたりできます。

データベースルーターにはいくつかのメソッドを実装しますが、その中の一つ、 allow_migrate を実装した際のマイグレーション処理の挙動について気になったので調べました。

試した環境は、Python3.7.1、Django 2.1.3。

前提

  • Djangoのプロジェクト名: project1
  • アプリケーション名: myapp

対象のモデルは以下の通り:

myapp/models.py:

from django.db import models

class Spam(models.Model):
    name = models.CharField(max_length=20)

この Spam モデルをデータベースルーターにて、マイグレーションの対象外とします。

データベースルーターは以下の通り:

project1/db_router.py:

class DatabaseRouter:
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'myapp':
            return False
        return None

settings.pyDATABASE_ROUTERS に設定しています。

project1/settings.py (抜粋):

DATABASE_ROUTERS = [
    'project1.db_router.DatabaseRouter',
]

makemigrationsを実行した場合

マイグレーションファイルを作成する makemigrations コマンドを実行した場合は、 allow_migrateFalse を返却しても、マイグレーションファイルは作成されます。

$ python manage.py makemigrations myapp
Migrations for 'myapp':
  myapp/migrations/0001_initial.py
    - Create model Spam

migrateを実行した場合

マイグレーションファイルを作成した状態で、マイグレーションを実行する migrate コマンドを実行した場合は、対象のマイグレーションはスキップされます。

$ python manage.py showmigrations myapp
myapp
 [ ] 0001_initial

$ python manage.py migrate myapp
Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0001_initial... OK

$ python manage.py showmigrations myapp
myapp
 [X] 0001_initial

このとき、Djangoマイグレーションの管理情報を格納するテーブルには、マイグレーションの実行済みを表すレコードが作成されます。

$ sqlite3 db.sqlite3 -line "select * from django_migrations"
     id = 1
    app = myapp
   name = 0001_initial
applied = 2018-11-20 20:59:28.637569

注意したい点

  • allow_migrateFalse を返しても、 migrate ではマイグレーションファイルがあればマイグレーションの管理テーブルには書き込まれる
    • 意図せず対象のデータベースに django_migrations データベースが作成されたり、レコードが書き込まれることがある
    • 管理情報を書き込みたくない場合は、モデルに Meta.managed = False を指定し、管理対象外にしておく必要がある

参考

Promiseとasyncとawait

Promiseとasync、awaitの使い方を理解するために書いたサンプルコード。 setTimeoutを使って1秒後に画面に文字列を出力する。

Promiseとthen()

Promiseオブジェクトの生成とthen()メソッドの利用例。

<html>
<head>
  <meta charset="utf-8"/>
  <title>Promiseとthen()</title>
</head>
<body>
<button onclick="main()">Run</button>
<div id="output">
</div>
<script>
function print(text) {
  const element = document.getElementById('output');
  element.innerText = element.innerText + text + '\n';
}

function main() {
  const promise = slowFunction();
  // Promiseの処理が完了したら(resolveが呼ばれたら)、結果を出力
  promise.then(result => {
    // resolve関数の引数で渡された値がresultに格納される
    print(result);
  })
}

function slowFunction() {
  // Promiseオブジェクトを返す
  return new Promise(resolve => {
    // 1秒後にresolveを呼び出す
    setTimeout(() => {
      resolve("Result");
    }, 1000);
  });
}
</script>
</body>
</html>

Promiseとthen() - JSFiddle

Promiseとreject()

Promiseオブジェクトの例外処理、reject()とcatch() メソッドを使う。

reject()を呼ぶと、catch()メソッドに渡したコールバック関数が実行される。

<html>
<head>
  <meta charset="utf-8"/>
  <title>Promiseとreject、catch()</title>
</head>
<body>
<button onclick="main()">Run</button>
<div id="output">
</div>
<script>
function print(text) {
  const element = document.getElementById('output');
  element.innerText = element.innerText + text + '\n';
}

function main() {
  const promise = slowFunction();
  // Promiseの処理が完了したら(resolveが呼ばれたら)、結果を出力
  promise.then(result => {
    print("Success");
  }).catch((message) => {
    // reject関数で渡された引数がcatchの引数に渡されてくる
    print(message);
  })
}

function slowFunction() {
  return new Promise((resolve, reject) => {
    // 1秒後にrejectを呼ぶ
    setTimeout(() => {
      // エラー内容などはreject関数の引数で渡す
      reject("Error!");
    }, 1000);
  });
}
</script>
</body>
</html>

Promiseとreject、catch() - JSFiddle

Promiseとthrow

then()メソッドに渡したコールバック関数内で例外を発生させる場合はthrowを使う。 catchメソッドに渡したコールバック関数が呼ばれる。

<html>
<head>
  <meta charset="utf-8"/>
  <title>Promiseとthrow、catch()</title>
</head>
<body>
<button onclick="main()">Run</button>
<div id="output">
</div>
<script>
function print(text) {
  const element = document.getElementById('output');
  element.innerText = element.innerText + text + '\n';
}

function main() {
  const promise = slowFunction();
  // When it is finished promise(called resolved), then output the result.
  promise.then(result => {
    throw new Error("Error!");
  }).catch((error) => {
    // result is Error object.
    print(error);
  })
}

function slowFunction() {
  // Returns promise object.
  return new Promise((resolve, reject) => {
    // Execute resolve function after a second.
    setTimeout(() => {
      resolve("Result");
    }, 1000);
  });
}
</script>
</body>
</html>

Promiseとthrow、catch() - JSFiddle

Promiseをawait構文で待つ

Promiseオブジェクトの処理が終わるのをawait構文で待つ例。

<html>
<head>
  <meta charset="utf-8"/>
  <title>Promiseとawait構文</title>
</head>
<body>
<button onclick="main()">Run</button>
<div id="output">
</div>
<script>
function print(text) {
  const element = document.getElementById('output');
  element.innerText = element.innerText + text + '\n';
}

// "await"構文は"async"キーワード付きの関数内でしか使えない
async function main() {
  // awaitで呼び出すと、resolveかrejectが呼ばれるまで止まる
  // resolveで渡された引数は、戻り値として返却される
  result = await slowFunction();
  print(result);
}

function slowFunction() {
  // Promiseオブジェクトを返す
  return new Promise(resolve => {
    // 1秒後にresolve関数を呼ぶ
    setTimeout(() => {
      resolve("Result");
    }, 1000);
  });
}
</script>
</body>
</html>

Promiseとawait構文 - JSFiddle

asyncとawait

async functionで関数を実装すれば、Promiseの発行を減らせるのと、then()のメソッドチェーン地獄がマシになる

<html>
<head>
  <meta charset="utf-8"/>
  <title>asyncとawait</title>
</head>
<body>
<button onclick="main()">Run</button>
<div id="output">
</div>
<script>
function print(text) {
  const element = document.getElementById('output');
  element.innerText = element.innerText + text + '\n';
}

// "await"構文は"async"キーワード付きの関数内でしか使えない
async function main() {
  // async functionの結果が返ってくるまで待つ
  result = await slowFunction();
  print(result);
}

async function slowFunction() {
  // setTimeoutをPromiseでラップし、終わるまでawaitで待つ
  await new Promise(resolve => setTimeout(resolve, 1000));
  // async functionの結果として文字列を返却
  return "Result";
}
</script>
</body>
</html>

asyncとawait - JSFiddle

async、awaitと例外処理

async、awaitを使う場合、例外はthrowで発生させて、try catch構文で処理できる。

<html>
<head>
  <meta charset="utf-8"/>
  <title>asyncとawait、throw()</title>
</head>
<body>
<button onclick="main()">Run</button>
<div id="output">
</div>
<script>
function print(text) {
  const element = document.getElementById('output');
  element.innerText = element.innerText + text + '\n';
}

// "await"構文は"async"キーワード付きの関数内でしか使えない
async function main() {
  // async functionでthrowされた例外はtry catch構文で処理できる
  try {
    result = await slowFunction();
    print(result);
  } catch (error) {
    print(error);
  }
}

async function slowFunction() {
  // setTimeoutをPromiseでラップし、終わるまでawaitで待つ
  await new Promise(resolve => setTimeout(resolve, 1000));
  // throwで例外を発生させる
  throw new Error("Error in async function!");
  return "Result";
}
</script>
</body>
</html>

asyncとawait、throw() - JSFiddle

参考

AWS IoT エンタープライズボタンを試してみる

AWS IoT エンタープライズボタンは、AmazonのDashボタンの汎用のやつ。

f:id:nullpobug:20180904024122j:plain
IoTボタン

www.amazon.co.jp

ボタンを押した際にEメール送信、SMS送信、Lambda関数の実行などを設定できる。

ボタンの動作にはWifiネットワークが必須となっている。初期セットアップはスマートフォンのアプリから実行すると簡単でした。

バイスの管理画面

管理画面は結構シンプル。バッテリー残量もわかるので便利。

f:id:nullpobug:20180904024124p:plain
IoT 1-Clickの管理画面

開発の流れ

  1. バイスを登録
  2. Lambda関数を用意しておく
  3. AWS IoT 1-Clickプロジェクトを作成
    • 実行したいテンプレートを選択(Lambda関数の実行など)
    • プレイスメントを選択(プレイスメントに対し、デバイスを登録する)

サンプルコードを用意して試す

Lambda関数でSlackに投稿してみるサンプルコードを作成し、試してました。Chaliceを使おうと思ってたんですが、AWS IoT 1-Clickのイベントに対応していないようだったので、素のPythonハンドラで作成。

app.py:

import requests
import json
import os


def post_to_slack(event, context):
    env_dict = os.environ
    url = env_dict['ENDPOINT']
    payload = {
        'text': 'ボタンが押されました。',
    }
    requests.post(url, data=json.dumps(payload))
    return {'status': 'ok'}

ソースコード一式はbitbucketに置いてます。

https://bitbucket.org/tokibito/sample_nullpobug/src/dd6f851e43e4872875e8c3dc57dd217b52017811/python/iot_button/?at=default

zipパッケージを作ってLambdaにアップロードし、環境変数でSlack APIのENDPOINTを設定しておきます。

また、ボタンを押すとこの関数が実行されるように設定しておきます。

ボタンを押すと、Slackにメッセージが投稿されることを確認しました。

f:id:nullpobug:20180904024120p:plain
Slackにメッセージが投稿された

うまく組み合わせれば使い所ありそうかな。

Python3.7+Django2.1のアプリケーションをGoogleAppEngine/Standard環境にデプロイする

GoogleAppEngineのStandard環境でPython3.7のランタイムがベータ版で利用できるようになったので、試していました。

ドキュメントにはFlaskアプリケーションをデプロイする手順が記載されていましたが、Djangoアプリケーションだと少し設定が追加で必要だったので、メモを残しておきます。

ファイル構成

django-adminツールの startproject コマンドでは、 mysite という名前のプロジェクトを作成しました。 manage.py ファイルの階層にapp.yamlを配置しています。

├── app.yaml
├── db.sqlite3
├── manage.py
├── mysite
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── venv

requirements.txtにはDjangoとgunicornを並べています。

AppEngineの設定

app.yamlの最低限の内容はこれぐらい。

runtime: python37
entrypoint: gunicorn -b :$PORT mysite.wsgi:application

skip_files:
- ^venv/.*$

静的ファイルなどがある場合は、従来と同様にhandlersを設定します。entrypointを省略すると、main.pyのapp変数をWSGIアプリケーションとして実行しようとするみたいです。

CloudSQL等を使う場合には追加の設定が必要です。

また、entrypointを記述した場合はgunicornがインストールされないようで、requirements.txtに書いておく必要がありました。

app.yaml Reference  |  App Engine standard environment for Python 3 docs  |  Google Cloud

デプロイコマンド

gcloudコマンドでデプロイします。事前に gcloud auth login コマンドで認証し、 gcloud config set project PROJECT_ID コマンドでGCPのプロジェクトIDを指定しておく必要があります。

gcloud app deploy

Flexible環境と比べるとデプロイに要する時間が1割ぐらい(Flexibleだと10分、Standardだと1分)とかなり差がありました。

気になっていること

従来のPython2.7ランタイムにあった各種GAE向けのサービスで使えないものが多いようです。今後はAppEngine専用のマネージドサービスではなく、GCP上で提供されるサービスを使っていく必要があるのかな。

従来のランタイムとの差異を見るとかなり違うようなので、移行する場合は気をつけたほうがよさそう。

Understanding differences between Python 2 and Python 3 on the App Engine standard environment  |  App Engine standard environment for Python 3 docs  |  Google Cloud

オープンソースカンファレンス2018 HokkaidoでDjangoの紹介をしました

7/7にオープンソースカンファレンス2018 Hokkaidoに行ってきました。

www.ospn.jp

北海道のOSCにはここ数年は参加し続けてますが、セミナーや展示の数も多く、毎年楽しめてます。

今回もdjango-ja名義でDjangoフレームワークの紹介をしました。資料は去年のものをアップデートしたものです。

スライドとサンプルコード

speakerdeck.com

github.com

Python札幌の勉強会

翌日にはPython札幌の勉強会で、Djangoチュートリアルを手伝ってきました。

python-sapporo.connpass.com

質疑応答もしっかりできたのでよかったと思います。

PythonでQRコードのSVGを生成する

PythonQRコードSVGを作るには、qrcodeモジュールを使えば簡単でした。

pypi.org

Python3.6で試しました。

pip install qrcode

SVGPNG(pymaging-png)はPure Pythonのモジュールだけで生成できるようです。

PILでPNGを生成させることもできるようです。

main.py:

import qrcode
import qrcode.image.svg


def make_qrcode_svg(text):
    # 拡大したときに余白が入らないようにする場合はSvgPathImageを使う
    return qrcode.make(text, image_factory=qrcode.image.svg.SvgPathImage)


def main():
    img = make_qrcode_svg('https://example.com/')
    with open('test.svg', 'wb') as output:
        img.save(output)

if __name__ == '__main__':
    main()

f:id:nullpobug:20180626232214p:plain
生成されたSVGChromeで拡大表示した

昨今のWebブラウザはSVGを表示できるので、これで十分かな。

DjangoCongress JP 2018に参加しました

DjangoCongress JP 2018にスタッフとして参加しました。

djangocongress.jp

Djangoフレームワークのみをテーマとした100人以上の規模のイベントは、日本では初めてだったように思います。

半年前から準備してきたイベントですが、無事終了できてよかったです。

会場

サイボウズ株式会社さまが無償で提供してくれました。かなり余裕のある広いスペースで、Wifiも快適でしたので、良かったと思います。ありがとうございました。

サイボウズではエンジニアとつながるイベントを開催しているそうです。

cybozu.connpass.com

開催の準備について

  • 月1回程度、集まって作業、それ以外は各自進められることを進めた
  • やることを極力減らせたので、開催直前の準備も持っていく荷物をチェックする程度ですぐに終わった
  • CfPへの応募が予想以上に多く、選定はすごく悩んだ
    • DjangoCongressでしか発表の場がなさそうな内容をなるべく選んだ
  • Webページを早くから公開できてたのはとても良かったと思う
    • 海外や遠方から来る人が準備できるよう、遅くとも3ヶ月前には確定情報を出せるように気をつけた

講演内容について

  • 全体を通して質が高く、ボリュームもあってすごく良かったと思います
    • 12枠全部聞きたいレベル
    • すべての枠が45分と長めなので、深い内容になっていたのかな

感想

  • やりきった感
  • 会場に入ってから受付開始までの準備が30分、撤収も30分ほどで終わったので、スムーズでとてもよかった
  • スタッフの人数もそこそこいたので、1人の負担が少なくてよかった
  • Twitterハッシュタグが盛り上がっていたのを眺めるのも楽しかった

来年以降は、今年の各種テンプレートを使い回せるので、もっと楽にできるかなと思います。