Compare commits

..

5 Commits

Author SHA1 Message Date
Aiirondev_dev 82898e34cb fix of the json upload format 2026-08-03 19:16:32 +02:00
Aiirondev_dev 44392c2c31 fix of the json upload format 2026-08-03 18:51:47 +02:00
Aiirondev_dev 58d94b716f fix of the json upload format 2026-08-03 18:34:27 +02:00
Aiirondev_dev 579a0ddb75 fix of the json upload format 2026-08-03 18:22:27 +02:00
Aiirondev_dev 0f21e8d9ca fix of the json upload format 2026-08-03 01:02:40 +02:00
2 changed files with 43 additions and 28 deletions
+25 -24
View File
@@ -417,6 +417,8 @@ def _is_csrf_exempt_request():
@app.before_request
def _enforce_csrf_protection():
if request.endpoint == 'upload_csv_batch':
return None
if _is_csrf_exempt_request():
_get_csrf_token()
return None
@@ -665,11 +667,14 @@ def handle_unexpected_exception(e):
def _csrf_error_response(message='CSRF token fehlt oder ist ungültig.'):
if request.is_json or request.path.startswith('/api/') or request.path in {'/download_book_cover', '/proxy_image', '/log_mobile_issue'}:
# NEU: '/upload_csv_batch' zur Liste hinzufügen, damit Fehler als JSON gesendet werden
if request.is_json or request.path.startswith('/api/') or request.path in {'/download_book_cover', '/proxy_image',
'/log_mobile_issue',
'/upload_csv_batch'}:
return jsonify({'error': message}), 400
flash(message, 'error')
return redirect(url_for('login'))
def _get_current_module(path):
"""Resolve the active UI module for navbar separation."""
mod = cfg.MODULES.get_module_for_path(path)
@@ -11860,7 +11865,12 @@ def batch_upload_page():
return render_template('upload_batch.html')
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
@app.route('/upload_csv_batch', methods=['POST'])
@csrf.exempt
def upload_csv_batch():
"""
Route for batch adding new items to the inventory via CSV.
@@ -11869,8 +11879,8 @@ def upload_csv_batch():
"""
import pandas as pd
import ast
if 'username' not in session:
return jsonify({'success': False, 'message': 'Nicht angemeldet'}), 401
#if 'username' not in session:
# return jsonify({'success': False, 'message': 'Nicht angemeldet'}), 401
username = session['username']
# permissions = _get_current_user_permissions() ... (anpassen wie in Original)
@@ -11913,8 +11923,6 @@ def upload_csv_batch():
image_log_prefix = f"[Upload {upload_session_id}][Image {index + 1}/{len(uploaded_images)}]"
try:
# Annahme: is_allowed, error_message = allowed_file(...)
image.seek(0)
image_bytes = image.read()
if not image_bytes:
@@ -11958,7 +11966,6 @@ def upload_csv_batch():
error_count += 1
# 4. Items gruppieren (Analog zu series_group_id aus upload_item)
# Gruppierung über den Namen: Alle Zeilen mit demselben Namen gehören zur selben Serie
df['Name'] = df['Name'].fillna('Unbenannt').astype(str)
# Optional: Fülle NaN Werte in der CSV mit sinnvollen Defaults für die Datenbank
@@ -11971,7 +11978,6 @@ def upload_csv_batch():
})
created_item_ids = []
grouped_items = df.groupby('Name')
for name, group in grouped_items:
@@ -11985,12 +11991,10 @@ def upload_csv_batch():
item_image_filenames = []
if 'Images' in row and pd.notna(row['Images']):
try:
# Aus "['Bild1.JPG', 'Bild2.JPG']" wird eine Liste
img_list = ast.literal_eval(str(row['Images']))
if isinstance(img_list, list):
for img_name in img_list:
base_img_name = os.path.splitext(img_name)[0]
# Falls das Bild hochgeladen wurde, die WebP GridFS ID/Name nehmen
if base_img_name in image_mapping:
item_image_filenames.append(image_mapping[base_img_name])
else:
@@ -11998,7 +12002,6 @@ def upload_csv_batch():
except (ValueError, SyntaxError):
pass
# Filter extrahieren (falls vorhanden, erwarte string list wie "['HSU', '', '', '']")
def parse_filter_col(col_data):
try:
res = ast.literal_eval(str(col_data))
@@ -12012,25 +12015,24 @@ def upload_csv_batch():
reservierbar = bool(row.get('Reservierbar', False))
# DB Insert Funktion aufrufen (orientiert an deiner upload_item)
# DB Insert Funktion aufrufen (mit korrigierten, positionellen Parametern)
item_id = it.add_item(
name=row['Name'],
ort=row['Ort'],
beschreibung=row['Beschreibung'],
image_filenames=item_image_filenames,
filter_upload=filter_upload,
filter_upload2=filter_upload2,
filter_upload3=filter_upload3,
anschaffungs_jahr=str(row['Anschaffungsjahr']) if row['Anschaffungsjahr'] else None,
anschaffungs_kosten=str(row['Anschaffungskosten']) if row['Anschaffungskosten'] else None,
code_4=str(row['Code_4']) if row['Code_4'] else None,
str(row['Name']),
str(row['Ort']),
str(row['Beschreibung']),
item_image_filenames,
filter_upload,
filter_upload2,
filter_upload3,
str(row['Anschaffungsjahr']) if row['Anschaffungsjahr'] else None,
str(row['Anschaffungskosten']) if row['Anschaffungskosten'] else None,
str(row['Code_4']) if row['Code_4'] else None,
reservierbar=reservierbar,
series_group_id=series_group_id,
series_count=item_count,
series_position=position,
is_grouped_sub_item=(position > 1),
parent_item_id=parent_item_id,
# Default Werte, falls keine Bibliotheks-CSV
isbn='',
item_type='other',
library_category='',
@@ -12039,7 +12041,6 @@ def upload_csv_batch():
if item_id:
created_item_ids.append(item_id)
# Das erste Item in einer Serie wird der Parent für die restlichen
if position == 1:
parent_item_id = str(item_id)
else:
+18 -4
View File
@@ -165,11 +165,25 @@
const formData = new FormData(form);
const fetchOptions = {
method: 'POST',
body: formData,
credentials: 'include',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
};
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
if (csrfToken) {
fetchOptions.headers = {
'X-CSRFToken': csrfToken
};
}
try {
const response = await fetch('/upload_csv_batch', {
method: 'POST',
body: formData
});
const response = await fetch('/upload_csv_batch', fetchOptions);
// Antwort einmalig als Text auslesen, um sowohl JSON als auch HTML-Fehler abzufangen
const responseText = await response.text();