From 421a213f5e8d6c2d8ee5d425ae8705898e9ff9bd Mon Sep 17 00:00:00 2001 From: Michael Jurke Date: Tue, 8 Nov 2016 21:40:02 +0100 Subject: [PATCH] Add estimated_start, estimated_finish filters to tasks, userstories and milestones --- CHANGELOG.md | 1 + taiga/base/filters.py | 16 +++++ taiga/projects/milestones/api.py | 8 ++- taiga/projects/tasks/api.py | 2 + taiga/projects/userstories/api.py | 2 + tests/integration/test_milestones.py | 98 +++++++++++++++++++++++++++ tests/integration/test_tasks.py | 32 +++++++++ tests/integration/test_userstories.py | 31 +++++++++ 8 files changed, 189 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08e9819c..644df116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Ability to create rich text custom fields in Epics, User Stories, Tasks and Isues. ### Misc +- API: Filter milestones, user stories and tasks by estimated_start and estimated_finish dates. - Lots of small and not so small bugfixes. diff --git a/taiga/base/filters.py b/taiga/base/filters.py index 06274128..80c7e280 100644 --- a/taiga/base/filters.py +++ b/taiga/base/filters.py @@ -526,6 +526,22 @@ class FinishDateFilter(BaseDateFilter): filter_name_base = "finish_date" +class EstimatedStartFilter(BaseDateFilter): + filter_name_base = "estimated_start" + + +class EstimatedFinishFilter(BaseDateFilter): + filter_name_base = "estimated_finish" + + +class MilestoneEstimatedStartFilter(BaseDateFilter): + filter_name_base = "milestone__estimated_start" + + +class MilestoneEstimatedFinishFilter(BaseDateFilter): + filter_name_base = "milestone__estimated_finish" + + ##################################################################### # Text search filters ##################################################################### diff --git a/taiga/projects/milestones/api.py b/taiga/projects/milestones/api.py index 820e90c7..e53c8110 100644 --- a/taiga/projects/milestones/api.py +++ b/taiga/projects/milestones/api.py @@ -46,7 +46,13 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, serializer_class = serializers.MilestoneSerializer validator_class = validators.MilestoneValidator permission_classes = (permissions.MilestonePermission,) - filter_backends = (filters.CanViewMilestonesFilterBackend,) + filter_backends = ( + filters.CanViewMilestonesFilterBackend, + filters.CreatedDateFilter, + filters.ModifiedDateFilter, + filters.EstimatedStartFilter, + filters.EstimatedFinishFilter, + ) filter_fields = ( "project", "project__slug", diff --git a/taiga/projects/tasks/api.py b/taiga/projects/tasks/api.py index 39fb8cbc..75026865 100644 --- a/taiga/projects/tasks/api.py +++ b/taiga/projects/tasks/api.py @@ -59,6 +59,8 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, Wa filters.QFilter, filters.CreatedDateFilter, filters.ModifiedDateFilter, + filters.MilestoneEstimatedStartFilter, + filters.MilestoneEstimatedFinishFilter, filters.FinishedDateFilter) filter_fields = ["user_story", "milestone", diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py index bb888f88..3df5ddc9 100644 --- a/taiga/projects/userstories/api.py +++ b/taiga/projects/userstories/api.py @@ -70,6 +70,8 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi base_filters.CreatedDateFilter, base_filters.ModifiedDateFilter, base_filters.FinishDateFilter, + base_filters.MilestoneEstimatedStartFilter, + base_filters.MilestoneEstimatedFinishFilter, base_filters.OrderByFilterMixin) filter_fields = ["project", "project__slug", diff --git a/tests/integration/test_milestones.py b/tests/integration/test_milestones.py index 18562a8e..61a54bc8 100644 --- a/tests/integration/test_milestones.py +++ b/tests/integration/test_milestones.py @@ -18,6 +18,10 @@ # along with this program. If not, see . import pytest +import pytz + +from datetime import datetime, timedelta +from urllib.parse import quote from django.core.urlresolvers import reverse @@ -82,3 +86,97 @@ def test_list_milestones_taiga_info_headers(client): assert response2.has_header("Taiga-Info-Total-Opened-Milestones") == True assert response2["taiga-info-total-closed-milestones"] == "3" assert response2["taiga-info-total-opened-milestones"] == "1" + + +def test_api_filter_by_created_date__lte(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project) + f.MembershipFactory.create( + project=project, user=user, role=role, is_admin=True + ) + one_day_ago = datetime.now(pytz.utc) - timedelta(days=1) + + old_milestone = f.MilestoneFactory.create( + project=project, owner=user, created_date=one_day_ago + ) + milestone = f.MilestoneFactory.create(project=project, owner=user) + + url = reverse("milestones-list") + "?created_date__lte=%s" % ( + quote(milestone.created_date.isoformat()) + ) + + client.login(milestone.owner) + response = client.get(url) + number_of_milestones = len(response.data) + + assert response.status_code == 200 + assert number_of_milestones == 2 + + +def test_api_filter_by_modified_date__gte(client): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project) + f.MembershipFactory.create( + project=project, user=user, role=role, is_admin=True + ) + one_day_ago = datetime.now(pytz.utc) - timedelta(days=1) + + older_milestone = f.MilestoneFactory.create( + project=project, owner=user, created_date=one_day_ago + ) + milestone = f.MilestoneFactory.create(project=project, owner=user) + # we have to refresh as it slightly differs + milestone.refresh_from_db() + + assert older_milestone.modified_date < milestone.modified_date + + url = reverse("milestones-list") + "?modified_date__gte=%s" % ( + quote(milestone.modified_date.isoformat()) + ) + + client.login(milestone.owner) + response = client.get(url) + number_of_milestones = len(response.data) + + assert response.status_code == 200 + assert number_of_milestones == 1 + assert response.data[0]["slug"] == milestone.slug + + +@pytest.mark.parametrize("field_name", [ + "estimated_start", "estimated_finish" +]) +def test_api_filter_by_milestone__estimated_start_and_end(client, field_name): + user = f.UserFactory.create() + project = f.ProjectFactory.create(owner=user) + role = f.RoleFactory.create(project=project) + f.MembershipFactory.create( + project=project, user=user, role=role, is_admin=True + ) + milestone = f.MilestoneFactory.create(project=project, owner=user) + + assert hasattr(milestone, field_name) + date = getattr(milestone, field_name) + before = (date - timedelta(days=1)).isoformat() + after = (date + timedelta(days=1)).isoformat() + + client.login(milestone.owner) + + expections = { + field_name + "__gte=" + quote(before): 1, + field_name + "__gte=" + quote(after): 0, + field_name + "__lte=" + quote(before): 0, + field_name + "__lte=" + quote(after): 1 + } + + for param, expection in expections.items(): + url = reverse("milestones-list") + "?" + param + response = client.get(url) + number_of_milestones = len(response.data) + + assert response.status_code == 200 + assert number_of_milestones == expection, param + if number_of_milestones > 0: + assert response.data[0]["slug"] == milestone.slug diff --git a/tests/integration/test_tasks.py b/tests/integration/test_tasks.py index 04546233..cdda8375 100644 --- a/tests/integration/test_tasks.py +++ b/tests/integration/test_tasks.py @@ -702,6 +702,38 @@ def test_api_filter_by_finished_date(client): assert response.data[0]["subject"] == finished_task.subject +@pytest.mark.parametrize("field_name", ["estimated_start", "estimated_finish"]) +def test_api_filter_by_milestone__estimated_start_and_end(client, field_name): + user = f.UserFactory(is_superuser=True) + task = f.create_task(owner=user) + + assert task.milestone + assert hasattr(task.milestone, field_name) + date = getattr(task.milestone, field_name) + before = (date - timedelta(days=1)).isoformat() + after = (date + timedelta(days=1)).isoformat() + + client.login(task.owner) + + full_field_name = "milestone__" + field_name + expections = { + full_field_name + "__gte=" + quote(before): 1, + full_field_name + "__gte=" + quote(after): 0, + full_field_name + "__lte=" + quote(before): 0, + full_field_name + "__lte=" + quote(after): 1 + } + + for param, expection in expections.items(): + url = reverse("tasks-list") + "?" + param + response = client.get(url) + number_of_tasks = len(response.data) + + assert response.status_code == 200 + assert number_of_tasks == expection, param + if number_of_tasks > 0: + assert response.data[0]["subject"] == task.subject + + def test_api_filters_data(client): project = f.ProjectFactory.create() user1 = f.UserFactory.create(is_superuser=True) diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index daae88a5..3ff5f345 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -655,6 +655,37 @@ def test_api_filter_by_finish_date(client): assert number_of_userstories == 1 assert response.data[0]["subject"] == userstory_to_finish.subject +@pytest.mark.parametrize("field_name", ["estimated_start", "estimated_finish"]) +def test_api_filter_by_milestone__estimated_start_and_end(client, field_name): + user = f.UserFactory(is_superuser=True) + userstory = f.create_userstory(owner=user) + + assert userstory.milestone + assert hasattr(userstory.milestone, field_name) + date = getattr(userstory.milestone, field_name) + before = (date - timedelta(days=1)).isoformat() + after = (date + timedelta(days=1)).isoformat() + + client.login(userstory.owner) + + full_field_name = "milestone__" + field_name + expections = { + full_field_name + "__gte=" + quote(before): 1, + full_field_name + "__gte=" + quote(after): 0, + full_field_name + "__lte=" + quote(before): 0, + full_field_name + "__lte=" + quote(after): 1 + } + + for param, expection in expections.items(): + url = reverse("userstories-list") + "?" + param + response = client.get(url) + number_of_userstories = len(response.data) + + assert response.status_code == 200 + assert number_of_userstories == expection, param + if number_of_userstories > 0: + assert response.data[0]["subject"] == userstory.subject + def test_api_filters_data(client): project = f.ProjectFactory.create()