nyanpyou Note

主な目的は調べたり作ったりしたプログラミング備忘録(予定)

衝動的にLightsailとDjangoでWebサイトを作る その3

前回からの続き。

nyanpyou.hatenablog.com

構成(再掲)

MySQLに外部からアクセスする

デフォルトの設定ではローカルホストからの接続しか許可されていないため、忘れずにmysqls.cofの記述を変更する必要がある。

qiita.com

/etc/mysql/my.cnfを見ると、/etc/mysql/conf.d/と/etc/mysql/mysql.conf.d/配下の.cnfファイルを、追加の設定ファイルとして読み込みますよと書かれているので、どうやら設定ファイルはここにまとめておいておけばいいらしい。

MySQLのパスワードポリシーを変更する

初期設定ではパスワードのレベルがMEDIUMに設定されている。MEDIUMの場合、パスワードは以下の条件で設定する必要がある。

テストで触りたい場合には少々不便なため、パスワードのレベルを下げてLOWにする手もあり。
前述の設定ファイルのどれかに、

[mysqld]
validate_password.policy=LOW

の記述を追加すればOK。 新しく専用の.cnfファイルに分けておくとわかりやすい。

Django事始め

Djangoの使い方は公式に丁寧なチュートリアルが用意されていたので、それを参考にしながら進める形にした。

docs.djangoproject.com

基本的な流れを掴むには丁度良かったのでお勧め。

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に変えておく必要あり。

qiita.com

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が被ることがありうるため、名前空間の設定をしておく必要あり。

docs.djangoproject.com

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)
...

続く

本日はここまで。