diff --git a/Web/app.py b/Web/app.py index 117cdc8..7ade039 100755 --- a/Web/app.py +++ b/Web/app.py @@ -5239,15 +5239,15 @@ def debug_favorites(): def upload_item(): """ Route for adding new items to the inventory. - Handles file uploads and creates QR codes. + Handles image file uploads and creates QR codes. Enhanced for mobile browser compatibility. - + Returns: flask.Response: Redirect to admin homepage """ if 'username' not in session: return jsonify({'success': False, 'message': 'Nicht angemeldet'}), 401 - + item_is_library = False # Check if user may insert items @@ -5265,20 +5265,20 @@ def upload_item(): # Detect if request is from mobile device is_mobile = 'Mobile' in request.headers.get('User-Agent', '') - - # Log mobile request for debugging + if is_mobile: - app.logger.info(f"Mobile upload from {request.headers.get('User-Agent', 'unknown')} by {encrypt_text(username)}") - + app.logger.info( + f"Mobile upload from {request.headers.get('User-Agent', 'unknown')} by {encrypt_text(username)}") + try: # Strip whitespace from all text fields name = sanitize_form_value(request.form['name']) ort = sanitize_form_value(request.form['ort']) beschreibung = sanitize_form_value(request.form['beschreibung']) - + # Check both possible image field names images = request.files.getlist('images') or request.files.getlist('new_images') - + filter_upload = sanitize_form_value(request.form.getlist('filter')) filter_upload2 = sanitize_form_value(request.form.getlist('filter2')) filter_upload3 = sanitize_form_value(request.form.getlist('filter3')) @@ -5291,7 +5291,7 @@ def upload_item(): item_count_raw = sanitize_form_value(request.form.get('item_count', '1')) item_type_input = sanitize_form_value(request.form.get('item_type_input', '')) library_category = sanitize_form_value(request.form.get('library_category', '')) - + if library_category != '': item_is_library = True @@ -5301,46 +5301,30 @@ def upload_item(): item_count = 1 item_count = max(1, min(item_count, 100)) - # Optional list of per-item codes (one code per line) + # Optional list of per-item codes individual_codes = [] if individual_codes_raw: - individual_codes = [c.strip() for c in str(individual_codes_raw).replace(',', '\n').splitlines() if c.strip()] + individual_codes = [c.strip() for c in str(individual_codes_raw).replace(',', '\n').splitlines() if + c.strip()] - - # Check if this is a duplication - is_duplicating = request.form.get('is_duplicating') == 'true' - - # Get duplicate_images if duplicating - duplicate_images = request.form.getlist('duplicate_images') if is_duplicating else [] - - # Make sure duplicate_images is always a list, even if there's only one - if is_duplicating and duplicate_images and not isinstance(duplicate_images, list): - duplicate_images = [duplicate_images] - - # Log details about each image - if is_duplicating and duplicate_images: - for i, img in enumerate(duplicate_images): - print(f"DEBUG: Duplicate image {i+1}/{len(duplicate_images)}: {img}") - # Get book cover image if downloaded book_cover_image = request.form.get('book_cover_image') - - # Special handling for mobile browsers that might send data differently + + # Special handling for mobile browsers if is_mobile and 'mobile_data' in request.form: try: mobile_data = json.loads(request.form['mobile_data']) - # Override values with mobile data if available if 'filters' in mobile_data: filter_upload = mobile_data.get('filters', []) if 'filters2' in mobile_data: filter_upload2 = mobile_data.get('filters2', []) if 'filters3' in mobile_data: filter_upload3 = mobile_data.get('filters3', []) - if 'duplicate_images' in mobile_data and mobile_data['duplicate_images']: - duplicate_images = mobile_data.get('duplicate_images', []) except json.JSONDecodeError as e: app.logger.error(f"Error parsing mobile data: {str(e)}") - except: + + except Exception as e: + app.logger.error(f"Upload Form Data Parsing Error: {str(e)}") flash('Fehler beim Verarbeiten der Formulardaten. Bitte versuchen Sie es erneut.', 'error') return redirect(url_for(success_redirect_endpoint)) @@ -5348,7 +5332,7 @@ def upload_item(): filter_upload = expand_filter_selection(filter_upload, 1) filter_upload2 = expand_filter_selection(filter_upload2, 2) - # Validation + # Base Validation if not name or not ort or not beschreibung: error_msg = 'Bitte füllen Sie alle erforderlichen Felder aus' if is_mobile: @@ -5384,9 +5368,8 @@ def upload_item(): flash(error_msg, 'error') return redirect(url_for('library_admin')) - # Only check for images if not duplicating and no duplicate images provided and no book cover - # For library mode, skip this check as images come only from ISBN fetch - if upload_mode != 'library' and not is_duplicating and not images and not duplicate_images and not book_cover_image: + # Strict Image Validation (Since duplication is removed) + if upload_mode != 'library' and not images and not book_cover_image: error_msg = 'Bitte laden Sie mindestens ein Bild hoch' if is_mobile: return jsonify({'success': False, 'message': error_msg}), 400 @@ -5395,28 +5378,20 @@ def upload_item(): return redirect(url_for(success_redirect_endpoint)) primary_code_raw = request.form.get('code_4', '').strip() - individual_codes_raw = request.form.get('individual_codes', '').strip() - + all_item_codes = [] - if primary_code_raw: all_item_codes.append(primary_code_raw) - - if individual_codes_raw: - extra_codes = [c.strip() for c in individual_codes_raw.replace('\r', '').split('\n') if c.strip()] - for c in extra_codes: + if individual_codes: + for c in individual_codes: if c not in all_item_codes: all_item_codes.append(c) if len(all_item_codes) > 0: item_count = len(all_item_codes) - else: - try: - item_count = int(request.form.get('item_count', 1)) - except ValueError: - item_count = 1 + # Check unique codes for code in all_item_codes: if not it.is_code_unique(code): app.logger.info(f"DEBUG: Code '{code}' is not unique.") @@ -5426,12 +5401,12 @@ def upload_item(): error_msg = 'Der Code wird bereits verwendet. Umleitung zum Eintrag.' if is_mobile: return jsonify({ - 'success': False, + 'success': False, 'message': error_msg, 'existing_item_id': str(existing_item['_id']), 'redirect_to_item': True }), 400 - + flash(error_msg, 'info') return redirect(url_for(success_redirect_endpoint, open_item=str(existing_item['_id']))) @@ -5443,14 +5418,11 @@ def upload_item(): return redirect(url_for(success_redirect_endpoint)) def generate_unique_batch_code(base_code, position): - """Generate a unique code for every item in a batch if no specific code is provided.""" if not base_code: return None - candidate = base_code if position == 1 else f"{base_code}" if it.is_code_unique(candidate): return candidate - suffix = 1 while suffix <= 1000: alternative = f"{candidate}" @@ -5465,14 +5437,11 @@ def upload_item(): skipped_count = 0 upload_session_id = str(uuid.uuid4())[:8] - app.logger.info(f"Starting image upload session {upload_session_id} - Files: {len(images)}, User: {encrypt_text(username)}") + app.logger.info( + f"Starting image upload session {upload_session_id} - Files: {len(images)}, User: {encrypt_text(username)}") + # 1. Process Manual Image Uploads for index, image in enumerate(images): - #if upload_mode == 'library': - # app.logger.info(f"[Upload {upload_session_id}] Skipping manual upload (Library Mode)") - # skipped_count += 1 - # continue - if not image or not image.filename: skipped_count += 1 continue @@ -5490,9 +5459,7 @@ def upload_item(): continue secure_name = secure_filename(image.filename) - image.seek(0) - image_bytes = image.read() if not image_bytes: @@ -5527,7 +5494,6 @@ def upload_item(): ) image_filenames.append(new_filename) - processed_count += 1 final_size_kb = len(optimized_io.getvalue()) / 1024 app.logger.info( @@ -5537,79 +5503,13 @@ def upload_item(): app.logger.error(f"{image_log_prefix} Processing failed: {str(e)}") error_count += 1 - app.logger.info(f"Upload session {upload_session_id} completed: {processed_count} processed, {error_count} errors, {skipped_count} skipped") - - # 1. Duplikate verarbeiten (Direkt via MongoDB / GridFS) - if duplicate_images: - app.logger.info(f"Processing {len(duplicate_images)} duplicate images from GridFS: {duplicate_images}") - duplicate_image_copies = [] - placeholder_used = False - - for i, dup_img in enumerate(duplicate_images): - try: - # Suche das Originalbild direkt in der GridFS-Datenbank - existing_file = fs.find_one({"filename": dup_img}) - - # Fallback: Falls der exakte Name nicht gefunden wird, suche über Regex nach dem Basisnamen - if not existing_file: - base_name = re.sub(r'_\d+(?=\.[^.]+$)', '', dup_img) # Entfernt Suffixe wie _800 - base_name_no_ext = os.path.splitext(base_name)[0] - - if len(base_name_no_ext) > 5: - app.logger.info(f"Trying regex search for base name: {base_name_no_ext}") - existing_file = fs.find_one({"filename": {"$regex": f"^{re.escape(base_name_no_ext)}"}}) - - new_filename = f"{uuid.uuid4().hex}_{int(time.time())}.webp" - - if existing_file: - app.logger.info(f"Found image in GridFS for {dup_img}. Duplicating...") - # Bilddaten in den Speicher laden und direkt als neue Datei in GridFS ablegen - image_data = existing_file.read() - - fs.put( - image_data, - filename=new_filename, - content_type=existing_file.content_type, - metadata={'original_duplicate_of': dup_img} - ) - duplicate_image_copies.append(new_filename) - processed_count += 1 - else: - app.logger.warning(f"Could not find {dup_img} in GridFS. Using placeholder.") - placeholder_path = os.path.join(app.static_folder, 'img', 'no-image.png') - - if os.path.exists(placeholder_path): - with open(placeholder_path, 'rb') as pf: - placeholder_data = pf.read() - - fs.put( - placeholder_data, - filename=new_filename, - content_type='image/png', - metadata={'is_placeholder': True} - ) - duplicate_image_copies.append(new_filename) - placeholder_used = True - - except Exception as e: - app.logger.error(f"Error duplicating image {dup_img}: {str(e)}") - error_count += 1 - - if placeholder_used: - app.logger.warning("Used placeholders for some missing images during duplication") - - image_filenames.extend(duplicate_image_copies) - - # 2. Buchcover verarbeiten + # 2. Process Book Cover if book_cover_image: new_filename = f"{uuid.uuid4().hex}_{int(time.time())}_book_cover.webp" - - # Prüfen, ob das Cover noch als lokale temporäre Datei existiert (z.B. von einer API geladen) local_path = os.path.join(app.config.get('UPLOAD_FOLDER', ''), book_cover_image) try: if os.path.exists(local_path): - # Lokales Bild einlesen, zu WebP optimieren und in GridFS schieben with Image.open(local_path) as img: if img.mode not in ('RGB', 'RGBA'): img = img.convert('RGBA') @@ -5627,7 +5527,6 @@ def upload_item(): image_filenames.append(new_filename) app.logger.info(f"Processed local book cover and saved to GridFS: {new_filename}") else: - # Falls es bereits in GridFS liegt, einfach duplizieren existing_cover = fs.find_one({"filename": book_cover_image}) if existing_cover: fs.put( @@ -5643,7 +5542,10 @@ def upload_item(): except Exception as e: app.logger.error(f"Error processing book cover {book_cover_image}: {str(e)}") - # 3. Item-Erstellung (Logik bleibt identisch, da it.add_item nun die GridFS-Filenames erhält) + app.logger.info( + f"Upload session {upload_session_id} completed: {processed_count} processed, {error_count} errors, {skipped_count} skipped") + + # 3. Item Creation predefined_locations = it.get_predefined_locations() if ort and ort not in predefined_locations: it.add_predefined_location(ort) @@ -5669,7 +5571,6 @@ def upload_item(): parent_item_id = str(created_item_ids[0]) if created_item_ids else None - # image_filenames enthält jetzt ausschließlich GridFS-Filenames (oder ObjectIds, je nach deinem Setup) item_id = it.add_item( name, ort, beschreibung, image_filenames, filter_upload, filter_upload2, filter_upload3, @@ -5715,8 +5616,8 @@ def upload_item(): 'codes': created_item_ids and [it.get_item(cid).get('Code_4') for cid in created_item_ids] or [] } ) - except Exception: - app.logger.warning('Audit write failed for library_item_created') + except Exception as e: + app.logger.warning(f"Audit write failed for library_item_created: {str(e)}") flash(success_msg, 'success') return redirect(url_for(success_redirect_endpoint)) diff --git a/Web/templates/upload_admin.html b/Web/templates/upload_admin.html index 9565f23..edf12a9 100755 --- a/Web/templates/upload_admin.html +++ b/Web/templates/upload_admin.html @@ -836,7 +836,7 @@ -