Ajout import Helloasso

This commit is contained in:
Jean-Marie Favreau 2025-04-12 15:38:54 +02:00
parent b186001132
commit 50579a6597
12 changed files with 431 additions and 185 deletions

1
.gitignore vendored
View File

@ -92,3 +92,4 @@ experimentations/events-augustes.json
# MacOS
.DS_Store
src/errors/

View File

@ -23,3 +23,6 @@ APP_DOMAIN=app.backend.dev
# APIs
ECHOSCIENCES_TOKEN=
HELLOASSO_ID=
HELLOASSO_SECRET=

View File

@ -0,0 +1,50 @@
#!/usr/bin/python3
# coding: utf-8
# To be able to run this import out of django, you'll have to set an environment variable with the Helloasso client id/secret:
# export HELLOASSO_SECRET=<your secret>
# export HELLOASSO_ID=<your ID>
import json
import os
import sys
# getting the name of the directory
# where the this file is present.
current = os.path.dirname(os.path.realpath(__file__))
# Getting the parent directory name
# where the current directory is present.
parent = os.path.dirname(current)
# adding the parent directory to
# the sys.path.
sys.path.append(parent)
sys.path.append(parent + "/src")
from src.agenda_culturel.import_tasks.downloader import SimpleDownloader
from src.agenda_culturel.import_tasks.generic_extractors.helloasso import (
CExtractor,
)
from src.agenda_culturel.import_tasks.importer import URL2Events
if __name__ == "__main__":
u2e = URL2Events(SimpleDownloader(), CExtractor())
#url = "https://www.helloasso.com/associations/federation-des-associations-laiques-du-puy-de-dome/evenements/de-fil-en-chemin-2025-11e-edition-rencontres-de-danse-amateur"
url = "https://www.helloasso.com/associations/federation-des-associations-laiques-du-puy-de-dome"
url_human = url
events = u2e.process(
url,
url_human,
cache="cache-helloasso.html",
default_values={},
published=True,
)
exportfile = "events-helloasso.json"
print("Saving events to file {}".format(exportfile))
with open(exportfile, "w") as f:
json.dump(events, f, indent=4, default=str)

View File

@ -39,6 +39,7 @@ from .import_tasks.generic_extractors import (
ical,
fbevents,
echosciences,
helloasso,
)
from .import_tasks.importer import URL2Events
@ -211,6 +212,8 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
extractor = lecameleon.CExtractor()
elif rimport.processor == RecurrentImport.PROCESSOR.ECHOSCIENCES:
extractor = echosciences.CExtractor()
elif rimport.processor == RecurrentImport.PROCESSOR.HELLOASSO:
extractor = helloasso.CExtractor()
else:
extractor = None

View File

@ -121,7 +121,10 @@ class ChromiumHeadlessDownloader(Downloader):
self.driver = webdriver.Chrome(service=self.service, options=self.options)
def __del__(self):
try:
self.driver.quit()
except Exception as e:
print('Error: ' + str(e))
def screenshot(self, url, path_image):
print("Screenshot {}".format(url))

View File

@ -316,21 +316,23 @@ class Extractor(ABC):
)
from .generic_extractors.ical import ICALExtractor
from .custom_extractors.associations_cf import CExtractor as AssociationsCF
from .generic_extractors.helloasso import CExtractor as HelloAssoExtractor
if single_event:
return [
FacebookEventExtractor(),
GoogleCalendarLinkEventExtractor(),
AssociationsCF(),
ICALExtractor(),
EventNotFoundExtractor(),
FacebookEventExtractor,
GoogleCalendarLinkEventExtractor,
AssociationsCF,
ICALExtractor,
HelloAssoExtractor,
EventNotFoundExtractor,
]
else:
return [
ICALExtractor(),
FacebookEventExtractor(),
GoogleCalendarLinkEventExtractor(),
EventNotFoundExtractor(),
ICALExtractor,
FacebookEventExtractor,
GoogleCalendarLinkEventExtractor,
EventNotFoundExtractor,
]

View File

@ -0,0 +1,156 @@
import logging
from datetime import datetime, timedelta
from django.conf import settings
import dateutil.parser
import requests
from bs4 import BeautifulSoup
import re
import json
from ..extractor import Extractor
logger = logging.getLogger(__name__)
# A class dedicated to get events from helloasso
class CExtractor(Extractor):
patternEvent = r'^https://www\.helloasso\.com/associations/([\w\-]+)/evenements/([\w\-]+)'
patternOrg = r'^https://www\.helloasso\.com/associations/([\w\-]+)'
urlAPI = "https://api.helloasso.com"
def __init__(self):
super().__init__()
self.no_downloader = True
def id_and_secret_available(self):
return settings.HELLOASSO_ID != "" and settings.HELLOASSO_SECRET != ""
def is_known_url(url):
return re.match(CExtractor.patternEvent, url) is not None or re.match(CExtractor.patternOrg, url) is not None
def get_header_with_token():
url = CExtractor.urlAPI + "/oauth2/token"
payload = {
"grant_type": "client_credentials",
"client_id": settings.HELLOASSO_ID,
"client_secret": settings.HELLOASSO_SECRET
}
headers = {
"accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(url, data=payload, headers=headers)
data = json.loads(response.text)
return {
"accept": "application/json",
"authorization": "Bearer " + data["access_token"]
}
def _get_single_event(org, event, headers=None):
if headers is None:
headers = CExtractor.get_header_with_token()
url = CExtractor.urlAPI + "/v5/organizations/{}/forms/Event/{}/public".format(org, event)
response = requests.get(url, headers=headers)
return json.loads(response.text)
def _get_all_events(org):
url = CExtractor.urlAPI + "/v5/organizations/{}/forms?states=Public&formTypes=Event&pageSize=20".format(org)
headers = CExtractor.get_header_with_token()
result = []
for i in range(1, 200):
response = requests.get(url + "&pageIndex={}".format(i), headers=headers)
events = json.loads(response.text)["data"]
if len(events) == 0:
return result
else:
for e in events:
result.append(CExtractor._get_single_event(org, e["formSlug"], headers))
def _get_events(self, url):
match = re.match(CExtractor.patternEvent, url)
if match:
return [CExtractor._get_single_event(match.group(1), match.group(2))]
else:
match = re.match(CExtractor.patternOrg, url)
if match:
return CExtractor._get_all_events(match.group(1))
return []
def extract(
self,
content,
url,
url_human=None,
default_values=None,
published=False,
):
self.set_header(url)
self.clear_events()
if not self.id_and_secret_available():
logger.error("ID/Secret not available")
raise Exception("ID/Secret not available")
if not CExtractor.is_known_url(url):
logger.error("Unknown url")
raise Exception("Unknown url")
events = self._get_events(url)
for event in events:
title = event["title"]
event_url = event["url"]
image = None
image_alt = None
if "banner" in event:
image = event["banner"]["publicUrl"]
if "description" in event["banner"]:
image_alt = event["banner"]["description"]
location = []
if "place" in event and event["place"] is not None:
for elplace in ["name", "address", "zipCode", "city"]:
if elplace in event["place"]:
location.append(event["place"][elplace])
location = ", ".join(location)
description = event["description"]
start = dateutil.parser.isoparse(event["startDate"])
end = dateutil.parser.isoparse(event["endDate"])
start_day = start.date()
start_time = start.time()
end_day = end.date()
end_time = end.time()
self.add_event(
default_values,
title,
None,
start_day,
location,
description,
[],
uuids=[event_url],
recurrences=None,
url_human=event_url,
start_time=start_time,
published=published,
image=image,
image_alt=image_alt,
end_day=end_day,
end_time=end_time,
)
return self.get_structure()

View File

@ -54,8 +54,9 @@ class URL2Events:
)
else:
# if the extractor is not defined, use a list of default extractors
for e in Extractor.get_default_extractors(self.single_event):
logger.info("Extractor::" + type(e).__name__)
for extrClass in Extractor.get_default_extractors(self.single_event):
e = extrClass()
logger.info("Extractor::" + str(type(e)))
e.set_downloader(self.downloader)
try:
events = e.extract(

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: agenda_culturel\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-06 13:18+0200\n"
"POT-Creation-Date: 2025-04-12 14:10+0200\n"
"PO-Revision-Date: 2023-10-29 14:16+0000\n"
"Last-Translator: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
"Language-Team: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
@ -143,11 +143,11 @@ msgid "Imported from"
msgstr "Importé depuis"
#: agenda_culturel/filters.py:470 agenda_culturel/models.py:875
#: agenda_culturel/models.py:2991
#: agenda_culturel/models.py:3000
msgid "Status"
msgstr "Status"
#: agenda_culturel/filters.py:471 agenda_culturel/models.py:2755
#: agenda_culturel/filters.py:471 agenda_culturel/models.py:2763
msgid "Closed"
msgstr "Fermé"
@ -156,7 +156,7 @@ msgid "Open"
msgstr "Ouvert"
#: agenda_culturel/filters.py:475 agenda_culturel/filters.py:476
#: agenda_culturel/models.py:2749
#: agenda_culturel/models.py:2757
msgid "Spam"
msgstr "Spam"
@ -164,7 +164,7 @@ msgstr "Spam"
msgid "Non spam"
msgstr "Non spam"
#: agenda_culturel/filters.py:481 agenda_culturel/models.py:2770
#: agenda_culturel/filters.py:481 agenda_culturel/models.py:2778
msgid "Type"
msgstr "Type"
@ -195,11 +195,11 @@ msgstr ""
msgid "Your email"
msgstr "Votre adresse email"
#: agenda_culturel/forms.py:168 agenda_culturel/models.py:2737
#: agenda_culturel/forms.py:168 agenda_culturel/models.py:2745
msgid "Your email address"
msgstr "Votre adresse email"
#: agenda_culturel/forms.py:174 agenda_culturel/models.py:2762
#: agenda_culturel/forms.py:174 agenda_culturel/models.py:2770
msgid "Comments"
msgstr "Commentaires"
@ -215,8 +215,8 @@ msgid "Receive notification of publication or leave a message for moderation"
msgstr "Être notifié de la publication ou laisser un message à la modération"
#: agenda_culturel/forms.py:208 agenda_culturel/models.py:315
#: agenda_culturel/models.py:883 agenda_culturel/models.py:2915
#: agenda_culturel/models.py:3026
#: agenda_culturel/models.py:883 agenda_culturel/models.py:2924
#: agenda_culturel/models.py:3035
msgid "Category"
msgstr "Catégorie"
@ -271,7 +271,7 @@ msgid "Details"
msgstr "Détails"
#: agenda_culturel/forms.py:383 agenda_culturel/models.py:912
#: agenda_culturel/models.py:2890
#: agenda_culturel/models.py:2899
msgid "Location"
msgstr "Localisation"
@ -351,7 +351,7 @@ msgid "Apply category {} to the event {}"
msgstr "Appliquer la catégorie {} à l'événement {}"
#: agenda_culturel/forms.py:868 agenda_culturel/models.py:700
#: agenda_culturel/models.py:3078
#: agenda_culturel/models.py:3087
msgid "Place"
msgstr "Lieu"
@ -389,7 +389,7 @@ msgstr "Informations"
msgid "Add a comment"
msgstr "Ajouter un commentaire"
#: agenda_culturel/forms.py:1011 agenda_culturel/models.py:3179
#: agenda_culturel/forms.py:1011 agenda_culturel/models.py:3188
msgid "Period type"
msgstr "Type de période"
@ -505,7 +505,7 @@ msgstr ""
#: agenda_culturel/models.py:198 agenda_culturel/models.py:245
#: agenda_culturel/models.py:324 agenda_culturel/models.py:619
#: agenda_culturel/models.py:660 agenda_culturel/models.py:766
#: agenda_culturel/models.py:2729 agenda_culturel/models.py:2840
#: agenda_culturel/models.py:2737 agenda_culturel/models.py:2849
msgid "Name"
msgstr "Nom"
@ -574,8 +574,8 @@ msgstr "Description"
msgid "Description of the tag"
msgstr "Description de l'étiquette"
#: agenda_culturel/models.py:339 agenda_culturel/models.py:2696
#: agenda_culturel/models.py:2743
#: agenda_culturel/models.py:339 agenda_culturel/models.py:2704
#: agenda_culturel/models.py:2751
msgid "Message"
msgstr "Message"
@ -742,8 +742,8 @@ msgid ""
"Place mainly associated with this organizer. Mainly used if there is a "
"similarity in the name, to avoid redundant displays."
msgstr ""
"Lieu principalement associé à cette organisation. Principalement utilisé s'il "
"y a une similarité de nom, pour éviter les affichages redondants."
"Lieu principalement associé à cette organisation. Principalement utilisé "
"s'il y a une similarité de nom, pour éviter les affichages redondants."
#: agenda_culturel/models.py:800
msgid "Organisation"
@ -753,7 +753,7 @@ msgstr "Organisme"
msgid "Organisations"
msgstr "Organismes"
#: agenda_culturel/models.py:812 agenda_culturel/models.py:2885
#: agenda_culturel/models.py:812 agenda_culturel/models.py:2894
msgid "Published"
msgstr "Publié"
@ -789,7 +789,7 @@ msgstr "Auteur de la dernière modération"
msgid "Title"
msgstr "Titre"
#: agenda_culturel/models.py:889 agenda_culturel/models.py:3164
#: agenda_culturel/models.py:889 agenda_culturel/models.py:3173
msgid "Start day"
msgstr "Date de début"
@ -797,7 +797,7 @@ msgstr "Date de début"
msgid "Start time"
msgstr "Heure de début"
#: agenda_culturel/models.py:897 agenda_culturel/models.py:3165
#: agenda_culturel/models.py:897 agenda_culturel/models.py:3174
msgid "End day"
msgstr "Date de fin"
@ -899,60 +899,60 @@ msgstr "import récurrent"
msgid "a non authenticated user"
msgstr "un utilisateur non connecté"
#: agenda_culturel/models.py:1777
#: agenda_culturel/models.py:1781
msgid "Your event has been published"
msgstr "Ton événement a été publié"
#: agenda_culturel/models.py:1782
#: agenda_culturel/models.py:1786
msgid "Your message has not been retained"
msgstr "Ton événement n'a pas été retenu"
#: agenda_culturel/models.py:1870 agenda_culturel/models.py:2693
#: agenda_culturel/models.py:1874 agenda_culturel/models.py:2701
msgid "Warning"
msgstr "Warning"
#: agenda_culturel/models.py:1872 agenda_culturel/models.py:1978
#: agenda_culturel/models.py:1876 agenda_culturel/models.py:1982
msgid "the date has not been imported correctly."
msgstr "la date n'a pas été importée correctement."
#: agenda_culturel/models.py:1960
#: agenda_culturel/models.py:1964
msgid "during import process"
msgstr "pendant le processus d'import"
#: agenda_culturel/models.py:1976 agenda_culturel/models.py:1986
#: agenda_culturel/models.py:1997
#: agenda_culturel/models.py:1980 agenda_culturel/models.py:1990
#: agenda_culturel/models.py:2001
msgid "warning"
msgstr "attention"
#: agenda_culturel/models.py:1988
#: agenda_culturel/models.py:1992
msgid "the title has not been imported correctly."
msgstr "le titre n'a pas été importé correctement."
#: agenda_culturel/models.py:2000
#: agenda_culturel/models.py:2004
msgid "The import was unable to find an event in the page."
msgstr "L'import a été incapable de trouver un événement dans la page."
#: agenda_culturel/models.py:2348
#: agenda_culturel/models.py:2356
msgid "Updated field(s): "
msgstr "Champ(s) mis à jour: "
#: agenda_culturel/models.py:2352
#: agenda_culturel/models.py:2360
msgid "Update"
msgstr "Mise à jour"
#: agenda_culturel/models.py:2353
#: agenda_culturel/models.py:2361
msgid "update process"
msgstr "processus de mise à jour"
#: agenda_culturel/models.py:2422
#: agenda_culturel/models.py:2430
msgid "Import"
msgstr "Import"
#: agenda_culturel/models.py:2423
#: agenda_culturel/models.py:2431
msgid "import process"
msgstr "processus d'import"
#: agenda_culturel/models.py:2425
#: agenda_culturel/models.py:2433
msgid ""
"The duration of the event is a little too long for direct publication. "
"Moderators can choose to publish it or not."
@ -960,195 +960,199 @@ msgstr ""
"La durée de l'événement est un peu trop longue pour qu'il soit publié "
"directement. Les modérateurs peuvent choisir de le publier ou non."
#: agenda_culturel/models.py:2684
#: agenda_culturel/models.py:2692
msgid "From contributor"
msgstr "D'un·e contributeurice"
#: agenda_culturel/models.py:2685
#: agenda_culturel/models.py:2693
msgid "Import process"
msgstr "Processus d'import"
#: agenda_culturel/models.py:2686
#: agenda_culturel/models.py:2694
msgid "Update process"
msgstr "Processus de mise à jour"
#: agenda_culturel/models.py:2687
#: agenda_culturel/models.py:2695
msgid "Contact form"
msgstr "Formulaire de contact"
#: agenda_culturel/models.py:2688
#: agenda_culturel/models.py:2696
msgid "Event report"
msgstr "Signalemet d'événement"
#: agenda_culturel/models.py:2691
#: agenda_culturel/models.py:2699
msgid "From contributor (without message)"
msgstr "D'un·e contributeurice (sans message)"
#: agenda_culturel/models.py:2697
#: agenda_culturel/models.py:2705
msgid "Messages"
msgstr "Messages"
#: agenda_culturel/models.py:2706
#: agenda_culturel/models.py:2714
msgid "Subject"
msgstr "Sujet"
#: agenda_culturel/models.py:2707
#: agenda_culturel/models.py:2715
msgid "The subject of your message"
msgstr "Sujet de votre message"
#: agenda_culturel/models.py:2713
#: agenda_culturel/models.py:2721
msgid "Related event"
msgstr "Événement associé"
#: agenda_culturel/models.py:2714
#: agenda_culturel/models.py:2722
msgid "The message is associated with this event."
msgstr "Le message est associé à cet événement."
#: agenda_culturel/models.py:2722
#: agenda_culturel/models.py:2730
msgid "Author of the message"
msgstr "Auteur du message"
#: agenda_culturel/models.py:2730
#: agenda_culturel/models.py:2738
msgid "Your name"
msgstr "Votre nom"
#: agenda_culturel/models.py:2736
#: agenda_culturel/models.py:2744
msgid "Email address"
msgstr "Adresse email"
#: agenda_culturel/models.py:2743
#: agenda_culturel/models.py:2751
msgid "Your message"
msgstr "Votre message"
#: agenda_culturel/models.py:2750
#: agenda_culturel/models.py:2758
msgid "This message is a spam."
msgstr "Ce message est un spam."
#: agenda_culturel/models.py:2757
#: agenda_culturel/models.py:2765
msgid "this message has been processed and no longer needs to be handled"
msgstr "Ce message a été traité et ne nécessite plus d'être pris en charge"
#: agenda_culturel/models.py:2763
#: agenda_culturel/models.py:2771
msgid "Comments on the message from the moderation team"
msgstr "Commentaires sur ce message par l'équipe de modération"
#: agenda_culturel/models.py:2796 agenda_culturel/models.py:2973
#: agenda_culturel/models.py:2804 agenda_culturel/models.py:2982
msgid "Recurrent import"
msgstr "Import récurrent"
#: agenda_culturel/models.py:2797
#: agenda_culturel/models.py:2805
msgid "Recurrent imports"
msgstr "Imports récurrents"
#: agenda_culturel/models.py:2801
#: agenda_culturel/models.py:2809
msgid "ical"
msgstr "ical"
#: agenda_culturel/models.py:2802
#: agenda_culturel/models.py:2810
msgid "ical no busy"
msgstr "ical sans busy"
#: agenda_culturel/models.py:2803
#: agenda_culturel/models.py:2811
msgid "ical no VC"
msgstr "ical sans VC"
#: agenda_culturel/models.py:2804
#: agenda_culturel/models.py:2812
msgid "ical naive timezone"
msgstr "ical timezone naïve"
#: agenda_culturel/models.py:2805
#: agenda_culturel/models.py:2813
msgid "lacoope.org"
msgstr "lacoope.org"
#: agenda_culturel/models.py:2806
#: agenda_culturel/models.py:2814
msgid "la comédie"
msgstr "la comédie"
#: agenda_culturel/models.py:2807
#: agenda_culturel/models.py:2815
msgid "le fotomat"
msgstr "le fotomat"
#: agenda_culturel/models.py:2808
#: agenda_culturel/models.py:2816
msgid "la puce à l'oreille"
msgstr "la puce à loreille"
#: agenda_culturel/models.py:2809
#: agenda_culturel/models.py:2817
msgid "Plugin wordpress MEC"
msgstr "Plugin wordpress MEC"
#: agenda_culturel/models.py:2810
#: agenda_culturel/models.py:2818
msgid "Événements d'une page FB"
msgstr "Événements d'une page FB"
#: agenda_culturel/models.py:2811
#: agenda_culturel/models.py:2819
msgid "Billetterie Clermont-Ferrand"
msgstr ""
#: agenda_culturel/models.py:2812
#: agenda_culturel/models.py:2820
msgid "Arachnée concert"
msgstr "Arachnée concert"
#: agenda_culturel/models.py:2813
#: agenda_culturel/models.py:2821
msgid "Le Rio"
msgstr "Le Rio"
#: agenda_culturel/models.py:2814
#: agenda_culturel/models.py:2822
msgid "La Raymonde"
msgstr "La Raymone"
#: agenda_culturel/models.py:2815
#: agenda_culturel/models.py:2823
msgid "Agenda apidae tourisme"
msgstr "Agenda apidae tourisme"
#: agenda_culturel/models.py:2816
#: agenda_culturel/models.py:2824
msgid "Agenda iguana (médiathèques)"
msgstr "Agenda iguana (médiathèques)"
#: agenda_culturel/models.py:2817
#: agenda_culturel/models.py:2825
msgid "Mille formes"
msgstr "Mille Formes"
#: agenda_culturel/models.py:2818
#: agenda_culturel/models.py:2826
msgid "Les Amis du Temps des Cerises"
msgstr "Les Amis du Temps des Cerises"
#: agenda_culturel/models.py:2819
#: agenda_culturel/models.py:2827
msgid "Mobilizon"
msgstr "Mobilizon"
#: agenda_culturel/models.py:2820
#: agenda_culturel/models.py:2828
msgid "Le caméléon"
msgstr ""
#: agenda_culturel/models.py:2821
#: agenda_culturel/models.py:2829
msgid "Echosciences"
msgstr ""
#: agenda_culturel/models.py:2824
#: agenda_culturel/models.py:2830
msgid "Hello Asso"
msgstr "Hello Asso"
#: agenda_culturel/models.py:2833
msgid "simple"
msgstr "simple"
#: agenda_culturel/models.py:2825
#: agenda_culturel/models.py:2834
msgid "Headless Chromium"
msgstr "chromium sans interface"
#: agenda_culturel/models.py:2828
#: agenda_culturel/models.py:2837
msgid "Headless Chromium (pause)"
msgstr "chromium sans interface (pause)"
#: agenda_culturel/models.py:2834
#: agenda_culturel/models.py:2843
msgid "daily"
msgstr "chaque jour"
#: agenda_culturel/models.py:2836
#: agenda_culturel/models.py:2845
msgid "weekly"
msgstr "chaque semaine"
#: agenda_culturel/models.py:2837
#: agenda_culturel/models.py:2846
msgid "never"
msgstr "jamais"
#: agenda_culturel/models.py:2842
#: agenda_culturel/models.py:2851
msgid ""
"Recurrent import name. Be careful to choose a name that is easy to "
"understand, as it will be public and displayed on the sites About page."
@ -1156,151 +1160,151 @@ msgstr ""
"Nom de l'import récurrent. Attention à choisir un nom compréhensible, car il "
"sera public, et affiché sur la page à propos du site."
#: agenda_culturel/models.py:2849
#: agenda_culturel/models.py:2858
msgid "Processor"
msgstr "Processeur"
#: agenda_culturel/models.py:2855
#: agenda_culturel/models.py:2864
msgid "Downloader"
msgstr "Téléchargeur"
#: agenda_culturel/models.py:2862
#: agenda_culturel/models.py:2871
msgid "Import recurrence"
msgstr "Récurrence d'import"
#: agenda_culturel/models.py:2869
#: agenda_culturel/models.py:2878
msgid "Source"
msgstr "Source"
#: agenda_culturel/models.py:2870
#: agenda_culturel/models.py:2879
msgid "URL of the source document"
msgstr "URL du document source"
#: agenda_culturel/models.py:2875
#: agenda_culturel/models.py:2884
msgid "Browsable url"
msgstr "URL navigable"
#: agenda_culturel/models.py:2877
#: agenda_culturel/models.py:2886
msgid "URL of the corresponding document that will be shown to visitors."
msgstr "URL correspondant au document et qui sera montrée aux visiteurs"
#: agenda_culturel/models.py:2886
#: agenda_culturel/models.py:2895
msgid "Status of each imported event (published or draft)"
msgstr "Status de chaque événement importé (publié ou brouillon)"
#: agenda_culturel/models.py:2891
#: agenda_culturel/models.py:2900
msgid "Address for each imported event"
msgstr "Adresse de chaque événement importé"
#: agenda_culturel/models.py:2898
#: agenda_culturel/models.py:2907
msgid "Force location"
msgstr "Focer la localisation"
#: agenda_culturel/models.py:2899
#: agenda_culturel/models.py:2908
msgid "force location even if another is detected."
msgstr "Forcer la localisation même si une autre a été détectée."
#: agenda_culturel/models.py:2905
#: agenda_culturel/models.py:2914
msgid "Organiser"
msgstr "Organisation"
#: agenda_culturel/models.py:2906
#: agenda_culturel/models.py:2915
msgid "Organiser of each imported event"
msgstr "Organisme à l'origine de chaque événement importé"
#: agenda_culturel/models.py:2916
#: agenda_culturel/models.py:2925
msgid "Category of each imported event"
msgstr "Catégorie de chaque événement importé"
#: agenda_culturel/models.py:2924
#: agenda_culturel/models.py:2933
msgid "Tags for each imported event"
msgstr "Étiquettes de chaque événement importé"
#: agenda_culturel/models.py:2925
#: agenda_culturel/models.py:2934
msgid "A list of tags that describe each imported event."
msgstr "Une liste d'étiquettes décrivant chaque événement importé"
#: agenda_culturel/models.py:2954
#: agenda_culturel/models.py:2963
msgid "Running"
msgstr "En cours"
#: agenda_culturel/models.py:2955
#: agenda_culturel/models.py:2964
msgid "Canceled"
msgstr "Annulé"
#: agenda_culturel/models.py:2956
#: agenda_culturel/models.py:2965
msgid "Success"
msgstr "Succès"
#: agenda_culturel/models.py:2957
#: agenda_culturel/models.py:2966
msgid "Failed"
msgstr "Erreur"
#: agenda_culturel/models.py:2960
#: agenda_culturel/models.py:2969
msgid "Batch importation"
msgstr "Importation par lot"
#: agenda_culturel/models.py:2961
#: agenda_culturel/models.py:2970
msgid "Batch importations"
msgstr "Importations par lot"
#: agenda_culturel/models.py:2974
#: agenda_culturel/models.py:2983
msgid "Reference to the recurrent import processing"
msgstr "Référence du processus d'import récurrent"
#: agenda_culturel/models.py:2982
#: agenda_culturel/models.py:2991
msgid "URL (if not recurrent import)"
msgstr "URL (si pas d'import récurrent)"
#: agenda_culturel/models.py:2983
#: agenda_culturel/models.py:2992
msgid "Source URL if no RecurrentImport is associated."
msgstr "URL source si aucun import récurrent n'est associé"
#: agenda_culturel/models.py:2998
#: agenda_culturel/models.py:3007
msgid "Error message"
msgstr "Votre message"
#: agenda_culturel/models.py:3002
#: agenda_culturel/models.py:3011
msgid "Number of collected events"
msgstr "Nombre d'événements collectés"
#: agenda_culturel/models.py:3005
#: agenda_culturel/models.py:3014
msgid "Number of imported events"
msgstr "Nombre d'événements importés"
#: agenda_culturel/models.py:3008
#: agenda_culturel/models.py:3017
msgid "Number of updated events"
msgstr "Nombre d'événements mis à jour"
#: agenda_culturel/models.py:3011
#: agenda_culturel/models.py:3020
msgid "Number of removed events"
msgstr "Nombre d'événements supprimés"
#: agenda_culturel/models.py:3019
#: agenda_culturel/models.py:3028
msgid "Weight"
msgstr "Poids"
#: agenda_culturel/models.py:3020
#: agenda_culturel/models.py:3029
msgid "The lower is the weight, the earlier the filter is applied"
msgstr "Plus le poids est léger, plus le filtre sera appliqué tôt"
#: agenda_culturel/models.py:3027
#: agenda_culturel/models.py:3036
msgid "Category applied to the event"
msgstr "Catégorie appliquée à l'événement"
#: agenda_culturel/models.py:3032
#: agenda_culturel/models.py:3041
msgid "Contained in the title"
msgstr "Contenu dans le titre"
#: agenda_culturel/models.py:3033
#: agenda_culturel/models.py:3042
msgid "Text contained in the event title"
msgstr "Texte contenu dans le titre de l'événement"
#: agenda_culturel/models.py:3039
#: agenda_culturel/models.py:3048
msgid "Exact title extract"
msgstr "Extrait exact du titre"
#: agenda_culturel/models.py:3041
#: agenda_culturel/models.py:3050
msgid ""
"If checked, the extract will be searched for in the title using the exact "
"form (capitals, accents)."
@ -1308,19 +1312,19 @@ msgstr ""
"Si coché, l'extrait sera recherché dans le titre en utilisant la forme "
"exacte (majuscules, accents)"
#: agenda_culturel/models.py:3047
#: agenda_culturel/models.py:3056
msgid "Contained in the description"
msgstr "Contenu dans la description"
#: agenda_culturel/models.py:3048
#: agenda_culturel/models.py:3057
msgid "Text contained in the description"
msgstr "Texte contenu dans la description"
#: agenda_culturel/models.py:3054
#: agenda_culturel/models.py:3063
msgid "Exact description extract"
msgstr "Extrait exact de description"
#: agenda_culturel/models.py:3056
#: agenda_culturel/models.py:3065
msgid ""
"If checked, the extract will be searched for in the description using the "
"exact form (capitals, accents)."
@ -1328,19 +1332,19 @@ msgstr ""
"Si coché, l'extrait sera recherché dans la description en utilisant la forme "
"exacte (majuscules, accents)"
#: agenda_culturel/models.py:3062
#: agenda_culturel/models.py:3071
msgid "Contained in the location"
msgstr "Contenu dans la localisation"
#: agenda_culturel/models.py:3063
#: agenda_culturel/models.py:3072
msgid "Text contained in the event location"
msgstr "Texte contenu dans la localisation de l'événement"
#: agenda_culturel/models.py:3069
#: agenda_culturel/models.py:3078
msgid "Exact location extract"
msgstr "Extrait exact de localisation"
#: agenda_culturel/models.py:3071
#: agenda_culturel/models.py:3080
msgid ""
"If checked, the extract will be searched for in the location using the exact "
"form (capitals, accents)."
@ -1348,55 +1352,55 @@ msgstr ""
"Si coché, l'extrait sera recherché dans la localisation en utilisant la "
"forme exacte (majuscules, accents)"
#: agenda_culturel/models.py:3079
#: agenda_culturel/models.py:3088
msgid "Location from place"
msgstr "Localisation depuis le lieu"
#: agenda_culturel/models.py:3088
#: agenda_culturel/models.py:3097
msgid "Categorisation rule"
msgstr "Règle de catégorisation"
#: agenda_culturel/models.py:3089
#: agenda_culturel/models.py:3098
msgid "Categorisation rules"
msgstr "Règles de catégorisation"
#: agenda_culturel/models.py:3163
#: agenda_culturel/models.py:3172
msgid "Period name"
msgstr "Nom de la période"
#: agenda_culturel/models.py:3168
#: agenda_culturel/models.py:3177
msgid "Special period"
msgstr "Période remarquable"
#: agenda_culturel/models.py:3169
#: agenda_culturel/models.py:3178
msgid "Special periods"
msgstr "Périodes remarquables"
#: agenda_culturel/models.py:3175
#: agenda_culturel/models.py:3184
msgid "public holidays"
msgstr "Jour férié"
#: agenda_culturel/models.py:3176
#: agenda_culturel/models.py:3185
msgid "school vacations"
msgstr "Vacances scolaires"
#: agenda_culturel/models.py:3194
#: agenda_culturel/models.py:3203
msgid "The end date must be after or equal to the start date."
msgstr "La date de fin doit être après ou identique à la date de début."
#: agenda_culturel/models.py:3202
#: agenda_culturel/models.py:3211
msgid " on "
msgstr " du "
#: agenda_culturel/models.py:3205
#: agenda_culturel/models.py:3214
msgid " from "
msgstr " du "
#: agenda_culturel/models.py:3205
#: agenda_culturel/models.py:3214
msgid " to "
msgstr " au "
#: agenda_culturel/settings/base.py:184
#: agenda_culturel/settings/base.py:188
msgid "French"
msgstr "français"
@ -1668,17 +1672,17 @@ msgstr ""
"par défaut ont été catégorisés"
#: agenda_culturel/views.py:2495 agenda_culturel/views.py:2557
#: agenda_culturel/views.py:2597
#: agenda_culturel/views.py:2601
msgid "{} events have been updated."
msgstr "{} événements ont été mis à jour."
#: agenda_culturel/views.py:2498 agenda_culturel/views.py:2559
#: agenda_culturel/views.py:2600
#: agenda_culturel/views.py:2604
msgid "1 event has been updated."
msgstr "1 événement a été mis à jour"
#: agenda_culturel/views.py:2500 agenda_culturel/views.py:2561
#: agenda_culturel/views.py:2602
#: agenda_culturel/views.py:2606
msgid "No events have been modified."
msgstr "Aucun événement n'a été modifié."
@ -1690,37 +1694,36 @@ msgstr "Le lieu a été modifié avec succès."
msgid "The place has been successfully created."
msgstr "Le lieu a été créé avec succès."
#: agenda_culturel/views.py:2584
#: agenda_culturel/views.py:2586
msgid ""
"The selected place has been assigned to the event <a href=\"{}\">{}</a>."
msgstr ""
"Le lieu sélectionné a été assigné à l'événement <a href=\"{}\">{}</a>."
msgstr "Le lieu sélectionné a été assigné à l'événement <a href=\"{}\">{}</a>."
#: agenda_culturel/views.py:2589
#: agenda_culturel/views.py:2593
msgid "A new alias has been added to the selected place."
msgstr "Un nouvel alias a été créé pour le lieu sélectionné."
#: agenda_culturel/views.py:2716
#: agenda_culturel/views.py:2720
msgid "The organisation has been successfully updated."
msgstr "L'organisme a été modifié avec succès."
#: agenda_culturel/views.py:2723
#: agenda_culturel/views.py:2727
msgid "The organisation has been successfully created."
msgstr "L'organisme a été créé avec succès."
#: agenda_culturel/views.py:2742
#: agenda_culturel/views.py:2746
msgid "The tag has been successfully updated."
msgstr "L'étiquette a été modifiée avec succès."
#: agenda_culturel/views.py:2749
#: agenda_culturel/views.py:2753
msgid "The tag has been successfully created."
msgstr "L'étiquette a été créée avec succès."
#: agenda_culturel/views.py:2849
#: agenda_culturel/views.py:2853
msgid "You have not modified the tag name."
msgstr "Vous n'avez pas modifié le nom de l'étiquette."
#: agenda_culturel/views.py:2864
#: agenda_culturel/views.py:2868
msgid ""
"This tag {} is already in use, and is described by different information "
"from the current tag. You can force renaming by checking the corresponding "
@ -1733,7 +1736,7 @@ msgstr ""
"sera supprimée, et tous les événements associés à l'étiquette {} seront "
"associés à l'étiquette {}."
#: agenda_culturel/views.py:2878
#: agenda_culturel/views.py:2882
msgid ""
"This tag {} is already in use. You can force renaming by checking the "
"corresponding option."
@ -1741,55 +1744,55 @@ msgstr ""
"Cette étiquette {} est déjà utilisée. Vous pouvez forcer le renommage en "
"cochant l'option correspondante."
#: agenda_culturel/views.py:2911
#: agenda_culturel/views.py:2915
msgid "The tag {} has been successfully renamed to {}."
msgstr "L'étiquette {} a été renommée avec succès en {}."
#: agenda_culturel/views.py:2953
#: agenda_culturel/views.py:2957
msgid "The tag {} has been successfully deleted."
msgstr "L'événement {} a été supprimé avec succès."
#: agenda_culturel/views.py:3100
#: agenda_culturel/views.py:3104
msgid "Cache successfully cleared."
msgstr "Le cache a été vidé avec succès."
#: agenda_culturel/views.py:3120
#: agenda_culturel/views.py:3124
msgid "Your user profile has been successfully modified."
msgstr "Votre profil utilisateur a été modifié avec succès."
#: agenda_culturel/views.py:3138
#: agenda_culturel/views.py:3142
msgid "The special period has been successfully created."
msgstr "La période remarquable a été créée avec succès."
#: agenda_culturel/views.py:3159
#: agenda_culturel/views.py:3163
msgid "The special period has been successfully deleted."
msgstr "La période remarquable a été supprimée avec succès."
#: agenda_culturel/views.py:3170
#: agenda_culturel/views.py:3174
msgid "The special period has been successfully updated."
msgstr "La période remarquable a été modifiée avec succès."
#: agenda_culturel/views.py:3187
#: agenda_culturel/views.py:3191
#, python-format
msgid "%(nb_created)d interval inserted."
msgid_plural "%(nb_created)d intervals inserted."
msgstr[0] "%(nb_created)d intervalle inséré."
msgstr[1] "%(nb_created)d intervalles insérés."
#: agenda_culturel/views.py:3198
#: agenda_culturel/views.py:3202
#, python-format
msgid "%(nb_overlap)d insersion was not possible due to overlap."
msgid_plural "%(nb_overlap)d insersion were not possible due to overlap."
msgstr[0] "%(nb_overlap)d insersion impossible à cause d'une intersection."
msgstr[1] "%(nb_overlap)d insersions impossibles à cause d'une intersection."
#: agenda_culturel/views.py:3209
#: agenda_culturel/views.py:3213
#, python-format
msgid "%(nb_error)d error while reading ical file."
msgid_plural "%(nb_error)d error while reading ical file."
msgstr[0] "%(nb_error)d erreur pendant la lecture du fichier ical."
msgstr[1] "%(nb_error)d erreurs pendant la lecture du fichier ical."
#: agenda_culturel/views.py:3217
#: agenda_culturel/views.py:3221
msgid "Error during file reading: {}"
msgstr "Erreur pendant la lecture du fichier: {}"

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.19 on 2025-04-12 14:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0166_specialperiod_agenda_cult_start_d_40e6a4_idx'),
]
operations = [
migrations.AlterField(
model_name='recurrentimport',
name='processor',
field=models.CharField(choices=[('ical', 'ical'), ('icalnobusy', 'ical no busy'), ('icalnovc', 'ical no VC'), ('ical naive tz', 'ical naive timezone'), ('lacoope', 'lacoope.org'), ('lacomedie', 'la comédie'), ('lefotomat', 'le fotomat'), ('lapucealoreille', "la puce à l'oreille"), ('Plugin wordpress MEC', 'Plugin wordpress MEC'), ('Facebook events', "Événements d'une page FB"), ('Billetterie CF', 'Billetterie Clermont-Ferrand'), ('arachnee', 'Arachnée concert'), ('rio', 'Le Rio'), ('raymonde', 'La Raymonde'), ('apidae', 'Agenda apidae tourisme'), ('iguana', 'Agenda iguana (médiathèques)'), ('Mille formes', 'Mille formes'), ('Amis cerises', 'Les Amis du Temps des Cerises'), ('Mobilizon', 'Mobilizon'), ('Le Caméléon', 'Le caméléon'), ('Echosciences', 'Echosciences'), ('HelloAsso', 'Hello Asso')], default='ical', max_length=20, verbose_name='Processor'),
),
]

View File

@ -1717,6 +1717,11 @@ class Event(models.Model):
self.update_recurrence_dtstartend()
# clear cache
for is_auth in [False, True]:
key = make_template_fragment_key("event_body", [is_auth, self.pk])
cache.delete(key)
# if the image is defined but not locally downloaded
if self.image and (
not self.local_image or not default_storage.exists(self.local_image.name)
@ -1835,11 +1840,6 @@ class Event(models.Model):
if "request" in kwargs:
self.notify_if_required(kwargs.get("request"))
# clear cache
for is_auth in [False, True]:
key = make_template_fragment_key("event_body", [is_auth, self.pk])
cache.delete(key)
# save message if required
if self.has_message():
for msg in self.get_messages():
@ -2053,7 +2053,8 @@ class Event(models.Model):
def get_updateable_uuid(self):
if self.uuids and len(self.uuids) > 0:
for s in self.uuids:
if FacebookEventExtractor.is_known_url(s):
for e in Extractor.get_default_extractors(True):
if e.is_known_url(s):
return s
return None
@ -2827,6 +2828,7 @@ class RecurrentImport(models.Model):
MOBILIZON = "Mobilizon", _("Mobilizon")
LECAMELEON = "Le Caméléon", _("Le caméléon")
ECHOSCIENCES = "Echosciences", _("Echosciences")
HELLOASSO = "HelloAsso", _("Hello Asso")
class DOWNLOADER(models.TextChoices):
SIMPLE = "simple", _("simple")

View File

@ -37,6 +37,10 @@ SERVER_EMAIL = os_getenv("SERVER_EMAIL", "")
ECHOSCIENCES_TOKEN = os_getenv("ECHOSCIENCES_TOKEN", "")
HELLOASSO_ID = os_getenv("HELLOASSO_ID", "")
HELLOASSO_SECRET = os_getenv("HELLOASSO_SECRET", "")
# Application definition
INSTALLED_APPS = [