全文鏈接
第一章 創(chuàng)建一個blog應(yīng)用
第二章 使用高級特性來增強(qiáng)你的blog
第三章 擴(kuò)展你的blog應(yīng)用
第四章上 創(chuàng)建一個社交網(wǎng)站
第四章下 創(chuàng)建一個社交網(wǎng)站
第五章 在你的網(wǎng)站中分享內(nèi)容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴(kuò)展你的商店
第九章下 擴(kuò)展你的商店
第十章上 創(chuàng)建一個在線學(xué)習(xí)平臺
第十章下 創(chuàng)建一個在線學(xué)習(xí)平臺
第十一章 緩存內(nèi)容
第十二章 構(gòu)建一個API
書籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé
(譯者@ucag注:咳咳,第七章終于來了腊满。其實(shí)在一月份就翻譯完了但是后來我回老家了套么,就沒發(fā)出來。各位久等了~)
(譯者@夜夜月注:真羨慕有寒假和暑假的人- -愿你們的寒暑假作業(yè)越多越好碳蛋,粗略的校對了下胚泌,精校版本請大家繼續(xù)等待)
第七章
建立一個在線商店
在上一章,你創(chuàng)建了一個用戶跟蹤系統(tǒng)和建立了一個用戶活躍流肃弟。你也學(xué)習(xí)了 Django 信號是如何工作的玷室,并且把 Redis 融合進(jìn)了項(xiàng)目中來為圖像視圖計(jì)數(shù)。在這一章中笤受,你將學(xué)會如何建立一個最基本的在線商店穷缤。你將會為你的產(chǎn)品創(chuàng)建目錄和使用 Django sessions 實(shí)現(xiàn)一個購物車。你也將學(xué)習(xí)怎樣定制上下文處理器( context processors )以及用 Celery 來激活動態(tài)任務(wù)箩兽。
在這一章中津肛,你將學(xué)會:
- 創(chuàng)建一個產(chǎn)品目錄
- 使用 Django sessions 建立購物車
- 管理顧客的訂單
- 用 Celery 發(fā)送異步通知
創(chuàng)建一個在線商店項(xiàng)目(project)
我們將從新建一個在線商店項(xiàng)目開始。我們的用戶可以瀏覽產(chǎn)品目錄并且可以向購物車中添加商品汗贫。最后身坐,他們將清點(diǎn)購物車然后下單。這一章涵蓋了在線商店的以下幾個功能:
- 創(chuàng)建產(chǎn)品目錄模型(模型)芳绩,將它們添加到管理站點(diǎn)掀亥,創(chuàng)建基本的視圖(view)來展示目錄
- 使用 Django sessions 建立一個購物車系統(tǒng),使用戶可以在瀏覽網(wǎng)站的過程中保存他們選中的商品
- 創(chuàng)建下單表單和功能
- 發(fā)送一封異步的確認(rèn)郵件在用戶下單的時候
首先妥色,用以下命令來為你的新項(xiàng)目創(chuàng)建一個虛擬環(huán)境搪花,然后激活它:
mkdir env
virtualenv env/myshop
source env/myshop/bin/activate
用以下命令在你的虛擬環(huán)境中安裝 Django :
pip install django==1.8.6
創(chuàng)建一個叫做 myshop
的新項(xiàng)目,再創(chuàng)建一個叫做 shop
的應(yīng)用,命令如下:
django-admin startproject myshop
cd myshop
django-admin startapp shop
編輯你項(xiàng)目中的 settings.py
文件撮竿,像下面這樣將你的應(yīng)用添加到 INSTALLED_APPS
中:
INSTALLED_APPS = [
# ...
'shop',
]
現(xiàn)在你的應(yīng)用已經(jīng)在項(xiàng)目中激活吮便。接下來讓我們?yōu)楫a(chǎn)品目錄定義模型(models)。
創(chuàng)建產(chǎn)品目錄模型(models)
我們商店中的目錄將會由不同分類的產(chǎn)品組成幢踏。每一個產(chǎn)品會有一個名字髓需,一段可選的描述,一張可選的圖片房蝉,價格僚匆,以及庫存。 編輯位于shop
應(yīng)用中的models.py
文件搭幻,添加以下代碼:
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=200,
db_index=True)
slug = models.SlugField(max_length=200,
db_index=True,
unique=True)
class Meta:
ordering = ('name',)
verbose_name = 'category'
verbose_name_plural = 'categories'
def __str__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(Category,
related_name='products')
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True)
image = models.ImageField(upload_to='products/%Y/%m/%d',
blank=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField()
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('name',)
index_together = (('id', 'slug'),)
def __str__(self):
return self.name
這是我們的 Category
和 Product
模型(models)咧擂。Category
模型(models)由一個 name
字段和一個唯一的 slug
字段構(gòu)成。Product
模型(model):
-
category
: 這是一個鏈接向Category
的ForeignKey
檀蹋。這是個多對一(many-to-one)關(guān)系松申。一個產(chǎn)品可以屬于一個分類,一個分類也可包含多個產(chǎn)品俯逾。 -
name
: 這是產(chǎn)品的名字 -
slug
: 用來為這個產(chǎn)品建立 URL 的 slug -
image
: 可選的產(chǎn)品圖片 -
description
: 可選的產(chǎn)品描述 -
price
: 這是個DecimalField
(譯者@ucag注:十進(jìn)制字段)贸桶。這個字段使用 Python 的decimal.Decimal
元類來保存一個固定精度的十進(jìn)制數(shù)。max_digits
屬性可用于設(shè)定數(shù)字的最大值桌肴,decimal_places
屬性用于設(shè)置小數(shù)位數(shù)皇筛。 -
stock
: 這是個PositiveIntegerField
(譯者@ucag注:正整數(shù)字段) 來保存這個產(chǎn)品的庫存。 -
available
: 這個布爾值用于展示產(chǎn)品是否可供購買识脆。這使得我們可在目錄中使產(chǎn)品廢棄或生效设联。 -
created
: 當(dāng)對象被創(chuàng)建時這個字段被保存。 -
update
: 當(dāng)對象最后一次被更新時這個字段被保存灼捂。
對于 price
字段,我們使用 DecimalField
而不是 FloatField
來避免精度問題换团。
我們總是使用
DecimalField
來保存貨幣值悉稠。FloatField
在內(nèi)部使用 Python 的float
類型。反之艘包,DecimalField
使用的是 Python 中的Decimal
類型的猛,使用Decimal
類型可以避免精度問題。
在 Product
模型(model)中的 Meta
類中想虎,我們使用 index_together
元選項(xiàng)來指定 id
和 slug
字段的共同索引卦尊。我們定義這個索引,因?yàn)槲覀儨?zhǔn)備使用這兩個字段來查詢產(chǎn)品舌厨,兩個字段被索引在一起來提高使用雙字段查詢的效率岂却。
由于我們會在模型(models)中和圖片打交道,打開 shell ,用下面的命令安裝 Pillow :
pip isntall Pillow==2.9.0
現(xiàn)在躏哩,運(yùn)行下面的命令來為你的項(xiàng)目創(chuàng)建初始遷移:
python manage.py makemigrations
你將會看到以下輸出:
Migrations for 'shop':
0001_initial.py:
- Create model Category
- Create model Product
- Alter index_together for product (1 constraint(s))
用下面的命令來同步你的數(shù)據(jù)庫:
python mange.py migrate
你將會看到包含下面這一行的輸出:
Applying shop.0001_initial... OK
現(xiàn)在數(shù)據(jù)庫已經(jīng)和你的模型(models)同步了署浩。
注冊目錄模型(models)到管理站點(diǎn)
讓我們把模型(models)注冊到管理站點(diǎn),這樣我們就可以輕松管理產(chǎn)品和產(chǎn)品分類了扫尺。編輯 shop
應(yīng)用的 admin.py
文件筋栋,添加如下代碼:
from django.contrib import admin
from .models import Category, Product
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug']
prepopulated_fields = {'slug': ('name',)}
admin.site.register(Category, CategoryAdmin)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'price', 'stock',
'available', 'created', 'updated']
list_filter = ['available', 'created', 'updated']
list_editable = ['price', 'stock', 'available']
prepopulated_fields = {'slug': ('name',)}
admin.site.register(Product, ProductAdmin)
記住,我們使用 prepopulated_fields
屬性來指定那些要使用其他字段來自動賦值的字段正驻。正如你以前看到的那樣弊攘,這樣做可以很方便的生成 slugs 。我們在 ProductAdmin
類中使用 list_editable
屬性來設(shè)置可被編輯的字段姑曙,并且這些字段都在管理站點(diǎn)的列表頁被列出肴颊。這樣可以讓你一次編輯多行。任何在 list_editable
的字段也必須在 list_display
中渣磷,因?yàn)橹挥羞@樣被展示的字段才可以被編輯婿着。
現(xiàn)在,使用如下命令為你的站點(diǎn)創(chuàng)建一個超級用戶:
python manage.py createsuperuser
使用命令 python manage.py runserver
啟動開發(fā)服務(wù)器醋界。 訪問 http://127.0.0.1:8000/admin/shop/product/add ,登錄你剛才創(chuàng)建的超級用戶竟宋。在管理站點(diǎn)的交互界面添加一個新的品種和產(chǎn)品。 product 的更改頁面如下所示:
創(chuàng)建目錄視圖(views)
為了展示產(chǎn)品目錄形纺, 我們需要創(chuàng)建一個視圖(view)來列出所有產(chǎn)品或者是給出的篩選后的產(chǎn)品丘侠。編輯 shop
應(yīng)用中的 views.py
文件,添加如下代碼:
from django.shortcuts import render, get_object_or_404
from .models import Category, Product
def product_list(request, category_slug=None):
category = None
categories = Category.objects.all()
products = Product.objects.filter(available=True)
if category_slug:
category = get_object_or_404(Category, slug=category_slug)
products = products.filter(category=category)
return render(request,
'shop/product/list.html',
{'category': category,
'categories': categories,
'products': products})
我們只篩選 available=True
的查詢集來檢索可用的產(chǎn)品逐样。我們使用一個可選參數(shù) category_slug
通過所給產(chǎn)品類別來有選擇性的篩選產(chǎn)品蜗字。
我們也需要一個視圖來檢索和展示單一的產(chǎn)品。把下面的代碼添加進(jìn)去:
def product_detail(request, id, slug):
product = get_object_or_404(Product,
id=id,
slug=slug,
available=True)
return render(request,
'shop/product/detail.html',
{'product': product})
product_detail
視圖(view)接收 id
和 slug
參數(shù)來檢索 Product
實(shí)例脂新。我們可以只用 ID 就可以得到這個實(shí)例挪捕,因?yàn)樗且粋€獨(dú)一無二的屬性。盡管争便,我們在 URL 中引入了 slug 來建立搜索引擎友好(SEO-friendly)的 URL级零。
在創(chuàng)建了產(chǎn)品列表和明細(xì)視圖(views)之后,我們該為它們定義 URL 模式了滞乙。在 shop
應(yīng)用的路徑下創(chuàng)建一個新的文件奏纪,命名為 urls.py
,然后添加如下代碼:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.product_list, name='product_list'),
url(r'^(?P<category_slug>[-\w]+)/$',
views.product_list,
name='product_list_by_category'),
url(r'^(?P<id>\d+)/(?P<slug>[-\w]+)/$',
views.product_detail,
name='product_detail'),
]
這些是我們產(chǎn)品目錄的URL模式斩启。 我們?yōu)?product_list
視圖(view)定義了兩個不同的 URL 模式让蕾。 命名為product_list
的模式不帶參數(shù)調(diào)用 product_list
視圖(view)湖雹;命名為 product_list_by_category
的模式向視圖(view)函數(shù)傳遞一個 category_slug
參數(shù)芥玉,以便通過給定的產(chǎn)品種類來篩選產(chǎn)品锹淌。我們?yōu)?product_detail
視圖(view)添加的模式傳遞了 id
和 slug
參數(shù)來檢索特定的產(chǎn)品硬耍。
像這樣編輯 myshop
項(xiàng)目中的 urls.py
文件:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('shop.urls', namespace='shop')),
]
在項(xiàng)目的主要 URL 模式中,我們引入了 shop
應(yīng)用的 URL 模式朴摊,并指定了一個命名空間默垄,叫做 shop
。
現(xiàn)在甚纲,編輯 shop
應(yīng)用中的 models.py
文件口锭,導(dǎo)入 reverse()
函數(shù),然后給 Category
模型和 Product
模型添加 get_absolute_url()
方法:
from django.core.urlresolvers import reverse
# ...
class Category(models.Model):
# ...
def get_absolute_url(self):
return reverse('shop:product_list_by_category',
args=[self.slug])
class Product(models.Model):
# ...
def get_absolute_url(self):
return reverse('shop:product_detail',
args=[self.id, self.slug])
正如你已經(jīng)知道的那樣介杆, get_absolute_url()
是檢索一個對象的 URL 約定俗成的方法鹃操。這里,我們將使用我們剛剛在 urls.py
文件中定義的 URL 模式春哨。
創(chuàng)建目錄模板(templates)
現(xiàn)在荆隘,我們需要為產(chǎn)品列表和明細(xì)視圖創(chuàng)建模板(templates)。在 shop
應(yīng)用的路徑下創(chuàng)建如下路徑和文件:
templates/
shop/
base.html
product/
list.html
detail.html
我們需要定義一個基礎(chǔ)模板(template)赴背,然后在產(chǎn)品列表和明細(xì)模板(templates)中繼承它椰拒。 編輯 shop/base.html
模板(template),添加如下代碼:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}My shop{% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
<a href="/" class="logo">My shop</a>
</div>
<div id="subheader">
<div class="cart">
Your cart is empty.
</div>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
</body>
</html>
這就是我們將為我們的商店應(yīng)用使用的基礎(chǔ)模板(template)凰荚。為了引入模板使用的 CSS 和圖像燃观,你需要復(fù)制這一章示例代碼中的靜態(tài)文件,位于 shop
應(yīng)用中的 static/
路徑下便瑟。把它們復(fù)制到你的項(xiàng)目中相同的地方缆毁。
編輯 shop/product/list.html
模板(template),然后添加如下代碼:
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
{% if category %}{{ category.name }}{% else %}Products{% endif %}
{% endblock %}
{% block content %}
<div id="sidebar">
<h3>Categories</h3>
<ul>
<li {% if not category %}class="selected"{% endif %}>
<a href="{% url "shop:product_list" %}">All</a>
</li>
{% for c in categories %}
<li {% if category.slug == c.slug %}class="selected"{% endif %}>
<a href="{{ c.get_absolute_url }}">{{ c.name }}</a>
</li>
{% endfor %}
</ul>
</div>
<div id="main" class="product-list">
<h1>{% if category %}{{ category.name }}{% else %}Products{% endif %}</h1>
{% for product in products %}
<div class="item">
<a href="{{ product.get_absolute_url }}">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
</a>
<a href="{{ product.get_absolute_url }}">{{ product.name }}</a><br>
${{ product.price }}
</div>
{% endfor %}
</div>
{% endblock %}
這是產(chǎn)品列表模板(template)到涂。它繼承了 shop/base.html
并且使用了 categories
上下文變量來展示所有在側(cè)邊欄里的產(chǎn)品種類脊框,以及 products
上下文變量來展示當(dāng)前頁面的產(chǎn)品。相同的模板用于展示所有的可用的產(chǎn)品以及經(jīng)目錄分類篩選后的產(chǎn)品践啄。由于Product
模型的 image
字段可以為空浇雹,我們需要為沒有圖片的產(chǎn)品提供一個默認(rèn)圖像。這個圖片位于我們的靜態(tài)文件路徑下往核,相對路徑為 img/no_image.png
箫爷。
因?yàn)槲覀冊谑褂?ImageField
來保存產(chǎn)品圖片,我們需要開發(fā)服務(wù)器來服務(wù)上傳圖片文件聂儒。編輯 myshop
項(xiàng)目的 settings.py
文件,添加以下設(shè)置:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL
是基礎(chǔ) URL硫痰,它為用戶上傳的媒體文件提供服務(wù)衩婚。MEDIA_ROOT
是一個本地路徑,媒體文件就在這個路徑下效斑,并且是由我們動態(tài)的將 BASE_DIR
添加到它的前面而得到的非春。
為了讓 Django 給通過開發(fā)服務(wù)器上傳的媒體文件提供服務(wù),編輯myshop
中的 urls.py
文件,添加如下代碼:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
記住奇昙,我們僅僅在開發(fā)中像這樣提供靜態(tài)文件服務(wù)护侮。在生產(chǎn)環(huán)境下,你不應(yīng)該用 Django 來服務(wù)靜態(tài)文件储耐。
使用管理站點(diǎn)為你的商店添加幾個產(chǎn)品羊初,然后訪問 http://127.0.0.1:8000/ 。你可以看到如下的產(chǎn)品列表頁:
如果你用管理站點(diǎn)創(chuàng)建了幾個產(chǎn)品什湘,并且沒有上傳任何圖片的話长赞,就會顯示默認(rèn)的 no_img.png
。
讓我們編輯產(chǎn)品明細(xì)模板(template)闽撤。 編輯 shop/product/detail.html
模板(template)得哆,添加以下代碼:
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
{% if category %}{{ category.title }}{% else %}Products{% endif %}
{% endblock %}
{% block content %}
<div class="product-detail">
![]({% if product.image %}{{ product.image.url }}{% else %}{% static )
<h1>{{ product.name }}</h1>
<h2><a href="{{ product.category.get_absolute_url }}">{{ product.category }}</a></h2>
<p class="price">${{ product.price }}</p>
{{ product.description|linebreaks }}
</div>
{% endblock %}
我們可以調(diào)用相關(guān)聯(lián)的產(chǎn)品類別的 get_absolute_url()
方法來展示有效的屬于同一目錄的產(chǎn)品。現(xiàn)在哟旗,訪問 http://127.0.0.1:8000 贩据,點(diǎn)擊任意產(chǎn)品,查看產(chǎn)品明細(xì)頁面闸餐”チ粒看起來像這樣:
創(chuàng)建購物車
在創(chuàng)建了產(chǎn)品目錄之后,下一步我們要創(chuàng)建一個購物車系統(tǒng)绎巨,這個購物車系統(tǒng)可以讓用戶選中他們想買的商品近尚。購物車允許用戶在最終下單之前選中他們想要的物品并且可以在用戶瀏覽網(wǎng)站時暫時保存它們。購物車存在于會話中场勤,所以購物車中的物品會在用戶訪問期間被保存戈锻。
我們將會使用 Django 的會話框架(seesion framework)來保存購物車。在購物車最終被完成或用戶下單之前和媳,購物車將會保存在會話中格遭。我們需要為購物車和購物車?yán)锏纳唐穭?chuàng)建額外的 Django 模型(models)。
使用 Django 會話
Django 提供了一個會話框架留瞳,這個框架支持匿名會話和用戶會話拒迅。會話框架允許你為任意訪問對象保存任何數(shù)據(jù)。會話數(shù)據(jù)保存在服務(wù)端她倘,并且如果你使用基于 cookies 的會話引擎的話璧微, cookies 會包含 session ID 。會話中間件控制發(fā)送和接收 cookies 硬梁。默認(rèn)的會話引擎把會話保存在數(shù)據(jù)庫中前硫,但是正如你一會兒會看到的那樣,你也可以選擇不同的會話引擎荧止。為了使用會話屹电,你必須確認(rèn)你項(xiàng)目的 MIDDLEWARE_CLASSES
設(shè)置中包含了 django.contrib.sessions.middleware.SessionMiddleware
阶剑。這個中間件負(fù)責(zé)控制會話,并且是在你使用命令startproject
創(chuàng)建項(xiàng)目時被默認(rèn)添加的危号。
會話中間件使當(dāng)前會話在 request
對象中可用牧愁。你可以用 request.seesion
連接當(dāng)前會話,它的使用方式和 Python 的字典相似外莲。會話字典接收任何默認(rèn)的可被序列化為 JSON 的 Python 對象猪半。你可以在會話中像這樣設(shè)置變量:
request.session['foo'] = 'bar'
檢索會話中的鍵:
request.session.get('foo')
刪除會話中已有鍵:
del request.session['foo']
正如你所見,我們像使用 Python 字典一樣使用 request.session
苍狰。
當(dāng)用戶登錄時办龄,他們的匿名會話將會丟失,然后新的會話將會為認(rèn)證后的用戶創(chuàng)建淋昭。如果你在匿名會話中儲存了在登錄后依然需要被持有的數(shù)據(jù)俐填,你需要從舊的會話中復(fù)制數(shù)據(jù)到新的會話。
會話設(shè)置
你可以使用幾種設(shè)置來為你的項(xiàng)目配置會話系統(tǒng)翔忽。最重要的部分是 SESSION_ENGINE
.這個設(shè)置讓你可以配置會話將會在哪里被儲存英融。默認(rèn)地, Django 用 django.contrib.sessions
的 Sessions
模型把會話保存在數(shù)據(jù)庫中歇式。
Django 提供了以下幾個選擇來保存會話數(shù)據(jù):
- Database sessions(數(shù)據(jù)庫會話):會話數(shù)據(jù)將會被保存在數(shù)據(jù)庫中驶悟。這是默認(rèn)的會話引擎。
- File-based sessions(基于文件的會話):會話數(shù)據(jù)保存在文件系統(tǒng)中材失。
- Cached sessions(緩存會話):會話數(shù)據(jù)保存在緩存后端中痕鳍。你可以使用
CACHES
設(shè)置來指定一個緩存后端。在緩存系統(tǒng)中保存會話擁有最好的性能龙巨。 - Cached sessions(緩存會話):會話數(shù)據(jù)儲存于緩存后端笼呆。你可以使用
CACHES
設(shè)置來制定一個緩存后端。在緩存系統(tǒng)中儲存會話數(shù)據(jù)會有更好的性能表現(xiàn)旨别。 - Cached database sessions(緩存于數(shù)據(jù)庫中的會話):會話數(shù)據(jù)保存于可高速寫入的緩存和數(shù)據(jù)庫中诗赌。只會在緩存中沒有數(shù)據(jù)時才會從數(shù)據(jù)庫中讀取數(shù)據(jù)。
- Cookie-based sessions(基于 cookie 的會話):會話數(shù)據(jù)儲存于發(fā)送向?yàn)g覽器的 cookie 中秸弛。
為了得到更好的性能铭若,使用基于緩存的會話引擎( cach-based session engine)吧。 Django 支持 Mercached 递览,以及 Redis 的第三方緩存后端和其他的緩存系統(tǒng)叼屠。
你可以用其他的設(shè)置來定制你的會話。這里有一些和會話有關(guān)的重要設(shè)置:
-
SESSION_COOKIE_AGE
:cookie 會話保持的時間绞铃。以秒為單位环鲤。默認(rèn)值為 1209600 (2 周)。 -
SESSION_COOKIE_DOMAIN
:這是為會話 cookie 使用的域名憎兽。把它的值設(shè)置為.mydomain.com
來使跨域名 cookie 生效冷离。 -
SESSION_COOKIE_SECURE
:這是一個布爾值。它表示只有在連接為 HTTPS 時 cookie 才會被發(fā)送纯命。 -
SESSION_EXPIRE_AT_BROWSER_CLOSE
:這是一個布爾值西剥。它表示會話會在瀏覽器關(guān)閉時就過期。 -
SESSION_SAVE_EVERY_REQUEST
:這是一個布爾值亿汞。如果為True
瞭空,每一次請求的 session 都將會被儲存進(jìn)數(shù)據(jù)庫中。 session 的過期時間也會每次刷新疗我。
在這個網(wǎng)站你可以看到所有的 session 設(shè)置:https://docs.djangoproject.com/en/1.8/ref/settings/#sessions
會話過期
你可以通過 SESSION_EXPIRE_AT_BROWSER_CLOSE
選擇使用 browser-length 會話或者持久會話咆畏。默認(rèn)的設(shè)置是 False
,強(qiáng)制把會話的有效期設(shè)置為 SESSION_COOKIE_AGE
的值吴裤。如果你把 SESSION_EXPIRE_AT_BROWSER_CLOSE
的值設(shè)為 True
旧找,會話將會在用戶關(guān)閉瀏覽器時過期,且 SESSION_COOKIE_AGE
將不會對此有任何影響麦牺。
你可以使用 request.session
的 set_expiry()
方法來覆寫當(dāng)前會話的有效期钮蛛。
在會話中保存購物車
我們需要創(chuàng)建一個能序列化為 JSON 的簡單結(jié)構(gòu),這樣就可以把購物車中的東西儲存在會話中剖膳。購物車必須包含以下數(shù)據(jù)魏颓,每個物品的數(shù)據(jù)都要包含在其中:
-
Product
實(shí)例的id
- 選擇的產(chǎn)品數(shù)量
- 產(chǎn)品的總價格
因?yàn)楫a(chǎn)品的價格可能會變化,我們采取當(dāng)產(chǎn)品被添加進(jìn)購物車時同時保存產(chǎn)品價格和產(chǎn)品本身的辦法吱晒。這樣做甸饱,我們就可以保持用戶在把商品添加進(jìn)購物車時他們看到的商品價格不變了,即使產(chǎn)品的價格在之后有了變更仑濒。
現(xiàn)在叹话,你需要把購物車和會話關(guān)聯(lián)起來。購物車像下面這樣工作:
- 當(dāng)需要一個購物車時躏精,我們檢查顧客是否已經(jīng)設(shè)置了一個會話鍵( session key)渣刷。如果會話中沒有購物車,我們就創(chuàng)建一個新的購物車矗烛,然后把它保存在購物車的會話鍵中辅柴。
- 對于連續(xù)的請求,我們在會話鍵中執(zhí)行相同的檢查和獲取購物車內(nèi)物品的操作瞭吃。我們在會話中檢索購物車的物品和他們在數(shù)據(jù)庫中相關(guān)聯(lián)的
Product
對象碌嘀。
編輯你的項(xiàng)目中 settings.py
,把以下設(shè)置添加進(jìn)去:
CART_SESSION_ID = 'cart'
添加的這個鍵將會用于我們的會話中來儲存購物車歪架。因?yàn)?Django 的會話對于每個訪問者是獨(dú)立的(譯者@ucag注:原文為 per-visitor 股冗,沒能想出一個和它對應(yīng)的中文詞,根據(jù)上下文和蚪,我就把這個詞翻譯為了一個短語)止状,我們可以在所有的會話中使用相同的會話鍵烹棉。
讓我們創(chuàng)建一個應(yīng)用來管理我們的購物車。打開終端怯疤,然后創(chuàng)建一個新的應(yīng)用浆洗,在項(xiàng)目路徑下運(yùn)行以下命令:
python manage.py startapp cart
然后編輯你添加的項(xiàng)目中的 settings.py
,在 INSTALLED_APPS
中添加 cart
:
INSTALLED_APPS = (
# ...
'shop',
'cart',
)
在 cart
應(yīng)用路徑內(nèi)創(chuàng)建一個新的文件集峦,命名為 cart.py
伏社,把以下代碼添加進(jìn)去:
from decimal import Decimal
from django.conf import settings
from shop.models import Product
class Cart(object):
def __init__(self, request):
"""
Initialize the cart.
"""
self.session = request.session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
# save an empty cart in the session
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
這個 Cart
類可以讓我們管理購物車。我們需要把購物車與一個 request
對象一同初始化塔淤。我們使用 self.session = request.session
保存當(dāng)前會話以便使其對 Cart
類的其他方法可用摘昌。首先,我們使用 self.session.get(settings.CART_SESSION_ID)
嘗試從當(dāng)前會話中獲取購物車高蜂。如果當(dāng)前會話中沒有購物車聪黎,我們就在會話中設(shè)置一個空字典,這樣就可以在會話中設(shè)置一個空的購物車妨马。我們希望我們的購物車字典使用產(chǎn)品 ID 作為鍵挺举,以數(shù)量和價格為鍵值對的字典為值。這樣做烘跺,我們就能保證一個產(chǎn)品在購物車當(dāng)中不被重復(fù)添加湘纵;我們也能簡化獲取任意購物車物品數(shù)據(jù)的步驟。
讓我們寫一個方法來向購物車當(dāng)中添加產(chǎn)品或者更新產(chǎn)品的數(shù)量滤淳。把 save()
和 add()
方法添加進(jìn) Cart
類當(dāng)中:
def add(self, product, quantity=1, update_quantity=False):
"""
Add a product to the cart or update its quantity.
"""
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0,
'price': str(product.price)}
if update_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def save(self):
# update the session cart
self.session[settings.CART_SESSION_ID] = self.cart
# mark the session as "modified" to make sure it is saved
self.session.modified = True
add()
函數(shù)接受以下參數(shù):
-
product
:需要在購物車中更新或者向購物車添加的Product
對象 -
quantity
:一個產(chǎn)品數(shù)量的可選參數(shù)梧喷。默認(rèn)為 1 -
update_quantity
:這是一個布爾值,它表示數(shù)量是否需要按照給定的數(shù)量參數(shù)更新(True
)脖咐,不然新的數(shù)量必須要被加進(jìn)已存在的數(shù)量中(False
)
我們在購物車字典中把產(chǎn)品 id
作為鍵铺敌。我們把產(chǎn)品 id
轉(zhuǎn)換為字符串,因?yàn)?Django 使用 JSON 來序列化會話數(shù)據(jù)屁擅,而 JSON 又只接受支字符串的鍵名偿凭。產(chǎn)品 id
為鍵,一個有 quantity
和 price
的字典作為值派歌。產(chǎn)品的價格從十進(jìn)制數(shù)轉(zhuǎn)換為了字符串弯囊,這樣才能將它序列化。最后胶果,我們調(diào)用 save()
方法把購物車保存到會話中匾嘱。
save()
方法會把購物車中所有的改動都保存到會話中,然后用 session.modified = True
標(biāo)記改動了的會話早抠。這是為了告訴 Django 會話已經(jīng)被改動霎烙,需要將它保存起來。
我們也需要一個方法來從購物車當(dāng)中刪除購物車。把下面的方法添加進(jìn) Cart
類當(dāng)中:
def remove(self, product):
"""
Remove a product from the cart.
"""
product_id = str(product.id)
if product_id in self.cart:
del self.cart[product_id]
self.save()
remove
方法從購物車字典中刪除給定的產(chǎn)品悬垃,然后調(diào)用 save()
方法來更新會話中的購物車游昼。
我們將迭代購物車當(dāng)中的物品,然后獲取相應(yīng)的 Product
實(shí)例盗忱。為惡劣達(dá)到我們的目的酱床,你需要定義 __iter__()
方法。把下列代碼添加進(jìn) Cart
類中:
def __iter__(self):
"""
Iterate over the items in the cart and get the products
from the database.
"""
product_ids = self.cart.keys()
# get the product objects and add them to the cart
products = Product.objects.filter(id__in=product_ids)
for product in products:
self.cart[str(product.id)]['product'] = product
for item in self.cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item
在 __iter__()
方法中趟佃,我們檢索購物車中的 Product
實(shí)例來把他們添加進(jìn)購物車的物品中。之后昧捷,我們迭代所有的購物車物品闲昭,把他們的 price
轉(zhuǎn)換回十進(jìn)制數(shù),然后為每個添加一個 total_price
屬性∶一樱現(xiàn)在我們就可以很容易的在購物車當(dāng)中迭代物品了序矩。
我們還需要一個方法來返回購物車中物品的總數(shù)量。當(dāng) len()
方法在一個對象上執(zhí)行時跋破,Python 會調(diào)用對象的 __len__()
方法來檢索它的長度簸淀。我們將會定義一個定制的 __len__()
方法來返回保存在購物車中保存的所有物品數(shù)量。把下面這個 __len__()
方法添加進(jìn) Cart
類中:
def __len__(self):
"""
Count all items in the cart.
"""
return sum(item['quantity'] for item in self.cart.values())
我們返回所有購物車物品的數(shù)量毒返。
添加下列方法來計(jì)算購物車中物品的總價:
def get_total_price(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
最后租幕,添加一個方法來清空購物車會話:
def clear(self):
# remove cart from session
del self.session[settings.CART_SESSION_ID]
self.session.modified = True
我們的 Cart
類現(xiàn)在已經(jīng)準(zhǔn)備好管理購物車了。
創(chuàng)建購物車視圖
既然我們已經(jīng)創(chuàng)建了 Cart
類來管理購物車拧簸,我們就需要創(chuàng)建添加劲绪,更新,或者刪除物品的視圖了盆赤。我們需要創(chuàng)建以下視圖:
- 用于添加或者更新物品的視圖贾富,且能夠控制當(dāng)前的和更新的數(shù)量
- 從購物車中刪除物品的視圖
- 展示購物車物品和總數(shù)的視圖
添加物品
為了把物品添加進(jìn)購物車,我們需要一個允許用戶選擇數(shù)量的表單牺六。在 cart
應(yīng)用路徑下創(chuàng)建一個 forms.py
文件颤枪,然后添加以下代碼:
from django import forms
PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)]
class CartAddProductForm(forms.Form):
quantity = forms.TypedChoiceField(
choices=PRODUCT_QUANTITY_CHOICES,
coerce=int)
update = forms.BooleanField(required=False,
initial=False,
widget=forms.HiddenInput)
我們將要使用這個表單來向購物車添加產(chǎn)品。我們的 CartAddProductForm
類包含以下兩個字段:
-
quantity
:讓用戶可以在 1~20 之間選擇產(chǎn)品的數(shù)量淑际。我們使用了帶有coerce=int
的TypeChoiceField
字段來把輸入轉(zhuǎn)換為整數(shù) -
update
:讓你展示數(shù)量是否要被加進(jìn)已當(dāng)前的產(chǎn)品數(shù)量上(False
)畏纲,否則如果當(dāng)前數(shù)量必須被用給定的數(shù)量給更新(True
)。我們?yōu)檫@個字段使用了HiddenInput
控件庸追,因?yàn)槲覀儾幌氚阉故窘o用戶霍骄。
讓我們一個新的視圖來想購物車中添加物品。編輯 cart
應(yīng)用的 views.py
淡溯,添加以下代碼:
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from shop.models import Product
from .cart import Cart
from .forms import CartAddProductForm
@require_POST
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = CartAddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
cart.add(product=product,
quantity=cd['quantity'],
update_quantity=cd['update'])
return redirect('cart:cart_detail')
這個視圖是為了想購物車添加新的產(chǎn)品或者更新當(dāng)前產(chǎn)品的數(shù)量读整。我們使用 require_POST
裝飾器來只響應(yīng) POST 請求,因?yàn)檫@個視圖將會變更數(shù)據(jù)咱娶。這個視圖接收產(chǎn)品 ID 作為參數(shù)米间。我們用給定的 ID 來檢索 Product
實(shí)例强品,然后驗(yàn)證 CartAddProductForm
。如果表單是合法的屈糊,我們將在購物車中添加或者更新產(chǎn)品的榛。我們將創(chuàng)建 cart_detail
視圖。
我們還需要一個視圖來刪除購物車中的物品逻锐。將以下代碼添加進(jìn) cart
應(yīng)用的 views.py
中:
def cart_remove(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart:cart_detail')
cart_detail
視圖接收產(chǎn)品 ID 作為參數(shù)夫晌。我們根據(jù)給定的產(chǎn)品 ID 檢索相應(yīng)的 Product
實(shí)例,然后將它從購物車中刪除昧诱。然后晓淀,我們將用戶重定向到 cart_detail
URL。
最后盏档,我們需要一個視圖來展示購物車和其中的物品凶掰。講一下代碼添加進(jìn) veiws.py
中:
def cart_detail(request):
cart = Cart(request)
return render(request, 'cart/detail.html', {'cart': cart})
cart_detail
視圖獲取當(dāng)前購物車并展示它。
我們已經(jīng)創(chuàng)建了視圖來向購物車中添加物品蜈亩,或從購物車中更新數(shù)量懦窘,刪除物品,還有展示他們稚配。然我們?yōu)檫@些視圖添加 URL 模式畅涂。在 cart
應(yīng)用中創(chuàng)建一個新的文件,命名為 urls.py
药有。把下面這些 URL 模式添加進(jìn)去:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.cart_detail, name='cart_detail'),
url(r'^add/(?P<product_id>\d+)/$',
views.cart_add,
name='cart_add'),
url(r'^remove/(?P<product_id>\d+)/$',
views.cart_remove,
name='cart_remove'),
]
編輯 myshop
應(yīng)用的主 urls.py
文件毅戈,添加以下 URL 模式來引用 cart
URLs:
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^cart/', include('cart.urls', namespace='cart')),
url(r'^', include('shop.urls', namespace='shop')),
]
確保你在 shop.urls
之前引用它,因?yàn)樗惹罢吒佑邢拗菩浴?/p>
創(chuàng)建展示購物車的模板
cart_add
和 cart_remove
視圖沒有渲染任何模板愤惰,但是我們需要為 cart_detail
創(chuàng)建模板苇经。
在 cart
應(yīng)用路徑下創(chuàng)建以下文件結(jié)構(gòu):
templates/
cart/
detail.html
編輯 cart/detail.html
模板,然后添加以下代碼:
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
Your shopping cart
{% endblock %}
{% block content %}
<h1>Your shopping cart</h1>
<table class="cart">
<thead>
<tr>
<th>Image</th>
<th>Product</th>
<th>Quantity</th>
<th>Remove</th>
<th>Unit price</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for item in cart %}
{% with product=item.product %}
<tr>
<td>
<a href="{{ product.get_absolute_url }}">
![]({% if product.image %}{{ product.image.url }}{% else %}{% static )
</a>
</td>
<td>{{ product.name }}</td>
<td>{{ item.quantity }}</td>
<td><a href="{% url "cart:cart_remove" product.id %}">Remove</a></td>
<td class="num">${{ item.price }}</td>
<td class="num">${{ item.total_price }}</td>
</tr>
{% endwith %}
{% endfor %}
<tr class="total">
<td>Total</td>
<td colspan="4"></td>
<td class="num">${{ cart.get_total_price }}</td>
</tr>
</tbody>
</table>
<p class="text-right">
<a href="{% url "shop:product_list" %}" class="button light">Continue shopping</a>
<a href="#" class="button">Checkout</a>
</p>
{% endblock %}
這個模板被用于展示購物車的內(nèi)容宦言。它包含了一個保存于當(dāng)前購物車物品的表格扇单。我們允許用用戶使用發(fā)送到 cart_add
表單來改變選中的產(chǎn)品數(shù)量。我們通過提供一個 Remove 鏈接來允許用戶從購物車中刪除物品奠旺。
向購物車中添加物品
現(xiàn)在蜘澜,我們需要在產(chǎn)品詳情頁添加一個 Add to cart 按鈕。編輯 shop
應(yīng)用中的 views.py
响疚,然后把 CartAddProductForm
添加進(jìn) product_detail
視圖中:
from cart.forms import CartAddProductForm
def product_detail(request, id, slug):
product = get_object_or_404(Product, id=id,
slug=slug,
available=True)
cart_product_form = CartAddProductForm()
return render(request,
'shop/product/detail.html',
{'product': product,
'cart_product_form': cart_product_form})
編輯 shop
應(yīng)用的 shop/product/detail.html
模板鄙信,然后將如下表格按照這樣添加產(chǎn)品價格:
<p class="price">${{ product.price }}</p>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
<input type="submit" value="Add to cart">
</form>
確保用 python manage.py runserver
運(yùn)行開發(fā)服務(wù)器。現(xiàn)在忿晕,打開 http://127.0.0.1:8000/装诡,導(dǎo)航到產(chǎn)品詳情頁。現(xiàn)在它包含了一個表單來選擇數(shù)量在將產(chǎn)品添加進(jìn)購物車之前。這個頁面看起來像這樣:
選擇一個數(shù)量鸦采,然后點(diǎn)擊 Add to cart 按鈕宾巍。表單將會通過 POST 方法提交到 cart_add
視圖。視圖會把產(chǎn)品添加進(jìn)當(dāng)前會話的購物車當(dāng)中渔伯,包括當(dāng)前產(chǎn)品的價格和選定的數(shù)量顶霞。然后,用戶將會被重定向到購物車詳情頁锣吼,它長得像這個樣子:
在購物車中更新產(chǎn)品數(shù)量
當(dāng)用戶看到購物車時选浑,他們可能想要在下單之前改變產(chǎn)品數(shù)量。我們將會允許用戶在詳情頁改變產(chǎn)品數(shù)量吐限。
編輯 cart
應(yīng)用的 views.py
鲜侥,然后把 cart_detail
改成這個樣子:
def cart_detail(request):
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(
initial={'quantity': item['quantity'],
'update': True})
return render(request, 'cart/detail.html', {'cart': cart})
我們?yōu)槊恳粋€購物車中的物品創(chuàng)建了 CartAddProductForm
實(shí)例來允許用戶改變產(chǎn)品的數(shù)量。我們把表單和當(dāng)前物品數(shù)量一同初始化诸典,然后把 update
字段設(shè)為 True
,這樣當(dāng)我們提交表單到 cart_add
視圖時崎苗,當(dāng)前的數(shù)量就被新的數(shù)量替換了狐粱。
現(xiàn)在,編輯 cart
應(yīng)用的 cart/detail.html
模板胆数,然后找到這一行:
<td> {{ item.quantity }} </td>
把它替換為下面這樣的代碼:
<td>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ item.update_quantity_form.quantity }}
{{ item.update_quantity_form.update }}
<input type="submit" value="Update">
{% csrf_token %}
</form>
</td>
在你的瀏覽器中打開 http://127.0.0.1:8000/cart/ 肌蜻。你將會看到一個表單來編輯每個物品的數(shù)量,長得像下面這樣:
改變物品的數(shù)量必尼,然后點(diǎn)擊 Update 按鈕來測試新的功能蒋搜。
為當(dāng)前購物車創(chuàng)建上下文處理器
你可能已經(jīng)注意到我們在網(wǎng)站的頭部展示了 Your cart is empty 的信息。當(dāng)我們開始向購物車添加物品時判莉,我們將看到它已經(jīng)替換為了購物車中物品的總數(shù)和總花費(fèi)豆挽。由于這是個展示在整個頁面的東西,我們將創(chuàng)建一個上下文處理器來引用當(dāng)前請求中的購物車券盅,盡管我們的視圖函數(shù)已經(jīng)處理了它帮哈。
上下文處理器
上下文處理器是一個接收 request
對象為參數(shù)并返回一個已經(jīng)添加了請求上下文字典的 Python 函數(shù)。他們在你需要讓什么東西在所有模板都可用時遲早會派上用場锰镀。
一般的娘侍,當(dāng)你用 startproject
命令創(chuàng)建一個新的項(xiàng)目時,你的項(xiàng)目將會包含下面的模板上下文處理器泳炉,他們位于 TEMPLATES
設(shè)置中的 context_processors
內(nèi):
-
django.template.context_processors.debug
:在上下文中設(shè)置debug
布爾值和sql_queries
變量憾筏,來表示在 request 中執(zhí)行的 SQL 查詢語句表 -
django.template.context_processors.request
:在上下文中設(shè)置 request 變量 -
django.contrib.auth.context_processors.auth
:在請求中設(shè)置用戶變量 -
django.contrib.messages.context_processors.messages
:在包含所有使用消息框架發(fā)送的信息的上下文中設(shè)置一個messages
變量
Django 也使用 django.template.context_processors.csrf
來避免跨站請求攻擊。這個上下文處理器不在設(shè)置中花鹅,但是它總是可用的并且由安全原因不可被關(guān)閉氧腰。
你可以在這個網(wǎng)站看到所有的內(nèi)建上下文處理器:https://docs.djangoproject.com/en/1.8/ref/templates/api/#built-in-template-context-processors
把購物車添加進(jìn)請求上下文中
讓我們創(chuàng)建一個上下文處理器來將當(dāng)前購物車添加進(jìn)模板請求上下文中。這樣我們就可以在任意模板中獲取任意購物車了。
在 cart
應(yīng)用路徑里添加一個新文件容贝,并命名為 context_processors.py
自脯。上下文處理器可以位于你代碼中的任何地方,但是在這里創(chuàng)建他們將會使你的代碼變得組織有序斤富。將以下代碼添加進(jìn)去:
from .cart import Cart
def cart(request):
return {'cart': Cart(request)}
如你所見膏潮,一個上下文處理器是一個函數(shù),這個函數(shù)接收一個 request
對象作為參數(shù)满力,然后返回一個對象字典焕参,這些對象可用于所有使用 RequestContext
渲染的模板。在我們的上下文處理器中油额,我們使用 request
對象實(shí)例化了購物車叠纷,然后讓它作為一個名為 cart
的參數(shù)對模板可用。
編輯項(xiàng)目中的 settings.py
潦嘶,然后把 cart.context_processors.cart
添加進(jìn) TEMPLATE
內(nèi)的 context_processors
選項(xiàng)中涩嚣。改變后的設(shè)置如下:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'cart.context_processors.cart',
],
},
},
]
你的上下文處理器將會在使用 RequestContext
渲染 模板時執(zhí)行。 cart
變量將會被設(shè)置在模板上下文中掂僵。
上下文處理器會在所有的使用
RequestContext
的請求中執(zhí)行挨约。你可能想要創(chuàng)建一個定制的模板標(biāo)簽來代替一個上下文處理器谨朝,如果你想要鏈接到數(shù)據(jù)庫的話。
現(xiàn)在,編輯 shop
應(yīng)用的 shop/base.html
模板角溃,然后找到這一行:
<div class="cart">
Your cart is empty.
</div>
把它替換為下面的代碼:
<div class="cart">
{% with total_items=cart|length %}
{% if cart|length > 0 %}
Your cart:
<a href="{% url "cart:cart_detail" %}">
{{ total_items }} item{{ total_items|pluralize }},
${{ cart.get_total_price }}
</a>
{% else %}
Your cart is empty.
{% endif %}
{% endwith %}
</div>
使用 python manage.py runserver
重載你的服務(wù)器秒际。打開 http://127.0.0.1:8000/ ,添加一些產(chǎn)品到購物車?yán)镄痉簟T诰W(wǎng)站頭里脓匿,你可以看到當(dāng)前物品總數(shù)和總花費(fèi),就象這樣:
保存用戶訂單
當(dāng)購物車已經(jīng)結(jié)賬完畢時舱卡,你需要把訂單保存進(jìn)數(shù)據(jù)庫中辅肾。訂單將要保存客戶信息和他們購買的產(chǎn)品信息。
使用下面的命令創(chuàng)建一個新的應(yīng)用來管理用戶訂單:
python manage.py startapp orders
編輯項(xiàng)目中的 settings.py
灼狰,然后把 orders
添加進(jìn) INSTALLED_APPS
中:
INSTALLED_APPS = (
# ...
'orders',
)
現(xiàn)在你已經(jīng)激活了你的新應(yīng)用宛瞄。
創(chuàng)建訂單模型
你需要一個模型來保存訂單的詳細(xì)信息,第二個模型用來保存購買的物品交胚,包括物品的價格和數(shù)量份汗。編輯 orders
應(yīng)用的 models.py
,然后添加以下代碼:
from django.db import models
from shop.models import Product
class Order(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField()
address = models.CharField(max_length=250)
postal_code = models.CharField(max_length=20)
city = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
paid = models.BooleanField(default=False)
class Meta:
ordering = ('-created',)
def __str__(self):
return 'Order {}'.format(self.id)
def get_total_cost(self):
return sum(item.get_cost() for item in self.items.all())
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='items')
product = models.ForeignKey(Product,
related_name='order_items')
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveIntegerField(default=1)
def __str__(self):
return '{}'.format(self.id)
def get_cost(self):
return self.price * self.quantity
Order
模型包含幾個用戶信息的字段和一個 paid
布爾值字段蝴簇,這個字段默認(rèn)值為 False
杯活。待會兒,我們將使用這個字段來區(qū)分支付和未支付訂單熬词。我們也定義了一個 get_total_cost()
方法來得到訂單中購買物品的總花費(fèi)旁钧。
OrderItem
模型讓我們可以保存物品吸重,數(shù)量和每個物品的支付價格。我們引用 get_cost()
來返回物品的花費(fèi)歪今。
給 orders
應(yīng)用下運(yùn)行首次遷移:
python manage.py makemigrations
你將看到如下輸出:
Migrations for 'orders':
0001_initial.py:
- Create model Order
- Create model OrderItem
運(yùn)行以下命令來應(yīng)用新的遷移:
python manage.py migrate
你的訂單模型已經(jīng)同步到了數(shù)據(jù)庫中
在管理站點(diǎn)引用訂單模型
讓我們把訂單模型添加到管理站點(diǎn)嚎幸。編輯 orders
應(yīng)用的 admin.py
:
from django.contrib import admin
from .models import Order, OrderItem
class OrderItemInline(admin.TabularInline):
model = OrderItem
raw_id_fields = ['product']
class OrderAdmin(admin.ModelAdmin):
list_display = ['id', 'first_name', 'last_name', 'email',
'address', 'postal_code', 'city', 'paid',
'created', 'updated']
list_filter = ['paid', 'created', 'updated']
inlines = [OrderItemInline]
admin.site.register(Order, OrderAdmin)
我們在 OrderItem
使用 ModelInline
來把它引用為 OrderAdmin
類的內(nèi)聯(lián)元素。一個內(nèi)聯(lián)元素允許你在同一編輯頁引用模型寄猩,并且將這個模型作為父模型嫉晶。
用 python manage.py runserver
命令打開開發(fā)服務(wù)器,訪問 http://127.0.1:8000/admin/orders/order/add/ 田篇。你將會看到如下頁面:
創(chuàng)建顧客訂單
我們需要使用訂單模型來保存在用戶最終下單時在購物車中的物品替废,創(chuàng)建新的訂單的工作流程如下:
- 向用戶展示一個訂單表來讓他們填寫數(shù)據(jù)
- 我們用用戶輸入的數(shù)據(jù)創(chuàng)建一個新的
Order
實(shí)例,然后我們創(chuàng)建每個物品相關(guān)聯(lián)的OrderItem
實(shí)例泊柬。
- 我們用用戶輸入的數(shù)據(jù)創(chuàng)建一個新的
- 我們清空購物車椎镣,然后把用戶重定向到成功頁面
首先,我們需要一個表單來輸入訂單詳情兽赁。在orders
應(yīng)用路徑內(nèi)創(chuàng)建一個新的文件状答,命名為 forms.py
。添加以下代碼:
from django import forms
from .models import Order
class OrderCreateForm(forms.ModelForm):
class Meta:
model = Order
fields = ['first_name', 'last_name', 'email', 'address',
'postal_code', 'city']
這是我們將要用于創(chuàng)建新的 Order
對象的表單〉堆拢現(xiàn)在剪况,我們需要一個視圖來管理表格以及創(chuàng)建一個新的訂單。編輯orders
應(yīng)用的 views.py
蒲跨,添加以下代碼:
from django.shortcuts import render
from .models import OrderItem
from .forms import OrderCreateForm
from cart.cart import Cart
def order_create(request):
cart = Cart(request)
if request.method == 'POST':
form = OrderCreateForm(request.POST)
if form.is_valid():
order = form.save()
for item in cart:
OrderItem.objects.create(order=order,
product=item['product'],
price=item['price'],
quantity=item['quantity'])
# clear the cart
cart.clear()
return render(request,
'orders/order/created.html',
{'order': order})
else:
form = OrderCreateForm()
return render(request,
'orders/order/create.html',
{'cart': cart, 'form': form})
在 order_create
視圖中,我們將用 cart = Cart(request)
獲取到當(dāng)前會話中的購物車授翻』虮基于請求方法,我們將執(zhí)行以下幾個任務(wù):
-
GET 請求:實(shí)例化
OrderCreateForm
表單然后渲染模板orders/order/create.html
-
POST 請求:驗(yàn)證提交的數(shù)據(jù)堪唐。如果數(shù)據(jù)是合法的巡语,我們將使用
order = form.save()
來創(chuàng)建一個新的Order
實(shí)例。然后我們將會把它保存進(jìn)數(shù)據(jù)庫中淮菠,之后再把它保存進(jìn)order
變量里男公。在創(chuàng)建order
之后,我們將迭代無購車的物品然后為每個物品創(chuàng)建OrderItem
合陵。最后枢赔,我們清空購物車。
現(xiàn)在拥知,在 orders
應(yīng)用路徑下創(chuàng)建一個新的文件踏拜,把它命名為 urls.py
。添加以下代碼:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^create/$',
views.order_create,
name='order_create'),
]
這個是 order_create
視圖的 URL 模式低剔。編輯 myshop
的 urls.py
速梗,把下面的模式引用進(jìn)去肮塞。記得要把它放在 shop.urls
模式之前:
url(r'^orders/', include('orders.urls', namespace='orders')),
編輯 cart
應(yīng)用的 cart/detail.html
模板,找到下面這一行:
<a href="#" class="button">Checkout</a>
替換為:
<a href="{% url "orders:order_create" %}" class="button">
Checkout
</a>
用戶現(xiàn)在可以從購物車詳情頁導(dǎo)航到訂單表了姻锁。我們依然需要定義一個下單模板枕赵。在 orders
應(yīng)用路徑下創(chuàng)建如下文件結(jié)構(gòu):
templates/
orders/
order/
create.html
created.html
編輯 ordrs/order/create.html
模板,添加以下代碼:
{% extends "shop/base.html" %}
{% block title %}
Checkout
{% endblock %}
{% block content %}
<h1>Checkout</h1>
<div class="order-info">
<h3>Your order</h3>
<ul>
{% for item in cart %}
<li>
{{ item.quantity }}x {{ item.product.name }}
<span>${{ item.total_price }}</span>
</li>
{% endfor %}
</ul>
<p>Total: ${{ cart.get_total_price }}</p>
</div>
<form action="." method="post" class="order-form">
{{ form.as_p }}
<p><input type="submit" value="Place order"></p>
{% csrf_token %}
</form>
{% endblock %}
模板展示的購物車物品包括物品總量和下單表位隶。
編輯 orders/order/created.html
模板拷窜,然后添加以下代碼:
{% extends "shop/base.html" %}
{% block title %}
Thank you
{% endblock %}
{% block content %}
<h1>Thank you</h1>
<p>Your order has been successfully completed. Your order number is
<strong>{{ order.id }}</strong>.</p>
{% endblock %}
這是當(dāng)訂單成功創(chuàng)建時我們渲染的模板。打開開發(fā)服務(wù)器钓试,訪問 http://127.0.0.1:8000/ 装黑,在購物車當(dāng)中添加幾個產(chǎn)品進(jìn)去,然后結(jié)賬弓熏。
你就會看到下面這個頁面:
用合法的數(shù)據(jù)填寫表單恋谭,然后點(diǎn)擊 Place order 按鈕。訂單就會被創(chuàng)建挽鞠,然后你將會看到成功頁面:
使用 Celery 執(zhí)行異步操作
你在視圖執(zhí)行的每個操作都會影響響應(yīng)的時間疚颊。在很多場景下你可能想要盡快的給用戶返回響應(yīng),并且讓服務(wù)器異步地執(zhí)行一些操作信认。這特別和耗時進(jìn)程或從屬于失敗的進(jìn)程時需要重新操作時有著密不可分的關(guān)系材义。比如,一個視頻分享平臺允許用戶上傳視頻但是需要相當(dāng)長的時間來轉(zhuǎn)碼上傳的視頻嫁赏。這個網(wǎng)站可能會返回一個響應(yīng)給用戶其掂,告訴他們轉(zhuǎn)碼即將開始,然后開始異步轉(zhuǎn)碼潦蝇。另一個例子是給用戶發(fā)送郵件款熬。如果你的網(wǎng)站發(fā)送了通知郵件,SMTP 連接可能會失敗或者減慢響應(yīng)的速度攘乒。執(zhí)行異步操作來避免阻塞執(zhí)行就變得必要起來贤牛。
Celery 是一個分發(fā)隊(duì)列,它可以處理大量的信息则酝。它既可以執(zhí)行實(shí)時操作也支持任務(wù)調(diào)度殉簸。使用 Celery 不僅可以讓你很輕松的創(chuàng)建異步任務(wù)還可以讓這些任務(wù)盡快執(zhí)行,但是你需要在一個指定的時間調(diào)度他們執(zhí)行沽讹。
你可以在這個網(wǎng)站找到 Celery 的官方文檔:http://celery.readthedocs.org/en/latest/
安裝 Celery
讓我們安裝 Celery 然后把它整合進(jìn)你的項(xiàng)目中般卑。用下面的命令安裝 Celery:
pip install celery==3.1.18
Celery 需要一個消息代理(message broker)來管理請求。這個代理負(fù)責(zé)向 Celery 的 worker 發(fā)送消息妥泉,當(dāng)接收到消息時 worker 就會執(zhí)行任務(wù)椭微。讓我們安裝一個消息代理。
安裝 RabbitMQ
有幾個 Celery 的消息代理可供選擇盲链,包括鍵值對儲存蝇率,比如 Redis 或者是實(shí)時消息系統(tǒng)迟杂,比如 RabbitMQ。我們會用 RabbitMQ 配置 Celery 本慕,因?yàn)樗?Celery 推薦的 message worker排拷。
如果你用的是 Linux,你可以用下面這個命令安裝 RabbitMQ :
apt-get install rabbitmg
(譯者@夜夜月注:這是debian系linux的安裝方式)
如果你需要在 Mac OSX 或者 Windows 上安裝 RabbitMQ锅尘,你可以在這個網(wǎng)站找到獨(dú)立的支持版本:
https://www.rabbitmq.com/download.html
在安裝它之后监氢,使用下面的命令執(zhí)行 RabbitMQ:
rabbitmg-server
你將會在最后一行看到這樣的輸出:
Starting broker... completed with 10 plugins
RabbitMQ 正在運(yùn)行了,準(zhǔn)備接收消息藤违。
把 Celery 添加進(jìn)你的項(xiàng)目
你必須為 Celery 實(shí)例提供配置浪腐。在 myshop
的 settings.py
文件的旁邊創(chuàng)建一個新的文件,命名為 celery.py
顿乒。這個文件會包含你項(xiàng)目的 Celery 配置议街。添加以下代碼:
import os
from celery import Celery
from django.conf import settings
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myshop.settings')
app = Celery('myshop')
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
在這段代碼中,我們?yōu)?Celery 命令行程序設(shè)置了 DJANGO_SETTINGS_MODULE
變量璧榄。然后我們用 app=Celery('myshop')
創(chuàng)建了一個實(shí)例特漩。我們用 config_from_object()
方法來加載項(xiàng)目設(shè)置中任意的定制化配置。最后骨杂,我們告訴 Celery 自動查找我們列舉在 INSTALLED_APPS
設(shè)置中的異步應(yīng)用任務(wù)涂身。Celery 將在每個應(yīng)用路徑下查找 task.py
來加載定義在其中的異步任務(wù)。
你需要在你項(xiàng)目中的 __init__.py
文件中導(dǎo)入 celery
來確保在 Django 開始的時候就會被加載搓蚪。編輯 myshop/__init__.py
然后添加以下代碼:
# import celery
from .celery import app as celery_app
現(xiàn)在蛤售,你可以為你的項(xiàng)目開始編寫異步任務(wù)了。
CELERY_ALWAYS_EAGER
設(shè)置允許你在本地用異步的方式執(zhí)行任務(wù)而不是把他們發(fā)送向隊(duì)列中妒潭。這在不運(yùn)行 Celery 的情況下悍抑, 運(yùn)行單元測試或者是運(yùn)行在本地環(huán)境中的項(xiàng)目是很有用的。
向你的應(yīng)用中添加異步任務(wù)
我們將創(chuàng)建一個異步任務(wù)來發(fā)送消息郵件來讓用戶知道他們下單了杜耙。
約定俗成的一般用法是,在你的應(yīng)用路徑下的 tasks
模型里引入你應(yīng)用的異步任務(wù)拂盯。在 orders
應(yīng)用內(nèi)創(chuàng)建一個新的文件佑女,并命名為 task.py
。這是 Celery 尋找異步任務(wù)的地方谈竿。添加以下代碼:
from celery import task
from django.core.mail import send_mail
from .models import Order
@task
def order_created(order_id):
"""
Task to send an e-mail notification when an order is
successfully created.
"""
order = Order.objects.get(id=order_id)
subject = 'Order nr. {}'.format(order.id)
message = 'Dear {},\n\nYou have successfully placed an order.\
Your order id is {}.'.format(order.first_name,
order.id)
mail_sent = send_mail(subject,
message,
'admin@myshop.com',
[order.email])
return mail_sent
我們通過使用 task
裝飾器來定義我們的 order_created
任務(wù)团驱。如你所見,一個 Celery 任務(wù) 只是一個用 task
裝飾的 Python 函數(shù)空凸。我們的 task
函數(shù)接收一個 order_id
參數(shù)嚎花。通常推薦的做法是只傳遞 ID 給任務(wù)函數(shù)然后在任務(wù)被執(zhí)行的時候需找相關(guān)的對象,我們使用 Django 提供的 send_mail()
函數(shù)來發(fā)送一封提示郵件給用戶告訴他們下單了呀洲。如果你不想安裝郵件設(shè)置紊选,你可以通過一下 settings
.py` 中的設(shè)置告訴 Django 把郵件傳給控制臺:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
異步任務(wù)不僅僅適用于耗時進(jìn)程啼止,也適用于失敗進(jìn)程組中的進(jìn)程,這些進(jìn)程或許不會消耗太多時間兵罢,但是他們或許會鏈接失敗或者需要再次嘗試連接策略献烦。
現(xiàn)在我們要把任務(wù)添加到 order_create
視圖中。打開 orders
應(yīng)用的 views.py
文件卖词,按照如下導(dǎo)入任務(wù):
from .tasks import order_created
然后在清除購物車之后調(diào)用 order_created
異步任務(wù):
# clear the cart
cart.clear()
# launch asynchronous task
order_created.delay(order.id)
我們調(diào)用任務(wù)的 delay()
方法并異步地執(zhí)行它巩那。之后任務(wù)將會被添加進(jìn)隊(duì)列中,將會盡快被一個 worker 執(zhí)行此蜈。
打開另外一個 shell 即横,使用以下命令開啟 celery worker :
celery -A myshop worker -l info
Celery worker 現(xiàn)在已經(jīng)運(yùn)行,準(zhǔn)備好執(zhí)行任務(wù)了裆赵。確保 Django 的開發(fā)服務(wù)器也在運(yùn)行當(dāng)中东囚。訪問 http://127.0.0.1:8000/ ,在購物車中添加一些商品,然后完成一個訂單顾瞪。在 shell 中舔庶,你已經(jīng)打開過了 Celery worker 所以你可以看到以下的相似輸出:
[2015-09-14 19:43:47,526: INFO/MainProcess] Received task: orders.
tasks.order_created[933e383c-095e-4cbd-b909-70c07e6a2ddf]
[2015-09-14 19:43:50,851: INFO/MainProcess] Task orders.tasks.
order_created[933e383c-095e-4cbd-b909-70c07e6a2ddf] succeeded in
3.318835098994896s: 1
任務(wù)已經(jīng)被執(zhí)行了,你會接收到一封訂單通知郵件陈醒。
監(jiān)控 Celery
你或許想要監(jiān)控執(zhí)行了的異步任務(wù)惕橙。下面就是一個基于 web 的監(jiān)控 Celery 的工具。你可以用下面的命令安裝 Flower:
pip install flower
安裝之后钉跷,你可以在你的項(xiàng)目路徑下用以下命令啟動 Flower :
celery -A myshop flower
在你的瀏覽器中訪問 http://localhost:5555/dashboard 弥鹦,你可以看到激活了的 Celery worker 和正在執(zhí)行的異步任務(wù)統(tǒng)計(jì):
你可以在這個網(wǎng)站找到 Flower 的文檔:http://flower.readthedocs.org/en/latest/
總結(jié)
在這一章中,你創(chuàng)建了一個最基本的商店應(yīng)用爷辙。你創(chuàng)建了產(chǎn)品目錄以及使用會話的購物車彬坏。你實(shí)現(xiàn)了定制化的上下文處理器來使購物車在你的模板中可用,實(shí)現(xiàn)了一個下單表格膝晾。你也學(xué)到了如何用 Celery 執(zhí)行異步任務(wù)栓始。
在下一章中,你將會學(xué)習(xí)在你的商店中整合一個支付網(wǎng)關(guān)血当,添加管理站點(diǎn)的定制化動作幻赚,以 CSV 的形式導(dǎo)出數(shù)據(jù),以及動態(tài)的生成 PDF 文件臊旭。
(譯者@夜夜月注:接下來就是第八章了落恼,請大家等待,正在進(jìn)行中= =离熏,不確定啥時候放出來= =佳谦,我是懶人)