Skip to content

Commit

Permalink
Project, organisation teams deletion and other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
prabinoid committed Nov 6, 2024
1 parent 028972c commit eb6a842
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 23 deletions.
6 changes: 3 additions & 3 deletions backend/api/projects/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,10 @@ async def delete(
},
status_code=403,
)

try:
await ProjectAdminService.delete_project(project_id, user.id, db)
return JSONResponse(content={"Success": "Project deleted"}, status_code=200)
async with db.transaction():
await ProjectAdminService.delete_project(project_id, user.id, db)
return JSONResponse(content={"Success": "Project deleted"}, status_code=200)
except ProjectAdminServiceError as e:
return JSONResponse(
content={"Error": str(e).split("-")[1], "SubCode": str(e).split("-")[0]},
Expand Down
36 changes: 30 additions & 6 deletions backend/models/postgis/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,11 +627,8 @@ async def update(self, project_dto: ProjectDTO, db: Database):
self.allowed_users.append(user)

# Update teams and projects relationship.
self.teams = []
await db.execute(delete(ProjectTeams).where(ProjectTeams.project_id == self.id))
if hasattr(project_dto, "project_teams") and project_dto.project_teams:
await db.execute(
delete(ProjectTeams).where(ProjectTeams.project_id == self.id)
)
for team_dto in project_dto.project_teams:
team = await Team.get(team_dto.team_id, db)
if team is None:
Expand Down Expand Up @@ -814,8 +811,35 @@ async def update(self, project_dto: ProjectDTO, db: Database):
)

async def delete(self, db: Database):
"""Deletes the current model from the DB"""
await db.execute(delete(Project.__table__).where(Project.id == self.id))
"""Deletes the current project and related records from the database using raw SQL."""
# List of tables to delete from, in the order required to satisfy foreign key constraints
related_tables = [
"project_favorites",
"project_custom_editors",
"project_interests",
"project_priority_areas",
"project_allowed_users",
"project_teams",
"task_invalidation_history",
"task_history",
"tasks",
"project_info",
"project_chat",
]

# Start a transaction to ensure atomic deletion
async with db.transaction():
# Loop through each table and execute the delete query
for table in related_tables:
await db.execute(
f"DELETE FROM {table} WHERE project_id = :project_id",
{"project_id": self.id},
)

# Finally, delete the project itself
await db.execute(
"DELETE FROM projects WHERE id = :project_id", {"project_id": self.id}
)

@staticmethod
async def exists(project_id: int, db: Database) -> bool:
Expand Down
25 changes: 19 additions & 6 deletions backend/models/postgis/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
ForeignKey,
String,
insert,
delete,
)
from sqlalchemy.orm import relationship, backref
from backend.exceptions import NotFound
Expand Down Expand Up @@ -230,12 +229,26 @@ async def update(team, team_dto: TeamDTO, db: Database):
await Team.update_team_members(team, team_dto, db)

async def delete(self, db: Database):
"""Deletes the current model from the DB"""
await db.execute(delete(Team.__table__).where(Team.id == self.id))
"""Deletes the current team and its members from the DB"""

# Delete team members associated with this team
delete_team_members_query = """
DELETE FROM team_members WHERE team_id = :team_id
"""
await db.execute(delete_team_members_query, values={"team_id": self.id})

def can_be_deleted(self) -> bool:
"""A Team can be deleted if it doesn't have any projects"""
return len(self.projects) == 0
# Delete the team
delete_team_query = """
DELETE FROM teams WHERE id = :team_id
"""
await db.execute(delete_team_query, values={"team_id": self.id})

@staticmethod
async def can_be_deleted(team_id: int, db: Database) -> bool:
"""Check if a Team can be deleted by querying for associated projects"""
query = "SELECT COUNT(*) FROM project_teams WHERE team_id = :team_id"
result = await db.fetch_one(query, {"team_id": team_id})
return result[0] == 0

async def get(team_id: int, db: Database):
"""
Expand Down
5 changes: 2 additions & 3 deletions backend/services/organisation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,8 @@ async def delete_organisation(organisation_id: int, db: Database):
except Exception as e:
raise HTTPException(status_code=500, detail="Deletion failed") from e
else:
raise HTTPException(
status_code=400,
detail="Organisation has projects or teams, cannot be deleted",
raise OrganisationServiceError(
"Organisation has projects, cannot be deleted"
)

@staticmethod
Expand Down
8 changes: 5 additions & 3 deletions backend/services/project_admin_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ async def update_project(
)

if project_dto.license_id:
ProjectAdminService._validate_imagery_licence(project_dto.license_id)
await ProjectAdminService._validate_imagery_licence(
project_dto.license_id, db
)

# To be handled before reaching this function
if await ProjectAdminService.is_user_action_permitted_on_project(
Expand All @@ -155,10 +157,10 @@ async def update_project(
return project

@staticmethod
def _validate_imagery_licence(license_id: int):
async def _validate_imagery_licence(license_id: int, db: Database):
"""Ensures that the suppliced license Id actually exists"""
try:
LicenseService.get_license_as_dto(license_id)
await LicenseService.get_license_as_dto(license_id, db)
except NotFound:
raise ProjectAdminServiceError(
f"RequireLicenseId- LicenseId {license_id} not found"
Expand Down
3 changes: 1 addition & 2 deletions backend/services/team_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,7 @@ async def is_user_team_manager(team_id: int, user_id: int, db: Database) -> bool
async def delete_team(team_id: int, db: Database):
"""Deletes a team"""
team = await TeamService.get_team_by_id(team_id, db)

if Team.can_be_deleted(team):
if await Team.can_be_deleted(team_id, db):
await Team.delete(team, db)
return JSONResponse(content={"Success": "Team deleted"}, status_code=200)
else:
Expand Down

0 comments on commit eb6a842

Please sign in to comment.