From 0906fb0a810236f92a6a485f1d8b62daed0c31eb Mon Sep 17 00:00:00 2001 From: prabinoid <38830224+prabinoid@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:20:29 +0545 Subject: [PATCH] Aoi, tasks and project non geometries exports and cleanups --- backend/api/projects/resources.py | 39 ++++++---- backend/api/tasks/resources.py | 11 +-- backend/models/postgis/task.py | 117 ------------------------------ 3 files changed, 31 insertions(+), 136 deletions(-) diff --git a/backend/api/projects/resources.py b/backend/api/projects/resources.py index 74eca44770..28b285409a 100644 --- a/backend/api/projects/resources.py +++ b/backend/api/projects/resources.py @@ -6,7 +6,7 @@ import geojson from databases import Database from fastapi import APIRouter, Depends, Request -from fastapi.responses import FileResponse, JSONResponse +from fastapi.responses import FileResponse, JSONResponse, StreamingResponse from loguru import logger from backend.db import get_db @@ -1043,13 +1043,20 @@ async def get(request: Request, project_id: int, db: Database = Depends(get_db)) project_dto = await ProjectService.get_project_dto_for_mapper( project_id, None, db, locale, True ) - # TODO Send file. + # Handle file download if requested if as_file: - return send_file( - io.BytesIO(geojson.dumps(project_dto).encode("utf-8")), - mimetype="application/json", - as_attachment=True, - download_name=f"project_{str(project_id)}.json", + project_dto_str = geojson.dumps( + project_dto, indent=4 + ) # Convert to GeoJSON string + file_bytes = io.BytesIO(project_dto_str.encode("utf-8")) + file_bytes.seek(0) # Reset stream position + + return StreamingResponse( + file_bytes, + media_type="application/geo+json", + headers={ + "Content-Disposition": f'attachment; filename="project_{project_id}.geojson"' + }, ) return project_dto @@ -1158,15 +1165,19 @@ async def get(request: Request, project_id: int, db: Database = Depends(get_db)) ) project_aoi = await ProjectService.get_project_aoi(project_id, db) - # TODO as file. + if as_file: - return send_file( - io.BytesIO(geojson.dumps(project_aoi).encode("utf-8")), - mimetype="application/json", - as_attachment=True, - download_name=f"{str(project_id)}.geojson", - ) + aoi_str = geojson.dumps(project_aoi, indent=4) # Convert AOI to GeoJSON string + file_bytes = io.BytesIO(aoi_str.encode("utf-8")) + file_bytes.seek(0) # Reset stream position + return StreamingResponse( + file_bytes, + media_type="application/geo+json", + headers={ + "Content-Disposition": f'attachment; filename="{project_id}.geojson"' + }, + ) return project_aoi diff --git a/backend/api/tasks/resources.py b/backend/api/tasks/resources.py index a560ee6ca0..e223eea1f7 100644 --- a/backend/api/tasks/resources.py +++ b/backend/api/tasks/resources.py @@ -121,18 +121,19 @@ async def get(request: Request, project_id: int, db: Database = Depends(get_db)) tasks_json = await ProjectService.get_project_tasks(db, int(project_id), tasks) if as_file: - tasks_json = json.dumps(tasks_json, indent=4) # Pretty-printed JSON - file_bytes = io.BytesIO(tasks_json.encode("utf-8")) + tasks_str = json.dumps(tasks_json, indent=4) # Pretty-printed GeoJSON + file_bytes = io.BytesIO(tasks_str.encode("utf-8")) file_bytes.seek(0) # Reset stream position - # Return the file response for download + # Return the GeoJSON file response for download return StreamingResponse( file_bytes, - media_type="application/json", + media_type="application/geo+json", headers={ - "Content-Disposition": f'attachment; filename="{project_id}-tasks.json"' + "Content-Disposition": f'attachment; filename="{project_id}-tasks.geojson"' }, ) + return tasks_json except ProjectServiceError as e: return JSONResponse(content={"Error": str(e)}, status_code=403) diff --git a/backend/models/postgis/task.py b/backend/models/postgis/task.py index 622315a8d9..8d0ff166ec 100644 --- a/backend/models/postgis/task.py +++ b/backend/models/postgis/task.py @@ -476,48 +476,6 @@ async def remove_duplicate_task_history_rows( await db.execute(query=duplicate_query, values=values) - # @staticmethod - # async def update_expired_and_locked_actions( - # project_id: int, task_id: int, expiry_date: datetime, action_text: str, session - # ): - # """ - # Sets auto unlock state to all not finished actions, that are older then the expiry date. - # Action is considered as a not finished, when it is in locked state and doesn't have action text - # :param project_id: Project ID in scope - # :param task_id: Task in scope - # :param expiry_date: Action created before this date is treated as expired - # :param action_text: Text which will be set for all changed actions - # :return: - # """ - # result = await session.execute( - # select(TaskHistory).filter( - # TaskHistory.task_id == task_id, - # TaskHistory.project_id == project_id, - # TaskHistory.action_text.is_(None), - # TaskHistory.action.in_( - # [ - # TaskAction.LOCKED_FOR_VALIDATION.name, - # TaskAction.LOCKED_FOR_MAPPING.name, - # TaskAction.EXTENDED_FOR_MAPPING.name, - # TaskAction.EXTENDED_FOR_VALIDATION.name, - # ] - # ), - # TaskHistory.action_date <= expiry_date, - # ) - # ) - # all_expired = result.scalars().all() - # for task_history in all_expired: - # unlock_action = ( - # TaskAction.AUTO_UNLOCKED_FOR_MAPPING - # if task_history.action in ["LOCKED_FOR_MAPPING", "EXTENDED_FOR_MAPPING"] - # else TaskAction.AUTO_UNLOCKED_FOR_VALIDATION - # ) - - # task_history.set_auto_unlock_action(unlock_action) - # task_history.action_text = action_text - - # await session.commit() - @staticmethod async def update_expired_and_locked_actions( task_id: int, @@ -663,25 +621,6 @@ async def get_last_locked_action(project_id: int, task_id: int, db: Database): db, ) - # @staticmethod - # async def get_last_locked_or_auto_unlocked_action( - # project_id: int, task_id: int, session - # ): - # """Gets the most recent task history record with locked or auto unlocked action for the task""" - - # result = await TaskHistory.get_last_action_of_type( - # project_id, - # task_id, - # [ - # TaskAction.LOCKED_FOR_MAPPING.name, - # TaskAction.LOCKED_FOR_VALIDATION.name, - # TaskAction.AUTO_UNLOCKED_FOR_MAPPING.name, - # TaskAction.AUTO_UNLOCKED_FOR_VALIDATION.name, - # ], - # session, - # ) - # return result - @staticmethod async def get_last_locked_or_auto_unlocked_action( task_id: int, project_id: int, db: Database @@ -913,48 +852,6 @@ async def get_tasks_by_status(project_id: int, status: str, db: Database): async def auto_unlock_delta(): return parse_duration(settings.TASK_AUTOUNLOCK_AFTER) - # @staticmethod - # async def auto_unlock_tasks(project_id: int, db: Database): - # """Unlock all tasks locked for longer than the auto-unlock delta""" - # expiry_delta = await Task.auto_unlock_delta() - # lock_duration = (datetime.datetime.min + expiry_delta).time().isoformat() - - # expiry_date = datetime.datetime.utcnow() - expiry_delta - - # old_tasks = await db.execute( - # select(Task.id) - # .join( - # TaskHistory, - # (Task.id == TaskHistory.task_id) - # & (Task.project_id == TaskHistory.project_id), - # ) - # .filter(Task.task_status.in_([1, 3])) - # .filter( - # TaskHistory.action.in_( - # [ - # "EXTENDED_FOR_MAPPING", - # "EXTENDED_FOR_VALIDATION", - # "LOCKED_FOR_VALIDATION", - # "LOCKED_FOR_MAPPING", - # ] - # ) - # ) - # .filter(TaskHistory.action_text.is_(None)) - # .filter(Task.project_id == project_id) - # .filter(TaskHistory.action_date <= expiry_date) - # ) - # old_tasks = old_tasks.scalars().all() - # if not old_tasks: - # # no tasks older than the delta found, return without further processing - # return - - # for old_task_id in old_tasks: - # task = await db.get(Task, (old_task_id, project_id)) - # if task: - # await Task.auto_unlock_expired_tasks( - # expiry_date, lock_duration, db - # ) - @staticmethod async def auto_unlock_tasks(project_id: int, db: Database): """Unlock all tasks locked for longer than the auto-unlock delta.""" @@ -992,20 +889,6 @@ async def auto_unlock_tasks(project_id: int, db: Database): for task_id in old_task_ids: await Task.auto_unlock_expired_tasks(task_id, project_id, expiry_date, db) - # async def auto_unlock_expired_tasks(self, expiry_date, lock_duration, session): - # """Unlock all tasks locked before expiry date. Clears task lock if needed""" - # await TaskHistory.update_expired_and_locked_actions( - # self.project_id, self.id, expiry_date, lock_duration, session - # ) - # last_action = await TaskHistory.get_last_locked_or_auto_unlocked_action( - # self.project_id, self.id, session - # ) - # if last_action.action in [ - # "AUTO_UNLOCKED_FOR_MAPPING", - # "AUTO_UNLOCKED_FOR_VALIDATION", - # ]: - # self.clear_lock() - @staticmethod async def auto_unlock_expired_tasks( task_id: int, project_id: int, expiry_date: datetime, db: Database