Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project, organisation teams deletion and other fixes #6618

Merged
merged 1 commit into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading