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

全文鏈接

第一章 創(chuàng)建一個blog應(yīng)用
第二章 使用高級特性來增強(qiáng)你的blog
第三章 擴(kuò)展你的blog應(yīng)用
第四章上 創(chuàng)建一個社交網(wǎng)站
第四章下 創(chuàng)建一個社交網(wǎng)站
第五章 在你的網(wǎng)站中分享內(nèi)容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴(kuò)展你的商店
第九章下 擴(kuò)展你的商店
第十章上 創(chuàng)建一個在線學(xué)習(xí)平臺
第十章下 創(chuàng)建一個在線學(xué)習(xí)平臺
第十一章 緩存內(nèi)容
第十二章 構(gòu)建一個API

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

(審校@夜夜月:本章分上下兩部分,這里是下半部。)

(譯者@ucag 注:哈哈哈,第九章終于來啦。這是在線商店的最后一章,下一章將會開始一個新的項目。所以這一章的內(nèi)容會偏難,最難的是推薦引擎的編寫,其中的算法可能還需要各位好好的推敲,如果看了中文的看不懂,大家可以去看看英文原版的書以及相關(guān)的文檔。最近我也在研究機(jī)器學(xué)習(xí),有興趣的大家可以一起交流哈~)

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

第九章(下)

使用 Rosetta 翻譯交互界面

Rosetta 是一個讓你可以編輯翻譯的第三方應(yīng)用,它有類似 Django 管理站點的交互界面。Rosetta 讓編輯 .po 文件和更新它變得很簡單,讓我們把它添加進(jìn)項目中:

pip install django-rosetta==0.7.6

然后,把 rosetts 添加進(jìn)項目中的setting.py文件中的INSTALLED_APPS 設(shè)置中。

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

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

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

訪問 http://127.0.0.1:8000/admin/ ,然后使用超級管理員賬戶登錄。然后導(dǎo)航到 http://127.0.0.1:8000/rosetta/ 。你可以看到當(dāng)前語言列表:

django-9-5

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

django-9-6

你可以在 Spanish 行下輸入翻譯。Occurences 行展示了代碼中翻譯字符串被找到的文件和行數(shù)、

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

django-9-7

Rosetta 使用了不同的背景色來展示占位符。當(dāng)你翻譯內(nèi)容時,確保你沒有翻譯占位符。比如,用下面這個字符串舉例:

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

它翻譯為西班牙語之后長得像這樣:

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

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

當(dāng)你完成編輯翻譯后,點擊Save and translate next block 按鈕來保存翻譯到 .po 文件中。Rosetta 在你保存翻譯時編輯信息文件,所以并不需要你運行 compilemessages 命令。盡管,Rosetta 需要 locale 路徑的寫入權(quán)限來寫入信息文件。確保路徑有有效權(quán)限。

如果你想讓其他用戶編輯翻譯,訪問:http://127.0.0.1:8000/admin/auth/group/add/,然后創(chuàng)建一個名為 translations 的新組。當(dāng)編輯一個用戶時,在Permissions模塊下,把 translations 組添加進(jìn)ChosenGroups中。Rosetta 只對超級用戶和 translations 中的用戶是可用的。

你可以在這個網(wǎng)站閱讀 Rosetta 的文檔:http://django-rosetta.readthedocs.org/en/latest/

當(dāng)你在生產(chǎn)環(huán)境中添加新的翻譯時,如果你的 Django 運行在一個真實服務(wù)器上,你必須在運行 compilemessages 或保存翻譯之后重啟你的服務(wù)器來讓 Rosetta 的更改生效。

惰性翻譯

你或許已經(jīng)注意到了 Rosetta 有 Fuzzy 這一行。這不是 Rosetta 的特性,它是由 gettext 提供的。如果翻譯的 flag 是激活的,那么它就不會被包含進(jìn)編譯后的信息文件中。flag 用于需要翻譯器修訂的翻譯字符串。當(dāng) .po 文件在更新新的翻譯字符串時,一些翻譯字符串可能被自動標(biāo)記為 fuzzy. 當(dāng) gettext 找到一些變動不大的 msgid 時就會發(fā)生這樣的情況,gettext 就會把它認(rèn)為的舊的翻譯和匹配在一起然后會在回把它標(biāo)記為 fuzzy 以用于回查。翻譯器之后會回查模糊翻譯,會移除 fuzzy 標(biāo)簽然后再次編譯信息文件。

國際化的 URL 模式

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

  • URL 模式中的語言前綴:把語言的前綴添加到 URLs 當(dāng)中,以便在不同的基本 URL 下提供每一種語言的版本。
  • 翻譯后的 URL 模式:標(biāo)記要翻譯的 URL 模式,這樣同樣的 URL 就可以服務(wù)于不同的語言。

翻譯 URLs 的原因是這樣就可以優(yōu)化你的站點,方便搜索引擎搜索。通過添加語言前綴,你就可以為每一種語言提供索引,而不是所有語言用一種索引。并且, 把 URLs 為不同語言,你就可以提供給搜索引擎更好的搜索序列。

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

Django 允許你可以把語言前綴添加到 URLs 模式中。舉個例子,網(wǎng)站的英文版可以服務(wù)于 /en/ 起始路徑之下,西班牙語版服務(wù)于 /es/ 起始路徑之下。

為了在你的 URLs 模式中使用不同語言,你必須確保 settings.py 中的 MIDDLEWARE_CLASSES 設(shè)置中有 django.middleware.localMiddlewar 。Django 將會使用它來辨別當(dāng)前請求中的語言。

讓我們把語言前綴添加到 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 模式結(jié)合起來,這樣一些模式就會包含語言前綴另一些就不會包含。盡管,最好還是使用翻譯后的 URLs 來避免 URL 匹配一個未翻譯的 URL 模式的可能。

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

翻譯 URL 模式

Django 支持 URL 模式中有翻譯了的字符串。你可以為每一種語言使用不同的 URL 模式。你可以使用 ugettet_lazy() 函數(shù)標(biāo)記 URL 模式中需要翻譯的字符串。

編輯 myshop 項目中的 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 模式,因為它們是用變量創(chuàng)建的,而且也沒有包含其他的任何字符。

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

django-admin makemessages --all

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

允許用戶切換語言

因為我們正在提供多語種服務(wù),我們應(yīng)當(dā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 %}` 來提供簡易的語言屬性連接入口
5. 我們創(chuàng)建了一個 HTML 列表來展示所有的可用語言列表然后我們?yōu)楫?dāng)前激活語言添加了 `selected` 屬性

我們使用基于項目設(shè)置中語言變量的 i18n提供的模板(template)標(biāo)簽。現(xiàn)在訪問:http://127.0.0.1:8000/` ,你應(yīng)該能在站點頂部的右邊看到語言選擇器:

django-9-8

用戶現(xiàn)在可以輕易的切換他們的語言了。

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

Django 沒有提供開箱即用的模型(models)翻譯的解決辦法。你必須要自己實現(xiàn)你自己的解決辦法來管理不同語言中的內(nèi)容或者使用第三方模塊來管理模型(model)翻譯。有幾種第三方應(yīng)用允許你翻譯模型(model)字段。每一種手采用了不同的方法保存和連接翻譯。其中一種應(yīng)用是 django-parler 。這個模塊提供了非常有效的辦法來翻譯模型(models)并且它和 Django 的管理站點整合的非常好。

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

**安裝 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,
    }
}

這個設(shè)置為 django-parler 定義了可用語言 enes 。我們指定默認(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)來使 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 之間有一個一對多關(guān)系。一個 ProductTranslation 對象會為每個可用語言生成 Product 對象。

因為 Django 為翻譯都使用了相互獨立的表,所以有一些 Django 的特性我們是用不了。使用翻譯后的字段來默認(rèn)排序是不可能的。你可以在查詢集(QuerySets)中使用翻譯字段來過濾,但是你不可以在 ordering Meta 選項中引入可翻譯字段。編輯 shop 應(yīng)用的 models.py ,然后注釋掉 Category Meta 類的 ordering 屬性:

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

我們也必須注釋掉 Product Meta 類的 index_together 屬性,因為當(dāng)前的 django-parler 的版本不支持對它的驗證。編輯 Product Meta

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

你可以在這個網(wǎng)站閱讀更多 django-parler 兼容的有關(guān)內(nèi)容:
http://django-parler.readthedocs.org/en/latest/compatibility.html

創(chuàng)建一次定制的遷移

當(dāng)你創(chuàng)建新的翻譯模型(models)時,你需要執(zhí)行 makemessages 來生成模型(models)的遷移,然后把變更同步到數(shù)據(jù)庫中。盡管當(dāng)使已存在字段可翻譯化時,你或許有想要在數(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)中刪除字段

運行下面的命令來為我們添加到 CategoryProduct 模型(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)在我們需要創(chuàng)建定制遷移來把已有數(shù)據(jù)拷貝到新的翻譯模型(model)中。使用這個命令創(chuàng)建一個空的遷移:

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

這段遷移包括了用于執(zhí)行應(yīng)用和反轉(zhuǎn)遷移的 forwards_func()backwards_func()

遷移工作流程如下:

1. 我們在 `translatable_models` 字典中定義了模型(model)和它們的可翻譯字段
2. 為了應(yīng)用遷移,我們使用 `app.get_model()` 來迭代包含翻譯的模型(model)來得到這個模型(model)和其可翻譯的模型(model)
3. 我們在數(shù)據(jù)庫中迭代所有的當(dāng)前對象,然后為定義在項目設(shè)置中的 `LANGUAGE_CODE` 創(chuàng)建一個翻譯對象。我們引入了 `ForeignKey` 到源對像和一份從源字段中可翻譯字段的拷貝。

backwards 函數(shù)執(zhí)行的是反轉(zhuǎn)操作,它檢索默認(rèn)的翻譯對象,然后把可翻譯字段的值拷貝到新的模型(model)中。

最后,我們需要刪除我們不再需要的源字段。編輯 shop 應(yīng)用的 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)

現(xiàn)在我們必須要創(chuàng)建最后一次遷移來應(yīng)用其中的變更。如果我們嘗試 manage.py 工具,我們將會得到一個錯誤,因為我們還沒有讓管理站點適應(yīng)翻譯模型(models)。讓我們先來修整一下管理站點。

在管理站點中整合翻譯

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

編輯 shop 應(yīng)用的 admin.py 文件,添加下面的庫:

from parler.admin import TranslatableAdmin

CategoryAdminProductAdmin 的繼承類改為 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)使管理站點能夠和新的翻譯模型(model)工作了。現(xiàn)在我們就能把變更同步到數(shù)據(jù)庫中了。

應(yīng)用翻譯模型(model)遷移

我們在變更管理站點之前已經(jīng)從模型(model)中刪除了舊的字段。現(xiàn)在我們需要為這次變更創(chuàng)建遷移。打開 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

遷移之后,我們就已經(jīng)刪除了源字段保留了可翻譯字段。

總結(jié)一下,我們創(chuàng)建了以下遷移:

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

運行下面的命令來應(yīng)用我們創(chuàng)建的遷移:

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)現(xiàn)在已經(jīng)同步到了數(shù)據(jù)庫中。讓我們來翻譯一個對象。

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

django-9-10

你現(xiàn)在可以添加一個翻譯然后點擊Save按鈕。確保你在切換標(biāo)簽之前保存了他們,不然讓門就會丟失。

使視圖(views)適應(yīng)翻譯

我們要使我們的 shop 視圖(views)適應(yīng)我們的翻譯查詢集(QuerySets)。在命令行運行 python manage.py shell ,看看你是如何檢索和查詢翻譯字段的。為了從當(dāng)前激活語言中得到一個字段的內(nèi)容,你只需要用和連接一般字段相同的辦法連接這個字段:

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

當(dāng)你連接到被翻譯了字段時,他們已經(jīng)被用當(dāng)前語言處理過了。你可以為一個對象設(shè)置不同的當(dāng)前語言,這樣你就可以獲得指定的翻譯了:

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

當(dāng)使用 filter() 執(zhí)行一次查詢集(QuerySets)時,你可以使用 translations__ 語法篩選相關(guān)聯(lián)對象:

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

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

讓我們修改一下產(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_listproduct_detail 已經(jīng)可以使用翻譯字段來檢索了。運行開發(fā)服務(wù)器,訪問:http://127.0.0.1:8000/es/ ,你可以看到產(chǎn)品列表頁,包含了所有被翻譯為西班牙語的產(chǎn)品:

django-9-11

現(xiàn)在每個產(chǎn)品的 URLs 已經(jīng)使用 slug 字段被翻譯為了的當(dāng)前語言。比如,一個西班牙語產(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-9-12

如果你想要更多了解 django-parler ,你可以在這個網(wǎng)站找到所有的文檔:http//django-parler.readthedocs.org/en/latest/ 。

你已經(jīng)學(xué)習(xí)了如何翻譯 Python 代碼,模板(template),URL 模式,模型(model)字段。為了完成國際化和本地化的工作,我們需要展示本地化格式的時間和日期、以及數(shù)字。

本地格式化

基于用戶的語言,你可能想要用不同的格式展示日期、時間和數(shù)字。本第格式化可通過修改 settings.py 中的 USE_L1ON 設(shè)置為 True 來使本地格式化生效。

當(dāng) USE_L1ON 可用時,Django 將會在模板(template)任何輸出一個值時嘗試使用某一語言的特定格式。你可以看到你的網(wǎng)站中用一個點來做分隔符的十進(jìn)制英文數(shù)字,盡管在西班牙語版中他們使用逗號來做分隔符。這和 Django 里為 es 指定的語言格式。你可以在這里查看西班牙語的格式配置:https://github.com/django/django/blob/stable/1.8.x/django/conf/locale/es/formats.py

通常,你把 USE_L10N 的值設(shè)為 True 然后 Django 就會為每一種語言應(yīng)用本地化格式。雖然在某些情況下你有不想要被格式化的值。這和輸出特別相關(guān), JavaScript 或者 JSON 必須要提供機(jī)器可讀的格式。

Django 提供了 {% localize %} 模板(template)表標(biāo)簽來讓你可以開啟或者關(guān)閉模板(template)的本地化。這使得你可以控制本地格式化。你需要載入 l10n 標(biāo)簽來使用這個模板(template)標(biāo)簽。下面的例子是如何在模板(template)中開啟和關(guān)閉它:

{% load l10n %}

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

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

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

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

你也可以創(chuàng)建定制化的格式文件來指定特定語言的格式。你可以在這個網(wǎng)站找到所有關(guān)于本第格式化的信息:https://docs.djangoproject.com/en/1.8/topics/i18n/formatting/

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

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

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

pip install django-localflavor==1.1

編輯項目中的 settings.py ,把 localflavor 添加進(jìn) INSTALLED_APPS 設(shè)置中。

我們將會添加美國(U.S.)郵政編碼字段,這樣之后創(chuàng)建一個新的訂單就需要一個美國郵政編碼了。

編輯 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',]

我們從 localflavorus 包中引入了 USZipCodeField 然后將它應(yīng)用在 OrderCreateFormpostal_code 字段中。訪問:http://127.0.0.1:8000/en/orders/create/ 然后嘗試輸入一個 3 位郵政編碼。你將會得到 USZipCodeField 引發(fā)的驗證錯誤:

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

這只是一個在你的項目中使用定制字段來達(dá)到驗證目的的簡單例子,localflavor 提供的本地組件對于讓你的項目適應(yīng)不同的國家是非常有用的。你可以閱讀 django-localflavor 的文檔,看看所有的可用地區(qū)組件:https://django-localflavor.readthedocs.org/en/latest/

下面,我們將會為我們的店鋪創(chuàng)建一個推薦引擎。

創(chuàng)建一個推薦引擎

推薦引擎是一個可以預(yù)測用戶對于某一物品偏好和比率的系統(tǒng)。這個系統(tǒng)會基于用戶的行為和它對于用戶的了解選出相關(guān)的商品。如今,推薦系統(tǒng)用于許多的在線服務(wù)。它們幫助用戶從大量的相關(guān)數(shù)據(jù)中挑選他們可能感興趣的產(chǎn)品。提供良好的推薦可以鼓勵用戶更多的消費。電商網(wǎng)站受益于日益增長的相關(guān)產(chǎn)品推薦銷售中。

我們將會創(chuàng)建一個簡單但強(qiáng)大的推薦引擎來推薦經(jīng)常一起購買的商品。我們將基于歷史銷售來推薦商品,這樣就可以辨別出哪些商品通常是一起購買的了。我們將會在兩種不同的場景下推薦互補的產(chǎn)品:

  • 產(chǎn)品詳情頁:我們將會展示和所給商品經(jīng)常一起購買的產(chǎn)品列表。比如像這樣展示:Users who bought this also bought X, Y, Z(購買了這個產(chǎn)品的用戶也購買了X, Y,Z)。我們需要一個數(shù)據(jù)結(jié)構(gòu)來讓我們能儲被展示產(chǎn)品中和每個產(chǎn)品一起購買的次數(shù)。
  • 購物車詳情頁:基于用戶添加到購物車當(dāng)中的產(chǎn)品,我們將會推薦經(jīng)常和他們一起購買的產(chǎn)品。在這樣的情況下,我們計算的包含相關(guān)產(chǎn)品的評分一定要相加。

我們將會使用 Redis 來儲存被一起購買的產(chǎn)品。記得你已經(jīng)在**第六章 跟蹤用戶操作 **使用了 Redis 。如果你還沒有安裝 Redis ,你可以在那一章找到安裝指導(dǎo)。

推薦基于歷時購物的產(chǎn)品

現(xiàn)在,讓我們推薦給用戶一些基于他們添加到購物車的產(chǎn)品。我們將會在 Redis 中為每個產(chǎn)品儲存一個鍵(key)。產(chǎn)品的鍵將會和它的評分一同儲存在 Redis 的有序集中。在一次新的訂單被完成時,我們每次都會為一同購買的產(chǎn)品的評分加一。

當(dāng)一份訂單付款成功后,我們保存每個購買產(chǎn)品的鍵,包括同意訂單中的有序產(chǎn)品集。這個有序集讓我們可以為一起購買的產(chǎn)品打分。

編輯項目中的額 settings.py ,添加以下設(shè)置:

REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 1

這里的設(shè)置是為了和 Redis 服務(wù)器建立連接。在 shop 應(yīng)用內(nèi)創(chuàng)建一個新的文件,命名為 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ǎn)品然后檢索所給產(chǎn)品的產(chǎn)品推薦。get_product_key() 方法檢索 Product 對象的 id ,然后為儲存產(chǎn)品的有序集創(chuàng)建一個 Redis 鍵(key),看起來像這樣:product:[id]:purchased_with

products_bought() 方法檢索被一起購買的產(chǎn)品列表(它們都屬于同一個訂單)。在這個方法中,我們執(zhí)行以下幾個任務(wù):

1. 得到所給 `Product` 對象的產(chǎn)品 ID
2. 迭代所有的產(chǎn)品 ID。對于每個 `id` ,我們迭代所有的產(chǎn)品 ID 并且跳過所有相同的產(chǎn)品,這樣我們就可以得到和每個產(chǎn)品一起購買的產(chǎn)品。
3. 我們使用 `get_product_id()` 方法來獲取 Redis 產(chǎn)品鍵。對于一個 ID 為 33 的產(chǎn)品,這個方法返回鍵 `product:33:purchased_with` 。這個鍵用于包含和這個產(chǎn)品被一同購買的產(chǎn)品 ID 的有序集。
4. 我們把有序集中的每個產(chǎn)品 `id` 的評分加一。評分表示另一個產(chǎn)品和所給產(chǎn)品一起購買的次數(shù)。

我們有一個方法來保存和對一同購買的產(chǎn)品評分。現(xiàn)在我們需要一個方法來檢索被所給購物列表的一起購買的產(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:這是一個 Product 對象列表。它可以包含一個或者多個產(chǎn)品
  • max_results:這是一個整數(shù),用于展示返回的推薦的最大數(shù)量

在這個方法中,我們執(zhí)行以下的動作:

1. 得到所給 `Product` 對象的 ID
2. 如果只有一個產(chǎn)品,我們就檢索一同購買的產(chǎn)品的 ID,并按照他們的購買時間來排序。這樣做,我們就可以使用 Redis 的 `ZRANGE` 命令。我們通過 `max_results` 屬性來限制結(jié)果的數(shù)量。
3. 如果有多于一個的產(chǎn)品被給予,我們就生成一個臨時的和產(chǎn)品 ID 一同創(chuàng)建的 Redis 鍵。
4. 我們把包含在每個所給產(chǎn)品的有序集中東西的評分組合并相加,我們使用 Redis 的 `ZUNIONSTORE` 命令來實現(xiàn)這個操作。`ZUNIONSTORE` 命令執(zhí)行了對有序集的所給鍵的求和,然后在新的 Redis 鍵中保存每個元素的求和。你可以在這里閱讀更多關(guān)于這個命令的信息:http://redisio/commands/ZUNIONSTORE 。我們在臨時鍵中保存分?jǐn)?shù)的求和。
5. 因為我們已經(jīng)求和了評分,我們或許會獲取我們推薦的重復(fù)商品。我們就使用 `ZREM` 命令來把他們從生成的有序集中刪除。
6. 我們從臨時鍵中檢索產(chǎn)品 ID,使用 `ZREM` 命令來按照他們的評分排序。我們把結(jié)果的數(shù)量限制在 `max_results` 屬性指定的值內(nèi)。然后我們刪除了臨時鍵。
7. 最后,我們用所給的 `id` 獲取 `Product` 對象,并且按照同樣的順序來排序。

為了更加方便使用,讓我們添加一個方法來清除所有的推薦。
把下列方法添加進(jìn) Recommender 類中:

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

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

src/redis-server

打開另外一個 shell ,執(zhí)行 python manage.py shell ,輸入下面的代碼來檢索幾個產(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')

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

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)保存了下面的評分:

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)品的順序正式基于他們的評分。讓我們來看看為多個產(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)品的順序和評分總和的順序是一樣的。比如 black_tea, red_tea, tea_powder(2+1)green_tea(1=1) 的產(chǎn)品推薦就是這樣。

我們必須保證我們的推薦算法按照預(yù)期那樣工作。讓我們在我們的站點上展示我們的推薦吧。

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

我們得到了四個最多產(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 %}

運行開發(fā)服務(wù)器,訪問:http://127.0.0.1:8000/en/ 。點擊一個產(chǎn)品來查看它的詳情頁。你應(yīng)該看到展示在下方的推薦產(chǎn)品,就象這樣:

django-9-13

我們也將在購物車當(dāng)中引入產(chǎn)品推薦。產(chǎn)品推薦將會基于用戶購物車中添加的產(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/ 時,你可以在購物車下看到銳減的產(chǎn)品,就像下面這樣:

django-9-14

恭喜你!你已經(jīng)使用 Django 和 Redis 構(gòu)建了一個完整的推薦引擎。

總結(jié)

在這一章中,你使用會話創(chuàng)建了一個優(yōu)惠券系統(tǒng)。你學(xué)習(xí)了國際化和本地化是如何工作的。你也使用 Redis 構(gòu)建了一個推薦引擎。

在下一章中,你將開始一個新的項目。你將會用 Django 創(chuàng)建一個在線學(xué)習(xí)平臺,并且使用基于類的視圖(view)。你將學(xué)會創(chuàng)建一個定制的內(nèi)容管理系統(tǒng)(CMS)。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,747評論 2 370

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