前言
快要結尾了,稍微喘口氣,來做個簡單的Todolist頁面吧!
會參考以下兩篇文章進行
- I built the same app 3 times | Which Python Framework is best? Django vs Flask vs FastAPI
- 前端框架 - 先別急著學 React
稍微以這兩個為基底下去修改和融合~
正題
初始化
首先先建立django project 和 app,然後把一些基礎的設定弄完再做一個簡單的hello world吧!相信大家都很熟了~
- 先下
django-admin startproject todolist
- 然後進去
cd todolist
- 接著下
python manage.py startapp todoapp
- 再來呢修改
settings.py
# 在installed apps裡面加上剛剛新增的app name INSTALLED_APPS = [ 'todoapp', # ... ] # templates 中的 dirs 加上 TEMPLATES = [ { # ... 'DIRS' : ['templates'], # ... } ]
- 建個templates資料夾備用
mkdir templates
,在裡面丟個index.html
備用 - 在todoapp裡面新增一個
urls.py
內容如下from django.urls import path from . import views urlpatterns = [ path('', views.index), ]
- todoapp裡面的
views.py
稍微加上from django.shortcuts import render from django.http import JsonResponse # Create your views here. def index(request): return JsonResponse({'hello': 'world'})
- 再來在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 migrate
後 python manage.py runserver
,就能看到json格式的hello world囉~
開始新增todo
- 在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
- 接著下
python manage.py makemigrations
後python manage.py migrate
- 修改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})
- 接著是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"), ]
- 最後在把我們一開始新增的
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囉~
那整體的介面是從上面那個youtube影片摳過來的,我自己對網頁的設計美感有點差而且CSS苦手XD
而JS則是透過下面那篇文章去修改的,當時想要學習React剛好看到這篇,改完就變成純JS的component和state的用法,收穫頗大!以前都是直接打API拿到Json後把html重組,現在知道了component和state後覺得JS那邊的可讀性變高很多~
結語
明天就來試試看對這個簡單的ToDoList寫些test吧!