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