使用 Django 項目中的 ORM 編寫偽造測試數據腳本
- 2019 年 11 月 1 日
- 筆記
文中所涉及的示例代碼,已同步更新到 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='nn'.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
的值返回對應的時區對象。 -
'nn'.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='nn'.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 多篇測試文章,用來測試分頁效果就十分簡單了,接下來讓我們來實現功能完整的分頁效果。
『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟着我們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~