Skip to content

Commit

Permalink
Possibilty to add custom export views in synthese - closes #2955
Browse files Browse the repository at this point in the history
  • Loading branch information
TheoLechemia committed Mar 18, 2024
1 parent 7bf6332 commit 83ffac9
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 11 deletions.
22 changes: 19 additions & 3 deletions backend/geonature/core/gn_synthese/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,16 +531,29 @@ def export_observations_web(permissions):
POST parameters: Use a list of id_synthese (in POST parameters) to filter the v_synthese_for_export_view
:query str export_format: str<'csv', 'geojson', 'shapefiles', 'gpkg'>
:query str export_format: str<'csv', 'geojson', 'shapefiles', 'gpkg'>
"""
params = request.args
# set default to csv
export_format = params.get("export_format", "csv")
view_name_param = params.get("view_name", "gn_synthese.v_synthese_for_export")
# Test export_format
if not export_format in current_app.config["SYNTHESE"]["EXPORT_FORMAT"]:
if export_format not in current_app.config["SYNTHESE"]["EXPORT_FORMAT"]:
raise BadRequest("Unsupported format")

# Test export view name is config params for security reason
if view_name_param != "gn_synthese.v_synthese_for_export" and view_name_param not in map(
lambda exp: exp["view_name"],
current_app.config["SYNTHESE"]["EXPORT_OBSERVATIONS_CUSTOM_VIEWS"],
):
raise Forbidden("This view is not available for export")
try:
schema_name, view_name = view_name_param.split(".")
except ValueError:
raise BadRequest("view_name parameter must be a string with schema dot view_name")

# get list of id synthese from POST
id_list = request.get_json()

Expand All @@ -555,13 +568,16 @@ def export_observations_web(permissions):
# Useful to have geom column so that they can be replaced by blurred geoms
# (only if the user has sensitive permissions)
export_view = GenericTableGeo(
tableName="v_synthese_for_export",
schemaName="gn_synthese",
tableName=view_name,
schemaName=schema_name,
engine=DB.engine,
geometry_field=None,
srid=local_srid,
)

if "id_synthese" not in map(lambda col: col.name, export_view.db_cols):
raise BadRequest(f"The custom view {view_name} has not 'id_synthese' mandatory column")

# If there is no sensitive permissions => same path as before blurring implementation
if not blurring_permissions:
# Get the CTE for synthese filtered by user permissions
Expand Down
25 changes: 25 additions & 0 deletions backend/geonature/tests/test_synthese.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,31 @@ def test_export(self, users):
)
assert response.status_code == 200

@pytest.mark.parametrize(
"view_name, response_status_code",
[
("gn_synthese.v_synthese_for_web_app", 200),
("gn_synthese.not_in_config", 403),
("v_synthese_for_web_app", 400),
("gn_synthese.v_metadata_for_export", 400),
],
)
def test_export_observations_custom_view(self, users, app, view_name, response_status_code):
set_logged_user(self.client, users["self_user"])
if view_name != "gn_synthese.not_in_config":
app.config["SYNTHESE"]["EXPORT_OBSERVATIONS_CUSTOM_VIEWS"] = [
{"label": "Test export custom", "view_name": view_name}
]
response = self.client.post(
url_for("gn_synthese.export_observations_web"),
json=[1, 2, 3],
query_string={
"export_format": "geojson",
"view_name": view_name,
},
)
assert response.status_code == response_status_code

def test_export_observations(self, users, synthese_data, synthese_sensitive_data, modules):
data_synthese = synthese_data.values()
data_synthese_sensitive = synthese_sensitive_data.values()
Expand Down
8 changes: 8 additions & 0 deletions backend/geonature/utils/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ class GnFrontEndConf(Schema):
DISPLAY_EMAIL_DISPLAY_INFO = fields.List(fields.String(), load_default=["NOM_VERN"])


class ExportObservationSchema(Schema):
label = fields.String()
view_name = fields.String()


class Synthese(Schema):
# --------------------------------------------------------------------
# SYNTHESE - SEARCH FORM
Expand Down Expand Up @@ -364,6 +369,9 @@ class Synthese(Schema):
# --------------------------------------------------------------------
# SYNTHESE - DOWNLOADS (AKA EXPORTS)
EXPORT_COLUMNS = fields.List(fields.String(), load_default=DEFAULT_EXPORT_COLUMNS)
EXPORT_OBSERVATIONS_CUSTOM_VIEWS = fields.List(
fields.Nested(ExportObservationSchema), load_default=[]
)
# Certaines colonnes sont obligatoires pour effectuer les filtres CRUVED
EXPORT_ID_SYNTHESE_COL = fields.String(load_default="id_synthese")
EXPORT_ID_DATASET_COL = fields.String(load_default="jdd_id")
Expand Down
4 changes: 4 additions & 0 deletions config/default_config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ MEDIA_CLEAN_CRONTAB = "0 1 * * *"
# Formats d'export disponibles ["csv", "geojson", "shapefile", "gpkg"]
EXPORT_FORMAT = ["csv", "geojson", "shapefile"]

# Vues d'export personnalisées
EXPORT_OBSERVATIONS_CUSTOM_VIEWS = [
{"label" : "export customisé", "view_name"= "gn_synthese.ma_vue_personnalisee"}
]
# Noms des colonnes obligatoires de la vue ``gn_synthese.v_metadata_for_export``
EXPORT_METADATA_ID_DATASET_COL = "jdd_id"
EXPORT_METADATA_ACTOR_COL = "acteurs"
Expand Down
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ CHANGELOG

- [Synthèse] Possibilité d'ajouter des champs supplémentaires à la liste de résultats via le paramètre `ADDITIONAL_COLUMNS_FRONTEND`. Ces champs sont masqués par défaut et controlables depuis l'interface (#2946)

- [Synthèse] Possibilité d'ajouter des exports customisés basé sur des vues SQL via le paramètre `EXPORT_OBSERVATIONS_CUSTOM_VIEWS`

2.14.0 - Talpa europaea 👓 (2024-02-28)
---------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion frontend/cypress/e2e/synthese-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('Tests gn_synthese', () => {
// });

it('Should download data at the csv format', function () {
cy.intercept('POST', '/synthese/export_observations?export_format=csv').as('exportCall');
cy.intercept('POST', '/synthese/export_observations?export_format=csv**').as('exportCall');

cy.get('[data-qa="synthese-download-btn"]').click();
cy.get('[data-qa="download-csv"]').click({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ export class SyntheseDataService {
return this._api.get<any>(`${this.config.API_ENDPOINT}/synthese/taxons_tree`);
}

downloadObservations(idSyntheseList: Array<number>, format: string) {
downloadObservations(idSyntheseList: Array<number>, format: string, view_name: string) {
this.isDownloading = true;
const queryString = new HttpParams().set('export_format', format);

let queryString = new HttpParams().set('export_format', format);
queryString = queryString.set('view_name', view_name);
const source = this._api.post(
`${this.config.API_ENDPOINT}/synthese/export_observations`,
idSyntheseList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,37 @@

<div *ngIf="!tooManyObs || syntheseConfig.NB_MAX_OBS_EXPORT > syntheseConfig.NB_MAX_OBS_MAP">
<div class="my-3 pt-2">
<h5 class="second-color">Télécharger les observations</h5>
<h5 class="second-color">Télécharger les observations - format standard</h5>
<button
[disabled]="_dataService.isDownloading"
style="margin-left: 5px"
*ngFor="let format of syntheseConfig.EXPORT_FORMAT"
mat-raised-button
(click)="downloadObservations(format)"
(click)="downloadObservations(format, 'gn_synthese.v_synthese_for_export')"
type="button"
class="buttonLoad button-success format-btn"
[attr.data-qa]="'download-' + format"
>
Format {{ format }}
</button>
</div>
<div
*ngFor="let export of syntheseConfig.EXPORT_OBSERVATIONS_CUSTOM_VIEWS"
class="my-3 pt-2"
>
<h5 class="second-color">Télécharger les observations - {{ export.label }}</h5>
<button
[disabled]="_dataService.isDownloading"
style="margin-left: 5px"
*ngFor="let format of syntheseConfig.EXPORT_FORMAT"
mat-raised-button
(click)="downloadObservations(format, export.view_name)"
type="button"
class="buttonLoad button-success format-btn"
>
Format {{ format }}
</button>
</div>

<div class="my-3 pt-2">
<h5 class="second-color">Télécharger les taxons</h5>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class SyntheseModalDownloadComponent {
this.syntheseConfig = this.config.SYNTHESE;
}

downloadObservations(format) {
this._dataService.downloadObservations(this._storeService.idSyntheseList, format);
downloadObservations(format, view_name) {
this._dataService.downloadObservations(this._storeService.idSyntheseList, format, view_name);
}

downloadTaxons(format, filename) {
Expand Down

0 comments on commit 83ffac9

Please sign in to comment.