diff --git a/backend/routers/caldav_router.py b/backend/routers/caldav_router.py index 514017b..edf1991 100644 --- a/backend/routers/caldav_router.py +++ b/backend/routers/caldav_router.py @@ -352,6 +352,21 @@ def get_events( .all() ) if readable_ids else [] name_cache = {u.id: (u.display_name or u.username) for u in db.query(models.User).all()} + # A personal calendar shared WITH the current user is relabelled under the + # owner's name (so Guido's "Persönlich" reads as "Guido" for his mum) and + # flagged read_only unless the share grants write. Group calendars are + # excluded — they keep their own name and stay writable for members. + shares_map = { + s.calendar_id: s.permission + for s in db.query(models.CalendarShare).filter( + models.CalendarShare.user_id == current_user.id + ) + } + group_cal_ids = { + r[0] for r in db.query(models.GroupCalendar.calendar_id).filter( + models.GroupCalendar.calendar_id.in_(readable_ids) + ) + } if readable_ids else set() # Cache each owner's private-event visibility (one lookup per owner, not per event). vis_cache: dict = {} @@ -361,6 +376,13 @@ def get_events( return vis_cache[uid] for local_cal in local_calendars: + # Decoration for a personal calendar shared with (not owned by) me. + is_shared_personal = ( + local_cal.user_id != current_user.id + and local_cal.id not in group_cal_ids + ) + shared_owner_name = name_cache.get(local_cal.user_id) if is_shared_personal else None + shared_read_only = is_shared_personal and shares_map.get(local_cal.id) != "read_write" local_events = ( db.query(models.LocalEvent) .filter( @@ -391,6 +413,10 @@ def get_events( else: built = [build_local_event_dict(ev, local_cal, rrule=None, creator=creator)] for b in built: + if shared_owner_name: + b["calendar_name"] = shared_owner_name + if shared_read_only: + b["read_only"] = True b = apply_event_privacy( b, owner_id=owner_id, is_private=is_priv, requester_id=current_user.id, visibility=visibility, diff --git a/backend/routers/groups_router.py b/backend/routers/groups_router.py index 314e088..f87b0f0 100644 --- a/backend/routers/groups_router.py +++ b/backend/routers/groups_router.py @@ -317,17 +317,12 @@ def _first_name(name: Optional[str]) -> str: def _decorate_title(title: str, *, is_group: bool, creator: Optional[dict], owner: Optional[dict], me_id: int) -> str: - """Server-side display title for the combined view so every client (web, - iOS, Android) renders identically: another member's / creator's first name - is prefixed. No icon glyph is embedded — group icons are semantic keys the - clients render as native vector icons, and group-calendar events are - distinguished by their (group) colour. The raw `title` stays for editing.""" - if is_group: - if creator and creator.get("id") is not None and creator.get("id") != me_id: - return f"{_first_name(creator.get('display_name'))}: {title}" - return title - if owner and owner.get("id") is not None and owner.get("id") != me_id: - return f"{_first_name(owner.get('display_name'))}: {title}" + """Server-side display title for the combined view. The former owner/creator + first-name prefix ("Guido: …") was dropped: each member already has a + distinct colour, so the prefix was redundant noise. We still return a + non-empty `display_title` (== raw title) so the clients' legacy fallback — + which rebuilds a prefix when `display_title` is empty — never kicks in. + `display_color` continues to carry the per-person colour.""" return title