《Django By Example》第九章 中文 翻譯 (我的學習,渣翻)

書籍出處:https://www.packtpub.com/web-development/django-example
原做者:Antonio Melécss

(譯者@ucag 注:哈哈哈,第九章終於來啦。這是在線商店的最後一章,下一章將會開始一個新的項目。因此這一章的內容會偏難,最難的是推薦引擎的編寫,其中的算法可能還須要各位好好的推敲,若是看了中文的看不懂,你們能夠去看看英文原版的書以及相關的文檔。最近我也在研究機器學習,有興趣的你們能夠一塊兒交流哈~)html

(審校@夜夜月:你們好,我是來打醬油的~,粗校,主要更正了一些錯字和格式,精校訂進行到四章樣子。)python

第九章

拓展你的商店

在上一章中,你學習瞭如何把支付網關整合進你的商店。你處理了支付通知,學會了如何生成 CSV 和 PDF 文件。在這一章中,你會把優惠券添加進你的商店中。你將學到國際化(internationalization)和本地化(localization)是如何工做的,你還會建立一個推薦引擎。
在這一章中將會包含一下知識點:git

  • 建立一個優惠券系統來應用折扣
  • 把國際化添加進你的項目中
  • 使用 Rosetta 來管理翻譯
  • 使用 django-parler 來翻譯模型(model)
  • 創建一個產品推薦引擎

建立一個優惠券系統

不少的在線商店會送出不少優惠券,這些優惠券能夠在顧客的採購中兌換爲相應的折扣。在線優惠券一般是由一串給顧客的代碼構成,這串代碼在一個特定的時間段內是有效的。這串代碼能夠被兌換一次或者屢次。github

咱們將會爲咱們的商店建立一個優惠券系統。優惠券將會在顧客在某一個特定的時間段內輸入時生效。優惠券沒有任何兌換數的限制,他們也可用於購物車的總金額中。對於這個功能,咱們將會建立一個模型(model)來儲存優惠券代碼,優惠券有效的時間段,以及折扣的力度。web

myshop 項目內使用以下命令建立一個新的應用:正則表達式

python manage.py startapp coupons

編輯 myshopsettings.py 文件,像下面這樣把把應用添加到 INSTALLED_APPS 中:redis

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

新的應用已經在咱們的 Django 項目中激活了。算法

建立優惠券模型(model)

讓咱們開始建立 Coupon 模型(model)。編輯 coupons 應用中的 models.py 文件,添加如下代碼:shell

from django.db import models
from django.core.validators import MinValueValidator,\
                                    MaxValueValidator
class Coupon(models.Model):
    code = models.CharField(max_length=50,
                            unique=True)
    valid_from = models.DateTimeField()
    valid_to = models.DateTimeField()
    discount = models.IntegerField(
                validators=[MinValueValidator(0),
                            MaxValueValidator(100)])
    active = models.BooleanField()
    
    def __str__(self):
        return self.code

咱們將會用這個模型(model)來儲存優惠券。 Coupon 模型(model)包含如下幾個字段:

  • code:用戶必需要輸入的代碼來將優惠券應用到他們購買的商品中
  • valid_from:表示優惠券會在什麼時候生效的時間和日期值
  • valid_to:表示優惠券會在什麼時候過時
  • discount:折扣率(這是一個百分比,因此它的值的範圍是 0 到 1000)。咱們使用驗證器來限制接收的最小值和最大值
  • active:表示優惠券是否激活的布爾值

執行下面的命令來爲 coupons 生成首次遷移:

python manage.py makemigrations

輸出應該包含如下這幾行:

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

以後咱們執行下面的命令來應用遷移:

python manage.py migrate

你能夠看見包含下面這一行的輸出:

Applying coupons.0001_initial... OK

遷移如今已經被應用到了數據庫中。讓咱們把 Coupon 模型(model)添加到管理站點。編輯 coupons 應用的 admin.py 文件,添加如下代碼:

from django.contrib import admin
from .models improt Coupon

class CouponAdmin(admin.ModelAdmin):
    list_display = ['code', 'valid_from', 'valid_to',
                    'discount', 'active']
    list_filter = ['active', 'valid_from', 'valid_to']
    search_fields = ['code']
admin.site.register(Coupon, CouponAdmin)

Coupon 模型(model)如今已經註冊進了管理站點中。確保你已經用命令 python manage.py runserver 打開了開發服務器。訪問 http://127.0.0.1:8000/admin/coupons/add 。你能夠看見下面的表單:

django-9-1

填寫表單建立一個在當前日期有效的新優惠券,確保你點擊了 Active 複選框,而後點擊 Save按鈕。

把應用優惠券到購物車中

咱們能夠保存新的優惠券以及檢索目前的優惠券。如今咱們須要一個方法來讓顧客能夠應用他們的優惠券到他們購買的產品中。花點時間來想一想這個功能該如何實現。應用一張優惠券的流程以下:

1. 用戶將產品添加進購物車
2. 用戶在購物車詳情頁的表單中輸入優惠代碼
3. 當用戶輸入優惠代碼而後提交表單時,咱們查找一張和所給優惠代碼相符的有效優惠券。咱們必須檢查用戶輸入的優惠券代碼, `active` 屬性爲 `True` ,當前時間位於 `valid_from 和 `valid_to` 之間。
4. 若是查找到了相應的優惠券,咱們把它保存在用戶會話中,而後展現包含折扣了的購物車以及更新總價。
5. 當用戶下單時,咱們把優惠券保存到所給的訂單中。

coupons 應用路徑下建立一個新的文件,命名爲 forms.py 文件,添加如下代碼:

from django import forms

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

咱們將會用這個表格來讓用戶輸入優惠券代碼。編輯 coupons 應用中的 views.py 文件,添加如下代碼:

from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import Coupon
from .forms import CouponApplyForm

@require_POST
def coupon_apply(request):
    now = timezone.now()
    form = CouponApplyForm(request.POST)
    if form.is_valid():
        code = form.cleaned_data['code']
        try:
            coupon = Coupon.objects.get(code__iexact=code,
                                    valid_from__lte=now,
                                    valid_to__gte=now,
                                    active=True)
            request.session['coupon_id'] = coupon.id
        except Coupon.DoesNotExist:
            request.session['coupon_id'] = None
    return redirect('cart:cart_detail')

coupon_apply 視圖(view)驗證優惠券而後把它保存在用戶會話(session)中。咱們使用 require_POST 裝飾器來限制這個視圖(view)僅接受 POST 請求。在視圖(view)中,咱們執行了如下幾個任務:

1.咱們用上傳的數據實例化了 `CouponApplyForm` 而後檢查表單是否合法。
2. 若是表單是合法的,咱們就從表單的 `cleaned_data` 字典中獲取 `code` 。咱們嘗試用所給的代碼檢索 `Coupon` 對象。咱們使用 `iexact` 字段來對照查找大小寫不敏感的精確匹配項。優惠券在當前必須是激活的(`active=True`)以及必須在當前日期內是有效的。咱們使用 Django 的 `timezone.now()` 函數來得到當前的時區識別時間和日期(time-zone-aware) 而後咱們把它和 `valid_from` 和 `valid_to` 字段作比較,對這兩個日期分別執行 `lte` (小於等於)運算和 `gte` (大於等於)運算來進行字段查找。
3. 咱們在用戶的會話中保存優惠券的 `id``。
4. 咱們把用戶重定向到 `cart_detail` URL 來展現應用了優惠券的購物車。

咱們須要一個 coupon_apply 視圖(view)的 URL 模式。在 coupon 應用路徑下建立一個新的文件,命名爲 urls.py ,添加如下代碼:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^apply/$', views.coupon_apply, name='apply'),
]

而後,編輯 myshop 項目的主 urls.py 文件,引入 coupons 的 URL 模式:

url(r'^coupons/', include('coupons.urls', namespace='coupons')),

記得把這個放在 shop.urls 模式以前。

如今編輯 cart 應用的 cart.py,包含如下導入:

from coupons.models import Coupon

把下面這行代碼添加進 Cart 類的 __init__() 方法中來從會話中初始化優惠券:

# store current applied coupon
self.coupon_id = self.session.get('coupon_id')

在這行代碼中,咱們嘗試從當前會話中獲得 coupon_id 會話鍵,而後把它保存在 Cart 對象中。把如下方法添加進 Cart 對象中:

@property
def coupon(self):
    if self.coupon_id:
        return Coupon.objects.get(id=self.coupon_id)
    return None

def get_discount(self):
    if self.coupon:
        return (self.coupon.discount / Decimal('100')) \
                * self.get_total_price()
    return Decimal('0')

def get_total_price_after_discount(self):
    return self.get_total_price() - self.get_discount()

下面是這幾個方法:

  • coupon():咱們定義這個方法做爲 property 。若是購物車包含 coupon_id 函數,就會返回一個帶有給定 idCoupon 對象
  • get_discount():若是購物車包含 coupon ,咱們就檢索它的折扣比率,而後返回購物車中被扣除折扣的總和。
  • get_total_price_after_discount():返回被減去折扣以後的總價。

Cart 類如今已經準備好處理應用於當前會話的優惠券了,而後將它應用於相應折扣中。

讓咱們在購物車詳情視圖(view)中引入優惠券系統。編輯 cart 應用的 views.py ,而後把下面這一行添加到頂部:

from coupons.forms import CouponApplyForm

以後,編輯 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()
    return render(request,
            'cart/detail.html',
            {'cart': cart,
            'coupon_apply_form': coupon_apply_form})

編輯 cart 應用的 acrt/detail.html 文件,找到下面這幾行:

<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">${{ cart.get_total_price }}</td>
</tr>

把它們換成如下幾行:

{% if cart.coupon %}
<tr class="subtotal">
    <td>Subtotal</td>
    <td colspan="4"></td>
    <td class="num">${{ cart.get_total_price }}</td>
</tr>
<tr>
<td>
    "{{ cart.coupon.code }}" coupon
    ({{ cart.coupon.discount }}% off)
    </td>
    <td colspan="4"></td>
    <td class="num neg">
    - ${{ cart.get_discount|floatformat:"2" }}
    </td>
</tr>
{% endif %}
<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">
    ${{ cart.get_total_price_after_discount|floatformat:"2" }}
    </td>
</tr>

這段代碼用於展現可選優惠券以及折扣率。若是購物車中有優惠券,咱們就在第一行展現購物車的總價做爲 小計。而後咱們在第二行展現當前可應用於購物車的優惠券。最後,咱們調用 cart 對象的 get_total_price_after_discount() 方法來展現折扣了的總價格。

在同一個文件中,在 </table> 標籤以後引入如下代碼:

<p>Apply a coupon:</p>
<form action="{% url "coupons:apply" %}" method="post">
    {{ coupon_apply_form }}
    <input type="submit" value="Apply">
    {% csrf_token %}
</form>

咱們將會展現一個表單來讓用戶輸入優惠券代碼,而後將它應用於當前的購物車當中。

訪問 http://127.0.0.1:8000 ,向購物車當中添加一個商品,而後在表單中輸入你建立的優惠代碼來應用你的優惠券。你能夠看到購物車像下面這樣展現優惠券折扣:

django-9-2

讓咱們把優惠券添加到購物流程中的下一步。編輯 orders 應用的 orders/order/create.html 模板(template),找到下面這幾行:

<ul>
{% for item in cart %}
<li>
    {{ item.quantity }}x {{ item.product.name }}
    <span>${{ item.total_price }}</span>
</li>
{% endfor %}
</ul>

把它替換爲下面這幾行:

<ul>
{% for item in cart %}
    <li>
    {{ item.quantity }}x {{ item.product.name }}
    <span>${{ item.total_price }}</span>
    </li>
{% endfor %}
{% if cart.coupon %}
<li>
"{{ cart.coupon.code }}" ({{ cart.coupon.discount }}% off)
<span>- ${{ cart.get_discount|floatformat:"2" }}</span>
</li>
{% endif %}
</ul>

訂單彙總如今已經包含使用了的優惠券,若是有優惠券的話。如今找到下面這一行:

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

把他們換成如下一行:

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

這樣作以後,總價也將會在減去優惠券折扣被計算出來。
訪問 http://127.0.0.1:8000/orders/create/ 。你會看到包含使用了優惠券的訂單小計:

django-9-3

用戶如今能夠在購物車當中使用優惠券了。儘管,當用戶結帳時,咱們依然須要在訂單中儲存優惠券信息。

在訂單中使用優惠券

咱們會儲存每張訂單中使用的優惠券。首先,咱們須要修改 Order 模型(model)來儲存相關聯的 Coupon 對象,若是有這個對象的話。

編輯 orders 應用的 models.py 文件,添加如下代碼:

from decimal import Decimal
from django.core.validators import MinValueValidator, \
                                    MaxValueValidator
from coupons.models import Coupon

而後,把下列字段添加進 Order 模型(model)中:

coupon = models.ForeignKey(Coupon,
                            related_name='orders',
                            null=True,
                            blank=True)
discount = models.IntegerField(default=0,
                        validators=[MinValueValidator(0),
                                MaxValueValidator(100)])

這些字段讓用戶能夠在訂單中儲存可選的優惠券信息,以及優惠券的相應折扣。折扣被存在關聯的 Coupon 對象中,可是咱們在 Order 模型(model)中引入它以便咱們在優惠券被更改或者刪除時好保存它。

由於 Order 模型(model)已經被改變了,咱們須要建立遷移。執行下面的命令:

python manage.py makemigrations

你能夠看到以下輸出:

Migrations for 'orders':
    0002_auto_20150606_1735.py:
        - Add field coupon to order
        - Add field discount to order

用下面的命令來執行遷移:

python manage.py migrate orders

你必需要確保新的遷移已經被應用了。 Order 模型(model)的字段變動如今已經同步到了數據庫中。

回到 models.py 文件中,按照以下更改 Order 模型(model)的 get_total_cost() 方法:

def get_total_cost(self):
    total_cost = sum(item.get_cost() for item in self.items.all())
    return total_cost - total_cost * \
        (self.discount / Decimal('100'))

Order 模型(model)的 get_total_cost() 如今已經把使用了的折扣包含在內了,若是有折扣的話。

編輯 orders 應用的 views.py 文件,更改 order_create 視圖(view)以便在建立新的訂單時保存相關聯的優惠券和折扣。找到下面這一行:

order = form.save()

把它替換爲下面這幾行:

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

在新的代碼中,咱們使用 OrderCrateForm 表單的 save() 方法建立了一個 Order 對象。咱們使用 commit=False 來避免將它保存在數據庫中。若是購物車當中有優惠券,咱們就會保存相關聯的優惠券和折扣。而後才把 order 對象保存到數據庫中。

確保用 python manage.py runserver 運行了開發服務器。使用 ./ngrok http:8000 命令來啓動 Ngrok 。

在你的瀏覽器中打開 Ngrok 提供的 URL , 而後用用你建立的優惠券完成一次購物。當你完成一次成功的支付後,有能夠訪問 http://127.0.0.1:8000/admin/orders/order/ ,檢查 order 對象是否包含了優惠券和折扣,以下:

django-9-4

你也能夠更改管理界面的訂單詳情模板(template)和訂單的 PDF 帳單,以便用展現購物車的方式來展現使用了的優惠券。

下面。咱們將會爲咱們的項目添加國際化(internationalization)。

添加國際化(internationalization)和本地化(localization)

Django 提供了完整的國際化和本地化的支持。這使得你能夠把你的項目翻譯爲多種語言以及處理地區特性的 日期,時間,數字,和時區 的格式。讓咱們一塊兒來搞清楚國際化和本地化的區別。國際化(一般簡寫爲:i18n)是爲讓軟件適應潛在的不一樣語言和多種語言的使用作的處理,這樣它就不是以某種特定語言或某幾種語言爲硬編碼的軟件了。本地化(簡寫爲:l10n)是對軟件的翻譯以及使軟件適應某種特定語言的處理。Django 自身已經使用自帶的國際化框架被翻譯爲了50多種語言。

使用 Django 國際化

國際化框架讓你能夠容易的在 Python 代碼和模板(template)中標記須要翻譯的字符串。它依賴於 GNU 文本獲取集來生成和管理消息文件。消息文件是一個表示一種語言的純文本文件。它包含某一語言的一部分或者全部的翻譯字符串。消息文件有 .po 擴展名。

一旦翻譯完成,信息文件就會被編譯一遍快速的鏈接到被翻譯的字符串。編譯後的翻譯文件的擴展名爲 .mo

國際化和本地化設置

Django 提供了幾種國際化的設置。下面是幾種最有關聯的設置:

  • USE_I18N:布爾值。用於設定 Django 翻譯系統是否啓動。默認值爲 True
    -USE_L10N:布爾值。表示本地格式化是否啓動。當被激活式,本地格式化被用於展現日期和數字。默認爲 False
  • USE_TZ:布爾值。用於指定日期和時間是不是時區別(timezone-aware)。
  • LANGUAGE_CODE:項目的默認語言。在標準的語言 ID 格式中,好比,en-us 是美式英語,en-gb 是英式英語。這個設置要 USE_I18NTrue 才能生效。你能夠在這個網站找到一個合法的語言 ID 表:http://www.i18nguy.com/unicode/language-identifiers.html
  • LANGUAGES:包含項目可用語言的元組。它們由包含 語言代碼語言名字 的雙元組構成的。你能夠在 django.conf.global_settions 裏看到可用語言的列表。當你選擇你的網站將會使用哪種語言時,你就把 LANGUAGES 設置爲那個列表的子列表。
  • LOCAL_PATHS:Django 尋找包含翻譯的信息文件的路徑列表。
  • TIME_ZONE:表明項目時區的字符串。當你使用 startproject 命令建立新項目時它被設置爲 UTC 。你也能夠把它設置爲其餘的時區,好比 Europe/Madrid

這是一些可用的國際化和本地化的設置。你能夠在這個網站找到所有的(設置)列表: https://docs.djangoproject.com/en/1.8/ref/settings/#globalization-i18n-l10n 。

國際化管理命令

Django 使用 manage.py 或者 django-admin.py 工具包管理翻譯,包含如下命令:

  • makemessages:運行於源代碼樹中,尋找全部被用於翻譯的字符串,而後在 locale 路徑中建立或更新 .po 信息文件。每一種語言建立一個單一的 .po 文件。
  • compilemessages: 把擴展名爲 .po 的信息文件編譯爲用於檢索翻譯的 .mo 文件。

你須要文本獲取工具集來建立,更新,以及編譯信息文件。大部分的 Linux 發行版都包含文本獲取工具集。若是你在使用 Mac OS X,用 Honebrew (http://brew.sh)是最簡單的安裝它的方法,使用如下命令 :brew install gettext。你或許也須要將它和命令行強制鏈接 brew link gettext --force 。對於 Windows ,安裝步驟以下 : https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#gettext-on-windows 。

怎麼把翻譯添加到 Django 項目中

讓咱們看看國際化項目的過程。咱們須要像下面這樣作:

1. 咱們在 Python 代碼和模板(template)中中標記須要翻譯的字符串。
2. 咱們運行 `makemessages` 命令來建立或者更新包含全部翻譯字符串的信息文件。
3. 咱們翻譯包含在信息文件中的字符串,而後使用  `compilemessages` 編譯他們。

Django 如何決定當前語言

Django 配備有一個基於請求數據的中間件,這個中間件用於決定當前語言是什麼。這個中間件是位於 django.middleware.locale.localMiddlewareLocaleMiddleware ,它執行下面的任務:

1. 若是使用 `i18_patterns` —— 這是你使用的被翻譯的 URL 模式,它在被請求的 URL 中尋找一個語言前綴來決定當前語言。
2. 若是沒有找到語言前綴,就會在當前用戶會話中尋找當前的 `LANGUAGE_SESSION_KEY` 。
3. 若是在會話中沒有設置語言,就會尋找帶有當前語言的 cookie 。這個 cookie 的定製名可在 `LANGUAGE_COOKIE_NAME` 中設置。默認地,這個 cookie 的名字是 `django_language` 。
4. 若是沒有找到 cookie,就會在請求 HTTP 頭中尋找 `Accept-Language` 。
5. 若是 `Accept-Language` 頭中沒有指定語言,Django 就使用在 `LANGUAGE_CODE` 設置中定義的語言。

默認的,Django 會使用 LANGUAGE_OCDE 中設置的語言,除非你正在使用 LocaleMiddleware 。上述操做進會在使用這個中間件時生效。

爲咱們的項目準備國際化

讓咱們的項目準備好使用不一樣的語言吧。咱們將要爲咱們的商店建立英文版和西班牙語版。編輯項目中的 settings.py 文件,添加下列 LANGUAGES 設置。把它放在 LANGUAGE_OCDE 旁邊:

LANGUAGES = (
        ('en', 'English'),
        ('es', 'Spanish'),
)

LANGUAGES 設置包含兩個由語言代碼和語言名的元組構成,好比 en-usen-gb ,或者通常的設置爲 en 。這樣設置以後,咱們指定咱們的應用只會對英語和西班牙語可用。若是咱們不指定 LANGUAGES 設置,網站將會對全部 Django 的翻譯語言有效。

確保你的 LANGUAGE_OCDE 像以下設置:

LANGUAGE_OCDE = 'en'

django.middleware.locale.LocaleMiddleware 添加到 MIDDLEWARE_CLASSES 設置中。確保這個設置在 SessionsMiddleware 以後,由於 LocaleMiddleware 須要使用會話數據。它一樣必須放在 CommonMiddleware 以前,由於後者須要一種激活了的語言來解析請求 URL 。MIDDLEWARE_CLASSES 設置看起來應該以下:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
)

中間件的順序很是重要,由於每一箇中間件所依賴的數據多是由其餘中間件處理以後的纔得到的。中間件按照 MIDDLEWARE_CLASSES 的順序應用在每一個請求上,以及反序應用於響應中。

在主項目路徑下,在 manage.py 文件同級,建立如下文件結構:

locale/
    en/
    es/

locale 路徑是放置應用信息文件的地方。再次編輯 settings.py ,而後添加如下設置:

LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale/'),
)

LOCALE_PATHS 設置指定了 Django 尋找翻譯文件的路徑。第一個路徑有最高優先權。

當你在你的項目路徑下使用 makemessages 命令時,信息文件將會在 locale 路徑下生成。儘管,對於包含 locale 路徑的應用,信息文件就會保存在這個應用的 locale 路徑中。

翻譯 Python 代碼

咱們翻譯在 Python 代碼中的字母,你可使用 django.utils.translation 中的 gettext() 函數來標記須要翻譯的字符串。這個函數翻譯信息而後返回一個字符串。約定俗成的用法是導入這個函數後把它命名爲 _ (下劃線)。

你能夠在這個網站找到全部關於翻譯的文檔 : https://docs.djangoproject.com/en/1.8/topics/i18n/translation/ 。

標準翻譯

下面的代碼展現瞭如何標記一個翻譯字符串:

from django.utils.translation import gettext as _
output = _('Text to be translated.')

惰性翻譯(Lazy translation)

Django 對全部的翻譯函數引入了惰性(lazy)版,這些惰性函數都帶有後綴 _lazy() 。當使用惰性函數時,字符串在值被鏈接時就會被翻譯,而不是在被調用時翻譯(這也是它爲何被惰性翻譯的緣由)。惰性函數早晚會派上用場,特別是當標記字符串在模型(model)加載的執行路徑中時。

使用 gettext_lazy() 而不是 gettext() ,字符串就會在鏈接到值的時候被翻譯而不會在函數調用的時候被翻譯。Django 爲全部的翻譯都提供了惰性版本。

翻譯引入的變量

標記的翻譯字符串能夠包含佔位符來引入翻譯中的變量。下面就是一個帶有佔位符的翻譯字字符串的例子:

from django.utils.translation import gettext as _
month = _('April')
day = '14'
output = _('Today is %(month)s %(day)s') % {'month': month,
                                            'day': day}

經過使用佔位符,你能夠從新排序文字變量。舉個例子,之前的英文版本是 'Today is April 14' ,可是西班牙的版本是這樣的 'Hoy es 14 de Abril' 。當你的翻譯字符串有多於一個參數時,咱們老是使用字符串插值來代替位置插值

翻譯中的複數形式

對於複數形式,你可使用 gettext()gettext_lazy() 。這些函數基於一個能夠表示對象數量的參數來翻譯單數和複數形式。下面這個例子展現瞭如何使用它們:

output = ngettext('there is %(count)d product',
                'there are %(count)d products',
                count) % {'count': count}

如今你已經基本知道了如何在你的 Python 代碼中翻譯字符了,如今是把翻譯應用到項目中的時候了。

翻譯你的代碼

編輯項目中的 settings.py ,導入 gettext_lazy() 函數,而後像下面這樣修改 LANGUAGES 的設置:

from django.utils.translation import gettext_lazy as _

LANGUAGES = (
    ('en', _('English')),
    ('es', _('Spanish')),
)

咱們使用 gettext_lazy() 而不是 gettext() 來避免循環導入,這樣就能夠在語言名被鏈接時就翻譯它們。

在你的項目路徑下執行下面的命令:

django-admin makemessages --all

你能夠看到以下輸出:

processing locale es
processing locale en

看下 locale/ 路徑。你能夠看到以下文件結構:

en/
    LC_MESSAGES/
        django.po
es/
    LC_MESSAGES/
        django.po

.po 文件已經爲每個語言建立好了。用文本編輯器打開 es/LC_MESSAGES/django.po 。在文件的末尾,你能夠看到以下幾行:

#: settings.py:104
msgid "English"
msgstr ""

#: settings.py:105
msgid "Spanish"
msgstr ""

每個翻譯字符串前都有一個顯示該文件詳情的註釋以及它在哪一行被找到。每一個翻譯都包含兩個字符串:

  • msgid:在源代碼中的翻譯字符串
  • msgstr:對應語言的翻譯,默認爲空。這是你輸入所給字符串翻譯的地方。

按照以下,根據所給的 msgidmsgtsr 中填寫翻譯:

#: settings.py:104
msgid "English"
msgstr "Inglés"

#: settings.py:105
msgid "Spanish"
msgstr "Español"

保存修改後的信息文件,打開 shell ,運行下面的命令:

django-admin compilemessages

若是一切順利,你能夠看到像以下的輸出:

processing file django.po in myshop/locale/en/LC_MESSAGES
processing file django.po in myshop/locale/es/LC_MESSAGES

輸出給出了被編譯的信息文件的相關信息。再看一眼 locale 路徑的 myshop 。你能夠看到下面的文件:

en/
    LC_MESSAGES/
        django.mo
        django.po
es/
    LC_MESSAGES/
        django.mo
        django.po

你能夠看到每一個語言的 .mo 的編譯文件已經生成了。

咱們已經翻譯了語言自己的名字。如今讓咱們翻譯展現在網站中模型(model)字段的名字。編輯 orders 應用的 models.py ,爲 Order 模型(model)添加翻譯的被標記名:

from django.utils.translation import gettext_lazy as _

class Order(models.Model):
    first_name = models.CharField(_('first name'),
                                max_length=50)
    last_name = models.CharField(_('last name'),
                                max_length=50)
    email = models.EmailField(_('e-mail'),)
    address = models.CharField(_('address'),
                                max_length=250)
    postal_code = models.CharField(_('postal code'),
                                max_length=20)
    city = models.CharField(_('city'),
                        max_length=100)
#...

咱們添加爲當用戶下一個新訂單時展現的字段添加了名字,它們分別是 first_name ,last_name, email, address, postal_code ,city。記住,你也可使用每一個字段的 verbose_name 屬性。

orders 應用路徑內建立如下路徑:

locale/
    en/
    es/

經過建立 locale 路徑,這個應用的翻譯字符串就會儲存在這個路徑下的信息文件裏,而不是在主信息文件裏。這樣作以後,你就能夠爲每一個應用生成獨自的翻譯文件。

在項目路徑下打開 shell ,運行下面的命令:

django-admin makemessages --all

你能夠看到以下輸出:

processing locale es
processing locale en

用文本編輯器打開 es/LC_MESSAGES/django.po 文件。你將會看到每個模型(model)的翻譯字符串。爲每個所給的 msgid 字符串填寫 msgstr 的翻譯:

#: orders/models.py:10
msgid "first name"
msgstr "nombre"

#: orders/models.py:12
msgid "last name"
msgstr "apellidos"

#: orders/models.py:14
msgid "e-mail"
msgstr "e-mail"

#: orders/models.py:15
msgid "address"
msgstr "dirección"

#: orders/models.py:17
msgid "postal code"
msgstr "código postal"

#: orders/models.py:19
msgid "city"
msgstr "ciudad"

在你添加完翻譯以後,保存文件。

在文本編輯器內,你可使用 Poedit 來編輯翻譯。 Poedit 是一個用來編輯翻譯的軟件,它使用 gettext 。它有 Linux ,Windows,Mac OS X 版,你能夠在這個網站下載 Poedit : http://poedit.net/ 。

讓咱們來翻譯項目中的表單吧。 orders 應用的 OrderCrateForm 尚未被翻譯,由於它是一個 ModelForm ,使用 Order 模型(model)的 verbose_name 屬性做爲每一個字段的標籤。咱們將會翻譯 cartcoupons 應用的表單。

編輯 cart 應用路徑下的 forms.py 文件,給 CartAddProductFormquantity 字段添加一個 label 屬性,而後按照以下標記這個須要翻譯的字段:

from django import forms
from django.utils.translation import gettext_lazy as _

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,
                        label=_('Quantity'))
    update = forms.BooleanField(required=False,
                        initial=False,
                        widget=forms.HiddenInput)

編輯 coupons 應用的 forms.py ,按照以下翻譯 CouponApplyForm

from django import forms
from django.utils.translation import gettext_lazy as _

class CouponApplyForm(forms.Form):
    code = forms.CharField(label=_('Coupon'))

翻譯模板(templates)

Django 提供了 {% trans %}{% blocktrans %} 模板(template)標籤來翻譯模板(template)中的字符串。爲了使用翻譯模板(template)標籤,你須要在你的模板(template)頂部添加 {% load i18n %} 來載入她們。

{% trans %}模板(template)標籤

{% trans %}模板(template)標籤讓你能夠標記須要翻譯的字符串,常量,或者是參數。在內部,Django 對所給文本執行 gettext() 。這是如何標記模板(template)中的翻譯字符串:

{% trans "Text to be translated" %}

你可以使用 as 來儲存你在模板(template)內使用的全局變量裏的被翻譯內容。下面這個例子保存了一個變量中叫作 greeting 的被翻譯文本:

{% trans "Hello!" as greeting %}
<h1>{{ greeting }}</h1>

{% trans %} 標籤對於簡單的翻譯字符串是頗有用的,可是它不能包含變量的翻譯內容。

{% blocktrans %}模板(template)標籤

{% blocktrans %} 模板(template)標籤讓你能夠標記含有佔位符的變量和字符的內容。線面這個例子展現瞭如何使用 {% blocktrans %} 標籤來標記包含一個 name 變量的翻譯內容:

{% blocktrans %}Hello {{ name }}!{% endblocktrans %}

你能夠用 with 來引入模板(template)描述,好比鏈接對象的屬性或者應用模板(template)的變量過濾器。你必須爲他們用佔位符。你不能在 blocktrans 內鏈接任何描述或者對象屬性。下面的例子展現瞭如何使用 with 來引入一個應用了 capfirst 過濾器的對象屬性:

{% blocktrans with name=user.name|capfirst %}
Hello {{ name }}!
{% endblocktrans %}

當你的字符串中含有變量時使用 {% blocktrans %} 代替 {% trans %}

翻譯商店模板(template)

編輯 shop 應用的 shop/base.html 。確保你已經在頂部載入了 i18n 標籤,而後按照以下標記翻譯字符串:

{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>
{% block title %}{% trans "My shop" %}{% endblock %}
</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
<a href="/" class="logo">{% trans "My shop" %}</a>
</div>
<div id="subheader">
<div class="cart">
{% with total_items=cart|length %}
{% if cart|length > 0 %}
{% trans "Your cart" %}:
<a href="{% url "cart:cart_detail" %}">
{% blocktrans with total_items_plural=total_
items|pluralize
total_price=cart.get_total_price %}
{{ total_items }} item{{ total_items_plural }},
${{ total_price }}
{% endblocktrans %}
</a>
{% else %}
{% trans "Your cart is empty." %}
{% endif %}
{% endwith %}
</div>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
</body>
</html>

注意展現在購物車小計的 {% blocktrans %} 標籤。購物車小計在以前是這樣的:

{{ total_items }} item{{ total_items|pluralize }},
${{ cart.get_total_price }}

咱們使用 {% blocktrans with ... %} 來使用 total_ items|pluralize (模板(template)標籤生效的地方)和 cart_total_price (鏈接對象方法的地方)的佔位符:

{% blocktrans with total_items_plural=total_items|pluralize
total_price=cart.get_total_price %}
{{ total_items }} item{{ total_items_plural }},
${{ total_price }}
{% endblocktrans %}

下面,編輯 shop 應用的 shop/product/detail.html 模板(template)而後在頂部載入 i18n 標籤,可是它必須位於 {% extends %} 標籤的下面:

{% load i18n %}

找到下面這一行:

<input type="submit" value="Add to cart">

把它替換爲下面這一行:

<input type="submit" value="{% trans "Add to cart" %}">

如今翻譯 orders 應用模板(template)。編輯 orders 應用的 orders/order/create.html 模板(template),而後標記翻譯文本:

{% extends "shop/base.html" %}
{% load i18n %}
{% block title %}
{% trans "Checkout" %}
{% endblock %}
{% block content %}
<h1>{% trans "Checkout" %}</h1>
<div class="order-info">
<h3>{% trans "Your order" %}</h3>
<ul>
{% for item in cart %}
<li>
{{ item.quantity }}x {{ item.product.name }}
<span>${{ item.total_price }}</span>
</li>
{% endfor %}
{% if cart.coupon %}
<li>
{% blocktrans with code=cart.coupon.code
discount=cart.coupon.discount %}
"{{ code }}" ({{ discount }}% off)
{% endblocktrans %}
<span>- ${{ cart.get_discount|floatformat:"2" }}</span>
</li>
{% endif %}
</ul>
<p>{% trans "Total" %}: ${{
cart.get_total_price_after_discount|floatformat:"2" }}</p>
</div>
<form action="." method="post" class="order-form">
{{ form.as_p }}
<p><input type="submit" value="{% trans "Place order" %}"></p>
{% csrf_token %}
</form>
{% endblock %}

看看本章中下列文件中的代碼,看看字符串是如何被標記的:

  • shop 應用:shop/product/list.html
  • orders 應用:orders/order/created.html
  • cart 應用:cart/detail.html

讓位咱們更新信息文件來引入新的翻譯字符串。打開 shell ,運行下面的命令:

django-admin makemessages --all

.po 文件已經在 myshop 項目的 locale 路徑下,你將看到 orders 應用如今已經包含咱們標記的全部須要翻譯的字符串。

編輯項目和orderes 應用中的 .po 翻譯文件,而後引入西班牙語翻譯。你能夠參考本章中翻譯了的 .po 文件:

在項目路徑下打開 shell ,而後運行下面的命令:

cd orders/
django-admin compilemessages
cd ../

咱們已經編譯了 orders 應用的翻譯。

運行下面的命令,這樣應用中不包含 locale 路徑的翻譯就被包含進了項目的信息文件中:

django-admin compilemessages

使用 Rosetta 翻譯交互界面

Rosetta 是一個讓你能夠編輯翻譯的第三方應用,它有相似 Django 管理站點的交互界面。Rosetta 讓編輯 .po 文件和更新它變得很簡單,讓咱們把它添加進項目中:

pip install django-rosetta==0.7.6

而後,把 rosetts 添加進項目中的setting.py文件中的INSTALLED_APPS 設置中。

你須要把 Rosetta 的 URL 添加進主 URL 配置中。編輯項目中的主 urls.py 文件,把下列 URL 模式添加進去:

url(r'^rosetta/', include('rosetta.urls')),

確保你把它放在了 shop.urls 模式以前以免錯誤的匹配。

訪問 http://127.0.0.1:8000/admin/ ,而後使用超級管理員帳戶登陸。而後導航到 http://127.0.0.1:8000/rosetta/ 。你能夠看到當前語言列表:

django-9-5

Filter 模塊,點擊 All 來展現全部可用的信息文件,包括屬於 orders 應用的信息文件。點擊Spanish模塊下的 Myshop 連接來編輯西班牙語翻譯。你能夠看到翻譯字符串的列表:

django-9-6

你能夠在 Spanish 行下輸入翻譯。Occurences 行展現了代碼中翻譯字符串被找到的文件和行數、

包含佔位符的翻譯看起來像這樣:

django-9-7

Rosetta 使用了不一樣的背景色來展現佔位符。當你翻譯內容時,確保你沒有翻譯佔位符。好比,用下面這個字符串舉例:

%(total_items)s item%(total_items_plural)s, $%(total_price)s

它翻譯爲西班牙語以後長得像這樣:

%(total_items)s producto%(total_items_plural)s, $%(total_price)s

你能夠看看本章項目中使用相同西班牙語翻譯的文件源代碼。

當你完成編輯翻譯後,點擊Save and translate next block 按鈕來保存翻譯到 .po 文件中。Rosetta 在你保存翻譯時編輯信息文件,因此並不須要你運行 compilemessages 命令。儘管,Rosetta 須要 locale 路徑的寫入權限來寫入信息文件。確保路徑有有效權限。

若是你想讓其餘用戶編輯翻譯,訪問:http://127.0.0.1:8000/admin/auth/group/add/,而後建立一個名爲 translations 的新組。當編輯一個用戶時,在Permissions模塊下,把 translations 組添加進ChosenGroups中。Rosetta 只對超級用戶和 translations 中的用戶是可用的。

你能夠在這個網站閱讀 Rosetta 的文檔:http://django-rosetta.readthedocs.org/en/latest/ 。

當你在生產環境中添加新的翻譯時,若是你的 Django 運行在一個真實服務器上,你必須在運行 compilemessages 或保存翻譯以後重啓你的服務器來讓 Rosetta 的更改生效。

惰性翻譯

你或許已經注意到了 Rosetta 有 Fuzzy 這一行。這不是 Rosetta 的特性,它是由 gettext 提供的。若是翻譯的 flag 是激活的,那麼它就不會被包含進編譯後的信息文件中。flag 用於須要翻譯器修訂的翻譯字符串。當 .po 文件在更新新的翻譯字符串時,一些翻譯字符串可能被自動標記爲 fuzzy. 當 gettext 找到一些變更不大的 msgid 時就會發生這樣的狀況,gettext 就會把它認爲的舊的翻譯和匹配在一塊兒而後會在回把它標記爲 fuzzy 以用於回查。翻譯器以後會回查模糊翻譯,會移除 fuzzy 標籤而後再次編譯信息文件。

國際化的 URL 模式

Django 提供了用於國際化的 URLs。它包含兩種主要用於國際化的 URLs:

  • URL 模式中的語言前綴:把語言的前綴添加到 URLs 當中,以便在不一樣的基本 URL 下提供每一種語言的版本。
  • 翻譯後的 URL 模式:標記要翻譯的 URL 模式,這樣一樣的 URL 就能夠服務於不一樣的語言。

翻譯 URLs 的緣由是這樣就能夠優化你的站點,方便搜索引擎搜索。經過添加語言前綴,你就能夠爲每一種語言提供索引,而不是全部語言用一種索引。而且, 把 URLs 爲不一樣語言,你就能夠提供給搜索引擎更好的搜索序列。

把語言前綴添加到 URLs 模式中

Django 容許你能夠把語言前綴添加到 URLs 模式中。舉個例子,網站的英文版能夠服務於 /en/ 起始路徑之下,西班牙語版服務於 /es/ 起始路徑之下。

爲了在你的 URLs 模式中使用不一樣語言,你必須確保 settings.py 中的 MIDDLEWARE_CLASSES 設置中有 django.middleware.localMiddlewar 。Django 將會使用它來辨別當前請求中的語言。

讓咱們把語言前綴添加到 URLs 模式中。編輯 myshop 項目的 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 模式結合起來,這樣一些模式就會包含語言前綴另外一些就不會包含。儘管,最好仍是使用翻譯後的 URLs 來避免 URL 匹配一個未翻譯的 URL 模式的可能。

打開開發服務器,訪問 http://127.0.0.1:8000/ ,由於你在 Django 中使用 LocaleMiddleware 來執行 How Django determines the current language 中描述的步驟來決定當前語言,而後它就會把你重定向到包含相同語言前綴的 URL。看看瀏覽器中 URL ,它看起來像這樣:http://127.0.0.1:8000/en/.當前語言將會被設置在瀏覽器的 Accept-Language 頭中,設爲英語或者西班牙語或者是 LANGUAGE_OCDE(English) 中的默認設置。

翻譯 URL 模式

Django 支持 URL 模式中有翻譯了的字符串。你能夠爲每一種語言使用不一樣的 URL 模式。你可使用 ugettet_lazy() 函數標記 URL 模式中須要翻譯的字符串。

編輯 myshop 項目中的 urls.py 文件,把翻譯字符串添加進 cart,orders,payment,coupons 的 URLs 模式的正則表達式中:

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 應用的 urls.py 文件,標記須要翻譯的 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 應用的 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 應用的 URL 模式,由於它們是用變量建立的,並且也沒有包含其餘的任何字符。

打開 shell ,運行下面的命令來把新的翻譯更新到信息文件:

django-admin makemessages --all

確保開發服務器正在運行中,訪問:http://127.0.0.1:8000/en/rosetta/ ,點擊Spanish下的Myshop 連接。你可使用顯示過濾器(display filter)來查看沒有被翻譯的字符串。確保你的 URL 翻譯有正則表達式中的特殊字符。翻譯 URLs 是一個精細活兒;若是你替換了正則表達式,你可能會破壞 URL。

容許用戶切換語言

由於咱們正在提供多語種服務,咱們應當讓用戶能夠選擇站點的語言。咱們將會爲咱們的站點添加語言選擇器。語言選擇器由可用的語言列表組成,咱們使用連接來展現這些語言選項:

編輯 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>

咱們是這樣建立咱們的語言選擇器的:

1. 首先使用 `{% load i18n %}` 加載國際化
2. 使用 `{% get_current_language %}` 標籤來檢索當前語言
3. 使用 `{% get_available_languages %}` 模板(template)標籤來過去 `LANGUAGES` 中定義語言
4. 使用 `{% get_language_info_list %}` 來提供簡易的語言屬性鏈接入口
5. 咱們建立了一個 HTML 列表來展現全部的可用語言列表而後咱們爲當前激活語言添加了 `selected` 屬性

咱們使用基於項目設置中語言變量的 i18n提供的模板(template)標籤。如今訪問:http://127.0.0.1:8000/` ,你應該能在站點頂部的右邊看到語言選擇器:

django-9-8

用戶如今能夠輕易的切換他們的語言了。

使用 django-parler 翻譯模型(models)

Django 沒有提供開箱即用的模型(models)翻譯的解決辦法。你必需要本身實現你本身的解決辦法來管理不一樣語言中的內容或者使用第三方模塊來管理模型(model)翻譯。有幾種第三方應用容許你翻譯模型(model)字段。每一種手採用了不一樣的方法保存和鏈接翻譯。其中一種應用是 django-parler 。這個模塊提供了很是有效的辦法來翻譯模型(models)而且它和 Django 的管理站點整合的很是好。

django-parler 爲每個模型(model)生成包含翻譯的分離數據庫表。這個表包含了全部的翻譯的字段和源對象的外鍵翻譯。它也包含了一個語言字段,一位內每一行都會保存一個語言的內容。

安裝 django-parler

使用 pip 安裝 django-parler :

pip install django-parler==1.5.1

編輯項目中 settings.py ,把 parler 添加到 INSTALLED_APPS 中,一樣也把下面的配置添加到配置文件中:

PARLER_LANGUAGES = {
    None: (
        {'code': 'en',},
        {'code': 'es',},
    ),
    'default': {
        'fallback': 'en',
        'hide_untranslated': False,
    }
}

這個設置爲 django-parler 定義了可用語言 enes 。咱們指定默認語言爲 en ,而後咱們指定 django-parler 應該隱藏未翻譯的內容。

翻譯模型(model)字段

讓咱們爲咱們的產品目錄添加翻譯。 django-parler 提供了 TranslatedModel 模型(model)類和 TranslatedFields 閉包(wrapper)來翻譯模型(model)字段。編輯 shop 應用路徑下的 models.py 文件,添加如下導入:

from parler.models import TranslatableModel, TranslatedFields

而後更改 Category 模型(model)來使 nameslug 字段能夠被翻譯。
咱們依然保留了每行未翻譯的字段:

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。而且 nameslug 字段都被引入到了 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 爲每一個可翻譯模型(model)生成了一個模型(model)。在下面的圖片中,你能夠看到 Product 字段和生成的 ProductTranslation 模型(model):

django-9-9

django-parler 生成的 ProductTranslation 模型(model)有 name,slug,description 可翻譯字段,一個 language_code 字段,和主要的 Product 對象的 ForeignKey 字段。ProductProductTranslation 之間有一個一對多關係。一個 ProductTranslation 對象會爲每一個可用語言生成 Product 對象。

由於 Django 爲翻譯都使用了相互獨立的表,因此有一些 Django 的特性咱們是用不了。使用翻譯後的字段來默認排序是不可能的。你能夠在查詢集(QuerySets)中使用翻譯字段來過濾,可是你不能夠在 ordering Meta 選項中引入可翻譯字段。編輯 shop 應用的 models.py ,而後註釋掉 Category Meta 類的 ordering 屬性:

class Meta:
    # ordering = ('name',)
    verbose_name = 'category'
    verbose_name_plural = 'categories'

咱們也必須註釋掉 Product Meta 類的 index_together 屬性,由於當前的 django-parler 的版本不支持對它的驗證。編輯 Product Meta

class Meta:
    ordering = ('-created',)
    # index_together = (('id', 'slug'),)

你能夠在這個網站閱讀更多 django-parler 兼容的有關內容:
http://django-parler.readthedocs.org/en/latest/compatibility.html 。

建立一次定製的遷移

當你建立新的翻譯模型(models)時,你須要執行 makemessages 來生成模型(models)的遷移,而後把變動同步到數據庫中。儘管當使已存在字段可翻譯化時,你或許有想要在數據庫中保留的數據。咱們將遷移當前數據到新的翻譯模型(models)中。所以,咱們添加了翻譯字段可是有意保存了原始字段。

翻譯添加到當前字段的步驟以下:

1. 在保留原始字段的狀況下,建立新翻譯模型(model)的遷移
2. 建立定製化遷移,從當前已有的字段中拷貝一份數據到翻譯模型(models)中
3. 從源模型(models)中刪除字段

運行下面的命令來爲咱們添加到 CategoryProduct 模型(model)中的翻譯字段建立遷移:

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

遷移已有數據

如今咱們須要建立定製遷移來把已有數據拷貝到新的翻譯模型(model)中。使用這個命令建立一個空的遷移:

python manage.py makemigrations --empty shop --name "migrate_translatable_fields"

你將會看到以下輸出:

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

這段遷移包括了用於執行應用和反轉遷移的 forwards_func()backwards_func()

遷移工做流程以下:

1. 咱們在 `translatable_models` 字典中定義了模型(model)和它們的可翻譯字段
2. 爲了應用遷移,咱們使用 `app.get_model()` 來迭代包含翻譯的模型(model)來獲得這個模型(model)和其可翻譯的模型(model)
3. 咱們在數據庫中迭代全部的當前對象,而後爲定義在項目設置中的 `LANGUAGE_CODE` 建立一個翻譯對象。咱們引入了 `ForeignKey` 到源對像和一份從源字段中可翻譯字段的拷貝。

backwards 函數執行的是反轉操做,它檢索默認的翻譯對象,而後把可翻譯字段的值拷貝到新的模型(model)中。

最後,咱們須要刪除咱們再也不須要的源字段。編輯 shop 應用的 models.py ,而後刪除 Category 模型(model)的 nameslug 字段:

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)的 nameslug 字段:

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)

如今咱們必需要建立最後一次遷移來應用其中的變動。若是咱們嘗試 manage.py 工具,咱們將會獲得一個錯誤,由於咱們尚未讓管理站點適應翻譯模型(models)。讓咱們先來修整一下管理站點。

在管理站點中整合翻譯

Django-parler 很好和 Django 的管理站點相融合。它用 TranslatableAdmin 重寫了 Django 的 ModelAdmin 類 來管理翻譯。

編輯 shop 應用的 admin.py 文件,添加下面的庫:

from parler.admin import TranslatableAdmin

CategoryAdminProductAdmin 的繼承類改成 TranslatableAdmin 取代原來的 ModelAdmin。 Django-parler 如今還不支持 prepopulated_fields 屬性,可是它支持 get_prepopulated_fields() 方法來提供相同的功能。讓咱們據此作一些改變。 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)

咱們已經使管理站點可以和新的翻譯模型(model)工做了。如今咱們就能把變動同步到數據庫中了。

應用翻譯模型(model)遷移

咱們在變動管理站點以前已經從模型(model)中刪除了舊的字段。如今咱們須要爲此次變動建立遷移。打開 shell ,運行下面的命令:

python manage.py makemigrations shop --name "remove_untranslated_fields"

你將會看到以下輸出:

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

遷移以後,咱們就已經刪除了源字段保留了可翻譯字段。

總結一下,咱們建立了如下遷移:

1. 將可翻譯字段添加到模型(models)中
2. 將源字段中的數據遷移到可翻譯字段中
3. 從源模型(models)中刪除源字段

運行下面的命令來應用咱們建立的遷移:

python manage.py migrate shop

你將會看到以下輸出:

Applying shop.0002_add_translation_model... OK
Applying shop.0003_migrate_translatable_fields... OK
Applying shop.0004_remove_untranslated_fields... OK

咱們的模型(models)如今已經同步到了數據庫中。讓咱們來翻譯一個對象。

用命令 pythong manage.py runserver 運行開發服務器,在瀏覽器中訪問:http://127.0.0.1:8000/en/admin/shop/category/add/ 。你就會看到包含兩個標籤的 Add category 頁,一個標籤是英文的,一個標籤是西班牙語的。

django-9-10

你如今能夠添加一個翻譯而後點擊Save按鈕。確保你在切換標籤以前保存了他們,否則讓門就會丟失。

使視圖(views)適應翻譯

咱們要使咱們的 shop 視圖(views)適應咱們的翻譯查詢集(QuerySets)。在命令行運行 python manage.py shell ,看看你是如何檢索和查詢翻譯字段的。爲了從當前激活語言中獲得一個字段的內容,你只須要用和鏈接通常字段相同的辦法鏈接這個字段:

>>> from shop.models import Product
>>> product=Product.objects.first()
>>> product.name
'Black tea'

當你鏈接到被翻譯了字段時,他們已經被用當前語言處理過了。你能夠爲一個對象設置不一樣的當前語言,這樣你就能夠得到指定的翻譯了:

>>> product.set_current_language('es')
>>> product.name
'Té negro'
>>> product.get_current_language()
'es

當使用 filter() 執行一次查詢集(QuerySets)時,你可使用 translations__ 語法篩選相關聯對象:

>>> Product.objects.filter(translations__name='Black tea')
[<Product: Black tea>]

你也可使用 language() 管理器來爲每一個檢索的對象指定特定的語言:

>>> Product.objects.language('es').all()
[<Product: Té negro>, <Product: Té en polvo>, <Product: Té rojo>,
<Product: Té verde>]

如你所見,鏈接到翻譯字段的方法仍是很直接的。

讓咱們修改一下產品目錄的視圖(views)吧。編輯 shop 應用的 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)

如今 product_listproduct_detail 已經可使用翻譯字段來檢索了。運行開發服務器,訪問:http://127.0.0.1:8000/es/ ,你能夠看到產品列表頁,包含了全部被翻譯爲西班牙語的產品:

django-9-11

如今每一個產品的 URLs 已經使用 slug 字段被翻譯爲了的當前語言。好比,一個西班牙語產品的 URL 爲:http://127.0.0.1:8000/es/2/te-rojo/ ,可是它的英文 URL 爲:http://127.0.0.1:8000/en/2/red-tea/ 。若是你導航到產品詳情頁,你能夠看到用被選語言翻譯後的 URL 和內容,就像下面的例子:

django-9-12

若是你想要更多瞭解 django-parler ,你能夠在這個網站找到全部的文檔:http//django-parler.readthedocs.org/en/latest/ 。

你已經學習瞭如何翻譯 Python 代碼,模板(template),URL 模式,模型(model)字段。爲了完成國際化和本地化的工做,咱們須要展現本地化格式的時間和日期、以及數字。

本地格式化

基於用戶的語言,你可能想要用不一樣的格式展現日期、時間和數字。本第格式化可經過修改 settings.py 中的 USE_L1ON 設置爲 True 來使本地格式化生效。

USE_L1ON 可用時,Django 將會在模板(template)任何輸出一個值時嘗試使用某一語言的特定格式。你能夠看到你的網站中用一個點來作分隔符的十進制英文數字,儘管在西班牙語版中他們使用逗號來作分隔符。這和 Django 裏爲 es 指定的語言格式。你能夠在這裏查看西班牙語的格式配置:https://github.com/django/django/blob/stable/1.8.x/django/conf/locale/es/formats.py 。

一般,你把 USE_L10N 的值設爲 True 而後 Django 就會爲每一種語言應用本地化格式。雖然在某些狀況下你有不想要被格式化的值。這和輸出特別相關, JavaScript 或者 JSON 必需要提供機器可讀的格式。

Django 提供了 {% localize %} 模板(template)表標籤來讓你能夠開啓或者關閉模板(template)的本地化。這使得你能夠控制本地格式化。你須要載入 l10n 標籤來使用這個模板(template)標籤。下面的例子是如何在模板(template)中開啓和關閉它:

{% load l10n %}

{% localize on %}
{{ value }}
{% endlocalize %}

{% localize off %}
{{ value }}
{% endlocalize %}

Django 一樣也提供了 localizeunlocalize 模板(template)過濾器來強制或取消一個值的本地化。過濾器能夠像下面這樣使用:

{{ value|localize }}
{{ value|unlocalize }}

你也能夠建立定製化的格式文件來指定特定語言的格式。你能夠在這個網站找到全部關於本第格式化的信息:https://docs.djangoproject.com/en/1.8/topics/i18n/formatting/ 。

使用 django-localflavor 來驗證表單字段

django-localflavor 是一個包含特殊工具的第三方模塊,好比它的有些表單字段或者模型(model)字段對於每一個國家是不一樣的。它對於驗證本地地區,本地電話號碼,身份證號,社保號等等都是很是有用的。這個包被集成進了一系列以 ISO 3166 國家代碼命名的模塊裏。

使用下面的命令安裝 django-localflavor :

pip install django-localflavor==1.1

編輯項目中的 settings.py ,把 localflavor 添加進 INSTALLED_APPS 設置中。

咱們將會添加美國(U.S.)郵政編碼字段,這樣以後建立一個新的訂單就須要一個美國郵政編碼了。

編輯 orders 應用的 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',]

咱們從 localflavorus 包中引入了 USZipCodeField 而後將它應用在 OrderCreateFormpostal_code 字段中。訪問:http://127.0.0.1:8000/en/orders/create/ 而後嘗試輸入一個 3 位郵政編碼。你將會獲得 USZipCodeField 引起的驗證錯誤:

Enter a zip code in the format XXXXX or XXXXX-XXXX.

這只是一個在你的項目中使用定製字段來達到驗證目的的簡單例子,localflavor 提供的本地組件對於讓你的項目適應不一樣的國家是很是有用的。你能夠閱讀 django-localflavor 的文檔,看看全部的可用地區組件:https://django-localflavor.readthedocs.org/en/latest/ 。

下面,咱們將會爲咱們的店鋪建立一個推薦引擎。

建立一個推薦引擎

推薦引擎是一個能夠預測用戶對於某一物品偏好和比率的系統。這個系統會基於用戶的行爲和它對於用戶的瞭解選出相關的商品。現在,推薦系統用於許多的在線服務。它們幫助用戶從大量的相關數據中挑選他們可能感興趣的產品。提供良好的推薦能夠鼓勵用戶更多的消費。電商網站受益於日益增加的相關產品推薦銷售中。

咱們將會建立一個簡單但強大的推薦引擎來推薦常常一塊兒購買的商品。咱們將基於歷史銷售來推薦商品,這樣就能夠辨別出哪些商品一般是一塊兒購買的了。咱們將會在兩種不一樣的場景下推薦互補的產品:

  • 產品詳情頁:咱們將會展現和所給商品常常一塊兒購買的產品列表。好比像這樣展現:Users who bought this also bought X, Y, Z(購買了這個產品的用戶也購買了X, Y,Z)。咱們須要一個數據結構來讓咱們能儲被展現產品中和每一個產品一塊兒購買的次數。
  • 購物車詳情頁:基於用戶添加到購物車當中的產品,咱們將會推薦常常和他們一塊兒購買的產品。在這樣的狀況下,咱們計算的包含相關產品的評分必定要相加。

咱們將會使用 Redis 來儲存被一塊兒購買的產品。記得你已經在第六章 跟蹤用戶操做 使用了 Redis 。若是你尚未安裝 Redis ,你能夠在那一章找到安裝指導。

推薦基於歷時購物的產品

如今,讓咱們推薦給用戶一些基於他們添加到購物車的產品。咱們將會在 Redis 中爲每一個產品儲存一個鍵(key)。產品的鍵將會和它的評分一同儲存在 Redis 的有序集中。在一次新的訂單被完成時,咱們每次都會爲一同購買的產品的評分加一。

當一份訂單付款成功後,咱們保存每一個購買產品的鍵,包括贊成訂單中的有序產品集。這個有序集讓咱們能夠爲一塊兒購買的產品打分。

編輯項目中的額 settings.py ,添加如下設置:

REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 1

這裏的設置是爲了和 Redis 服務器創建鏈接。在 shop 應用內建立一個新的文件,命名爲 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 類使咱們能夠儲存購買的產品而後檢索所給產品的產品推薦。get_product_key() 方法檢索 Product 對象的 id ,而後爲儲存產品的有序集建立一個 Redis 鍵(key),看起來像這樣:product:[id]:purchased_with

products_bought() 方法檢索被一塊兒購買的產品列表(它們都屬於同一個訂單)。在這個方法中,咱們執行如下幾個任務:

1. 獲得所給 `Product` 對象的產品 ID
2. 迭代全部的產品 ID。對於每一個 `id` ,咱們迭代全部的產品 ID 而且跳過全部相同的產品,這樣咱們就能夠獲得和每一個產品一塊兒購買的產品。
3. 咱們使用 `get_product_id()` 方法來獲取 Redis 產品鍵。對於一個 ID 爲 33 的產品,這個方法返回鍵 `product:33:purchased_with` 。這個鍵用於包含和這個產品被一同購買的產品 ID 的有序集。
4. 咱們把有序集中的每一個產品 `id` 的評分加一。評分表示另外一個產品和所給產品一塊兒購買的次數。

咱們有一個方法來保存和對一同購買的產品評分。如今咱們須要一個方法來檢索被所給購物列表的一塊兒購買的產品。把 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() 方法接收下列參數:

  • products:這是一個 Product 對象列表。它能夠包含一個或者多個產品
  • max_results:這是一個整數,用於展現返回的推薦的最大數量

在這個方法中,咱們執行如下的動做:

1. 獲得所給 `Product` 對象的 ID
2. 若是隻有一個產品,咱們就檢索一同購買的產品的 ID,並按照他們的購買時間來排序。這樣作,咱們就可使用 Redis 的 `ZRANGE` 命令。咱們經過 `max_results` 屬性來限制結果的數量。
3. 若是有多於一個的產品被給予,咱們就生成一個臨時的和產品 ID 一同建立的 Redis 鍵。
4. 咱們把包含在每一個所給產品的有序集中東西的評分組合並相加,咱們使用 Redis 的 `ZUNIONSTORE` 命令來實現這個操做。`ZUNIONSTORE` 命令執行了對有序集的所給鍵的求和,而後在新的 Redis 鍵中保存每一個元素的求和。你能夠在這裏閱讀更多關於這個命令的信息:http://redisio/commands/ZUNIONSTORE 。咱們在臨時鍵中保存分數的求和。
5. 由於咱們已經求和了評分,咱們或許會獲取咱們推薦的重複商品。咱們就使用 `ZREM` 命令來把他們從生成的有序集中刪除。
6. 咱們從臨時鍵中檢索產品 ID,使用 `ZREM` 命令來按照他們的評分排序。咱們把結果的數量限制在 `max_results` 屬性指定的值內。而後咱們刪除了臨時鍵。
7. 最後,咱們用所給的 `id` 獲取 `Product` 對象,而且按照一樣的順序來排序。

爲了更加方便使用,讓咱們添加一個方法來清除全部的推薦。
把下列方法添加進 Recommender 類中:

def clear_purchases(self):
    for id in Product.objects.values_list('id', flat=True):
        r.delete(self.get_product_key(id))

讓咱們試試咱們的推薦引擎。確保你在數據庫中引入了幾個 Product 對象而且在 shell 中使用了下面的命令來初始化 Redis 服務器:

src/redis-server

打開另一個 shell ,執行 python manage.py shell ,輸入下面的代碼來檢索幾個產品:

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

而後,添加一些測試購物到推薦引擎中:

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

咱們已經保存了下面的評分:

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)

讓咱們看看爲單一產品的推薦吧:

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

正如你所看到的那樣,推薦產品的順序正式基於他們的評分。讓咱們來看看爲多個產品的推薦吧:

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

你能夠看到推薦產品的順序和評分總和的順序是同樣的。好比 black_tea, red_tea, tea_powder(2+1)green_tea(1=1) 的產品推薦就是這樣。

咱們必須保證咱們的推薦算法按照預期那樣工做。讓咱們在咱們的站點上展現咱們的推薦吧。

編輯 shop 應用的 views.py ,添加如下庫:

from .recommender import Recommender

把下面的代碼添加進 product_detail 視圖(view)中的 render() 以前:

r = Recommender()
recommended_products = r.suggest_products_for([product], 4)

咱們獲得了四個最多產品推薦。 product_detail 視圖(view)如今看起來像這樣:

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

如今編輯 shop 應用的 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 %}

運行開發服務器,訪問:http://127.0.0.1:8000/en/ 。點擊一個產品來查看它的詳情頁。你應該看到展現在下方的推薦產品,就象這樣:

django-9-13

咱們也將在購物車當中引入產品推薦。產品推薦將會基於用戶購物車中添加的產品。編輯 cart 應用的 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 應用的 cart/detail.html ,把下列代碼添加在 <table>HTML 標籤後面:

{% 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/ ,在購物車當中添加一些商品。東航擬導航向 http://127.0.0.1:8000/en/cart/ 時,你能夠在購物車下看到銳減的產品,就像下面這樣:

django-9-14

恭喜你!你已經使用 Django 和 Redis 構建了一個完整的推薦引擎。

總結

在這一章中,你使用會話建立了一個優惠券系統。你學習了國際化和本地化是如何工做的。你也使用 Redis 構建了一個推薦引擎。

在下一章中,你將開始一個新的項目。你將會用 Django 建立一個在線學習平臺,而且使用基於類的視圖(view)。你將學會建立一個定製的內容管理系統(CMS)。

(譯者@夜夜月:- -下一章又輪到我了。。。。。。放出時間不固定。。。。。。)

相關文章
相關標籤/搜索