Bonjour à tous,
Egalement en charge de ce projet avec Sylvain au Parc Normandie-Maine, nous avons donc finalement fait le choix de solliciter Makina Corpus ( très pro) pour la création du fichier Parsers.py pour l'intégration des flux touristiques Tourinsoft.
Nous aurions souhaité que soit faite en même temps la mise à jour de la documentation mais cela me parait difficile au regard du fichier qui nous a été fourni et les quelques modification que j'ai effectuées.
Je m'explique...
Sur notre territoire, les cdt61 et 72 nous fournissent des flux, mais pour une même catégorie à créer (hébergement par exemple), le (ou les champ) champ(s) à identifier et à utiliser sont parfois différents.
Egalement certaines valeurs d'un même champ peuvent être différente pour une même catégorie :
A titre d'exemple pour la catégorie campings il a fallu pour le flux du cdt 72 sélectionner la valeur "Hôtellerie de plein air (camping)" dans le champ "ObjectTypeName", alors que pour pour le cdT 61 il faut utiliser les valeurs : "Terrain de camping", "Aire Naturelle", "Camping rural" et "Camping" dans le Champ "Classification".
L'identification des champs et des valeurs n'a pas été très simple, je me suis appuyé sur la documentation en ligne de Tourinsoft (
http://api-doc.tourinsoft.com/#/syndication-fonctionnelle), ainsi que les adresse des flux au format json.
Si vous souhaitez jeter un œil au résultat , c'est à cette adresse :
http://149.202.129.97 Les catégories créées par l'import Tourinsoft sont : loisirs et découvertes, Hébergement et restaurant
Il s'agit de notre portail provisoire de travail, merci de ne pas diffuser l'adresse
Donc,souhaitant néanmoins que communauté Geotrek profite du travail réalisé, je vous joins notre fichier parsers.py qui devra être adapté à votre configuration (champs, valeurs,...)
# -*- encoding: utf-8 -*-import requestsfrom django.conf import settingsfrom django.contrib.gis.geos import Pointfrom django.utils.translation import ugettext as _from geotrek.common.parsers import TourInSoftParser, RowImportError, GlobalImportErrorfrom geotrek.tourism.models import TouristicContent, TouristicContentTypeclass NormandieMainParser(TourInSoftParser): delete_attachments = True url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' model = TouristicContent eid = 'eid' constant_fields = { 'category': u"Test", 'published': True, } natural_keys = { 'category': 'label', 'type1': 'label', } non_fields = { 'attachments': 'Photos', } field_options = { 'name': { 'required': True, }, 'geom': { 'required': True, }, 'category': { 'create': True, }, 'type1': { 'create': True, }, } def filter_attachments(self, src, val): if not val: return [] return [subval.split('|') for subval in val.split('#') if subval.split('|')[0]] def filter_contact(self, src, val): (Adresse1, Adresse1Suite, Adresse2, Adresse3, CodePostal, Commune, Cedex) = val lines = [line for line in [ ' '.join([part for part in [Adresse1, Adresse1Suite] if part]), Adresse2, Adresse3, ' '.join([part for part in [CodePostal, Commune, Cedex] if part]), ] if line] return '<br>'.join(lines) def filter_geom(self, src, val): lat, lng = val if lng == '' or lat == '': raise RowImportError(u"Required value for fields 'GmapLatitude' and 'GmapLongitude'.") geom = Point(float(lng), float(lat), srid=4326) # WGS84 geom.transform(settings.SRID) return geomclass Normandie61MainParser(NormandieMainParser): fields = { 'eid': 'SyndicObjectID', 'name': 'SyndicObjectName', 'description_teaser': 'DescriptifSynthetique', 'description': 'DescriptionCommerciale', 'contact': ('Adresse1', 'Adresse1Suite', 'Adresse2', 'Adresse3', 'CP', 'Commune', 'Cedex'), 'email': 'CommMail', 'website': 'CommWeb', 'geom': ('GmapLatitude', 'GmapLongitude'), }class NormandieAccomodation61Parser(Normandie61MainParser): constant_fields = { 'category': u"Hébergement", 'published': True, } def filter_type1(self, src, val): type1 = None if val == u"Meublés, locations, gîtes": type1 = u"Gîtes" if val == u"Hébergement groupe": type1 = u"Hébergements collectifs" if val == u"Chambre d'hôtes": type1 = u"Chambres d'hôtes" if val == u"Insolite": type1 = u"Hébergements insolites" if val in [u"Terrain de camping", u"Aire Naturelle", u"Camping rural", u"Parc résidentiel de loisir", u"Camping à la ferme", u"Camping"]: type1 = u"Camping" if val in [u"Hôtel", u"Hôtel-Restaurant"]: type1 = u"Hôtels" if val == u"Aire de camping-car": type1 = u"Camping-car" if type1: type1_val, created = (TouristicContentType.objects.get_or_create(label=type1, in_list=1, category=self.obj.category)) if created: self.add_warning(_(u"type1 '{val}' did not exist in Geotrek-Admin and was automatically created").format(val=type1_val)) return [type1_val] raise RowImportError(u"No type1 {type1}".format(type1=type1))class Normandie72MainParser(NormandieMainParser): base_url = 'http://cdt72.media.tourinsoft.eu/upload/' fields = { 'eid': 'SyndicObjectID', 'name': 'SyndicObjectName', 'description_teaser': 'DescriptifSynthetique', 'description': 'Descriptif', 'contact': ('Adresse1', 'Adresse1Suite', 'Adresse2', 'Adresse3', 'CodePostal', 'Commune', 'Cedex'), 'email': 'CommMail', 'website': 'CommWeb', 'geom': ('GmapLatitude', 'GmapLongitude'), } @property def items(self): return self.root['value'] def next_row(self): skip = 0 while True: params = { '$format': 'json', '$inlinecount': 'allpages', '$top': 1000, '$skip': skip, } response = requests.get(self.url, params=params) if response.status_code != 200: raise GlobalImportError(_(u"Failed to download {url}. HTTP status code {status_code}").format(url=self.url, status_code=response.status_code)) self.root = response.json() self.nb = int(self.root['odata.count']) for row in self.items: yield {self.normalize_field_name(src): val for src, val in row.iteritems()} skip += 1000 if skip >= self.nb: return def filter_attachments(self, src, val): if not val: return [] return [subval.split('||') for subval in val.split('##') if subval.split('||')[0]]class NormandieRestaurants61Parser(Normandie61MainParser): label = u"Restaurants - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' constant_fields = { 'category': u"Restaurants", 'published': True, }class NormandieRestaurants72Parser(Normandie72MainParser): label = u"Restaurants - 72" url = 'http://wcf.tourinsoft.com/Syndication/3.0/cdt72/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' constant_fields = { 'category': u"Restaurants", 'published': True, }class NormandieAccomodationCamping61Parser(NormandieAccomodation61Parser): label = u"Hébergements - Camping - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_fields = { 'type1': ('Classification'), }class NormandieAccomodationRent61Parser(NormandieAccomodation61Parser): label = u"Hébergements - Location - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_fields = { 'type1': ('Classification'), }class NormandieAccomodationHotel61Parser(NormandieAccomodation61Parser): label = u"Hébergements - Hôtel - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_fields = { 'type1': ('Classification'), }class NormandieAccomodationVillage61Parser(Normandie61MainParser): constant_fields = { 'category': u"Hébergement", 'published': True, } label = u"Hébergements - Gîte - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_constant_fields = { 'type1': ["Gîtes"], }class NormandieAccomodation72Parser(Normandie72MainParser): constant_fields = { 'category': u"Hébergement", 'published': True, } def filter_type1(self, src, val): object_type, object_subtype = val type1 = None if object_type == u"Hôtellerie de plein air (camping)": type1 = u"Camping" if object_type == u"Aire de Camping Car": type1 = u"Camping-car" if object_type == u"Hébergements locatifs (meublés et chambres d'hôtes)": if object_subtype in [u"Meublés", u"Locatif sur camping", u"Village locatif"]: type1 = u"Gîtes" if object_subtype == u"Chambres d'hôtes": type1 = u"Chambres d'hôtes" if object_subtype == u"Hébergement insolite": type1 = u"Hébergements insolites" if object_type == u"Hébergements Collectifs": type1 = u"Hébergements collectifs" if object_type == u"Hôtellerie": if object_subtype in [u"Hôtel", u"Hôtel - Restaurant"]: type1 = u"Hôtels" if type1: type1_val, created = (TouristicContentType.objects.get_or_create(label=type1, in_list=1, category=self.obj.category)) if created: self.add_warning(_(u"type1 '{val}' did not exist in Geotrek-Admin and was automatically created").format(val=type1_val)) return [type1_val] self.add_warning(_(u"No parsing defined for type1 '{val}' ").format(val=type1)) return [] class NormandieAccomodationRent72Parser(NormandieAccomodation72Parser): label = u"Hébergements - 72" url = 'http://wcf.tourinsoft.com/Syndication/3.0/cdt72/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_fields = { 'type1': ('ObjectTypeName', 'Type'), }class NormandieLeisureSportAndCulture61Parser(Normandie61MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Sport et culture - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_fields = { 'type1': ('ClassificationActivitesCulturelles', 'ClassificationActivitesSportives'), } def filter_type1(self, src, val): culture_val, sport_val = val print(culture_val, sport_val) type1 = None if culture_val is not None: type1 = u"Activités culturelles" if sport_val is not None: type1 = u"Activités sportives" if type1: type1_val, created = (TouristicContentType.objects.get_or_create(label=type1, in_list=1, category=self.obj.category)) if created: self.add_warning(_(u"type1 '{val}' did not exist in Geotrek-Admin and was automatically created").format(val=type1_val)) return [type1_val] raise RowImportError(u"No type1 {type1}".format(type1=type1))class NormandieLeisureNature61Parser(Normandie61MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Patrimoine naturel - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_constant_fields = { 'type1': ["Patrimoine naturel"], }class NormandieLeisureFood61Parser(Normandie61MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Dégustation - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_constant_fields = { 'type1': ["Dégustation"], }class NormandieLeisureMuseum61Parser(Normandie61MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Musées et patrimoine culturel - 61" url = 'http://wcf.tourinsoft.com/Syndication/cdt61/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_constant_fields = { 'type1': ["Musées - Patrimoine culturel"], }class NormandieLeisureCulture72Parser(Normandie72MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Activités culturelles - 72" url = 'http://wcf.tourinsoft.com/Syndication/3.0/cdt72/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_constant_fields = { 'type1': ["Activités culturelles"], }class NormandieLeisureSport72Parser(Normandie72MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Activités sport et nature - 72" url = 'http://wcf.tourinsoft.com/Syndication/3.0/cdt72/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_constant_fields = { 'type1': ["Activités sportives"], }class NormandieLeisureDiscovery72Parser(Normandie72MainParser): constant_fields = { 'category': u"Loisirs et découvertes", 'published': True, } label = u"Loisirs et découvertes - Découverte - 72" url = 'http://wcf.tourinsoft.com/Syndication/3.0/cdt72/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/Objects' m2m_fields = { 'type1': ('ObjectTypeName'), } def filter_type1(self, src, val): type1 = None if val == u"Patrimoine naturel": type1 = u"Patrimoine naturel" if val == u"D\u00e9gustations (tous produits)": type1 = u"Dégustation" if val == u"Patrimoine culturel": type1 = u"Musées - Patrimoine culturel" if type1: type1_val, created = (TouristicContentType.objects.get_or_create(label=type1, in_list=1, category=self.obj.category)) if created: self.add_warning(_(u"type1 '{val}' did not exist in Geotrek-Admin and was automatically created").format(val=type1_val)) return [type1_val] raise RowImportError(u"No type1 {type1}".format(type1=type1)) Voilà, espérant avoir été utile, passez un bon été malgré les événements malheureux.
Sébastien