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)

#マイグレーションの実行
まず、タスクテーブルを作成していきます。
タスクテーブルの定義は以下のようにします。

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


これをもとに`models.py`を編集していきます。以下のTaskモデルを追加します。

```todo/models.py
class Task(models.Model):
    STATUS_CHOICES = [(1, '未完了'),(2, '作業中'),(3, '完了')]

    title = models.CharField(max_length=100)
    status = models.IntegerField(choices=STATUS_CHOICES, default=1)
    due_date =  models.DateField(default=timezone.now)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(blank=True, null=True)
    folder_id = models.ForeignKey(Folder, on_delete = models.CASCADE)

    def publish(self):
        self.update_at = timezone.now()
        self.save()

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

ここでのポイントは2つ。  

1つめは、`status`の部分です。

```python:todo/models.py
STATUS_CHOICES = [(1, '未完了'),(2, '作業中'),(3, '完了')]

status = models.IntegerField(choices=STATUS_CHOICES, default=1)
```
statusをIntergerFieldに設定しますが、defaultの値として1を設定しています。
タスクを作成した最初の状態は必ず「未完了」の状態なので、何も指定しないときは「1」が入るようにしています。
また、[choicesオプション](https://docs.djangoproject.com/en/3.0/ref/models/fields/#choices)によって、整数に未完了、作業中、完了を振り分けています。


２つめは`folder_id`の部分です。

```python:todo/models.py
folder_id = models.ForeignKey(Folder, on_delete = models.CASCADE)
```
ForeignKeyによって、FolderモデルとTaskモデルを紐付けています。
on_delete=...の部分では、CASCADEを設定することによって削除するオブジェクトに紐づいたオブジェクトを全て削除することができます。[こちらの記事](https://djangobrothers.com/blogs/on_delete/)が具体例が示されていてわかりやすいです。
(ちなみに、「cascade」は「何段も重なった小さな滝」という意味でありこれを覚えておくと、連なってオブジェクトが削除されることが理解しやすい)

ここまで来たら、`makemigraions`でマイグレーションファイルを作ります。
次のコマンドを実行してください。

```
$ docker-compose run web python3 manage.py makemigrations
```

実行すると以下のようになり、`todo/migrations`ディレクトリに新たにマイグレーションファイルが作成されます。

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


マイグレーションファイルを作成したので、次のコマンドを実行してマイグレートします。

```
$ docker-compose run web python3 manage.py migrate
```

実行すると以下のようになります。

```
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.0002_task... OK
```

#管理サイトの表示
マイグレートしたら、adminサイト( http://localhost:8000/admin/ )を確認してみましょう。
すると、TaskモデルをマイグレートしたはずなのにTaskに関する項目が出てきていません。

![Screenshot from 2020-04-17 10-48-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/b9c59c61-e5dd-6a1f-2f2d-d720b67294f9.png)


Taskモデルを表示するには、admin.pyでTaskモデルをインポートする必要があります。以下のようにadmin.pyに追記します。

```python:todo/aadmin.py
from django.contrib import admin
from .models import Folder, Task#追加

admin.site.register(Folder)
admin.site.register(Task)#追加
```

もう一度adminサイトにアクセスしてみると、下の画像のようにFoldersの下にTasksの項目が追加されていると思います。

![Screenshot from 2020-04-17 10-51-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/8d64f90f-9a3f-8315-68ca-187dda94ad83.png)

ここでも、管理サイト上でいくつかタスクを追加しておきましょう。


#Viewを書く
`views.py`を以下のように編集します。

```python:todo/views.py
from django.shortcuts import render, get_object_or_404#追加
from django.utils import timezone
from .models import Folder, Task#追加

def index(request, id):
    #すべてのフォルダを取得する
    folders = Folder.objects.filter(created_at__lte=timezone.now()).order_by('created_at')
    #選ばれたフォルダを取得する
    current_folder = get_object_or_404(Folder, id=id)
    #選ばれたフォルダのタスクを取得する
    tasks = Task.objects.filter(folder_id = current_folder.id)

    return render(request, 'index.html', {
        'folders':folders,
        'tasks':tasks,
        'current_folder_id': current_folder.id,
    })![Screenshot from 2020-04-17 11-03-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/2effc6d9-ccd6-3485-3eaa-4dceb2ae76da.png)

```


1行目で[get_object_or_404関数]
(https://docs.djangoproject.com/ja/3.0/topics/http/shortcuts/#get-object-or-404)をインポートします。
3行目では、`models.py`で定義したTaskモデルをインポートしています。

`get_object_or_404`関数では、Folderモデルにおける`pk(primary key)`、すなわち`id`に一致する`id`を受け取れば、`id`に応じたオブジェクトが`current_folder`に代入されます。
Folderモデルの`id`に存在しない`id`を受け取ったときは404エラーを吐きます。
例えば、Folderが3つしか挿入されていないのに、http://localhost:8000/folders/5/tasks にアクセスすると以下のように404エラーとなります。
![Screenshot from 2020-04-17 11-03-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/2a5d22fa-6fc4-b286-f7e6-c195c40e7d09.png)


次に、この部分について。

```python
tasks = Task.objects.filter(folder_id = current_folder.id)
```

ここでは、`filter`関数を用いてタスクの`folder_id`が、選ばれたフォルダの`id`と等しいようなタスクをすべて取得し、`tasks`に代入しています。

#テンプレートの編集
タスク一覧を表示するために、`templates/index.html`で`<!-- タスクはここに表示される -->`とコメントが書かれていたところに以下のコードを挿入してください。

```html:templates/index.html
<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="#">編集</a></td>
        </tr>
      {% endfor %}
    </tbody>
  </table>
  <div class="panel-body">
    <div class="text-right">
      <a href="#" class="btn btn-default btn-block">
        タスクを追加する
      </a>
    </div>
  </div>
</div>
```
注意するべきところは以下の部分だと思います。

```html
<span 
class="label {% if task.status == 1 %}label-danger{% endif %}{% if task.status == 2 %}label-info{% endif %}"
>
{{ task.get_status_display }}
</span>
```
choice属性で値を限定しているフィールドの「名前」を取得するには、get_status_displayメソッドを利用する必要があります。（get_status_displayメソッドは、勝手に定義されます。くわしくは、[Djangoのモデルでchoice属性で値を限定しているフィールドの「名前」を取得する方法](https://qiita.com/ryu22e/items/37bf4f5f6b60ccccebe2)）

また、if文を使って、task.statusが1のとき、つまり未完了のときは`label-danger`、task.statusが2のとき、つまり作業中のときは`label-info`とクラスを割り当て、cssをタスクの状態ごとに適用させています。

ここまできたら、http://localhost:8000/folders/1/tasks にアクセスして確認してみてください！
以下のようになっていればOKです！

![Screenshot from 2020-04-17 11-11-36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370196/b592cc03-5fae-d035-2a41-422ce63a3dc7.png)

#おわりに
これで、タスクを一覧表示することができました！
ここまでのコードは、[リポジトリのchapter3ブランチ](https://github.com/takusugar0/django_todo/tree/chapter3)にあります。
次は、フォルダー作成機能を実装していきます！

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