【Django】django-summernoteを使ってブログアプリに便利なテキストエディターを構築


投稿日 2019年11月17日 >> 更新日 2024年4月18日

今回はブログ記事作成に便利なSummernote(サマーノート)のWYSIWYG(ウィジウィグ)機能を実装していきたいと思います。

Summernoteを知るにはまず、WYSIWYGを知らなければなりません。

WYSIWYGとは、「What You See Is What You Get:あなたが見たもの、それがあなたの手に入れたものだ」の略で、マークアップ言語などで装飾された記述をリアルタイムで表示しながら確認して進めることができるエディターです。

下図のようなMarkdown(マークダウン)記法ではなく、Markup(マークアップ)言語のHTMLをWYSIWYG機能によって簡単に使用することができます。

ちなみに下図の機能は、「django-markdownx」を使用したエディターです。

詳しくはこちら「【Django】ブログアプリにマークダウン(django-markdownx)を構築しカスタマイズする」をご覧ください。

WYSIWYGエディターとも呼ばれており、この機能を簡単に使用できるようにしたフレームワークの1つが「Summernote」です。

さらにそのSummernoteをDjangoで簡単に実装できるようにしたツールが「django-summernote」となります。

最終更新は2019年11月現在で「2019年4月」となっているので、Django2.Xでも問題無く使用することができます。

あとはソースコードを強調表示させるために使われるシンタックスハイライト機能も合わせて実装するので宜しれければお付き合いください。

それでは実際にブログアプリを例にしてdjango-summernoteを組み込んでいきます。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.6.8
pip 9.0.1
使用ライブラリ ライセンス
Django==2.2.6 BSD
django-summernote==0.8.11.4 MIT

adminサイトでWYSIWYGエディターを使う

今回は以下のようなモデルです。

# project/blog/models.py

from django.db import models


class Blog(models.Model):
    title = models.CharField('タイトル', max_length=50)
    text = models.TextField('テキスト')
    created_at = models.DateField('作成日', auto_now_add=True)
    updated_at = models.DateField('更新日', auto_now=True)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = 'ブログ'
        verbose_name_plural = 'ブログ'

管理画面の表示はデフォルトです。

# project/blog/admin.py

from django.contrib import admin
from .models import Blog


admin.site.register(Blog)

ターミナルにてモデルをデータベースに反映させスーパーユーザーを作成します。


$ python3 manage.py makemigrations

$ python3 manage.py migrate

$ python3 manage.py createsuperuser

$ python3 manage.py runserver
127.0.0.1:8000

adminサイトの追加・編集ページではこのようになります。

それでは「django-summernote」をインストールして、公式のドキュメント通りに設定していきましょう。

django-summernoteのインストール


$ pip3 install django-summernote

インストールができたら、プロジェクトディレクトリ内の「settings.py」に追記します。

# project/project/settings.py

INSTALLED_APPS = [
    'blog.apps.BlogConfig',   # アプリ
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_summernote',    # 追記
]

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

......

STATIC_URL = '/static/'

""" 画像や動画を保存するディレクトリ """
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

次にプロジェクトディレクトリ内の「urls.py」に追記します。

開発環境下(DEBUG = True)でメディアファイルを扱う設定も行います。

# project/project/urls.py

from django.contrib import admin
from django.urls import path, include

from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    path('admin/', admin.site.urls),
    path('summernote/', include('django_summernote.urls')),   # 追記
]

""" 開発環境下のみ設定 """
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

adminサイトで表示させるために、「admin.py」にてクラス定義します。

# project/blog/admin.py

from django.contrib import admin
from .models import Blog

from django_summernote.admin import SummernoteModelAdmin


class BlogAdmin(SummernoteModelAdmin):
    summernote_fields = '__all__'


admin.site.register(Blog, BlogAdmin)

設定を反映させるために、ターミナルにてマイグレーションを実行します。


$ python3 manage.py migrate

adminサイトに移動すると「DJANGO SUMMERNOTE」というアプリが作成されています。

このアプリ内には、記事で使われた画像や動画が自動的に納められます。

そして追加・編集ページに移動するとWYSIWYGエディターが使えるようになります。

下図は、ツールバーの赤線内に隠れているタグを使用している様子です。

保存をし、どのように表示されるか確認してみます。

テンプレートを作成

テンプレートで表示させるためにDjangoプロジェクトのファイルを編集していきます。

プロジェクトディレクトリ内の「urls.py」を編集。

※先ほど追記しなかったのは、単にadminサイト内での表示のため。

# project/project/urls.py

......
......


urlpatterns = [
    path('admin/', admin.site.urls),
    path('summernote/', include('django_summernote.urls')),
    path('', include('blog.urls')),    # 追記
]

.....

アプリディレクトリ内に新しく「urls.py」を作成。

# project/blog/urls.py

from django.urls import path
from . import views


app_name = 'blog'

urlpatterns = [
        path('', views.index, name='index'),
        path('detail/<int:blog_id>/', views.detail, name='detail'),
]

次に「views.py」を編集します。

# project/blog/views.py

from django.shortcuts import render, get_object_or_404
from .models import Blog


def index(request):
    blog = Blog.objects.order_by('-id')
    return render(request, 'blog/index.html', {'blog': blog })


def detail(request, blog_id):
    blog_text = get_object_or_404(Blog, id=blog_id)
    return render(request, 'blog/detail.html', {'blog_text': blog_text })

テンプレートファイルを作るために、「templates/blog」というディレクトリ内に、「base.html」「index.html」「detail.html」ファイルを作成します。

<!-- project/blog/templates/blog/base.html -->

<!doctype html>
<html>
    <head>
        <title>Blog</title>
    </head>
     <body>
        <h1>Blog</h1>
        {% block content %}
        {% endblock %}
    </body>
</html>
<!-- project/blog/templates/blog/index.html -->

{% extends 'blog/base.html' %}

{% block content %}

    <h1>一覧</h1>
    <br>

    {% for blog in blog %}
        <ul>
            <li>
                {{ blog.created_at }}
                {{ blog.title }}
                <a href="{% url 'blog:detail' blog_id=blog.id %}">詳細</a>
            </li>
        </ul>
    {% endfor %}

{% endblock %}

問題の詳細表示ですが、HTMLタグがエスケープされないようにDjango組み込みフィルタの「{{ safe }}」をオプションで使います。

<!-- project/blog/templates/blog/detail.html -->

{% extends 'blog/base.html' %}

{% block content %}

    <h1>{{ blog_text.title }}</h1>
    <p>{{ blog_text.updated_at }}</p>
    {{ blog_text.text | safe }}<hr />
    <br>

    <a href="{% url 'blog:index' %}">トップページに戻る</a>

{% endblock %}

ではシステムを再起動させ表示してみましょう。

エディターに書かれた通りの表示になっています。

ただ気になるのは「ソースコード」の部分です。

上手いこと行間を空けなければ見えずらいですし、できれば強調表示させたいです。

なので技術ブログなどでよく見かけるシンタックスハイライト機能を取り入れたいと思います。

その前に、面倒ですが自作フォームを作成してadminサイトに移動しなくても記事を追加できるようにしていきましょう。

不要な方は「シンタックスハイライト」まで飛ばしてください。

自作フォームの作成

では自作フォームを作っていきます。

フォームビューが必用なので、アプリディレクトリ内の「urls.py」から追記していきます。

# project/blog/urls.py

....

urlpatterns = [
        path('', views.index, name='index'),
        path('detail/<int:blog_id>/', views.detail, name='detail'),
        path('add_form', views.add_form, name='add_form'),   # 追記
]

アプリディレクトリ内に新しく「forms.py」を作成します。

※公式ドキュメントで詳しく説明されているので、そちらも参照ください。

# project/blog/forms.py

from django import forms
from .models import Blog

from django_summernote.widgets import SummernoteWidget


class BlogForm(forms.ModelForm):

    class Meta:
        model = Blog
        fields = ('title', 'text')
        widgets = {
                'text': SummernoteWidget(),
        }

「views.py」の編集。

# project/blog/views.py

from django.shortcuts import render, get_object_or_404, redirect  # 追記
from .models import Blog
from .forms import BlogForm  # 追記

........
........


def add_form(request):

    if request.method == "POST":
        form = BlogForm(request.POST)

        if form.is_valid():
            form.save()
            return redirect('blog:index')

    else:
        form = BlogForm

    return render(request, 'blog/form.html', {'form': form })

「templates/blog」ディレクトリ内に「form.html」を作成します。

<!-- project/blog/templates/blog/form.html -->

{% extends 'blog/base.html' %}

{% block content %}

    <a href="{% url 'blog:index' %}">トップページに戻る</a>

    <form action="{% url 'blog:add_form' %}" method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">投稿</button>
    </form>

{% endblock %}

「index.html」にて、記事作成画面に移るためのリンクを追記します。

<!-- project/blog/templates/blog/index.html -->

{% extends 'blog/base.html' %}

{% block content %}

    <h1>一覧</h1>
    <br>

    <!-- 追記 -->
    <a href="{% url 'blog:add_form' %}">追加</a>

    {% for blog in blog %}
        <ul>
            <li>
                {{ blog.created_at }}
                {{ blog.title }}
                <a href="{% url 'blog:detail' blog_id=blog.id %}">詳細</a>
            </li>
        </ul>
    {% endfor %}

{% endblock %}

これでadminサイトに飛ばずに記事作成を実行できます。

シンタックスハイライト

シンタックスハイライトを使うのに有名なフレームワークは、「Google Code Prettify」もしくわ「highlight.js」です。

上記以外にもあるかもしれませんが、今回は「highlight.js」を使って行きたいと思います。

ハイライトするテーマが豊富に揃っていて、非常に人気があるそうです。

このフレームワークを使うに当たって、必要なファイルをダウンロードするか、Web経由でCSS/Java Scriptを使用できるCDNを使うかのどちらかになります。

今回はコピーアンドペーストで簡単に導入できるCDNの方法で行っていきたいと思います。

まずは「highlight.js」の公式サイトへ行き、「Usage」をクリックします。

ページが移動したら、「Getting Started」という見出しのある青線内のソースコードをコピーして、「base.html」のbodyタグ内もしくわheadタグ内にペーストします。

<!-- project/blog/templates/blog/base.html -->

<!doctype html>
<html>
    <head>

        <title>Blog</title>

    </head>
    <body>

        <h1>Blog</h1>
        {% block content %}
        {% endblock %}

        <!-- ここにペースト -->
        <script>hljs.initHighlightingOnLoad();</script>

    </body>
</html>

次に同じページ内の下の方に「Getting the Library」という見出しがあるので、その文章内の「 "Common" section,」リンクをクリックします。

ページ移動できたら「jsdelivr」という見出し内のコードをコピーし、「base.html」にそれぞれペーストします。

<!-- project/blog/templates/blog/base.html -->

<!doctype html>
<html>
    <head>

        <title>Blog</title>
        <!-- ペースト-->
        <link rel="stylesheet"href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/styles/default.min.css">

    </head>
    <body>

        <h1>Blog</h1>
        {% block content %}
        {% endblock %}

        <!-- ペースト-->
        <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.16.2/build/highlight.min.js"></script>
        <script>hljs.initHighlightingOnLoad();</script>

    </body>
</html>

たったこれだけでソースコードを強調表示させることができます。

では実際に試してみましょう。

エディター内でソースコードを書いたら、赤線の「コード表示」をクリックします。

するとエディター内で使われているタグのソースコードが現れます。

記事にソースコードとして表示させるpreタグですが、そのpreタグと同じようにcodeタグを追記します。

「highlight.js」はcodeタグ内のソースコードを認識するので、django-summernoteのデフォルト設定では逐次codeタグを挿入する必要があります。

今回は表示させるだけなのでご了承ください。

エディタのカスタム

Summernoteを使っていれば分かる通り、エディタの幅や高さが気になってきます。

特に高さに関してはもう少し遊びが欲しいところです。

django-summernoteでは、設定ファイルに「SUMMERNOTE_CONFIG」を設定することで簡単にカスタムすることができます。

ではエディターの高さを変更してみましょう。

# project/project/settings.py

.......
.......

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

""" サマーノートの設定 """
SUMMERNOTE_CONFIG = {
    'summernote': {
        'height': '1000',
    },
}

この設定は高さを1000pxにしています。

他にも、SummernoteのテーマをBootstrap3もしくわBootstrap4に変更したり、プラグインをしてカスタマイズすることができるそうです。

詳しくはdjango-summernoteの公式ドキュメントもしくわSummernoteの公式サイトをご参照ください。

それでは以上となります。

最後までご覧いただきありがとうございました。