Add page organiser routes; fix bulk upload tuple unpack and harden reorder

This commit is contained in:
jessikitty committed 2026-10-02 19:55:21 +10:00
1 parent 2e2f2d66d6
commit 48c82ab763
1 file changed
+137 -33
+137 -33
View File
@@ -5,6 +5,7 @@ from app import db
from app.models.set import Set from app.models.set import Set
from app.models.instruction import Instruction from app.models.instruction import Instruction
from app.services.file_handler import FileHandler from app.services.file_handler import FileHandler
from app.services import page_order
import os import os
instructions_bp = Blueprint('instructions', __name__, url_prefix='/instructions') instructions_bp = Blueprint('instructions', __name__, url_prefix='/instructions')
@@ -41,14 +42,7 @@ def upload(set_id):
# Determine page number for images # Determine page number for images
page_number = 1 page_number = 1
if file_type == 'IMAGE': if file_type == 'IMAGE':
# Get the highest page number for this set page_number = page_order.next_page_number(set_id)
max_page = db.session.query(
db.func.max(Instruction.page_number)
).filter_by(
set_id=set_id,
file_type='IMAGE'
).scalar()
page_number = (max_page or 0) + 1
# Create instruction record # Create instruction record
instruction = Instruction( instruction = Instruction(
@@ -156,25 +150,25 @@ def view(instruction_id):
@instructions_bp.route('/<int:set_id>/reorder', methods=['POST']) @instructions_bp.route('/<int:set_id>/reorder', methods=['POST'])
@login_required @login_required
def reorder(set_id): def reorder(set_id):
"""Reorder image instructions for a set.""" """Save a new page order for a set's image instructions."""
lego_set = Set.query.get_or_404(set_id) Set.query.get_or_404(set_id)
# Get new order from request payload = request.get_json(silent=True) or {}
new_order = request.json.get('order', []) new_order = payload.get('order', [])
if not new_order: if not new_order:
return jsonify({'error': 'No order provided'}), 400 return jsonify({'error': 'No order provided'}), 400
try: try:
# Update page numbers pages = page_order.apply_order(set_id, new_order)
for index, instruction_id in enumerate(new_order, start=1): return jsonify({
instruction = Instruction.query.get(instruction_id) 'success': True,
if instruction and instruction.set_id == set_id: 'pages': [p.to_dict() for p in pages],
instruction.page_number = index 'audit': page_order.audit(set_id),
})
db.session.commit() except ValueError as e:
return jsonify({'success': True}) db.session.rollback()
return jsonify({'error': str(e)}), 400
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@@ -201,8 +195,10 @@ def bulk_upload(set_id):
# Determine file type # Determine file type
file_type = FileHandler.get_file_type(file.filename) file_type = FileHandler.get_file_type(file.filename)
# Save file # Save file (save_file returns three values; the thumbnail was being
file_path, file_size = FileHandler.save_file( # dropped here, which blew up every bulk upload with a tuple-unpack
# error before it ever reached the database)
file_path, file_size, thumbnail_path = FileHandler.save_file(
file, file,
lego_set.set_number, lego_set.set_number,
file_type file_type
@@ -211,13 +207,7 @@ def bulk_upload(set_id):
# Determine page number for images # Determine page number for images
page_number = 1 page_number = 1
if file_type == 'IMAGE': if file_type == 'IMAGE':
max_page = db.session.query( page_number = page_order.next_page_number(set_id)
db.func.max(Instruction.page_number)
).filter_by(
set_id=set_id,
file_type='IMAGE'
).scalar()
page_number = (max_page or 0) + 1
# Create instruction record # Create instruction record
instruction = Instruction( instruction = Instruction(
@@ -227,6 +217,7 @@ def bulk_upload(set_id):
file_name=secure_filename(file.filename), file_name=secure_filename(file.filename),
file_size=file_size, file_size=file_size,
page_number=page_number, page_number=page_number,
thumbnail_path=thumbnail_path,
user_id=current_user.id user_id=current_user.id
) )
@@ -303,3 +294,116 @@ def debug_paths(set_id):
debug_info['instructions'].append(info) debug_info['instructions'].append(info)
return jsonify(debug_info) return jsonify(debug_info)
# ---------------------------------------------------------------------------
# Page organiser: fixing the order of image instructions after upload.
# ---------------------------------------------------------------------------
@instructions_bp.route('/<int:set_id>/organise')
@login_required
def organise(set_id):
"""Drag-and-drop page organiser for a set's image instructions."""
lego_set = Set.query.get_or_404(set_id)
pages = page_order.image_pages(set_id)
if not pages:
flash('There are no image pages to organise for this set.', 'info')
return redirect(url_for('sets.view_set', set_id=set_id))
return render_template(
'instructions/organise.html',
set=lego_set,
pages=pages,
page_hints={p.id: page_order.page_hint(p.file_name or '') for p in pages},
strategies=page_order.strategy_choices(),
audit=page_order.audit(set_id),
)
@instructions_bp.route('/<int:set_id>/auto-sort', methods=['POST'])
@login_required
def auto_sort(set_id):
"""Sort a set's pages by filename, detected page number, or upload order."""
Set.query.get_or_404(set_id)
payload = request.get_json(silent=True) or request.form
strategy = (payload.get('strategy') or 'filename').strip()
reverse = str(payload.get('reverse', '')).lower() in ('1', 'true', 'yes', 'on')
try:
if strategy == page_order.REVERSIBLE:
pages = page_order.reverse_order(set_id)
message = 'Page order reversed.'
else:
pages = page_order.auto_sort(set_id, strategy, reverse=reverse)
label = dict(page_order.strategy_choices()).get(strategy, strategy)
message = f'Sorted {len(pages)} pages by {label.lower()}.'
except ValueError as e:
if request.is_json:
return jsonify({'error': str(e)}), 400
flash(str(e), 'danger')
return redirect(url_for('instructions.organise', set_id=set_id))
except Exception as e:
db.session.rollback()
if request.is_json:
return jsonify({'error': str(e)}), 500
flash(f'Could not sort pages: {e}', 'danger')
return redirect(url_for('instructions.organise', set_id=set_id))
if request.is_json:
return jsonify({
'success': True,
'message': message,
'pages': [p.to_dict() for p in pages],
})
flash(message, 'success')
return redirect(url_for('instructions.organise', set_id=set_id))
@instructions_bp.route('/<int:set_id>/renumber', methods=['POST'])
@login_required
def renumber(set_id):
"""Close gaps and clear duplicate page numbers without moving anything."""
Set.query.get_or_404(set_id)
try:
pages = page_order.renumber(set_id)
except Exception as e:
db.session.rollback()
if request.is_json:
return jsonify({'error': str(e)}), 500
flash(f'Could not renumber pages: {e}', 'danger')
return redirect(url_for('instructions.organise', set_id=set_id))
message = f'Renumbered {len(pages)} pages 1 to {len(pages)}.'
if request.is_json:
return jsonify({'success': True, 'message': message,
'pages': [p.to_dict() for p in pages]})
flash(message, 'success')
return redirect(url_for('instructions.organise', set_id=set_id))
@instructions_bp.route('/<int:instruction_id>/move', methods=['POST'])
@login_required
def move(instruction_id):
"""Move one page to a given position, shuffling the rest around it."""
payload = request.get_json(silent=True) or request.form
position = payload.get('position')
try:
pages = page_order.move_page(instruction_id, int(position))
except (TypeError, ValueError) as e:
db.session.rollback()
return jsonify({'error': str(e)}), 400
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
return jsonify({
'success': True,
'pages': [p.to_dict() for p in pages],
})