衝動的にLightsailとDjangoでWebサイトを作る その3
前回からの続き。
構成(再掲)
- レンタルサーバー:AWS Lightsail
- OS:Ubuntu 20.04.2
- データベース:MySQL 8.0.25
- サーバーソフト:Apache 2.4.41
- 言語:Python 3.8.5
- フレームワーク:Django 3.2.2
- ドメイン取得:AWS Route 53
- 構成(再掲)
- MySQLに外部からアクセスする
- MySQLのパスワードポリシーを変更する
- Django事始め
- migrateでエラーが出た時
- 既存のデータベースをDjangoで利用する
- Djangoでhtmlのformタグを使う時の覚書
- templateに特に渡したい値がない時
- ちょっと複雑なクエリを実行したい時
- 続く
MySQLに外部からアクセスする
デフォルトの設定ではローカルホストからの接続しか許可されていないため、忘れずにmysqls.cofの記述を変更する必要がある。
/etc/mysql/my.cnfを見ると、/etc/mysql/conf.d/と/etc/mysql/mysql.conf.d/配下の.cnfファイルを、追加の設定ファイルとして読み込みますよと書かれているので、どうやら設定ファイルはここにまとめておいておけばいいらしい。
MySQLのパスワードポリシーを変更する
初期設定ではパスワードのレベルがMEDIUMに設定されている。MEDIUMの場合、パスワードは以下の条件で設定する必要がある。
- LOW ポリシーは、パスワードの長さのみテストします。 パスワードは少なくとも 8 文字の長さでなければなりません。
- MEDIUM ポリシーでは、パスワードに少なくとも 1 つの数字、1 つの小文字、1 つの大文字および 1 つの特殊文字 (英数字以外) を含める必要があるという条件が追加されます。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 6.4.3 パスワード検証コンポーネント
テストで触りたい場合には少々不便なため、パスワードのレベルを下げてLOWにする手もあり。
前述の設定ファイルのどれかに、
[mysqld] validate_password.policy=LOW
の記述を追加すればOK。 新しく専用の.cnfファイルに分けておくとわかりやすい。
Django事始め
Djangoの使い方は公式に丁寧なチュートリアルが用意されていたので、それを参考にしながら進める形にした。
基本的な流れを掴むには丁度良かったのでお勧め。
migrateでエラーが出た時
python manage.py migrate
を実行した時に、
django.db.utils.OperationalError: (1050, "Table 'django_content_type' already exists")
と表示されたら、対象のデータベースに既にdjango_content_typeという名前のテーブルが作成されてしまっているので、サーバーからMySQLに接続し、drop tableコマンドで削除してからもう一回実行すると良い。
既存のデータベースをDjangoで利用する
Djangoにはmodels.pyの記載に従ってデータベースのテーブルを作成してくれる機能があるが、既に作っておいたテーブルを使いたい場合もある。集めておいたデータを使ってどうこうしたいとか。
その場合は、ありがたいことにmodels.pyを手打ちする必要がない。
settings.pyで接続するデータベースの設定(設定 | Django ドキュメント | Django)を済ませた後、
python manage.py inspectdb
を実行すると、自動的にmodels.pyに書くべき内容をDjangoが生成して表示してくれる。
これをコピペしておけばOK。
docs.djangoproject.com qiita.com
後からDjango側からデータを追加したりしたい場合は、
class Meta: managed = True
に変更しておく。
Djangoにテーブルを作らせた場合は、models.pyで指定せずとも勝手にidというint型の項目を作るらしい。
既に作成済みのテーブルに、idのような通し番号を示す項目が存在しているのなら、models.pyで該当項目の引数にprimary_key=Trueを追加すると、idの代わりに使ってくれる。
そしてこのprimary_keyの設定をしていないと、DjangoからMySQLのデータを取得しようとして例えば、
from testapp.models import Test Test.objects.all()
を実行した際に
django.db.utils.OperationalError: (1054, "Unknown column 'test.id' in 'field list'")
のエラーが出る。
加えてnull=Trueになっているとそれもまたエラーになるので、Falseに変えておく必要あり。
Djangoでhtmlのformタグを使う時の覚書
formタグのaction属性とmethod属性に
action="{% url 'test_get'%}" method="get"
と指定すると、urls.pyに記載したURLのうち、name="test_get"のものに対してGETを行うフォームが作れる。
例えば、http://example.com/testに、
<form action="{% url 'test_get'%}" method="get"> <label>性別</label> <select class="form-select" name="gender"> <option value="M">male</option> <option value="F">female</option> </select> <label>所属</label> <select class="form-select" name="belong"> <option value="EFF">EFF</option> <option value="ZEON">ZEON</option> </select> <button type="submit">GET</button> </form>
上記のようなaction属性とGETのmethod属性を持つformを設置し、
urls.pyで"test_get/"をname="test_get"と設定していたとする。
このようなformを送信すると、
http://example.com/test_get/?gender=male&belong=ZEON
にアクセスすることになる。
従ってtest_get/に対するviewをviews.pyで用意しておけば、formから送信された内容を反映したhtmlを返すことが出来る。
test_get/の後ろにGETの内容がくっついているため、urls.pyで"test_get/"に対するルーティングを設定するだけではエラーが出そうに感じたが、?以下はGETの内容だと勝手に判断されるようで、問題はなかった。
プロジェクトが複数のアプリを含む場合は、URLのnameが被ることがありうるため、名前空間の設定をしておく必要あり。
templateに特に渡したい値がない時
GETやPOSTで取得した値によってHTMLの内容を変化させたい場合は、まずviews.pyに、
def test_get(request): template = loader.get_template("testapp/test_get.html") context = { "gender" : M, "belong" : ZEON, } return HttpResponse(template.render(context, request))
のような関数を作ってurls.pyでURLと結びつける。
その後テンプレート側で{{ gender }}のように記載すると、cotextの中身にアクセスできるようになり、晴れてGETの値を反映した簡易的な動的ページが完成する。
逆にいつも同じHTMLを返す静的ページを作りたい場合は、contextとしてNoneを渡しておけばOK。
テンプレート | Django ドキュメント | Django
ちょっと複雑なクエリを実行したい時
Djangoからデータベースのデータにアクセスしたい時は、例えば
Test.objects.filter(name="クワトロ")
とすると、QuerySetオブジェクトが取得できる。このQuerySetオブジェクトからはfor文で個別のデータに分離することができる。
引数を増やしてfilterを実行することも可能だが、残念ながらfilterの引数は、ANDによる結合しかできない。
#filter | QuerySet API reference | Django documentation | Django
OR検索や、より複雑な条件での検索がしたい場合は、Qオブジェクトを使うと実現できる。
#Q() objects | QuerySet API reference | Django documentation | Django
#Complex lookups with Q objects | Making queries | Django documentation | Django
Qオブジェクトを使うと検索条件を使いまわしたり、特定の場合に検索条件を減らしたり、複数の検索条件をANDとORで組み合わせたりとクエリ発行の自由度が上がる。
#例:GETでcountryとgenderの値を受け取る。 #country="all"の時はcountryを検索条件から外す。 #views.py ... get_country = response.GET["country"] get_gender = response.GET["gender"] q_counry = Q(country=get_country) q_gender = Q(country=get_gender) if get_country=="all": q_counry = Q() result_queryset = Test.objects.filter(q_country & q_gender) ...
続く
本日はここまで。