# 網頁前後端程式設計 (2021.06.05)
## 後端理論 (以 Django 為例)
### MVC architecture
MVC 是由三個字組成,分別為
> **M**odel
> **V**iew
> **C**ontroller
**在後端裡面**
> *Model*: DB, DBMS, 操作 DB 的 algorithm, etc.
>
> *View*: 在以前前後端混和的時代裡,view 代表 HTML 的 template。但是在前後端分離情況下,view 會到前端實作,所以,在後端就沒有 view 了
>
> *Controller*: 使用者在前端會輸入一個 URL (http://.../.../),此時,後端的 router 接收到 URL 之後,會解析這個 URL,交給對應的 controller 處理,controller 就要負責從 model 取出資料給前端,如果回傳資料量較大,則會需要套用模板回傳給前端。而在前後端分離的情況,後端就會使用 **JSON** 格式回傳資料
JSON (JavaScript Object Notation) 為將結構化資料 (structured data) 呈現為 JavaScript 物件的標準格式,常用於網站上的資料呈現、傳輸 (例如將資料從 server 送至 client 端,以利顯示網頁)。
簡單來說,JSON 就是一個 key 對應一個 value,例如
```json=
{
"firstName": "John",
"lastName": "Smith",
"sex": "male",
"age": 25,
"address":
{
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
},
"phoneNumber":
[
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}
```
以上面為例,firstname 就是一個 key,其對應的 value 就是 John
JSON 的概念可以對應到 python 的 dictionary
### MVC 在 Django 裡的概念
這點須特別注意,在 Django 裡
**controller 叫做 view
view 叫做 template**
### Controller 的一些細節
```graphviz
digraph "details of controller" {
URL -> middleware;
middleware -> Authentication;
middleware -> Authorization;
Authorization -> controller
Authentication -> Login;
Authentication -> Captcha;
Authentication -> Log;
Login, Captcha,Log -> controller;
}
```
Server 會記錄 Log,大致流程如下
```graphviz
digraph "log" {
Server -> Session, cookie;
Session -> SessionID;
cookie -> key;
key -> SessionID;
SessionID -> Authentication;
Authentication -> Data;
}
```
Server 會創造一個 session,和一個 cookie 回傳給 client。當 client 要求資料的時候,server 會看這個 session ID 與 cookie 有沒有符合
Session 是一個很大的 Hashtable,裡面會存著每位 client 的 session ID,每個 session ID 就會記錄著 user ID, last login time, etc.
大概像這樣
```json=
{
"sujkfrukhfakhfukuahfl": { // session ID
"uid": 0;
"name": "otischung";
...
}
}
```
在前後端分離的實作中,server 需要有一個 **Authentication 的 model 處理 client 的驗證**,client 使用 POST 對 server 發出 login 的請求,如果驗證成功,server 就會回傳 **token**,client 之後每次請求就要帶著這個 token 對於 server 發出請求,server 也會對於每次請求驗證 token 之後回應 client
## 後端實作 (Django) (使用 pycharm 開發)
### 安裝 Django 暨初始化專案
首先安裝 Django
```bash=
pip install django
```
安裝完成之後,就會有 `django-admin` 這個指令,可以查看有哪些指令可用
接下來,我們就要使用 Django 開發後端
```bash=
django-admin startproject <project_name> <location>
```
例如:
```bash=
django-admin startproject tmp_project ./
```
這樣 Django 就會在這個這個目錄下新增目錄名為 tmp_project,並且會在專案目錄下新增 **manage.py**,之後我們會一直使用 **manage.py** 操作後端
我們可以用以下指令啟動後端
```bash=
python manage.py runserver
```
此時會看到 server 運作在 http://127.0.0.1:8000/ 裡面,到瀏覽器輸入就能看到 Django 的 Hello World 畫面
### Django 預設的管理介面
可以發現在 tmp_project 目錄下,有一個 urls.py,其中有一項
```python=
urlpatterns = [
path('admin/', admin.site.urls),
]
```
這是 Django 預設的管理介面,我們實際去 http://127.0.0.1:8000/admin/ ,就能看到 Django 的 admin 登入頁面
### SQLite 與 Django 的關聯
在我們第一次 runserver 之後,就會在專案目錄下新增 db.sqlite3 的檔案,**sqlite 會將 DB 存在一個檔案裡**,所以不需要另外架設 SQL server
我們看一下 tmp_project 目錄下的 settings.py,會看到 Django 預設使用的 DB 是 SQLite3
```python=
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
```
#### 同步 DB 與 program: Migrate
一般來說,要架設一個 server,首先就是要安裝 server,create DB,然後在上述的地方輸入一些例如帳號密碼的驗證規則。再來就是要寫一些 SQL 的指令來保證 DB 的資料和程式上的資料是一致的,雖然比較麻煩,但是在一些大型專案,還是免不了人工設定
目前主流的 Relative Database 是 PostgreSQL,MySQL 已經有點過時了,
而 Django 有一個東西稱為 **Migration**,當你寫完 model 之後,migration 會自動產生 SQL 語句,讓 DB 的資料和程式上的資料是一致的,優點是很方便,缺點就是沒有彈性
執行以下指令
```bash=
python manage.py makemigrations
```
第一次會顯示 No changes detected
**<font color=#FF0000>之後如果有改到 models.py 的話,一定要記得執行以上指令</font>**
接著執行
```bash=
python manage.py migrate
```
這樣就會把所有變更寫到 db.sqlite3 裡面
### 新增管理員
Django 的框架是自帶後台的,所以我們可以執行以下指令新增管理員
```bash=
python manage.py createsuperuser
```
接著輸入帳號密碼,即設定完成,這樣我們就可以登入進去 http://127.0.0.1:8000/admin/ 裡面了
我們可以發現,Django 預設有 Groups 和 Users 這兩個 DB
## 後端新增功能 (留言板 comment)
後端的架構大致上是如下圖所示
```graphviz
digraph "details of controller" {
Site -> Application;
Application -> MVC;
}
```
所以,我們要新增一個 app 名為 comment
```bash=
python manage.py startapp comment
```
我們就能看到 Django 為我們新增了名為 comment 的目錄,也在這個目錄底下新增了一些檔案
### 連結 DB models.py
這裡的寫法就是用 class 將 models 包進來
```python=
from django.contrib.auth.models import User
from django.db import models
# Create your models here.
class Comment(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE) # ForeignKey: 關聯到另一張表; CASCADE: 連動刪除
created_at = models.DateTimeField(auto_now_add=True)
content = models.TextField(max_length=300)
def __str__(self):
return f"{self.author.username}: {self.content[:20]}"
```
#### User
Django 裡有一個 field (model) 名為 User,將其 import 進來即可使用
User 裡面有一個 field 名為 Username
#### <font color=#FF0000>正規化 (Normalization)</font> use <font color=#FF0000>ForeignKey</font>
假設有一張表叫做 Comments,這張表有一個欄位叫做 Author,裡面會儲存很多關於這個 Author 的訊息。當我們想要存取、新增或修改 Author 裡的某一個訊息的時候,我們不希望走訪所有欄位 (linear search)
Comments
| Author | Sudoer | Contents | Write Time |
| ---------- | ------ | ----------------- | ----------- |
| Otis Chung | yes | text1, text2, ... | t1, t2, ... |
| Ray Chung | no | text3, text4, ... | t3, t4, ... |
##### 第一正規化 NF1
我們會將所有含多筆資料的欄位展開,使 key 與 value 形成 one-to-one 的形式;並且給 Author 這張表所有欄位一個 id 為主鍵值 (primary key),隱含著id為這張表中不可重複且具代表性的**唯一**值
Comments
| ID | Author | Sudoer | Contents | Write Time |
| -- | ---------- | ------ | --------- | ----------- |
| 1 | Otis Chung | yes | text1 | t1 |
| 2 | Otis Chung | yes | text2 | t2 |
| 3 | Ray Chung | no | text3 | t3 |
| 4 | Ray Chung | no | text4 | t4 |
如此一來,client 要查詢的時候,只需要輸入 ID 即可搜尋到想要的資料
##### 第二正規化 NF2
隨著留言數越來越多,我們會發現為了製造 one-to-one 的形式,形成空間浪費
Comments
| ID | Author | Sudoer | Contents | Write Time |
| -- | ---------- | ------ | --------- | ----------- |
| 1 | Otis Chung | yes | text1 | t1 |
| 2 | Otis Chung | yes | text2 | t2 |
| 3 | Ray Chung | no | text3 | t3 |
| 4 | Ray Chung | no | text4 | t4 |
| 5 | Otis Chung | yes | text5 | t5 |
| 6 | Otis Chung | yes | text6 | t6 |
| 7 | Ray Chung | no | text7 | t7 |
| 8 | Ray Chung | no | text8 | t8 |
所以,我們會將 Author 獨立出來建表,如此一來就能省下空間
Comments
| ID | Author_ID | Contents | Write Time |
| -- | ---------- | --------- | ----------- |
| 1 | A1 | text1 | t1 |
| 2 | A1 | text2 | t2 |
| 3 | A2 | text3 | t3 |
| 4 | A2 | text4 | t4 |
| 5 | A1 | text5 | t5 |
| 6 | A1 | text6 | t6 |
| 7 | A2 | text7 | t7 |
| 8 | A2 | text8 | t8 |
Author
| Author_ID | Author | Sudoer |
| --------- | ---------- | ------ |
| A1 | Otis Chung | yes |
| A2 | Ray Chung | no |
如此一來,client 要查詢的時候,只需要輸入 Author_ID 和 ID 即可快速又簡單得搜尋到想要的資料
<font color=#FF0000>ForeignKey</font> 的意思就是參考到另一張表
### 新增寫好的 app: comment
將寫好的 models 註冊到管理頁面
在 admin.py 裡面這樣寫
```python=
from django.contrib import admin
from .models import Comment
# Register your models here.
admin.site.register(Comment)
```
在 tmp_project 裡面的 settings.py 裡面的 INSTALLED_APPS 加上我們所新增的 app: comment
```python=
INSTALLED_APPS = [
...,
'comment'
]
```
**記得,我們已經修改了 models.py,所以要重新 migrate**
```bash=
python manage.py makemigrations
python manage.py migrate
```