「Django + microCMSでつくるブログサイト」シリーズの6番目の記事です。
前回までで、ある程度ブログの形はできてきたので、デプロイの準備をしていきます。
今回は公開にあたり、サイト内のすべてのページを事前に静的なhtmlファイルとして書き出しておく処理の説明になります。
django-distill
というライブラリを使用しました。
こちらを使うと設定したURLパターンに応じて、サイトのhtmlファイルを網羅的に作成してくれます。
まずは仮想環境を立ち上げた後に、pipでinstallします。
myvenv\\scripts\\activate
pip install django-distill
settings.pyを編集します。
# project/settings.py
...
INSTALLED_APPS = [
...
'django_distill',
]
...
STATIC_ROOT = Path(BASE_DIR, 'static')
# 静的ファイルの配信path
DISTILL_DIR = Path(BASE_DIR, 'dist')
設定の準備としてはこれだけです。
次にアプリ内のurls.pyの全文を以下のように書き換えます。
# blog/urls.py
from django_distill import distill_path
from . import views
from django.conf import settings
import requests
import math
app_name = 'blog'
limit = 2 # 一覧ページに表示する記事数
url = getattr(settings, "BASE_URL", None)
api_key = getattr(settings, "API_KEY", None)
headers = {'X-API-KEY': api_key}
def _post_total_count():
"""ポストAPIのトータル数を返す"""
return requests.request(method='GET',
url=url + '/post',
headers=headers).json()['totalCount']
def _tag_total_count():
"""タグAPIのトータル数を返す"""
return requests.request(method='GET',
url=url + '/tag',
headers=headers).json()['totalCount']
def get_index():
"""
トップページ
引数はないので、Noneを返す
"""
return None
def get_posts():
"""
記事詳細ページを生成するためのpost idを返す
"""
post_total_count = _post_total_count()
end_point = f'/post?limit={post_total_count}&fields=id'
res = requests.request('GET', url=url + end_point, headers=headers)
for data in res.json()['contents']:
yield data['id']
def get_pages():
"""
ページ数を指定した一覧ページを生成するためのページ数を返す
"""
post_total_count = _post_total_count()
num_page = math.ceil(post_total_count / limit)
for page_num in range(1, num_page + 1):
yield {'page': str(page_num)}
def get_tags():
"""
タグとページ数を指定した一覧ページを生成するための
タグ+ページ数を返す
"""
tag_total_count = _tag_total_count()
post_total_count = _post_total_count()
# タグの一覧を取得
end_point = f'/tag?limit={tag_total_count}&fields=id'
tag_res = requests.request(method='GET',
url=url + end_point,
headers=headers)
for data in tag_res.json()['contents']:
# タグIDを取得
tag_id = data['id']
# タグに紐づく記事が何個あるか?を取得
res = requests.request(method='GET',
url=url + f'/post?limit={post_total_count}&filters=tag[contains]{tag_id}',
headers=headers)
post_total_count_with_tag = res.json()['totalCount']
# 一ページあたりの記事数で割り出して、何ページあるか?を計算
num_page = math.ceil(post_total_count_with_tag / limit)
# タグIDとページ数をyield
for page_num in range(1, num_page + 1):
yield {'tag_id': tag_id, 'page': str(page_num)}
# urlパターン
urlpatterns = [
# トップページの普通の記事一覧
distill_path('',
views.post_list,
name='index',
distill_func=get_index,
distill_file='index.html'),
# 記事詳細ページ
distill_path('post/<slug:slug>/',
views.post_detail,
name='post_detail',
distill_func=get_posts),
# ページを指定した記事一覧
distill_path('page/<str:page>/',
views.post_list,
name='index_with_page',
distill_func=get_pages,
),
# タグを指定した記事一覧
distill_path('tag/<str:tag_id>/page/<str:page>/',
views.post_list,
name='index_with_tag',
distill_func=get_tags
),
]
そして以下のようにコマンドラインからmanage.py
で呼び出します。
collect static
を最初に行い、staticフォルダを事前に作っておくのが注意です。
python manage.py collectstatic
python ./manage.py distill-local
>>>
You have requested to create a static version of
this site into the output path directory:
Source static path: C:\\Users\\your\\DjangoProject\\static
Distill output path: C:\\Users\\your\\DjangoProject\\dist
Does not exist, create it? (YES/no):
settings.pyで設定した配信pathDISTILL_PATH
に配信しますがいいですか?と聞かれているわけです。
yes
とすると静的ファイルの生成が始まります。
yes
>>>
Creating directory...
Generating static site into directory: C:\\Users\\your\\DjangoProject\\dist
Loading site URLs
...省略
Site generation complete
うまくいきますとこのような階層構造でdist
配下にhtmlファイルが生成されます。
htmlファイルを開くと、このような表示です。
開発環境だとcssとリンクが効いていないですが、本番環境にデプロイすると、ちゃんとした見た目で動作します。
distill-local
コマンドを呼び出すと、distill_path
内で指定しているパターンに沿ってhtmlファイルが生成されます。
distill_path('post/<slug:slug>/',
views.post_detail,
name='post_detail',
distill_func=get_posts),
この場合、post
フォルダの中にslug
のフォルダを作り、その中にindex.html
を生成するように解釈されます。
slugがhogehoge
だったらpost
フォルダ→hogehoge
フォルダ→index.html
という構造になるということです。
views.post_detail
とname='post_detail'
の部分は通常のurlパターンを書く時と同じです。
distill_func=get_posts
の部分が特殊です。 こちらが生成の際にviews.py内のpost_detail
に引数slug
を渡す役割を担っています。
get_posts
関数は以下のようになっています。
def get_posts():
"""
記事詳細ページを生成するためのpost idを返す
"""
post_total_count = _post_total_count()
end_point = f'/post?limit={post_total_count}&fields=id'
res = requests.request('GET', url=url + end_point, headers=headers)
for data in res.json()['contents']:
yield data['id']
難しいことは考えずに作成する全てのパターンをyieldする処理をここで書けば動きます。
views.py内のpost_detail
関数はslug(=postのid)を指定することでレンダリングする処理でした。
なので、postのidを網羅的にyieldする処理をするわけです。
/page/{ページ番号}
のパターンです。 これは簡単で、ページ数を取得して渡すだけです。
def get_pages():
"""
ページ数を指定した一覧ページを生成するためのページ数を返す
"""
post_total_count = _post_total_count()
num_page = math.ceil(post_total_count / limit)
for page_num in range(1, num_page + 1):
yield {'page': str(page_num)}
/tag/{タグID}/page/{ページ番号}
のパターンです。 ここがちょっと複雑ですが以下のような形で全パターンを網羅的に渡せます。
def get_tags():
"""
タグとページ数を指定した一覧ページを生成するための
タグ+ページ数を返す
"""
tag_total_count = _tag_total_count()
post_total_count = _post_total_count()
# タグの一覧を取得
end_point = f'/tag?limit={tag_total_count}&fields=id'
tag_res = requests.request(method='GET',
url=url + end_point,
headers=headers)
for data in tag_res.json()['contents']:
# タグIDを取得
tag_id = data['id']
# タグに紐づく記事が何個あるか?を取得
res = requests.request(method='GET',
url=url + f'/post?limit={post_total_count}&filters=tag[contains]{tag_id}',
headers=headers)
post_total_count_with_tag = res.json()['totalCount']
# 一ページあたりの記事数で割り出して、何ページあるか?を計算
num_page = math.ceil(post_total_count_with_tag / limit)
# タグIDとページ数をyield
for page_num in range(1, num_page + 1):
yield {'tag_id': tag_id, 'page': str(page_num)}
以上です。
次回は実際に生成した静的なhtmlファイルをNetlifyにデプロイします。