django by example 實踐--blog 項目(一)


點我查看本文集的說明及目錄。


本項目相關內容( github傳送 )包括:

實現過程

CH1 創建一個博客應用

CH2 使用高級特性增強博客功能

CH3 擴展博客功能

項目總結及改進

使用類視圖實現 blog list 和 detail 視圖


內容更新頻率:每周三、周五中午更新一篇。


CH1 創建一個博客應用

我們將在這本書中學習如何創建可用于生產環境的完整 Django 項目。如果你還沒有安裝 Django ,那么請在本章第一節學習如何安裝。這章介紹如何使用Django創建一個簡單的博客應用。本章的目的在于介紹框架如何工作,不同部分如何相互作用及如何通過基本功能創建 django 項目。你將在不過分關注細節的情況下創建一個完整的項目。框架細節將在后續章節進行介紹。

本章涉及以下內容:

  • 安裝 Django 并創建第一個項目
  • 設計模型并生成模型遷移文件
  • 為模型創建 administration 網站
  • 使用 Queryset 和 manager 工作
  • 創建視圖、模板和 URLs
  • 為 list 視圖添加分頁
  • 使用 Django 類視圖

安裝 Django


如果你已經安裝了 Django,可以跳過這一節,直接從下一節(創建第一個項目)開始。作為 Python 庫 Django 可以在任何 python 環境中安裝。如果你還沒有安裝 Django,請閱讀下面的 Django 安裝快速向導。

Django 可用于 Python2.7 和 Python3。本書的例子使用 Python3 。如果你使用 Linux 或 Mac OS X ,很可能已經預先安裝了 python 。在 teminal 中輸入 python 來確定是否安裝了 python 。如果你看到下面的輸出,那么 python 已經安裝好了。

Python 3.5.0 (v3.5.0:374f501f4567, Sep 12 2015, 11:00:19) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

如果你安裝的 python 版本低于 3.0 ,或者沒有安裝 python ,可以從http://www.python.org/download/ 下載Python3.5 并進行安裝。

筆者注:

筆者電腦同時安裝了 python2.7 和 python3.6。

在 terminal 輸入 python,可以看到下面的輸出:

[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

在 terminal 輸入 python3,可以看到下面的輸出:

[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

由于我們將使用 Python3.0 ,因此不需要安裝數據庫,python 內置 SQLite 數據庫,SQLite 是一個可用于 Django 開發的輕量級數據庫。如果希望將應用部署到生產環境,那么請使用PostGreSQL、MySQL 或 Oracle 等高級數據庫。我們可以從https://docs.djangoproject.com/en/1.8/topics/install/#database-installation.了解如何在 Django 中使用這些數據庫。

筆者注:

python2.7 也不需要安裝數據庫。

創建獨立的 Python 運行環境

推薦使用 virtualenv 創建獨立的 Python 運行環境,以便針對不同的項目使用不同版本的 python 庫文件,這樣做比在系統中安裝 Python 庫文件更實用。使用 virtualenv 的另一個優點是安裝 Python 庫文件時不需要管理權限。在 shell 中運行以下命令來安裝 virtualenv:

pip install virtualenv

安裝完 virtualenv 后,使用以下命令創建獨立的 Python 運行環境:

virtualenv my_env

這個命令將在 Python 環境中創建 my_env 目錄,虛擬環境處于激活狀態時,安裝的所有 Python 庫文件都將保存在 my_env/lib/python3.X/site-packages 目錄下。

如果系統自帶 Python 2.X ,我們又額外安裝了 Python 3.X,我們需要告訴虛擬環境使用后者。這時通過下面的步驟創建虛擬環境:

在terminal 中通過以下命令獲取 python3 的路徑:

which python3

? 這里得到的結果是:

/usr/local/bin/python3

使用以下命令創建獨立的 Python 運行環境:

virtualenv my_env -p /usr/local/bin/python3

運行以下命令激活虛擬環境:

source my_env/bin/active

shell 提示將使用括號包含激活的虛擬環境,看起來是這樣的:

(my_env)latop:~ user_name$

我們可以在任何時間使用 deactivate 命令關閉虛擬環境。

我們可以從 https://virtualenv.python/io/en/latest/ 找到更多虛擬環境的信息。

我們可以使用 virtualenvwrapper 來管理虛擬環境。這個工具可以可以幫助我們快速創建和管理虛擬環境,我們可以根據 http://virtualenvwrapper.readthedocs.io/en/latest/ 的介紹安裝virtualenvwrapper 。

筆者注:

筆者習慣使用 PyCharm ,因此直接通過 PyCharm 創建虛擬環境,創建方法為:

  1. 創建項目時設置虛擬環境:


    virtualenv.png

    點擊右側 Create VirtualEnv 創建虛擬環境。

  2. 通過 pycharm 的偏好設置-Project Interpreter 進行設置:


    pycharm_virtualenv.png

    點擊右側 Create VirtualEnv 創建虛擬環境。

注意,方法 2 要在使用 django-admin startproject 命令之前使用。

創建完成后,項目即可使用虛擬環境了。

使用 pip 安裝 Django

推薦使用 pip 安裝 Django, Python3.5 預裝了 pip,我們也可以從 https://pip.pypa.io/en/stable/installing/ 找到 pip 的安裝說明,在 shell 中運行以下命令安裝 Django:

pip install Django==1.11 

筆者注:

==1.11 表示安裝Django 1.11,這里如果不指定版本號,會安裝 python 版本能夠滿足的最新版本。

Django 現在已經安裝在虛擬環境的 site-packages/ 目錄下了。

現在檢查一下 Django 是否成功安裝了,在 terminal 中運行 python3 并導入 Django 來檢查它的版本:

>>> import django
>>> django.VERSION
(1, 11, 0, 'final', 1)

如果可以看到輸出,則表示我們已經成功安裝了 Django。

我們也可以使用其它方法安裝 Django ,https://docs.djangoproject.com/en/1.11/topics/install/ 有詳細的安裝說明。

創建第一個項目

我們的第一個項目將完成一個博客網站。Django 提供了創建項目文件結構的命令,在shell中運行以下命令:

django-admin startproject mysite 

這個命令將創建一個名為 mysite 的 Django 項目。讓我們來看一下項目的結構:

structure.png

這些文件的作用是:

  • manage.py:與項目進行交互的命令行工具。它是 django-admin.py 工具的輕量級封裝,我們不需要編輯這個文件。

  • mysite/:項目目錄包含以下文件:

    • __init__.py: 空白文件,表示將 mysite 文件夾當做一個模塊進行處理;

    • settings.py:設置及配置項目。包括初始化缺省設置;

    • urls.py:保存 URL 模式。這里的每個 url 都與一個視圖相匹配。

    • wsgi.py: 項目作為 WSGI 應用運行時的配置。

生成的 settings.py 文件是項目的基本配置(包含使用 SQLite 數據庫及默認安裝的 Django 應用)。我們需要在數據庫中為初始應用創建數據庫表。

打開 shell ,跳轉到項目的根目錄并運行以下命令:

cd mysite3
python manage.py migrate

將會輸出以下內容:

Operations to perform:
 Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
 Applying contenttypes.0001_initial... OK
 Applying auth.0001_initial... OK
 Applying admin.0001_initial... OK
 Applying admin.0002_logentry_remove_auto_add... OK
 Applying contenttypes.0002_remove_content_type_name... OK
 Applying auth.0002_alter_permission_name_max_length... OK
 Applying auth.0003_alter_user_email_max_length... OK
 Applying auth.0004_alter_user_username_opts... OK
 Applying auth.0005_alter_user_last_login_null... OK
 Applying auth.0006_require_contenttypes_0002... OK
 Applying auth.0007_alter_validators_add_error_messages... OK
 Applying auth.0008_alter_user_username_max_length... OK
 Applying sessions.0001_initial... OK

項目初始應用的數據庫表已經建好了,我們對 migrate 命令有了初步了解。

運行開發服務器

Django 內置一個快速運行代碼的輕量級 web服務器,這樣可以節約配置生產服務器的時間。Django 服務器運行期間會自動監測代碼的變化并自動重載代碼。但是,它無法識別向項目中添加文件的操作,這種情況下需要手動重啟服務器。

打開 shell 并跳轉到項目的根目錄,運行以下命令來啟動開發服務器:

python manage.py runserver

你見看到以下輸出:

Performing system checks...
?
System check identified no issues (0 silenced).
October 24, 2017 - 04:48:13
Django version 1.11.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

現在在瀏覽器中打開 URL :http://127.0.0.1:8000/,你將看到一個告訴你項目成功運行的頁面,類似這樣:

first_start.png

django 開發服務器可以在自定義主機和端口運行,或者載入其它的 setting 文件,例如,可以這樣運行 manage.py 命令:

python manage.py runserver 127.0.0.1:8001 --settings=mysite.settings

這樣可以手動處理需要不同配置的多個環境。需要牢記的是,開發服務器只適用于開發,但是并不適合生產。部署生產環境應該使用 Apache、Gunicorn、uWSGI 等 web服務器以 WSGI 應用的形式運行。使用不同 web服務器部署 django 的詳細文檔鏈接為:https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/

此外,可以下載本書的第十三章,它將介紹如何為 Django 項目生成生產環境。

項目設置

打開 setting.py 文件來看一下項目配置。Django 在這個文件中進行了一些配置,但這只是一部分配置,我們可以從以下鏈接查看所有可用配置及其默認值:https://docs.djangoproject.com/en/1.11/ref/settings/

下面的設置需要格外注意:

  • DEBUG :打開/關閉項目調試模式的布爾值。如果設置為 True ,當捕捉到異常時 Django 將顯示詳細錯誤信息。項目部署到生產環境時,要將其設為 False 。不要在生產環境中打開 DEBUG ,否則將可能暴露項目敏感信息。

  • ALLOW_HOST : DEBUG 模式或者運行測試下不使用該設置。一旦將網站遷移到生產環境并將 DEBUG 設置為 False ,則需要在該設置中添加域名/主機來使用 Django 網站。

  • INSTALLED_APPS :所有項目都需要編輯的設置,Django 從這個設置中讀取處于激活狀態的應用。默認情況下,Django 包含以下應用:

    • django.contrib.admin: administration 網站;

    • django.contrib.auth: 權限框架;

    • django.contrib.contenttypes: 內容框架;

    • django.contrib.sessions: 會話框架;

    • django.contrib.messages: 消息框架;

    • django.contrib.staticfiles: 靜態文件管理框架;

  • MIDDLEWARE_CLASSSES:需要執行的中間件元組。

  • ROOT_URLCONF :定義項目應用的主 URL 模式的 Python 模塊。

  • DATABASES:設置項目使用的所有數據庫的字典。必須設置一個 default 數據庫。默認使用 SQLite3 數據庫。

  • LANGUAGE_CODE: 定義 Django 網站默認使用的語言。

如果你不理解這些內容,不用擔心。我們會通過下面的章節熟悉這些設置。

項目和應用

本書將會不斷的涉及項目和應用這兩個術語。 Django 中,項目是指具有一些設置的 Django 安裝文件;應用是模型、視圖、模板和 URLs 的組合。應用與框架交互來提供一些特定功能,并可以在不同項目中重用。我們把項目當作網站,它可以包括幾個應用,如博客、wiki、論壇等,這些應用也可以用在其它項目中。

創建一個應用

現在我們來創建第一個 Django 應用。我們將從創建一個博客應用開始。在 terminal 中跳轉到項目的根目錄并運行以下命令:

python manage.py startapp blog

它將創建應用的基本結構,看起來是這樣的:

first_app.png

這些文件包括:

  • admin.py:該文件用于將模型注冊到 administration 網站。

  • migrations: 這個目錄將包含應用的數據庫遷移記錄。它允許 Django 追蹤模型變化并進行相應的數據庫同步。

  • models.py: 應用的數據庫模型,所有的 Django 應用都要有一個 models.py 文件,但這個文件可以是空的。

  • tests.py:該文件用于添加引用的測試程序。

  • views.py:該文件實現應用邏輯。每個 view 接收一個 HTTP請求,對其進行處理并返回一個響應。

    ?

設計 blog 數據模式

我們將從為 blog 定義初始數據模型開始。每個模型都是 django.db.models.Model 的子類,它的每個屬性都表示數據庫表的一個字段。Django 將為 models.py 中定義的每個模型創建一個數據庫表。為了便于數據查詢,當你創建一個模型后,Django 將提供一個 API 。

首先,我們將定義一個 Post 模型,在 blog 應用的 models.py 文件中添加以下代碼:

from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone


# Create your models here.

class Post(models.Model):
    STATUS_CHOICES = (('draft', 'Draft'), ('published', 'Published'),)
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250, unique_for_date='publish')
    author = models.ForeignKey(User, related_name='blog_posts')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES,
                              default='draft')

    class Meta:
        ordering = ('-publish',)

    def __str__(self):
        return self.title

這是 blog 文章的基本模型,我們來看下這個模型的字段:

  • title:文章標題字段。這個字段是 CharField ,在 SQL 數據庫中將轉化為 VARCHAR 列。

  • slug:URLs 中使用的字段,slug 是只包含字母、數字、下劃線、連字符的短標簽。我們將使用 slug 字段為blog 文章創建漂亮、SEO 友好的 URLs 。我們為該字段添加了 unique_for_date 參數,這樣我們可以使用date 和 slug 為文章創建標簽,設置該參數后將不能為相同日期的多篇文章使用相同的 slug 。

  • author:外鍵字段(ForeignKey)。這個字段定義了一個多對一關系。我們告訴 Django 每篇文章有一個作者而一個作者可以寫多篇文章。Django 將在數據庫中使用相關模型的 id 創建一個外鍵。在這里,我們使用Django 權限系統的 User 模型。我們使用 related_name 屬性為指定反向關系(從 User 到 Post )的名稱,后面我們將針對這一點學習更多知識。

  • body:這個字段是文章的正文。這個字段是 TextField ,在 SQL 數據庫中將轉化為Text 列。

  • publish:文章發表日期,我們使用 Django timezone 模塊的 now 方法作為默認值。該方法是時區敏感的datetime.now。

  • create:表示文章創建日期,這里我們使用 auto_now_add ,該字段將在創建對象時自動添加。

  • updated:表示文章最后一次更新日期,這里使用 auto_now ,當我們保存對象時將會自動更新該字段。

  • status:表示文章狀態的字段。我們使用 choices 參數,該字段只能為給定的 choices 中的一個。

我們可以看到,Django 可以使用不同類型的字段來定義模型。可以從以下鏈接找到所有字段:https://docs.djangoproject.com/en/1.11/ref/models/fields/

模型內部的 Meta 類包含 metadata 。通過它告訴 Django 查詢數據庫時按照 publish 降序的順序對查詢結果進行排序。這里通過 - 前綴表示降序。

__str__方法是默認表示對象的方法。Django 將在很多地方使用它,比如 administration 網站。

注意:

Python3默認所有字符串都使用 unicode 編碼,因此我們使用__str__方法,如果你使用 Pyhton2.X ,可以使用__unicode__方法代替__str__方法。

由于我們將處理 datetime,我們將安裝 pytz 模塊。這個模塊為 python 提供 timezone 定義而且 SQLite 需要用它來處理時間。打開 shell 并使用以下命令安裝 pytz :

pip install pytz

筆者注:
django 1.11 中,pytz 為 django 模塊的依賴庫文件,因此,安裝 django 時已經自動安裝了 pytz,不需要再次安裝。

Django 支持時區敏感的 datetime 。我們可以在項目的 settings.py 中設置 USE_TZ 來激活/關閉時區。當使用startproject 創建一個項目時該設置為 True 。

激活你的應用

為了讓 Django 追蹤我們的應用并為應用的模型創建數據庫表,我們需要激活該應用。通過編輯項目的 settings.py 文件在 INSTALLED_APPS 中添加 blog 來激活應用。看起來是這樣的:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
]

現在 Django 知道我們的應用已經激活而且能夠引入它的模型了。

創建并實現 migrations

我們在數據庫中為模型創建一個數據表。Django 內置遷移系統來追蹤模型的變化并將這些更新同步到數據庫中。migrate 命令將遷移 INSTALL_APPS 中所有應用的模型,它對同步當前模型及數據庫。

首先,我們需要為剛剛創建的新模型創建 migration ,從項目的根目錄輸入以下命令:

python manage.py makemigrations blog 

你將看到以下輸出:

Migrations for 'blog':
 blog/migrations/0001_initial.py
 - Create model Post

Django 剛剛在 blog 應用的 migrations 目錄下創建了一個名為 0001_initial.py 的文件。你可以打開文件查看一下遷移文件。

讓我們查看一下 Django 為模型創建數據庫表時執行的 SQL 代碼。sqlmigrate 命令根據 migration 的名字返回 SQL 但不執行。運行以下命令:

 python manage.py sqlmigrate blog 0001

輸出是這樣的:

BEGIN;
--
-- Create model Post
--
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(250) NOT NULL, "slug" varchar(250) NOT NULL, "body" text NOT NULL, "publish" datetime NOT NULL, "created" datetime NOT NULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_slug_b95473f2" ON "blog_post" ("slug");
CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
COMMIT;

具體的輸出會因使用的數據庫稍有變化。上面是 SQLite 的輸出。你可以看到,Django 使用應用名稱的小寫形式和數據庫表名稱的小寫形式組成數據庫表名( blog_post ),你也可以在 Meta 類中使用 db_table 屬性指定數據庫表名。Django 自動為每個模型創建一個主鍵字段,你也可以通過為模型的某個字段設置 primary_key=True 來設置主鍵。

我們來為模型同步數據庫,在項目根目錄運行以下命令:

python manage.py migrate

將會看到以下輸出:

Operations to perform:
 Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
 Applying blog.0001_initial... OK

我們剛為 INSTALL_APPS 中的應用實現了遷移。遷移之后模型與數據庫一致。(打開數據庫,將會發現名為blog_post 的數據庫表)。

如果編輯 models.py 文件來為已經存在的模型添加、刪除或修改字段,或者添加新模型,需要使用makemigrations 命令創建新的遷移文件,Django 通過遷移文件追蹤模型變化,然后運行 migrate 命令來同步數據庫。

為模型創建 administration 網站

現在,我們已經定義了 Post 模型,我們將創建一個簡單的 administration 網站來管理 blog 文章。Django 內置非常有用的 administration 接口。接口通過讀取模型的 metadata 動態創建 Django admin 網站并提供穩定的編輯內容的接口,我們還可以使用它來編輯模型的展示形式。

筆者注:

后面的內容,django admin 網站稱為 admin網站。

由于 django.contrib.admin 已經自動放到項目 settings.py 的 INSTALLED_APPS 中了,因此我們無需再次添加。

創建 superuser

首先,我們需要創建一個管理 admin網站的用戶,跳轉到項目根目錄,運行以下命令:

python manage.py createsuperuser

你將看到如下輸出,根據提示逐步輸入你的用戶名、郵箱及密碼:

Username (leave blank to use 'apple'): admin
Email address: my_email@163.com
Password: 
Password (again): 
Superuser created successfully.

admin網站

現在,通過python manage.py runserver運行開發服務器,在瀏覽器中打開http://127.0.0.1:8000/admin,將會看到 admin網站的登錄頁面,如下圖所示:

admin login.png

使用上一步驟創建的用戶登錄,你將看到 admin網站的索引頁面如下圖所示:

first_login.png

這里的 Group 和 User 模型是 Django 權限框架( Django.contrib.auth )的模型。如果點擊 Users ,你將看到剛剛創建的用戶。blog 應用的 Post 模型與 User 模型有一個關系(由 author 字段定義的關系)。

將模型添加到 admin網站

讓我們將 blog 模型添加到 admin網站。向 blog 應用的 admin.py 文件寫入以下代碼:

from django.contrib import admin
?
from .models import Post
?
# Register your models here.
admin.site.register(Post)

現在在瀏覽器中重新載入 admin網站,你應該在網站中看到如下圖所示的 Post 模型:


edit_post.png

向 admin網站注冊一個模型后,可以得到一個接口,通過這個接口我們可以很容易的列出模型對象以及創建、修改、刪除模型對象。

點擊 Posts 右側的 Add 可以添加新的文章。你可以看到 Django 為我們剛剛創建的模型自動生成的表單,如下圖所示:
Django 為每種類型的字段使用不同的表單組件。即使 DateTimeField 這樣的復雜字段也通過接口(如 JavaScript date picker )進行展示。

填寫表單并點擊 save按鈕,你將重定向到文章列表頁面,在這里我們將看到剛剛成功添加的信息,如下圖所示:

add_post.png

自定義模型展示方式

現在,我們來看看如何自動以admin網站。編輯blog應用的admin.py文件,將其更改為:

from django.contrib import admin
?
from .models import Post
?
?
# Register your models here.
class PostAdmin(admin.ModelAdmin):
 list_display = ('title', 'slug', 'author', 'publish', 'status')
?
?
admin.site.register(Post, PostAdmin)

我們告訴 admin網站使用繼承 ModelAdmin 的自定義類進行模型注冊。在這個類中,我們可以設置模型的展示及交互方式。這里的 list_display 屬性用來設置 admin 對象列表頁面展示的字段。

讓我們使用如下代碼設置更多自定義選項:

from django.contrib import admin
?
from .models import Post
?
?
# Register your models here.
class PostAdmin(admin.ModelAdmin):
 list_display = ('title', 'slug', 'author', 'publish', 'status')
 list_filter = ('status', 'created', 'publish', 'author')
 search_fields = ('title', 'body')
 prepopulated_fields = {'slug': ('title',)}
 raw_id_fields = ('author',)
 date_hierarchy = 'publish'
 ordering = ['status', 'publish']
?
?
admin.site.register(Post, PostAdmin)
?

回到瀏覽器重新載入文章列表頁面,你將看到如下頁面:

raw_id_fields.png

你將看到文章列表頁面展示的字段是 list_display 設置的字段。列表頁面包括一個右側邊欄來允許按照 list_filters屬性設置的字段對列表進行過濾。頁面還多了一個搜索條,這是因為我們定義了 search_field 屬性。搜索條下方是一個日期面包屑導航菜單,這是由 date_hierarchy 屬性定義的。我們還可以看到文章默認通過 Status 和 Publish 字段排序,我們可以使用 ordering 屬性定義默認排序字段。

現在點擊右上角的 Add Post 鏈接,我們也將看到一些變化。當輸入新文章標題時會自動生成 slug 。我們使用prepopulated_field 告訴 Django 輸入 title 時自動生成 slug 。而且,通過設置 raw_id_fields 將 author 字段用 lookup 組件展示,當用戶數量很多時,這種操作從從下拉框中尋找更容易,如下圖所示。

raw_id_fields.png

點擊圖中的放大鏡,將會出現搜索頁面,如下圖所示:

raw_id_fields_lookup.png

我們已經使用很少的代碼自定義了 admin網站的模型的展示形式,后續章節我們會涉及更多自定義及擴展 admin網站的方法。

使用Queryset和manager

我們已經有了管理 blog 內容的 admin網站,現在要學習如何從數據庫中獲取信息并與其進行交互。Django 內置幫助我們創建、獲取、更新和刪除對象的數據庫API--Django Object-Relational-Mapper(ORM),它可以與MySQL、PostGreSQL、SQLite 及 Oracle 兼容。我們可以編輯項目 settings.py 中的 DATABASES 來定義項目使用的數據庫。Django 可以同時與多個數據庫工作。

一旦創建了數據模型,Django 免費提供與模型進行交互的 API,數據模型的官方文檔鏈接為:https://docs.djangoproject.com/en/1.11/ref/models/

創建對象

打開 terminal ,在項目根目錄運行以下命令:

python manage.py shell

然后輸入以下信息:

In [1]: from django.contrib.auth.models import User
?
In [2]: from blog.models import Post
?
In [3]: user = User.objects.get(username='admin')
?
In [4]: post = Post(title='One more post',slug='one-more-post',body='Post body',author=user)
?
In [5]: post.save()
?
In [6]: Post.objects.create(title='Another post',slug='another-post',body='Post body',author=user)
Out[6]: <Post: another post>

筆者注:

原文中的 post.save() 是多余的,創建模型可以采用兩種方法:

方法1 :

In [4]: post = Post(title='One more post',slug='one-more-post',body='Post body',author=user)

In [5]: post.save()

post 定義了 Post 對象,然后通過 post.save() 方法保存到數據庫。

方法2:

Post.objects.create(title='another post',slug='another-post',body='Post body',author=user)

這里,Post.objects 為 Post 模型管理器,它的 create 方法可以將創建的模型保存到數據庫,不需要調用 save() 方法。

我們來分析下這些代碼,首先,我們獲取名為admin的用戶:

In [3]: user = User.objects.get(username='admin')

get() 方法允許我們從數據庫中獲取單個對象。注意,這個方法期望查詢結果為一個結果。如果數據庫沒有返回結果,這個方法將引發 DoesNotExist 異常,如果數據庫返回的結果多于一個,這個方法將引發MultipleObjectsReturened 異常。這兩個異常都是模型類的屬性。

然后,我們使用自定義的 title,slug,body 和 author 創建了一個 Post 對象,其中 author 的值為上一步獲取的user:

In [4]: post = Post(title='One more post',slug='one-more-post',body='Post body',author=user)

注意:這一步并沒有影響數據庫。

最后,我們將 Post 對象保存到數據庫:

In [5]: post.save()

這個操作實現了一個 Insert SQL 。我們已經看到了如何創建對象并保存到數據庫。我們也可以使用create()方法在數據庫中創建對象:

In [6]: Post.objects.create(title='another post',slug='another-post',body='Post body',author=user)
Out[6]: <Post: another post>

現在再次查看admin網站的Post列表,我們會發現下面三條記錄:


queryset_1.png

更新對象

現在,更改 post 的標題:

In [4]: post.title='New title'
?
In [5]: post.save()
?
In [6]: post
Out[6]: <Post: New title>

這次,save() 方法實現 UPDATE SQL 語句。

筆者注:

我們也可以直接使用 update() 函數直接對數據庫中的記錄進行更改。

獲取對象

Django ORM 基于 QuerySet ,Queryset 是數據庫中可以使用多個 filter 進行限制的對象集合。我們已經知道如何使用 get() 獲取單個對象。每個 Django 模型至少有一個 manager ,manager 默認的名字為 objects 。我們使用模型的 manager 獲取 Queryset 對象。使用 objects 的 all() 方法可以獲取一個數據庫表中的所有對象:

In [9]: all_post = Post.objects.all()

注意,這個 Queryset 并沒有執行。Django 的 Queryset 是惰性的。這是為了保證 Queryset 高效。如果我們不將QuerySet 設為一個變量,而是直接寫到 Python shell 中,QuerySet 將被執行,因為這樣做需要輸出結果:

In [10]: Post.objects.all()
Out[10]: <QuerySet [<Post: another post>, <Post: New title>, <Post: Why Django?>]>

使用 filter() 方法

我們可以使用 filter() 方法過濾 QuerySet ,比如獲取所有 2015年發布的文章:

In [12]: Post.objects.filter(publish__year=2015)
Out[12]: <QuerySet []>

我們也可以使用多個字段進行過濾,比如,我們要獲取用戶 admin 在 2015年發布的文章:

In [13]: Post.objects.filter(publish__year=2015,author__username='admin')
Out[13]: <QuerySet []>

這與多級 filter 效果一樣:

In [14]: Post.objects.filter(publish__year=2015).filter(author__username='admin')
Out[14]: <QuerySet []>

注意:

我們使用雙下劃線來獲取字段變量,比如publish__year,還可以用雙下劃線獲取字段對應的模型,比如author__username

使用 exclude()

我們可以使用 manager 的 exclude() 方法排除某些結果,比如,我們要查詢 2015年發布的標題不以 Why 開頭的文章:

In [16]: Post.objects.filter(publish__year=2015).exclude(title__startswith='Why')
Out[16]: <QuerySet []>

使用 order_by()

我們可以使用 manager 的 order_by() 函數根據字段進行排序,比如,通過標題排序:

In [17]: Post.objects.order_by('title')
Out[17]: <QuerySet [<Post: Another post>, <Post: New title>, <Post: Why Django?>]>

上述查詢執行的順序排序,倒序排序需要在字段前加上'-',比如:

In [18]: Post.objects.order_by('-title')
Out[18]: <QuerySet [<Post: Why Django?>, <Post: New title>, <Post: Another post>]>

刪除對象

如果要刪除對象,我們可以這樣實現:

In [19]: post = Post.objects.get(pk=3)
In [20]: post.delete()
Out[20]: (1, {u'blog.Post': 1})

注意:

刪除對象將同時刪除依賴該對象的關系。

何時執行 QuerySet

我們可以為 QuerySet 設置任意多的 filter ,QuerySet 被執行之前并不會影響數據庫。只有下面的情況會觸發QuerySet 執行:

  • 進行第一次迭代時

  • 進行切片時,比如 Post.objects.all()[:3]

  • 進行 pickle 或者緩存

  • 使用 repr() 或者 len() 函數

  • 使用 list() 或 values() 函數

  • 使用 bool()、or 、 and 或 if 語句進行測試

創建模型 Manager

正如我們上一節提到的,所有模型默認的 manager 為 objects ,它將獲取數據庫中的所有數據。我們也可以為模型自定義 manager ,我們將創建一個獲取所有 published 的文章的 manager 。

可以采用兩種方法為模型添加 manager :1. 添加額外的 manager 方法;2. 修改最初 manager 的 Queryset 。第一種方法看起來像 Post.objects.my_manager() ,第二種看起來像 Post.my_manager.all() 。manager 允許我們使用 Post.published 獲取文章。

編輯 blog 應用的 models.py 文件來添加自定義 manager :

from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone


# Create your models here.

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super(PublishedManager, self).get_queryset().filter(
            status='published')


class Post(models.Model):
    STATUS_CHOICES = (('draft', 'Draft'), ('published', 'Published'),)
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250, unique_for_date='publish')
    author = models.ForeignKey(User, related_name='blog_posts')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES,
                              default='draft')

    objects = models.Manager()
    published = PublishedManager()

    class Meta:
        ordering = ('-publish',)

    def __str__(self):
        return self.title


get_queryset() 是獲取 QuerySet 的方法,自定義的 manger 增加了 filter(status='published') 限制。我們已經定義了自定義 manager 并將其添加到 Post 模型中,現在我們用它來實現查詢,比如,我們需要查詢所有標題以 Who 開頭的已發表的文章:

In [4]: Post.published.filter(title__startswith='Who')
Out[4]: <QuerySet []>

建立 list 和 detail 視圖


現在我們已經學習了一些 ORM 的知識,可以為 blog 創建視圖了。Django 視圖是一個 python 函數,用來接收web請求并返回 web響應,視圖內部實現獲得期望響應的所有邏輯。

首先,我們將創建應用視圖,然后為每個視圖定義 URL ,最后我們創建 HTML模板來渲染視圖生成的數據。每個視圖都將為模板傳入數據,然后返回一個渲染好的 HTTP響應。

創建 list 和 detail 視圖

我們先來創建一個展示文章列表的視圖,編輯 blog 應用的 views.py 文件:

from django.shortcuts import render, get_object_or_404
?
from .models import Post
?
?
# Create your views here.
def post_list(request):
    posts = Post.published.all()
    return render(request, 'blog/post/list.html', {'posts': posts})

我們剛剛創建了第一個 Django 視圖,post_list 視圖只有一個參數( request對象)。所有視圖都需要輸入 request 。在這個視圖中,我們獲取所有 published 狀態的文章(通過前面的 published manager 獲得)。

最后,我們使用 Django 提供的 render 渲染模板。這個函數的輸入參數包括 request對象、模板路徑和模板渲染需要的數據,它返回一個包含渲染文本的 HttpResponse對象。render() 快捷函數處理 request 的 context ,因此,給定模板可以獲得模板 context 處理器設置的任意變量。模板 context 處理器只是用來為 context 設置變量。我們將在第三章(擴展模板應用)學習如何使用。

我們創建第二個視圖來展示一篇文章。向 views.py 中增加下面的函數:

def post_detail(request, year, month, day, slug):
    post = get_object_or_404(Post, slug=slug, status='published',
                             publish__year=year, publish__month=month,
                             publish__day=day)
    return render(request, 'blog/post/detail.html', {'post': post})

這是文章詳細視圖,視圖的輸入參數包括獲取某天slug為slug的文章需要使用的 year、month、day、slug。注意,我們在創建 Post 模型時向 slug 字段設置了 unique_for_date 參數,這樣,我們可以確定滿足某天某個 slug的文章是唯一的,首先,通過 get_object_or_404() 來獲取需要的文章,這個函數獲得滿足輸入參數的對象,如果沒找到相應對象則加載 HTTP 404 。最后,我們使用 render() 函數來使用模板渲染數據。

為視圖添加 URL 模式

URL 模式由一個 python 正則表達式、一個視圖和一個項目范圍內唯一的名稱組成。Django 遍歷每個 URL 模式然后停在第一個匹配的 URL 。然后,Django 導入匹配 URL 模式的視圖(傳入一個 HttpRequset 類實例、關鍵詞或位置參數)并執行。

如果你之前沒有處理過正則表達式,可能需要了解一下:https://docs.python.org/3/howto/regex.html

在 blog 應用的目錄下創建一個 urls.py 文件,并添加下面的代碼:

from django.conf.urls import url

from . import views

app_name = 'blog'
urlpatterns = [  # post views
    url(r'^$', views.post_list, name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$',
        views.post_detail, name='post_detail'), ]

筆者注:

根據 django 1.11 的定義方法,這里設置了 app_name,代替在后面主 URL 中設置 app_name。

第一個 URL 模式并不接收任何參數并與 post_list 視圖匹配。第二個模式接收四個參數并與 post_detail 視圖匹配。讓我們看一下 URL 模式的正則表達式:

  • year:需要四位數字;

  • month:需要兩位數字,1-9 月需要前面加 0 ;

  • days:需要兩位數字;

  • slug:由單詞和連字符組成。

注意:

為每個 app 創建一個 urls.py 文件以便于其它項目重用你的應用。

現在需要將 blog 應用的 URL 模式放到項目的主 URL 模式中。編輯項目 mysite3 目錄下的 urls.py :

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [url(r'^admin/', admin.site.urls),
    url(r'^blog/', include('blog.urls'))]

這樣 Django 就可以將 blog 應用 urls.py 中定義的 url 放到 blog/ 下。

為模型設置 URLs

我們可以使用上一節定義的 post_detail URL 來為 Post 對象創建 URL 。Django 可以通過為模型添加get_absolute_url() 方法來設置對象的 URL 。在這個方法里,我們使用 reverse() 方法實現通過名稱和參數創建URL 。編輯 models.py 文件并添加以下代碼:

from django.urls import reverse

class Post(models.Model):

    # ...
    
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                       args=[self.publish.strftime('%Y'),
                             self.publish.strftime('%m'),
                             self.publish.strftime('%d'),
                             self.slug])

筆者注:

Django 1.11 使用 from django.urls import reverse 代替 Django1.8 中的 from django.core.urlresolvers import reverse。

注意,我們使用 strftime() 函數來創建 URL 。我們將在模板中使用 get_absolute_url() 方法。

筆者注:

官方相關材料:https://docs.djangoproject.com/en/1.11/ref/models/instances/#get_absolute_url

為視圖添加模板


我們已經為應用創建了視圖和 URL 模式。現在需要添加模板來展示文章了。

在 blog 應用下創建以下目錄和文件:

template.png

上圖是模板的文件結構。base.html 文件將包含網站的 HTML 結構并將內容劃分為主內容和邊欄。list.html 和detail.html 文件將繼承 base.html 文件來分別渲染文章列表視圖和細節視圖。

Django 的模板語言可以指定如何展示數據。它基于模板標簽(格式為 {% tag %} )、變量(格式為 {{ variable }})、過濾器(格式為 {{ variable|filter }} )。可以從官網查看所有內置標簽和過濾器:https://docs.djangoproject.com/en/1.11/ref/templates/builtins/

在 base.html 中添加以下代碼:

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
    <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
<div id="content">
    {% block content %}
    {% endblock %}
</div>
<div id="sidebar">
    <h2>My blog</h2>
    <p>This is my blog.</p>
</div>
</body>
</html>

筆者注:

Django 1.11 使用 {% load static %} 代替了原文的 {% load staticfiles %}。

{% load static %} 告訴 Django 加載 django.contrib.staticfiles 應用提供的模板標簽和過濾器。加載完后,可以在整個模板范圍內使用 {% static %} 標簽,通過這個標簽我們可以 include 本例中 blog 應用 static 文件夾下的blog.css 文件。將這個目錄的文件復制到你項目中的相同位置來使用靜態文件。

我們可以看到這里有兩個 {% block %} 標簽。這些標簽告訴 Django 在該區域定義一個 block 。繼承這個模板的模板可以填充這個 block 。我們定義了一個名為 title 的 block 和一個名為 content 的 block 。

我們來編輯 post/list.html 文件:

{% extends "blog/base.html" %}

{% block title %}My Blog{% endblock %}

{% block content %}
    <h1>My Blog</h1>
    {% for post in posts %}
        <h2>
            <a href="{{ post.get_absolute_url }}">
                {{ post.title }}
            </a>
        </h2>
        <p class="date">
            Published {{ post.publish }} by {{ post.author }}
        </p>
        {{ post.body|truncatewords:30|linebreaks }}
    {% endfor %}
{% endblock %}

我們使用 {% extends %} 模板標簽來繼承 blog/base.html 。然后填充 title 和 content block 。模板遍歷所有文章,依次展示它們的標題、日期、作者、內容,標題附帶指向文章頁面的鏈接。文章內容使用了兩個模板過濾器:truncatewords 截取指定長度的內容,linebreaks 將輸出轉換為 HTML 斷行格式。還可以添加任意過濾器應用到之前的輸出。

打開 shell,在 blog 應用根目錄運行 python manage.py runserver 命令來啟動開發服務器。在瀏覽器中打開http://http://127.0.0.1:8000/blog/,我們將看到下面的內容,注意,這里需要設置一些狀態為 published 的文章,才能看到類似這樣的輸出:

html_list.png

現在,我們來編輯 post/detail.html 文件:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
        Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|linebreaks }}
{% endblock %}

現在我們回到瀏覽器,點擊任意一篇文章的標題,將看到以下頁面:

html_post.png

它的 url 為 : http://127.0.0.1:8000/blog/2017/10/25/meet-django/。我們已經創建了用戶友好的 blog 文章。

添加分頁

添加一定數量的文章后,我們會發現需要對文章進行分頁。Django內置 paginator 類來幫助我們非常容易地實現分頁。

編輯 blog 應用的 views.py 視圖導入 Django paginator 類,并修改 post_list 函數:

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def post_list(request):
    object_list = Post.published.all()
    paginator = Paginator(object_list, 3)  # 3 post in each page
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        # if page is not an integer deliver the first page
        posts = paginator.page(1)
    except EmptyPage:
        # if page is out of range deliver last page of results
        posts = paginator.page(paginator.num_pages)
        return render(request, 'blog/post/list.html',
                  {'page': page, 'posts': posts})

分頁是這樣工作的:

  1. 使用每頁想要顯示的文章數量創建 Paginator 實例;

  2. 從 request 的 GET 參數中獲取請求的頁碼;

  3. 從 Paginator 的 page() 方法獲取文章列表的 queryset ,并設為posts;

  4. 如果步驟 2 中得到的頁碼不是整數,則取第一頁的文章進行展示,如果得到的頁碼大于最大分頁數,那么取最后一頁的文章進行展示。

  5. 使用模板渲染步驟 2 中的 page 和步驟 3 中的 posts ,返回 httpResponse。

然后,我們需要創建一個展示分頁的模板,這個模板可以用于任意需要分頁的模板。在 blog 應用的templates\blog 文件夾下的新建一個名為 pagination.html 的文件,并添加以下代碼:

<div class="pagination">
  <span class="step-links">
    {% if page.has_previous %}
        <a href="?page={{ page.previous_page_number }}">Previous</a>
    {% endif %}
      <span class="current">
      Page {{ page.number }} of {{ page.paginator.num_pages }}.
    </span>
      {% if page.has_next %}
          <a href="?page={{ page.next_page_number }}">Next</a>
      {% endif %}
  </span>
</div>

分頁模板需要一個 Page 對象來渲染前一頁鏈接、后一頁鏈接、展示當前頁碼及總頁碼。讓我們回到blog/post/list.html 模板將 pagination.html 模板添加到 {% content %} 的底部:

{% extends "blog/base.html" %}

{% block title %}My Blog{% endblock %}

{% block content %}
    ...
    {% include "blog/pagination.html" with page=posts %}
{% endblock %}

筆者注:

原文中為 {% include "pagination.html" %},Django 1.11 需要使用 {% include "blog/pagination.html" %}

由于我們傳入模板的 Page 對象的名稱為 posts ,現在我們已經將分頁模板放到了文章列表模板。我們可以采用這個方法來復用分頁模板。

現在,打開http://http://127.0.0.1:8000/blog/,我們會發現底部多了頁碼信息:

pagination.png

筆者注:

分頁功能正常顯示需要 Post 模型有 3 個以上對象,點擊頁面下方的 Next 后,后一頁的url 變為 http://127.0.0.1:8000/blog/?page=2 ,點擊previous會回到前一個頁面,前一個頁面的 url 由http://127.0.0.1:8000/blog/ 變為 http://127.0.0.1:8000/blog/?page=1

使用類視圖

視圖是一個輸入 web請求輸出 web響應的函數,我們也可以使用類方法定義視圖。Django 提供視圖類,這些視圖類解決了 HTTP 匹配和其它功能。下面是創建視圖的另一種方法。

我們將使用 Django 提供的類視圖 ListView 來修改 post_list 視圖。 ListView 類視圖允許我們列出任意類型的對象。

編輯 blog 應用的 views.py 文件并添加以下代碼:

from django.views.generic import ListView

class PostListView(ListView):
    queryset = Post.objects.all()
    context_object_name = 'posts'
    paginate_by = 3
    template_name = 'blog/post/list.html'

這個類視圖與前面的 post_list 視圖功能類似。我們告訴 ListView :

  • 使用 queryset 獲取所有對象,我們也可以指定 model=Post ,Django 將為我們創建 Post.objects.all() 。

  • 使用內容變量 posts 表示查詢結果,如果不指 定context_object_name ,默認變量名為object_list。

  • 將查詢結果按照每頁 3 項進行分頁。

  • 使用自定義模板渲染頁面。如果我們不設置模板,ListView將使用 blog/post_list.html 作為默認模板。

現在打開 blog 應用的 urls.py 文件,注釋掉原來的 post_list URL 并為 PostListView 添加 URL :

from django.conf.urls import url

from . import views

urlpatterns = [  
    # url(r'^$', views.post_list, name='post_list'),# post views
    url(r'^$', views.PostListView.as_view(), name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$',
        views.post_detail, name='post_detail'), ]

為了保證分頁正常工作,我們需要向模板傳入正確的 page 對象,Django ListView 中 page 對象為 page_obj ,因此,我們需要修改 templates/blog/post/list.html ,將context最后一句更改為:

{% include "blog/pagination.html" with page=page_obj %}

筆者注:

整個html為:

?
{% block title %}My Blog{% endblock %}
?
{% block content %}
 <h1>My Blog</h1>
 {% for post in posts %}
 <h2>
 <a href="{{ post.get_absolute_url }}">
 {{ post.title }}
 </a>
 </h2>
 <p class="date">
 Published {{ post.publish }} by {{ post.author }}
 </p>
 {{ post.body|truncatewords:30|linebreaks }}
 {% endfor %}
 {% include "blog/pagination.html" with page=page_obj %}
{% endblock %}```

在瀏覽器中打開 http://127.0.0.1:8000/blog/,檢查是否與 post_list 視圖實現的頁面一樣。這是類視圖的簡單例子,我們將在第十章(Building an e-Learning Platform)及后續章節繼續學習。

總結

這一章,我們已經通過創建一個簡單的 blog 應用了解了 Django web 框架的基本內容。我們設計了數據模型并為項目應用了遷移,還為 blog 創建了視圖、模板和 URL,并實現了分頁。

下一章,我們將為 blog 應用添加評論系統、標簽功能、并實現通過 e-mail 分享文章。

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