def get_mapped_projects(user_id: int, preferred_locale: str) -> UserMappedProjectsDTO: """ Get all projects a user has mapped on """ from backend.models.postgis.task import Task from backend.models.postgis.project import Project query = db.session.query(func.unnest( User.projects_mapped)).filter_by(id=user_id) query_validated = (db.session.query( Task.project_id.label("project_id"), func.count(Task.validated_by).label("validated"), ).filter(Task.project_id.in_(query)).filter_by( validated_by=user_id).group_by(Task.project_id, Task.validated_by).subquery()) query_mapped = (db.session.query( Task.project_id.label("project_id"), func.count(Task.mapped_by).label("mapped"), ).filter(Task.project_id.in_(query)).filter_by( mapped_by=user_id).group_by(Task.project_id, Task.mapped_by).subquery()) query_union = (db.session.query( func.coalesce(query_validated.c.project_id, query_mapped.c.project_id).label("project_id"), func.coalesce(query_validated.c.validated, 0).label("validated"), func.coalesce(query_mapped.c.mapped, 0).label("mapped"), ).join( query_mapped, query_validated.c.project_id == query_mapped.c.project_id, full=True, ).subquery()) results = (db.session.query( Project.id, Project.status, Project.default_locale, query_union.c.mapped, query_union.c.validated, functions.ST_AsGeoJSON(Project.centroid), ).filter(Project.id == query_union.c.project_id).order_by( desc(Project.id)).all()) mapped_projects_dto = UserMappedProjectsDTO() for row in results: mapped_project = MappedProject() mapped_project.project_id = row[0] mapped_project.status = ProjectStatus(row[1]).name mapped_project.tasks_mapped = row[3] mapped_project.tasks_validated = row[4] mapped_project.centroid = geojson.loads(row[5]) project_info = ProjectInfo.get_dto_for_locale( row[0], preferred_locale, row[2]) mapped_project.name = project_info.name mapped_projects_dto.mapped_projects.append(mapped_project) return mapped_projects_dto
def clone(project_id: int, author_id: int): """ Clone project """ orig = Project.get(project_id) if orig is None: raise NotFound() # Transform into dictionary. orig_metadata = orig.__dict__.copy() # Remove unneeded data. items_to_remove = ["_sa_instance_state", "id", "allowed_users"] [orig_metadata.pop(i, None) for i in items_to_remove] # Remove clone from session so we can reinsert it as a new object orig_metadata.update({ "total_tasks": 0, "tasks_mapped": 0, "tasks_validated": 0, "tasks_bad_imagery": 0, "last_updated": timestamp(), "created": timestamp(), "author_id": author_id, "status": ProjectStatus.DRAFT.value, "geometry": None, "centroid": None, }) new_proj = Project(**orig_metadata) db.session.add(new_proj) proj_info = [] for info in orig.project_info.all(): info_data = info.__dict__.copy() info_data.pop("_sa_instance_state") info_data.update({ "project_id": new_proj.id, "project_id_str": str(new_proj.id) }) proj_info.append(ProjectInfo(**info_data)) new_proj.project_info = proj_info # Replace changeset comment. default_comment = current_app.config["DEFAULT_CHANGESET_COMMENT"] if default_comment is not None: orig_changeset = f"{default_comment}-{orig.id}" # Preserve space new_proj.changeset_comment = orig.changeset_comment.replace( orig_changeset, "") # Copy array relationships. for field in ["interests", "campaign", "teams"]: value = getattr(orig, field) setattr(new_proj, field, value) new_proj.custom_editor = orig.custom_editor db.session.commit() return new_proj
def as_dto_for_mapping( self, authenticated_user_id: int = None, locale: str = "en", abbrev: bool = True ) -> Optional[ProjectDTO]: """ Creates a Project DTO suitable for transmitting to mapper users """ # Check for project visibility settings is_allowed_user = True if self.status == ProjectStatus.DRAFT.value: if not self.check_draft_project_visibility(authenticated_user_id): is_allowed_user = False if self.private: is_allowed_user = False if authenticated_user_id: user = User.get_by_id(authenticated_user_id) if ( UserRole(user.role) == UserRole.ADMIN or authenticated_user_id == self.author_id ): is_allowed_user = True for user in self.allowed_users: if user.id == authenticated_user_id: is_allowed_user = True break if is_allowed_user: project, project_dto = self._get_project_and_base_dto() if abbrev is False: project_dto.tasks = Task.get_tasks_as_geojson_feature_collection( self.id, None ) else: project_dto.tasks = Task.get_tasks_as_geojson_feature_collection_no_geom( self.id ) project_dto.project_info = ProjectInfo.get_dto_for_locale( self.id, locale, project.default_locale ) if project.organisation_id: project_dto.organisation = project.organisation.id project_dto.organisation_name = project.organisation.name project_dto.organisation_logo = project.organisation.logo project_dto.project_info_locales = ProjectInfo.get_dto_for_all_locales( self.id ) return project_dto
def create_draft_project(self, draft_project_dto: DraftProjectDTO): """ Creates a draft project :param draft_project_dto: DTO containing draft project details :param aoi: Area of Interest for the project (eg boundary of project) """ self.project_info.append( ProjectInfo.create_from_name(draft_project_dto.project_name)) self.status = ProjectStatus.DRAFT.value self.author_id = draft_project_dto.user_id self.last_updated = timestamp()
def as_dto_for_admin(self, project_id): """ Creates a Project DTO suitable for transmitting to project admins """ project, project_dto = self._get_project_and_base_dto() if project is None: return None project_dto.project_info_locales = ProjectInfo.get_dto_for_all_locales( project_id) return project_dto
def as_dto_for_mapping(self, authenticated_user_id: int = None, locale: str = "en", abbrev: bool = True) -> Optional[ProjectDTO]: """ Creates a Project DTO suitable for transmitting to mapper users """ project, project_dto = self._get_project_and_base_dto() if abbrev is False: project_dto.tasks = Task.get_tasks_as_geojson_feature_collection( self.id, None) else: project_dto.tasks = Task.get_tasks_as_geojson_feature_collection_no_geom( self.id) project_dto.project_info = ProjectInfo.get_dto_for_locale( self.id, locale, project.default_locale) if project.organisation_id: project_dto.organisation = project.organisation.id project_dto.organisation_name = project.organisation.name project_dto.organisation_logo = project.organisation.logo project_dto.project_info_locales = ProjectInfo.get_dto_for_all_locales( self.id) return project_dto
def get_mapped_projects(user_id: int, preferred_locale: str) -> UserMappedProjectsDTO: """ Get all projects a user has mapped on """ # This query looks scary, but we're really just creating an outer join between the query that gets the # counts of all mapped tasks and the query that gets counts of all validated tasks. This is necessary to # handle cases where users have only validated tasks on a project, or only mapped on a project. sql = """SELECT p.id, p.status, p.default_locale, c.mapped, c.validated, st_asgeojson(p.centroid) FROM projects p, (SELECT coalesce(v.project_id, m.project_id) project_id, coalesce(v.validated, 0) validated, coalesce(m.mapped, 0) mapped FROM (SELECT t.project_id, count (t.validated_by) validated FROM tasks t WHERE t.project_id IN (SELECT unnest(projects_mapped) FROM users WHERE id = :user_id) AND t.validated_by = :user_id GROUP BY t.project_id, t.validated_by) v FULL OUTER JOIN (SELECT t.project_id, count(t.mapped_by) mapped FROM tasks t WHERE t.project_id IN (SELECT unnest(projects_mapped) FROM users WHERE id = :user_id) AND t.mapped_by = :user_id GROUP BY t.project_id, t.mapped_by) m ON v.project_id = m.project_id) c WHERE p.id = c.project_id ORDER BY p.id DESC""" results = db.engine.execute(text(sql), user_id=user_id) mapped_projects_dto = UserMappedProjectsDTO() for row in results: mapped_project = MappedProject() mapped_project.project_id = row[0] mapped_project.status = ProjectStatus(row[1]).name mapped_project.tasks_mapped = row[3] mapped_project.tasks_validated = row[4] mapped_project.centroid = geojson.loads(row[5]) project_info = ProjectInfo.get_dto_for_locale( row[0], preferred_locale, row[2]) mapped_project.name = project_info.name mapped_projects_dto.mapped_projects.append(mapped_project) return mapped_projects_dto
def get_user_invalidated_tasks( as_validator, username: str, preferred_locale: str, closed=None, project_id=None, page=1, page_size=10, sort_by="updated_date", sort_direction="desc", ) -> InvalidatedTasks: """ Get invalidated tasks either mapped or invalidated by the user """ user = UserService.get_user_by_username(username) query = ( TaskInvalidationHistory.query.filter_by(invalidator_id=user.id) if as_validator else TaskInvalidationHistory.query.filter_by(mapper_id=user.id) ) if closed is not None: query = query.filter_by(is_closed=closed) if project_id is not None: query = query.filter_by(project_id=project_id) results = query.order_by(text(sort_by + " " + sort_direction)).paginate( page, page_size, True ) project_names = {} invalidated_tasks_dto = InvalidatedTasks() for entry in results.items: dto = InvalidatedTask() dto.task_id = entry.task_id dto.project_id = entry.project_id dto.history_id = entry.invalidation_history_id dto.closed = entry.is_closed dto.updated_date = entry.updated_date if dto.project_id not in project_names: project_names[dto.project_id] = ProjectInfo.get_dto_for_locale( dto.project_id, preferred_locale ).name dto.project_name = project_names[dto.project_id] invalidated_tasks_dto.invalidated_tasks.append(dto) invalidated_tasks_dto.pagination = Pagination(results) return invalidated_tasks_dto
def get_project_title(self, preferred_locale): project_info = ProjectInfo.get_dto_for_locale(self.id, preferred_locale, self.default_locale) return project_info.name
def get_project_summary(self, preferred_locale) -> ProjectSummary: """ Create Project Summary model for postgis project object""" summary = ProjectSummary() summary.project_id = self.id priority = self.priority if priority == 0: summary.priority = "URGENT" elif priority == 1: summary.priority = "HIGH" elif priority == 2: summary.priority = "MEDIUM" else: summary.priority = "LOW" summary.author = User.get_by_id(self.author_id).username summary.default_locale = self.default_locale summary.country_tag = self.country summary.changeset_comment = self.changeset_comment summary.due_date = self.due_date summary.created = self.created summary.last_updated = self.last_updated summary.osmcha_filter_id = self.osmcha_filter_id summary.mapper_level = MappingLevel(self.mapper_level).name summary.mapping_permission = MappingPermission( self.mapping_permission).name summary.validation_permission = ValidationPermission( self.validation_permission).name summary.random_task_selection_enforced = self.enforce_random_task_selection summary.private = self.private summary.license_id = self.license_id summary.status = ProjectStatus(self.status).name summary.id_presets = self.id_presets summary.imagery = self.imagery if self.organisation_id: summary.organisation = self.organisation_id summary.organisation_name = self.organisation.name summary.organisation_logo = self.organisation.logo if self.campaign: summary.campaigns = [i.as_dto() for i in self.campaign] # Cast MappingType values to related string array mapping_types_array = [] if self.mapping_types: for mapping_type in self.mapping_types: mapping_types_array.append(MappingTypes(mapping_type).name) summary.mapping_types = mapping_types_array if self.mapping_editors: mapping_editors = [] for mapping_editor in self.mapping_editors: mapping_editors.append(Editors(mapping_editor).name) summary.mapping_editors = mapping_editors if self.validation_editors: validation_editors = [] for validation_editor in self.validation_editors: validation_editors.append(Editors(validation_editor).name) summary.validation_editors = validation_editors if self.custom_editor: summary.custom_editor = self.custom_editor.as_dto() # If project is private, fetch list of allowed users if self.private: allowed_users = [] for user in self.allowed_users: allowed_users.append(user.username) summary.allowed_users = allowed_users centroid_geojson = db.session.scalar(self.centroid.ST_AsGeoJSON()) summary.aoi_centroid = geojson.loads(centroid_geojson) summary.percent_mapped = Project.calculate_tasks_percent( "mapped", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) summary.percent_validated = Project.calculate_tasks_percent( "validated", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) summary.percent_bad_imagery = Project.calculate_tasks_percent( "bad_imagery", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) summary.project_teams = [ ProjectTeamDTO( dict( team_id=t.team.id, team_name=t.team.name, role=TeamRoles(t.role).name, )) for t in self.teams ] project_info = ProjectInfo.get_dto_for_locale(self.id, preferred_locale, self.default_locale) summary.project_info = project_info return summary
def update(self, project_dto: ProjectDTO): """ Updates project from DTO """ self.status = ProjectStatus[project_dto.project_status].value self.priority = ProjectPriority[project_dto.project_priority].value self.default_locale = project_dto.default_locale self.enforce_random_task_selection = project_dto.enforce_random_task_selection self.private = project_dto.private self.mapper_level = MappingLevel[ project_dto.mapper_level.upper()].value self.changeset_comment = project_dto.changeset_comment self.due_date = project_dto.due_date self.imagery = project_dto.imagery self.josm_preset = project_dto.josm_preset self.id_presets = project_dto.id_presets self.last_updated = timestamp() self.license_id = project_dto.license_id if project_dto.osmcha_filter_id: # Support simple extraction of OSMCha filter id from OSMCha URL match = re.search(r"aoi=([\w-]+)", project_dto.osmcha_filter_id) self.osmcha_filter_id = (match.group(1) if match else project_dto.osmcha_filter_id) else: self.osmcha_filter_id = None if project_dto.organisation: org = Organisation.get(project_dto.organisation) if org is None: raise NotFound("Organisation does not exist") self.organisation = org # Cast MappingType strings to int array type_array = [] for mapping_type in project_dto.mapping_types: type_array.append(MappingTypes[mapping_type].value) self.mapping_types = type_array # Cast Editor strings to int array mapping_editors_array = [] for mapping_editor in project_dto.mapping_editors: mapping_editors_array.append(Editors[mapping_editor].value) self.mapping_editors = mapping_editors_array validation_editors_array = [] for validation_editor in project_dto.validation_editors: validation_editors_array.append(Editors[validation_editor].value) self.validation_editors = validation_editors_array self.country = project_dto.country_tag # Add list of allowed users, meaning the project can only be mapped by users in this list if hasattr(project_dto, "allowed_users"): self.allowed_users = [ ] # Clear existing relationships then re-insert for user in project_dto.allowed_users: self.allowed_users.append(user) # Update teams and projects relationship. self.teams = [] if hasattr(project_dto, "project_teams") and project_dto.project_teams: for team_dto in project_dto.project_teams: team = Team.get(team_dto.team_id) if team is None: raise NotFound("Team not found") role = TeamRoles[team_dto.role].value ProjectTeams(project=self, team=team, role=role) # Set Project Info for all returned locales for dto in project_dto.project_info_locales: project_info = self.project_info.filter_by( locale=dto.locale).one_or_none() if project_info is None: new_info = ProjectInfo.create_from_dto( dto) # Can't find info so must be new locale self.project_info.append(new_info) else: project_info.update_from_dto(dto) self.priority_areas = [ ] # Always clear Priority Area prior to updating if project_dto.priority_areas: for priority_area in project_dto.priority_areas: pa = PriorityArea.from_dict(priority_area) self.priority_areas.append(pa) if project_dto.custom_editor: if not self.custom_editor: new_editor = CustomEditor.create_from_dto( self.id, project_dto.custom_editor) self.custom_editor = new_editor else: self.custom_editor.update_editor(project_dto.custom_editor) else: if self.custom_editor: self.custom_editor.delete() # handle campaign update try: new_ids = [c.id for c in project_dto.campaigns] new_ids.sort() except TypeError: new_ids = [] current_ids = [c.id for c in self.campaign] current_ids.sort() if new_ids != current_ids: self.campaign = Campaign.query.filter( Campaign.id.in_(new_ids)).all() if project_dto.mapping_permission: self.mapping_permission = MappingPermission[ project_dto.mapping_permission.upper()].value if project_dto.validation_permission: self.validation_permission = ValidationPermission[ project_dto.validation_permission.upper()].value # handle interests update try: new_ids = [c.id for c in project_dto.interests] new_ids.sort() except TypeError: new_ids = [] current_ids = [c.id for c in self.interests] current_ids.sort() if new_ids != current_ids: self.interests = Interest.query.filter( Interest.id.in_(new_ids)).all() # try to update country info if that information is not present if not self.country: self.set_country_info() db.session.commit()