iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
自我挑戰組

打掉重練!Django的還債之旅~系列 第 27

Day27. 首先先來簡單做個Todolist頁面~

  • 分享至 

  • xImage
  •  

前言

快要結尾了,稍微喘口氣,來做個簡單的Todolist頁面吧!
會參考以下兩篇文章進行

稍微以這兩個為基底下去修改和融合~

正題

初始化

首先先建立django project 和 app,然後把一些基礎的設定弄完再做一個簡單的hello world吧!相信大家都很熟了~

  1. 先下django-admin startproject todolist
  2. 然後進去cd todolist
  3. 接著下python manage.py startapp todoapp
  4. 再來呢修改settings.py
# 在installed apps裡面加上剛剛新增的app name
INSTALLED_APPS = [
    'todoapp',
    # ...
]

# templates 中的 dirs 加上
TEMPLATES = [
    {
        # ...
        'DIRS' : ['templates'],
        # ...
    }
]
  1. 建個templates資料夾備用mkdir templates,在裡面丟個index.html備用
  2. 在todoapp裡面新增一個urls.py內容如下
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),
]
  1. todoapp裡面的views.py稍微加上
from django.shortcuts import render
from django.http import JsonResponse

# Create your views here.

def index(request):
    return JsonResponse({'hello': 'world'})
  1. 再來在todolist的urls.py加上
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todoapp.urls'))
]

如此一來python manage.py migratepython manage.py runserver,就能看到json格式的hello world囉~

開始新增todo

  1. 在todoapp中的models.py加上
from django.db import models

# Create your models here.
class Todo(models.Model):
    title=models.CharField(max_length=350)
    complete=models.BooleanField(default=False)

    def __str__(self):
        return self.title
  1. 接著下python manage.py makemigrationspython manage.py migrate
  2. 修改todoapp下的views.py
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.forms.models import model_to_dict
from .models import Todo
import json

# Create your views here.

def index(request):
    return render(request, 'base.html')


def api(request):
    todos = Todo.objects.all()
    return JsonResponse({"data":list(todos.values())})


@require_http_methods(["POST"])
def add(request):
    body = request.body.decode("utf-8")
    body = json.loads(body)
    title = body.get("title", "")
    todo = Todo(title=title)
    todo.save()
    return JsonResponse({"todo_id": todo.id, "complete": todo.complete, "todo_title": todo.title})


def update(request, todo_id):
    todo = Todo.objects.get(id=todo_id)
    todo.complete = not todo.complete
    todo.save()
    return JsonResponse({"todo_id": todo_id, "complete": todo.complete})


def delete(request, todo_id):
    todo = Todo.objects.get(id=todo_id)
    todo.delete()
    return JsonResponse({"todo_id": todo_id})
  1. 接著是todoapp下的urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name="index"),
    path('api/', views.api, name="api"),
    path('api/add/', views.add, name="add"),
    path('api/delete/<int:todo_id>', views.delete, name="delete"),
    path('api/update/<int:todo_id>', views.update, name="update"),
]
  1. 最後在把我們一開始新增的index.html修改
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
    
    <script src="https://code.jquery.com/jquery-3.7.0.js" integrity="sha256-JlqSTELeR4TLqP0OG9dxM7yDPqX1ox/HfgiSLBj8+kM=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>

<body onload="get_all_list()">
    <div style="margin-top: 50px;" class="ui container">
        <h1 class="ui center aligned header">To Do App</h1>

        <form class="ui form">
            <div class="field">
                <label>Todo Title</label>
                <input class="input-todo" name="title" id="title" placeholder="Enter Todo..." value=""><br>
            </div>
            <button class="ui blue button btn-add" type="button" >Add</button>
        </form>

        <hr>
        <div class="todos">
        </div>

        <script>
            let state = {
                todos: []
            }

            function get_all_list(){
                axios.get("/api/")
                    .then(response => {
                        response.data.data.forEach(todo => {
                            state = {
                                todos: [...state.todos, {
                                    id: todo.id,
                                    content: todo.title,
                                    isDone: todo.complete
                                }]
                            }
                        });
                        updateState(state)
                    })
                }

            // 更新 state
            function updateState(newState) {
                state = newState;
                render()
            }
            
            // state => UI
            function render() {
                // 先把畫面清空
                $('.todos').empty();
                console.log(state.todos)
                $('.todos').append(
                // 把每個 todo 的 HTML 集合起來放到畫面上
                state.todos.map(todo => Todo(todo)).join('')
                );
            }
            
            // Todo component
            function Todo({id, content, isDone}) {
                return `
                <div class="ui segment todo" data-id="${id}">
                    <p class="ui big header"> ${id} | ${content} </p>

                    ${Span({
                        className: isDone ? 'ui green label' : 'ui gray label',
                        content: isDone ? 'Complete' : 'Not Completed'
                    })}

                    ${Button({
                        className: 'blue btn-update',
                        content: 'Update'
                    })}

                    ${Button({
                        className: 'red btn-delete',
                        content: 'Delete'
                    })}

                </div>
                `
            }

            function Span(props){
                return `<span class="${props.className}">${props.content}</span>`
            }
            
            // Button component
            function Button(props) {
                return `
                <a class="ui ${props.className} button">${props.content}</a>
                `
            }
            
            // 新增 todo
            $('.btn-add').click(() => {
                const content = $('.input-todo').val();
                if (!content) return;
                $('.input-todo').val('');
                axios.post("/api/add/", 
                    {
                        "title": content
                    },
                    {
                        headers: { 
                        "X-CSRFToken": "{{csrf_token}}",
                        },
                    }
                )
                .then(response => {
                    todo_id = response.data["todo_id"]
                    title = response.data["todo_title"]
                    complete = response.data["complete"]
                    // 更新 state
                    updateState({
                        todos: [...state.todos, {
                            id: todo_id,
                            content: title,
                            isDone: complete
                        }]
                    });
                })
            });
            
            // 刪除 todo
            $('.todos').on('click', '.btn-delete', e => {
                const id = Number($(e.target).parents('.todo').attr('data-id'));
                axios.get("/api/delete/"+id)
                .then(response => {
                    d_id = response.data["todo_id"]
                    updateState({
                        todos: state.todos = state.todos.filter(todo => todo.id !== d_id)
                    });
                })
            });
            
            // 未完成 -> 已完成
            $('.todos').on('click', '.btn-update', e => {
                const id = Number($(e.target).parents('.todo').attr('data-id'));
                axios.get("/api/update/"+id)
                .then(response => {
                    u_id = response.data["todo_id"]
                    complete = response.data["complete"]
                    updateState({
                        todos: state.todos.map(todo => {
                            if (todo.id !== u_id) return todo;
                            return {
                            ...todo,
                            isDone: complete
                            }
                        })
                    })
                });
            }); 
        </script>
    </div>
</body>

</html>

完成以上步驟後下python manage.py runserver就能看到簡單的ToDoList囉~
https://ithelp.ithome.com.tw/upload/images/20231010/20162905rK4vgXs1dm.png
那整體的介面是從上面那個youtube影片摳過來的,我自己對網頁的設計美感有點差而且CSS苦手XD
而JS則是透過下面那篇文章去修改的,當時想要學習React剛好看到這篇,改完就變成純JS的component和state的用法,收穫頗大!以前都是直接打API拿到Json後把html重組,現在知道了component和state後覺得JS那邊的可讀性變高很多~

結語

明天就來試試看對這個簡單的ToDoList寫些test吧!

完整的程式碼在這邊
https://github.com/m124578n/IronMan_ToDoList


上一篇
Day26. render,終於要render context了!
下一篇
Day28. 寫些test來測試我們的網頁!
系列文
打掉重練!Django的還債之旅~30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言