次は、フォルダー、タスク作成機能を実装していきます。
##記事一覧
DjangoでTodoアプリを作る①Dockerで環境を構築する
DjangoでTodoアプリを作る②フォルダ一覧ページの作成
DjangoでTodoアプリを作る③タスク一覧ページの作成
DjangoでTodoアプリを作る④フォルダー、タスク作成機能の実装
DjangoでTodoアプリを作る⑤タスク編集機能の作成
#フォルダー作成機能の実装
##URLの設定
まず、以下の定義を踏まえてURLを設定していきます。
URL | 処理 |
---|---|
/folders/create | フォルダーを新たに作成する |
この設計にするために、todo/urls.py
に次の一文を加えてください。
path('create', views.create_folder, name='folders.create')
##フォームの作成
まず、todo
ディレクトリの下に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
のフォルダーを追加する
の部分にリンクを以下のように追加します。
<a href="{% url 'folders.create' %}" class="btn btn-default btn-block">
##テンプレート
templates
ディレクトリ下にcreate_folders.html
を作成します。
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対策を行っています。
{% csrf_token %}
また、以下の部分でフォルダー作成のためのフォームを展開しています。
{{ form.as_p }}
as_p
とは、
<p>
formの内容
</p>
という形で展開されるということです。
次に、viewを書いていきます。
##View
viewに以下のcreate_folder
メソッドを追加します。
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です!
#タスク作成機能の実装
##URLの設定
まず、以下の定義を踏まえてURLを設定していきます。
URL | 処理 |
---|---|
<int:id>/tasks/create | タスクを新たに作成する |
この設計にするために、todo/urls.py
に次の一文を加えてください。
path('<int:id>/tasks/create', views.create_task, name='tasks.create')
##フォームの作成
forms.pyでタスク作成のためのフォームを作成します。
以下の、TaskFormクラスを追加します
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
のタスクを追加する
の部分にリンクを以下のように追加します。
<a href="{% url 'tasks.create' id=current_folder
_id %}" class="btn btn-default btn-block">
##テンプレート
templates
ディレクトリ下にcreate_tasks.html
を作成します。
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
メソッドを追加します。
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
を以下のように編集します。
{% 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
を以下のように編集します。
{% 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 %}
{% 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 %}
{% 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アプリを作る⑤タスク編集機能の作成