Open Cynthia-Borot-PNE opened 5 years ago
Problème toujours d'actualité @camillemonchicourt ?
De notre côté on a des parsers APIDAE Agenda qui fonctionnent très bien sans avoir à surcharger le code particulièrement :
```python class TouristicEventsParser(TouristicEventApidaeParser): api_key = 'XXXXXXXX' project_id = 1234 selection_id = 12345 label = "Manifestations Sport et Nature" def __init__(self, *args, **kwarg): super(TouristicEventsParser, self).__init__(*args, **kwarg) # Do not import themes del self.m2m_fields['themes'] ```
Si le problème existe toujours chez vous, peut-être faut-il surcharger plus finement la classe et en particulier la correspondance des champs geotrek <> apidae ? Voici un extrait de classe de Parser créant des evenements issus d'APIDAE plus complète qu'on utilise pour un parc :
```python
class PNRXXAgendaParser(TouristicEventApidaeParser):
api_key = "XXXXXXXXX"
project_id = 1234
selection_id = 12345
label = u"Agenda"
delete = True
constant_fields = {
'published': True,
'approved': False,
}
responseFields = [
'id',
'nom',
'ouverture',
'informationsFeteEtManifestation',
'presentation.descriptifCourt',
'presentation.descriptifDetaille',
'localisation.adresse',
'localisation.geolocalisation.geoJson.coordinates',
'localisation.geolocalisation.complement.libelleFr',
'informations.moyensCommunication',
'informations.structureGestion.nom.libelleFr',
'descriptionTarif.tarifsEnClair',
'descriptionTarif.modesPaiement',
'prestations',
'gestion.dateModification',
'gestion.membreProprietaire.nom',
'illustrations',
'informationsCommerceEtService',
'informationsDegustation',
'criteresInternes'
]
fields = {
'name': 'nom.libelleFr',
'eid': 'id',
'description_teaser': 'presentation.descriptifCourt.libelleFr',
'description': ('presentation.descriptifDetaille.libelleFr', "ouverture.periodeEnClair.libelleFr", 'prestations.labelsTourismeHandicap.*.elementReferenceType', 'informationsFeteEtManifestation.themes.*.libelleFr', 'ouverture.periodesOuvertures'),
'begin_date': ('ouverture.periodesOuvertures'),
'end_date': ('ouverture.periodesOuvertures'),
'start_time': ('ouverture.periodesOuvertures'),
'end_time': ('ouverture.periodesOuvertures'),
'contact': ('localisation.adresse.adresse1', 'localisation.adresse.adresse2', 'localisation.adresse.adresse3', 'localisation.adresse.codePostal', 'localisation.adresse.commune.nom',
'informations.moyensCommunication'),
'geom': 'localisation.geolocalisation.geoJson.coordinates',
'type': 'informationsFeteEtManifestation.typesManifestation.*.libelleFr',
'organizer': ('criteresInternes.*.libelle'),
}
def filter_contact(self, src, val):
(address1, address2, address3, zipCode, commune, comm) = val
tel = self.filter_comm(comm, 201, multiple=True)
mail = self.filter_comm(comm, 204, multiple=True)
web = self.filter_comm(comm, 205, multiple=True)
fax = self.filter_comm(comm, 202, multiple=True)
if tel:
tel = "Tél. " + tel
if mail:
mail = "Mail " + mail
if web:
web = "Site web " + f"{web}"
if fax:
fax = "Fax " + fax
lines = [line for line in [
address1,
address2,
address3,
' '.join([part for part in [zipCode, commune] if part]),
tel,
mail,
web,
fax
] if line]
return '
'.join(lines)
def filter_comm(self, val, code, multiple=True):
if not val:
return None
vals = [subval['coordonnees']['fr'] for subval in val if subval['type']['id'] == code]
if multiple:
return ' / '.join(vals)
if vals:
return vals[0]
return None
def filter_attachments(self, src, val):
dst = []
for atta in val:
url = atta.get("traductionFichiers", None)[0].get("url", None)
if url is None:
continue
legend = atta.get("nom", None)
if legend is not None:
legend = legend.get("libelleFr", None)
author = atta.get("copyright", None)
if author is not None:
author = author.get("libelleFr", None)
dst.append((url, legend, author))
return dst
def filter_type(self, src, val):
type = None
if val:
for label in val:
if label == "Manifestations commerciales":
type, _ = TouristicEventType.objects.get_or_create(type="Marchés")
elif label in ["Sports", "Nature et détente"]:
type, _ = TouristicEventType.objects.get_or_create(type="Manifestations sportives")
elif label in ["Traditions et folklore", "Culture", "Distractions et loisirs"]:
type, _ = TouristicEventType.objects.get_or_create(type="Manifestations culturelles")
else:
type, _ = TouristicEventType.objects.get_or_create(type=label)
return type
def filter_organizer(self, src, val):
partenariats = val
orga = None
if partenariats:
for libelle in partenariats:
if libelle == "Animation Parc" or libelle == "Partenaire du Parc":
orga, _ = TouristicEventOrganizer.objects.get_or_create(label=libelle)
return orga
def filter_begin_date(self, src, val):
begin_date_to_keep = None
end_date_to_keep = None
if val:
if len(val) == 1:
val = val[0]
begin_date = val.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = val.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
time = val.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
begin_date_to_keep = parsed_begin_date
else:
for subval in val:
begin_date = subval.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = subval.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
if parsed_end_date.date() > date.today(): # Keep these ones as next event
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
time = subval.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
break
if not begin_date_to_keep:
raise ValueImportError("Empty begin_date")
return begin_date_to_keep
def filter_end_time(self, src, val):
end_time_to_keep = None
end_date_to_keep = None
if val:
if len(val) == 1:
val = val[0]
begin_date = val.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = val.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
time = val.get('horaireFermeture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
end_time_to_keep = parsed_time
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
else:
for subval in val:
begin_date = subval.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = subval.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
if parsed_end_date.date() > date.today(): # Keep these ones as next event
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
time = subval.get('horaireFermeture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
end_time_to_keep = parsed_time
break
return end_time_to_keep
def filter_end_date(self, src, val):
begin_date_to_keep = None
end_date_to_keep = None
if val:
if len(val) == 1:
val = val[0]
begin_date = val.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = val.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
time = val.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
else:
for subval in val:
begin_date = subval.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = subval.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
if parsed_end_date.date() > date.today(): # Keep these ones as next event
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
time = subval.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
break
return end_date_to_keep
def filter_start_time(self, src, val):
start_time_to_keep = None
end_date_to_keep = None
if val:
if len(val) == 1:
val = val[0]
begin_date = val.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = val.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
time = val.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
start_time_to_keep = parsed_time
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
else:
for subval in val:
begin_date = subval.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = subval.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
if parsed_end_date.date() > date.today(): # Keep these ones as next event
begin_date_to_keep = parsed_begin_date
end_date_to_keep = parsed_end_date
time = subval.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
start_time_to_keep = parsed_time
break
return start_time_to_keep
def filter_description(self, src, val):
desc, ouverture, label, themes, ouverture_parsed = val
desc_txt=""
if desc:
desc_txt = '
'.join(desc.splitlines())
next_date = ""
meeting_time_txt = ""
end_date_txt = ""
end_time_txt = ""
begin_date_txt = ""
ouverture_txt = ""
label_txt = ""
themes_txt = ""
if ouverture:
ouverture_txt = "
Ouverture:
" + "
".join(ouverture.splitlines()) + "
"
if label:
label_txt = "
Label Tourisme et Handicap:
" + "
Oui
"
if themes:
themes_txt = "
Thèmes:
" + "
".join(themes) + "
"
if ouverture_parsed:
for subval in ouverture_parsed:
begin_date = subval.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
end_date = subval.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
if parsed_end_date.date() > date.today():
continue
begin_date_txt = f"{parsed_begin_date.day}/{parsed_begin_date.month}/{parsed_begin_date.year}"
end_date = subval.get('dateFin', None)
if end_date:
parsed_end_date = datetime.strptime(end_date, "%Y-%m-%d")
if parsed_end_date.date() > date.today():
continue
end_date_txt = f"{parsed_end_date.day}/{parsed_end_date.month}/{parsed_end_date.year}"
begin_date = subval.get('dateDebut', None)
if begin_date:
parsed_begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
if parsed_begin_date.date() > date.today():
continue
time = subval.get('horaireOuverture', None)
if time:
parsed_time = datetime.strptime(time, "%H:%M:%S").time()
meeting_time_txt = str(parsed_time)
end_time = subval.get('horaireFermeture', None)
if end_time:
parsed_end_time = datetime.strptime(end_time, "%H:%M:%S").time()
end_time_txt = str(parsed_time)
if meeting_time_txt and begin_date_txt and end_date_txt:
next_date = f"Prochain évènement du {begin_date_txt} à {meeting_time_txt} au {end_date_txt}"
if end_time_txt:
next_date = next_date + f" à {end_time_txt}"
elif begin_date_txt and end_date_txt:
next_date = f"Prochain évènement du {begin_date_txt} au {end_date_txt}"
elif meeting_time_txt and begin_date_txt:
next_date = f"Prochain évènement le {begin_date_txt} à {meeting_time_txt}"
lines = [line for line in [desc_txt, ouverture_txt, next_date, themes_txt, label_txt] if line]
return '
'.join(lines)
```
OK merci pour ces exemples intéressants. On n'utilise pas actuellement les imports d’événements depuis Apidae, donc je n'ai pas de retour sur le fonctionnement.
Mais OK si ça fonctionne pour vous. Peut-être mentionner la possibilité d'importer des événements Apidae dans Geotrek dans la doc d'import avec le premier exemple et un lien vers ce ticket pour l'exemple plus spécifique ?
Salut,
On a rencontré un soucis au PNR des Volcans d'Auvergne avec l'import via parser des Touristic Events depuis Apidae. Le parser en question est construit sur le même modèle que le 2ème exemple custom que tu cite dans ton dernier message de cette issue @babastienne.
L'exécution du parser PNRXXAgendaParser
lèvait l'erreur suivante :
File "/opt/geotrek-admin/lib/python3.8/site-packages/django/db/models/options.py", line 610, in get_field
raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))
django.core.exceptions.FieldDoesNotExist: TouristicEvent has no field named 'organizer'
J'ai déduis que cela semble lié à l'implémentation d'une relation many to many sur les organizers des touristic-events depuis la v2.102.2 de GTA (#3587)
J'ai donc simplement commenté la ligne 'organizer': ('criteresInternes.*.libelle'),
dans fields
ce qui semble solutionner le problème.
J'avais tout de même une autre erreur de levée :
UnboundLocalError : local variable 'parsed_time' referenced before assignment
J'ai donc aussi adapté les lignes suivantes :
if end_time:
parsed_end_time = datetime.strptime(end_time, "%H:%M:%S").time()
end_time_txt = str(parsed_time)
En remplaçant str(parsed_time)
par str(parsed_end_time)
, l'import va désormais au bout sans erreur.
Les organizers et les relations aux events ainsi que les dates de fin me semblent correctement intégrées dans la BDD et remontent bien dans GTRv3 (sans mails d'erreur de l'API) mais pourriez-vous me confirmer que ces adaptations sont pertinentes et suffisantes ?
Merci d'avance !
Salut @xavyeah39
Le champ organizer
a en effet été passé en Many2Many et par la même occasion renommé en organizers
.
Tes modifications semblent tout à fait correctes, pour moi c'est ok.
Merci pour le commentaire qui servira sûrement pour d'autres personnes. :pray:
@xavyeah39 essaye dans ton parser d'ajouter la ligne suivante ?
m2m_fields = {
'themes': 'informationsFeteEtManifestation.themes.*.libelleFr',
'organizers': ('criteresInternes.*.libelle',),
}
Si tu relances est-ce que ça permet d'avoir des valeurs différentes pour le champ 'organisateur' ?
Merci @babastienne.
Pour mémo du problème rencontré au PNR des Volcans d'Auvergne :
En effet pour corriger l’exécution du parser PNRXXAgendaParser suite au passage du champ
organizer
en many2many, j'ai commenté la ligne'organizer': ('criteresInternes.*.libelle')
, dansfields
. Mais du coup, le peuplement des organisateurs avec ce nouveaux fonctionnement n'exploite plus le champcriteresInternes
renseigné dans Apidae qui nous permettait de simplifier les organisateurs selon 2 valeurs : "Partenaires du Parc" et "Animation Parc".Il faudrait donc adapter le parser pour permettre cela dans la relation m2m implémentée en peuplant non plus le champ "organizer" mais les tables
tourism_touristiceventorganizer
ettourism_touristicevent_organizers
".
J'ai ajouté la ligne m2m_fields
que tu indiques mais j'ai l'erreur suivante désormais :
===== Run PNRVAAgendaParser parser Read env configuration from /opt/geotrek-admin/lib/python3.8/site-packages/geotrek/settings/env_prod.py Read custom configuration from /opt/geotrek-admin/var/conf/custom.py 0001: 4640813 (00%) 0002: 4640814 (00%) 0003: 4640815 (00%) Traceback (most recent call last): File "/usr/sbin/geotrek", line 20, in
execute_from_command_line(sys.argv) File "/opt/geotrek-admin/lib/python3.8/site-packages/django/core/management/init.py", line 442, in execute_from_command_line utility.execute() File "/opt/geotrek-admin/lib/python3.8/site-packages/django/core/management/init.py", line 436, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/opt/geotrek-admin/lib/python3.8/site-packages/django/core/management/base.py", line 412, in run_from_argv self.execute(*args, *cmd_options) File "/opt/geotrek-admin/lib/python3.8/site-packages/django/core/management/base.py", line 458, in execute output = self.handle(args, options) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/management/commands/import.py", line 55, in handle parser.parse(options['filename'], limit=limit) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 545, in parse self.parse_row(row) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 407, in parse_row self.parse_obj(row, operation) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 342, in parse_obj update_fields += self.parse_fields(row, self.m2m_fields) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 318, in parse_fields self.parse_field(row, dst, src, updated, non_field) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 307, in parse_field modified = self.parse_real_field(dst, src, val) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 230, in parse_real_field val = self.apply_filter(dst, src, val) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 194, in apply_filter val = self.filter_m2m(src, val, to, natural_key, kwargs) File "/opt/geotrek-admin/lib/python3.8/site-packages/geotrek/common/parsers.py", line 484, in filter_m2m self.addwarning(("{model} '{val}' did not exist in Geotrek-Admin and was automatically created").format(model=model._meta.verbose_name.title(), val=subval)) TypeError: str returned non-string (type list)
Merci !
Bonjour,
Nous essayons d'importer par héritage de la class TouristicEventApidaeParser, des animations d'Apidae.
Création de la class AnimationParser dans bulkimport/parser.py :
A l'execution de l'import
Nous obtenons l'erreur suivante :
En effet la clé 'objetsTouristiques' dont fait référence la méthode ApidaeParser.getItems() n'existe dans aucune des classes héritées.