どこにでもいるSEの備忘録

たぶん動くと思うからリリースしようぜ

Djangoについて勉強したメモ

f:id:nogawanogawa:20201229165348p:plain

年末年始なんで、2020年にやり残した事を勉強していこうと思います。 今日は、Djangoについて勉強していこうと思います。

事情としては、最近になってDjangoのコードを触ることがあって、その際にどうやって書くのが正解なのか、思想的なものがあんまりよくわからなくなってしまったので、初心に戻ってやり直そうというくらいの気持ちです。

今回参考にさせていただいた書籍はこちらです。

Python Django 3超入門

Python Django 3超入門

超入門と書いてあるだけあって、初学者にも非常にわかりやすい表現となっていました。 Django・Python初心者にとっては良い書籍だと感じました。

今回は、こちらの書籍を参考に色々弄ってみたので、今回はそのメモです。

Django is 何?

docs.djangoproject.com

Djangoは、Pythonで実装されたWebアプリケーションフレームワーク。MVCデザインパターンに緩やかに従う。もともとはアメリカ合衆国カンザス州ローレンスにあるWorld Companyのために、ニュース系のサイトを管理する目的で開発され、2005年7月にBSD Licenseで公式にリリースされた。 Django - Wikipedia

要するに、フルスタックなwebフレームワークってことです。 Ruby on RailsとかLaravelとかと似たようなもののPython版って認識くらいで大丈夫かと思います。(専門の人に怒られそうだけど、ざっくりとはこんなもんかと)

下準備: 動かす環境を作る

まずは色々いじるための環境を準備します。

やりたいこと

  • Docker
    • Dockerを使って、その中にDjangoのサーバーを立てていきたいと思います。
  • Poetry
    • せっかくなので、この前使用したPoetryでパッケージ管理してみたいと思います。

下準備の成果

下準備についてはすべてすっ飛ばして、最終的にこんな感じになりましたってのだけおいておきます。 まあ、適当にcommit log追っかければ何やってるかは思い出せるので…

github.com

注意点として、

  1. Dockerコンテナ外からアクセスできなかった
    • 外部アクセスできるようにしないといけない(参考)
  2. secret keyがsettings.pyに含まれてしまう
    • local_settings.pyをgitignoreして、それを読み込んで使用するように変更する(参考)

らへんでチョット引っかかりましたので、備忘録として残しておきます。

一応、この段階で動かしてみるとこんな感じです。

f:id:nogawanogawa:20201229135800p:plain

とりあえず動いてそうなので、インストールはうまくいってそうです。

Djangoを使ってみる

さて、ここから本格的にDjangoを使っていきます。

MVCアーキテクチャ

DjangoはMVCアーキテクチャの思想に基づいて設計されています。 ざっくり言えば、

  • Model
    • データアクセス関係の処理を行う
  • View
    • ユーザーに見える部分に関する処理を行う
  • Controller
    • ModelとViewの間の制御を行う

といったように、機能を論理的に分割する設計思想です。 この辺はメジャーなWebサービスの設計思想で、色んな所で語られている内容ですね。。 ざっと探した限り、下記の記事なんかはくわしく書いてありそうでした。

digitalidentity.co.jp

一点注意点としては、Djangoでコードを記述する上で登場するviews.pyというファイルがありますが、それとMVCアーキテクチャのViewが混同されるケースが多いようです。 Djangoにおいて、本来の意味で画面表示を制御するのはTemplateであり、views.pyはControllerに当たるという点は認識しておく必要があります。 Djangoでは、MVCというよりMTV(Model Template View)アーキテクチャと呼んだ方が実態に即すような表現になっています。 思想としてはMVCアーキテクチャなので、呼び名が混同しないようにだけは注意する必要がありそうです。

docs.djangoproject.com

プロジェクトとアプリケーション

DjangoでWebサービスを作る上で、プロジェクトという概念とアプリケーションと言う概念は別々のものになっています。 「プロジェクトは1つ以上の(Django)アプリケーションを組み合わせて動作する」というように考えられているようです。 ドキュメントにも下記のような記載があります。

プロジェクトとアプリの違いは何でしょうか? アプリとは、ウェブログシステム、公的記録のデータベース、小規模な投票アプリなど、何かを行う Web アプリケーションです。プロジェクトは、特定のウェブサイトの構成とアプリのコレクションです。プロジェクトには複数のアプリを含めることができます。 アプリは複数のプロジェクトに存在できます。 はじめての Django アプリ作成、その 1 | Django ドキュメント | Django

プロジェクト・アプリケーションはコマンドで作成することができます。

プロジェクトは

$ django-admin startproject <project_name>

のようにすると自動でディレクトリ・ファイルが生成されます。 mysiteプロジェクトは下記のようなファイルが生成されます。

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

アプリケーションは

$ python manage.py startapp <app_name>

のようにすると自動でディレクトリ・ファイルが生成されます。 チュートリアルを確認すると、pollアプリケーションは下記のようなファイルが作成されます。

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

urls.pyとviews.py

ブラウザからのアクセスされる際、特定のurlへアクセスされたときにどの関数へさばくかを記述するのがurls.pyです。

初期状態ではこのようになっています。(コメントは省略)

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

このurlpatternsの中身によってアクセスされた際に振り分けられる関数を設定します。

urls.pyは何もしないとプロジェクトの中で管理するようになっていますが、アプリケーションごとに管理し、それらを大本のurls.pyで読み込むということも可能です。 詳細はgithubにサンプルプロジェクトがおいてあるのでそちらで確認してください。

urls.pyで振り分けられた関数の実体が記述されるのがviews.pyです。

例えばこんな感じに記述します。

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def index(request):
    return HttpResponse("Hello Django!!")

この関数をurls.pyで指定することでアクセス時にこの関数が実行されるようになります。

クエリパラメータ

クエリパラメータを取得したいときはviews.pyを調整する必要があります。

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def index(request):
    if 'test' in request.GET:
        test_query = request.GET['test']
        result = "Query is " + test_query
    else:
        result = "No query parameter"
    return HttpResponse(result)

詳細はこの辺参照。

urlパターンの制御

場合によってはurl自体をパラメータにしたいケースもあります。 そんなurls.pyをこんな感じで記述します。

urlpatterns = [
    path('<param1>/<param2>/', views.index, name='index'),
]

views.pyも変更します。

def index(request, param1, param2):
    result = 'param1:' + param1 + ', param2: ' + param2 + '.' 
    return HttpResponse(result)

このように記述することで、urlの中身をパラメータにすることができたりします。

Template

urls.pyとviews.pyでは、Controllerに当たる物であり、実際の画面表示の定義の大部分はTemplateに記述し、その一部を穴あき状態にしておき、その穴をviews.pyで埋めていくのが主な使い方になります。

templateとは、基本的にはhtmlの形式をしており、一部django特有の記述が埋め込まれたような記述が行われます。 例えばこんな感じです。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>{{title}}</title>
    </head>
    <body>
        <h1>{{title}}</h1>
        <p>{{msg}}</p>
    </body>
</html>

templateはアプリケーションごとにtemplate/<app名>ディレクトリを作成し、その中に配置していきます。

template内にパラメータを埋め込むときはhtml内に{{パラメータ名}}として記述していきます。 また、views.py側でdict形式で対応するパラメータの値を用意し、それをrender関数の引数にセットしていきます。

def index(request):
    params = {
        'title': 'TITLE',
        'msg': 'パラメータを埋め込んだページです'
    }
    return render(request, 'hello/index.html', params)

テンプレートタグ

先に示したような{{}}のようなタグの他に、テンプレートタグと呼ばれる特殊なタグも存在します。

{% %}のような形式で記述し、それぞれに用途が異なるようです。

テンプレートタグについては下記から探してみると良さそうです。

docs.djangoproject.com

staticファイルを使用する

多くの場合、画面には何かしらのcssやjavascriptが付与されています。 そういったcssやjavascriptのような静的ファイルはstaticディレクトリ内に配置していきます。

試しにbootstrapを導入してみます。

こちらから必要なファイルをダウンロードして配置しました。

getbootstrap.com

配置は、こんな感じでstaticディレクトリを作成してその中にcss・jsディレクトリを作成、その中にファイルを配置します。

.
│   ├── <application>
----- 省略 -----
│   │   ├── static
│   │   │   └── <application>
│   │   │       ├── css
│   │   │       │   ├── bootstrap-grid.css
│   │   │       │   ├── bootstrap-grid.css.map
│   │   │       │   ├── bootstrap-grid.min.css
----- 省略 -----
│   │   │       └── js
│   │   │           ├── bootstrap.bundle.js
│   │   │           ├── bootstrap.bundle.js.map
│   │   │           ├── bootstrap.bundle.min.js
----- 省略 -----

これでbootstrapのきれいなオブジェクトが使用できるようになりました。(多分)

POST

POSTメソッドを使用するときは、

 <form action="{% url 'form' %}" method="post">
{% csrf_token%}

----- 省略 -----

</form>

みたいな感じで、htmlにformを作成します。 {% 'form' %}が受け取るときのurls.py内での名前になっているので、そこと調整する必要があります。

views.py側では、request.POST['key']でform内のパラメータを取得することができます。

Formクラス

HTML側にFormをすべて記述していくのも一つの方法ですが、これは"Djangoっぽく"はありません。 よりDjangoっぽく書くのであれば、Formクラスを使用する方法があります。

こんな感じで画面に表示したいFormの要素をPython側で記述していきます。

from django import forms

class UserForm(forms.Form):
    name = forms.CharField(label="name")
    mail = forms.CharField(label="mail")
    age = forms.IntegerField(label="age")

これをhtml側で呼び出してあげると、勝手にそれに応じたFormを生成してくれます。 このとき、生成されるDOMにbootstrapを適用させる際は下記のようなものを使用するとできるようです。

poetry add django-widget-tweaks

これをsettings で使用できるように読み込んであげます。

----- 省略 -----

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello',
    'widget_tweaks', # <- ココ
]
----- 省略 -----

html側も書き換えてあげます。

----- 省略 -----

                {% load widget_tweaks %}
                <div class="row">
                    <div class="col">
                        {{form.name|add_class:"form-control"}}
                    </div>
                    <div class="col">
                    {{form.mail|add_class:"form-control"}}
                    </div>   
                    <div class="col">
                    {{form.age|add_class:"form-control"}}
                    </div>
                </div>

----- 省略 -----

こうするとBootstrapが適用された状態で表示する事ができました。

python.keicode.com

Model

Modelは主にDBに対するアクセスを制御するものです。 djangoでは、この辺の制御に関してmodel.pyというファイルの中に記述をしていきます。

DBを用意する

modelの使い方を学ぶ前に、DBのセットアップを行います。 djangoが便利機能を提供しているのが、MySQL・PostgreSQL・SQLiteなので、今回はMySQLを使ってやっていきたいと思います。

modelにはこんな感じで定義していきます。

from django.db import models

# Create your models here.
class User(models.Model):
    name = models.CharField(max_length=100)
    mail = models.CharField(max_length=100)
    age = models.IntegerField(default=0)

    def __str__(self):
        return '<User: ' + str(self.id) + ',' + \
                self.name + ', ' + self.mail + ', ' + str(self.age) + '>'

pythonのクラスによって、テーブルを定義しています。 これを作ったら、これをDBに反映させる準備をします。

python django_app/manage.py makemigrations hello

これでpythonで記述した定義をDBに反映させるためのmigration用のファイルが生成されます。 その上でmigrationを実行する際には、下記のコマンドを投入します。

python django_app/manage.py migrate

これでテーブルが作成されます。

中身を直接mysqlのコマンドを叩いて確認してみます。

MySQL [users]> show tables;
+----------------------------+
| Tables_in_users            |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
| hello_user                 |
+----------------------------+
11 rows in set (0.003 sec)

なんかたくさんできてます。一番下のが定義したテーブルで、この構造を見てみると、

MySQL [users]> SHOW COLUMNS FROM hello_user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(100) | NO   |     | NULL    |                |
| mail  | varchar(100) | NO   |     | NULL    |                |
| age   | int(11)      | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
4 rows in set (0.002 sec)

と、pythonで記述した定義がテーブルに反映されるようになっています。 この辺のテーブルの中身はDjangoのGUIからいじることもできます。 詳しくはこちら。

docs.djangoproject.com

CRUDをやってみる

テーブルが作れたので、CRUD操作を試します。 なんとなく、下記のような関数を雰囲気で書けばCRUDができるみたいです。 細かいところはgithubにコード突っ込んだのでそちらをご参照ください。

CREATE
def create(request):
    params = {
        'title': 'Hello',
        'form': UserForm(),
    }

    if request.method == 'POST':
        name = request.POST['name']
        mail = request.POST['mail']
        age = request.POST['age']

        person = Person(name=name, mail=mail, age=age)
        person.save()
        return redirect(to='/hello')

    return render(request, 'hello/index.html', params)
READ
def index(request):
    data = Person.objects.all()
    params = {
        'title': 'Hello',
        'msg': 'your data',
        'form': UserForm(),
        'data' : data
    }

    return render(request, 'hello/index.html', params)
UPDATE
def edit(request, num):
    obj = Person.objects.get(id=num)
    if request.method == 'POST':
        user = UserForm(request.POST, instance=obj) 
        user.save()
        return redirect(to='/hello') 

    params = {
        'title': 'edit',
        'id' : num,
        'form' : UserForm(instance=obj)
    }
    return render(request, 'hello/edit.html', params)
DELETE
def delete(request, num):
    user = Person.objects.get(id=num)

    if request.method == 'POST':
        user.delete()
        return redirect(to='/hello') 

    params = {
        'title': 'delete',
        'id' : num,
        'form' : user
    }

その他

その他、今回は使いませんがそのうち使いそうな機能をメモっていきます。

session

sessionは、この辺に書いてあります。

docs.djangoproject.com

test

一応、djangoではunittestをベースにしたテストツールがデフォルトで備わっています。

docs.djangoproject.com

pytestを使いたいような場合には、pytest-djangoを使用することでpytestでdjangoのテストをすることができるようです。

thinkami.hatenablog.com

今回使ったコード

今回書いたコードを自分の備忘のためにおいておきます。

github.com

参考文献

その他、下記の文献を参考にさせていただきました。

prism-cube.com

qiita.com

感想

正直なところを言うと、個人的にはあまりフルスタックなフレームワークに興味がなく、フレームワークによる思想・お作法が少々重い気がするので、「Djangoしか使わない!Djangoを中心にWebアプリケーションを作っていくんだ!」という強い意志が必要なのかな、という印象でした。

ただ、何事も勉強ですし、こういうのを勉強したからこそ見えてくるものもあると思いますので、そういう意味では非常に良かったと思いました。 冬休みの自由研究としては良いかと思うので、この冬Djangoで遊んでみてはいかがでしょう?