275 lines
10 KiB
Python
275 lines
10 KiB
Python
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)
|