Add estimated_start, estimated_finish filters to tasks, userstories and milestones
parent
e3226f4cb9
commit
421a213f5e
|
@ -10,6 +10,7 @@
|
||||||
- Ability to create rich text custom fields in Epics, User Stories, Tasks and Isues.
|
- Ability to create rich text custom fields in Epics, User Stories, Tasks and Isues.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
- API: Filter milestones, user stories and tasks by estimated_start and estimated_finish dates.
|
||||||
- Lots of small and not so small bugfixes.
|
- Lots of small and not so small bugfixes.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -526,6 +526,22 @@ class FinishDateFilter(BaseDateFilter):
|
||||||
filter_name_base = "finish_date"
|
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
|
# Text search filters
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
|
@ -46,7 +46,13 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin,
|
||||||
serializer_class = serializers.MilestoneSerializer
|
serializer_class = serializers.MilestoneSerializer
|
||||||
validator_class = validators.MilestoneValidator
|
validator_class = validators.MilestoneValidator
|
||||||
permission_classes = (permissions.MilestonePermission,)
|
permission_classes = (permissions.MilestonePermission,)
|
||||||
filter_backends = (filters.CanViewMilestonesFilterBackend,)
|
filter_backends = (
|
||||||
|
filters.CanViewMilestonesFilterBackend,
|
||||||
|
filters.CreatedDateFilter,
|
||||||
|
filters.ModifiedDateFilter,
|
||||||
|
filters.EstimatedStartFilter,
|
||||||
|
filters.EstimatedFinishFilter,
|
||||||
|
)
|
||||||
filter_fields = (
|
filter_fields = (
|
||||||
"project",
|
"project",
|
||||||
"project__slug",
|
"project__slug",
|
||||||
|
|
|
@ -59,6 +59,8 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, Wa
|
||||||
filters.QFilter,
|
filters.QFilter,
|
||||||
filters.CreatedDateFilter,
|
filters.CreatedDateFilter,
|
||||||
filters.ModifiedDateFilter,
|
filters.ModifiedDateFilter,
|
||||||
|
filters.MilestoneEstimatedStartFilter,
|
||||||
|
filters.MilestoneEstimatedFinishFilter,
|
||||||
filters.FinishedDateFilter)
|
filters.FinishedDateFilter)
|
||||||
filter_fields = ["user_story",
|
filter_fields = ["user_story",
|
||||||
"milestone",
|
"milestone",
|
||||||
|
|
|
@ -70,6 +70,8 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
|
||||||
base_filters.CreatedDateFilter,
|
base_filters.CreatedDateFilter,
|
||||||
base_filters.ModifiedDateFilter,
|
base_filters.ModifiedDateFilter,
|
||||||
base_filters.FinishDateFilter,
|
base_filters.FinishDateFilter,
|
||||||
|
base_filters.MilestoneEstimatedStartFilter,
|
||||||
|
base_filters.MilestoneEstimatedFinishFilter,
|
||||||
base_filters.OrderByFilterMixin)
|
base_filters.OrderByFilterMixin)
|
||||||
filter_fields = ["project",
|
filter_fields = ["project",
|
||||||
"project__slug",
|
"project__slug",
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
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.has_header("Taiga-Info-Total-Opened-Milestones") == True
|
||||||
assert response2["taiga-info-total-closed-milestones"] == "3"
|
assert response2["taiga-info-total-closed-milestones"] == "3"
|
||||||
assert response2["taiga-info-total-opened-milestones"] == "1"
|
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
|
||||||
|
|
|
@ -702,6 +702,38 @@ def test_api_filter_by_finished_date(client):
|
||||||
assert response.data[0]["subject"] == finished_task.subject
|
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):
|
def test_api_filters_data(client):
|
||||||
project = f.ProjectFactory.create()
|
project = f.ProjectFactory.create()
|
||||||
user1 = f.UserFactory.create(is_superuser=True)
|
user1 = f.UserFactory.create(is_superuser=True)
|
||||||
|
|
|
@ -655,6 +655,37 @@ def test_api_filter_by_finish_date(client):
|
||||||
assert number_of_userstories == 1
|
assert number_of_userstories == 1
|
||||||
assert response.data[0]["subject"] == userstory_to_finish.subject
|
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):
|
def test_api_filters_data(client):
|
||||||
project = f.ProjectFactory.create()
|
project = f.ProjectFactory.create()
|
||||||
|
|
Loading…
Reference in New Issue