Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event Apidae Import error #1956

Open
Cynthia-Borot-PNE opened this issue May 17, 2019 · 6 comments
Open

Event Apidae Import error #1956

Cynthia-Borot-PNE opened this issue May 17, 2019 · 6 comments

Comments

@Cynthia-Borot-PNE
Copy link

Cynthia-Borot-PNE commented May 17, 2019

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 :

class AnimationParser(TouristicEventApidaeParser):
    label = u"Animations PNE"
    api_key = 'xxxxxx'
    project_id = xxx
    selection_id = xxx
    source = ['Apidae']
    portal = ['Rando Ecrins']
    delete = True

    def parse_obj(self, row, operation):
        if not self.obj.pk:
            self.obj.published = True
        super(AnimationParser, self).parse_obj(row, operation)

A l'execution de l'import

./bin/django import bulkimport.parsers.AnimationParser

Nous obtenons l'erreur suivante :

[...]
File "/home/geotrek/Geotrek-admin-2.27.1/geotrek/common/management/commands/import.py", line 41, in handle
parser.parse(options['shapefile'], limit=limit)
File "/home/geotrek/Geotrek-admin-2.27.1/geotrek/common/parsers.py", line 443, in parse
for i, row in enumerate(self.next_row()):
File "/home/geotrek/Geotrek-admin-2.27.1/geotrek/tourism/parsers.py", line 83, in next_row
for row in self.items:
File "/home/geotrek/Geotrek-admin-2.27.1/geotrek/tourism/parsers.py", line 65, in items
return self.root['objetsTouristiques']
KeyError: 'objetsTouristiques'

En effet la clé 'objetsTouristiques' dont fait référence la méthode ApidaeParser.getItems() n'existe dans aucune des classes héritées.

@GeotrekCE GeotrekCE deleted a comment from Cynthia-Borot-PNE Jun 4, 2019
@babastienne
Copy link
Member

babastienne commented Jan 4, 2024

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 :

Code source

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 :

Code source

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"<a href=\"{web}\">{web}</a>"
        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 '<br>'.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 = '<br>'.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 = "<br><b>Ouverture:</b><br>" + "<br>".join(ouverture.splitlines()) + "<br>"
        if label:
            label_txt = "<br><b>Label Tourisme et Handicap:</b><br>" + "<br> Oui <br>"
        if themes:
            themes_txt = "<br><b>Thèmes:</b><br>" + "<br>".join(themes) + "<br>"

        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 '<br>'.join(lines)

@camillemonchicourt
Copy link
Member

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 ?

@xavyeah39
Copy link

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 !

@babastienne
Copy link
Member

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. 🙏

@babastienne
Copy link
Member

@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' ?

@xavyeah39
Copy link

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'), dans fields. Mais du coup, le peuplement des organisateurs avec ce nouveaux fonctionnement n'exploite plus le champ criteresInternes 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 et tourism_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.add_warning(_("{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 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants