用 Django 建立網站起手式(下)

下篇來記錄子目錄的設定、前端環境的建立和 model 資料庫的連結與處理,整體流程上來說,通常會以 urls -> views -> model -> render templates 來進行。

專案子目錄的建立

一個子目錄是一個資料夾,也可視為一個模組,必須包含 urls.pyviews.py 檔案,urls.py 是用來管理整個模組的頁面路徑結構,而 views.py 則是負責渲染程式到頁面的 Python 函式模組。為了每個子目錄連結都可以在全站中來去自如,需要將資料夾模組化,所以要建立 __init__.py 檔案。

在專案資料夾內新增子目錄

除了自己手動新建資料夾之外,Django 也有指令來創建:

  • $ django-admin startproapp 資料夾名稱
  • $ python manage.py startapp 資料夾名稱: 這款較可確保執行環境的穩定

每一個透過 Django startapp 指令生成的資料夾,都可以當作一個應用程式,功能比較完整,不用再自己刻,但是接下來兩個檔案,就必須自己建立了XD。

  • urls.py
    經過上篇的介紹,就會知道這個檔案是用來管理入口頁的結構和路徑,而且順序很重要。
    1
    2
    3
    4
    5
    6
    7
    8
    from django.urls import path
    from . import views

    app_name = '子目錄'

    urlpatterns = [
    path('', views.home, name='home'),
    ]
  • views.py
    而 views 則是負責渲染路徑的程式。
    1
    2
    3
    4
    from django.shortcuts import render

    def home(req):
    return render(req, '子目錄/index.html')

前端環境的設定與載入

若是想要直接在專案資料夾裡處理 CSS,在上篇有提到可以在專案資料夾下建立 static 資料夾,專門存放靜態檔案,例如 CSS、JS 和 圖片。若不使用套件,就可以很單純的加上 CSS 檔案並連結到樣版 html。

1
2
3
4
5
6
7
8
9
10
11
12
13
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}網站名稱{% endblock %}</title>
<link rel="stylesheet" href="{% static "styles/style.css" %}">
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>

詳細的前端工具載入與設定會在 JS 打包篇詳解~


在 Django 與資料庫連結

在 html 製作要與資料庫連動的表單(form),有兩個跟資料庫有關的屬性:action 和 method

1
<form action="把資料送去哪裡" method="用什麼方法送"></form>

action 的值是一個網頁路徑,是指這個表單的內容在啟動觸發器時,會把資料送去哪裡處理;method 的值則是選擇用什麼方法送去,目前 html 只有兩種方法:GET 和 POST。

  • GET: 網址會把輸入資料顯示在網址上,稱作 QueryString,用在查詢或搜尋比較好
  • POST: 反之,網址會把輸入資料隱藏,資料會在 request 物件裡的 body,撈資料也是從這裡,但至少在前台不會顯示,像是新增、更新和刪除等操作用 POST 會較合適。

model.py 生成資料庫

要先在 Django 專案資料夾裡生成資料庫,就可以直接在專案資料夾裡生成操作資料表(table)。用 ORM(Object-Relationship Mapping 物件關聯性對照)寫入資料或讀取在子目錄下的 model.py。

1
2
3
4
5
6
7
8
9
10
from django.db import models

class Name(models.Model):
name = models.CharField(max-length=100)
description = models.TextField(max-length=200)
create_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
.
.
etc.

建完模組後,記得也要去 settings.py 註冊 APP。
增加完之後用 Django 指令生成資料表和欄位:

1
$ python manage.py makemigrations

生成一個 migrations/0001.initial.py 給子目錄,但目前只是生成,還要去 Apply 它,所以

1
$ python manage.py migrate

就會產生從 model.py 寫好的模組建成資料庫檔案,而 table 的名稱就會叫做 子目錄_class名稱。若要更新修改模組檔案,還是要再跑上述兩個指令。另外,還可以指定要回到哪條 migration,但是該條所做的內容也會跟著消失,雖然有版控的感覺,但倒退嚕還是會有一定的風險在,雖然再執行一次 migrate 資料結構會回來,但內容卻會永遠消失。


CRUD

接下來開始資料庫最基本的操作,就是 CRUD,分別是 Create 新增創建、Read 查詢搜尋顯示、Update 更新資料、Delete 刪除資料。以下是在 Django 框架下操作 CRUD 的練習和心得,在 Django 操作 CRUD,必須同時處理三個檔案,分別是 urls.pyviews.pyhtml,那我們就繼續看下去~😄


Create 新增

  • urls.py: 新增路徑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from django.urls import path
    from . import views

    app_name = '子目錄'

    urlpatterns = [
    path("", views.home, name="index") # 子目錄首頁
    path("new", views.new, name="new") # 要輸入資料的頁面
    path("add", views.create, name="add") # 寫入模組
    ]
  • views.py: 渲染路徑和程式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from django.shortcuts import render, redirect # 渲染路徑、轉址兼解讀路徑
    from django.views.decorators.http import require_POST # 判斷是不是 POST
    from .models import class名稱 # 從同家的 model 取 class

    # 要輸入資料的頁面
    def new(req):
    return render(req, "子目錄/new.html")

    # 寫入資料庫
    @require_POST
    def create(req):
    變數_單 = class名稱(
    name=req.POST["name"],
    description=req.POST["description"]
    )
    變數_單.save()

    return redirect("root")
  • new.html: 渲染到輸入資料的頁面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <h1>新增</h1>
    <form action="{% url '子目錄:add' %}" method="post">
    <!-- 使用 POST 方法時的安全驗證機制 -->
    {% csrf_token %}

    <div>
    <label for="name">姓名</label>
    <input type="text" id="name" name="name">

    <label for="description">簡介</label>
    <textarea id="description" name="description"></textarea>
    .
    .
    etc.
    </div>

    <input type="submit" value="新增">
    </form>

Read 查詢

查詢有兩件事要做:

  1. 在子目錄首頁檢視總列表(index)
  2. 點擊總列表內容後會連到詳細資料頁面去(show)

要在子目錄的 index.html 中列出清單,會用 class.object.all() 來列出:

  • views.py: 渲染路徑和程式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from django.shortcuts import render, redirect # 渲染路徑、轉址兼解讀路徑
    from django.views.decorators.http import require_POST # 判斷是不是 POST
    from .models import class名稱 # 從同家的 model.py 取 class

    # 要列出總清單的頁面
    def home(req):
    變數_複 = class名稱.objects.all()
    return render(req, "子目錄/index.html", {"key": 變數_複})

    # 要輸入資料的頁面
    def new(req):
    return render(req, "子目錄/new.html")

    # 寫入資料庫
    @require_POST
    def create(req):
    變數_單 = class名稱(
    name=req.POST["name"],
    description=req.POST["description"]
    )
    變數_單.save()

    return redirect("root")
  • index.html: 渲染到要列出總清單的頁面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <h1>總列表</h1>
    <!-- 前往新增的頁面 -->
    <a href="{% url '子目錄:new' %}">新增</a>
    <!-- 用迴圈渲染出總列表 -->
    {% for 變數_單 in 變數_複 %}
    <!-- 設計卡片囉~ -->
    <div>
    <!-- 渲染出名稱 -->
    {{ 變數_單.name }}
    </div>
    {% endfor %}

第二個是點擊內容會連去該筆資料的詳細頁面,網址的目標設定為 子目錄/編號

  • urls.py: 新增路徑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from django.urls import path
    from . import views

    app_name = '子目錄'

    urlpatterns = [
    path("", views.home, name="index") # 子目錄首頁
    path("new", views.new, name="new") # 要輸入資料的頁面
    path("add", views.create, name="add") # 寫入模組

    # 網址應該是一個變動的變數,所以要用<>包住,而且順序非常重要,讓比對性的變數網址放在最後,才不會讓其他網址變成變數的值
    path("<id>", views.show, name="show") # 單筆資料的詳細頁面
    ]
  • views.py: 渲染路徑和程式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    from django.shortcuts import render, redirect get_object_or_404 # 渲染路徑、轉址兼解讀路徑、判斷有無值不然就404
    from django.views.decorators.http import require_POST # 判斷是不是 POST
    from .models import class名稱 # 從同家的 model.py 取 class

    # 要列出總清單的頁面
    def home(req):
    變數_複 = class名稱.objects.all()
    return render(req, "子目錄/index.html", {"key": 變數_複})

    # 要列出單筆資料詳細內容的頁面
    def show(req, id):
    # 為了防止使用者在 QueryString 自己亂找編號,要做一個防止措施
    變數_單 = get_object_or_404(class名稱, pk=id) # 主鍵是 id
    return render(req, "子目錄/show.html", {"key": 變數_單})

    # 要輸入資料的頁面
    def new(req):
    return render(req, "子目錄/new.html")

    # 寫入資料庫
    @require_POST
    def create(req):
    變數_單 = class名稱(
    name=req.POST["name"],
    description=req.POST["description"]
    )
    變數_單.save()

    return redirect("root")
  • show.html: 渲染到要列出單筆資料詳細內容的頁面
    1
    2
    <h1>{{ 變數_單.name }}</h1>
    <p>{{ 變數_單.description }}</p>

Update 更新

接下來會在 show.html 裡放上編輯按鈕,會進入更新頁面輸入內容,按下更新按鈕後回到該筆資料頁面,網址的目標設定為 子目錄/編號/edit

  • urls.py: 新增路徑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from django.urls import path
    from . import views

    app_name = '子目錄'

    urlpatterns = [
    path("", views.home, name="index") # 子目錄首頁
    path("new", views.new, name="new") # 要輸入資料的頁面
    path("add", views.create, name="add") # 寫入模組
    path("<id>/edit", views.edit, name="edit") # 重新輸入資料的頁面
    path("<id>", views.show, name="show") # 單筆資料的詳細頁面
    ]
  • edit.html: 渲染到重新輸入資料的頁面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <h1>更新</h1>
    <form action="{% url '子目錄:show' 變數_單.id %}" method="post">
    {% csrf_token %}

    <div>
    <label for="name">姓名</label>
    <input type="text" id="name" name="name" value="{{ 變數_單.name }}">

    <label for="description">簡介</label>
    <textarea id="description" name="description">{{ 變數_單.description }}</textarea>
    .
    .
    etc.
    </div>

    <input type="submit" value="更新">
    </form>
  • views.py: 渲染路徑和程式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    from django.shortcuts import render, redirect get_object_or_404 # 渲染路徑、轉址兼解讀路徑、判斷有無值不然就404
    from django.views.decorators.http import require_POST # 判斷是不是 POST
    from .models import class名稱 # 從同家的 model.py 取 class

    # 要列出總清單的頁面
    def home(req):
    變數_複 = class名稱.objects.all()
    return render(req, "子目錄/index.html", {"key": 變數_複})

    # 要列出單筆資料詳細內容的頁面,若從編輯按鈕的 POST 進入,則會進入 if 流程
    def show(req, id):
    變數_單 = get_object_or_404(class名稱, pk=id)

    if req.method == "POST":
    變數_單.name = req.POST["name"]
    變數_單.description = req.POST["description"]
    .
    .
    etc.
    變數_單.save()

    return redirect(f"/子目錄/{變數_單.id}")
    else:
    return render(req, "子目錄/show.html", {"key": 變數_單})

    # 在單筆資料頁面下按下編輯按鈕時後進入的頁面
    def edit(req, id):
    變數_單 = get_object_or_404(class名稱, pk=id)
    return render(req, "子目錄/edit.html", {"key": 變數_單})

    # 要輸入資料的頁面
    def new(req):
    return render(req, "子目錄/new.html")

    # 寫入資料庫
    @require_POST
    def create(req):
    變數_單 = class名稱(
    name=req.POST["name"],
    description=req.POST["description"]
    )
    變數_單.save()

    return redirect("root")
  • show.html: 渲染到要列出單筆資料詳細內容的頁面
    1
    2
    3
    4
    <h1>{{ 變數_單.name }}</h1>
    <p>{{ 變數_單.description }}</p>

    <a href="{% url '子目錄:edit' 變數_單.id %}">編輯</a>

Delete 刪除

最後的刪除按鈕也會放在 show.html 裡,按下刪除後會連動資料苦的內容一併刪除,按下刪除按鈕後回到 index.html 總列表頁面,網址的目標設定為 子目錄/編號/delete

  • urls.py: 新增路徑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from django.urls import path
    from . import views

    app_name = '子目錄'

    urlpatterns = [
    path("", views.home, name="index") # 子目錄首頁
    path("new", views.new, name="new") # 要輸入資料的頁面
    path("add", views.create, name="add") # 寫入模組
    path("<id>/edit", views.edit, name="edit") # 重新輸入資料的頁面
    path("<id>/delete", views.delete, name="delete") # 刪除後跳轉
    path("<id>", views.show, name="show") # 單筆資料的詳細頁面
    ]
  • views.py: 渲染路徑和程式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    from django.shortcuts import render, redirect, get_object_or_404 # 渲染路徑、轉址兼解讀路徑、判斷有無值不然就404
    from django.views.decorators.http import require_POST # 判斷是不是 POST
    from .models import class名稱 # 從同家的 model.py 取 class

    # 要列出總清單的頁面
    def home(req):
    變數_複 = class名稱.objects.all()
    return render(req, "子目錄/index.html", {"key": 變數_複})

    # 要列出單筆資料詳細內容的頁面,若從編輯按鈕的 POST 進入,則會進入 if 流程
    def show(req, id):
    變數_單 = get_object_or_404(class名稱, pk=id)

    if req.method == "POST":
    變數_單.name = req.POST["name"]
    變數_單.description = req.POST["description"]
    .
    .
    etc.
    變數_單.save()

    return redirect(f"/子目錄/{變數_單.id}")
    else:
    return render(req, "子目錄/show.html", {"key": 變數_單})

    # 在單筆資料頁面下按下編輯按鈕時後進入的頁面
    def edit(req, id):
    變數_單 = get_object_or_404(class名稱, pk=id)
    return render(req, "子目錄/edit.html", {"key": 變數_單})

    # 要輸入資料的頁面
    def new(req):
    return render(req, "子目錄/new.html")

    # 寫入資料庫
    @require_POST
    def create(req):
    變數_單 = class名稱(
    name=req.POST["name"],
    description=req.POST["description"]
    )
    變數_單.save()

    return redirect("root")

    # 連同資料庫刪除該筆資料(硬刪)
    @require_POST
    def delete(req, id):
    變數_單 = get_object_or_404(class名稱, pk=id)
    變數_單.delete()
    return redirect("子目錄:index")
  • show.html: 渲染到要列出單筆資料詳細內容的頁面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <h1>{{ 變數_單.name }}</h1>
    <p>{{ 變數_單.description }}</p>

    <a href="{% url '子目錄:edit' 變數_單.id %}">編輯</a>

    <form action="{% url '子目錄:delete' 變數_單.id %}" method="post">
    {% csrf_token %}
    <button>刪除</button>
    </form>

小結

Django 起手式下篇從子目錄的建立開始介紹,我這邊指的子目錄,其實就代表一個獨立的應用程式,它們各自管理自己名下的檔案們,就不會跟其它的子目錄有衝突,我也很喜歡這種設計。接下來淺談了在 Django 環境中也可以把前端環境整合進來,但我打算將過程、實作和打包再另外寫一篇。

然後就開始介紹連接資料庫、table 生成和 CRUD(我真的很常把 CRUD 寫成 CRUD…好吃!),一開始學習時覺得好多東西,但一旦了解脈絡,再加上練習,我想也能很快駕馭它。

dark
sans