Qwen3.5でDjango旅行予約サイトを生成
Qwen3.5-122B-A10BとMCPによるローカルWebアプリ開発の検証です。旅行予約、管理画面、6アプリへの拡張を扱い、業務システム開発での生成範囲と必要だった手修正を記録しました。
ローカルモデルでDjango予約サイトを生成
Qwen3.5-122B-A10BのQ5_K_Mを使い、Django 5の旅行予約サイトを生成しました。クラウドAPIは使っていません。MCPエージェントでファイルを操作し、初期生成後に手修正と2回の機能追加を行いました。Webアプリ・業務システム開発で、ローカルLLMを使える範囲を確認します。
量子化MoEのローカル推論で、モデル、ビュー、テンプレート、管理画面を揃えられるかを見ました。
ローカル推論環境
| 項目 | 内容 |
|---|---|
| モデル | Qwen3.5-122B-A10B(MoE、アクティブ 10B / 総パラメータ 122B) |
| 量子化 | Q5_K_M(GGUF、3分割シャード) |
| 推論エンジン | ik_llama.cpp(OpenAI 互換 API サーバー) |
| MCP ツール(自作) | ctree(コードシンボル解析)、pathfinder(パス解決) |
| MCP ツール(OSS) | serena(セマンティックコード操作)、filesystem(ファイル読み書き)、ripgrep(検索) |
| コンテキスト使用量 | 約 77K プロンプトトークン |
推論はすべてローカルです。serenaでシンボル、filesystemで読み書き、ripgrepで検索、ctreeで構造、pathfinderでパスを扱います。外部APIとトークン課金はありません。
検証の意図
クラウドではなくローカルで試す理由
クラウドのClaude SonnetやGPT-4oを使う方法には、次の条件があります。
- 繰り返すほどトークン費用が増える
- コードを外部へ送る
- APIの稼働とレート制限に依存する
122B MoEをQ5_K_Mに量子化し、ik_llama.cpp上でコーディングエージェントに使えるかを検証しました。クラウド同等の品質を確認したものではありません。
ワンショット生成のアプローチ
仕様を一度に渡して全ファイルを生成します。モデルとビュー、フォームとテンプレート、シードデータ、管理画面の対応を揃える狙いです。
仕様設計
技術スタック
| 要素 | 選定 | 理由 |
|---|---|---|
| バックエンド | Django 5.x | Python 3.13 互換、ORM 完備、管理画面自動生成 |
| フロントエンド JS | Alpine.js v3 (CDN) | Django テンプレート内で動作、ビルド不要 |
| CSS | Tailwind CSS | Material Design 3 インスパイアのユーティリティファースト |
| パッケージ管理 | uv | 高速な Python パッケージマネージャー |
| データベース | SQLite | 開発環境のみ、設定不要 |
初期ドメインモデル
初期仕様の3モデルです。
TravelPackage 1 ──── N Tour
Tour 1 ──── N Reservation
TravelPackage(旅行商品):
- タイトル、スラグ、リージョン、期間、画像 URL
is_publishedフラグで公開制御min_priceプロパティで最安ツアー価格を動的取得
Tour(出発日程):
- 特定の出発日・帰着日・価格・残席数
- ステータス管理(available / soldout / cancelled)
is_reservableプロパティで予約可否判定
Reservation(予約):
- 顧客情報(氏名、メール、電話、人数、備考)
- Tour への FK で紐付け
- 決済なし(DB 保存のみ)
ビジネスルール
予約と公開の条件です。
is_published=Trueのパッケージのみフロントエンドに表示status="available"のツアーのみ予約可能- 価格表示はパッケージ内の最安ツアー価格(
Min集約) - 予約フローはフォーム → 確認プレビュー → 完了の3ステップ
- 確認画面での POST で初めて DB に保存(セッション経由)
UI ワイヤーフレーム
ASCIIアートのレイアウトに、Tailwindクラスの指定を組み合わせました。
┌─────────────────────────────────────────────────┐
│ HERO SECTION: bg-gradient-to-r from-blue-600 │
│ [SVG airplane animation flying across] │
│ "Discover Your Next Adventure" │
│ [ Browse All Packages → ] │
├─────────────────────────────────────────────────┤
│ FEATURED PACKAGES (card grid) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Card 1│ │Card 2│ │Card 3│ │
│ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────────────┘
Material Design 3 デザイン指定
| 役割 | Tailwind クラス | 用途 |
|---|---|---|
| Primary | bg-blue-600 | ボタン、リンク、アクティブ状態 |
| Surface | bg-white | カード、モーダル、フォーム背景 |
| Elevation Level 2 | shadow-md | メインカード、ヘッダー |
| Border Radius | rounded-2xl | カード、rounded-xl ボタン |
ホバーエフェクト:hover:shadow-lg hover:-translate-y-1 transition-all duration-200
ローカル LLM が生成したコード
Alpine.js フィルタリング
Alpine.jsの指定に沿ったクライアント側フィルターです。
x-data="{
region: '',
maxPrice: ''
}"
x-show="(region === '' || $el.dataset.region === region) &&
(maxPrice === '' || parseInt($el.dataset.minPrice) <= parseInt(maxPrice))"
サーバー側APIを使わない処理です。
セッションベース予約フロー
3段階の予約フローを生成しました。
- フォーム入力(
/reserve/<tour_id>/):バリデーション後、データをセッションに保存 - 確認プレビュー(
/reserve/<tour_id>/confirm/):セッションから読み出し、サマリー表示 - 完了(
/reserve/success/):確認 POST で DB 保存、セッションクリア
# ReservationCreateView: POST でセッションに保存
request.session['reservation_data'] = form.cleaned_data
return redirect('reservation_confirm', tour_id=tour.id)
# ReservationConfirmView: POST で DB 保存
data = request.session.pop('reservation_data')
Reservation.objects.create(tour=tour, **data)
return redirect('reservation_success')
Django 管理画面
モデルに合わせ、インライン編集を含むadmin設定を生成しました。
class TourInline(admin.TabularInline):
model = Tour
extra = 1
@admin.register(TravelPackage)
class TravelPackageAdmin(admin.ModelAdmin):
list_display = ["title", "region", "duration_days", "is_published"]
list_editable = ["is_published"]
prepopulated_fields = {"slug": ("title",)}
inlines = [TourInline]
シードデータ
seed_demoで5パッケージ、計14ツアーを生成します。地域別フィルターを確認するデータです。
エージェントによる段階的拡張
初期生成を手修正した後、2回の追加指示を出しました。既存コードを読み、新モデルとアプリを追加しています。各段階で手修正も必要でした。
| 追加アプリ | 主なモデル | エージェントの作業内容 |
|---|---|---|
shop | Shop(店舗情報) | models.py に Shop モデル追加、admin 登録、テンプレート作成 |
reviews | Review, Rating, ReviewPhoto | ツアーレビュー機能一式、承認フロー付き |
search | SearchIndex, TrendingKeyword | 検索インデックスとトレンド管理 |
accounts | User, UserProfile, UserActivity | ユーザー認証とプロフィール管理 |
tours/models.pyのFKを確認して拡張しました。引用符のエスケープ、import漏れ、テンプレート間の不整合は人が直しています。作業量は生成8割、手修正2割ほどの印象でした。
生成結果
画面キャプチャ




初期生成ファイル構成
| ファイル | 内容 |
|---|---|
tours/models.py | 3モデル + プロパティ + バリデーション |
tours/admin.py | 3モデルの管理画面設定 + インライン |
tours/forms.py | ReservationForm (ModelForm) |
tours/views.py | 6ビュー(Home, List, Detail, Form, Confirm, Success) |
tours/urls.py | URL パターン定義 |
tours/management/commands/seed_demo.py | シードデータ |
tours/templatetags/tour_filters.py | カスタムフィルター(multiply) |
tours/templates/tours/*.html | 7テンプレート |
static/css/custom.css | SVG アニメーション |
config/settings.py | INSTALLED_APPS 追加 |
動作確認
手修正後に確認できた動作です。
- マイグレーション後にサーバーが起動
- シード投入でパッケージを表示
- Alpine.jsのフィルターとソートが動作
- 入力→確認→完了の予約フローが完走
- 管理画面のインライン編集が動作
手修正が必要だった主な箇所:
- パッケージ詳細ページのテンプレートで Django テンプレートタグの引用符エスケープ(
{{ tour.end_date|date:"M j, Y" }}が生文字列として表示) - 一部のインポート漏れや型の不整合
- 機能拡張時のテンプレート間の整合性
SVG アニメーション
SVG + CSSで、飛行機の8秒ループと雲の15秒ループを生成しました。
生成と修正で分かったこと
1. ローカル MoE モデルのコーディング能力
Qwen3.5-122B-A10B(Q5_K_M)は、Djangoのモデル、ビュー、テンプレート、adminを生成し、77Kトークンの文脈で拡張できました。手修正は必要で、クラウド同等とまでは評価していません。
2. 仕様書の詳細度が成否を分ける
今回、仕様で役立った項目です。
- フィールドの型、制約、初期値
- URLとビューの対応
- ASCIIアートのUI
- Tailwindクラスの指定
曖昧な仕様は出力のぶれにつながる印象でした。クラウドとの定量比較はしていません。
3. 複数 MCP サーバーの連携
ctree(シンボル解析)、pathfinder(パス解決)、serena(意味に基づく操作)、filesystem、ripgrepを組み合わせました。エージェントはシンボル検索、読み書き、構造確認を実行できました。
4. テンプレートのエスケープ問題
{{ value|date:"M j, Y" }}のような引用符付きフィルター引数に、エスケープの誤りがありました。クラウドでも起こり得ますが、今回は修正対象として目立ちました。
