From bc274da006ff7a84ac553325a4f22101006e84b2 Mon Sep 17 00:00:00 2001 From: AIIrondev Date: Tue, 4 Aug 2026 21:42:52 +0200 Subject: [PATCH] fix of the json upload format --- Web/app.py | 90 +++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/Web/app.py b/Web/app.py index ffca8f4..c3ade09 100755 --- a/Web/app.py +++ b/Web/app.py @@ -11863,6 +11863,23 @@ def batch_upload_page(): return render_template('upload_batch.html') +def clean_db_field(val): + """Bereinigt Werte, die fälschlicherweise als String-Listen aus der CSV kommen.""" + import pandas as pd + if not val or pd.isna(val): + return None + + val_str = str(val).strip() + + # Erkennt und entpackt String-Listen wie "['100465']" oder '["100465"]' + if (val_str.startswith("['") and val_str.endswith("']")) or (val_str.startswith('["') and val_str.endswith('"]')): + inner = val_str[2:-2].strip() + return inner if inner and inner != "''" and inner != '""' else None + + if val_str in ("[]", "['']", '[""]', "nan", "None"): + return None + + return val_str @app.route('/upload_csv_batch', methods=['POST']) def upload_csv_batch(): @@ -11878,13 +11895,6 @@ def upload_csv_batch(): username = session.get('username', 'System') def generate_unique_batch_code(base_code, position): - """ - Generiert einen eindeutigen Code für einen Artikel innerhalb einer Serie (Batch). - - :param base_code: Der Code des ersten Artikels in der Gruppe (String oder None). - :param position: Die Position des aktuellen Artikels in der Gruppe (Integer). - :return: Ein eindeutiger Code als String. - """ if base_code: return f"{base_code}-{position}" else: @@ -11913,8 +11923,8 @@ def upload_csv_batch(): return jsonify({"success": False, "message": "Die CSV muss zwingend eine 'Name' Spalte enthalten."}), 400 # 3. Bilder verarbeiten & Duplikate im selben Durchlauf filtern (Hash-Matching) - image_mapping = {} # Original-Dateiname (ohne Ext) -> GridFS Filename (.webp) - processed_hashes = {} # SHA-256 Hash -> GridFS Filename (.webp) + image_mapping = {} + processed_hashes = {} processed_count = 0 dedup_count = 0 error_count = 0 @@ -11934,18 +11944,14 @@ def upload_csv_batch(): error_count += 1 continue - # SHA-256 Hash des Bildinhalts zur Erkennung identischer Bilder img_hash = hashlib.sha256(image_bytes).hexdigest() if img_hash in processed_hashes: - # Bild ist identisch zu einem bereits verarbeiteten Bild im selben Batch existing_filename = processed_hashes[img_hash] image_mapping[base_name_no_ext] = existing_filename dedup_count += 1 - app.logger.info(f"{image_log_prefix} Duplikat erkannt ({original_secure_name}). Wiederverwendung von: {existing_filename}") continue - # Neues Bild verarbeiten und nach WebP konvertieren optimized_io = io.BytesIO() with Image.open(io.BytesIO(image_bytes)) as img: if img.mode not in ('RGB', 'RGBA'): @@ -11962,8 +11968,7 @@ def upload_csv_batch(): optimized_io.seek(0) new_filename = f"{uuid.uuid4().hex}_{int(time.time())}.webp" - # In GridFS speichern - file_id = fs.put( + fs.put( optimized_io, filename=new_filename, content_type='image/webp', @@ -11974,7 +11979,6 @@ def upload_csv_batch(): } ) - # In Hash-Tabelle und Mapping sichern processed_hashes[img_hash] = new_filename image_mapping[base_name_no_ext] = new_filename processed_count += 1 @@ -11989,8 +11993,8 @@ def upload_csv_batch(): except Exception: predefined_locations = [] - # 5. Dataframe bereinigen & gruppieren - df['Name'] = df['Name'].fillna('Unbenannt').astype(str) + # 5. Dataframe bereinigen + df['Name'] = df['Name'].fillna('Unbenannt').astype(str).str.strip() df = df.fillna({ 'Ort': 'Unbekannt', 'Beschreibung': '', @@ -11999,21 +12003,28 @@ def upload_csv_batch(): 'Anschaffungskosten': '' }) - created_item_ids = [] - grouped_items = df.groupby('Name') + # --- WICHTIG: Gruppierung über einen normalisierten Schlüssel ermöglichen --- + # Erstellt eine unsichtbare Hilfsspalte, die Leerzeichen/Groß-Kleinschreibung ignoriert, + # damit identische Artikel-Typen sauber als Serie erkannt werden. + df['GroupKey'] = df['Name'].str.lower() - for name, group in grouped_items: + created_item_ids = [] + grouped_items = df.groupby('GroupKey') + + for group_key, group in grouped_items: item_count = len(group) series_group_id = str(uuid.uuid4()) if item_count > 1 else None parent_item_id = None + # Originalen Namen des ersten Elements der Gruppe übernehmen + actual_group_name = group.iloc[0]['Name'] + # Basis-Code für automatisierte Seriencodes ermitteln - first_row_code = str(group.iloc[0].get('Code_4', '')).strip() + first_row_code = clean_db_field(group.iloc[0].get('Code_4', '')) base_code = first_row_code if first_row_code else None for position, (index, row) in enumerate(group.iterrows(), start=1): - # Ort automatisch zu predefined_locations hinzufügen, falls neu ort_val = str(row['Ort']).strip() if ort_val and ort_val not in predefined_locations: try: @@ -12022,7 +12033,7 @@ def upload_csv_batch(): except Exception as e: app.logger.warning(f"Ort {ort_val} konnte nicht hinzugefügt werden: {e}") - # Bilder für diesen Artikel zuordnen + # Bilder zuordnen und pro Artikel deduplizieren item_image_filenames = [] if 'Images' in row and pd.notna(row['Images']): try: @@ -12032,19 +12043,13 @@ def upload_csv_batch(): base_img_name = os.path.splitext(img_name)[0] if base_img_name in image_mapping: item_image_filenames.append(image_mapping[base_img_name]) - else: - app.logger.warning(f"Bild {img_name} in CSV definiert, aber nicht hochgeladen.") except (ValueError, SyntaxError): pass - # --- NEU: BILDER-REFERENZEN PRO ARTIKEL DEDUPLIZIEREN --- - # Falls die CSV z.B. ['bild1.jpg', 'bild1.jpg'] enthält, filtern wir das hier heraus, - # damit die GridFS-Datei nicht doppelt als Referenz gespeichert wird. unique_image_filenames = [] for img in item_image_filenames: if img not in unique_image_filenames: unique_image_filenames.append(img) - # -------------------------------------------------------- def parse_filter_col(col_data): try: @@ -12059,26 +12064,26 @@ def upload_csv_batch(): reservierbar = bool(row.get('Reservierbar', False)) - # Code_4 Behandlung: Falls in CSV definiert nutzen, sonst Batch-Code erzeugen - row_code = str(row.get('Code_4', '')).strip() + # Code_4 Behandlung + row_code = clean_db_field(row.get('Code_4', '')) if row_code: unique_code = row_code - elif 'generate_unique_batch_code' in globals(): + elif item_count > 1: unique_code = generate_unique_batch_code(base_code, position) else: unique_code = None - # DB Insert (exakt abgestimmt auf die 10 positionellen Argumente) + # DB Insert item_id = it.add_item( - str(row['Name']), # 1. Name + str(actual_group_name), # 1. Name ort_val, # 2. Ort str(row['Beschreibung']), # 3. Beschreibung - unique_image_filenames, # 4. Image Filenames (GridFS) -> HIER GEÄNDERT + unique_image_filenames, # 4. Image Filenames (GridFS) filter_upload, # 5. Filter 1 filter_upload2, # 6. Filter 2 filter_upload3, # 7. Filter 3 - str(row['Anschaffungsjahr']) if row['Anschaffungsjahr'] else None, # 8. Jahr - str(row['Anschaffungskosten']) if row['Anschaffungskosten'] else None, # 9. Kosten + clean_db_field(row.get('Anschaffungsjahr')), # 8. Jahr + clean_db_field(row.get('Anschaffungskosten')),# 9. Kosten unique_code, # 10. Unique Code / Code_4 reservierbar=reservierbar, series_group_id=series_group_id, @@ -12097,16 +12102,11 @@ def upload_csv_batch(): if position == 1: parent_item_id = str(item_id) else: - app.logger.error(f"Fehler beim Erstellen von Item: {row['Name']} (Index {index})") - - app.logger.info( - f"Batch Upload abgeschlossen: {len(created_item_ids)} Items erstellt. " - f"{processed_count} neue Bilder hochgeladen, {dedup_count} Bild-Duplikate zusammengeführt." - ) + app.logger.error(f"Fehler beim Erstellen von Item: {actual_group_name} (Index {index})") return jsonify({ "success": True, - "message": f"Upload erfolgreich. {len(created_item_ids)} Items importiert. {processed_count} neue Bilder gespeichert ({dedup_count} Duplikate zusammengeführt).", + "message": f"Upload erfolgreich. {len(created_item_ids)} Items importiert.", "created_count": len(created_item_ids), "images_processed": processed_count, "images_deduplicated": dedup_count,