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:
157
routes/styles.py
157
routes/styles.py
@@ -4,23 +4,24 @@ import re
|
||||
import random
|
||||
import logging
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash, session, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask import render_template, request, redirect, url_for, flash, session
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from models import db, Character, Style, Detailer, Settings
|
||||
from models import db, Character, Style, Settings
|
||||
from services.workflow import _prepare_workflow, _get_default_checkpoint
|
||||
from services.job_queue import _enqueue_job, _make_finalize, _enqueue_task
|
||||
from services.prompts import build_prompt, _resolve_character, _ensure_character_fields, _append_background
|
||||
from services.sync import sync_styles
|
||||
from services.file_io import get_available_loras
|
||||
from services.llm import load_prompt, call_llm
|
||||
from utils import allowed_file, _WARDROBE_KEYS
|
||||
from routes.shared import register_common_routes
|
||||
from utils import _WARDROBE_KEYS
|
||||
|
||||
logger = logging.getLogger('gaze')
|
||||
|
||||
|
||||
def register_routes(app):
|
||||
register_common_routes(app, 'styles')
|
||||
|
||||
def _build_style_workflow(style_obj, character=None, selected_fields=None, fixed_seed=None, extra_positive=None, extra_negative=None):
|
||||
"""Build and return a prepared ComfyUI workflow dict for a style generation."""
|
||||
@@ -188,40 +189,11 @@ def register_routes(app):
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Edit error: {e}")
|
||||
logger.exception("Edit error: %s", e)
|
||||
flash(f"Error saving changes: {str(e)}")
|
||||
|
||||
return render_template('styles/edit.html', style=style, loras=loras)
|
||||
|
||||
@app.route('/style/<path:slug>/upload', methods=['POST'])
|
||||
def upload_style_image(slug):
|
||||
style = Style.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
if 'image' not in request.files:
|
||||
flash('No file part')
|
||||
return redirect(request.url)
|
||||
|
||||
file = request.files['image']
|
||||
if file.filename == '':
|
||||
flash('No selected file')
|
||||
return redirect(request.url)
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
# Create style subfolder
|
||||
style_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"styles/{slug}")
|
||||
os.makedirs(style_folder, exist_ok=True)
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
file_path = os.path.join(style_folder, filename)
|
||||
file.save(file_path)
|
||||
|
||||
# Store relative path in DB
|
||||
style.image_path = f"styles/{slug}/{filename}"
|
||||
db.session.commit()
|
||||
flash('Image uploaded successfully!')
|
||||
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
@app.route('/style/<path:slug>/generate', methods=['POST'])
|
||||
def generate_style_image(slug):
|
||||
style_obj = Style.query.filter_by(slug=slug).first_or_404()
|
||||
@@ -269,59 +241,12 @@ def register_routes(app):
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Generation error: {e}")
|
||||
logger.exception("Generation error: %s", e)
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'error': str(e)}, 500
|
||||
flash(f"Error during generation: {str(e)}")
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
@app.route('/style/<path:slug>/save_defaults', methods=['POST'])
|
||||
def save_style_defaults(slug):
|
||||
style = Style.query.filter_by(slug=slug).first_or_404()
|
||||
selected_fields = request.form.getlist('include_field')
|
||||
style.default_fields = selected_fields
|
||||
db.session.commit()
|
||||
flash('Default prompt selection saved for this style!')
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
@app.route('/style/<path:slug>/replace_cover_from_preview', methods=['POST'])
|
||||
def replace_style_cover_from_preview(slug):
|
||||
style = Style.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(app.config['UPLOAD_FOLDER'], preview_path)):
|
||||
style.image_path = preview_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
flash('No valid preview image selected.', 'error')
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
@app.route('/get_missing_styles')
|
||||
def get_missing_styles():
|
||||
missing = Style.query.filter((Style.image_path == None) | (Style.image_path == '')).order_by(Style.name).all()
|
||||
return {'missing': [{'slug': s.slug, 'name': s.name} for s in missing]}
|
||||
|
||||
@app.route('/get_missing_detailers')
|
||||
def get_missing_detailers():
|
||||
missing = Detailer.query.filter((Detailer.image_path == None) | (Detailer.image_path == '')).order_by(Detailer.name).all()
|
||||
return {'missing': [{'slug': d.slug, 'name': d.name} for d in missing]}
|
||||
|
||||
@app.route('/clear_all_detailer_covers', methods=['POST'])
|
||||
def clear_all_detailer_covers():
|
||||
detailers = Detailer.query.all()
|
||||
for detailer in detailers:
|
||||
detailer.image_path = None
|
||||
db.session.commit()
|
||||
return {'success': True}
|
||||
|
||||
@app.route('/clear_all_style_covers', methods=['POST'])
|
||||
def clear_all_style_covers():
|
||||
styles = Style.query.all()
|
||||
for style in styles:
|
||||
style.image_path = None
|
||||
db.session.commit()
|
||||
return {'success': True}
|
||||
|
||||
@app.route('/styles/generate_missing', methods=['POST'])
|
||||
def generate_missing_styles():
|
||||
missing = Style.query.filter(
|
||||
@@ -347,7 +272,7 @@ def register_routes(app):
|
||||
_make_finalize('styles', style_obj.slug, Style))
|
||||
enqueued += 1
|
||||
except Exception as e:
|
||||
print(f"Error queuing cover generation for style {style_obj.name}: {e}")
|
||||
logger.exception("Error queuing cover generation for style %s: %s", style_obj.name, e)
|
||||
|
||||
flash(f"Queued {enqueued} style cover image generation job{'s' if enqueued != 1 else ''}.")
|
||||
return redirect(url_for('styles_index'))
|
||||
@@ -511,73 +436,9 @@ def register_routes(app):
|
||||
flash('Style created successfully!')
|
||||
return redirect(url_for('style_detail', slug=safe_slug))
|
||||
except Exception as e:
|
||||
print(f"Save error: {e}")
|
||||
logger.exception("Save error: %s", e)
|
||||
flash(f"Failed to create style: {e}")
|
||||
return render_template('styles/create.html', form_data=form_data)
|
||||
|
||||
return render_template('styles/create.html', form_data=form_data)
|
||||
|
||||
@app.route('/style/<path:slug>/clone', methods=['POST'])
|
||||
def clone_style(slug):
|
||||
style = Style.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
base_id = style.style_id
|
||||
match = re.match(r'^(.+?)_(\d+)$', base_id)
|
||||
if match:
|
||||
base_name = match.group(1)
|
||||
current_num = int(match.group(2))
|
||||
else:
|
||||
base_name = base_id
|
||||
current_num = 1
|
||||
|
||||
next_num = current_num + 1
|
||||
while True:
|
||||
new_id = f"{base_name}_{next_num:02d}"
|
||||
new_filename = f"{new_id}.json"
|
||||
new_path = os.path.join(app.config['STYLES_DIR'], new_filename)
|
||||
if not os.path.exists(new_path):
|
||||
break
|
||||
next_num += 1
|
||||
|
||||
new_data = style.data.copy()
|
||||
new_data['style_id'] = new_id
|
||||
new_data['style_name'] = f"{style.name} (Copy)"
|
||||
|
||||
with open(new_path, 'w') as f:
|
||||
json.dump(new_data, f, indent=2)
|
||||
|
||||
new_slug = re.sub(r'[^a-zA-Z0-9_]', '', new_id)
|
||||
new_style = Style(
|
||||
style_id=new_id, slug=new_slug, filename=new_filename,
|
||||
name=new_data['style_name'], data=new_data
|
||||
)
|
||||
db.session.add(new_style)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Style cloned as "{new_id}"!')
|
||||
return redirect(url_for('style_detail', slug=new_slug))
|
||||
|
||||
@app.route('/style/<path:slug>/save_json', methods=['POST'])
|
||||
def save_style_json(slug):
|
||||
style = Style.query.filter_by(slug=slug).first_or_404()
|
||||
try:
|
||||
new_data = json.loads(request.form.get('json_data', ''))
|
||||
except (ValueError, TypeError) as e:
|
||||
return {'success': False, 'error': f'Invalid JSON: {e}'}, 400
|
||||
style.data = new_data
|
||||
flag_modified(style, 'data')
|
||||
db.session.commit()
|
||||
if style.filename:
|
||||
file_path = os.path.join(app.config['STYLES_DIR'], style.filename)
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(new_data, f, indent=2)
|
||||
return {'success': True}
|
||||
|
||||
@app.route('/style/<path:slug>/favourite', methods=['POST'])
|
||||
def toggle_style_favourite(slug):
|
||||
style_obj = Style.query.filter_by(slug=slug).first_or_404()
|
||||
style_obj.is_favourite = not style_obj.is_favourite
|
||||
db.session.commit()
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'success': True, 'is_favourite': style_obj.is_favourite}
|
||||
return redirect(url_for('style_detail', slug=slug))
|
||||
|
||||
Reference in New Issue
Block a user