# Django # 基本介紹 ## web service概述 - C/S結構:client/server,需要下載軟體 - B/S結構:browser/server,是C/S結構特例,不需下載軟體,透過http/https協定溝通 瀏覽器向server發送http請求,server回應html,由瀏覽器解析 ## HTTP hypertext transfer protocol 超文本傳輸協議。 網路中使用的基本協議是TCP/IP協議,目前廣泛採用http, https, ftp, archie, gopher等是建立在TCP/IP協議之上的應用層協議,不同的協議對應著不同的應用。 web service使用的主要為http協議,屬於應用層的面向對象的協議,因簡潔、快速適用於分布式超文本訊息的傳輸。 無連結協議:每一次請求建立連接,服務器處理完客戶端的請求後,回應給客戶端然後斷開連結,不會一直佔用網路資源。 http使用請求方法:get, post, ……. ## HTTPS hypertext transfer protocol secure 超文本傳輸安全協議,http + ssl,用以加密溝通及對網路服務器身份的鑑定。 https使用的port為443,http使用80來與TCP/IP進行通訊。 # Django介紹 python web框架 ## MVT Model-View-Template 設計模式 ![Untitled](https://hackmd.io/_uploads/H1ddx8Dp6.png) - URL (URL映射器):根據請求URL將http請求重新導向到相應的view - View (視圖):請求處理函數,接受http請求並返回http響應。view通過model訪問滿足請求所需的數據,並響應給template - Model (模型):定義應用程式數據結構的python對象,並提供在資料庫管理(CRUD)的機制 - Template (模板):定義文件(ex. html頁面)的結構或佈局的文本文件,用於表示實際內容的佔位符(動態部分),不限於html ## MVC 禁止model, view之間通訊 - Model: 負責數據的處理,業務邏輯的計算 - View: 負責顯示數據,也可以接受用戶事件處理 - Controller: 負責控制view顯示,調用model獲得數據 # 環境設置 for macos 使用Terminal ## 一、安裝python 1. 安裝[Homebrew](https://brew.sh/) 2. 安裝[Python](https://matthung0807.blogspot.com/2021/08/python-macos-homebrew-install-python3.html) ```bash brew install python ``` ### **Python指令** `python3 --version` — 檢查python版本 `python3` — 在終端機使用py3 `quit()` — 離開py3 ## 二、設定虛擬環境 可直接跳建立django專案,將django安裝在虛擬環境中 ### 選擇一:venv python 3.3 後預設的虛擬環境 venv #### 指定專案資料夾 - `ls` — 查看資料夾 - `cd Documents` — 進入Documents資料夾 - `mkdir project` — 建立project資料夾 - `cd project` — 進入project資料夾 #### 建立虛擬環境 ```bash python3 -m venv test_env ``` #### 激活虛擬環境 ```bash source test_env/bin/activate ``` ##### venv指令 `python3 -m venv <env_name>` — 創建python虛擬環境 `source <env_name>/bin/activate` — 激活指定的 Python 虛擬環境 `deactivate` — 退出當前的 Python 虛擬環境 `rm -r <env_name>` — 刪除指定的環境 ### 選擇二:virtualenvwrapper #### 使用 pip 安裝 virtualenvwrapper(並捆綁 virtualenv) ```bash sudo pip3 install virtualenvwrapper ``` #### 找到shell啟動文件(/Users/usr/.bash_profile),若找不到則執行 `touch ~/.bash_profile` ```bash open ~/.bash_profile ``` #### 將下列貼入文件中,確認python, virtualenvwrapper.sh路徑是否正確 ```bash export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 export PROJECT_HOME=$HOME/Devel source /usr/local/bin/virtualenvwrapper.sh ``` #### 尋找路徑 - `which virtualenvwrapper.sh`  - `which python3` #### 重啟 ```bash source ~/.bash_profile ``` PS. 刪除virtualenvwrapper ```bash sudo pip3 uninstall virtualenvwrapper ``` ##### virtualenvwrapper指令 - `workon` — 列出可用的虛擬環境 - `mkvirtualenv <env_name>` —創建 Python 虛擬環境 - `workon <env_name>` — 激活指定的 Python 虛擬環境 - `deactivate` — 退出當前的 Python 虛擬環境 - `rmvirtualenv <env_name>` — 刪除指定的環境 # django專案 project 項目:可包含多個app application 應用程式:執行某些具體操作的web的app,可包含在多個project中 ## Create Project ### 一、虛擬環境 [How to create a Django project in visual studio code, Virtual Environment, Home Page #1](https://youtu.be/eOVLhM6_6t0?si=r5-qJp4XliW5nnIp&t=298) #### 使用venv建立並激活虛擬環境 ```bash python3 -m venv myVenv source myVenv/bin/activate ``` ### 二、安裝django ```bash pip3 install Django ``` `pip3 list` — 列出所有被安裝的 python 模組及其版本 ```bash django-admin startproject <projectname> ``` ``` myproject/ manage.py myproject/ __init__.py setting.py urls.py wsgi.py ``` - myproject/:project的根目錄,是project的容器,可以自行修改名字 - manage.py:一個命令行程序,允許管理Django project - myproject/myproject/:內層文件夾,實際project的包 - setting.py:project設定 - urls.py:配置project的URL映射器 - wsgi.py:一個wsgi (web服務器網關接口)兼容web服務器的入口,以便運行project ### 三、啟動服務器 ```bash python3 [manage.py](http://manage.py/) runserver ``` ### 四、設置資料庫 myproject/setting.py中的DATABASE可以設置資料庫。 ```python # 默認 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # 資料庫 'NAME': BASE_DIR / 'db.sqlite3', } } ``` ENGINE是資料庫引擎,Django主要提供: - `django.db.backends.postgresql` - `django.db.backends.mysql` - `django.db.backends.sqlite3` - `django.db.backends.oracle` 初始化/升級資料庫:admin, auth, contenttypes, session ```bash python3 manage.py migrate ``` ## Create Application ```bash cd <projectname> django-admin startapp <appname> # 無效 python3 manage.py startapp <appname> ls ``` ``` myproject/books/ __init__.py admin.py app.py migrations/ __init__.py model.py tests.py views.py ``` ## URL ```python # Function views # 1. Add an import from my_app import views # Add a URL to urlpatterns path('', views.home, name='home') # Class-based views # 1. Add an import from other_app.views import Home # 2. Add a URL to urlpatterns path('', Home.as_view(), name='home') # Including another URLconf # 1. Import the include() function from django.urls import include, path # 2. Add a URL to urlpatterns path('blog/', include('blog.urls')) ``` ### URL conf - `urlpatterns` — 路由模式列表,通過url映射到view - path function — return `urlpatterns` 元素 ```python path(route, view, kwargs=None, name=None) ``` - include function — 引入其他的模塊 ### 創建hello world #### 一、建立view ```python from django.http import HttpResponse # Create your views here. def hello(request): return HttpResponse('<h1>hello world</h1>') ``` #### 二、設定url myproject/myproject/urls.py 導向 books/urls.py ```python path('books/', include('books.urls')) ``` 建立books/urls.py,並設置導向views.hello的路徑 ```python from . import views # 匯入同個資料夾的views.py path('hello/', views.hello, name='hello') ``` ### 其他 `path('admin/', admin.site.urls)`:自帶的admin介面,可用manage.py創建帳號 ```bash python3 manage.py createsuperuser ``` ## View ### Function views ```python def hello(request): return HttpResponse('<h1>函數view</h1>') ``` ### Class-based views 減少code重複,繼承自:django.views, generic.ListView, generic.DetailView generic.ListView, generic.DetailView: form,通用views ```python from django.views import View class MyView(View): def get(self, request): return HttpResponse('<h1>類基礎view</h1>') ``` 在book.urls引入Class-based views ```python path('myview/', views.MyView.as_view(), name='myview') ``` # URL介紹 ## URL function ```python # django.urls.path() 使用普通模式創建url # route 路由模式 path(route, view, kwargs=None, name=None) # django.urls.re_path() 使用正規表達式創建url # route 正規表達式的路由模式 re_path(route, view, kwargs=None, name=None) re_path('^xxx$') ``` ### re_path book.urls ```python re_path('^id/(?P<book_id>[a-zA-Z0-9]{4})/$', views.show_book_id), re_path('^(?P<price>\d+\.\d+)/$', views.show_book_price) ``` book.views ```python def show_book_id(request, book_id): s = '<h3>您選擇的圖書編號:{0}</h3>'.format(book_id) return HttpResponse(s) def show_book_price(request, price): s = '<h3>圖書價格:{0}</h3>'.format(price) return HttpResponse(s) ``` ## URL 轉換器 路徑轉換器可取得url的數值進行轉換為有效的參數,傳遞給function view `<參數名>` — 對應到function view的參數名 ```python path('hello1/<str:name>/', views.hello1, name='hello1'), path('hello2/<slug:name>/', views.hello2), path('hello3/<uuid:name>/', views.hello3), path('hello4/<path:name>/', views.hello4), path('<int:book_id>/', views.show_book_id), ``` `str` — 匹配除了路徑分隔符號 (/) 之外的任何非空字串 `slug` — 匹配由ASCII字母或數字組成的任何字串,可通過 ‘-’ , ‘_’ 連接 `uuid` — 匹配格式化的uuid,必須包含 ‘-’,且字母必須為小寫 > uuid: 通用唯一識別碼 8-4-4-4-12的32位字串 > `path` — 匹配任何非空字串,包括 ‘/’ ## 重定向 ### redirect 重定向 透過函數view中,使用redirect函數返回一個應答對象 ```python from django.shortcuts import redirect # 1. 跨站重定向 def hello(request): return redirect("https://www.google.com.tw") # 2. 網站內重定向 def show_book_id(request, book_id): s = '<h3>您選擇的圖書編號:{0}</h3>'.format(book_id) price = 50.09 if book_id == 123: price = 35.69 return redirect('book_price', price=price) def show_book_price(request, price): s = '<h3>圖書價格:Y{0}.</h3>'.format(price) return HttpResponse(s) ``` 定義url ```python # 在url.py中定義 path('<int:book_id>/', views.show_book_id), re_path('^(?P<price>\d+\.\d+)/$', views.show_book_price, name='book_price'), # show_book_id呼叫name='book_price'的url ``` ### 網站內重定向 `redirect(name, para1 = para2)` `name` — 會呼叫path中的相同name的url `para1` — 呼叫的view傳入的參數名 `para2` — 該function用的變數 ### RedirectView 重定向view 需要子類化RedirectView,重寫pattern_name屬性,該屬性是指定view (url.py中定義的) ```python from django.views.generic import RedirectView class MyRedirectView(RedirectView): pattern_name='book_info' def show_book_info1(request): s = ''' <html> <body> <h3>書名:No Game No Life</h3> <h3>作者:榎宮祐</h3> </body> </html> ''' return HttpResponse(s) ``` `pattern_name` — 指向path中相同name的url 定義url ```python # url.py中定義 path('info1/', views.show_book_info1, name='book_info'), path('redirect/', views.MyRedirectView.as_view(), name='MyRedirectView'), # redirect/呼叫name='book_info'的url ``` # Template介紹 ## 一、設置 在books下的app新增設定 ```python from django.apps import AppConfig class BooksConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'books' ``` myproject/settings.py 把app加進`INSTALLED_APPS` ```python # 'books(包名).apps(模塊名).BooksConfig(類名)’ INSTALLED_APPS = [ 'books.apps.BooksConfig', ] ``` 修改`TEMPLATES` ```python TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, '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', ], }, }, ] ``` - `BACKEND` — 設置採用哪個模板引擎 ```python # django自己的模板 'django.template.backends.django.DjangoTemplates' # jinja2模板 'django.template.backends.jinja2.jinja2' ``` - `DIRS` — 設置模板文件儲存資料夾列表 - `APP_DIRS` — 默認false,設置是否在應用中搜尋模板文件 ## 二、創建 在book的應用程式中創建templates資料夾(名稱固定只能是**templates**) #### 新增html ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>模板</title> </head> <body> <h3>書名:{{ book_name }}</h3> <h3>作者:{{ author }}</h3> </body> </html> ``` #### 新增views 1. 使用函式view作為template,render指定template的檔案以及傳送的參數(dictionary)(上下文對象) 2. 使用類基礎view作為template,需要繼承TemplateView父類別,在template_name指定模板名,並重寫get_context_data方法 - get_context_data([LINIK](https://docs.djangoproject.com/en/2.0/ref/class-based-views/mixins-simple/#django.views.generic.base.ContextMixin.get_context_data)) - **kwargs([LINK](https://skylinelimit.blogspot.com/2018/04/python-args-kwargs.html)) ```python from django.shortcuts import render # 有使用template # 使用函式view作為模板 def show_book_info2(request): context={'book_name': 'No Game No Life', 'author': '榎宮祐'} return render(request, 'book.html', context) # 使用類基礎(class)作為模板 from django.views.generic import TemplateView class MyView(TemplateView): # 繼承TemplateView template_name='book.html' # 重寫模板名字 def get_context_data(self, **kwargs): context=super().get_context_data(**kwargs) # 重寫get_context_data方法 # context={} # 創建一個新的空字典,會將原有的context內容丟失 context['book_name']='No Game No Life' # 根據需要增加data context['author']='榎宮祐' return context ``` #### 設定url ```python path('info2/', views.show_book_info2), path('myview/', views.MyView.as_view()), ``` ## 三、django模板語言 與jinja2類似 [Django 模板语言 | Django 文档 | Django (djangoproject.com)](https://docs.djangoproject.com/zh-hans/4.2/ref/templates/language/) - `{{...}}` — 變量 - `{{變量|過濾器}}` — 過濾器(Filters),將變量進行過濾 ```python from django.shortcuts import render import datetime def hello(request, name): s1 = "Long long ago," s2 = "there's a girl named betty!" s3 = "She was 5 years old." today = datetime.datetime.now().date() context = {'name': name, 'message': (s1, s2, s3), 'date': today, 'number': 12.6666} return render(request, 'hello.html', context) ``` ```python path('hello/<str:name>/', views.hello, name='hello'), ``` ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Filters</title> </head> <body> <h3>name參數: {{name}}</h3> <h3>title 把首字母改成大寫: {{name|title}}</h3> <h3>lower 所有都小寫: {{name|lower}}</h3> <h3>upper 所有都大寫: {{name|upper}}</h3> <h3>first message的第一個元素: {{message|first}}</h3> <h3>join 將元素連接起來: {{message|join:'&nbsp;我是空格'}}</h3> <h3>truncatewords 取得前2個單字: {{message|first|truncatewords:2}}</h3> <h3>date 日期格式化: {{date|date:'Y-m-d'}}</h3> <h3>floatformat 數字格式化保留2位小數: {{number|floatformat:2}}</h3> </body> </html> ``` - {%…%}` — 模板標籤,if、for和模板繼承等 ```html <!-- 判斷結構 --> {% if condition %} ... {% endif %} <!-- 循環結構 --> {% for item in list %} ... {% endfor %} ``` views ```python def show_book_price1(request, price): info = '''書名:<輸入整數價格>''' context = {'info': info, 'price': float(price)} # 將int轉為float return render(request, 'book_info.html', context) def show_book_price2(request, price): info = '''書名:<輸入小數價格>''' context = {'info': info, 'price': float(price)} # 將字串轉為float,否則為字串無法進行比較 return render(request, 'book_info.html', context) def show_book_info(request): book1={'book_name': 'No Game No Life', 'author': '榎'} book2={'book_name': 'No Game No Life', 'author': '榎'} book3={'book_name': 'No Game No Life', 'author': '榎'} list = [] list.append(book1) list.append(book2) list.append(book3) return render(request, 'books.html', {'book_list': list}) ``` url 若路徑一樣,會根據傳的參數選擇呼叫的view(多型?) ```python path('price/<int:price>/', views.show_book_price1, name='book_price1'), re_path('^price/(?P<price>\d+\.\d+)/$', views.show_book_price2, name='book_price2'), path('', views.show_book_info, name='book_info'), ``` book_info.html ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>模板</title> </head> <body> <h3>{{ info }}</h3> {% if price <= 50.0 %} <h3>{{price}},便宜!</h3> {% else %} <h3>好貴!{{ price }}</h3> {% endif %} </body> </html> ``` books.html ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>模板標籤</title> </head> <body> {% for book in book_list %} <h3>書名:{{ book.book_name }}</h3> <h3>作者:{{ book.author }}</h3> <hr> {% endfor %} </body> </html> ``` ## 四、模板繼承 view ```python def login(request): return render(request, 'login.html') def register(request): return render(request, 'register.html') ``` url ```python path('login/', views.login), path('reg/', views.register), ``` template - `{% extends "template name" %}` — 繼承模板 - `{% block 模板區塊名 %}content{% endblock %}` — 繼承模板中指定模板區塊名中顯示的內容 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 %}{% endblock %}</title> <style type="text/css"> #footer, #header { text-align: center; } td.label{ text-align: right; } </style> </head> <body> <div id="header"> {% block header %}{% endblock %} <hr> </div> {% block body %} <div id="content"> <table width="40%" border="0"> <tbody> <tr> <td>使用者ID:</td> <td><input type="text"></td> </tr> <tr> <td>密碼:</td> <td><input type="password"></td> </tr> </tbody> </table> </div> {% endblock %} <div id="footer"> <hr> Copyright © maru 2023. All Rights Reserved </div> </body> </html> ``` login繼承base的模板 ```html {% extends "base.html" %} {% block title %}Login{% endblock %} {% block header %}使用者登入{% endblock %} {% block body %} <div id="content"> <table width="40%" border="0"> <tbody> <tr> <td>使用者ID:</td> <td><input type="text"></td> </tr> <tr> <td>密碼:</td> <td><input type="password"></td> </tr> </tbody> </table> </div> {% endblock %} ``` register繼承base模板 ```html {% extends "base.html" %} {% block title %}Register{% endblock %} {% block header %}使用者註冊{% endblock %} {% block body %} <div id="content"> <table width="40%" border="0"> <tbody> <tr> <td>姓名:</td> <td><input type="text"></td> </tr> <tr> <td>性別:</td> <td><input type="text"></td> </tr> <tr> <td>手機:</td> <td><input type="text"></td> </tr> <tr> <td>使用者ID:</td> <td><input type="text"></td> </tr> <tr> <td>密碼:</td> <td><input type="password"></td> </tr> </tbody> </table> </div> {% endblock %} ``` ## 五、訪問靜態文件 設置 - myproject/settings.py - 把`staticfiles`加進`INSTALLED_APPS`的設定 ```python INSTALLED_APPS = [ 'django.contrib.staticfiles', ] ``` 確認`STATIC_URL`路徑 ```python STATIC_URL = '/static/' ``` 在books下新增static資料夾,把靜態文件放在裡面 ``` static/ css/ images/ ``` 修改template - `{% load static %}` — 載入靜態文件資料夾 - `{% static '路徑' %}` — 引用靜態文件 ```html {% load static %} <link rel="stylesheet" type="text/css" href="{% static 'css/book.css' %}"> ``` PS. 若無作用可能是需要重啟server # HTTP ## HttpRequest請求對象 HTTP協議有請求和應答過程,客戶端提交的數據被封裝在請求對象中。Flask提供了請求對象HttpRequest。 ### HttpRequest的屬性: - `METHOD` — 返回當前請求方法 - `POST` — 返回HTTP POST請求參數,他是QueryDict字典類型數據 (key-value),包含客戶端提交表單的數據,key是客戶端控制項name,value是客戶端控制項的值 - `GET` — 返回HTTP GET請求內容 - `Cookies` — Cookie對象 - `Session` — Session對象 - `FILES` — 與上傳文件相關數據 ### POST傳值範例 #### 在前端做好表單 * `action` — 來自url.py的path定義,要寫ip外完整的url,且最後的`/`不可省略 * `method` — 定義傳值方法 * `input`中的`name`屬性 — 後端接收值的key,不可重複 * `{% csrf_token %}` — 為了防止CSRF攻擊,需要在表單區塊內的任意位置添加,若無添加會導致CSRF verification failed. Request aborted. (403)錯誤 ```html <!-- 來自url的定義,最後的斜槓不能省略 --> <form action="/books/login/" method="post"> {% csrf_token %} <table width="40%" border="0"> <tbody> <tr> <td>使用者ID:</td> <td><input type="text" name="userid"></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="userpwd"></td> </tr> <tr align="center"> <td colspan="2"> <input type="submit" value="確定"> <input type="reset" value="取消"> </td> </tr> </tbody> </table> </form> ``` #### 定義url ```python path('login/', views.login), path('', views.index), ``` #### 實作views ```python def login(request): if request.method == 'POST': datas = request.POST print(datas['userid']) print(datas['userpwd']) return render(request, 'result.html', {'result': datas}) else: # http://127.0.0.1:8000/books/login/?userid=123&userpwd=asd args_info = request.GET print(args_info['userid']) print(args_info['userpwd']) return render(request, 'result.html', {'result': args_info}) def index(request): return render(request, 'login.html') ``` #### result前端 - 在模板中使用序列or字典的方法不能加小括號`result.items` ```html <table border="1"> {% for key, value in result.items %} <tr> <th>{{ key }}</th> <td>{{ value }}</td> </tr> {% endfor %} </table> ``` ### CSRF Cross-site request forgery 跨站請求偽造 假設網站A為存在CSRF漏洞的網站,網站B是攻擊者的惡意網站,用戶C為網站A的合法用戶。 1. 用戶C在網站A輸入資料 2. 用戶C通過驗證後,信任網站A給予cookie存取權,網站A產生Cookie並返回給用戶C的瀏覽器,此時用戶C可以正常發送請求到網站A 3. cookie: 用戶可以保存資料在本機 4. 用戶C退出網站A之前,在同一個瀏覽器中,打開另一個瀏覽器訪問網站B 5. 網站B接收到用戶請求後,返回一些攻擊代碼,並發出一個請求要求訪問第三方網站A 6. 瀏覽器接受到這些攻擊代碼後,根據網站B的請求,在用戶C不知情的情況下攜帶cookie向網站A發出請求。網站A無法得知該請求是由B發起的,所以會根據用戶C的Cookie以C的權限處理該請求,導致來自網站B的惡意代碼被執行 處理方式:token [認識Cookie、Session、Token與JWT | Yang Yang (yyisyou.tw)](https://blog.yyisyou.tw/5d272c64/) ## HttpResponse應答對象 HTTP協議有請求和應答過程,伺服器端返回的數據被封裝在應答對象中。HTTP協議包煩了返回客戶端的字串、狀態碼(默認200) HttpResponse應答對象主要屬性: - `content` — 返回給客戶端內容,默認是字串 (下載不是字串) - `charset` — 指定返回給客戶端內容的字符集,默認是utf-8 - `status_code` — 響應HTTP狀態代碼 - `reason_phrase` — 響應HTTP狀態原因 HttpResponse應答對象主要方法: - `HttpResponse(content=’’, content_type=None, status=200, reason=None)` — 構造方法 - `content_type` — 應答MIME類型,用於填充HTTP Content-type標頭,默認是text/html, charset=utf-8 - `set_cookie(key, value=’’)` — 設置cookies - `delete_cookie(key, path=’/’, domain=None)` — 刪除cookies,三個三數都要符合才能刪除 ```python def hello(request): reponse = HttpResponse('<h3>Hello</h3>' , charset='gbk') # reponse.charset='utf-8' reponse.write('<h3>安安</h3>') # 向客戶端輸出字串 return reponse ``` ![Untitled](https://hackmd.io/_uploads/SJkT98DTT.png) 若charset重複指定會顯示亂碼 ## Cookie對象 Cookie 是伺服器(Server)傳送給瀏覽器(Client)的一小片段資料,並請瀏覽器保存起來,以便往後向相同的伺服器發送請求時,附上這 Cookie 的資料。 Cookie為字典結構。 #### 設置cookie,使用`HttpResponse`的`set_cookie`函數 1. cookie是字典結構,會有`key`, `value`,將cookie訊息儲存起來 2. cookie有效時間 * `expires` — HTTP/1.0中定義,設定為未來特定時間,需為日期字串、datetime,客戶端與伺服器的時間可能會有差異 * `max_age` — HTTP/1.1中定義,設定為未來的時間區間,單位為秒 3. `path`, `domain` — cookie所在的域,如果想設定一個跨域的cookie,可以設定域名 ```python # set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None) def index(request): response = HttpResponse("<h3>設定cookies</h3>") # 設定cookies response.set_cookie("userid","tony") return response # 使用expires設定超時時間 import datetime timeoutdate = datetime.datetime.today() + datetime.timedelta(days=10) response.set_cookie("userid","tony", expires=timeoutdate) # 使用max_age設定超時時間 response.set_cookie("userid","tony", max_age=60) ``` #### 取得cookie,使用`Httprequest`的`COOKIES`屬性返回cookie對象,再通過cookie的key獲得 ```python def hello(request): # 取出cookies name = request.COOKIES.get("userid") reponse = HttpResponse("<h3>取出cookies</h3>") reponse.write("<h3>Cookies中userid: "+str(name) + "</h3>") return reponse ``` #### urls映射 ```python path('', views.index), path('hello/', views.hello, name='hello') ``` ## Session對象 Session儲存在伺服器端,伺服器為每一個客戶端分配一個session ID,當瀏覽器關閉或session操作超時,session就會失效。session常常用來儲存用戶的登入訊息,session為字典結構。 在django中session的數據是放到資料庫中的,因此需要保證session已經創建,如果沒有需要執行以下指令: ```python python [manage.py](http://manage.py) migrate ``` 若沒有設定的話會發生錯誤:OperationalRttot| no such table: django_session #### 設定session ```python def index(request): return render(request, 'login.html') def login(request): if request.method == 'POST': userid = request.POST['userid'] request.session['userid']=userid return render(request, 'result.html') ``` #### 取得session傳到模板 1. `session.key` — 在模板中可直接取得session數據使用,但不能通過方法和下標?`session['userid']`方法 result.html ```html <h3>儲存在Session中的userid數據,{{ request.session.userid }}</h3> <h3><a href="/books/logout">Logout</a></h3> ``` #### 刪除session ```python def logout(request): if request.session.has_key("userid"): # 如果session包含key=userid del request.session["userid"] return render(request, 'result.html') ``` #### urls映射 [django中urls.py的path函数中name有何作用_django path name-CSDN博客](https://blog.csdn.net/myt2000/article/details/120350758) ```python path('login/', views.login), path('logout/', views.logout), path('', views.index), ``` #### 關閉瀏覽器就讓session失效,需在setting.py加上 ```python SESSION_EXPIRE_AT_BROWSER_CLOSE = True ``` [[筆記] HTTP Cookies 和 Session 使用. HTTP 是一個「無狀態協議 Stateless… | by Mike Huang | 麥克的半路出家筆記 | Medium](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98-http-cookie-%E5%92%8C-session-%E4%BD%BF%E7%94%A8-19bc740e49b5) # 表單 ## 一、介紹 djamgo提供了表單類(class)用來增強表單功能: - 增加表單驗證:web應用程式的表單中的輸入字段,需要進行驗證(凡有輸入,必有驗證) - 防止CSRF攻擊,因為表單可以生成一個CSRF token - 方便渲染頁面 django表單累`django.forms.Form`, `django.forms.ModelForm`,Form是普通的表單類,ModelForm是資料庫模型對應的表單類 ### 自定義表單類 自定義表單類繼承`django.forms.Form` ```python from django import forms class RegistrationForm(forms.Form): ... ``` ### Form字段類 - `CharField` — 渲染文本框、密碼框和文本域等HTML - `IntegerField` — 渲染`<input type="text">`的HTML元素,只能放整數 - `DecimalField` — 渲染<`input type="text">`的HTML元素,只能放小數 - `BooleanField` — 渲染`<input type="checkbox">`的HTML元素 - `ChoiceField` — 渲染radio, select 等HTML元素 - `DateField` — 日期 - `EmailField` — email - `RegexField` — 正規表達式 ## 二、範例:註冊 #### books/forms.py新增表單類 ```python from django import forms class RegistrationForm(forms.Form): username = forms.CharField(label="帳號:", max_length=20) email = forms.EmailField(label="信箱:") password1 = forms.CharField(label="密碼:", widget=forms.PasswordInput()) password2 = forms.CharField(label="再次確認密碼:", widget=forms.PasswordInput()) birthday = forms.DateField(label="出生日期:", error_messages={"invalid":"輸入的出生日期無效"}) # 驗證方法,命名為clean_[last_field_name which u want to valid] def clean_passwords(self): password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: raise forms.ValidationError("再次確認的密碼不相符") return password2 ``` #### view ```python from django.shortcuts import render from django.http import HttpResponseRedirect from .forms import RegistrationForm def login(request): return render(request, 'login.html') def register(request): if request.method == 'POST': form = RegistrationForm(request.POST) if form.is_valid(): # 表單驗證過跳轉login頁面 return HttpResponseRedirect('/books/login/') else: form = RegistrationForm() # 給予空的表單 return render(request, 'registration.html', {'form': form}) # 進行渲染 ``` url ```python path('login/', views.login), path('register/', views.register), ``` #### 表單類對應的模板 registration.html 1. 使用`form` tag 2. 將`form.變數.label` 為設定的Label,`form.變數` 為控制項 3. 顯示錯誤訊息 `form.errors.items` 4. 防止CSRF攻擊 `{% csrf_token %}` ```python {% extends "base.html" %} {% block title %}Register{% endblock %} {% block header %}使用者註冊{% endblock %} {% block body %} <ul> {% for field, errors in form.errors.items %} {% for message in errors %} <li style="color:red;">{{ message }}</li> {% endfor %} {% endfor %} </ul> <form action="/books/register/" method="POST"> {% csrf_token %} <div id="content"> <table width="40%" border="0"> <tbody> <tr> <td>{{ form.username.label }}</td> <td>{{ form.username }}</td> </tr> <tr> <td>{{ form.email.label }}</td> <td>{{ form.email }}</td> </tr> <tr> <td>{{ form.password1.label }}</td> <td>{{ form.password1 }}</td> </tr> <tr> <td>{{ form.password2.label }}</td> <td>{{ form.password2 }}</td> </tr> <tr> <td>{{ form.birthday.label }}</td> <td>{{ form.birthday }}</td> </tr> <tr align="center"> <td colspan="2"><input type="submit"></td> </tr> </tbody> </table> </div> </form> {% endblock %} ``` ### 如何在VS Code上debug? #### 開啟專案/環境資料夾 可裝方便更換資料夾VS code marketplace: Open Folder Context Menus for VS Code #### Create a debugger launch profile 1. 點選左側debug 2. create launch.json file #### 檢查路徑、其他設定 ```json "configurations": [ { "name": "Python: Django", "type": "python", "request": "launch", "program": "${workspaceFolder}/myproject/manage.py", "args": [ "runserver" ], "django": true, "justMyCode": true } ``` [Python and Django tutorial in Visual Studio Code](https://code.visualstudio.com/docs/python/tutorial-django) # 資料庫 ## SQLite3資料庫 - SQLite是開源,採C語言編寫,具有可移植性強、可靠性高,小而容易使用。 - SQLite運行時與使用它的應用程式之間共用相同的Process空間,而非單獨兩個Process,用於嵌入式。 - SQLite支援多table、index、transaction、view、trigger。 - SQLite是無資料庫類型的資料庫,可不用指定資料類型。 - python內建支援SQLite3,不需額外安裝,提供支援DB-API2規範的SQLite3驅動。 [『Android studio』SQLite資料庫建立、資料表建立與操作及查看資料工具 - HackMD](https://hackmd.io/@YubUeGjDS8C4yMh0F9Fn1g/Sylx4XQTU)[SQLite Show Tables: Listing All Tables in a Database (sqlitetutorial.net)](https://www.sqlitetutorial.net/sqlite-show-tables/) [How To Download & Install SQLite Tools (sqlitetutorial.net)](https://www.sqlitetutorial.net/download-install-sqlite/) ## ORM 物件關聯對映(映射) 大部分程式語言都是物件導向設計,採object model(物件模型)。資料被保存在關聯式資料庫中,採Relational model。 - 物件模型:抽象(Abstraction)、封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism) - 關聯式:一對一、一對多、多對多 這兩種模型間的不和諧為阻抗不區配( Impedance Mismatch),可透過ORM解決。 [關於物件和關聯資料庫(RDBMS)的阻抗不區配( Impedance Mismatch) | by 明仁 | mingjen-workshop | Medium](https://medium.com/mingjen/%E9%97%9C%E6%96%BC%E7%89%A9%E4%BB%B6%E5%92%8C%E9%97%9C%E8%81%AF%E8%B3%87%E6%96%99%E5%BA%AB-rdbms-%E7%9A%84%E9%98%BB%E6%8A%97%E4%B8%8D%E5%8D%80%E9%85%8D-impedance-mismatch-edbbd009be12) ORM是關係資料模型和物件模型之間的一個樞紐 [什麼是 ORM , 認識物件關聯對映 - HackMD](https://hackmd.io/@yoji/SJhowL8Ij?utm_source=preview-mode&utm_medium=rec) ## 定義data model ```python # 建立用戶模型 class User(models.Model): name = models.CharField(primary_key=True, max_length = 100) password = models.CharField(max_length = 100) email = models.CharField(max_length = 50, null = True) birthday = models.DateField(null = True) # 定義表的元data:描述其他數據的元素 class Meta: db_table = 'users' # table name ordering = ['name'] # 排序 ``` ### 一、繼承Model 繼承`django.db.models.Model`類 ```python from django.db import models ``` ### 二、字斷類型 - `AutoField` — 長度自動增長 - `CharField` — 可以指定最大值和最小值 - `TextField` — 長度和資料庫一致 - `DataField` — 日期 - `DateTimeField` - `TimeField` - `EmailField` - `IntegerField` - `DecimalField` - `FloatField` - [field types](https://docs.djangoproject.com/en/5.0/ref/models/fields/#field-types) ### 三、Field可選項 - null — 設定字段是否可以為null - blank — 設定字段是否可以為空白 - default — 設定默認值 - primary_key — 設定pk - unique — 設定候選key - [field options](https://docs.djangoproject.com/en/5.0/ref/models/fields/#field-options) ### 四、表的Meta 對模型進行描述 - db_name — 指定表名 - ordering — 設定排序 - abstract — 如果設定為True,說明該模型為抽象,系統不會生成對應的table,只能作為其他模型的父類使用 ### 五、同步資料庫 模型修改後,需要執行下列指令,會同步至資料庫 ```bash cd myproject python3 manage.py makemigrations python3 manage.py migrate ``` `makemigrations` — 負責生成資料庫遷移,生成執行文件 `migrate` — 負責執行或取消資料庫遷移 ## CRUD 測試CRUD可使用`manage.py shell`指令 ```bash python3 manage.py shell ``` ### Query #### 查詢所有 使用`class.objects.all()`方法,回傳QuerySet集合 ```python from books.models import User User.objects.all() ``` ![Untitled](https://hackmd.io/_uploads/H1mooUwTa.png) #### 條件查詢 - 使用`class.objects.filter()`方法,回傳QuerySet集合 - 使用`class.objects.get()`方法回傳模型對象 ```python from books.models import User User.objects.filter(name='maru') User.objects.get(name='maru') ``` ![Untitled 1](https://hackmd.io/_uploads/HJIno8wTp.png) ### Create ```python from books.models import User User.objects.create(name='丸子', password='wanzi', birthday='2000-07-17') ``` ```python from books.models import User user = User(name = '菠蘿麵包', password='boloupan', birthday='2001-04-04') user.save() ``` ### Update ```python from books.models import User user2 = User.objects.get(name='丸子') user2.email = 'wanziii@gmail.com' user.save() ``` ### Delete ```python from books.models import User user2 = User.objects.get(name='丸子') user.delete() ``` ## 範例 view ```python from .models import User def show_list(request): list = User.objects.all() return render(request, 'user_list.html', {'list': list}) def remove_user(request): # 取出url後面的username username = request.GET['username'] u = User.objects.get(name = username) u.delete() return HttpResponseRedirect('/books/list/') ``` template ```html {% block body %} <table width="60%" border="1" align="center"> <tbody> <tr> <th width="30%">使用者名稱</th> <th >信箱</th> <th width="30%">編輯</th> </tr> {% for row in list %} <tr> <td>{{ row.name }}</td> <td>{{ row.email }}</td> <td> <a href="#">修改</a>&nbsp;&nbsp; <a href="/books/del/?username={{ row.name }}">刪除</a> </td> </tr> {% endfor %} </tbody> </table> {% endblock %} ``` url ```python path('list/', views.show_list), path('del/', views.remove_user), ``` ## 查詢條件 使用`Q` ```bash User.objects.filter(name='maru') ``` ```bash from django.db.models import Q # 使用Q將查詢條件包起來 # & and User.objects.filter(Q(name='maru') & Q(email='maru@gmail.com')) # | or User.objects.filter(Q(name='maru') | Q(email='maru@gmail.com')) ``` ```bash from django.db.models import Q # 使用Q將查詢條件包起來 # 欄位__contains 包含,區分大小寫 User.objects.filter(name__contains='maru') # 欄位__icontains 包含,不區分大小寫 # in 包含在一個列表中 User.objects.filter(name__in=['maru', 'bolou']) # gt 大於 User.objects.filter(password__gt=111) # gte 大於等於 # lt 小魚 # lte小於等於 # startswith 以xx開頭,區分大小寫 User.objects.filter(name__startswith='m') # istartswith 以xx開頭,不區分大小寫 # endswith 以xx結尾,區分大小寫 # iendswith 以xx結尾,不區分大小寫 # range 在一個範圍內 User.objects.filter(birthday__range=('2000-07-01', '2001-07-01')) ``` PS. SQLite不區分大小寫 # 通用view 類基礎view (class-based view)通常是繼承`django.views.View`。 通用view可以與資料庫模型綁定,模板命名是固定的,可以簡化開發過程。通用view分為:通用顯示view、通用編輯view。 通用顯示view 用於顯示,包括 - 列表 `django.views.generic.ListView` - 詳細 `django.views.generic.DetailView` 模板名稱命名固定,eg. 資料庫模型是User,模板名為user_list.html, user_detail.html,存放位置為templates/projectname/ ## user_list #### views.py ```python from django.views.generic import ListView class UserListView(ListView): model = User ordering = ['name'] queryset = User.objects.filter(name__contains='m') ``` ListView有很多屬性,常用: - `model` — 綁定的數據模型 - `ordering` — 排序 - `queryset` — 顯示的數據,省略則顯示所有數據 - `template_name` — 指定模板文件名稱,省略的話預設命名user_list.html,存放在tmplates/books目錄下 - `paginate_by` — 設定分頁,每頁顯示資料筆數 urls.py ```python path('list/', views.UserListView.as_view(), name = 'show_users') ``` ## user_detail #### views ```python from django.views.generic import DetailView class UserDetailView(DetailView): model = User ``` DetailView有很多屬性,常用: - model - template_name urls ```python path('detail/<str:pk>', views.UserDetailView.as_view(), name = 'detail_users') ``` `pk` — 主鍵 #### user_detail.hmtl ```html {% extends "base.html" %} {% block title %}Detail{% endblock %} {% block header %}使用者明細{% endblock %} {% block body %} <div id="content"> <table width="40%" border="0"> <tbody> <tr> <td>使用者名稱:</td> <td>{{ user.name }}</td> </tr> <tr> <td>信箱:</td> <td>{{ user.email }}</td> </tr> <tr> <td>密碼:</td> <td>{{ user.password }}</td> </tr> <tr> <td>出生日期:</td> <td>{{ user.birthday|date:"Y-m-d"}}</td> </tr> <tr align="center"> <td colspan="2"><input type="button" value="返回" onclick="window.history.go(-1)"></td> </tr> </tbody> </table> </div> {% endblock %} ``` user_list.html ```html <a href="/books/detail/{{ [row.name](http://row.name/) }}">明細</a> ``` ### 分頁 #### view添加分頁 ```python from django.views.generic import ListView class UserListView(ListView): model = User ordering = ['name'] paginate_by = 10 # 分頁 ``` #### template list add 分頁 引入boostrap [v3.3.7](https://blog.getbootstrap.com/2016/07/25/bootstrap-3-3-7-released/) ```html <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> ``` ```html <div align="center"> {% if is_paginated %} <ul class="pagination"> <!-- 是否有前一頁 --> {% if page_obj.has_previous %} <!-- &laquo; << 符號 --> <!-- 上一頁連結 --> <li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li> {% else %} <li><li class="disable"><span>&laquo;</span></li></li> {% endif %} {% for i in paginator.page_range %} {% if page_obj.number == i %} <!-- 當前頁連結 --> <li class="active"><span>{{ i }}<span class="sr-only">(current)</span></span></li> {% else %} <li><a href="?page={{ i }}">{{i}}</a></li> {% endif %} {% endfor %} <!-- 是否有下一頁 --> {% if page_obj.has_next %} <!-- 下一頁連結 --> <li><a href="?page={{page_obj.next_page_number}}">&raquo;</a></li> {% else %} <li class="disable"><span>&raquo;</span></li> {% endif %} </ul> {% endif %} </div> ``` - `page_obj` — 分頁對象 - `has_next` — 是否有下一頁 - `has_previous` — 是否有上一頁 - `previous_page_number` — 上一頁頁碼 - `next_page_number` — 下一頁頁碼 - `number` — 當前頁碼 ## 通用編輯view 1. 表單view (FormView):顯示表單內容,提交表單時會驗證內容,成功後跳轉成功url。 - `django.views.generic.edit.FormView` 2. 創建view (CreateView):顯示表單,用於create model,表單驗證成功後新增資料。 - `django.views.generic.edit.CreateView` 3. 更新view (UpdateView):顯示表單,用於更新model對象,表單驗證成功後更新資料。 - user_form.html,可使用`template_name_suffix`修改 - `django.views.generic.edit.UpdateView` 4. 刪除view (DeleteView):顯示刪除確認頁面,確認後刪除資料。 - user_confirm_delete.html,可使用`template_name_suffix`修改 - `django.views.generic.edit.DeleteView` ### FormView views ```python from django.views.generic import FormView class RegistrationFormView(FormView): template_name = 'registration.html' # template name form_class = RegistrationForm # 表單class success_url = '/books/list/' # 驗證成功後跳轉的頁面 def form_valid(self, form): # 驗證成功後執行 user = User() user.name = form.cleaned_data['username'] user.password = form.cleaned_data['password1'] user.birthday = form.cleaned_data['birthday'] user.email = form.cleaned_data['email'] user.save() # 儲存data return super().form_valid(form) # 調用父類方法 ``` ```python path('register/', views.RegistrationFormView.as_view(), name='register'), ``` ### DeleteView views ```python from django.views.generic import DeleteView from django.urls import reverse_lazy class UserDeleteView(DeleteView): model = User # 綁定的model success_url = reverse_lazy('show_user') # reverse_lazy根據url設置的name,反向解析獲得url # lazy: 系統加載URLconf之前 # reverse(viewname, urlconf=None, args=None, lwargs=None, current_app=None)函數也是反向解析 # 若使用reverse,會出問題 ``` url ```python path('del/<str:pk>', views.UserDeleteView.as_view(), name = 'remove_user'), ``` template ```html <p>確認刪除該用戶:{{ user.name }}?</p> <form action="" method="POST"> {% csrf_token %} <input type="submit" value="Yes, delete."> <input type="button" value="No" onclick="window.history.go(-1)"> </form> ``` # 文件上傳 使用FileField form ```python class UploadFileForm(forms.Form): file = forms.FileField() # 上傳控制項 ``` template ```html <form action="/books/upload" method="post" enctype="multipart/form-data"> {% csrf_token %} <input type="file" name="file"> <br><br> <input type="submit" name="上傳"> </form> ``` 1. 使用`post`方法 2. 指定`enctype` — 編碼方式,`multipart/form-data` — 表示表單不進行編碼 3. `<input type="file" name="file">` — HTML上傳控制項 view ```python from .forms import UploadFileForm def upload(request): if request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) # 要指定files if form.is_valid(): handle_upload_file(request.FILES['file']) # 前端控制項name=file return HttpResponse('<h5>上傳成功</h5>') else: form = UploadFileForm() return render(request, 'upload.html', {'form': form}) def handle_upload_file(f): path = 'myproject/upload/'+f.name # 指定上傳資料夾,看目前環境資料夾在那邊 with open(path, 'wb+') as destination: # f.chunks() 將大文件分割成小塊,進行寫入數據 for chunk in f.chunks(): destination.write(chunk) ``` # 發送郵件 ## 一、介紹 #### 設定郵件 settings.py ```python # 設定email EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 EMAIL_HOST_USER = 'maru' # DEFAULT_FROM_EMAIL = 'maru@gmial.com' EMAIL_HOST_PASSWORD = '' EMAIL_USE_TLS = True # TLS加密 EMAIL_USE_SSL = False # SSL加密 ``` #### 簡單的郵件發送 `send_mail()`可以發一份郵件 - `subject` — 郵件主旨 - `message` — 郵件內容 - `from_email` — 發送郵件地址 - `recipent_list` — 接收郵件列表 ```python send_mail( 'Subject', 'Message', 'from@example.com', ['john@example.com', 'jane@example.com'] ) ``` `send_mass_mail()`可以發送多封郵件 - `datatuple` — 發送多個郵件訊息元組`(subject, message, from_email, recipient_list)` ```python datatuple = ( ('Subject', 'Message', 'from@example.com', ['john@example.com']), ('Subject', 'Message', 'from@example.com', ['jane@example.com']), ) send_mass_mail(datatuple) ``` #### 有附件的郵件 使用`EmailMessage` class - `subject` — 郵件主旨 - `body` — 郵件內容 - `from_email` — 發送郵件地址 - `to` — 接收郵件列表或元組 - `bcc` — 密送郵件列表或元組 - `cc` — 抄送郵件列表或元組 - `attachments` — 郵件附件 ```python email = EmailMessage( 'Subject', 'Body', 'from@example.com', ['to1@example.com', 'to2@example.com'], ['bcc@example.com'] ) email.attach('design.png', img_data, 'image/png') # 添加附件 email.send() # 發送郵件 ``` ## 二、範例 #### 設定郵件 #### 表單 ```python class EmailForm(forms.Form): file = forms.FileField(required=True) to = forms.EmailField(max_length= 50) cc = forms.EmailField(max_length=50, required=False) title = forms.CharField(max_length=50) content = forms.CharField(max_length=500, widget=forms.Textarea( attrs={'rows': 10, 'cols': 60} )) ``` #### 模板文件 ```python <form action="/books/send/" method="post" enctype="multipart/form-data"> {% csrf_token %} <table width="80%" border="0" align="center"> <tbody> <tr> <td width="50%">收件人:</td> <td>{{ form.to }}</td> </tr> <tr> <td width="50%">cc:</td> <td>{{ form.cc }}</td> </tr> <tr> <td width="50%">主旨:</td> <td>{{ form.title }}</td> </tr> <tr> <td width="50%">內容:</td> <td>{{ form.content }}</td> </tr> <tr> <td width="50%">附件:</td> <td>{{ form.file }}</td> </tr> <tr> <td height = 47></td> <td><input type="submit" value="發送郵件"></td> </tr> </tbody> </table> </form> ``` #### views.py ```python from .forms import EmailForm from django.core.mail import EmailMessage def sendmail(request): if request.method == 'POST': form = EmailForm(request.POST, request.FILES) if form.is_valid(): email = EmailMessage(form.cleaned_data['title'], form.cleaned_data['content'], from_email='maru@gmail.com', to=[form.cleaned_data['to']], cc=[form.cleaned_data['cc']]) # 添加附件 # 1 獲得上傳的文件對象 upload_file = request.FILE['file'] # 2 attch方法設定要發送的附件 email.attach(upload_file.name, # 文件名 upload_file.read(), # 讀取文件內容 upload_file.content_type) # 獲得文件類型 # 3 email.send() return HttpResponse('<h5>發送成功</h5>') else: form = EmailForm() return render(request, 'send_mail.html', {'form': form}) ``` #### urls.py ```python path('', views.sendmail, name='index'), path('send/', views.sendmail, name='sendmail'), ``` Q: 開啟二段認證且使用app password但還是無法寄送? A: 設定email的`EMAIL_HOST_USER`輸入帳號,不用`@gmail.com` [python - Login credentials not working with Gmail SMTP - Stack Overflow](https://stackoverflow.com/questions/16512592/login-credentials-not-working-with-gmail-smtp) > [time=2024/3/7]