Django で Markdown のファイルを表示する!

記事
IT・テクノロジー

Django で Markdown のファイルを表示する!

前回の投稿では、復習も兼ねて、仮想環境を作って Django の基本設定をするまでを紹介しました。 今回は、入力フォームで Markdown で書かれたファイルを選択して、そのファイルの中身を表示する機能を Django で作るやり方を紹介します。

ファイルのアップロード先の指定

これも以前の投稿で紹介していますが、復習を兼ねて再度取り上げておきます。 以前の投稿でも触れていますが、ファイルのアップロードなどの機能は利用する Web サーバーによっても変わってきます。

Django の場合、開発で利用するサーバーと実際にインターネットで公開する際の Web サーバーは違います。そこで前回同様にこの記事でもまずは、開発用の環境で機能を実現する方法を紹介します。

Django の場合は、アップロードされたファイルを置く場所を指定する必要があります。その設定はプロジェクトフォルダの「settings.py」で行います。

追加するのは

* MEDIA_ROOT
* MEDIA_URL
になります。この2つでアップロードしたファイルなどを置くフォルダを指定します。静的ファイル(スタティックなファイル)の置き場所と似た設定なので、「settings.py」の最後の方に設定されている「STATIC_URL」の近くで設定すると管理しやすくなります。

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

ここで指定したフォルダ「media」は自動的には作られません。従って、プロジェクトフォルダに「media」というサブフォルダを作成します。名前はなんでも構いませんが、作成するフォルダ名と設定で指定するフォルダ名は一致している必要があります。

フォームと Markdown の中身の表示

今回作成する Web アプリでは2つの表示モードがあります。

* Markdown のファイルを選択して Web サーバーに送るページ
* 選択した Markdown の中身を表示するページ
もう一つの方針は全て Django で実装するということです。

そこで方針としては、フロントエンドのブラウザー側では、Javascript を使わずに、フォームで選択したファイルを送る(submit)して、送られてきたファイルの中身をサーバー側で描画(レンダリング)して表示させるという方法を使います。

フォームの表示
フォームを表示する際は Web ブラウザは通常のリクエストをするだけなので、HTTP のメソッドは「GET」になります。

一方で Markdown のファイルを選択して送信ボタン(Submit)を押した場合は、フォームのデータとしてファイルを「POST」で送るようにします。

こうすると、サーバー側では、送られてくるリクエスの HTTP のメソッドのタイプをみて処理を変える事ができます。

このアクセスに使われる HTTP のメッソドを見て、利用するテンプレートを入れ替えます。「GET」の場合は、ファイル選択フォームのテンプレートを表示して、「POST」の場合は送られてきた Markdown の中身を表示するようにしています。

import os
from django.conf import settings
from django.shortcuts import render
from pathlib import Path

# Create your views here.

def index(request):
    return render(request, 'basic.html')

def upload(request):
    if request.method == "GET":
        '''Webブラウザーからアップロードフォームのリクエスト’'''
        return render(request, 'app/upload.html')
    elif request.method == "POST":
        ''' 選択されたMarkdownを送ってくるためのリクエスト '''
        upload_path = os.path.join(settings.MEDIA_ROOT, request.FILES['file_upload'].name)
        with open(upload_path, "wb") as output_file:
            for chunk in request.FILES['file_upload'].chunks():
                output_file.write(chunk)
        f = open(upload_path, "r")
        contents = f.read()
        import markdown2

        mk = markdown2.markdown(contents)

        print(mk)
        context = {
            'html': mk
        }
        return render(request, 'app/markdown.html', context)
フォームの場合は、ファイル選択フォームのテンプレート(upload.html)を呼び出すだけです。

Markdown のファイルの処理は今回は「markdown2」というパッケージを利用しています。一旦送られてきたファイルを先程作成した「media」フォルダに保存した後、改めて読み込んでいます。 そのファイルを、markdown2 のパッケージを利用して HTML に変換し h てテンプレート(markdown.html)に渡しています。

markdown2 を利用するには、pip を使ってインストールする必要があります

$ pip3 install markdown2

アプリのテンプレートの作成

あとは、テンプレートを作成すれば作業は完了です。 前回作成したテンプレートは「プロジェクトレベル」のテンプレートなのでプロジェクトフォルダに「templates」というフォルダを作成してテンプレートを保存しました。

今回は、アプリケーションのテンプレートを作成します。 もちろん、プロジェクトのテンプレートのフォルダに入れても良いのですが、Django の場合、プロジェクトの下に複数のアプリを作成する事ができます。従って、プロジェクト特有のテンプレートの場合は、各アプリのフォルダに作成した方が、管理がやりやすくなります。

ところが、各アプリのフォルダに「templates」のフォルダだけだとアプリ間で区別をするのが難しくなります。そこで、Django では各アプリのテンプレートは「templates/[アプリ名]」のようにフォルダを作成してそこにテンプレートを保存します。 今回の例の場合、アプリ名は「app」なので、「templates/app」をアプリのフォルダに作成してそこにテンプレートを置きます。

今回作成するテンプレートは

* upload.html
* markdown.html
の2つです。

upload.html
「upload.html」の例です。 前回作成した、「basic.html」を書き換えて、中身を埋め込めるようにしています。

プロジェクトレベルのテンプレート「basic.html」の例です。 「{% block content %}」と「{% endblock %}」の間にアプリのテンプレートの中身を埋め込むようにしています。こうすることで、全てのページにコピーライトを入れる事ができて、ページ毎に入れる必要はありません。便利ですよね!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Home Page</title>
  </head>
  <body>
    <div>{% block content %} {% endblock %}</div>
    <div class="footer">
      <footer>
        Copyright(c) 2021 by Silicon Valley Super Ware, all rights reserved.
      </footer>
    </div>
  </body>
</html>
アプリのテンプレートでは、プロジェクトレベルのテンプレートを拡張する形で呼び出します。フォームの部分で送信ボタン(submit)が押されたら「POST」でデータを送るように指定します。送り先の URL は表示しているページと同じです。ファイルを選択する「input」の DOM に「file_upload」という名前をつけているので「file_uplad」というタグの着いた JSON がサーバーに送られます。

{% extends 'basic.html' %} {% block content %}
<form method="POST" enctype="multipart/form-data" method="POST">
  {% csrf_token %}
  <div class="form-group">
    <label>File</label>
    <input class="form-control" type="file" name="file_upload" />
    <button class="btn btn-primary" type="submit">Submit</button>
  </div>
</form>
{% endblock %}
markdown.html
もう一つが、「markdown.html」です。テンプレートに渡されるデータが HTML のテキストになります。

{% extends 'basic.html' %} {% block content %} {{html|safe}} {% endblock %}
これだけです。Django のテンプレートで HTML のテキストを表示させる場合は、「safe」というフィルタを使います。このデータをプロジェクトレベルの「basic.html」に埋め込みます。

あとは、URL のマッピングをすれば完了です。

urls.py の変更
アプリのフォルダの「urls.py」を変更して、「upload」を指定された場合、今回作成したビューを読み込むように設定します。

from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name="index"),
    path('upload/',views.upload, name="upload"),
]
これで、「127.0.0.1:8000/upload」を指定するとファイル選択のフォームが表示されて、Markdown のファイルを選択して送信ボタン(Submit)をクリックすると HTML に変換されたデータが表示されます。

問題もあります。。。

実際に作成した方はお気づきの方もいらっしゃると思いますが、今回利用している Markdown を扱うモジュール「markdown2」は Markdown を HTML に変換するには、「extras」で拡張機能を指定する必要があります。それでも、テーブルなどは上手く表示できません。

今回この例のために初めて試したので全てドキュメントを読んでいませんが、感覚的には、Javascript のパッケージの方が格段に使いやすいと思います。

今後もう少し、調べてもう少し効率良く Markdown を表示する機能を実現する方法を調べる予定です。現状だと、Django を使って Python で処理するよりは、フロントエンドで Javascript のモジュールを利用してレンダリングした方が簡単に綺麗な表示が可能だと思います。

まとめ
Django を利用して、Markdown で書いた内容を HTML に変換して表示する例を紹介してみました。今回、採用した「markdown2」は勉強不足もあって、Javascript のモジュールに比べると Markdown の記述のサポートを完全にすることができませんでした。もう少し調査が必要なようです。

手っ取り早く綺麗な表示をしたい場合は、Javascript のモジュールを活用した方が簡単に綺麗な表示が可能かと思います。Python で処理する場合には、もう少しドキュメントを良く読んだり、別のパッケージを活用する必要がありそうです。

しかし、今回の例は、Django でのページの表示の切り替えやフロントエンドからのデータの送信をどのようにやるかが主なポイントです。基本的なやり取りが含まれているのでぜひ実際に作って動かしてみると、Django の動きの理解が深まると思います。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す