diff --git a/taiga/projects/tasks/api.py b/taiga/projects/tasks/api.py index bc76f24f..9584a17b 100644 --- a/taiga/projects/tasks/api.py +++ b/taiga/projects/tasks/api.py @@ -179,8 +179,8 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, self.check_permissions(request, "destroy", self.object) self.check_permissions(request, "create", new_project) - sprint_id = request.DATA.get('milestone', None) - if sprint_id is not None and new_project.milestones.filter(pk=sprint_id).count() == 0: + milestone_id = request.DATA.get('milestone', None) + if milestone_id is not None and new_project.milestones.filter(pk=milestone_id).count() == 0: request.DATA['milestone'] = None us_id = request.DATA.get('user_story', None) @@ -259,7 +259,7 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, raise exc.Blocked(_("Blocked element")) tasks = services.create_tasks_in_bulk( - data["bulk_tasks"], milestone_id=data["sprint_id"], user_story_id=data["us_id"], + data["bulk_tasks"], milestone_id=data["milestone_id"], user_story_id=data["us_id"], status_id=data.get("status_id") or project.default_task_status_id, project=project, owner=request.user, callback=self.post_save, precall=self.pre_save) diff --git a/taiga/projects/tasks/validators.py b/taiga/projects/tasks/validators.py index 033b72e3..b9061bde 100644 --- a/taiga/projects/tasks/validators.py +++ b/taiga/projects/tasks/validators.py @@ -30,7 +30,6 @@ from taiga.projects.tagging.fields import TagsAndTagsColorsField from taiga.projects.userstories.models import UserStory from taiga.projects.validators import ProjectExistsValidator - from . import models @@ -45,23 +44,27 @@ class TaskValidator(WatchersValidator, EditableWatchedResourceSerializer, valida class TasksBulkValidator(ProjectExistsValidator, validators.Validator): project_id = serializers.IntegerField() - sprint_id = serializers.IntegerField() + milestone_id = serializers.IntegerField() status_id = serializers.IntegerField(required=False) us_id = serializers.IntegerField(required=False) bulk_tasks = serializers.CharField() - def validate_sprint_id(self, attrs, source): - filters = {"project__id": attrs["project_id"]} - filters["id"] = attrs["sprint_id"] + def validate_milestone_id(self, attrs, source): + filters = { + "project__id": attrs["project_id"], + "id": attrs[source] + } if not Milestone.objects.filter(**filters).exists(): - raise ValidationError(_("Invalid sprint id.")) + raise ValidationError(_("Invalid milestone id.")) return attrs def validate_status_id(self, attrs, source): - filters = {"project__id": attrs["project_id"]} - filters["id"] = attrs["status_id"] + filters = { + "project__id": attrs["project_id"], + "id": attrs[source] + } if not TaskStatus.objects.filter(**filters).exists(): raise ValidationError(_("Invalid task status id.")) @@ -71,13 +74,13 @@ class TasksBulkValidator(ProjectExistsValidator, validators.Validator): def validate_us_id(self, attrs, source): filters = {"project__id": attrs["project_id"]} - if "sprint_id" in attrs: - filters["milestone__id"] = attrs["sprint_id"] + if "milestone_id" in attrs: + filters["milestone__id"] = attrs["milestone_id"] filters["id"] = attrs["us_id"] if not UserStory.objects.filter(**filters).exists(): - raise ValidationError(_("Invalid sprint id.")) + raise ValidationError(_("Invalid user story id.")) return attrs @@ -96,19 +99,55 @@ class UpdateTasksOrderBulkValidator(ProjectExistsValidator, validators.Validator milestone_id = serializers.IntegerField(required=False) bulk_tasks = _TaskOrderBulkValidator(many=True) - def validate(self, data): - filters = {"project__id": data["project_id"]} - if "status_id" in data: - filters["status__id"] = data["status_id"] - if "us_id" in data: - filters["user_story__id"] = data["us_id"] - if "milestone_id" in data: - filters["milestone__id"] = data["milestone_id"] + def validate_status_id(self, attrs, source): + filters = {"project__id": attrs["project_id"]} + filters["id"] = attrs[source] - filters["id__in"] = [t["task_id"] for t in data["bulk_tasks"]] + if not TaskStatus.objects.filter(**filters).exists(): + raise ValidationError(_("Invalid task status id. The status must belong to " + "the same project.")) + + return attrs + + def validate_us_id(self, attrs, source): + filters = {"project__id": attrs["project_id"]} + + if "milestone_id" in attrs: + filters["milestone__id"] = attrs["milestone_id"] + + filters["id"] = attrs[source] + + if not UserStory.objects.filter(**filters).exists(): + raise ValidationError(_("Invalid user story id. The user story must belong to " + "the same project.")) + + return attrs + + def validate_milestone_id(self, attrs, source): + filters = { + "project__id": attrs["project_id"], + "id": attrs[source] + } + + if not Milestone.objects.filter(**filters).exists(): + raise ValidationError(_("Invalid milestone id. The milestone must belong to " + "the same project.")) + + return attrs + + def validate_bulk_tasks(self, attrs, source): + filters = {"project__id": attrs["project_id"]} + if "status_id" in attrs: + filters["status__id"] = attrs["status_id"] + if "us_id" in attrs: + filters["user_story__id"] = attrs["us_id"] + if "milestone_id" in attrs: + filters["milestone__id"] = attrs["milestone_id"] + + filters["id__in"] = [t["task_id"] for t in attrs[source]] if models.Task.objects.filter(**filters).count() != len(filters["id__in"]): raise ValidationError(_("Invalid task ids. All tasks must belong to the same project and, " "if it exists, to the same status, user story and/or milestone.")) - return data + return attrs diff --git a/taiga/projects/userstories/validators.py b/taiga/projects/userstories/validators.py index bc52db1a..e20a704e 100644 --- a/taiga/projects/userstories/validators.py +++ b/taiga/projects/userstories/validators.py @@ -73,13 +73,14 @@ class UserStoriesBulkValidator(ProjectExistsValidator, validators.Validator): bulk_stories = serializers.CharField() def validate_status_id(self, attrs, source): - filters = {"project__id": attrs["project_id"]} - if source in attrs: - filters["id"] = attrs[source] + filters = { + "project__id": attrs["project_id"], + "id": attrs[source] + } - if not UserStoryStatus.objects.filter(**filters).exists(): - raise ValidationError(_("Invalid user story status id. The status must belong to " - "the same project.")) + if not UserStoryStatus.objects.filter(**filters).exists(): + raise ValidationError(_("Invalid user story status id. The status must belong to " + "the same project.")) return attrs @@ -98,24 +99,26 @@ class UpdateUserStoriesOrderBulkValidator(ProjectExistsValidator, validators.Val bulk_stories = _UserStoryOrderBulkValidator(many=True) def validate_status_id(self, attrs, source): - filters = {"project__id": attrs["project_id"]} - if source in attrs: - filters["id"] = attrs[source] + filters = { + "project__id": attrs["project_id"], + "id": attrs[source] + } - if not UserStoryStatus.objects.filter(**filters).exists(): - raise ValidationError(_("Invalid user story status id. The status must belong " - "to the same project.")) + if not UserStoryStatus.objects.filter(**filters).exists(): + raise ValidationError(_("Invalid user story status id. The status must belong " + "to the same project.")) return attrs def validate_milestone_id(self, attrs, source): - filters = {"project__id": attrs["project_id"]} - if source in attrs: - filters["id"] = attrs[source] + filters = { + "project__id": attrs["project_id"], + "id": attrs[source] + } - if not Milestone.objects.filter(**filters).exists(): - raise ValidationError(_("Invalid milestone id. The milistone must belong to the " - "same project.")) + if not Milestone.objects.filter(**filters).exists(): + raise ValidationError(_("Invalid milestone id. The milistone must belong to the " + "same project.")) return attrs diff --git a/tests/integration/test_tasks.py b/tests/integration/test_tasks.py index c9fd4d01..04546233 100644 --- a/tests/integration/test_tasks.py +++ b/tests/integration/test_tasks.py @@ -104,7 +104,7 @@ def test_api_create_in_bulk_with_status_milestone_userstory(client): "bulk_tasks": "Story #1\nStory #2", "us_id": us.id, "project_id": us.project.id, - "sprint_id": us.milestone.id, + "milestone_id": us.milestone.id, "status_id": us.project.default_task_status.id } @@ -129,7 +129,7 @@ def test_api_create_in_bulk_with_status_milestone(client): data = { "bulk_tasks": "Story #1\nStory #2", "project_id": us.project.id, - "sprint_id": us.milestone.id, + "milestone_id": us.milestone.id, "status_id": us.project.default_task_status.id } @@ -158,7 +158,7 @@ def test_api_create_in_bulk_with_invalid_status(client): "bulk_tasks": "Story #1\nStory #2", "us_id": us.id, "project_id": project.id, - "sprint_id": milestone.id, + "milestone_id": milestone.id, "status_id": status.id } @@ -166,6 +166,7 @@ def test_api_create_in_bulk_with_invalid_status(client): response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 + assert "status_id" in response.data def test_api_create_in_bulk_with_invalid_milestone(client): @@ -183,7 +184,7 @@ def test_api_create_in_bulk_with_invalid_milestone(client): "bulk_tasks": "Story #1\nStory #2", "us_id": us.id, "project_id": project.id, - "sprint_id": milestone.id, + "milestone_id": milestone.id, "status_id": project.default_task_status.id } @@ -191,6 +192,7 @@ def test_api_create_in_bulk_with_invalid_milestone(client): response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 + assert "milestone_id" in response.data def test_api_create_in_bulk_with_invalid_userstory_1(client): @@ -208,7 +210,7 @@ def test_api_create_in_bulk_with_invalid_userstory_1(client): "bulk_tasks": "Story #1\nStory #2", "us_id": us.id, "project_id": project.id, - "sprint_id": milestone.id, + "milestone_id": milestone.id, "status_id": project.default_task_status.id } @@ -216,6 +218,7 @@ def test_api_create_in_bulk_with_invalid_userstory_1(client): response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 + assert "us_id" in response.data def test_api_create_in_bulk_with_invalid_userstory_2(client): @@ -233,7 +236,7 @@ def test_api_create_in_bulk_with_invalid_userstory_2(client): "bulk_tasks": "Story #1\nStory #2", "us_id": us.id, "project_id": us.project.id, - "sprint_id": milestone.id, + "milestone_id": milestone.id, "status_id": us.project.default_task_status.id } @@ -241,6 +244,7 @@ def test_api_create_in_bulk_with_invalid_userstory_2(client): response = client.json.post(url, json.dumps(data)) assert response.status_code == 400 + assert "us_id" in response.data def test_api_create_invalid_task(client): @@ -309,14 +313,17 @@ def test_api_update_order_in_bulk_invalid_tasks(client): client.login(project.owner) - response1 = client.json.post(url1, json.dumps(data)) - response2 = client.json.post(url2, json.dumps(data)) + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data - assert response1.status_code == 400, response1.data - assert response2.status_code == 400, response2.data + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data -def test_api_update_order_in_bulk_invalid_status(client): + +def test_api_update_order_in_bulk_invalid_tasks_for_status(client): project = f.create_project() f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) task1 = f.create_task(project=project) @@ -336,14 +343,16 @@ def test_api_update_order_in_bulk_invalid_status(client): client.login(project.owner) - response1 = client.json.post(url1, json.dumps(data)) - response2 = client.json.post(url2, json.dumps(data)) + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data - assert response1.status_code == 400, response1.data - assert response2.status_code == 400, response2.data + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data -def test_api_update_order_in_bulk_invalid_milestone(client): +def test_api_update_order_in_bulk_invalid_tasks_for_milestone(client): project = f.create_project() f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) mil1 = f.MilestoneFactory.create(project=project) @@ -364,19 +373,21 @@ def test_api_update_order_in_bulk_invalid_milestone(client): client.login(project.owner) - response1 = client.json.post(url1, json.dumps(data)) - response2 = client.json.post(url2, json.dumps(data)) + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data - assert response1.status_code == 400, response1.data - assert response2.status_code == 400, response2.data + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data -def test_api_update_order_in_bulk_invalid_user_story(client): +def test_api_update_order_in_bulk_invalid_tasks_for_user_story(client): project = f.create_project() f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) us1 = f.create_userstory(project=project) - task1 = f.create_task(project=project, user_story=us1) - task2 = f.create_task(project=project, user_story=us1) + task1 = f.create_task(project=project) + task2 = f.create_task(project=project) task3 = f.create_task(project=project) url1 = reverse("tasks-bulk-update-taskboard-order") @@ -392,11 +403,143 @@ def test_api_update_order_in_bulk_invalid_user_story(client): client.login(project.owner) - response1 = client.json.post(url1, json.dumps(data)) - response2 = client.json.post(url2, json.dumps(data)) + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data - assert response1.status_code == 400, response1.data - assert response2.status_code == 400, response2.data + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "bulk_tasks" in response.data + + +def test_api_update_order_in_bulk_invalid_status(client): + project = f.create_project() + f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) + status = f.TaskStatusFactory.create() + task1 = f.create_task(project=project) + task2 = f.create_task(project=project) + task3 = f.create_task(project=project) + + url1 = reverse("tasks-bulk-update-taskboard-order") + url2 = reverse("tasks-bulk-update-us-order") + + data = { + "project_id": project.id, + "status_id": status.id, + "bulk_tasks": [{"task_id": task1.id, "order": 1}, + {"task_id": task2.id, "order": 2}, + {"task_id": task3.id, "order": 3}] + } + + client.login(project.owner) + + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "status_id" in response.data + assert "bulk_tasks" in response.data + + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "status_id" in response.data + assert "bulk_tasks" in response.data + + +def test_api_update_order_in_bulk_invalid_milestone(client): + project = f.create_project() + f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) + mil1 = f.MilestoneFactory.create() + task1 = f.create_task(project=project) + task2 = f.create_task(project=project) + task3 = f.create_task(project=project) + + url1 = reverse("tasks-bulk-update-taskboard-order") + url2 = reverse("tasks-bulk-update-us-order") + + data = { + "project_id": project.id, + "milestone_id": mil1.id, + "bulk_tasks": [{"task_id": task1.id, "order": 1}, + {"task_id": task2.id, "order": 2}, + {"task_id": task3.id, "order": 3}] + } + + client.login(project.owner) + + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "milestone_id" in response.data + assert "bulk_tasks" in response.data + + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "milestone_id" in response.data + assert "bulk_tasks" in response.data + + +def test_api_update_order_in_bulk_invalid_user_story_1(client): + project = f.create_project() + f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) + us1 = f.create_userstory() + task1 = f.create_task(project=project) + task2 = f.create_task(project=project) + task3 = f.create_task(project=project) + + url1 = reverse("tasks-bulk-update-taskboard-order") + url2 = reverse("tasks-bulk-update-us-order") + + data = { + "project_id": project.id, + "us_id": us1.id, + "bulk_tasks": [{"task_id": task1.id, "order": 1}, + {"task_id": task2.id, "order": 2}, + {"task_id": task3.id, "order": 3}] + } + + client.login(project.owner) + + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "us_id" in response.data + assert "bulk_tasks" in response.data + + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "us_id" in response.data + assert "bulk_tasks" in response.data + + +def test_api_update_order_in_bulk_invalid_user_story_2(client): + project = f.create_project() + f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) + milestone = f.MilestoneFactory.create(project=project) + us1 = f.create_userstory(project=project) + task1 = f.create_task(project=project) + task2 = f.create_task(project=project) + task3 = f.create_task(project=project) + + url1 = reverse("tasks-bulk-update-taskboard-order") + url2 = reverse("tasks-bulk-update-us-order") + + data = { + "project_id": project.id, + "us_id": us1.id, + "milestone_id": milestone.id, + "bulk_tasks": [{"task_id": task1.id, "order": 1}, + {"task_id": task2.id, "order": 2}, + {"task_id": task3.id, "order": 3}] + } + + client.login(project.owner) + + response = client.json.post(url1, json.dumps(data)) + assert response.status_code == 400, response.data + assert "us_id" in response.data + assert "bulk_tasks" in response.data + + response = client.json.post(url2, json.dumps(data)) + assert response.status_code == 400, response.data + assert "us_id" in response.data + assert "bulk_tasks" in response.data def test_get_invalid_csv(client):