乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      第八章 管理支付和訂單

       ly88 2018-05-16

      在上一章中,你創(chuàng)建了一個包括商品目錄和訂單系統(tǒng)的在線商店。你還學習了如何用Celery啟動異步任務。在這一章中,你會學習如何在網站中集成支付網關。你還會擴展管理站點,用于管理訂單和導出不同格式的訂單。

      我們會在本章覆蓋以下知識點:

      • 在項目中集成支付網關
      • 管理支付通知
      • 導出訂單到CSV文件中
      • 為管理站點創(chuàng)建自定義視圖
      • 動態(tài)生成PDF單據

      8.1 集成支付網關

      支付網關允許你在線處理支付。你可以使用支付網關管理用戶訂單,以及通過可靠的,安全的第三方代理處理支付。這意味著你不用考慮在自己的系統(tǒng)中存儲信用卡。

      有很多支付網關可供選擇。我們將集成PayPal,它是最流行的支付網關之一。

      PayPal提供了幾種方法在網站中集成它的網關。標準集成包括一個Buy now按鈕,你可能在其它網站見過。這個按鈕把顧客重定向到PayPal來處理支付。我們將在網站中集成包括一個自定義Buy now按鈕的PayPal Payments Standard。PayPal會處理支付,并發(fā)送一條支付狀態(tài)的信息到我們的服務器。

      8.1.1 創(chuàng)建PayPal賬戶

      你需要一個PayPal商家賬戶,才能在網站中集成支付網關。如果你還沒有PayPal賬戶,在這里注冊。確保你選擇了商家賬戶。

      在注冊表單填寫詳細信息完成注冊。PayPal會給你發(fā)送一封郵件確認賬戶。

      8.1.2 安裝django-paypal

      django-paypal是一個第三方Django應用,可以簡化在Django項目中集成PayPal。我們將用它在我們的商店中集成PayPal Payments Standard。你可以在這里查看django-paypal的文檔。

      在終端使用以下命令安裝django-paypal:

      pip install django-paypal

      編輯項目的settings.py文件,在INSTALLED_APPS設置中添加paypal.standard.ipn

      INSTALLED_APPS = [ # ... 'paypal.standard.ipn',]

      這個應用是django-paypal提供的,通過Instant Payment Notification(IPN)集成PayPal Payments Standard。我們之后會處理支付通知。

      myshopsettings.py文件添加以下設置來配置django-paypal:

      # django-paypal settingsPAYPAL_RECEIVER_EMAIL = 'mypaypalemail@myshop.com'PAYPAL_TEST = True

      這些設置分別是:

      • PAYPAL_RECEIVER_EMAIL:你PayPal賬戶的郵箱地址。用你創(chuàng)建PayPal賬戶的郵箱替換mypaypalemail@myshop.com。
      • PAYPAL_TEST:一個布爾值,表示是否用PayPal的Sandbox環(huán)境處理支付。在遷移到生產環(huán)境之前,你可以用Sandbox測試PayPal集成。

      打開終端執(zhí)行以下命令,同步django-paypal的模型到數據庫中:

      python manage.py migrate

      你會看到類似這樣結尾的輸出:

      Running migrations: Applying ipn.0001_initial... OK Applying ipn.0002_paypalipn_mp_id... OK Applying ipn.0003_auto_20141117_1647... OK Applying ipn.0004_auto_20150612_1826... OK Applying ipn.0005_auto_20151217_0948... OK Applying ipn.0006_auto_20160108_1112... OK Applying ipn.0007_auto_20160219_1135... OK

      現在django-paypal的模型已經同步到數據庫中。你還需要添加django-paypal的URL模式到項目中。編輯myshop項目的主urls.py文件,并添加以下URL模式。記住,把它放在shop.urls模式之前,避免錯誤的模式匹配:

      url(r'^paypal/', include('paypal.standard.ipn.urls')),

      讓我們把支付網關添加到結賬過程中。

      8.1.3 添加支付網關

      結賬流程是這樣的:

      1. 用戶添加商品到購物車中。
      2. 用戶結賬購物車。
      3. 重定向用戶到PayPal進行支付。
      4. PayPal發(fā)送支付通知到我們的服務器。
      5. PayPal重定向用戶返回我們的網站。

      使用以下命令在項目中創(chuàng)建一個新應用:

      python manage.py startapp payment

      我們將使用這個應用管理結賬流程和用戶支付。

      編輯項目的settings.py文件,在INSTALLED_APP設置中添加payment

      INSTALLED_APPS = [ # ... 'paypal.standard.ipn', 'payment',]

      現在payment應用已經在項目中激活了。編輯orders應用的views.py文件,添加以下導入:

      from django.shortcuts import render, redirectfrom django.core.urlresolvers import reverse

      找到order_create視圖中的以下代碼:

      # launch asynchronous taskorder_created.delay(order.id)return render(request, 'orders/order/created.html', {'order': order})

      替換為下面的代碼:

      # launch asynchronous taskorder_created.delay(order.id)request.session['order_id'] = order.idreturn redirect(reverse('payment:process'))

      創(chuàng)建訂單成功之后,我們用order_id會話鍵在當前會話中設置訂單ID。然后我們把用戶重定向到接下來會創(chuàng)建的payment:process URL。

      編輯payment應用的views.py文件,并添加以下代碼:

      from decimal import Decimalfrom django.conf import settingsfrom django.core.urlresolvers import reversefrom django.shortcuts import render, get_object_or_404from paypal.standard.forms import PayPalPaymentsFormfrom orders.models import Orderdef 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視圖中,我們生成了一個自定義PayPal的Buy now按鈕用于支付。首先我們從order_id會話鍵中獲得當前訂單,這個鍵值之前在order_create視圖中設置過。我們獲得指定ID的Order對象,并創(chuàng)建了包括以下字段的PayPalPaymentForm

      • business:處理支付的PayPal商家賬戶。在這里我們使用PAYPAL_RECEIVER_EMAIL設置中定義的郵箱賬戶。
      • amount:向顧客收取的總價。
      • item_name:出售的商品名。我們使用商品ID,因為訂單里可能包括多個商品。
      • invoice:單據ID。每次支付對應的這個ID應用是唯一的。我們使用訂單ID。
      • currency_code:這次支付的貨幣。我們設置為USD使用美元。使用與PayPal賬戶中設置的相同貨幣(EUR對應歐元)。
      • notify_url:PayPal發(fā)送IPN請求到這個URL。我們使用django-paypal提供的paypal-ipn URL。這個URL關聯(lián)的視圖處理負責支付通知和在數據庫中保存支付通知。
      • return_url:支付成功后重定向用戶到這個URL。我們使用之后會創(chuàng)建的payment:done URL。
      • cancel_return:如果支付取消,或者遇到其它問題,重定向用戶到這個URL。我們使用之后會創(chuàng)建的payment:canceled URL。

      PayPalPaymentForm會被渲染為帶隱藏字典的標準表單,用戶只能看到Buy now按鈕。點用戶點擊這個按鈕,表單會通過POST提交到PayPal。

      讓我們創(chuàng)建一個簡單的視圖,當支付完成,或者因為某些原因取消支付,讓PayPal重定向用戶。在同一個views.py文件中添加以下代碼:

      from django.views.decorators.csrf import csrf_exempt@csrf_exemptdef payment_done(request): return render(request, 'payment/done.html')@csrf_exemptdef payment_canceled(request): return render(request, 'payment/canceled.html')

      因為PayPal可以通過POST重定向用戶到這些視圖的任何一個,所以我們用csrf_exempt裝飾器避免Django期望的CSRF令牌。在payment應用目錄中創(chuàng)建urls.py文件,并添加以下代碼:

      from django.conf.urls import urlfrom . import viewsurlpatterns = [ 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:用于生成帶Buy now按鈕的PayPal表單的視圖
      • done:當支付成功后,用于PayPal重定向用戶
      • canceled:當支付取消后,用于PayPal重定向用戶

      編輯myshop項目的主urls.py文件,引入payment應用的URL模式:

      url(r'^payment/', include('payment.urls', namespace='payment')),

      記住把它放在shop.urls模式之前,避免錯誤的模式匹配。

      payment應用目錄中創(chuàng)建以下文件結構:

      templates/ payment/ process.html done.html canceled.html

      編輯payment/process.html模板,添加以下代碼:

      {% extends 'shop/base.html' %}{% block title %}Pay using PayPal{% endblock title %}{% block content %}

      Pay using PayPal

      {{ form.render }}{% endblock content %}

      這個模板用于渲染PayPalPaymentForm和顯示Buy now按鈕。

      編輯payment/done.html模板,添加以下代碼:

      {% extends 'shop/base.html' %}{% block content %}

      Your payment was successful

      Your payment has been successfully received.

      {% endblock content %}

      用戶支付成功后,會重定向到這個模板頁面。

      編輯payment/canceled.html模板,并添加以下代碼:

      {% extends 'shop/base.html' %}{% block content %}

      Your payment has not been processed

      There was a problem processing your payment.

      {% endblock content %}

      處理支付遇到問題,或者用戶取消支付時,會重定向到這個模板頁面。

      讓我們嘗試完整的支付流程。

      8.1.4 使用PayPal的Sandbox

      在瀏覽器中打開http://developer.paypal.com,并用你的PayPal商家賬戶登錄。點擊Dashboard菜單項,然后點擊Sandbox下的Accounts選項。你會看到你的sandbox測試賬戶列表,如下圖所示:

      最初,你會看到一個商家賬戶和一個PayPal自動生成的個人測試賬戶。你可以點擊Create Account按鈕創(chuàng)建新的sandbox測試賬戶。

      點擊列表中TypePERSONAL的賬戶,然后點擊Pofile鏈接。你會看到測試賬戶的信息,包括郵箱地址和個人資料信息,如下圖所示:

      Funding標簽頁中,你會看到銀行賬戶,信用卡數據,以及PayPal貸方余額。

      當你的網站使用sandbox環(huán)境時,測試賬戶可以用來處理支付。導航到Profile標簽頁,然后點擊修改Change password鏈接。為這個測試賬戶創(chuàng)建一個自定義密碼。

      在終端執(zhí)行python manage.py runserver命令啟動開發(fā)服務器。在瀏覽器中打開http://127.0.0.1:8000/,添加一些商品到購物車中,然后填寫結賬表單。當你點擊Place order按鈕時,訂單會存儲到數據庫中,訂單ID會保存在當前會話中,然后會重定向到支付處理頁面。這個頁面從會話中獲得訂單,并渲染帶Buy now按鈕的PayPal表單,如下圖所示:

      譯者注:啟動開發(fā)服務器后,還需要啟動RabbitMQ和Celery,因為我們要用它們異步發(fā)送郵件,否則會拋出異常。

      你可以看一眼HTML源碼,查看生成的表單字段。

      點擊Buy now按鈕。你會被重定向到PayPal,如下圖所示:

      輸入顧客測試賬號的郵箱地址和密碼,然后點擊登錄按鈕。你會被重定向到以下頁面:

      譯者注:即之前修改過密碼的個人賬戶。

      現在點擊立即付款按鈕。最后,你會看到一個包括交易ID的確認頁面,如下圖所示:

      點擊返回商家按鈕。你會被重定向到PayPalPaymentFormreturn_url字段指定的URL。這是payment_done視圖的URL,如下圖所示:

      支付成功!但是因為我們在本地運行項目,127.0.0.1不是一個公網IP,所以PayPal不能給我們的應用發(fā)送支付狀態(tài)通知。我們接下來學習如何讓我們的網站可以從Internet訪問,從而接收IPN通知。

      8.1.5 獲得支付通知

      IPN是大部分支付網關都會提供的方法,用于實時跟蹤購買。當網關處理完一個支付后,會立即給你的服務器發(fā)送一個通知。該通知包括所有支付細節(jié),包括狀態(tài)和用于確認通知來源的支付簽名。這個通知作為獨立的HTTP請求發(fā)送到你的服務器。出現問題的時候,PayPal會多次嘗試發(fā)送通知。

      django-payapl自帶兩個不同的IPN信號,分別是:

      • valid_ipn_received:當從PayPal接收的IPN消息是正確的,并且不會與數據庫中現在消息重復時觸發(fā)
      • invalid_ipn_received:當從PayPal接收的消息包括無效數據或者格式不對時觸發(fā)

      我們將創(chuàng)建一個自定義接收函數,并把它連接到valid_ipn_received信號來確認支付。

      payment應用目錄中創(chuàng)建signals.py文件,并添加以下代碼:

      from django.shortcuts import get_object_or_404from paypal.standard.models import ST_PP_COMPLETEDfrom paypal.standard.ipn.signals import valid_ipn_receivedfrom orders.models import Orderdef 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接收函數連接到django-paypal提供的valid_ipn_received信號。接收函數是這樣工作的:

      1. 我們接收sender對象,它是在paypal.standard.ipn.models中定義的PayPalPN模型的一個實例。
      2. 我們檢查paypal_status屬性,確保它等于django-paypal的完成狀態(tài)。這個狀態(tài)表示支付處理成功。
      3. 接著我們用get_object_or_404快捷函數獲得訂單,這個訂單的ID必須匹配我們提供給PayPal的invoice參數。
      4. 我們設置訂單的paid屬性為True,標記訂單狀態(tài)為已支付,并把Order對象保存到數據庫中。

      valid_ipn_received信號觸發(fā)時,你必須確保信號模塊已經加載,這樣接收函數才會被調用。最好的方式是在包括它們的應用加載的時候,加載你自己的信號??梢酝ㄟ^定義一個自定義的應用配置來實現,我們會在下一節(jié)中講解。

      8.1.6 配置我們的應用

      你已經在第六章學習了應用配置。我們將為payment應用定義一個自定義配置,用來加載我們的信號接收函數。

      payment應用目錄中創(chuàng)建apps.py文件,并添加以下代碼:

      from django.apps import AppConfigclass PaymentConfig(AppConfig): name = 'payment' verbose_name = 'Payment' def ready(self): # improt signal handlers import payment.signals

      在這段代碼中,我們?yōu)?code>payment應用定義了一個AppConfig類。name參數是應用的名字,verbose_name是一個可讀的名字。我們在ready()方法中導入信號模板,確保應用初始化時會加載信號模塊。

      編輯payment應用的__init__.py文件,并添加這一行代碼:

      default_app_config = 'payment.apps.PaymentConfig'

      這會讓Django自動加載你的自定義應用配置類。你可以在這里閱讀更多關于應用配置的信息。

      8.1.7 測試支付通知

      因為我們在本地環(huán)境開發(fā),所以我們需要讓PayPal可以訪問我們的網站。有幾個應用程序可以讓開發(fā)環(huán)境通過Internet訪問。我們將使用Ngrok,是最流行的之一。

      這里下載你的操作系統(tǒng)版本的Ngrok,并使用以下命令運行:

      ./ngrok http 8000

      這個命令告訴Ngrok在8000端口為你的本地主機創(chuàng)建一個鏈路,并為它分配一個Internet可訪問的主機名。你可以看到類似這樣的輸入:

      Session Status onlineAccount lakerszhy (Plan: Free)Update update available (version 2.2.4, Ctrl-U to update)Version 2.1.18Region United States (us)Web Interface http://127.0.0.1:4040Forwarding http://c0f17d7c. -> localhost:8000Forwarding https://c0f17d7c. -> localhost:8000Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00

      Ngrok告訴我們,我們網站使用的Django開發(fā)服務器在本機的8000端口運行,現在可以通過http://c0f17d7c.https://c0f17d7c.(分別對應HTTP和HTTPS協(xié)議)在Internet上訪問。Ngrok還提供了一個網頁URL,這個網頁顯示發(fā)送到這個服務器的信息。在瀏覽器中打開Ngrok提供的URL,比如http://c0f17d7c.。在購物車中添加一些商品,下單,然后用PayPal測試賬戶支付。此時,PayPal可以訪問payment_process視圖中PayPalPaymentFormnotify_url字段生成的URL。如果你查看渲染的表單,你會看類似這樣的HTML表單:

      完成支付處理后,在瀏覽器中打開http://127.0.0.1:8000/admin/ipn/paypalipn/。你會看到一個IPN對象,對應狀態(tài)是Completed的最新一筆支付。這個對象包括支付的所有信息,它由PayPal發(fā)送到你提供給IPN通知的URL。

      譯者注:如果通過http://c0f17d7c.訪問在線商店,則需要在項目的settings.py文件的ALLOWED_HOSTS設置中添加c0f17d7c.。

      譯者注:我在后臺看到的一直都是Pending狀態(tài),一直沒有找出原因。哪位朋友知道的話,請給我留言,謝謝。

      你也可以在這里使用PayPal的模擬器發(fā)送IPN。模擬器允許你指定通知的字段和類型。

      除了PayPal Payments Standard,PayPal還提供了Website Payments Pro,它是一個訂購服務,可以在你的網站接收支付,而不用重定向到PayPal。你可以在這里查看如何集成Website Payments Pro

      8.2 導出訂單到CSV文件

      有時你可能希望把模型中的信息導出到文件中,然后把它導入到其它系統(tǒng)中。其中使用最廣泛的格式是Comma-Separated Values(CSV)。CSV文件是一個由若干條記錄組成的普通文本文件。通常一行包括一條記錄和一些定界符號,一般是逗號,用于分割記錄的字段。我們將自定義管理站點,讓它可以到處訂單到CSV文件。

      8.2.1 在管理站點你添加自定義操作

      Django提供了大量自定義管理站點的選項。我們將修改對象列表視圖,在其中包括一個自定義的管理操作。

      一個管理操作是這樣工作的:用戶在管理站點的對象列表頁面用復選框選擇對象,然后選擇一個在所有選中選項上執(zhí)行的操作,最后執(zhí)行操作。下圖顯示了操作位于管理站點的哪個位置:

      創(chuàng)建自定義管理操作允許工作人員一次在多個元素上進行操作。

      你可以編寫一個常規(guī)函數來創(chuàng)建自定義操作,該函數需要接收以下參數:

      • 當前顯示的ModelAdmin
      • 當前請求對象——一個HttpRequest實例
      • 一個用戶選中對象的QuerySet

      當在管理站點觸發(fā)操作時,會執(zhí)行這個函數。

      我們將創(chuàng)建一個自定義管理操作,來下載一組訂單的CSV文件。編輯orders應用的admin.py文件,在OrderAdmin類之前添加以下代碼:

      import csvimport datetimefrom django.http import HttpResponsedef 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 responseexport_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. 我們用模型的_meta選項的get_fields()方法動態(tài)獲得模型的字段。我們派出了對多對和一對多關系。
      4. 我們用字段名寫入標題行。
      5. 我們迭代給定的QuerySet,并為QuerySet返回的每個對象寫入一行數據。因為CSV的輸出值必須為字符串,所以我們格式化datetime對象。
      6. 我們設置函數的short_description屬性,指定這個操作在模板中顯示的名字。

      我們創(chuàng)建了一個通用的管理操作,可以添加到所有ModelAdmin類上。

      最后,如下添加export_to_csv管理操作到OrderAdmin類上:

      calss OrderAdmin(admin.ModelAdmin): # ... actions = [export_to_csv]

      在瀏覽器中打開http://127.0.0.1:8000/admin/orders/order/,管理操作如下圖所示:

      選中幾條訂單,然后在選擇框中選擇Export to CSV操作,接著點擊Go按鈕。你的瀏覽器會下載生成的order.csv文件。用文本編輯器打開下載的文件。你會看到以下格式的內容,其中包括標題行,以及你選擇的每個Order對象行:

      ID,first name,last name,email,address,postal code,city,created,updated,paid1,allen,iverson,lakerszhy@gmail.com,北京市朝陽區(qū),100012,北京市,11/05/2017,11/05/2017,False2,allen,kobe,lakerszhy@gmail.com,北京市朝陽區(qū),100012,北京市,11/05/2017,11/05/2017,False

      正如你所看到的,創(chuàng)建管理操作非常簡單。

      8.3 用自定義視圖擴展管理站點

      有時,你可能希望通過配置ModelAdmin,創(chuàng)建管理操作和覆寫管理目標來定制管理站點。這種情況下,你需要創(chuàng)建自定義的管理視圖。使用自定義視圖,可以創(chuàng)建任何你需要的功能。你只需要確保只有工作人員能訪問你的視圖,以及讓你的模板繼承自管理模板來維持管理站點的外觀。

      讓我們創(chuàng)建一個自定義視圖,顯示訂單的相關信息。編輯orders應用的views.py文件,并添加以下代碼:

      from django.contrib.admin.views.decorators import staff_member_requiredfrom django.shortcuts import get_object_or_404from .models import Order@staff_member_requireddef 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_activeis_staff字段是否為True。這個視圖中,我們用給定的ID獲得Order對象,然后渲染一個模板顯示訂單。

      現在編輯orders應用的urls.py文件,添加以下URL模式:

      url(r'^admin/order/(?P\d+)/$', views.admin_order_detail, name='admin_order_detail'),

      orders應用的templates目錄中創(chuàng)建以下目錄結構:

      admin/ orders/ order/ detail.html

      編輯detail.html模板,添加以下代碼:

      {% extends 'admin/base_site.html' %}{% load static %}{% block extrastyle %} {% endblock extrastyle %}{% block title %} Order {{ order.id }} {{ block.super }}{% endblock title %}{% block breadcrumbs %} {% endblock breadcrumbs %}{% block content %}

      Order {{ order.id }}

      Created {{ order.created }}
      Customer {{ order.first_name }} {{ order.last_name }}
      E-mail {{ order.email }}
      Address {{ order.address }}, {{ order.postal_code }} {{ order.city }}
      Total amount ${{ order.get_total_cost }}
      Status {% if order.paid %}Paid{% else %}Pending payment{% endif %}
      {% endblock content %}

      這個模板用于在管理站點顯示訂單詳情。模板擴展自Django管理站點的admin/base_site.html模板,其中包括主HTML結構和管理站的CSS樣式。我們加載自定義的靜態(tài)文件css/admin.css

      為了使用靜態(tài)文件,我們可以從本章的示例代碼中獲得它們。拷貝orders應用的static/目錄中的靜態(tài)文件,添加到你項目中的相同位置。

      我們使用父模板中定義的塊引入自己的內容。我們顯示訂單信息和購買的商品。

      當你想要擴展一個管理模板時,你需要了解它的結構,并確定它存在哪些塊。你可以在這里查看所有管理模板。

      如果需要,你也可以覆蓋一個管理模板。把要覆蓋的模板拷貝到templates目錄中,保留一樣的相對路徑和文件。Django的管理站點會使用你自定義的模板代替默認模板。

      最后,讓我們?yōu)楣芾碚军c的列表顯示頁中每個Order對象添加一個鏈接。編輯orders應用的amdin.py文件,在OrderAdmin類之前添加以下代碼:

      from django.core.urlresolvers import reversedef order_detail(obj): return 'View'.format(reverse('orders:admin_order_detail', args=[obj.id]))order_detail.allow_tags = True

      這個函數接收一個Order對象作為參數,并返回一個admin_order_detail的HTML鏈接。默認情況下,Django會轉義HTML輸出。我們必須設置函數的allow_tags屬性為True,從而避免自動轉義。

      在任何Model方法,ModelAdmin方法,或者可調用函數中設置allow_tags屬性為True可以避免HTML轉義。使用allow_tags時,確保轉義用戶的輸入,以避免跨站點腳本。

      然后編輯OrderAdmin類來顯示鏈接:

      class OrderAdmin(admin.ModelAdmin): list_display = [... order_detail]

      在瀏覽器中打開http://127.0.0.1:8000/admin/orders/order/,現在每行都包括一個View鏈接,如下圖所示:

      點擊任何一個訂單的View鏈接,會加載自定義的訂單詳情頁面,如下圖所示:

      8.4 動態(tài)生成PDF單據

      我們現在已經有了完成的結賬和支付系統(tǒng),可以為每個訂單生成PDF單據了。有幾個Python庫可以生成PDF文件。一個流行的生成PDF文件的Python庫是Reportlab。你可以在這里查看如果使用Reportlab輸出PDF文件。

      大部分情況下,你必須在PDF文件中添加自定義樣式和格式。你會發(fā)現,讓Python遠離表現層,渲染一個HTML模板,然后把它轉換為PDF文件更加方便。我們將采用這種方法,在Django中用模塊生成PDF文件。我們會使用WeasyPrint,它是一個Python庫,可以從HTML模板生成PDF文件。

      8.4.1 安裝WeasyPrint

      首先,為你的操作系統(tǒng)安裝WeasyPrint的依賴,請訪問這里

      然后用以下命令安裝WeasyPrint:

      pip install WeasyPrint

      8.4.2 創(chuàng)建PDF模板

      我們需要一個HTML文檔作為WeasyPrint的輸入。我們將創(chuàng)建一個HTML模板,用Django渲染它,然后把它傳遞給WeasyPrint生成PDF文件。

      orders應用的templates/orders/order/目錄中創(chuàng)建pdf.html文件,并添加以下代碼:

      My Shop

      Invoice no. {{ order.id }}
      {{ order.created|date:'M d, Y' }}

      Bill to

      {{ order.first_name }} {{ order.last_name }}
      {{ order.email }}
      {{ order.address }}
      {{ order.postal_code }}, {{ order.city }}

      Items bought

      {% for item in order.items.all %} {% endfor %}
      Product Price Quantity Cost
      {{ item.product.name }} ${{ item.price }} {{ item.quantity }} ${{ item.get_cost }}
      Total ${{ order.get_total_cost }}
      {% if order.paid %}Paid{% else %}Pending payment{% endif %}

      這是PDF單據的模板。在這個模板中,我們顯示所有訂單詳情和一個包括商品的HTML的

      元素。我們還包括一個消息,顯示訂單是否支付。

      8.4.3 渲染PDF文件

      我們將創(chuàng)建一個視圖,在管理站點中生成已存在訂單的PDF單據。編輯orders應用的views.py文件,并添加以下代碼:

      from django.conf import settingsfrom django.http import HttpResponsefrom django.template.loader import render_to_stringimport weasyprint@staff_member_requireddef 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

      這個視圖用于生成訂單的PDF單據。我們用staff_member_required裝飾器確保只有工作人員可以訪問這個視圖。我們用給定的ID獲得Order對象,并用Django提供的render_to_string()函數渲染orders/order/pdf.html文件。被渲染的HTML保存在html變量中。然后,我們生成一個新的HttpResponse對象,指定application/pdf內容類型,并用Content-Disposition指定文件名。我們用WeasyPrint從被渲染的HTML代碼生成一個PDF文件,并把文件寫到HttpResponse對象中。我們用css/pdf.css靜態(tài)文件為生成的PDF文件添加CSS樣式。我們從STATIC_ROOT設置中的本地路徑加載它。最后返回生成的響應。

      因為我們需要使用STATIC_ROOT設置,所以需要把它添加到我們項目中。這是項目的靜態(tài)文件存放的路徑。編輯myshop項目的settings.py文件,添加以下設置:

      STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

      接著執(zhí)行python manage.py collectstatic命令。你會看到這樣結尾的輸出:

      You have requested to collect static files at the destinationlocation as specified in your settings: /Users/lakerszhy/Documents/GitHub/Django-By-Example/code/Chapter 8/myshop/staticThis will overwrite existing files!Are you sure you want to do this?

      輸入yes并按下Enter。你會看到一條消息,顯示靜態(tài)文件已經拷貝到STATIC_ROOT目錄中。

      collectstatic命令拷貝應用中所有靜態(tài)文件到STATIC_ROOT設置中定義的目錄。這樣每個應用可以在static/目錄中包括靜態(tài)文件。你還可以在STATICFILES_DIRS設置中提供其它靜態(tài)文件源。執(zhí)行collectstatic命令時,STATICFILES_DIRS中列出的所有目錄都會被拷貝到STATIC_ROOT目錄中。

      編輯orders應用中的urls.py文件,添加以下URL模式:

      url(r'admin/order/(?P\d+)/pdf/$', views.admin_order_pdf, name='admin_order_pdf'),

      現在,我們可以編輯管理列表顯示頁面,為Order模型的每條記錄添加一個PDF文件鏈接。編輯orders應用的admin.py文件,在OrderAdmin類之前添加以下代碼:

      def order_pdf(obj): return 'PDF'.format(reverse('orders:admin_order_pdf', args=[obj.id]))order_pdf.allow_tags = Trueorder_pdf.short_description = 'PDF bill'

      order_pdf添加到OrderAdmin類的list_display屬性中,如下所示:

      class OrderAdmin(admin.ModelAdmin): list_display = [..., order_detail, order_pdf]

      如果你為可調用對象指定了short_description屬性,Django將把它作為列名。

      在瀏覽器中打開http://127.0.0.1:8000/admin/orders/order。每行都會包括一個PDF鏈接,如下圖所示:

      點擊任意一條訂單的PDF鏈接。你會看到生成的PDF文件,下圖是未支付的訂單:

      已支付訂單如下圖所示:

      8.4.4 通過郵件發(fā)送PDF文件

      當收到支付時,讓我們給顧客發(fā)送一封包括PDF單據的郵件。編輯payment應用的signals.py文件,并添加以下導入:

      from django.template.loader import render_to_stringfrom django.core.mail import EmailMessagefrom django.conf import settingsimport weasyprintfrom io import BytesIO

      然后在order.save()行之后添加以下代碼,保持相同的縮進:

      # create invoice e-mailsubject = '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 PDFhtml = 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 fileemail.attach('order_{}.pdf'.format(order.id), out.getvalue(), 'application/pdf')# send e-mailemail.send()

      在這個信號中,我們用Django提供的EmailMessage類創(chuàng)建了一個郵件對象。然后把模板渲染到html變量中。我們從渲染的模板中生成PDF文件,并把它輸出到一個BytesIO實例(內存中的字節(jié)緩存)中。接著我們用EmailMessage對象的attach()方法,把生成的PDF文件和out緩存中的內容添加到EmailMessage對象中。

      記得在項目settings.py文件中設置發(fā)送郵件的SMTP設置,你可以參考第二章。

      現在打開Ngrok提供的應用URL,完成一筆新的支付,就能在郵件中收到PDF單據了。

      8.5 總結

      在這一章中,你在項目中集成了支付網關。你自定義了Django管理站點,并學習了如果動態(tài)生成CSV和PDF文件。

      下一章會深入了解Django項目的國際化和本地化。你還會創(chuàng)建一個優(yōu)惠券系統(tǒng)和商品推薦引擎。

        本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多