Python + Django 上で CMS を構築する、 Mezzanine というシステムがあります。 ブログエントリは標準形式では WYSIWYG な HTML エディタで書きますが、 reStructured Text (reST, rst) で書きたかったため調べてみました。
pygments でコードシンタックスハイライティングもしてくれるようになるため、便利です。
環境としては
です。これを書いている 2015年5月現在、Django は 1.8 が使えるのですが、 mezzanine が Django 1.6 までしか対応していなためこのようなバージョンになっています。
後述しますが、これでは Sphinx と mezzanine-meze のバージョン関係に不具合があります。
mezzanine のインストール方法も書いておきます。mac 内に環境を作ります。
$ pyvenv-3.4 .virtualenvs/my-blog $ workon my-blog $ pip install mezzanine # Django ごとインストールされます
Mezzanine プロジェクトをつくるには
$ mezzanine-project my_blog
mezzanine-meze をインストールします。これは、ブログ記事の保存時、 Sphinx を使って reST からHTMLを生成するようになります。
pip install pip install mezzanine-meze
https://github.com/abakan/mezzanine-meze
ここにある通り、設定をしていきます。
settings.py の INSTALLED_APPS に、meze を追加
settings.py の下部に追記
help_text = ("Source in reStructuredText format will be converted to "
"HTML and result will replace content field.")
EXTRA_MODEL_FIELDS = (
# Enable Meze for blog posts
("mezzanine.blog.models.BlogPost.source",
"TextField", (), {"blank": True, "help_text": help_text}),
("mezzanine.blog.models.BlogPost.convert",
"BooleanField", ("Convert source",), {"default": True}),
# Enable Meze for rich text pages
("mezzanine.pages.models.RichTextPage.source",
"TextField", (), {"blank": True, "help_text": help_text}),
("mezzanine.pages.models.RichTextPage.convert",
"BooleanField", ("Convert source",), {"default": True}),
)
del help_text
settings.py の下部に追記
MEZE_SETTINGS = {
'workdir': os.path.join(PROJECT_ROOT, 'meze_workdir'),
}
SPHINX_CONF = """
project = u''
copyright = u''
version = '0'
release = '0'
master_doc = 'index'
pygments_style = 'sphinx'
html_theme = 'default'
html_sidebars = {'**': []}
html_domain_indices = False
html_use_index = False
html_show_sourcelink = False
html_add_permalinks = None
source_suffix = '.rst'
intersphinx_mapping = {'python': ('http://docs.python.org/', None)}
extlinks = {'wiki': ('http://en.wikipedia.org/wiki/%s', ''),}
extensions = ['sphinx.ext.intersphinx', 'sphinx.ext.extlinks']
"""
HTMLテンプレート (例: base.html) に、Pygments の設定を追加します。
{% compress css %}
...
<link rel="stylesheet" href="{% static "meze/css/meze.css" %}">
<link rel="stylesheet" href="{% static "meze/css/pygments.css" %}">
...
{% compress js %}
...
<script src="{% static "meze/js/copybutton.js" %}"></script>
...
実行時、不足しているライブラリをインストールします。
$ pip install sphinx $ pip install south
なぜ south をインストールしたかというと、3-2 でモデルにフィールドを追加しているため、 それを DB に反映しなければならないためです。
少しトリッキーな方法ですが、プロジェクト直下に、migrations ディレクトリを作って、 そこに south のマイグレーションファイルを入れ、マイグレーションさせます。
my_blog ディレクトリは、manage.py が含まれる Django のプロジェクトディレクトリですが、 それを django の app ディレクトリとしても認識させます。
settings.py の INSTALLED_APPS に my_blog を入れておき
$ cd my_blog $ mkdir migrations $ touch modles.py $ ./manage.py schemamigration blog --auto --stdout >> migrations/0001_blog_customization.py $ ./manage.py schemamigration pages --auto --stdout >> migrations/0002_pages_customization.py $ ./manage.py migrate my_blog
こんな感じでマイグレーションできると思います。
データが無い場合は、DBを全部消して、./manage.py createdb から行っても良いでしょう。
ここまでは、mezzanine-meze のチュートリアルにある通りなのですが、 このまま起動すると meze/meze.py の 195行目
rst = os.path.join(workdir, 'index' + app.config.source_suffix)
ここで
TypeError at /admin/blog/blogpost/add/ Can't convert 'list' object to str implicitly
が出てしまいます。
おそらく、Sphix の現行バージョンでは、app.config.source_suffix が str ではなく list に 変わったのでしょう。
Sphinx の昔のバージョンをインストールすれば良いかもしれませんが、私はモンキーパッチを書いて対応しました。
urls.py の下部かどこかで
# Meze monkey patching
from meze.meze import os, sys, codecs, time, messages, Sphinx, SPHINX_CONF, SPHINX_CONF_APPEND
from meze.meze import Meze, MezeBuilder, MezeStream
def sphinx_build_monkey(self):
workdir = self._workdir
if not os.path.isdir(workdir):
os.makedirs(workdir)
conf = os.path.join(workdir, 'conf.py')
with codecs.open(conf, encoding='utf-8', mode='w') as out:
out.write(SPHINX_CONF)
out.write(SPHINX_CONF_APPEND)
start = time.time()
status = MezeStream(sys.stdout)
warning = MezeStream(sys.stderr)
app = Sphinx(srcdir=workdir, confdir=workdir, outdir=workdir,
doctreedir=workdir, buildername='meze',
confoverrides={}, status=status, warning=warning,
freshenv=False, warningiserror=False, tags=[])
if isinstance(app.config.source_suffix, (list, tuple)):
rst = os.path.join(workdir, 'index' + app.config.source_suffix[0])
else:
rst = os.path.join(workdir, 'index' + app.config.source_suffix)
with codecs.open(rst, encoding='utf-8', mode='w') as out:
out.write(self._source)
app.build(False, [rst])
self._messages.append((messages.INFO, 'Source was converted '
'into HTML using Sphinx in {:.2f}'.
format(time.time() - start)))
for msg in warning.messages:
items = msg.split('WARNING: ')
if len(items) == 2:
msg = items[1]
self._messages.append((messages.WARNING, msg))
self._content, MezeBuilder.context = MezeBuilder.context['body'], None
Meze.sphinx_build = sphinx_build_monkey
このように、sphinx_build メソッドを上書きしました。
これで、私は問題なく reStructuredText から HTML に変換できるようになりました。
ちなみに、reST ではなく markdown を扱えるライブラリとして、mezzanine-mdown というのがあります。
こちらの動作としては、テンプレートが HTML ファイルを生成する際のテンプレートフィルタとして、markdown -> HTML の変換をしているようです。
pip install mezzanine-mdown
ではインストールできなかった(見つからなかった)ため、
easy_install mezzanine-mdown
でインストールしました。…が、使ってません。