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建立出來的檔案會包含:

### 新增``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








# 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.")
```