Assign a color when create a new tag
parent
3e555de7c4
commit
8c45033f18
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
@ -71,7 +72,7 @@ class IssueListSerializer(IssueSerializer):
|
|||
class Meta:
|
||||
model = models.Issue
|
||||
read_only_fields = ('id', 'ref', 'created_date', 'modified_date')
|
||||
exclude=("description", "description_html")
|
||||
exclude = ("description", "description_html")
|
||||
|
||||
|
||||
class IssueNeighborsSerializer(NeighborsSerializerMixin, IssueSerializer):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
######################################################
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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())
|
|
@ -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)
|
|
@ -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))
|
||||
|
|
@ -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"]:
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
@ -76,7 +76,7 @@ class TaskListSerializer(TaskSerializer):
|
|||
class Meta:
|
||||
model = models.Task
|
||||
read_only_fields = ('id', 'ref', 'created_date', 'modified_date')
|
||||
exclude=("description", "description_html")
|
||||
exclude = ("description", "description_html")
|
||||
|
||||
|
||||
class TaskNeighborsSerializer(NeighborsSerializerMixin, TaskSerializer):
|
||||
|
@ -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):
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
@ -112,7 +111,7 @@ class UserStoryListSerializer(UserStorySerializer):
|
|||
model = models.UserStory
|
||||
depth = 0
|
||||
read_only_fields = ('created_date', 'modified_date')
|
||||
exclude=("description", "description_html")
|
||||
exclude = ("description", "description_html")
|
||||
|
||||
|
||||
class UserStoryNeighborsSerializer(NeighborsSerializerMixin, UserStorySerializer):
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
Loading…
Reference in New Issue