第九章 擴展你的商店(上)

9 擴展你的商店

上一章中,你學(xué)習(xí)了如何在商店中集成支付網(wǎng)關(guān)。你完成了支付通知,學(xué)習(xí)了如何生成CSV和PDF文件。在這一章中,你會在商店中添加優(yōu)惠券系統(tǒng)。你將學(xué)習(xí)如何處理國際化和本地化,并構(gòu)建一個推薦引擎。

本章會覆蓋以下知識點:

  • 創(chuàng)建優(yōu)惠券系統(tǒng)實現(xiàn)折扣
  • 在項目中添加國際化
  • 使用Rosetta管理翻譯
  • 使用django-parler翻譯模型
  • 構(gòu)建一個商品推薦系統(tǒng)

9.1 創(chuàng)建優(yōu)惠券系統(tǒng)

很多在線商店會給顧客發(fā)放優(yōu)惠券,在購買商品時可以兌換折扣。在線優(yōu)惠券通常是一組發(fā)放給用戶的代碼,這個代碼在某個時間段內(nèi)有效。這個代碼可以兌換一次或多次。

我們將為我們的商品創(chuàng)建一個優(yōu)惠券系統(tǒng)。顧客在某個時間段內(nèi)輸入我們的優(yōu)惠券才有效。優(yōu)惠券沒有使用次數(shù)限制,可以抵扣購物車的總金額。對于這個功能,我們需要創(chuàng)建一個模型,用于存儲優(yōu)惠券碼,有效時間和折扣金額。

使用以下命令在myshop項目中添加一個新應(yīng)用:

python manage.py startapp coupons

編輯myshopsettings.py文件,把應(yīng)用添加到INSTALLED_APPS中:

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

現(xiàn)在新應(yīng)用已經(jīng)在我們的Django項目中激活了。

9.1.1 構(gòu)建優(yōu)惠券模型

讓我們從創(chuàng)建Coupon模型開始。編輯coupons應(yīng)用的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

這是存儲優(yōu)惠券的模型。Coupon模型包括以下字段:

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

執(zhí)行以下命令,生成coupon應(yīng)用的初始數(shù)據(jù)庫遷移:

python manage.py makemigrations

輸出會包括以下行:

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

然后執(zhí)行以下命令,讓數(shù)據(jù)庫遷移生效:

python manage.py migrate

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

Applying coupons.0001_initial... OK

現(xiàn)在遷移已經(jīng)應(yīng)用到數(shù)據(jù)庫中了。讓我們把Coupon模型添加到管理站點。編輯coupons應(yīng)用的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)

現(xiàn)在Coupon模型已經(jīng)在管理站點注冊。執(zhí)行python manage.py runserver命令啟動開發(fā)服務(wù)器,然后在瀏覽器中打開http://127.0.0.1:8000/admin/coupons/coupon/add/。你會看下圖中的表單:

填寫表單,創(chuàng)建一個當天可用的優(yōu)惠券,并勾選Active,然后點擊Save按鈕。

9.1.2 在購物車中使用優(yōu)惠券

我們可以存儲新的優(yōu)惠券,并且可以查詢已存在的優(yōu)惠券。現(xiàn)在我們需要讓顧客可以使用優(yōu)惠券。考慮一下應(yīng)該怎么實現(xiàn)這個功能。使用優(yōu)惠券的流程是這樣的:

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

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

from django import forms

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

我們用這個表單讓用戶輸入優(yōu)惠券碼。編輯coupons應(yīng)用的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視圖驗證優(yōu)惠券,并把它存儲在用戶會話中。我們用require_POST裝飾器裝飾這個視圖,只允許POST請求。在視圖中,我們執(zhí)行以下任務(wù):

  1. 我們用提交的數(shù)據(jù)實例化CouponApplyForm表單,并檢查表單是否有效。
  2. 如果表單有效,我們從表單的cleaned_data字典中獲得用戶輸入的優(yōu)惠券碼。我們用給定的優(yōu)惠券碼查詢Coupon對象。我們用iexact字段執(zhí)行大小寫不敏感的精確查詢。優(yōu)惠券必須是有效的(active=True),并且在當前時間是有效地。我們用Django的timezone.now()函數(shù)獲得當前時區(qū)的時間,我們把它與valid_fromvalid_to字段比較,對這兩個字段分別執(zhí)行lte(小于等于)和gte(大于等于)字段查詢。
  3. 我們在用戶會話中存儲優(yōu)惠券ID。
  4. 我們重定向用戶到cart_detail URL,顯示使用了優(yōu)惠券的購物車。

我們需要一個coupon_apply視圖的URL模式。在coupons應(yīng)用目錄中添加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模式之前。

現(xiàn)在編輯cart應(yīng)用的cart.py文件,添加以下導(dǎo)入:

from coupons.models import Coupon

Cart類的__init__()方法最后添加以下代碼,從當前會話中初始化優(yōu)惠券:

# 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()方法返回的金額后,購物車的總金額。

現(xiàn)在Cart類已經(jīng)準備好處理當前會話中的優(yōu)惠券,并且可以減去相應(yīng)的折扣。

讓我們在購物車詳情視圖中引入優(yōu)惠券系統(tǒng)。編輯cart應(yīng)用的views.py,在文件頂部添加以下導(dǎo)入:

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應(yīng)用的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>

這段代碼顯示一個可選的優(yōu)惠券和它的折扣率。如果購物車包括一張優(yōu)惠券,我們在第一行顯示購物車總金額為Subtotal。然后在第二行顯示購物車使用的當前優(yōu)惠券。最后,我們調(diào)用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>

這會顯示輸入優(yōu)惠券碼的表單,并在當前購物車中使用。

在瀏覽器中打開http://127.0.0.1:8000/,添加一個商品到購物車中,然后使用表單中輸入的優(yōu)惠券碼。你會看到購物車顯示優(yōu)惠券折扣,如下圖所示:

讓我們把優(yōu)惠券添加到購物流程的下一步。編輯orders應(yīng)用的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>

如果有優(yōu)惠券的話,訂單匯總已經(jīng)使用了優(yōu)惠券。現(xiàn)在找到這行代碼:

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

替換為下面這一行:

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

這樣,總價格是使用優(yōu)惠券之后的價格。

在瀏覽器中打開中http://127.0.0.1:8000/orders/create/。你會看到訂單匯總包括了使用的優(yōu)惠券:

現(xiàn)在用戶可以在購物車中使用優(yōu)惠券了。但是當用戶結(jié)賬時,我們還需要在創(chuàng)建的訂單中存儲優(yōu)惠券信息。

9.1.3 在訂單中使用優(yōu)惠券

我們將存儲每個訂單使用的優(yōu)惠券。首先,我們需要修改Order模型來存儲關(guān)聯(lián)的Coupon對象(如果存在的話)。

編輯orders應(yīng)用的models.py文件,并添加以下導(dǎo)入:

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)])

這些字段允許我們存儲一個可選的訂單使用的優(yōu)惠券和優(yōu)惠券的折扣。折扣存儲在關(guān)聯(lián)的Coupon對象中,但我們在Order模型中包括它,以便優(yōu)惠券被修改或刪除后還能保存。

因為修改了Order模型,所以我們需要創(chuàng)建一個數(shù)據(jù)庫遷移。在命令行中執(zhí)行以下命令:

python manage.py makemigrations

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

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

執(zhí)行以下命令同步數(shù)據(jù)庫遷移:

python manage.py migrate orders

你會看到新的數(shù)據(jù)庫遷移已經(jīng)生效,現(xiàn)在Order模型的字段修改已經(jīng)同步到數(shù)據(jù)庫中。

回到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')

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

編輯orders應(yīng)用的views.py文件,修改其中的order_create視圖,當創(chuàng)建新訂單時,保存關(guān)聯(lián)的優(yōu)惠券。找到這一行代碼:

order = form.save()

替換為以下代碼:

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

在新代碼中,我們用OrderCreateForm表單的save()方法創(chuàng)建了一個Order對象,并用commit=False避免保存到數(shù)據(jù)庫中。如果購物車中包括優(yōu)惠券,則存儲使用的關(guān)聯(lián)優(yōu)惠券和折扣。然后我們把order對象存儲到數(shù)據(jù)庫中。

執(zhí)行python manage.py runserver命令啟動開發(fā)服務(wù)器,并使用./ngrok http 8000命令啟動Ngrok。

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

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

接下來,我們要為項目添加國際化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內(nèi)容