前言

快要結尾了,稍微喘口氣,來做個簡單的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'],
            # ...
        }
    ]
  5. 建個templates資料夾備用mkdir templates,在裡面丟個index.html備用
  6. 在todoapp裡面新增一個urls.py內容如下
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('', views.index),
    ]
  7. todoapp裡面的views.py稍微加上
    from django.shortcuts import render
    from django.http import JsonResponse
    
    # Create your views here.
    
    def index(request):
        return JsonResponse({'hello': 'world'})
  8. 再來在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
  2. 接著下python manage.py makemigrationspython manage.py migrate
  3. 修改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})
  4. 接著是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"),
    ]
  5. 最後在把我們一開始新增的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吧!

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