Skip to content

Commit

Permalink
Merge pull request #3452 from hotosm/develop
Browse files Browse the repository at this point in the history
v4.1.4 Release
  • Loading branch information
willemarcel authored Aug 11, 2020
2 parents 1fee3b3 + 2c4bc39 commit 3aa971d
Show file tree
Hide file tree
Showing 64 changed files with 802 additions and 485 deletions.
3 changes: 2 additions & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"Component: Frontend":
- frontend/**/*
"Component: Backend":
- server/**/*
- backend/**/*
- tests/**/*
- migrations/**/*
- ./manage.py
Expand All @@ -12,3 +12,4 @@
- scripts/aws/**/*
- scripts/docker/**/*
- scripts/install/**/*
- docker-*.yml
16 changes: 9 additions & 7 deletions backend/models/dtos/organisation_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class OrganisationDTO(Model):


class ListOrganisationsDTO(Model):
def __init__(self):
super().__init__()
self.organisations = []

organisations = ListType(ModelType(OrganisationDTO))


Expand All @@ -58,12 +62,10 @@ class NewOrganisationDTO(Model):


class UpdateOrganisationDTO(OrganisationDTO):

organisation_id = IntType(serialized_name="organisationId", required=False)
managers = ListType(StringType())
name = StringType()


class OrganisationProjectsDTO(Model):
""" Describes a JSON model to create a project team """

project_name = StringType(serialize_when_none=False)
project_id = IntType(serialize_when_none=False)
logo = StringType()
description = StringType()
url = StringType()
3 changes: 0 additions & 3 deletions backend/models/dtos/team_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
ModelType,
)
from backend.models.postgis.statuses import TeamMemberFunctions, TeamVisibility
from backend.models.dtos.organisation_dto import OrganisationProjectsDTO


def validate_team_visibility(value):
Expand Down Expand Up @@ -67,7 +66,6 @@ def __init__(self):
super().__init__()
self.members = []
self.team_projects = []
self.organisation_projects = []

""" Describes JSON model for a team """
team_id = IntType(serialized_name="teamId")
Expand All @@ -86,7 +84,6 @@ def __init__(self):
is_general_admin = BooleanType(default=False)
members = ListType(ModelType(TeamMembersDTO))
team_projects = ListType(ModelType(ProjectTeamDTO))
organisation_projects = ListType(ModelType(OrganisationProjectsDTO))


class TeamDTO(Model):
Expand Down
27 changes: 26 additions & 1 deletion backend/models/postgis/project_chat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import bleach
from markdown import markdown
from flask import current_app
from backend import db
from backend.models.postgis.user import User
Expand Down Expand Up @@ -30,7 +31,31 @@ def create_from_dto(cls, dto: ChatMessageDTO):
new_message.user_id = dto.user_id

# Use bleach to remove any potential mischief
clean_message = bleach.clean(dto.message)
allowed_tags = [
"a",
"b",
"blockquote",
"br",
"code",
"em",
"h1",
"h2",
"h3",
"img",
"i",
"li",
"ol",
"p",
"pre",
"strong",
"ul",
]
allowed_atrributes = {"a": ["href", "rel"], "img": ["src", "alt"]}
clean_message = bleach.clean(
markdown(dto.message, output_format="html"),
tags=allowed_tags,
attributes=allowed_atrributes,
)
clean_message = bleach.linkify(clean_message)
new_message.message = clean_message

Expand Down
34 changes: 31 additions & 3 deletions backend/models/postgis/team.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
from backend import db
from backend.models.dtos.team_dto import TeamDTO, NewTeamDTO
from backend.models.dtos.team_dto import (
TeamDTO,
NewTeamDTO,
TeamMembersDTO,
TeamProjectDTO,
)
from backend.models.dtos.organisation_dto import OrganisationTeamsDTO
from backend.models.postgis.organisation import Organisation
from backend.models.postgis.statuses import TeamVisibility, TeamMemberFunctions
from backend.models.postgis.statuses import (
TeamVisibility,
TeamMemberFunctions,
TeamRoles,
)
from backend.models.postgis.user import User
from backend.models.postgis.utils import NotFound

Expand Down Expand Up @@ -148,7 +157,7 @@ def get(team_id: int):
def get_team_by_name(team_name: str):
"""
Gets specified team by name
:param team_id: team name in scope
:param team_name: team name in scope
:return: Team if found otherwise None
"""
return Team.query.filter_by(name=team_name).one_or_none()
Expand Down Expand Up @@ -178,6 +187,25 @@ def as_dto_inside_org(self):
team_dto.visibility = TeamVisibility(self.visibility).name
return team_dto

def as_dto_team_member(self, member) -> TeamMembersDTO:
""" Returns a dto for the team member"""
member_dto = TeamMembersDTO()
user = User.get_by_id(member.user_id)
member_function = TeamMemberFunctions(member.function).name
member_dto.username = user.username
member_dto.function = member_function
member_dto.picture_url = user.picture_url
member_dto.active = member.active
return member_dto

def as_dto_team_project(self, project) -> TeamProjectDTO:
""" Returns a dto for the team project """
project_team_dto = TeamProjectDTO()
project_team_dto.project_name = project.name
project_team_dto.project_id = project.project_id
project_team_dto.role = TeamRoles(project.role).name
return project_team_dto

def _get_team_members(self):
""" Helper to get JSON serialized members """
members = []
Expand Down
3 changes: 2 additions & 1 deletion backend/services/messaging/message_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,8 @@ def send_message_after_chat(chat_from: int, chat: str, project_id: int):
message.message = chat
messages.append(dict(message=message, user=user))

MessageService._push_messages(messages)
# it's important to keep that line inside the if to avoid duplicated emails
MessageService._push_messages(messages)

@staticmethod
def send_favorite_project_activities(user_id: int):
Expand Down
12 changes: 9 additions & 3 deletions backend/services/messaging/smtp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from backend.services.messaging.template_service import (
get_template,
template_var_replacing,
format_username_link,
)


Expand Down Expand Up @@ -76,9 +77,12 @@ def send_email_alert(
["[ORG_CODE]", org_code],
["[PROFILE_LINK]", inbox_url],
["[SETTINGS_LINK]", settings_url],
["[CONTENT]", content],
]
html_template = template_var_replacing(html_template, replace_list)
html_replace_list = replace_list + [
["[CONTENT]", format_username_link(content)]
]
html_template = template_var_replacing(html_template, html_replace_list)
replace_list += [["[CONTENT]", content]]
text_template = template_var_replacing(text_template, replace_list)

SMTPService._send_message(to_address, subject, html_template, text_template)
Expand All @@ -96,7 +100,9 @@ def _send_message(

msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = from_address
msg["From"] = "{} Tasking Manager <{}>".format(
current_app.config["ORG_CODE"], from_address
)
msg["To"] = to_address

# Record the MIME types of both parts - text/plain and text/html.
Expand Down
13 changes: 13 additions & 0 deletions backend/services/messaging/template_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import re

from flask import current_app


Expand Down Expand Up @@ -31,3 +32,15 @@ def clean_html(raw_html):
cleanr = re.compile("<.*?>")
clean_text = re.sub(cleanr, "", raw_html)
return clean_text


def format_username_link(content):
expression = re.compile("@\\[.*?\\]")
names = expression.findall(content)
for name in names:
username = name[2:-1]
content = content.replace(
name,
f'<a href="{current_app.config["APP_BASE_URL"]}/users/{username}/">@{username}</a>',
)
return content
21 changes: 2 additions & 19 deletions backend/services/organisation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
)
from backend.models.postgis.organisation import Organisation
from backend.models.postgis.project import Project, ProjectInfo
from backend.models.postgis.team import Team
from backend.models.postgis.utils import NotFound
from backend.services.users.user_service import UserService

Expand Down Expand Up @@ -43,7 +42,6 @@ def get_organisation_by_id_as_dto(
raise NotFound()

organisation_dto = org.as_dto(abbreviated)
organisation_dto.teams = []

if user_id != 0:
organisation_dto.is_manager = OrganisationService.can_user_manage_organisation(
Expand All @@ -55,9 +53,7 @@ def get_organisation_by_id_as_dto(
if abbreviated:
return organisation_dto

teams = OrganisationService.get_teams_by_organisation_id(organisation_id)
for team in teams:
organisation_dto.teams.append(team.as_dto_inside_org())
organisation_dto.teams = [team.as_dto_inside_org() for team in org.teams]

return organisation_dto

Expand Down Expand Up @@ -130,7 +126,6 @@ def get_organisations_as_dto(
):
orgs = OrganisationService.get_organisations(manager_user_id)
orgs_dto = ListOrganisationsDTO()
orgs_dto.organisations = []
for org in orgs:
org_dto = org.as_dto(omit_managers)
if not authenticated_user_id:
Expand All @@ -151,10 +146,7 @@ def get_organisations_managed_by_user(user_id: int):
def get_organisations_managed_by_user_as_dto(user_id: int):
orgs = OrganisationService.get_organisations_managed_by_user(user_id)
orgs_dto = ListOrganisationsDTO()
orgs_dto.organisations = []
for org in orgs:
orgs_dto.organisations.append(org.as_dto())

orgs_dto.organisations = [org.as_dto() for org in orgs]
return orgs_dto

@staticmethod
Expand All @@ -171,15 +163,6 @@ def get_projects_by_organisation_id(organisation_id: int) -> Organisation:

return projects

@staticmethod
def get_teams_by_organisation_id(organisation_id: int) -> Organisation:
teams = Team.query.filter(Team.organisation_id == organisation_id).all()

if teams is None:
raise NotFound()

return teams

@staticmethod
def assert_validate_name(org: Organisation, name: str):
""" Validates that the organisation name doesn't exist """
Expand Down
29 changes: 19 additions & 10 deletions backend/services/project_search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,18 @@ def search_projects(search_dto: ProjectSearchDTO, user) -> ProjectSearchResultsD
@staticmethod
def _filter_projects(search_dto: ProjectSearchDTO, user):
""" Filters all projects based on criteria provided by user"""

query = ProjectSearchService.create_search_query(user)

query = query.join(ProjectInfo).filter(
ProjectInfo.locale.in_([search_dto.preferred_locale, "en"])
)
project_status_array = []
if search_dto.project_statuses:
for project_status in search_dto.project_statuses:
project_status_array.append(ProjectStatus[project_status].value)
project_status_array = [
ProjectStatus[project_status].value
for project_status in search_dto.project_statuses
]
query = query.filter(Project.status.in_(project_status_array))
else:
if not search_dto.created_by:
Expand All @@ -232,7 +236,6 @@ def _filter_projects(search_dto: ProjectSearchDTO, user):
projects_mapped = UserService.get_projects_mapped(search_dto.mapped_by)
query = query.filter(Project.id.in_(projects_mapped))
if search_dto.favorited_by:
user = UserService.get_user_by_id(search_dto.favorited_by)
projects_favorited = user.favorites
query = query.filter(
Project.id.in_([project.id for project in projects_favorited])
Expand All @@ -254,16 +257,16 @@ def _filter_projects(search_dto: ProjectSearchDTO, user):
).filter(ProjectTeams.team_id == search_dto.team_id)

if search_dto.campaign:
query = query.join(Campaign, Project.campaign).group_by(
Project.id, Campaign.name
)
query = query.join(Campaign, Project.campaign).group_by(Campaign.name)
query = query.filter(Campaign.name == search_dto.campaign)

if search_dto.mapping_types:
# Construct array of mapping types for query
mapping_type_array = []
for mapping_type in search_dto.mapping_types:
mapping_type_array.append(MappingTypes[mapping_type].value)
mapping_type_array = [
MappingTypes[mapping_type].value
for mapping_type in search_dto.mapping_types
]

query = query.filter(Project.mapping_types.contains(mapping_type_array))

Expand All @@ -276,7 +279,7 @@ def _filter_projects(search_dto: ProjectSearchDTO, user):
ProjectInfo.text_searchable.match(
or_search, postgresql_regconfig="english"
),
ProjectInfo.name.like(f"%{or_search}%"),
ProjectInfo.name.ilike(f"%{or_search}%"),
]
try:
opts.append(Project.id == int(search_dto.text_search))
Expand Down Expand Up @@ -325,7 +328,13 @@ def _filter_projects(search_dto: ProjectSearchDTO, user):

all_results = []
if not search_dto.omit_map_results:
all_results = query.all()
query_result = query
query_result.column_descriptions.clear()
query_result.add_column(Project.id)
query_result.add_column(Project.centroid.ST_AsGeoJSON().label("centroid"))
query_result.add_column(Project.priority)
all_results = query_result.all()

paginated_results = query.paginate(search_dto.page, 14, True)

return all_results, paginated_results
Expand Down
2 changes: 1 addition & 1 deletion backend/services/project_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def get_contribs_by_day(project_id: int) -> ProjectContribsDTO:
)
.group_by("action_text", "day", "task_id")
.order_by("day")
)
).all()

contribs_dto = ProjectContribsDTO()
# Filter and store unique dates
Expand Down
Loading

0 comments on commit 3aa971d

Please sign in to comment.