(back) extract ICS URL property and display in invitation emails

Add visio conference URL support to invitation emails:
- Add url field to EventDetails dataclass
- Extract URL property in ICalendarParser.parse()
- Fix extract_property regex to preserve full URLs (was truncating
  https:// by splitting on colon)
- Add conditional visio section to all 8 email templates
  (invitation, update, cancel, reply — HTML and text)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nathan Panchout
2026-02-06 17:39:08 +01:00
parent eac8cde272
commit e26193dc3c
9 changed files with 33 additions and 10 deletions

View File

@@ -50,6 +50,7 @@ class EventDetails:
summary: str summary: str
description: Optional[str] description: Optional[str]
location: Optional[str] location: Optional[str]
url: Optional[str]
dtstart: datetime dtstart: datetime
dtend: Optional[datetime] dtend: Optional[datetime]
organizer_email: str organizer_email: str
@@ -95,17 +96,10 @@ class ICalendarParser:
# Handle multi-line values (lines starting with space/tab are continuations) # Handle multi-line values (lines starting with space/tab are continuations)
icalendar = re.sub(r"\r?\n[ \t]", "", icalendar) icalendar = re.sub(r"\r?\n[ \t]", "", icalendar)
pattern = rf"^{property_name}[;:](.+)$" pattern = rf"^{property_name}(;[^:]*)?:(.+)$"
match = re.search(pattern, icalendar, re.MULTILINE | re.IGNORECASE) match = re.search(pattern, icalendar, re.MULTILINE | re.IGNORECASE)
if match: if match:
value = match.group(1) return match.group(2).strip()
# Remove parameters (everything before the last colon if there are parameters)
if ";" in property_name or ":" not in value:
return value.strip()
# Handle properties with parameters like ORGANIZER;CN=Name:mailto:email
if ":" in value:
return value.split(":")[-1].strip()
return value.strip()
return None return None
@staticmethod @staticmethod
@@ -205,6 +199,7 @@ class ICalendarParser:
summary = cls.extract_property(vevent_block, "SUMMARY") or "(Sans titre)" summary = cls.extract_property(vevent_block, "SUMMARY") or "(Sans titre)"
description = cls.extract_property(vevent_block, "DESCRIPTION") description = cls.extract_property(vevent_block, "DESCRIPTION")
location = cls.extract_property(vevent_block, "LOCATION") location = cls.extract_property(vevent_block, "LOCATION")
url = cls.extract_property(vevent_block, "URL")
# Parse dates with timezone support - from VEVENT block only # Parse dates with timezone support - from VEVENT block only
dtstart_raw, dtstart_params = cls.extract_property_with_params( dtstart_raw, dtstart_params = cls.extract_property_with_params(
@@ -265,6 +260,7 @@ class ICalendarParser:
summary=summary, summary=summary,
description=description, description=description,
location=location, location=location,
url=url,
dtstart=dtstart, dtstart=dtstart,
dtend=dtend, dtend=dtend,
organizer_email=organizer_email, organizer_email=organizer_email,

View File

@@ -119,6 +119,12 @@
<td>{{ event.location }}</td> <td>{{ event.location }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if event.url %}
<tr>
<td>Visio</td>
<td><a href="{{ event.url }}" style="color: #0066cc;">{{ event.url }}</a></td>
</tr>
{% endif %}
<tr> <tr>
<td>Organisateur</td> <td>Organisateur</td>
<td>{{ organizer_display }} &lt;{{ event.organizer_email }}&gt;</td> <td>{{ organizer_display }} &lt;{{ event.organizer_email }}&gt;</td>

View File

@@ -6,6 +6,7 @@ Détails de l'événement
Titre : {{ event.summary }} Titre : {{ event.summary }}
Quand : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %} Quand : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %}
{% if event.location %}Lieu : {{ event.location }} {% if event.location %}Lieu : {{ event.location }}
{% endif %}{% if event.url %}Visio : {{ event.url }}
{% endif %}Organisateur : {{ organizer_display }} <{{ event.organizer_email }}> {% endif %}Organisateur : {{ organizer_display }} <{{ event.organizer_email }}>
{% if event.description %} {% if event.description %}

View File

@@ -114,6 +114,12 @@
<td>{{ event.location }}</td> <td>{{ event.location }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if event.url %}
<tr>
<td>Visio</td>
<td>{{ event.url }}</td>
</tr>
{% endif %}
<tr> <tr>
<td>Organisateur</td> <td>Organisateur</td>
<td>{{ organizer_display }} &lt;{{ event.organizer_email }}&gt;</td> <td>{{ organizer_display }} &lt;{{ event.organizer_email }}&gt;</td>

View File

@@ -6,6 +6,7 @@ Détails de l'événement annulé
Titre : {{ event.summary }} Titre : {{ event.summary }}
Était prévu le : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %} Était prévu le : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %}
{% if event.location %}Lieu : {{ event.location }} {% if event.location %}Lieu : {{ event.location }}
{% endif %}{% if event.url %}Visio : {{ event.url }}
{% endif %}Organisateur : {{ organizer_display }} <{{ event.organizer_email }}> {% endif %}Organisateur : {{ organizer_display }} <{{ event.organizer_email }}>
Cet événement a été annulé. Vous pouvez le supprimer de votre calendrier en ouvrant le fichier .ics en pièce jointe. Cet événement a été annulé. Vous pouvez le supprimer de votre calendrier en ouvrant le fichier .ics en pièce jointe.

View File

@@ -101,6 +101,12 @@
<td>{{ event.location }}</td> <td>{{ event.location }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if event.url %}
<tr>
<td>Visio</td>
<td><a href="{{ event.url }}" style="color: #28a745;">{{ event.url }}</a></td>
</tr>
{% endif %}
<tr> <tr>
<td>Participant</td> <td>Participant</td>
<td>{{ attendee_display }} &lt;{{ event.attendee_email }}&gt;</td> <td>{{ attendee_display }} &lt;{{ event.attendee_email }}&gt;</td>

View File

@@ -6,8 +6,8 @@ Détails de l'événement
Titre : {{ event.summary }} Titre : {{ event.summary }}
Quand : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %} Quand : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %}
{% if event.location %}Lieu : {{ event.location }} {% if event.location %}Lieu : {{ event.location }}
{% endif %}{% if event.url %}Visio : {{ event.url }}
{% endif %} {% endif %}
La réponse du participant a été enregistrée dans le fichier .ics en pièce jointe. La réponse du participant a été enregistrée dans le fichier .ics en pièce jointe.
--- ---

View File

@@ -124,6 +124,12 @@
<td>{{ event.location }}</td> <td>{{ event.location }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if event.url %}
<tr>
<td>Visio</td>
<td><a href="{{ event.url }}" style="color: #e65100;">{{ event.url }}</a></td>
</tr>
{% endif %}
<tr> <tr>
<td>Organisateur</td> <td>Organisateur</td>
<td>{{ organizer_display }} &lt;{{ event.organizer_email }}&gt;</td> <td>{{ organizer_display }} &lt;{{ event.organizer_email }}&gt;</td>

View File

@@ -6,6 +6,7 @@ Détails de l'événement mis à jour
Titre : {{ event.summary }} Titre : {{ event.summary }}
Quand : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %} Quand : {{ start_date }} {{ time_str }}{% if start_date != end_date %} - {{ end_date }}{% endif %}
{% if event.location %}Lieu : {{ event.location }} {% if event.location %}Lieu : {{ event.location }}
{% endif %}{% if event.url %}Visio : {{ event.url }}
{% endif %}Organisateur : {{ organizer_display }} <{{ event.organizer_email }}> {% endif %}Organisateur : {{ organizer_display }} <{{ event.organizer_email }}>
{% if event.description %} {% if event.description %}