Djangoハンズオン

## Djangoハンズオン

- 目標
    - ごくごく簡単なTodoアプリを作成し、Djangoの処理の流れを理解する。
- 前提知識
    - 簡単なHTMLの理解
    - httpメソッドの理解(GET,POST)
- このハンズオンではやらないこと
    - アプリケーションの見た目をきれいにすること(CSS,bootstrap) 

#### 環境

OS: Ubuntu 18.04
Python: 3.6.8
Django: 2.2.5　-> 2.2.8

Windowsについては後々から追加していきます。Macは手元にないですがLinuxと一緒でしょう（？）。可能であれば追加します。

[前回](https://qiita.com/cuz/items/bee679dc426b690d2fe3)の記事を参照し、ディレクトリ構成が以下の状態になっていることが前提です。
もしまだ設定できていない場合は、先に環境の作成をお願いします。

## Todoアプリの作成

ここからアプリを作成していきます。
``django-admin startapp mytodo``とコンソール上に入力します。
`python manage.py startapp mytodo`でも構いません。

作成後は以下のように、``mytodo``というアプリのディレクトリ＋ファイルが作成されます。

```
.
|-- config
|   |-- __init__.py
|   |-- __pycache__
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
|-- db.sqlite3
|-- manage.py
|-- myenv
|   |-- bin
|   |-- include
|   `-- lib
`-- mytodo
    |-- __init__.py
    |-- admin.py
    |-- apps.py
    |-- migrations
    |-- models.py
    |-- tests.py
    `-- views.py
```
Djangoのタイムゾーンと言語の設定を最初に行います。

```python:settings.py
#デフォルト'EN-en'
LANGUAGE_CODE = 'ja'
#デフォルト'UTC'
TIME_ZONE = 'Asia/Tokyo'

```


現在は作成されたTodoアプリがDjangoからはわからない状態です。Djangoがアプリケーションを認識するためには、``config/settings.py``の中の設定に、作成したアプリケーションを追加することが必要です。

```python:settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'mytodo', 　　　　　　　　　　　　　　　　#追加
]
```

これでアプリの登録までが終わり、いよいよアプリを作り込んで行きます。

## モデルの作成


Todoなら作成日や終了日などを入れたほうがより実践的ですが、今回はとことん簡単にします。
今回定義するモデルは下記のとおりです。

```python:models.py
from django.db import models


class Task(models.Model):
    doing = 0
    done = 1
    TASK_STATUS_CHOICES = [
        (doing,'進行中'),
        (done,'完了'),
    ]
    title = models.CharField(max_length=50)
    body = models.models.TextField()
    status =models.IntegerField(default=0,choices=TASK_STATUS_CHOICES)
```

モデルのコーディングが終わったら、manage.pyのある階層で、コンソール上で``python manage.py makemigrations``を実行します。
このコマンドによって、モデルからDBを作成するのに必要なファイルが作成されます。
エラーなく作成されると下記のように表示されます。

```
(myenv)~/Projects/python/django_handson$ python manage.py makemigrations
Migrations for 'mytodo':
  mytodo/migrations/0001_initial.py
    - Create model Task

```

この時点ではDBに対して変更は入っていません。次の``python manage.py migrate``コマンドをうつことで、DBに対して先程のマイグレーションファイルが実行されます。

```
(myenv)~/Projects/python/django_handson$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, mytodo, 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 mytodo.0001_initial... OK
  Applying sessions.0001_initial... OK
```

TODO以外にもuserという文字が表示されています。
余談ですが、Djnagoがデフォルトで用意しているUserモデルがDBに反映されています。

ここまでできればモデルの作成は完了です。

## ルーティング・ビュー・テンプレートの作成

先程作成したモデルをベースにCRUDを実装していきます。
個人で開発の際は、私はビュー→テンプレート→ルーティングの順番で書いていきますが、個人差があるところだと思いますので慣れてくれば自分の順番で書いてください。

まず、Todoをリストですべて表示するページを作成していきます。
１つのモデルを1欄で表示する際は、Djangoのクラスベースビューを使用するとコード量がぐっと減らせて楽です。

### ビューの作成

ビューの役割は受け取ったリクエストからDBからデータを取得し、テンプレートに埋め込みレスポンスとして返すことです。

ビューには2種類の書き方があり、関数ベースのビューとクラスベースのビューが存在します。
どちらでもかけますが、今回はクラスベースビューで書きます。

クラスベースビューには、参照をするためのListView(一覧),DetailView(詳細)、
更新のためのCreateView(作成),UpdateView(更新),DeleteView(削除)などが用意されています。
これらを使用することで、単純な処理であれば一瞬でビューを用意できるようになっています。

今回は一覧を作成しますが、必要な記述は以下のとおりです。

```python:mytodo/views.py
from django.views.generic import ListView

from mytodo.models import Task

class TaskListView(ListView):
    model = Task
    template = 'mytodo/list.html'
```

``model``には表示したいモデルを指定します。先程作成したモデルをインポートし使用します。
テンプレートはHTMLにpythonの制御文を埋め込んだようなファイルで拡張子は``.html``です。

### テンプレートの作成

日本語に訳すと鋳型とか型とか言われるものですが、ビューがDBなどから取ってきた値を埋め込むための型です。ビューがテンプレートとDBから取得してきた値などを埋め込むことをレンダリングといいます。ビューでDjangoがテンプレートファイルに書かれた制御分をもとにデータを埋め込んだhtmlファイルを吐き出します。


`{{}}`はデータの表示、`{% %}`はPythonライクな制御分の埋め込みに使用します。

まずはtemplateフォルダの場所をsettings.pyに以下のように記述します。

```config/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(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',
            ],
        },
    },
]
```
templatesフォルダを``manage.py``と同じ階層に作成し、その中に、mytodoというフォルダを作成します。

その中にlist.htmlというファイルを作成します。
今回、全く見た目についてはカバーしませんので、適当なhtmlファイルを作成します

```templates/mytodo/list.html
<h1>リスト</h1>

<ul>
  {% for task in object_list %}
  <li><a href="#">{{ task.title }}</a> {{ task.get_status_display }}</li>
  {% endfor %}
</ul>

```



### ルーティングの設定
requestのurlから適切なビューを呼ぶための設定を行います。
urls.pyファイル内で設定します。


``config``フォルダの中のurls.pyにすべて書いても構わないのですが、複数のアプリを作成した際には大量のコードで溢れてしまい著しく保守性を損ないます。アプリの移植性も損ないます。
そこで、``mytodo``アプリの中にもurls.pyを作成し、``config``側でそれをインクルードするという手法がよく取られます。

まず``mytodo/urls.py``を作成し、編集していきます。

```python:urls.py

from django.urls import path

from .views import TaskListView

app_name = 'mytodo'

urlpatterns = [
    path('',TaskListView.as_view(), name='task-list'),
]
```

urls.pyで、リクエストされたURLとビューの対応付けを行います。
これをconfig側で追加します。

```config/urls.py
from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('mytodo/', include('mytodo.urls',namespace='mytodo')),#追加
]
```

``path``関数は、第一引数にurlの'/'以降に登録する文字列、第二引数にviewや他のURLConfを登録することができます。

ここまで作成することでルーティングが完了します。
早速確認しましょう``python manage.py runserver``でサーバーを起動し、確認します。

ルートには何もルーティングしていないため、Page not foundが表示されます。
登録されているurlは少し下にリストが表示されています。

![rooting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251535/9666dabf-0a11-3ce9-2382-248610e0bca0.png)


urlにmytodoを追加して``http://127.0.0.1:8000/mytodo``としてアクセスします。
現状は何もデータが登録されていないため、見出しのみしか表示されていません。真っ白です。

![no-task.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251535/db826373-a491-779b-ab2b-141f0f490278.png)


## Createの実装

同じような手順でCRUDのCREATEを実装していきます。

### ビュー
Createも同様にクラスベースビューを使って実装していきます。
先程のviews.pyに追記していきます。

```mytodo/views.py
import django.views.generic import ListView,CreateView # 追記

(中略)

class TaskCreateView(CreateView):
    model = Task
    fields = '__all__'
    initial = {'status':0}
    template_name = 'mytodo/create.html'
    success_url = reverse_lazy('mytodo:task-list')

```
実装方法はListView同様にmodelとtemplateを登録します。
initialは``Task``モデルのChoiceフィールドの初期状態を設定することができます。
fieldsは、サーバ側で受け取るデータを指定することができます。
今回はすべてのデータを受け取るとしていますが、``Task``モデルの中の``title``のみ受け取るということも可能です。


### テンプレート
次はテンプレートを作成します。

```templates/mytodo/create.html
<form action="" method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="登録">
</form>
```

``{{form.as_p}}``にはCreateViewのmodelとfieldsに対応したフォームのhtmlが生成されます。
生成したフォームにcssに当てるのは少し工夫が必要ですが、今回は味のあるhtml独特の見た目を楽しみましょう。

``{% csrf_token %}``については気にしないでください。

### ルーティング

urls.pyにviewを登録します。

```mytodo/urls.py
from django.urls import path

from .views import TaskListView, TaskCreateView #追記

app_name = 'mytodo'

urlpatterns = [
    path('',TaskListView.as_view(), name='task-list'),
    path('create',TaskCreateView.as_view(), name='task-create'),#追記
]
```

path関数の最後の``name``とはurlに名前をつけることができます。
先程のviews.pyに出てきた``reverse_lazy``などの関数の引数として使うことができます。

以上でCreateの実装が完了です。
サーバーを起動し、このURL[http://127.0.0.1:8000/mytodo/create](http://127.0.0.1:8000/mytodo/create)にアクセスします。
以下のような味のあるフォームが表示されれば成功です。

![task-create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251535/7cfc5602-14e1-65fd-d9b3-3196a299a1ae.png)

試しに何個か登録させてみましょう。

## 詳細画面の作成

残りはCRUDのうちUPDATEとDELETEが残っていますが、先にDetailViewを作成します。
現在はタスクの一覧(ListView)だけですが、よりタスクの詳細な情報を表示するページ(DetailView)を作成します。

先程同様ビューから作成していきます。
実装は以下の通り、とてもシンプルです

```mytodo/views.py
from django.views.generic import ListView,CreateView,DetailView

(中略)

class TaskDetailView(DetailView):
    model = Task
    template_name = 'mytodo/detail.html'

```

templateの実装は以下のとおりです。
また同じようにtemplates/mytodoフォルダにdetail.htmlというファイルを作成します。

```templates/mytodo/detail.html
<h1>詳細</h1>
<div>タイトル:{{object.title}}</div>
<div>status:{{object.get_status_display}}</div>
<div>内容:{{object.body}}</div>
<p><a href="#">編集</a></p>
```

最後にURLを登録します。
``urlpatterns``のリストに``path('detail',TaskDetailView.as_view(), name='task-detail')
]``を追加します。

これでDetailViewの実装は完了です。

一覧ページからのDetailViewへの遷移を実装します。
list.htmlのaタグのリンクを編集します。

```templates/mytodo/detail.html
<h1>詳細</h1>
<div>タイトル:{{object.title}}</div>
<div>status:{{object.get_status_display}}</div>
<div>内容:{{object.body}}</div>
<p><a href="{% url 'mytodo:task-update' object.pk%}">編集</a></p>　#変更
```

object.pkでテンプレートに受け渡されたオブジェクトの主キーを取得することができます。


## 更新機能の実装

これも同様にViewから実装していきます。

```python:mytodo/views.py

from django.views.generic import ListView,CreateView,DetailView, UpdateView
from django.urls import reverse_lazy

from mytodo.models import Task

(中略)

class TaskUpdateView(UpdateView):
    model = Task
    fields = '__all__'
    template_name = 'mytodo/update.html'
    success_url = reverse_lazy('mytodo:task-list')

```

CreateViewとほぼ同じですね。
もし、アップデートを許すフィールドを限定したい場合はfieldsをいじるといいでしょう。

ほぼ同じ作業なので一気にルーティングとテンプレートの実装を行います。

テンプレートはCreateとまるっきり一緒です。

```html:templates/mytodo/update.html

<form action="" method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="登録">
</form>
```

ルーティングに新たにUpdateViewを追記します。
detailと同じく、主キーによって更新をかけるべきデータを判別します。。

```python:mytodo/urls.py
urlpatterns = [
    path('',TaskListView.as_view(), name='task-list'),
    path('detail/<int:pk>/',TaskDetailView.as_view(), name='task-detail'),
    path('create/', TaskCreateView.as_view(), name='task-create'),
    path('update/<int:pk>/', TaskUpdateView.as_view(), name='task-update'),                 #追加
]
```

この編集画面への遷移を詳細画面から行えるように若干変更しておきます。

```html:templates/mytodo/detail.html
<h1>詳細</h1>
<div>タイトル:{{object.title}}</div>
<div>status:{{object.get_status_display}}</div>
<div>内容:{{object.body}}</div>
<p><a href="{% url 'mytodo:task-update' object.pk%}">編集</a></p>
```

## 削除機能の追加

いよいよ最後です。
削除ビューを実装します。
例のごとく、ビューから実装していきます。

ビューの実装。

```python:mytodo/views.py
from django.views.generic import ListView,CreateView,DetailView, UpdateView
from django.urls import reverse_lazy

from mytodo.models import Task

(中略)

class TaskDeleteView(DeleteView):
    model = Task
    template_name = 'mytodo/delete.html'
    success_url = reverse_lazy('mytodo:task-list')

```

テンプレートの実装。
削除するよっていうのを遅れればいいのでとても簡素

```html:templates/mytodo/delete.html
<form action="" method="post">
  {% csrf_token %}
  <p>本当に削除しますか？</p>
  <p><input type="submit" value="Yes"></p>
  <p><a href="{% url 'mytodo:task-list' %}">戻る</a></p>
</form>
```

ルーティングの実装。
最終型が以下のとおりです。

```python:mytodo/urls.py
from django.urls import path

from .views import TaskListView, TaskCreateView, TaskDetailView, TaskUpdateView, TaskDeleteView

app_name = 'mytodo'

urlpatterns = [
    path('',TaskListView.as_view(), name='task-list'),
    path('detail/<int:pk>/',TaskDetailView.as_view(), name='task-detail'),
    path('create/', TaskCreateView.as_view(), name='task-create'),
    path('update/<int:pk>/', TaskUpdateView.as_view(), name='task-update'),
    path('delete/<int:pk>/', TaskDeleteView.as_view(), name='task-delete'),            #追加
]
```
最後にこの削除ビューに遷移するurlを追記しておしまいです。
ついでにlist.htmlをテンプレートの制御文を使って申し訳程度の処理を行っておきます。

```html:templates/mytodo/list.html
<h1>リスト</h1>

<a href="create/"><p>新規追加</p></a>
<ul>
  {% for task in object_list %}
  {% if task.status != 0 %}
    <li><del><a href="detail/{{ task.id }}">{{ task.title }}</a>  </del>{{ task.get_status_display }} <a href="{% url 'mytodo:task-delete' task.pk %}">削除</a></li>
  {% else %}
    <li><a href="detail/{{ task.id }}">{{ task.title }}</a>  {{ task.get_status_display }} <a href="{% url 'mytodo:task-delete' task.pk %}">削除</a></li>
  {% endif %}
  
  {% empty %}
    <li>No tasks</li>
  {% endfor %}
</ul>
```


# おわりに

アマチュアDjango使いの記事に最後までお付き合い頂きありがとうございました。
何か間違い等があれば遠慮なくツッコミお待ちしております。