From a17ed83755cb7e6e66d579d8ff00995cf38d810c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Hermida?= Date: Fri, 28 Dec 2018 12:44:12 +0100 Subject: [PATCH] Move tasks to another sprint --- taiga/projects/milestones/api.py | 27 +++++++++- taiga/projects/milestones/permissions.py | 3 +- taiga/projects/milestones/services.py | 48 +++++++++++++++++ taiga/projects/tasks/signals.py | 6 +-- tests/integration/test_milestones.py | 65 +++++++++++++++++++++++- 5 files changed, 142 insertions(+), 7 deletions(-) diff --git a/taiga/projects/milestones/api.py b/taiga/projects/milestones/api.py index 5493c80c..2071ce84 100644 --- a/taiga/projects/milestones/api.py +++ b/taiga/projects/milestones/api.py @@ -31,6 +31,8 @@ from taiga.projects.models import Project from taiga.projects.notifications.mixins import WatchedResourceMixin from taiga.projects.notifications.mixins import WatchersViewSetMixin from taiga.projects.history.mixins import HistoryResourceMixin +from taiga.projects.tasks.validators import UpdateMilestoneBulkValidator as \ + TasksUpdateMilestoneValidator from . import serializers from . import services @@ -148,7 +150,7 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, def move_userstories_to_sprint(self, request, pk=None, **kwargs): milestone = get_object_or_404(models.Milestone, pk=pk) - self.check_permissions(request, "bulk_update_items", milestone) + self.check_permissions(request, "move_related_items", milestone) validator = validators.UpdateMilestoneBulkValidator(data=request.DATA) if not validator.is_valid(): @@ -159,12 +161,33 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"]) if data["bulk_stories"]: - permissions = self.check_permissions(request, "move_uss_to_sprint", project) + self.check_permissions(request, "move_uss_to_sprint", project) services.update_userstories_milestone_in_bulk(data["bulk_stories"], milestone_result) services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user) return response.NoContent() + @detail_route(methods=["POST"]) + def move_tasks_to_sprint(self, request, pk=None, **kwargs): + milestone = get_object_or_404(models.Milestone, pk=pk) + + self.check_permissions(request, "move_related_items", milestone) + + validator = TasksUpdateMilestoneValidator(data=request.DATA) + if not validator.is_valid(): + return response.BadRequest(validator.errors) + + data = validator.data + project = get_object_or_404(Project, pk=data["project_id"]) + milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"]) + + if data["bulk_tasks"]: + self.check_permissions(request, "move_tasks_to_sprint", project) + services.update_tasks_milestone_in_bulk(data["bulk_tasks"], milestone_result) + services.snapshot_tasks_in_bulk(data["bulk_tasks"], request.user) + + return response.NoContent() + class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet): permission_classes = (permissions.MilestoneWatchersPermission,) diff --git a/taiga/projects/milestones/permissions.py b/taiga/projects/milestones/permissions.py index 4010203c..a4454c19 100644 --- a/taiga/projects/milestones/permissions.py +++ b/taiga/projects/milestones/permissions.py @@ -33,8 +33,9 @@ class MilestonePermission(TaigaResourcePermission): stats_perms = HasProjectPerm('view_milestones') watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones') unwatch_perms = IsAuthenticated() & HasProjectPerm('view_milestones') - bulk_update_items_perms = HasProjectPerm('modify_milestone') + move_related_items_perms = HasProjectPerm('modify_milestone') move_uss_to_sprint_perms = HasProjectPerm('modify_us') + move_tasks_to_sprint_perms = HasProjectPerm('modify_task') class MilestoneWatchersPermission(TaigaResourcePermission): diff --git a/taiga/projects/milestones/services.py b/taiga/projects/milestones/services.py index 37e47601..017b16c5 100644 --- a/taiga/projects/milestones/services.py +++ b/taiga/projects/milestones/services.py @@ -94,3 +94,51 @@ def snapshot_userstories_in_bulk(bulk_data, user): take_snapshot(us, user=user) except UserStory.DoesNotExist: pass + + +def update_tasks_milestone_in_bulk(bulk_data: list, milestone: object): + """ + Update the milestone and the milestone order of some tasks adding + the extra orders needed to keep consistency. + `bulk_data` should be a list of dicts with the following format: + [{'task_id': , 'order': }, ...] + """ + tasks = milestone.tasks.all() + task_orders = {task.id: getattr(task, "taskboard_order") for task in tasks} + new_task_orders = {} + for e in bulk_data: + new_task_orders[e["task_id"]] = e["order"] + # The base orders where we apply the new orders must containg all + # the values + task_orders[e["task_id"]] = e["order"] + + apply_order_updates(task_orders, new_task_orders) + + task_milestones = {e["task_id"]: milestone.id for e in bulk_data} + task_ids = task_milestones.keys() + + events.emit_event_for_ids(ids=task_ids, + content_type="tasks.task", + projectid=milestone.project.pk) + + + task_instance_list = [] + task_values = [] + for task_id in task_ids: + task = Task.objects.get(pk=task_id) + task_instance_list.append(task) + task_values.append({'milestone_id': milestone.id}) + + db.update_in_bulk(task_instance_list, task_values) + db.update_attr_in_bulk_for_ids(task_orders, "taskboard_order", Task) + + return task_milestones + + +def snapshot_tasks_in_bulk(bulk_data, user): + for task_data in bulk_data: + try: + task = Task.objects.get(pk=task_data['task_id']) + take_snapshot(task, user=user) + except Task.DoesNotExist: + pass diff --git a/taiga/projects/tasks/signals.py b/taiga/projects/tasks/signals.py index aa40ca96..6dd981b7 100644 --- a/taiga/projects/tasks/signals.py +++ b/taiga/projects/tasks/signals.py @@ -31,9 +31,9 @@ def cached_prev_task(sender, instance, **kwargs): instance.prev = sender.objects.get(id=instance.id) -#################################### -# Signals for close US and Milestone -#################################### +###################################### +# Signals for close Task and Milestone +###################################### def try_to_close_or_open_us_and_milestone_when_create_or_edit_task(sender, instance, created, **kwargs): _try_to_close_or_open_us_when_create_or_edit_task(instance) diff --git a/tests/integration/test_milestones.py b/tests/integration/test_milestones.py index 3e488f70..ce94ceb5 100644 --- a/tests/integration/test_milestones.py +++ b/tests/integration/test_milestones.py @@ -26,7 +26,6 @@ from urllib.parse import quote from django.core.urlresolvers import reverse from taiga.base.utils import json -from taiga.projects.userstories.serializers import UserStorySerializer from .. import factories as f @@ -270,3 +269,67 @@ def test_api_move_userstories_to_another_sprint_close_previous(client): assert project.milestones.get(id=milestone1.id).user_stories.count() == 1 assert project.milestones.get(id=milestone2.id).user_stories.count() == 1 assert project.milestones.get(id=milestone1.id).closed + + +def test_api_move_tasks_to_another_sprint(client): + project = f.create_project() + f.MembershipFactory.create(project=project, user=project.owner, + is_admin=True) + milestone1 = f.MilestoneFactory.create(project=project) + milestone2 = f.MilestoneFactory.create(project=project) + + task1 = f.create_task(project=project, milestone=milestone1, taskboard_order=1) + task2 = f.create_task(project=project, milestone=milestone1, taskboard_order=2) + + assert project.milestones.get(id=milestone1.id).tasks.count() == 2 + + url = reverse("milestones-move-tasks-to-sprint", kwargs={"pk": milestone1.pk}) + data = { + "project_id": project.id, + "milestone_id": milestone2.id, + "bulk_tasks": [{"task_id": task2.id, "order": 2}] + } + client.login(project.owner) + + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 204, response.data + assert project.milestones.get(id=milestone1.id).tasks.count() == 1 + assert project.milestones.get(id=milestone2.id).tasks.count() == 1 + + +def test_api_move_tasks_to_another_sprint_close_previous(client): + project = f.create_project() + f.MembershipFactory.create(project=project, user=project.owner, + is_admin=True) + milestone1 = f.MilestoneFactory.create(project=project) + + milestone2 = f.MilestoneFactory.create(project=project) + + closed_status = f.TaskStatusFactory.create(project=project, is_closed=True) + task1 = f.create_task(project=project, milestone=milestone1, taskboard_order=1, + status=closed_status) + task2 = f.create_task(project=project, milestone=milestone1, taskboard_order=2) + + milestone = project.milestones.get(id=milestone1.id) + manager_tasks = milestone.tasks + # all_tasks = list(manager_tasks.all()) + count_result = manager_tasks.count() + + assert count_result == 2 + assert not milestone1.closed + + url = reverse("milestones-move-tasks-to-sprint", kwargs={"pk": milestone1.pk}) + data = { + "project_id": project.id, + "milestone_id": milestone2.id, + "bulk_tasks": [{"task_id": task2.id, "order": 2}] + } + client.login(project.owner) + + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 204, response.data + assert project.milestones.get(id=milestone1.id).tasks.count() == 1 + assert project.milestones.get(id=milestone2.id).tasks.count() == 1 + assert project.milestones.get(id=milestone1.id).closed