US #55: Custom fields - Create API end point
parent
38d4eacd76
commit
c17157f8ec
|
@ -0,0 +1,57 @@
|
||||||
|
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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.api import ModelCrudViewSet
|
||||||
|
from taiga.base import filters
|
||||||
|
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
from . import serializers
|
||||||
|
from . import permissions
|
||||||
|
from . import services
|
||||||
|
|
||||||
|
|
||||||
|
class UserStoryCustomAttributeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
|
model = models.UserStoryCustomAttribute
|
||||||
|
serializer_class = serializers.UserStoryCustomAttributeSerializer
|
||||||
|
permission_classes = (permissions.UserStoryCustomAttributePermission,)
|
||||||
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
|
filter_fields = ("project",)
|
||||||
|
bulk_update_param = "bulk_userstory_custom_attributes"
|
||||||
|
bulk_update_perm = "change_userstory_custom_attributes"
|
||||||
|
bulk_update_order_action = services.bulk_update_userstory_custom_attribute_order
|
||||||
|
|
||||||
|
|
||||||
|
class TaskCustomAttributeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
|
model = models.TaskCustomAttribute
|
||||||
|
serializer_class = serializers.TaskCustomAttributeSerializer
|
||||||
|
permission_classes = (permissions.TaskCustomAttributePermission,)
|
||||||
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
|
filter_fields = ("project",)
|
||||||
|
bulk_update_param = "bulk_task_custom_attributes"
|
||||||
|
bulk_update_perm = "change_task_custom_attributes"
|
||||||
|
bulk_update_order_action = services.bulk_update_task_custom_attribute_order
|
||||||
|
|
||||||
|
|
||||||
|
class IssueCustomAttributeViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
|
||||||
|
model = models.IssueCustomAttribute
|
||||||
|
serializer_class = serializers.IssueCustomAttributeSerializer
|
||||||
|
permission_classes = (permissions.IssueCustomAttributePermission,)
|
||||||
|
filter_backends = (filters.CanViewProjectFilterBackend,)
|
||||||
|
filter_fields = ("project",)
|
||||||
|
bulk_update_param = "bulk_issue_custom_attributes"
|
||||||
|
bulk_update_perm = "change_issue_custom_attributes"
|
||||||
|
bulk_update_order_action = services.bulk_update_issue_custom_attribute_order
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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.api.permissions import TaigaResourcePermission
|
||||||
|
from taiga.base.api.permissions import HasProjectPerm
|
||||||
|
from taiga.base.api.permissions import IsProjectOwner
|
||||||
|
from taiga.base.api.permissions import AllowAny
|
||||||
|
|
||||||
|
|
||||||
|
class UserStoryCustomAttributePermission(TaigaResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
create_perms = IsProjectOwner()
|
||||||
|
update_perms = IsProjectOwner()
|
||||||
|
destroy_perms = IsProjectOwner()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
|
|
||||||
|
|
||||||
|
class TaskCustomAttributePermission(TaigaResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
create_perms = IsProjectOwner()
|
||||||
|
update_perms = IsProjectOwner()
|
||||||
|
destroy_perms = IsProjectOwner()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
bulk_update_order_perms = IsProjectOwner()
|
||||||
|
|
||||||
|
|
||||||
|
class IssueCustomAttributePermission(TaigaResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
create_perms = IsProjectOwner()
|
||||||
|
update_perms = IsProjectOwner()
|
||||||
|
destroy_perms = IsProjectOwner()
|
||||||
|
list_perms = AllowAny()
|
||||||
|
bulk_update_order_perms = IsProjectOwner()
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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_lazy as _
|
||||||
|
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
|
from taiga.base.serializers import ModelSerializer
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Base Serializer Class
|
||||||
|
#######################################################
|
||||||
|
|
||||||
|
class BaseCustomAttributeSerializer(ModelSerializer):
|
||||||
|
def validate(self, data):
|
||||||
|
"""
|
||||||
|
Check the name is not duplicated in the project. Check when:
|
||||||
|
- create a new one
|
||||||
|
- update the name
|
||||||
|
- update the project (move to another project)
|
||||||
|
"""
|
||||||
|
data_name = data.get("name", None)
|
||||||
|
data_project = data.get("project", None)
|
||||||
|
if self.object:
|
||||||
|
data_name = data_name or self.object.name
|
||||||
|
data_project = data_project or self.object.project
|
||||||
|
|
||||||
|
model = self.Meta.model
|
||||||
|
qs = model.objects.filter(project=data_project, name=data_name)
|
||||||
|
if qs.exists():
|
||||||
|
raise ValidationError(_("There is a custom field with the same name in this project."))
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Custom Field Serializers
|
||||||
|
#######################################################
|
||||||
|
|
||||||
|
class UserStoryCustomAttributeSerializer(BaseCustomAttributeSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.UserStoryCustomAttribute
|
||||||
|
|
||||||
|
|
||||||
|
class TaskCustomAttributeSerializer(BaseCustomAttributeSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.TaskCustomAttribute
|
||||||
|
|
||||||
|
|
||||||
|
class IssueCustomAttributeSerializer(BaseCustomAttributeSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.IssueCustomAttribute
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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.db import transaction
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def bulk_update_userstory_custom_attribute_order(project, user, data):
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
prepare bulk_update_order as update custom_attributes_userstorycustomattribute set "order" = $1
|
||||||
|
where custom_attributes_userstorycustomattribute.id = $2 and
|
||||||
|
custom_attributes_userstorycustomattribute.project_id = $3;
|
||||||
|
"""
|
||||||
|
cursor.execute(sql)
|
||||||
|
for id, order in data:
|
||||||
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def bulk_update_task_custom_attribute_order(project, user, data):
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
prepare bulk_update_order as update custom_attributes_taskcustomattribute set "order" = $1
|
||||||
|
where custom_attributes_taskcustomattribute.id = $2 and
|
||||||
|
custom_attributes_taskcustomattribute.project_id = $3;
|
||||||
|
"""
|
||||||
|
cursor.execute(sql)
|
||||||
|
for id, order in data:
|
||||||
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def bulk_update_issue_custom_attribute_order(project, user, data):
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
prepare bulk_update_order as update custom_attributes_issuecustomattribute set "order" = $1
|
||||||
|
where custom_attributes_issuecustomattribute.id = $2 and
|
||||||
|
custom_attributes_issuecustomattribute.project_id = $3;
|
||||||
|
"""
|
||||||
|
cursor.execute(sql)
|
||||||
|
for id, order in data:
|
||||||
|
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||||
|
(order, id, project.id))
|
||||||
|
cursor.execute("DEALLOCATE bulk_update_order")
|
||||||
|
cursor.close()
|
|
@ -35,23 +35,10 @@ from taiga.userstorage.api import StorageEntriesViewSet
|
||||||
router.register(r"user-storage", StorageEntriesViewSet, base_name="user-storage")
|
router.register(r"user-storage", StorageEntriesViewSet, base_name="user-storage")
|
||||||
|
|
||||||
|
|
||||||
# Resolver
|
# Notify policies
|
||||||
from taiga.projects.references.api import ResolverViewSet
|
from taiga.projects.notifications.api import NotifyPolicyViewSet
|
||||||
|
|
||||||
router.register(r"resolver", ResolverViewSet, base_name="resolver")
|
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
|
||||||
|
|
||||||
|
|
||||||
# Search
|
|
||||||
from taiga.searches.api import SearchViewSet
|
|
||||||
|
|
||||||
router.register(r"search", SearchViewSet, base_name="search")
|
|
||||||
|
|
||||||
|
|
||||||
# Importer
|
|
||||||
from taiga.export_import.api import ProjectImporterViewSet, ProjectExporterViewSet
|
|
||||||
|
|
||||||
router.register(r"importer", ProjectImporterViewSet, base_name="importer")
|
|
||||||
router.register(r"exporter", ProjectExporterViewSet, base_name="exporter")
|
|
||||||
|
|
||||||
|
|
||||||
# Projects & Selectors
|
# Projects & Selectors
|
||||||
|
@ -80,6 +67,31 @@ router.register(r"priorities", PriorityViewSet, base_name="priorities")
|
||||||
router.register(r"severities",SeverityViewSet , base_name="severities")
|
router.register(r"severities",SeverityViewSet , base_name="severities")
|
||||||
|
|
||||||
|
|
||||||
|
# Custom Attributes
|
||||||
|
from taiga.projects.custom_attributes.api import UserStoryCustomAttributeViewSet
|
||||||
|
from taiga.projects.custom_attributes.api import TaskCustomAttributeViewSet
|
||||||
|
from taiga.projects.custom_attributes.api import IssueCustomAttributeViewSet
|
||||||
|
|
||||||
|
router.register(r"userstory-custom-attributes", UserStoryCustomAttributeViewSet,
|
||||||
|
base_name="userstory-custom-attributes")
|
||||||
|
router.register(r"task-custom-attributes", TaskCustomAttributeViewSet,
|
||||||
|
base_name="task-custom-attributes")
|
||||||
|
router.register(r"issue-custom-attributes", IssueCustomAttributeViewSet,
|
||||||
|
base_name="issue-custom-attributes")
|
||||||
|
|
||||||
|
|
||||||
|
# Search
|
||||||
|
from taiga.searches.api import SearchViewSet
|
||||||
|
|
||||||
|
router.register(r"search", SearchViewSet, base_name="search")
|
||||||
|
|
||||||
|
|
||||||
|
# Resolver
|
||||||
|
from taiga.projects.references.api import ResolverViewSet
|
||||||
|
|
||||||
|
router.register(r"resolver", ResolverViewSet, base_name="resolver")
|
||||||
|
|
||||||
|
|
||||||
# Attachments
|
# Attachments
|
||||||
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
||||||
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
||||||
|
@ -93,11 +105,21 @@ router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-
|
||||||
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
||||||
|
|
||||||
|
|
||||||
# Webhooks
|
# Project components
|
||||||
from taiga.webhooks.api import WebhookViewSet, WebhookLogViewSet
|
from taiga.projects.milestones.api import MilestoneViewSet
|
||||||
|
from taiga.projects.userstories.api import UserStoryViewSet
|
||||||
|
from taiga.projects.tasks.api import TaskViewSet
|
||||||
|
from taiga.projects.issues.api import IssueViewSet
|
||||||
|
from taiga.projects.issues.api import VotersViewSet
|
||||||
|
from taiga.projects.wiki.api import WikiViewSet, WikiLinkViewSet
|
||||||
|
|
||||||
router.register(r"webhooks", WebhookViewSet, base_name="webhooks")
|
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
||||||
router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs")
|
router.register(r"userstories", UserStoryViewSet, base_name="userstories")
|
||||||
|
router.register(r"tasks", TaskViewSet, base_name="tasks")
|
||||||
|
router.register(r"issues", IssueViewSet, base_name="issues")
|
||||||
|
router.register(r"issues/(?P<issue_id>\d+)/voters", VotersViewSet, base_name="issue-voters")
|
||||||
|
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
||||||
|
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
|
||||||
|
|
||||||
|
|
||||||
# History & Components
|
# History & Components
|
||||||
|
@ -120,27 +142,12 @@ router.register(r"timeline/user", UserTimeline, base_name="user-timeline")
|
||||||
router.register(r"timeline/project", ProjectTimeline, base_name="project-timeline")
|
router.register(r"timeline/project", ProjectTimeline, base_name="project-timeline")
|
||||||
|
|
||||||
|
|
||||||
# Project components
|
# Webhooks
|
||||||
from taiga.projects.milestones.api import MilestoneViewSet
|
from taiga.webhooks.api import WebhookViewSet
|
||||||
from taiga.projects.userstories.api import UserStoryViewSet
|
from taiga.webhooks.api import WebhookLogViewSet
|
||||||
from taiga.projects.tasks.api import TaskViewSet
|
|
||||||
from taiga.projects.issues.api import IssueViewSet
|
|
||||||
from taiga.projects.issues.api import VotersViewSet
|
|
||||||
from taiga.projects.wiki.api import WikiViewSet, WikiLinkViewSet
|
|
||||||
|
|
||||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
router.register(r"webhooks", WebhookViewSet, base_name="webhooks")
|
||||||
router.register(r"userstories", UserStoryViewSet, base_name="userstories")
|
router.register(r"webhooklogs", WebhookLogViewSet, base_name="webhooklogs")
|
||||||
router.register(r"tasks", TaskViewSet, base_name="tasks")
|
|
||||||
router.register(r"issues", IssueViewSet, base_name="issues")
|
|
||||||
router.register(r"issues/(?P<issue_id>\d+)/voters", VotersViewSet, base_name="issue-voters")
|
|
||||||
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
|
||||||
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
|
|
||||||
|
|
||||||
|
|
||||||
# Notify policies
|
|
||||||
from taiga.projects.notifications.api import NotifyPolicyViewSet
|
|
||||||
|
|
||||||
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
|
|
||||||
|
|
||||||
|
|
||||||
# GitHub webhooks
|
# GitHub webhooks
|
||||||
|
@ -161,5 +168,12 @@ from taiga.hooks.bitbucket.api import BitBucketViewSet
|
||||||
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
|
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
|
||||||
|
|
||||||
|
|
||||||
|
# Importer
|
||||||
|
from taiga.export_import.api import ProjectImporterViewSet, ProjectExporterViewSet
|
||||||
|
|
||||||
|
router.register(r"importer", ProjectImporterViewSet, base_name="importer")
|
||||||
|
router.register(r"exporter", ProjectExporterViewSet, base_name="exporter")
|
||||||
|
|
||||||
|
|
||||||
# feedback
|
# feedback
|
||||||
# - see taiga.feedback.routers and taiga.feedback.apps
|
# - see taiga.feedback.routers and taiga.feedback.apps
|
||||||
|
|
Loading…
Reference in New Issue