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