add exclude mode for issues filters

stable
Héctor Fernández Cascallar 2019-01-15 18:16:12 +01:00 committed by Alex Hermida
parent df9830bb4f
commit 77fa09a953
2 changed files with 171 additions and 85 deletions

View File

@ -375,14 +375,18 @@ class IsProjectAdminFromWebhookLogFilterBackend(FilterBackend, BaseIsProjectAdmi
class BaseRelatedFieldsFilter(FilterBackend): class BaseRelatedFieldsFilter(FilterBackend):
filter_name = None filter_name = None
param_name = None param_name = None
exclude_param_name = None
def __init__(self, filter_name=None, param_name=None): def __init__(self, filter_name=None, param_name=None, exclude_param_name=None):
if filter_name: if filter_name:
self.filter_name = filter_name self.filter_name = filter_name
if param_name: if param_name:
self.param_name = param_name self.param_name = param_name
if exclude_param_name:
self.exclude_param_name
def _prepare_filter_data(self, query_param_value): def _prepare_filter_data(self, query_param_value):
def _transform_value(value): def _transform_value(value):
try: try:
@ -396,10 +400,13 @@ class BaseRelatedFieldsFilter(FilterBackend):
values = map(_transform_value, values) values = map(_transform_value, values)
return list(values) return list(values)
def _get_queryparams(self, params): def _get_queryparams(self, params, mode=''):
param_name = self.param_name or self.filter_name if mode == 'exclude':
raw_value = params.get(param_name, None) param_name = self.exclude_param_name
else:
param_name = self.param_name or self.filter_name
raw_value = params.get(param_name, None)
if raw_value: if raw_value:
value = self._prepare_filter_data(raw_value) value = self._prepare_filter_data(raw_value)
@ -414,27 +421,39 @@ class BaseRelatedFieldsFilter(FilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
query = self._get_queryparams(request.QUERY_PARAMS) query = self._get_queryparams(request.QUERY_PARAMS)
exclude_query = None
if self.exclude_param_name:
exclude_query = self._get_queryparams(request.QUERY_PARAMS, mode='exclude')
if query: if query:
if isinstance(query, dict): if isinstance(query, dict):
queryset = queryset.filter(**query) queryset = queryset.filter(**query)
else: else:
queryset = queryset.filter(query) queryset = queryset.filter(query)
if exclude_query:
if isinstance(exclude_query, dict):
queryset = queryset.exclude(**exclude_query)
else:
queryset = queryset.exclude(exclude_query)
return super().filter_queryset(request, queryset, view) return super().filter_queryset(request, queryset, view)
class OwnersFilter(BaseRelatedFieldsFilter): class OwnersFilter(BaseRelatedFieldsFilter):
filter_name = 'owner' filter_name = 'owner'
exclude_param_name = 'exclude_owner'
class AssignedToFilter(BaseRelatedFieldsFilter): class AssignedToFilter(BaseRelatedFieldsFilter):
filter_name = 'assigned_to' filter_name = 'assigned_to'
exclude_param_name = 'exclude_assigned_to'
class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter): class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
filter_name = 'assigned_users' filter_name = 'assigned_users'
def _get_queryparams(self, params): def _get_queryparams(self, params, mode=''):
param_name = self.param_name or self.filter_name param_name = self.param_name or self.filter_name
raw_value = params.get(param_name, None) raw_value = params.get(param_name, None)
@ -461,29 +480,43 @@ class AssignedUsersFilter(FilterModelAssignedUsers, BaseRelatedFieldsFilter):
class StatusesFilter(BaseRelatedFieldsFilter): class StatusesFilter(BaseRelatedFieldsFilter):
filter_name = 'status' filter_name = 'status'
exclude_param_name = 'exclude_status'
class IssueTypesFilter(BaseRelatedFieldsFilter): class IssueTypesFilter(BaseRelatedFieldsFilter):
filter_name = 'type' filter_name = 'type'
param_name = 'type'
exclude_param_name = 'exclude_type'
class PrioritiesFilter(BaseRelatedFieldsFilter): class PrioritiesFilter(BaseRelatedFieldsFilter):
filter_name = 'priority' filter_name = 'priority'
exclude_param_name = 'exclude_priority'
class SeveritiesFilter(BaseRelatedFieldsFilter): class SeveritiesFilter(BaseRelatedFieldsFilter):
filter_name = 'severity' filter_name = 'severity'
exclude_param_name = 'exclude_severity'
class TagsFilter(FilterBackend): class TagsFilter(FilterBackend):
filter_name = 'tags' filter_name = 'tags'
exclude_param_name = 'exclude_tags'
def __init__(self, filter_name=None): def __init__(self, filter_name=None, exclude_param_name=None):
if filter_name: if filter_name:
self.filter_name = filter_name self.filter_name = filter_name
def _get_tags_queryparams(self, params): if exclude_param_name:
tags = params.get(self.filter_name, None) self.exclude_param_name = exclude_param_name
def _get_tags_queryparams(self, params, mode=''):
if mode == 'exclude':
tags = params.get(self.exclude_param_name, None)
else:
tags = params.get(self.filter_name, None)
if tags: if tags:
return tags.split(",") return tags.split(",")
@ -491,9 +524,14 @@ class TagsFilter(FilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
query_tags = self._get_tags_queryparams(request.QUERY_PARAMS) query_tags = self._get_tags_queryparams(request.QUERY_PARAMS)
exclude_query_tags = self._get_tags_queryparams(request.QUERY_PARAMS, mode='exclude')
if query_tags: if query_tags:
queryset = queryset.filter(tags__contains=query_tags) queryset = queryset.filter(tags__contains=query_tags)
if exclude_query_tags:
queryset = queryset.exclude(tags__contains=exclude_query_tags)
return super().filter_queryset(request, queryset, view) return super().filter_queryset(request, queryset, view)
@ -631,10 +669,13 @@ class QFilter(FilterBackend):
class RoleFilter(BaseRelatedFieldsFilter): class RoleFilter(BaseRelatedFieldsFilter):
filter_name = "role_id" filter_name = "role_id"
param_name = "role" param_name = "role"
exclude_param_name = "exclude_role"
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
Membership = apps.get_model('projects', 'Membership') Membership = apps.get_model('projects', 'Membership')
query = self._get_queryparams(request.QUERY_PARAMS) query = self._get_queryparams(request.QUERY_PARAMS)
exclude_query = self._get_queryparams(request.QUERY_PARAMS, mode='exclude')
if query: if query:
if isinstance(query, dict): if isinstance(query, dict):
memberships = Membership.objects.filter(**query).values_list("user_id", flat=True) memberships = Membership.objects.filter(**query).values_list("user_id", flat=True)
@ -644,6 +685,14 @@ class RoleFilter(BaseRelatedFieldsFilter):
if memberships: if memberships:
queryset = queryset.filter(assigned_to__in=memberships) queryset = queryset.filter(assigned_to__in=memberships)
if exclude_query:
if isinstance(exclude_query, dict):
memberships = Membership.objects.filter(**exclude_query).values_list("user_id", flat=True)
else:
memberships = Membership.objects.filter(exclude_query).values_list("user_id", flat=True)
if memberships:
queryset = queryset.exclude(assigned_to__in=memberships)
return FilterBackend.filter_queryset(self, request, queryset, view) return FilterBackend.filter_queryset(self, request, queryset, view)

View File

@ -39,6 +39,78 @@ import pytest
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def create_filter_issues_context():
data = {}
data["project"] = f.ProjectFactory.create()
project = data["project"]
data["users"] = [f.UserFactory.create(is_superuser=True) for i in range(0, 3)]
data["roles"] = [f.RoleFactory.create() for i in range(0, 3)]
user_roles = zip(data["users"], data["roles"])
# Add membership fixtures
[f.MembershipFactory.create(user=user, project=project, role=role) for (user, role) in user_roles]
data["statuses"] = [f.IssueStatusFactory.create(project=project) for i in range(0, 4)]
data["types"] = [f.IssueTypeFactory.create(project=project) for i in range(0, 2)]
data["severities"] = [f.SeverityFactory.create(project=project) for i in range(0, 4)]
data["priorities"] = [f.PriorityFactory.create(project=project) for i in range(0, 4)]
data["tags"] = ["test1test2test3", "test1", "test2", "test3"]
# ------------------------------------------------------------------------------------------------
# | Issue | Owner | Assigned To | Status | Type | Priority | Severity | Tags |
# |-------#--------#-------------#---------#-------#-----------#-----------#---------------------|
# | 0 | user2 | None | status3 | type1 | priority2 | severity1 | tag1 |
# | 1 | user1 | None | status3 | type2 | priority2 | severity1 | tag2 |
# | 2 | user3 | None | status1 | type1 | priority3 | severity2 | tag1 tag2 |
# | 3 | user2 | None | status0 | type2 | priority3 | severity1 | tag3 |
# | 4 | user1 | user1 | status0 | type1 | priority2 | severity3 | tag1 tag2 tag3 |
# | 5 | user3 | user1 | status2 | type2 | priority3 | severity2 | tag3 |
# | 6 | user2 | user1 | status3 | type1 | priority2 | severity0 | tag1 tag2 |
# | 7 | user1 | user2 | status0 | type2 | priority1 | severity3 | tag3 |
# | 8 | user3 | user2 | status3 | type1 | priority0 | severity1 | tag1 |
# | 9 | user2 | user3 | status1 | type2 | priority0 | severity2 | tag0 |
# ------------------------------------------------------------------------------------------------
(user1, user2, user3, ) = data["users"]
(status0, status1, status2, status3 ) = data["statuses"]
(type1, type2, ) = data["types"]
(severity0, severity1, severity2, severity3, ) = data["severities"]
(priority0, priority1, priority2, priority3, ) = data["priorities"]
(tag0, tag1, tag2, tag3, ) = data["tags"]
f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
status=status3, type=type1, priority=priority2, severity=severity1,
tags=[tag1])
f.IssueFactory.create(project=project, owner=user1, assigned_to=None,
status=status3, type=type2, priority=priority2, severity=severity1,
tags=[tag2])
f.IssueFactory.create(project=project, owner=user3, assigned_to=None,
status=status1, type=type1, priority=priority3, severity=severity2,
tags=[tag1, tag2])
f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
status=status0, type=type2, priority=priority3, severity=severity1,
tags=[tag3])
f.IssueFactory.create(project=project, owner=user1, assigned_to=user1,
status=status0, type=type1, priority=priority2, severity=severity3,
tags=[tag1, tag2, tag3])
f.IssueFactory.create(project=project, owner=user3, assigned_to=user1,
status=status2, type=type2, priority=priority3, severity=severity2,
tags=[tag3])
f.IssueFactory.create(project=project, owner=user2, assigned_to=user1,
status=status3, type=type1, priority=priority2, severity=severity0,
tags=[tag1, tag2])
f.IssueFactory.create(project=project, owner=user1, assigned_to=user2,
status=status0, type=type2, priority=priority1, severity=severity3,
tags=[tag3])
f.IssueFactory.create(project=project, owner=user3, assigned_to=user2,
status=status3, type=type1, priority=priority0, severity=severity1,
tags=[tag1])
f.IssueFactory.create(project=project, owner=user2, assigned_to=user3,
status=status1, type=type2, priority=priority0, severity=severity2,
tags=[tag0])
return data
def test_get_issues_from_bulk(): def test_get_issues_from_bulk():
data = """ data = """
Issue #1 Issue #1
@ -370,86 +442,51 @@ def test_api_filter_by_finished_date(client):
assert response.data[0]["ref"] == finished_issue.ref assert response.data[0]["ref"] == finished_issue.ref
@pytest.mark.parametrize("filter_name,collection,expected,exclude_expected,is_text", [
('type', 'types', 5, 5, False),
('severity', 'severities', 1, 9, False),
('priority', 'priorities', 2, 8, False),
('status', 'statuses', 3, 7, False),
('assigned_to', 'users', 3, 7, False),
('tags', 'tags', 1, 9, True),
('owner', 'users', 3, 7, False),
('role', 'roles', 3, 7, False),
])
def test_api_filters(client, filter_name, collection, expected, exclude_expected, is_text):
data = create_filter_issues_context()
project = data["project"]
options = data[collection]
client.login(data["users"][0])
if is_text:
param = options[0]
else:
param = options[0].id
# include test
url = f"{reverse('issues-list')}?project={project.id}&{filter_name}={param}"
response = client.get(url)
assert response.status_code == 200
assert len(response.data) == expected
# exclude test
url = f"{reverse('issues-list')}?project={project.id}&exclude_{filter_name}={param}"
response = client.get(url)
assert response.status_code == 200
assert len(response.data) == exclude_expected
def test_api_filters_data(client): def test_api_filters_data(client):
project = f.ProjectFactory.create() data = create_filter_issues_context()
user1 = f.UserFactory.create(is_superuser=True) project = data["project"]
f.MembershipFactory.create(user=user1, project=project) (user1, user2, user3, ) = data["users"]
user2 = f.UserFactory.create(is_superuser=True) (status0, status1, status2, status3, ) = data["statuses"]
f.MembershipFactory.create(user=user2, project=project) (type1, type2, ) = data["types"]
user3 = f.UserFactory.create(is_superuser=True) (priority0, priority1, priority2, priority3, ) = data["priorities"]
f.MembershipFactory.create(user=user3, project=project) (severity0, severity1, severity2, severity3, ) = data["severities"]
(tag0, tag1, tag2, tag3, ) = data["tags"]
status0 = f.IssueStatusFactory.create(project=project)
status1 = f.IssueStatusFactory.create(project=project)
status2 = f.IssueStatusFactory.create(project=project)
status3 = f.IssueStatusFactory.create(project=project)
type1 = f.IssueTypeFactory.create(project=project)
type2 = f.IssueTypeFactory.create(project=project)
severity0 = f.SeverityFactory.create(project=project)
severity1 = f.SeverityFactory.create(project=project)
severity2 = f.SeverityFactory.create(project=project)
severity3 = f.SeverityFactory.create(project=project)
priority0 = f.PriorityFactory.create(project=project)
priority1 = f.PriorityFactory.create(project=project)
priority2 = f.PriorityFactory.create(project=project)
priority3 = f.PriorityFactory.create(project=project)
tag0 = "test1test2test3"
tag1 = "test1"
tag2 = "test2"
tag3 = "test3"
# ------------------------------------------------------------------------------------------------
# | Issue | Owner | Assigned To | Status | Type | Priority | Severity | Tags |
# |-------#--------#-------------#---------#-------#-----------#-----------#---------------------|
# | 0 | user2 | None | status3 | type1 | priority2 | severity1 | tag1 |
# | 1 | user1 | None | status3 | type2 | priority2 | severity1 | tag2 |
# | 2 | user3 | None | status1 | type1 | priority3 | severity2 | tag1 tag2 |
# | 3 | user2 | None | status0 | type2 | priority3 | severity1 | tag3 |
# | 4 | user1 | user1 | status0 | type1 | priority2 | severity3 | tag1 tag2 tag3 |
# | 5 | user3 | user1 | status2 | type2 | priority3 | severity2 | tag3 |
# | 6 | user2 | user1 | status3 | type1 | priority2 | severity0 | tag1 tag2 |
# | 7 | user1 | user2 | status0 | type2 | priority1 | severity3 | tag3 |
# | 8 | user3 | user2 | status3 | type1 | priority0 | severity1 | tag1 |
# | 9 | user2 | user3 | status1 | type2 | priority0 | severity2 | tag0 |
# ------------------------------------------------------------------------------------------------
issue0 = f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
status=status3, type=type1, priority=priority2, severity=severity1,
tags=[tag1])
issue1 = f.IssueFactory.create(project=project, owner=user1, assigned_to=None,
status=status3, type=type2, priority=priority2, severity=severity1,
tags=[tag2])
issue2 = f.IssueFactory.create(project=project, owner=user3, assigned_to=None,
status=status1, type=type1, priority=priority3, severity=severity2,
tags=[tag1, tag2])
issue3 = f.IssueFactory.create(project=project, owner=user2, assigned_to=None,
status=status0, type=type2, priority=priority3, severity=severity1,
tags=[tag3])
issue4 = f.IssueFactory.create(project=project, owner=user1, assigned_to=user1,
status=status0, type=type1, priority=priority2, severity=severity3,
tags=[tag1, tag2, tag3])
issue5 = f.IssueFactory.create(project=project, owner=user3, assigned_to=user1,
status=status2, type=type2, priority=priority3, severity=severity2,
tags=[tag3])
issue6 = f.IssueFactory.create(project=project, owner=user2, assigned_to=user1,
status=status3, type=type1, priority=priority2, severity=severity0,
tags=[tag1, tag2])
issue7 = f.IssueFactory.create(project=project, owner=user1, assigned_to=user2,
status=status0, type=type2, priority=priority1, severity=severity3,
tags=[tag3])
issue8 = f.IssueFactory.create(project=project, owner=user3, assigned_to=user2,
status=status3, type=type1, priority=priority0, severity=severity1,
tags=[tag1])
issue9 = f.IssueFactory.create(project=project, owner=user2, assigned_to=user3,
status=status1, type=type2, priority=priority0, severity=severity2,
tags=[tag0])
url = reverse("issues-filters-data") + "?project={}".format(project.id) url = reverse("issues-filters-data") + "?project={}".format(project.id)
client.login(user1) client.login(user1)
## No filter ## No filter