---
# System prepended metadata

title: Python
tags: [Class]

---

Python
===
## 環境安裝
### 終端機 terminal
確認正在使用哪個版本的python：
```python!
$ which python 
```
離開python的REPL環境
👽✏️ python的REPL環境就是在終端機上輸入python後~
```py!
$ exit || control+d
```
### 版本控制 - pyenv
用來管理不同版本的python工具箱。
👽✏️ 它只是用來管理python的，並不是系統級別的工具箱。
系統級別的工具箱舉例：Homebrew->用於macOS的包管理器

### python套件工具包
``PyPI(Python Package Index)``是python套件工具包，可以上PyPI網站查找需要的套件，再使用``pip``安裝需要的套件
```PY!
$ pip install package_name
```
而``pip``是Python的管理工具，用於安装、卸载、更新和管理PyPI。
```py!
$ pip3 install -r requirements.txt

# 根據requirements.txt文件裡列出的套件清單，把所有的套件都安装起來。
```
🆘🆘🆘 ```pip install```是直接在全域環境中下載套件，可能會造成原有的套件版本被刪除。解決辦法 -> 創建一個虛擬環境。

### 虛擬環境工具
在每個專案中建立一個「套件級別環境(package level env)」，以確保該專案中使用的套件設置是獨立且隔離的。

```pip包管理工具```會將套件安裝在系統全域環境下，這意味著如果你在同一台電腦上安裝了多個專案，並且它們使用了不同版本的套件，就可能會導致衝突。

這時候就可以使用```venv```建立一個「套件級別環境」，以確保該專案中使用的套件設置是獨立且隔離的。

**1. 建立虛擬環境(在專案資料夾中建立)**
```py!
$ python -m venv .venv

# 建立一個名叫.venv的虛擬環境，啟動py時自動載入套件(-m)
```
**2. 啟動虛擬環境**
```py!
$ source .venv/bin/activate

# 啟動.venv(虛擬環境)中bin資料夾內的activate
```
**3. 離開虛擬環境**
```py!
$ deactivate
```

> 💬 管理層級：系統 -> python -> 套件
> 
> 系統管理：Homebrew
> python：pyenv
> 套件：venv, poetry

**poetry** 比venv高階的套件管理工具
👽✏️ 我的電腦已經用pipx安裝全域poetry了，在不同版本的python都可以直接使用，不用再另外安裝。

**1. 開啟**
已經有既有專案下可以直接使用：
```py!
$ poetry init -n

# 在當前目錄下初始化一個Poetry專案並產生一個名為pyproject.toml的描述檔
```
另外要開全新專案的話：
```py!
$ poetry new project_name

# 生出一個全新的專案
```

**2. 啟用poetry**
```py!
$ poetry shell

# 產生虛擬環境
```

**3. 安裝專案的lock**
```python!
$ poetry install

# 會生成poetry.lock檔
```
## 來寫個python程式碼
### 宣告
```py!
a = 1
b = "Hello"

# py的宣告就是那麼簡單，不需要var, let, const...
```
### 底線
在REPL中底線代表最後一次的運算結果(前提是底線不能在之前被宣告成其他值)

### 常數
python沒有常數的設計(像JS有const常數)，所以這邊有個慣例是把常數的變數都寫成大寫

### 標準函式庫
在python中，為了讓跑程式的效能提高，不常用的函式需要另外匯入才能使用。匯入函式有兩種方法，而這兩種方法有些微的差別。
先來看第一種：
```python!
import math #import math這個函式類別裡的所有方法

math.ceil() #再使用其中的方法時，要連名帶姓的呼叫
```
這種方式可以清楚的分別每一個使用的函式方法，可以用在篇幅較長的程式碼。

第二種則是import要使用的方法就好：
```python!
from math import ceil, floor...

ceil() 
floor() #這邊在呼叫方法時，不用帶姓
```
這個方法建議使用在篇幅較小的程式碼，為了避免跟自己def的函式名稱重疊（如果有重複的函式名稱，後面的會把前面的蓋掉）

### 位元組
「字串是無法存擋的」先理解這句話，意思是說字串本人是沒有辦法存進電腦中的，那我們從電腦中看到的字是哪裡來的？
首先，字串在存進電腦前會轉換成二進位，而我們看到的字是由編譯器打開此檔案後再轉回字串。

- Unicode -> 這個可以看成一個大表格(包含ASCII 中譯：美國訊息交換標準代碼)，因為ASCII的編碼字元數太少，不夠中文字使用所以衍生出Unicode(萬國通)。
- utf-8 -> 編碼方式。

要注意的是，在轉換二進位及轉回字串所使用的編碼方式要是一樣的，不然就會造成亂碼的現象。

python內建編碼/解碼方式
以中文字為例：
```python!
name = '悟空'

name.encode() #編碼
name.decode() #解碼

# b'\xe6\x82\x9f\xe7\xa9\xba'
# '悟空'
```

### 串列（List）
網路上很多人會說，python的串列就像是其他程式語言的陣列，但這樣的說法並不太對...

那為什麼python會稱list為串列？而串列和陣列的差別又是為何？

首先，我們要先了解其他語言定義的陣列為：在陣列中要放同樣型態的元素！我們可以把陣列想像成很多大小相同的小方盒組合而成的。每個方盒裡會放一個元素，正因如此我們可以使用偏差值來取得每一個小方盒中的元素。

BUT!串列裡的元素不用同型態喔！

~~不過串列的背後其實也是透過陣列來記事的。~~

## f字串
python的相加只能是int + int || str + str，不會像JavaScript一樣有``2 + '3' = '23'``的狀況發生。

先來看F字串的使用方法：
```python!
name = 芳潔
age = 18
print(f"我的名字叫{name}, 我今年{age}歲!")

#我的名字叫芳潔, 我今年18歲!
```
f字串的其他用法：
```python!
my_money = 1000000
print(f"{my_money:,}") #冒號後面得是要加上去的修飾詞

# 1,000,000
```

f字串有非常多用法，可以參考*為你自己學Python*。

### Immutable(不可改變的)
python中有些型態是immutable，像是字串,tuple,整數,布林值,整數,float,frozenset,None

這種不可改變的特性，可以確保資料的正確性，不會被有意或無意的改掉。

#### - List v.s turple -

    List是可以修改的
    turple是不能修改的
舉個List的例子：
```python!
nums = [1, 2, 3]
a = [:]
b = [:]

a == b #True (==為只確認內容看起來是否一樣)
a is b #False (is是要確認我的跟你的是同一個id)
```
正因為List是可以修改的，所以``a is b``才會是``False``。

### unpack(開箱)
```py!
x = 1
y = 2
x, y = y, x

print(x, y)
# 2, 1
```
上面程式碼的解釋並不是單純的把x變成y；y變成x喔！
這邊的流程稱為：unpack(類似JS的解構)

先說明等號右邊的y, x並不是兩個值，而要看成一包東西
例如：
```py!
a = 1, 2
type(a)

# 'tuple'
```
宣告a = 1, 2後使用type(a)去看他的資料型態後，會發現結果會是'tuple'。

回到一開始的程式碼，等號右邊的y, x要看成一包tuple。

👽✏️ 所以，我們可以利用unpack的手法來交換x和y的值!!!這就是為什麼上面那段程式碼的結果回傳會是(2, 1)

註：**tuple** 遇到單一元素的tuple情況，寫法為：
```py!
t1 = (1,) || t1 = 1,

# 加一個逗號，讓py知道這個不是數字1而是單一元素的tuple
```
#### unpack情況(一)
```py!
t = 1, 2, 3, 4
# optput x=1 y=4

x, _, _, y = t
# 底線-> I dont care
```
這樣的方式，就能順利得到x = 1 和 y = 4了

#### unpack情況(二)
```py!
t = 1, 2, 3, 4, 5, 66, 78, 89, 10
# optput x=1 y=10

x, *_, y
# *-> 全部，類似JS的...
# 除了頭和尾，中間的全部都是I dont care
```
這樣就能抓出頭和尾的數

補充：這邊的*_會印出：[2, 3, 4, 5, 66, 78, 89]
只要使用到*系統內建會把它印成list(等於JS的陣列)

### slice(start : stop(不包含) : [step])
切片會創建一個新的對象，而不是直接引用原始對象的某個部分。

### range( )
生成一個整數序列，這個序列通常用於循環中的索引控制。
```python!
range(stop)
range(start, stop不包含 [,step])
```
```python!
for i in range(3):
    print(i)
    
# 0
# 1
# 2
```
### for
python的for迴圈寫法與JavaScript不同。

1. 嘗試把[1, 2, 3]裡的值都乘2，類似JS的.map()
```python!
nums = [1, 2, 3]
result = []
for n in nums:
    result.append(n * 2) #類似JS的.push()
print(result)

# [2, 4, 6]
```
2. 用迴圈寫99乘法表
```python!
for i in range(1, 10):
    for j in range(1, 10):
        print(f"{i}x{j}={i*j}") #搭配f字元
```
3. 用迴圈寫出聖誕樹，一樣可以搭配f字串去寫
```python!
for i in range(1, 10, 2):
    print(f"{'*'* i:^11}") #一排設定為11個空白字元並且置中
    
       *
      ***
     *****
    *******
   *********
  ***********
```

### 推導式：做資料的轉換
```python!
nums = [1, 2, 3, 4, 5]

[_n_ for _n_ in _nums_ if ___ ]
#結果    #類似參數      ＃if放在for的後面，類似JS的.filter()
```
``in``後面可以以放任何集合體，eq:turple, list, str...
再看外殼，如果是[]->output結果為list;()->output結果turple...etc

```python!
nums = [1, 2, 3]
result = [n * 2 for n in nums]

print(result)
# [2, 4, 6]
```
嘗試將[9, 5, 2, 7] -> [9, 5, 4, 7]，碰到偶數才乘2：
```python!
result = [n * 2 if n % 2 == 0 else n for n in [9, 5, 2, 7]]

print(result)
```
if...else...判斷句會寫在for...in...的前面。
>以上面程式碼的白話為：
>讓n是[9, 5, 2, 7]的參數並帶入執行迴圈。
>在迴圈中判斷，如果n除以2的餘數為0，就把n乘以2；否則就直接回傳n。

### Positional Arguments v.s. Keyword Arguments
```py!
def hi (a, b, /, c, d, e)
# a, b只能放Positional arguments 但c, d, e隨意

def hey (a, b, *, c, d, e)
# c, d, e只能放Keyword arguments但a, b隨意

def hola (a, b, /, c, *, d, e)
# a, b 要放Positional argument d, e要放Keyword arguments c隨意啦
```

### import [file的位置很重要]
寫法有：
```python!
from filename import method

import method

from . import method
# . -> 這裡
```

```py!
#module.py

if __name_- == "__main__"
#直接執行此檔，module檔獨立執行-> 通常開發者才會使用
```

### function decorator
> 用來輔助其他一般的函式運作

寫法：
```python!
def 裝飾器名稱(要控制的函式名稱):
    def 內部函式名稱():
        # 裝飾器內部程式碼
        要控制的函式名稱()
    return 內部函式名稱 || 要控制的函式名稱
#這邊的return是因為，裝飾器函式控制了函式，所以要記得在執行完後，還回去(可以還被控制函式本身，也可以還其他的韓式給他)
```
```py!
@裝飾器名稱
def 函式A名稱
    #函式程式碼
    
函式A名稱() #呼叫帶有裝飾器的函式
```
當你呼叫了一個帶有裝飾器的函式時，他會先執行裝飾器內部的程式碼，再執行本來的程式碼。

以下舉一個例子：
```py!
def testDecorator(fn->要被控制的函式)
    def innerFn()
        print("我是裝飾器")
        fn()
    return innerFn

@testDecorator        
def decoratedFn()
    print("我會被控制")
    
decoratedFn()
```
會印出：
```py!
# 我是裝飾器
# 我會被控制
```
因出這樣順序的原因是：在呼叫帶有裝飾器的``decoratedFn()``時，``decoratedFn()``會被當作``testDecorator()``的引數然後丟進去，接著``testDecorator()``內部的``innerFn()``會先被執行，再來才會回來執行``decratedFn()``。

所以～我們可以用來傳遞``參數``
例如：
```py!
def testDecorator(fn)
    def innerFn()
        print("我是裝飾器")
        fn(3)
    return innerFn

@testDecorator        
def decoratedFn(data)
    print("我會被控制", data)
    
decoratedFn()
```
注意看！在執行``innerFn()``時，裡面所呼叫的``fn()``裡的引數3，才會被傳遞到``decoratedFn()``中的參數``data``執行。
所以會印出：
```py!
# 我是裝飾器
# 我會被控制, 3
```

### 『＊』v.s 『＊＊』
```python!
def hi(*args, **kwargs):
    print(args)  #output tuple
    print(kargs) #output dict

hi(1, 2, 3, a = "xyz")
```
會印出：
```py!
# (1, 2, 3)
# {'a': 'xyz'}
```
``*args``是指有的我全包了，且以``tuple``的型態印出，但－``**kwargs``只會拿``keyword arguments``，並且以``dict``的型態印出。

#### 補充： 用『*』會遇到的坑
```py!
a = [[1, 2, 3]] * 3
print(a)

a[0][1] = "x"
print(a)

# [[1, 2, 3], [1, 2, 3], [1, 2, 3] 
# [[1, "x", 3], [1, "x", 3], [1, "x", 3]]
```
從上面的例子，可以發現：用乘法複製的list會指向同一顆list，所以你以為是修正選定的那個list，結果卻是一次改了三個list。

### 用於說明函式的文件用法
```py!
def hi():
    """
    hello world
    這是測試
    """
    print("hi there")

print(hi.__doc__)
```
會印出：
```python!
# hello word
# 這是測試
```
文字用上下各三個雙引號包住的用法通常用於說明函數的使用方法，可以使用``fn.__doc__``印出文件內容。

### Class 類別
```python!
class Cat:
    a = 123
    def hi():
        print("hi there")
print(Cat.a)  # 123
Cat.hi() -> 呼叫Cat的hi() #類別方法
```
以上面的程式碼來說，在名為Cat的類別中指定變數a為123，以及定義hi()，如果要印出a或是呼叫hi()，寫為``Cat.hi()``

# Django指令
### 在本地安裝Django
- $ pip install django
- $ django-admin  - -v version (檢查版本)
* $ django-admin startproject project_name
* 進到專案資料夾中(vscode)
* $ poetry init -n 初始化 ->長出``pyproject.toml ``
* 建立``README.md`` 

> ``README.md`` 檔案包含以下內容：
> - 專案的名稱
> - 簡要描述專案的目的和功能
> - 安裝指南
> - 使用指南
> - 貢獻方式（如果希望其他人參與貢獻）
> - 作者信息
> - 授權訊息

### 建立虛擬環境
* $ poetry shell (建虛擬環境)
* $ poetry install -> 長出lock檔 
* $ pip install django(再建立一次虛擬環境中的django)
* $ python manage.py runserver

### 撰寫並設定檔案路徑…
* $ python manage.py startapp app_name
> 使用startapp建立出來的檔案會包含：
![apps.py](https://hackmd.io/_uploads/SyjvhLHZC.png)


### 新增``urls.py``，用於設定路徑
>``urls.py``內容包括：
>    * 導入django相關模組(like: from django.urls import path)
>    * 定義路由（使用path( )函式來映射URL到視圖）
>    * 路由定義的列表，使用變數urlpatterns  = [ ]
```py!
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('about/', views.about, name='about'),
]

```
關於路由列表，當用戶訪問根路徑（''）時，將調用名為index的視圖；當訪問/about/時，將調用名為about的視圖。

新增一個``templates/pages`` 的資料夾，把上面所提到的``index.html``及``about.html``放進去。

🆘🆘🆘注意～在新增``templates``檔案時，要記得去``setting.py``中的TEMPLATES = [...] 中寫入``"DIRS": [BASE_DIR / "templates"],``，這樣才能順利抓到html檔。


🆘🆘🆘注意～在新增``static``檔案時，要記得去``setting.py``中寫入:
```
STATICFILES_DIRS = [
    BASE_DIR / "static",
]
```
這樣才能順利抓到css檔。

把layout寫出來，方便套用到每個分頁html中，寫法refer：
```html!
<!-- templates/layouts/base.html -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}practice 1{% endblock %}</title>
</head>

<body>
    {% block content %}
    {% endblock %}
</body>

</html>
```
使用類似神社的語法，來合併分割出的layout樣板及其他分頁樣板
```html!
<!-- templates/pages/home.html -->

{% extends 'layouts/base.html' %}
<!-- 特別注意這邊的路徑要使用的是單引號喔，不然連不到 -->

{% block content %}
<div>
    <h1>我的第一次練習</h1>
</div>
{% endblock %}
```

接著，如果你想要製作跳轉主/分頁的話，還記得我們在``urls.py``中幫路徑

### 套用前端框架，CSS-Tailwind為例：
#### 建立Tailwind CSS
因為是前端的框架，所以使用的指令為``npm``
- $  npm init -y -> 生成``package.json``
- $  npm install -> 根據json檔中，所需的依賴項目生成至``node_modules``
- $  npm install -D tailwindcss
- $  npx tailwindcss init -> 初始化Tailwind CSS配置並生成``tailwind.config.js``

找到``tailwind.config.js``檔案並調整content: ``["./src/**/*.{html,js}"],``內文（html,js放在什麼路徑），如果只要套用在html的話，就不需要大括號了喔！

#### 建立一個src/tailwind/style.css資料夾
在資料夾中的css檔案加入
```
@tailwind base;
@tailwind components;
@tailwind utilities;
```
#### 新增``package.json``內容，跟``npm``指令有關的都會在這檔案裡面
```
 "build-css": "tailwindcss -i (輸入)./src/tailwind/style.css -o（輸出） ./static/styles/style.css --watch"
```
這個步驟主要是為了在開發過程中使用Tailwind CSS並實時監聽文件的變化。

將這個指令添加到 ``package.json`` 中的腳本中，可以方便地通過運行 ``npm run`` 來啟動 Tailwind CSS 的開發監聽模式。這樣做的好處是，每次你修改原始 CSS 文件時，都可以自動更新處理後的 CSS 文件，從而加速開發流程並確保你的樣式始終保持最新。

#### 執行
```
$ npm run build-css
```
這邊記得分離兩個終端機，一個for解析Tailwind，一個for$ python -m mange.py runserver。


# CRUD

## Migrations : 檔案有更新/修改/遷移時生成的描述檔 

先把``model.py``建立起來：
1. 在model.py中定義class類別，每一個類別相當於一個資料表
2. 在class類別中定義變數，每一個變數相當於一個資料表欄位

舉例如下：
``model.py 檔案``

新增需要的表格(表單) 舉例：
```py!
class Nanny(models.Model):
    # Cat繼承models.Model這個方法
    name = models.CharField(max_length=100)
    gender = models.CharField(max_length=1)
    tel = models.CharField(max_length=20)
```
3. 建立資料庫和Django間的中介檔
```py!
$ python manage.py makemigrations -> #建立Migrations檔
```
🆘🆘🆘 記得在``setting.py`` 中加入model所在的**資料夾名稱**
```py!
INSTALLED_APP = [“nannies”] 
                #這邊的名字是資料夾(模組)名稱喔！
```

4. 同步更新資料庫內容
```py!
$python manage.py migrate -> #應用Migrations -> 這時候查看db SQLite會發現表格已經建立好(感謝Django)
```
    
再來就可以開始執行CRUD啦🤓💤

👽💬/ 流程： urls -> views.#action -> (model) -> render template 記起來在建檔案的時候比較不會亂掉唷～

我的邏輯：先做出給使用者使用的頁面(html)，再來要寫出對於這個頁面要執行的動作(views)，最後處理urls分辨並連結這些。

## Create 新增

今天要收集各隻小貓咪的資料，首先要先建立一個頁面讓各方貓奴們填寫，所以～先來
1. ``html`` - 新增一個前端頁面：
```html!
<div>
    <h1>新增貓貓區</h1>
</div>
<form action="{% url 'cats:add' %}" method="post">
    {% csrf_token %}
    <div><label for="name">姓名</label>
        <input type="text" id="name" name="name">
    </div>
    <div><label for="breed">品種</label>
        <input type="text" id="breed" name="breed">
    </div>
    <div><label for="description">介紹</label>
        <textarea id="description" name="description">
    </div>

    <input type="submit" value="新增">
</form>
```
步驟重點:
- 只有form表單能夠發動POST
- ``action="{% url 'cats:add' %}"`` -> 指定表單提交的目標 URL，這個 URL 指向``'cat:add'``就是接收並處理表單數據的地方。
- 使用POST原因 -> ``{% csrf_token %}``，可以保護貓奴們輸入的資料不會被第三方擷取或修改。

2. ``views.py`` - 再來建立一個``add(veiws#action)``，來接收並處理表單資料
```py!
from django.shortcuts import render, redirect
from django.views.decorators.http import require_POST
from .models import Cats

@require_POST #確保只有在收到 POST 請求時才會調用。
def add(request):
    cat = Cats(
        name = request.POST["name"],
        gender = request.POST["gender"],
        breed = request.POST["breed"],
        description = request.POST["description"],
        #request中的POST行為裡["key"]->進而求值，並將新拿到的值放到name, gender, breed, description中，然後一起整理給cat
    )
    cat.save()
    #儲存得到新值後的cat -> DB中就會有新資料出現囉

    return redirect("home")
    #成功完成後，重指向新的目標URL，然後將用戶帶到該URL的頁面。
    #如果除了name外還有設定app_name="pages"的話，要寫成"pages:home"
```

3. ``urls.py`` - 處理urls路徑
```py!
path("add/", views.add, name="add"),
```
以新增的這個頁面(new.html)來說，我們會到網址結尾是add/的url，這時候就會去找views裡面的add(#action)去執行收到的POST資料。

## Read 讀取
資料都新增好了後，再來就是要看看到底都有哪些資料，這時候就要來Read!
順序一樣可以想流程（先做出一個可以讓貓奴們看到的頁面，再來使用views去執行讀取的動作，當然要有對的urls!）

1-1. ``views.pt`` - 這邊是使用現成的index.html，這邊要列出所有的小貓咪，所以！開做：
首先要抓到所有的小貓咪資料，到views去寫一個action
```python!
# views.py
def index(request):
    cat = Cats.objects.all()
    return render(request, "cats/index.html", {"cat": cat})
```
寫法說明：
- ``Cats.objects.all()`` -> 從Cats中拿出所有物件(QuerySet-查詢集合)並存在cat中
- 使用``render()``將資料渲染到畫面上。這邊會回傳request及{"Cat": cat}的data結合到"cats/index.html"這個模板上，形成響應式頁面。 
- 🆘🆘🆘``{"cat": cat}`` -> ``"cat"``是為了要和html對上，讓html知道要帶什麼資料；而cat則是``cat = Cats.objects.all()``的cat變數。

渲染頁面該做的動作都已經寫好了，再來就是要來確定urls的路徑

1-2. ``html`` - 把資料嵌入index.html上的處理 -> 顯示小貓咪列表

```html!
<!-- cats/index.html -->
<div>
    <h1>🐱貓咪列表🐱</h1>
</div>
<a class="rounded-full bg-orange-300" href="{% url 'cats:new'%}">新增</a>
<!-- 在頁面上做一個新增的按鍵，相連結導向'cats:new'的頁面，讓貓奴們可以新增新的貓貓資訊 -->

{% for cat in cat %} 
<!-- 寫一個for迴圈遍歷cat集合中的每一個cat物件(要跟views裡面的{"cat": cat}的"cat"同名才抓得到)，並為每個貓咪生成一個連結，連結到該貓咪的詳細資訊頁面。-->
<div>
    <a href="{% url 'cats:show' cat.id %}">{{cat.name}}</a>
<!-- 🆘將cat.id作為參數傳遞給URL模式的目的是確保所生成的超連結是到貓咪的特定詳細資訊頁面。 -->
</div>
{% endfor %} 
<!-- 🆘一定要記得結尾寫上'endfor' -->
```
1-3. ``urls.py`` - 小貓咪列表urls
```py!
# cats/urls.py
urlpatterns = [
    path("", views.index, name="index")
    path("<id>", views.show, name="show"),
]
```

2-1. ``html`` - 小貓咪詳細資料頁 
建一個``show.html``的頁面，放入各隻小貓咪的詳細資料，以及編輯/刪除功能(結合到Update/Delete)
```html!
<!-- cats/show.html -->
<div>
    <h2>{{ cat.name }}</h2>
    <!-- 一個模板變量，模板被渲染時它會被動態替換為貓咪物件的名稱 -->
</div>
<div>
    <p>
        {{ cat.description }}
    </p>
</div>
```
2-2. ``views.py`` - 在views中加入show(#action，用於處理頁面中小貓咪的詳細資料
```py!
from django.shortcuts import render, redirect, get_object_or_404

def show(request, id):
    cat = get_object_or_404(Cats, pk=id)
    #拿Cats(models.py -> class)物件，並用物件中的id值來分類不同的小貓咪；如果要找的id並不在物件中的話，就會拿不到並顯示404給使用者
    if request.method == "POST":
    #判斷使用者的HTTP請求方法是否為POST，如果是的話：
        cat.name = request.POST["name"]
        cat.gender = request.POST["gender"]
        cat.breed = request.POST["breed"]
        cat.description = request.POST["description"]
        #從客戶端POST請求中的名稱為"name"的表單欄位的值賦予到cat中的name屬性
        cat.save()

        return redirect(f"/cats/{cat.id}")
        #最後在將使用者頁面導向至urls為cats/<id>的網址頁面
    else:
        return render(request, "cats/show.html", {"cat": cat})
        #不然就是直接渲染cats/show.html的頁面
```

2-3 ``urls.py`` - 處理show頁面的urls
```py!
app_name = "cats"

urlpatterns = [
    path("<id>", views.show, name="show")
    #用<id>判斷路徑，並且提供views的show(action)去執行此路徑的動作。
]
```

## Update 更新
1. ``html`` - 提供貓奴能自由編輯自家小貓貓的資料內容，所以新增出一個edit.html的頁面
```html!
<div>
    <h1>更新小貓貓</h1>
</div>
<form action="{% url 'cats:show' cat.id %}" method="post">
<!-- 更新後的小貓貓資訊會存到'cats:show'中進行處理，cat.id用於指定特定小貓貓 -->
    {% csrf_token %}
    <div><label for="name">姓名</label>
        <input type="text" id="name" name="name" value="{{ cat.name }}">
<!-- 編輯頁面的輸入值要是一開始新增的資料內容，所以會加上 value="{{ cat.name }}"-->

    <input type="submit" value="更新">
</form>
```
按下更新鍵之後，會導向url 'cats:show'，然後判斷需要使用views中的哪個動作去處理修正後的資料。

## Delete 刪除
1. ``html`` - 刪除資料相對前面幾個步驟簡單，我們可以把刪除寫在小貓貓詳細資訊頁面中(``show.html``)
```html!
<form action="{% url 'cats:delete' cat.id %}" method="post">
    {% csrf_token %}
    <button>刪除</button>
</form>
<!-- 使用form原因是，為了發送一個刪除的POST請求。 -->
```
2. ``views.py`` - 要做一個執行刪除資料的動作
```py!
def delete(request, id):
    cat = get_object_or_404(Cats, pk=id)
    cat.delete()
    return redirect("cats:index")
    #執行完後將頁面轉指到小貓咪列表。    
```
3. ``urls.py`` - 處理urls
```py!
app_name = "cats"

urlpatterns = [
    path("<id>/delete", views.delete, name="delete")
]
```

### Soft Delete 軟刪除
首先，要在``#models.py``中新增欄位：
```python!
deleted_at = models.DateTimeFiled(null = True)
# 讓預設可以是空值 -> 之後可以對應py的None
```
再來要重新定義``delete()``
```py!
from django.utils import timezone

def delete(slef):
    self.deleted_at = timezone.now()
    self.save()
```
因為此delete函式是在class底下的**實例函式**，所以必須給一個參數self。

### Manager 管理器
重新定義客製化管理器，讓我們可以filter出想要的資料。一樣在``#models.py``中：
```py!
class CatManger(models.Model):
    def get_queryset(self):
        return super().get_queryset().filter(deleted_at = None)
```
這邊我們重新定義了``get_querysat()``讓它幫我們過濾出``deleted_at``為``None``的資料。

🆘🆘🆘要記得在class Cat中，把objects = CatManger()

## form/ModelForm 表單
為了讓html/views.py裡面有關於表單的部分不要寫太多，我們把表單直接拉出來寫。

```py!
#forms.py
from django import forms
from .model import Cats

class CatsForm(forms.ModelForm):
    class Meta: #Meta是專有名詞，記得大寫
        model = Cats
        fields = ["name", "gender", "age"]
        labels = {
            "name": "姓名",
            "gender": "性別",
            "age": "年紀",
        }
```

寫法說明：
1. 先定義我們表單中的資料欄有哪些-> model = Cats (別忘記import)
2. 再來定義我們要顯示出的欄位有哪些 -> fields=[...]
3. 欄位的default值為英文，可使用labels改標籤 -> labels ={"name": "姓名",...}

再來就可以把需要使用到form的地方都，改為form寫法囉
#### views / create
```py!
#views.py / create
from django.contrib import messages

@require_POST
def create(req):
    form = CatsForm(req.POST)
    if form.is_vaild():
        form.save()
        
        messages.success(req, "新增成功")
    return redirect("cats:index")
```
#### shared/messages.html
```html!
{% if messages %}
{% for m in messages %}
<div>
    <P class="{{ m.tags }}">
        {{ m }}
    </P>
</div>
{% endfor %}
{% endif %}
```

#### views / show
```py!
#views.py / show
def show(req, id):
    cat = get_object_or_404(Cats, deleted_at=None, pk=id)
    form = CatsForm(instance=cat)
    if req.method == "POST":
        form = CatsForm(req.POST, instance=cat)
            if form.is_valid():
                form.save()
        return redirect("cats:show", id=cat.id)
    else:
        return render(req, "cats/show.html", {"cat": cat})
```
#### views / new
```py!
#views.py / new
def new(req):
    form = CatsForm
    return render(req, "cats/new.html", {"form": form})
```
#### html / newpage
```html!
<!-- cats/new.html -->
{% extends 'layouts/base.html' %}

{% block content %}
<h1>新增保姆</h1>
<form action="{% url 'cats:add' %}" method="post">
    
    {% csrf_token %}
    {{ form }}

    <input type="submit" value="新增">
</form>
{% endblock %}
```

#### views / edit
```py!
#views.py / edit
def edit(req, id):
    cat = get_object_or_404(Cats, deleted_at=None, pk=id)
    form = CatsForm(instance=cat)
    
    return render(req, "cats/edit.html", {"cat": cat}, {"form": form})
```
```html!
<!-- cats/edit.html -->

{% extends 'layouts/base.html' %}

{% block content %}
<h1>更新保姆</h1>
<form action="{% url 'nannies:show' nanny.id %}" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="編輯">
</form>
{% endblock %}
```

## CBV (Class-Based Views)
### IndexView
```py!
# cats/view.py
from django.views.generic import ListView
from .models import Cats

class IndexView(ListView):
    model = Cats
    template_name = "cats/index.html"
    context_object_name = "cat"
```
寫法說明：
1. 定義了一個名為IndexView的類別，並且它是繼承ListView。
2. 指定了這個視圖所要操作的資料庫模型是Cats(記得import)。
3. ``template_name = "nannies/index.html"``：指定視圖所要使用的HTML模板的路徑和檔名。``defualt name of HTML = cat.list.html`` 如果直接使用預設名字，就不用寫template_name
4. ``context_object_name = "nannies"``：這行定義了傳遞給HTML的資料的名字。``defualt name = cat_list``，如果直接使用預設名字，就不用寫context_object_name。

### urls of IndexView
```py!
#cats/urls.py

from .views import IndexView

app_name = "cats"

urlpatterns = [
    path("", IndexView.as_view(), name="index"),
            #as_view()可以把IndexView從類別轉換為可呼叫的函式，進而執行網頁的渲染。
```

### NewView
```py!
from django.views.generic import FormView
from .forms import CatsForm

class NewView(FormView):
    #新增頁面繼承FormView，聯想一下：這頁本就是表單頁。
    form_class = NannyForm #慣例寫法(背起來唄🥲)
    template_name = "nannies/new.html"
```

### urls of NewView
```py!
#cats/urls.py

from .views import NewView

app_name = "cats"

urlpatterns = [
    path("", NewView.as_view(), name="new"),
            #as_view()可以把NewView從類別轉換為可呼叫的函式，進而執行網頁的渲染。
```

### ShowView
```py!
form django.views.generic import DetailView

class ShowView(DetailView):
    model = Cats
    template_name = "cats/show.html"
    context_object_name = "cat"
    #這樣才能順利在html中使用cat變數

    def post(self, req, pk):
        cat = self.get_object()
        form = CatsForm(req.POST, instance=cat)
        if form.is_valid():
            form.save()
        return redirect("cats:show", pk=cat.id)
```
寫法說明：
1. DetailView為Django提供的一個視圖類別，用於顯示單個模型實例的詳細資料。
2. 一樣指定這邊要渲染的模組資料為何？已經要渲染的頁面為何？ -> ``model = Cats ; template_name = "cats/show.html"``也可以直接把此頁面html名稱設定為default名(cat_detial.html)就無需寫template_name囉～
3. ``def post``重新定義post(#action)
4. ``slef.get_object`` 調用了DetailView此類別的方法，針對指定的pk取得單個小貓咪紀錄。
5. 指定form時，帶入req.POST的方法以及單個小貓咪紀錄(cat)。
6. 判斷form是否有效，最後轉址到pk為cat.id的show頁面。


## 關聯性
再建立一個app名稱為comments，做出與每隻小貓咪有關的評論區。（一對多：一隻小貓咪可以有很多評論）

## 會員系統
Django有內建authenticate可以使用，很方便！
會用到的套件有 :
```py!
#views.py
from django.contrib.auth import authenticate, login, logout

#forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
```

首先，要記得在主入口的``urls.py``加上：
```python!
path("accounts/", include("django.contrib.auth.urls"))
```

從login開始！先做出登入頁面，這邊可以找看看像bootstrap的框架，要特別注意的是，因為是內建的方法所以有通用的寫法，要把``login.html``放在``templates/registration``!不然會噴錯喔～

來看看views該怎麼寫：
```py!
#members/views.py

def login_user(req):
    if req.method == "POST":
        username = req.POST["username"]
        password = req.POST["password"]
        #從POST請求中獲取用戶提交的用戶名和密碼
        user = authenticate(req, username=username, password=password) #使用Django提供的authenticate函數來驗證用戶的身份，傳入用戶名和密碼，如果驗證成功，將返回一個用戶對象  
        if user is not None and user.is_active:
        #檢查驗證的用戶對象是否存在且為活動狀態
            login(req, user)
            #使用login函數登錄該用戶，這樣該用戶就被標記為已登錄
            return redirect("home")
        else:
            messages.success(req, "帳號/密碼不正確")
            return redirect("login")
    else:
        return render(req, "registration/login.html")
```
最後不要忘記設定urls
```py!
#root/urls.py
urlpatterns = [
path("members/", include("members.urls"))
]

#members/urls.py
urlpatterns = [
path("logout/", views.logout_user, name="logout")
]
```
補充！因為Django預設成功login後的路徑會是(/accounts/profile/)為了能讓登入後成功導向我們要的路徑，可以到setting.py裡面加入：
```py!
LOGIN_REDIRECT_URL = "/index/" #寫上你要轉址到哪
```


再來是登出！先補充一個登入/登出，的navbar邏輯：
```html!
<div>
    <ul>
{% if user.is_authenticated %}
        <li>
            <a href="{% url 'logout' %}">登出</a>
        </li>
{% else %}
        <li>
            <a href="{% url 'login' %}">登入</a>
        </li>
        <li>
            <a href="{% url 'register' %}">註冊</a>
        </li>
{% endif %}
    </ul>
</div>
```
判斷如果user是經過認證且成功登入的，那畫面就只會渲染出"登出"；反之如果還沒登入的話，就會出現"登入/註冊"。

OK，進入登出的views：
```py!
def logout_user(req):
    logout(req)
    messages.success(req, "登出成功！")
    return redirect("home")
```
簡單明暸！使用Django內建logout函式登出帳號，最後轉址到home頁面～end

註冊頁面比較麻煩一點，不過Django有內建的UserCreationForm方法，會建立註冊的form，當然我們也可以自己修改樣式，先來看看如何生出form
```py!
#members/form.py
class SignUp(UserCreationForm):
    username = forms.CharField(
        label="帳號", widget=forms.TextInput(attrs={"class": "form-control"})
    )
    email = forms.EmailField(
        label="電子郵件", widget=forms.EmailInput(attrs={"class": "form-control"})
    )
    password1 = forms.CharField(
        label="密碼", widget=forms.PasswordInput(attrs={"class": "form-control"})
    )
    password2 = forms.CharField(
        label="密碼確認", widget=forms.PasswordInput(attrs={"class": "form-control"})
    )
#以上程式碼是在建立一個名為SignUp的類別，並且繼承UserCreationForm的方法，可以使用label更改標籤，以及attrs來設定一個class名稱，方便修改css樣式。

    class Meta: #Meta是提供表單的元數據的內部class
        model = User
        fields = ("username", "email", "password1", "password2")
#用來指定表單與資料庫模型的關聯，以及表單中應該包含哪些欄位
```
有了register form之後，就能來寫views了：
```py!
def register_user(req):
    if req.method == "POST":
        form = SignUp(req.POST)
        #創建一個SignUp表單的實例，並將用戶提交的 POST數據傳遞給這個表單
        if form.is_valid():
            form.save()
            messages.success(req, "註冊成功！")
            return redirect("login")
    else:
        form = SignUp()
    return render(req, "registration/register.html", {"form": form}) #將表單對象傳遞給模板，以便在頁面上渲染表單
```
最後一樣不要忘記urls：
```py!
urlpatterns = 
    path("logout/", views.logout_user, name="logout"),
    path("register/", views.register_user, name="register"),
]
```

## 幫自己理解的圖形化 CRUD by myself
![專案-13](https://hackmd.io/_uploads/SyJFh8GPC.jpg)
![專案-14](https://hackmd.io/_uploads/BkBK3UGPR.jpg)
![專案-15](https://hackmd.io/_uploads/S1vF2UMPC.jpg)
![專案-16](https://hackmd.io/_uploads/SJYthLzvA.jpg)
![專案-17](https://hackmd.io/_uploads/BkoFhUGwA.jpg)
![專案-18](https://hackmd.io/_uploads/HJpYnLGD0.jpg)
![專案-19](https://hackmd.io/_uploads/H1kq2UMw0.jpg)
![專案-20](https://hackmd.io/_uploads/SyWch8zvA.jpg)


# Note

## 「海象運算符」(walrus operator)
- e.q.
```python=
value = len(data)
if value > 10:
    print(f"Data is too large: {value} elements.")
```
when you use the walrus operator will be like:
```python=
if (value := len(data)) > 10:
    print(f"Data is too large: {value} elements.")
```
