13
25

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 5 years have passed since last update.

Djangoでタスク管理アプリをつくりたい! タスク管理編1

Posted at

はじめに

前回(ユーザ情報編)で使用したプロジェクトを引き継いで作成する。

開発環境

・Windows10 professional 64bit
・Python 3.7.1
・Django 2.1.2
・MySQL 8.0.12

タスク管理

タスク登録

タスクModelの作成

models.pyにタスク用のモデルと,プロジェクトとタスクを結びつけるモデルを作成する

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を編集し登録画面を作成する

our_project.html(編集前)
<div style="margin:20px"></div>
    <h4>タスクがありません</h4>
</div>
our_project.html(編集後)
<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%;"> &emsp; <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も編集する

modal.js
// タスク追加モーダル管理
$(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.js
// プロジェクトタスク追加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も追加します

slider.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;
}
adjustment.css
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.css
#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に追加する

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でパスを作成する

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に作成する

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関連はクラスを作りそこで処理するように変更した。

画面確認

image.png

タスク追加ボタンを押しタスク情報を入力して登録する
image.png
image.png

新しいタスクが表示される
image.png

優先度によって左端の色が変わる
image.png

タスク詳細を表示

画面の作成

詳細を表示する画面モーダルとAjaxで作成する

htmlに以下を追加

our_project.html
<div id="modal-task-details"></div>
<button type="button" id="TDI" style="display:none;"></button>

各AjaxとCSSを編集

modal.js
// タスク詳細モーダル管理
$(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.js
// タスク詳細取得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.css
#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.pyviews.pyを編集しタスク情報を取得する処理を作成

urls.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')  ## 追加
]
views.py
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)とは

画面確認

タスクを押すと画面が詳細が表示されることが確認できる
image.png

今回のまとめ

今回はこのシステムのメインとなるタスク登録を作成した。登録自体は簡単だが,使いやすさや画面遷移の少なさを意識して作ってみた。今後はこの考えをベースに作っていきたい。
また,仕事のほうが忙しくなり,空き時間にプログラムを適当に触っていたためMVCモデルからかけ離れた構成になってしまったので,続きを作る前に構成を整理する。

シリーズ

Djangoでタスク管理アプリをつくりたい! 環境構築編
Djangoでタスク管理アプリをつくりたい! ユーザ認証編
Djangoでタスク管理アプリをつくりたい! ユーザ情報編
Djangoでタスク管理アプリをつくりたい! プロジェクト管理編
・Djangoでタスク管理アプリをつくりたい! タスク登録編1

13
25
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
13
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?