Initial commit - LEGO Instructions Manager v1.5.0
This commit is contained in:
274
app/routes/sets.py
Normal file
274
app/routes/sets.py
Normal file
@@ -0,0 +1,274 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app
|
||||
from flask_login import login_required, current_user
|
||||
import os
|
||||
from app import db
|
||||
from app.models.set import Set
|
||||
from app.models.instruction import Instruction
|
||||
from app.services.brickset_api import BricksetAPI
|
||||
from app.services.file_handler import FileHandler
|
||||
|
||||
sets_bp = Blueprint('sets', __name__, url_prefix='/sets')
|
||||
|
||||
|
||||
@sets_bp.route('/debug/<int:set_id>')
|
||||
@login_required
|
||||
def debug_set_image(set_id):
|
||||
"""Debug route to check image paths."""
|
||||
lego_set = Set.query.get_or_404(set_id)
|
||||
|
||||
debug_info = {
|
||||
'set_number': lego_set.set_number,
|
||||
'set_name': lego_set.set_name,
|
||||
'cover_image_db': lego_set.cover_image,
|
||||
'image_url_db': lego_set.image_url,
|
||||
'get_image_result': lego_set.get_image(),
|
||||
'cover_image_exists': False,
|
||||
'cover_image_full_path': None
|
||||
}
|
||||
|
||||
# Check if file actually exists
|
||||
if lego_set.cover_image:
|
||||
from flask import current_app
|
||||
full_path = os.path.join(current_app.config['UPLOAD_FOLDER'], lego_set.cover_image)
|
||||
debug_info['cover_image_full_path'] = full_path
|
||||
debug_info['cover_image_exists'] = os.path.exists(full_path)
|
||||
|
||||
return jsonify(debug_info)
|
||||
|
||||
|
||||
@sets_bp.route('/')
|
||||
@login_required
|
||||
def list_sets():
|
||||
"""List all LEGO sets with sorting and filtering."""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
sort_by = request.args.get('sort', 'set_number')
|
||||
theme_filter = request.args.get('theme', '')
|
||||
year_filter = request.args.get('year', type=int)
|
||||
search_query = request.args.get('q', '')
|
||||
|
||||
# Build query
|
||||
query = Set.query
|
||||
|
||||
# Apply filters
|
||||
if theme_filter:
|
||||
query = query.filter(Set.theme == theme_filter)
|
||||
if year_filter:
|
||||
query = query.filter(Set.year_released == year_filter)
|
||||
if search_query:
|
||||
query = query.filter(
|
||||
db.or_(
|
||||
Set.set_name.ilike(f'%{search_query}%'),
|
||||
Set.set_number.ilike(f'%{search_query}%')
|
||||
)
|
||||
)
|
||||
|
||||
# Apply sorting
|
||||
if sort_by == 'set_number':
|
||||
query = query.order_by(Set.set_number)
|
||||
elif sort_by == 'name':
|
||||
query = query.order_by(Set.set_name)
|
||||
elif sort_by == 'theme':
|
||||
query = query.order_by(Set.theme, Set.set_number)
|
||||
elif sort_by == 'year':
|
||||
query = query.order_by(Set.year_released.desc(), Set.set_number)
|
||||
elif sort_by == 'newest':
|
||||
query = query.order_by(Set.created_at.desc())
|
||||
|
||||
# Paginate
|
||||
from flask import current_app
|
||||
per_page = current_app.config.get('SETS_PER_PAGE', 20)
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
# Get unique themes and years for filters
|
||||
themes = db.session.query(Set.theme).distinct().order_by(Set.theme).all()
|
||||
themes = [t[0] for t in themes]
|
||||
|
||||
years = db.session.query(Set.year_released).distinct().order_by(Set.year_released.desc()).all()
|
||||
years = [y[0] for y in years]
|
||||
|
||||
return render_template('sets/list.html',
|
||||
sets=pagination.items,
|
||||
pagination=pagination,
|
||||
themes=themes,
|
||||
years=years,
|
||||
current_theme=theme_filter,
|
||||
current_year=year_filter,
|
||||
current_sort=sort_by,
|
||||
search_query=search_query)
|
||||
|
||||
|
||||
@sets_bp.route('/<int:set_id>')
|
||||
@login_required
|
||||
def view_set(set_id):
|
||||
"""View detailed information about a specific set."""
|
||||
lego_set = Set.query.get_or_404(set_id)
|
||||
|
||||
# Get instructions grouped by type
|
||||
pdf_instructions = lego_set.pdf_instructions
|
||||
image_instructions = lego_set.image_instructions
|
||||
|
||||
return render_template('sets/detail.html',
|
||||
set=lego_set,
|
||||
pdf_instructions=pdf_instructions,
|
||||
image_instructions=image_instructions)
|
||||
|
||||
|
||||
@sets_bp.route('/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def add_set():
|
||||
"""Add a new LEGO set or MOC."""
|
||||
if request.method == 'POST':
|
||||
set_number = request.form.get('set_number', '').strip()
|
||||
set_name = request.form.get('set_name', '').strip()
|
||||
theme = request.form.get('theme', '').strip()
|
||||
year_released = request.form.get('year_released', type=int)
|
||||
piece_count = request.form.get('piece_count', type=int)
|
||||
image_url = request.form.get('image_url', '').strip()
|
||||
|
||||
# MOC fields
|
||||
is_moc = request.form.get('is_moc') == 'on'
|
||||
moc_designer = request.form.get('moc_designer', '').strip() if is_moc else None
|
||||
moc_description = request.form.get('moc_description', '').strip() if is_moc else None
|
||||
|
||||
# Validation
|
||||
if not all([set_number, set_name, theme, year_released]):
|
||||
flash('Set number, name, theme, and year are required.', 'danger')
|
||||
return render_template('sets/add.html')
|
||||
|
||||
# Check if set already exists
|
||||
if Set.query.filter_by(set_number=set_number).first():
|
||||
flash(f'Set {set_number} already exists in the database.', 'warning')
|
||||
return redirect(url_for('sets.list_sets'))
|
||||
|
||||
# Handle cover image upload
|
||||
cover_image_path = None
|
||||
if 'cover_image' in request.files:
|
||||
file = request.files['cover_image']
|
||||
if file and file.filename and FileHandler.allowed_file(file.filename):
|
||||
try:
|
||||
cover_image_path, _ = FileHandler.save_cover_image(file, set_number)
|
||||
except Exception as e:
|
||||
flash(f'Error uploading cover image: {str(e)}', 'warning')
|
||||
|
||||
# Create new set
|
||||
new_set = Set(
|
||||
set_number=set_number,
|
||||
set_name=set_name,
|
||||
theme=theme,
|
||||
year_released=year_released,
|
||||
piece_count=piece_count,
|
||||
image_url=image_url,
|
||||
cover_image=cover_image_path,
|
||||
is_moc=is_moc,
|
||||
moc_designer=moc_designer,
|
||||
moc_description=moc_description,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
db.session.add(new_set)
|
||||
db.session.commit()
|
||||
|
||||
set_type = "MOC" if is_moc else "Set"
|
||||
flash(f'{set_type} {set_number}: {set_name} added successfully!', 'success')
|
||||
return redirect(url_for('sets.view_set', set_id=new_set.id))
|
||||
|
||||
return render_template('sets/add.html')
|
||||
|
||||
|
||||
@sets_bp.route('/<int:set_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_set(set_id):
|
||||
"""Edit an existing LEGO set or MOC."""
|
||||
lego_set = Set.query.get_or_404(set_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
lego_set.set_name = request.form.get('set_name', '').strip()
|
||||
lego_set.theme = request.form.get('theme', '').strip()
|
||||
lego_set.year_released = request.form.get('year_released', type=int)
|
||||
lego_set.piece_count = request.form.get('piece_count', type=int)
|
||||
lego_set.image_url = request.form.get('image_url', '').strip()
|
||||
|
||||
# Update MOC fields
|
||||
lego_set.is_moc = request.form.get('is_moc') == 'on'
|
||||
lego_set.moc_designer = request.form.get('moc_designer', '').strip() if lego_set.is_moc else None
|
||||
lego_set.moc_description = request.form.get('moc_description', '').strip() if lego_set.is_moc else None
|
||||
|
||||
# Handle cover image upload
|
||||
if 'cover_image' in request.files:
|
||||
file = request.files['cover_image']
|
||||
if file and file.filename and FileHandler.allowed_file(file.filename):
|
||||
try:
|
||||
# Delete old cover image if exists
|
||||
if lego_set.cover_image:
|
||||
old_path = os.path.join(current_app.config['UPLOAD_FOLDER'], lego_set.cover_image)
|
||||
if os.path.exists(old_path):
|
||||
os.remove(old_path)
|
||||
|
||||
# Save new cover image
|
||||
cover_image_path, _ = FileHandler.save_cover_image(file, lego_set.set_number)
|
||||
lego_set.cover_image = cover_image_path
|
||||
except Exception as e:
|
||||
flash(f'Error uploading cover image: {str(e)}', 'warning')
|
||||
|
||||
# Option to remove cover image
|
||||
if request.form.get('remove_cover_image') == 'on':
|
||||
if lego_set.cover_image:
|
||||
old_path = os.path.join(current_app.config['UPLOAD_FOLDER'], lego_set.cover_image)
|
||||
if os.path.exists(old_path):
|
||||
os.remove(old_path)
|
||||
lego_set.cover_image = None
|
||||
|
||||
db.session.commit()
|
||||
flash('Set updated successfully!', 'success')
|
||||
return redirect(url_for('sets.view_set', set_id=set_id))
|
||||
|
||||
return render_template('sets/edit.html', set=lego_set)
|
||||
|
||||
|
||||
@sets_bp.route('/<int:set_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_set(set_id):
|
||||
"""Delete a LEGO set and all its instructions."""
|
||||
lego_set = Set.query.get_or_404(set_id)
|
||||
|
||||
# Delete associated files
|
||||
from app.services.file_handler import FileHandler
|
||||
for instruction in lego_set.instructions:
|
||||
FileHandler.delete_file(instruction.file_path)
|
||||
|
||||
db.session.delete(lego_set)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Set {lego_set.set_number} deleted successfully.', 'success')
|
||||
return redirect(url_for('sets.list_sets'))
|
||||
|
||||
|
||||
@sets_bp.route('/search-brickset')
|
||||
@login_required
|
||||
def search_brickset():
|
||||
"""Search for sets using Brickset API."""
|
||||
query = request.args.get('q', '').strip()
|
||||
|
||||
if not query:
|
||||
return jsonify({'error': 'Search query is required'}), 400
|
||||
|
||||
api = BricksetAPI()
|
||||
|
||||
# Try to search by set number first, then by name
|
||||
results = api.search_sets(set_number=query)
|
||||
if not results:
|
||||
results = api.search_sets(query=query)
|
||||
|
||||
# Format results for JSON response
|
||||
formatted_results = []
|
||||
for result in results[:10]: # Limit to 10 results
|
||||
formatted_results.append({
|
||||
'setNumber': result.get('number'),
|
||||
'name': result.get('name'),
|
||||
'theme': result.get('theme'),
|
||||
'year': result.get('year'),
|
||||
'pieces': result.get('pieces'),
|
||||
'imageUrl': result.get('image', {}).get('imageURL') if result.get('image') else None
|
||||
})
|
||||
|
||||
return jsonify(formatted_results)
|
||||
Reference in New Issue
Block a user