Assign a color when create a new tag

remotes/origin/issue/4795/notification_even_they_are_disabled
David Barragán Merino 2016-06-08 21:26:09 +02:00
parent 3e555de7c4
commit 8c45033f18
24 changed files with 680 additions and 119 deletions

View File

@ -18,7 +18,7 @@
from django.forms import widgets
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from taiga.base.api import serializers
@ -99,35 +99,6 @@ class PickledObjectField(serializers.WritableField):
return data
class TagsField(serializers.WritableField):
"""
Pickle objects serializer.
"""
def to_native(self, obj):
return obj
def from_native(self, data):
if not data:
return data
ret = sum([tag.split(",") for tag in data], [])
return ret
class TagsColorsField(serializers.WritableField):
"""
PgArray objects serializer.
"""
widget = widgets.Textarea
def to_native(self, obj):
return dict(obj)
def from_native(self, data):
return list(data.items())
class WatchersField(serializers.WritableField):
def to_native(self, obj):
return obj

View File

@ -66,9 +66,9 @@ from . import services
######################################################
## Project
######################################################
class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
BlockeableSaveMixin, BlockeableDeleteMixin, ModelCrudViewSet):
queryset = models.Project.objects.all()
serializer_class = serializers.ProjectDetailSerializer
admin_serializer_class = serializers.ProjectDetailAdminSerializer

View File

@ -25,13 +25,14 @@ from django.db.models import signals
def connect_projects_signals():
from . import signals as handlers
from .tagging import signals as tagging_handlers
# On project object is created apply template.
signals.post_save.connect(handlers.project_post_save,
sender=apps.get_model("projects", "Project"),
dispatch_uid='project_post_save')
# Tags normalization after save a project
signals.pre_save.connect(handlers.tags_normalization,
signals.pre_save.connect(tagging_handlers.tags_normalization,
sender=apps.get_model("projects", "Project"),
dispatch_uid="tags_normalization_projects")

View File

@ -27,11 +27,11 @@ from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.api.utils import get_object_or_404
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType
from taiga.projects.tagging.mixins import TaggedResourceMixin
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from . import models
@ -41,7 +41,7 @@ from . import serializers
class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
BlockedByProjectMixin, ModelCrudViewSet):
TaggedResourceMixin, BlockedByProjectMixin, ModelCrudViewSet):
queryset = models.Issue.objects.all()
permission_classes = (permissions.IssuePermission, )
filter_backends = (filters.CanViewIssuesFilterBackend,
@ -196,7 +196,6 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W
owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
priorities_filter_backends = (f for f in filter_backends if f != filters.PrioritiesFilter)
severities_filter_backends = (f for f in filter_backends if f != filters.SeveritiesFilter)
tags_filter_backends = (f for f in filter_backends if f != filters.TagsFilter)
queryset = self.get_queryset()
querysets = {

View File

@ -23,6 +23,7 @@ from django.db.models import signals
def connect_issues_signals():
from taiga.projects import signals as generic_handlers
from taiga.projects.tagging import signals as tagging_handlers
from . import signals as handlers
# Finished date
@ -31,7 +32,7 @@ def connect_issues_signals():
dispatch_uid="set_finished_date_when_edit_issue")
# Tags
signals.pre_save.connect(generic_handlers.tags_normalization,
signals.pre_save.connect(tagging_handlers.tags_normalization,
sender=apps.get_model("issues", "Issue"),
dispatch_uid="tags_normalization_issue")

View File

@ -17,15 +17,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api import serializers
from taiga.base.fields import TagsField
from taiga.base.fields import PgArrayField
from taiga.base.neighbors import NeighborsSerializerMixin
from taiga.mdrender.service import render as mdrender
from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer
from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.serializers import BasicIssueStatusSerializer
from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer
from taiga.mdrender.service import render as mdrender
from taiga.projects.tagging.fields import TagsAndTagsColorsField
from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin
from taiga.users.serializers import UserBasicInfoSerializer
@ -33,8 +33,9 @@ from taiga.users.serializers import UserBasicInfoSerializer
from . import models
class IssueSerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer):
tags = TagsField(required=False)
class IssueSerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer,
serializers.ModelSerializer):
tags = TagsAndTagsColorsField(default=[], required=False)
external_reference = PgArrayField(required=False)
is_closed = serializers.Field(source="is_closed")
comment = serializers.SerializerMethodField("get_comment")

View File

@ -24,28 +24,27 @@ from django.db.models import Q
from taiga.base.api import serializers
from taiga.base.fields import JsonField
from taiga.base.fields import PgArrayField
from taiga.base.fields import TagsField
from taiga.base.fields import TagsColorsField
from taiga.projects.notifications.choices import NotifyLevel
from taiga.users.services import get_photo_or_gravatar_url
from taiga.users.serializers import UserSerializer
from taiga.users.serializers import UserBasicInfoSerializer
from taiga.users.serializers import ProjectRoleSerializer
from taiga.users.validators import RoleExistsValidator
from taiga.permissions.services import get_user_project_permissions
from taiga.permissions.services import is_project_admin, is_project_owner
from taiga.projects.mixins.serializers import ValidateDuplicatedNameInProjectMixin
from . import models
from . import services
from .notifications.mixins import WatchedResourceModelSerializer
from .validators import ProjectExistsValidator
from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
from .custom_attributes.serializers import TaskCustomAttributeSerializer
from .custom_attributes.serializers import IssueCustomAttributeSerializer
from .likes.mixins.serializers import FanResourceSerializerMixin
from .mixins.serializers import ValidateDuplicatedNameInProjectMixin
from .notifications.choices import NotifyLevel
from .notifications.mixins import WatchedResourceModelSerializer
from .tagging.fields import TagsField
from .tagging.fields import TagsColorsField
from .validators import ProjectExistsValidator
######################################################

View File

@ -18,6 +18,7 @@
from django.db import connection
def tag_exist_for_project_elements(project, tag):
return tag in dict(project.tags_colors).keys()

View File

@ -29,13 +29,6 @@ from easy_thumbnails.files import get_thumbnailer
# Signals over project items
####################################
## TAGS
def tags_normalization(sender, instance, **kwargs):
if isinstance(instance.tags, (list, tuple)):
instance.tags = list(map(str.lower, instance.tags))
## Membership
def membership_post_delete(sender, instance, using, **kwargs):

View File

View File

@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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 <http://www.gnu.org/licenses/>.
from django.forms import widgets
from django.utils.translation import ugettext_lazy as _
from taiga.base.api import serializers
from django.core.exceptions import ValidationError
import re
class TagsAndTagsColorsField(serializers.WritableField):
"""
Pickle objects serializer fior stories, tasks and issues tags.
"""
def __init__(self, *args, **kwargs):
def _validate_tag_field(value):
# Valid field:
# - ["tag1", "tag2", "tag3"...]
# - ["tag1", ["tag2", None], ["tag3", "#ccc"], [tag4, #cccccc]...]
for tag in value:
if isinstance(tag, str):
continue
if isinstance(tag, (list, tuple)) and len(tag) == 2:
name = tag[0]
color = tag[1]
if isinstance(name, str):
if color is None:
continue
if isinstance(color, str) and re.match('^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', color):
continue
raise ValidationError(_("Invalid tag '{value}'. The color is not a "
"valid HEX color or null.").format(value=tag))
raise ValidationError(_("Invalid tag '{value}'. it must be the name or a pair "
"'[\"name\", \"hex color/\" | null]'.").format(value=tag))
super().__init__(*args, **kwargs)
self.validators.append(_validate_tag_field)
def to_native(self, obj):
return obj
def from_native(self, data):
return data
class TagsField(serializers.WritableField):
"""
Pickle objects serializer for tags names.
"""
def __init__(self, *args, **kwargs):
def _validate_tag_field(value):
for tag in value:
if isinstance(tag, str):
continue
raise ValidationError(_("Invalid tag '{value}'. It must be the tag name.").format(value=tag))
super().__init__(*args, **kwargs)
self.validators.append(_validate_tag_field)
def to_native(self, obj):
return obj
def from_native(self, data):
return data
class TagsColorsField(serializers.WritableField):
"""
PgArray objects serializer.
"""
widget = widgets.Textarea
def to_native(self, obj):
return dict(obj)
def from_native(self, data):
return list(data.items())

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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 <http://www.gnu.org/licenses/>.
def _pre_save_new_tags_in_project_tagss_colors(obj):
current_project_tags = [t[0] for t in obj.project.tags_colors]
new_obj_tags = set()
new_tags_colors = {}
for tag in obj.tags:
if isinstance(tag, (list, tuple)):
name, color = tag
if color and name not in current_project_tags:
new_tags_colors[name] = color
new_obj_tags.add(name)
elif isinstance(tag, str):
new_obj_tags.add(tag.lower())
obj.tags = list(new_obj_tags)
if new_tags_colors:
obj.project.tags_colors += [[k, v] for k,v in new_tags_colors.items()]
obj.project.save(update_fields=["tags_colors"])
class TaggedResourceMixin:
def pre_save(self, obj):
if obj.tags:
_pre_save_new_tags_in_project_tagss_colors(obj)
super().pre_save(obj)

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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 <http://www.gnu.org/licenses/>.
def tags_normalization(sender, instance, **kwargs):
if isinstance(instance.tags, (list, tuple)):
instance.tags = list(map(str.lower, instance.tags))

View File

@ -16,6 +16,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.http import HttpResponse
from django.utils.translation import ugettext as _
from taiga.base.api.utils import get_object_or_404
@ -24,15 +25,13 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.projects.models import Project, TaskStatus
from django.http import HttpResponse
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.models import Project, TaskStatus
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.tagging.mixins import TaggedResourceMixin
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from . import models
from . import permissions
from . import serializers
@ -40,13 +39,18 @@ from . import services
class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
BlockedByProjectMixin, ModelCrudViewSet):
TaggedResourceMixin, BlockedByProjectMixin, ModelCrudViewSet):
queryset = models.Task.objects.all()
permission_classes = (permissions.TaskPermission,)
filter_backends = (filters.CanViewTasksFilterBackend, filters.WatchersFilter)
retrieve_exclude_filters = (filters.WatchersFilter,)
filter_fields = ["user_story", "milestone", "project", "assigned_to",
"status__is_closed"]
filter_fields = [
"user_story",
"milestone",
"project",
"assigned_to",
"status__is_closed"
]
def get_serializer_class(self, *args, **kwargs):
if self.action in ["retrieve", "by_ref"]:

View File

@ -23,13 +23,15 @@ from django.db.models import signals
def connect_tasks_signals():
from taiga.projects import signals as generic_handlers
from taiga.projects.tagging import signals as tagging_handlers
from . import signals as handlers
# Finished date
signals.pre_save.connect(handlers.set_finished_date_when_edit_task,
sender=apps.get_model("tasks", "Task"),
dispatch_uid="set_finished_date_when_edit_task")
# Tags
signals.pre_save.connect(generic_handlers.tags_normalization,
signals.pre_save.connect(tagging_handlers.tags_normalization,
sender=apps.get_model("tasks", "Task"),
dispatch_uid="tags_normalization_task")

View File

@ -17,19 +17,18 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api import serializers
from taiga.base.fields import TagsField
from taiga.base.fields import PgArrayField
from taiga.base.neighbors import NeighborsSerializerMixin
from taiga.mdrender.service import render as mdrender
from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.milestones.validators import SprintExistsValidator
from taiga.projects.tasks.validators import TaskExistsValidator
from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer
from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.serializers import BasicTaskStatusSerializerSerializer
from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer
from taiga.mdrender.service import render as mdrender
from taiga.projects.tagging.fields import TagsAndTagsColorsField
from taiga.projects.tasks.validators import TaskExistsValidator
from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin
from taiga.users.serializers import UserBasicInfoSerializer
@ -37,8 +36,9 @@ from taiga.users.serializers import UserBasicInfoSerializer
from . import models
class TaskSerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer):
tags = TagsField(required=False, default=[])
class TaskSerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer,
serializers.ModelSerializer):
tags = TagsAndTagsColorsField(default=[], required=False)
external_reference = PgArrayField(required=False)
comment = serializers.SerializerMethodField("get_comment")
milestone_slug = serializers.SerializerMethodField("get_milestone_slug")
@ -101,6 +101,7 @@ class TasksBulkSerializer(ProjectExistsValidator, SprintExistsValidator,
us_id = serializers.IntegerField(required=False)
bulk_tasks = serializers.CharField()
## Order bulk serializers
class _TaskOrderBulkSerializer(TaskExistsValidator, serializers.Serializer):

View File

@ -19,7 +19,6 @@
from django.apps import apps
from django.db import transaction
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse
from taiga.base import filters
@ -31,12 +30,13 @@ from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.models import Project, UserStoryStatus
from taiga.projects.milestones.models import Milestone
from taiga.projects.history.services import take_snapshot
from taiga.projects.milestones.models import Milestone
from taiga.projects.models import Project, UserStoryStatus
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.tagging.mixins import TaggedResourceMixin
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from . import models
@ -46,7 +46,7 @@ from . import services
class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
BlockedByProjectMixin, ModelCrudViewSet):
TaggedResourceMixin, BlockedByProjectMixin, ModelCrudViewSet):
queryset = models.UserStory.objects.all()
permission_classes = (permissions.UserStoryPermission,)
filter_backends = (filters.CanViewUsFilterBackend,
@ -113,6 +113,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
def pre_save(self, obj):
# This is very ugly hack, but having
# restframework is the only way to do it.
#
# NOTE: code moved as is from serializer
# to api because is not serializer logic.
related_data = getattr(obj, "_related_data", {})
@ -124,7 +125,8 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
super().pre_save(obj)
def post_save(self, obj, created=False):
# Code related to the hack of pre_save method. Rather, this is the continuation of it.
# Code related to the hack of pre_save method.
# Rather, this is the continuation of it.
if self._role_points:
Points = apps.get_model("projects", "Points")
RolePoints = apps.get_model("userstories", "RolePoints")
@ -134,14 +136,16 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
role_points = RolePoints.objects.get(role__id=role_id, user_story_id=obj.pk,
role__computable=True)
except (ValueError, RolePoints.DoesNotExist):
raise exc.BadRequest({"points": _("Invalid role id '{role_id}'").format(
role_id=role_id)})
raise exc.BadRequest({
"points": _("Invalid role id '{role_id}'").format(role_id=role_id)
})
try:
role_points.points = Points.objects.get(id=points_id, project_id=obj.project_id)
except (ValueError, Points.DoesNotExist):
raise exc.BadRequest({"points": _("Invalid points id '{points_id}'").format(
points_id=points_id)})
raise exc.BadRequest({
"points": _("Invalid points id '{points_id}'").format(points_id=points_id)
})
role_points.save()
@ -200,7 +204,6 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
statuses_filter_backends = (f for f in filter_backends if f != filters.StatusesFilter)
assigned_to_filter_backends = (f for f in filter_backends if f != filters.AssignedToFilter)
owners_filter_backends = (f for f in filter_backends if f != filters.OwnersFilter)
tags_filter_backends = (f for f in filter_backends if f != filters.TagsFilter)
queryset = self.get_queryset()
querysets = {

View File

@ -23,6 +23,7 @@ from django.db.models import signals
def connect_userstories_signals():
from taiga.projects import signals as generic_handlers
from taiga.projects.tagging import signals as tagging_handlers
from . import signals as handlers
# When deleting user stories we must disable task signals while delating and
@ -59,7 +60,7 @@ def connect_userstories_signals():
dispatch_uid="try_to_close_milestone_when_delete_us")
# Tags
signals.pre_save.connect(generic_handlers.tags_normalization,
signals.pre_save.connect(tagging_handlers.tags_normalization,
sender=apps.get_model("userstories", "UserStory"),
dispatch_uid="tags_normalization_user_story")

View File

@ -16,23 +16,22 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import apps
from taiga.base.api import serializers
from taiga.base.api.utils import get_object_or_404
from taiga.base.fields import TagsField
from taiga.base.fields import PickledObjectField
from taiga.base.fields import PgArrayField
from taiga.base.neighbors import NeighborsSerializerMixin
from taiga.base.utils import json
from taiga.mdrender.service import render as mdrender
from taiga.projects.models import Project
from taiga.projects.validators import ProjectExistsValidator, UserStoryStatusExistsValidator
from taiga.projects.milestones.validators import SprintExistsValidator
from taiga.projects.userstories.validators import UserStoryExistsValidator
from taiga.projects.models import Project
from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.serializers import BasicUserStoryStatusSerializer
from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer
from taiga.projects.serializers import BasicUserStoryStatusSerializer
from taiga.mdrender.service import render as mdrender
from taiga.projects.tagging.fields import TagsAndTagsColorsField
from taiga.projects.userstories.validators import UserStoryExistsValidator
from taiga.projects.validators import ProjectExistsValidator, UserStoryStatusExistsValidator
from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin
from taiga.users.serializers import UserBasicInfoSerializer
@ -50,9 +49,9 @@ class RolePointsField(serializers.WritableField):
return json.loads(obj)
class UserStorySerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer,
serializers.ModelSerializer):
tags = TagsField(default=[], required=False)
class UserStorySerializer(WatchersValidator, VoteResourceSerializerMixin,
EditableWatchedResourceModelSerializer, serializers.ModelSerializer):
tags = TagsAndTagsColorsField(default=[], required=False)
external_reference = PgArrayField(required=False)
points = RolePointsField(source="role_points", required=False)
total_points = serializers.SerializerMethodField("get_total_points")
@ -142,7 +141,8 @@ class _UserStoryOrderBulkSerializer(UserStoryExistsValidator, serializers.Serial
order = serializers.IntegerField()
class UpdateUserStoriesOrderBulkSerializer(ProjectExistsValidator, UserStoryStatusExistsValidator, serializers.Serializer):
class UpdateUserStoriesOrderBulkSerializer(ProjectExistsValidator, UserStoryStatusExistsValidator,
serializers.Serializer):
project_id = serializers.IntegerField()
bulk_stories = _UserStoryOrderBulkSerializer(many=True)

View File

@ -19,7 +19,7 @@
from django.core.exceptions import ObjectDoesNotExist
from taiga.base.api import serializers
from taiga.base.fields import TagsField, PgArrayField, JsonField
from taiga.base.fields import PgArrayField, JsonField
from taiga.front.templatetags.functions import resolve as resolve_front_url
@ -29,6 +29,7 @@ from taiga.projects.milestones import models as milestone_models
from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer
from taiga.projects.services import get_logo_big_thumbnail_url
from taiga.projects.tasks import models as task_models
from taiga.projects.tagging.fields import TagsField
from taiga.projects.userstories import models as us_models
from taiga.projects.wiki import models as wiki_models

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
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_issue_add_new_tags_with_error(client):
project = f.ProjectFactory.create()
issue = f.create_issue(project=project, status__project=project)
f.MembershipFactory.create(project=project, user=issue.owner, is_admin=True)
url = reverse("issues-detail", kwargs={"pk": issue.pk})
data = {
"tags": [],
"version": issue.version
}
client.login(issue.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_issue_add_new_tags_without_colors(client):
project = f.ProjectFactory.create()
issue = f.create_issue(project=project, status__project=project)
f.MembershipFactory.create(project=project, user=issue.owner, is_admin=True)
url = reverse("issues-detail", kwargs={"pk": issue.pk})
data = {
"tags": [
["back", None],
["front", None],
["ux", None]
],
"version": issue.version
}
client.login(issue.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_issue_add_new_tags_with_colors(client):
project = f.ProjectFactory.create()
issue = f.create_issue(project=project, status__project=project)
f.MembershipFactory.create(project=project, user=issue.owner, is_admin=True)
url = reverse("issues-detail", kwargs={"pk": issue.pk})
data = {
"tags": [
["back", "#fff8e7"],
["front", None],
["ux", "#fabada"]
],
"version": issue.version
}
client.login(issue.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_issue_with_tags(client):
project = f.ProjectFactory.create()
status = f.IssueStatusFactory.create(project=project)
project.default_issue_status = status
project.save()
f.MembershipFactory.create(project=project, user=project.owner, is_admin=True)
url = reverse("issues-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
assert ("back" in response.data["tags"] and
"front" in response.data["tags"] and
"ux" in response.data["tags"])
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"

View File

@ -67,19 +67,6 @@ def test_create_task_without_default_values(client):
assert response.data['status'] == None
def test_api_update_task_tags(client):
project = f.ProjectFactory.create()
task = f.create_task(project=project, status__project=project, milestone=None, user_story=None)
f.MembershipFactory.create(project=project, user=task.owner, is_admin=True)
url = reverse("tasks-detail", kwargs={"pk": task.pk})
data = {"tags": ["back", "front"], "version": task.version}
client.login(task.owner)
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 200, response.data
def test_api_create_in_bulk_with_status(client):
us = f.create_userstory()
f.MembershipFactory.create(project=us.project, user=us.owner, is_admin=True)

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
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_task_add_new_tags_with_error(client):
project = f.ProjectFactory.create()
task = f.create_task(project=project, status__project=project, milestone=None, user_story=None)
f.MembershipFactory.create(project=project, user=task.owner, is_admin=True)
url = reverse("tasks-detail", kwargs={"pk": task.pk})
data = {
"tags": [],
"version": task.version
}
client.login(task.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_task_add_new_tags_without_colors(client):
project = f.ProjectFactory.create()
task = f.create_task(project=project, status__project=project, milestone=None, user_story=None)
f.MembershipFactory.create(project=project, user=task.owner, is_admin=True)
url = reverse("tasks-detail", kwargs={"pk": task.pk})
data = {
"tags": [
["back", None],
["front", None],
["ux", None]
],
"version": task.version
}
client.login(task.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_task_add_new_tags_with_colors(client):
project = f.ProjectFactory.create()
task = f.create_task(project=project, status__project=project, milestone=None, user_story=None)
f.MembershipFactory.create(project=project, user=task.owner, is_admin=True)
url = reverse("tasks-detail", kwargs={"pk": task.pk})
data = {
"tags": [
["back", "#fff8e7"],
["front", None],
["ux", "#fabada"]
],
"version": task.version
}
client.login(task.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_task_with_tags(client):
project = f.ProjectFactory.create()
status = f.TaskStatusFactory.create(project=project)
project.default_task_status = status
project.save()
f.MembershipFactory.create(project=project, user=project.owner, is_admin=True)
url = reverse("tasks-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
assert ("back" in response.data["tags"] and
"front" in response.data["tags"] and
"ux" in response.data["tags"])
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"

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
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_user_story_add_new_tags_with_error(client):
project = f.ProjectFactory.create()
user_story = f.create_userstory(project=project, status__project=project)
f.MembershipFactory.create(project=project, user=user_story.owner, is_admin=True)
url = reverse("userstories-detail", kwargs={"pk": user_story.pk})
data = {
"tags": [],
"version": user_story.version
}
client.login(user_story.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_user_story_add_new_tags_without_colors(client):
project = f.ProjectFactory.create()
user_story = f.create_userstory(project=project, status__project=project)
f.MembershipFactory.create(project=project, user=user_story.owner, is_admin=True)
url = reverse("userstories-detail", kwargs={"pk": user_story.pk})
data = {
"tags": [
["back", None],
["front", None],
["ux", None]
],
"version": user_story.version
}
client.login(user_story.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_user_story_add_new_tags_with_colors(client):
project = f.ProjectFactory.create()
user_story = f.create_userstory(project=project, status__project=project)
f.MembershipFactory.create(project=project, user=user_story.owner, is_admin=True)
url = reverse("userstories-detail", kwargs={"pk": user_story.pk})
data = {
"tags": [
["back", "#fff8e7"],
["front", None],
["ux", "#fabada"]
],
"version": user_story.version
}
client.login(user_story.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_user_story_with_tags(client):
project = f.ProjectFactory.create()
status = f.UserStoryStatusFactory.create(project=project)
project.default_userstory_status = status
project.save()
f.MembershipFactory.create(project=project, user=project.owner, is_admin=True)
url = reverse("userstories-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
assert ("back" in response.data["tags"] and
"front" in response.data["tags"] and
"ux" in response.data["tags"])
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"