【Django x JS】固定のSQLクエリとCSV形式のデータを合体させてSQLファイルとしてダウンロードする方法

# 概要
Djangoを使用して、固定のSQLクエリとCSV形式のデータからなるダウンロード可能なSQLファイルを生成するコードを実装したので、紹介します。

# サンプルコード

```py
from django.http import HttpResponse
import csv

# 任意のCSVデータをリストに格納。別モジュールで取得する処理を実装するなりDBからデータ取得するなり
data_export_list = [
    (1, "hogehoge"),(2,"foofoo")
]

response = HttpResponse(content_type='text/sql')
response['Content-Disposition'] = 'attachment; filename="data_sql.sql"'  
print(response) 
# <HttpResponse status_code=200, "text/sql">

writer = csv.writer(response, delimiter="\t")
writer.writerow(["INSERT INTO `sample_table` (sample_id, sample_phrase) VALUES "])
print(writer) 
# <_csv.writer object at 0xffff7f83cc70>

for index, data in enumerate(data_export_list):
    if index == len(data_export_list) - 1:
        writer.writerow([data, ";"])
    else:
        writer.writerow([data, ","])

byte_string = response.content
print(response.content)
# b"INSERT INTO `sample_table` (sample_id, sample_phrase) VALUES \r\n(1, 'hogehoge')\t,\r\n(2, 'foofoo')\t;\r\n"

# バイト文字列を通常の文字列に変換
normal_string = byte_string.decode('utf-8')
print(normal_string)
# INSERT INTO `sample_table` (sample_id, sample_phrase) VALUES 
# (1, 'hogehoge') ,
# (2, 'foofoo')   ;
```

上記のようにすることで、最終的に以下のように先頭固定のSQL文とCSV形式のデータを合体させることができました。

```sql
INSERT INTO `sample_table` (sample_id, sample_phrase) VALUES 
(1, 'hogehoge') ,
(2, 'foofoo')   ;
```

## サンプルコード解説
### ①HTTPレスポンスを作成する
- そもそもの話ですが、Webアプリケーションがクライアント（ブラウザなど）に対してデータを返すには、HTTPレスポンスのオブジェクトが必要になります。今回はSQLファイルのダウンロードなので、`text/sql`というMIMEタイプのファイルがダウンロードされることをクライアントを示す必要があります。ということで以下が詳細
    - `HttpResponse(content_type='text/sql')'`：HTTPレスポンスの`Content-Type`ヘッダーを `'text/sql'` に設定。テキスト形式のSQLデータが指定される
    - `response['Content-Disposition'] = 'attachment; filename="data_sql.sql"'`：ブラウザに対してレスポンスの扱い方を指示する`'Content-Disposition'`を設定。`'attachment'`は、ブラウザがファイルをダウンロードするよう指定。`filename="data_sql.sql"`は、ダウンロードされるファイルの名前を指定（テンプレートで指定もできます。後述）
    - レスポンス結果の`print`からわかるように、中身は`HttpResponse`オブジェクトの文字列表現。通常は、ステータスコードとコンテンツタイプが表示されます

### ②CSV形式のデータを書き込む
- 次に、CSVライターオブジェクトを生成してCSV形式のデータを書き込みます
    - `writer = csv.writer(response, delimiter="\t")`：`response`に対するCSVライターオブジェクトを生成し、タブ（`\t`）をデリミタとして設定
    - `writer.writerow...`：CSVファイルにSQL先頭行の1行を書き込む
    - `print`では、`csv.writer`オブジェクトの文字列表現を表示。これはPythonの標準のオブジェクト表現です
    - ちなみに、`delimiter="\t"`がない場合、結果は以下のようになります。なぜこうなってしまうかというと、`csv.writer`はデフォルトではカンマをセルの区切り文字として使用しているからです。データ内にカンマが含まれている場合、誤ってそれがセルの区切りと解釈されてしまい、結果CSVデータが崩れる、ということが起きます。セルの区切りにタブ文字を使用することで、問題なくデータが区切られるようになります

```
# print(response.content)
b'"INSERT INTO `sample_table` (sample_id, sample_phrase) VALUES "\r\n"(1, \'hogehoge\')",","\r\n"(2, \'foofoo\')",;\r\n'

# print(normal_string)
"INSERT INTO `sample_table` (sample_id, sample_phrase) VALUES "
"(1, 'hogehoge')",","
"(2, 'foofoo')",;
```

### ③リストから取得したデータを使ってCSVファイルに書き込む
- そして、冒頭にあるリストのデータを書き込んでいきます
    - `enumerate`を使って、リストの各要素とインデックスに対して反復処理
    - インデックスが最後の場合（＝最後の行の場合）、`;`（セミコロン）で行を終える形式にする。それ以外の場合は`,`（カンマ）で行を終える形式にする
### ④【デバッグ用】HTTPレスポンスから取得したバイト文字列を通常の文字列に変換する
- これはダウンロード前に`print`デバッグによって実際どんな文字列がHTTPレスポンスオブジェクトに入っているか確認するためのコード。
    - `response.content`：HTTPレスポンスの本文を表すバイト文字列。これを`print`することで、書き込んできた中身がわかります。`response`の`print`だけでは見られないので注意
    - 上記のままだと、バイト文字列のため、`b"INSERT INTO ...`のような形になります。そこで`byte_string.decode('utf-8')`を使って、バイト文字列をUTF-8エンコーディングを使用して通常の文字列に変換します。テキストデータをバイトから文字列に変換するにはUTF-8が使われるのが一般的だと思います

## DjangoアプリからDLする方法
上記のサンプルコードでは`print`しているだけですが、djangoではこれをダウンロードできる形にしてあげれば`"sample-data.sql"`というファイル名でエクスポートできます。

具体的には、まず`urls.py`で`path()`関数を使い、テンプレートとJSで設定してあげれば良いです。
（上述のサンプルコードは`views.py`の`export_to_sql`で関数化、返り値にSQLが来るとします）

```py
urlpatterns = [
    path('export-sql', views.export_to_sql, name="export_to_sample_sql")
    ...
```

`'export-sql'`は、URLのパスで、こちらにアクセスがあった場合に、`views.py`の`export_to_sql`というビュー関数が呼び出される、という意味です。`"export_to_sample_sql"`はURLパターンというもので、テンプレートとかで参照できます。`{% url %}`テンプレートタグなどが便利ですね。

テンプレートとJSはこんな感じになります。

```html
<button type="button" class="xxx" id="export-sql-sample" data-url="{% url 'export_to_sample_sql' %}" file-name="sample_data.sql">
SQL Download!!
</button>
```

以下のJavaScriptコードは、特定のボタンがクリックされたときにサーバにデータを要求し、取得したデータをCSV形式のファイルとしてダウンロードするための処理です。

```js
    var exportData = function () {
        var file_name = $(this).attr('file-name')
        var csrftoken = $("[name=csrfmiddlewaretoken]").val();
        var sample_id = $('#sampleSelect').val();
        $.ajax({
            type: "POST",
            url: $(this).attr('data-url'),
            headers: {"X-CSRFToken": csrftoken},
            data: {sample_id: sample_id},
            success: function (msg) {
                var downloadLink = document.createElement("a");
                var blob = new Blob(["\ufeff", msg]);
                var url = URL.createObjectURL(blob);
                downloadLink.href = url;
                downloadLink.download = file_name;
                document.body.appendChild(downloadLink);
                downloadLink.click();
                document.body.removeChild(downloadLink);
            },
            error: function (err) {
                console.log("ERROR", err);
            }
        });
    }

    // export to file sql
    $(document).on("click", '#export-sql-sample', exportData);
```