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
編輯myshop
的settings.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)惠券的流程是這樣的:
- 用戶添加商品到購物車中。
- 用戶在購物車詳情頁面的表單中輸入優(yōu)惠券碼。
- 當用戶輸入了優(yōu)惠券碼,并提交了表單,我們用這個優(yōu)惠券碼查找當前有效地一張優(yōu)惠券。我們必須檢查這張優(yōu)惠券碼匹配用戶輸入的優(yōu)惠券碼,
active
屬性為True
,以及當前時間在valid_from
和valid_to
之間。 - 如果找到了優(yōu)惠券,我們把它保存在用戶會話中,顯示包括折扣的購物車,然后更新總金額。
- 當用戶下單時,我們把優(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ù):
- 我們用提交的數(shù)據(jù)實例化
CouponApplyForm
表單,并檢查表單是否有效。 - 如果表單有效,我們從表單的
cleaned_data
字典中獲得用戶輸入的優(yōu)惠券碼。我們用給定的優(yōu)惠券碼查詢Coupon
對象。我們用iexact
字段執(zhí)行大小寫不敏感的精確查詢。優(yōu)惠券必須是有效的(active=True
),并且在當前時間是有效地。我們用Django的timezone.now()
函數(shù)獲得當前時區(qū)的時間,我們把它與valid_from
和valid_to
字段比較,對這兩個字段分別執(zhí)行lte
(小于等于)和gte
(大于等于)字段查詢。 - 我們在用戶會話中存儲優(yōu)惠券ID。
- 我們重定向用戶到
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
屬性,則返回給定id
的Coupon
對象。 -
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)惠券。
接下來,我們要為項目添加國際化。