全文鏈接
第一章 創(chuàng)建一個blog應用
第二章 使用高級特性來增強你的blog
第三章 擴展你的blog應用
第四章上 創(chuàng)建一個社交網站
第四章下 創(chuàng)建一個社交網站
第五章 在你的網站中分享內容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴展你的商店
第九章下 擴展你的商店
第十章上 創(chuàng)建一個在線學習平臺
第十章下 創(chuàng)建一個在線學習平臺
第十一章 緩存內容
第十二章 構建一個API
書籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé
(譯者注:還有4章!還有4章全書就翻譯完成了?。?/p>
第八章
管理付款和訂單
在上一章,你創(chuàng)建了一個基礎的在線商店包含一個產品列表以及訂單系統(tǒng)。你還學習了如何執(zhí)行異步的任務通過使用Celery。在這一章中,你會學習到如何集成一個支付網關(譯者注:支付網關(Payment Gateway)是銀行金融網絡系統(tǒng)和Internet網絡之間的接口,是由銀行操作的將Internet上傳輸?shù)臄?shù)據(jù)轉換為金融機構內部數(shù)據(jù)的一組服務器設備,或由指派的第三方處理商家支付信息和顧客的支付指令。以上是我百度的。)到你的站點中。你還會擴展管理平臺站點來管理訂單和用不同的格式導出它們。
在這一章中,我們會覆蓋以下幾點:
- 集成一個支付網關到你的站點中
- 管理支付通知
- 導出訂單為CSV格式
- 創(chuàng)建定制視圖給管理頁面
- 動態(tài)的生成PDF支票
集成一個支付網關
一個支付網關允許你在線處理支付。通過使用一個支付網關,你可以管理顧客的訂單以及委托一個可靠的,安全的第三方處理支付。這意味著你無需擔心存儲信用卡信息到你的系統(tǒng)中。
PayPal 提供了多種方法來集成它的網管到你的站點中。標準的集成由一個Buy now按鈕組成,這個按鈕你可以已經在別的網站見到過(譯者注:國內還是支付寶和微信比較多)。這個按鈕會重定向購買者到PayPal去處理支付。我們將要集成PayPal支付標準包含一個定制的Buy now按鈕到我們的站點中。PayPal將會處理支付并且發(fā)送一個消息通知給我們的服務指明該筆支付的狀態(tài)。
創(chuàng)建一個PayPal賬戶
你需要有一個PayPal商業(yè)賬戶來集成支付網關到你的站點中。如果你還沒有一個PayPal賬戶,去 https://www.paypal.com/signup/account 注冊。確保你選擇了一個Bussiness Account并且注冊成為PayPal支付標準解決方案,如下圖所示:
填寫你的詳情在注冊表單中并且完成注冊流程。PayPal會發(fā)送給你一封e-mail來核對你的賬戶。
安裝django-paypal
Django-paypal是一個第三方django應用,它可以簡化集成PayPal到Django項目中。我們將要使用它來集成PayPal支付標準解決方案到我們的商店中。你可以找到django-paypal的文檔,訪問 http://django-paypal.readthedocs.org/。
安裝django-paypal在shell中通過以下命令:
pip install django-paypal==0.2.5
(譯者注:現(xiàn)在應該有最新版本,書上使用的是0.2.5版本)
編輯你的項目中的settings.py文件,添加'paypal.standard.ipn'到INSTALLED_APPS設置中,如下所示:
INSTALLED_APPS = (
# ...
'paypal.standard.ipn',
)
這個應用提供自django-paypal來集成PayPal支付標準通過Instant Payment Notification(IPN)。我們之后會操作支付通知。
添加以下設置到myshop的settings.py文件來配置django-paypal:
# django-paypal settings
PAYPAL_RECEIVER_EMAIL = 'mypaypalemail@myshop.com'
PAYPAL_TEST = True
以上兩個設置含義如下:
- PAYPAL_RECEIVER_EMAIL:你的PayPal賬戶e-mail。使用你創(chuàng)建的PayPal賬戶e-mail替換mypaypalemail@myshop.com。
- PAYPAL_TEST:一個布爾類型指示是否PayPal的沙箱環(huán)境,該環(huán)境可以用來處理支付。這個沙箱允許你測試你的PayPal集成在遷移到一個正式生產的環(huán)境之前。
打開shell運行如下命令來同步django-paypal的模型(models)到數(shù)據(jù)庫中:
python manage.py migrate
你會看到如下類似的輸出:
Running migrations:
Rendering model states... DONE
Applying ipn.0001_initial... OK
Applying ipn.0002_paypalipn_mp_id... OK
Applying ipn.0003_auto_20141117_1647... OK
django-paypal的模型(models)如今已經同步到了數(shù)據(jù)庫中。你還需要添加django-paypal的URL模式到你的項目中。編輯主的urls.py文件,該文件位于myshop目錄,然后添加以下的URL模式。記住粘貼該URL模式要在shop.urls模式之前為了避免錯誤的模式匹配:
url(r'^paypal/', include('paypal.standard.ipn.urls')),
讓我們添加支付網關到結賬流程中。
添加支付網關
結賬流程工作如下:
- 1.用戶添加物品到他們的購物車中
- 2.用戶結賬他們的購物車
- 3.用戶被重定向到PayPal進行支付
- 4.PayPal發(fā)送一個支付通知給我們的站點
- 5.PayPal重定向用戶回到我們的網站
創(chuàng)建一個新的應用到你的項目中使用如下命令:
python manage.py startapp payment
我們將要使用這個應用去管理結賬過程和用戶支付。
編輯你的項目的settings.py文件,添加'payment'到INSTALLED_APPS設置中,如下所示:
INSTALLED_APPS = (
# ...
'paypal.standard.ipn',
'payment',
)
payment應用現(xiàn)在已經在項目中激活。編輯orders應用的views.py文件并且確保包含以下導入:
from django.shortcuts import render, redirect
from django.core.urlresolvers import reverse
替換以下order_create視圖(view)的內容:
# launch asynchronous task
order_created.delay(order.id)
return render(request, 'orders/order/created.html', locals())
新的內容為:
# launch asynchronous task
order_created.delay(order.id) # set the order in the session
request.session['order_id'] = order.id # redirect to the payment
return redirect(reverse('payment:process'))
在成功的創(chuàng)建一個新的訂單之后,我們設置這個訂單ID到當前的會話中使用order_id會話鍵(session key)。之后,我們重定向用戶到payment:process
URL,這個我們下一步就是創(chuàng)建。
編輯payment應用的views.py文件然后添加如下代碼:
from decimal import Decimal
from django.conf import settings
from django.core.urlresolvers import reverse
from django.shortcuts import render, get_object_or_404
from paypal.standard.forms import PayPalPaymentsForm
from orders.models import Order
def payment_process(request):
order_id = request.session.get('order_id')
order = get_object_or_404(Order, id=order_id)
host = request.get_host()
paypal_dict = {
'business': settings.PAYPAL_RECEIVER_EMAIL,
'amount': '%.2f' % order.get_total_cost().quantize(
Decimal('.01')),
'item_name': 'Order {}'.format(order.id),
'invoice': str(order.id),
'currency_code': 'USD',
'notify_url': 'http://{}{}'.format(host,
reverse('paypal-ipn')),
'return_url': 'http://{}{}'.format(host,
reverse('payment:done')),
'cancel_return': 'http://{}{}'.format(host,
reverse('payment:canceled')),
}
form = PayPalPaymentsForm(initial=paypal_dict)
return render(request,
'payment/process.html',
{'order': order, 'form':form})
在payment_process
視圖(view)中,我們生成了一個PayPal的Buy now按鈕用來支付一個訂單。首先,我們拿到當前的訂單從order_id
會話鍵中,這個鍵值被之前的order_create
視圖(view)設置。我們拿到這個order
對象通過給予的ID并且構建一個新的PayPalPaymentsForm
,該表單表單包含以下字段:
- business:PayPal商業(yè)賬戶用來處理支付。我們使用e-mail賬戶,該賬戶定義在
PAYPAL_RECEIVER_EMAIL
設置那里。 - amount:向顧客索要的總價。
- item_name:正在出售的商品名。我們使用訂單ID,因為訂單可能包含很多產品。
- currency_code:本次支付的貨幣。我們設置這里為USD使用U.S. Dollar(譯者注:傳說中的美金)。需要使用相同的貨幣,該貨幣被設置在你的PayPal賬戶中(例如:EUR 對應歐元)。
- notify_url:這個URL PayPal將會發(fā)送IPN請求過去。我們使用django-paypal提供的
paypal-ipn
URL。這個視圖(view)與這個URL關聯(lián)來操作支付通知以及存儲它們到數(shù)據(jù)庫中。 - return_url:這個URL用來重定向用戶當他的支付成功之后。我們使用URL
payment:done
,這個我們接下來會創(chuàng)建。 - cancel_return:這個URL用來重定向用戶如果這個支付被取消或者有其他問題。我們使用URL
payment:canceled
,這個我們接下來會創(chuàng)建。
PayPalpaymentsForm
將會被渲染成一個標準表單帶有隱藏的字段,并且用戶將來只能看到Buy now按鈕。當用戶點擊該按鈕,這個表單將會提交到PayPal通過POST渠道。
讓我們創(chuàng)建簡單的視圖(views)給PayPal用來重定向用戶當支付成功,或者當支付被取消因為某些原因。添加以下代碼到相同的views.py文件:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def payment_done(request):
return render(request, 'payment/done.html')
@csrf_exempt
def payment_canceled(request):
return render(request, 'payment/canceled.html')
我們使用csrf_exempt
裝飾器來避免Django期待一個CSRF標記,因為PayPal能重定向用戶到以上兩個視圖(views)通過POST渠道。創(chuàng)建新的文件在payment應用目錄下并且命名為urls.py。添加以下代碼:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^process/$', views.payment_process, name='process'),
url(r'^done/$', views.payment_done, name='done'),
url(r'^canceled/$', views.payment_canceled, name='canceled'),
]
這些URL是給支付工作流的。我們已經包含了以下URL模式:
- process:給這個視圖(view)用來生成PayPal表單給Buy now按鈕。
- done:給PayPal用來重定向用戶當支付成功的時候。
- canceled:給PayPal用來重定向用戶當支付取消的時候。
編輯主的myshop項目的urls.py文件,包含URL模式給payment應用:
url(r'^payment/', include('payment.urls',namespace='payment')),
記住粘貼以上內容在shop.urls
模式之前用來避免錯誤的模式匹配。
創(chuàng)建以下文件建構在payment應用目錄下:
templates/
payment/
process.html
done.html
canceled.html
編輯payment/process.html模板(template)并且添加以下代碼:
{% extends "shop/base.html" %}
{% block title %}Pay using PayPal{% endblock %}
{% block content %}
<h1>Pay using PayPal</h1>
{{ form.render }}
{% endblock %}
這個模板(template)會渲染PayPalPaymentsForm
并且展示Buy now按鈕。
編輯payment/done.html模板(template)并且添加如下代碼:
{% extends "shop/base.html" %}
{% block content %}
<h1>Your payment was successful</h1>
<p>Your payment has been successfully received.</p>
{% endblock %}
這個模板(template)的頁面給用戶重定向當成功支付之后。
編輯payment/canceled.html模板(template)并且添加以下代碼:
{% extends "shop/base.html" %}
{% block content %}
<h1>Your payment has not been processed</h1>
<p>There was a problem processing your payment.</p>
{% endblock %}
這個模板(template)的頁面給用戶重定向當有這個支付過程出現(xiàn)問題或者用戶取消了這次支付。
讓我們嘗試完成的支付過程。
使用PayPal的沙箱
打開 http://developer.paypal.com 在你的瀏覽器中然后進行登錄使用你的PayPal商業(yè)賬戶。點擊Dashboard菜單項,在左方菜單點擊Accounts選項在Sandbox下方。你會看到你的沙箱測試賬戶列,如下所示:
一開始,你將會看到一個商業(yè)以及一個個人測試賬戶由PayPal動態(tài)創(chuàng)建。你可以創(chuàng)建新的沙箱測試賬戶通過使用Create Account按鈕。
點擊Personal Account在列中擴大它,之后點擊Profile鏈接。你會看到一些信息關于這個測試賬戶包含e-mail和profile信息,如下所示:
在Funding tab中,你會找到銀行賬戶,信用卡日期,以及PayPal信用余額。
這些測試賬戶能夠被用來做支付在你的網站中當使用沙箱環(huán)境。跳轉到Profile tab然后點擊Change password鏈接。創(chuàng)建一個定制密碼給這個測試賬戶。
打開shell并且啟動開發(fā)服務器使用命令python manage.py runserver
。打開 http://127.0.0.1:8000 在你的瀏覽器中,添加一些產品到購物車中,并且填寫結賬表單。當你點擊Place order按鈕,這個訂單會被保存在數(shù)據(jù)庫中,這個訂單ID會被保存在當前的會話中,并且你會被重定向到支付處理頁面。這個頁面從會話中獲取訂單并且渲染PayPal表單顯示一個Buy now按鈕,如下所示:
你可以看下HTML源碼來看下生成的表單字段。
點擊Buy now按鈕。你會被重定向到PayPal,并且你會看到如下頁面:
輸入購買者測試賬戶e-mail和密碼然后點擊Log In按鈕。你會被重定向到以下頁面:
現(xiàn)在,點擊Pay now按鈕。最后,你會看到批準頁面該頁面包含你的交易ID。這個頁面看上去如下所示:
點擊Return to e-mail@domain.com按鈕。你會被重定向到的URL是你之前在PayPalPaymentsForm
中的return_url
字段中定義的。這個URL對應payment_done
視圖(view)。這個頁面看上去如下所示:
這個支付已經成功了。然而,PayPal并沒有發(fā)送一個支付狀態(tài)通知給我們的應用,因為我們運行我們的項目在我們本地主機,IP是 127.0.0.1 這并不是一個公開地址。我們將要學習如何使我們的站點可以從Internet訪問并且接收IPN通知。
獲取支付通知
IPN是一個方法提供自大部分的支付網關用來跟蹤實時的購買。一個通知會立即發(fā)送到你的服務當這個網關處理了一個支付。這個通知包含所有支付詳情,包括狀態(tài)以及一個支付的簽名,該簽名可以用來確定這個消息的來源點。這個消息被發(fā)送通過一個單獨的HTTP請求給你的服務。在出現(xiàn)連接問題的情況下,PayPal將會多次企圖通知你的站點。
django-paypal應用內置兩種不同的信號給IPNs。如下:
- valid_ipn_received:會被觸發(fā)當IPN信息獲取自PayPal是正確的并且不是一個已存在數(shù)據(jù)庫中的消息的復制。
- invalid_ipn_received:這個信號會觸發(fā)當IPN獲取自PayPal包含無效的數(shù)據(jù)或者不是一個良好的形式。
我們將要創(chuàng)建一個定制的接受函數(shù)并且連接它給valid_ipn_received
信號用來確定支付。
創(chuàng)建新的文件在payment應用目錄下,并且命名為signals.py,添加如下代碼:
from django.shortcuts import get_object_or_404
from paypal.standard.models import ST_PP_COMPLETED
from paypal.standard.ipn.signals import valid_ipn_received
from orders.models import Order
def payment_notification(sender, **kwargs):
ipn_obj = sender
if ipn_obj.payment_status == ST_PP_COMPLETED:
# payment was successful
order = get_object_or_404(Order, id=ipn_obj.invoice)
# mark the order as paid
order.paid = True
order.save()
valid_ipn_received.connect(payment_notification)
我們連接payment_notification
接收函數(shù)給django-paypal提供的valid_ipn_received
信號。這個接收函數(shù)工作如下:
- 1.我們獲取發(fā)送對象,該對象是一個PayPalIPN模型的實例,位于
paypal.standard.ipn.models
。 - 2.我們檢查
payment_status
屬性來確保它和django-payapl的完整狀態(tài)相同。這個狀態(tài)指示這個支付已經成功處理。 - 3.之后我們使用
get_object_or_404()
快捷函數(shù)來拿到訂單,該訂單的ID匹配invoice
參數(shù)我們之前提供給PayPal。 - 4.我們備注這個訂單已經支付通過設置它的
paid
屬性為True
并且保存這個訂單對象到數(shù)據(jù)庫中。
你需要確保你的信號方法已經加載,這樣這個接收函數(shù)會被調用當valid_ipn_received
信號被觸發(fā)的時候。The best practice is to load your signals when the application containing them is loaded. (譯者注:誰幫我翻一下,好拗口?。?/strong>。這能夠實現(xiàn)通過定義一個定制應用配置,這方面會在下一節(jié)進行解釋。
配置我們的應用
你已經學習了關于應用的配置在第六章 跟蹤用戶操作。我們將要定義一個定制配置給我們的payment應用為了加載我們的信號接收函數(shù)。
創(chuàng)建一個新的文件在payment應用目錄下命名為apps.py。添加如下代碼:
from django.apps import AppConfig
class PaymentConfig(AppConfig):
name = 'payment'
verbose_name = 'Payment'
def ready(self):
# import signal handlers
import payment.signals
在上述代碼中,我們定義了一個定制AppConfif
類給payment應用。name
參數(shù)是這個應用的名字,verbose_name包含可讀的樣式。我們導入信號方法在ready()
方法中確保它們會被加載當這個應用初始化的時候。
編輯payment應用的init.py文件,添加以下行:
default_app_config = 'payment.apps.PaymentConfig'
以上操作可以使Django動態(tài)加載你的定制應用配置類。你可以找到更進一步的信息關于應用配置,通過訪問 https://docs.djangoproject.com/en/1.8/ref/applications/ 。
測試支付通知
由于我們工作在本地環(huán)境中,我們需要確保我們的站點可以被PayPal獲得。有不少應用允許你使你的開發(fā)環(huán)境在Internet中可獲得。我們將要使用Ngrok,它就是其中一個最著名的。
./ngrok http 8000
通過這條命名,你告訴Ngrok去創(chuàng)建一條隧道給你的本地主機在端口8000上并且分配一個Internet可訪問主機名給它。你可以看到如下類似輸出:
Tunnel Status online
Version 2.0.17/2.0.17
Web Interface http://127.0.0.1:4040
Forwarding http://1a1b50f2.ngrok.io -> localhost:8000
Forwarding https://1a1b50f2.ngrok.io -> localhost:8000
Connnections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Ngrok告訴我們關于我們的站點,運行在本地8000端口使用Django開發(fā)服務器,已經可以在Internet訪問到通過URLs http://1a1b50f2.ngrok.io 以及 https://1a1b50f2.ngrok.io ,前者是HTTP,后者是HTTPS。Ngrok還提供一個URL來訪問一個web接口用來顯示信息關于發(fā)送到這個服務的請求。
打開Ngrok提供的URL在瀏覽器中;例如,http://1a1b50f2.ngrok.io 。添加一些產品到購物車中,放置一個訂單,然后使用你的PayPal測試賬戶進行支付。這個時候,PayPal將能夠拿到這個URL,這個URL由PayPalPaymentsForm
的notify_url
字段生成,在payment_process
視圖(view)中。如果你看一下這個渲染過的表單,你會看到這個HTML表單字段看上去如下所示:
<input id="id_notify_url" name="notify_url" type="hidden"
value="http://1a1b50f2.ngrok.io/paypal/">
在結束支付過程之后,打開 http://127.0.0.1:8000/admin/ipn/paypalipn/ 在你的瀏覽器中。你會看到一個IPN對象對應最新的支付狀態(tài)為Completed。這個對象包含所有的支付信息,該對象由PayPal發(fā)送給你提供給IPN通知的URL。IPN管理列展示頁面看上去如下所示:
你還可以啟動IPNs通過使用PayPal的IPN模擬器位于 https://developer.paypal.com/developer/ipnSimulator/ 。這個模擬器允許你指定字段和發(fā)送的通知類型。
除了PayPal支付標準外,PayPal提供Website Payments Pro,它是一個訂購服務允許你接受支付在你的站點中而不需要重定向用戶到PayPal。你可以找到更多信息關于如何集成Website Payments Pro,通過訪問 http://django-paypal.readthedocs.org/en/v0.2.5/pro/index.html。
導出訂單為CSV文件
有時候,你可能想要導出包含在模型的信息到一個文件中,這樣你可以導入它到其他的系統(tǒng)中。其中一個范圍最廣的格式用來導出/導入數(shù)據(jù)就是Comma-Separated Values(CSV)。一個CSV文件就是一個純文本文件包含若干記錄。一個csv文件中,通常一行記錄為一個訂單,以及一些分割符(通常是逗號) 。我們將要定制管理平臺站點能夠導出訂單為CSV文件。
添加定制操作到管理平臺站點中
Django提供你多種不同的選項來定制管理平臺站點。我們將要修改對象列視圖(view)來包含一個定制的管理操作。
一個管理操作工作如下:一個用戶選擇對象從管理對象列頁面通過復選框,之后選擇一個操作去執(zhí)行在所有被選擇的項上,然后執(zhí)行該操作。以下
展示操作會位于管理頁面的哪個地方:
創(chuàng)建定制管理操作允許管理人員一次性應用操作多個元素。
你可以創(chuàng)建一個定制操作通過編寫一個經常性的函數(shù)獲取以下參數(shù):
- 當前展示的ModelAdmin
- 當前請求對象,一個HttpRequest實例
- 一個查詢集(QuerySet)給用戶所選擇的對象
這個函數(shù)將會被執(zhí)行當這個操作被觸發(fā)在管理平臺站點上。
我們將要創(chuàng)建一個定制管理操作來下載訂單列表的CSV文件。編輯orders應用的admin.py文件,添加如下代碼在OrderAdmin
類之前:
import csv
import datetime
from django.http import HttpResponse
def export_to_csv(modeladmin, request, queryset):
opts = modeladmin.model._meta
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; \
filename={}.csv'.format(opts.verbose_name)
writer = csv.writer(response)
fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many]
# Write a first row with header information
writer.writerow([field.verbose_name for field in fields])
# Write data rows
for obj in queryset:
data_row = []
for field in fields:
value = getattr(obj, field.name)
if isinstance(value, datetime.datetime):
value = value.strftime('%d/%m/%Y')
data_row.append(value)
writer.writerow(data_row)
return response
export_to_csv.short_description = 'Export to CSV'
在這代碼中,我們執(zhí)行以下任務:
- 1.我們創(chuàng)建一個
HttpResponse
實例包含一個定制text/csv
內容類型來告訴瀏覽器這個響應需要處理為一個CSV文件。我們還添加一個Content-Disposition
頭來指示這個HTTP響應包含一個附件。 - 2.我們創(chuàng)建一個CSV
writer
對象,該對象將會被寫入response
對象。 - 3.我們動態(tài)的獲取
model
字段通過使用模型(moedl)_meta
選項的get_fields()
方法。我們排除多對多以及一對多的關系。 - 4.我們編寫了一個頭行包含字段名。
- 5.我們迭代給予的查詢集(QuerySet)并且為每一個查詢集中返回的對象寫入行。我們注意格式化
datetime
對象因為這個輸出值給CSV必須是一個字符串。 - 6.我們定制這個操作的顯示名在模板(template)中通過設置一個
short_description
屬性給這個函數(shù)。
我們已經創(chuàng)建了一個普通的管理操作可以添加到任意的ModelAdmin類。
最后,添加新的export_to_csv
管理操作給OrderAdmin
類如下所示:
class OrderAdmin(admin.ModelAdmin):
# ...
actions = [export_to_csv]
打開 http://127.0.0.1:8000/admin/orders/order/ 在你的瀏覽器中。管理操作看上去如下所示:
選擇一些訂單然后選擇Export to CSV操作從下拉選框中,之后點擊Go按鈕。你的瀏覽器會下載生成的CSV文件名為order.csv。打開下載的文件使用一個文本編輯器。你會看到的內容如以下的格式,包含一個頭行以及你之前選擇的每行訂單對象:
ID,first name,last name,email,address,postal
code,city,created,updated,paid
3,Antonio,Mele?,antonio.mele@gmail.com,Bank Street 33,WS J11,London,25/05/2015,25/05/2015,False
...
如你所見,創(chuàng)建管理操作是非常簡單的。
擴展管理站點通過定制視圖(view)
有時候你可能想要定制管理平臺站點,比如處理ModelAdmin的配置,管理操作的創(chuàng)建,以及覆蓋管理模板(templates)。在這樣的場景中,你需要創(chuàng)建一個定制的管理視圖(view)。通過一個定制的管理視圖(view),你可以構建任何你需要的功能。你只需要確保只有管理用戶能訪問你的視圖并且你維護這個管理的外觀和感覺通過你的模板(template)擴展自一個管理模板(template)。
讓我們創(chuàng)建一個定制視圖(view)來展示關于一個訂單的信息。編輯orders應用下的views.py文件,添加以下代碼:
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import get_object_or_404
from .models import Order
@staff_member_required
def admin_order_detail(request, order_id):
order = get_object_or_404(Order, id=order_id)
return render(request,
'admin/orders/order/detail.html',
{'order': order})
這個staff_member_required
裝飾器檢查用戶請求這個頁面的is_active
以及is_staff
字段是被設置為True
。在這個視圖(view)中,我們獲取Order
對象通過給予的id以及渲染一個模板來展示這個訂單。
現(xiàn)在,編輯orders應用中的urls.py文件并且添加以下URL模式:
url(r'^admin/order/(?P<order_id>\d+)/$',
views.admin_order_detail,
name='admin_order_detail'),
創(chuàng)建以下文件結構在orders應用的templates/目錄下:
admin/
orders/
order/
detail.html
編輯detail.html模板(template),添加以下內容:
{% extends "admin/base_site.html" %}
{% load static %}
{% block extrastyle %}
<link rel="stylesheet" type="text/css" href="{% static "css/admin.css" %}" />
{% endblock %}
{% block title %}
Order {{ order.id }} {{ block.super }}
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url "admin:index" %}">Home</a> ?
<a href="{% url "admin:orders_order_changelist" %}">Orders</a>
?
<a href="{% url "admin:orders_order_change" order.id %}">Order {{ order.id }}</a>
? Detail
</div>
{% endblock %}
{% block content %}
<h1>Order {{ order.id }}</h1>
<ul class="object-tools">
<li>
<a href="#" onclick="window.print();">Print order</a>
</li>
</ul>
<table>
<tr>
<th>Created</th>
<td>{{ order.created }}</td>
</tr>
<tr>
<th>Customer</th>
<td>{{ order.first_name }} {{ order.last_name }}</td>
</tr>
<tr>
<th>E-mail</th>
<td><a href="mailto:{{ order.email }}">{{ order.email }}</a></td>
</tr>
<tr>
<th>Address</th>
<td>{{ order.address }}, {{ order.postal_code }} {{ order.city
}}</td>
</tr>
<tr>
<th>Total amount</th>
<td>${{ order.get_total_cost }}</td>
</tr>
<tr>
<th>Status</th>
<td>{% if order.paid %}Paid{% else %}Pending payment{% endif %}</td>
</tr>
</table>
<div class="module">
<div class="tabular inline-related last-related">
<table>
<h2>Items bought</h2>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for item in order.items.all %}
<tr class="row{% cycle "1" "2" %}">
<td>{{ item.product.name }}</td>
<td class="num">${{ item.price }}</td>
<td class="num">{{ item.quantity }}</td>
<td class="num">${{ item.get_cost }}</td>
</tr>
{% endfor %}
<tr class="total">
<td colspan="3">Total</td>
<td class="num">${{ order.get_total_cost }}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}
這個模板(template)是用來顯示一個訂單詳情在管理平臺站點中。這個模板(template)擴展Djnago的管理平臺站點的admin/base_site.html模板,它包含管理的主要HTML結構和CSS樣式。我們加載定制的靜態(tài)文件css/admin.css。
為了使用靜態(tài)文件,你需要拿到它們從這章教程的實例代碼中。復制位于orders應用的static/目錄下的靜態(tài)文件然后添加它們到你的項目的相同位置。
我們使用定義在父模板(template)的區(qū)塊包含我們自己的內容。我們展示信息關于訂單和購買的商品。
當你想要擴展一個管理模板(template),你需要知道它的結構以及確定存在的區(qū)塊。你可以找到所有管理模板(template),通過訪問 https://github.com/django/django/tree/1.8.6/django/contrib/admin/templates/admin 。
你也可以重寫一個管理模板(template)如果你需要的話。為了重寫一個管理模板(template),拷貝它到你的template目錄保持相同的相對路徑以及文件名。Django管理平臺站點將會使用你的定制模板(template)替代默認的模板。
最后,讓我們添加一個鏈接給每個Order對象在管理平臺站點的列展示頁面。編輯orders應用的admin.py文件然后添加以下代碼,在OrderAdmin
類上面:
from django.core.urlresolvers import reverse
def order_detail(obj):
return '<a href="{}">View</a>'.format(
reverse('orders:admin_order_detail', args=[obj.id]))
order_detail.allow_tags = True
這個函數(shù)需要一個Order對象作為參數(shù)并且返回一個HTML鏈接給admind_order_detail
URL。Django會避開默認的HTML輸出。我們必須設置allow_tags
屬性為True
來避開auto-escaping。
設置
allow_tags
屬性為True
來避免HTML-escaping在一些Model方法,ModelAdmin方法,以及任何其他的調用中。當你使用allow_tags
的時候,能確保避開用戶輸入的跨域腳本。
之后,編輯OrderAdmin
類來展示鏈接:
class OrderAdmin(admin.ModelAdmin):
list_display = ['id',
'first_name',
# ...
'updated',
order_detail]
打開 http://127.0.0.1:8000/admin/orders/order/ 在你的瀏覽器中。每一行現(xiàn)在都會包含一個View鏈接如下所示:
點擊某個訂單的View鏈接來加載定制訂單詳情頁面。你會看到一個頁面如下所示:
生成動態(tài)的PDF發(fā)票
如今我們已經有了一個完整的結賬和支付系統(tǒng),我們可以生成一張PDF發(fā)票給每個訂單。有幾個Python庫可以生成PDF文件。一個最流行的生成PDF的Python庫是Reportlab。你可以找到關于如何使用Reportlab輸出PDF文件的信息,通過訪問 https://docs.djangoproject.com/en/1.8/howto/outputting-pdf/ 。
在大部分的場景中,你還需要添加定制樣式和格式給你的PDF文件。你會發(fā)現(xiàn)渲染一個HTML模板(template)以及轉化該模板(template)為一個PDF文件更加的方便,保持Python遠離表現(xiàn)層。我們要遵循這個方法并且使用一個模塊來生成PDF文件通過Django。我們將要使用WeasyPrint,它是一個Python庫可以生成PDF文件從HTML模板中。
安裝WeasyPrint
首先,安裝WeasyPrint的依賴給你的OS,這些依賴你可以找到通過訪問 http://weasyprint.org/docs/install/#platforms 。
之后,安裝WeasyPrint通過pip渠道使用如下命令:
pip install WeasyPrint==0.24
創(chuàng)建一個PDF模板(template)
我們需要一個HTML文檔給WeasyPrint輸入。我們將要創(chuàng)建一個HTML模板(template),渲染它使用Django,并且傳遞它給WeasyPrint來生成PDF文件。
創(chuàng)建一個新的模板(template)文件在orders應用的templates/orders/order/目錄下命名為pdf.html*。添加如下內容:
<html>
<body>
<h1>My Shop</h1>
<p>
Invoice no. {{ order.id }}</br>
<span class="secondary">
{{ order.created|date:"M d, Y" }}
</span>
</p>
<h3>Bill to</h3>
<p>
{{ order.first_name }} {{ order.last_name }}<br>
{{ order.email }}<br>
{{ order.address }}<br>
{{ order.postal_code }}, {{ order.city }}
</p>
<h3>Items bought</h3>
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
{% for item in order.items.all %}
<tr class="row{% cycle "1" "2" %}">
<td>{{ item.product.name }}</td>
<td class="num">${{ item.price }}</td>
<td class="num">{{ item.quantity }}</td>
<td class="num">${{ item.get_cost }}</td>
</tr>
{% endfor %}
<tr class="total">
<td colspan="3">Total</td>
<td class="num">${{ order.get_total_cost }}</td>
</tr>
</tbody>
</table>
<span class="{% if order.paid %}paid{% else %}pending{% endif %}">
{% if order.paid %}Paid{% else %}Pending payment{% endif %}
</span>
</body>
</html>
這個模板(template)就是PDF發(fā)票。在這個模板(template)中,我們展示所有訂單詳情以及一個HTML <table>
元素包含所有商品。我們還包含了一條消息來展示如果該訂單已經支付或者支付還在進行中。
渲染PDF文件
我們將要創(chuàng)建一個視圖(view)來生成PDF發(fā)票給存在的訂單通過使用管理平臺站點。編輯order應用的views.py文件添加如下代碼:
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string
import weasyprint
@staff_member_required
def admin_order_pdf(request, order_id):
order = get_object_or_404(Order, id=order_id)
html = render_to_string('orders/order/pdf.html',
{'order': order})
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'filename=\
"order_{}.pdf"'.format(order.id)
weasyprint.HTML(string=html).write_pdf(response,
stylesheets=[weasyprint.CSS(
settings.STATIC_ROOT + 'css/pdf.css')])
return response
這個視圖(view)用來生成一個PDF發(fā)票給一個訂單。我們使用staff_member_required
裝飾器來確保只有管理人員能夠訪問這個視圖(view)。我們獲取Order對象通過給予的ID并且我們使用rander_to_string()
函數(shù)提供自Django來渲染orders/order/pdf.html。這個渲染過的HTML會被保存到html
變量中。之后,我們生成一個新的HttpResponse
對象指定application/pdf
的內容類型并且包含Content-Disposition
頭來指定這個文件名。我們使用WeasyPrint來生成一個PDF文件從渲染的HTML代碼中并且將該文件寫入HttpResponse
對象中。我們加載它從本地路徑通過使用STATIC_ROOT
設置。最后,我們返回這個生成的響應。
由于我們需要使用STATIC_ROOT
設置,我們需要添加它到我們的項目中。這個項目將會是靜態(tài)文件的所在地。編輯myshop項目的settings.py文件,添加如下設置:
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
之后,運行命令python manage.py collectstatic
。你會在輸出末尾看到如下輸出:
You have requested to collect static files at the destination
location as specified in your settings:
code/myshop/static
This will overwrite existing files!
Are you sure you want to do this?
輸入yes然后回車。你會得到一條消息,告知那個靜態(tài)文件已經復制到STATIC_ROOT
目錄中。
collectstatic命令復制所有靜態(tài)文件從你的應用到定義在STATIC_ROOT
設置的目錄中。這允許每個應用去提供它自己的靜態(tài)文件通過使用一個static/目錄來包含它們。你還可以提供額外的靜態(tài)文件來源在STATICFILES_DIRS
設置。所有的目錄被指定在STATICFILED_DIRS
列中的都將會被復制到STATIC_ROOT
目錄中當collectstatic被執(zhí)行的時候。
編輯orders應用目錄下的urls.py文件并且添加如下URL模式:
url(r'^admin/order/(?P<order_id>\d+)/pdf/$',
views.admin_order_pdf,
name='admin_order_pdf'),
現(xiàn)在,我們可以編輯管理列展示頁面給Order模型(model)來添加一個鏈接給PDF文件給每一個結果。編輯orders應用的admin.py文件并且添加以下代碼在OrderAdmin
類上面:
def order_pdf(obj):
return '<a href="{}">PDF</a>'.format(
reverse('orders:admin_order_pdf', args=[obj.id]))
order_pdf.allow_tags = True
order_pdf.short_description = 'PDF bill'
添加order_pdf
給OrderAdmin
類的list_display
屬性:
class OrderAdmin(admin.ModelAdmin):
list_display = ['id',
# ...
order_detail,
order_pdf]
如果你指定一個short_description
屬性給你的調用,Django將會使用它給這個列命名。
打開 http://127.0.0.1:8000/admin/orders/order/ 在你的瀏覽器中。每一行現(xiàn)在都包含一個PDF鏈接,如下所示:
點擊某一個訂單的PDF。你會看到一個生成的PDF文件,如下所示一個訂單還沒有支付完成:
對于支付完成的訂單,你會看到如下所示的PDF文件:
通過e-mail發(fā)送PDF文件
讓我們發(fā)送一封e-mail給我們的顧客包含生成的PDF發(fā)表但一個支付被接收的時候。編輯payment應用下的signals.py文件并且添加如下導入:
from django.template.loader import render_to_string
from django.core.mail import EmailMessage
from django.conf import settings
import weasyprint
from io import BytesIO
之后添加如下代碼在order.save()
行之后,需要同樣的縮進等級:
# create invoice e-mail
subject = 'My Shop - Invoice no. {}'.format(order.id)
message = 'Please, find attached the invoice for your recent
purchase.'
email = EmailMessage(subject,
message,
'admin@myshop.com',
[order.email])
# generate PDF
html = render_to_string('orders/order/pdf.html', {'order': order})
out = BytesIO()
stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/pdf.css')]
weasyprint.HTML(string=html).write_pdf(out,
stylesheets=stylesheets)
# attach PDF file
email.attach('order_{}.pdf'.format(order.id),
out.getvalue(),
'application/pdf')
# send e-mail
email.send()
在這個信號中,我們使用Django提供的EmailMessage
類來創(chuàng)建一個e-mail對象。之后我們渲染這個模板(template)到html
變量中。我們生成PDF文件從渲染的模板(template)中,并且我們輸出它到一個BytesIO
實例中,該實例是一個內容字節(jié)緩存。之后我們附加這個生成的PDF文件到EmailMessage
對象通過使用它的attach()
方法,包含這個out
緩存的內容。
記住設置你的SMTP設置在項目的settings.py文件中來發(fā)送e-mail。你可以到第二章 通過高級特性擴展你的blog去看下一個SMTP配置的例子。
現(xiàn)在你可以打開Ngrok提供給你的應用的URL然后完成一個新的支付處理為了收到PDF發(fā)票到你的e-mail中。
總結
在這一章中,你集成了一個支付網關到你的項目中。你定制了Django管理平臺頁面并且學習到了如何動態(tài)的生成CSV以及PDF文件。
在下一章中將會給你一個深刻理解關于國際化和本地化給Django項目。你還會學習到創(chuàng)建一個贈券系統(tǒng)已經構建一個產品推薦引擎。
譯者總結
不知不覺,第八章也翻譯完成了,還是渣翻,精校之前大家先湊合著看吧,有問題我會及時更新。目前全書翻譯已完成三分之二,離不開各位的支持,我們下章再見!對了,本章完成日是三八婦女(女神?)節(jié),各位女看客們節(jié)日快樂!