diff --git a/taiga/projects/tagging/services.py b/taiga/projects/tagging/services.py index 30e9f9dc..43cf8567 100644 --- a/taiga/projects/tagging/services.py +++ b/taiga/projects/tagging/services.py @@ -24,7 +24,7 @@ def tag_exist_for_project_elements(project, tag): def create_tags(project, new_tags_colors): - project.tags_colors += [[k, v] for k,v in new_tags_colors.items()] + project.tags_colors += [[k, v] for k, v in new_tags_colors.items()] project.save(update_fields=["tags_colors"]) @@ -33,41 +33,8 @@ def create_tag(project, tag, color): project.save(update_fields=["tags_colors"]) -def edit_tag(project, from_tag, to_tag=None, color=None): - tags_colors = dict(project.tags_colors) - - if color is not None: - tags_colors = dict(project.tags_colors) - tags_colors[from_tag] = color - - if to_tag is not None: - color = dict(project.tags_colors)[from_tag] - sql = """ - UPDATE userstories_userstory - SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) - WHERE project_id = {project_id}; - - UPDATE tasks_task - SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) - WHERE project_id = {project_id}; - - UPDATE issues_issue - SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) - WHERE project_id = {project_id}; - """ - sql = sql.format(project_id=project.id, from_tag=from_tag, to_tag=to_tag) - cursor = connection.cursor() - cursor.execute(sql) - - tags_colors[to_tag] = tags_colors.pop(from_tag) - - - project.tags_colors = list(tags_colors.items()) - project.save(update_fields=["tags_colors"]) - - -def rename_tag(project, from_tag, to_tag, color=None): - color = color or dict(project.tags_colors)[from_tag] +def edit_tag(project, from_tag, to_tag, color): + print("edit_tag", project, from_tag, to_tag, color) sql = """ UPDATE userstories_userstory SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) @@ -80,6 +47,45 @@ def rename_tag(project, from_tag, to_tag, color=None): UPDATE issues_issue SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) WHERE project_id = {project_id}; + + UPDATE epics_epic + SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) + WHERE project_id = {project_id}; + """ + sql = sql.format(project_id=project.id, from_tag=from_tag, to_tag=to_tag) + cursor = connection.cursor() + cursor.execute(sql) + + tags_colors = dict(project.tags_colors) + tags_colors.pop(from_tag) + tags_colors[to_tag] = color + project.tags_colors = list(tags_colors.items()) + project.save(update_fields=["tags_colors"]) + + +def rename_tag(project, from_tag, to_tag, **kwargs): + # Kwargs can have a color parameter + update_color = "color" in kwargs + if update_color: + color = kwargs.get("color") + else: + color = dict(project.tags_colors)[from_tag] + sql = """ + UPDATE userstories_userstory + SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) + WHERE project_id = {project_id}; + + UPDATE tasks_task + SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) + WHERE project_id = {project_id}; + + UPDATE issues_issue + SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) + WHERE project_id = {project_id}; + + UPDATE epics_epic + SET tags = array_distinct(array_replace(tags, '{from_tag}', '{to_tag}')) + WHERE project_id = {project_id}; """ sql = sql.format(project_id=project.id, from_tag=from_tag, to_tag=to_tag, color=color) cursor = connection.cursor() @@ -105,6 +111,10 @@ def delete_tag(project, tag): UPDATE issues_issue SET tags = array_remove(tags, '{tag}') WHERE project_id = {project_id}; + + UPDATE epics_epic + SET tags = array_remove(tags, '{tag}') + WHERE project_id = {project_id}; """ sql = sql.format(project_id=project.id, tag=tag) cursor = connection.cursor() @@ -119,4 +129,4 @@ def delete_tag(project, tag): def mix_tags(project, from_tags, to_tag): color = dict(project.tags_colors)[to_tag] for from_tag in from_tags: - rename_tag(project, from_tag, to_tag, color) + rename_tag(project, from_tag, to_tag, color=color) diff --git a/taiga/projects/tagging/validators.py b/taiga/projects/tagging/validators.py index 779c247c..ea0c32c8 100644 --- a/taiga/projects/tagging/validators.py +++ b/taiga/projects/tagging/validators.py @@ -82,6 +82,15 @@ class EditTagTagValidator(ProjectTagValidator): return attrs + def validate(self, data): + if "to_tag" not in data: + data["to_tag"] = data.get("from_tag") + + if "color" not in data: + data["color"] = dict(self.project.tags_colors).get(data.get("from_tag")) + + return data + class DeleteTagValidator(ProjectTagValidator): tag = serializers.CharField() diff --git a/tests/factories.py b/tests/factories.py index 5cec5800..50f35122 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -670,6 +670,26 @@ def create_userstory(**kwargs): return UserStoryFactory(**defaults) +def create_epic(**kwargs): + "Create an epic along with its dependencies" + + owner = kwargs.pop("owner", None) + if not owner: + owner = UserFactory.create() + + project = kwargs.pop("project", None) + if project is None: + project = ProjectFactory.create(owner=owner) + + defaults = { + "project": project, + "owner": owner, + } + defaults.update(kwargs) + + return EpicFactory(**defaults) + + def create_project(**kwargs): "Create a project along with its dependencies" defaults = {} diff --git a/tests/integration/test_epics_tags.py b/tests/integration/test_epics_tags.py new file mode 100644 index 00000000..3e2c66c8 --- /dev/null +++ b/tests/integration/test_epics_tags.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino +# Copyright (C) 2014-2016 David Barragán +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Anler Hernández +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from unittest import mock +from collections import OrderedDict + +from django.core.urlresolvers import reverse + +from taiga.base.utils import json + +from .. import factories as f + +import pytest +pytestmark = pytest.mark.django_db + + +def test_api_epic_add_new_tags_with_error(client): + project = f.ProjectFactory.create() + epic = f.create_epic(project=project, status__project=project) + f.MembershipFactory.create(project=project, user=epic.owner, is_admin=True) + url = reverse("epics-detail", kwargs={"pk": epic.pk}) + data = { + "tags": [], + "version": epic.version + } + + client.login(epic.owner) + + data["tags"] = [1] + response = client.json.patch(url, json.dumps(data)) + assert response.status_code == 400, response.data + assert "tags" in response.data + + data["tags"] = [["back"]] + response = client.json.patch(url, json.dumps(data)) + assert response.status_code == 400, response.data + assert "tags" in response.data + + data["tags"] = [["back", "#cccc"]] + response = client.json.patch(url, json.dumps(data)) + assert response.status_code == 400, response.data + assert "tags" in response.data + + data["tags"] = [[1, "#ccc"]] + response = client.json.patch(url, json.dumps(data)) + assert response.status_code == 400, response.data + assert "tags" in response.data + + +def test_api_epic_add_new_tags_without_colors(client): + project = f.ProjectFactory.create() + epic = f.create_epic(project=project, status__project=project) + f.MembershipFactory.create(project=project, user=epic.owner, is_admin=True) + url = reverse("epics-detail", kwargs={"pk": epic.pk}) + data = { + "tags": [ + ["back", None], + ["front", None], + ["ux", None] + ], + "version": epic.version + } + + client.login(epic.owner) + + response = client.json.patch(url, json.dumps(data)) + + assert response.status_code == 200, response.data + + tags_colors = OrderedDict(project.tags_colors) + assert not tags_colors.keys() + + project.refresh_from_db() + + tags_colors = OrderedDict(project.tags_colors) + assert "back" in tags_colors and "front" in tags_colors and "ux" in tags_colors + + +def test_api_epic_add_new_tags_with_colors(client): + project = f.ProjectFactory.create() + epic = f.create_epic(project=project, status__project=project) + f.MembershipFactory.create(project=project, user=epic.owner, is_admin=True) + url = reverse("epics-detail", kwargs={"pk": epic.pk}) + data = { + "tags": [ + ["back", "#fff8e7"], + ["front", None], + ["ux", "#fabada"] + ], + "version": epic.version + } + + client.login(epic.owner) + + response = client.json.patch(url, json.dumps(data)) + assert response.status_code == 200, response.data + + tags_colors = OrderedDict(project.tags_colors) + assert not tags_colors.keys() + + project.refresh_from_db() + + tags_colors = OrderedDict(project.tags_colors) + assert "back" in tags_colors and "front" in tags_colors and "ux" in tags_colors + assert tags_colors["back"] == "#fff8e7" + assert tags_colors["ux"] == "#fabada" + + +def test_api_create_new_epic_with_tags(client): + project = f.ProjectFactory.create(tags_colors=[["front", "#aaaaaa"], ["ux", "#fabada"]]) + status = f.EpicStatusFactory.create(project=project) + project.default_epic_status = status + project.save() + f.MembershipFactory.create(project=project, user=project.owner, is_admin=True) + url = reverse("epics-list") + + data = { + "subject": "Test user story", + "project": project.id, + "tags": [ + ["back", "#fff8e7"], + ["front", None], + ["ux", "#fabada"] + ] + } + + client.login(project.owner) + + response = client.json.post(url, json.dumps(data)) + assert response.status_code == 201, response.data + + epic_tags_colors = OrderedDict(response.data["tags"]) + + assert epic_tags_colors["back"] == "#fff8e7" + assert epic_tags_colors["front"] == "#aaaaaa" + assert epic_tags_colors["ux"] == "#fabada" + + tags_colors = OrderedDict(project.tags_colors) + + project.refresh_from_db() + + tags_colors = OrderedDict(project.tags_colors) + assert tags_colors["back"] == "#fff8e7" + assert tags_colors["ux"] == "#fabada" + assert tags_colors["front"] == "#aaaaaa" diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py index fbcc5e1e..a002aa0f 100644 --- a/tests/integration/test_projects.py +++ b/tests/integration/test_projects.py @@ -31,6 +31,7 @@ from taiga.projects.models import Project from taiga.projects.userstories.models import UserStory from taiga.projects.tasks.models import Task from taiga.projects.issues.models import Issue +from taiga.projects.epics.models import Epic from taiga.projects.choices import BLOCKED_BY_DELETING from .. import factories as f @@ -1918,6 +1919,7 @@ def test_edit_tag_only_name(client, settings): user_story = f.UserStoryFactory.create(project=project, tags=["tag"]) task = f.TaskFactory.create(project=project, tags=["tag"]) issue = f.IssueFactory.create(project=project, tags=["tag"]) + epic = f.EpicFactory.create(project=project, tags=["tag"]) role = f.RoleFactory.create(project=project, permissions=["view_project"]) membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) @@ -1940,6 +1942,8 @@ def test_edit_tag_only_name(client, settings): assert task.tags == ["renamed_tag"] issue = Issue.objects.get(id=issue.pk) assert issue.tags == ["renamed_tag"] + epic = Epic.objects.get(id=epic.pk) + assert epic.tags == ["renamed_tag"] def test_edit_tag_only_color(client, settings): @@ -1948,6 +1952,7 @@ def test_edit_tag_only_color(client, settings): user_story = f.UserStoryFactory.create(project=project, tags=["tag"]) task = f.TaskFactory.create(project=project, tags=["tag"]) issue = f.IssueFactory.create(project=project, tags=["tag"]) + epic = f.EpicFactory.create(project=project, tags=["tag"]) role = f.RoleFactory.create(project=project, permissions=["view_project"]) membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) @@ -1969,6 +1974,8 @@ def test_edit_tag_only_color(client, settings): assert task.tags == ["tag"] issue = Issue.objects.get(id=issue.pk) assert issue.tags == ["tag"] + epic = Epic.objects.get(id=epic.pk) + assert epic.tags == ["tag"] def test_edit_tag(client, settings): @@ -1977,6 +1984,7 @@ def test_edit_tag(client, settings): user_story = f.UserStoryFactory.create(project=project, tags=["tag"]) task = f.TaskFactory.create(project=project, tags=["tag"]) issue = f.IssueFactory.create(project=project, tags=["tag"]) + epic = f.EpicFactory.create(project=project, tags=["tag"]) role = f.RoleFactory.create(project=project, permissions=["view_project"]) membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) @@ -1999,6 +2007,8 @@ def test_edit_tag(client, settings): assert task.tags == ["renamed_tag"] issue = Issue.objects.get(id=issue.pk) assert issue.tags == ["renamed_tag"] + epic = Epic.objects.get(id=epic.pk) + assert epic.tags == ["renamed_tag"] def test_delete_tag(client, settings): @@ -2007,6 +2017,7 @@ def test_delete_tag(client, settings): user_story = f.UserStoryFactory.create(project=project, tags=["tag"]) task = f.TaskFactory.create(project=project, tags=["tag"]) issue = f.IssueFactory.create(project=project, tags=["tag"]) + epic = f.EpicFactory.create(project=project, tags=["tag"]) role = f.RoleFactory.create(project=project, permissions=["view_project"]) membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) @@ -2027,6 +2038,8 @@ def test_delete_tag(client, settings): assert task.tags == [] issue = Issue.objects.get(id=issue.pk) assert issue.tags == [] + epic = Epic.objects.get(id=epic.pk) + assert epic.tags == [] def test_mix_tags(client, settings): @@ -2035,6 +2048,7 @@ def test_mix_tags(client, settings): user_story = f.UserStoryFactory.create(project=project, tags=["tag1", "tag3"]) task = f.TaskFactory.create(project=project, tags=["tag2", "tag3"]) issue = f.IssueFactory.create(project=project, tags=["tag1", "tag2", "tag3"]) + epic = f.EpicFactory.create(project=project, tags=["tag1", "tag2", "tag3"]) role = f.RoleFactory.create(project=project, permissions=["view_project"]) membership = f.MembershipFactory.create(project=project, user=user, role=role, is_admin=True) @@ -2056,6 +2070,8 @@ def test_mix_tags(client, settings): assert set(task.tags) == set(["tag2", "tag3"]) issue = Issue.objects.get(id=issue.pk) assert set(issue.tags) == set(["tag2", "tag3"]) + epic = Epic.objects.get(id=epic.pk) + assert set(epic.tags) == set(["tag2", "tag3"]) def test_color_tags_project_fired_on_element_create():