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

* Export projects as csv * Project partnership filters * Changeset comment population on project creation and update * Cleanups #6660

Merged
merged 1 commit into from
Dec 17, 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
61 changes: 61 additions & 0 deletions backend/api/projects/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ProjectSearchDTO,
)
from backend.models.dtos.user_dto import AuthUserDTO
from backend.models.postgis.statuses import UserRole
from backend.services.organisation_service import OrganisationService
from backend.services.project_admin_service import (
InvalidData,
Expand Down Expand Up @@ -545,6 +546,10 @@ def setup_search_dto(request) -> ProjectSearchDTO:
search_dto.last_updated_lte = request.query_params.get("lastUpdatedTo")
search_dto.created_gte = request.query_params.get("createdFrom")
search_dto.created_lte = request.query_params.get("createdTo")
search_dto.partner_id = request.query_params.get("partnerId")
search_dto.partnership_from = request.query_params.get("partnershipFrom")
search_dto.partnership_to = request.query_params.get("partnershipTo")
search_dto.download_as_csv = request.query_params.get("downloadAsCSV")

# See https://github.com/hotosm/tasking-manager/pull/922 for more info
try:
Expand All @@ -565,6 +570,7 @@ def setup_search_dto(request) -> ProjectSearchDTO:

if request.query_params.get("managedByMe") == "true":
search_dto.managed_by = authenticated_user_id

if request.query_params.get("basedOnMyInterests") == "true":
search_dto.based_on_user_interests = authenticated_user_id

Expand Down Expand Up @@ -740,6 +746,61 @@ async def get(
if user_id:
user = await UserService.get_user_by_id(user_id, db)
search_dto = setup_search_dto(request)

if search_dto.omit_map_results and search_dto.download_as_csv:
return JSONResponse(
content={
"Error": "omitMapResults and downloadAsCSV cannot be both set to true"
},
status_code=400,
)

if (
search_dto.partnership_from is not None
or search_dto.partnership_to is not None
) and search_dto.partner_id is None:
return JSONResponse(
content={
"Error": "partnershipFrom or partnershipTo cannot be provided without partnerId"
},
status_code=400,
)

if (
search_dto.partner_id is not None
and search_dto.partnership_from is not None
and search_dto.partnership_to is not None
and search_dto.partnership_from > search_dto.partnership_to
):
return JSONResponse(
content={
"Error": "partnershipFrom cannot be greater than partnershipTo"
},
status_code=400,
)

if any(
map(
lambda x: x is not None,
[
search_dto.partner_id,
search_dto.partnership_from,
search_dto.partnership_to,
],
)
) and (user is None or not user.role == UserRole.ADMIN.value):
error_msg = "Only admins can search projects by partnerId, partnershipFrom, partnershipTo"
return JSONResponse(content={"Error": error_msg}, status_code=401)

if search_dto.download_as_csv:
all_results_csv = await ProjectSearchService.search_projects_as_csv(
search_dto, user, db, True
)
return StreamingResponse(
iter([all_results_csv]),
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=data.csv"},
)
results_dto = await ProjectSearchService.search_projects(search_dto, user, db)
return results_dto
except NotFound:
Expand Down
12 changes: 11 additions & 1 deletion backend/models/dtos/project_dto.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import date, datetime
from typing import Dict, List, Optional, Union, Any
from typing import Any, Dict, List, Optional, Union

from fastapi import HTTPException
from pydantic import BaseModel, Field, root_validator
Expand Down Expand Up @@ -368,6 +368,10 @@ class ProjectSearchDTO(BaseModel):
last_updated_gte: Optional[str] = None
created_lte: Optional[str] = None
created_gte: Optional[str] = None
partner_id: Optional[int] = None
partnership_from: Optional[str] = None
partnership_to: Optional[str] = None
download_as_csv: Optional[bool] = None

def __hash__(self):
"""Make object hashable so we can cache user searches"""
Expand Down Expand Up @@ -441,6 +445,12 @@ class ListSearchResultDTO(BaseModel):
total_contributors: Optional[int] = Field(alias="totalContributors", default=None)
country: Optional[List[str]] = Field(default=None)

# csv fields
creation_date: Optional[datetime] = Field(alias="creationDate", default=None)
author: Optional[str] = None
partner_names: Optional[List[str]] = Field(default=None, alias="partnerNames")
total_area: Optional[float] = Field(None, alias="totalAreaSquareKilometers")

class Config:
populate_by_name = True

Expand Down
19 changes: 15 additions & 4 deletions backend/models/postgis/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
from geoalchemy2.shape import to_shape
from loguru import logger
from shapely.geometry import shape
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.hybrid import hybrid_property


from sqlalchemy import (
BigInteger,
Boolean,
Expand All @@ -33,6 +29,8 @@
select,
update,
)
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref, relationship

from backend.config import settings
Expand Down Expand Up @@ -358,6 +356,19 @@ async def create(self, project_name: str, db: Database):
project_id=project, locale="en", name=project_name
)
)
# Set the default changeset comment
default_comment = settings.DEFAULT_CHANGESET_COMMENT
self.changeset_comment = (
f"{default_comment}-{project} {self.changeset_comment}"
if self.changeset_comment is not None
else f"{default_comment}-{project}"
)
# Update the changeset comment in the database
await db.execute(
Project.__table__.update()
.where(Project.__table__.c.id == project)
.values(changeset_comment=self.changeset_comment)
)

for task in self.tasks:
await db.execute(
Expand Down
5 changes: 2 additions & 3 deletions backend/services/project_admin_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,16 @@ async def create_draft_project(
tasks = draft_project_dto.tasks

await ProjectAdminService._attach_tasks_to_project(draft_project, tasks, db)
draft_project.set_default_changeset_comment()
draft_project.set_country_info()

if draft_project_dto.cloneFromProjectId:
draft_project.set_default_changeset_comment()
await draft_project.save(db) # Update the clone
return draft_project.id

else:
project_id = await Project.create(
draft_project, draft_project_dto.project_name, db
) # Create the new project

return project_id

@staticmethod
Expand Down
Loading
Loading