DjangoでTodoアプリを作る④フォルダー、タスク作成機能の実装

次は、フォルダー、タスク作成機能を実装していきます。

##記事一覧
[DjangoでTodoアプリを作る①Dockerで環境を構築する](https://qiita.com/takos/items/b9ba0b60c6f71b428aac)
[DjangoでTodoアプリを作る②フォルダ一覧ページの作成](https://qiita.com/takos/items/3e480183e72f41b69b84)
[DjangoでTodoアプリを作る③タスク一覧ページの作成](https://qiita.com/takos/items/828283aad5e5e2d8573d)
[DjangoでTodoアプリを作る④フォルダー、タスク作成機能の実装](https://qiita.com/takos/items/ce580c5781956a7c7eba)
[DjangoでTodoアプリを作る⑤タスク編集機能の作成](https://qiita.com/takos/items/dc4b91784cf895cc71c3)

#フォルダー作成機能の実装

##URLの設定
まず、以下の定義を踏まえてURLを設定していきます。

| URL |  処理 |
|:--|:---|
|/folders/create  |フォルダーを新たに作成する |

この設計にするために、`todo/urls.py`に次の一文を加えてください。

```python:todo/urls.py
path('create', views.create_folder, name='folders.create')
```
##フォームの作成
まず、`todo`ディレクトリの下に`forms.py`というファイルを作成してください。
このファイルは、フォームを作成するのに必要なファイルです。

forms.pyを以下のように編集します。

```forms.py
from django import forms
from .models import Folder

class FolderForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(FolderForm, self).__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs = {
                'class': 'form-control'
            }
    class Meta:
        model = Folder
        fields = ('title',)
        labels = {'title' : 'フォルダー名'}
```
formを作成するために必要な`forms`と`Folder`モデルを1,2行目でインポートしています。
\_\_init\_\_の部分で、FolderFormのクラス名をform-controlにしています。これは、bootstrapを適用させるためです。

djangoには、django.forms.ModelForm と言う Modelクラスを元にFieldを自動的に生成してくれるクラスがあります。登録や更新処理ではModelFormクラスを使うのが便利なようです。

Metaクラスの変数の意味は以下の通りです。

| 変数名 | 意味 |
|:--|:---|
|model | 紐付けるModelクラスを指定する|
|fields |Modelから入力フォームを生成する対象のフィールドをタプル形式で指定する|
|labels |入力欄の表示名を変更する。（例えば、今回指定していなければ、表示名が「title」になる）|

##フォルダー作成ページへのリンク
`templates/index.html`の`フォルダーを追加する`の部分にリンクを以下のように追加します。

```html:index.html
<a href="{% url 'folders.create' %}" class="btn btn-default btn-block">
```

##テンプレート
`templates`ディレクトリ下に`create_folders.html`を作成します。
`create_folders.html`を以下のように編集します。

```html:templates/create_folders.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Todo</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootflat/2.0.4/css/bootflat.min.css">
  <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
  <header>
    <nav class="my-navbar">
      <a class="my-navbar-brand" href="/">Todo</a>
    </nav>
  </header>
  <main>
    <div class="container">
        <div class="row">
          <div class="col col-md-offset-3 col-md-6">
            <nav class="panel panel-default">
              <div class="panel-heading">フォルダーを追加する</div>
              <div class="panel-body">
                <form method="POST">
                  {% csrf_token %}
                  {{ form.as_p }}
                  <div class="text-right">
                      <button type="submit" class="btn btn-primary">送信</button>
                  </div>
                </form>
              </div>
            </nav>
          </div>
        </div>
      </div>
  </main>
</body>
</html>
```

以下の部分でCSRF対策を行っています。

```templates/create_folders.html
{% csrf_token %}
```
また、以下の部分でフォルダー作成のためのフォームを展開しています。

```create_folders.html
{{ form.as_p }}
```

`as_p`とは、

```
<p>
formの内容
</p>
```
という形で展開されるということです。

次に、viewを書いていきます。

##View
viewに以下の`create_folder`メソッドを追加します。


```python:views.py
from django.shortcuts import render, get_object_or_404, redirect#redirect関数を追加
from .forms import FolderForm


def create_folder(request):
    if request.method == "POST":
        form = FolderForm(request.POST)
        if form.is_valid():
            folder = form.save(commit=False)
            folder.created_at = timezone.now()
            folder.save()
            return redirect('tasks.index', id=folder.id)
    else:
        form = FolderForm()
    return render(request, 'create_folders.html', {'form': form})
```
まず最初に、作成したFolderFormとredirect関数をインポートしています。

その後に、`create_folder`関数を定義しています。
request.POST にデータが追加されているとき、formに入力された内容がデータベースに保存されるようにif文の処理を書いています。

##確認
これでフォルダー作成機能は実装できました！
http://localhost:8000/folders/create にしてみて、以下のように表示されていればOKです！
![フォルダー作成機能.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/f1068894-9e61-ec8f-e4cc-af8bbb1fd68b.png)


#タスク作成機能の実装
##URLの設定
まず、以下の定義を踏まえてURLを設定していきます。

| URL |  処理 |
|:--|:---|
|\<int:id>/tasks/create |タスクを新たに作成する |

この設計にするために、`todo/urls.py`に次の一文を加えてください。

```python:todo/urls.py
path('<int:id>/tasks/create', views.create_task, name='tasks.create')
```
##フォームの作成
forms.pyでタスク作成のためのフォームを作成します。
以下の、TaskFormクラスを追加します

```todo/forms.py
from .models import Folder, Task#Taskモデルをインポート

class TaskForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(TaskForm, self).__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs = {
                'class': 'form-control'
            }
    class Meta:
        STATUS_CHOICES = [(1, '未完了'),(2, '作業中'),(3, '完了')]
        model = Task
        fields = ('title', 'status','due_date')
        labels = {
            'title': 'タスク名',
            'status': '状態',
            'due_date': '期限',
        }
```
まずはじめに、Taskモデルをインポートしています。
そして`TaskForm`クラスを追加しています。

##フォルダー作成ページへのリンク
`templates/index.html`の`タスクを追加する`の部分にリンクを以下のように追加します。

```html:index.html
<a href="{% url 'tasks.create' id=current_folder
_id %}" class="btn btn-default btn-block">
```

##テンプレート
`templates`ディレクトリ下に`create_tasks.html`を作成します。
`create_tasks.html`を以下のように編集します。

```html:templates/create_tasks.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Todo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
  <link rel="stylesheet" href="https://npmcdn.com/flatpickr/dist/themes/material_blue.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootflat/2.0.4/css/bootflat.min.css">
  <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<header>
  <nav class="my-navbar">
    <a class="my-navbar-brand" href="/">Todo</a>
  </nav>
</header>
<main>
    <div class="container">
        <div class="row">
          <div class="col col-md-offset-3 col-md-6">
            <nav class="panel panel-default">
              <div class="panel-heading">タスクを追加する</div>
              <div class="panel-body">
                <form method="POST">
                  {% csrf_token %}
                  {{ form.as_p }}
                  <div class="text-right">
                    <button type="submit" class="btn btn-primary">送信</button>
                  </div>
                </form>
              </div>
            </nav>
          </div>
        </div>
      </div>
</main>

<script src="https://npmcdn.com/flatpickr/dist/flatpickr.min.js"></script>
<script src="https://npmcdn.com/flatpickr/dist/l10n/ja.js"></script>
<script>
  flatpickr(document.getElementsByName('due_date'), {
    locale: 'ja',
    minDate: new Date()
  });
</script>
</body>
</html>
```

次に、viewを書いていきます。

##View
viewに以下の`create_task`メソッドを追加します。

```python:views.py
from .forms import FolderForm, TaskForm#TaskFormをインポートする

def create_task(request, id):
    #選ばれたフォルダを取得する
    current_folder = get_object_or_404(Folder, id=id)
    if request.method == "POST":
        form = TaskForm(request.POST)
        if form.is_valid():
            task = form.save(commit=False)
            task.created_at = timezone.now()
            task.folder_id = current_folder
            task.save()
            return redirect('tasks.index', id=current_folder.id)
    else:
        form = TaskForm()
    return render(request, 'create_tasks.html', {'form': form}, {'id':current_folder.id})
```

注意するところは以下の部分。

```
task.folder_id = current_folder
```

`folder_id`は外部キーですが、外部キーを設定するときはオブジェクトを渡す必要があります（今回の場合はFolderオブジェクト）。


#テンプレートの拡張
テンプレートは同じ情報やレイアウトを複数の場所で利用したいときに役立ちます。 各ファイル内で繰り返す必要はありません。

元となるテンプレートを作るために、`todo/templates`ディレクトリ配下に`base.html`ファイルを作ります。

```
templates
├── base.html
├── create_folders.html
├── create_tasks.html
└── index.html
```

`base.html`を以下のように編集します。

```html:base.html
{% load static %}
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Todo</title>
  {% block styles %}
  {% endblock %}
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootflat/2.0.4/css/bootflat.min.css">
  <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<header>
  <nav class="my-navbar">
    <a class="my-navbar-brand" href="{% url 'tasks.index' id=1 %}">Todo</a>
  </nav>
</header>
<main>
    {% block content %}
    {% endblock %}
</main>
{% block scripts %}
{% endblock %}
</body>
</html>
```

以下の部分、HTMLが挿入されていきます。

```
  {% block styles %}
  {% endblock %}
```

ファイルの最初に以下を記述することで、base.htmlを拡張していくことができます。

```
{% extends 'base.html' %}
```
これをもとに、`create_folders.html`、`create_tasks`、`index.html`を以下のように編集します。


```html:create_folders.html
{% extends 'base.html' %}

{% block content %}
  <div class="container">
    <div class="row">
      <div class="col col-md-offset-3 col-md-6">
        <nav class="panel panel-default">
          <div class="panel-heading">フォルダーを追加する</div>
          <div class="panel-body">
            <form method="POST">
              {% csrf_token %}
              {{ form.as_p }}
              <div class="text-right">
                  <button type="submit" class="btn btn-primary">送信</button>
              </div>
            </form>
          </div>
        </nav>
      </div>
    </div>
  </div>
{% endblock %}
```

```html:create_tasks.html
{% extends 'base.html' %}

{% block styles %}
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
  <link rel="stylesheet" href="https://npmcdn.com/flatpickr/dist/themes/material_blue.css">
{% endblock %}

{% block content %}
  <div class="container">
    <div class="row">
      <div class="col col-md-offset-3 col-md-6">
        <nav class="panel panel-default">
          <div class="panel-heading">タスクを追加する</div>
          <div class="panel-body">
            <form method="POST">
              {% csrf_token %}
              {{ form.as_p }}
              <div class="text-right">
                <button type="submit" class="btn btn-primary">送信</button>
              </div>
            </form>
          </div>
        </nav>
      </div>
    </div>
  <div>
{% endblock %}

{% block scripts %}
  <script src="https://npmcdn.com/flatpickr/dist/flatpickr.min.js"></script>
  <script src="https://npmcdn.com/flatpickr/dist/l10n/ja.js"></script>
  <script>
    flatpickr(document.getElementsByName('due_date'), {
      locale: 'ja',
      minDate: new Date()
    });
  </script>
{% endblock %}


```

```html:index.html
{% extends 'base.html' %}

{% block content %}
  <div class="container">
    <div class="row">
      <div class="col col-md-4">
        <nav class="panel panel-default">
          <div class="panel-heading">フォルダー</div>
          <div class="list-group">
            {% for folder in folders %}
            <a 
            href="{% url 'tasks.index' id=folder.id %}",
            class="list-group-item {% if current_folder_id == folder.id %}active{% endif %}"
          　>
              {{ folder.title }}
            </a>
            {% endfor %}
          </div>
          <div class="panel-body">
            <a href="{% url 'folders.create' %}" class="btn btn-default btn-block">
              フォルダーを追加する
            </a>
          </div>
        </nav>
      </div>
      <div class="column col-md-8">
        <div class="panel panel-default">
          <div class="panel-heading">タスク</div>
          <table class="table">
            <thead>
            <tr>
              <th>タイトル</th>
              <th>状態</th>
              <th>期限</th>
              <th></th>
            </tr>
            </thead>
            <tbody>
              {% for task in tasks %}
                <tr>
                  <td>{{ task.title }}</td>
                  <td>
                    <span 
                    class="label {% if task.status == 1 %}label-danger{% endif %}{% if task.status == 2 %}label-info{% endif %}"
                    >
                    {{ task.get_status_display }}
                    </span>
                  </td>
                  <td>{{ task.due_date }}</td>
                  <td><a href="{% url 'tasks.edit' id=current_folder_id task_id=task.id %}">編集</a></td>
                </tr>
              {% endfor %}
            </tbody>
          </table>
          <div class="panel-body">
            <div class="text-right">
              <a href="{% url 'tasks.create' id=current_folder_id  %}" class="btn btn-default btn-block">
                タスクを追加する
              </a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
{% endblock %}
```

#おわりに
これでこのチャプターは終わりです！
ここまでのコードは、リポジトリの[chapter4ブランチ](https://github.com/takusugar0/django_todo/tree/chapter4)にあります。
次のチャプターではタスク編集機能を実装していきます！

##記事一覧
[DjangoでTodoアプリを作る①Dockerで環境を構築する](https://qiita.com/takos/items/b9ba0b60c6f71b428aac)
[DjangoでTodoアプリを作る②フォルダ一覧ページの作成](https://qiita.com/takos/items/3e480183e72f41b69b84)
[DjangoでTodoアプリを作る③タスク一覧ページの作成](https://qiita.com/takos/items/828283aad5e5e2d8573d)
[DjangoでTodoアプリを作る④フォルダー、タスク作成機能の実装](https://qiita.com/takos/items/ce580c5781956a7c7eba)
[DjangoでTodoアプリを作る⑤タスク編集機能の作成](https://qiita.com/takos/items/dc4b91784cf895cc71c3)