Move tasks to another sprint
parent
fe4cddac30
commit
a17ed83755
|
@ -31,6 +31,8 @@ from taiga.projects.models import Project
|
||||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||||
from taiga.projects.notifications.mixins import WatchersViewSetMixin
|
from taiga.projects.notifications.mixins import WatchersViewSetMixin
|
||||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||||
|
from taiga.projects.tasks.validators import UpdateMilestoneBulkValidator as \
|
||||||
|
TasksUpdateMilestoneValidator
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from . import services
|
from . import services
|
||||||
|
@ -148,7 +150,7 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin,
|
||||||
def move_userstories_to_sprint(self, request, pk=None, **kwargs):
|
def move_userstories_to_sprint(self, request, pk=None, **kwargs):
|
||||||
milestone = get_object_or_404(models.Milestone, pk=pk)
|
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)
|
validator = validators.UpdateMilestoneBulkValidator(data=request.DATA)
|
||||||
if not validator.is_valid():
|
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"])
|
milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"])
|
||||||
|
|
||||||
if data["bulk_stories"]:
|
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.update_userstories_milestone_in_bulk(data["bulk_stories"], milestone_result)
|
||||||
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
|
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
|
||||||
|
|
||||||
return response.NoContent()
|
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):
|
class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
|
||||||
permission_classes = (permissions.MilestoneWatchersPermission,)
|
permission_classes = (permissions.MilestoneWatchersPermission,)
|
||||||
|
|
|
@ -33,8 +33,9 @@ class MilestonePermission(TaigaResourcePermission):
|
||||||
stats_perms = HasProjectPerm('view_milestones')
|
stats_perms = HasProjectPerm('view_milestones')
|
||||||
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
|
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
|
||||||
unwatch_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_uss_to_sprint_perms = HasProjectPerm('modify_us')
|
||||||
|
move_tasks_to_sprint_perms = HasProjectPerm('modify_task')
|
||||||
|
|
||||||
|
|
||||||
class MilestoneWatchersPermission(TaigaResourcePermission):
|
class MilestoneWatchersPermission(TaigaResourcePermission):
|
||||||
|
|
|
@ -94,3 +94,51 @@ def snapshot_userstories_in_bulk(bulk_data, user):
|
||||||
take_snapshot(us, user=user)
|
take_snapshot(us, user=user)
|
||||||
except UserStory.DoesNotExist:
|
except UserStory.DoesNotExist:
|
||||||
pass
|
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': <value>, 'order': <value>}, ...]
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
|
@ -31,9 +31,9 @@ def cached_prev_task(sender, instance, **kwargs):
|
||||||
instance.prev = sender.objects.get(id=instance.id)
|
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):
|
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)
|
_try_to_close_or_open_us_when_create_or_edit_task(instance)
|
||||||
|
|
|
@ -26,7 +26,6 @@ from urllib.parse import quote
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.projects.userstories.serializers import UserStorySerializer
|
|
||||||
|
|
||||||
from .. import factories as f
|
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=milestone1.id).user_stories.count() == 1
|
||||||
assert project.milestones.get(id=milestone2.id).user_stories.count() == 1
|
assert project.milestones.get(id=milestone2.id).user_stories.count() == 1
|
||||||
assert project.milestones.get(id=milestone1.id).closed
|
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
|
||||||
|
|
Loading…
Reference in New Issue