全文鏈接
第一章 創(chuàng)建一個(gè)blog應(yīng)用
第二章 使用高級(jí)特性來增強(qiáng)你的blog
第三章 擴(kuò)展你的blog應(yīng)用
第四章上 創(chuàng)建一個(gè)社交網(wǎng)站
第四章下 創(chuàng)建一個(gè)社交網(wǎng)站
第五章 在你的網(wǎng)站中分享內(nèi)容
第六章 跟蹤用戶動(dòng)作
第七章 建立一個(gè)在線商店
第八章 管理付款和訂單
第九章上 擴(kuò)展你的商店
第九章下 擴(kuò)展你的商店
第十章上 創(chuàng)建一個(gè)在線學(xué)習(xí)平臺(tái)
第十章下 創(chuàng)建一個(gè)在線學(xué)習(xí)平臺(tái)
第十一章 緩存內(nèi)容
第十二章 構(gòu)建一個(gè)API
書籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé
(審校@夜夜月:本章分上下兩部分,這里是下半部。)
(譯者@ucag 注:哈哈哈,第九章終于來啦。這是在線商店的最后一章,下一章將會(huì)開始一個(gè)新的項(xiàng)目。所以這一章的內(nèi)容會(huì)偏難,最難的是推薦引擎的編寫,其中的算法可能還需要各位好好的推敲,如果看了中文的看不懂,大家可以去看看英文原版的書以及相關(guān)的文檔。最近我也在研究機(jī)器學(xué)習(xí),有興趣的大家可以一起交流哈~)
(審校@夜夜月:大家好,我是來打醬油的~,粗校,主要更正了一些錯(cuò)字和格式,精校正進(jìn)行到四章樣子。)
第九章(下)
使用 Rosetta 翻譯交互界面
Rosetta 是一個(gè)讓你可以編輯翻譯的第三方應(yīng)用,它有類似 Django 管理站點(diǎn)的交互界面。Rosetta 讓編輯 .po 文件和更新它變得很簡(jiǎn)單,讓我們把它添加進(jìn)項(xiàng)目中:
pip install django-rosetta==0.7.6
然后,把 rosetts 添加進(jìn)項(xiàng)目中的setting.py文件中的INSTALLED_APPS 設(shè)置中。
你需要把 Rosetta 的 URL 添加進(jìn)主 URL 配置中。編輯項(xiàng)目中的主 urls.py 文件,把下列 URL 模式添加進(jìn)去:
url(r'^rosetta/', include('rosetta.urls')),
確保你把它放在了 shop.urls 模式之前以避免錯(cuò)誤的匹配。
訪問 http://127.0.0.1:8000/admin/ ,然后使用超級(jí)管理員賬戶登錄。然后導(dǎo)航到 http://127.0.0.1:8000/rosetta/ 。你可以看到當(dāng)前語言列表:

在 Filter 模塊,點(diǎn)擊 All 來展示所有可用的信息文件,包括屬于 orders 應(yīng)用的信息文件。點(diǎn)擊Spanish模塊下的 Myshop 鏈接來編輯西班牙語翻譯。你可以看到翻譯字符串的列表:

你可以在 Spanish 行下輸入翻譯。Occurences 行展示了代碼中翻譯字符串被找到的文件和行數(shù)、
包含占位符的翻譯看起來像這樣:

Rosetta 使用了不同的背景色來展示占位符。當(dāng)你翻譯內(nèi)容時(shí),確保你沒有翻譯占位符。比如,用下面這個(gè)字符串舉例:
%(total_items)s item%(total_items_plural)s, $%(total_price)s
它翻譯為西班牙語之后長得像這樣:
%(total_items)s producto%(total_items_plural)s, $%(total_price)s
你可以看看本章項(xiàng)目中使用相同西班牙語翻譯的文件源代碼。
當(dāng)你完成編輯翻譯后,點(diǎn)擊Save and translate next block 按鈕來保存翻譯到 .po 文件中。Rosetta 在你保存翻譯時(shí)編輯信息文件,所以并不需要你運(yùn)行 compilemessages 命令。盡管,Rosetta 需要 locale 路徑的寫入權(quán)限來寫入信息文件。確保路徑有有效權(quán)限。
如果你想讓其他用戶編輯翻譯,訪問:http://127.0.0.1:8000/admin/auth/group/add/,然后創(chuàng)建一個(gè)名為 translations 的新組。當(dāng)編輯一個(gè)用戶時(shí),在Permissions模塊下,把 translations 組添加進(jìn)ChosenGroups中。Rosetta 只對(duì)超級(jí)用戶和 translations 中的用戶是可用的。
你可以在這個(gè)網(wǎng)站閱讀 Rosetta 的文檔:http://django-rosetta.readthedocs.org/en/latest/ 。
當(dāng)你在生產(chǎn)環(huán)境中添加新的翻譯時(shí),如果你的 Django 運(yùn)行在一個(gè)真實(shí)服務(wù)器上,你必須在運(yùn)行
compilemessages或保存翻譯之后重啟你的服務(wù)器來讓 Rosetta 的更改生效。
惰性翻譯
你或許已經(jīng)注意到了 Rosetta 有 Fuzzy 這一行。這不是 Rosetta 的特性,它是由 gettext 提供的。如果翻譯的 flag 是激活的,那么它就不會(huì)被包含進(jìn)編譯后的信息文件中。flag 用于需要翻譯器修訂的翻譯字符串。當(dāng) .po 文件在更新新的翻譯字符串時(shí),一些翻譯字符串可能被自動(dòng)標(biāo)記為 fuzzy. 當(dāng) gettext 找到一些變動(dòng)不大的 msgid 時(shí)就會(huì)發(fā)生這樣的情況,gettext 就會(huì)把它認(rèn)為的舊的翻譯和匹配在一起然后會(huì)在回把它標(biāo)記為 fuzzy 以用于回查。翻譯器之后會(huì)回查模糊翻譯,會(huì)移除 fuzzy 標(biāo)簽然后再次編譯信息文件。
國際化的 URL 模式
Django 提供了用于國際化的 URLs。它包含兩種主要用于國際化的 URLs:
- URL 模式中的語言前綴:把語言的前綴添加到 URLs 當(dāng)中,以便在不同的基本 URL 下提供每一種語言的版本。
- 翻譯后的 URL 模式:標(biāo)記要翻譯的 URL 模式,這樣同樣的 URL 就可以服務(wù)于不同的語言。
翻譯 URLs 的原因是這樣就可以優(yōu)化你的站點(diǎn),方便搜索引擎搜索。通過添加語言前綴,你就可以為每一種語言提供索引,而不是所有語言用一種索引。并且, 把 URLs 為不同語言,你就可以提供給搜索引擎更好的搜索序列。
把語言前綴添加到 URLs 模式中
Django 允許你可以把語言前綴添加到 URLs 模式中。舉個(gè)例子,網(wǎng)站的英文版可以服務(wù)于 /en/ 起始路徑之下,西班牙語版服務(wù)于 /es/ 起始路徑之下。
為了在你的 URLs 模式中使用不同語言,你必須確保 settings.py 中的 MIDDLEWARE_CLASSES 設(shè)置中有 django.middleware.localMiddlewar 。Django 將會(huì)使用它來辨別當(dāng)前請(qǐng)求中的語言。
讓我們把語言前綴添加到 URLs 模式中。編輯 myshop 項(xiàng)目的 urls.py ,添加以下庫:
from django.conf.urls.i18n import i18n_patterns
然后添加 i18n_patterns() :
urlpatterns = i18n_patterns(
url(r'^admin/', include(admin.site.urls)),
url(r'^cart/', include('cart.urls', namespace='cart')),
url(r'^orders/', include('orders.urls', namespace='orders')),
url(r'^payment/', include('payment.urls',
namespace='payment')),
url(r'^paypal/', include('paypal.standard.ipn.urls')),
url(r'^coupons/', include('coupons.urls',
namespace='coupons')),
url(r'^rosetta/', include('rosetta.urls')),
url(r'^', include('shop.urls', namespace='shop')),
)
你可以把 i18n_patterns() 和 patterns() URLs 模式結(jié)合起來,這樣一些模式就會(huì)包含語言前綴另一些就不會(huì)包含。盡管,最好還是使用翻譯后的 URLs 來避免 URL 匹配一個(gè)未翻譯的 URL 模式的可能。
打開開發(fā)服務(wù)器,訪問 http://127.0.0.1:8000/ ,因?yàn)槟阍?Django 中使用 LocaleMiddleware 來執(zhí)行 How Django determines the current language 中描述的步驟來決定當(dāng)前語言,然后它就會(huì)把你重定向到包含相同語言前綴的 URL??纯礊g覽器中 URL ,它看起來像這樣:http://127.0.0.1:8000/en/.當(dāng)前語言將會(huì)被設(shè)置在瀏覽器的 Accept-Language 頭中,設(shè)為英語或者西班牙語或者是 LANGUAGE_OCDE(English) 中的默認(rèn)設(shè)置。
翻譯 URL 模式
Django 支持 URL 模式中有翻譯了的字符串。你可以為每一種語言使用不同的 URL 模式。你可以使用 ugettet_lazy() 函數(shù)標(biāo)記 URL 模式中需要翻譯的字符串。
編輯 myshop 項(xiàng)目中的 urls.py 文件,把翻譯字符串添加進(jìn) cart,orders,payment,coupons 的 URLs 模式的正則表達(dá)式中:
from django.utils.translation import gettext_lazy as _
urlpatterns = i18n_patterns(
url(r'^admin/', include(admin.site.urls)),
url(_(r'^cart/'), include('cart.urls', namespace='cart')),
url(_(r'^orders/'), include('orders.urls',
namespace='orders')),
url(_(r'^payment/'), include('payment.urls',
namespace='payment')),
url(r'^paypal/', include('paypal.standard.ipn.urls')),
url(_(r'^coupons/'), include('coupons.urls',
namespace='coupons')),
url(r'^rosetta/', include('rosetta.urls')),
url(r'^', include('shop.urls', namespace='shop')),
)
編輯 orders 應(yīng)用的 urls.py 文件,標(biāo)記需要翻譯的 URLs 模式:
from django.conf.urls import url
from .import views
from django.utils.translation import gettext_lazy as _
urlpatterns = [
url(_(r'^create/$'), views.order_create, name='order_create'),
# ...
]
編輯 payment 應(yīng)用的 urls.py 文件,把代碼改成這樣:
from django.conf.urls import url
from . import views
from django.utils.translation import gettext_lazy as _
urlpatterns = [
url(_(r'^process/$'), views.payment_process, name='process'),
url(_(r'^done/$'), views.payment_done, name='done'),
url(_(r'^canceled/$'),
views.payment_canceled,
name='canceled'),
]
我們不需要翻譯 shop 應(yīng)用的 URL 模式,因?yàn)樗鼈兪怯米兞縿?chuàng)建的,而且也沒有包含其他的任何字符。
打開 shell ,運(yùn)行下面的命令來把新的翻譯更新到信息文件:
django-admin makemessages --all
確保開發(fā)服務(wù)器正在運(yùn)行中,訪問:http://127.0.0.1:8000/en/rosetta/ ,點(diǎn)擊Spanish下的Myshop 鏈接。你可以使用顯示過濾器(display filter)來查看沒有被翻譯的字符串。確保你的 URL 翻譯有正則表達(dá)式中的特殊字符。翻譯 URLs 是一個(gè)精細(xì)活兒;如果你替換了正則表達(dá)式,你可能會(huì)破壞 URL。
允許用戶切換語言
因?yàn)槲覀冋谔峁┒嗾Z種服務(wù),我們應(yīng)當(dāng)讓用戶可以選擇站點(diǎn)的語言。我們將會(huì)為我們的站點(diǎn)添加語言選擇器。語言選擇器由可用的語言列表組成,我們使用鏈接來展示這些語言選項(xiàng):
編輯 shop/base.html 模板(template),找到下面這一行:
<div id="header">
<a href="/" class="logo">{% trans "My shop" %}</a>
</div>
把他們換成以下幾行::
<div id="header">
<a href="/" class="logo">{% trans "My shop" %}</a>
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
<div class="languages">
<p>{% trans "Language" %}:</p>
<ul class="languages">
{% for language in languages %}
<li>
<a href="/{{ language.code }}/" {% if language.code ==
LANGUAGE_CODE %} class="selected"{% endif %}>
{{ language.name_local }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
我們是這樣創(chuàng)建我們的語言選擇器的:
1. 首先使用 `{% load i18n %}` 加載國際化
2. 使用 `{% get_current_language %}` 標(biāo)簽來檢索當(dāng)前語言
3. 使用 `{% get_available_languages %}` 模板(template)標(biāo)簽來過去 `LANGUAGES` 中定義語言
4. 使用 `{% get_language_info_list %}` 來提供簡(jiǎn)易的語言屬性連接入口
5. 我們創(chuàng)建了一個(gè) HTML 列表來展示所有的可用語言列表然后我們?yōu)楫?dāng)前激活語言添加了 `selected` 屬性
我們使用基于項(xiàng)目設(shè)置中語言變量的 i18n提供的模板(template)標(biāo)簽?,F(xiàn)在訪問:http://127.0.0.1:8000/` ,你應(yīng)該能在站點(diǎn)頂部的右邊看到語言選擇器:

用戶現(xiàn)在可以輕易的切換他們的語言了。
使用 django-parler 翻譯模型(models)
Django 沒有提供開箱即用的模型(models)翻譯的解決辦法。你必須要自己實(shí)現(xiàn)你自己的解決辦法來管理不同語言中的內(nèi)容或者使用第三方模塊來管理模型(model)翻譯。有幾種第三方應(yīng)用允許你翻譯模型(model)字段。每一種手采用了不同的方法保存和連接翻譯。其中一種應(yīng)用是 django-parler 。這個(gè)模塊提供了非常有效的辦法來翻譯模型(models)并且它和 Django 的管理站點(diǎn)整合的非常好。
django-parler 為每一個(gè)模型(model)生成包含翻譯的分離數(shù)據(jù)庫表。這個(gè)表包含了所有的翻譯的字段和源對(duì)象的外鍵翻譯。它也包含了一個(gè)語言字段,一位內(nèi)每一行都會(huì)保存一個(gè)語言的內(nèi)容。
**安裝 django-parler **
使用 pip 安裝 django-parler :
pip install django-parler==1.5.1
編輯項(xiàng)目中 settings.py ,把 parler 添加到 INSTALLED_APPS 中,同樣也把下面的配置添加到配置文件中:
PARLER_LANGUAGES = {
None: (
{'code': 'en',},
{'code': 'es',},
),
'default': {
'fallback': 'en',
'hide_untranslated': False,
}
}
這個(gè)設(shè)置為 django-parler 定義了可用語言 en 和 es 。我們指定默認(rèn)語言為 en ,然后我們指定 django-parler 應(yīng)該隱藏未翻譯的內(nèi)容。
翻譯模型(model)字段
讓我們?yōu)槲覀兊漠a(chǎn)品目錄添加翻譯。 django-parler 提供了 TranslatedModel 模型(model)類和 TranslatedFields 閉包(wrapper)來翻譯模型(model)字段。編輯 shop 應(yīng)用路徑下的 models.py 文件,添加以下導(dǎo)入:
from parler.models import TranslatableModel, TranslatedFields
然后更改 Category 模型(model)來使 name 和 slug 字段可以被翻譯。
我們依然保留了每行未翻譯的字段:
class Category(TranslatableModel):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200,
db_index=True,
unique=True)
translations = TranslatedFields(
name = models.CharField(max_length=200,
db_index=True),
slug = models.SlugField(max_length=200,
db_index=True,
unique=True)
)
Category 模型(model)繼承了 TranslatableModel 而不是 models.Model。并且 name 和 slug 字段都被引入到了 TranslatedFields 閉包(wrapper)中。
編輯 Product 模型(model),為 name,slug ,description 添加翻譯,同樣我們也保留了每行未翻譯的字段:
class Product(TranslatableModel):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True)
description = models.TextField(blank=True)
translations = TranslatedFields(
name = models.CharField(max_length=200, db_index=True),
slug = models.SlugField(max_length=200, db_index=True),
description = models.TextField(blank=True)
)
category = models.ForeignKey(Category,
related_name='products')
image = models.ImageField(upload_to='products/%Y/%m/%d',
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)
django-parler 為每個(gè)可翻譯模型(model)生成了一個(gè)模型(model)。在下面的圖片中,你可以看到 Product 字段和生成的 ProductTranslation 模型(model):

django-parler 生成的 ProductTranslation 模型(model)有 name,slug,description 可翻譯字段,一個(gè) language_code 字段,和主要的 Product 對(duì)象的 ForeignKey 字段。Product 和 ProductTranslation 之間有一個(gè)一對(duì)多關(guān)系。一個(gè) ProductTranslation 對(duì)象會(huì)為每個(gè)可用語言生成 Product 對(duì)象。
因?yàn)?Django 為翻譯都使用了相互獨(dú)立的表,所以有一些 Django 的特性我們是用不了。使用翻譯后的字段來默認(rèn)排序是不可能的。你可以在查詢集(QuerySets)中使用翻譯字段來過濾,但是你不可以在 ordering Meta 選項(xiàng)中引入可翻譯字段。編輯 shop 應(yīng)用的 models.py ,然后注釋掉 Category Meta 類的 ordering 屬性:
class Meta:
# ordering = ('name',)
verbose_name = 'category'
verbose_name_plural = 'categories'
我們也必須注釋掉 Product Meta 類的 index_together 屬性,因?yàn)楫?dāng)前的 django-parler 的版本不支持對(duì)它的驗(yàn)證。編輯 Product Meta :
class Meta:
ordering = ('-created',)
# index_together = (('id', 'slug'),)
你可以在這個(gè)網(wǎng)站閱讀更多 django-parler 兼容的有關(guān)內(nèi)容:
http://django-parler.readthedocs.org/en/latest/compatibility.html 。
創(chuàng)建一次定制的遷移
當(dāng)你創(chuàng)建新的翻譯模型(models)時(shí),你需要執(zhí)行 makemessages 來生成模型(models)的遷移,然后把變更同步到數(shù)據(jù)庫中。盡管當(dāng)使已存在字段可翻譯化時(shí),你或許有想要在數(shù)據(jù)庫中保留的數(shù)據(jù)。我們將遷移當(dāng)前數(shù)據(jù)到新的翻譯模型(models)中。因此,我們添加了翻譯字段但是有意保存了原始字段。
翻譯添加到當(dāng)前字段的步驟如下:
1. 在保留原始字段的情況下,創(chuàng)建新翻譯模型(model)的遷移
2. 創(chuàng)建定制化遷移,從當(dāng)前已有的字段中拷貝一份數(shù)據(jù)到翻譯模型(models)中
3. 從源模型(models)中刪除字段
運(yùn)行下面的命令來為我們添加到 Category 和 Product 模型(model)中的翻譯字段創(chuàng)建遷移:
python manage.py makemigrations shop --name "add_translation_model"
你可以看到如下輸出:
Migrations for 'shop':
0002_add_translation_model.py:
- Create model CategoryTranslation
- Create model ProductTranslation
- Change Meta options on category
- Alter index_together for product (0 constraint(s))
- Add field master to producttranslation
- Add field master to categorytranslation
- Alter unique_together for producttranslation (1 constraint(s))
- Alter unique_together for categorytranslation (1 constraint(s))
遷移已有數(shù)據(jù)
現(xiàn)在我們需要?jiǎng)?chuàng)建定制遷移來把已有數(shù)據(jù)拷貝到新的翻譯模型(model)中。使用這個(gè)命令創(chuàng)建一個(gè)空的遷移:
python manage.py makemigrations --empty shop --name "migrate_translatable_fields"
你將會(huì)看到如下輸出:
Migrations for 'shop':
0003_migrate_translatable_fields.py
編輯 shop/migrations/0003_migrate_translatable_fields.py ,然后添加下面的代碼:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.apps import apps
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
translatable_models = {
'Category': ['name', 'slug'],
'Product': ['name', 'slug', 'description'],
}
def forwards_func(apps, schema_editor):
for model, fields in translatable_models.items():
Model = apps.get_model('shop', model)
ModelTranslation = apps.get_model('shop',
'{}Translation'.format(model))
for obj in Model.objects.all():
translation_fields = {field: getattr(obj, field) for field in fields}
translation = ModelTranslation.objects.create(
master_id=obj.pk,
language_code=settings.LANGUAGE_CODE,
**translation_fields)
def backwards_func(apps, schema_editor):
for model, fields in translatable_models.items():
Model = apps.get_model('shop', model)
ModelTranslation = apps.get_model('shop',
'{}Translation'.format(model))
for obj in Model.objects.all():
translation = _get_translation(obj, ModelTranslation)
for field in fields:
setattr(obj, field, getattr(translation, field))
obj.save()
def _get_translation(obj, MyModelTranslation):
translations = MyModelTranslation.objects.filter(master_id=obj.pk)
try:
# Try default translation
return translations.get(language_code=settings.LANGUAGE_CODE)
except ObjectDoesNotExist:
# Hope there is a single translation
return translations.get()
class Migration(migrations.Migration):
dependencies = [
('shop', '0002_add_translation_model'),
]
operations = [
migrations.RunPython(forwards_func, backwards_func),
]
這段遷移包括了用于執(zhí)行應(yīng)用和反轉(zhuǎn)遷移的 forwards_func() 和 backwards_func() 。
遷移工作流程如下:
1. 我們?cè)?`translatable_models` 字典中定義了模型(model)和它們的可翻譯字段
2. 為了應(yīng)用遷移,我們使用 `app.get_model()` 來迭代包含翻譯的模型(model)來得到這個(gè)模型(model)和其可翻譯的模型(model)
3. 我們?cè)跀?shù)據(jù)庫中迭代所有的當(dāng)前對(duì)象,然后為定義在項(xiàng)目設(shè)置中的 `LANGUAGE_CODE` 創(chuàng)建一個(gè)翻譯對(duì)象。我們引入了 `ForeignKey` 到源對(duì)像和一份從源字段中可翻譯字段的拷貝。
backwards 函數(shù)執(zhí)行的是反轉(zhuǎn)操作,它檢索默認(rèn)的翻譯對(duì)象,然后把可翻譯字段的值拷貝到新的模型(model)中。
最后,我們需要?jiǎng)h除我們不再需要的源字段。編輯 shop 應(yīng)用的 models.py ,然后刪除 Category 模型(model)的 name 和 slug 字段:
class Category(TranslatableModel):
translations = TranslatedFields(
name = models.CharField(max_length=200, db_index=True),
slug = models.SlugField(max_length=200,
db_index=True,
unique=True)
)
刪除 Product 模型(model)的 name 和 slug 字段:
class Product(TranslatableModel):
translations = TranslatedFields(
name = models.CharField(max_length=200, db_index=True),
slug = models.SlugField(max_length=200, db_index=True),
description = models.TextField(blank=True)
)
category = models.ForeignKey(Category,
related_name='products')
image = models.ImageField(upload_to='products/%Y/%m/%d',
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)
現(xiàn)在我們必須要?jiǎng)?chuàng)建最后一次遷移來應(yīng)用其中的變更。如果我們嘗試 manage.py 工具,我們將會(huì)得到一個(gè)錯(cuò)誤,因?yàn)槲覀冞€沒有讓管理站點(diǎn)適應(yīng)翻譯模型(models)。讓我們先來修整一下管理站點(diǎn)。
在管理站點(diǎn)中整合翻譯
Django-parler 很好和 Django 的管理站點(diǎn)相融合。它用 TranslatableAdmin 重寫了 Django 的 ModelAdmin 類 來管理翻譯。
編輯 shop 應(yīng)用的 admin.py 文件,添加下面的庫:
from parler.admin import TranslatableAdmin
把 CategoryAdmin 和 ProductAdmin 的繼承類改為 TranslatableAdmin 取代原來的 ModelAdmin。 Django-parler 現(xiàn)在還不支持 prepopulated_fields 屬性,但是它支持 get_prepopulated_fields() 方法來提供相同的功能。讓我們據(jù)此做一些改變。 admin.py 文件看起來像這樣:
from django.contrib import admin
from .models import Category, Product
from parler.admin import TranslatableAdmin
class CategoryAdmin(TranslatableAdmin):
list_display = ['name', 'slug']
def get_prepopulated_fields(self, request, obj=None):
return {'slug': ('name',)}
admin.site.register(Category, CategoryAdmin)
class ProductAdmin(TranslatableAdmin):
list_display = ['name', 'slug', 'category', 'price', 'stock',
'available', 'created', 'updated']
list_filter = ['available', 'created', 'updated', 'category']
list_editable = ['price', 'stock', 'available']
def get_prepopulated_fields(self, request, obj=None):
return {'slug': ('name',)}
admin.site.register(Product, ProductAdmin)
我們已經(jīng)使管理站點(diǎn)能夠和新的翻譯模型(model)工作了。現(xiàn)在我們就能把變更同步到數(shù)據(jù)庫中了。
應(yīng)用翻譯模型(model)遷移
我們?cè)谧兏芾碚军c(diǎn)之前已經(jīng)從模型(model)中刪除了舊的字段。現(xiàn)在我們需要為這次變更創(chuàng)建遷移。打開 shell ,運(yùn)行下面的命令:
python manage.py makemigrations shop --name "remove_untranslated_fields"
你將會(huì)看到如下輸出:
Migrations for 'shop':
0004_remove_untranslated_fields.py:
- Remove field name from category
- Remove field slug from category
- Remove field description from product
- Remove field name from product
- Remove field slug from product
遷移之后,我們就已經(jīng)刪除了源字段保留了可翻譯字段。
總結(jié)一下,我們創(chuàng)建了以下遷移:
1. 將可翻譯字段添加到模型(models)中
2. 將源字段中的數(shù)據(jù)遷移到可翻譯字段中
3. 從源模型(models)中刪除源字段
運(yùn)行下面的命令來應(yīng)用我們創(chuàng)建的遷移:
python manage.py migrate shop
你將會(huì)看到如下輸出:
Applying shop.0002_add_translation_model... OK
Applying shop.0003_migrate_translatable_fields... OK
Applying shop.0004_remove_untranslated_fields... OK
我們的模型(models)現(xiàn)在已經(jīng)同步到了數(shù)據(jù)庫中。讓我們來翻譯一個(gè)對(duì)象。
用命令 pythong manage.py runserver 運(yùn)行開發(fā)服務(wù)器,在瀏覽器中訪問:http://127.0.0.1:8000/en/admin/shop/category/add/ 。你就會(huì)看到包含兩個(gè)標(biāo)簽的 Add category 頁,一個(gè)標(biāo)簽是英文的,一個(gè)標(biāo)簽是西班牙語的。

你現(xiàn)在可以添加一個(gè)翻譯然后點(diǎn)擊Save按鈕。確保你在切換標(biāo)簽之前保存了他們,不然讓門就會(huì)丟失。
使視圖(views)適應(yīng)翻譯
我們要使我們的 shop 視圖(views)適應(yīng)我們的翻譯查詢集(QuerySets)。在命令行運(yùn)行 python manage.py shell ,看看你是如何檢索和查詢翻譯字段的。為了從當(dāng)前激活語言中得到一個(gè)字段的內(nèi)容,你只需要用和連接一般字段相同的辦法連接這個(gè)字段:
>>> from shop.models import Product
>>> product=Product.objects.first()
>>> product.name
'Black tea'
當(dāng)你連接到被翻譯了字段時(shí),他們已經(jīng)被用當(dāng)前語言處理過了。你可以為一個(gè)對(duì)象設(shè)置不同的當(dāng)前語言,這樣你就可以獲得指定的翻譯了:
>>> product.set_current_language('es')
>>> product.name
'Té negro'
>>> product.get_current_language()
'es
當(dāng)使用 filter() 執(zhí)行一次查詢集(QuerySets)時(shí),你可以使用 translations__ 語法篩選相關(guān)聯(lián)對(duì)象:
>>> Product.objects.filter(translations__name='Black tea')
[<Product: Black tea>]
你也可以使用 language() 管理器來為每個(gè)檢索的對(duì)象指定特定的語言:
>>> Product.objects.language('es').all()
[<Product: Té negro>, <Product: Té en polvo>, <Product: Té rojo>,
<Product: Té verde>]
如你所見,連接到翻譯字段的方法還是很直接的。
讓我們修改一下產(chǎn)品目錄的視圖(views)吧。編輯 shop 應(yīng)用的 views.py ,在 product_list 視圖(view)中找到下面這一行:
category = get_object_or_404(Category, slug=category_slug)
把它替換為下面這幾行:
language = request.LANGUAGE_CODE
category = get_object_or_404(Category,
translations__language_code=language,
translations__slug=category_slug)
然后,編輯 product_detail 視圖(view),找到下面這幾行:
product = get_object_or_404(Product,
id=id,
slug=slug,
available=True)
把他們換成以下幾行::
language = request.LANGUAGE_CODE
get_object_or_404(Product,
id=id,
translations__language_code=language,
translations__slug=slug,
available=True)
現(xiàn)在 product_list 和 product_detail 已經(jīng)可以使用翻譯字段來檢索了。運(yùn)行開發(fā)服務(wù)器,訪問:http://127.0.0.1:8000/es/ ,你可以看到產(chǎn)品列表頁,包含了所有被翻譯為西班牙語的產(chǎn)品:

現(xiàn)在每個(gè)產(chǎn)品的 URLs 已經(jīng)使用 slug 字段被翻譯為了的當(dāng)前語言。比如,一個(gè)西班牙語產(chǎn)品的 URL 為:http://127.0.0.1:8000/es/2/te-rojo/ ,但是它的英文 URL 為:http://127.0.0.1:8000/en/2/red-tea/ 。如果你導(dǎo)航到產(chǎn)品詳情頁,你可以看到用被選語言翻譯后的 URL 和內(nèi)容,就像下面的例子:

如果你想要更多了解 django-parler ,你可以在這個(gè)網(wǎng)站找到所有的文檔:http//django-parler.readthedocs.org/en/latest/ 。
你已經(jīng)學(xué)習(xí)了如何翻譯 Python 代碼,模板(template),URL 模式,模型(model)字段。為了完成國際化和本地化的工作,我們需要展示本地化格式的時(shí)間和日期、以及數(shù)字。
本地格式化
基于用戶的語言,你可能想要用不同的格式展示日期、時(shí)間和數(shù)字。本第格式化可通過修改 settings.py 中的 USE_L1ON 設(shè)置為 True 來使本地格式化生效。
當(dāng) USE_L1ON 可用時(shí),Django 將會(huì)在模板(template)任何輸出一個(gè)值時(shí)嘗試使用某一語言的特定格式。你可以看到你的網(wǎng)站中用一個(gè)點(diǎn)來做分隔符的十進(jìn)制英文數(shù)字,盡管在西班牙語版中他們使用逗號(hào)來做分隔符。這和 Django 里為 es 指定的語言格式。你可以在這里查看西班牙語的格式配置:https://github.com/django/django/blob/stable/1.8.x/django/conf/locale/es/formats.py 。
通常,你把 USE_L10N 的值設(shè)為 True 然后 Django 就會(huì)為每一種語言應(yīng)用本地化格式。雖然在某些情況下你有不想要被格式化的值。這和輸出特別相關(guān), JavaScript 或者 JSON 必須要提供機(jī)器可讀的格式。
Django 提供了 {% localize %} 模板(template)表標(biāo)簽來讓你可以開啟或者關(guān)閉模板(template)的本地化。這使得你可以控制本地格式化。你需要載入 l10n 標(biāo)簽來使用這個(gè)模板(template)標(biāo)簽。下面的例子是如何在模板(template)中開啟和關(guān)閉它:
{% load l10n %}
{% localize on %}
{{ value }}
{% endlocalize %}
{% localize off %}
{{ value }}
{% endlocalize %}
Django 同樣也提供了 localize 和 unlocalize 模板(template)過濾器來強(qiáng)制或取消一個(gè)值的本地化。過濾器可以像下面這樣使用:
{{ value|localize }}
{{ value|unlocalize }}
你也可以創(chuàng)建定制化的格式文件來指定特定語言的格式。你可以在這個(gè)網(wǎng)站找到所有關(guān)于本第格式化的信息:https://docs.djangoproject.com/en/1.8/topics/i18n/formatting/ 。
使用 django-localflavor 來驗(yàn)證表單字段
django-localflavor 是一個(gè)包含特殊工具的第三方模塊,比如它的有些表單字段或者模型(model)字段對(duì)于每個(gè)國家是不同的。它對(duì)于驗(yàn)證本地地區(qū),本地電話號(hào)碼,身份證號(hào),社保號(hào)等等都是非常有用的。這個(gè)包被集成進(jìn)了一系列以 ISO 3166 國家代碼命名的模塊里。
使用下面的命令安裝 django-localflavor :
pip install django-localflavor==1.1
編輯項(xiàng)目中的 settings.py ,把 localflavor 添加進(jìn) INSTALLED_APPS 設(shè)置中。
我們將會(huì)添加美國(U.S.)郵政編碼字段,這樣之后創(chuàng)建一個(gè)新的訂單就需要一個(gè)美國郵政編碼了。
編輯 orders 應(yīng)用的 forms.py ,讓它看起來像這樣:
from django import forms
from .models import Order
from localflavor.us.forms import USZipCodeField
class OrderCreateForm(forms.ModelForm):
postal_code = USZipCodeField()
class Meta:
model = Order
fields = ['first_name', 'last_name', 'email', 'address',
'postal_code', 'city',]
我們從 localflavor 的 us 包中引入了 USZipCodeField 然后將它應(yīng)用在 OrderCreateForm 的 postal_code 字段中。訪問:http://127.0.0.1:8000/en/orders/create/ 然后嘗試輸入一個(gè) 3 位郵政編碼。你將會(huì)得到 USZipCodeField 引發(fā)的驗(yàn)證錯(cuò)誤:
Enter a zip code in the format XXXXX or XXXXX-XXXX.
這只是一個(gè)在你的項(xiàng)目中使用定制字段來達(dá)到驗(yàn)證目的的簡(jiǎn)單例子,localflavor 提供的本地組件對(duì)于讓你的項(xiàng)目適應(yīng)不同的國家是非常有用的。你可以閱讀 django-localflavor 的文檔,看看所有的可用地區(qū)組件:https://django-localflavor.readthedocs.org/en/latest/ 。
下面,我們將會(huì)為我們的店鋪創(chuàng)建一個(gè)推薦引擎。
創(chuàng)建一個(gè)推薦引擎
推薦引擎是一個(gè)可以預(yù)測(cè)用戶對(duì)于某一物品偏好和比率的系統(tǒng)。這個(gè)系統(tǒng)會(huì)基于用戶的行為和它對(duì)于用戶的了解選出相關(guān)的商品。如今,推薦系統(tǒng)用于許多的在線服務(wù)。它們幫助用戶從大量的相關(guān)數(shù)據(jù)中挑選他們可能感興趣的產(chǎn)品。提供良好的推薦可以鼓勵(lì)用戶更多的消費(fèi)。電商網(wǎng)站受益于日益增長的相關(guān)產(chǎn)品推薦銷售中。
我們將會(huì)創(chuàng)建一個(gè)簡(jiǎn)單但強(qiáng)大的推薦引擎來推薦經(jīng)常一起購買的商品。我們將基于歷史銷售來推薦商品,這樣就可以辨別出哪些商品通常是一起購買的了。我們將會(huì)在兩種不同的場(chǎng)景下推薦互補(bǔ)的產(chǎn)品:
- 產(chǎn)品詳情頁:我們將會(huì)展示和所給商品經(jīng)常一起購買的產(chǎn)品列表。比如像這樣展示:Users who bought this also bought X, Y, Z(購買了這個(gè)產(chǎn)品的用戶也購買了X, Y,Z)。我們需要一個(gè)數(shù)據(jù)結(jié)構(gòu)來讓我們能儲(chǔ)被展示產(chǎn)品中和每個(gè)產(chǎn)品一起購買的次數(shù)。
- 購物車詳情頁:基于用戶添加到購物車當(dāng)中的產(chǎn)品,我們將會(huì)推薦經(jīng)常和他們一起購買的產(chǎn)品。在這樣的情況下,我們計(jì)算的包含相關(guān)產(chǎn)品的評(píng)分一定要相加。
我們將會(huì)使用 Redis 來儲(chǔ)存被一起購買的產(chǎn)品。記得你已經(jīng)在**第六章 跟蹤用戶操作 **使用了 Redis 。如果你還沒有安裝 Redis ,你可以在那一章找到安裝指導(dǎo)。
推薦基于歷時(shí)購物的產(chǎn)品
現(xiàn)在,讓我們推薦給用戶一些基于他們添加到購物車的產(chǎn)品。我們將會(huì)在 Redis 中為每個(gè)產(chǎn)品儲(chǔ)存一個(gè)鍵(key)。產(chǎn)品的鍵將會(huì)和它的評(píng)分一同儲(chǔ)存在 Redis 的有序集中。在一次新的訂單被完成時(shí),我們每次都會(huì)為一同購買的產(chǎn)品的評(píng)分加一。
當(dāng)一份訂單付款成功后,我們保存每個(gè)購買產(chǎn)品的鍵,包括同意訂單中的有序產(chǎn)品集。這個(gè)有序集讓我們可以為一起購買的產(chǎn)品打分。
編輯項(xiàng)目中的額 settings.py ,添加以下設(shè)置:
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 1
這里的設(shè)置是為了和 Redis 服務(wù)器建立連接。在 shop 應(yīng)用內(nèi)創(chuàng)建一個(gè)新的文件,命名為 recommender.py 。添加以下代碼:
import redis
from django.conf import settings
from .models import Product
# connect to redis
r = redis.StrictRedis(host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=settings.REDIS_DB)
class Recommender(object):
def get_product_key(self, id):
return 'product:{}:purchased_with'.format(id)
def products_bought(self, products):
product_ids = [p.id for p in products]
for product_id in product_ids:
for with_id in product_ids:
# get the other products bought with each product
if product_id != with_id:
# increment score for product purchased together
r.zincrby(self.get_product_key(product_id),
with_id,
amount=1)
Recommender 類使我們可以儲(chǔ)存購買的產(chǎn)品然后檢索所給產(chǎn)品的產(chǎn)品推薦。get_product_key() 方法檢索 Product 對(duì)象的 id ,然后為儲(chǔ)存產(chǎn)品的有序集創(chuàng)建一個(gè) Redis 鍵(key),看起來像這樣:product:[id]:purchased_with
products_bought() 方法檢索被一起購買的產(chǎn)品列表(它們都屬于同一個(gè)訂單)。在這個(gè)方法中,我們執(zhí)行以下幾個(gè)任務(wù):
1. 得到所給 `Product` 對(duì)象的產(chǎn)品 ID
2. 迭代所有的產(chǎn)品 ID。對(duì)于每個(gè) `id` ,我們迭代所有的產(chǎn)品 ID 并且跳過所有相同的產(chǎn)品,這樣我們就可以得到和每個(gè)產(chǎn)品一起購買的產(chǎn)品。
3. 我們使用 `get_product_id()` 方法來獲取 Redis 產(chǎn)品鍵。對(duì)于一個(gè) ID 為 33 的產(chǎn)品,這個(gè)方法返回鍵 `product:33:purchased_with` 。這個(gè)鍵用于包含和這個(gè)產(chǎn)品被一同購買的產(chǎn)品 ID 的有序集。
4. 我們把有序集中的每個(gè)產(chǎn)品 `id` 的評(píng)分加一。評(píng)分表示另一個(gè)產(chǎn)品和所給產(chǎn)品一起購買的次數(shù)。
我們有一個(gè)方法來保存和對(duì)一同購買的產(chǎn)品評(píng)分。現(xiàn)在我們需要一個(gè)方法來檢索被所給購物列表的一起購買的產(chǎn)品。把 suggest_product_for() 方法添加到 Recommender 類中:
def suggest_products_for(self, products, max_results=6):
product_ids = [p.id for p in products]
if len(products) == 1:
# only 1 product
suggestions = r.zrange(
self.get_product_key(product_ids[0]),
0, -1, desc=True)[:max_results]
else:
# generate a temporary key
flat_ids = ''.join([str(id) for id in product_ids])
tmp_key = 'tmp_{}'.format(flat_ids)
# multiple products, combine scores of all products
# store the resulting sorted set in a temporary key
keys = [self.get_product_key(id) for id in product_ids]
r.zunionstore(tmp_key, keys)
# remove ids for the products the recommendation is for
r.zrem(tmp_key, *product_ids)
# get the product ids by their score, descendant sort
suggestions = r.zrange(tmp_key, 0, -1,
desc=True)[:max_results]
# remove the temporary key
r.delete(tmp_key)
suggested_products_ids = [int(id) for id in suggestions]
# get suggested products and sort by order of appearance
suggested_products = list(Product.objects.filter(id__in=suggested_
products_ids))
suggested_products.sort(key=lambda x: suggested_products_ids.
index(x.id))
return suggested_products
suggest_product_for() 方法接收下列參數(shù):
-
products:這是一個(gè)Product對(duì)象列表。它可以包含一個(gè)或者多個(gè)產(chǎn)品 -
max_results:這是一個(gè)整數(shù),用于展示返回的推薦的最大數(shù)量
在這個(gè)方法中,我們執(zhí)行以下的動(dòng)作:
1. 得到所給 `Product` 對(duì)象的 ID
2. 如果只有一個(gè)產(chǎn)品,我們就檢索一同購買的產(chǎn)品的 ID,并按照他們的購買時(shí)間來排序。這樣做,我們就可以使用 Redis 的 `ZRANGE` 命令。我們通過 `max_results` 屬性來限制結(jié)果的數(shù)量。
3. 如果有多于一個(gè)的產(chǎn)品被給予,我們就生成一個(gè)臨時(shí)的和產(chǎn)品 ID 一同創(chuàng)建的 Redis 鍵。
4. 我們把包含在每個(gè)所給產(chǎn)品的有序集中東西的評(píng)分組合并相加,我們使用 Redis 的 `ZUNIONSTORE` 命令來實(shí)現(xiàn)這個(gè)操作。`ZUNIONSTORE` 命令執(zhí)行了對(duì)有序集的所給鍵的求和,然后在新的 Redis 鍵中保存每個(gè)元素的求和。你可以在這里閱讀更多關(guān)于這個(gè)命令的信息:http://redisio/commands/ZUNIONSTORE 。我們?cè)谂R時(shí)鍵中保存分?jǐn)?shù)的求和。
5. 因?yàn)槲覀円呀?jīng)求和了評(píng)分,我們或許會(huì)獲取我們推薦的重復(fù)商品。我們就使用 `ZREM` 命令來把他們從生成的有序集中刪除。
6. 我們從臨時(shí)鍵中檢索產(chǎn)品 ID,使用 `ZREM` 命令來按照他們的評(píng)分排序。我們把結(jié)果的數(shù)量限制在 `max_results` 屬性指定的值內(nèi)。然后我們刪除了臨時(shí)鍵。
7. 最后,我們用所給的 `id` 獲取 `Product` 對(duì)象,并且按照同樣的順序來排序。
為了更加方便使用,讓我們添加一個(gè)方法來清除所有的推薦。
把下列方法添加進(jìn) Recommender 類中:
def clear_purchases(self):
for id in Product.objects.values_list('id', flat=True):
r.delete(self.get_product_key(id))
讓我們?cè)囋囄覀兊耐扑]引擎。確保你在數(shù)據(jù)庫中引入了幾個(gè) Product 對(duì)象并且在 shell 中使用了下面的命令來初始化 Redis 服務(wù)器:
src/redis-server
打開另外一個(gè) shell ,執(zhí)行 python manage.py shell ,輸入下面的代碼來檢索幾個(gè)產(chǎn)品:
from shop.models import Product
black_tea = Product.objects.get(translations__name='Black tea')
red_tea = Product.objects.get(translations__name='Red tea')
green_tea = Product.objects.get(translations__name='Green tea')
tea_powder = Product.objects.get(translations__name='Tea powder')
然后,添加一些測(cè)試購物到推薦引擎中:
from shop.recommender import Recommender
r = Recommender()
r.products_bought([black_tea, red_tea])
r.products_bought([black_tea, green_tea])
r.products_bought([red_tea, black_tea, tea_powder])
r.products_bought([green_tea, tea_powder])
r.products_bought([black_tea, tea_powder])
r.products_bought([red_tea, green_tea])
我們已經(jīng)保存了下面的評(píng)分:
black_tea: red_tea (2), tea_powder (2), green_tea (1)
red_tea: black_tea (2), tea_powder (1), green_tea (1)
green_tea: black_tea (1), tea_powder (1), red_tea(1)
tea_powder: black_tea (2), red_tea (1), green_tea (1)
讓我們看看為單一產(chǎn)品的推薦吧:
>>> r.suggest_products_for([black_tea])
[<Product: Tea powder>, <Product: Red tea>, <Product: Green tea>]
>>> r.suggest_products_for([red_tea])
[<Product: Black tea>, <Product: Tea powder>, <Product: Green tea>]
>>> r.suggest_products_for([green_tea])
[<Product: Black tea>, <Product: Tea powder>, <Product: Red tea>]
>>> r.suggest_products_for([tea_powder])
[<Product: Black tea>, <Product: Red tea>, <Product: Green tea>]
正如你所看到的那樣,推薦產(chǎn)品的順序正式基于他們的評(píng)分。讓我們來看看為多個(gè)產(chǎn)品的推薦吧:
>>> r.suggest_products_for([black_tea, red_tea])
[<Product: Tea powder>, <Product: Green tea>]
>>> r.suggest_products_for([green_tea, red_tea])
[<Product: Black tea>, <Product: Tea powder>]
>>> r.suggest_products_for([tea_powder, black_tea])
[<Product: Red tea>, <Product: Green tea>]
你可以看到推薦產(chǎn)品的順序和評(píng)分總和的順序是一樣的。比如 black_tea, red_tea, tea_powder(2+1) 和 green_tea(1=1) 的產(chǎn)品推薦就是這樣。
我們必須保證我們的推薦算法按照預(yù)期那樣工作。讓我們?cè)谖覀兊恼军c(diǎn)上展示我們的推薦吧。
編輯 shop 應(yīng)用的 views.py ,添加以下庫:
from .recommender import Recommender
把下面的代碼添加進(jìn) product_detail 視圖(view)中的 render() 之前:
r = Recommender()
recommended_products = r.suggest_products_for([product], 4)
我們得到了四個(gè)最多產(chǎn)品推薦。 product_detail 視圖(view)現(xiàn)在看起來像這樣:
from .recommender import Recommender
def product_detail(request, id, slug):
product = get_object_or_404(Product,
id=id,
slug=slug,
available=True)
cart_product_form = CartAddProductForm()
r = Recommender()
recommended_products = r.suggest_products_for([product], 4)
return render(request,
'shop/product/detail.html',
{'product': product,
'cart_product_form': cart_product_form,
'recommended_products': recommended_products})
現(xiàn)在編輯 shop 應(yīng)用的 shop/product/detail.html 模板(template),把以下代碼添加在 {{ product.description|linebreaks }} 的后面:
{% if recommended_products %}
<div class="recommendations">
<h3>{% trans "People who bought this also bought" %}</h3>
{% for p in recommended_products %}
<div class="item">
<a href="{{ p.get_absolute_url }}">
<img src="{% if p.image %}{{ p.image.url }}{% else %}{%
static "img/no_image.png" %}{% endif %}">
</a>
<p><a href="{{ p.get_absolute_url }}">{{ p.name }}</a></p>
</div>
{% endfor %}
</div>
{% endif %}
運(yùn)行開發(fā)服務(wù)器,訪問:http://127.0.0.1:8000/en/ 。點(diǎn)擊一個(gè)產(chǎn)品來查看它的詳情頁。你應(yīng)該看到展示在下方的推薦產(chǎn)品,就象這樣:

我們也將在購物車當(dāng)中引入產(chǎn)品推薦。產(chǎn)品推薦將會(huì)基于用戶購物車中添加的產(chǎn)品。編輯 cart 應(yīng)用的 views.py ,添加以下庫:
from shop.recommender import Recommender
編輯 cart_detail 視圖(view),讓它看起來像這樣:
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()
r = Recommender()
cart_products = [item['product'] for item in cart]
recommended_products = r.suggest_products_for(cart_products,
max_results=4)
return render(request,
'cart/detail.html',
{'cart': cart,
'coupon_apply_form': coupon_apply_form,
'recommended_products': recommendeproducts})
編輯 cart 應(yīng)用的 cart/detail.html ,把下列代碼添加在 <table>HTML 標(biāo)簽后面:
{% if recommended_products %}
<div class="recommendations cart">
<h3>{% trans "People who bought this also bought" %}</h3>
{% for p in recommended_products %}
<div class="item">
<a href="{{ p.get_absolute_url }}">
<img src="{% if p.image %}{{ p.image.url }}{% else %}{%
static "img/no_image.png" %}{% endif %}">
</a>
<p><a href="{{ p.get_absolute_url }}">{{ p.name }}</a></p>
</div>
{% endfor %}
</div>
{% endif %}
訪問 http://127.0.0.1:8000/en/ ,在購物車當(dāng)中添加一些商品。東航擬導(dǎo)航向 http://127.0.0.1:8000/en/cart/ 時(shí),你可以在購物車下看到銳減的產(chǎn)品,就像下面這樣:

恭喜你!你已經(jīng)使用 Django 和 Redis 構(gòu)建了一個(gè)完整的推薦引擎。
總結(jié)
在這一章中,你使用會(huì)話創(chuàng)建了一個(gè)優(yōu)惠券系統(tǒng)。你學(xué)習(xí)了國際化和本地化是如何工作的。你也使用 Redis 構(gòu)建了一個(gè)推薦引擎。
在下一章中,你將開始一個(gè)新的項(xiàng)目。你將會(huì)用 Django 創(chuàng)建一個(gè)在線學(xué)習(xí)平臺(tái),并且使用基于類的視圖(view)。你將學(xué)會(huì)創(chuàng)建一個(gè)定制的內(nèi)容管理系統(tǒng)(CMS)。
(譯者@夜夜月:- -下一章又輪到我了。。。。。。放出時(shí)間不固定。。。。。。)