Djangoを用いてhtmlからPythonファイルを実行する

#ここで書くこと
この記事では「Djangoを用いてWebページからサーバ上に用意したPythonファイルを実行し、htmlから渡したデータをcsvファイルに出力する方法」について書いています。

また、執筆環境は
OS：macOS Catalina バージョン 10.15.4
Python：3.7.6
Django：3.0.3
となっています。

#Djangoについて
[前回の記事](https://qiita.com/kkkei257/items/40fa58849125e389f466)にてDjangoのインストール→htmlの表示までの流れについて書かせていただきましたので、Djangoの導入等についてはそちらをご参考ください。

#Pythonファイルの準備
###Pythonファイルの作成
まず、実行したいPythonファイルをDjangoのサーバ上に用意します。説明のために今回は以下のようなPythonファイルを用意します。

```write_data.py
# coding:utf-8
import os
import csv

# htmlからのデータをcsvファイルに記録
def write_csv(data):
    datas = [data]
    with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(datas)
```
これにより、write_csv()の引数にデータを渡して呼び出すことでdata.csvにそのデータが書き込まれます。なおここでは```<アプリ名>/application```フォルダ内にPythonファイルを配置＆csvファイルを出力することを想定しているため```os.getcwd()+'/myapp/application/'+'data.csv'```としていますが、この部分は環境に応じて適宜読み替えてください。

###Pythonファイルの配置
用意したPythonファイルをDjangoのサーバ上に配置します。Pythonファイルを```<アプリ名>/application```フォルダ内に置く場合、アプリ内のディレクトリは以下のようになるかと思います。

```
<プロジェクト名>
- db.sqlite3
- manage.py
- <プロジェクト名>
  - __init__.py
  - asgi.py
  - settings.py
  - urls.py
  - wsgi.py
  - __pycashe__
    - (.pycファイルが複数)
- <アプリ名>
  - __init__.py
  - admin.py
  - apps.py
  - models.py
  - tests.py
  - urls.py
  - views.py
  - migrations
    - __init__.py
  - application  # 作成したフォルダ
    - write_data.py  # 用意したPythonファイル
- templates
- static
```
もちろん、ここに置かないといけないという訳ではないのでファイルの配置場所は任意の場所で大丈夫です。

#htmlからPythonファイルを実行できるようにする
ファイルの用意と配置が完了しましたら、実際にhtmlからそのPythonファイルを実行させてcsvファイルを作成してみます。その前に、データの流れがどのようになっているかを把握してから実装した方が作業しやすいと思いますので、今回想定するデータの流れを書いてみたいと思います。
<img width="861" alt="スクリーンショット 2020-05-30 19.04.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/515612/5ef32df3-29a9-2965-cc93-32e97fe14f63.png">
パワポで作ってみました、ガサツな図で申し訳ありません、、
簡略化の為にプログラムと異なる部分も多々ありますが、csvファイルへの書き込みまでのデータの流れとしてはこんな感じです。何となくでもイメージが伝われば嬉しいです。

html(index.html)
　→myapp/views.pyのcall_write_data()にデータを送信
　→call_write_data()内でapplication/write_data.pyのwrite_csv()メソッドを実行
　→それにより渡したデータがcsvファイルに書き込まれる

言葉で表すとこんな感じです。これを踏まえ、次から実際にhtmlからPythonファイルを実行できるよう各ファイルを編集していきます。

###html：ajaxを用いてviews.pyにデータを送信する
views.py内のcall_write_data()メソッドにデータを渡す為に、html上でajaxを用いてデータを送信してみたいと思います。ここに関しては色々な方法があるかと思いますので、アプリケーションに合う方法を用いていただければと思います。

```index.html
{% load static %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>HTML</title>
    <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/>
    <script type="text/javascript" src="{% static 'script.js' %}"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  </head>
    
  <body>
      <span>文字を入力した後にボタンを押してください</span>
      <br>
      <input type="text" size="50" id="input_form">
      <button type="button" onclick="clickBtn()">送信</button>
      
      <script>
        function clickBtn() {
          var txt = document.getElementById("input_form").value;
          
          $.ajax({
            url: "{% url 'myapp:call_write_data' %}",
            method: 'GET',
            data: {"input_data": txt},
            dataType: "text",
            contentType: "application/json",
            beforeSend: function(xhr, settings) {
              if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
              }
            },
            error: function(xhr, status, error) {
              console.log("error")
            }
          })
          .done(function(data) {
            console.log("Success"); 
          });
          
          // csrf_tokenの取得に使う
          function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
              var cookies = document.cookie.split(';');
              for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
                }
              }
            }
            return cookieValue;
          }

          // ヘッダにcsrf_tokenを付与する関数
          function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          };
        }
        
      </script>
  </body>
</html>
```
送信ボタンを押すとclickBtn()が実行され、ajaxによりデータが送信されるようになっています。urlの部分は```myapp: call_write_data```となっており、これによりviews.pyに記述したcall_write_data()というメソッドにデータが送信されます。

データ部分は```data: {"input_data": txt}```となっており、データを受け取る側では"input_data"と指定することで目的のデータを取得することができます。ここでは一つだけにしていますが、```data: {"data1": txt1, "data2": txt2}```のようにデータの個数や型などデータの形式については自由に設定できます。

###myapp/views.py：実行したいPythonファイルとメソッドを指定して実行する
```myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
# application/write_data.pyをインポートする
from .application import write_data

# Create your views here.
def index(req):
    return render(req, 'index.html')

# ajaxでurl指定したメソッド
def call_write_data(req):
    if req.method == 'GET':
        # write_data.pyのwrite_csv()メソッドを呼び出す。
        # ajaxで送信したデータのうち"input_data"を指定して取得する。
        write_data.write_csv(req.GET.get("input_data"))
        return HttpResponse()
```
ここではajaxで送信されたデータを取得し、実行したいPythonファイルのメソッドにそのデータを渡して呼び出しています。

###myapp/urls.py：htmlからviews.pyのcall_write_data()にデータを送信できるようにする
```myapp/urls.py
from django.urls import path
from . import views

app_name = 'myapp'
urlpatterns = [
    path(r'', views.index, name='index'),
    # 以下を追記(views.pyのcall_write_data()にデータを送信できるようにする)
    path("ajax/", views.call_write_data, name="call_write_data"),
]

```
パスを通すことで、html上からajax通信を用いてviews.pyの指定したメソッドにデータを送信できるようになります。

#実際にcsvファイルに書き込まれるかを確認する
以上で必要な編集は完了です。

```
$ python manage.py runserver
```
でサーバを立ち上げ、表示されたアドレスにアクセス(htmlを表示)し入力フォームに適当な文字を入力してから送信ボタンを押してみてください。

```
- <アプリ名>
  - __init__.py
  ...
  - application
    - write_data.py
    - data.csv  # 生成されたcsvファイル
```
このようにアプリ内に作成したapplicationフォルダの中にcsvファイルが生成され、入力した文字列がファイルに記録されていれば問題なくデータが送信されている＆Pythonファイルが実行されています。

# 補足
今回はhtmlからPythonファイルを実行し、送信されたデータをcsvファイルに書き込む方法についてご説明しましたがその逆も可能です。

説明の簡略化の為に、”write_data.pyから渡されたデータをviews.pyで取得しそれをhtmlに渡して表示する”ことを行ってみたいと思います。変更箇所のあるファイルだけ以下に載せていきます。

###myapp/application/write_data.py
return_text()というメソッドを追記する。

```diff_python:myapp/application/write_data.py
   # coding:utf-8

   import os
   import csv

   # htmlからのデータをcsvファイルに記録
   def write_csv(data):
       datas = [data]
       with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f:
           writer = csv.writer(f, lineterminator='\n')
           writer.writerow(datas)

   # 以下を追記(return_text()を呼び出すと"Hello!!"が返される)        
+  def return_text():
+      return "Hello!!"
```
###myapp/views.py
write_data.pyにて追記したreturn_text()を呼び出し、返ってきた文字列を取得する(dataに格納する)。そのデータを```HttpResponse()```を用いてhtmlに渡す。

```diff_python:myapp/views.py
  from django.shortcuts import render
  from django.http import HttpResponse
  # application/write_data.pyをインポートする
  from .application import write_data

  # Create your views here.
  def index(req):
      return render(req, 'index.html')

  # ajaxでurl指定したメソッド
  def call_write_data(req):
      if req.method == 'GET':
          # write_data.pyのwrite_csvメソッドを呼び出す。
          # ajaxで送信したデータのうち"input_data"を指定して取得する。
          write_data.write_csv(req.GET.get("input_data"))
        
          # write_data.pyの中に新たに記述したメソッド(return_text())を呼び出す。
+         data = write_data.return_text()
          # 受け取ったデータをhtmlに渡す。
+         return HttpResponse(data)
```

###index.html
ajax通信が成功するとHttpResponse()で渡した引数が```.done(function(data) {```の部分に渡されるので、そのデータをページに表示する。

```diff_html:index.html
  {% load static %}
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <title>HTML</title>
      <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/>
      <script type="text/javascript" src="{% static 'script.js' %}"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    </head>
    
    <body>
      <span>文字を入力した後にボタンを押してください</span>
      <br>
      <input type="text" size="50" id="input_form">
      <button type="button" onclick="clickBtn()">送信</button>
      
      <!-- views.pyから渡された文字列を表示する。 -->
+     <br>
+     <span id="text"></span>
      
      <script>
        function clickBtn() {
          var txt = document.getElementById("input_form").value;
          
          $.ajax({
            url: "{% url 'myapp:call_write_data' %}",
            method: 'GET',
            data: {"input_data": txt},
            dataType: "text",
            contentType: "application/json",
            beforeSend: function(xhr, settings) {
              if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
              }
            },
            error: function(xhr, status, error) {
              console.log("error")
            }
          })
          .done(function(data) {
            // views.pyのcall_write_data()にてreturnしたHttpResponse(data)のデータはここで取得できる。
            // フォームの下部に追記したspan部分の内容を書き換える。
+           document.getElementById("text").textContent = data;
            console.log("Success"); 
          });
          
          // csrf_tokenの取得に使う
          function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
              var cookies = document.cookie.split(';');
              for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
                }
              }
            }
            return cookieValue;
          }

          // ヘッダにcsrf_tokenを付与する関数
          function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          };
        }
        
      </script>
    </body>
  </html>
```
これにより、送信ボタンを押して正しくPythonファイルが実行されると"Hello!!"という文字列がhtmlに渡され、入力フォームの下の部分に渡されたHello!!という文字列が表示されると思います。

これを応用することで、サーバ上のPythonファイルを実行しサーバ上のファイルのデータを読み書きしたり、そのデータをhtml上に反映させることが可能になります。