はじめに
前回(ユーザ情報編)で使用したプロジェクトを引き継いで作成する。
開発環境
・Windows10 professional 64bit
・Python 3.7.1
・Django 2.1.2
・MySQL 8.0.12
タスク管理
タスク登録
タスクModelの作成
models.py
にタスク用のモデルと,プロジェクトとタスクを結びつけるモデルを作成する
class Task(models.Model):
PRIOLITY_NUMBER = (
(0, '未選択'),
(1, '最低'),
(2, '低'),
(3, '常'),
(4, '高'),
(5, '最高')
)
task_cd = models.AutoField(
primary_key=True
)
task_name = models.CharField(
max_length=100,
verbose_name='タスク名'
)
user_name = models.CharField(
max_length=30,
null=True,
blank=True,
verbose_name='担当者名'
)
user = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
verbose_name='担当者'
)
start_date = models.DateField(
null=True,
blank=True,
verbose_name='開始日'
)
end_date = models.DateField(
null=True,
blank=True,
verbose_name='終了日'
)
details = models.CharField(
max_length=200,
null=True,
blank=True,
verbose_name='詳細'
)
priolity = models.IntegerField(
choices=PRIOLITY_NUMBER,
default=0
)
update_date = models.DateField(
auto_now=True
)
is_delete = models.BooleanField(
default=False
)
""" プロジェクト To タスク (1 対 多) """
class ProjectToTask(models.Model):
project_cd = models.ForeignKey(
Project,
on_delete=models.CASCADE
)
task_cd = models.ForeignKey(
Task,
on_delete=models.CASCADE
)
登録画面を作成
our_project.html
を編集し登録画面を作成する
<div style="margin:20px"></div>
<h4>タスクがありません</h4>
</div>
<div id="taskList">
{% if task == None %}
<ul>
<li>タスクがありません</li>
</ul>
{% else %}
<ul id="taskAccordion">
{% for content in task %}
<li class="selectTask task{{content.priolity}}" value="{{content.task_cd}}"> {{content.task_name}} </li>
{% endfor %}
</ul>
{% endif %}
</div>
<div id="modal-task-add">
<h4>追加タスク</h4>
<form name="addTask" method="POST" action="{% url 'task:add_task' project.0.pk %}">
<table class="table">
<tr>
<th>タスク名</th>
<td><input type="text" name="taskName" style="width:80%;"></td>
</tr>
<tr>
<th>担当</th>
<td>
<select name="user" id="selectC" style="width:80%;">
<option value="">---</option>
<option value={{ project.0.leader.pk }}>{{ project.0.leader }}</option>
{% for person in member %}
<option value={{ person.pk }}>{{ person }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<th></th>
<td><input type="text" id="writeCN" name="userName" style="width:80%;"></td>
</tr>
<tr>
<th>詳細</th>
<td><textarea name="details" rows="4" cols="50" style="width:80%;"></textarea></td>
</tr>
<tr>
<th>開始日</th>
<td>
<input type="date" name="startDate" style="width: 80%;">
</td>
</tr>
<tr>
<th>終了日</th>
<td>
<input type="date" name="endDate" style="width: 80%;">
</td>
</tr>
<tr>
<th>優先度</th>
<td>
<input type="range" name="priolity" min="1" max="5" value="3" style="width: 80%;">   <span id="priolityNo" style="font-size:14px;">3</span>
<br>
<p>数値が大きいほど優先度が高いです</p>
</td>
</tr>
</table>
{% csrf_token %}
</form>
<button type="button" id="CTBC" class="btn btn-primary" style="margin-right:10px;">キャンセル</button>
<button type="button" id="TaskAddAjax" class="btn btn-success" id="addTask">追加</button>
</div>
タスク登録画面はモーダルで制御し,Ajaxで通信するのでJavaScriptも編集する
// タスク追加モーダル管理
$(function () {
//追加ボタンを押した際
$('#CTB').click(function () {
//背景を付ける
$("body").append('<div id="modal-bg"></div>');
//モーダルウィンドウを表示
$("#modal-bg,#modal-task-add").fadeIn("fast");
//画面の背景もしくは戻るボタンを押したらモーダルを閉じる
$('#modal-bg, #CTBC').click(function () {
$("#modal-bg,#modal-task-add").fadeOut("fast", function () {
$('#modal-bg').remove();
});
});
});
});
// プロジェクトタスク追加Ajax
$(function () {
$('#TaskAddAjax').click(function () {
//必須事項チェック
let taskName = $('input[name=taskName]').val();
if (taskName != "") {
var form = $('form[name=addTask]');
$.ajax({
url: form.prop("action"),
method: form.prop("method"),
data: form.serialize(),
dataType: "JSON",
})
.done(function (data) {
var result = JSON.parse(data['taskdata'])
$('#taskAccordion').empty()
$.each(result, function (k, v) {
let taskData = v["fields"]
let taskHtml = '<li class="task' + taskData["priolity"] + '">' + taskData["task_name"] + '</li>'
$('#taskAccordion').append(taskHtml)
})
})
} else {
alert('タスク名を入力してください')
}
})
})
CSSも追加します
input[type=range].vertical {
-webkit-appearance: none;
background-color: #0099FF;
width: 150px;
height: 4px;
/*margin-top: 100px;*/
-webkit-transform:rotate(-90deg);
-moz-transform:rotate(-90deg);
-o-transform:rotate(-90deg);
-ms-transform:rotate(-90deg);
transform:rotate(-90deg);
z-index: 0;
}
input[type=range] {
-webkit-appearance: none;
background-color: #0099FF;
height: 4px;
width: 300px;
border: none;
border-radius: 4px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: #0099FF;
border: none;
height: 30px;
width: 30px;
border-radius: 50%;
opacity: 0.8;
}
input[type=range]:focus{
outline: none;
}
input[type=range]:hover{
background: #B2E0FF;
}
ul, li {
padding: 0px;
margin: 0px;
}
#taskList ul li,#taskList ol li{
border-left: solid 8px black;/*左側の線*/
border-bottom: solid 2px #dadada;/*下に灰色線*/
background: #494949;/*背景色*/
margin-bottom: 5px;/*下のバーとの余白*/
border-radius: 15px 15px 15px 15px;
padding: 20px;
list-style-type: none!important;
color: white;
margin: 5px 50px;
}
#taskList .task5{
border-left: solid 8px rgb(252, 44, 44);/*左側の線*/
}
#taskList .task4{
border-left: solid 8px rgb(252, 255, 70);/*左側の線*/
}
#taskList .task3{
border-left: solid 8px rgb(85, 250, 98);/*左側の線*/
}
#taskList .task2{
border-left: solid 8px rgb(100, 253, 253);/*左側の線*/
}
#taskList .task1{
border-left: solid 8px rgb(56, 53, 255);/*左側の線*/
}
.inDetail{
display: none;
}
#taskAccordion li {
cursor: pointer;
}
#modal-task-add{
display: none;
width: 50%;
height: 100%;
padding:100px 20px 20px 20px;
border-radius:10px;
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: auto;
background-color: white;
position:fixed;
z-index: 2;
overflow:auto;
}
追加したcssやJavaScriptをbase.html
に追加する
<link href="{% static 'css/slider.css' %}" rel="stylesheet" type="text/css">
<script src="{% static 'js/ajax.js' %}"></script>
画面の準備が完了した
登録処理
Ajaxによって渡されたタスク情報を登録し画面に表示する処理を作成する
URLルートの作成
task/urls.py
でパスを作成する
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
path('build_project/', views.BuildProject.as_view(), name='build_project'),
path('ProjectPage/<int:pk>/', views.ProjectPage.as_view(), name='our_project'),
path('ProjectPage/<int:pk>/detail', views.ProjectDetail.as_view(), name='our_project_detail'),
path('ProjectPage/<int:pk>/update', views.ProjectUpdate.as_view(), name='our_project_update'),
path('ProjectPage/<int:pk>/delete', views.ProjectDelete.as_view(), name='our_project_delete'),
path('ProjectPage/<int:pk>/update_member', views.UpdateProjectMember.as_view(), name='update_member'),
path('ProjectPage/<int:pk>/delete_member', views.ProjectDeleteMember.as_view(), name='our_project_delete_member'),
path('ProjectPage/<int:pk>/AATask', views.AddTaskForAjax.ajax_response, name='add_task'), ## 追加
]
登録して表示する関数の作成
タスクを登録しプロジェクト内のタスク情報を取得する関数をviews.py
に作成する
from django.http.response import JsonResponse
from django.core import serializers
from .models import ProjectToUsers, Project, ProjectToTask, Task
""" タスク追加 """
class AddTaskForAjax():
def ajax_response(self, *args, **kwargs):
taskName = self.POST['taskName']
if self.POST['user'] != "":
user = User.objects.get(pk=self.POST['user'])
else:
user = None
if user == None:
userName = self.POST['userName'] if self.POST['userName'] != "" else None
else:
userName = None
details = self.POST['details'] if self.POST['details'] != "" else None
startDate = self.POST['startDate'] if self.POST['startDate'] != "" else None
endDate = self.POST['endDate'] if self.POST['endDate'] != "" else None
priolity = self.POST['priolity'] if self.POST['priolity'] != "" else None
task = Task.objects.create(
task_name=taskName, user=user,
user_name=userName, details=details,
start_date=startDate, end_date=endDate,
priolity=priolity
)
projectCd = kwargs['pk']
project = Project.objects.get(project_cd=projectCd)
result = ProjectToTask.objects.create(
project_cd=project, task_cd=task
)
projectTask = ManupilateDataBase.getProjectTask(projectCd)
json_serializer = serializers.get_serializer("json")()
taskData = json_serializer.serialize(projectTask, ensure_ascii=False)
return JsonResponse({"taskdata" : taskData})
""" DB操作クラス """
class ManupilateDataBase():
def getJoinProject(joinCd):
p_user = ProjectToUsers.objects.filter(user_cd=joinCd)
if len(p_user) > 0:
result = []
for p in p_user:
member = Project.objects.filter(project_cd=p.project_cd.pk, is_delete=0)
result.extend(member)
else:
result = None
return result
def getProjectTask(projectCd):
p_task = ProjectToTask.objects.filter(project_cd=projectCd)
if len(p_task) > 0:
result = []
for t in p_task:
task = Task.objects.filter(pk=t.task_cd.pk)
result.extend(task)
else:
result = None
return result
def getProjectMenber(memberCd):
p_user = ProjectToUsers.objects.filter(project_cd=memberCd)
if len(p_user) > 0:
result = []
for person in p_user:
member = User.objects.filter(pk=person.user_cd.pk)
result.extend(member)
else:
result = None
return result
def getTaskInfo(taskCd):
task = Task.objects.filter(task_cd=taskCd)
if len(task) > 0:
return task
else:
return None
def getUserInfo(userCd):
user = User.objects.filter(use_cd=userCd)
if len(user) > 0:
return user
else:
return None
渡されたタスク情報を登録し,登録したタスクとプロジェクトを結びつけるそして,プロジェクトに結びついているタスク情報を取得してJSON形式に変換して返すという処理になっている。
DB関連はクラスを作りそこで処理するように変更した。
画面確認
タスク詳細を表示
画面の作成
詳細を表示する画面モーダルとAjaxで作成する
htmlに以下を追加
<div id="modal-task-details"></div>
<button type="button" id="TDI" style="display:none;"></button>
各AjaxとCSSを編集
// タスク詳細モーダル管理
$(function () {
//追加ボタンを押した際
$('#TDI').click(function () {
//背景を付ける
$("body").append('<div id="modal-bg"></div>');
//モーダルウィンドウを表示
$("#modal-bg,#modal-task-details").fadeIn("fast");
//画面の背景もしくは戻るボタンを押したらモーダルを閉じる
$('#modal-bg, #CTDI').click(function () {
$("#modal-bg,#modal-task-details").fadeOut("fast", function () {
$('#modal-bg').remove();
});
});
});
});
// タスク詳細取得Ajax
$(function () {
$('.selectTask').click(function () {
// タスクコードを取得
let task_cd = this.value;
if (task_cd != "") {
$.ajax({
url: "/task/ajax/getProjectTaskInfo",
method: "POST",
data: {task_cd : task_cd},
dataType: "JSON",
})
.done(function (data) {
var result = JSON.parse(data['taskdata'])
$('#modal-task-details').empty()
$.each(result, function (k, v) {
let taskData = v["fields"]
let userName = ""
let details = ""
let period = ""
let taskHtml = ""
let formHtml = ""
if (taskData['user'] == "" || taskData['user'] == null) {
taskData['user'] = 0;
}
if (taskData['user_name'] != null) {
userName = taskData['user_name']
}
if (taskData['details'] != null) {
details = taskData['details'];
}
if (taskData['start_date'] != null) {
period += taskData['start_date']
}
if (period != "") {
period += " ~ "
}
if (taskData['end_date'] != null) {
period += taskData['end_date']
}
taskHtml = "<h4>" + taskData['task_name'] + "</h4>"
+ "<table class='table'>"
+ " <tr>"
+ " <th>担当</th>"
+ " <td>" + userName + "<span style='display:none'>" + taskData['user'] + "</span></td>"
+ " </tr>"
+ " <tr>"
+ " <th>詳細</th>"
+ " <td>" + details + "</td>"
+ " </tr>"
+ " <tr>"
+ " <th>期間</th>"
+ " <td>" + period + "</td>"
+ " </tr>"
+ "</table>"
$('#modal-task-details').append(taskHtml)
formHtml = "<button type='button' id='CTDI' class='btn btn-primary' style='margin-right:10px;'>閉じる</button>"
+ "<button type='button' class='btn btn-primary' style='margin-right:10px;'>変更</button>"
+ "<button type='button' class='btn delete-btn'>削除</button>"
$('#modal-task-details').append(formHtml)
})
$('#TDI').click();
})
}
})
})
#modal-task-add, #modal-task-details{
display: none;
width: 50%;
height: 100%;
padding:100px 20px 20px 20px;
border-radius:10px;
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: auto;
background-color: white;
position:fixed;
z-index: 2;
overflow:auto;
}
詳細情報取関数を作成
urls.py
とviews.py
を編集しタスク情報を取得する処理を作成
from django.urls import path
from . import views
app_name = 'task'
urlpatterns = [
path('', views.taskTop.as_view(), name='task_top'),
path('build_project/', views.BuildProject.as_view(), name='build_project'),
path('ProjectPage/<int:pk>/', views.ProjectPage.as_view(), name='our_project'),
path('ProjectPage/<int:pk>/detail', views.ProjectDetail.as_view(), name='our_project_detail'),
path('ProjectPage/<int:pk>/update', views.ProjectUpdate.as_view(), name='our_project_update'),
path('ProjectPage/<int:pk>/delete', views.ProjectDelete.as_view(), name='our_project_delete'),
path('ProjectPage/<int:pk>/update_member', views.UpdateProjectMember.as_view(), name='update_member'),
path('ProjectPage/<int:pk>/delete_member', views.ProjectDeleteMember.as_view(), name='our_project_delete_member'),
path('ProjectPage/<int:pk>/AATask', views.AddTaskForAjax.ajax_response, name='add_task'),
path('ajax/getProjectTaskInfo',views.getProjectTaskInfoAjax.ajax_response, name='getPJTask') ## 追加
]
from django.views.decorators.csrf import csrf_exempt
""" プロジェクトタスク情報取得 """
class getProjectTaskInfoAjax():
@csrf_exempt
def ajax_response(self, *args, **kwargs):
task_cd = self.POST['task_cd']
projectTask = ManupilateDataBase.getTaskInfo(task_cd)
if (projectTask[0].user != None):
projectTask[0].user_name = projectTask[0].user.username
json_serializer = serializers.get_serializer("json")()
taskData = json_serializer.serialize(projectTask, ensure_ascii=False)
return JsonResponse({"taskdata" : taskData})
@csrf_exempt
を関数につけることによりCSRF検証をはずせるので,POSTされた値にCSRFトークンがなくても処理が行われる。ただし,検証がないのでCSRFを受ける可能性が上がる
※CSRF(Cross-Site Request Forgeries)とは
画面確認
今回のまとめ
今回はこのシステムのメインとなるタスク登録を作成した。登録自体は簡単だが,使いやすさや画面遷移の少なさを意識して作ってみた。今後はこの考えをベースに作っていきたい。
また,仕事のほうが忙しくなり,空き時間にプログラムを適当に触っていたためMVCモデルからかけ離れた構成になってしまったので,続きを作る前に構成を整理する。
シリーズ
・Djangoでタスク管理アプリをつくりたい! 環境構築編
・Djangoでタスク管理アプリをつくりたい! ユーザ認証編
・Djangoでタスク管理アプリをつくりたい! ユーザ情報編
・Djangoでタスク管理アプリをつくりたい! プロジェクト管理編
・Djangoでタスク管理アプリをつくりたい! タスク登録編1