Major refactor: deduplicate routes, sync, JS, and fix bugs
- Extract 8 common route patterns into factory functions in routes/shared.py (favourite, upload, replace cover, save defaults, clone, save JSON, get missing, clear covers) — removes ~1,100 lines across 9 route files - Extract generic _sync_category() in sync.py — 7 sync functions become one-liner wrappers, removing ~350 lines - Extract shared detail page JS into static/js/detail-common.js — all 9 detail templates now call initDetailPage() with minimal config - Extract layout inline JS into static/js/layout-utils.js (~185 lines) - Extract library toolbar JS into static/js/library-toolbar.js - Fix finalize missing-image bug: raise RuntimeError instead of logging warning so job is marked failed - Fix missing scheduler default in _default_checkpoint_data() - Fix N+1 query in Character.get_available_outfits() with batch IN query - Convert all print() to logger across services and routes - Add missing tags display to styles, scenes, detailers, checkpoints detail - Update delete buttons to use trash.png icon with solid red background - Update CLAUDE.md to reflect new architecture Net reduction: ~1,600 lines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,23 +2,19 @@ import json
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import random
|
||||
from flask import render_template, request, redirect, url_for, flash, session, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
from models import db, Character, Preset, Outfit, Action, Style, Scene, Detailer, Checkpoint, Look, Settings
|
||||
from models import db, Character, Preset, Outfit, Action, Style, Scene, Detailer, Checkpoint, Look
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from services.prompts import build_prompt, _dedup_tags, _resolve_character, _ensure_character_fields, _append_background
|
||||
from services.workflow import _prepare_workflow, _get_default_checkpoint
|
||||
from services.job_queue import _enqueue_job, _make_finalize
|
||||
from services.sync import sync_presets, _resolve_preset_entity, _resolve_preset_fields, _PRESET_ENTITY_MAP
|
||||
from services.sync import sync_presets
|
||||
from services.generation import generate_from_preset
|
||||
from services.llm import load_prompt, call_llm
|
||||
from utils import allowed_file
|
||||
from routes.shared import register_common_routes
|
||||
|
||||
logger = logging.getLogger('gaze')
|
||||
|
||||
|
||||
def register_routes(app):
|
||||
register_common_routes(app, 'presets')
|
||||
|
||||
@app.route('/presets')
|
||||
def presets_index():
|
||||
@@ -79,37 +75,6 @@ def register_routes(app):
|
||||
flash(f"Error during generation: {str(e)}")
|
||||
return redirect(url_for('preset_detail', slug=slug))
|
||||
|
||||
@app.route('/preset/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_preset_cover_from_preview(slug):
|
||||
preset = Preset.query.filter_by(slug=slug).first_or_404()
|
||||
preview_path = request.form.get('preview_path')
|
||||
if preview_path and os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
preset.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('preset_detail', slug=slug))
|
||||
|
||||
@app.route('/preset/<path:slug>/upload', methods=['POST'])
|
||||
def upload_preset_image(slug):
|
||||
preset = Preset.query.filter_by(slug=slug).first_or_404()
|
||||
if 'image' not in request.files:
|
||||
flash('No file uploaded.')
|
||||
return redirect(url_for('preset_detail', slug=slug))
|
||||
file = request.files['image']
|
||||
if file.filename == '':
|
||||
flash('No file selected.')
|
||||
return redirect(url_for('preset_detail', slug=slug))
|
||||
filename = secure_filename(file.filename)
|
||||
folder = os.path.join(current_app.config['UPLOAD_FOLDER'], f'presets/{slug}')
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
file.save(os.path.join(folder, filename))
|
||||
preset.image_path = f'presets/{slug}/{filename}'
|
||||
db.session.commit()
|
||||
flash('Image uploaded!')
|
||||
return redirect(url_for('preset_detail', slug=slug))
|
||||
|
||||
@app.route('/preset/<path:slug>/edit', methods=['GET', 'POST'])
|
||||
def edit_preset(slug):
|
||||
preset = Preset.query.filter_by(slug=slug).first_or_404()
|
||||
@@ -196,51 +161,6 @@ def register_routes(app):
|
||||
styles=styles, scenes=scenes, detailers=detailers,
|
||||
looks=looks, checkpoints=checkpoints)
|
||||
|
||||
@app.route('/preset/<path:slug>/save_json', methods=['POST'])
|
||||
def save_preset_json(slug):
|
||||
preset = Preset.query.filter_by(slug=slug).first_or_404()
|
||||
try:
|
||||
new_data = json.loads(request.form.get('json_data', ''))
|
||||
preset.data = new_data
|
||||
preset.name = new_data.get('preset_name', preset.name)
|
||||
flag_modified(preset, "data")
|
||||
db.session.commit()
|
||||
if preset.filename:
|
||||
file_path = os.path.join(current_app.config['PRESETS_DIR'], preset.filename)
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(new_data, f, indent=2)
|
||||
return {'success': True}
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}, 400
|
||||
|
||||
@app.route('/preset/<path:slug>/clone', methods=['POST'])
|
||||
def clone_preset(slug):
|
||||
original = Preset.query.filter_by(slug=slug).first_or_404()
|
||||
new_data = dict(original.data)
|
||||
|
||||
base_id = f"{original.preset_id}_copy"
|
||||
new_id = base_id
|
||||
counter = 1
|
||||
while Preset.query.filter_by(preset_id=new_id).first():
|
||||
new_id = f"{base_id}_{counter}"
|
||||
counter += 1
|
||||
|
||||
new_slug = re.sub(r'[^a-zA-Z0-9_]', '', new_id)
|
||||
new_data['preset_id'] = new_id
|
||||
new_data['preset_name'] = f"{original.name} (Copy)"
|
||||
new_filename = f"{new_id}.json"
|
||||
|
||||
os.makedirs(current_app.config['PRESETS_DIR'], exist_ok=True)
|
||||
with open(os.path.join(current_app.config['PRESETS_DIR'], new_filename), 'w') as f:
|
||||
json.dump(new_data, f, indent=2)
|
||||
|
||||
new_preset = Preset(preset_id=new_id, slug=new_slug, filename=new_filename,
|
||||
name=new_data['preset_name'], data=new_data)
|
||||
db.session.add(new_preset)
|
||||
db.session.commit()
|
||||
flash(f"Cloned as '{new_data['preset_name']}'")
|
||||
return redirect(url_for('preset_detail', slug=new_slug))
|
||||
|
||||
@app.route('/presets/rescan', methods=['POST'])
|
||||
def rescan_presets():
|
||||
sync_presets()
|
||||
@@ -322,7 +242,3 @@ def register_routes(app):
|
||||
|
||||
return render_template('presets/create.html', form_data=form_data)
|
||||
|
||||
@app.route('/get_missing_presets')
|
||||
def get_missing_presets():
|
||||
missing = Preset.query.filter((Preset.image_path == None) | (Preset.image_path == '')).order_by(Preset.filename).all()
|
||||
return {'missing': [{'slug': p.slug, 'name': p.name} for p in missing]}
|
||||
|
||||
Reference in New Issue
Block a user