commit
aed3159026
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,6 +1,20 @@
|
||||||
# Changelog #
|
# Changelog #
|
||||||
|
|
||||||
|
|
||||||
|
## 3.2.0 Betula nana (2018-03-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Add role filtering in US.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.1.3 (2018-02-28)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Increase token entropy.
|
||||||
|
- Squash field changes on notification emails
|
||||||
|
- Minor bug fixes.
|
||||||
|
|
||||||
|
|
||||||
## 3.1.0 Perovskia Atriplicifolia (2017-03-10)
|
## 3.1.0 Perovskia Atriplicifolia (2017-03-10)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -587,3 +587,22 @@ class QFilter(FilterBackend):
|
||||||
queryset = queryset.extra(where=[where_clause], params=[to_tsquery(q)])
|
queryset = queryset.extra(where=[where_clause], params=[to_tsquery(q)])
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class RoleFilter(BaseRelatedFieldsFilter):
|
||||||
|
filter_name = "role_id"
|
||||||
|
param_name = "role"
|
||||||
|
|
||||||
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
Membership = apps.get_model('projects', 'Membership')
|
||||||
|
query = self._get_queryparams(request.QUERY_PARAMS)
|
||||||
|
if query:
|
||||||
|
if isinstance(query, dict):
|
||||||
|
memberships = Membership.objects.filter(**query).values_list("user_id", flat=True)
|
||||||
|
queryset = queryset.filter(assigned_to__in=memberships)
|
||||||
|
else:
|
||||||
|
memberships = Membership.objects.filter(query).values_list("user_id", flat=True)
|
||||||
|
if memberships:
|
||||||
|
queryset = queryset.filter(assigned_to__in=memberships)
|
||||||
|
|
||||||
|
return FilterBackend.filter_queryset(self, request, queryset, view)
|
||||||
|
|
|
@ -50,6 +50,7 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W
|
||||||
queryset = models.Issue.objects.all()
|
queryset = models.Issue.objects.all()
|
||||||
permission_classes = (permissions.IssuePermission, )
|
permission_classes = (permissions.IssuePermission, )
|
||||||
filter_backends = (filters.CanViewIssuesFilterBackend,
|
filter_backends = (filters.CanViewIssuesFilterBackend,
|
||||||
|
filters.RoleFilter,
|
||||||
filters.OwnersFilter,
|
filters.OwnersFilter,
|
||||||
filters.AssignedToFilter,
|
filters.AssignedToFilter,
|
||||||
filters.StatusesFilter,
|
filters.StatusesFilter,
|
||||||
|
@ -188,6 +189,7 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W
|
||||||
owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
|
owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
|
||||||
priorities_filter_backends = (f for f in filter_backends if f != filters.PrioritiesFilter)
|
priorities_filter_backends = (f for f in filter_backends if f != filters.PrioritiesFilter)
|
||||||
severities_filter_backends = (f for f in filter_backends if f != filters.SeveritiesFilter)
|
severities_filter_backends = (f for f in filter_backends if f != filters.SeveritiesFilter)
|
||||||
|
roles_filter_backends = (f for f in filter_backends if f != filters.RoleFilter)
|
||||||
|
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
querysets = {
|
querysets = {
|
||||||
|
@ -197,7 +199,8 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W
|
||||||
"owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
|
"owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
|
||||||
"priorities": self.filter_queryset(queryset, filter_backends=priorities_filter_backends),
|
"priorities": self.filter_queryset(queryset, filter_backends=priorities_filter_backends),
|
||||||
"severities": self.filter_queryset(queryset, filter_backends=severities_filter_backends),
|
"severities": self.filter_queryset(queryset, filter_backends=severities_filter_backends),
|
||||||
"tags": self.filter_queryset(queryset)
|
"tags": self.filter_queryset(queryset),
|
||||||
|
"roles": self.filter_queryset(queryset, filter_backends=roles_filter_backends),
|
||||||
}
|
}
|
||||||
return response.Ok(services.get_issues_filters_data(project, querysets))
|
return response.Ok(services.get_issues_filters_data(project, querysets))
|
||||||
|
|
||||||
|
|
|
@ -420,6 +420,57 @@ def _get_issues_owners(project, queryset):
|
||||||
return sorted(result, key=itemgetter("full_name"))
|
return sorted(result, key=itemgetter("full_name"))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_issues_roles(project, queryset):
|
||||||
|
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
||||||
|
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
||||||
|
where = queryset_where_tuple[0]
|
||||||
|
where_params = queryset_where_tuple[1]
|
||||||
|
|
||||||
|
extra_sql = """
|
||||||
|
WITH "issue_counters" AS (
|
||||||
|
SELECT DISTINCT "issues_issue"."status_id" "status_id",
|
||||||
|
"issues_issue"."id" "issue_id",
|
||||||
|
"projects_membership"."role_id" "role_id"
|
||||||
|
FROM "issues_issue"
|
||||||
|
INNER JOIN "projects_project"
|
||||||
|
ON ("issues_issue"."project_id" = "projects_project"."id")
|
||||||
|
LEFT OUTER JOIN "projects_membership"
|
||||||
|
ON "projects_membership"."user_id" = "issues_issue"."assigned_to_id"
|
||||||
|
WHERE {where}
|
||||||
|
),
|
||||||
|
"counters" AS (
|
||||||
|
SELECT "role_id" as "role_id",
|
||||||
|
COUNT("role_id") "count"
|
||||||
|
FROM "issue_counters"
|
||||||
|
GROUP BY "role_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT "users_role"."id",
|
||||||
|
"users_role"."name",
|
||||||
|
"users_role"."order",
|
||||||
|
COALESCE("counters"."count", 0)
|
||||||
|
FROM "users_role"
|
||||||
|
LEFT OUTER JOIN "counters"
|
||||||
|
ON "counters"."role_id" = "users_role"."id"
|
||||||
|
WHERE "users_role"."project_id" = %s
|
||||||
|
ORDER BY "users_role"."order";
|
||||||
|
""".format(where=where)
|
||||||
|
|
||||||
|
with closing(connection.cursor()) as cursor:
|
||||||
|
cursor.execute(extra_sql, where_params + [project.id])
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for id, name, order, count in rows:
|
||||||
|
result.append({
|
||||||
|
"id": id,
|
||||||
|
"name": _(name),
|
||||||
|
"color": None,
|
||||||
|
"order": order,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
return sorted(result, key=itemgetter("order"))
|
||||||
|
|
||||||
def _get_issues_tags(project, queryset):
|
def _get_issues_tags(project, queryset):
|
||||||
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
||||||
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
||||||
|
@ -478,6 +529,7 @@ def get_issues_filters_data(project, querysets):
|
||||||
("assigned_to", _get_issues_assigned_to(project, querysets["assigned_to"])),
|
("assigned_to", _get_issues_assigned_to(project, querysets["assigned_to"])),
|
||||||
("owners", _get_issues_owners(project, querysets["owners"])),
|
("owners", _get_issues_owners(project, querysets["owners"])),
|
||||||
("tags", _get_issues_tags(project, querysets["tags"])),
|
("tags", _get_issues_tags(project, querysets["tags"])),
|
||||||
|
("roles", _get_issues_roles(project, querysets["roles"])),
|
||||||
])
|
])
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -51,6 +51,7 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, Wa
|
||||||
queryset = models.Task.objects.all()
|
queryset = models.Task.objects.all()
|
||||||
permission_classes = (permissions.TaskPermission,)
|
permission_classes = (permissions.TaskPermission,)
|
||||||
filter_backends = (filters.CanViewTasksFilterBackend,
|
filter_backends = (filters.CanViewTasksFilterBackend,
|
||||||
|
filters.RoleFilter,
|
||||||
filters.OwnersFilter,
|
filters.OwnersFilter,
|
||||||
filters.AssignedToFilter,
|
filters.AssignedToFilter,
|
||||||
filters.StatusesFilter,
|
filters.StatusesFilter,
|
||||||
|
@ -219,13 +220,15 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, Wa
|
||||||
statuses_filter_backends = (f for f in filter_backends if f != filters.StatusesFilter)
|
statuses_filter_backends = (f for f in filter_backends if f != filters.StatusesFilter)
|
||||||
assigned_to_filter_backends = (f for f in filter_backends if f != filters.AssignedToFilter)
|
assigned_to_filter_backends = (f for f in filter_backends if f != filters.AssignedToFilter)
|
||||||
owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
|
owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
|
||||||
|
roles_filter_backends = (f for f in filter_backends if f != filters.RoleFilter)
|
||||||
|
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
querysets = {
|
querysets = {
|
||||||
"statuses": self.filter_queryset(queryset, filter_backends=statuses_filter_backends),
|
"statuses": self.filter_queryset(queryset, filter_backends=statuses_filter_backends),
|
||||||
"assigned_to": self.filter_queryset(queryset, filter_backends=assigned_to_filter_backends),
|
"assigned_to": self.filter_queryset(queryset, filter_backends=assigned_to_filter_backends),
|
||||||
"owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
|
"owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
|
||||||
"tags": self.filter_queryset(queryset)
|
"tags": self.filter_queryset(queryset),
|
||||||
|
"roles": self.filter_queryset(queryset, filter_backends=roles_filter_backends),
|
||||||
}
|
}
|
||||||
return response.Ok(services.get_tasks_filters_data(project, querysets))
|
return response.Ok(services.get_tasks_filters_data(project, querysets))
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,58 @@ def _get_tasks_assigned_to(project, queryset):
|
||||||
return sorted(result, key=itemgetter("full_name"))
|
return sorted(result, key=itemgetter("full_name"))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_tasks_roles(project, queryset):
|
||||||
|
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
||||||
|
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
||||||
|
where = queryset_where_tuple[0]
|
||||||
|
where_params = queryset_where_tuple[1]
|
||||||
|
|
||||||
|
extra_sql = """
|
||||||
|
WITH "task_counters" AS (
|
||||||
|
SELECT DISTINCT "tasks_task"."status_id" "status_id",
|
||||||
|
"tasks_task"."id" "us_id",
|
||||||
|
"projects_membership"."role_id" "role_id"
|
||||||
|
FROM "tasks_task"
|
||||||
|
INNER JOIN "projects_project"
|
||||||
|
ON ("tasks_task"."project_id" = "projects_project"."id")
|
||||||
|
LEFT OUTER JOIN "projects_membership"
|
||||||
|
ON "projects_membership"."user_id" = "tasks_task"."assigned_to_id"
|
||||||
|
WHERE {where}
|
||||||
|
),
|
||||||
|
"counters" AS (
|
||||||
|
SELECT "role_id" as "role_id",
|
||||||
|
COUNT("role_id") "count"
|
||||||
|
FROM "task_counters"
|
||||||
|
GROUP BY "role_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT "users_role"."id",
|
||||||
|
"users_role"."name",
|
||||||
|
"users_role"."order",
|
||||||
|
COALESCE("counters"."count", 0)
|
||||||
|
FROM "users_role"
|
||||||
|
LEFT OUTER JOIN "counters"
|
||||||
|
ON "counters"."role_id" = "users_role"."id"
|
||||||
|
WHERE "users_role"."project_id" = %s
|
||||||
|
ORDER BY "users_role"."order";
|
||||||
|
""".format(where=where)
|
||||||
|
|
||||||
|
with closing(connection.cursor()) as cursor:
|
||||||
|
cursor.execute(extra_sql, where_params + [project.id])
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for id, name, order, count in rows:
|
||||||
|
result.append({
|
||||||
|
"id": id,
|
||||||
|
"name": _(name),
|
||||||
|
"color": None,
|
||||||
|
"order": order,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
return sorted(result, key=itemgetter("order"))
|
||||||
|
|
||||||
|
|
||||||
def _get_tasks_owners(project, queryset):
|
def _get_tasks_owners(project, queryset):
|
||||||
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
||||||
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
||||||
|
@ -384,6 +436,7 @@ def get_tasks_filters_data(project, querysets):
|
||||||
("assigned_to", _get_tasks_assigned_to(project, querysets["assigned_to"])),
|
("assigned_to", _get_tasks_assigned_to(project, querysets["assigned_to"])),
|
||||||
("owners", _get_tasks_owners(project, querysets["owners"])),
|
("owners", _get_tasks_owners(project, querysets["owners"])),
|
||||||
("tags", _get_tasks_tags(project, querysets["tags"])),
|
("tags", _get_tasks_tags(project, querysets["tags"])),
|
||||||
|
("roles", _get_tasks_roles(project, querysets["roles"])),
|
||||||
])
|
])
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -62,6 +62,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
|
||||||
permission_classes = (permissions.UserStoryPermission,)
|
permission_classes = (permissions.UserStoryPermission,)
|
||||||
filter_backends = (base_filters.CanViewUsFilterBackend,
|
filter_backends = (base_filters.CanViewUsFilterBackend,
|
||||||
filters.EpicFilter,
|
filters.EpicFilter,
|
||||||
|
base_filters.RoleFilter,
|
||||||
base_filters.OwnersFilter,
|
base_filters.OwnersFilter,
|
||||||
base_filters.AssignedToFilter,
|
base_filters.AssignedToFilter,
|
||||||
base_filters.StatusesFilter,
|
base_filters.StatusesFilter,
|
||||||
|
@ -306,6 +307,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
|
||||||
assigned_to_filter_backends = (f for f in filter_backends if f != base_filters.AssignedToFilter)
|
assigned_to_filter_backends = (f for f in filter_backends if f != base_filters.AssignedToFilter)
|
||||||
owners_filter_backends = (f for f in filter_backends if f != base_filters.OwnersFilter)
|
owners_filter_backends = (f for f in filter_backends if f != base_filters.OwnersFilter)
|
||||||
epics_filter_backends = (f for f in filter_backends if f != filters.EpicFilter)
|
epics_filter_backends = (f for f in filter_backends if f != filters.EpicFilter)
|
||||||
|
roles_filter_backends = (f for f in filter_backends if f != base_filters.RoleFilter)
|
||||||
|
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
querysets = {
|
querysets = {
|
||||||
|
@ -313,7 +315,8 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
|
||||||
"assigned_to": self.filter_queryset(queryset, filter_backends=assigned_to_filter_backends),
|
"assigned_to": self.filter_queryset(queryset, filter_backends=assigned_to_filter_backends),
|
||||||
"owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
|
"owners": self.filter_queryset(queryset, filter_backends=owners_filter_backends),
|
||||||
"tags": self.filter_queryset(queryset),
|
"tags": self.filter_queryset(queryset),
|
||||||
"epics": self.filter_queryset(queryset, filter_backends=epics_filter_backends)
|
"epics": self.filter_queryset(queryset, filter_backends=epics_filter_backends),
|
||||||
|
"roles": self.filter_queryset(queryset, filter_backends=roles_filter_backends)
|
||||||
}
|
}
|
||||||
return response.Ok(services.get_userstories_filters_data(project, querysets))
|
return response.Ok(services.get_userstories_filters_data(project, querysets))
|
||||||
|
|
||||||
|
|
|
@ -589,6 +589,60 @@ def _get_userstories_epics(project, queryset):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _get_userstories_roles(project, queryset):
|
||||||
|
compiler = connection.ops.compiler(queryset.query.compiler)(queryset.query, connection, None)
|
||||||
|
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
|
||||||
|
where = queryset_where_tuple[0]
|
||||||
|
where_params = queryset_where_tuple[1]
|
||||||
|
|
||||||
|
extra_sql = """
|
||||||
|
WITH "us_counters" AS (
|
||||||
|
SELECT DISTINCT "userstories_userstory"."status_id" "status_id",
|
||||||
|
"userstories_userstory"."id" "us_id",
|
||||||
|
"projects_membership"."role_id" "role_id"
|
||||||
|
FROM "userstories_userstory"
|
||||||
|
INNER JOIN "projects_project"
|
||||||
|
ON ("userstories_userstory"."project_id" = "projects_project"."id")
|
||||||
|
LEFT OUTER JOIN "epics_relateduserstory"
|
||||||
|
ON "userstories_userstory"."id" = "epics_relateduserstory"."user_story_id"
|
||||||
|
LEFT OUTER JOIN "projects_membership"
|
||||||
|
ON "projects_membership"."user_id" = "userstories_userstory"."assigned_to_id"
|
||||||
|
WHERE {where}
|
||||||
|
),
|
||||||
|
"counters" AS (
|
||||||
|
SELECT "role_id" as "role_id",
|
||||||
|
COUNT("role_id") "count"
|
||||||
|
FROM "us_counters"
|
||||||
|
GROUP BY "role_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT "users_role"."id",
|
||||||
|
"users_role"."name",
|
||||||
|
"users_role"."order",
|
||||||
|
COALESCE("counters"."count", 0)
|
||||||
|
FROM "users_role"
|
||||||
|
LEFT OUTER JOIN "counters"
|
||||||
|
ON "counters"."role_id" = "users_role"."id"
|
||||||
|
WHERE "users_role"."project_id" = %s
|
||||||
|
ORDER BY "users_role"."order";
|
||||||
|
""".format(where=where)
|
||||||
|
|
||||||
|
with closing(connection.cursor()) as cursor:
|
||||||
|
cursor.execute(extra_sql, where_params + [project.id])
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for id, name, order, count in rows:
|
||||||
|
result.append({
|
||||||
|
"id": id,
|
||||||
|
"name": _(name),
|
||||||
|
"color": None,
|
||||||
|
"order": order,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
return sorted(result, key=itemgetter("order"))
|
||||||
|
|
||||||
|
|
||||||
def get_userstories_filters_data(project, querysets):
|
def get_userstories_filters_data(project, querysets):
|
||||||
"""
|
"""
|
||||||
Given a project and an userstories queryset, return a simple data structure
|
Given a project and an userstories queryset, return a simple data structure
|
||||||
|
@ -600,6 +654,7 @@ def get_userstories_filters_data(project, querysets):
|
||||||
("owners", _get_userstories_owners(project, querysets["owners"])),
|
("owners", _get_userstories_owners(project, querysets["owners"])),
|
||||||
("tags", _get_userstories_tags(project, querysets["tags"])),
|
("tags", _get_userstories_tags(project, querysets["tags"])),
|
||||||
("epics", _get_userstories_epics(project, querysets["epics"])),
|
("epics", _get_userstories_epics(project, querysets["epics"])),
|
||||||
|
("roles", _get_userstories_roles(project, querysets["roles"])),
|
||||||
])
|
])
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
Loading…
Reference in New Issue