1年くらい前にDjangoで少しアプリ書いたのですが忘れてしまったので備忘録的に残しながら思い出していきます。日本語訳のチュートリアルが1.4とかなり古いリリースのものしか無いため公式ドキュメントの最新のチュートリアルを見比べてましたが、こちらの記事が大変参考になったと記憶しています。
すでに1.8が出てますが、前回1.7でやったので1.7でやってきます。
この記事での環境
[sayamada@~]$cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=12.04
DISTRIB_CODENAME=precise
DISTRIB_DESCRIPTION="Ubuntu 12.04.5 LTS"
[sayamada@~]$uname -a
Linux XXXXXX 3.13.0-65-generic #106~precise1-Ubuntu SMP Fri Oct 2 22:07:14 UTC 2015 i686 i686 i386 GNU/Linux
[sayamada@~]$python -V
Python 2.7.9 :: Anaconda 2.0.1 (32-bit)
[sayamada@~]$pip list |grep -i django
Django (1.7.1)
django-bootstrap-form (3.1)
社用PCではrootがとれないので、Anacondaでお茶を濁してます。普通の方は
$ pip install django==1.7.1
でいいんじゃないでしょうか。
プロジェクトつくる
WEBサイト1個が1プロジェクトに相当するんですかね。とりあえずまずプロジェクト作りました。
[sayamada@git]$django-admin.py startproject demoPrj
[sayamada@git]$tree demoPrj/
demoPrj/
├── demoPrj
│ ├── __init__.py
│ ├── settings.py --設定とか書く奴
│ ├── urls.py -- 一番上のルーティング書く奴
│ └── wsgi.py
└── manage.py -- 管理コマンド用の実行スクリプト
バックエンドDBの変更
デフォルトでsqliteで動くみたいですが、職業柄PostgreSQLが好きなので変更します。pyscopg2いれないといけなかった気がします。PostgreSQLが好きじゃない人はそのままsqliteでいいと思います。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'DB名',
'USER': 'ユーザー名',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '5432'
}
}
ロケールの変更
日本人なので変えておきます。
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'
アプリケーションの作成
サイト(プロジェクト)は複数のアプリケーションで構成される、みたいです。機能ごとにアプリをつくって追加する感じ?なんですかね。
[sayamada@git]$cd demoPrj/
[sayamada@demoPrj]$python manage.py startapp demoApp
[sayamada@demoPrj]$tree
.
├── demoApp --このディレクトリが増えた
│ ├── __init__.py
│ ├── admin.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py -- モデル定義するやつ
│ ├── tests.py
│ └── views.py -- VIEWというかロジックのエントリポイント書く奴?
├── demoPrj
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── settings.py
│ ├── settings.pyc
│ ├── urls.py
│ └── wsgi.py
└── manage.py
3 directories, 13 files
ちなみに、デフォルトでは作られませんが大きなルーティングをdemoPrj/urls.pyに定義してアプリケーションごとのルーティングは個別に設定するものらしいので、demoApp/urls.pyを手動で作っておきます。
ユーザ認証
djangoは事前にアカウント管理のためのモデル(User)を定義しています。また認証用のミドルウェアも用意されています。今回は認証処理をまとめてAuthというアプリケーションでつくておきましたので、INSTALLED_APPSにも追加します。
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth', ★これと
'django.contrib.contenttypes', ★これが必要
:
'Authctl', ★これはstartappで作った
:
LOGIN_URL = '/auth/login'
LOGIN_REDIRECT_URL = '/auth/home'
:
AUTHENTICATION_BACKENDS = (
'Authctl.AuthLogic.AuthBackEnd',
'django.contrib.auth.backends.ModelBackend',
)
プロジェクト作った時点で組み込まれています。あとはAUTHENTICATION_BACKENDSに認証用のロジックを指定します。上は自分で作った認証ロジックです。なんで2個いるのかは忘れました。認証が必要とするページのview側は以下にします。今回はAuth配下AuthLogic.pyを作り、その中でAuthBackendというファンクションを定義することにしました。
# coding:utf-8
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
@login_required
def index(request):
return HttpResponse(u'インデックス')
login_requiredデコレータをつけたメソッドは事前にログイン済みかのチェックが行われます。 ログインしていない場合はsettings.pyのLOGIN_URLにリダイレクトされます。今回は/authctl/loginとしているので、それを解決できるようにルーティングを設定する必要があります。
forms.ModelFormでexcludeしたフィールドがある場合
form.save時に大体そこがNOT NULL制約でエラーになる。formのコンストラクタにinstance=でexcludeした分の属性をもたせたModelのインスタンスを渡せばいける
python: views.py
any_model_inst = AnyModel()
any_model_inst.excluded_any_ked = u"hoge"
form = AnyForm(request.POST, instance=any_model_inst)
form.save()
デコレータでリクエストオブジェクトを見る
requestオブジェクトどこだっけ、てなやんだ。
# アクセス許可があるAPIアクセスかのチェック用のデコレータ
def check_access_permission(func):
import functools
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 第一引数はrequestオブジェクト
request = args[0]
print request.user
if check_any_func():
return HttpResponseForbidden()
# OK
return func(*args, **kwargs)
return wrapper
Authorizationヘッダーを見たかった
こういうのを投げた時のヘッダを見たかった
curl localhost:8000/api/ \
-H "Authorization: demo token" \
-X GET
こうだった
def check_access_permission(func):
import functools
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 第一引数はrequestオブジェクト
request = args[0]
# これでとれる
print request.META.get('HTTP_AUTHORIZATION')
if False:
return HttpResponseForbidden()
return func(*args, **kwargs)
return wrapper
FormでENUMみたいなことしたかった
RESTで指定される値を絞りたい時にValidationのためだけにFormつくってChoiceFileldが丁度良かった
contact_choice = (
("TEL", "TEL"),
("Email", "Email"),
("FAX", "FAX"),
("Web", "Web"),
)
contact_way = forms.ChoiceField(label=u"問合せ方法", choices=contact_choice,required=False, initial=None)
これで、contact_wayにhogeとか入ってたらis_valid()で死ぬのでいい
Formでなんでvalid失敗したのか知りたい
マニュアルにちゃんと書いてありました。1.7以降はいろいろ表示させ方もあるみたいです。
if form.is_valid():
something doing..
else:
# FromClass.errorsにエラーが入っている
# as_json()でJSON形式で取り出せる
print form.errors.as_json()
Formのinitialがcleaned_dataでは取得されない
sender_name = forms.CharField(label=u'送信者名', required=False, initial=u"default sender")
で、こうすると
sender_name = form.cleaned_data["sender_name"] # -> I expect "default sender" but None
になってしまいました。
ここを見る感じ、initialは表示上の初期データであり、cleaned_dataでは取得できない模様。form.cleanをオーバーライドする必要があるみたいです。ちょっと動かない箇所があったので最終的には以下のように代えました。
class DisplaySharerForm(forms.Form):
...
def clean(self):
cleaned_data = super(DisplaySharerForm, self).clean()
for key, value in cleaned_data.items():
if not value:
cleaned_data[key] = self.fields[key].initial
return cleaned_data