# 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 設計模式

- 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:' 我是空格'}}</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
```

若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()
```

#### 條件查詢
- 使用`class.objects.filter()`方法,回傳QuerySet集合
- 使用`class.objects.get()`方法回傳模型對象
```python
from books.models import User
User.objects.filter(name='maru')
User.objects.get(name='maru')
```

### 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>
<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 %}
<!-- « << 符號 -->
<!-- 上一頁連結 -->
<li><a href="?page={{ page_obj.previous_page_number }}">«</a></li>
{% else %}
<li><li class="disable"><span>«</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}}">»</a></li>
{% else %}
<li class="disable"><span>»</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]