ToDoリスト作り[Python&Django]

[編集追記]2019/11/24
settings.pyの設定を一部変更しました。
 todoアプリを作成する前に、'todo.apps.TodoConfig'をsettings.pyに追記してしまうとエラーが発生するということだったので、アプリ作成後に追記する順にしました。

#目的
[参考][Djangoで作るToDoリスト](https://qiita.com/acecrc/items/a4302ca6cf0897c45302)
 pythonで簡単なものを作ろうと思った際に、ToDoアプリが良いのではないかと思い挑戦したみた。

- 機能
    - 一覧表示
    - 詳細表示
    - リストの作成&編集
    - 削除

この4つの機能を持つToDoアプリを作成していきます。

#環境
pythonとDjangoのバージョンは以下の通りである。
Djangoは後の手順の中で仮想環境にインストールする。

```
$ python -V
Python 3.7.3
```

```
$ pip list
Package    Version
---------- -------
Django     2.1.8  
```

#作成手順
大雑把に分けると
1. 開発環境を整える
2. ToDoアプリ作成(python編)
3. ToDoアプリ作成(html編)
4. マイグレーション
5. 動作確認
の5ステップ
#1.開発環境を整える
##仮想環境の構築
 pythonのライブラリは150以上あり、どのプロジェクトの時どのライブラリが必要だか確認するのも一苦労。また、ライブラリ同士が干渉しあいうまく動作しない場合も...。
それを防ぐためにも、以下のコマンドで仮想環境を構築を行う。

```
$ virtualenv ~/eb-virt
$ source ~/eb-virt/bin/activate
```

##Djangoのインストール
仮想環境を構築できたので、そこにDjangoをインストールする。

```
(eb-virt)~$ pip3 install django==2.1.8
(eb-virt)~$ pip3 freeze
```

##プロジェクトのテンプレ作成
 プロジェクト用にディレクトリを作成し、そのディレクトリの中でプロジェクトのテンプレを作成します。

```
(eb-virt)~$ mkdir django-todo
(eb-virt)~$ cd django-todo
(eb-virt)~$ python3 -m django startproject mysite .
```

mysiteの中身はこのようになっている。

```:mysiteの構造
mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
```

##settings.pyの設定
 作成したプロジェクト内のsettings.pyの設定を行う。ALLOWED_HOSTSの欄はDjango==1.5から導入されたようなので、それ以降のバージョンを使う場合設定を忘れずに。

```python:mysite/settings.py

# ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['*']#公開するサイトのドメイン名でも可

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'#Japanese

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'#Japanの時間に
```

##ToDoアプリケーションの作成と設定
 本題であるToDoアプリを作成するため、アプリケーションのテンプレを作成する。アプリケーションtodoは先ほど作成したプロジェクトmysiteと同じ階層で作成することに注意する。

```
$ python3 manage.py startapp todo
```

todoアプリに関するsettings.pyの設定を行う。

```python:mysite/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo.apps.TodoConfig', #todoアプリケーションの組み込み
]
```

#2.ToDoアプリ
##モデル作成
 「ToDo」「作成日時」「更新日時」の3つを表示するため、日時などを取得する。この内容がデータベースに反映される。

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


class Todo(models.Model): 
    todo = models.CharField('ToDo', max_length=100, blank=False)
    created_at = models.DateTimeField('作成日時', auto_now_add=True)
    updated_at = models.DateTimeField('更新日時',auto_now=True)

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

##フォームクラス作成
テンプレ内にforms.pyは含まれていないので、todo階層の下に作成する。
入力項目はToDoのみなので、作成日時と更新日時は除外する。

```python:todo/forms.py
from django import forms
from .models import Todo

class TodoForm(forms.ModelForm):
    class Meta:
        model = Todo
        exclude = ('created_at','updated_at',)  #入力項目から作成日時、更新日時を除外
```

##ビュー作成
ToDoの各機能に対応するように、todoアプリケーションのビューを作成する。

```python:todo/views.py
from django.views import generic
from django.urls import reverse_lazy
from .models import Todo
from .forms import TodoForm

# ToDoの一覧表示機能
class TodoListView(generic.ListView):
    model = Todo
    paginate_by = 5

# ToDoの詳細表示機能
class TodoDetailView(generic.DetailView):
    model = Todo

# ToDoの作成機能
class TodoCreateView(generic.CreateView):
    model = Todo
    form_class = TodoForm
    success_url = reverse_lazy('todo:list')

# ToDoの編集機能
class TodoUpdateView(generic.UpdateView):
    model = Todo
    form_class = TodoForm
    success_url = reverse_lazy('todo:list')

# ToDoの削除機能
class TodoDeleteView(generic.DeleteView):
    model = Todo
    success_url = reverse_lazy('todo:list')
```

##URL作成
 最初に、todo/でブラウザから接続された際、mysiteのURL設定ではなく、todoアプリケーションのURL設定を参照するように変更する。そのためmysite/urls.pyとtodo/urls.pyの2つをいじる必要がある。区別して変更しよう。

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

urlpatterns = [
    path('todo/', include('todo.urls')),    # todo/でアクセスした際はtodoのurl設定を参照させる
    path('admin/', admin.site.urls),
]
```

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

app_name = 'todo' 

urlpatterns = [
    path('list/', views.TodoListView.as_view(), name='list'), 
    path('detail/<int:pk>/', views.TodoDetailView.as_view(), name='detail'), 
    path('create/', views.TodoCreateView.as_view(), name='create'), 
    path('update/<int:pk>/', views.TodoUpdateView.as_view(), name='update'), 
    path('delete/<int:pk>/', views.TodoDeleteView.as_view(), name='delete'), 
]
```
#3.htmlでテンプレート作成
ここからはUIの話。まずは作業用のディレクトリを作成する。

```
(eb-virt) $mkdir -p todo/templates/todo
```
##ベース
これから作るhtmlのベースになるものを作成。下で作成する4つのhtmlに共通部分を継承させる。

```html:todo/templates/base.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
</head>
<body>
    {% block content %}
    {% endblock %}
</body>
</html>
```

##一覧表示機能
ToDoの一覧表示機能のUI。

```html:todo/templates/todo/todo_list.html
{% extends 'base.html' %}

{% block content %}
<h1>ToDoの一覧</h1>
<a href="{% url 'todo:create' %}">追加</a>
<table>
  <thead>
    <tr>
      <th scope="col">ID</th>
      <th scope="col">ToDo</th>
      <th scope="col">作成日時</th>
      <th scope="col">更新日時</th>
      <th scope="col">操作</th>
    </tr>
  </thead>
  <tbody>
    {% for item in object_list %}
    <tr>
      <td scope="row"><a href="{% url 'todo:detail' pk=item.id %}">{{ item.id }}</a></td>
      <td>{{ item.todo }}</td>
      <td>{{ item.created_at }}</td>
      <td>{{ item.updated_at }}</td>
      <td>
        <a href="{% url 'todo:update' pk=item.id %}">修正</a>
        <a href="{% url 'todo:delete' pk=item.id %}">削除</a>
      </td>
    </tr>
    {% endfor %}
  </tbody>
</table>
{%endblock%}
```

##詳細表示機能
ToDoの詳細表示機能のUI。

```html:todo/templates/todo/todo_detail.html
{% extends 'base.html' %}

{% block content %}
<h1>ToDoの詳細</h1>
<table>
    <tr>
        <th>ID</th>
        <td>{{ object.id }}</td>
    </tr>
    <tr>
        <th>ToDo</th>
        <td>{{ object.todo }}</td>
    </tr>
    <tr>
        <th>作成日時</th>
        <td>{{ object.created_at }}</td>
    </tr>
    <tr>
        <th>更新日時</th>
        <td>{{ object.updated_at }}</td>
    </tr>
</table>
<a href="{% url 'todo:list' %}">戻る</a>
{%endblock%}
```
##作成と編集機能
ToDoの作成と編集機能のUI。

```html:todo/templates/todo/todo_form.html
{% extends 'base.html' %}

{% block content %}

{% if object %}
<h1>ToDoの編集</h1>
{% else %}
<h1>ToDoの作成</h1>
{% endif %}

<form method="post">
    {% csrf_token %}
    {{ form }} 
    <button type="submit">送信</button>
</form>
<a href="{% url 'todo:list' %}">戻る</a>
{%endblock%}
```

##削除機能
ToDoの削除機能のUI。

```html:todo/templates/todo/todo_confirm_delete.html
{% extends 'base.html' %}

{% block content %}
<h1>ToDoの削除</h1>
<table>
    <tr>
        <th>ID</th>
        <td>{{ object.id }}</td>
    </tr>
    <tr>
        <th>ToDo</th>
        <td>{{ object.todo }}</td>
    </tr>
    <tr>
        <th>作成日時</th>
        <td>{{ object.created_at }}</td>
    </tr>
    <tr>
        <th>更新日時</th>
        <td>{{ object.updated_at }}</td>
    </tr>
</table>
<form action="" method="post">
    {% csrf_token %}
    <input type="submit"value="削除" />
</form>
<a href="{% url 'todo:list' %}">戻る</a>
{%endblock%}
```


#4.データベースマイグレーション
 データベースマイグレーションとは、アプリケーションで使うデータベースの定義を自動的に作成・管理する機能のこと。最初に設定したmodel.pyの射影を、データベースに作成するのがデータベースマイグレーションの目的。

 1つ目のコマンドでマイグレーションファイルを作成、2つ目のコマンドでマイグレーションファイルをデータベースに適用(実行)する。

```
(eb-virt) $ python3 manage.py makemigrations
(eb-virt) $ python3 manage.py migrate
```


#5.動作確認
これで一通りの準備は完了。動作するか確認しよう。
まず、下記のコマンドでDjangoサーバを起動。

```
(eb-virt) $ python3 manage.py runserver 8080
```

次にブラウザを起動し以下のURLにアクセスする。
[http://127.0.0.1:8080/todo/list/](http://127.0.0.1:8080/todo/list/)

成功した場合このように表示されるはず。
<img width="697" alt="スクリーンショット 2019-11-18 18.09.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475761/c1a0b285-faa2-f902-4e5b-97c131d07e42.png">

 途中ソースコードの置き場を間違えたりと凡ミスはしたものの比較的、楽に作成できた。できれば改良を加えたい(主にUI面)