2024-12-22

ChatGPTを使って、アプリ開発(ワークフロー6):開発4

 前回のChatGPTの提案で作成したコードで開発環境サーバーを起動したところ、「django.core.exceptions.FieldError: Unknown field(s) (assigned_user) specified for Task」というエラーが出ました。これは、最初に作成した「Task」モデルに「assigned_user」というフィールドがないためです。次の担当者を示す「assigned_user」ですが、ワークフローの実行時に選択するようにするので、form.pyを以下のように変更します。

from django import forms
from .models import Flow, Task


class FlowForm(forms.ModelForm):
    class Meta:
        model = Flow
        fields = ['name']


class TaskForm(forms.ModelForm):
    class Meta:
        model = Task
        fields = ['name']さ

最初の画面の「新規作成」をクリックします。






「django.template.exceptions.TemplateDoesNotExist: flow_create.html」というエラーが発生しました。これは、「flow_create.html」というテンプレートとが存在しないためです。ChatGPTの指示で作成したテンプレートは「flow_create_edit.html」なので、views.pyを以下のように修正します。

# フロー作成ビュー
def flow_create(request):
    if request.method == 'POST':
        # フォームの処理を追加
        # 新しいフローを作成し保存する
        return redirect('flow_list')
    return render(request, 'workflow/flow_create_edit.html')

前の画面に戻って、再度、「新規作成」をクリックします。

以下のような画面が開きます。








フロー作成画面について、もう少し画面構成や機能を追加していきます。まずは、ChatGPTに要求を伝えます。


ChatGPTへのスクリプト:

タスクを作成するエリアを四角で囲み、その中に「START」と「END」というボックス(角丸のボックス)をデフォルトで作成し、「タスク作成」ボタンをクリックしたら、タスク名を入力する欄と、「入力項目設定」というボタンを中に含んだボックス(角丸のボックス)を追加したい。なお、タスクを作成するエリアは可変とし、画面オーバーした場合はスクロールできるようにする。デフォルトの大きさは、1200px×600pxにしたい。 「START」のボックスは左上に、「END」のボックスは右下に配置したい。


ChatGPTからの回答:

この要件に基づいて、Djangoのテンプレートに必要なHTMLとCSSを追加し、タスク作成エリアを実装します。タスクエリアは可変で、デフォルトのサイズが1200px × 600pxとなり、スクロールが可能な仕組みにします。また、「START」と「END」のボックスは指定の位置に配置し、タスクを追加できるようにします。

1. HTMLとCSSのテンプレート

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>フロー作成</title>
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
    <style>
        /* タスク作成エリアのスタイル */
        #task-area {
            width: 1200px;
            height: 600px;
            border: 2px solid #ccc;
            position: relative;
            overflow: auto;
            padding: 10px;
        }

        /* STARTとENDボックスのスタイル */
        .task-box {
            width: 150px;
            height: 50px;
            border-radius: 10px;
            background-color: #f0f0f0;
            border: 2px solid #333;
            display: flex;
            justify-content: center;
            align-items: center;
            position: absolute;
            text-align: center;
        }

        #start-box {
            top: 10px;
            left: 10px;
        }

        #end-box {
            bottom: 10px;
            right: 10px;
        }

        /* 新しいタスクボックスのスタイル */
        .new-task-box {
            width: 200px;
            height: 100px;
            border-radius: 10px;
            background-color: #e0f7fa;
            border: 2px solid #00796b;
            position: absolute;
            padding: 10px;
            box-sizing: border-box;
        }

        .new-task-box input[type="text"] {
            width: 90%;
            margin-bottom: 10px;
        }

        .new-task-box button {
            width: 100%;
        }

        /* タスクエリア全体がスクロール可能な場合のスタイル */
        #task-area::-webkit-scrollbar {
            width: 12px;
            height: 12px;
        }

        #task-area::-webkit-scrollbar-thumb {
            background-color: #888;
            border-radius: 10px;
        }

        #task-area::-webkit-scrollbar-track {
            background-color: #f0f0f0;
        }

    </style>
</head>
<body>
    <h1>フロー作成</h1>

    <!-- タスク作成エリア -->
    <div id="task-area">
        <!-- STARTボックス -->
        <div id="start-box" class="task-box">
            START
        </div>

        <!-- ENDボックス -->
        <div id="end-box" class="task-box">
            END
        </div>
    </div>

    <!-- タスク作成ボタン -->
    <button type="button" id="add-task-btn">タスク作成</button>

    <script>
        let taskCounter = 0;  // タスクIDカウンター

        // タスク作成ボタンのクリックイベント
        document.getElementById('add-task-btn').addEventListener('click', function() {
            taskCounter += 1;
            const taskArea = document.getElementById('task-area');

            // 新しいタスクボックスを作成
            const newTaskBox = document.createElement('div');
            newTaskBox.className = 'new-task-box';
            newTaskBox.style.top = (50 + taskCounter * 60) + 'px';  // 位置を動的に設定
            newTaskBox.style.left = (50 + taskCounter * 60) + 'px'; // 位置を動的に設定

            // タスク名入力欄を追加
            const taskInput = document.createElement('input');
            taskInput.type = 'text';
            taskInput.placeholder = 'タスク名を入力';

            // 入力項目設定ボタンを追加
            const settingsButton = document.createElement('button');
            settingsButton.textContent = '入力項目設定';

            // 新しいタスクボックスに要素を追加
            newTaskBox.appendChild(taskInput);
            newTaskBox.appendChild(settingsButton);

            // タスクエリアに新しいタスクボックスを追加
            taskArea.appendChild(newTaskBox);
        });
    </script>
</body>
</html>


2. 説明

  1. タスク作成エリア (#task-area):
    このエリアはスクロール可能で、デフォルトのサイズが1200px × 600pxです。overflow: autoで、内容がエリアを超える場合にスクロールできるようにしています。

  2. STARTENDのボックス:
    #start-box#end-boxというIDを持った角丸のボックスがそれぞれ左上(top: 10px; left: 10px;)と右下(bottom: 10px; right: 10px;)に配置されています。

  3. タスク作成ボタン:
    「タスク作成」ボタンをクリックすると、タスク名を入力する欄と「入力項目設定」ボタンを含んだ新しいタスクボックスがタスクエリアに動的に追加されます。ボックスは自動的に位置が調整されますが、最初はtopleftの位置を固定的にずらすことで配置しています。

  4. JavaScript:
    タスク作成ボタンをクリックすることで、new-task-boxが作成され、タスクエリア内に追加されます。taskCounterによって、ボックスが配置される位置が異なるように動的に調整されます。

3. 今後の拡張

  • タスクのドラッグ&ドロップ:
    例えば、jsPlumbDragulaなどのライブラリを利用して、タスクボックスをドラッグ&ドロップで移動可能にすることができます。

  • 入力項目設定のポップアップ:
    「入力項目設定」ボタンをクリックした際に、ポップアップウィンドウを表示して、入力項目を動的に追加・削除する機能を実装することができます。

  • タスク間の接続:
    タスク間を線で結ぶ機能も後で追加することで、ワークフローの全体像を視覚化できます。

このテンプレートを基に、実際のワークフロー作成・編集機能のユーザーインターフェースを実装することができます。


このページを再表示すると、以下のような画面になります。









左下の「タスク作成」ボタンをクリックすると、タスクのボックスが表示されます。









ChatGPTへのスクリプト:

flow_create_edit.htmlでタスクを作成後、そのタスクボックスにある「入力項目設定ボタン」をクリックしたときに、ダイアログボックスを表示させ、項目を設定したい。ダイアログボックスの仕様は以下の通り。 ・上部に項目名、データタイプ(一覧から選択)、データタイプがCharなら最大文字数、データタイプがマスターなら対象マスターを一覧から選択 ・下の部分にはすでに登録済の項目の一覧、一覧には上部の入力項目をテーブル形式で表示


ChatGPTからの回答:

目的は「入力項目設定ボタン」をクリックした際に、指定された仕様のダイアログボックスを表示させることです。

実装手順

1. ダイアログボックスのHTML構造

ダイアログボックスはモーダル形式で表示します。必要な項目を含むHTMLを作成し、CSSとJavaScriptを使用して表示・非表示を制御します。

2. JavaScriptでダイアログの動作を制御

「入力項目設定」ボタンをクリックしたときに、モーダルを表示します。また、入力内容を保持するための操作を実装します。

3. CSSでモーダルの見た目を整える

ダイアログボックスのレイアウトを設定し、背景が暗くなるようにします。


実装コード


<!-- タスクボックス内に含まれる入力項目設定ボタンのダイアログボックス -->
<div id="dialog" class="modal" style="display: none;">
    <div class="modal-content">
        <span class="close">×</span>
        <h2>入力項目設定</h2>
        
        <!-- 上部: 項目名、データタイプ、追加設定 -->
        <div>
            <label for="field-name">項目名:</label>
            <input type="text" id="field-name" placeholder="項目名を入力">

            <label for="data-type">データタイプ:</label>
            <select id="data-type">
                <option value="Char">Char</option>
                <option value="Master">Master</option>
                <option value="Integer">Integer</option>
            </select>

            <div id="char-settings" style="display: none;">
                <label for="max-length">最大文字数:</label>
                <input type="number" id="max-length" placeholder="文字数を入力">
            </div>

            <div id="master-settings" style="display: none;">
                <label for="master-list">対象マスター:</label>
                <select id="master-list">
                    <option value="Master1">Master1</option>
                    <option value="Master2">Master2</option>
                    <option value="Master3">Master3</option>
                </select>
            </div>
        </div>

        <!-- 下部: 登録済の項目一覧 -->
        <h3>登録済の項目一覧</h3>
        <table id="field-table" border="1">
            <thead>
                <tr>
                    <th>項目名</th>
                    <th>データタイプ</th>
                    <th>設定</th>
                </tr>
            </thead>
            <tbody>
                <!-- JavaScriptで動的に項目を追加 -->
            </tbody>
        </table>

        <button id="save-field">項目を追加</button>
    </div>
</div>

<script>
    // ダイアログボックスを制御するスクリプト
    document.addEventListener('DOMContentLoaded', function() {
        const dialog = document.getElementById('dialog');
        const closeBtn = document.querySelector('.close');
        const addFieldButton = document.getElementById('save-field');
        const dataTypeSelect = document.getElementById('data-type');
        const charSettings = document.getElementById('char-settings');
        const masterSettings = document.getElementById('master-settings');
        const fieldTableBody = document.querySelector('#field-table tbody');

        // 「入力項目設定」ボタンがクリックされたときにモーダルを表示
        document.querySelectorAll('.new-task-box button').forEach(button => {
            button.addEventListener('click', function() {
                dialog.style.display = 'block';
            });
        });

        // モーダルを閉じる
        closeBtn.addEventListener('click', function() {
            dialog.style.display = 'none';
        });

        // データタイプに応じて追加設定を表示
        dataTypeSelect.addEventListener('change', function() {
            const selectedType = this.value;
            charSettings.style.display = selectedType === 'Char' ? 'block' : 'none';
            masterSettings.style.display = selectedType === 'Master' ? 'block' : 'none';
        });

        // 項目を追加
        addFieldButton.addEventListener('click', function() {
            const fieldName = document.getElementById('field-name').value;
            const dataType = dataTypeSelect.value;
            const maxLength = document.getElementById('max-length').value;
            const masterList = document.getElementById('master-list').value;

            // 入力内容をテーブルに追加
            const newRow = document.createElement('tr');
            newRow.innerHTML = `
                <td>${fieldName}</td>
                <td>${dataType}</td>
                <td>${dataType === 'Char' ? `最大文字数: ${maxLength}` : dataType === 'Master' ? `対象: ${masterList}` : ''}</td>
            `;
            fieldTableBody.appendChild(newRow);

            // フィールドをリセット
            document.getElementById('field-name').value = '';
            dataTypeSelect.value = 'Char';
            charSettings.style.display = 'none';
            masterSettings.style.display = 'none';
        });
    });
</script>

<style>
    /* モーダルのスタイル */
    .modal {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .modal-content {
        background-color: white;
        padding: 20px;
        border-radius: 8px;
        width: 500px;
    }

    .close {
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
    }
</style>

仕様に基づいた機能説明

  1. 入力項目設定フォーム:

    • 項目名、データタイプを入力または選択。
    • データタイプが Char の場合は最大文字数を入力。
    • データタイプが Master の場合は対象マスターを選択。
  2. 登録済の項目一覧:

    • 入力された項目をテーブル形式で下部に表示。
  3. 動的なインタラクション:

    • JavaScriptでデータタイプに応じた追加設定の表示切り替え。
    • 項目を追加するたびにフォームをリセット。

これで、タスクごとにダイアログボックスを使って入力項目を設定する基本的な仕組みが完成します。


ChatGPTへのスクリプト:

flow_create_edit.htmlの全体のコードは?


ChatGPTからの回答:

以下は、flow_create_edit.html の全体コードです。これには、タスクボックスの「入力項目設定」ボタンをクリックするとモーダルダイアログが表示され、入力項目を設定する機能を追加したものを含みます。


完全版 flow_create_edit.html

{% extends '../base.html' %}
{% load static %}
{% block contents %}
<style>
    /* タスク作成エリアのスタイル */
    #task-area {
        width: 1200px;
        height: 600px;
        border: 2px solid #ccc;
        position: relative;
        overflow: auto;
        padding: 10px;
    }

    /* STARTとENDボックスのスタイル */
    .task-box {
        width: 150px;
        height: 50px;
        border-radius: 10px;
        background-color: #f0f0f0;
        border: 2px solid #333;
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        text-align: center;
    }

    #start-box {
        top: 10px;
        left: 10px;
    }

    #end-box {
        bottom: 10px;
        right: 10px;
    }

    /* 新しいタスクボックスのスタイル */
    .new-task-box {
        width: 200px;
        height: 100px;
        border-radius: 10px;
        background-color: #e0f7fa;
        border: 2px solid #00796b;
        position: absolute;
        padding: 10px;
        box-sizing: border-box;
    }

    .new-task-box input[type="text"] {
        width: 90%;
        margin-bottom: 10px;
    }

    .new-task-box button {
        width: 100%;
    }

    /* タスクエリア全体がスクロール可能な場合のスタイル */
    #task-area::-webkit-scrollbar {
        width: 12px;
        height: 12px;
    }

    #task-area::-webkit-scrollbar-thumb {
        background-color: #888;
        border-radius: 10px;
    }

    #task-area::-webkit-scrollbar-track {
        background-color: #f0f0f0;
    }

    /* モーダルのスタイル */
    .modal {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .modal-content {
        background-color: white;
        padding: 20px;
        border-radius: 8px;
        width: 500px;
        position: relative;
    }

    .close {
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
    }
</style>

<h1>フロー作成</h1>

<!-- タスク作成エリア -->
<div id="task-area">
    <!-- STARTボックス -->
    <div id="start-box" class="task-box">
        START
    </div>

    <!-- ENDボックス -->
    <div id="end-box" class="task-box">
        END
    </div>
</div>

<!-- タスク作成ボタン -->
<button type="button" id="add-task-btn">タスク作成</button>

<!-- ダイアログボックス -->
<div id="dialog" class="modal" style="display: none;">
    <div class="modal-content">
        <span class="close">×</span>
        <h2>入力項目設定</h2>
        
        <!-- 上部: 項目名、データタイプ、追加設定 -->
        <div>
            <label for="field-name">項目名:</label>
            <input type="text" id="field-name" placeholder="項目名を入力">

            <label for="data-type">データタイプ:</label>
            <select id="data-type">
                <option value="Char">Char</option>
                <option value="Master">Master</option>
                <option value="Integer">Integer</option>
            </select>

            <div id="char-settings" style="display: none;">
                <label for="max-length">最大文字数:</label>
                <input type="number" id="max-length" placeholder="文字数を入力">
            </div>

            <div id="master-settings" style="display: none;">
                <label for="master-list">対象マスター:</label>
                <select id="master-list">
                    <option value="Master1">Master1</option>
                    <option value="Master2">Master2</option>
                    <option value="Master3">Master3</option>
                </select>
            </div>
        </div>

        <!-- 下部: 登録済の項目一覧 -->
        <h3>登録済の項目一覧</h3>
        <table id="field-table" border="1">
            <thead>
                <tr>
                    <th>項目名</th>
                    <th>データタイプ</th>
                    <th>設定</th>
                </tr>
            </thead>
            <tbody>
                <!-- JavaScriptで動的に項目を追加 -->
            </tbody>
        </table>

        <button id="save-field">項目を追加</button>
    </div>
</div>

<script>
    let taskCounter = 0;

    // タスク作成ボタンのクリックイベント
    document.getElementById('add-task-btn').addEventListener('click', function() {
        taskCounter += 1;
        const taskArea = document.getElementById('task-area');

        const newTaskBox = document.createElement('div');
        newTaskBox.className = 'new-task-box';
        newTaskBox.style.top = (50 + taskCounter * 60) + 'px';
        newTaskBox.style.left = (50 + taskCounter * 60) + 'px';

        const taskInput = document.createElement('input');
        taskInput.type = 'text';
        taskInput.placeholder = 'タスク名を入力';

        const settingsButton = document.createElement('button');
        settingsButton.textContent = '入力項目設定';
        settingsButton.addEventListener('click', function() {
            document.getElementById('dialog').style.display = 'block';
        });

        newTaskBox.appendChild(taskInput);
        newTaskBox.appendChild(settingsButton);

        taskArea.appendChild(newTaskBox);
    });

    // ダイアログ制御
    const dialog = document.getElementById('dialog');
    const closeBtn = document.querySelector('.close');
    const dataTypeSelect = document.getElementById('data-type');
    const charSettings = document.getElementById('char-settings');
    const masterSettings = document.getElementById('master-settings');
    const fieldTableBody = document.querySelector('#field-table tbody');

    closeBtn.addEventListener('click', function() {
        dialog.style.display = 'none';
    });

    dataTypeSelect.addEventListener('change', function() {
        charSettings.style.display = this.value === 'Char' ? 'block' : 'none';
        masterSettings.style.display = this.value === 'Master' ? 'block' : 'none';
    });

    document.getElementById('save-field').addEventListener('click', function() {
        const fieldName = document.getElementById('field-name').value;
        const dataType = dataTypeSelect.value;
        const maxLength = document.getElementById('max-length').value;
        const masterList = document.getElementById('master-list').value;

        const newRow = document.createElement('tr');
        newRow.innerHTML = `
            <td>${fieldName}</td>
            <td>${dataType}</td>
            <td>${dataType === 'Char' ? `最大文字数: ${maxLength}` : dataType === 'Master' ? `対象: ${masterList}` : ''}</td>
        `;
        fieldTableBody.appendChild(newRow);

        document.getElementById('field-name').value = '';
        dataTypeSelect.value = 'Char';
        charSettings.style.display = 'none';
        masterSettings.style.display = 'none';
    });
</script>
{% endblock %}

このコードで、タスクボックスの「入力項目設定」ボタンをクリックすると、仕様に基づくモーダルダイアログが表示され、項目を追加することが可能になります。


このコードを実装後、「入力項目設定」ボタンをクリックすると、以下のような入力ボックスが表示されます。






次回はこの入力ボックスをアレンジしていきたいと思います。

2024-12-11

ChatGPTを使って、アプリ開発(ワークフロー5):開発3

 表示される画面の装飾を行います。ただし、今後作成する画面に毎回同じ装飾設定をするのは、面倒なので、基本となる画面デザインを使いまわす方法をとります。

1.基本となる画面作成

 ①「base.html」というファイルを作成します。

   「templates」で右クリックし、「新規」→「HTMLファイル」を選択します。






②「base」と入力し、base.htmlを作成します。




③base.htmlの中身をすべて削除し、以下のコードに書き換えます。

<!DOCTYPE html>
<html lang="ja">

{% load static %}
<head>
<title>workflow Tools</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}">
</head>
<style>
.side-bar{
    width: 160px;
    height: auto;
    background: #b0c4de;
    padding-top : 5px;
}
.sidebar-list{
    list-style: none;
    margin: 10px 0px 0px -35px;
}
a   {
    text-decoration:none;
}
a:hover {
    text-decoration: underline;
    color: #ffffe0;
}
</style>
<body class="dashboard"
  data-admin-utc-offset="32400">
    <!-- Container -->
        <div id="header">
            <div id="branding">
                <h1 id="site-name"><a href="/wf/">ワークフロートップページ</a></h1>
            </div>
            {% if user.is_authenticated %}
                <div id="user-tools">
                    <div>ようこそ {{user.last_name}} {{user.first_name}} さん /
                        <a href="/wf/">ワークフロートップページ</a> /
                        <a href="{% url 'admin:password_change' %}">パスワードの変更</a> /
                        <a href="/accounts/logout/">ログアウト</a>
                    </div>
                </div>
            {% endif %}
        </div>
        <!-- END Header -->
        <!-- Content -->
        <div class="container-fluid">
            <div class="row">
                <div style="float:left;width:170px">
                    <div class="side-bar" style="height:93.5vh">
                        <div class="col-sm-3 col-md-2 sidebar">
                            {% if user.is_authenticated %}
                                <ul class="nav nav-sidebar">
                                    <li class="sidebar-list"><a href="/admin/">システム管理</a></li>
                                </ul>
                            {% endif %}
                        </div>
                    </div>
                </div>
                <div class="contents_area" style="float:left;">
                    {% block contents %}{% endblock %}
                </div>
            </div>
        </div>
</body>
</html>

・{% load static %}の部分で、「static」ディレクトリのファイルを読み込めるようにします。

・下から6行目の{% block contents %}{% endblock %}の部分が、実際に内容を表示させる部分になります。

・<style>にて、サイドバーの装飾を記述しています。

④装飾用のCSSファイル(base.css)は「static」→「admin」→「css」のbase.cssファイルを「static」→「css」にコピーします。

 ・コピー元のファイルで右クリックし、「コピー」を選択します。











 ・コピー先のディレクトリで右クリックし、「貼り付け」を選択します。














・名前は変更せず、「OK」をクリックします。








2.以前作成したflow_list.htmlを以下のように変更します。

{% extends '../base.html' %}
{% load static %}
{% block contents %}
    <h1>フロー一覧</h1>

    <!-- フローの新規作成ボタン -->
    <a href="{% url 'flow_create' %}" class="btn btn-primary">新規作成</a>

    <!-- ユーザーが作成したフローの一覧 -->
    {% if flows %}
        <table class="table">
            <thead>
                <tr>
                    <th>フロー名</th>
                    <th>作成日</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                {% for flow in flows %}
                    <tr>
                        <td>{{ flow.name }}</td>
                        <td>{{ flow.created_at|date:"Y/m/d H:i" }}</td>
                        <td>
                            <!-- 編集ボタン -->
                            <a href="{% url 'flow_edit' flow.id %}" class="btn btn-warning">編集</a>

                            <!-- 削除ボタン -->
                            <form action="{% url 'flow_delete' flow.id %}" method="post" style="display:inline;">
                                {% csrf_token %}
                                <button type="submit" class="btn btn-danger" onclick="return confirm('本当に削除しますか?');">削除</button>
                            </form>
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
    {% else %}
        <p>フローがまだ作成されていません。</p>
    {% endif %}

{% endblock %}


以上の設定で、以下のような画面になります。










 では、ここからフローを作成する部分を作っていきたいと思います。

構想として、以下のようにします。

・フローは視覚的に作成できるようにする。

・条件分岐や並行処理ができるようにする。

フォローの中で記録したり、閲覧する項目も自由に設定できるようにする。

ChatGPTへのスクリプト:

workflowを作成/編集する部分は?


ChatGPTからの回答:

ワークフローを作成・編集するためのページのテンプレートでは、ユーザーがタスクを追加、編集、そしてフロー全体を定義できるようにします。以下は、フロー作成・編集ページのテンプレートの例です。

フロー作成・編集ページのテンプレート (flow_create_edit.html)


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{% if flow %}フロー編集{% else %}フロー作成{% endif %}</title>
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
    <script src="{% static 'js/flow_editor.js' %}"></script> <!-- jsPlumb等のライブラリを読み込む -->
</head>
<body>
    <h1>{% if flow %}フロー編集{% else %}フロー作成{% endif %}</h1>

    <!-- フロー名入力フォーム -->
    <form method="post" action="{% if flow %}{% url 'flow_edit' flow.id %}{% else %}{% url 'flow_create' %}{% endif %}">
        {% csrf_token %}
        <label for="flow_name">フロー名</label>
        <input type="text" id="flow_name" name="name" value="{% if flow %}{{ flow.name }}{% endif %}" required>

        <!-- タスク追加エリア -->
        <h2>タスク</h2>
        <div id="task-container">
            <!-- タスクを表示するエリア -->
            {% if flow and flow.tasks.exists %}
                {% for task in flow.tasks.all %}
                    <div class="task-box" id="task-{{ task.id }}">
                        <h3>{{ task.name }}</h3>
                        <p>タスク処理者: {{ task.assigned_user }}</p>
                        <button type="button" class="edit-task" data-task-id="{{ task.id }}">タスク編集</button>
                        <button type="button" class="delete-task" data-task-id="{{ task.id }}">タスク削除</button>
                    </div>
                {% endfor %}
            {% else %}
                <!-- 初期状態で START と END タスクを表示 -->
                <div class="task-box" id="task-start">
                    <h3>START</h3>
                </div>
                <div class="task-box" id="task-end">
                    <h3>END</h3>
                </div>
            {% endif %}
        </div>

        <!-- タスク作成ボタン -->
        <button type="button" id="add-task-btn">タスク作成</button>

        <!-- タスク編集/入力項目設定ポップアップ -->
        <div id="task-modal" style="display:none;">
            <h3>タスク設定</h3>
            <label for="task-name">タスク名</label>
            <input type="text" id="task-name" name="task-name">
            
            <h4>入力項目設定</h4>
            <div id="input-fields-container">
                <!-- ここに動的に入力項目が追加される -->
            </div>
            <button type="button" id="add-input-field-btn">入力項目追加</button>
            
            <button type="button" id="save-task-btn">タスク保存</button>
            <button type="button" id="cancel-task-btn">キャンセル</button>
        </div>

        <!-- フローの保存 -->
        <button type="submit">登録</button>
    </form>

    <!-- 削除ボタン(フロー編集時のみ表示) -->
    {% if flow %}
        <form method="post" action="{% url 'flow_delete' flow.id %}" style="margin-top: 20px;">
            {% csrf_token %}
            <button type="submit" class="btn btn-danger" onclick="return confirm('本当に削除しますか?');">削除</button>
        </form>
    {% endif %}

    <script>
        // タスク作成や編集、削除のためのJavaScript
        document.getElementById('add-task-btn').addEventListener('click', function() {
            // タスク作成ロジック
            // 例えば、タスクボックスを追加するコードをここに記述
        });

        document.querySelectorAll('.edit-task').forEach(function(button) {
            button.addEventListener('click', function() {
                const taskId = button.getAttribute('data-task-id');
                // タスク編集ロジック(モーダルを開くなど)
            });
        });

        document.querySelectorAll('.delete-task').forEach(function(button) {
            button.addEventListener('click', function() {
                const taskId = button.getAttribute('data-task-id');
                // タスク削除ロジック
            });
        });
    </script>
</body>
</html>


説明

  1. フロー名の入力: フロー名を入力できるテキストフィールドがあり、編集時には既存のフロー名が表示されます。

  2. タスク作成ボタン: ユーザーが新しいタスクを追加できるボタン。クリックすると、タスクを作成するためのモーダルウィンドウが表示され、タスクの名前や入力項目を設定できます。

  3. タスク表示エリア: 既存のタスク(編集時の場合)を表示し、タスクの編集や削除ができるようにしています。また、フロー作成時は「START」と「END」のタスクボックスをデフォルトで表示します。

  4. タスク編集用モーダル: ユーザーがタスクを作成または編集する際に、入力項目やタスク名を設定するためのポップアップウィンドウ。

  5. JavaScript部分: タスクの追加、編集、削除などの動的操作を行うために、JavaScriptを使用しています。このコードではイベントリスナーを設定し、ボタンをクリックするとタスクを追加したり、既存のタスクを編集するモーダルを開いたりすることができます。

  6. フローの保存: フォームが送信されると、フローのデータが保存されます。編集時にはフローの削除ボタンも表示されます。

補足

  • このテンプレートには、jsPlumbDrag and Dropのようなタスク同士を結ぶ機能を追加するためのJavaScriptライブラリも組み込むことが可能です。
  • タスク作成、編集、削除の処理はバックエンド側でハンドリングする必要がありますので、各機能に対応するビューとロジックが必要です。

base.htmlを継承するように、以下のように変更する。


{% extends '../base.html' %}
{% load static %}
{% block contents %}
    <h1>{% if flow %}フロー編集{% else %}フロー作成{% endif %}</h1>

    <!-- フロー名入力フォーム -->
    <form method="post" action="{% if flow %}{% url 'flow_edit' flow.id %}{% else %}{% url 'flow_create' %}{% endif %}">
        {% csrf_token %}
        <label for="flow_name">フロー名</label>
        <input type="text" id="flow_name" name="name" value="{% if flow %}{{ flow.name }}{% endif %}" required>

        <!-- タスク追加エリア -->
        <h2>タスク</h2>
        <div id="task-container">
            <!-- タスクを表示するエリア -->
            {% if flow and flow.tasks.exists %}
                {% for task in flow.tasks.all %}
                    <div class="task-box" id="task-{{ task.id }}">
                        <h3>{{ task.name }}</h3>
                        <p>タスク処理者: {{ task.assigned_user }}</p>
                        <button type="button" class="edit-task" data-task-id="{{ task.id }}">タスク編集</button>
                        <button type="button" class="delete-task" data-task-id="{{ task.id }}">タスク削除</button>
                    </div>
                {% endfor %}
            {% else %}
                <!-- 初期状態で START と END タスクを表示 -->
                <div class="task-box" id="task-start">
                    <h3>START</h3>
                </div>
                <div class="task-box" id="task-end">
                    <h3>END</h3>
                </div>
            {% endif %}
        </div>

        <!-- タスク作成ボタン -->
        <button type="button" id="add-task-btn">タスク作成</button>

        <!-- タスク編集/入力項目設定ポップアップ -->
        <div id="task-modal" style="display:none;">
            <h3>タスク設定</h3>
            <label for="task-name">タスク名</label>
            <input type="text" id="task-name" name="task-name">

            <h4>入力項目設定</h4>
            <div id="input-fields-container">
                <!-- ここに動的に入力項目が追加される -->
            </div>
            <button type="button" id="add-input-field-btn">入力項目追加</button>

            <button type="button" id="save-task-btn">タスク保存</button>
            <button type="button" id="cancel-task-btn">キャンセル</button>
        </div>

        <!-- フローの保存 -->
        <button type="submit">登録</button>
    </form>

    <!-- 削除ボタン(フロー編集時のみ表示) -->
    {% if flow %}
        <form method="post" action="{% url 'flow_delete' flow.id %}" style="margin-top: 20px;">
            {% csrf_token %}
            <button type="submit" class="btn btn-danger" onclick="return confirm('本当に削除しますか?');">削除</button>
        </form>
    {% endif %}

    <script>
        // タスク作成や編集、削除のためのJavaScript
        document.getElementById('add-task-btn').addEventListener('click', function() {
            // タスク作成ロジック
            // 例えば、タスクボックスを追加するコードをここに記述
        });

        document.querySelectorAll('.edit-task').forEach(function(button) {
            button.addEventListener('click', function() {
                const taskId = button.getAttribute('data-task-id');
                // タスク編集ロジック(モーダルを開くなど)
            });
        });

        document.querySelectorAll('.delete-task').forEach(function(button) {
            button.addEventListener('click', function() {
                const taskId = button.getAttribute('data-task-id');
                // タスク削除ロジック
            });
        });
    </script>
{% endblock %}


ChatGPTへのスクリプト:

ビューは?


ChatGPTからの回答:

フロー作成・編集ページのためのDjangoビューを実装するには、以下のようにCreateViewUpdateViewを使って、新規作成と編集機能を提供することができます。タスクの動的な追加・削除に関しては、Ajaxを使用してサーバーと非同期通信を行うと、ユーザー体験がスムーズになります。

以下では、フローの作成・編集、タスクの追加・編集、および削除に対応するDjangoビューの例を示します。

1. フロー作成ビュー

まず、flow_createflow_editに対応するビューを作成します。

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from .models import Flow, Task
from .forms import FlowForm, TaskForm

# フローの作成
@login_required
def flow_create(request):
    if request.method == 'POST':
        form = FlowForm(request.POST)
        if form.is_valid():
            flow = form.save(commit=False)
            flow.creator = request.user
            flow.save()
            # STARTとENDタスクを自動作成
            Task.objects.create(flow=flow, name='START')
            Task.objects.create(flow=flow, name='END')
            return redirect('flow_edit', flow.id)
    else:
        form = FlowForm()
    
    return render(request, 'workflow/flow_create_edit.html', {'form': form, 'flow': None})

# フローの編集
@login_required
def flow_edit(request, flow_id):
    flow = get_object_or_404(Flow, id=flow_id, creator=request.user)
    
    if request.method == 'POST':
        form = FlowForm(request.POST, instance=flow)
        if form.is_valid():
            form.save()
            return redirect('flow_edit', flow.id)
    else:
        form = FlowForm(instance=flow)
    
    tasks = flow.tasks.all()
    
    return render(request, 'workflow/flow_create_edit.html', {'form': form, 'flow': flow, 'tasks': tasks})


2. タスク追加・編集ビュー

タスクはフロー内で動的に追加されるため、Ajaxを使って非同期に追加・編集することが推奨されます。以下は、タスク追加と編集をAjax経由で行うビューの例です。

# タスクの追加
@login_required
def add_task(request, flow_id):
    flow = get_object_or_404(Flow, id=flow_id, creator=request.user)
    
    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
            task = form.save(commit=False)
            task.flow = flow
            task.save()
            return JsonResponse({'task_id': task.id, 'task_name': task.name}, status=201)
    
    return JsonResponse({'error': 'Invalid data'}, status=400)

# タスクの編集
@login_required
def edit_task(request, task_id):
    task = get_object_or_404(Task, id=task_id, flow__creator=request.user)
    
    if request.method == 'POST':
        form = TaskForm(request.POST, instance=task)
        if form.is_valid():
            form.save()
            return JsonResponse({'task_id': task.id, 'task_name': task.name}, status=200)
    
    return JsonResponse({'error': 'Invalid data'}, status=400)

3. タスク削除ビュー

タスクの削除もAjaxで非同期に行う場合の例です。

# タスクの削除
@login_required
def delete_task(request, task_id):
    task = get_object_or_404(Task, id=task_id, flow__creator=request.user)
    
    if request.method == 'POST':
        task.delete()
        return JsonResponse({'message': 'Task deleted'}, status=200)
    
    return JsonResponse({'error': 'Failed to delete task'}, status=400)


4. フォーム

タスクやフローのデータを操作するために、forms.pyでフォームを定義します。

from django import forms
from .models import Flow, Task

class FlowForm(forms.ModelForm):
    class Meta:
        model = Flow
        fields = ['name']

class TaskForm(forms.ModelForm):
    class Meta:
        model = Task
        fields = ['name', 'assigned_user']

5. URL設定

これらのビューにアクセスできるように、urls.pyでルーティングを設定します。

from django.urls import path
from . import views

urlpatterns = [
    path('flows/create/', views.flow_create, name='flow_create'),
    path('flows/<int:flow_id>/edit/', views.flow_edit, name='flow_edit'),
    path('flows/<int:flow_id>/add_task/', views.add_task, name='add_task'),
    path('tasks/<int:task_id>/edit/', views.edit_task, name='edit_task'),
    path('tasks/<int:task_id>/delete/', views.delete_task, name='delete_task'),
]

6. Ajaxのフロントエンド操作

JavaScriptを使用して、非同期でタスクを追加・編集・削除するコードを用意します。例えば、flow_create_edit.htmlのテンプレート内にJavaScriptを含めることで、動的にタスクを操作できます。

document.getElementById('add-task-btn').addEventListener('click', function() {
    const taskName = prompt("タスク名を入力してください");
    if (taskName) {
        fetch(`/flows/${flowId}/add_task/`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCookie('csrftoken')
            },
            body: JSON.stringify({ name: taskName })
        })
        .then(response => response.json())
        .then(data => {
            if (data.task_id) {
                // 新しいタスクをDOMに追加する処理
                const taskContainer = document.getElementById('task-container');
                taskContainer.innerHTML += `<div class="task-box" id="task-${data.task_id}"><h3>${data.task_name}</h3></div>`;
            }
        });
    }
});

まとめ

  • flow_createおよびflow_editビューでは、ユーザーがフローを作成・編集するロジックを実装します。
  • タスクの追加・編集・削除はAjaxを利用して非同期で行い、ページのリロードを最小限に抑え、ユーザー体験を向上させます。
  • フロントエンド側でJavaScriptを使い、タスクの動的操作を実現し、サーバー側ではDjangoのビューを使用してデータベースと連携します。

これにより、Djangoでのワークフロー作成・編集機能が実装できます。

次回から、開発環境サーバーで実行確認しながら、さらにブラシュアップしていきます。