def wrapper(*args, **kwargs): root_dir = os.path.dirname(os.path.abspath(__file__)) info = inspect.getframeinfo(inspect.stack()[1][0]) logger.debug('Run {} (called by file "{}", line {}, in {})'.format( f.__qualname__, info.filename.replace(root_dir, '.'), info.lineno, info.function)) try: return f(*args, **kwargs) except Exception as e: logger.exception(e) if isinstance(e, timeout_decorator.TimeoutError): raise CkanTimeoutError if self.is_ignored(e): return f(*args, **kwargs) if e.__class__.__qualname__ == 'ValidationError': try: err = e.error_dict del err['__type'] msg = ', '.join([ '"{0}" {1}'.format( k, isinstance(v, list) and ', '.join(v) or v) for k, v in err.items() ]) except Exception as e: msg = e.__str__() raise ValidationError(msg) if e.__str__() in ('Indisponible', 'Not Found'): raise CkanNotFoundError raise CkanSyncingError(e.__str__())
def wrapper(*args, **kwargs): root_dir = os.path.dirname(os.path.abspath(__file__)) info = inspect.getframeinfo(inspect.stack()[1][0]) logger.debug( 'Run {} (called by file "{}", line {}, in {})'.format( f.__qualname__, info.filename.replace(root_dir, '.'), info.lineno, info.function)) try: return f(*args, **kwargs) except Exception as e: if self.is_ignored(e): return f(*args, **kwargs) if e.__class__.__qualname__ == 'HTTPError': if e.response.status_code == 404: raise MRANotFoundError() if e.response.status_code == 409: raise MRAConflictError() if e.response.status_code == 500: raise MRACriticalError() if isinstance(e, timeout_decorator.TimeoutError): raise MRATimeoutError() if self.is_ignored(e): return f(*args, **kwargs) raise MRASyncingError(e.__str__())
def wrapper(*args, **kwargs): root_dir = os.path.dirname(os.path.abspath(__file__)) info = inspect.getframeinfo(inspect.stack()[1][0]) logger.debug( 'Run {} (called by file "{}", line {}, in {})'.format( f.__qualname__, info.filename.replace(root_dir, '.'), info.lineno, info.function)) try: return f(*args, **kwargs) except Exception as e: logger.exception(e) if isinstance(e, timeout_decorator.TimeoutError): raise DcatTimeoutError if self.is_ignored(e): return f(*args, **kwargs) raise DcatError("Une erreur critique est survenue lors de l'appel au DCAT distant.")
def save(self, *args, current_user=None, synchronize=False, file_extras=None, **kwargs): # Version précédante de la ressource (avant modification) previous, created = self.pk \ and (Resource.objects.get(pk=self.pk), False) or (None, True) if previous: # crs est immuable sauf si le jeu de données change (Cf. plus bas) self.crs = previous.crs # Quelques valeur par défaut à la création de l'instance if created or not ( # Ou si l'éditeur n'est pas partenaire du CRIGE current_user and current_user.profile.crige_membership): # Mais seulement s'il s'agit de données SIG, sauf # qu'on ne le sait pas encore... self.geo_restriction = False self.ogc_services = True self.extractable = True # La restriction au territoire de compétence désactive toujours les services OGC if self.geo_restriction: self.ogc_services = False # Quelques contrôles sur les fichiers de données téléversée ou à télécharger filename = False content_type = None file_must_be_deleted = False # permet d'indiquer si les fichiers doivent être supprimés à la fin de la chaine de traitement publish_raw_resource = True # permet d'indiquer si les ressources brutes sont publiées dans CKAN if self.ftp_file: filename = self.ftp_file.file.name # Si la taille de fichier dépasse la limite autorisée, # on traite les données en fonction du type détecté if self.ftp_file.size > DOWNLOAD_SIZE_LIMIT: extension = self.format_type.extension.lower() if self.format_type.is_gis_format: try: gdalogr_obj = get_gdalogr_object(filename, extension) except NotDataGISError: # On essaye de traiter le jeux de données normalement, même si ça peut être long. pass else: if gdalogr_obj.__class__.__name__ == 'GdalOpener': s0 = str(self.ckan_id) s1, s2, s3 = s0[:3], s0[3:6], s0[6:] dir = os.path.join(CKAN_STORAGE_PATH, s1, s2) os.makedirs(dir, mode=0o777, exist_ok=True) shutil.copyfile(filename, os.path.join(dir, s3)) src = os.path.join(dir, s3) dst = os.path.join(dir, filename.split('/')[-1]) try: os.symlink(src, dst) except FileNotFoundError as e: logger.error(e) else: logger.debug( 'Created a symbolic link {dst} pointing to {src}.' .format(dst=dst, src=src)) # if gdalogr_obj.__class__.__name__ == 'OgrOpener': # On ne publie que le service OGC dans CKAN publish_raw_resource = False elif (self.up_file and file_extras): # GDAL/OGR ne semble pas prendre de fichier en mémoire.. # ..à vérifier mais si c'est possible comment indiquer le vsi en préfixe du filename ? super().save(*args, **kwargs) kwargs['force_insert'] = False filename = self.up_file.file.name file_must_be_deleted = True elif self.dl_url: try: directory, filename, content_type = download( self.dl_url, settings.MEDIA_ROOT, max_size=DOWNLOAD_SIZE_LIMIT) except SizeLimitExceededError as e: l = len(str(e.max_size)) if l > 6: m = '{0} mo'.format(Decimal(int(e.max_size) / 1024 / 1024)) elif l > 3: m = '{0} ko'.format(Decimal(int(e.max_size) / 1024)) else: m = '{0} octets'.format(int(e.max_size)) raise ValidationError(('La taille du fichier dépasse ' 'la limite autorisée : {0}.').format(m), code='dl_url') except Exception as e: if e.__class__.__name__ == 'HTTPError': if e.response.status_code == 404: msg = ('La ressource distante ne semble pas exister. ' "Assurez-vous que l'URL soit correcte.") if e.response.status_code == 403: msg = ("Vous n'avez pas l'autorisation pour " 'accéder à la ressource.') if e.response.status_code == 401: msg = ('Une authentification est nécessaire ' 'pour accéder à la ressource.') else: msg = 'Le téléchargement du fichier a échoué.' raise ValidationError(msg, code='dl_url') file_must_be_deleted = True # Synchronisation avec CKAN # ========================= # La synchronisation doit s'effectuer avant la publication des # éventuelles couches de données SIG car dans le cas des données # de type « raster », nous utilisons le filestore de CKAN. if synchronize and publish_raw_resource: self.synchronize(content_type=content_type, file_extras=file_extras, filename=filename, with_user=current_user) elif synchronize and not publish_raw_resource: url = reduce(urljoin, [ settings.CKAN_URL, 'dataset/', str(self.dataset.ckan_id) + '/', 'resource/', str(self.ckan_id) + '/', 'download/', Path(self.ftp_file.name).name ]) self.synchronize(url=url, with_user=current_user) # Détection des données SIG # ========================= if filename: # On vérifie s'il s'agit de données SIG, uniquement pour # les extensions de fichier autorisées.. extension = self.format_type.extension.lower() if self.format_type.is_gis_format: # Si c'est le cas, on monte les données dans la base PostGIS dédiée # et on déclare la couche au service OGC:WxS de l'organisation. # Mais d'abord, on vérifie si la ressource contient # déjà des « Layers », auquel cas il faudra vérifier si # la table de données a changée. existing_layers = {} if not created: existing_layers = dict( (re.sub('^(\w+)_[a-z0-9]{7}$', '\g<1>', layer.name), layer.name) for layer in self.get_layers()) try: # C'est carrément moche mais c'est pour aller vite. # Il faudrait factoriser tout ce bazar et créer # un décorateur pour gérer le rool-back sur CKAN. try: gdalogr_obj = get_gdalogr_object(filename, extension) except NotDataGISError: tables = [] pass else: try: self.format_type = ResourceFormats.objects.get( extension=extension, ckan_format=gdalogr_obj.format) # except ResourceFormats.MultipleObjectsReturned: # pass except Exception: pass # ========================== # Jeu de données vectorielle # ========================== if gdalogr_obj.__class__.__name__ == 'OgrOpener': # On convertit les données vers PostGIS try: tables = ogr2postgis( gdalogr_obj, update=existing_layers, epsg=self.crs and self.crs.auth_code or None, encoding=self.encoding) except NotOGRError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( "Le fichier reçu n'est pas reconnu " 'comme étant un jeu de données SIG correct.' ) raise ValidationError(msg, code='__all__') except DataDecodingError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( 'Impossible de décoder correctement les ' "données. Merci d'indiquer l'encodage " 'ci-dessous.') raise ValidationError(msg, code='encoding') except WrongDataError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( 'Votre ressource contient des données SIG que ' 'nous ne parvenons pas à lire correctement. ' 'Un ou plusieurs objets sont erronés.') raise ValidationError(msg) except NotFoundSrsError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( 'Votre ressource semble contenir des données SIG ' 'mais nous ne parvenons pas à détecter le système ' 'de coordonnées. Merci de sélectionner le code du ' 'CRS dans la liste ci-dessous.') raise ValidationError(msg, code='crs') except NotSupportedSrsError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( 'Votre ressource semble contenir des données SIG ' 'mais le système de coordonnées de celles-ci ' "n'est pas supporté par l'application.") raise ValidationError(msg, code='__all__') except ExceedsMaximumLayerNumberFixedError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) raise ValidationError(e.__str__(), code='__all__') else: # Avant de créer des relations, l'objet doit exister if created: # S'il s'agit d'une création, alors on sauve l'objet. super().save(*args, **kwargs) kwargs['force_insert'] = False # Ensuite, pour tous les jeux de données SIG trouvés, # on crée le service ows à travers la création de `Layer` try: Layer = apps.get_model( app_label='idgo_admin', model_name='Layer') for table in tables: try: Layer.objects.get(name=table['id'], resource=self) except Layer.DoesNotExist: save_opts = { 'synchronize': synchronize } Layer.vector.create( bbox=table['bbox'], name=table['id'], resource=self, save_opts=save_opts) except Exception as e: logger.error(e) file_must_be_deleted and remove_file( filename) for table in tables: drop_table(table['id']) raise e # ========================== # Jeu de données matricielle # ========================== if gdalogr_obj.__class__.__name__ == 'GdalOpener': coverage = gdalogr_obj.get_coverage() try: tables = [ gdalinfo( coverage, update=existing_layers, epsg=self.crs and self.crs.auth_code or None) ] except NotFoundSrsError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( 'Votre ressource semble contenir des données SIG ' 'mais nous ne parvenons pas à détecter le système ' 'de coordonnées. Merci de sélectionner le code du ' 'CRS dans la liste ci-dessous.') raise ValidationError(msg, code='crs') except NotSupportedSrsError as e: logger.warning(e) file_must_be_deleted and remove_file(filename) msg = ( 'Votre ressource semble contenir des données SIG ' 'mais le système de coordonnées de celles-ci ' "n'est pas supporté par l'application.") raise ValidationError(msg, code='__all__') else: if created: # S'il s'agit d'une création, alors on sauve l'objet. super().save(*args, **kwargs) kwargs['force_insert'] = False # Super Crado Code s0 = str(self.ckan_id) s1, s2, s3 = s0[:3], s0[3:6], s0[6:] dir = os.path.join(CKAN_STORAGE_PATH, s1, s2) src = os.path.join(dir, s3) dst = os.path.join(dir, filename.split('/')[-1]) try: os.symlink(src, dst) except FileExistsError as e: logger.warning(e) except FileNotFoundError as e: logger.error(e) else: logger.debug( 'Created a symbolic link {dst} pointing to {src}.' .format(dst=dst, src=src)) try: Layer = apps.get_model(app_label='idgo_admin', model_name='Layer') for table in tables: try: Layer.objects.get(name=table['id'], resource=self) except Layer.DoesNotExist: Layer.raster.create(bbox=table['bbox'], name=table['id'], resource=self) except Exception as e: logger.error(e) file_must_be_deleted and remove_file(filename) raise e except Exception as e: if created: if current_user: username = current_user.username apikey = CkanHandler.get_user(username)['apikey'] with CkanUserHandler(apikey) as ckan: ckan.delete_resource(str(self.ckan_id)) else: CkanHandler.delete_resource(str(self.ckan_id)) for layer in self.get_layers(): layer.delete(current_user=current_user) # Puis on « raise » l'erreur raise e # On met à jour les champs de la ressource SupportedCrs = apps.get_model(app_label='idgo_admin', model_name='SupportedCrs') crs = [ SupportedCrs.objects.get(auth_name='EPSG', auth_code=table['epsg']) for table in tables ] # On prend la première valeur (c'est moche) self.crs = crs and crs[0] or None # Si les données changent.. if existing_layers and \ previous.get_layers() != self.get_layers(): # on supprime les anciens `layers`.. for layer in previous.get_layers(): layer.delete() #### if self.get_layers(): extent = self.get_layers().aggregate( models.Extent('bbox')).get('bbox__extent') if extent: xmin, ymin = extent[0], extent[1] xmax, ymax = extent[2], extent[3] setattr(self, 'bbox', bounds_to_wkt(xmin, ymin, xmax, ymax)) else: # Si la ressource n'est pas de type SIG, on passe les trois arguments # qui concernent exclusivement ces dernières à « False ». self.geo_restriction = False self.ogc_services = False self.extractable = False super().save(*args, **kwargs) # Puis dans tous les cas.. # on met à jour le statut des couches du service cartographique.. if not created: self.update_enable_layers_status() # on supprime les données téléversées ou téléchargées.. if file_must_be_deleted: remove_file(filename) # [Crado] on met à jour la ressource CKAN if synchronize: CkanHandler.update_resource(str(self.ckan_id), extracting_service=str( self.extractable)) for layer in self.get_layers(): layer.save(synchronize=synchronize) self.dataset.date_modification = timezone.now().date() self.dataset.save(current_user=None, synchronize=True, update_fields=['date_modification'])