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  | 処理 |
|:---|:---|
|/foleders/{フォルダのID}/tasks |フォルダー一覧ページを表示する |

この設計を踏まえて、`django_todo/urls.py`、`todo/urls.py`を編集していきます。

##django_todo/urls.py
まず、include関数を使いたいので、`from django.urls…`の行を変更し、インポートを追加します。そして、`todo.urls` をインポートする行を追加します。結果、`django_todo/urls.py`は以下のようになります。

```python:django_todo/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('folders/', include('todo.urls')),
]
```
urlが'folders/'だった場合は、`todo/urls.py`を参照しなされという意味です。
その`todo/urls.py`は次のように定義します。

##todo/urls.py
`todo` ディレクトリの下に、新しく `urls.py` という空のファイルを作ります。

```
$ touch todo/urls.py
```
作成した、`todo/urls.py`を以下のように編集します。

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

urlpatterns = [
    path('<int:id>/tasks', views.index, name='tasks.index'),
]
```
最初の２行は、Djangoの path 関数と、todoの全ての ビュー（今は１つもありません。これから作っていきます！）をインポートするという意味です。

アドレスの文字列の部分にある< int:id >が引数の型と名称を指定しています。その結果、この部分に現れるint型の値が、idという名前で取得され，対応するview関数（ここでは、view.pyのindex関数）に引数として渡されます。

`urlpatterns = ...`の部分において、folders/の下に`< int:id >/tasks`がある、つまり、`folders/< 整数 >/tasks`というアドレスが来ると、view.pyのindex関数を呼び出すという処理をします。

`name=`では、このURLパターンに名前をつけています。

#モデルの定義
folderテーブルの定義は以下のようにします。

|カラム論理名  |カラム物理名  | 
|---|---| 
|ID  |id  | 
|タイトル  |title  | 
|作成日  |created_at  | 

このテーブル定義をもとに、`todo/models.py`を編集していきます。

```python:todo/models.py
from django.conf import settings
from django.db import models
from django.utils import timezone


class Folder(models.Model):
    title = models.CharField(max_length=20)
    created_at = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title
```
idについての記述がありませんが、Djangoでは自動的に定義されます。詳細は、[モデル |Djangoドキュメント](https://docs.djangoproject.com/ja/2.2/topics/db/models/#automatic-primary-key-fields)が詳しいです。
定義したフィールドの内容は以下の通りです。

| フィールド | どんなフィールドか |
|---|---| 
|models.CharField | 文字数が制限されたテキストを定義するフィールド |
| models.DateTimeField |– 日付と時間のフィールド

\_\_str__関数では、管理画面などで表示される文字列を定義しています。([models.pyに記述されたdef \_\_str\_\_(self)とは？](https://code-schools.com/django-str/))


#view関数の定義
`todo.py`にビューを記述していきます。
以下のように編集してください。

```python:todo/views.py
from django.shortcuts import render
from django.utils import timezone
from .models import Folder

def index(request, id):
    folders = Folder.objects.filter(created_at__lte=timezone.now()).order_by('created_at')
    return render(request, 'index.html', {'folders':folders})
```
一行目で[render関数](https://docs.djangoproject.com/ja/3.0/topics/http/shortcuts/#render)をインポートしています。
二行目では、`models.py`で定義した`Folder`モデルをインポートしています。



[render関数](https://docs.djangoproject.com/ja/3.0/topics/http/shortcuts/#render)では、第一引数にrequest、第二引数にテンプレートファイル名を記述しています。
第3引数では、テンプレートに渡すデータについて記述しています。renderの第3引数に辞書を渡すことでtemplateに値を渡すことができます。
template内で渡された値を使う場合は{{ folders }}の形式で書きます。

※「render」は、「与える」、「差し出す」といった意味を持った英単語。requestやfoldersをindex.htmlに「差し出す」という意味で捉えるとわかりやすそう。

#templateの作成
テンプレートとはアプリケーションがレスポンスする HTML の雛形で、制御構文（if や foreach など）や変数の展開を記述することができます。ページの枠組みだけ用意して、URL に応じて変わる箇所だけが穴埋めになっているイメージ。

以下のコマンドで`todo`ディレクトリに`templates`ディレクトリを作成し、その中に`index.html`を作成します。

```
$ mkdir todo/templates
$ touch todo/templates/index.html
```

`index.html`は以下のように編集してください。

```html:index.html
<!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">
  
</head>
<body>
<header>
  <nav class="my-navbar">
    <a class="my-navbar-brand" href="folders/1/tasks">Todo</a>
  </nav>
</header>
<main>
  <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="#" class="btn btn-default btn-block">
              フォルダーを追加する
            </a>
          </div>
        </nav>
      </div>
      <div class="column col-md-8">
        <!-- タスクはここに表示される -->
      </div>
    </div>
  </div>
</main>
</body>
</html>
```
head部分については[HTML5のhead内の記述](https://qiita.com/yuh/items/49ba589fc014d7be542a)が詳しいです。

上記のhtmlの記述の中で、注目すべきところは以下の部分。

```html:index.html
{% 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 %}
```
テンプレートの中では、{%  %}で囲むことによってpythonのようにfor文を使用することができます。

href = ... では、`todo/urls.py`の path() 関数の name 引数を定義していた（ここでは、`tasks.index`）ので、テンプレートタグの` {％url％}` を使用してリンク先を表しています。

```html
class="list-group-item {% if current_folder_id == folder.id %}active{% endif %}"
```

この部分では、現在選択されているフォルダのIDをactiveに設定しています。

#CSSの作成
まず、以下のようなディレクトリ構成になるようにtodoディレクトリ配下にstaticディレクトリ、cssディレクトリ、style.cssファイルを作成します。

```
todo
├── static
│   └── css
　   　   └── style.css
```

style.cssは以下のように記述します。

```css:style.css
body {
    background-color: #b4e7cf;
  }

  .navbar {
    margin: 2rem 0 2.5rem 0;
  }

  .my-navbar {
    align-items: center;
    background: #474a2c;
    display: flex;
    height: 6rem;
    justify-content: space-between;
    padding: 0 2%;
    margin-bottom: 3rem;
  }

  .my-navbar-brand {
    font-size: 20px;
  }

  .my-navbar-brand,
  .my-navbar-item {
    color: #b4e7cf;
  }

  .my-navbar-brand:hover,
  a.my-navbar-item:hover {
    color: #ffffff;
  }

  .panel-default>.panel-heading {
    color: #474a2c;
    background-color: #9bdeac;
    border-color: #e6e9ed;
}

a.list-group-item:hover {
  color: #fff;
  background-color: #474a;
}

a.list-group-item.active,
a.list-group-item.active:focus,
a.list-group-item.active:hover{
  color: #fff;
  background-color: #474a;
  border-color: #e6e9ed;
}

a {
  color: #474a;
  text-decoration: none;
}

a:hover{
  color: #4909;
}

.btn-default:hover {
  color: #fff;
  background-color: #474a;
}

.btn{
  border-color: #474a;
}

.btn-primary {
  background-color: #474a;
  border-color: #474a;
}

.btn-primary:hover {
  background-color: #4909;
}

  .table td:nth-child(2),
  .table td:nth-child(3),
  .table td:nth-child(4) {
    white-space: nowrap;
    width: 1px;
  }

  .form-control[disabled],
  .form-control[readonly] {
    background-color: #fff;
  }
```

`index.html`にCSSを読み込ませるため、index.htmlを編集します。
1行目に以下を追加してください。

```html:index.html
{% load static %}
```
`head`部分に以下を追加してください。

```html:index.html
<link rel="stylesheet" href="{% static 'css/style.css' %}">
```

#マイグレーション
次のコマンドでマイグレーションファイルを作成します。以下のようになればOKです。

```
$ docker-compose run web python3 manage.py makemigrations
Starting django_todo_db_1 ... done
Migrations for 'todo':
  todo/migrations/0001_initial.py
    - Create model Folder
```

コマンド実行後、以下のようなマイグレーションファイルが作成されます。

```python:todo/migrations/0001_initial.py
# Generated by Django 2.2.12 on 2020-04-16 22:22

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Folder',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=20)),
                ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
            ],
        ),
    ]
```

このファイルをマイグレートすることによって、データベースを作成することができます。

```
$ docker-compose run web python3 manage.py migrate       
Starting django_todo_db_1 ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
  Applying todo.0001_initial... OK
```

#adminページの作成
作成したFolderクラスにおいて追加、編集、削除するのにDjango adminを使います。
`todo/admin.py`ファイルを、内容を次のように編集してください。

```python:todo/admin.py
from django.contrib import admin
from .models import Folder

admin.site.register(Folder)
```
先ほど定義したFolderモデルをimportしています。 また、モデルをAdminページ（管理画面）上で見えるようにするため、admin.site.register(Folder)でモデルを登録しています。
そして、http://localhost:8000/admin/ にアクセスしてみてください。　
次のようなログインページが表示されます。 
![Screenshot from 2020-03-17 16-41-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/13246392-6072-3d66-c6ff-b62aff36d840.png)
ログインするには、superuser （サイトの全てを管理するユーザー）を作る必要があります。 
以下のコマンドでsuperuserを作成します。

```
$ docker-compose run web python3 manage.py createsuperuser
```
ユーザーネーム、メールアドレス、パスワードを入力していき、

```
Superuser created successfully.
```
となればユーザーの作成が完了です。
adminページに戻り、ログインすると以下のようなページが表示されます。

![Screenshot from 2020-04-17 07-29-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/c24c23ae-fc5b-a0c6-27f4-ed2566430270.png)




Foldersの、「追加」をクリックすると次のページが出てくるはずです。

![Screenshot from 2020-04-17 07-31-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/1d0701c5-986a-72b4-ae2b-b1b35b92daa9.png)


なんでもよいので、2,3個folderを追加しておきましょう。ぼくは、「プログラミング」、「大学の課題」、「その他」を追加しておきました:v:
次のコマンドでデータが挿入されていることが確認できます。詳しくは[Django ORM(クエリセット) ・Django Girls Tutorial](https://tutorial.djangogirls.org/ja/django_orm/)

```
$ docker-compose run web python3 manage.py shell
Starting django_todo_db_1 ... done
Python 3.8.2 (default, Feb 26 2020, 14:58:38) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from todo.models import Folder
>>> Folder.objects.all()
<QuerySet [<Folder: プログラミング>, <Folder: 大学の課題>, <Folder: その他>]>
```



#確認
`$ docker-compose up -d`でサーバーを立ち上げて、http://localhost:8000/folders/1/tasks にアクセスしてみましょう。
以下のようになっていたらOKです！
![Screenshot from 2020-04-17 09-22-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/8ae47052-eeff-7c4c-7aa6-c8799f54057d.png)

#おわりに
これでこのチャプターは終わりです！
ここまでのコードは、[リポジトリのchapter2ブランチ](https://github.com/takusugar0/django_todo/tree/chapter2)にあります。
次のチャプターではタスク一覧ページを実装していきます！
[DjangoでTodoアプリを作る③タスク一覧ページの作成](https://qiita.com/takos/items/828283aad5e5e2d8573d)

##記事一覧
[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)

##参考文献
[Djangoで外部キー制約を記述する](https://qiita.com/RyoMa_0923/items/89a490d2bc3fb34f123a)