Djangoフレームワークでwebアプリ開発~チュートリアル~

　Pythonエンジニアとして、これまでデータ関係のお仕事をしてきました。その派生で今後アプリケーション開発のバックエンドを担当しそうな雰囲気があるので、フロントエンドの知識もあった方がいいだろうとWebアプリの開発を練習がてらやってみました。
　とはいいつつ、Webアプリ開発の経験自体は個人開発であったわけですが、所謂フレームワークを使った経験がなかった(フレームワークでどうこうなる仕事じゃなかった)ため、職場で使いそうなDjangoを使ってみました。
　内容としては自分向けチートシートでありますが、触り始めの気持ちが分かるうちになら書けることもあるので、立ち上げからちょっとカスタマイズできるところまで少し細かく書き残していこうと思います。
　Djangoって何？　ってところは、僕よりも理解が進んでいる先人達の記事や公式ドキュメントに任せます。

# ０：Djangoのインストール
pipコマンドを使ってDjangoをインストールします。
```
pip install django
```

# １：Djangoプロジェクト、アプリ作成
Djangoでルーティングやモデルなどの設定を行うテンプレートファイルの作成を行います。

```
### 作業ディレクトリ配下で以下のコマンドを実行
# プロジェクトを作成
python -m django startproject (プロジェクト名) 
# 作業ディレクトリを移動
cd (プロジェクト名)
# アプリを作成
python -m django startapp (アプリ名)
```
まず"Qiita"という作業ディレクトリを作成し、その下で"startproject"コマンドによってプロジェクトを作成します。プロジェクトを作成すると、プロジェクト名のディレクトリが作成されるので、"cd"で移動し、次は"startapp"でアプリを作成します。
プロジェクト名は"practice"、アプリ名は"practice_app"としました。
結果としては、こんな感じで立ち上がります。
※"static"と"templates"は後で使うのであらかじめ手で作成しています。
![ディレクトリ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/8abc2282-66fe-48c3-e672-91bc447de0a0.jpeg)
図：ディレクトリ構造

# ２：setting.pyを変更する
プロジェクトファイル直下の"setting.py"は、プロジェクト全体におよぶ設定を行うファイルです。
今回は、アプリの追加や汎用ファイルの場所、言語の設定を行いたいと思います。
## アプリの追加
アプリの追加というのは、先ほど作成した"practice_app"を認識できるようにする作業です。
難しいことはなく、"setting.py"の33行目あたりに"INSTALLED_APPS"というリストがあるので、そこにアプリを追記するだけです。今回は"practice_app"直下の"apps.py"に作成されているclassオブジェクト"PracticeAppConfig"を追加するので、書き方は"practice_app.apps.PracticeAppConfig"となります。
```python:practice/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'practice_app.apps.PracticeAppConfig', # 追記
]
```
```python:practice_app/apps.py
from django.apps import AppConfig


class PracticeAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'practice_app'
```
## 汎用ファイル設定
汎用ファイルの置き場所を設定します。ここでいう汎用ファイルとは、プロジェクト全体で使用するファイルのことで、例えばhtmlのヘッダのような共通部分を記述したhtmlファイルや、汎用処理を記述したjsファイルなどになります。今回は"templates"と"static"というディレクトリを、"practice"ディレクトリ直下に作成し、"setting.py"には次のように追記します。
これ以降、"templates"にはhtmlファイル、"static"にはcss、jsファイルを格納していきます。
```python:practice/setting.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [ BASE_DIR / 'templates' ], # 追記
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

###省略###

STATIC_URL = 'static/'
STATICFILES_DIRS = [ BASE_DIR / 'static' ] # 追記
```

## タイムゾーン、言語設定
デフォルトでは英語、世界標準時で設定されているところを、日本人なので、日本語、日本時間へ変更していきます。
コメントアウト部分が元々の記載で、1行下に変更後の記載をしています。
```python:practice/setting.py
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja' # 追記

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo' # 追記
```

# ３：トップページを用意
この後、ルーティング設定をして、Webページを表示させますが、そのためのページをここで作成しておきます。
ページ類は"views.py"で管理をしますが、作成したプロジェクトでは存在しないファイルなので、"practice"直下へ作成してください（アプリ側にはデフォルトで存在します）。
![views,pro.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/7f6abd21-9479-f6b9-2ff4-730a346fd86d.png)
図：practice/views.py インデックスページの設定

作成しているのは関数型viewといって、実際にブラウザに表示するページを設定する関数になります。これと別にクラス型viewというのもありますが、そちらはまた後で登場します。
インポートしている"render"というモジュールは、第一引数にhttpリクエスト、第二引数に表示したいhtmlのファイルパスを渡すことでwebページを返してくれます。今回は、templatesディレクトリに格納したindex.html(中身は大見出しの"トップページ"を表示するだけ)を表示したいと思うので、index.htmlとだけ記述します。
# ４：ルーティング設定
"urls.py"でルーティングの設定を行い、先ほど作成したview関数を呼び出せるようにします。
```python:practice/urls.py
from django.contrib import admin
from django.urls import path
from .views import index # 追記


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index, name='index') # 追記
]
```

デフォルトから追記した部分は、次の2つです。
・18行目：views.pyのindex関数をインポートする。
・22行目："path"モジュールに"url"、"view関数"、"ページ名"を渡す。
"path"モジュールは第一引数に渡されたurlへのアクセスに対して、第二引数の関数の戻り値の"html"を返却するものになっています。また、第三引数の"name"は、後述するルーティング方法で使うので、とりあえず書いておきましょう。
# ５：起動
ここで一度、起動できるか確認をしてみます。次のコマンドでwebサーバが起動します。
```
python .\manage.py runserver
```
起動すると
```
Django version 4.1.7, using settings 'practice.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
```
こんな感じでローカルホストURLが発行されるので、アクセスしてみます。
![トップページ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/8b820039-050f-4137-7ddc-7f77d928f932.png)
すると、先ほど関数型viewで設定した"index.html"が表示されます。
因みに、/admin へアクセスすると、デフォルトで用意されている管理者ページにアクセスできるので、試しにやってみてください。

# 閑話：管理者ページでテーブル内容を確認できるようにする
このあと、アカウント作成処理を実装していきます。処理が完了して、ちゃんと情報が格納されているか確認するために、管理者ページを使えるようにしていきます。
## 管理者アカウントの作成
![admin.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/41e22f65-ea61-b8fb-44f4-6fa332896e65.jpeg)
図：スーパーユーザの作成
パスワードはadminとしたところ、短いよと怒られますが、その後それでもいいの的な質問が来るので、yesを選べば先に行けます。今回はチュートリアルなので、これで良いでしょう。
/admin へアクセスして、スーパーユーザのadminでログインをします。
![adminログイン.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/18ec1c44-5658-3bd5-3857-40e18fca4379.jpeg)
図；admin ログイン
## 管理者ページでテーブルを認識できるようにする
ログインしたあと、このままだとテーブルを参照できません。認識してもらうための作業が必要です。
```python:practice_app/admin.py
from django.contrib import admin
from .models import UserModel # 追記

admin.site.register(UserModel) # 追記
```

保存すると、"UserModels"という項目が増えて、次のようになるはずです。
![テーブル確認.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/f1aeb8eb-d384-4cb4-556a-391f42b29848.jpeg)
# ６：アプリケーションを実装する
起動までいけたので、機能を追加していきたいと思います。
具体的には、1で作成した"practice_app"直下のファイルを編集していきます。
チュートリアル的なアプリとして、ログインやログアウト、更新などの基本的な機能の実装をやっていきたいと思います。
## モデル作成
機能を実装する前段として、まずはアカウント情報を管理するテーブルを作成していきます。
方法としては、"practice_app"直下の"models.py"を編集します。
```python:practice_app/models.py
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator

# アカウント情報テーブル
class UserModel(models.Model): 

    user_name = models.CharField(max_length=20)
    password = models.CharField(max_length=255, unique=True)
    gender = models.CharField(max_length=2, blank=True, null=True)
    age = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(130)])
    registration_date = models.DateTimeField(db_column="登録日", max_length=10, blank=True, auto_now_add=True)
    update_date = models.DateTimeField(db_column="更新日", max_length=10, auto_now=True)

```

デフォルトでインポートされている"models"はDjangoが用意してくれているモデルのテンプレートモジュールです。
まず、アカウント情報テーブルを管理するクラスオブジェクト"UserModel()"を作成し、Modelクラスを継承します。
その後、クラス変数を羅列していきますが、このクラス変数一つひとつが列に相当するものになります。列情報に当たるのが、models.CharField()等で、例えば文字列、数値、日付などなどを指定できます。どんなものがあるかは、公式ドキュメントを参照しましょう。また、ここでid列を指定しなかった場合は、プライマリーキーとなるid列が暗黙で作成されるので、特に指定が無ければ省略できます。
今回例として作成するテーブルの制約を箇条書きします。
・user_name:ユーザ名。文字列。20文字制約。
・password:パスワード。文字列。255文字制約。ユニーク制約。
・gender:性別。文字列。2文字制約。空欄可。
・age:年齢。整数。0から130まで記入可能。
・registration_date:登録日。日付。空欄可。新規作成したときの日付。
・update_date:更新日。日付。行が操作されたときの日付。
列の型はmodelsから何を使うかで決めることができ、引数で各種制約を設けていくのが基本的な作りになります。
長さ指定は"max_length"(CharFieldは必須)、ユニーク制約は"unique=True"、nullを許可する(null=True,blank=True)(デフォルトでnull制約がある)といった具合です。
その他、バリデーションを設けたい場合は、2行目にインポートしたようなvalidatorsが用意されているので、調べながら進めていきましょう。今回は例として、数値の上限と下限を指定してみました。
加えて、日付系の処理で便利な、auto_now_add(作成したときのみ日付を挿入)とauto_now(更新したら日付を挿入)があるので、例として載せておきました。登録日列でblank=Trueしている理由としては、初回以降の入力値が空欄になるために、エラーが出てしまうのを防ぐためです。
## フォーム作成
アカウント作成するためのフォームを実装します。
方法としては、"practice_app"直下に"forms.py"を新規作成します。
```python:practice_app/forms.py
from django import forms
from .models import UserModel


class UserCreateForm(forms.Form):

    user_name = forms.CharField(label='ユーザ名', max_length=20, required=True)
    password = forms.CharField(label='パスワード', max_length=225, required=True, widget=forms.PasswordInput)
    gender = forms.ChoiceField(
        choices = [
            ('', '未選択'),
            ('男性', '男性'),
            ('女性', '女性'),
        ],
        label = '性別',
        required=False,
        widget = forms.Select,
    )
    age = forms.IntegerField(label='年齢', min_value=0, max_value=130, required=True)

    def create(self):
        data = self.cleaned_data
        post = UserModel(
            user_name = data['user_name'],
            password = data['password'],
            gender = data['gender'],
            age = data['age'],
        )
        post.save()
```
インポートされている"forms"はDjangoが用意してくれているフォームのテンプレートモジュールです。
まず、フォームを管理するクラスオブジェクト"UserCreateForm()"を作成し、Formクラスを継承します。
その後、クラス変数を羅列していきますが、このクラス変数一つひとつがフォームのinputに相当するものになります。これらは先ほど作成したモデルの列名と一致するように書きましょう。
今回例として作成するテーブルの制約を箇条書きします。
・user_name:ユーザ名。文字列。20文字制約。必須項目。
・password:パスワード。文字列。必須項目。文字列を隠す。
・gender:性別。選択肢。空欄可。
・age:年齢。整数。0から130まで記入可能。必須項目。
各項目は、デフォルトで必須項目ではないため、入力を必須にする場合は、"required=True"を指定します。label項目は、フォームのlabel部分に相当しますので、実際のwebページの入力欄の横に表示されます。widgetはフォームの形式を指定する項目で、今回ですとパスワード文字列とセレクタになります。ほかにもいろいろあるので、公式ドキュメントで探してみましょう。
最後に、作成している"create()"という関数ですが、こちらは自作関数になります。後述するviewクラスでフォームの値を受け取ったあと、テーブルに保存するための処理を記載しています。これができると自由度の高い実装ができます。
この部分は、"save()"という関数が"ModelForm"クラスを継承した際にデフォルトで用意されていますので、凝ったことをしないのであればそれを使うのが良いと思います。
## アカウント作成ページ作成
アカウント作成を行うページの作成を行います。
方法としては、"practice_app"直下の"views.py"を編集します。
```python:practice_app/views.py
from django.views.generic import FormView
from django.shortcuts import render
from .models import UserModel
from .forms import UserCreateForm
from django.urls import reverse_lazy

class UserCreateView(FormView):
    template_name = 'user_create.html'
    model = UserModel
    form_class = UserCreateForm
    success_url = reverse_lazy('index')

    def form_valid(self, form):
        form.create()
        return super().form_valid(form)
```
アカウント情報登録フォームを管理するクラスオブジェクト"UserCreateView()"を作成し、"FormView"クラスを継承します。
1行目で、Djangoが用意してくれているフォーム関連のテンプレートクラスをインポートしています。アカウント作成で言えば、"CreateView"というクラスがありますが、セレクタみたいな少し凝ったことをしたいのであれば、"FormView"クラスを利用したほうが自由度が上がります。今回は、カスタムできるようになりたいので、"FormView"クラスを利用します。
用意が出来たら、次のクラス変数定義していきます。
・template_name：ブラウザに表示するhtmlのファイルパス
・model：このクラスで扱うモデル
・form_class：このクラスで扱うモデル
・success_url：処理が成功したあとに遷移するページ
success_urlで利用しているモジュール"reverse_lazy"ですが、こちらに記述している"index"が何を指しているかと言うと、先に"practice/urls.py"で設定していたモジュール"path"で設定していた"name"がここで使えます。この書き方をすると、"name"の値と一致するurlへ遷移します。便利ですね。
htmlファイルの格納位置ですが、"practice_app"直下に"templates"ディレクトリを切り、そこへ格納することで認識されるようになります。
テーブルへの書き込みは、16行目の"form.create()"で行います。この関数が、先ほど作成したクラスオブジェクト"UserCreateForm"の関数になります。
## アカウント作成ページへのルーティング設定
さて、アカウント作成ページが完成したので、そこへ遷移するためのルーティング設定をしていきます。
まずは、"practice/urls.py"に追記していきます。
```python:practice/urls.py
from django.contrib import admin
from django.urls import path, include # 追記
from .views import index


urlpatterns = [
    path('admin/', admin.site.urls),
    path('app/', include('practice_app.urls')), # 追記
    path('', index, name='index')
]
```
17行目に追加した"include"モジュールを利用することで、"app/"へのアクセスがあったときに、"practice_app/urls.py"へ橋渡しをしてくれます。
では、その橋渡し先の"practice_app/urls.py"を作成しましょう(デフォルトでは作成されない)。
```python:practice_app/urls.py
from django.urls import path
from .views import UserCreateView
from practice.views import index

urlpatterns = [
    path('create/', UserCreateView.as_view(), name='user_create'),
    path('', index, name='index')
]
```
こちらでは、app/create/ へのアクセスがあった場合に、UserCreateViewの内容を返し、app/ 以下に何も指定されていなければ、不正なURLとしてindexを返してあげる形式にしています。

## マイグレーション
今回、新規でモデルを作成したので、それをプロジェクトに反映して利用できるようにしていきます。その作業がマイグレーションです。
マイグレーションファイルを作成するコマンド"makemigrations"を実行し、そのファイルを使ってテーブルを作成するコマンド"migrate"を行うことで次のようになります。
```
>python manage.py makemigrations
Migrations for 'practice_app':
  practice_app\migrations\0001_initial.py
    - Create model UserModel
>python manage.py migrate
Operations to perform:
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying practice_app.0001_initial... OK
  Applying sessions.0001_initial... OK
```

## 確認
では、アカウント作成をしていきます。トップページに、アカウント作成ページに遷移するためのリンクを用意しました。リンクは次のように記述すると反映されます。
```html:templates/index.html
<a href="{% url 'user_create' %}">アカウント作成</a>
```
![トップページ+.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/f2a73fe3-69f6-b1db-3e88-72c2c631c61e.png)
図：トップページ リンク有

遷移先が次のように表示されれば、遷移と表示は成功です。
![アカウント作成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/76ff67ed-2198-87be-7637-16c451c0efbb.png)

これで値を入力して登録ボタンを押すと、晴れて登録完了です。
成功した場合はトップページへ遷移します。
遷移したら、本当にアカウントが作成されたのか確認しましょう。
/admin へアクセスし、UserModelsのページへ遷移します。
![usermodels.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/9acfdeb4-bbba-e6d7-dbc7-43a957cdf45b.jpeg)
図：テーブル内容確認

UserModel object(1)というのが、先ほど追加したアカウント情報になるので、クリックして遷移します。

![確認.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608540/ce01707f-f6e9-a561-886e-d73a1a03e6e9.jpeg)
図：行の詳細


以上で、立ち上げは終わりです。
ここから、webページ自体をリッチにしたり、javascriptで動的処理を導入したりしていきます。

長くなってきたので、[次の記事](https://qiita.com/sho_oboegaki/items/94479447b48741503124)へ行きたいと思います。
次の記事では、開発の自由度を上げるためのアレコレやっていきたいと思います。