diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 13862e16f..ddb903625 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -26,10 +26,6 @@ jobs: fail-fast: false matrix: include: - - name: "Debian 10" - python-version: "3.7" - postgres-version: 11 - postgis-version: 2.5 - name: "Debian 11" python-version: "3.9" postgres-version: 13 diff --git a/backend/gn_module_monitoring/command/cmd.py b/backend/gn_module_monitoring/command/cmd.py index 0d40a3df8..3b7015d17 100644 --- a/backend/gn_module_monitoring/command/cmd.py +++ b/backend/gn_module_monitoring/command/cmd.py @@ -4,7 +4,7 @@ from pathlib import Path from flask.cli import with_appcontext -from sqlalchemy.sql import text +from sqlalchemy.sql import text, select from geonature.utils.env import DB, BACKEND_DIR from geonature.core.gn_synthese.models import TSources @@ -15,7 +15,7 @@ from ..monitoring.models import TMonitoringModules from ..config.repositories import get_config from ..config.utils import json_from_file, monitoring_module_config_path -from ..modules.repositories import get_module, get_simple_module +from ..modules.repositories import get_simple_module from .utils import ( process_export_csv, @@ -145,15 +145,15 @@ def cmd_install_monitoring_module(module_code): if (module_config_dir_path / "synthese.sql").exists: click.secho("Execution du script synthese.sql") sql_script = module_config_dir_path / "synthese.sql" + txt = ( + Path(sql_script) + .read_text() + .replace(":'module_code'", "'{}'".format(module_code)) + .replace(":module_code", "{}".format(module_code)) + ) try: - DB.engine.execute( - text( - open(sql_script, "r") - .read() - .replace(":'module_code'", "'{}'".format(module_code)) - .replace(":module_code", "{}".format(module_code)) - ).execution_options(autocommit=True) - ) + DB.session.execute(text(txt)) + DB.session.commit() except Exception as e: print(e) click.secho("Erreur dans le script synthese.sql", fg="red") @@ -238,7 +238,9 @@ def synchronize_synthese(module_code, offset): Synchronise les données d'un module dans la synthese """ click.secho(f"Start synchronize data for module {module_code} ...", fg="green") - module = TModules.query.filter_by(module_code=module_code).one() + module = DB.session.execute( + select(TModules).where(TModules.module_code == module_code) + ).scalar_one() table_name = "v_synthese_{}".format(module_code) import_from_table( "gn_monitoring", diff --git a/backend/gn_module_monitoring/command/utils.py b/backend/gn_module_monitoring/command/utils.py index efbb2198b..22fa8baf7 100644 --- a/backend/gn_module_monitoring/command/utils.py +++ b/backend/gn_module_monitoring/command/utils.py @@ -2,12 +2,19 @@ from pathlib import Path from flask import current_app -from sqlalchemy import and_, text +from sqlalchemy import and_, text, delete, select from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.dialects.postgresql import insert as pg_insert + from geonature.utils.env import DB, BACKEND_DIR -from geonature.core.gn_permissions.models import PermObject, PermissionAvailable, PermAction +from geonature.core.gn_permissions.models import ( + PermObject, + PermissionAvailable, + PermAction, + cor_object_module, +) from geonature.core.gn_commons.models import TModules from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes @@ -73,13 +80,10 @@ def process_export_csv(module_code=None): for f in files: if not f.endswith(".sql"): continue - + txt = Path(Path(root) / Path(f)).read_text() try: - DB.engine.execute( - text(open(Path(root) / f, "r").read()) - .execution_options(autocommit=True) - .bindparams(module_code=module_code) - ) + DB.session.execute(text(txt).bindparams(module_code=module_code)) + DB.session.commit() print("{} - export csv file : {}".format(module_code, f)) except Exception as e: @@ -119,37 +123,41 @@ def insert_module_available_permissions(module_code, perm_object_code, session): print(f"L'object {perm_object_code} n'est pas traité") try: - module = session.query(TModules).filter_by(module_code=module_code).one() + module = session.scalars(select(TModules).where(TModules.module_code == module_code)).one() except NoResultFound: print(f"Le module {module_code} n'est pas présent") return try: - perm_object = session.query(PermObject).filter_by(code_object=perm_object_code).one() + perm_object = session.execute( + select(PermObject).where(PermObject.code_object == perm_object_code) + ).scalar_one_or_none() except NoResultFound: print(f"L'object de permission {perm_object_code} n'est pas présent") return - txt_cor_object_module = f""" - INSERT INTO gn_permissions.cor_object_module( - id_module, - id_object - ) - VALUES({module.id_module}, {perm_object.id_object}) - ON CONFLICT DO NOTHING - """ - session.execute(txt_cor_object_module) + stmt = ( + pg_insert(cor_object_module) + .values(id_module=module.id_module, id_object=perm_object.id_object) + .on_conflict_do_nothing() + ) + session.execute(stmt) + session.commit() # Création d'une permission disponible pour chaque action object_actions = PERMISSION_LABEL.get(perm_object_code)["actions"] for action in object_actions: - permaction = session.query(PermAction).filter_by(code_action=action).one() + permaction = session.execute( + select(PermAction).where(PermAction.code_action == action) + ).scalar_one() try: - perm = ( - session.query(PermissionAvailable) - .filter_by(module=module, object=perm_object, action=permaction) - .one() - ) + perm = session.execute( + select(PermissionAvailable).where( + PermissionAvailable.module == module, + PermissionAvailable.object == perm_object, + PermissionAvailable.action == permaction, + ) + ).scalar_one() except NoResultFound: perm = PermissionAvailable( module=module, @@ -171,13 +179,14 @@ def remove_monitoring_module(module_code): # remove module in db try: # suppression des permissions disponibles pour ce module - txt = f"DELETE FROM gn_permissions.t_permissions_available WHERE id_module = {module.id_module}" - DB.engine.execution_options(autocommit=True).execute(txt) + # txt = f"DELETE FROM gn_permissions.t_permissions_available WHERE id_module = {module.id_module}" + stmt = delete(PermissionAvailable).where(PermissionAvailable.id_module == module.id_module) - # HACK pour le moment suppresion avec un sql direct - # Car il y a un soucis de delete cascade dans les modèles sqlalchemy - txt = f"""DELETE FROM gn_commons.t_modules WHERE id_module ={module.id_module}""" - DB.engine.execution_options(autocommit=True).execute(txt) + DB.session.execute(stmt) + + stmt = delete(TModules).where(TModules.id_module == module.id_module) + DB.session.execute(stmt) + DB.session.commit() except IntegrityError: print("Impossible de supprimer le module car il y a des données associées") return @@ -214,11 +223,11 @@ def add_nomenclature(module_code): for data in nomenclature.get("types", []): nomenclature_type = None try: - nomenclature_type = ( - DB.session.query(BibNomenclaturesTypes) - .filter(data.get("mnemonique") == BibNomenclaturesTypes.mnemonique) - .one() - ) + nomenclature_type = DB.session.execute( + select(BibNomenclaturesTypes).where( + data.get("mnemonique") == BibNomenclaturesTypes.mnemonique + ) + ).scalar_one() except Exception: pass @@ -239,19 +248,18 @@ def add_nomenclature(module_code): for data in nomenclature["nomenclatures"]: nomenclature = None try: - nomenclature = ( - DB.session.query(TNomenclatures) + nomenclature = DB.session.execute( + select(TNomenclatures) .join( BibNomenclaturesTypes, BibNomenclaturesTypes.id_type == TNomenclatures.id_type ) - .filter( + .where( and_( data.get("cd_nomenclature") == TNomenclatures.cd_nomenclature, data.get("type") == BibNomenclaturesTypes.mnemonique, ) ) - .one() - ) + ).scalar_one() except Exception as e: pass @@ -266,14 +274,13 @@ def add_nomenclature(module_code): continue id_type = None - try: - id_type = ( - DB.session.query(BibNomenclaturesTypes.id_type) - .filter(BibNomenclaturesTypes.mnemonique == data["type"]) - .one() - )[0] - except Exception: + id_type = DB.session.execute( + select(BibNomenclaturesTypes.id_type).where( + BibNomenclaturesTypes.mnemonique == data["type"] + ) + ).scalar_one() + except Exception as e: pass if not id_type: @@ -295,6 +302,11 @@ def add_nomenclature(module_code): nomenclature = TNomenclatures(**data) DB.session.add(nomenclature) DB.session.commit() + print( + "nomenclature {} - {} added".format( + nomenclature.cd_nomenclature, nomenclature.label_default + ) + ) def installed_modules(session=None): diff --git a/backend/gn_module_monitoring/config/data_utils.py b/backend/gn_module_monitoring/config/data_utils.py deleted file mode 100644 index 792ee525f..000000000 --- a/backend/gn_module_monitoring/config/data_utils.py +++ /dev/null @@ -1,65 +0,0 @@ -# from pypnusershub.db.models import User -from apptax.taxonomie.models import Taxref, CorNomListe - -from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes -from geonature.utils.env import DB -from sqlalchemy import and_ - -from .utils import config_from_files_customized - - -def config_data(module_code): - return config_from_files_customized("data", module_code) - - -def get_data_utils(module_code): - return { - "nomenclature": get_nomenclature(module_code), - "taxonomy": get_taxonomy(module_code), - "users": {}, - } - - -def get_nomenclature(module_code): - nomenclature_types = config_data(module_code).get("nomenclature") - - if not nomenclature_types: - return {} - - q = ( - DB.session.query(TNomenclatures) - .join(BibNomenclaturesTypes, BibNomenclaturesTypes.id_type == TNomenclatures.id_type) - .filter(BibNomenclaturesTypes.mnemonique.in_(nomenclature_types)) - .all() - ) - - return {d.id_nomenclature: d.as_dict() for d in q} - - -def get_taxonomy(module_code): - id_list = config_data(module_code)["taxonomy"].get("id_list") - taxonomy = get_taxonomy_from_id_list(id_list) - - return taxonomy - - -def get_taxonomy_from_id_list(id_list): - if not id_list: - return {} - - id_list - q = ( - DB.session.query(Taxref) - .join( - CorNomListe, and_(CorNomListe.id_liste == id_list, CorNomListe.id_nom == Taxref.cd_nom) - ) - .join() - .all() - ) - - return {(d.cd_nom): (d.nom_complet) for d in q} - - -def get_users(module_code): - return {} - pass diff --git a/backend/gn_module_monitoring/config/repositories.py b/backend/gn_module_monitoring/config/repositories.py index a35fed59f..1cbcc24b5 100644 --- a/backend/gn_module_monitoring/config/repositories.py +++ b/backend/gn_module_monitoring/config/repositories.py @@ -162,7 +162,6 @@ def get_config(module_code=None, force=False, customSpecConfig=None): return config module = get_monitoring_module(module_code) - # derniere modification # fichiers # file_last_modif = get_directory_last_modif(monitoring_config_path()) @@ -193,7 +192,6 @@ def get_config(module_code=None, force=False, customSpecConfig=None): var_name = "__MODULE.{}".format(field_name.upper()) config["custom"][var_name] = getattr(module, field_name) config["module"][field_name] = getattr(module, field_name) - config["custom"]["__MONITORINGS_PATH"] = get_monitorings_path() config["default_display_field_names"].update(config.get("display_field_names", {})) diff --git a/backend/gn_module_monitoring/config/utils.py b/backend/gn_module_monitoring/config/utils.py index 8b1f572ce..d476d95a3 100644 --- a/backend/gn_module_monitoring/config/utils.py +++ b/backend/gn_module_monitoring/config/utils.py @@ -3,7 +3,8 @@ from pathlib import Path import json -from sqlalchemy import and_ +from sqlalchemy import and_, select +from sqlalchemy.exc import NoResultFound from geonature.core.gn_commons.models import BibTablesLocation, TModules from geonature.utils.errors import GeoNatureError @@ -35,21 +36,16 @@ def get_monitoring_module(module_code): if module_code == "generic": return None - res = ( - DB.session.query(TMonitoringModules) - .filter(TMonitoringModules.module_code == module_code) - .all() - ) - - return res[0] if len(res) else None + return DB.session.execute( + select(TMonitoringModules).where(TMonitoringModules.module_code == module_code) + ).scalar_one_or_none() def get_monitorings_path(): - return ( - DB.session.query(TModules.module_path) - .filter(TModules.module_code == "MONITORINGS") - .one()[0] - ) + module = DB.session.execute( + select(TModules.module_path).where(TModules.module_code == "MONITORINGS") + ).scalar_one() + return module def get_base_last_modif(module): @@ -88,16 +84,14 @@ def get_id_table_location(object_type): id_table_location = None try: - id_table_location = ( - DB.session.query(BibTablesLocation.id_table_location) - .filter( + id_table_location = DB.session.execute( + select(BibTablesLocation.id_table_location).where( and_( BibTablesLocation.schema_name == schema_name, BibTablesLocation.table_name == table_name, ) ) - .one() - )[0] + ).scalar_one() except Exception as e: print(schema_name, table_name, e) pass diff --git a/backend/gn_module_monitoring/modules/repositories.py b/backend/gn_module_monitoring/modules/repositories.py index 98264b29f..0e0b748b1 100644 --- a/backend/gn_module_monitoring/modules/repositories.py +++ b/backend/gn_module_monitoring/modules/repositories.py @@ -6,6 +6,7 @@ from sqlalchemy.orm import Load from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound +from sqlalchemy import select from geonature.utils.env import DB from geonature.utils.errors import GeoNatureError @@ -55,7 +56,9 @@ def get_module(field_name, value, moduleCls=TMonitoringModules): ) try: - module = DB.session.query(moduleCls).filter(getattr(moduleCls, field_name) == value).one() + module = DB.session.execute( + select(moduleCls).where(getattr(moduleCls, field_name) == value) + ).scalar_one() return module @@ -82,7 +85,9 @@ def get_modules(session=None): if not session: session = DB.session try: - res = session.query(TMonitoringModules).order_by(TMonitoringModules.module_label).all() + res = session.scalars( + select(TMonitoringModules).order_by(TMonitoringModules.module_label) + ).all() return res @@ -93,7 +98,9 @@ def get_modules(session=None): def get_source_by_code(value): try: - source = DB.session.query(TSources).filter(TSources.name_source == value).one() + source = DB.session.execute( + select(TSources).where(TSources.name_source == value) + ).scalar_one() return source diff --git a/backend/gn_module_monitoring/monitoring/admin.py b/backend/gn_module_monitoring/monitoring/admin.py index d7931dd53..40a1d21ff 100644 --- a/backend/gn_module_monitoring/monitoring/admin.py +++ b/backend/gn_module_monitoring/monitoring/admin.py @@ -5,6 +5,7 @@ from geonature.utils.env import DB from pypnnomenclature.models import BibNomenclaturesTypes, TNomenclatures from wtforms.validators import ValidationError +from sqlalchemy import exists from gn_module_monitoring.monitoring.models import BibTypeSite from gn_module_monitoring.monitoring.utils import json_formatter @@ -26,9 +27,11 @@ def __init__(self, model, field, compare_field, message=None): def __call__(self, form, field): if field.object_data == field.data: return - if self.model.query.filter( - getattr(self.model, self.field) == getattr(field.data, self.compare_field) - ).first(): + if DB.session.scalar( + exists() + .where(getattr(self.model, self.field) == getattr(field.data, self.compare_field)) + .select() + ): raise ValidationError(self.message) diff --git a/backend/gn_module_monitoring/monitoring/models.py b/backend/gn_module_monitoring/monitoring/models.py index 3263f420c..d0f95cac7 100644 --- a/backend/gn_module_monitoring/monitoring/models.py +++ b/backend/gn_module_monitoring/monitoring/models.py @@ -1,26 +1,17 @@ """ Modèles SQLAlchemy pour les modules de suivi """ +import geoalchemy2 from flask import g -from sqlalchemy import join, select, func, and_, or_, false -from sqlalchemy.inspection import inspect -from sqlalchemy.sql import case -from sqlalchemy.orm import ( - column_property, - ColumnProperty, - RelationshipProperty, - class_mapper, - aliased, -) + +from uuid import uuid4 + +from sqlalchemy import join, select, func, and_ from sqlalchemy.orm import ( column_property, - ColumnProperty, - RelationshipProperty, - class_mapper, aliased, ) from sqlalchemy.dialects.postgresql import JSONB, UUID -from uuid import uuid4 from utils_flask_sqla.serializers import serializable from utils_flask_sqla_geo.serializers import geoserializable @@ -28,7 +19,7 @@ from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.declarative import declared_attr -from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes +from pypnnomenclature.models import TNomenclatures from geonature.core.gn_commons.models import TMedias from geonature.core.gn_monitoring.models import TBaseSites, TBaseVisits from geonature.core.gn_meta.models import TDatasets @@ -37,7 +28,7 @@ from pypnusershub.db.models import User from geonature.core.gn_monitoring.models import corVisitObserver from gn_module_monitoring.monitoring.queries import ( - Query as MonitoringQuery, + GnMonitoringGenericFilter as MonitoringQuery, SitesQuery, SitesGroupsQuery, VisitQuery, @@ -45,64 +36,8 @@ ) from geonature.core.gn_permissions.tools import has_any_permissions_by_action -# from geoalchemy2 import Geometry -import geoalchemy2 - -class GenericModel: - @declared_attr - def __tablename__(cls): - return cls.__name__.lower() - - @classmethod - def set_id(cls) -> None: - pk_string = class_mapper(cls).primary_key[0].name - if hasattr(cls, "id_g") == False: - pk_value = getattr(cls, pk_string) - setattr(cls, "id_g", pk_value) - if hasattr(cls, "id_g") == False: - pk_value = getattr(cls, pk_string) - setattr(cls, "id_g", pk_value) - - @classmethod - def get_id_name(cls) -> None: - pk_string = class_mapper(cls).primary_key[0].name - # print('======= ==>', pk_string) - if hasattr(cls, "id_g") == False: - pk_value = getattr(cls, pk_string) - setattr(cls, "id_g", pk_value) - if hasattr(cls, "id_g") == False: - pk_value = getattr(cls, pk_string) - setattr(cls, "id_g", pk_value) - return pk_string - - @classmethod - def find_by_id(cls, _id: int) -> "GenericModel": - cls.set_id() - return cls.query.get_or_404(_id) - - @classmethod - def attribute_names(cls): - return [ - prop.key - for prop in class_mapper(cls).iterate_properties - if isinstance(prop, ColumnProperty) - ] - - # TODO: Voir si on garde cette méthode pour simplifier la recherche des relationship lors des filtres - @classmethod - def attribute_names_relationship(cls): - relationship_cols = inspect(cls).relationships.items() - return relationship_cols - # return [ cols[0] for cols in relationship_cols] - # return [ - # prop.key - # for prop in class_mapper(cls).iterate_properties - # if isinstance(prop, RelationshipProperty) - # ] - - -class PermissionModel(GenericModel): +class PermissionModel: def has_permission( self, cruved_object={"C": False, "R": False, "U": False, "D": False, "E": False, "V": False}, @@ -153,35 +88,6 @@ def get_permission_by_action(self, module_code=None, object_code=None): ) -@serializable -class BibTypeSite(DB.Model, PermissionModel): - __tablename__ = "bib_type_site" - __table_args__ = {"schema": "gn_monitoring"} - query_class = MonitoringQuery - - id_nomenclature_type_site = DB.Column( - DB.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), - nullable=False, - primary_key=True, - ) - - id_nomenclature_type_site = DB.Column( - DB.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), - nullable=False, - primary_key=True, - ) - config = DB.Column(JSONB) - nomenclature = DB.relationship( - TNomenclatures, uselist=False, backref=DB.backref("bib_type_site", uselist=False) - TNomenclatures, uselist=False, backref=DB.backref("bib_type_site", uselist=False) - ) - - sites = DB.relationship("TMonitoringSites", secondary=cor_type_site, lazy="noload") - - - sites = DB.relationship("TMonitoringSites", secondary=cor_type_site, lazy="noload") - - @serializable class TMonitoringObservationDetails(DB.Model): __tablename__ = "t_observation_details" @@ -197,6 +103,7 @@ class TMonitoringObservationDetails(DB.Model): TMedias, primaryjoin=(TMedias.uuid_attached_row == uuid_observation_detail), foreign_keys=[TMedias.uuid_attached_row], + overlaps="medias,medias", ) @@ -229,15 +136,13 @@ class TObservations(DB.Model, PermissionModel): @serializable -class TMonitoringObservations(TObservations, PermissionModel): +class TMonitoringObservations(TObservations, PermissionModel, ObservationsQuery): __tablename__ = "t_observation_complements" __table_args__ = {"schema": "gn_monitoring"} __mapper_args__ = { "polymorphic_identity": "monitoring_observation", } - query_class = ObservationsQuery - data = DB.Column(JSONB) id_observation = DB.Column( @@ -278,13 +183,13 @@ def has_instance_permission(self, scope): @serializable -class TMonitoringVisits(TBaseVisits, PermissionModel): +class TMonitoringVisits(TBaseVisits, PermissionModel, VisitQuery): __tablename__ = "t_visit_complements" __table_args__ = {"schema": "gn_monitoring"} __mapper_args__ = { "polymorphic_identity": "monitoring_visit", } - query_class = VisitQuery + id_base_visit = DB.Column( DB.ForeignKey("gn_monitoring.t_base_visits.id_base_visit"), nullable=False, @@ -297,6 +202,7 @@ class TMonitoringVisits(TBaseVisits, PermissionModel): TMedias, primaryjoin=(TMedias.uuid_attached_row == TBaseVisits.uuid_base_visit), foreign_keys=[TMedias.uuid_attached_row], + overlaps="medias,medias", ) observers = DB.relationship(User, lazy="joined", secondary=corVisitObserver) @@ -310,9 +216,9 @@ class TMonitoringVisits(TBaseVisits, PermissionModel): ) nb_observations = column_property( - select([func.count(TObservations.id_base_visit)]).where( - TObservations.id_base_visit == id_base_visit - ) + select(func.count(TObservations.id_base_visit)) + .where(TObservations.id_base_visit == id_base_visit) + .scalar_subquery() ) module = DB.relationship( @@ -356,13 +262,12 @@ def has_instance_permission(self, scope): @geoserializable(geoCol="geom", idCol="id_base_site") -class TMonitoringSites(TBaseSites, PermissionModel): +class TMonitoringSites(TBaseSites, PermissionModel, SitesQuery): __tablename__ = "t_site_complements" __table_args__ = {"schema": "gn_monitoring"} __mapper_args__ = { "polymorphic_identity": "monitoring_site", } - query_class = SitesQuery id_base_site = DB.Column( DB.ForeignKey("gn_monitoring.t_base_sites.id_base_site"), nullable=False, primary_key=True @@ -384,6 +289,7 @@ class TMonitoringSites(TBaseSites, PermissionModel): primaryjoin=(TBaseSites.id_base_site == TBaseVisits.id_base_site), foreign_keys=[TBaseVisits.id_base_site], cascade="all,delete", + overlaps="t_base_visits", ) medias = DB.relationship( @@ -392,26 +298,28 @@ class TMonitoringSites(TBaseSites, PermissionModel): primaryjoin=(TMedias.uuid_attached_row == TBaseSites.uuid_base_site), foreign_keys=[TMedias.uuid_attached_row], cascade="all", + overlaps="medias", ) last_visit = column_property( - select([func.max(TBaseVisits.visit_date_min)]).where( - TBaseVisits.id_base_site == id_base_site - ) + select(func.max(TBaseVisits.visit_date_min)) + .where(TBaseVisits.id_base_site == id_base_site) + .scalar_subquery() ) nb_visits = column_property( - select([func.count(TBaseVisits.id_base_site)]).where( - TBaseVisits.id_base_site == id_base_site - ) + select(func.count(TBaseVisits.id_base_site)) + .where(TBaseVisits.id_base_site == id_base_site) + .scalar_subquery() ) geom_geojson = column_property( - select([func.st_asgeojson(TBaseSites.geom)]) + select(func.st_asgeojson(TBaseSites.geom)) .where(TBaseSites.id_base_site == id_base_site) .correlate_except(TBaseSites) + .scalar_subquery() ) - types_site = DB.relationship("BibTypeSite", secondary=cor_type_site) + types_site = DB.relationship("BibTypeSite", secondary=cor_type_site, overlaps="sites") @hybrid_property def organism_actors(self): @@ -440,11 +348,28 @@ def has_instance_permission(self, scope): return True +@serializable +class BibTypeSite(DB.Model, PermissionModel, MonitoringQuery): + __tablename__ = "bib_type_site" + __table_args__ = {"schema": "gn_monitoring"} + + id_nomenclature_type_site = DB.Column( + DB.ForeignKey("ref_nomenclatures.t_nomenclatures.id_nomenclature"), + nullable=False, + primary_key=True, + ) + config = DB.Column(JSONB) + nomenclature = DB.relationship( + TNomenclatures, uselist=False, backref=DB.backref("bib_type_site", uselist=False) + ) + + sites = DB.relationship("TMonitoringSites", secondary=cor_type_site, lazy="noload") + + @geoserializable(geoCol="geom", idCol="id_sites_group") -class TMonitoringSitesGroups(DB.Model, PermissionModel): +class TMonitoringSitesGroups(DB.Model, PermissionModel, SitesGroupsQuery): __tablename__ = "t_sites_groups" __table_args__ = {"schema": "gn_monitoring"} - query_class = SitesGroupsQuery id_sites_group = DB.Column(DB.Integer, primary_key=True, nullable=False, unique=True) id_digitiser = DB.Column(DB.Integer, DB.ForeignKey("utilisateurs.t_roles.id_role")) @@ -466,6 +391,7 @@ class TMonitoringSitesGroups(DB.Model, PermissionModel): TMedias, primaryjoin=(TMedias.uuid_attached_row == uuid_sites_group), foreign_keys=[TMedias.uuid_attached_row], + overlaps="medias", ) sites = DB.relationship( @@ -477,20 +403,20 @@ class TMonitoringSitesGroups(DB.Model, PermissionModel): ) nb_sites = column_property( - select([func.count(TMonitoringSites.id_sites_group)]).where( - TMonitoringSites.id_sites_group == id_sites_group - ) + select(func.count(TMonitoringSites.id_sites_group)) + .where(TMonitoringSites.id_sites_group == id_sites_group) + .scalar_subquery() ) altitude_min = DB.Column(DB.Integer) altitude_max = DB.Column(DB.Integer) nb_visits = column_property( - select([func.count(TMonitoringVisits.id_base_site)]).where( - and_( - TMonitoringVisits.id_base_site == TMonitoringSites.id_base_site, - TMonitoringSites.id_sites_group == id_sites_group, - ) + select(func.count(TMonitoringVisits.id_base_site)) + .where( + TMonitoringVisits.id_base_site == TMonitoringSites.id_base_site, + TMonitoringSites.id_sites_group == id_sites_group, ) + .scalar_subquery() ) # @hybrid_property @@ -542,13 +468,12 @@ def has_instance_permission(self, scope): @serializable -class TMonitoringModules(TModules, PermissionModel): +class TMonitoringModules(TModules, PermissionModel, MonitoringQuery): __tablename__ = "t_module_complements" __table_args__ = {"schema": "gn_monitoring"} __mapper_args__ = { "polymorphic_identity": "monitoring_module", } - query_class = MonitoringQuery id_module = DB.Column( DB.ForeignKey("gn_commons.t_modules.id_module"), @@ -571,6 +496,7 @@ class TMonitoringModules(TModules, PermissionModel): primaryjoin=(TMedias.uuid_attached_row == uuid_module_complement), foreign_keys=[TMedias.uuid_attached_row], lazy="select", + overlaps="medias,medias", ) # TODO: restore it with CorCategorySite @@ -594,6 +520,7 @@ class TMonitoringModules(TModules, PermissionModel): "TDatasets", secondary=cor_module_dataset, join_depth=0, + overlaps="modules", ) types_site = DB.relationship( @@ -658,6 +585,7 @@ class TMonitoringModules(TModules, PermissionModel): primaryjoin=(TMonitoringModules.id_module == TMonitoringVisits.id_module), foreign_keys=[TMonitoringVisits.id_module], cascade="all", + overlaps="sites,sites_group,module", ) @@ -669,6 +597,7 @@ class TMonitoringModules(TModules, PermissionModel): cascade="all", lazy="select", uselist=False, + overlaps="sites", ) TMonitoringSitesGroups.visits = DB.relationship( @@ -676,21 +605,22 @@ class TMonitoringModules(TModules, PermissionModel): primaryjoin=(TMonitoringSites.id_sites_group == TMonitoringSitesGroups.id_sites_group), secondaryjoin=(TMonitoringVisits.id_base_site == TMonitoringSites.id_base_site), secondary="gn_monitoring.t_site_complements", + overlaps="sites,sites_group", ) TMonitoringSitesGroups.nb_visits = column_property( - select([func.count(TMonitoringVisits.id_base_site)]).where( - and_( - TMonitoringVisits.id_base_site == TMonitoringSites.id_base_site, - TMonitoringSites.id_sites_group == TMonitoringSitesGroups.id_sites_group, - ) + select(func.count(TMonitoringVisits.id_base_site)) + .where( + TMonitoringVisits.id_base_site == TMonitoringSites.id_base_site, + TMonitoringSites.id_sites_group == TMonitoringSitesGroups.id_sites_group, ) + .scalar_subquery() ) # note the alias is mandotory otherwise the where is done on the subquery table # and not the global TMonitoring table TMonitoringSitesGroups.geom_geojson = column_property( - select([func.st_asgeojson(func.st_convexHull(func.st_collect(TBaseSites.geom)))]) + select(func.st_asgeojson(func.st_convexHull(func.st_collect(TBaseSites.geom)))) .select_from( TMonitoringSitesGroups.__table__.alias("subquery").join( TMonitoringSites, @@ -700,6 +630,7 @@ class TMonitoringModules(TModules, PermissionModel): .where( TMonitoringSites.id_sites_group == TMonitoringSitesGroups.id_sites_group, ) + .scalar_subquery() ) # case([(TMonitoringSitesGroups.geom is None, select([func.st_asgeojson(func.st_convexHull(func.st_collect(TBaseSites.geom)))]) diff --git a/backend/gn_module_monitoring/monitoring/objects.py b/backend/gn_module_monitoring/monitoring/objects.py index d23537ba9..66eea6cb5 100644 --- a/backend/gn_module_monitoring/monitoring/objects.py +++ b/backend/gn_module_monitoring/monitoring/objects.py @@ -26,12 +26,7 @@ class MonitoringSite(MonitoringObjectGeom): """ def preprocess_data(self, properties, data=[]): - if len(properties.get("types_site", [])) != 0: - if hasattr(self._model, "types_site"): - properties["id_nomenclature_type_site"] = properties["types_site"][0] - properties["types_site"] = data["types_site"] - - elif len(data) != 0: + if len(data) != 0: if len(data["types_site"]) > 0 and all(isinstance(x, int) for x in data["types_site"]): properties["id_nomenclature_type_site"] = data["types_site"][0] properties["types_site"] = data["types_site"] @@ -42,13 +37,17 @@ def preprocess_data(self, properties, data=[]): properties["id_nomenclature_type_site"] = data["types_site"][0][ "id_nomenclature_type_site" ] + elif len(properties.get("types_site", [])) != 0: + if hasattr(self._model, "types_site"): + properties["id_nomenclature_type_site"] = properties["types_site"][0] + properties["types_site"] = data["types_site"] - # properties["types_site"] = [] - # # TODO: performance? - # # for type in properties['types_site']: - # # properties['types_site'].append(types_site) - # types_site = [ - # typ.nomenclature.id_nomenclature for typ in self._model.types_site - # ] - # properties["types_site"] = types_site - # TODO: A enlever une fois qu'on aura enelever le champ "id_nomenclature_type_site" du model et de la bdd + # properties["types_site"] = [] + # # TODO: performance? + # # for type in properties['types_site']: + # # properties['types_site'].append(types_site) + # types_site = [ + # typ.nomenclature.id_nomenclature for typ in self._model.types_site + # ] + # properties["types_site"] = types_site + # TODO: A enlever une fois qu'on aura enelever le champ "id_nomenclature_type_site" du model et de la bdd diff --git a/backend/gn_module_monitoring/monitoring/queries.py b/backend/gn_module_monitoring/monitoring/queries.py index 754e1fe78..794075517 100644 --- a/backend/gn_module_monitoring/monitoring/queries.py +++ b/backend/gn_module_monitoring/monitoring/queries.py @@ -1,30 +1,31 @@ from flask import g -from flask_sqlalchemy import BaseQuery -from sqlalchemy import Unicode, and_, Unicode, func, or_, false +from sqlalchemy import Unicode, and_, Unicode, func, or_, false, true +from sqlalchemy.orm import class_mapper from sqlalchemy.types import DateTime +from sqlalchemy.sql.expression import Select from werkzeug.datastructures import MultiDict -from geonature.core.gn_permissions.tools import get_scopes_by_action -import gn_module_monitoring.monitoring.models as Models -class Query(BaseQuery): - def _get_entity(self, entity): - if hasattr(entity, "_entities"): - return self._get_entity(entity._entities[0]) - return entity.entities[0] +from geonature.core.gn_permissions.tools import get_scopes_by_action +import gn_module_monitoring.monitoring.models as Models - def _get_model(self): - # When sqlalchemy is updated: - # return self._raw_columns[0].entity_namespace - # But for now: - entity = self._get_entity(self) - return entity.c - def filter_by_params(self, params: MultiDict = None): - model = self._get_model() - and_list = [] +class GnMonitoringGenericFilter: + @classmethod + def get_id_name(cls) -> None: + pk_string = class_mapper(cls).primary_key[0].name + if hasattr(cls, "id_g") == False: + pk_value = getattr(cls, pk_string) + setattr(cls, "id_g", pk_value) + return pk_string + + @classmethod + def filter_by_params(cls, query: Select, params: MultiDict = None, **kwargs): + and_list = [ + true(), + ] for key, value in params.items(): - column = getattr(model, key) + column = getattr(cls, key) if isinstance(column.type, Unicode): and_list.append(column.ilike(f"%{value}%")) elif isinstance(column.type, DateTime): @@ -32,17 +33,18 @@ def filter_by_params(self, params: MultiDict = None): else: and_list.append(column == value) and_query = and_(*and_list) - return self.filter(and_query) + return query.where(and_query) - def sort(self, label: str, direction: str): - model = self._get_model() - order_by = getattr(model, label) + @classmethod + def sort(cls, query: Select, label: str, direction: str): + order_by = getattr(cls, label) if direction == "desc": order_by = order_by.desc() - return self.order_by(order_by) + return query.order_by(order_by) - def _get_cruved_scope(self, module_code=None, object_code=None, user=None): + @classmethod + def _get_cruved_scope(cls, module_code=None, object_code=None, user=None): if user is None: user = g.current_user cruved = get_scopes_by_action( @@ -50,7 +52,8 @@ def _get_cruved_scope(self, module_code=None, object_code=None, user=None): ) return cruved - def _get_read_scope(self, module_code="MONITORINGS", object_code=None, user=None): + @classmethod + def _get_read_scope(cls, module_code="MONITORINGS", object_code=None, user=None): if user is None: user = g.current_user cruved = get_scopes_by_action( @@ -58,21 +61,26 @@ def _get_read_scope(self, module_code="MONITORINGS", object_code=None, user=None ) return cruved["R"] - def filter_by_readable(self, module_code="MONITORINGS", object_code=None, user=None): + @classmethod + def filter_by_readable( + cls, query: Select, module_code="MONITORINGS", object_code=None, user=None + ): """ Return the object where the user has autorization via its CRUVED """ - return self.filter_by_scope( - self._get_read_scope(module_code=module_code, object_code=object_code, user=user) + return cls.filter_by_scope( + query=query, + scope=cls._get_read_scope(module_code=module_code, object_code=object_code, user=user), ) -class SitesQuery(Query): - def filter_by_scope(self, scope, user=None): +class SitesQuery(GnMonitoringGenericFilter): + @classmethod + def filter_by_scope(cls, query: Select, scope, user=None): if user is None: user = g.current_user if scope == 0: - self = self.filter(false()) + query = query.where(false()) elif scope in (1, 2): ors = [ Models.TMonitoringSites.id_digitiser == user.id_role, @@ -84,16 +92,17 @@ def filter_by_scope(self, scope, user=None): Models.TMonitoringSites.inventor.has(id_organisme=user.id_organisme), Models.TMonitoringSites.digitiser.has(id_organisme=user.id_organisme), ] - self = self.filter(or_(*ors)) - return self + query = query.where(or_(*ors)) + return query -class SitesGroupsQuery(Query): - def filter_by_scope(self, scope, user=None): +class SitesGroupsQuery(GnMonitoringGenericFilter): + @classmethod + def filter_by_scope(cls, query: Select, scope, user=None): if user is None: user = g.current_user if scope == 0: - self = self.filter(false()) + query = query.where(false()) elif scope in (1, 2): ors = [ Models.TMonitoringSitesGroups.id_digitiser == user.id_role, @@ -103,17 +112,18 @@ def filter_by_scope(self, scope, user=None): ors += [ Models.TMonitoringSitesGroups.digitiser.has(id_organisme=user.id_organisme) ] - self = self.filter(or_(*ors)) - return self + query = query.where(or_(*ors)) + return query -class VisitQuery(Query): - def filter_by_scope(self, scope, user=None): +class VisitQuery(GnMonitoringGenericFilter): + @classmethod + def filter_by_scope(cls, query: Select, scope, user=None): # Problem pas le même comportement que pour les sites et groupes de site if user is None: user = g.current_user if scope == 0: - self = self.filter(false()) + query = query.where(false()) elif scope in (1, 2): ors = [ Models.TMonitoringVisits.id_digitiser == user.id_role, @@ -125,16 +135,17 @@ def filter_by_scope(self, scope, user=None): Models.TMonitoringVisits.observers.any(id_organisme=user.id_organisme), Models.TMonitoringVisits.digitiser.has(id_organisme=user.id_organisme), ] - self = self.filter(or_(*ors)) - return self + query = query.where(or_(*ors)) + return query -class ObservationsQuery(Query): - def filter_by_scope(self, scope, user=None): +class ObservationsQuery(GnMonitoringGenericFilter): + @classmethod + def filter_by_scope(cls, query: Select, scope, user=None): if user is None: user = g.current_user if scope == 0: - self = self.filter(false()) + query = query.where(false()) elif scope in (1, 2): ors = [ Models.TObservations.id_digitiser == user.id_role, @@ -142,5 +153,5 @@ def filter_by_scope(self, scope, user=None): # if organism is None => do not filter on id_organism even if level = 2 if scope == 2 and user.id_organisme is not None: ors += [Models.TObservations.digitiser.has(id_organisme=user.id_organisme)] - self = self.filter(or_(*ors)) - return self + query = query.where(or_(*ors)) + return query diff --git a/backend/gn_module_monitoring/monitoring/repositories.py b/backend/gn_module_monitoring/monitoring/repositories.py index 0e5a2254c..e6ea940e8 100644 --- a/backend/gn_module_monitoring/monitoring/repositories.py +++ b/backend/gn_module_monitoring/monitoring/repositories.py @@ -1,5 +1,9 @@ from flask import current_app + +from sqlalchemy.orm import joinedload +from sqlalchemy import select + from geonature.utils.env import DB from geonature.utils.errors import GeoNatureError from geonature.core.gn_synthese.utils.process import import_from_table @@ -8,7 +12,6 @@ import logging from ..utils.utils import to_int -from sqlalchemy.orm import joinedload from gn_module_monitoring.utils.routes import get_objet_with_permission_boolean from gn_module_monitoring.monitoring.models import PermissionModel, TMonitoringModules @@ -29,7 +32,7 @@ def get(self, value=None, field_name=None, depth=0): try: Model = self.MonitoringModel() - req = DB.session.query(Model) + req = select(Model) # Test pour mettre les relations à joined # if depth > 0: @@ -38,7 +41,11 @@ def get(self, value=None, field_name=None, depth=0): # relation_name = children_type + 's' # req = req.options(joinedload(relation_name)) - self._model = req.filter(getattr(Model, field_name) == value).one() + self._model = ( + DB.session.execute(req.where(getattr(Model, field_name) == value)) + .unique() + .scalar_one() + ) self._id = getattr(self._model, self.config_param("id_field_name")) if isinstance(self._model, PermissionModel) and not isinstance( @@ -124,10 +131,11 @@ def create_or_update(self, post_data): if b_creation: DB.session.add(self._model) DB.session.commit() - self._id = getattr(self._model, self.config_param("id_field_name")) - self.process_synthese() + # TODO module have synthese enabled + if not post_data["properties"]["id_module"] == "generic": + self.process_synthese() return self @@ -216,7 +224,7 @@ def get_list(self, args=None): order_by = args.getlist("order_by") - req = DB.session.query(Model) + req = select(Model) # Traitement de la liste des colonnes à retourner fields_list = args.getlist("fields") @@ -231,7 +239,7 @@ def get_list(self, args=None): for key in args: if hasattr(Model, key) and args[key] not in ["", None, "null", "undefined"]: vals = args.getlist(key) - req = req.filter(getattr(Model, key).in_(vals)) + req = req.where(getattr(Model, key).in_(vals)) # # filtres config @@ -250,7 +258,7 @@ def get_list(self, args=None): # TODO page etc... - res = req.limit(limit).all() + res = DB.session.scalars(req.limit(limit)).all() # patch order by number out = [r.as_dict(fields=fields_list) for r in res] diff --git a/backend/gn_module_monitoring/monitoring/schemas.py b/backend/gn_module_monitoring/monitoring/schemas.py index ca800ea73..37ff48908 100644 --- a/backend/gn_module_monitoring/monitoring/schemas.py +++ b/backend/gn_module_monitoring/monitoring/schemas.py @@ -88,7 +88,7 @@ def get_id_digitiser(self, obj): return obj.id_digitiser def set_pk(self, obj): - return self.Meta.model.get_id_name() + return "id_sites_group" def set_is_geom_from_child(self, obj): if obj.geom is None and obj.geom_geojson is None: @@ -139,7 +139,7 @@ def serialize_geojson(self, obj): return geojson.dumps(obj.as_geofeature().get("geometry")) def set_pk(self, obj): - return self.Meta.model.get_id_name() + return "id_base_site" def get_id_sites_group(self, obj): return obj.id_sites_group @@ -165,7 +165,7 @@ class Meta: observers = MA.Pluck(ObserverSchema, "id_role", many=True) def set_pk(self, obj): - return self.Meta.model.get_id_name() + return "id_base_visit" class MonitoringObservationsSchema(MA.SQLAlchemyAutoSchema): diff --git a/backend/gn_module_monitoring/monitoring/serializer.py b/backend/gn_module_monitoring/monitoring/serializer.py index f3241a81f..9c7080823 100644 --- a/backend/gn_module_monitoring/monitoring/serializer.py +++ b/backend/gn_module_monitoring/monitoring/serializer.py @@ -3,7 +3,7 @@ """ import datetime import uuid -from flask import current_app +from flask import current_app, g from marshmallow import EXCLUDE from .base import MonitoringObjectBase, monitoring_definitions from ..utils.utils import to_int @@ -84,17 +84,18 @@ def unflatten_specific_properties(self, properties): def get_readable_list_object(self, relation_name, children_type): childs_model = monitoring_definitions.MonitoringModel(object_type=children_type) - if isinstance(childs_model, PermissionModel) and not isinstance( - childs_model, TMonitoringModules - ): - all_object_readable = childs_model.query.filter_by_readable( + if getattr(childs_model, "has_instance_permission"): + scope = get_scopes_by_action( + id_role=g.current_user.id_role, module_code=self._module_code, object_code=current_app.config["MONITORINGS"].get("PERMISSION_LEVEL", {})[ children_type ], - ).all() - # child_object_readable = [v for v in childs_model if v in all_object_readable] - return all_object_readable + )["R"] + childs_model = [ + m for m in getattr(self._model, relation_name) if m.has_instance_permission(scope) + ] + return childs_model else: childs_model = getattr(self._model, relation_name) return childs_model diff --git a/backend/gn_module_monitoring/routes/data_utils.py b/backend/gn_module_monitoring/routes/data_utils.py index 7180ae389..c56eea7fb 100644 --- a/backend/gn_module_monitoring/routes/data_utils.py +++ b/backend/gn_module_monitoring/routes/data_utils.py @@ -8,7 +8,7 @@ """ from flask import request -from sqlalchemy import and_, inspect, cast +from sqlalchemy import and_, inspect, cast, select from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound from pypnnomenclature.models import TNomenclatures, BibNomenclaturesTypes @@ -93,7 +93,9 @@ def get_init_data(module_code): # user if data.get("user"): - res_user = DB.session.query(VUserslistForallMenu).filter_by(id_menu=data.get("user")).all() + res_user = DB.session.scalars( + select(VUserslistForallMenu).where(VUserslistForallMenu.id_menu == data.get("user")) + ).all() out["user"] = [user.as_dict() for user in res_user] # sites_group @@ -104,7 +106,9 @@ def get_init_data(module_code): # dataset (cruved ??) res_dataset = ( - DB.session.query(TDatasets).filter(TDatasets.modules.any(module_code=module_code)).all() + DB.session.scalars(select(TDatasets).where(TDatasets.modules.any(module_code=module_code))) + .unique() + .all() ) out["dataset"] = [dataset.as_dict() for dataset in res_dataset] @@ -133,10 +137,10 @@ def get_util_nomenclature_api(code_nomenclature_type, cd_nomenclature): if not hasattr(TNomenclatures, field_name) and field_name != "all": raise GeoNatureError("TNomenclatures n'a pas de champs {}".format(field_name)) + # requête try: - scope = TNomenclatures if field_name == "all" else getattr(TNomenclatures, field_name) - res = ( - DB.session.query(scope) + res = DB.session.execute( + select(TNomenclatures) .join( BibNomenclaturesTypes, and_( @@ -144,12 +148,19 @@ def get_util_nomenclature_api(code_nomenclature_type, cd_nomenclature): BibNomenclaturesTypes.mnemonique == code_nomenclature_type, ), ) - .filter(TNomenclatures.cd_nomenclature == cd_nomenclature) - .one() + .where(TNomenclatures.cd_nomenclature == cd_nomenclature) + ).scalar_one() + + return ( + res.as_dict() + if field_name == "all" + else res.as_dict( + fields=[ + field_name, + ] + ) ) - return res.as_dict() if field_name == "all" else res[0] - except MultipleResultsFound: raise GeoNatureError( "Nomenclature : multiple results for given type {} and code {}".format( @@ -194,65 +205,25 @@ def get_util_from_id_api(type_util, id): if not obj or not id_field_name: return None - scope = obj if field_name == "all" else getattr(obj, field_name) # requête try: res = ( - DB.session.query(scope) - .filter(cast(getattr(obj, id_field_name), DB.String) == id) - .one() + DB.session.execute( + select(obj).where(cast(getattr(obj, id_field_name), DB.String) == id) + ) + .unique() + .scalar_one() ) - return res.as_dict() if field_name == "all" else res[0] + return ( + res.as_dict() + if field_name == "all" + else res.as_dict( + fields=[ + field_name, + ] + ) + ) except NoResultFound: raise GeoNatureError("{} : no results found for id {}".format(type_util, id)) - - -@blueprint.route("util//", methods=["GET"]) -@json_resp -def get_util_from_ids_api(type_util, ids): - """ - variante de get_util_from_id_api pour plusieurs id - renvoie un tableau de valeur (ou de dictionnaire si key est 'all') - - parametre get - key: all renvoie tout l'objet - sinon renvoie un champ - separator_out: - pour reformer une chaine de caractere a partir du tableau résultat de la requete - si separator_out == ' ,' - alors ['jean', 'pierre', 'paul'].join(separator_out) -> 'jean, pierre, paul' - - :param type_util: 'nomenclature' | 'taxonomy' | 'utilisateur' - :param ids: plusieurs id reliée par des '-' (ex: 1-123-3-4) - :type type_util: str - :type ids: str - :return list si key=all ou chaine de caratere - - """ - - field_name = request.args.get("field_name", "all") - separator_out = request.args.get("sep_out", ", ") - - # tableau d'id depuis ids - list_ids = list(ids.split("-")) - - obj = model_dict.get(type_util) - id_field_name = id_field_name_dict.get(type_util) - - if not hasattr(obj, field_name) and field_name != "all": - raise GeoNatureError("{} n'a pas de champs {}".format(type_util, field_name)) - - # requête - scope = obj if field_name == "all" else getattr(obj, field_name) - res = DB.session.query(scope).filter(getattr(obj, id_field_name).in_(list_ids)).all() - - if len(res) != len(list_ids): - raise GeoNatureError("{} : pas toutes les id trouvées parmis {}".format(type_util, ids)) - - if field_name == "all": - return [r.as_dict() for r in res] - - # renvoie une chaine de caratère - return separator_out.join([r[0] for r in res]) diff --git a/backend/gn_module_monitoring/routes/monitoring.py b/backend/gn_module_monitoring/routes/monitoring.py index 2371991d2..37b0c85f5 100644 --- a/backend/gn_module_monitoring/routes/monitoring.py +++ b/backend/gn_module_monitoring/routes/monitoring.py @@ -9,11 +9,12 @@ from flask import request, send_from_directory, url_for, g, current_app import datetime as dt +from sqlalchemy import select from sqlalchemy.orm import joinedload from utils_flask_sqla.response import json_resp, json_resp_accept_empty_list from utils_flask_sqla.response import to_csv_resp, to_json_resp -from utils_flask_sqla_geo.generic import GenericTableGeo +from utils_flask_sqla_geo.generic import GenericQueryGeo from utils_flask_sqla.generic import serializeQuery @@ -40,10 +41,11 @@ def set_current_module(endpoint, values): # recherche du sous-module courrant requested_module_code = values.get("module_code") or MODULE_CODE - current_module = ( - TModules.query.options(joinedload(TModules.objects)) - .filter_by(module_code=requested_module_code) - .first_or_404(f"No module with code {requested_module_code} {endpoint}") + current_module = DB.first_or_404( + statement=select(TModules) + .options(joinedload(TModules.objects)) + .where(TModules.module_code == requested_module_code), + description=f"No module with code {requested_module_code} {endpoint}", ) g.current_module = current_module @@ -59,10 +61,11 @@ def set_current_module(endpoint, values): return # Test si l'object de permission existe - requested_permission_object = TObjects.query.filter_by( - code_object=requested_permission_object_code - ).first_or_404( - f"No permission object with code {requested_permission_object_code} {endpoint}" + requested_permission_object = DB.first_or_404( + statement=select(TObjects).where( + TObjects.code_object == requested_permission_object_code + ), + description=f"No permission object with code {requested_permission_object_code} {endpoint}", ) # si l'object de permission est associé au module => il devient l'objet courant @@ -133,38 +136,7 @@ def get_monitoring_object_api(scope, module_code=None, object_type="module", id= ) -def create_or_update_object_api(module_code, object_type, id): - """ - route pour la création ou la modification d'un objet - si id est renseigné, c'est une création (PATCH) - sinon c'est une modification (POST) - - :param module_code: reference le module concerne - :param object_type: le type d'object (site, visit, obervation) - :param id : l'identifiant de l'object (de id_base_site pour site) - :type module_code: str - :type object_type: str - :type id: int - :return: renvoie l'object crée ou modifié - :rtype: dict - """ - depth = to_int(request.args.get("depth", 1)) - - # recupération des données post - post_data = dict(request.get_json()) - module = get_module("module_code", module_code) - - # on rajoute id_module s'il n'est pas renseigné par défaut ?? - post_data["properties"]["id_module"] = module.id_module - - return ( - monitoring_definitions.monitoring_object_instance(module_code, object_type, id) - .create_or_update(post_data) - .serialize(depth) - ) - - -def create_or_update_object_api_sites_sites_group(module_code, object_type, id=None): +def create_or_update_object_api(module_code, object_type, id=None): """ route pour la création ou la modification d'un objet si id est renseigné, c'est une création (PATCH) @@ -191,8 +163,7 @@ def create_or_update_object_api_sites_sites_group(module_code, object_type, id=N # on rajoute id_module s'il n'est pas renseigné par défaut ?? if "id_module" not in post_data["properties"]: - module["id_module"] = "generic" - post_data["properties"]["id_module"] = module["id_module"] + post_data["properties"]["id_module"] = "generic" else: post_data["properties"]["id_module"] = module.id_module @@ -374,25 +345,31 @@ def export_all_observations(module_code, method): :returns: Array of dict """ id_dataset = request.args.get("id_dataset", None, int) - - view = GenericTableGeo( + export = GenericQueryGeo( + DB=DB, tableName=f"v_export_{module_code.lower()}_{method}", schemaName="gn_monitoring", - engine=DB.engine, + filters=[], + limit=50000, + offset=0, + geometry_field=None, + srid=None, ) - columns = view.tableDef.columns - q = DB.session.query(*columns) - # Filter with dataset if is set - if hasattr(columns, "id_dataset") and id_dataset: - q = q.filter(columns.id_dataset == id_dataset) - data = q.all() + model = export.get_model() + columns = export.view.tableDef.columns + schema = export.get_marshmallow_schema() + + q = select(model) + # Filter with dataset if is set + if hasattr(model, "id_dataset") and id_dataset: + q = q.where(getattr(model, "id_dataset") == id_dataset) + data = DB.session.scalars(q).all() timestamp = dt.datetime.now().strftime("%Y_%m_%d_%Hh%Mm%S") filename = f"{module_code}_{method}_{timestamp}" - return to_csv_resp( filename, - data=serializeQuery(data, q.column_descriptions), + data=schema().dump(data, many=True), separator=";", columns=[ db_col.key for db_col in columns if db_col.key != "geom" diff --git a/backend/gn_module_monitoring/routes/site.py b/backend/gn_module_monitoring/routes/site.py index baf3b3355..16fc6077a 100644 --- a/backend/gn_module_monitoring/routes/site.py +++ b/backend/gn_module_monitoring/routes/site.py @@ -3,7 +3,7 @@ import json from geonature.core.gn_commons.schemas import ModuleSchema from geonature.utils.env import db -from sqlalchemy import and_ +from sqlalchemy import and_, select from sqlalchemy.orm import Load, joinedload from sqlalchemy.sql import func from werkzeug.datastructures import MultiDict @@ -23,7 +23,7 @@ from geonature.core.gn_permissions.decorators import check_cruved_scope from gn_module_monitoring.monitoring.schemas import BibTypeSiteSchema, MonitoringSitesSchema from gn_module_monitoring.routes.monitoring import ( - create_or_update_object_api_sites_sites_group, + create_or_update_object_api, get_config_object, ) from gn_module_monitoring.routes.modules import get_modules @@ -56,8 +56,8 @@ def get_types_site(): params=params, default_sort="id_nomenclature_type_site", default_direction="desc" ) - query = filter_params(query=BibTypeSite.query, params=params) - query = sort(query=query, sort=sort_label, sort_dir=sort_dir) + query = filter_params(BibTypeSite, query=select(BibTypeSite), params=params) + query = sort(query=query, model=BibTypeSite, sort=sort_label, sort_dir=sort_dir) return paginate( query=query, @@ -74,17 +74,20 @@ def get_types_site_by_label(): sort_label, sort_dir = get_sort( params=params, default_sort="label_fr", default_direction="desc" ) - joinquery = BibTypeSite.query.join(BibTypeSite.nomenclature).filter( - TNomenclatures.label_fr.ilike(f"%{params['label_fr']}%") + + query = ( + select(BibTypeSite) + .join(BibTypeSite.nomenclature) + .where(TNomenclatures.label_fr.ilike(f"%{params['label_fr']}%")) ) if sort_dir == "asc": - joinquery = joinquery.order_by(TNomenclatures.label_fr.asc()) + query = query.order_by(TNomenclatures.label_fr.asc()) # See if there are not too much labels since they are used # in select in the frontend side. And an infinite select is not # implemented return paginate( - query=joinquery, + query=query, schema=BibTypeSiteSchema, limit=limit, page=page, @@ -93,7 +96,8 @@ def get_types_site_by_label(): @blueprint.route("/sites/types/", methods=["GET"]) def get_type_site_by_id(id_type_site): - res = BibTypeSite.find_by_id(id_type_site) + res = db.get_or_404(BibTypeSite, id_type_site) + schema = BibTypeSiteSchema() return schema.dump(res) @@ -116,11 +120,11 @@ def get_sites(object_type): params=params, default_sort="id_base_site", default_direction="desc" ) - query = TMonitoringSites.query + query = select(TMonitoringSites) query = filter_according_to_column_type_for_site(query, params) query = sort_according_to_column_type_for_site(query, sort_label, sort_dir) - query_allowed = query.filter_by_readable(object_code=object_code) + query_allowed = TMonitoringSites.filter_by_readable(query=query, object_code=object_code) return paginate_scope( query=query_allowed, schema=MonitoringSitesSchema, @@ -141,7 +145,7 @@ def get_sites(object_type): "R", get_scope=True, module_code=MODULE_CODE, object_code="MONITORINGS_SITES" ) def get_site_by_id(scope, id, object_type): - site = TMonitoringSites.query.get_or_404(id) + site = db.get_or_404(TMonitoringSites, id) if not site.has_instance_permission(scope=scope): raise Forbidden(f"User {g.current_user} cannot read site {site.id_base_site}") schema = MonitoringSitesSchema() @@ -158,18 +162,16 @@ def get_site_by_id(scope, id, object_type): def get_all_site_geometries(object_type): object_code = "MONITORINGS_SITES" params = MultiDict(request.args) - query = TMonitoringSites.query - query_allowed = query.filter_by_readable(object_code=object_code) - subquery = ( - query_allowed.with_entities( - TMonitoringSites.id_base_site, - TMonitoringSites.base_site_name, - TMonitoringSites.geom, - TMonitoringSites.id_sites_group, - ) - .filter_by_params(params) - .subquery() + query = select(TMonitoringSites) + query_allowed = TMonitoringSites.filter_by_readable(query=query, object_code=object_code) + query_allowed.with_only_columns( + TMonitoringSites.id_base_site, + TMonitoringSites.base_site_name, + TMonitoringSites.geom, + TMonitoringSites.id_sites_group, ) + query_allowed = TMonitoringSites.filter_by_params(query=query_allowed, params=params) + subquery = query_allowed.subquery() result = geojson_query(subquery) @@ -184,17 +186,25 @@ def get_module_by_id_base_site(id_base_site: int): modules_object, object_code="MONITORINGS_VISITES", depth=0 ) ids_modules_allowed = [module["id_module"] for module in modules if module["cruved"]["R"]] - query = TMonitoringModules.query.options( - Load(TMonitoringModules).raiseload("*"), - joinedload(TMonitoringModules.types_site).options(joinedload(BibTypeSite.sites)), - ).filter( - and_( - TMonitoringModules.id_module.in_(ids_modules_allowed), - TMonitoringModules.types_site.any(BibTypeSite.sites.any(id_base_site=id_base_site)), + + query = ( + select(TMonitoringModules) + .options( + Load(TMonitoringModules).raiseload("*"), + joinedload(TMonitoringModules.types_site).options(joinedload(BibTypeSite.sites)), + ) + .where( + and_( + TMonitoringModules.id_module.in_(ids_modules_allowed), + TMonitoringModules.types_site.any( + BibTypeSite.sites.any(id_base_site=id_base_site) + ), + ) ) ) + schema = ModuleSchema() - result = query.all() + result = db.session.scalars(query).all() # TODO: Is it usefull to put a limit here? Will there be more than 200 modules? # If limit here, implement paginated/infinite scroll on frontend side return [schema.dump(res) for res in result] @@ -216,7 +226,7 @@ def post_sites(object_type): get_config_with_specific(module_code, force=True, complements=post_data["dataComplement"]) - return create_or_update_object_api_sites_sites_group(module_code, object_type), 201 + return create_or_update_object_api(module_code, object_type), 201 @blueprint.route("/sites/", methods=["DELETE"], defaults={"object_type": "site"}) @@ -224,10 +234,10 @@ def post_sites(object_type): "D", get_scope=True, module_code=MODULE_CODE, object_code="MONITORINGS_SITES" ) def delete_site(scope, _id, object_type): - site = TMonitoringSites.query.get_or_404(_id) + site = db.get_or_404(TMonitoringSites, _id) if not site.has_instance_permission(scope=scope): raise Forbidden(f"User {g.current_user} cannot delete site {site.id_base_site}") - TMonitoringSites.query.filter_by(id_base_site=_id).delete() + db.session.delete(site) db.session.commit() return {"success": "Item is successfully deleted"}, 200 @@ -237,7 +247,7 @@ def delete_site(scope, _id, object_type): "U", get_scope=True, module_code=MODULE_CODE, object_code="MONITORINGS_SITES" ) def patch_sites(scope, _id, object_type): - site = TMonitoringSites.query.get_or_404(_id) + site = db.get_or_404(TMonitoringSites, _id) if not site.has_instance_permission(scope=scope): raise Forbidden(f"User {g.current_user} cannot update site {site.id_base_site}") module_code = "generic" @@ -245,4 +255,4 @@ def patch_sites(scope, _id, object_type): get_config_with_specific(module_code, force=True, complements=post_data["dataComplement"]) - return create_or_update_object_api_sites_sites_group(module_code, object_type, _id), 201 + return create_or_update_object_api(module_code, object_type, _id), 201 diff --git a/backend/gn_module_monitoring/routes/sites_groups.py b/backend/gn_module_monitoring/routes/sites_groups.py index 1c86ac39a..3122b05fc 100644 --- a/backend/gn_module_monitoring/routes/sites_groups.py +++ b/backend/gn_module_monitoring/routes/sites_groups.py @@ -2,7 +2,7 @@ from flask import jsonify, request, g from geonature.utils.env import db from marshmallow import ValidationError -from sqlalchemy import func +from sqlalchemy import func, select from werkzeug.datastructures import MultiDict from werkzeug.exceptions import Forbidden from geonature.core.gn_permissions import decorators as permissions @@ -26,7 +26,7 @@ get_objet_with_permission_boolean, ) from gn_module_monitoring.routes.monitoring import ( - create_or_update_object_api_sites_sites_group, + create_or_update_object_api, get_config_object, ) from gn_module_monitoring.utils.utils import to_int @@ -44,14 +44,16 @@ def get_sites_groups(object_type: str): object_code = "MONITORINGS_GRP_SITES" params = MultiDict(request.args) limit, page = get_limit_page(params=params) + sort_label, sort_dir = get_sort( params=params, default_sort="id_sites_group", default_direction="desc" ) - query = filter_params(query=TMonitoringSitesGroups.query, params=params) + query = select(TMonitoringSitesGroups) + query = filter_params(TMonitoringSitesGroups, query=query, params=params) - query = sort(query=query, sort=sort_label, sort_dir=sort_dir) + query = sort(TMonitoringSitesGroups, query=query, sort=sort_label, sort_dir=sort_dir) - query_allowed = query.filter_by_readable(object_code=object_code) + query_allowed = TMonitoringSitesGroups.filter_by_readable(query=query, object_code=object_code) return paginate_scope( query=query_allowed, schema=MonitoringSitesGroupsSchema, @@ -59,12 +61,6 @@ def get_sites_groups(object_type: str): page=page, object_code=object_code, ) - # return paginate( - # query=query, - # schema=MonitoringSitesGroupsSchema, - # limit=limit, - # page=page, - # ) @blueprint.route( @@ -75,16 +71,15 @@ def get_sites_groups(object_type: str): "R", get_scope=True, module_code=MODULE_CODE, object_code="MONITORINGS_GRP_SITES" ) def get_sites_group_by_id(scope, id_sites_group: int, object_type: str): - sites_group = TMonitoringSitesGroups.query.get_or_404(id_sites_group) + sites_group = db.get_or_404(TMonitoringSitesGroups, id_sites_group) if not sites_group.has_instance_permission(scope=scope): raise Forbidden( f"User {g.current_user} cannot read site group {sites_group.id_sites_group}" ) schema = MonitoringSitesGroupsSchema() - result = TMonitoringSitesGroups.query.get_or_404(id_sites_group) - response = schema.dump(result) + response = schema.dump(sites_group) response["cruved"] = get_objet_with_permission_boolean( - [result], object_code="MONITORINGS_GRP_SITES" + [sites_group], object_code="MONITORINGS_GRP_SITES" )[0]["cruved"] response["geometry"] = ( json.loads(response["geometry"]) @@ -100,10 +95,10 @@ def get_sites_group_by_id(scope, id_sites_group: int, object_type: str): @check_cruved_scope("R", module_code=MODULE_CODE, object_code="MONITORINGS_GRP_SITES") def get_sites_group_geometries(object_type: str): object_code = "MONITORINGS_GRP_SITES" - query = TMonitoringSitesGroups.query - query_allowed = query.filter_by_readable(object_code=object_code) + query = select(TMonitoringSitesGroups) + query = TMonitoringSitesGroups.filter_by_readable(query=query, object_code=object_code) subquery_not_geom = ( - query_allowed.with_entities( + query.with_only_columns( TMonitoringSitesGroups.id_sites_group, TMonitoringSitesGroups.sites_group_name, func.st_convexHull(func.st_collect(TMonitoringSites.geom)), @@ -113,20 +108,21 @@ def get_sites_group_geometries(object_type: str): TMonitoringSites, TMonitoringSites.id_sites_group == TMonitoringSitesGroups.id_sites_group, ) - .filter(TMonitoringSitesGroups.geom == None) + .where(TMonitoringSitesGroups.geom == None) .subquery() ) subquery_with_geom = ( - query_allowed.with_entities( + query.with_only_columns( TMonitoringSitesGroups.id_sites_group, TMonitoringSitesGroups.sites_group_name, TMonitoringSitesGroups.geom, - ).filter(TMonitoringSitesGroups.geom != None) + ).where(TMonitoringSitesGroups.geom != None) ).subquery() result_1 = geojson_query(subquery_not_geom) result_2 = geojson_query(subquery_with_geom) + if result_1["features"] is not None: if result_2["features"] is not None: result_2["features"].extend(result_1["features"]) @@ -145,7 +141,7 @@ def get_sites_group_geometries(object_type: str): def patch(scope, _id: int, object_type: str): # ###############################"" # FROM route/monitorings - sites_group = TMonitoringSitesGroups.query.get_or_404(_id) + sites_group = db.get_or_404(TMonitoringSitesGroups, _id) if not sites_group.has_instance_permission(scope=scope): raise Forbidden( f"User {g.current_user} cannot update site group {sites_group.id_sites_group}" @@ -153,7 +149,7 @@ def patch(scope, _id: int, object_type: str): module_code = "generic" get_config(module_code, force=True) - return create_or_update_object_api_sites_sites_group(module_code, object_type, _id), 201 + return create_or_update_object_api(module_code, object_type, _id), 201 @blueprint.route( @@ -163,12 +159,12 @@ def patch(scope, _id: int, object_type: str): "D", get_scope=True, module_code=MODULE_CODE, object_code="MONITORINGS_GRP_SITES" ) def delete(scope, _id: int, object_type: str): - sites_group = TMonitoringSitesGroups.query.get_or_404(_id) + sites_group = db.get_or_404(TMonitoringSitesGroups, _id) if not sites_group.has_instance_permission(scope=scope): raise Forbidden( f"User {g.current_user} cannot delete site group {sites_group.id_sites_group}" ) - TMonitoringSitesGroups.query.filter_by(id_sites_group=_id).delete() + db.session.delete(sites_group) db.session.commit() return {"success": "Item is successfully deleted"}, 200 @@ -178,7 +174,7 @@ def delete(scope, _id: int, object_type: str): def post(object_type: str): module_code = "generic" get_config(module_code, force=True) - return create_or_update_object_api_sites_sites_group(module_code, object_type), 201 + return create_or_update_object_api(module_code, object_type), 201 @blueprint.errorhandler(ValidationError) diff --git a/backend/gn_module_monitoring/routes/visit.py b/backend/gn_module_monitoring/routes/visit.py index 98fb144f0..70b7fbdab 100644 --- a/backend/gn_module_monitoring/routes/visit.py +++ b/backend/gn_module_monitoring/routes/visit.py @@ -1,7 +1,9 @@ from flask import request, current_app +from sqlalchemy import select from sqlalchemy.orm import joinedload from werkzeug.datastructures import MultiDict +from geonature.utils.env import db from gn_module_monitoring.blueprint import blueprint from gn_module_monitoring.monitoring.models import TMonitoringVisits from gn_module_monitoring.monitoring.schemas import MonitoringVisitsSchema @@ -31,17 +33,17 @@ def get_visits(object_type): modules = get_objet_with_permission_boolean(modules_object, object_code=OBJECT_CODE) ids_modules_allowed = [module["id_module"] for module in modules if module["cruved"]["R"]] - query = TMonitoringVisits.query - query = query.options(joinedload(TMonitoringVisits.module)).filter( + query = select(TMonitoringVisits) + query = query.options(joinedload(TMonitoringVisits.module)).where( TMonitoringVisits.id_module.in_(ids_modules_allowed) ) - query = filter_params(query=query, params=params) - query = sort(query=query, sort=sort_label, sort_dir=sort_dir) + query = filter_params(TMonitoringVisits, query=query, params=params) + query = sort(model=TMonitoringVisits, query=query, sort=sort_label, sort_dir=sort_dir) query_allowed = query for module in modules: if module["id_module"] in ids_modules_allowed: - query_allowed = query_allowed.filter_by_readable( - module_code=module["module_code"], object_code=OBJECT_CODE + query_allowed = TMonitoringVisits.filter_by_readable( + query=query_allowed, module_code=module["module_code"], object_code=OBJECT_CODE ) return paginate_scope( query=query_allowed, diff --git a/backend/gn_module_monitoring/tests/fixtures/generic.py b/backend/gn_module_monitoring/tests/fixtures/generic.py index 8261bfb13..b961a161c 100644 --- a/backend/gn_module_monitoring/tests/fixtures/generic.py +++ b/backend/gn_module_monitoring/tests/fixtures/generic.py @@ -4,6 +4,7 @@ from pypnusershub.db.models import User from utils_flask_sqla_geo.generic import GenericQueryGeo +from sqlalchemy import select from flask import current_app from pathlib import Path @@ -27,17 +28,25 @@ @pytest.fixture(scope="session") def monitorings_users(app): - app = Application.query.filter(Application.code_application == "GN").one() - profil = Profil.query.filter(Profil.nom_profil == "Lecteur").one() + app = db.session.execute( + select(Application).where(Application.code_application == "GN") + ).scalar_one() + profil = db.session.execute(select(Profil).where(Profil.nom_profil == "Lecteur")).scalar_one() - modules = TModules.query.all() + modules = db.session.scalars(select(TModules)).all() - actions = {code: PermAction.query.filter_by(code_action=code).one() for code in "CRUVED"} + actions = { + code: db.session.execute( + select(PermAction).where(PermAction.code_action == code) + ).scalar_one() + for code in "CRUVED" + } type_code_object = [ "MONITORINGS_MODULES", "MONITORINGS_GRP_SITES", "MONITORINGS_SITES", "MONITORINGS_VISITES", + "ALL", ] def create_user(username, organisme=None, scope=None, sensitivity_filter=False): @@ -60,10 +69,12 @@ def create_user(username, organisme=None, scope=None, sensitivity_filter=False): db.session.add(right) if scope > 0: for co in type_code_object: - object_all = PermObject.query.filter_by(code_object=co).one() + object_all = db.session.scalars( + select(PermObject).where(PermObject.code_object == co) + ).all() for action in actions.values(): for module in modules: - for obj in [object_all] + module.objects: + for obj in object_all + module.objects: permission = Permission( role=user, action=action, diff --git a/backend/gn_module_monitoring/tests/fixtures/module.py b/backend/gn_module_monitoring/tests/fixtures/module.py index 981b6215b..02e7685fd 100644 --- a/backend/gn_module_monitoring/tests/fixtures/module.py +++ b/backend/gn_module_monitoring/tests/fixtures/module.py @@ -1,6 +1,7 @@ from uuid import uuid4 import pytest +from sqlalchemy import select from geonature.utils.env import db from gn_module_monitoring.monitoring.models import TMonitoringModules @@ -21,6 +22,31 @@ UserApplicationRight, ) from .generic import monitorings_users +import pytest +import shutil + +from pathlib import Path +from flask import current_app + +from gn_module_monitoring.command.cmd import ( + cmd_install_monitoring_module, + cmd_remove_monitoring_module_cmd, +) +from gn_module_monitoring.monitoring.models import TMonitoringModules +from geonature.utils.env import BACKEND_DIR, DB + + +@pytest.fixture +def install_module_test(): + # Copy des fichiers du module de test + path_gn_monitoring = Path(__file__).absolute().parent.parent.parent.parent.parent + path_module_test = path_gn_monitoring / Path("contrib/test") + path_gn_monitoring = BACKEND_DIR / Path("media/monitorings/test") + shutil.copytree(path_module_test, path_gn_monitoring, dirs_exist_ok=True) + + # Installation du module + runner = current_app.test_cli_runner() + result = runner.invoke(cmd_install_monitoring_module, ["test"]) @pytest.fixture @@ -31,6 +57,7 @@ def monitoring_module(types_site, monitorings_users): module_label="test", active_frontend=True, active_backend=False, + b_synthese=False, module_path="test", types_site=list(types_site.values()), ) @@ -38,7 +65,14 @@ def monitoring_module(types_site, monitorings_users): with db.session.begin_nested(): db.session.add(t_monitoring_module) # Set module Permission - actions = {code: PermAction.query.filter_by(code_action=code).one() for code in "CRUVED"} + + actions = { + code: db.session.execute( + select(PermAction).where(PermAction.code_action == code) + ).scalar_one() + for code in "CRUVED" + } + type_code_object = [ "MONITORINGS_MODULES", "MONITORINGS_GRP_SITES", @@ -46,7 +80,10 @@ def monitoring_module(types_site, monitorings_users): "MONITORINGS_VISITES", ] for co in type_code_object: - object_all = PermObject.query.filter_by(code_object=co).one() + object_all = db.session.execute( + select(PermObject).where(PermObject.code_object == co) + ).scalar_one() + for action in actions.values(): for obj in [object_all] + t_monitoring_module.objects: permission = Permission( @@ -70,6 +107,7 @@ def monitoring_module_wo_types_site(): active_frontend=True, active_backend=False, module_path="NoType", + b_synthese=False, ) with db.session.begin_nested(): diff --git a/backend/gn_module_monitoring/tests/fixtures/type_site.py b/backend/gn_module_monitoring/tests/fixtures/type_site.py index 9aa191673..681cd84c8 100644 --- a/backend/gn_module_monitoring/tests/fixtures/type_site.py +++ b/backend/gn_module_monitoring/tests/fixtures/type_site.py @@ -2,6 +2,9 @@ import os import pytest + +from sqlalchemy import select + from geonature.utils.env import db from pypnnomenclature.models import BibNomenclaturesTypes, TNomenclatures @@ -21,8 +24,10 @@ def get_test_data(filename): def nomenclature_types_site(): mnemoniques = ("Test_Grotte", "Test_Mine") nomenclatures = [] - type_site = BibNomenclaturesTypes.query.filter( - BibNomenclaturesTypes.mnemonique == "TYPE_SITE" + type_site = db.session.scalars( + select(BibNomenclaturesTypes) + .where(BibNomenclaturesTypes.mnemonique == "TYPE_SITE") + .limit(1) ).first() for mnemo in mnemoniques: nomenclatures.append( diff --git a/backend/gn_module_monitoring/tests/test_commands/__init__.py b/backend/gn_module_monitoring/tests/test_commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/gn_module_monitoring/tests/test_commands/test_commands.py b/backend/gn_module_monitoring/tests/test_commands/test_commands.py new file mode 100644 index 000000000..9bb822fa6 --- /dev/null +++ b/backend/gn_module_monitoring/tests/test_commands/test_commands.py @@ -0,0 +1,104 @@ +import pytest +import shutil + +from pathlib import Path +from flask import url_for, current_app + +from pypnusershub.tests.utils import set_logged_user_cookie + +from sqlalchemy import select + +from gn_module_monitoring.tests.fixtures.generic import * +from gn_module_monitoring.command.cmd import ( + cmd_remove_monitoring_module_cmd, + cmd_process_all, + cmd_process_export_csv, + cmd_process_available_permission_module, + cmd_add_module_nomenclature_cli, +) +from gn_module_monitoring.monitoring.models import TMonitoringModules +from geonature.utils.env import BACKEND_DIR, DB + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestCommands: + def test_install_monitoring_module(self, install_module_test): + # Installation du module + # Test Installation + result = DB.session.execute( + select(TMonitoringModules).where(TMonitoringModules.module_code == "test") + ).scalar_one() + assert result.module_code == "test" + + def test_remove_monitoring_module(self, install_module_test): + runner = current_app.test_cli_runner() + + # Suppression du module de test + result = runner.invoke(cmd_remove_monitoring_module_cmd, ["test"]) + + # Test suppression + result = DB.session.execute( + select(TMonitoringModules).where(TMonitoringModules.module_code == "test") + ).scalar_one_or_none() + assert result == None + + def test_process_all_with_module(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + result = runner.invoke(cmd_process_all, ["test"]) + # Pas de result juste + assert result + result = runner.invoke(cmd_process_export_csv, ["test"]) + # Pas de result juste + assert result + + def test_process_all_without_module(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + result = runner.invoke(cmd_process_all) + # Pas de result + assert result.exit_code == 0 + + result = runner.invoke(cmd_process_export_csv) + # Pas de result + assert result.exit_code == 0 + + def test_process_all_with_module(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + # import pdb + result = runner.invoke(cmd_process_all, ["test"]) + # Pas de result juste + assert result.exit_code == 0 + + def test_process_available_permission_module_without_module(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + result = runner.invoke(cmd_process_available_permission_module) + # Pas de result juste + assert result.exit_code == 0 + assert "Création des permissions pour test" in result.output + + def test_process_available_permission_module_with_module(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + result = runner.invoke(cmd_process_available_permission_module, ["test"]) + # Pas de result juste + assert result.exit_code == 0 + assert "Création des permissions pour test" in result.output + + def test_process_available_permission_module_bad_module(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + result = runner.invoke(cmd_process_available_permission_module, ["bad_module"]) + # Pas de result juste + assert result.exit_code == 0 + assert "le module n'existe pas" in result.output + + def test_cmd_add_module_nomenclature_cli(self, install_module_test): + runner = current_app.test_cli_runner() + # Commande process all + result = runner.invoke(cmd_add_module_nomenclature_cli) + # Pas de result juste + assert result.exit_code == 2 + assert "Missing argument 'MODULE_CODE'" in result.output diff --git a/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_bib_type_site.py b/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_bib_type_site.py index ae580ccbf..2e973a5e5 100644 --- a/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_bib_type_site.py +++ b/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_bib_type_site.py @@ -1,5 +1,8 @@ import pytest +from sqlalchemy import select + +from geonature.utils.env import db from gn_module_monitoring.monitoring.models import BibTypeSite @@ -7,14 +10,16 @@ class TestBibTypeSite: def test_get_bib_type_site(self, types_site): type_site = list(types_site.values())[0] - get_type_site = BibTypeSite.query.filter_by( - id_nomenclature_type_site=type_site.id_nomenclature_type_site - ).one() + get_type_site = db.session.execute( + select(BibTypeSite).where( + BibTypeSite.id_nomenclature_type_site == type_site.id_nomenclature_type_site + ) + ).scalar_one() assert get_type_site.id_nomenclature_type_site == type_site.id_nomenclature_type_site def test_get_all_bib_type_site(self, types_site): - get_types_site = BibTypeSite.query.all() + get_types_site = db.session.scalars(select(BibTypeSite)).all() assert all( type_site.id_nomenclature_type_site diff --git a/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_module.py b/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_module.py index 978b9d1ca..2e66e56ff 100644 --- a/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_module.py +++ b/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_module.py @@ -1,4 +1,7 @@ import pytest + +from sqlalchemy import select + from geonature.utils.env import db from gn_module_monitoring.monitoring.models import TMonitoringModules @@ -14,6 +17,10 @@ def test_remove_categorie_from_module(self, monitoring_module, types_site): with db.session.begin_nested(): monitoring_module.types_site.pop(0) - mon = TMonitoringModules.query.filter_by(id_module=monitoring_module.id_module).one() + module = db.session.execute( + select(TMonitoringModules).where( + TMonitoringModules.id_module == monitoring_module.id_module + ) + ).scalar_one() - assert len(mon.types_site) == len(types_site) - 1 + assert len(module.types_site) == len(types_site) - 1 diff --git a/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_sites_groups.py b/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_sites_groups.py index 99a54f811..43a71cf5f 100644 --- a/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_sites_groups.py +++ b/backend/gn_module_monitoring/tests/test_monitoring/test_models/test_sites_groups.py @@ -1,8 +1,12 @@ import pytest +from sqlalchemy import select from gn_module_monitoring.monitoring.models import TMonitoringSitesGroups +from geonature.utils.env import DB + + @pytest.mark.usefixtures("temporary_transaction") class TestTMonitoringSitesGroups: def test_sort_desc(self, sites_groups): @@ -11,12 +15,16 @@ def test_sort_desc(self, sites_groups): "This test cannot work if there is less than 2 sites_groups in database (via fixtures or not)" ) - query = TMonitoringSitesGroups.query.filter( - TMonitoringSitesGroups.id_sites_group.in_( - group.id_sites_group for group in sites_groups.values() + query = ( + select(TMonitoringSitesGroups) + .where( + TMonitoringSitesGroups.id_sites_group.in_( + group.id_sites_group for group in sites_groups.values() + ) ) - ).sort(label="id_sites_group", direction="desc") - result = query.all() + .order_by(TMonitoringSitesGroups.id_sites_group.desc()) + ) + result = DB.session.scalars(query).all() assert result[0].id_sites_group > result[1].id_sites_group @@ -26,11 +34,15 @@ def test_sort_asc(self, sites_groups): "This test cannot work if there is less than 2 sites_groups in database (via fixtures or not)" ) - query = TMonitoringSitesGroups.query.filter( - TMonitoringSitesGroups.id_sites_group.in_( - group.id_sites_group for group in sites_groups.values() + query = ( + select(TMonitoringSitesGroups) + .where( + TMonitoringSitesGroups.id_sites_group.in_( + group.id_sites_group for group in sites_groups.values() + ) ) - ).sort(label="id_sites_group", direction="asc") - result = query.all() + .order_by(TMonitoringSitesGroups.id_sites_group.asc()) + ) + result = DB.session.scalars(query).all() assert result[0].id_sites_group < result[1].id_sites_group diff --git a/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_bib_site_type_schema.py b/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_bib_site_type_schema.py index 91aa19e0f..1993f1a95 100644 --- a/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_bib_site_type_schema.py +++ b/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_bib_site_type_schema.py @@ -1,5 +1,9 @@ import pytest +from sqlalchemy import select + +from geonature.utils.env import db + from gn_module_monitoring.monitoring.models import BibTypeSite from gn_module_monitoring.monitoring.schemas import BibTypeSiteSchema @@ -7,7 +11,7 @@ @pytest.mark.usefixtures("temporary_transaction") class TestBibSiteTypeSchema: def test_dump(self, types_site): - one_type_site = BibTypeSite.query.first() + one_type_site = db.session.scalars(select(BibTypeSite).limit(1)).first() schema = BibTypeSiteSchema() type_site = schema.dump(one_type_site) diff --git a/backend/gn_module_monitoring/tests/test_routes/test_generic_with_module.py b/backend/gn_module_monitoring/tests/test_routes/test_generic_with_module.py new file mode 100644 index 000000000..19ce1de7a --- /dev/null +++ b/backend/gn_module_monitoring/tests/test_routes/test_generic_with_module.py @@ -0,0 +1,22 @@ +import pytest +from flask import url_for + +from pypnusershub.tests.utils import set_logged_user_cookie + +from gn_module_monitoring.tests.fixtures.generic import * + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestModules: + def test_get_export_csv(self, monitoring_module, monitorings_users): + set_logged_user_cookie(self.client, monitorings_users["admin_user"]) + response = self.client.get( + url_for("monitorings.export_all_observations", module_code="test", method="sites") + ) + + expected_headers_content_type = "text/plain" + expected = '"base_site_code";"longitude";"latitude"' + + assert response.status_code == 200 + assert response.headers.get("content-type") == expected_headers_content_type + assert expected in response.text diff --git a/backend/gn_module_monitoring/tests/test_routes/test_modules.py b/backend/gn_module_monitoring/tests/test_routes/test_modules.py new file mode 100644 index 000000000..e68e4c5d8 --- /dev/null +++ b/backend/gn_module_monitoring/tests/test_routes/test_modules.py @@ -0,0 +1,21 @@ +import pytest +from flask import url_for + +from pypnusershub.tests.utils import set_logged_user_cookie + +from gn_module_monitoring.tests.fixtures.generic import * + + +@pytest.mark.usefixtures("client_class", "temporary_transaction") +class TestModules: + def test_get_modules_api(self, monitorings_users): + set_logged_user_cookie(self.client, monitorings_users["admin_user"]) + r = self.client.get(url_for("monitorings.get_modules_api")) + # TODO test response + assert r.status_code == 200 + + def test_get_cruved_monitorings(self, monitorings_users): + set_logged_user_cookie(self.client, monitorings_users["admin_user"]) + r = self.client.get(url_for("monitorings.get_cruved_monitorings")) + # TODO test response + assert r.status_code == 200 diff --git a/backend/gn_module_monitoring/tests/test_routes/test_site.py b/backend/gn_module_monitoring/tests/test_routes/test_site.py index 41c23c0fc..59dab2e2c 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_site.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_site.py @@ -197,7 +197,7 @@ def test_post_sites( assert response.status_code == 201 obj_created = response.json - res = TMonitoringSites.find_by_id(obj_created["id"]) + res = db.get_or_404(TMonitoringSites, obj_created["id"]) assert ( res.as_dict()["base_site_name"] == site_to_post_with_types["properties"]["base_site_name"] @@ -213,5 +213,5 @@ def test_delete_site(self, sites, monitorings_users): assert r.json["success"] == "Item is successfully deleted" with pytest.raises(Exception) as e: - TMonitoringSites.query.get_or_404(id_base_site) + db.get_or_404(TMonitoringSites, id_base_site) assert "404 Not Found" in str(e.value) diff --git a/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py b/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py index baebf47c4..f9fe90469 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_sites_groups.py @@ -1,6 +1,10 @@ import pytest from flask import url_for +from sqlalchemy import select + +from geonature.utils.env import db + from pypnusershub.tests.utils import set_logged_user_cookie from gn_module_monitoring.monitoring.models import TMonitoringSitesGroups @@ -59,9 +63,11 @@ def test_get_sites_groups_filter_name(self, sites_groups, monitorings_users): assert schema.dump(sites_groups[name_not_present]) not in json_sites_groups def test_serialize_sites_groups(self, sites_groups, sites): - groups = TMonitoringSitesGroups.query.filter( - TMonitoringSitesGroups.id_sites_group.in_( - [s.id_sites_group for s in sites_groups.values()] + groups = db.session.scalars( + select(TMonitoringSitesGroups).where( + TMonitoringSitesGroups.id_sites_group.in_( + [s.id_sites_group for s in sites_groups.values()] + ) ) ).all() schema = MonitoringSitesGroupsSchema() diff --git a/backend/gn_module_monitoring/tests/test_routes/test_visit.py b/backend/gn_module_monitoring/tests/test_routes/test_visit.py index 667d50a07..b9d72b58f 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_visit.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_visit.py @@ -16,11 +16,8 @@ def test_get_visits(self, visits, monitorings_users): ) ) - print(TMonitoringVisits.query.all()) expected_visits = {visit.id_base_visit for visit in visits} current_visits = {visit["id_base_visit"] for visit in r.json["items"]} - print(expected_visits) - print(r.json["items"], current_visits) assert expected_visits.issubset(current_visits) assert all(visit["module"] is not None for visit in r.json["items"]) diff --git a/backend/gn_module_monitoring/tests/test_true.py b/backend/gn_module_monitoring/tests/test_true.py deleted file mode 100644 index 082867288..000000000 --- a/backend/gn_module_monitoring/tests/test_true.py +++ /dev/null @@ -1,6 +0,0 @@ -import pytest - - -class Tests: - def test_true_is_true(self): - assert True == True diff --git a/backend/gn_module_monitoring/tests/test_utils/test_routes.py b/backend/gn_module_monitoring/tests/test_utils/test_routes.py index e81cb8017..6faedda5f 100644 --- a/backend/gn_module_monitoring/tests/test_utils/test_routes.py +++ b/backend/gn_module_monitoring/tests/test_utils/test_routes.py @@ -1,6 +1,8 @@ import pytest from werkzeug.datastructures import MultiDict +from sqlalchemy import select + from gn_module_monitoring.monitoring.models import TMonitoringSites from gn_module_monitoring.monitoring.schemas import MonitoringSitesSchema from gn_module_monitoring.utils.routes import get_limit_page, paginate @@ -21,7 +23,7 @@ def test_paginate(sites): page = 2 res = paginate( - query=TMonitoringSites.query, schema=MonitoringSitesSchema, limit=limit, page=page + query=select(TMonitoringSites), schema=MonitoringSitesSchema, limit=limit, page=page ) assert res.json["page"] == page diff --git a/backend/gn_module_monitoring/utils/routes.py b/backend/gn_module_monitoring/utils/routes.py index e55155529..fdeb67893 100644 --- a/backend/gn_module_monitoring/utils/routes.py +++ b/backend/gn_module_monitoring/utils/routes.py @@ -4,6 +4,8 @@ from flask import Response, g from flask.json import jsonify from geonature.utils.env import DB +from geonature.core.gn_permissions.models import TObjects, PermObject, PermissionAvailable +from geonature.utils.errors import GeoNatureError from pypnusershub.db.models import User from gn_module_monitoring.monitoring.models import ( BibTypeSite, @@ -15,15 +17,15 @@ TModules, TNomenclatures, ) -from geonature.core.gn_permissions.models import TObjects, PermObject, PermissionAvailable -from geonature.utils.errors import GeoNatureError + + from marshmallow import Schema -from sqlalchemy import cast, func, text +from sqlalchemy import cast, func, text, select from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy.orm import Query, load_only, joinedload +from sqlalchemy.orm import load_only, joinedload +from sqlalchemy.sql.expression import Select from werkzeug.datastructures import MultiDict -from gn_module_monitoring.monitoring.queries import Query as MonitoringQuery from gn_module_monitoring.monitoring.schemas import paginate_schema @@ -35,8 +37,8 @@ def get_sort(params: MultiDict, default_sort: str, default_direction) -> Tuple[s return params.pop("sort", default_sort), params.pop("sort_dir", default_direction) -def paginate(query: Query, schema: Schema, limit: int, page: int) -> Response: - result = query.paginate(page=page, error_out=False, per_page=limit) +def paginate(query: Select, schema: Schema, limit: int, page: int) -> Response: + result = DB.paginate(query, page=page, per_page=limit, error_out=False) pagination_schema = paginate_schema(schema) data = pagination_schema().dump( dict(items=result.items, count=result.total, limit=limit, page=page) @@ -45,14 +47,16 @@ def paginate(query: Query, schema: Schema, limit: int, page: int) -> Response: def paginate_scope( - query: Query, schema: Schema, limit: int, page: int, object_code=None + query: Select, schema: Schema, limit: int, page: int, object_code=None ) -> Response: - result = query.paginate(page=page, error_out=False, per_page=limit) + result = DB.paginate(query, page=page, per_page=limit, error_out=False) + pagination_schema = paginate_schema(schema) + datas_allowed = pagination_schema().dump( dict(items=result.items, count=result.total, limit=limit, page=page) ) - cruved_item_dict = get_objet_with_permission_boolean(result.items, object_code=object_code) + cruved_item_dict = get_objet_with_permission_boolean(result, object_code=object_code) for cruved_item in cruved_item_dict: for i, data in enumerate(datas_allowed["items"]): if data[data["pk"]] == cruved_item[data["pk"]]: @@ -60,22 +64,22 @@ def paginate_scope( return jsonify(datas_allowed) -def filter_params(query: MonitoringQuery, params: MultiDict) -> MonitoringQuery: +def filter_params(model, query: Select, params: MultiDict) -> Select: if len(params) != 0: - query = query.filter_by_params(params) + query = model.filter_by_params(query=query, params=params) return query -def sort(query: MonitoringQuery, sort: str, sort_dir: str) -> MonitoringQuery: +def sort(model, query: Select, sort: str, sort_dir: str) -> Select: if sort_dir in ["desc", "asc"]: - query = query.sort(label=sort, direction=sort_dir) + query = model.sort(query=query, label=sort, direction=sort_dir) return query def geojson_query(subquery) -> bytes: subquery_name = "q" subquery = subquery.alias(subquery_name) - query = DB.session.query( + query = select( func.json_build_object( text("'type'"), text("'FeatureCollection'"), @@ -83,7 +87,7 @@ def geojson_query(subquery) -> bytes: func.json_agg(cast(func.st_asgeojson(subquery), JSON)), ) ) - result = query.first() + result = DB.session.execute(query.limit(1)).first() if len(result) > 0: return result[0] return b"" @@ -91,7 +95,8 @@ def geojson_query(subquery) -> bytes: def get_sites_groups_from_module_id(module_id: int): query = ( - TMonitoringSitesGroups.query.options( + select(TMonitoringSitesGroups) + .options( # Load(TMonitoringSitesGroups).raiseload("*"), load_only(TMonitoringSitesGroups.id_sites_group) ) @@ -109,34 +114,36 @@ def get_sites_groups_from_module_id(module_id: int): cor_module_type.c.id_type_site == BibTypeSite.id_nomenclature_type_site, ) .join(TModules, TModules.id_module == cor_module_type.c.id_module) - .filter(TModules.id_module == module_id) + .where(TModules.id_module == module_id) ) - - return query.all() + return DB.session.scalars(query).all() def query_all_types_site_from_site_id(id_site: int): query = ( - BibTypeSite.query.join( + select(BibTypeSite) + .join( cor_type_site, BibTypeSite.id_nomenclature_type_site == cor_type_site.c.id_type_site, ) .join(TBaseSites, cor_type_site.c.id_base_site == TBaseSites.id_base_site) - .filter(cor_type_site.c.id_base_site == id_site) + .where(cor_type_site.c.id_base_site == id_site) ) - return query.all() + + return DB.session.scalars(query).unique().all() def query_all_types_site_from_module_id(id_module: int): query = ( - BibTypeSite.query.join( + select(BibTypeSite) + .join( cor_module_type, BibTypeSite.id_nomenclature_type_site == cor_module_type.c.id_type_site, ) .join(TModules, cor_module_type.c.id_module == TModules.id_module) - .filter(cor_module_type.c.id_module == id_module) + .where(cor_module_type.c.id_module == id_module) ) - return query.all() + return DB.session.scalars(query).unique().all() def filter_according_to_column_type_for_site(query, params): @@ -145,16 +152,16 @@ def filter_according_to_column_type_for_site(query, params): query = ( query.join(TMonitoringSites.types_site) .join(BibTypeSite.nomenclature) - .filter(TNomenclatures.label_fr.ilike(f"%{params_types_site}%")) + .where(TNomenclatures.label_fr.ilike(f"%{params_types_site}%")) ) elif "id_inventor" in params: params_inventor = params.pop("id_inventor") query = query.join( User, User.id_role == TMonitoringSites.id_inventor, - ).filter(User.nom_complet.ilike(f"%{params_inventor}%")) + ).where(User.nom_complet.ilike(f"%{params_inventor}%")) if len(params) != 0: - query = filter_params(query=query, params=params) + query = filter_params(TMonitoringSites, query=query, params=params) return query @@ -171,7 +178,7 @@ def sort_according_to_column_type_for_site(query, sort_label, sort_dir): else: query = query.order_by(User.nom_complet.desc()) else: - query = sort(query=query, sort=sort_label, sort_dir=sort_dir) + query = sort(TMonitoringSites, query=query, sort=sort_label, sort_dir=sort_dir) return query @@ -182,8 +189,8 @@ def get_object_list_monitorings(): :return: """ try: - object_list_monitorings = ( - DB.session.query( + object_list_monitorings = DB.session.execute( + select( PermObject.code_object, ) .join(PermissionAvailable, PermissionAvailable.id_object == PermObject.id_object) @@ -195,8 +202,7 @@ def get_object_list_monitorings(): ), ) .group_by(PermObject.code_object) - .all() - ) + ).all() return object_list_monitorings except Exception as e: raise GeoNatureError("MONITORINGS - get_object_list_monitorings : {}".format(str(e))) @@ -210,20 +216,21 @@ def get_objet_with_permission_boolean( objects_out = [] for object in objects: if module_code: - cruved_object = object.query._get_cruved_scope( + cruved_object = object._get_cruved_scope( module_code=module_code, object_code=object_code ) elif hasattr(object, "module"): - cruved_object = object.query._get_cruved_scope( + cruved_object = object._get_cruved_scope( module_code=object.module.module_code, object_code=object_code ) elif hasattr(object, "module_code"): - cruved_object = object.query._get_cruved_scope( + cruved_object = object._get_cruved_scope( module_code=object.module_code, object_code=object_code ) else: - cruved_object = object.query._get_cruved_scope(object_code=object_code) + cruved_object = object._get_cruved_scope(object_code=object_code) object_out = object.as_dict(depth=depth) + if hasattr(object, "module_code"): object_out["cruved"] = object.get_permission_by_action( module_code=object.module_code, object_code=object_code @@ -233,39 +240,3 @@ def get_objet_with_permission_boolean( objects_out.append(object_out) return objects_out - - -# from gn_module_monitoring.monitoring.definitions import MonitoringPermissions_dict -# from gn_module_monitoring import MODULE_CODE -# def set_permission_global_session(module_code=None,object_type=None): -# # requested_module_code = values.get("module_code") or MODULE_CODE -# module_code = module_code or MODULE_CODE -# current_module = ( -# TModules.query.options(joinedload(TModules.objects)) -# .filter_by(module_code=module_code) -# .first_or_404(f"No module with code {module_code}") -# ) -# g.current_module = current_module - -# # recherche de l'object de permission courrant -# # object_type = values.get("object_type") -# if object_type: -# requested_permission_object_code = MonitoringPermissions_dict.get(object_type) - -# if requested_permission_object_code is None: -# # error ? -# return - -# # Test si l'object de permission existe -# requested_permission_object = TObjects.query.filter_by( -# code_object=requested_permission_object_code -# ).first_or_404( -# f"No permission object with code {requested_permission_object_code}" -# ) - -# # si l'object de permission est associé au module => il devient l'objet courant -# # - sinon se sera 'ALL' par defaut -# for module_perm_object in current_module.objects: -# if module_perm_object == requested_permission_object: -# g.current_object = requested_permission_object -# return diff --git a/dependencies/GeoNature b/dependencies/GeoNature index 7ee68c555..f427e36bd 160000 --- a/dependencies/GeoNature +++ b/dependencies/GeoNature @@ -1 +1 @@ -Subproject commit 7ee68c5555ecd4e3d1b252dd78254a492cd2d7eb +Subproject commit f427e36bde02817b5974925674a3c055bb1cefae