Python-Flaskで作る！テーブル定義書とCREATE文を画面入力から自動生成するWebツール（プログラム付き）

## はじめに

データベース設計時、**テーブル定義書を作成しながらCREATE文を同時に生成できるツール**があったら便利だと思いませんか？本記事では、Pythonの軽量なWebフレームワーク「Flask」を使い、**テーブル定義書とCREATE文を自動生成するWebツール**を作る方法を解説します。

完成イメージ：
1. ユーザーがWebフォームにテーブル名とカラム情報を入力
2. 「生成」ボタンをクリック
3. 結果画面に以下を表示：
   - テーブル定義書（カラム情報を表形式で表示）
   - CREATE文

---

## 完成イメージ
![スクリーンショット 2024-12-24 18.48.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75802/9affb348-525e-9a71-8fd5-ce8c815f8241.png)
![スクリーンショット 2024-12-24 18.48.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75802/38ba0f00-e920-cebb-8dfe-89ac38ecdbe5.png)


## 構成と準備

### 必要なツール

- Python（バージョン3.7以上）
- Flask（軽量Webフレームワーク）
- pandas（テーブルデータを扱うためのライブラリ）

以下のコマンドで必要なライブラリをインストールします。

```bash
pip install flask pandas
```

---

### ディレクトリ構成

作成するディレクトリ構成は以下の通りです：

```
table_generator/
├── app.py                   # Flaskアプリケーションのメインコード
├── requirements.txt         # 必要なライブラリのリスト
├── templates/               # HTMLテンプレートを格納
│   ├── index.html           # 入力フォームページ
│   └── result.html          # 結果表示ページ
├── static/                  # 静的ファイルを格納
│   ├── css/                 # CSSファイルを格納
│   │   └── styles.css       # スタイルシート
│   └── js/                  # JavaScriptファイルを格納
│       └── scripts.js       # 行追加用スクリプト
```

---

## コード詳細

### **1. Flaskアプリケーション: `app.py`**

`app.py`は、Flaskアプリケーションの中心となるコードです。ユーザーの入力データを受け取り、CREATE文とテーブル定義書を生成します。

```python
from flask import Flask, render_template, request
import pandas as pd

app = Flask(__name__)

@app.route('/')
def index():
    """入力フォームページを表示"""
    return render_template('index.html')

@app.route('/generate', methods=['POST'])
def generate():
    """フォームデータからCREATE文とテーブル定義書を生成して結果ページを表示"""
    try:
        # フォームデータを取得
        table_name = request.form.get('table_name', '').strip()  # テーブル名
        columns = request.form.getlist('columns[]')  # カラム名リスト
        data_types = request.form.getlist('data_types[]')  # データ型リスト
        lengths = request.form.getlist('lengths[]')  # 長さリスト
        not_nulls = request.form.getlist('not_nulls[]')  # NOT NULL制約リスト
        defaults = request.form.getlist('defaults[]')  # デフォルト値リスト
        primary_keys = request.form.getlist('primary_keys[]')  # 主キーリスト

        # 入力データのバリデーション
        if not table_name:
            return "テーブル名を入力してください。", 400
        if not columns or not any(columns):
            return "少なくとも1つのカラム情報を入力してください。", 400

        # データ整合性を補正
        max_length = max(len(columns), len(data_types), len(lengths), len(not_nulls), len(defaults), len(primary_keys))

        def pad_list(lst, length):
            return lst + [''] * (length - len(lst))

        # 配列を補正して長さを揃える
        columns = pad_list(columns, max_length)
        data_types = pad_list(data_types, max_length)
        lengths = pad_list(lengths, max_length)
        not_nulls = pad_list(not_nulls, max_length)
        defaults = pad_list(defaults, max_length)
        primary_keys = pad_list(primary_keys, max_length)

        # CREATE文を生成
        create_sql = f"CREATE TABLE {table_name} (\n"
        primary_keys_list = []

        for i in range(max_length):
            if not columns[i]:
                continue
            line = f"  {columns[i]} {data_types[i]}"
            if lengths[i]:
                line += f"({lengths[i]})"
            if not_nulls[i] == 'Yes':
                line += " NOT NULL"
            if defaults[i]:
                line += f" DEFAULT {defaults[i]}"
            if primary_keys[i] == 'Yes':
                primary_keys_list.append(columns[i])
            line += ",\n"
            create_sql += line

        # 主キー定義を追加
        if primary_keys_list:
            create_sql += f"  PRIMARY KEY ({', '.join(primary_keys_list)}),\n"

        create_sql = create_sql.rstrip(',\n') + "\n);"

        # テーブル定義データを作成
        table_definition = [
            {
                "column": columns[i],
                "data_type": data_types[i],
                "length": lengths[i],
                "not_null": "Yes" if not_nulls[i] == "Yes" else "No",
                "default": defaults[i],
                "primary_key": "Yes" if primary_keys[i] == "Yes" else "No",
            }
            for i in range(max_length)
            if columns[i]
        ]

        return render_template('result.html', create_sql=create_sql, table_definition=table_definition)

    except Exception as e:
        return f"エラーが発生しました: {e}", 500

if __name__ == '__main__':
    app.run(debug=True)
```

---

### **2. フォーム入力ページ: `templates/index.html`**

フォームにテーブル情報を入力できるページです。

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>テーブル定義作成ツール</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body>
    <h1>テーブル定義作成ツール</h1>
    <form action="/generate" method="POST">
        <label>テーブル名:</label>
        <input type="text" name="table_name" required>

        <table id="columns-table">
            <tr>
                <th>カラム名</th>
                <th>データ型</th>
                <th>長さ</th>
                <th>NOT NULL</th>
                <th>デフォルト値</th>
                <th>主キー</th>
            </tr>
            <tr>
                <td><input type="text" name="columns[]" required></td>
                <td><input type="text" name="data_types[]" required></td>
                <td><input type="text" name="lengths[]"></td>
                <td><input type="checkbox" name="not_nulls[]" value="Yes"></td>
                <td><input type="text" name="defaults[]"></td>
                <td><input type="checkbox" name="primary_keys[]" value="Yes"></td>
            </tr>
        </table>
        <button type="button" onclick="addRow()">行を追加</button>
        <button type="submit">生成</button>
    </form>
    <script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
</body>
</html>
```

---

### **3. 結果表示ページ: `templates/result.html`**

生成されたCREATE文とテーブル定義書を表示します。

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生成結果</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body>
    <h1>生成結果</h1>
    <form action="/">
        <button type="submit">戻る</button>
    </form>
    <h2>CREATE文</h2>
    <pre>{{ create_sql }}</pre>
    <h2>テーブル定義書</h2>
    <table>
        <tr>
            <th>カラム名</th>
            <th>データ型</th>
            <th>長さ</th>
            <th>NOT NULL</th>
            <th>デフォルト値</th>
            <th>主キー</th>
        </tr>
        {% for row in table_definition %}
        <tr>
            <td>{{ row.column }}</td>
            <td>{{ row.data_type }}</td>
            <td>{{ row.length }}</td>
            <td>{{ row.not_null }}</td>
            <td>{{ row.default }}</td>
            <td>{{ row.primary_key }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>
```

---

### **4. スタイル: `static/css/styles.css`**

見た目を整えるためのCSSです。

```css
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f9f9f9;
}

h1 {
    text-align: center;
    margin: 20px 0;
}

.form-container {
    max-width: 800px;
    margin: 20px auto;
    padding: 20px;
    background: #ffffff;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

label {
    display: block;
    margin-bottom: 10px;
    font-weight: bold;
}

input[type="text"] {
    width: calc(100% - 10px);
    padding: 8px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 3px;
    box-sizing: border-box;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 10px;
}

th, td {
    padding: 10px;
    text-align: left;
    border: 1px solid #ddd;
}

button {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    font-size: 16px;
}

button:hover {
    background-color: #45a049;
}

.add-row {
    margin-top: 10px;
    background-color: #2196F3;
}

.add-row:hover {
    background-color: #1976D2;
}

.submit-btn {
    display: block;
    width: 100%;
    margin-top: 20px;
}

.result-container {
    max-width: 800px;
    margin: 20px auto;
    background: #ffffff;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.sql-preview {
    background: #f8f8f8;
    padding: 10px;
    border-radius: 5px;
    font-family: monospace;
    overflow-x: auto;
}

.back-btn {
    background-color: #f44336;
}

.back-btn:hover {
    background-color: #d32f2f;
}

```

---

### **5. 動的行追加スクリプト: `static/js/scripts.js`**

```javascript
function addRow() {
    const table = document.getElementById('columns-table');
    const newRow = table.insertRow();
    ['columns[]', 'data_types[]', 'lengths[]', 'not_nulls[]', 'defaults[]', 'primary_keys[]'].forEach((name) => {
        const cell = newRow.insertCell();
        if (name.includes('not_nulls') || name.includes('primary_keys')) {
            cell.innerHTML = `<input type="checkbox" name="${name}" value="Yes">`;
        } else {
            cell.innerHTML = `<input type="text" name="${name}" required>`;
        }
    });
}
```

---

### 実行方法

1. サーバーを起動します。
   ```bash
   python app.py
   ```

2. ブラウザで `http://localhost:5000` にアクセス。

---

## まとめ
この記事では、Flaskを使った簡単なWebツールの作成方法を解説しました。このツールをカスタマイズして、より複雑なデータベーススキーマ設計にも応用できます。