Using reStructuredText with Mezzanine

Django
2015-05-10 16:19 (9 years ago) ytyng

There is a system called Mezzanine for building a CMS on Python + Django. By default, blog entries are written using a WYSIWYG HTML editor, but I wanted to write them using reStructured Text (reST, rst), so I did some research.

Using pygments, it also provides code syntax highlighting, which is convenient.

The environment is as follows:

  • Python 3.4
  • Django==1.6.11
  • Mezzanine==3.1.10
  • mezzanine-meze==0.3
  • Sphinx==1.3.1

As of May 2015, Django 1.8 is available, but Mezzanine only supports up to Django 1.6, so these versions are being used.

As mentioned later, this setup has issues with the version compatibility between Sphinx and mezzanine-meze.

1. Installing Mezzanine

Here is the method to install Mezzanine. The environment is set up within a Mac.

$ pyvenv-3.4 .virtualenvs/my-blog
$ workon my-blog
$ pip install mezzanine  # Django is installed along with Mezzanine

To create a Mezzanine project:

$ mezzanine-project my_blog

2. Installing the reStructured Text Library mezzanine-meze

Install mezzanine-meze. This library uses Sphinx to generate HTML from reST when saving blog entries.

pip install mezzanine-meze

3. Configuration

Follow the instructions on https://github.com/abakan/mezzanine-meze to configure the settings.

3-1. Registering INSTALLED_APPS

Add meze to the INSTALLED_APPS in settings.py.

3-2. Registering Additional Fields

Add the following to the bottom of 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

3-3. Registering MEZE_SETTINGS and SPHINX_CONF

Add the following to the bottom of 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']
"""

3-4. Modifying HTML Templates

Add Pygments settings to your HTML template (e.g., base.html):

{% 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>
...

3-5. Installing Additional Libraries

Install any missing libraries during runtime:

$ pip install sphinx
$ pip install south

3-6. Database Migration

South is installed because additional fields have been added to the model in step 3-2, and these need to be reflected in the database.

A somewhat tricky method is to create a migrations directory directly under the project, put the South migration files there, and migrate them.

The my_blog directory contains the manage.py file and is recognized as a Django app directory.

Add my_blog to INSTALLED_APPS in settings.py.

$ cd my_blog
$ mkdir migrations
$ touch models.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

With this, you should be able to migrate.

If there is no data, you can delete the entire database and start from ./manage.py createdb.

4. Additional Configuration

Following the Mezzanine-meze tutorial up to this point, when you start it, you get an error at line 195 of meze/meze.py:

rst = os.path.join(workdir, 'index' + app.config.source_suffix)

Here, you get:

TypeError at /admin/blog/blogpost/add/

Can't convert 'list' object to str implicitly

This is probably because in the current version of Sphinx, app.config.source_suffix has changed from str to list.

Instead of installing an older version of Sphinx, I wrote a monkey patch to handle this.

Add the following somewhere at the bottom of 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

I overwrote the sphinx_build method like this.

With this, I was able to convert from reStructuredText to HTML without any issues.

5. Using Markdown

Incidentally, there is a library called mezzanine-mdown for handling markdown instead of reST.

It appears that this library converts markdown to HTML as a template filter when generating HTML files from templates.

pip install mezzanine-mdown

However, I couldn't install it with pip (it wasn't found), so I used:

easy_install mezzanine-mdown

But I haven't actually used it.

Currently unrated

Comments

Archive

2024
2023
2022
2021
2020
2019
2018
2017
2016
2015
2014
2013
2012
2011