changed 7 years ago
Published Linked with GitHub

Django 網站開發

【目次】


Reference Website:

  1. Django documentation
  2. Django Packages(可直接引用"功能套件",不用自己重複造)
  3. Stack Overflow (技術交流平台寫程式遇到問題,用以尋求解答)
  4. github (專案的免費開源分享空間專案下載、協作)
  5. Model field reference
  6. Making queries
  7. Templates

課程簡介與目標

  • Django 是 python 實作而成的網頁框架
  • 具備基本的 python 能力,並懂一點 HTML、CSS 與 Javascript
  • 建立簡單的靜態網頁,並加入動態資料
  • 將網頁應用程式佈署到免費雲端空間

網頁運作原理

  • 一個網頁在運作時,主要有三項參與其中:

    1. client(瀏覽器)
    2. web server(資料的運算和處理)
    3. database(儲存資料)
  • 流程:

    client 瀏覽器輸入網址
    > 網址會被路由器帶到正確的 web server 的位置
    > web server 接收到請求後,視其必要跟 database 索取資料

    索取 database 資料後
    > 經過 web server 對資料的運算和處理
    > 再把結果傳回 client 瀏覽器


Django 基本介紹

  • 誰使用 Django ?
    Top 10 sites built with Django Framework:Instagram, Mozilla Firefox, Pinterest, NASA

  • Django 特點

    • 歷史悠久,經得起考驗。 (首次發行:2005年7月)
    • 市佔率最高
    • 最完整的技術文件
    • 豐富的套件
    • 強大的社群 (當開發上遇到困難,都能快速的找到解決方法)
    • 提供 total solution,初學者可透過 Django 內建的模塊了解網站運作。(包山包海的套件,已內建 各種網站運作需要的模塊)

知名の Python 網頁框架 特點 備註
Django 大而全,內建全套解決方案 讓初學者能專注搞懂如何運作,就能快速開發網站
Flask 微框架,輕盈,簡單 適用時機:小專案
Tornado 微框架,少而精,non-blocking network IO 可實現異步操作
Pyramid 注重靈活性,所有模塊都需另外找套件搭配運用 Django 的相反版本,適合"不喜歡被某個套件綁住的人使用"

  • Django 的原理與架構
    • Django 存放在 web server
    • MTV
      • Models(定義資料庫的結構,e.g.XX資料表中,有XX欄位.欄位長度.是否可為空值
      • Templates(最後的 HTML 呈現)
      • Views(資料的邏輯運算)
    • Browser > URLS > Views > Models -(ORM 語法)-> Database
      > Models > Views > Templates > Browser
    • Why MTV ?
      • 把 Models, Templates, Views 做切割
      • 避免 Spaghetti Code (捲成一團)
        1. 分離商業邏輯(後端)與UI(前端)
        2. 前後端可獨立作業
        3. 擁有更多彈性
        4. 較容易維護
        5. 降低複雜度

    傳統 -> 把 Models, Templates, Views 寫在同一支程式中,造成混亂和維護困難


Step00:搭建 python3 虛擬環境

  • 把它想像成 - 完全獨立且隔絕的作業環境
    專門給這個專案使用の
    完全不會跟原本的作業環境有衝突
  • why 虛擬環境?
    • 假設"現在"作業環境中的套件是"最新的版本",但此專案卻需要舊的版本。
    • 把新的版本降為舊的版本

新建 Django 專案資料夾

  • myproject 代換為你所想命名的"資料夾名稱"
~$ mkdir myproject_test
~$ cd myproject_test

在 Django 專案資料夾中,建立 python3 虛擬環境

  • venv 套件
  • py3env 代換為你所想命名的"虛擬環境名稱"
    • 啟動 python3 虛擬環境的資料夾
(linux/mac)
~/myproject_test$ python3 -m venv py3env

(windows)
C:\Python35\python -m venv myvenv  

啟動虛擬環境

  • 出現 (py3env) > 成功
  • 關閉虛擬環境:
    • ~/myproject_test$ deactivate
    • (py3env) 消失了~
(linux/mac)
~/myproject_test$ source py3env/bin/activate

(windows)
py3env/Scripts/activate

Step01:在 python3 虛擬環境下,安裝 Django

下載與安裝 Django

  • 若要安裝 Django 的某個特定版本
    • pip install Django==1.9.10
    • pip install Django
~/myproject_test$ pip install Django==1.9.10
  • pip freeze 檢查當前的 Django 版本
~/myproject_test$ pip freeze

在本關卡中會學習到什麼

  • Django 網站開發:快速打造完美網頁應用程式
    • 自己打造專屬の記帳 app

Step1:建立 Django の project

project 與 apps

把一個 project 專案 想像成 一個網站

  • 以 project 為最上層單位,一個 project 可以包含多個 app
  • What is app?
    • 代表 同一個網站中,不同的功能模塊
    • 如同 樂高積木,可隨時擴充、拔脫,也可把 app 拿去給別的 project 使用
    • 使用時機:一個論壇類型的網站可能會有會員管理功能、語音內容區塊、文字內容區塊等,可建立不同的 app 來管理。
django-admin startproject project_chia .
  • project_chia 代換為 你所想命名的"資料夾名稱"
  • . 代表 在當下的位置
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ ls py3env (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ django-admin startproject project_chia . (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ ls manage.py project_chia py3env (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ cd project_chia (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/project_chia$ ls __init__.py settings.py urls.py wsgi.py

解釋各個 .py 的作用:

  • manage.py:跟 django-admin 幾乎是一模一樣的功能,它是一個管理器。很多功能都需要透過這個管理器來執行。
  • project_chia 資料夾底下有 settings.pyurls.pywsgi.pyinit.py
    • settings.py:Django 中各種環境設定
    • urls.py:根據瀏覽器輸入的網址,指派 views.py 底下的哪一支程式來做事情
    • wsgi.py:web server 跟網頁應用程式(django)之間如何做溝通
    • init.py:讓 project_chia 變成一個套件模組

Step2:Project 參數設定介紹

編輯 settings.py

# 這些 app 是 Django 內建的 app , # 亦即"一個 project 可包含多個 app" の app INSTALLED_APPS = [ 'django.contrib.admin', #後台管理系統 'django.contrib.auth', #權限的管控 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', #我們要建立static資料夾 ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', #DjangoTemplates-->我們要建立Templates資料夾 'DIRS': [], #定義templates的路徑,它是一個list 'APP_DIRS': True, #當我們以後在project底下建立app之後,Django會自動探索app底下是否有templates的資源 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', #sqlite3是Django裡預設的資料庫 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), #最後會產生db.sqlite3的檔案 } } TIME_ZONE = 'UTC' #時區:世界標準時間 * 改為--->TIME_ZONE = 'Asia/Taipei' #時區:台北 STATIC_URL = '/static/' #存取靜態資源(CSS, JavaScript, Images)

為 project 建立專屬資料庫

  • 回到 manage.py 所在的位置,再執行 python manage.py migrate
    • 資料的寫進寫出,都是與 db.sqlite3 做互動
    • 與資料庫結構相關的檔案,還有 models.py (之後會使用到~先記在腦袋裡)
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/project_chia$ ls __init__.py settings.py urls.py wsgi.py (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/project_chia$ cd .. (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ ls manage.py project_chia py3env (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ python manage.py migrate Operations to perform: Apply all migrations: contenttypes, auth, admin, sessions Running migrations: Rendering model states... DONE Applying contenttypes.0001_initial... OK ... Applying sessions.0001_initial... OK (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ ls db.sqlite3 manage.py project_chia py3env

建立app

  • 回到 manage.py 所在的位置,再執行 python manage.py startapp chia_app
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ python manage.py startapp chia_app (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ ls chia_app db.sqlite3 manage.py project_chia py3env

解釋各個 .py 的作用:

  • chia_app 資料夾底下有 models.py、migrations (資料夾)、admin.pyviews.pytests.pyapps.pyinit.py
    • models.py
      • 把它當作一個空的 database
      • 設定 database 的結構:可建立一個一個的 table,每個 table 中又有好幾個 column
    • migrations (資料夾):每當我們對 models.py 做修改,紀錄修改 database 的歷史步驟,還有版本控制的功能
    • admin.py:後台管理介面的管理程式。
      • 打開後台管理介面針對 models.py 中所定義的 table 做其內容的 "新增/修改/刪除"
    • views.py:定義各種邏輯運算
    • tests.py:方便我們測試的檔案
    • apps.py:針對 app 的組態管理,存放這支 app 的 meta 檔案(目前比較不會用到)

Step3:製作網頁的第一步:利用 Django 生成靜態網頁

在本關卡中會學習到什麼

  • 初步熟悉 Django 的 MTV 架構
  • 使用 Django 生成簡單的靜態網頁
    • 靜態網頁:
      先不跟 database 做互動,只做單純的展示功能。資料寫死在 HTML 中。

建立一個簡單的靜態網頁

  • 打開 Sublime Text 3 ,建立 example_hello.html
  • 路徑:/home/testchia/myproject_test/example_hello.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Example_chia</title> </head> <body> <h1>Hello Django 0.0</h1> </body> </html>

Url Dispatcher - 如何解析使用者於瀏覽器輸入的 url

  • Url Dispatcher:使用 urls.py
    • urls.py 用來解析使用者於瀏覽器輸入的 url
    • 根據解析後的結果,去指派某一支特定的程式來做運作

urls.py

from django.conf.urls import url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), #若輸入的網址符合 admin/ 的patterns,才會指派admin.site.urls這支程式來運作 => 開啟後台管理介面 #admin.site.urls這支程式 --> django內建之名稱為admin的apps其底下有一支程式叫site,site.py裡面有一個叫urls的function ]
  • Regular Expression:
    • ^: 以什麼為開頭
    • $: 以什麼為結尾
    • .: 任意字元
    • +: 一個字元以上
    • ?: 是否有某個字元出現都可以
    • \d: 數字
    • (): 擷取符合的 pattern
  • 回到 manage.py 所在的位置,再執行 python manage.py runserver
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ python manage.py runserver Performing system checks... System check identified no issues (0 silenced). August 31, 2018 - 16:21:17 Django version 1.9.10, using settings 'project_chia.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. Not Found: / [31/Aug/2018 16:21:20] "GET / HTTP/1.1" 200 1767 Not Found: /favicon.ico [31/Aug/2018 16:21:20] "GET /favicon.ico HTTP/1.1" 404 1942
  • http://127.0.0.1:8000/
    -> http://127.0.0.1 網頁伺服器現在搭建在"本機端localhost的電腦"
    -> :8000/ 由於本機端裝有各式各樣的應用,這邊就必須指定其中一個入口(專門給 Django 使用)
  • 若想更改原本預設的 port 為 8001,並 runserver
    • python manage.py runserver 8001
    • 127.0.0.1:8001/admin

Url Dispatcher and View - Django 如何根據 url 指派正確的 view 程式運行

urls.py

[方法一] 效能好

from django.conf.urls import url from django.contrib import admin from chia_app import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^hello/', views.hello), ]

[方法二] 效能差一點,因為 Django 需要另外去解析

from django.conf.urls import url from django.contrib import admin #from chia_app import views urlpatterns = [ url(r'^admin/', admin.site.urls), #url(r'^hello/', views.hello), url(r'^hello/', 'chia_app.views.hello'), ]

views.py

from django.shortcuts import render, HttpResponse # Create your views here. def hello(request): return HttpResponse('HELLO 0.0')

Views and Templates - 用 Django 生成靜態網頁

  • 用 render 來達成 Views 和 Templates 的溝通
    • 將之前寫的 example_hello.html 放入 templates 資料夾
      • 路徑:/home/testchia/myproject_test/chia_app/templates/app/example_hello.html
    • 改寫 views.py
    • settings.py 加入 chia_app
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ cd chia_app (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app$ mkdir templates (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app$ cd templates (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app/templates$ mkdir app

views.py

from django.shortcuts import render, HttpResponse # Create your views here. def hello(request): return render(request,'app/example_hello.html',{})

settings.py

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'chia_app', ]

static 資料夾 - 裝飾網頁

  • 使用 bootstrap 的免費模板 -> download "Dashboard" 並修改它
    • bootstrap 是免費的 CSS, JavaScript 的資源套件,我們可以把它 download 並套用

擺放位置

  1. 找到 "Dashboard" 所在的位置:/home/testchia/下載/bootstrap-4.1.3/site/docs/4.1/examples/dashboard
  2. 將 index.html 移至 /home/testchia/myproject_test/chia_app/templates/app/index.html
  3. 將 dashboard.css 移至 /home/testchia/myproject_test/chia_app/static/css/dashboard.css
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ cd chia_app (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app$ mkdir static (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app$ cd static (py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app/static$ mkdir css
  1. 放置 bootstrap.min.css 到路徑:/home/testchia/myproject_test/chia_app/static/css/bootstrap.min.css

urls.py

from django.conf.urls import url from django.contrib import admin from chia_app import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^hello/', views.hello), url(r'^$', views.frontpage), ]

views.py

from django.shortcuts import render, HttpResponse # Create your views here. def hello(request): return render(request,'app/example_hello.html',{}) def frontpage(request): return render(request,'app/index.html',{})

修改路徑 index.html

[方法一] (寫死的方式)

<link rel="icon" href="../../../../favicon.ico"> #刪除 <!-- Bootstrap core CSS --> <link href="static/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="static/css/dashboard.css" rel="stylesheet">

[方法二] (有彈性的方式)

{% load staticfiles %} #加上 <link rel="icon" href="../../../../favicon.ico"> #刪除 <!-- Bootstrap core CSS --> <!-- Bootstrap core CSS --> <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{% static 'css/dashboard.css' %}" rel="stylesheet">


Step4:利用 Django 製作動態網頁 - 以記帳本網頁應用程式為例

在本關卡中會學習到什麼

  • 使用動態資料產生網頁內容
  • 製作可互動的網頁
    • 動態網頁:跟 database 做互動。

Model - 操作資料庫結構

  • 使用 Django 的 Model 定義資料庫結構

models.py

  • 一個 類別(class) 就是 1個table
    • 本專案需要 2 個 table:
      • 紀錄(欄位包含:日期、描述、分類、金額、收支)
      • 分類設定(欄位包含:分類自己本身 -> 自己輸入的分類)
from django.db import models from django.db.models import CharField,DateField,ForeignKey,IntegerField # Create your models here. BALANCE_TYPE = ((u'收入',u'收入'),(u'支出',u'支出')) #元組 tuple:可以視為不可改變的串列 ((key,value),(key,value)) class Category(models.Model): category = CharField(max_length=20) class Record(models.Model): date = DateField() description = CharField(max_length=300) category = ForeignKey(Category,on_delete=models.SET_NULL,null=True) #外來鍵是一個(或數個)指向另外一個表格主鍵的欄位。 cash = IntegerField() balance_type= CharField(max_length=2,choices=BALANCE_TYPE) #收/支choices只有兩種,已定義在上方的BALANCE_TYPE
  • 示範專案用到的 Model field types:

  • ForeignKey 的 on_delete 參數:

    • models.CASCADE:相關的資料,全部都會被刪除
    • models.PROTECT:不允許某個資料被刪除
    • models.SET_NULL:要搭配 null = True。被刪除的,會被設成 None
    • models.SET_DEFAULT:要搭配 default。被刪除的,會被設成預設的 未分類
    • models.SET():要傳入 function

Model - 操作資料庫的版本控制 - migrations 資料夾

  • 理解 migrations 的用法與意義
  • 回到 manage.py 所在的位置,再執行
    • 建立 migrations 腳本 (此操作 尚未更動資料庫)
      python manage.py makemigrations [app]

      出現 0001_initial.py 腳本檔案(版本): /home/testchia/myproject_test/chia_app/migrations/0001_initial.py

    • 執行腳本 (此操作 更動資料庫)
      python manage.py migrate [chia_app]
    • 退回特定版本 (使用時機:當後悔更動資料庫時)
      python manage.py migrate [chia_app] [migrate version(0001_initial -->想要退回的版本名稱)]

操作網頁 後台管理介面

  • 使用後台管理介面可輕鬆管理資料庫內容

建立 Super user 帳號、密碼

  • 回到 manage.py 所在的位置,再執行
    python manage.py createsuperuser
    • 統一建立
      帳號:admin
      密碼:@@12345678
      python manage.py runserver

admin.py

  • 要先到 admin.py 註冊 models.py 上面所寫的 2 個 table,這樣才能在後台管理介面中做內容的編輯
from django.contrib import admin from .models import Record, Category # Register your models here. admin.site.register(Record) admin.site.register(Category)

進入後台管理介面



models.py中定義,將後台管理介面的英文顯示,變成中文

from django.db import models from django.db.models import CharField,DateField,ForeignKey,IntegerField # Create your models here. BALANCE_TYPE = ((u'收入',u'收入'),(u'支出',u'支出')) #元組 tuple:可以視為不可改變的串列 ((key,value),(key,value)) class Category(models.Model): category = CharField(max_length=20) def __str__(self): return self.category class Record(models.Model): date = DateField() description = CharField(max_length=300) category = ForeignKey(Category,on_delete=models.SET_NULL,null=True) #外來鍵是一個(或數個)指向另外一個表格主鍵的欄位。 cash = IntegerField() balance_type= CharField(max_length=2,choices=BALANCE_TYPE) #收/支choices只有兩種,已定義在上方的BALANCE_TYPE def __str__(self): return self.description


Django ORM 語法 操作資料庫中的資料

>>> dir(r_get) #不能再做其他query的動作 >>> dir(r_filter) #可再做其他query的動作 >>> r_filter[0] >>> r_filter[0] == r_get


MTV 協作 - 使用資料庫內的動態資料渲染網頁

  • 理解 MTV 之間如何協調溝通,使用動態資料(資料庫中的資料)生成網頁內容。

修改 index.html

{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <meta name="description" content=""> <meta name="author" content=""> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{% static 'css/dashboard.css' %}" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow"> <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">My Project</a> <ul class="navbar-nav px-3"> <li class="nav-item text-nowrap"> <a class="nav-link" href="/accounts/logout">Logout</a> </li> </ul> </nav> <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="#"> <span data-feather="home"></span> 帳務總覽<span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="#"> <span data-feather="file"></span> 設定 </a> </li> </ul> </div> </nav> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="page-header">帳務總覽</h1> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="row placeholders"> <div class="col-xs-8 col-sm-4 placeholder"> <h3>收入</h3> <span class="text-muted"><h2>{{ income }}</h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>支出</h3> <span class="text-muted"><h2><font color="#DF565C">{{ outcome }}</font></h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>存款</h3> <span class="text-muted"><h2><font color="#53DF7D">{{ net }}</font></h2></span> </div> </div> </div> </main> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <h2 class="sub-header">歷史記錄</h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>日期</th> <th>描述</th> <th>分類</th> <th>金額</th> <th>收/支</th> </tr> </thead> <tbody> </tbody> </table> </div> </main> </div> </div> <!-- Bootstrap core JavaScript ================================================== --> </body> </html>

urls.py

  • chia_app 底下的 urls.py(將 urls.py 複製並新增至 chia_app 底下),路徑:/home/testchia/myproject_test/chia_app/urls.py
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.frontpage), ]
  • project_chia 底下的 urls.py,路徑:/home/testchia/myproject_test/project_chia/urls.py
"""project_chia URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.9/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include('chia_app.urls')), ]

index.html

  • The Django template language
    • Variables:{{ variable }}
    • Tags:{% tag %} 處理邏輯的部份
{{ records }}


{% for record in records %}
<tr>
    <td>{{ record.date }}</td>
    <td>{{ record.description }}</td>
    <td>{{ record.category }}</td>
    <td>{{ record.cash }}</td>
    <td>{{ record.balance_type }}</td>
    </tr>
{% endfor %}


{% for record in records %}
<tr>
    <td>{{ record.date | date:"Y-m-d"}}</td>
    <td>{{ record.description }}</td>
    <td>{{ record.category }}</td>
    <td>{{ record.cash }}</td>
    <td>{{ record.balance_type }}</td>
    </tr>
{% endfor %}


views.py

from django.shortcuts import render, HttpResponse from .models import Record # Create your views here. def hello(request): return render(request,'app/example_hello.html',{}) def frontpage(request): records = Record.objects.filter() income_list = [record.cash for record in records if record.balance_type == '收入'] outcome_list = [record.cash for record in records if record.balance_type == '支出'] income = sum(income_list) if len(income_list)!=0 else 0 outcome = sum(outcome_list) if len(outcome_list)!=0 else 0 net = income - outcome return render(request,'app/index.html',{'records':records,'income':income,'outcome':outcome,'net':net}) #可改寫成 return render(request,'app/index.html',locals())

Step5:Template - 模版繼承避免重複的 HTML code

base.html

{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> <meta name="description" content=""> <meta name="author" content=""> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{% static 'css/dashboard.css' %}" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow"> <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">My Project</a> <ul class="navbar-nav px-3"> <li class="nav-item text-nowrap"> <a class="nav-link" href="/accounts/logout">Logout</a> </li> </ul> </nav> {% block content %} {% endblock %} <!-- Bootstrap core JavaScript ================================================== --> </body> </html>

index.html

{% extends 'app/base.html' %} {% block content %} <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="/"> <span data-feather="home"></span> 帳務總覽<span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="/settings"> <span data-feather="file"></span> 設定 </a> </li> </ul> </div> </nav> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="page-header">帳務總覽</h1> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="row placeholders"> <div class="col-xs-8 col-sm-4 placeholder"> <h3>收入</h3> <span class="text-muted"><h2>{{ income }}</h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>支出</h3> <span class="text-muted"><h2><font color="#DF565C">{{ outcome }}</font></h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>存款</h3> <span class="text-muted"><h2><font color="#53DF7D">{{ net }}</font></h2></span> </div> </div> </div> </main> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <h2 class="sub-header">歷史記錄</h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>日期</th> <th>描述</th> <th>分類</th> <th>金額</th> <th>收/支</th> </tr> </thead> <tbody> {% for record in records %} <tr> <td>{{ record.date | date:"Y-m-d"}}</td> <td>{{ record.description }}</td> <td>{{ record.category }}</td> <td>{{ record.cash }}</td> <td>{{ record.balance_type }}</td> </tr> {% endfor %} </tbody> </table> </div> </main> </div> </div> {% endblock %}

Step6:製作 settings.html 設定的頁面

chia_app 底下的 urls.py

from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.frontpage), url(r'^settings$', views.settings), ]

views.py

from django.shortcuts import render, HttpResponse from .models import Record # Create your views here. def hello(request): return render(request,'app/example_hello.html',{}) def frontpage(request): records = Record.objects.filter() income_list = [record.cash for record in records if record.balance_type == '收入'] outcome_list = [record.cash for record in records if record.balance_type == '支出'] income = sum(income_list) if len(income_list)!=0 else 0 outcome = sum(outcome_list) if len(outcome_list)!=0 else 0 net = income - outcome return render(request,'app/index.html',locals()) def settings(request): return render(request,'app/settings.html',{})

settings.html

{% extends 'app/base.html' %} {% block content %} <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="/"> <span data-feather="home"></span> 帳務總覽<span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="/settings"> <span data-feather="file"></span> 設定 </a> </li> </ul> </div> </nav> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3"> <h1 class="page-header">分類設定</h1> </div> <table class="table table-scrollable"> <thead> <tr> <th>分類</th> </tr> </thead> <tbody> <td> </td> </tbody> </table> </main> </div> </div> {% endblock %}
<tbody>
{% for category in categories %}
   <td>{{ category.category }}</td>
{% endfor %}
</tbody>


Form - 利用 HTML 表單與網頁產生互動

  • 透過操作表單讓使用者可以新增、刪除資料庫內的資料,並讓網頁產生改變。

settings.html

{% extends 'app/base.html' %} {% block content %} <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="/"> <span data-feather="home"></span> 帳務總覽<span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="/settings"> <span data-feather="file"></span> 設定 </a> </li> </ul> </div> </nav> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex flex-wrap justify-content-between flex-md-nowrap align-items-center pt-3 pb-2 mb-3"> <h1 class="page-header">分類設定</h1> </div> <form action="/add_category" method="post"> {% csrf_token %} <input type="text" name="add_category_name"> <input type="submit" value="新增分類" class="btn"> </form> <br/> <table class="table table-scrollable"> <thead> <tr> <th>分類</th> </tr> </thead> <tbody> {% for category in categories %} <td> <div class="col-8 .col-sm-6">{{ category.category }}</div> <div class="col-8 .col-sm-6"><a href="/delete_category/{{ category.category }}">刪除</a></div> </td> {% endfor %} </tbody> </table> </main> </div> </div> {% endblock %}

urls.py

om django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.frontpage), url(r'^settings$', views.settings), url(r'^add_category$', views.addCategory), url(r'^delete_category/(?P<category>\w+)',views.deleteCategory) ]

views.py

from django.shortcuts import render, HttpResponse, redirect from .models import Record, Category # Create your views here. def hello(request): return render(request,'app/example_hello.html',{}) def frontpage(request): records = Record.objects.filter() income_list = [record.cash for record in records if record.balance_type == '收入'] outcome_list = [record.cash for record in records if record.balance_type == '支出'] income = sum(income_list) if len(income_list)!=0 else 0 outcome = sum(outcome_list) if len(outcome_list)!=0 else 0 net = income - outcome return render(request,'app/index.html',locals()) def settings(request): categories = Category.objects.filter() return render(request,'app/settings.html',locals()) def addCategory(request): if request.method == 'POST': posted_data = request.POST category = posted_data['add_category_name'] Category.objects.get_or_create(category=category) return redirect('/settings') def deleteCategory(request,category): Category.objects.filter(category=category).delete() return redirect('/settings')

Step7:Form - 利用 Django 表單物件替代 HTML 表單(使用 ModelForm)

chia_app 底下新增 forms.py

  • forms.py 路徑:/home/testchia/myproject_test/chia_app/forms.py
from django.forms import ModelForm from .models import Record class RecordForm(ModelForm): class Meta: model = Record fields = ['date','description','category','cash','balance_type']

index.html

{% extends 'app/base.html' %} {% block content %} <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="/"> <span data-feather="home"></span> 帳務總覽<span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="/settings"> <span data-feather="file"></span> 設定 </a> </li> </ul> </div> </nav> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="page-header">帳務總覽</h1> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="row placeholders"> <div class="col-xs-8 col-sm-4 placeholder"> <h3>收入</h3> <span class="text-muted"><h2>{{ income }}</h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>支出</h3> <span class="text-muted"><h2><font color="#DF565C">{{ outcome }}</font></h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>存款</h3> <span class="text-muted"><h2><font color="#53DF7D">{{ net }}</font></h2></span> </div> </div> </div> </main> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h2 class="sub-header">新增紀錄</h3> </div> <div class="table-responsive"> <form action="/add_record" method="post"> {% csrf_token %} <table class="table table-striped"> <col style="width:15%"> <col style="width:35%"> <col style="width:20%"> <col style="width:18%"> <col style="width:7%"> <thead> <tr> <th>日期</th> <th>描述</th> <th>分類</th> <th>金額</th> <th>收支</th> </tr> </thead> <tbody> <tr> {% for field in record_form %} <td>{{ field }}</td> {% endfor %} </tr> </tbody> </table> <div class="right-area"> <input type="submit" class="btn show-new-item" value="新增紀錄" /> </div> </form> </div> </main> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <h2 class="sub-header">歷史記錄</h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>日期</th> <th>描述</th> <th>分類</th> <th>金額</th> <th>收/支</th> </tr> </thead> <tbody> {% for record in records %} <tr> <td>{{ record.date | date:"Y-m-d"}}</td> <td>{{ record.description }}</td> <td>{{ record.category }}</td> <td>{{ record.cash }}</td> <td>{{ record.balance_type }}</td> </tr> {% endfor %} </tbody> </table> </div> </main> </div> </div> {% endblock %}

dashboard.css

.right-area { float: right; z-index: 2; vertical-align: middle; display: inline-block; margin-top: -15px; } .left-area { float: left; margin-left: 3px; z-index: 2; vertical-align: middle; display: inline-block; margin-top: -15px; } form input { width: 100%; }

views.py

from django.shortcuts import render, HttpResponse, redirect from .models import Record, Category from .forms import RecordForm # Create your views here. def hello(request): return render(request,'app/example_hello.html',{}) def frontpage(request): record_form = RecordForm(initial={'balance_type':'支出'}) records = Record.objects.filter() income_list = [record.cash for record in records if record.balance_type == '收入'] outcome_list = [record.cash for record in records if record.balance_type == '支出'] income = sum(income_list) if len(income_list)!=0 else 0 outcome = sum(outcome_list) if len(outcome_list)!=0 else 0 net = income - outcome return render(request,'app/index.html',locals()) def settings(request): categories = Category.objects.filter() return render(request,'app/settings.html',locals()) def addCategory(request): if request.method == 'POST': posted_data = request.POST category = posted_data['add_category_name'] Category.objects.get_or_create(category=category) return redirect('/settings') def deleteCategory(request,category): Category.objects.filter(category=category).delete() return redirect('/settings')

補充:跳出月曆,選擇日期

jQuery CDN:https://code.jquery.com/

  • 複製 minified 並貼到 base.html 下方:
    bootstrap datepicker cdn:https://cdnjs.com/libraries/bootstrap-datepicker
  • 複製 https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/js/bootstrap-datepicker.min.js 並貼到 base.html 下方
... {% block content %} {% endblock %} <!-- Bootstrap core JavaScript ================================================== --> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/js/bootstrap-datepicker.min.js"></script> <script> $(function(){ $('#datepicker1').datepicker({ format:'yyyy-mm-dd' }); }) </script> </body> </html>

forms.py

from django.forms import ModelForm, TextInput from datetime import date from .models import Record class RecordForm(ModelForm): class Meta: model = Record fields = ['date','description','category','cash','balance_type'] widgets = { 'date':TextInput( attrs = { 'id':'datepicker1', 'value':date.today().strftime('%Y-%m-%d') } ) }

views.py

from django.shortcuts import render, HttpResponse, redirect from .models import Record, Category from .forms import RecordForm # Create your views here. def hello(request): return render(request,'app/example_hello.html',{}) def frontpage(request): record_form = RecordForm(initial={'balance_type':'支出'}) records = Record.objects.filter() income_list = [record.cash for record in records if record.balance_type == '收入'] outcome_list = [record.cash for record in records if record.balance_type == '支出'] income = sum(income_list) if len(income_list)!=0 else 0 outcome = sum(outcome_list) if len(outcome_list)!=0 else 0 net = income - outcome return render(request,'app/index.html',locals()) def settings(request): categories = Category.objects.filter() return render(request,'app/settings.html',locals()) def addCategory(request): if request.method == 'POST': posted_data = request.POST category = posted_data['add_category_name'] Category.objects.get_or_create(category=category) return redirect('/settings') def deleteCategory(request,category): Category.objects.filter(category=category).delete() return redirect('/settings') def addRecord(request): if request.method == 'POST': form = RecordForm(request.POST) if form.is_valid(): form.save() return redirect('/') def deleteRecord(request): if request.method == 'POST': id = request.POST['delete_val'] Record.objects.filter(id=id).delete() return redirect('/')

index.html

{% extends 'app/base.html' %} {% block content %} <div class="container-fluid"> <div class="row"> <nav class="col-md-2 d-none d-md-block bg-light sidebar"> <div class="sidebar-sticky"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link active" href="/"> <span data-feather="home"></span> 帳務總覽<span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" href="/settings"> <span data-feather="file"></span> 設定 </a> </li> </ul> </div> </nav> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="page-header">帳務總覽</h1> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="row placeholders"> <div class="col-xs-8 col-sm-4 placeholder"> <h3>收入</h3> <span class="text-muted"><h2>{{ income }}</h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>支出</h3> <span class="text-muted"><h2><font color="#DF565C">{{ outcome }}</font></h2></span> </div> <div class="col-xs-8 col-sm-4 placeholder"> <h3>存款</h3> <span class="text-muted"><h2><font color="#53DF7D">{{ net }}</font></h2></span> </div> </div> </div> </main> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h2 class="sub-header">新增紀錄</h3> </div> <div class="table-responsive"> <form action="/add_record" method="post"> {% csrf_token %} <table class="table table-striped"> <col style="width:15%"> <col style="width:35%"> <col style="width:20%"> <col style="width:18%"> <col style="width:7%"> <thead> <tr> <th>日期</th> <th>描述</th> <th>分類</th> <th>金額</th> <th>收支</th> </tr> </thead> <tbody> <tr> {% for field in record_form %} <td>{{ field }}</td> {% endfor %} </tr> </tbody> </table> <div class="right-area"> <input type="submit" class="btn show-new-item" value="新增紀錄" /> </div> </form> </div> </main> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4"> <h2 class="sub-header">歷史記錄</h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <col style="width:18%"> <col style="width:27%"> <col style="width:20%"> <col style="width:18%"> <col style="width:7%"> <col style="width:5%"> <thead> <tr> <th>日期</th> <th>描述</th> <th>分類</th> <th>金額</th> <th>收/支</th> <th></th> </tr> </thead> <tbody> {% for record in records %} <tr> <td>{{ record.date | date:"Y-m-d"}}</td> <td>{{ record.description }}</td> <td>{{ record.category }}</td> <td>{{ record.cash }}</td> <td>{{ record.balance_type }}</td> <td> <form method="post" action="/delete_record"> {% csrf_token %} <input type="hidden" value="{{ record.id }}" name="delete_val"> <input type="submit" class="btn" value="刪除" /> </form> </td> </tr> {% endfor %} </tbody> </table> </div> </main> </div> </div> {% endblock %}

myproject_test 底下的 urls.py

from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.frontpage), url(r'^settings$', views.settings), url(r'^add_category$', views.addCategory), url(r'^delete_category/(?P<category>\w+)',views.deleteCategory), url(r'^add_record$', views.addRecord), url(r'^delete_record$', views.deleteRecord), ]

Step8:使用者登入與登出




views.py

from django.shortcuts import render, HttpResponse, redirect from .models import Record, Category from .forms import RecordForm from django.contrib.auth.decorators import login_required # Create your views here. @login_required def hello(request): return render(request,'app/example_hello.html',{}) @login_required def frontpage(request): record_form = RecordForm(initial={'balance_type':'支出'}) records = Record.objects.filter() income_list = [record.cash for record in records if record.balance_type == '收入'] outcome_list = [record.cash for record in records if record.balance_type == '支出'] income = sum(income_list) if len(income_list)!=0 else 0 outcome = sum(outcome_list) if len(outcome_list)!=0 else 0 net = income - outcome return render(request,'app/index.html',locals()) @login_required def settings(request): categories = Category.objects.filter() return render(request,'app/settings.html',locals()) @login_required def addCategory(request): if request.method == 'POST': posted_data = request.POST category = posted_data['add_category_name'] Category.objects.get_or_create(category=category) return redirect('/settings') @login_required def deleteCategory(request,category): Category.objects.filter(category=category).delete() return redirect('/settings') @login_required def addRecord(request): if request.method == 'POST': form = RecordForm(request.POST) if form.is_valid(): form.save() return redirect('/') @login_required def deleteRecord(request): if request.method == 'POST': id = request.POST['delete_val'] Record.objects.filter(id=id).delete() return redirect('/')

myproject_test 中的 urls.py

"""project_chia URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.9/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url, include from django.contrib import admin from django.contrib.auth import views as auth_views from . import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include('chia_app.urls')), url(r'^accounts/login', auth_views.login), url(r'^accounts/logout', views.logout), ]

新增 registration 資料夾,並放入 login.html

(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test$ cd chia_app
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app$ ls
admin.py  forms.py     migrations  __pycache__  templates  urls.py
apps.py   __init__.py  models.py   static       tests.py   views.py
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app$ cd templates
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app/templates$ ls
app
(py3env) testchia@fengchia-swift-sf314-52:~/myproject_test/chia_app/templates$ mkdir registration

login.html

{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>accounts demo</title> <!-- Bootstrap core CSS --> <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{% static 'css/dashboard.css' %}" rel="stylesheet"> <style> td { padding: 5px } </style> </head> <body> <nav class="navbar-dark bg-dark shadow fixed-top"> <center><h1 class="navbar-brand col-sm-9">My 帳本</h1></center> </nav> {% if form.errors %} <p>您的帳號或密碼有誤。</p> {% endif %} {% if next %} {% if user.is_authenticated %} <p>您沒有權限造訪此網頁。</p> {% else %} <p>請先登入。</p> {% endif %} {% endif %} <form method="post" action="/accounts/login"> {% csrf_token %} <table align="center" style="margin-top:100px"> <tr> <td>{{ form.username.label_tag }}</td> <td>{{ form.username }}</td> </tr> <tr> <td>{{ form.password.label_tag }}</td> <td>{{ form.password }}</td> </tr> <tr> <!--我是next:{{ next }}--> </tr> <tr> <td></td> <td> <input type="submit" value="login" class="btn small-button" /> </td> </tr> </table> <input type="hidden" name="next" value="{{ next }}" /> </form> </body> </html>

myproject_test 底下新增 views.py

from django.contrib import auth from django.shortcuts import redirect def logout(request): auth.logout(request) return redirect('/')

Step9:挑戰關卡-使用者專屬內容

Step10:上傳專案到github部署專案至Python anywhere

Select a repo