From acee91ed16fc1c410fa81149673c54a1c22318d8 Mon Sep 17 00:00:00 2001 From: Sam <78538841+spwoodcock@users.noreply.github.com> Date: Fri, 12 Apr 2024 18:26:35 +0000 Subject: [PATCH] fix: XLSForm template download endpoint for specified categories (#1441) --- src/backend/app/central/central_crud.py | 23 ++++------- src/backend/app/central/central_routes.py | 22 +++-------- src/backend/app/helpers/helper_routes.py | 18 ++++++++- src/backend/app/models/enums.py | 12 ++++++ src/backend/app/projects/project_routes.py | 39 ------------------- src/backend/app/static/__init__.py | 23 ----------- .../app/static/template/template.geojson | 26 ------------- 7 files changed, 40 insertions(+), 123 deletions(-) delete mode 100644 src/backend/app/static/__init__.py delete mode 100644 src/backend/app/static/template/template.geojson diff --git a/src/backend/app/central/central_crud.py b/src/backend/app/central/central_crud.py index 11b7bf086a..170c937857 100644 --- a/src/backend/app/central/central_crud.py +++ b/src/backend/app/central/central_crud.py @@ -41,7 +41,7 @@ javarosa_to_geojson_geom, parse_and_filter_geojson, ) -from app.models.enums import HTTPStatus +from app.models.enums import HTTPStatus, XLSFormType from app.projects import project_schemas @@ -291,31 +291,22 @@ def list_submissions( return submissions -def get_form_list(db: Session, skip: int, limit: int): - """Returns the list of id and title of xforms from the database.""" +async def get_form_list(db: Session) -> dict: + """Returns the dict of {id:title} for XLSForms in the database.""" try: - categories_to_filter = [ - "amenities", - "camping", - "cemeteries", - "education", - "nature", - "places", - "wastedisposal", - "waterpoints", - ] + include_categories = [category.value for category in XLSFormType] sql_query = text( """ SELECT id, title FROM xlsforms - WHERE title NOT IN + WHERE title IN (SELECT UNNEST(:categories)); """ ) - result = db.execute(sql_query, {"categories": categories_to_filter}).fetchall() + result = db.execute(sql_query, {"categories": include_categories}).fetchall() - result_dict = [{"id": row.id, "title": row.title} for row in result] + result_dict = {row.id: row.title for row in result} return result_dict diff --git a/src/backend/app/central/central_routes.py b/src/backend/app/central/central_routes.py index c0eed23c47..746728df6e 100644 --- a/src/backend/app/central/central_routes.py +++ b/src/backend/app/central/central_routes.py @@ -55,26 +55,14 @@ async def list_projects(): @router.get("/list-forms") async def get_form_lists( - db: Session = Depends(database.get_db), skip: int = 0, limit: int = 100 -): - """Get a list of all XForms on ODK Central. - - Option to skip a certain number of records and limit the number of - records returned. - - - Parameters: - skip (int): the number of records to skip before starting to retrieve records. - Defaults to 0 if not provided. - limit (int): the maximum number of records to retrieve. - Defaults to 10 if not provided. - + db: Session = Depends(database.get_db), +) -> dict: + """Get a list of all XLSForms available in FMTM. Returns: - list[dict]: list of id:title dicts of each XForm record. + dict: JSON of {id:title} with each XLSForm record. """ - # NOTE runs in separate thread using run_in_threadpool - forms = await run_in_threadpool(lambda: central_crud.get_form_list(db, skip, limit)) + forms = await central_crud.get_form_list(db) return forms diff --git a/src/backend/app/helpers/helper_routes.py b/src/backend/app/helpers/helper_routes.py index dea11d9005..f61523b088 100644 --- a/src/backend/app/helpers/helper_routes.py +++ b/src/backend/app/helpers/helper_routes.py @@ -27,7 +27,8 @@ UploadFile, ) from fastapi.exceptions import HTTPException -from fastapi.responses import Response +from fastapi.responses import FileResponse, Response +from osm_fieldwork.xlsforms import xlsforms_path from app.auth.osm import AuthUser, login_required from app.central.central_crud import ( @@ -40,7 +41,7 @@ javarosa_to_geojson_geom, parse_and_filter_geojson, ) -from app.models.enums import GeometryType, HTTPStatus +from app.models.enums import GeometryType, HTTPStatus, XLSFormType router = APIRouter( prefix="/helper", @@ -49,6 +50,19 @@ ) +@router.get("/download-template-xlsform") +async def download_template( + category: XLSFormType, + current_user: AuthUser = Depends(login_required), +): + """Download an XLSForm template to fill out.""" + xlsform_path = f"{xlsforms_path}/{category}.xls" + if Path(xlsform_path).exists: + return FileResponse(xlsform_path, filename="form.xls") + else: + raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Form not found") + + @router.post("/append-geojson-properties") async def append_required_geojson_properties( geojson: UploadFile, diff --git a/src/backend/app/models/enums.py b/src/backend/app/models/enums.py index c5eb987197..1368fd20d7 100644 --- a/src/backend/app/models/enums.py +++ b/src/backend/app/models/enums.py @@ -312,3 +312,15 @@ class GeometryType(str, Enum): Polygon = "Polygon" LineString = "LineString" Point = "Point" + + +class XLSFormType(str, Enum): + """Enum for XLSForm categories.""" + + BUILDING = "buildings" + HIGHWAYS = "highways" + HEALTH = "health" + TOILETS = "toilets" + RELIGIOUS = "religious" + LANDUSAGE = "landusage" + WATERWAYS = "waterways" diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 128472ffa6..5e7c4f3291 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -56,7 +56,6 @@ from app.models.enums import TILES_FORMATS, TILES_SOURCE, HTTPStatus from app.organisations import organisation_deps from app.projects import project_crud, project_deps, project_schemas -from app.static import data_path from app.submissions import submission_crud from app.tasks import tasks_crud @@ -836,20 +835,6 @@ async def update_project_form( return project -@router.get("/download_template/") -async def download_template( - category: str, - db: Session = Depends(database.get_db), - current_user: AuthUser = Depends(mapper), -): - """Download an XLSForm template to fill out.""" - xlsform_path = f"{xlsforms_path}/{category}.xls" - if os.path.exists(xlsform_path): - return FileResponse(xlsform_path, filename="form.xls") - else: - raise HTTPException(status_code=404, detail="Form not found") - - @router.get("/{project_id}/download") async def download_project_boundary( project_id: int, @@ -1155,30 +1140,6 @@ async def get_task_status( ) -@router.get("/templates/") -async def get_template_file( - file_type: str = Query( - ..., enum=["data_extracts", "form"], description="Choose file type" - ), - current_user: AuthUser = Depends(login_required), -): - """Get template file. - - Args: file_type: Type of template file. - - returns: Requested file as a FileResponse. - """ - file_type_paths = { - "data_extracts": f"{data_path}/template/template.geojson", - "form": f"{data_path}/template/template.xls", - } - file_path = file_type_paths.get(file_type) - filename = file_path.split("/")[-1] - return FileResponse( - file_path, media_type="application/octet-stream", filename=filename - ) - - @router.get( "/project_dashboard/{project_id}", response_model=project_schemas.ProjectDashboard ) diff --git a/src/backend/app/static/__init__.py b/src/backend/app/static/__init__.py deleted file mode 100644 index b28277a6f6..0000000000 --- a/src/backend/app/static/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2022, 2023 Humanitarian OpenStreetMap Team -# -# This file is part of FMTM. -# -# FMTM is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# FMTM is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with FMTM. If not, see . - - -"""Static files.""" - -import os - -data_path = os.path.dirname(os.path.abspath(__file__)) diff --git a/src/backend/app/static/template/template.geojson b/src/backend/app/static/template/template.geojson deleted file mode 100644 index 58faad9eb8..0000000000 --- a/src/backend/app/static/template/template.geojson +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "FeatureCollection", - "name": "sample_data_extract", - "crs": { - "type": "name", - "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } - }, - "features": [ - { - "type": "Feature", - "properties": { "id": 1, "title": null }, - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [85.927672852225669, 26.730898147144199], - [85.92779345357259, 26.731001012998917], - [85.927896319427305, 26.730859129061376], - [85.927779265178827, 26.730777545797284], - [85.927672852225669, 26.730898147144199] - ] - ] - } - } - ] -}