feat: improve calendar sharing
This commit is contained in:
+3
-1
@@ -98,7 +98,9 @@ def _event(assignment: Assignment, person: User | None, tzid: str) -> list[str]:
|
|||||||
end = datetime.combine(assignment.date, schedule.end_time)
|
end = datetime.combine(assignment.date, schedule.end_time)
|
||||||
if end <= start: # a shift that runs past midnight
|
if end <= start: # a shift that runs past midnight
|
||||||
end += timedelta(days=1)
|
end += timedelta(days=1)
|
||||||
summary = f"{schedule.name} · {window_label(schedule.start_time, schedule.end_time)}"
|
# The person's name is part of the event title, so a list of shifts reads at a glance.
|
||||||
|
summary = f"{person.name} · {schedule.name} · {window_label(schedule.start_time, schedule.end_time)}" \
|
||||||
|
if person is not None else f"{schedule.name} · {window_label(schedule.start_time, schedule.end_time)}"
|
||||||
description = [
|
description = [
|
||||||
day_long(assignment.date),
|
day_long(assignment.date),
|
||||||
f"{schedule.start_time:%H:%M}–{schedule.end_time:%H:%M}",
|
f"{schedule.start_time:%H:%M}–{schedule.end_time:%H:%M}",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Every start copies the SQLite file aside before anything else runs, using SQLite
|
|||||||
- `/calendar` is the page holding those links: the rota only has a button pointing there, so it keeps showing nothing but who works each day. Each address sits in a readonly field with a **Copiar** button (clipboard when the browser allows it, selecting the field otherwise) plus a `webcal://` shortcut.
|
- `/calendar` is the page holding those links: the rota only has a button pointing there, so it keeps showing nothing but who works each day. Each address sits in a readonly field with a **Copiar** button (clipboard when the browser allows it, selecting the field otherwise) plus a `webcal://` shortcut.
|
||||||
- Administrators also get one shared feed with the whole rota, using the team token in `CALENDAR_TOKEN` (generated on first use and shown on `/admin/schedules`).
|
- Administrators also get one shared feed with the whole rota, using the team token in `CALENDAR_TOKEN` (generated on first use and shown on `/admin/schedules`).
|
||||||
- A feed publishes one month back and six months forward. Times carry the bar's timezone (`DTSTART;TZID=Europe/Lisbon:20261017T203000`) and the feed declares it in `X-WR-TIMEZONE` plus a `VTIMEZONE` block with the European summer-time rules (WET/WEST), so every client shows a night at 20:30 and keeps the right offset across the year. The zone comes from `TIMEZONE`; an unknown zone falls back to `Europe/Lisbon`. Times and names are private member data: the token is the only protection, so the shared link goes only to the team.
|
- A feed publishes one month back and six months forward. Times carry the bar's timezone (`DTSTART;TZID=Europe/Lisbon:20261017T203000`) and the feed declares it in `X-WR-TIMEZONE` plus a `VTIMEZONE` block with the European summer-time rules (WET/WEST), so every client shows a night at 20:30 and keeps the right offset across the year. The zone comes from `TIMEZONE`; an unknown zone falls back to `Europe/Lisbon`. Times and names are private member data: the token is the only protection, so the shared link goes only to the team.
|
||||||
|
- Each event title is `<pessoa> · <escala> · <horas>` so a list of shifts reads at a glance; a shift without a person keeps `<escala> · <horas>`.
|
||||||
- An unknown token returns 404, and a deactivated person's token stops working.
|
- An unknown token returns 404, and a deactivated person's token stops working.
|
||||||
|
|
||||||
### Night (`Noite`)
|
### Night (`Noite`)
|
||||||
|
|||||||
+32
-2
@@ -41,8 +41,8 @@ def test_the_personal_feed_only_carries_that_person_shifts(client, db):
|
|||||||
flat = unfold(feed)
|
flat = unfold(feed)
|
||||||
|
|
||||||
assert "BEGIN:VCALENDAR" in feed and feed.endswith("END:VCALENDAR\r\n")
|
assert "BEGIN:VCALENDAR" in feed and feed.endswith("END:VCALENDAR\r\n")
|
||||||
assert "SUMMARY:Noite · 20:30–00:00" in flat
|
assert "SUMMARY:Ana · Noite · 20:30–00:00" in flat
|
||||||
assert "SUMMARY:Almoço - Sábados · 13:00–16:00" not in flat # Bruno's shift
|
assert "SUMMARY:Ana · Almoço - Sábados" not in flat # Bruno's shift
|
||||||
assert "Pessoa: Ana" in flat
|
assert "Pessoa: Ana" in flat
|
||||||
assert "Bruno" not in flat
|
assert "Bruno" not in flat
|
||||||
assert f"DTSTART;TZID=Europe/Lisbon:{today:%Y%m%d}T203000" in flat
|
assert f"DTSTART;TZID=Europe/Lisbon:{today:%Y%m%d}T203000" in flat
|
||||||
@@ -191,3 +191,33 @@ def test_an_unknown_timezone_falls_back_to_lisbon(client, db, monkeypatch):
|
|||||||
|
|
||||||
monkeypatch.setattr("app.config.settings.timezone", "Marte/Phobos")
|
monkeypatch.setattr("app.config.settings.timezone", "Marte/Phobos")
|
||||||
assert feed_timezone() == "Europe/Lisbon"
|
assert feed_timezone() == "Europe/Lisbon"
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_unassigned_shift_still_has_an_event(client, db):
|
||||||
|
night, _, _, ana, _, _ = seed(db)
|
||||||
|
_hours(db, night, "20:30", "00:00")
|
||||||
|
assignment = Assignment(schedule_id=night.id, date=date.today(), user_id=None, source="generated")
|
||||||
|
db.add(assignment)
|
||||||
|
db.commit()
|
||||||
|
night_row = db.get(Assignment, assignment.id)
|
||||||
|
|
||||||
|
feed = unfold(build_feed(db, None, "Rota completa"))
|
||||||
|
|
||||||
|
assert "SUMMARY:Noite · 20:30–00:00" in feed # nobody to name
|
||||||
|
assert "SUMMARY:Noite · 20:30–00:00 ·" not in feed
|
||||||
|
|
||||||
|
|
||||||
|
def test_both_feeds_publish_the_same_timezone(client, db):
|
||||||
|
"""A person's own feed and the shared one must not drift apart on the timezone."""
|
||||||
|
night, sat, _, ana, bruno, admin = seed(db)
|
||||||
|
_hours(db, night, "20:30", "00:00")
|
||||||
|
_shift(db, night, date.today(), ana)
|
||||||
|
|
||||||
|
personal = unfold(build_feed(db, ana, "Rota — Ana"))
|
||||||
|
shared = unfold(build_feed(db, None, settings.app_name))
|
||||||
|
|
||||||
|
for feed in (personal, shared):
|
||||||
|
assert "X-WR-TIMEZONE:Europe/Lisbon" in feed
|
||||||
|
assert "TZID:Europe/Lisbon" in feed
|
||||||
|
assert "BEGIN:VTIMEZONE" in feed and "END:VTIMEZONE" in feed
|
||||||
|
assert f"DTSTART;TZID=Europe/Lisbon:{date.today():%Y%m%d}T203000" in feed
|
||||||
|
|||||||
Reference in New Issue
Block a user