《Django By Example》第七章 中文 翻譯 (個人學(xué)習(xí)其骄,渣翻)

全文鏈接

第一章 創(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

這是我們的 CategoryProduct 模型(models)咧擂。Category 模型(models)由一個 name 字段和一個唯一的 slug 字段構(gòu)成。Product 模型(model):

  • category: 這是一個鏈接向 CategoryForeignKey 檀蹋。這是個多對一(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)來指定 idslug 字段的共同索引卦尊。我們定義這個索引,因?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 的更改頁面如下所示:

django-7-1

創(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)接收 idslug 參數(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)添加的模式傳遞了 idslug 參數(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)品列表頁:

django-7-2

如果你用管理站點(diǎn)創(chuàng)建了幾個產(chǎn)品什湘,并且沒有上傳任何圖片的話长赞,就會顯示默認(rèn)的 no_img.png

django-7-3

讓我們編輯產(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ì)頁面闸餐”チ粒看起來像這樣:

django-7-4

創(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.sessionsSessions 模型把會話保存在數(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.sessionset_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 為鍵,一個有 quantityprice 的字典作為值派歌。產(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=intTypeChoiceField 字段來把輸入轉(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_addcart_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)購物車之前。這個頁面看起來像這樣:

django-7-5

選擇一個數(shù)量鸦采,然后點(diǎn)擊 Add to cart 按鈕宾巍。表單將會通過 POST 方法提交到 cart_add 視圖。視圖會把產(chǎn)品添加進(jìn)當(dāng)前會話的購物車當(dāng)中渔伯,包括當(dāng)前產(chǎn)品的價格和選定的數(shù)量顶霞。然后,用戶將會被重定向到購物車詳情頁锣吼,它長得像這個樣子:

django-7-6

在購物車中更新產(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ù)量,長得像下面這樣:

django-7-7

改變物品的數(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),就象這樣:

django-7-8

保存用戶訂單

當(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/ 田篇。你將會看到如下頁面:

django-7-9

創(chuàng)建顧客訂單

我們需要使用訂單模型來保存在用戶最終下單時在購物車中的物品替废,創(chuàng)建新的訂單的工作流程如下:

    1. 向用戶展示一個訂單表來讓他們填寫數(shù)據(jù)
    1. 我們用用戶輸入的數(shù)據(jù)創(chuàng)建一個新的 Order 實(shí)例,然后我們創(chuàng)建每個物品相關(guān)聯(lián)的 OrderItem 實(shí)例泊柬。
    1. 我們清空購物車椎镣,然后把用戶重定向到成功頁面

首先,我們需要一個表單來輸入訂單詳情兽赁。在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 模式低剔。編輯 myshopurls.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é)賬弓熏。
你就會看到下面這個頁面:

django-7-10

用合法的數(shù)據(jù)填寫表單恋谭,然后點(diǎn)擊 Place order 按鈕。訂單就會被創(chuàng)建挽鞠,然后你將會看到成功頁面:

django-7-11

使用 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í)例提供配置浪腐。在 myshopsettings.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ì):

django-7-12

你可以在這個網(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)行中= =离熏,不確定啥時候放出來= =佳谦,我是懶人)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滋戳,隨后出現(xiàn)的幾起案子钻蔑,更是在濱河造成了極大的恐慌啥刻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矢棚,死亡現(xiàn)場離奇詭異郑什,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蒲肋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蘑拯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兜粘,你說我怎么就攤上這事申窘。” “怎么了孔轴?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵剃法,是天一觀的道長。 經(jīng)常有香客問我路鹰,道長贷洲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任晋柱,我火速辦了婚禮优构,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雁竞。我一直安慰自己钦椭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布碑诉。 她就那樣靜靜地躺著彪腔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪进栽。 梳的紋絲不亂的頭發(fā)上德挣,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音快毛,去河邊找鬼盲厌。 笑死,一個胖子當(dāng)著我的面吹牛祸泪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播建芙,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼没隘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了禁荸?” 一聲冷哼從身側(cè)響起右蒲,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤阀湿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瑰妄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陷嘴,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年间坐,在試婚紗的時候發(fā)現(xiàn)自己被綠了灾挨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡竹宋,死狀恐怖劳澄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜈七,我是刑警寧澤秒拔,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站飒硅,受9級特大地震影響砂缩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜三娩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一庵芭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尽棕,春花似錦喳挑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至回官,卻和暖如春曹宴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歉提。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工笛坦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苔巨。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓版扩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侄泽。 傳聞我的和親對象是個殘疾皇子礁芦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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