1. 程式人生 > >使用 Django 專案中的 ORM 編寫偽造測試資料指令碼

使用 Django 專案中的 ORM 編寫偽造測試資料指令碼

作者:HelloGitHub-追夢人物

文中所涉及的示例程式碼,已同步更新到 HelloGitHub-Team 倉庫

為了防止部落格首頁展示的文章過多以及提升載入速度,可以對文章列表進行分頁展示。不過這需要比較多的文章才能達到分頁效果,但本地開發時一般都只有幾篇測試文章,如果一篇篇手工新增將會非常麻煩。

解決方案是我們可以寫一個指令碼,自動生成任意數量的測試資料。指令碼寫好後,只需執行指令碼就可以往資料庫填充大量測試資料。指令碼就是一段普通的 Python 程式碼,非常簡單,但是通過這個指令碼你將學會如何在 django 外使用 ORM,而不僅僅在 django 應用的內部模組使用。

指令碼目錄結構

一般習慣於將專案有關的指令碼統一放在專案根目錄的 scripts 包中,當然這只是一個慣例,你也可以採用自己覺得合理的目錄結構,只要保證這個包所在目錄能夠被 Python 找到。

依據慣例,我們部落格專案中指令碼的目錄結構如下:

HelloDjango-blog-tutorial\
    blog\
    blogproject\
    ...
    scripts\
        __init__.py
        fake.py
        md.sample

其中 fake.py 是生成測試資料的指令碼,md.sample 是一個純文字檔案,內容是用於測試 Markdown 的文字。

使用 Faker 快速生成測試資料

部落格文章包含豐富的內容元素,例如標題、正文、分類、標籤。如果手工輸入這些相關元素的文字會非常耗時,我們將藉助一個 Python 的第三方庫 Faker 來快速生成這些測試用的文字內容。Faker 意為造假工廠,顧名即可思義。

首先安裝 Faker:

$ pipenv install Faker

Faker 通過不同的 Provider 來提供各種不同型別的假資料,我們將在下面的指令碼中講解它的部分用法,完整的用法可以參考其官方文件。

批量生成測試資料

現在我們來編寫一段 Python 指令碼用於自動生成部落格測試資料。思路非常簡單,部落格內容包括作者、分類、標籤、文章等元素,只需依次生成這些元素的內容即可。當然為了使指令碼能夠正常執行,很多細節需要注意,我們會對需要注意的地方進行詳細講解。

先來看指令碼 fake.py 開頭的內容:

import os
import pathlib
import random
import sys
from datetime import timedelta

import django
import faker
from django.utils import timezone

# 將專案根目錄新增到 Python 的模組搜尋路徑中
back = os.path.dirname
BASE_DIR = back(back(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)

這一段很簡單,只是匯入一些會用到的模組,然後通過指令碼所在檔案找到專案根目錄,將根目錄新增到 Python 的模組搜尋路徑中,這樣在執行指令碼時 Python 才能夠找到相應的模組並執行。

接下來是指令碼的邏輯,先看第一段:

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blogproject.settings.local")
    django.setup()

    from blog.models import Category, Post, Tag
    from comments.models import Comment
    from django.contrib.auth.models import User

這是整個指令碼最為重要的部分。首先設定 DJANGO_SETTINGS_MODULE 環境變數,這將指定 django 啟動時使用的配置檔案,然後執行 django.setup() 啟動 django。這是關鍵步驟,只有在 django 啟動後,我們才能使用 django 的 ORM 系統。django 啟動後,就可以匯入各個模型,以便建立資料。

接下來的邏輯就很簡單了,不斷生成所需的測試資料即可,我們來一段一段地看:

    print('clean database')
    Post.objects.all().delete()
    Category.objects.all().delete()
    Tag.objects.all().delete()
    Comment.objects.all().delete()
    User.objects.all().delete()

這一段指令碼用於清除舊資料,因此每次執行指令碼,都會清除原有資料,然後重新生成。

    print('create a blog user')
    user = User.objects.create_superuser('admin', '[email protected]', 'admin')

    category_list = ['Python學習筆記', '開源專案', '工具資源', '程式設計師生活感悟', 'test category']
    tag_list = ['django', 'Python', 'Pipenv', 'Docker', 'Nginx', 'Elasticsearch', 'Gunicorn', 'Supervisor', 'test tag']
    a_year_ago = timezone.now() - timedelta(days=365)

    print('create categories and tags')
    for cate in category_list:
        Category.objects.create(name=cate)

    for tag in tag_list:
        Tag.objects.create(name=tag)

    print('create a markdown sample post')
    Post.objects.create(
        title='Markdown 與程式碼高亮測試',
        body=pathlib.Path(BASE_DIR).joinpath('scripts', 'md.sample').read_text(encoding='utf-8'),
        category=Category.objects.create(name='Markdown測試'),
        author=user,
    )

這個指令碼沒什麼說的,簡單地使用 django 的 ORM API 生成部落格使用者、分類、標籤以及一篇 Markdown 測試文章。

    print('create some faked posts published within the past year')
    fake = faker.Faker()  # English
    for _ in range(100):
        tags = Tag.objects.order_by('?')
        tag1 = tags.first()
        tag2 = tags.last()
        cate = Category.objects.order_by('?').first()
        created_time = fake.date_time_between(start_date='-1y', end_date="now",
                                              tzinfo=timezone.get_current_timezone())
        post = Post.objects.create(
            title=fake.sentence().rstrip('.'),
            body='\n\n'.join(fake.paragraphs(10)),
            created_time=created_time,
            category=cate,
            author=user,
        )
        post.tags.add(tag1, tag2)
        post.save()

這段指令碼用於生成 100 篇英文部落格文章。部落格文章通常內容比較長,因此我們使用了之前提及的 Faker 庫來自動生成文字內容。指令碼邏輯很清晰,只對其中涉及的幾個知識點進行講解:

  • fake = faker.Faker(),要使用 Faker 自動生成資料,首先例項化一個 Faker 物件,然後我們可以在指令碼中使用這個例項的一些方法生成需要的資料。Faker 預設生成英文資料,但也支援國際化。至於如何生成中文資料在下一段指令碼中會看到。

  • order_by('?') 將返回隨機排序的結果,指令碼中這塊程式碼的作用是達到隨機選擇標籤(Tag) 和分類(Category) 的效果。

  • 然後就是 2 個 Faker 的 API 了:

    • fake.date_time_between

      這個方法將返回 2 個指定日期間的隨機日期。三個引數分別是起始日期,終止日期和時區。我們在這裡設定起始日期為 1 年前(-1y),終止日期為當下(now),時區為 get_current_timezone 返回的時區,這個函式是 django.utils.timezone 模組的輔助函式,它會根據 django 設定檔案中 TIME_ZONE 的值返回對應的時區物件。

    • '\n\n'.join(fake.paragraphs(10))

      fake.paragraphs(10) 用於生成 10 個段落文字,以列表形式返回,列表的每個元素即為一個段落。要注意使用 2 個換行符連起來是為了符合 Markdown 語法,Markdown 中只有 2 個換行符分隔的文字才會被解析為段落。

    fake = faker.Faker('zh_CN')
    for _ in range(100):  # Chinese
        tags = Tag.objects.order_by('?')
        tag1 = tags.first()
        tag2 = tags.last()
        cate = Category.objects.order_by('?').first()
        created_time = fake.date_time_between(start_date='-1y', end_date="now",
                                              tzinfo=timezone.get_current_timezone())
        post = Post.objects.create(
            title=fake.sentence().rstrip('.'),
            body='\n\n'.join(fake.paragraphs(10)),
            created_time=created_time,
            category=cate,
            author=user,
        )
        post.tags.add(tag1, tag2)
        post.save()

這一段指令碼和上一段幾乎完全一樣,唯一不同的是構造 Faker 例項時,傳入了一個語言程式碼 zh_CN,這將生成中文的虛擬資料,而不是預設的英文。

    print('create some comments')
    for post in Post.objects.all()[:20]:
        post_created_time = post.created_time
        delta_in_days = '-' + str((timezone.now() - post_created_time).days) + 'd'
        for _ in range(random.randrange(3, 15)):
            Comment.objects.create(
                name=fake.name(),
                email=fake.email(),
                url=fake.uri(),
                text=fake.paragraph(),
                created_time=fake.date_time_between(
                     start_date=delta_in_days, 
                     end_date="now", 
                     tzinfo=timezone.get_current_timezone()),
                post=post,
            )

    print('done!')

最後依葫蘆畫瓢,給前 20 篇文章(Post) 生成評論資料。要注意的是評論的釋出時間必須位於被評論文章的釋出時間和當前時間之間,這就是 delta_in_days = '-' + str((timezone.now() - post_created_time).days) + 'd' 這句程式碼的作用。

執行指令碼

指令碼寫好了,在專案根目錄執行下面的命令執行整個指令碼:

$ pipenv run python -m scripts.fake

看到如下的輸出說明指令碼執行成功了。

clean database
create a blog user
create categories and tags
create a markdown sample post
create some faked posts published within the past year
create some comments
done!

執行開發伺服器,訪問部落格首頁可以看到生成的測試資料,是不是有點以假亂真的感覺?

現在,我們有了 200 多篇測試文章,用來測試分頁效果就十分簡單了,接下來讓我們來實現功能完整的分頁效果。


『講解開源專案系列』——讓對開源專案感興趣的人不再畏懼、讓開源專案的發起者不再孤單。跟著我們的文章,你會發現程式設計的樂趣、使用和發現參與開源專案如此簡單。歡迎留言聯絡我們、加入我們,讓更多人愛上開源、貢獻開源~