Wagtail(Django)でシンタックスハイライトを使う

Wagtail(Django)でシンタックスハイライトを使うやり方に詰まったので
やり方をメモしておきます。

また、シンタックスハイライトを適応できるのは、マークダウン形式で保存したフィールドです。
(wagtailの標準のリッチテキストエディタには適応できません。)
マークダウン形式で保存するためのカスタマイズは<a hreh="https://qiita.com/Atsu_Tech/items/c13ac50c9fe55472fe51">こちら</a>を参照してください。



## 1. Pygmentsとwagtail markdown pygmentsのインストール
シンタックスハイライトの実装にあたり、今回はPygmentsというパッケージを使用します　。

↓公式のドキュメントPygments
https://pygments.org/docs/styles/

下記コマンドより、wagtailのプロジェクトにインストールします。
```
pip install Pygments
```

```
pip install wagtail markdown pygments
```

## 2. オリジナルのテンプレートタグとフィルタの作成
オリジナルのテンプレートタグとフィルタを作成します。
テンプレートで表示したい変数に自作のフィルタを当ててシンタックスハイライトを適応します。
フィルタとは下の画像のように、赤枠で囲んだ部分です。
![filter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3332453/068f04aa-8c84-868e-f273-89759c8cf403.png)
この例では、markdownというフィルタが掛けられています。
今回は、このフィルタを自作しシンタックスハイライトを適応していく流れとなります。

自作する方法は以下で説明していきますが、djangoの公式ドキュメントで手順が紹介されているので
良ければ参考にしてみてください。
https://docs.djangoproject.com/ja/5.0/howto/custom-template-tags/

まずは、テンプレートタグを作成します。
と言ってもただディレクトリを作成するだけですので簡単です。

自分が作ったwagtailのアプリケーション(python manage.py startapp コマンドで作成したもの)の中に
直下で"templatetags"という名前のディレクトリを作成してください。

例として私はcontentsという名前のアプリケーションを作成していて、そこでフィルタを使いたかったので
アプリケーションの中の直下の階層に"templatetags"を作成しました。

![名称未設タg定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3332453/8e1b3188-b92d-8aab-82d0-46b398e8d221.png)

作成できたら、templatetagsの中に"__init__.py"というファイルを作ってください。
__init__.pyは作ったら中は何もコード書かず空白の状態でOKです。


テンプレートタグの作成はこれで終わりです。
続いてフィルタを作っていきます。
templatetagsの中にフィルタのpythonファイルを好きな名前で作成してください。
今回は、"markdown_with_highlight.py"としました。
ファイルを作成したら、中身を下記のように編集しましょう。

```python:markdown_with_highlight.py
from django import template
from django.template.defaultfilters import stringfilter
import markdown

register = template.Library()

@register.filter
@stringfilter
def markdown_with_highlight(value):

    html_output = markdown.markdown(value, extensions=['fenced_code', 'codehilite'])
    return(html_output)
```
これでフィルタの作成が完了です。

## 3. CSSの生成
フィルタを作成しただけでは、シンタックスハイライトが掛かった見た目になりません。
CSSでシンタックスハイライトの見た目を適応します。

以下のコマンドで、シンタックスハイライト用のCSSファイルを作成します。
```
pygmentize -S monokai -f html -a .highlight > monokai.css
```
すると、wagtaiプロジェクトの直下に"monokai.css"が作成されてます。

```css:monokai.css
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #49483e }
.highlight { background: #272822; color: #f8f8f2 }
.highlight .c { color: #959077 } /* Comment */
.highlight .err { color: #ed007e; background-color: #1e0010 } /* Error */
.highlight .esc { color: #f8f8f2 } /* Escape */
.highlight .g { color: #f8f8f2 } /* Generic */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #ff4689 } /* Operator */
.highlight .x { color: #f8f8f2 } /* Other */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #959077 } /* Comment.Hashbang */
.highlight .cm { color: #959077 } /* Comment.Multiline */
.highlight .cp { color: #959077 } /* Comment.Preproc */
.highlight .cpf { color: #959077 } /* Comment.PreprocFile */
.highlight .c1 { color: #959077 } /* Comment.Single */
.highlight .cs { color: #959077 } /* Comment.Special */
.highlight .gd { color: #ff4689 } /* Generic.Deleted */
.highlight .ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */
.highlight .ges { color: #f8f8f2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #f8f8f2 } /* Generic.Error */
.highlight .gh { color: #f8f8f2 } /* Generic.Heading */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .go { color: #66d9ef } /* Generic.Output */
.highlight .gp { color: #ff4689; font-weight: bold } /* Generic.Prompt */
.highlight .gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #959077 } /* Generic.Subheading */
.highlight .gt { color: #f8f8f2 } /* Generic.Traceback */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #ff4689 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #ff4689 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #ff4689 } /* Operator.Word */
.highlight .pm { color: #f8f8f2 } /* Punctuation.Marker */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
```
このファイルの中身を、.highlight クラス名を .codehilite に変えます。

```css:monokai.css(編集後)
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.codehilite.hll { background-color: #49483e }
.codehilite{ background: #272822; color: #f8f8f2 }
.codehilite.c { color: #959077 } /* Comment */
.codehilite.err { color: #ed007e; background-color: #1e0010 } /* Error */
.codehilite.esc { color: #f8f8f2 } /* Escape */
.codehilite.g { color: #f8f8f2 } /* Generic */
.codehilite.k { color: #66d9ef } /* Keyword */
.codehilite.l { color: #ae81ff } /* Literal */
.codehilite.n { color: #f8f8f2 } /* Name */
.codehilite.o { color: #ff4689 } /* Operator */
.codehilite.x { color: #f8f8f2 } /* Other */
.codehilite.p { color: #f8f8f2 } /* Punctuation */
.codehilite.ch { color: #959077 } /* Comment.Hashbang */
.codehilite.cm { color: #959077 } /* Comment.Multiline */
.codehilite.cp { color: #959077 } /* Comment.Preproc */
.codehilite.cpf { color: #959077 } /* Comment.PreprocFile */
.codehilite.c1 { color: #959077 } /* Comment.Single */
.codehilite.cs { color: #959077 } /* Comment.Special */
.codehilite.gd { color: #ff4689 } /* Generic.Deleted */
.codehilite.ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */
.codehilite.ges { color: #f8f8f2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.codehilite.gr { color: #f8f8f2 } /* Generic.Error */
.codehilite.gh { color: #f8f8f2 } /* Generic.Heading */
.codehilite.gi { color: #a6e22e } /* Generic.Inserted */
.codehilite.go { color: #66d9ef } /* Generic.Output */
.codehilite.gp { color: #ff4689; font-weight: bold } /* Generic.Prompt */
.codehilite.gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */
.codehilite.gu { color: #959077 } /* Generic.Subheading */
.codehilite.gt { color: #f8f8f2 } /* Generic.Traceback */
.codehilite.kc { color: #66d9ef } /* Keyword.Constant */
.codehilite.kd { color: #66d9ef } /* Keyword.Declaration */
.codehilite.kn { color: #ff4689 } /* Keyword.Namespace */
.codehilite.kp { color: #66d9ef } /* Keyword.Pseudo */
.codehilite.kr { color: #66d9ef } /* Keyword.Reserved */
.codehilite.kt { color: #66d9ef } /* Keyword.Type */
.codehilite.ld { color: #e6db74 } /* Literal.Date */
.codehilite.m { color: #ae81ff } /* Literal.Number */
.codehilite.s { color: #e6db74 } /* Literal.String */
.codehilite.na { color: #a6e22e } /* Name.Attribute */
.codehilite.nb { color: #f8f8f2 } /* Name.Builtin */
.codehilite.nc { color: #a6e22e } /* Name.Class */
.codehilite.no { color: #66d9ef } /* Name.Constant */
.codehilite.nd { color: #a6e22e } /* Name.Decorator */
.codehilite.ni { color: #f8f8f2 } /* Name.Entity */
.codehilite.ne { color: #a6e22e } /* Name.Exception */
.codehilite.nf { color: #a6e22e } /* Name.Function */
.codehilite.nl { color: #f8f8f2 } /* Name.Label */
.codehilite.nn { color: #f8f8f2 } /* Name.Namespace */
.codehilite.nx { color: #a6e22e } /* Name.Other */
.codehilite.py { color: #f8f8f2 } /* Name.Property */
.codehilite.nt { color: #ff4689 } /* Name.Tag */
.codehilite.nv { color: #f8f8f2 } /* Name.Variable */
.codehilite.ow { color: #ff4689 } /* Operator.Word */
.codehilite.pm { color: #f8f8f2 } /* Punctuation.Marker */
.codehilite.w { color: #f8f8f2 } /* Text.Whitespace */
.codehilite.mb { color: #ae81ff } /* Literal.Number.Bin */
.codehilite.mf { color: #ae81ff } /* Literal.Number.Float */
.codehilite.mh { color: #ae81ff } /* Literal.Number.Hex */
.codehilite.mi { color: #ae81ff } /* Literal.Number.Integer */
.codehilite.mo { color: #ae81ff } /* Literal.Number.Oct */
.codehilite.sa { color: #e6db74 } /* Literal.String.Affix */
.codehilite.sb { color: #e6db74 } /* Literal.String.Backtick */
.codehilite.sc { color: #e6db74 } /* Literal.String.Char */
.codehilite.dl { color: #e6db74 } /* Literal.String.Delimiter */
.codehilite.sd { color: #e6db74 } /* Literal.String.Doc */
.codehilite.s2 { color: #e6db74 } /* Literal.String.Double */
.codehilite.se { color: #ae81ff } /* Literal.String.Escape */
.codehilite.sh { color: #e6db74 } /* Literal.String.Heredoc */
.codehilite.si { color: #e6db74 } /* Literal.String.Interpol */
.codehilite.sx { color: #e6db74 } /* Literal.String.Other */
.codehilite.sr { color: #e6db74 } /* Literal.String.Regex */
.codehilite.s1 { color: #e6db74 } /* Literal.String.Single */
.codehilite.ss { color: #e6db74 } /* Literal.String.Symbol */
.codehilite.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.codehilite.fm { color: #a6e22e } /* Name.Function.Magic */
.codehilite.vc { color: #f8f8f2 } /* Name.Variable.Class */
.codehilite.vg { color: #f8f8f2 } /* Name.Variable.Global */
.codehilite.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.codehilite.vm { color: #f8f8f2 } /* Name.Variable.Magic */
.codehilite.il { color: #ae81ff } /* Literal.Number.Integer.Long */
```

編集できたら、CSSをテンプレートに適応する準備をします。
自分の場合は、スタートプロジェクトの中のbase.htmlで共通のテンプレートを作っているので
そのbase.htmlにCSSのリンクを貼るというやり方をしました。
まずスタートプロジェクトのstaticディレクトリ>cssディレクトリに先ほどのmonokai.cssを移動してきてください。
次に、base.htmlにリンクを貼ります。
```html:base.html
<head>
    <link rel="stylesheet" type="text/css" href="{% static 'css/monokai.css' %}">
</head>
```
これで、シンタックスハイライトのCSSが使えるようになります。

## 4. テンプレートにフィルタ適応
テンプレートでフィルタを適応すには下記のように書きます。

```html

・・・省略・・・

{% load markdown_with_highlight %}

・・・省略・・・

・・・省略・・・

    <p>
        {{ page.body|markdown_with_highlight|safe }}
    </p>
・・・省略・・・

```
作ったフィルタをロードし、変数に当てます。
また"safe"と記述がありますがこちらはhtmlタグを認識するためのものです。
これを書かないと、htmlタグがレンダリングされすタグがそのまま出力されてしまいます。

## 5. サーバー再起動
サーバー再起動しないと自作フィルタ認識されませんので、一度再起動します。
ページを見てみると、シンタックスハイライトが適応されているのが確認できました。
![れs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3332453/119adc84-609b-7b95-a222-4256900adb74.png)