1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-26

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

##記事一覧
DjangoでTodoアプリを作る①Dockerで環境を構築する
DjangoでTodoアプリを作る②フォルダ一覧ページの作成
DjangoでTodoアプリを作る③タスク一覧ページの作成
DjangoでTodoアプリを作る④フォルダー、タスク作成機能の実装
DjangoでTodoアプリを作る⑤タスク編集機能の作成

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

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

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

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

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を作成するために必要なformsFolderモデルを1,2行目でインポートしています。
__init__の部分で、FolderFormのクラス名をform-controlにしています。これは、bootstrapを適用させるためです。

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

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

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

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

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

##テンプレート
templatesディレクトリ下にcreate_folders.htmlを作成します。
create_folders.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メソッドを追加します。

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

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

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

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

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タスクを追加するの部分にリンクを以下のように追加します。

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

##テンプレート
templatesディレクトリ下にcreate_tasks.htmlを作成します。
create_tasks.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メソッドを追加します。

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を以下のように編集します。

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.htmlcreate_tasksindex.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 %}
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 %}


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ブランチにあります。
次のチャプターではタスク編集機能を実装していきます!

##記事一覧
DjangoでTodoアプリを作る①Dockerで環境を構築する
DjangoでTodoアプリを作る②フォルダ一覧ページの作成
DjangoでTodoアプリを作る③タスク一覧ページの作成
DjangoでTodoアプリを作る④フォルダー、タスク作成機能の実装
DjangoでTodoアプリを作る⑤タスク編集機能の作成

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?