Add support to import with xl file format

This commit is contained in:
dvirlabs 2026-03-01 02:28:21 +02:00
parent e0169b803d
commit d338722880
3 changed files with 37 additions and 6 deletions

View File

@ -1493,6 +1493,32 @@ def _parse_csv_rows(content: bytes) -> list[dict]:
return [dict(row) for row in reader] return [dict(row) for row in reader]
def _parse_xlsx_rows(content: bytes) -> list[dict]:
"""Parse an XLSX (Excel) file and return a list of dicts.
Uses the first sheet; first row is treated as the header.
"""
import openpyxl
wb = openpyxl.load_workbook(io.BytesIO(content), read_only=True, data_only=True)
ws = wb.active
rows = list(ws.iter_rows(values_only=True))
wb.close()
if not rows:
return []
# First row = headers; normalise None headers to empty string
headers = [str(h).strip() if h is not None else "" for h in rows[0]]
result = []
for row in rows[1:]:
# Skip completely empty rows
if all(v is None or str(v).strip() == "" for v in row):
continue
result.append({
headers[i]: (str(v).strip() if v is not None else "")
for i, v in enumerate(row)
if i < len(headers) and headers[i] # skip header-less columns
})
return result
def _parse_json_rows(content: bytes) -> list[dict]: def _parse_json_rows(content: bytes) -> list[dict]:
"""Parse a JSON file — supports array at root OR {data: [...]}.""" """Parse a JSON file — supports array at root OR {data: [...]}."""
payload = json.loads(content.decode("utf-8-sig", errors="replace")) payload = json.loads(content.decode("utf-8-sig", errors="replace"))
@ -1593,16 +1619,20 @@ async def import_contacts(
try: try:
if filename.endswith(".json"): if filename.endswith(".json"):
raw_rows = _parse_json_rows(content) raw_rows = _parse_json_rows(content)
elif filename.endswith(".csv") or filename.endswith(".xlsx"): elif filename.endswith(".xlsx"):
# For XLSX export from our own app, treat as CSV (xlsx export from raw_rows = _parse_xlsx_rows(content)
# GuestList produces proper column headers in English) elif filename.endswith(".csv"):
raw_rows = _parse_csv_rows(content) raw_rows = _parse_csv_rows(content)
else: else:
# Sniff: try JSON then CSV # Sniff: try JSON → xlsx magic bytes → CSV
try: try:
raw_rows = _parse_json_rows(content) raw_rows = _parse_json_rows(content)
except Exception: except Exception:
raw_rows = _parse_csv_rows(content) # XLSX files start with PK (zip magic bytes 50 4B)
if content[:2] == b'PK':
raw_rows = _parse_xlsx_rows(content)
else:
raw_rows = _parse_csv_rows(content)
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=422, detail=f"Cannot parse file: {exc}") raise HTTPException(status_code=422, detail=f"Cannot parse file: {exc}")

View File

@ -6,3 +6,4 @@ pydantic[email]>=2.5.0
httpx>=0.25.2 httpx>=0.25.2
python-dotenv>=1.0.0 python-dotenv>=1.0.0
python-multipart>=0.0.7 python-multipart>=0.0.7
openpyxl>=3.1.2

View File

@ -118,7 +118,7 @@ function ImportContacts({ eventId, onImportComplete }) {
<input <input
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
accept=".csv,.json" accept=".csv,.json,.xlsx"
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleFileChange} onChange={handleFileChange}
/> />