1. 程式人生 > >第九章 擴充套件你的商店(上)

第九章 擴充套件你的商店(上)

9 擴充套件你的商店

上一章中,你學習瞭如何在商店中整合支付閘道器。你完成了支付通知,學習瞭如何生成CSV和PDF檔案。在這一章中,你會在商店中新增優惠券系統。你將學習如何處理國際化和本地化,並構建一個推薦引擎。

本章會覆蓋以下知識點:

  • 建立優惠券系統實現折扣
  • 在專案中新增國際化
  • 使用Rosetta管理翻譯
  • 使用django-parler翻譯模型
  • 構建一個商品推薦系統

9.1 建立優惠券系統

很多線上商店會給顧客發放優惠券,在購買商品時可以兌換折扣。線上優惠券通常是一組發放給使用者的程式碼,這個程式碼在某個時間段內有效。這個程式碼可以兌換一次或多次。

我們將為我們的商品建立一個優惠券系統。顧客在某個時間段內輸入我們的優惠券才有效。優惠券沒有使用次數限制,可以抵扣購物車的總金額。對於這個功能,我們需要建立一個模型,用於儲存優惠券碼,有效時間和折扣金額。

使用以下命令在myshop專案中新增一個新應用:

python manage.py startapp coupons

編輯myshopsettings.py檔案,把應用新增到INSTALLED_APPS中:

INSTALLED_APPS = (
    # ...
    'coupons',
)

現在新應用已經在我們的Django專案中激活了。

9.1.1 構建優惠券模型

讓我們從建立Coupon模型開始。編輯coupons應用的models.py檔案,並新增以下程式碼:

from django.db import models
from django.core.validators import
MinValueValidator from django.core.validators import MaxValueValidator class Coupon(models.Model): code = models.CharField(max_length=50, unique=True) valid_from = models.DateTimeField() valid_to = models.DateTimeField() discount = models.IntegerField( validators=[MinValueValidator(0
), MaxValueValidator(100)]) active = models.BooleanField() def __str__(self): return self.code

這是儲存優惠券的模型。Coupon模型包括以下欄位:

  • code:使用者必須輸入優惠券碼才能使用優惠券。
  • valid_from:優惠券開始生效的時間。
  • valid_to:優惠券過期的時間。
  • discount:折扣率(這是一個百分比,所以範圍是0到100)。我們使用驗證器限制這個欄位的最小值和最大值。
  • active:表示優惠券是否有效的布林值。

執行以下命令,生成coupon應用的初始資料庫遷移:

python manage.py makemigrations

輸出會包括以下行:

Migrations for 'coupons':
  coupons/migrations/0001_initial.py
    - Create model Coupon

然後執行以下命令,讓資料庫遷移生效:

python manage.py migrate

你會看到包括這一行的輸出:

Applying coupons.0001_initial... OK

現在遷移已經應用到資料庫中了。讓我們把Coupon模型新增到管理站點。編輯coupons應用的admin.py檔案,並新增以下程式碼:

from django.contrib import admin
from .models import Coupon

class CouponAdmin(admin.ModelAdmin):
    list_display = ['code', 'valid_from', 'valid_to', 'discount', 'active']
    list_filter = ['active', 'valid_from', 'valid_to']
    search_fields = ['code']
admin.site.register(Coupon, CouponAdmin)

現在Coupon模型已經在管理站點註冊。執行python manage.py runserver命令啟動開發伺服器,然後在瀏覽器中開啟http://127.0.0.1:8000/admin/coupons/coupon/add/。你會看下圖中的表單:

填寫表單,建立一個當天可用的優惠券,並勾選Active,然後點選Save按鈕。

9.1.2 在購物車中使用優惠券

我們可以儲存新的優惠券,並且可以查詢已存在的優惠券。現在我們需要讓顧客可以使用優惠券。考慮一下應該怎麼實現這個功能。使用優惠券的流程是這樣的:

  1. 使用者新增商品到購物車中。
  2. 使用者在購物車詳情頁面的表單中輸入優惠券碼。
  3. 當用戶輸入了優惠券碼,並提交了表單,我們用這個優惠券碼查詢當前有效地一張優惠券。我們必須檢查這張優惠券碼匹配使用者輸入的優惠券碼,active屬性為True,以及當前時間在valid_fromvalid_to之間。
  4. 如果找到了優惠券,我們把它儲存在使用者會話中,顯示包括折扣的購物車,然後更新總金額。
  5. 當用戶下單時,我們把優惠券儲存到指定的訂單中。

coupons應用目錄中建立forms.py檔案,並新增以下程式碼:

from django import forms

class CouponApplyForm(forms.Form):
    code = forms.CharField()

我們用這個表單讓使用者輸入優惠券碼。編輯coupons應用的views.py檔案,並新增以下程式碼:

from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import Coupon
from .forms import CouponApplyForm

@require_POST
def coupon_apply(request):
    now = timezone.now()
    form = CouponApplyForm(request.POST)
    if form.is_valid():
        code = form.cleaned_data['code']
        try:
            coupon = Coupon.objects.get(code__iexact=code, 
                valid_from__lte=now, 
                valid_to__gte=now, 
                active=True)
            request.session['coupon_id'] = coupon.id
        except Coupon.DoesNotExist:
            request.session['coupon_id'] = None
    return redirect('cart:cart_detail')

coupon_apply檢視驗證優惠券,並把它儲存在使用者會話中。我們用require_POST裝飾器裝飾這個檢視,只允許POST請求。在檢視中,我們執行以下任務:

  1. 我們用提交的資料例項化CouponApplyForm表單,並檢查表單是否有效。
  2. 如果表單有效,我們從表單的cleaned_data字典中獲得使用者輸入的優惠券碼。我們用給定的優惠券碼查詢Coupon物件。我們用iexact欄位執行大小寫不敏感的精確查詢。優惠券必須是有效的(active=True),並且在當前時間是有效地。我們用Django的timezone.now()函式獲得當前時區的時間,我們把它與valid_fromvalid_to欄位比較,對這兩個欄位分別執行lte(小於等於)和gte(大於等於)欄位查詢。
  3. 我們在使用者會話中儲存優惠券ID。
  4. 我們重定向使用者到cart_detail URL,顯示使用了優惠券的購物車。

我們需要一個coupon_apply檢視的URL模式。在coupons應用目錄中新增urls.py檔案,並新增以下程式碼:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^apply/$', views.coupon_apply, name='apply'),
]

然後編輯myshop專案的主urls.py檔案,新增coupons的URL模式:

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

記住,把這個模式放到shop.urls模式之前。

現在編輯cart應用的cart.py檔案,新增以下匯入:

from coupons.models import Coupon

Cart類的__init__()方法最後新增以下程式碼,從當前會話中初始化優惠券:

# store current applied coupon
self.coupon_id = self.session.get('coupon_id')

這行程式碼中,我們嘗試從當前會話中獲得coupon_id會話鍵,並把它儲存到Cart物件中。在Cart物件中新增以下方法:

@property
def coupon(self):
    if self.coupon_id:
        return Coupon.objects.get(id=self.coupon_id)
    return None

def get_discount(self):
    if self.coupon:
        return (self.coupon.discount / Decimal('100') * self.get_total_price())
    return Decimal('0')

def get_total_price_after_discount(self):
    return self.get_total_price() - self.get_discount()

這些方法分別是:

  • coupon():我們定義這個方法為property。如果cart中包括coupon_id屬性,則返回給定idCoupon物件。
  • get_discount():如果cart包括coupon,則查詢它的折扣率,並返回從購物車總金額中扣除的金額。
  • get_total_price_after_discount():減去get_discount()方法返回的金額後,購物車的總金額。

現在Cart類已經準備好處理當前會話中的優惠券,並且可以減去相應的折扣。

讓我們在購物車詳情檢視中引入優惠券系統。編輯cart應用的views.py,在檔案頂部新增以下匯入:

from coupons.forms import CouponApplyForm

接著編輯cart_detail檢視,並新增新表單:

def cart_detail(request):
    cart = Cart(request)
    for item in cart:
        item['update_quantity_form'] = CartAddProductForm(
            initial={'quantity': item['quantity'], 'update': True})
    coupon_apply_form = CouponApplyForm()
    return render(request, 'cart/detail.html', 
        {'cart': cart, 'coupon_apply_form': coupon_apply_form})

編輯cart應用的cart/detail.html目錄,找到以下程式碼:

<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">${{ cart.get_total_price }}</td>
</tr>

替換為下面的程式碼:

{% if cart.coupon %}
    <tr class="subtotal">
        <td>Subtotal</td>
        <td colspan="4"></td>
        <td class="num">${{ cart.get_total_price }}</td>
    </tr>
    <tr>
        <td>
            "{{ cart.coupon.code }}" coupon
            ({{ cart.coupon.discount }}% off)
        </td>
        <td colspan="4"></td>
        <td class="num neg">
            - ${{ cart.get_discount|floatformat:"2" }}
        </td>
    </tr>
{% endif %}
<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">
        ${{ cart.get_total_price_after_discount|floatformat:"2" }}
    </td>
</tr>

這段程式碼顯示一個可選的優惠券和它的折扣率。如果購物車包括一張優惠券,我們在第一行顯示購物車總金額為Subtotal。然後在第二行顯示購物車使用的當前優惠券。最後,我們呼叫cart物件的cart.get_total_price_after_discount()方法,顯示折扣之後的總金額。

在同一個檔案的</table>標籤之後新增以下程式碼:

<p>Apply a coupon:</p>
<form action="{% url "coupons:apply" %}" method="post">
    {{ coupon_apply_form }}
    <input type="submit" value="Apply">
    {% csrf_token %}
</form>

這會顯示輸入優惠券碼的表單,並在當前購物車中使用。

在瀏覽器中開啟http://127.0.0.1:8000/,新增一個商品到購物車中,然後使用表單中輸入的優惠券碼。你會看到購物車顯示優惠券折扣,如下圖所示:

讓我們把優惠券新增到購物流程的下一步。編輯orders應用的orders/order/create.html模板,找到以下程式碼:

<ul>
    {% for item in cart %}
        <li>
            {{ item.quantity }}x {{ item.product.name }}
            <span>${{ item.total_price }}</span>
        </li>
    {% endfor %}
</ul>

替換為以下程式碼:

<ul>
    {% for item in cart %}
        <li>
            {{ item.quantity }}x {{ item.product.name }}
            <span>${{ item.total_price }}</span>
        </li>
    {% endfor %}{% if cart.coupon %}
        <li>
            "{{ cart.coupon.code }}" ({{ cart.coupon.discount }}% off)
            <span>- ${{ cart.get_discount|floatformat:"2" }}</span>
        </li>
    {% endif %}
</ul>

如果有優惠券的話,訂單彙總已經使用了優惠券。現在找到這行程式碼:

<p>Total: ${{ cart.get_total_price }}</p>

替換為下面這一行:

<p>Total: ${{ cart.get_total_price_after_discount|floatformat:"2" }}</p>

這樣,總價格是使用優惠券之後的價格。

在瀏覽器中開啟中http://127.0.0.1:8000/orders/create/。你會看到訂單彙總包括了使用的優惠券:

現在使用者可以在購物車中使用優惠券了。但是當用戶結賬時,我們還需要在建立的訂單中儲存優惠券資訊。

9.1.3 在訂單中使用優惠券

我們將儲存每個訂單使用的優惠券。首先,我們需要修改Order模型來儲存關聯的Coupon物件(如果存在的話)。

編輯orders應用的models.py檔案,並新增以下匯入:

from decimal import Decimal
from django.core.validators import MinValueValidator
from django.core.validators import MaxValueValidator
from coupons.models import Coupon

然後在Order模型中新增以下欄位:

coupon = models.ForeignKey(Coupon, related_name='orders', null=True, blank=True)
discount = models.IntegerField(default=0, 
        validators=[MinValueValidator(0), MaxValueValidator(100)])

這些欄位允許我們儲存一個可選的訂單使用的優惠券和優惠券的折扣。折扣儲存在關聯的Coupon物件中,但我們在Order模型中包括它,以便優惠券被修改或刪除後還能儲存。

因為修改了Order模型,所以我們需要建立一個數據庫遷移。在命令列中執行以下命令:

python manage.py makemigrations

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

Migrations for 'orders':
  orders/migrations/0002_auto_20170515_0731.py
    - Add field coupon to order
    - Add field discount to order

執行以下命令同步資料庫遷移:

python manage.py migrate orders

你會看到新的資料庫遷移已經生效,現在Order模型的欄位修改已經同步到資料庫中。

回到models.py檔案,修改Order模型的get_total_cost()方法:

def get_total_cost(self):
    total_cost = sum(item.get_cost() for item in self.items.all())
    return total_cost - total_cost * self.discount / Decimal('100')

如果存在優惠券,Order模型的get_total_cost()方法會計算優惠券的折扣。

編輯orders應用的views.py檔案,修改其中的order_create檢視,當建立新訂單時,儲存關聯的優惠券。找到這一行程式碼:

order = form.save()

替換為以下程式碼:

order = form.save(commit=False)
if cart.coupon:
    order.coupon = cart.coupon
    order.discount = cart.coupon.discount
order.save()

在新程式碼中,我們用OrderCreateForm表單的save()方法建立了一個Order物件,並用commit=False避免儲存到資料庫中。如果購物車中包括優惠券,則儲存使用的關聯優惠券和折扣。然後我們把order物件儲存到資料庫中。

執行python manage.py runserver命令啟動開發伺服器,並使用./ngrok http 8000命令啟動Ngrok。

在瀏覽器中開啟Ngrok提供的URL,並使用你建立的優惠券完成一次購物。當你成功完成一次購物,你可以訪問http://127.0.0.1:8000/admin/orders/order/,檢查訂單是否包括優惠券和折扣,如下圖所示:

你還可以修改管理訂單詳情模板和訂單PDF賬單,用跟購物車同樣的方式顯示使用的優惠券。

接下來,我們要為專案新增國際化。

相關推薦

擴充套件商店

9 擴充套件你的商店 上一章中,你學習瞭如何在商店中整合支付閘道器。你完成了支付通知,學習瞭如何生成CSV和PDF檔案。在這一章中,你會在商店中新增優惠券系統。你將學習如何處理國際化和本地化,並構建一個推薦引擎。 本章會覆蓋以下知識點: 建立優惠券系統實

Spring入門篇——3 Spring Bean裝配

第3章 Spring Bean裝配(上) 介紹Bean的作用域、生命週期、Aware介面、自動裝配和Resource等內容。 3-1 Spring Bean裝配之Bean的配置項及作用域       3-2 Spring B

【SpringCloud Greenwich版本】鏈路追蹤Sleuth

一、SpringCloud版本 本文介紹的Springboot版本為2.1.1.RELEASE,SpringCloud版本為Greenwich.RC1,JDK版本為1.8,整合環境為IntelliJ IDEA 二、Spring Cloud Sleuth介紹 Spring Clou

6 HTTP首部

6.1 HTTP 報文首部 HTTP 請求報文 HTTP 響應報文 6.2 HTTP 首部欄位 6.2.1 HTTP 首部欄位傳遞重要資訊 6.2.2 HTTP 首部欄位結構 6.2.3 4 種 HTTP 首部欄位型別 6.2.4 HTTP/1.1 首部欄位一覽 6.2.5 非 HTTP/1

metasploit 滲透測試魔鬼訓練營 筆記 情報收集系統

第三章情報蒐集系統 (上) 3.1 外圍資訊收集 3.1.1 通過DNS 和IP地址挖掘目標的網路資訊。 1. whois域名註冊資訊查詢 通過whois,我們能夠獲取到testfire.net的一些基本資訊。管理員的email,傳真,

5 面向對象

6.2 port 3.1 父類 訪問控制 組合 5.2.1 3.4 ack 第5章 面向對象(上) 5.1 類和對象 5.1.1定義類 5.1.2 對象的生產和使用

讀書筆記:LearningPython五版 Tuples, Files, and Everything Else

重點: tuple的操作 collections模組中有其他的多功能型別 檔案操作 檔案本身是有buffer的,而且二進位制操作可以定址seek 文字檔案最好的讀取方式是直接迭代 檔案的文字處理會自

紫書-----動態規劃初步數字三角形

本文參考劉汝佳《演算法競賽入門經典》(第2版) 動態規劃的核心是狀態和狀態轉移方程 數字三角形 OpenJ_Bailian - 2760 【分析】 狀態:d(i,j)表示從點(i,j)

10 網絡安全4_網絡層安全IPSec

配置ip 分享 發送 eight 管理工具 win2003 soci 程序 插入 5. 網絡層安全IPSec 5.1 IPSec協議 (1)前面使用Outlook進行數字簽名和數字加密是應用層實現的安全。安全套接字實現的安全是在應用層和傳輸層之間插入了一層來實現數據通信安全

Learning Spark中文版----RDD編程2

翻譯 瓶頸 並集 ria multi guide 第六章 rabl 函數式 Common Transformations and Actions ??本章中,我們瀏覽了Spark中大多數常見的transformation(轉換)和action(動作)。在包含特定數據類型的R

Spring入門篇——4 Spring Bean裝配

第4章 Spring Bean裝配(下) 介紹Bean的註解實現,Autowired註解說明,基於java的容器註解說明,以及Spring對JSR支援的說明 4-1 Spring Bean裝配之Bean的定義及作用域的註解實現     &nbs

讀書筆記--《程式設計師的自我修養》4:靜態連結1

本章以 如何將a.c檔案與b.c檔案連結成一個可執行檔案 來探討如何進行靜態連結 其中a.c和b.c檔案如下: a.c檔案 extern int shared; int main() { int a = 100; swap(&a,&shared);

【計算機網路】 資料鏈路層1

一.資料鏈路層服務 1. 概述 (1)術語   ·主機和路由器:結點   ·連線相鄰結點的通訊通道:鏈路(有線、無線、區域網)   ·鏈路層資料分組:幀 (2)資料鏈路層主要任務:通過一條鏈路從一個結點向另一個物理鏈路直接相連的相鄰結點傳送資料報 2. 鏈路層服務 (1)組幀   ·封裝資料

【計算機網路】 資料鏈路層2

三.多路訪問控制(MAC)協議 1. 兩類鏈路 (1)點對點鏈路:撥號接入的PPP、乙太網交換機與主機間的點對點鏈路 (2)廣播鏈路(共享介質):早期的匯流排乙太網、HFC的上行鏈路、802.11無線區域網 2. 基本概念 (1)單一共享廣播通道 (2)兩個或兩個以上結點同時傳輸,則發生衝突;結點

C++筆記 課 函式過載分析---狄泰學院

如果在閱讀過程中發現有錯誤,望評論指正,希望大家一起學習,一起進步。 學習C++編譯環境:Linux 第九課 函式過載分析(下) 1.過載與指標 下面的函式指標將儲存哪個函式的地址?第一個 函式過載遇上函式指標 將過載函式名賦值給函式指標時 1.根據過載規則挑選與函式指標引

C++筆記 八課 函式過載分析---狄泰學院

如果在閱讀過程中發現有錯誤,望評論指正,希望大家一起學習,一起進步。 學習C++編譯環境:Linux 第八課 函式過載分析(上) 1.自然語言中的上下文 你知道下面詞彙中“洗”字的含義嗎? 結論:能和“洗”字搭配的詞彙有很多 “洗”字和不同的詞彙搭配有不同的含義 2.過

3 RFID基礎知識

1.電子標籤分為:有源電子標籤、無源電子標籤和半無源電子標籤。 2.電子標籤的儲存區域通常分為:保留區、EPC區、TID區、使用者區。     保留區:用於儲存標籤的滅活密碼以及訪問密碼,在沒有鎖定時可以進行讀寫操作,鎖定後不能讀取也不能修改。   

2 RFID基礎知識

1.RFID即射頻識別。(常稱為電子標籤)RFID射頻識別是一種非接觸式的自動識別技術,識別高速運動物體並可同時識別多個標籤,識別距離可達幾十米。 2.RFID的組成:一套完整的RFID系統必須由標籤、閱讀器和天線組成。 3.電子標籤的分類:標籤類、注塑類、卡片類。 4.閱讀器與電子標籤之間的射頻訊

Java程式優化待續

字串優化處理 String物件及其特點 String物件是java語言中重要的資料型別,但它並不是Java的基本資料型別。在C語言中,對字串的處理最通常的做法是使用char陣列,但這種方式的弊端是顯而易見的,陣列本身無法封裝字串操作所需的基本方法。而在Java語言中,String物件可以認為是char陣列

MATLAB七課:影象分析

目的: 一、介紹數字影象  介紹數字影象 讀取和展示數字影象 影象的四則運算 數字影象的分類: Binary:每個畫素只有黑色和白色 Grayscale:每個畫素是灰色,範圍是0到255 True color or RGB:每個畫素有