Python 3 + mod_wsgi + Django

CentOS 6.5, Python 3.6.1, Django 1.11.1

私がPythonについて持っている知識というのはこの程度のことしかない。
にわかというレベルですらないが、このたび、PythonでちょっとしたWebアプリを作ってみる機会があった。

以下は記事タイトルの環境を構築した時のメモである。

Python 3.6をインストールする

私が対象にした環境にはPython 2.6がインストールされており、これはyumが内部的に使用しているらしい。
そこで、2.6は残したまま、3.6をインストールすることにした。

$ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tgz
$ tar zxf Python-3.6.1.tgz
$ cd Python-3.6.1
# 64bit 環境で Python3 と mod_wsgi の組み合わせの場合、-fPIC が必要みたい
$ ./configure CFLAGS=-fPIC --enable-shared --prefix=/usr/local
$ make
$ sudo make install
# Python の .so を読み込むパスを通しておく
$ export LD_LIBRARY_PATH=/usr/local/lib
$ /usr/local/bin/python3.6 --version
Python 3.6.1

virtualenvを使ってPython Webアプリの稼働環境を構築する

virtualenvはRubyのrbenv的なもの。
pipはgemみたいなもんかな?
という認識。

ここでは、/var/www/python/ 以下にPythonのWebアプリをデプロイするつもり。
この環境を venv という仮想環境として、venv内では Python 3.6 を使うこととする。

$ curl -kLO https://bootstrap.pypa.io/get-pip.py
$ sudo LD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/python3.6
$ sudo LD_LIBRARY_PATH=/usr/local/lib /usr/local/bin/pip3.6 install virtualenv

$ sudo mkdir -p /var/www/python
$ sudo chown myuid:mygid /var/www/python
$ cd /var/www/python
$ virtualenv venv --python=/usr/local/bin/python3.6 --no-site-packages
$ source venv/bin/activate
(venv) $ which python
/var/www/python/venv/bin/python

Djangoをインストールする

Djangoってのは、Python WebアプリのフレームワークRoRみたいなもん。
という認識。

# pipでDjangoをインストール
(venv) $ pip install Django

# test_siteというプロジェクトを新規作成
(venv) $ django-admin.py startproject test_site
(venv) $ cd test_site

# 雛形プロジェクトがSQLiteを使うようになっているが、今は使わないのでコメントアウト
(venv) $ vim test_site/settings.py

DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
}

# ひとまず内包のHTTPサーバーを起動してみる(バックグラウンド起動させる)
(venv) $ python manage.py runserver &
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

May 5, 2017 - 00:00:00
Django version 1.11.1, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^M

# ローカルからHTTP経由でアクセスしてみる
$ curl -s http://localhost:8000/ | grep "<h1>"
  <h1>It worked!</h1>

# 動いているみたいなので、シャットダウン
$ fg 1
python manage.py runserver
^C(venv) $ 

mod_wsgiを使ってApache経由でアクセスできるようにする

mod_wsgiはよくあるApacheの拡張モジュール。
ApacheへのリクエストをPythonに処理させるために必要。
ソースから直接コンパイルするより、pipでインストールした方が良いらしい。

(venv) $ pip install mod_wsgi

これでvenv仮想環境内にmod_wsgiがインストールされたはずなので、ファイルパスを検索する。

$ find /var/www/python/venv -name 'mod_*.so'
/var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so

このパスをコピペして、httd.confに設定する。

$ sudo vim /etc/httpd/conf/httpd.conf
LoadModule wsgi_module /var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so

WSGIPythonHome /var/www/python/venv
WSGIPythonPath /var/www/python/venv/lib/python3.6/site-packages
WSGIScriptAlias /test-site /var/www/python/test_site/test_site/wsgi.py
  • WSGIPythonHomeは、Apacheに使わせるPythonのホームディレクトリー
  • WSGIPythonPathは、実行中にPythonに通させるパス
  • WSGIScriptAliasは引数を2つ取り、1つめのURLがアクセスされたら、2つめのwsgiスクリプトに移譲するという設定

と思われる。

さて、これでhttpd.confの設定確認を行ったところ、下記のようなエラーが発生した。

$ apachectl configtest
httpd: Syntax error on line 9999 of /etc/httpd/conf/httpd.conf: Cannot load /var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so into server: /var/www/python/venv/lib/python3.6/site-packages/mod_wsgi/server/mod_wsgi-xxx.so: undefined symbol: forkpty

どうも、環境によってなのか、Python3でmod_wsgiを使うとこのようなエラーが発生するそうな。
事前にApacheにとあるライブラリーをロードさせておくと良いらしい。

$ sudo vim /etc/sysconfig/httpd
# 以下2行を追加
export LD_PRELOAD=/usr/lib/libutil.so  # これはいらない場合もある
export LD_LIBRARY_PATH=/usr/local/lib  # Python を -fPIC でコンパイルした場合は .so が読み込まれるようにしておく(設定箇所はここじゃなくても良い)

$ sudo apachectl restart

この libutil.so ってやつは、私の環境には既に入っていた。

Apache経由でDjangoにアクセスしてみる

さて、お楽しみの時間だ。
みんな大好きコーディング。

まず、プロジェクト初期化時に自動生成されたsettings.py内のALLOWED_HOSTSという設定項目に、開発中(デバッグモード時)にどこからアクセスされるのか、という設定を行う。

$ vim /var/www/python/test_site/test_site/settings.py

ALLOWED_HOSTS = ['127.0.0.1', 'localhost']

ALLOWD_HOSTSの設定はおそらく、実際にアクセスされた時のHTTPヘッダーのホスト名と比較されるものと思われる。
上記設定ではローカルホストからしかアクセスできないので、実際には、クライアントからアクセスされる時のサーバーのホスト名を設定してやらなければならないだろう。

次に、自動生成されたwsgi.pyをカスタマイズする。
と言っても、プロジェクトのパスを追加するだけである。

$ vim /var/www/python/test_site/test_site/wsgi.py

import os
import sys  # 追加

from django.core.wsgi import get_wsgi_application

sys.path.append('/var/www/python/test_site')             # 追加
sys.path.append('/var/www/python/test_site/test_site')   # 追加

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_site.settings")

application = get_wsgi_application()

テスト用にview.pyという名のコントローラーを作ってみる。
ここでは、動作確認用に、単にHelloテキストを返すだけとする。

$ touch /var/www/python/test_site/test_site/view.py
$ vim /var/www/python/test_site/test_site/view.py

from django.http import HttpResponse

def hello(request):
	return HttpResponse('Hello Django\n', content_type='text/plain')

最後に、urls.py(こちらも自動生成)をカスタマイズして、URLマッピングを行う。
urlpatternsには、urlレコードを配列で登録する。
urlは引数を3つ取る。
1つ目は、アクセスされるURLだが、ここでは正規表現 ^$ として、トップページがパスなしでアクセスされた時を指定している。
2つ目は、そのURLがアクセスされた時に実行される関数を指定する。ここでは、カレントディレクトリーからimportしたview.pyの中のhello関数を指定している。

$ vim /var/www/python/test_site/test_site/urls.py

from django.conf.urls import url
from django.contrib import admin
from . import view  # 追加

urlpatterns = [
	url(r'^$', view.hello, name='hello')  # 元々あった設定を削除して追加
]

動作確認。

$ curl localhost/test-site/
Hello Django

外部キーとして参照されている列にauto_incrementをつけ忘れた場合

MariaDB 10.1

備忘録、メモ

構築済みのテーブルの主キー列にAUTO_INCREMENTをつけ忘れていたなどの理由により、後からAUTO_INCREMENTを付与したいとする。
その列が他のテーブルから外部キー制約で参照されていると、単純にALTER TABLEしようとしても以下のようなエラーが発生する。

ERROR 1833 (HY000): Cannot change column 'person_id': used in a foreign key constraint 'child1_fk1' of table 'mydb.child1'


【解決策】

  • 関連するテーブルをすべてWRITEロックする
  • 子テーブルの外部キー制約を一時的に削除する
  • ALTER TABLEで親テーブルの列にAUTO_INCREMENTをつける
  • 外部キー制約を戻す(子テーブルにもう一度外部キー制約をつける)
  • テーブルをアンロックする
lock tables 
	parent write,
	child1 write,
	child2 write,
	child3 write;

alter table child1 drop foreign key child1_fk1;
alter table child2 drop foreign key child2_fk1;
alter table child3 drop foreign key child3_fk1;

alter table parent modify person_id int auto_increment;

alter table child1 add foreign key(child1_fk1) references parent(person_id);
alter table child2 add foreign key(child2_fk1) references parent(person_id);
alter table child3 add foreign key(child3_fk1) references parent(person_id);

unlock tables;


【参考】
https://stackoverflow.com/questions/13606469/cannot-change-column-used-in-a-foreign-key-constraint

1人の開発者としてどんなアプリを作りたいのか

今の人はあまり知らないかもしれないけれども、私の青春時代に聴いていた音楽というのは、小室哲哉の作る曲とかユニコーンとかミスチルとか、その辺であった。

私は小室哲哉の作る曲が特別良いとは思っていないが、彼自身のことは少しだけ尊敬している。どれくらいかというと、マークザッカーバーグジョブズよりは尊敬している。RMSやリーナスほどではない。彼の作る曲というのは、なんとなく特徴があった。言葉ではうまく言い表せないけれども、曲自体や歌手が別の人でも、聴いただけで、ああ、これは小室の作った曲だな、とわかる気がしていた。

さて、プログラマーとしての私にそんなものはあるだろうか。

今は多分まだない。これから目指していく、そうなれたら良いなっていう、いわば願いのようなものだ。この曲はきっと小室が作ったんだろうな、ってのと同じように、ああ、このアプリはきっとピザ野郎が作ったんだなって、そう思ってもらえるようになりたいんだ私は。

そんで、それがどういうものかっていうのを忘れないようにここに書こうと思ったわけ。

目指す方向性というか、こういうところでオリジナリティを出したいってのは、あるにはある。

でもここまで書いてみて思ったのだ。

わざわざこんなパブリックなインターネッツに書かなくてもそんな大事なこと忘れるわけがないし、そもそもその私らしさをここで明かしてしまっては、その素晴らしい作風を誰かに模倣されて午前4時みた

そう

とにかく内緒にすることにしたのだ。

いいか、願い事ってやつは誰かに教えた時点で叶わないんだよ。

悟りを得るのか

自分の本性や深層心理というものを知るに至る方法は1つではない。

神の詩―バガヴァッド・ギーターより
http://1hiro.net/se-me-13.php

(25)
或る者は瞑想禅定によってそれを覚り、
或る者は理論哲学を学んでそれを知り、
或る者は名利を求めぬ仕事をしてそれを通して至上我を見る。

土星人の私には2つ目か3つ目が適しているように思うのだが、どうだろう。
私は積み重ねてこなかった。
多分もうそれほど時間がない。最後の12ハウスに入るのは近いという予感がするが、本当の自分を知ることはできるだろうか。

運転免許を免許と呼ぶのをやめて欲しい

身分証明書 = 運転免許という世の中の風潮が大嫌いだよ。せめて右辺と左辺を逆にして欲しい。いやそれじゃダメだ。演算子を部分集合にしなければ正しくならない。

さて、車が好きな人には申し訳ないが、私は車社会というものが嫌いだ。車そのものや車に乗っている人が嫌いなわけではないので、怒らないで欲しい。これからも仲良くしていこうじゃないか。私は道を歩いている時に車がこちらに曲がってこようとしていたら、先に通してあげたいと常々思っている。車の方が先に速く進むんだから、その方が道の状態を正しく保てる。合理的だ。

しかし、身分証明書として運転免許証が使われているのはどう考えてもおかしい。人の身分を証明するために、パスポートはともかく、運転免許証が当たり前のように利用されているばかりか、身分証明書としてのナンバーワンの地位を確立しているのが運転免許証であるというこの現実。そして単に「免許」と言えば、それは運転免許のことを指す。世の中にはごまんと免許なんてあるのに。

パスポートは海外旅行に行くために取得するもの。運転免許証は車を運転するために取得するもの。これらがないと身分を証明できないという世の中は狂っていたのだ。それ以前の日本社会というのは、車に乗らない、海外にも行かない、という人の人権を実質無視してきた。

ここ数年でようやく身分証明書として正しいものが交付された。ご存知住民基本台帳カードマイナンバーカードである。

ところがどっこい、今度はマイナンバーを他人に知られるとまずいから、マイナンバーカードを身分証明書として使うのは危険だというおかしな風潮が流れ始めた。全くもって意味がわからない。それが危険なことなら、そういう社会の方を変えていかなければならないと思う。

自動生成されたDDLをsedで調整

macOS 10.12 Sierra, MariaDB 10.1, ERMaster 1.0.0.v20150619-0219

ER図を手軽に書くにはEclipseプラグインのERMasterがとっても便利。
ここ数年、愛用している。

ところで、MariaDBのテーブル定義というのは、他の一般的なDBMSと比べてかなり特殊である。特に私がいつも気にすることが2つある。1つはVARCHAR列がデフォルトでignore case(大文字小文字区別無し)なのと、もう1つはTIMESTAMP列が2つ以上ある時に1つの列にしかDEFAULT CURRENT_TIMESTAMPを指定できないことだ。

私の場合は、varchar列は全てBINARYにしてしまう。それが良いかはさておき、sedで変換するには例えば以下のようにする。

sed -E '/BINARY/!s/(varchar\([0-9]+\))/\1 BINARY/g' create_table.sql

もう1つのTIMESTAMP問題については、たとえば、以下のような要件を仮定する。

  • 全テーブルに生成日時と更新日時を持たせる
  • 生成日時を create_ts とする
  • 更新日時を update_ts とする
  • create_ts は default current_timestamp にしたい
  • update_ts は default current_timestamp にして、かつ、on update current_timestamp にしたい

これはMariaDBの仕様上できないので、create_ts の方は default 0 にする。そして、update_ts は default current_timestamp にしておく。

ERMasterでDDLを自動生成すると、

CREATE TABLE some_table
(
    -- 
    create_ts timestamp DEFAULT 0 NOT NULL,
    update_ts timestamp DEFAULT current_timestamp NOT NULL
);

こうなるわけだ。

create_ts については、INSERT 時に NULL を指定することによって、現在日時を登録するようにする。((ここで、insert into some_table(create_ts) values(current_timestamp); としてしまうと、timestamp(3) のようにミリ秒まで保持させている場合に、create_ts と update_ts の値が微妙にずれる))

insert into some_table(create_ts) values(null);

update_ts の ON UPDATEについては、sed でつけ足してしまおう。

sed -E '/ON UPDATE/!s/(update_ts timestamp DEFAULT current_timestamp NOT NULL)/\1 ON UPDATE current_timestamp/g' create_table.sql

んー。

あんまりスマートじゃない気がするけども、まあ、1個ずつ手で直すよりは遥かにマシだろうて。

#!/usr/bin/env bash
# Mac OS X (using BSD sed)
# Script to adjust DDL made by ERMaster for MariaDB
sed -i .bak1 -E '/ON UPDATE/!s/(update_ts timestamp DEFAULT current_timestamp NOT NULL)/\1 ON UPDATE current_timestamp/g' create_table.sql
sed -i .bak2 -E '/BINARY/!s/(varchar\([0-9]+\))/\1 BINARY/g' create_table.sql
rm create_table.sql.bak1
rm create_table.sql.bak2

ポイント1. BSD sed では ignore case できない

なので、s/target/replace/gi と書きたくても書けない。

ポイント2. コマンドの引用符はダブルクォートでなくシングルクォートで

じゃないと、!s の ! が、BASHコマンドとして機能してしまってうまく動かない。

ポイント3. -i の後ろにバックアップファイルのサフィックスをつける

単純に上書きしたいだけなのに、バックアップが作られるので、後で消す。

ポイント4. -E をつければ正規表現エスケープは基本いらない

\( とか \+ とかは文字そのものを表す。
-e の時と逆。



ちょっとしたことだけれども、なんだかハマり所がたくさんあったなぁ(コナミ