306 lines
10 KiB
Python
306 lines
10 KiB
Python
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file
|
|
from flask_login import login_required, current_user
|
|
from werkzeug.utils import secure_filename
|
|
from app import db
|
|
from app.models.set import Set
|
|
from app.models.instruction import Instruction
|
|
from app.services.file_handler import FileHandler
|
|
import os
|
|
|
|
instructions_bp = Blueprint('instructions', __name__, url_prefix='/instructions')
|
|
|
|
|
|
@instructions_bp.route('/upload/<int:set_id>', methods=['GET', 'POST'])
|
|
@login_required
|
|
def upload(set_id):
|
|
"""Upload instruction files for a specific set."""
|
|
lego_set = Set.query.get_or_404(set_id)
|
|
|
|
if request.method == 'POST':
|
|
# Check if files were uploaded
|
|
if 'files[]' not in request.files:
|
|
flash('No files selected.', 'danger')
|
|
return redirect(request.url)
|
|
|
|
files = request.files.getlist('files[]')
|
|
uploaded_count = 0
|
|
|
|
for file in files:
|
|
if file and file.filename and FileHandler.allowed_file(file.filename):
|
|
try:
|
|
# Determine file type
|
|
file_type = FileHandler.get_file_type(file.filename)
|
|
|
|
# Save file and generate thumbnail
|
|
file_path, file_size, thumbnail_path = FileHandler.save_file(
|
|
file,
|
|
lego_set.set_number,
|
|
file_type
|
|
)
|
|
|
|
# Determine page number for images
|
|
page_number = 1
|
|
if file_type == 'IMAGE':
|
|
# Get the highest page number for this set
|
|
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
|
|
instruction = Instruction(
|
|
set_id=set_id,
|
|
file_type=file_type,
|
|
file_path=file_path,
|
|
file_name=secure_filename(file.filename),
|
|
file_size=file_size,
|
|
page_number=page_number,
|
|
thumbnail_path=thumbnail_path,
|
|
user_id=current_user.id
|
|
)
|
|
|
|
db.session.add(instruction)
|
|
uploaded_count += 1
|
|
|
|
except Exception as e:
|
|
flash(f'Error uploading {file.filename}: {str(e)}', 'danger')
|
|
continue
|
|
|
|
if uploaded_count > 0:
|
|
db.session.commit()
|
|
flash(f'Successfully uploaded {uploaded_count} file(s)!', 'success')
|
|
else:
|
|
flash('No files were uploaded.', 'warning')
|
|
|
|
return redirect(url_for('sets.view_set', set_id=set_id))
|
|
|
|
return render_template('instructions/upload.html', set=lego_set)
|
|
|
|
|
|
@instructions_bp.route('/<int:instruction_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def delete(instruction_id):
|
|
"""Delete an instruction file."""
|
|
instruction = Instruction.query.get_or_404(instruction_id)
|
|
set_id = instruction.set_id
|
|
|
|
# Delete the physical file
|
|
FileHandler.delete_file(instruction.file_path)
|
|
|
|
# Delete thumbnail if exists
|
|
if instruction.thumbnail_path:
|
|
FileHandler.delete_file(instruction.thumbnail_path)
|
|
|
|
# Delete the database record
|
|
db.session.delete(instruction)
|
|
db.session.commit()
|
|
|
|
flash('Instruction file deleted successfully.', 'success')
|
|
return redirect(url_for('sets.view_set', set_id=set_id))
|
|
|
|
|
|
@instructions_bp.route('/delete-all-images/<int:set_id>', methods=['POST'])
|
|
@login_required
|
|
def delete_all_images(set_id):
|
|
"""Delete all image instructions for a set."""
|
|
lego_set = Set.query.get_or_404(set_id)
|
|
|
|
# Get all image instructions
|
|
image_instructions = Instruction.query.filter_by(
|
|
set_id=set_id,
|
|
file_type='IMAGE'
|
|
).all()
|
|
|
|
count = len(image_instructions)
|
|
|
|
# Delete each one
|
|
for instruction in image_instructions:
|
|
# Delete physical file
|
|
FileHandler.delete_file(instruction.file_path)
|
|
|
|
# Delete thumbnail if exists
|
|
if instruction.thumbnail_path:
|
|
FileHandler.delete_file(instruction.thumbnail_path)
|
|
|
|
# Delete database record
|
|
db.session.delete(instruction)
|
|
|
|
db.session.commit()
|
|
|
|
flash(f'Successfully deleted {count} image instruction(s).', 'success')
|
|
return redirect(url_for('sets.view_set', set_id=set_id))
|
|
|
|
|
|
@instructions_bp.route('/<int:instruction_id>/view')
|
|
@login_required
|
|
def view(instruction_id):
|
|
"""View a specific instruction file."""
|
|
instruction = Instruction.query.get_or_404(instruction_id)
|
|
|
|
from flask import current_app
|
|
file_path = os.path.join(
|
|
current_app.config['UPLOAD_FOLDER'],
|
|
instruction.file_path
|
|
)
|
|
|
|
if not os.path.exists(file_path):
|
|
flash('File not found.', 'danger')
|
|
return redirect(url_for('sets.view_set', set_id=instruction.set_id))
|
|
|
|
return send_file(file_path)
|
|
|
|
|
|
@instructions_bp.route('/<int:set_id>/reorder', methods=['POST'])
|
|
@login_required
|
|
def reorder(set_id):
|
|
"""Reorder image instructions for a set."""
|
|
lego_set = Set.query.get_or_404(set_id)
|
|
|
|
# Get new order from request
|
|
new_order = request.json.get('order', [])
|
|
|
|
if not new_order:
|
|
return jsonify({'error': 'No order provided'}), 400
|
|
|
|
try:
|
|
# Update page numbers
|
|
for index, instruction_id in enumerate(new_order, start=1):
|
|
instruction = Instruction.query.get(instruction_id)
|
|
if instruction and instruction.set_id == set_id:
|
|
instruction.page_number = index
|
|
|
|
db.session.commit()
|
|
return jsonify({'success': True})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@instructions_bp.route('/bulk-upload/<int:set_id>', methods=['POST'])
|
|
@login_required
|
|
def bulk_upload(set_id):
|
|
"""Handle bulk upload via AJAX."""
|
|
lego_set = Set.query.get_or_404(set_id)
|
|
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'No file provided'}), 400
|
|
|
|
file = request.files['file']
|
|
|
|
if not file or not file.filename:
|
|
return jsonify({'error': 'Invalid file'}), 400
|
|
|
|
if not FileHandler.allowed_file(file.filename):
|
|
return jsonify({'error': 'File type not allowed'}), 400
|
|
|
|
try:
|
|
# Determine file type
|
|
file_type = FileHandler.get_file_type(file.filename)
|
|
|
|
# Save file
|
|
file_path, file_size = FileHandler.save_file(
|
|
file,
|
|
lego_set.set_number,
|
|
file_type
|
|
)
|
|
|
|
# Determine page number for images
|
|
page_number = 1
|
|
if file_type == 'IMAGE':
|
|
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
|
|
instruction = Instruction(
|
|
set_id=set_id,
|
|
file_type=file_type,
|
|
file_path=file_path,
|
|
file_name=secure_filename(file.filename),
|
|
file_size=file_size,
|
|
page_number=page_number,
|
|
user_id=current_user.id
|
|
)
|
|
|
|
db.session.add(instruction)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'instruction': instruction.to_dict()
|
|
})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@instructions_bp.route('/viewer/<int:set_id>')
|
|
@login_required
|
|
def image_viewer(set_id):
|
|
"""View image instructions in a scrollable PDF-like viewer."""
|
|
lego_set = Set.query.get_or_404(set_id)
|
|
|
|
# Get all image instructions sorted by page number
|
|
image_instructions = lego_set.image_instructions
|
|
|
|
if not image_instructions:
|
|
flash('No image instructions available for this set.', 'info')
|
|
return redirect(url_for('sets.view_set', set_id=set_id))
|
|
|
|
return render_template('instructions/viewer.html',
|
|
set=lego_set,
|
|
images=image_instructions)
|
|
|
|
|
|
@instructions_bp.route('/debug/<int:set_id>')
|
|
@login_required
|
|
def debug_paths(set_id):
|
|
"""Debug endpoint to check instruction paths."""
|
|
from flask import current_app
|
|
lego_set = Set.query.get_or_404(set_id)
|
|
|
|
debug_info = {
|
|
'set_number': lego_set.set_number,
|
|
'set_name': lego_set.set_name,
|
|
'upload_folder': current_app.config['UPLOAD_FOLDER'],
|
|
'instructions': []
|
|
}
|
|
|
|
for instruction in lego_set.instructions:
|
|
file_path = instruction.file_path.replace('\\', '/')
|
|
full_path = os.path.join(current_app.config['UPLOAD_FOLDER'], instruction.file_path)
|
|
|
|
info = {
|
|
'id': instruction.id,
|
|
'file_name': instruction.file_name,
|
|
'file_type': instruction.file_type,
|
|
'page_number': instruction.page_number,
|
|
'db_path': instruction.file_path,
|
|
'clean_path': file_path,
|
|
'full_disk_path': full_path,
|
|
'file_exists': os.path.exists(full_path),
|
|
'web_url': f'/static/uploads/{file_path}'
|
|
}
|
|
|
|
if instruction.thumbnail_path:
|
|
thumb_clean = instruction.thumbnail_path.replace('\\', '/')
|
|
thumb_full = os.path.join(current_app.config['UPLOAD_FOLDER'], instruction.thumbnail_path)
|
|
info['thumbnail_db'] = instruction.thumbnail_path
|
|
info['thumbnail_clean'] = thumb_clean
|
|
info['thumbnail_full'] = thumb_full
|
|
info['thumbnail_exists'] = os.path.exists(thumb_full)
|
|
info['thumbnail_url'] = f'/static/uploads/{thumb_clean}'
|
|
|
|
debug_info['instructions'].append(info)
|
|
|
|
return jsonify(debug_info)
|