Move all tags code to projects.tags
parent
fde98473c4
commit
13ef1b9af5
|
@ -22,38 +22,38 @@ from dateutil.relativedelta import relativedelta
|
|||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import signals, Prefetch
|
||||
from django.db.models import Value as V
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import Http404
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import timezone
|
||||
from django.http import Http404
|
||||
|
||||
from taiga.base import filters
|
||||
from taiga.base import response
|
||||
from taiga.base import exceptions as exc
|
||||
from taiga.base.decorators import list_route
|
||||
from taiga.base.decorators import detail_route
|
||||
from taiga.base import response
|
||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||
from taiga.base.api.mixins import BlockedByProjectMixin, BlockeableSaveMixin, BlockeableDeleteMixin
|
||||
from taiga.base.api.permissions import AllowAnyPermission
|
||||
from taiga.base.api.utils import get_object_or_404
|
||||
from taiga.base.decorators import list_route
|
||||
from taiga.base.decorators import detail_route
|
||||
from taiga.base.utils.slug import slugify_uniquely
|
||||
|
||||
from taiga.permissions import services as permissions_services
|
||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||
from taiga.projects.issues.models import Issue
|
||||
from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin
|
||||
from taiga.projects.notifications.models import NotifyPolicy
|
||||
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
|
||||
from taiga.projects.notifications.choices import NotifyLevel
|
||||
|
||||
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
||||
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
|
||||
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
||||
from taiga.projects.tasks.models import Task
|
||||
from taiga.projects.tagging.api import TagsColorsResourceMixin
|
||||
|
||||
from taiga.projects.userstories.models import UserStory, RolePoints
|
||||
from taiga.projects.tasks.models import Task
|
||||
from taiga.projects.issues.models import Issue
|
||||
from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin
|
||||
from taiga.permissions import services as permissions_services
|
||||
from taiga.users import services as users_services
|
||||
|
||||
from . import filters as project_filters
|
||||
|
@ -67,8 +67,8 @@ from . import services
|
|||
## Project
|
||||
######################################################
|
||||
|
||||
class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
||||
BlockeableSaveMixin, BlockeableDeleteMixin, ModelCrudViewSet):
|
||||
class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, BlockeableSaveMixin, BlockeableDeleteMixin,
|
||||
TagsColorsResourceMixin, ModelCrudViewSet):
|
||||
queryset = models.Project.objects.all()
|
||||
serializer_class = serializers.ProjectDetailSerializer
|
||||
admin_serializer_class = serializers.ProjectDetailAdminSerializer
|
||||
|
@ -327,12 +327,6 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
|||
self.check_permissions(request, "issues_stats", project)
|
||||
return response.Ok(services.get_stats_for_project_issues(project))
|
||||
|
||||
@detail_route(methods=["GET"])
|
||||
def tags_colors(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "tags_colors", project)
|
||||
return response.Ok(dict(project.tags_colors))
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def transfer_validate_token(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
|
@ -405,63 +399,6 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
|||
services.reject_project_transfer(project, request.user, token, reason)
|
||||
return response.Ok()
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def create_tag(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "create_tag", project)
|
||||
self._raise_if_blocked(project)
|
||||
serializer = serializers.CreateTagSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.create_tag(project, data.get("tag"), data.get("color"))
|
||||
return response.Ok()
|
||||
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def edit_tag(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "edit_tag", project)
|
||||
self._raise_if_blocked(project)
|
||||
serializer = serializers.EditTagTagSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.edit_tag(project, data.get("from_tag"),
|
||||
to_tag=data.get("to_tag", None),
|
||||
color=data.get("color", None))
|
||||
|
||||
return response.Ok()
|
||||
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def delete_tag(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "delete_tag", project)
|
||||
self._raise_if_blocked(project)
|
||||
serializer = serializers.DeleteTagSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.delete_tag(project, data.get("tag"))
|
||||
return response.Ok()
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def mix_tags(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "mix_tags", project)
|
||||
self._raise_if_blocked(project)
|
||||
serializer = serializers.MixTagsSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.mix_tags(project, data.get("from_tags"), data.get("to_tag"))
|
||||
return response.Ok()
|
||||
|
||||
def _raise_if_blocked(self, project):
|
||||
if self.is_blocked(project):
|
||||
raise exc.Blocked(_("Blocked element"))
|
||||
|
|
|
@ -31,7 +31,7 @@ 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.tagging.mixins import TaggedResourceMixin
|
||||
from taiga.projects.tagging.api import TaggedResourceMixin
|
||||
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
|
||||
|
||||
from . import models
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
# 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/>.
|
||||
|
||||
import re
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db.models import Q
|
||||
|
||||
|
@ -416,94 +414,3 @@ class ProjectTemplateSerializer(serializers.ModelSerializer):
|
|||
class UpdateProjectOrderBulkSerializer(ProjectExistsValidator, serializers.Serializer):
|
||||
project_id = serializers.IntegerField()
|
||||
order = serializers.IntegerField()
|
||||
|
||||
|
||||
######################################################
|
||||
## Project tags serializers
|
||||
######################################################
|
||||
|
||||
|
||||
class ProjectTagSerializer(serializers.Serializer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Don't pass the extra project arg
|
||||
self.project = kwargs.pop("project")
|
||||
|
||||
# Instantiate the superclass normally
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CreateTagSerializer(ProjectTagSerializer):
|
||||
tag = serializers.CharField()
|
||||
color = serializers.CharField(required=False)
|
||||
|
||||
def validate_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag exists."))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_color(self, attrs, source):
|
||||
color = attrs.get(source, None)
|
||||
if not re.match('^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', color):
|
||||
raise serializers.ValidationError(_("The color is not a valid HEX color."))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class EditTagTagSerializer(ProjectTagSerializer):
|
||||
from_tag = serializers.CharField()
|
||||
to_tag = serializers.CharField(required=False)
|
||||
color = serializers.CharField(required=False)
|
||||
|
||||
def validate_from_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_to_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag exists yet"))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_color(self, attrs, source):
|
||||
color = attrs.get(source, None)
|
||||
if len(color) != 7 or not re.match('^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', color):
|
||||
raise serializers.ValidationError(_("The color is not a valid HEX color."))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class DeleteTagSerializer(ProjectTagSerializer):
|
||||
tag = serializers.CharField()
|
||||
|
||||
def validate_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class MixTagsSerializer(ProjectTagSerializer):
|
||||
from_tags = TagsField()
|
||||
to_tag = serializers.CharField()
|
||||
|
||||
def validate_from_tags(self, attrs, source):
|
||||
tags = attrs.get(source, None)
|
||||
for tag in tags:
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_to_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
|
|
@ -57,6 +57,3 @@ from .stats import get_member_stats_for_project
|
|||
|
||||
from .transfer import request_project_transfer, start_project_transfer
|
||||
from .transfer import accept_project_transfer, reject_project_transfer
|
||||
|
||||
from .tags import tag_exist_for_project_elements, create_tag
|
||||
from .tags import edit_tag, delete_tag, mix_tags
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
# -*- 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 taiga.base import response
|
||||
from taiga.base.decorators import detail_route
|
||||
|
||||
from . import services
|
||||
from . import serializers
|
||||
|
||||
|
||||
class TagsColorsResourceMixin:
|
||||
@detail_route(methods=["GET"])
|
||||
def tags_colors(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "tags_colors", project)
|
||||
|
||||
return response.Ok(dict(project.tags_colors))
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def create_tag(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "create_tag", project)
|
||||
self._raise_if_blocked(project)
|
||||
|
||||
serializer = serializers.CreateTagSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.create_tag(project, data.get("tag"), data.get("color"))
|
||||
|
||||
return response.Ok()
|
||||
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def edit_tag(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "edit_tag", project)
|
||||
self._raise_if_blocked(project)
|
||||
|
||||
serializer = serializers.EditTagTagSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.edit_tag(project,
|
||||
data.get("from_tag"),
|
||||
to_tag=data.get("to_tag", None),
|
||||
color=data.get("color", None))
|
||||
|
||||
return response.Ok()
|
||||
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def delete_tag(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "delete_tag", project)
|
||||
self._raise_if_blocked(project)
|
||||
|
||||
serializer = serializers.DeleteTagSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.delete_tag(project, data.get("tag"))
|
||||
|
||||
return response.Ok()
|
||||
|
||||
@detail_route(methods=["POST"])
|
||||
def mix_tags(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, "mix_tags", project)
|
||||
self._raise_if_blocked(project)
|
||||
|
||||
serializer = serializers.MixTagsSerializer(data=request.DATA, project=project)
|
||||
if not serializer.is_valid():
|
||||
return response.BadRequest(serializer.errors)
|
||||
|
||||
data = serializer.data
|
||||
services.mix_tags(project, data.get("from_tags"), data.get("to_tag"))
|
||||
|
||||
return response.Ok()
|
||||
|
||||
|
||||
class TaggedResourceMixin:
|
||||
def pre_save(self, obj):
|
||||
if obj.tags:
|
||||
self._pre_save_new_tags_in_project_tagss_colors(obj)
|
||||
|
||||
super().pre_save(obj)
|
||||
|
||||
def _pre_save_new_tags_in_project_tagss_colors(self, obj):
|
||||
new_obj_tags = set()
|
||||
new_tags_colors = {}
|
||||
|
||||
for tag in obj.tags:
|
||||
if isinstance(tag, (list, tuple)):
|
||||
name, color = tag
|
||||
|
||||
if color and not services.tag_exist_for_project_elements(obj.project, name):
|
||||
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:
|
||||
services.create_tags(obj.project, new_tags_colors)
|
|
@ -16,11 +16,11 @@
|
|||
# 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.core.exceptions import ValidationError
|
||||
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
|
||||
from taiga.base.api import serializers
|
||||
|
||||
import re
|
||||
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
# -*- 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,112 @@
|
|||
# -*- 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.utils.translation import ugettext as _
|
||||
|
||||
from taiga.base.api import serializers
|
||||
|
||||
from . import services
|
||||
from . import fields
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class ProjectTagSerializer(serializers.Serializer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Don't pass the extra project arg
|
||||
self.project = kwargs.pop("project")
|
||||
|
||||
# Instantiate the superclass normally
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CreateTagSerializer(ProjectTagSerializer):
|
||||
tag = serializers.CharField()
|
||||
color = serializers.CharField(required=False)
|
||||
|
||||
def validate_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag exists."))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_color(self, attrs, source):
|
||||
color = attrs.get(source, None)
|
||||
if not re.match('^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', color):
|
||||
raise serializers.ValidationError(_("The color is not a valid HEX color."))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class EditTagTagSerializer(ProjectTagSerializer):
|
||||
from_tag = serializers.CharField()
|
||||
to_tag = serializers.CharField(required=False)
|
||||
color = serializers.CharField(required=False)
|
||||
|
||||
def validate_from_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_to_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag exists yet"))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_color(self, attrs, source):
|
||||
color = attrs.get(source, None)
|
||||
if not re.match('^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$', color):
|
||||
raise serializers.ValidationError(_("The color is not a valid HEX color."))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class DeleteTagSerializer(ProjectTagSerializer):
|
||||
tag = serializers.CharField()
|
||||
|
||||
def validate_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class MixTagsSerializer(ProjectTagSerializer):
|
||||
from_tags = fields.TagsField()
|
||||
to_tag = serializers.CharField()
|
||||
|
||||
def validate_from_tags(self, attrs, source):
|
||||
tags = attrs.get(source, None)
|
||||
for tag in tags:
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
||||
|
||||
def validate_to_tag(self, attrs, source):
|
||||
tag = attrs.get(source, None)
|
||||
if not services.tag_exist_for_project_elements(self.project, tag):
|
||||
raise serializers.ValidationError(_("The tag doesn't exist."))
|
||||
|
||||
return attrs
|
|
@ -23,9 +23,14 @@ def tag_exist_for_project_elements(project, tag):
|
|||
return tag in dict(project.tags_colors).keys()
|
||||
|
||||
|
||||
def create_tags(project, new_tags_colors):
|
||||
project.tags_colors += [[k, v] for k,v in new_tags_colors.items()]
|
||||
project.save(update_fields=["tags_colors"])
|
||||
|
||||
|
||||
def create_tag(project, tag, color):
|
||||
project.tags_colors.append([tag, color])
|
||||
project.save()
|
||||
project.save(update_fields=["tags_colors"])
|
||||
|
||||
|
||||
def edit_tag(project, from_tag, to_tag=None, color=None):
|
||||
|
@ -38,9 +43,17 @@ def edit_tag(project, from_tag, to_tag=None, color=None):
|
|||
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};
|
||||
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()
|
||||
|
@ -50,15 +63,23 @@ def edit_tag(project, from_tag, to_tag=None, color=None):
|
|||
|
||||
|
||||
project.tags_colors = list(tags_colors.items())
|
||||
project.save()
|
||||
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]
|
||||
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 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, color=color)
|
||||
cursor = connection.cursor()
|
||||
|
@ -68,14 +89,22 @@ def rename_tag(project, from_tag, to_tag, color=None):
|
|||
tags_colors.pop(from_tag)
|
||||
tags_colors[to_tag] = color
|
||||
project.tags_colors = list(tags_colors.items())
|
||||
project.save()
|
||||
project.save(update_fields=["tags_colors"])
|
||||
|
||||
|
||||
def delete_tag(project, tag):
|
||||
sql = """
|
||||
UPDATE userstories_userstory SET tags = array_remove(tags, '{tag}') WHERE project_id={project_id};
|
||||
UPDATE tasks_task SET tags = array_remove(tags, '{tag}') WHERE project_id={project_id};
|
||||
UPDATE issues_issue SET tags = array_remove(tags, '{tag}') WHERE project_id={project_id};
|
||||
UPDATE userstories_userstory
|
||||
SET tags = array_remove(tags, '{tag}')
|
||||
WHERE project_id = {project_id};
|
||||
|
||||
UPDATE tasks_task
|
||||
SET tags = array_remove(tags, '{tag}')
|
||||
WHERE project_id = {project_id};
|
||||
|
||||
UPDATE issues_issue
|
||||
SET tags = array_remove(tags, '{tag}')
|
||||
WHERE project_id = {project_id};
|
||||
"""
|
||||
sql = sql.format(project_id=project.id, tag=tag)
|
||||
cursor = connection.cursor()
|
||||
|
@ -84,7 +113,7 @@ def delete_tag(project, tag):
|
|||
tags_colors = dict(project.tags_colors)
|
||||
del tags_colors[tag]
|
||||
project.tags_colors = list(tags_colors.items())
|
||||
project.save()
|
||||
project.save(update_fields=["tags_colors"])
|
||||
|
||||
|
||||
def mix_tags(project, from_tags, to_tag):
|
|
@ -20,4 +20,3 @@
|
|||
def tags_normalization(sender, instance, **kwargs):
|
||||
if isinstance(instance.tags, (list, tuple)):
|
||||
instance.tags = list(map(str.lower, instance.tags))
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ 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.tagging.api import TaggedResourceMixin
|
||||
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
|
||||
|
||||
from . import models
|
||||
|
|
|
@ -36,7 +36,7 @@ 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.tagging.api import TaggedResourceMixin
|
||||
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
|
||||
|
||||
from . import models
|
||||
|
|
Loading…
Reference in New Issue