Django + Ace(django_ace) + DockerでオンラインPython実行環境を実装する

タイトルの通り,Django + Ace + DockerでオンラインPython実行環境を実装します. 一回で収まらないかもしれないので「その1」としています.

参考にしたサイト

環境

  • Django==3.0
  • django_ace==1.0.11
  • Python==3.6.8
  • Docker Pythonイメージ: python:3.7-slim-buster

構成の説明

  • Django: フレームワークとして使用
  • Ace: Webページ上でテキスト編集する際のシンタックスハイライト等に使用
  • django-ace: AceをDjango内のView定義から呼び出せるようにしたもの
  • Docker: 実行環境をサーバー内で隔離しないと悪意あるコードが実行できてしまうので,Dockerコンテナを使用

環境構築&必要ライブラリ群のインストール

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install Django==3.0
$ pip install django-ace==1.0.11

プロジェクトの作成

$ django-admin startproject project
$ cd project
$ django-admin startapp editor
$ mkdir history #実行するスクリプトの保存先

この時点で,ディレクトリは以下のような構成になっているはずです.

project/
 ├ editor/
 ├ project/
 ├ history/
 └ manage.py

フォームビューの作成

エディタ部分を実装するために,フォームビューを実装します. CDN等で提供されるAceを埋め込んで利用しても良いですが,ここではdjango-aceを使用します.

#forms.py
from django import forms
from django_ace import AceWidget

class EditorForm(forms.Form):
    code = forms.CharField(
        widget = AceWidget(
            mode="python",
            theme="twilight",
            width="100%"
        )
    )

EditorFormを作成したら,Viewを追加していきます. 下記のコードでは,index.htmlをテンプレートとして,HTML中のformから実行コードを取得します. 取得したコードは,historyフォルダ内に保存され,dockerコンテナ内で実行されます.

  • start_docker(code): dockerコンテナに実行するコードを投げ,実行するコード.
  • Home(views.FormVIew): html内のフォームから取得したコードを,start_dockerに投げます.
# views.py
from .forms import EditorForm

FILE_DIR = os.path.join(settings.BASE_DIR, 'history')
DOCKER_CMD = 'docker run -i --rm --name tmp_container -v {}:/usr/src/myapp -w /usr/src/myapp python:3.7 python {}'

def start_docker(code):
    # dockerコンテナ内でコードを実行する
    file_name = '{}.py'.format(datetime.datetime.now().isoformat())
    file_path = os.path.join(FILE_DIR, file_name)

    with open(file_path, 'w', encoding='utf-8') as file:
        file.write(code)

    cmd = DOCKER_CMD.format(FILE_DIR, file_name)
    ret = subprocess.run(
        cmd, timeout=15, shell=True,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT
    )

    return ret.stdout.decode()

class Home(generic.FormView):
    template_name = 'index.html'
    form_class = EditorForm
    sucess_url = reverse_lazy('home')

    def form_valid(self, form):
        # 送信ボタンで呼ばれる
        code = form.cleaned_data['code']
        output = start_docker(code)
        context = self.get_context_data(form=form, output=output)
        return self.render_to_response(context)
<!--index.htmlの一部のみ抜粋-->
<head>
....
{{ form.media}}
</head>

<body>
....
<div class="col-12">
    <form action="" method="POST">{% csrf_token %}
        {{ form.code }}
        <button class="btn btn-primary" type="submit">Run</button>
    </form>
</div>

<div class="col-12">
    <label for="exampleFormControlTextarea1" class="form-label">実行結果</label>
    <textarea class="form-control" id="exampleFormControlTextarea1" rows="3" disabled>{{ output }}</textarea>
</div>
...
</body>

以上をいい感じにまとめると,下記のようなコード編集画面が現れます.(あとで追記)


戻る

About

IMAXおじさんが(主に)技術系記事を備忘録として残していくブログです.

Category

  1. Tech
  2. Daily
  3. Job
  4. Other