akiyoko blog

akiyoko の IT技術系ブログです

Mezzanine の本番設定(その2:Mezzanine テーマのカスタマイズ)〜AWS 環境構築から運用設定まで〜

こんにちは、akiyoko です。

Mezzanine は Python製の WordPress風フルスタックCMSフレームワークです。

akiyoko.hatenablog.jp


今年の 7月に、Mezzanine を使った某ブログサイト(将来的に ECサイトを増設予定)の本番運用を開始しました。*1 その備忘録として、AWS の初期設定から Mezzanine の本番デプロイ、ちょっとした運用設定までの記録をまとめておくことにしました。

全ての記録を一つの記事にすると長くなり過ぎるので、テーマごとに、

の 4本に記事を分割することにしました。

今回はその 2本目、「その2:Mezzanine テーマのカスタマイズ」について説明します。


Mezzanine テーマのカスタマイズとして実施した内容としては、

  • Bootstrap テンプレートの選定
  • Bootstrap テンプレートの設定
  • 各種テンプレートファイルの修正

となります。



 

1. Bootstrap テンプレートの選定

Mezzanine は、テンプレートのCSS が Twitter Bootstrap の Version 3 に対応しています。

Twitter Bootstrap integration


Overview — Mezzanine 4.2.3 documentation


そこで、Mezzanine テーマをカスタマイズするための第一歩として、まずは Bootstrap テンプレートの選定を行いました。


結論から書くと、いろいろ探した中で、「WrapBootstrap」というサイトから「The Project - Multipurpose Template」という有料テンプレートを購入することにしました。価格は、1サイト分の利用ライセンスで $16 でした。


The Project - Multipurpose Template
wrapbootstrap.com


f:id:akiyoko:20161018081943p:plain

デモページもあります。
htmlcoder.me




他にも、

f:id:akiyoko:20161018082028p:plain

f:id:akiyoko:20161018082054p:plain

f:id:akiyoko:20161018082121p:plain

f:id:akiyoko:20161018082153p:plain
などを候補としてチェックしていました。


最終的にこの「The Project」テンプレートを採用することにしたのは、

  • 有料テンプレート($4 〜 $49)の中でも比較的安め($16)
  • 人気がある(購入数ランキングでトップ10)
  • テンプレートが多い(コーポレートやポートフォリオ、ブログ、ECサイトなど 30種類近くのテーマで 200ページ以上)
  • ちょくちょく更新されている

といった理由からです。

特に、必須要件である「ブログ」と「ECサイト」以外にも数多くのテンプレートファイルが含まれていて、今後もいろいろな使い勝手があるんじゃないかと思ったのが大きな決め手になりました。あと、使ってる人が多そう(購入者が多い)、サポートがきちんとしてるっぽい(更新頻度が多い)というのも安心ですよね。


なお、「The Project」の現時点の最新のバージョンは Version 1.3 で、Bootstrap 3.3 系と互換性があるとのこと(Bootstrap: Compatible with 3.3.x)。





 

2. Bootstrap テンプレートの設定

購入した Bootstrap テンプレート「The Project」を、Mac 上に構築した PyCharm プロジェクト(あるいは Ubuntu 上に構築した Mezzanine プロジェクト)に実際に当てていきます。


<前提>

  • Mac 上に構築した PyCharm プロジェクト名は「akiyokoproject」
    • プロジェクトのパスは「~/PycharmProjects/akiyokoproject」
  • ダウンロードした「products%2FWB0F82581.zip」をダブルクリックして解凍済み


 

2.1. Bootstrap テンプレートの static ファイルを配置

Mac 上に構築した PyCharm プロジェクトに、Bootstrap テンプレートの static ファイルを配置します。

以下、Mac での手順です。

### Bootstrap の js, css 等の static ファイルをコピーするための前準備
$ cd ~/Downloads/the_project_v.1.3/
### HFS Plus の拡張属性(Extended Attributes)を除去
$ xattr -cr .
$ find . -type d -exec chmod 775 {} \;
$ find . -type f -exec chmod 664 {} \;

### custom ディレクトリを作成
$ cd ~/PycharmProjects/akiyokoproject/
$ mkdir -p custom/{templates,static}
$ touch custom/__init__.py

### 「The Project」テンプレートの html/template 配下のファイルをコピー
$ cp -a ~/Downloads/the_project_v.1.3/html/template/bootstrap custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/css custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/fonts custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/images custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/js custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/plugins custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/videos custom/static/


 

2.2. collecttemplates コマンドを修正

Mezzanin のデフォルトテンプレートを custom アプリケーションにコピーしてくれる便利なコマンドが、Mezzanin には用意されています。

### テンプレートを全コピーしてくれるコマンド
$ python manage.py collecttemplates

(-a オプションを付けると、admin のテンプレートも対象に入れる)

しかしながら、コマンドを実行すると、

  (略)
  File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/mezzanine/core/management/commands/collecttemplates.py", line 42, in handle
    to_dir = settings.TEMPLATE_DIRS[0]
IndexError: list index out of range

という、Django 1.9 ならではのエラーが出たので(2016年7月時点)、
python manage.py collecttemplates fails with error IndexError: list index out of range · Issue #1512 · stephenmcd/mezzanine · GitHub
に従って暫定対応しました。 *2

diff --git a/config/settings.py b/config/settings.py
index bfd3958..5371af6 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -220,6 +220,7 @@ TEMPLATES = [
         },
     },
 ]
+TEMPLATE_DIRS = [TEMPLATES[0]['DIRS'][0]]

 if DJANGO_VERSION < (1, 9):
     del TEMPLATES[0]["OPTIONS"]["builtins"]


 

2.3. デフォルトテンプレートを templates 配下にコピー

一旦、Mezzanine デフォルトテンプレートを templates/ に全コピーしておきます。

$ python manage.py collecttemplates


 

2.4. custom アプリケーションを INSTALLED_APPS に登録

最後に、custom アプリケーションを settings.py の INSTALLED_APPS に登録します。

$ vi config/settings.py

---
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [
            os.path.join(PROJECT_ROOT, "custom/templates"),
            os.path.join(PROJECT_ROOT, "templates")
        ],
        "APP_DIRS": True,
    ・
    ・

INSTALLED_APPS = (
    ・
    ・
    # "mezzanine.accounts",
    # "mezzanine.mobile",
    "custom",
)
---

ちなみに、「INSTALLED_APPS」を修正しないと、「DEBUG = False」の場合は static ファイルをちゃんと認識してくれるものの、「DEBUG = True」の場合には認識してくれなかったので NG。つまり、「INSTALLED_APPS」への修正も必要だし、「TEMPLATES.DIRS」の修正もテンプレートファイル参照の優先度を付けるために必要となります。


またこの変更により、

$ python manage.py collecttemplates

を実行すると、Mezzanine のデフォルトテンプレートを custom 配下にコピーしてくれるようになります。

なので例えば、以下のコマンドは「cp -a ~/.virtualenvs/akiyokoproject/lib/python2.7/site-packages/mezzanine/core/templates/base.html custom/templates/」と同等になります。

$ python manage.py collecttemplates -t base.html

 

3. 各機能のテンプレートファイル修正

3.1. base.html のヘッダ修正

Bootstrap テンプレートの各種 static ファイルを読み込んでいるのが、base.html です。

なのでまず最初に、base.html の head 部分を
http://bitofpixels.com/blog/mezzatheming-creating-mezzanine-themes-part-1-basehtml/
に沿って修正しました。

diff --git a/custom/templates/base.html b/custom/templates/base.html
index 122e702..9d4149e 100644
--- a/custom/templates/base.html
+++ b/custom/templates/base.html
@@ -15,26 +15,71 @@
 <link rel="alternate" type="application/atom+xml" title="Atom" href="{% url "blog_post_feed" "atom" %}">
 {% endifinstalled %}

+<!-- Web Fonts -->
+<link href='//fonts.googleapis.com/css?family=Roboto:400,300,300italic,400italic,500,500italic,700,700italic' rel='stylesheet' type='text/css'>
+<link href='//fonts.googleapis.com/css?family=Raleway:700,400,300' rel='stylesheet' type='text/css'>
+<link href='//fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>
+<link href='//fonts.googleapis.com/css?family=PT+Serif' rel='stylesheet' type='text/css'>
+
 {% compress css %}
-<link rel="stylesheet" href="{% static "css/bootstrap.css" %}">
-<link rel="stylesheet" href="{% static "css/mezzanine.css" %}">
-<link rel="stylesheet" href="{% static "css/bootstrap-theme.css" %}">
-{% if LANGUAGE_BIDI %}
-<link rel="stylesheet" href="{% static "css/bootstrap-rtl.css" %}">
-{% endif %}
-{% ifinstalled cartridge.shop %}
-<link rel="stylesheet" href="{% static "css/cartridge.css" %}">
-{% if LANGUAGE_BIDI %}
-<link rel="stylesheet" href="{% static "css/cartridge.rtl.css" %}">
-{% endif %}
-{% endifinstalled %}
+<!-- Bootstrap core CSS -->
+<link href="{% static "bootstrap/css/bootstrap.css" %}" rel="stylesheet">
+<!-- Font Awesome CSS -->
+<link href="{% static "fonts/font-awesome/css/font-awesome.css" %}" rel="stylesheet">
+<!-- Fontello CSS -->
+<link href="{% static "fonts/fontello/css/fontello.css" %}" rel="stylesheet">
+<!-- Plugins -->
+<link href="{% static "plugins/magnific-popup/magnific-popup.css" %}" rel="stylesheet">
+<link href="{% static "plugins/rs-plugin/css/settings.css" %}" rel="stylesheet">
+<link href="{% static "css/animations.css" %}" rel="stylesheet">
+<link href="{% static "plugins/owl-carousel/owl.carousel.css" %}" rel="stylesheet">
+<link href="{% static "plugins/owl-carousel/owl.transitions.css" %}" rel="stylesheet">
+<link href="{% static "plugins/hover/hover-min.css" %}" rel="stylesheet">
+<!-- The Project's core CSS file -->
+<link href="{% static "css/style.css" %}" rel="stylesheet" >
+<!-- The Project's Typography CSS file, includes used fonts -->
+<!-- Used font for body: Roboto -->
+<!-- Used font for headings: Raleway -->
+<link href="{% static "css/typography-default.css" %}" rel="stylesheet" >
+<!-- Color Scheme (In order to change the color scheme, replace the blue.css with the color scheme that you prefer)-->
+<link href="{% static "css/skins/purple.css" %}" rel="stylesheet">
+<!-- Custom css -->
+<link href="{% static "css/custom.css" %}" rel="stylesheet">
 {% block extra_css %}{% endblock %}
 {% endcompress %}

 {% compress js %}
-<script src="{% static "mezzanine/js/"|add:settings.JQUERY_FILENAME %}"></script>
-<script src="{% static "js/bootstrap.js" %}"></script>
-<script src="{% static "js/bootstrap-extras.js" %}"></script>
+<!-- Jquery and Bootstap core js files -->
+<script type="text/javascript" src="{% static "plugins/jquery.min.js" %}"></script>
+<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.min.js" %}"></script>
+<!-- Modernizr javascript -->
+<script type="text/javascript" src="{% static "plugins/modernizr.js" %}"></script>
+<!-- jQuery Revolution Slider  -->
+<script type="text/javascript" src="{% static "plugins/rs-plugin/js/jquery.themepunch.tools.min.js" %}"></script>
+<script type="text/javascript" src="{% static "plugins/rs-plugin/js/jquery.themepunch.revolution.min.js" %}"></script>
+<!-- Isotope javascript -->
+<script type="text/javascript" src="{% static "plugins/isotope/isotope.pkgd.min.js" %}"></script>
+<!-- Magnific Popup javascript -->
+<script type="text/javascript" src="{% static "plugins/magnific-popup/jquery.magnific-popup.min.js" %}"></script>
+<!-- Appear javascript -->
+<script type="text/javascript" src="{% static "plugins/waypoints/jquery.waypoints.min.js" %}"></script>
+<!-- Count To javascript -->
+<script type="text/javascript" src="{% static "plugins/jquery.countTo.js" %}"></script>
+<!-- Parallax javascript -->
+<script src="{% static "plugins/jquery.parallax-1.1.3.js" %}"></script>
+<!-- Contact form -->
+<script src="{% static "plugins/jquery.validate.js" %}"></script>
+<!-- Background Video -->
+<script src="{% static "plugins/vide/jquery.vide.js" %}"></script>
+<!-- Owl carousel javascript -->
+<script type="text/javascript" src="{% static "plugins/owl-carousel/owl.carousel.js" %}"></script>
+<!-- SmoothScroll javascript -->
+<script type="text/javascript" src="{% static "plugins/jquery.browser.js" %}"></script>
+<script type="text/javascript" src="{% static "plugins/SmoothScroll.js" %}"></script>
+<!-- Initialization of Plugins -->
+<script type="text/javascript" src="{% static "js/template.js" %}"></script>
+<!-- Custom Scripts -->
+<script type="text/javascript" src="{% static "js/custom.js" %}"></script>
 {% block extra_js %}{% endblock %}
 {% endcompress %}


なお、
http://bitofpixels.com/blog/mezzatheming-creating-mezzanine-themes-part-1-basehtml/
の「Updating static asset locations」に記載されている通り、staticファイルのパス指定は「{% static %}」で囲まないと、ファイルアクセス時に

UncompressableFileError: 'bootstrap/css/bootstrap.css' isn't accessible via COMPRESS_URL ('/static/') and can't be compressed

というエラーが出てしまうので注意です。




 

3.2. 各機能のテンプレート修正

どの機能がどのテンプレートファイルに対応するかというのを理解するのが、Mezzanine のテンプレートをカスタマイズする上で非常に重要なポイントになります。

日本語の記事では、Monotalk さんのブログが分かりやすいかと思います(日本語の記事はとても少ないので貴重です!)。
mezzanineのテーマを作成してみました。 | Monotalk


以下、(他にも修正が必要なファイルがあるかもしれませんが)私が実際に修正したファイルの一覧です。


トップページ

  • custom/static/img/slide-1.jpg
  • custom/static/img/slide-2.jpg
  • custom/templates/base.html
  • custom/templates/index.html

ドロップダウンメニュー

  • custom/templates/base.html
  • custom/templates/pages/menus/dropdown.html

ブログ

  • custom/static/css/custom.css
  • custom/templates/blog/blog_post_detail.html
  • custom/templates/blog/blog_post_list.html
  • custom/templates/generic/includes/comment.html
  • custom/templates/generic/includes/comments.html

Twitter 連携

  • custom/static/css/custom.css
  • custom/templates/twitter/tweets.html

  

3.3. 入力フォームの form-control 問題対応

少し細かいですが、Form ページに使用されるテンプレート(custom/templates/includes/form_fields.html)内の入力フィールドタグに、Bootstrap 標準の「form-control」という CSS クラスが無いために、Bootstrap のスタイルがうまく当たらないという問題が発覚しました。


参考サイト


上記のサイトを参考にして、custom 配下に templatetags/__init__.py および templatetags/add_attributes.py を追加し、custom/templates/includes/form_fields.html を修正しました。

custom/
    static/
    templates/
    templatetags/
        __init__.py
        add_attributes.py
    __init__.py


custom/templatetags/add_attributes.py

from django import template
register = template.Library()


@register.filter(name='add_attributes')
def add_attributes(field, css):
    attrs = {}
    definition = css.split(',')

    for d in definition:
        if ':' not in d:
            attrs['class'] = d
        else:
            t, v = d.split(':')
            attrs[t] = v

    return field.as_widget(attrs=attrs)


custom/templates/includes/form_fields.html

diff --git a/custom/templates/includes/form_fields.html b/custom/templates/includes/form_fields.html
index f3d8697..f924e87 100644
--- a/custom/templates/includes/form_fields.html
+++ b/custom/templates/includes/form_fields.html
@@ -1,4 +1,4 @@
-{% load mezzanine_tags %}
+{% load mezzanine_tags add_attributes %}

 {% nevercache %}
 <input type="hidden" name="referrer" value="{{ request.META.HTTP_REFERER }}">
@@ -12,7 +12,7 @@
 <div class="form-group input_{{ field.id_for_label }} {{ field.field.type }}
     {% if field.errors %} has-error{% endif %}">
     {% if field.label %}<label class="control-label" for="{{ field.auto_id }}">{{ field.label }}</label>{% endif %}
-    {{ field }}
+    {{ field|add_attributes:"form-control" }}
     {% if field.errors %}
     <p class="help-block">
         {% for e in field.errors %}

 

まとめ

今回、Mezzanine テーマのカスタマイズとして、

  • Bootstrap テンプレートの選定
  • Bootstrap テンプレートの設定
  • 各種テンプレートファイルの修正

を実施した内容を記載しました。

次回は、Mezzanine 本番設定の第三弾として、「その3:Mezzanine の本番デプロイ」について記載します。


 

参考本

Twitter Bootstrap 3 関連の日本語の本は、数えるほどしかありません。
もし本格的に勉強したいのであれば、是非。

UIまで手の回らないプログラマのためのBootstrap 3実用ガイド

UIまで手の回らないプログラマのためのBootstrap 3実用ガイド

Bootstrapファーストガイド―CSS設計の手間を大幅に削減!

Bootstrapファーストガイド―CSS設計の手間を大幅に削減!

*1:Mezzanine を採用したのは、個人の趣味です。

*2:Mezzanine 4.2.0 から修正されたようです。 https://github.com/stephenmcd/mezzanine/commit/a84f38c38bdaee0b21c9e9209cec5148ba0ae108