Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
 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()
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
    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
Exemplo n.º 9
0
 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
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
    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()