全文鏈接
第一章 創建一個blog應用
第二章 使用高級特性來增強你的blog
第三章 擴展你的blog應用
第四章上 創建一個社交網站
第四章下 創建一個社交網站
第五章 在你的網站中分享內容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴展你的商店
第九章下 擴展你的商店
第十章上 創建一個在線學習平臺
第十章下 創建一個在線學習平臺
第十一章 緩存內容
第十二章 構建一個API
書籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé
(審校@夜夜月:本章分上下兩部分,這里是上半部。)
(譯者@ucag 注:哈哈哈,第九章終于來啦。這是在線商店的最后一章,下一章將會開始一個新的項目。所以這一章的內容會偏難,最難的是推薦引擎的編寫,其中的算法可能還需要各位好好的推敲,如果看了中文的看不懂,大家可以去看看英文原版的書以及相關的文檔。最近我也在研究機器學習,有興趣的大家可以一起交流哈~)
(審校@夜夜月:大家好,我是來打醬油的~,粗校,主要更正了一些錯字和格式,精校正進行到四章樣子。)
第九章(上)
擴展你的商店
在上一章中,你學習了如何把支付網關整合進你的商店。你處理了支付通知,學會了如何生成 CSV 和 PDF 文件。在這一章中,你會把優惠券添加進你的商店中。你將學到國際化(internationalization)和本地化(localization)是如何工作的,你還會創建一個推薦引擎。
在這一章中將會包含一下知識點:
- 創建一個優惠券系統來應用折扣
- 把國際化添加進你的項目中
- 使用 Rosetta 來管理翻譯
- 使用 django-parler 來翻譯模型(model)
- 建立一個產品推薦引擎
創建一個優惠券系統
很多的在線商店會送出很多優惠券,這些優惠券可以在顧客的采購中兌換為相應的折扣。在線優惠券通常是由一串給顧客的代碼構成,這串代碼在一個特定的時間段內是有效的。這串代碼可以被兌換一次或者多次。
我們將會為我們的商店創建一個優惠券系統。優惠券將會在顧客在某一個特定的時間段內輸入時生效。優惠券沒有任何兌換數的限制,他們也可用于購物車的總金額中。對于這個功能,我們將會創建一個模型(model)來儲存優惠券代碼,優惠券有效的時間段,以及折扣的力度。
在 myshop
項目內使用如下命令創建一個新的應用:
python manage.py startapp coupons
編輯 myshop
的 settings.py
文件,像下面這樣把把應用添加到 INSTALLED_APPS
中:
INSTALLED_APPS = (
# ...
'coupons',
)
新的應用已經在我們的 Django 項目中激活了。
創建優惠券模型(model)
讓我們開始創建 Coupon
模型(model)。編輯 coupons
應用中的 models.py
文件,添加以下代碼:
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 。你可以看見下面的表單:
填寫表單創建一個在當前日期有效的新優惠券,確保你點擊了 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
函數,就會返回一個帶有給定id
的Coupon
對象 -
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 ,向購物車當中添加一個商品,然后在表單中輸入你創建的優惠代碼來應用你的優惠券。你可以看到購物車像下面這樣展示優惠券折扣:
讓我們把優惠券添加到購物流程中的下一步。編輯 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/ 。你會看到包含使用了優惠券的訂單小計:
用戶現在可以在購物車當中使用優惠券了。盡管,當用戶結賬時,我們依然需要在訂單中儲存優惠券信息。
在訂單中使用優惠券
我們會儲存每張訂單中使用的優惠券。首先,我們需要修改 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
對象是否包含了優惠券和折扣,如下:
你也可以更改管理界面的訂單詳情模板(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_I18N
為True
才能生效。你可以在這個網站找到一個合法的語言 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.localMiddleware
的 LocaleMiddleware
,它執行下面的任務:
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-us
或 en-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
:對應語言的翻譯,默認為空。這是你輸入所給字符串翻譯的地方。
按照如下,根據所給的 msgid
在 msgtsr
中填寫翻譯:
#: 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
屬性作為每個字段的標簽。我們將會翻譯 cart
和 coupons
應用的表單。
編輯 cart
應用路徑下的 forms.py
文件,給 CartAddProductForm
的 quantity
字段添加一個 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
(審校@夜夜月:因為第九章過長,所以分成上下兩章,目前上半章結束。)