Pythonさえ分かればDjangoでWebアプリは作れる


この記事は[Django Advent Calendar 2020](https://qiita.com/advent-calendar/2020/django) の最終日の記事です。


# はじめに
はじめまして。

普段からDjangoを使用してWebアプリを開発しています。

今年もこの時期がやって来ましたね。
ちなみに、去年も同じ日に投稿させて頂きました。

 - [Djangoの個人的Tips](https://qiita.com/Taro_man/items/3a0e6256db22d5e64a68)

今年の Django Advent Calendar 2020 も素晴らしい、そして実用的な記事が多く、
楽しく拝見させて頂きました。

個人的にDjangoの良さは**「簡単な」**アプリケーションを**「手軽に」**作れるところにあると思っています。
そこでこの記事では、簡単な、そして基本的な**Djangoアプリケーションを作る過程を**コードとともに記載したいと思います。

この記事がDjangoのより一層の普及に少しでも貢献できれば幸いです。


# 目次
 - [前準備](#前準備)
 - [アプリケーションの作成](#アプリケーションの作成)
 - [Modelの作成](#modelの作成)
 - [Viewの作成](#viewの作成)
 - [Templateの作成](#templateの作成)
 - [ルーティング定義](#ルーティング定義)
 - [完成](#完成)
 

# 前準備

まず、アプリケーションを作っていく前に、**簡単な前準備**を済ませてしまいましょう。


## Django のインストール

djangoをインストールします。
Linux系OSやMacならターミナルから、Windowsならコマンドプロンプトから以下のコマンドを実行します。


```bash:
$ pip install django

Collecting django
  Downloading https://files.pythonhosted.org/packages/08/c7/7ce40e5a5cb47ede081b9fa8a3dd93d101c884882ae34927967b0792f5fb/Django-3.1.4-py3-none-any.whl (7.8MB)
     |████████████████████████████████| 7.8MB 2.1MB/s 
Requirement already satisfied: pytz in /Users/rinego/.pyenv/versions/3.8.3/lib/python3.8/site-packages (from django) (2019.3)
Collecting asgiref<4,>=3.2.10 (from django)
  Downloading https://files.pythonhosted.org/packages/89/49/5531992efc62f9c6d08a7199dc31176c8c60f7b2548c6ef245f96f29d0d9/asgiref-3.3.1-py3-none-any.whl
Collecting sqlparse>=0.2.2 (from django)
  Downloading https://files.pythonhosted.org/packages/14/05/6e8eb62ca685b10e34051a80d7ea94b7137369d8c0be5c3b9d9b6e3f5dae/sqlparse-0.4.1-py3-none-any.whl (42kB)
     |████████████████████████████████| 51kB 3.5MB/s 
Installing collected packages: asgiref, sqlparse, django
Successfully installed asgiref-3.3.1 django-3.1.4 sqlparse-0.4.1
```


## djangoプロジェクトの作成

以下のコマンドでdjangoプロジェクトを作成します。

```bash:
$ django-admin startproject my_project
```

コマンドを実行すると、以下のファイル群が作成されます。

```
my_project/
    manage.py
    my_project/
        __init__.py
        asgi.py
        settings.py
        urls.py
        wsgi.py
```

一階層目の `my_project` ディレクトリに移動して以降の各種設定を行います。

```bash:
$ cd my_project
```

## プロジェクト設定

Djangoの各種設定は `my_project/my_project/settings.py` で定義されています。

データベースの設定などは特にこだわりがなければ変えなくてOKです。
標準で良い感じの設定になっています。

**言語とタイムゾーン**だけ日本にしておきましょう。


```python:my_project/my_project/settings.py
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

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


## データベースのマイグレート

まず、データベースのマイグレートを実施して、Djangoで管理するデータベースを作っておきましょう。

```bash:
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
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 sessions.0001_initial... OK
```

コマンドを実行することで、 `my_project/db.sqlite3` というファイルが作成されます。


## super user の作成

このタイミングで **super user**（作成するアプリの管理者）も作成しておきましょう。

```bash:
$ python manage.py createsuperuser
Username (leave blank to use 'hoge'): admin
Email address: admin@example.com
Password: XXXXX
Password (again): XXXXX
Superuser created successfully.
```

以下の項目を聞かれます。

- ユーザー名
- メールアドレス
- パスワード
- パスワード(再入力)

後々使うので `Username` `Password` は自分が忘れないものにしましょう。
`Password` は簡単なものを入力すると「ありきたりだけど良いの？」と聞かれます。

```bash:
This password is too common.
Bypass password validation and create user anyway? [y/N]: 
```

メールアドレスは適当なものでも問題ないです。


## 開発用サーバーの起動

ここまで出来たら、一度、**開発用サーバー**を起動してみましょう。
`python manage.py runserver` というコマンドで開発用サーバを起動します。

```bash:
$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 18, 2020 - 08:54:54
Django version 3.1.4, using settings 'my_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

この状態で、ブラウザで `http://127.0.0.1:8000/` にアクセスしてみます。

以下のような画面が表示されていればOKです。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/0ebb1a37-2a15-7f0e-193e-ed65bd160aa8.png)


`control + c` で開発者サーバーを終了しましょう。

ここまでできれば、とりあえず必要な環境構築は出来ているはずなので、あとはコードを書いていくだけです。


# アプリケーションの作成


では、アプリケーションを作成していきましょう。

今回は簡単なデータの**作成、編集、参照、削除**を行えるアプリケーションを作成してみましょう。

ちょうど[ruby on rails tutorial](https://railstutorial.jp/) の toy app のようなものですね。

なお、Djangoでは**MTVモデルという設計思想**を取っています。
なにも難しいことはなくて、一般的なデザインパターンのMVCモデルをそれぞれ置き換えたものです。

![MTV.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/b33782d0-386a-b487-928a-2895427f2da0.png)

Model, Template, View を定義することでアプリケーションが完成します。
(Viewという呼び名が最高にややこしいのですが・・・)



## アプリケーションの作成

まずはアプリケーションを作成しましょう。

```bash:
python manage.py startapp sample_app
```

コマンドを実行すると以下のような階層構造になっているはずです。

```bash:
my_project/
    manage.py
    my_project/
        __init__.py
        asgi.py
        settings.py
        urls.py
        wsgi.py
    sample_app/
        __init__.py
        admin.py
        apps.py
        migrations
        models.py
        tests.py
        views.py
```


Djangoではプロジェクトディレクトリ `my_project/` の下に、
プロジェクト設定 `my_project/my_project/` とアプリケーションディレクトリ `my_project/sample_app/` が紐づくという形になります。


## アプリケーションのプロジェクトへの登録

アプリケーションを作成したら、プロジェクトに登録しましょう。
これをしないとプロジェクトからアプリケーションを認識出来ません。

`my_project/sample_app/apps.py` を開いてみると、`SampleAppConfig` というクラスが定義されています。
これを `my_project/my_project/settings.py` の `INSTALLED_APPS` に *'sample_app.apps.SampleAppConfig',*という文字列で追記します。

```python:my_project/my_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'sample_app.apps.SampleAppConfig', # Add
]
```

これでひとまずアプリケーションを作成できました。
次から実際にアプリケーションの各所を作り込んでいきます。


# Modelの作成

まず、**Model(データベース定義)**を作成しましょう。

作成したいアプリケーションのデータモデルを `my_project/sample_app/models.py` に定義します。


```python:my_project/sample_app/models.py
from django.db import models


class Post(models.Model):
    name = models.CharField('user name', max_length=15)
    micropost = models.CharField('tweet', max_length=140, blank=True)

    def __str__(self):
        return self.name
```


このように作成したいアプリケーションのデータ構造を定義することができます。
ちなみに、`user name` や `tweet` は任意の文字列です。
後から自分が何のデータのつもりで定義したかわかるような名前にしておきましょう。


## モデルの有効化

モデルを作成したら、そのモデルを有効化する必要があります。

以下のコマンドで `my_project/sample_app/models.py` の変更を反映するためのマイグレートファイルを作成します。

```bash:
$ python manage.py makemigrations sample_app
Migrations for 'sample_app':
  sample_app/migrations/0001_initial.py
    - Create model Post
```

これでマイグレートファイルができたので、以下のコマンドでデータベースに反映します。

```bash:
$ python manage.py migrate sample_app
Operations to perform:
  Apply all migrations: sample_app
Running migrations:
  Applying sample_app.0001_initial... OK
```


以上で、Modelの作成は完了です。


# Viewの作成

次に、**View（処理の定義）**を作成します。

 `my_project/sample_app/views.py` に「作成」「修正」「一覧」「削除」の各機能を関数で定義します。


```python:my_project/sample_app/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.forms import ModelForm

from sample_app.models import Post


def create_post(request):
    """
    新たなデータを作成する
    """
    # オブジェクトを新規作成する
    post = Post()

    # ページロード時
    if request.method == 'GET':
        # 新規作成オブジェクトにより form を作成
        form = PostForm(instance=post)

        # ページロード時は form を Template に渡す
        return render(request,
                      'sample_app/post_form.html',  # 呼び出す Template
                      {'form': form})  # Template に渡すデータ

    # 実行ボタン押下時
    if request.method == 'POST':
        # POST されたデータにより form を作成
        form = PostForm(request.POST, instance=post)

        # 入力されたデータのバリデーション
        if form.is_valid():
            # チェック結果に問題なければデータを作成する
            post = form.save(commit=False)
            post.save()

        return redirect('sample_app:read_post')


def read_post(request):
    """
    データの一覧を表示する
    """
    # 全オブジェクトを取得
    posts = Post.objects.all().order_by('id')
    return render(request,
                  'sample_app/post_list.html',  # 呼び出す Template
                  {'posts': posts})  # Template に渡すデータ


def edit_post(request, post_id):
    """
    対象のデータを編集する
    """
    # IDを引数に、対象オブジェクトを取得
    post = get_object_or_404(Post, pk=post_id)

    # ページロード時
    if request.method == 'GET':
        # 対象オブジェクトにより form を作成
        form = PostForm(instance=post)

        # ページロード時は form とデータIDを Template に渡す
        return render(request,
                      'sample_app/post_form.html',  # 呼び出す Template
                      {'form': form, 'post_id': post_id})  # Template に渡すデータ

    # 実行ボタン押下時
    elif request.method == 'POST':
        # POST されたデータにより form を作成
        form = PostForm(request.POST, instance=post)

        # 入力されたデータのバリデーション
        if form.is_valid():
            # チェック結果に問題なければデータを更新する
            post = form.save(commit=False)
            post.save()

        # 実行ボタン押下時は処理実行後、一覧画面にリダイレクトする
        return redirect('sample_app:read_post')


def delete_post(request, post_id):
    # 対象のオブジェクトを取得
    post = get_object_or_404(Post, pk=post_id)
    post.delete()

    # 削除リクエスト時は削除実行後、一覧表示画面へリダイレクトする
    return redirect('sample_app:read_post')


class PostForm(ModelForm):
    """
    フォーム定義
    """
    class Meta:
        model = Post
        # fields は models.py で定義している変数名
        fields = ('name', 'micropost')
```


軽く説明しておくと、以下のようになります。
（コード見てわかる方はすっ飛ばしてもらって問題ないです）


### create_post()
データ作成処理として呼ばれる関数です。
ページロード時 or 実行ボタン押下時 の処理を `request.method` によって分岐させています。

ページロード時は新たな `Post` オブジェクトによって、フォームを生成しています。
フォーム定義は別途classで定義しています。
Django標準の `ModelForm` を使うことで簡単にフォームを実装できます。

実行ボタン押下時はフォームに入力された値を使って、Postオブジェクトを生成しています。
これにより、新たにデータを作成しています。
また、入力データのバリデーションも行っています。
Django標準機能の `form.is_valid` を使うことでこれまた簡単に実装できます。

作成処理後はデータ一覧画面に飛ばしています。


### read_post()
データ一覧を表示する処理として呼ばれる関数です。
やってることは、データベースから全データを取ってきて、それをそのままTemplateに渡すだけです。


### edit_post()
データ編集処理として呼ばれる関数です。
作成処理と似ていますが、こちらは引数で渡されたidによって、`Post` オブジェクトを取得し、フォームを生成します。

関数内で使っている `get_object_or_404` はとても便利なDjango標準機能です。
objectを取得する機能ですが、対象のobjectがない場合は404Errorを送出してくれます。
これにより不用意にServer Error (500)を出さずに済みます。

実行ボタン押下時の処理は[データ作成処理](#create_post)と同じことをやっています。


### delete_post()
データ削除処理として呼ばれる関数です。
引数で渡されたidによって、 `Post` オブジェクトを取得し、対象オブジェクトを削除します。

削除処理はTemplateにデータを渡すことはなく、処理実行後はデータ一覧画面にリダイレクトしています。

このようにViewで各処理を定義することで、アプリケーション機能を実装することができます。



# Templateの作成

次に、**Template（表示の定義）**を作成します。

Templateでは画面表示を定義します。つまるところ、htmlファイルです。

DjangoではTemplateの置き場所が決まっています。

`my_project/sample_app/` の配下に2階層ディレクトリを作成して
 `my_project/sample_app/templates/sample_app/` というディレクトリを用意します。

ここに、アプリケーションで使う Template を作成していきます。

今回、必要なのは以下の2つの Template です。

- **データの一覧画面**
- **データの入力フォーム画面**

## データの一覧画面

`my_project/sample_app/templates/sample_app/post_list.html` を作成します。


```html:post_list.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post lists</title>
</head>
<body>
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>User Name</th>
        <th>Post</th>
      </tr>
    </thead>
    <tbody>
      {% for post in posts %}
      <tr>
        <th>{{ post.id }}</th>
        <td>{{ post.name }}</td>
        <td>{{ post.micropost }}</td>
        <td>
          <a href="{% url 'sample_app:edit_post' post_id=post.id %}">修正</a>
          <a href="{% url 'sample_app:delete_post' post_id=post.id %}">削除</a>
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
  <a href="{% url 'sample_app:create_post' %}">作成</a>
</body>
</html>
```


こちらのTemplate は View の `read_post()` から呼び出しています。

大事なのは以下の部分です。

```html
    <tbody>
      {% for post in posts %}
      <tr>
        <th>{{ post.id }}</th>
        <td>{{ post.name }}</td>
        <td>{{ post.micropost }}</td>
        <td>
          <a href="{% url 'sample_app:edit_post' post_id=post.id %}">修正</a>
          <a href="{% url 'sample_app:delete_post' post_id=post.id %}">削除</a>
        </td>
      </tr>
      {% endfor %}
    </tbody>
```

ここで View から受け取った `posts` を使ってループを回しています。
これにより、データの数分だけのテーブル行を表示しています。



## データの入力フォーム画面

`my_project/sample_app/templates/sample_app/post_form.html` を作成します。


```html:post_form.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Post Form</title>
</head>
<body>
    <h4>Post の編集</h4>
    {% if post_id %}
    <form action="{% url 'sample_app:edit_post' post_id=post_id %}" method="post">
    {% else %}
    <form action="{% url 'sample_app:create_post' %}" method="post">
    {% endif %}
      {% csrf_token %}
      {{ form }}
          <button type="submit">送信</button>
    </form>
    <a href="{% url 'sample_app:read_post' %}">戻る</a>
</body>
</html>
```


こちらのTemplate は View の `create_post()` と `edit_post()` から呼び出しています。

そのため、以下のように `post_id` の有無で `form action` を分岐させています。

```html
    {% if post_id %}
    <form action="{% url 'sample_app:edit_post' post_id=post_id %}" method="post">
    {% else %}
    <form action="{% url 'sample_app:create_post' %}" method="post">
    {% endif %}
```

以上で、Template まで作成できました。

あとはルーティングを定義してあげれば、アプリケーションとして動くはずです。


# ルーティング定義

最後に、ルーティングを定義しましょう。

ルーティングとは**「 URL と処理の紐付け」**です。

つまり `http://127.0.0.1:8000/sample_app/post/create/` にアクセスした時には、
View の `create_post()` を呼び出す...みたいなのを定義してあげます。

今回は以下のような紐付けにします。

- データ作成: http://127.0.0.1:8000/sample_app/post/create/
- データ編集: http://127.0.0.1:8000/sample_app/post/edit/1/
- データ一覧: http://127.0.0.1:8000/sample_app/post/
- データ削除: http://127.0.0.1:8000/sample_app/post/delete/1/


では、作っていきましょう。


`my_project/sample_app/urls.py` というファイルを新しく作ります。

```python:my_project/sample_app/urls.py
from django.urls import path
from sample_app import views


app_name = 'sample_app'
urlpatterns = [
    path('post/create/', views.create_post, name='create_post'),  # 作成
    path('post/edit/<int:post_id>/', views.edit_post, name='edit_post'),  # 修正
    path('post/', views.read_post, name='read_post'),   # 一覧表示
    path('post/delete/<int:post_id>/', views.delete_post, name='delete_post'),   # 削除
]
```

次に、作成した my_project/sample_app/urls.py をプロジェクト全体の my_project/urls.py で読み込みます。

```python:my_project/urls.py
from django.contrib import admin
from django.urls import path, include   # Add


urlpatterns = [
    path('sample_app/', include('sample_app.urls')),   # Add
    path('admin/', admin.site.urls),
]
```

こうすることで、プロジェクトがアプリケーションURLを認識してくれるようになります。
ちなみに`name='XXX'` は何でも良いです。（私は関数名と揃えるようにしていますが）


# 完成

ここまで実装できたら、アプリケーションとして動くはずです。

もう一度、 `python manage.py runserver` を実行してみましょう。

```bash
$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 20, 2020 - 09:12:49
Django version 3.1.4, using settings 'my_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

この状態で、ブラウザから http://127.0.0.1:8000/sample_app/post/ にアクセスすると、データ一覧表示画面が表示されます。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/1a6059e5-2e73-d22d-f4a2-ae81da45a17b.png)

作成ボタンや修正ボタンも用意しているので、データを作成したり、作成したデータを修正、削除することもできます。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/a8bc7cf9-9b21-4ffb-e430-6ff3700adbee.png)


![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/aafbeaac-d55b-f9a7-5d4f-fab89acd7352.png)



# さいごに
最後まで読んで下さり、ありがとうございます。
いかがだったでしょうか。

このようなアプリケーション機能を作成したら、
**cssでデザインを付けて、heroku にデプロイして、独自ドメインを取得すれば、Webサービスの完成です**。

なお、Webサービスの作成については以下の記事にまとめています。

- [最も低コストでプロダクトを公開したい](https://qiita.com/Taro_man/items/cea8c095fe8680ab12f3)
- [数十円で作ったwebサービス](https://qiita.com/Taro_man/items/fb3c941bc77fbcdca7ce)


それでは来年も良いDjangoライフを！