From 2f125da310d557986a83891bd81148cc96ebce0d Mon Sep 17 00:00:00 2001 From: Chatewgne Date: Thu, 29 Feb 2024 17:27:30 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20[FEAT]=20Add=20=20to=20retrieve=20T?= =?UTF-8?q?reks=20and=20Touristic=20Contents=20(refs=20#3947)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.rst | 1 + geotrek/cirkwi/parsers.py | 161 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 geotrek/cirkwi/parsers.py diff --git a/docs/changelog.rst b/docs/changelog.rst index d3b01029ed..5c9d939072 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,7 @@ CHANGELOG - Add `include_externals` filter to Cirkwi trek exports, to allow excluding treks with an external id (eid) (#3947) - Tourism : add price to TouristicEvent model - ref #3587 +- Add `CirkwiParser` to retrieve Treks and Touristic Contents from Cirkwi (refs #3947) **Improvments** diff --git a/geotrek/cirkwi/parsers.py b/geotrek/cirkwi/parsers.py new file mode 100644 index 0000000000..6968da3042 --- /dev/null +++ b/geotrek/cirkwi/parsers.py @@ -0,0 +1,161 @@ +from django.conf import settings +from django.contrib.gis.geos import Point +from django.utils.translation import gettext as _ + +from geotrek.common.parsers import AttachmentParserMixin, XmlParser +from geotrek.tourism.models import TouristicContent, TouristicContentType1 +from geotrek.trekking.models import Trek +from geotrek.trekking.parsers import ApidaeTrekParser + + +class CirkwiParser(AttachmentParserMixin, XmlParser): + eid = 'eid' + field_options = { + "geom": {"required": True}, + "name": {"required": True}, + } + constant_fields = { + 'published': True, + } + + def get_part(self, dst, src, val): + if val is None: + return None + # Recursively extract XML attributes + if '@@' in src and src[:2] != '@@': + part, attrib = src.split('@@', 1) + return self.get_part(dst, f"@@{attrib}", val.find(part)) + # Extract XML attributes + elif src[:2] == '@@': + return val.attrib[src[2:]] + else: + # Return a list of XML elements + if src[-2:] == '/*': + return val.findall(src[:-2]) + # Return inner text if XML element exists + if val.find(src) is None: + return None + return val.find(src).text + + def filter_description(self, src, val): + descr, compl_title, compl_descr = val + if compl_title and compl_descr: + return f"{descr}\n\n\n{compl_title}: {compl_descr}" + return descr + + def filter_attachments(self, src, val): + attachments = [] + for attachment in val: + legend = attachment.find('legende') + if legend is not None: + legend = legend.text + url = attachment.find('url').text + author = attachment.find('credit') + if author is not None: + author = author.text + attachments.append([url, legend, author]) + return attachments + + +class CirkwiTrekParserFr(CirkwiParser): + default_language = 'fr' + model = Trek + url = "https://demo-admin.geotrek.fr/static/circkwi.xml" + results_path = 'circuit' + fields = { + "eid": "@@id_circuit", + "name": f"informations/information[@langue='{default_language}']/titre", + "description": (f"informations/information[@langue='{default_language}']/description", + f"informations/information[@langue='{default_language}']/informations_complementaires/information_complementaire/titre", + f"informations/information[@langue='{default_language}']/informations_complementaires/information_complementaire/description"), + "geom": "sens_circuit/fichier_trace@@url", + } + constant_fields = { + 'practice': "Pédestre" + } + non_fields = { + 'attachments': f"informations/information[@langue='{default_language}']/medias/images/image/*" + } + natural_keys = { + 'practice': 'name' + } + + def filter_geom(self, src, val): + response = self.request_or_retry(url=val) + return ApidaeTrekParser._get_geom_from_gpx(response.content) + +class CirkwiTouristicContentParserFr(CirkwiParser): + default_language = 'fr' + model = TouristicContent + url = "https://demo-admin.geotrek.fr/static/poicirkwi.xml" + results_path = 'poi' + fields = { + "eid": "@@id_poi", + "name": f"informations/information[@langue='{default_language}']/titre", + "description": (f"informations/information[@langue='{default_language}']/description", + f"informations/information[@langue='{default_language}']/informations_complementaires/information_complementaire/titre", + f"informations/information[@langue='{default_language}']/informations_complementaires/information_complementaire/description"), + "geom": ("adresse/position/lng", "adresse/position/lat"), + "practical_info": ("adresse/num", "adresse/rue", "adresse/cp", "adresse/ville", f"informations/information[@langue='{default_language}']/informations_complementaires/information_complementaire/*"), + "category": "categories/categorie/*", + } + m2m_fields = { + "type1": "categories/categorie/*", + } + non_fields = { + 'attachments': f"informations/information[@langue='{default_language}']/medias/images/image/*" + } + field_options = { + "geom": {"required": True}, + "name": {"required": True}, + 'category': {'create': True}, + 'type1': {'create': True}, + } + natural_keys = { + 'category': 'label', + 'type1': 'label', + } + + def filter_practical_info(self, src, val): + num, street, code, city, other_infos = val + infos = '' + if (num and street) or (code and city): + infos += 'Adresse :
' + if num and street: + infos += f"{num} {street}
" + if code and city: + infos += f"{code} {city}
" + for other_info in other_infos: + infos += f"
{other_info.find('titre').text} :
" + infos += f"{other_info.find('description').text}
" + if not infos: + return None + return infos + + def filter_category(self, src, val): + if val is None: + return None + name = val[0].attrib["nom"] + return self.apply_filter('category', src, name) + + def filter_type1(self, src, val): + if val is None or len(val) < 2: + return None + label = val[1].attrib["nom"] + if self.field_options["type1"]["create"]: + type1, __ = TouristicContentType1.objects.get_or_create(category=self.obj.category, label=label) + else: + try: + type1 = TouristicContentType1.objects.get(category=self.obj.category, label=label) + except TouristicContentType1.DoesNotExist: + self.add_warning( + _("Type 1 '{type}' does not exist for category '{cat}'. Please add it").format( + type=label, cat=self.obj.category.label)) + return [type1] + + def filter_geom(self, src, val): + lng, lat = val + geom = Point(float(lng), float(lat), srid=4326) # WGS84 + geom.transform(settings.SRID) + return geom +