iT邦幫忙

2024 iThome 鐵人賽

DAY 24
0
Python

Django - 製作網頁一點通系列 第 24

Day24 - 任務創建功能實作

  • 分享至 

  • xImage
  •  

我們接下來會建立以下幾個頁面

  • 首頁
  • 登入頁面
  • 註冊頁面
  • 任務頁面

接下來是任務頁面!
首先我們來把查看任務的頁面做出來

  1. task

@login_required:這個裝飾器確保只有已登入的使用者才能訪問該頁面。
user = request.user:取得當前登入的使用者。
tasks = Task.objects.filter(user_id=user):查詢該使用者的所有任務。
sharedTasks = SharedTask.objects.filter(Q(user=user) | Q(task__user_id=user)):使用Q物件查詢與當前使用者相關的共享任務(無論是共享給他或是他創建的任務)。
my_tasks = tasks.exclude(id__in=sharedTasks.values_list('task__id', flat=True)):過濾掉共享任務,僅保留當前使用者的私有任務。
迴圈內為每個私有任務取得相關留言。

views.py

@login_required 
def task(request):
    user = request.user
    tasks = Task.objects.filter(user_id=user)
    # 取得共享的任務
    sharedTasks = SharedTask.objects.filter(Q(user=user) | Q(task__user_id=user))
    # 排除共享任務後的我的任務
    my_tasks = tasks.exclude(id__in=sharedTasks.values_list('task__id', flat=True))

    # 為每個任務獲取留言
    for task in my_tasks:
        task.comments = Comment.objects.filter(task=task)

    return render(request, 'task.html',locals())
  1. base.html

是其他繼承base.html的基礎,使用簡單的JavaScript函式toggleDetails來控制任務詳情的顯示和隱藏,透過按鈕點擊事件來切換詳細內容的顯示狀態。

{% load static %}
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TASK</title>
    <link rel="stylesheet" href="{% static 'css/task.css' %}">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> <!-- Font Awesome -->
    <script>
        // 切換顯示任務詳情
        function toggleDetails(id) {
            var details = document.getElementById('details-' + id);
            if (details.style.display === 'none' || details.style.display === '') {
                details.style.display = 'block';
            } else {
                details.style.display = 'none';
            }
        }
    </script>
</head>
<body>
    <div class="container">
        <!-- 左側欄 -->
        <nav class="sidebar">
            <h2>我的任務</h2>
            <ul>
                <li><a href="/task/">我的任務</a></li>
            </ul>

            <h2>共享的任務</h2>
            <ul>
                {% if sharedTasks %}
                <li><a href="/task/show_share/">共享任務</a></li>
                {% else %}
                <li>沒有共享任務</li>
                {% endif %}
            </ul>

            <h2>{{user.username}}</h2>
            <ul>
                <form method="post" action="{% url 'logout' %}">
                    {% csrf_token %}
                    <button type="submit" class="logout-link">登出</button>
                </form></li>
            </ul>

        </nav>

        <!-- 右側任務列表 -->
        <section class="main-content">
            <h1>任務列表<a href="{% url 'add_task' %}" class="icon-link"><i class="fas fa-plus"></i> 新增</a></h1>
            {% block content %}
            {% endblock %}
        </section>
    </div>
</body>
</html>
  1. task.html

使用了Bootstrap和Font Awesome來美化頁面和顯示圖標。
左側欄顯示「我的任務」和「共享的任務」分區,並根據是否有共享任務動態顯示「共享任務」連結。
右側是任務列表,按鈕可以顯示每個任務的詳情,並提供了編輯、刪除、共享和留言的操作按鈕。
任務的詳情以可展開/折疊的形式展示,並列出該任務的留言。
通過{% extends 'base.html' %},task.html繼承了基本的HTML框架,只修改了content部分來顯示任務列表。

{% extends 'base.html' %}
{% block content %}

<div>
    {% for task in my_tasks %}
    <div class="task">
        <h3></h3>
        <div class="task_line">
            <button class="task-button" onclick="toggleDetails({{ task.id }})">{{ task.title }}</button>
            <a class="icon-link" href="/task/edit/{{task.id}}"><i class="fa fa-edit"></i> 編輯</a>
            <a class="icon-link" href="/task/delete/{{task.id}}" onclick="return confirm('確定要刪除這個任務嗎?');"><i class="fa fa-trash"></i> 刪除</a>
            <a class="icon-link" href="/task/share/{{task.id}}"><i class="fa fa-share"></i> 共享</a>
            <a class="icon-link" href="/task/comment/{{task.id}}"><i class="fa fa-comment"></i> 留言</a>
            <a class="icon-link" href="/task/show_log/{{task.id}}"><i class="fas fa-history"></i> 變更歷史</a>
        </div>
        <div id="details-{{ task.id }}" class="task-details">
            <p>任務: {{task.title}}</p>
            <p>描述: {{ task.description }}</p>
            <p>截止日期: {{ task.due_date }}</p>
            <p>優先權: {{ task.priority }}</p>
            <p>狀態: {{ task.status }}</p>
            <hr>
            <h4>留言:</h4>
            {% for comment in task.comments %}
                <div class="comment">
                    <p><strong>{{ comment.user.username }}:</strong> {{ comment.content }} <em>({{ comment.created_at|date:"Y-m-d H:i" }})</em></p>
                </div>
            {% empty %}
                <p>沒有留言。</p>
            {% endfor %}
        </div>
    </div>
    {% empty %}
    <p>沒有任務</p>
    {% endfor %}
</div>

{% endblock %}
  1. CSS

定義了網站的主要配色,包括深綠色、淺綠色、白色等作為背景和字體顏色,並使用了線性漸變來設置背景。
使用了不同的樣式來控制左側欄位(sidebar)和右側主內容區(main-content)的佈局和外觀,確保頁面整潔且易於閱讀。
調整了按鈕、鏈接和留言框的樣式,提供了符合現代網頁設計風格的互動效果(如懸停時變色)。

body {
    height: auto;
    text-align: center;
    font-family: Arial, sans-serif;
}

body::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: url('../images/logo.png');
    background-repeat: repeat;
    background-size: 5%;
    opacity: 0.5;
    z-index: -1;
}


body {
    background: linear-gradient(to right, rgba(240, 244, 195, 1), rgba(165, 214, 167, 1)); /* 背景漸變 */
}

/* 背景與主色 */
:root {
    --dark-green: #1c3720;
    --light-green: #a5d6a7;
    --green : #70aa72;
    --dark-gray: #333333;
    --white: #ffffff;
    --gray: #e0e0e0;
}

h1{
    color: var( --dark-green);
}

h2 {
    color: var(--light-green);
}

a {
    color: var(--white);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}


.container {
    display: flex;
    height: 97vh;
    border-radius: 20px;
}

/* 左側欄位 */
.sidebar {
    width: 20%;
    background-color: var(--dark-green);
    padding: 20px;
    box-sizing: border-box;
    border-radius: 20px;
}

.sidebar h2 {
    margin-top: 0;
}

.sidebar ul {
    list-style-type: none;
    padding-left: 0;
}

.sidebar ul li {
    margin: 10px 0;
}

.sidebar ul li a {
    padding: 10px;
    display: block;
    background-color: var(--green);
    border-radius: 5px;
}

.sidebar ul li a:hover {
    background-color: var(--light-green);
    color: var(--dark-gray);
}

/* 右側內容區 */
.main-content {
    width: 80%;
    background-color: var(--gray);
    padding: 40px;
    box-sizing: border-box;
    overflow-y: auto;
    border-radius: 20px;
}

.main-content h1 {
    border-bottom: 2px solid var(--dark-green);
    padding-bottom: 10px;
}

.main-content p {
    margin: 20px 0;
    color: var(--dark-gray);
}

.task_line{
    width: 100%;
}

.task-button {
    padding: 10px 15px;
    border: none;
    border-radius: 5px;
    background-color: var(--green);
    color: var(--white);
    cursor: pointer;
    transition: background-color 0.3s;
    font-size: 16px; /* 增加字體大小 */
}

.task-button:hover {
    background-color: var(--light-green);
}

/* 圖標鏈接樣式 */
.icon-link {
    display: inline-flex;
    align-items: center; /* 垂直置中 */
    margin-left: 10px; /* 添加間距 */
    color: var(--dark-gray);
    text-decoration: none;
    transition: color 0.3s;
    font-size: 14px; /* 字體大小 */
}

.icon-link:hover {
    color: var(--green);
}

/* 圖標樣式 */
.icon-link i {
    margin-right: 5px; /* 圖標和文本之間的間距 */
}

/* 任務詳情的樣式 */
.task-details {
    display: none;
    margin-top: 10px;
    padding: 15px; /* 增加內邊距 */
    background-color: #f9f9f9;
    border: 1px solid #ddd;
    border-radius: 5px; /* 圓角 */
    text-align: left;
}

.add_task {
    background-color: #f9f9f9; /* 背景顏色 */
    border: 1px solid #ccc; /* 邊框 */
    border-radius: 8px; /* 圓角 */
    padding: 20px; /* 內邊距 */
    max-width: 500px; /* 最大寬度 */
    margin: 20px auto; /* 上下邊距*/
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /*陰影 */
    text-align: left;
}

.add_task h1 {
    font-size: 24px; 
    color: #333; 
    margin-bottom: 15px; 
}

.add_task form {
    display: flex; 
    flex-direction: column; 
}

.add_task button.task-button {
    background-color: #28a745; 
    color: white;
    border: none; 
    padding: 10px 15px; 
    border-radius: 5px; 
    cursor: pointer;
    transition: background-color 0.3s; 
}

.add_task button.task-button:hover {
    background-color: #218838;
}

.add_task a {
    display: inline-block;
    margin-top: 15px;
    text-decoration: none; 
    color: #007bff; 
}

.add_task a:hover {
    text-decoration: underline; 
}

.logout-link {
    color: var(--white);
    background-color: #66bd7a;
    text-decoration: none; 
    padding: 10px;
    border-radius: 5px; 
    width: 100%;
}

.logout-link:hover {
    background-color: var(--light-green); 
    color: var(--dark-gray);
}

任務未展開的畫面
https://ithelp.ithome.com.tw/upload/images/20241008/20169478WPfHL36Tp3.png

任務展開的畫面
https://ithelp.ithome.com.tw/upload/images/20241008/201694780g65DV5pPq.png


接下來,實作創建任務的表單

  1. add_task

add_task 函式中,使用了 Django 的 @login_required 裝飾器,確保只有已登入的用戶才能訪問這個頁面。這樣的設計能增強系統的安全性。

當用戶提交表單時,程式會檢查 POST 請求,如果表單有效,便會新增任務並保存至資料庫,並顯示一條成功訊息。

  • views.py
@login_required
def add_task(request):
    user = request.user
    tasks = Task.objects.filter(user_id=user)
    # 取得共享的任務
    sharedTasks = SharedTask.objects.filter(Q(user=user) | Q(task__user_id=user))
    # 排除共享任務後的我的任務
    my_tasks = tasks.exclude(id__in=sharedTasks.values_list('task__id', flat=True))

    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
            try:
                id = Task.objects.latest('id').id + 1
            except:
                id = 0
            title = form.cleaned_data['title']
            description = form.cleaned_data['description']
            due_date = form.cleaned_data['due_date']
            priority = form.cleaned_data['priority']
            status = form.cleaned_data['status']
            user_id = request.user  
            task = Task(
                id = id,
                title = title,
                description = description,
                due_date = due_date,
                priority = priority,
                status = status,
                user_id = user_id
            )
            task.save()  # 保存
            messages.success(request, '任務已新增!')
            return redirect('task')  # 重定向到任務列表頁面
    else:
        form = TaskForm()
    return render(request, 'add_task.html', locals())
  1. URL

add_task 函式與路徑 task/add/ 綁定,讓使用者能通過此路徑訪問新增任務的頁面。

    path('task/add/', add_task, name='add_task'),  # 新增任務的路由
  1. 前端模板add_task.html

在這個模板中,透過 {{ form.as_p }} 渲染表單,這樣能讓表單自動以 p 標籤包裝每一個欄位。CSRF 保護標籤 csrf_token 則是避免 CSRF 攻擊。此外,還提供了一個返回任務列表的連結。

{% extends 'base.html' %}
{% block content %}

    <div class="add_task">
        <h1>新增任務</h1>
        <form method="post">
            {% csrf_token %}
            {{ form.as_p }}  <!-- 渲染表單 -->
            <button type="submit" class="task-button">新增任務</button>
        </form>
        <a href="{% url 'task' %}">返回任務列表</a>
    </div>

{% endblock%}
  1. 表單TaskForm

TaskForm 使用 Djangoforms.ModelForm,將表單與 Task 模型關聯,並且指定了要使用的欄位。每個欄位都有對應的屬性(如最大長度、輸入格式等)。

  • forms.py
class TaskForm(forms.ModelForm):
    class Meta:
        model = Task
        fields = ['title', 'description', 'due_date', 'priority', 'status']  # 指定要使用的欄位

    title = forms.CharField(label='任務名稱', max_length=100)
    description = forms.CharField(label='描述', widget=forms.Textarea(attrs={'name': 'body'}))
    due_date = forms.DateTimeField(label='到期日', widget=forms.DateTimeInput(attrs={'type': 'datetime-local'}),input_formats=['%Y-%m-%dT%H:%M'])
    priority = forms.IntegerField(label='優先權')

    status_choice = [
        ['待辦', '待辦'],
        ['進行中', '進行中'],
        ['完成', '完成']
    ]
    status = forms.ChoiceField(label='狀態', choices=status_choice)

新增表單
https://ithelp.ithome.com.tw/upload/images/20241008/20169478aGIhLfm24n.png

新增後表單出現在任務列表中
https://ithelp.ithome.com.tw/upload/images/20241008/201694785ZIsZlx1Ar.png


上一篇
Day23 - 註冊頁面實作
下一篇
Day25 - 任務編輯功能實作
系列文
Django - 製作網頁一點通28
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言