fix of the json upload format

This commit is contained in:
2026-08-03 19:36:56 +02:00
parent 82898e34cb
commit 27f5280bbf
+74 -35
View File
@@ -11874,18 +11874,14 @@ csrf = CSRFProtect(app)
def upload_csv_batch(): def upload_csv_batch():
""" """
Route for batch adding new items to the inventory via CSV. Route for batch adding new items to the inventory via CSV.
Handles CSV parsing, bulk image upload (conversion to WebP), GridFS storage, Handles CSV parsing, bulk image upload with deduplication (SHA-256 hash matching),
and groups identical items based on their Name. GridFS storage, code generation, location syncing, and grouped item creation.
""" """
import pandas as pd import pandas as pd
import ast import ast
#if 'username' not in session: import hashlib
# return jsonify({'success': False, 'message': 'Nicht angemeldet'}), 401
username = session['username'] username = session.get('username', 'System')
# permissions = _get_current_user_permissions() ... (anpassen wie in Original)
# if not _action_access_allowed(permissions, 'can_insert'):
# return jsonify({'success': False, 'message': 'Einfüge-Rechte erforderlich'}), 403
fs = get_gridfs() fs = get_gridfs()
upload_session_id = str(uuid.uuid4())[:8] upload_session_id = str(uuid.uuid4())[:8]
@@ -11908,10 +11904,11 @@ def upload_csv_batch():
if 'Name' not in df.columns: if 'Name' not in df.columns:
return jsonify({"success": False, "message": "Die CSV muss zwingend eine 'Name' Spalte enthalten."}), 400 return jsonify({"success": False, "message": "Die CSV muss zwingend eine 'Name' Spalte enthalten."}), 400
# 3. Bilder verarbeiten, nach WebP konvertieren und in GridFS speichern # 3. Bilder verarbeiten & Duplikate im selben Durchlauf filtern (Hash-Matching)
# Mapping: Original-Dateiname (ohne Pfad/Erweiterung) -> GridFS Filename (.webp) image_mapping = {} # Original-Dateiname (ohne Ext) -> GridFS Filename (.webp)
image_mapping = {} processed_hashes = {} # SHA-256 Hash -> GridFS Filename (.webp)
processed_count = 0 processed_count = 0
dedup_count = 0
error_count = 0 error_count = 0
for index, image in enumerate(uploaded_images): for index, image in enumerate(uploaded_images):
@@ -11929,6 +11926,18 @@ def upload_csv_batch():
error_count += 1 error_count += 1
continue 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() optimized_io = io.BytesIO()
with Image.open(io.BytesIO(image_bytes)) as img: with Image.open(io.BytesIO(image_bytes)) as img:
if img.mode not in ('RGB', 'RGBA'): if img.mode not in ('RGB', 'RGBA'):
@@ -11945,7 +11954,7 @@ def upload_csv_batch():
optimized_io.seek(0) optimized_io.seek(0)
new_filename = f"{uuid.uuid4().hex}_{int(time.time())}.webp" new_filename = f"{uuid.uuid4().hex}_{int(time.time())}.webp"
# Speichern in GridFS analog zu upload_item # In GridFS speichern
file_id = fs.put( file_id = fs.put(
optimized_io, optimized_io,
filename=new_filename, filename=new_filename,
@@ -11957,7 +11966,8 @@ def upload_csv_batch():
} }
) )
# Im Mapping speichern (damit wir sie später der CSV zuordnen können) # In Hash-Tabelle und Mapping sichern
processed_hashes[img_hash] = new_filename
image_mapping[base_name_no_ext] = new_filename image_mapping[base_name_no_ext] = new_filename
processed_count += 1 processed_count += 1
@@ -11965,10 +11975,14 @@ def upload_csv_batch():
app.logger.error(f"{image_log_prefix} Processing failed: {str(e)}") app.logger.error(f"{image_log_prefix} Processing failed: {str(e)}")
error_count += 1 error_count += 1
# 4. Items gruppieren (Analog zu series_group_id aus upload_item) # 4. Predefined Locations laden
df['Name'] = df['Name'].fillna('Unbenannt').astype(str) try:
predefined_locations = it.get_predefined_locations()
except Exception:
predefined_locations = []
# Optional: Fülle NaN Werte in der CSV mit sinnvollen Defaults für die Datenbank # 5. Dataframe bereinigen & gruppieren
df['Name'] = df['Name'].fillna('Unbenannt').astype(str)
df = df.fillna({ df = df.fillna({
'Ort': 'Unbekannt', 'Ort': 'Unbekannt',
'Beschreibung': '', 'Beschreibung': '',
@@ -11985,9 +11999,22 @@ def upload_csv_batch():
series_group_id = str(uuid.uuid4()) if item_count > 1 else None series_group_id = str(uuid.uuid4()) if item_count > 1 else None
parent_item_id = None parent_item_id = None
# Basis-Code für automatisierte Seriencodes ermitteln
first_row_code = str(group.iloc[0].get('Code_4', '')).strip()
base_code = first_row_code if first_row_code else None
for position, (index, row) in enumerate(group.iterrows(), start=1): for position, (index, row) in enumerate(group.iterrows(), start=1):
# Bilder aus der CSV-Zeile extrahieren und über das image_mapping mappen # 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:
it.add_predefined_location(ort_val)
predefined_locations.append(ort_val)
except Exception as e:
app.logger.warning(f"Ort {ort_val} konnte nicht hinzugefügt werden: {e}")
# Bilder für diesen Artikel zuordnen
item_image_filenames = [] item_image_filenames = []
if 'Images' in row and pd.notna(row['Images']): if 'Images' in row and pd.notna(row['Images']):
try: try:
@@ -12006,7 +12033,7 @@ def upload_csv_batch():
try: try:
res = ast.literal_eval(str(col_data)) res = ast.literal_eval(str(col_data))
return res if isinstance(res, list) else [] return res if isinstance(res, list) else []
except: except Exception:
return [] return []
filter_upload = parse_filter_col(row.get('Filter', '[]')) filter_upload = parse_filter_col(row.get('Filter', '[]'))
@@ -12015,28 +12042,37 @@ def upload_csv_batch():
reservierbar = bool(row.get('Reservierbar', False)) reservierbar = bool(row.get('Reservierbar', False))
# DB Insert Funktion aufrufen (mit korrigierten, positionellen Parametern) # Code_4 Behandlung: Falls in CSV definiert nutzen, sonst Batch-Code erzeugen
row_code = str(row.get('Code_4', '')).strip()
if row_code:
unique_code = row_code
elif 'generate_unique_batch_code' in globals():
unique_code = generate_unique_batch_code(base_code, position)
else:
unique_code = None
# DB Insert (exakt abgestimmt auf die 10 positionellen Argumente)
item_id = it.add_item( item_id = it.add_item(
str(row['Name']), str(row['Name']), # 1. Name
str(row['Ort']), ort_val, # 2. Ort
str(row['Beschreibung']), str(row['Beschreibung']), # 3. Beschreibung
item_image_filenames, item_image_filenames, # 4. Image Filenames (GridFS)
filter_upload, filter_upload, # 5. Filter 1
filter_upload2, filter_upload2, # 6. Filter 2
filter_upload3, filter_upload3, # 7. Filter 3
str(row['Anschaffungsjahr']) if row['Anschaffungsjahr'] else None, str(row['Anschaffungsjahr']) if row['Anschaffungsjahr'] else None, # 8. Jahr
str(row['Anschaffungskosten']) if row['Anschaffungskosten'] else None, str(row['Anschaffungskosten']) if row['Anschaffungskosten'] else None, # 9. Kosten
str(row['Code_4']) if row['Code_4'] else None, unique_code, # 10. Unique Code / Code_4
reservierbar=reservierbar, reservierbar=reservierbar,
series_group_id=series_group_id, series_group_id=series_group_id,
series_count=item_count, series_count=item_count,
series_position=position, series_position=position,
is_grouped_sub_item=(position > 1), is_grouped_sub_item=(position > 1),
parent_item_id=parent_item_id, parent_item_id=parent_item_id,
isbn='', isbn=str(row.get('ISBN', '')),
item_type='other', item_type=str(row.get('Item_Type', 'other')),
library_category='', library_category=str(row.get('Library_Category', '')),
is_library=False is_library=bool(row.get('Is_Library', False))
) )
if item_id: if item_id:
@@ -12047,12 +12083,15 @@ def upload_csv_batch():
app.logger.error(f"Fehler beim Erstellen von Item: {row['Name']} (Index {index})") app.logger.error(f"Fehler beim Erstellen von Item: {row['Name']} (Index {index})")
app.logger.info( app.logger.info(
f"Batch Upload abgeschlossen: {len(created_item_ids)} Items erstellt. {processed_count} Bilder verarbeitet.") f"Batch Upload abgeschlossen: {len(created_item_ids)} Items erstellt. "
f"{processed_count} neue Bilder hochgeladen, {dedup_count} Bild-Duplikate zusammengeführt."
)
return jsonify({ return jsonify({
"success": True, "success": True,
"message": f"Upload erfolgreich. {len(created_item_ids)} Items importiert und {processed_count} Bilder konvertiert.", "message": f"Upload erfolgreich. {len(created_item_ids)} Items importiert. {processed_count} neue Bilder gespeichert ({dedup_count} Duplikate zusammengeführt).",
"created_count": len(created_item_ids), "created_count": len(created_item_ids),
"images_processed": processed_count, "images_processed": processed_count,
"images_deduplicated": dedup_count,
"images_failed": error_count "images_failed": error_count
}), 200 }), 200