fix: Import-500 bei doppelten UIDs, Picker-UI & Settings-URL-State
- Import: Dedupe doppelter UIDs innerhalb der Datei (Nextcloud exportiert wiederkehrende Termine als mehrere VEVENTs gleicher UID) -> kein UNIQUE-constraint-500 mehr; Commit abgesichert. Test ergaenzt (15 gruen). - Picker (Gruppen-Sichtbarkeit + Mitglieder): als <div>-Zeilen statt <label>, damit die globale ".form-group label"-Uppercase/Grau-Regel das Layout nicht mehr zerschiesst. Saubere .pick-row-Optik (Checkbox/Radio links, Name links). - Einstellungen haben jetzt eigenen URL-State (#...&settings=1): Reload/Cache- leeren bleibt in den Einstellungen statt zur Kalenderansicht zu springen. - Version v27. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -386,8 +386,17 @@ def _import_ics_into(cal: models.LocalCalendar, raw: bytes, db: Session) -> dict
|
||||
parsed = ical_io.parse_ics(raw)
|
||||
imported = 0
|
||||
skipped = 0
|
||||
errors = list(parsed["errors"])
|
||||
# local_events.uid is globally unique. Dedupe against the DB AND within this
|
||||
# file — e.g. Nextcloud exports recurring events as several VEVENTs sharing a
|
||||
# UID (RECURRENCE-ID overrides), which would otherwise violate the constraint.
|
||||
seen_uids: set[str] = set()
|
||||
for item in parsed["events"]:
|
||||
uid = item.get("uid") or str(uuid.uuid4())
|
||||
if uid in seen_uids:
|
||||
skipped += 1
|
||||
continue
|
||||
seen_uids.add(uid)
|
||||
existing = db.query(models.LocalEvent).filter(models.LocalEvent.uid == uid).first()
|
||||
if existing:
|
||||
skipped += 1
|
||||
@@ -407,8 +416,12 @@ def _import_ics_into(cal: models.LocalCalendar, raw: bytes, db: Session) -> dict
|
||||
)
|
||||
db.add(ev)
|
||||
imported += 1
|
||||
db.commit()
|
||||
return {"imported": imported, "skipped": skipped, "errors": parsed["errors"]}
|
||||
try:
|
||||
db.commit()
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
raise ValueError(f"Import fehlgeschlagen: {exc}")
|
||||
return {"imported": imported, "skipped": skipped, "errors": errors}
|
||||
|
||||
|
||||
@router.post("/calendars/{calendar_id}/import")
|
||||
|
||||
@@ -289,6 +289,39 @@ def test_import_dedupes_by_uid(client):
|
||||
assert imported and imported[0]["creator"]["display_name"] == "Max Mustermann (importiert)"
|
||||
|
||||
|
||||
DUP_UID_ICS = b"""BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Nextcloud
|
||||
BEGIN:VEVENT
|
||||
UID:recurring@nc
|
||||
SUMMARY:Standup
|
||||
DTSTART:20260601T090000Z
|
||||
DTEND:20260601T091500Z
|
||||
RRULE:FREQ=WEEKLY;BYDAY=MO
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:recurring@nc
|
||||
RECURRENCE-ID:20260608T090000Z
|
||||
SUMMARY:Standup verschoben
|
||||
DTSTART:20260608T100000Z
|
||||
DTEND:20260608T101500Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
"""
|
||||
|
||||
|
||||
def test_import_handles_duplicate_uid_in_file(client):
|
||||
"""Nextcloud exports recurring events as multiple VEVENTs sharing a UID;
|
||||
importing must not 500 on the unique constraint."""
|
||||
admin = register_admin(client)
|
||||
cal_id = _make_calendar(client, admin, "NC")
|
||||
files = {"file": ("nc.ics", DUP_UID_ICS, "text/calendar")}
|
||||
r = client.post(f"/api/local/calendars/{cal_id}/import", headers=auth(admin), files=files)
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["imported"] == 1 and body["skipped"] == 1
|
||||
|
||||
|
||||
def test_export_contains_organizer_and_rrule(client):
|
||||
admin = register_admin(client)
|
||||
cal_id = _make_calendar(client, admin, "Export-Test")
|
||||
|
||||
Reference in New Issue
Block a user