Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8eb240440b | |||
| 9dc6fe6eeb | |||
| acfa24d742 | |||
| 88f6b77455 | |||
| bc5e08142a | |||
| 373839cf03 | |||
| 8e31309c55 | |||
| e8391dbf1e |
+25
-9
@@ -11862,25 +11862,39 @@ 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."""
|
||||
"""Bereinigt Werte, die fälschlicherweise als String-Listen oder mit Klammern aus der CSV kommen."""
|
||||
import ast
|
||||
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
|
||||
# Wenn es wie eine Liste aussieht (z.B. "['100177']" oder "['']")
|
||||
if val_str.startswith("[") and val_str.endswith("]"):
|
||||
try:
|
||||
parsed = ast.literal_eval(val_str)
|
||||
if isinstance(parsed, list):
|
||||
# Nimm das erste Element der Liste, wenn vorhanden
|
||||
for item in parsed:
|
||||
cleaned_item = str(item).strip()
|
||||
if cleaned_item and cleaned_item not in ("", "''", '""', "None", "nan"):
|
||||
return cleaned_item
|
||||
return None
|
||||
except Exception:
|
||||
# Fallback bei Syntaxfehlern
|
||||
inner = val_str[1:-1].strip().replace("'", "").replace('"', '')
|
||||
return inner if inner and inner not in ("''", '""') else None
|
||||
|
||||
if val_str in ("[]", "['']", '[""]', "nan", "None"):
|
||||
if val_str in ("[]", "['']", '[""]', "nan", "None", "''", '""'):
|
||||
return None
|
||||
|
||||
return val_str
|
||||
|
||||
|
||||
|
||||
@app.route('/upload_csv_batch', methods=['POST'])
|
||||
def upload_csv_batch():
|
||||
"""
|
||||
@@ -12064,8 +12078,10 @@ def upload_csv_batch():
|
||||
|
||||
reservierbar = bool(row.get('Reservierbar', False))
|
||||
|
||||
# Code_4 Behandlung
|
||||
row_code = clean_db_field(row.get('Code_4', ''))
|
||||
# Code_4 / Barcode sauber extrahieren und bereinigen
|
||||
raw_code = row.get('Code_4') or row.get('Barcode') or ''
|
||||
row_code = clean_db_field(raw_code)
|
||||
|
||||
if row_code:
|
||||
unique_code = row_code
|
||||
elif item_count > 1:
|
||||
|
||||
@@ -497,6 +497,58 @@ def check_nm_pwd(username, password):
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
def add_admin(
|
||||
username,
|
||||
password,
|
||||
name='',
|
||||
last_name='',
|
||||
is_student=False,
|
||||
permission_preset='full_access',
|
||||
action_permissions=None,
|
||||
page_permissions=None,
|
||||
):
|
||||
"""
|
||||
Add a new user to the database.
|
||||
"""
|
||||
if not check_password_strength(password):
|
||||
return False
|
||||
|
||||
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
|
||||
try:
|
||||
db = _get_tenant_db(client)
|
||||
users = db['users']
|
||||
|
||||
permission_defaults = build_default_permission_payload(permission_preset)
|
||||
|
||||
if isinstance(action_permissions, dict):
|
||||
for key, value in action_permissions.items():
|
||||
permission_defaults['actions'][str(key)] = bool(value)
|
||||
|
||||
if isinstance(page_permissions, dict):
|
||||
for key, value in page_permissions.items():
|
||||
permission_defaults['pages'][str(key)] = bool(value)
|
||||
|
||||
safe_name = name.strip() if name else ''
|
||||
safe_last_name = last_name.strip() if last_name else ''
|
||||
|
||||
user_doc = {
|
||||
'Username': username,
|
||||
'Password': hashing(password),
|
||||
'Admin': True,
|
||||
'active_ausleihung': None,
|
||||
'name': dp.encrypt_text(safe_name) if safe_name else '',
|
||||
'last_name': dp.encrypt_text(safe_last_name) if safe_last_name else '',
|
||||
'IsStudent': bool(is_student),
|
||||
'PermissionPreset': permission_defaults['preset'],
|
||||
'ActionPermissions': permission_defaults['actions'],
|
||||
'PagePermissions': permission_defaults['pages'],
|
||||
}
|
||||
|
||||
users.insert_one(user_doc)
|
||||
return True
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def add_user(
|
||||
username,
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 1. Ein echter CSV-Parser, der Zeilenumbrüche und Kommas in Texten korrekt ignoriert
|
||||
// 1. Robuster CSV-Parser, der Zeilenumbrüche und Kommas in Texten korrekt ignoriert
|
||||
function parseCSV(csvString) {
|
||||
const rows = [];
|
||||
let currentRow = [];
|
||||
@@ -70,7 +70,6 @@ function parseCSV(csvString) {
|
||||
} else if ((char === '\n' || char === '\r') && !insideQuotes) {
|
||||
if (char === '\r' && nextChar === '\n') i++; // Windows Umbrüche überspringen
|
||||
currentRow.push(currentCell);
|
||||
// Leere Zeilen ignorieren
|
||||
if (currentRow.length > 1 || currentRow[0] !== '') {
|
||||
rows.push(currentRow);
|
||||
}
|
||||
@@ -94,7 +93,6 @@ function rowToCSV(rowArray) {
|
||||
return rowArray.map(cell => {
|
||||
if (cell === null || cell === undefined) return '';
|
||||
let cellStr = String(cell);
|
||||
// Wenn kritische Zeichen drin sind, sauber in Anführungszeichen verpacken
|
||||
if (cellStr.includes(',') || cellStr.includes('"') || cellStr.includes('\n') || cellStr.includes('\r')) {
|
||||
return '"' + cellStr.replace(/"/g, '""') + '"';
|
||||
}
|
||||
@@ -102,7 +100,7 @@ function rowToCSV(rowArray) {
|
||||
}).join(',');
|
||||
}
|
||||
|
||||
// 3. Bildnamen extrahieren (bleibt wie es war)
|
||||
// 3. Bildnamen extrahieren
|
||||
function extractImageNames(cellValue) {
|
||||
if (!cellValue) return [];
|
||||
let raw = String(cellValue).replace(/^["']|["']$/g, '').trim();
|
||||
@@ -155,11 +153,10 @@ document.getElementById('batchUploadForm').addEventListener('submit', async func
|
||||
imageMap.set(file.name.toLowerCase(), file);
|
||||
});
|
||||
|
||||
const BATCH_SIZE = 50;
|
||||
const BATCH_SIZE = 20;
|
||||
|
||||
try {
|
||||
const csvText = await csvFile.text();
|
||||
// Hier rufen wir jetzt unseren sicheren Parser auf!
|
||||
const allRows = parseCSV(csvText);
|
||||
|
||||
if (allRows.length <= 1) {
|
||||
@@ -171,10 +168,8 @@ document.getElementById('batchUploadForm').addEventListener('submit', async func
|
||||
|
||||
log(`${dataRows.length} Einträge gefunden. Bereite Batches vor...`);
|
||||
|
||||
// Spalten-Index von "Images" sicher ermitteln
|
||||
const imagesColIndex = headerRow.findIndex(h => h.toLowerCase().trim() === 'images');
|
||||
|
||||
// In Batches aufteilen
|
||||
const batches = [];
|
||||
for (let i = 0; i < dataRows.length; i += BATCH_SIZE) {
|
||||
batches.push(dataRows.slice(i, i + BATCH_SIZE));
|
||||
@@ -183,63 +178,77 @@ document.getElementById('batchUploadForm').addEventListener('submit', async func
|
||||
progressBar.max = batches.length;
|
||||
progressBar.value = 0;
|
||||
|
||||
// Sequenzieller Upload mit Fehlertoleranz pro Batch
|
||||
for (let b = 0; b < batches.length; b++) {
|
||||
const batchRows = batches[b];
|
||||
progressText.textContent = `Lade Batch ${b + 1} von ${batches.length} hoch...`;
|
||||
|
||||
const requiredImagesForBatch = new Set();
|
||||
try {
|
||||
const requiredImagesForBatch = new Set();
|
||||
|
||||
if (imagesColIndex !== -1) {
|
||||
batchRows.forEach(rowCols => {
|
||||
if (rowCols[imagesColIndex]) {
|
||||
const imgNames = extractImageNames(rowCols[imagesColIndex]);
|
||||
imgNames.forEach(name => {
|
||||
const fileMatch = imageMap.get(name.toLowerCase());
|
||||
if (fileMatch) {
|
||||
requiredImagesForBatch.add(fileMatch);
|
||||
}
|
||||
});
|
||||
if (imagesColIndex !== -1) {
|
||||
batchRows.forEach(rowCols => {
|
||||
if (rowCols[imagesColIndex]) {
|
||||
const imgNames = extractImageNames(rowCols[imagesColIndex]);
|
||||
imgNames.forEach(name => {
|
||||
const fileMatch = imageMap.get(name.toLowerCase());
|
||||
if (fileMatch) {
|
||||
requiredImagesForBatch.add(fileMatch);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const batchCsvArray = [headerRow, ...batchRows];
|
||||
const batchCsvText = batchCsvArray.map(rowToCSV).join('\n');
|
||||
const batchCsvBlob = new Blob([batchCsvText], { type: 'text/csv' });
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('csv_file', batchCsvBlob, `batch_${b + 1}.csv`);
|
||||
|
||||
if (csrfToken) {
|
||||
formData.append('csrf_token', csrfToken);
|
||||
}
|
||||
|
||||
requiredImagesForBatch.forEach(imgFile => {
|
||||
formData.append('images', imgFile);
|
||||
});
|
||||
|
||||
log(`Batch ${b + 1}: ${batchRows.length} Items & ${requiredImagesForBatch.size} zugehörige Bilder.`);
|
||||
|
||||
const response = await fetch('/upload_csv_batch', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken || ''
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sicherer Zusammenbau des CSV-Batches
|
||||
const batchCsvArray = [headerRow, ...batchRows];
|
||||
const batchCsvText = batchCsvArray.map(rowToCSV).join('\n');
|
||||
const batchCsvBlob = new Blob([batchCsvText], { type: 'text/csv' });
|
||||
const responseText = await response.text();
|
||||
let result;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('csv_file', batchCsvBlob, `batch_${b + 1}.csv`);
|
||||
|
||||
if (csrfToken) {
|
||||
formData.append('csrf_token', csrfToken);
|
||||
}
|
||||
|
||||
requiredImagesForBatch.forEach(imgFile => {
|
||||
formData.append('images', imgFile);
|
||||
});
|
||||
|
||||
log(`Batch ${b + 1}: ${batchRows.length} Items & ${requiredImagesForBatch.size} zugehörige Bilder.`);
|
||||
|
||||
const response = await fetch('/upload_csv_batch', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken || ''
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch (parseErr) {
|
||||
throw new Error(`Server-Fehler (Status ${response.status}). HTML statt JSON erhalten.`);
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.message || `Server-Fehler ${response.status}`);
|
||||
}
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.message || `Server-Fehler ${response.status}`);
|
||||
log(`Batch ${b + 1} erfolgreich abgeschlossen.`);
|
||||
|
||||
} catch (batchErr) {
|
||||
log(`⚠️ FEHLER in Batch ${b + 1}: ${batchErr.message}. Überspringe und fahre fort...`);
|
||||
console.error(`Batch ${b + 1} fehlgeschlagen:`, batchErr);
|
||||
}
|
||||
|
||||
log(`Batch ${b + 1} abgeschlossen.`);
|
||||
progressBar.value = b + 1;
|
||||
}
|
||||
|
||||
progressText.textContent = "Upload erfolgreich beendet! Keine verschobenen Spalten mehr.";
|
||||
progressText.textContent = "Upload-Prozess beendet!";
|
||||
uploadBtn.disabled = false;
|
||||
|
||||
} catch (err) {
|
||||
|
||||
+4
-52
@@ -201,63 +201,15 @@ sys.path.insert(0, "/app/Web")
|
||||
from Web.modules.database import settings
|
||||
from pymongo import MongoClient
|
||||
import Web.modules.inventarsystem.data_protection as dp
|
||||
import Web.modules.database.user as us
|
||||
|
||||
tenant_id = sys.argv[1].lower()
|
||||
mode = sys.argv[2]
|
||||
sanitized = "".join(c for c in tenant_id if c.isalnum() or c == "_")
|
||||
db_name = f"inventar_{sanitized}"
|
||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
||||
db = client[db_name]
|
||||
|
||||
pw_bytes = "admin123".encode("utf-8")
|
||||
random_salt = os.urandom(16)
|
||||
hashed = hashlib.scrypt(pw_bytes, salt=random_salt, n=16384, r=8, p=1)
|
||||
hashed_pw_string = f"v1${random_salt.hex()}${hashed.hex()}"
|
||||
|
||||
action_permissions = {
|
||||
"can_borrow": True,
|
||||
"can_insert": True,
|
||||
"can_edit": True,
|
||||
"can_delete": True,
|
||||
"can_manage_users": True,
|
||||
"can_manage_settings": True,
|
||||
"can_view_logs": True,
|
||||
}
|
||||
|
||||
page_permissions = {
|
||||
"home": True,
|
||||
"tutorial_page": True,
|
||||
"my_borrowed_items": True,
|
||||
"notifications_view": True,
|
||||
"impressum": True,
|
||||
"license": True,
|
||||
"library_view": True,
|
||||
"terminplan": True,
|
||||
"home_admin": True,
|
||||
"upload_admin": True,
|
||||
"library_admin": True,
|
||||
"admin_borrowings": True,
|
||||
"library_loans_admin": True,
|
||||
"admin_damaged_items": True,
|
||||
"admin_audit_dashboard": True,
|
||||
"logs": True,
|
||||
"manage_filters": True,
|
||||
"manage_locations": True,
|
||||
}
|
||||
|
||||
if db.users.count_documents({"Username": dp.encrypt_text("admin")}) == 0:
|
||||
db.users.insert_one({
|
||||
"Username": dp.encrypt_text("admin"),
|
||||
"Password": hashed_pw_string,
|
||||
"Admin": True,
|
||||
"active_ausleihung": None,
|
||||
"name": dp.encrypt_text("Admin"),
|
||||
"last_name": dp.encrypt_text("User"),
|
||||
"IsStudent": False,
|
||||
"PermissionPreset": "full_access",
|
||||
"ActionPermissions": action_permissions,
|
||||
"PagePermissions": page_permissions,
|
||||
})
|
||||
us.add_admin("admin", "admin123", "admin", "admin")
|
||||
us.make_admin("admin")
|
||||
print("Fallback successfully applied")
|
||||
|
||||
if mode == "trial":
|
||||
db.settings.update_one(
|
||||
|
||||
Reference in New Issue
Block a user