Wagtailブログ記事のPDFダウンロード機能の実装方法
Wagtailで作成したブログ記事をPDF形式でダウンロードできる機能の実装方法を解説します。 日本語フォントの対応、画像の表示、見出しのスタイリングなど、読みやすいPDFを生成するための実装手順を詳しく説明します。
1. はじめに
Wagtailで作成したブログ記事をPDF形式でダウンロードできる機能を実装します。 この機能により、ユーザーは記事をオフラインで読んだり、印刷したりすることができます。
2. 必要なパッケージのインストール
まず、PDF生成に必要なパッケージをインストールします:
pip install reportlab beautifulsoup4
3. 日本語フォントのインストール
PDFで日本語を正しく表示するために、日本語フォントをインストールします:
# Windowsの場合
# 1. IPAフォントをダウンロード(https://moji.or.jp/ipafont/)
# 2. ダウンロードしたフォントをC:\Windows\Fontsにインストール
4. ビューの実装
PDFを生成するビューを実装します。以下のコードをblog/views.py
に追加します:
from django.shortcuts import render
from django.http import HttpResponse
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Image, Table, TableStyle, PageBreak
from reportlab.lib.units import inch, mm
from reportlab.lib import colors
from io import BytesIO
import os
import re
from .models import BlogPage
from wagtail.images.models import Image as WagtailImage
from bs4 import BeautifulSoup
def clean_html(text):
# BeautifulSoupでHTMLを解析
soup = BeautifulSoup(text, 'html.parser')
# 見出しタグを処理
for h in soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
level = int(h.name[1]) # h1 -> 1, h2 -> 2, etc.
h.replace_with(f"\n\n{'#' * level} {h.get_text()}\n\n")
# 段落タグを処理
for p in soup.find_all('p'):
p.replace_with(f"\n{p.get_text()}\n")
# リストを処理
for ul in soup.find_all('ul'):
items = [f"• {li.get_text()}" for li in ul.find_all('li')]
ul.replace_with("\n" + "\n".join(items) + "\n")
for ol in soup.find_all('ol'):
items = [f"{i+1}. {li.get_text()}" for i, li in enumerate(ol.find_all('li'))]
ol.replace_with("\n" + "\n".join(items) + "\n")
# リンクを処理
for a in soup.find_all('a'):
href = a.get('href', '')
text = a.get_text()
a.replace_with(f"{text} ({href})")
# 太字を処理
for b in soup.find_all(['b', 'strong']):
b.replace_with(f"**{b.get_text()}**")
# 改行を処理
text = str(soup)
text = re.sub(r'\n\s*\n', '\n\n', text) # 複数の改行を1つに
text = text.strip() # 前後の空白を削除
return text
def blog_page_pdf(request, slug):
# ブログページを取得
blog_page = BlogPage.objects.get(slug=slug)
# PDFを生成するためのバッファを作成
buffer = BytesIO()
# 日本語フォントを登録
font_path = 'C:\\Windows\\Fonts\\ipag.ttf' # Windows用のフォントパス
pdfmetrics.registerFont(TTFont('IPAexGothic', font_path))
# PDFドキュメントを作成
doc = SimpleDocTemplate(
buffer,
pagesize=A4,
rightMargin=30*mm,
leftMargin=30*mm,
topMargin=30*mm,
bottomMargin=30*mm
)
# スタイルを設定
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=24,
spaceAfter=30,
fontName='IPAexGothic',
alignment=1, # 中央揃え
textColor=colors.HexColor('#2C3E50') # ダークブルー
)
date_style = ParagraphStyle(
'CustomDate',
parent=styles['Normal'],
fontSize=12,
spaceAfter=20,
fontName='IPAexGothic',
alignment=2, # 右揃え
textColor=colors.HexColor('#7F8C8D') # グレー
)
intro_style = ParagraphStyle(
'CustomIntro',
parent=styles['Normal'],
fontSize=14,
spaceAfter=30,
fontName='IPAexGothic',
leading=24, # 行間
textColor=colors.HexColor('#34495E') # ダークブルー
)
body_style = ParagraphStyle(
'CustomBody',
parent=styles['Normal'],
fontSize=12,
spaceAfter=12,
fontName='IPAexGothic',
leading=20, # 行間
textColor=colors.HexColor('#2C3E50') # ダークブルー
)
heading1_style = ParagraphStyle(
'CustomHeading1',
parent=styles['Heading1'],
fontSize=20,
spaceBefore=30,
spaceAfter=20,
fontName='IPAexGothic',
textColor=colors.HexColor('#2C3E50') # ダークブルー
)
heading2_style = ParagraphStyle(
'CustomHeading2',
parent=styles['Heading2'],
fontSize=16,
spaceBefore=25,
spaceAfter=15,
fontName='IPAexGothic',
textColor=colors.HexColor('#2C3E50') # ダークブルー
)
heading3_style = ParagraphStyle(
'CustomHeading3',
parent=styles['Heading3'],
fontSize=14,
spaceBefore=20,
spaceAfter=10,
fontName='IPAexGothic',
textColor=colors.HexColor('#2C3E50') # ダークブルー
)
# コンテンツを準備
story = []
# タイトルを追加
story.append(Paragraph(blog_page.title, title_style))
# 日付を追加
story.append(Paragraph(f"投稿日: {blog_page.date.strftime('%Y年%m月%d日')}", date_style))
story.append(Spacer(1, 20))
# サムネイル画像がある場合は追加
if blog_page.eyecatch:
try:
# 画像のパスを取得
img_path = blog_page.eyecatch.file.path
# 画像のサイズを調整(A4の幅に合わせる)
img_width = A4[0] - 60*mm # 左右のマージンを引いた幅
img_height = img_width * (blog_page.eyecatch.height / blog_page.eyecatch.width)
# 画像を追加
story.append(Image(img_path, width=img_width, height=img_height))
story.append(Spacer(1, 20))
except Exception as e:
print(f"画像の処理中にエラーが発生しました: {e}")
# イントロを追加
story.append(Paragraph(clean_html(blog_page.intro), intro_style))
story.append(Spacer(1, 20))
# 本文を追加(HTMLをクリーンアップ)
body_text = clean_html(blog_page.body)
paragraphs = body_text.split('\n\n')
for para in paragraphs:
if para.startswith('#'):
# 見出しの処理
level = len(para.split()[0])
text = para.replace('#' * level, '').strip()
if level == 1:
story.append(Paragraph(text, heading1_style))
elif level == 2:
story.append(Paragraph(text, heading2_style))
else:
story.append(Paragraph(text, heading3_style))
else:
# 通常の段落
story.append(Paragraph(para, body_style))
story.append(Spacer(1, 10))
# PDFを生成
doc.build(story)
# バッファからPDFを取得
pdf = buffer.getvalue()
buffer.close()
# レスポンスを作成
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{blog_page.slug}.pdf"'
response.write(pdf)
return response
5. URLパターンの追加
blog/urls.py
にPDFダウンロード用のURLパターンを追加します:
from django.urls import path
from . import views
urlpatterns = [
# ... 既存のURLパターン ...
path('/pdf/', views.blog_page_pdf, name='blog_page_pdf'),
]
6. テンプレートの修正
ブログ記事のテンプレートにPDFダウンロードボタンを追加します。
blog/templates/blog/blog_page.html
を以下のように修正します:
{% extends "base.html" %}
{% load static %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block extra_css %}
{% endblock %}
{% block extra_js %}
{% endblock %}
{% block body_class %}template-blogpage{% endblock %}
{% block content %}
{{ page.title }}
{{ page.date|date:"Y年m月d日" }}
PDFでダウンロード
{% if page.eyecatch %}
{% image page.eyecatch fill-800x400 as eyecatch %}
{% endif %}
{{ page.intro }}
{{ processed_body }}
{% endblock %}
7. 動作確認
開発サーバーを起動して、PDFダウンロード機能が正しく動作することを確認します:
python manage.py runserver
ブログ記事ページにアクセスし、PDFダウンロードボタンをクリックして、PDFが正しく生成されることを確認してください。
8. まとめ
この実装により、以下の機能が実現できます:
- 日本語フォントを使用したPDF生成
- サムネイル画像の表示
- 見出しの適切なスタイリング
- リンクのテキスト表示
- リストの整形表示
これらの機能により、ユーザーはブログ記事を読みやすいPDF形式でダウンロードできるようになります。
9. 注意点
- 日本語フォントのインストールが必要です
- 画像のパスが正しく設定されていることを確認してください
- HTMLタグの処理にBeautifulSoupを使用しています
- PDFの生成にはreportlabを使用しています