diff --git a/taiga/base/api/views.py b/taiga/base/api/views.py index c321c3d1..a2d51af9 100644 --- a/taiga/base/api/views.py +++ b/taiga/base/api/views.py @@ -287,6 +287,9 @@ class APIView(View): request.user def check_permissions(self, request, action, obj=None): + if action is None: + self.permission_denied(request) + for permission in self.get_permissions(): if not permission.check_permissions(action=action, obj=obj): self.permission_denied(request) diff --git a/taiga/export_import/api.py b/taiga/export_import/api.py new file mode 100644 index 00000000..b0eb3d62 --- /dev/null +++ b/taiga/export_import/api.py @@ -0,0 +1,43 @@ +from rest_framework.response import Response +from rest_framework import status + +from taiga.base.api.mixins import CreateModelMixin +from taiga.base.api.viewsets import GenericViewSet +from taiga.base.decorators import detail_route +from taiga.projects.models import Project + +from . import serializers +from . import service +from . import permissions + +from django.db.models import signals + +def __disconnect_signals(): + signals.pre_save.receivers = [] + signals.post_save.receivers = [] + +class ProjectImporterViewSet(CreateModelMixin, GenericViewSet): + model = Project + permission_classes = (permissions.ImportPermission, ) + + def create(self, request, *args, **kwargs): + self.check_permissions(request, 'import_project', None) + + data = request.DATA + project_serialized = service.store_project(data) + + if project_serialized: + service.store_choices(project_serialized.object, data, "points", project_serialized.object.points, serializers.PointsExportSerializer, "default_points") + service.store_choices(project_serialized.object, data, "issue_types", project_serialized.object.issue_types, serializers.IssueTypeExportSerializer, "default_issue_type") + service.store_choices(project_serialized.object, data, "issue_statuses", project_serialized.object.issue_statuses, serializers.IssueStatusExportSerializer, "default_issue_status") + service.store_choices(project_serialized.object, data, "us_statuses", project_serialized.object.us_statuses, serializers.UserStoryStatusExportSerializer, "default_us_status") + service.store_choices(project_serialized.object, data, "task_statuses", project_serialized.object.task_statuses, serializers.TaskStatusExportSerializer, "default_task_status") + service.store_choices(project_serialized.object, data, "priorities", project_serialized.object.priorities, serializers.PriorityExportSerializer, "default_priority") + service.store_choices(project_serialized.object, data, "severities", project_serialized.object.severities, serializers.SeverityExportSerializer, "default_severity") + service.store_default_choices(project_serialized.object, data) + service.store_roles(project_serialized.object, data) + service.store_memberships(project_serialized.object, data) + headers = self.get_success_headers(project_serialized.data) + return Response(project_serialized.data, status=status.HTTP_201_CREATED, headers=headers) + + return Response(service.get_errors(), status=status.HTTP_400_BAD_REQUEST) diff --git a/taiga/export_import/permissions.py b/taiga/export_import/permissions.py new file mode 100644 index 00000000..00e34fa1 --- /dev/null +++ b/taiga/export_import/permissions.py @@ -0,0 +1,24 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from taiga.base.api.permissions import (TaigaResourcePermission, + IsProjectOwner, IsAuthenticated) + + +class ImportPermission(TaigaResourcePermission): + import_project_perms = IsAuthenticated() + import_item_perms = IsProjectOwner() diff --git a/taiga/export_import/service.py b/taiga/export_import/service.py index 92b75c91..13dc4b6d 100644 --- a/taiga/export_import/service.py +++ b/taiga/export_import/service.py @@ -1,196 +1,228 @@ -from . import serializers -from taiga.projects.models import Project +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from django.db.models import signals from django.db import transaction from django.contrib.contenttypes.models import ContentType +from taiga.projects.models import Project + +from . import serializers + +_errors_log = [] + +def get_errors(): + _errors = _errors_log.copy() + _errors_log.clear() + return _errors + +def add_errors(errors): + _errors_log.append(errors) + def project_to_dict(project): return serializers.ProjectExportSerializer(project).data +@transaction.atomic +def store_project(data): + project_data = {} + for key, value in data.items(): + excluded_fields = [ + "default_points", "default_us_status", "default_task_status", + "default_priority", "default_severity", "default_issue_status", + "default_issue_type", "memberships", "points", "us_statuses", + "task_statuses", "issue_statuses", "priorities", "severities", + "issue_types", "roles", "milestones", "wiki_pages", + "wiki_links", "notify_policies", "user_stories", "issues" + ] + if key not in excluded_fields: + project_data[key] = value + + serialized = serializers.ProjectExportSerializer(data=project_data) + if serialized.is_valid(): + serialized.object._importing = True + serialized.object.save() + return serialized + else: + add_errors(serialized.errors) + return None + +@transaction.atomic +def store_choices(project, data, field, relation, serializer, default_field): + relation.all().delete() + + for point in data[field]: + serialized = serializer(data=point) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + +@transaction.atomic +def store_default_choices(project, data): + project.default_points = project.points.all().get(name=data['default_points']) + project.default_issue_type = project.issue_types.get(name=data['default_issue_type']) + project.default_issue_status = project.issue_statuses.get(name=data['default_issue_status']) + project.default_us_status = project.us_statuses.get(name=data['default_us_status']) + project.default_task_status = project.task_statuses.get(name=data['default_task_status']) + project.default_priority = project.priorities.get(name=data['default_priority']) + project.default_severity = project.severities.get(name=data['default_severity']) + project._importing = True + project.save() + +@transaction.atomic +def store_roles(project, data): + project.roles.all().delete() + for role in data['roles']: + serialized = serializers.RoleExportSerializer(data=role) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + +@transaction.atomic +def store_memberships(project, data): + for membership in data['memberships']: + serialized = serializers.MembershipExportSerializer(data=membership, context={"project": project}) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + +@transaction.atomic +def store_task(project, us, task): + serialized = serializers.TaskExportSerializer(data=task, context={"project": project}) + serialized.is_valid() + serialized.object.user_story = us + serialized.object.project = project + serialized.object._importing = True + serialized.save() + + for task_attachment in task['attachments']: + store_attachment(project, serialized.object, task_attachment) + +@transaction.atomic +def store_milestones(project, data): + for milestone in data['milestones']: + serialized = serializers.MilestoneExportSerializer(data=milestone) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + + for task_without_us in milestone['tasks_without_us']: + store_task(project, None, task_without_us) + +def store_attachment(project, obj, attachment): + serialized = serializers.AttachmentExportSerializer(data=attachment) + serialized.is_valid() + serialized.object.content_type = ContentType.objects.get_for_model(obj.__class__) + serialized.object.object_id = obj.id + serialized.object.project = project + serialized.object._importing = True + serialized.save() + +@transaction.atomic +def store_wiki_pages(project, data): + for wiki_page in data['wiki_pages']: + serialized = serializers.WikiPageExportSerializer(data=wiki_page) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + + for attachment in wiki_page['attachments']: + store_attachment(project, serialized.object, attachment) + +@transaction.atomic +def store_wiki_links(project, data): + for wiki_link in data['wiki_links']: + serialized = serializers.WikiLinkExportSerializer(data=wiki_link) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + +@transaction.atomic +def store_role_point(project, us, role_point): + serialized = serializers.RolePointsExportSerializer(data=role_point, context={"project": project} ) + serialized.is_valid() + serialized.object.user_story = us + serialized.save() + +@transaction.atomic +def store_user_stories(project, data): + for userstory in data['user_stories']: + userstory_data = {} + for key, value in userstory.items(): + excluded_fields = [ + 'tasks', 'role_points' + ] + if key not in excluded_fields: + userstory_data[key] = value + serialized_us = serializers.UserStoryExportSerializer(data=userstory_data, context={"project": project}) + serialized_us.is_valid() + serialized_us.object.project = project + serialized_us.object._importing = True + serialized_us.save() + + for task in userstory['tasks']: + store_task(project, serialized_us.object, task) + + for us_attachment in userstory['attachments']: + store_attachment(project, serialized_us.object, us_attachment) + + for role_point in userstory['role_points']: + store_role_point(project, serialized_us.object, role_point) + +@transaction.atomic +def store_issues(project, data): + for issue in data['issues']: + serialized = serializers.IssueExportSerializer(data=issue, context={"project": project}) + serialized.is_valid() + serialized.object.project = project + serialized.object._importing = True + serialized.save() + + for attachment in issue['attachments']: + store_attachment(project, serialized.object, attachment) + + def dict_to_project(data, owner=None): signals.pre_save.receivers = [] signals.post_save.receivers = [] signals.pre_delete.receivers = [] signals.post_delete.receivers = [] - @transaction.atomic - def store_project(data): - project_data = {} - for key, value in data.items(): - excluded_fields = [ - "default_points", "default_us_status", "default_task_status", - "default_priority", "default_severity", "default_issue_status", - "default_issue_type", "memberships", "points", "us_statuses", - "task_statuses", "issue_statuses", "priorities", "severities", - "issue_types", "roles", "milestones", "wiki_pages", - "wiki_links", "notify_policies", "user_stories", "issues" - ] - if key not in excluded_fields: - project_data[key] = value - - serialized = serializers.ProjectExportSerializer(data=project_data) - serialized.is_valid() - serialized.object._importing = True - serialized.object.save() - return serialized.object - - @transaction.atomic - def store_choices(project, data, field, relation, serializer, default_field): - relation.all().delete() - - for point in data[field]: - serialized = serializer(data=point) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - @transaction.atomic - def store_default_choices(project, data): - project.default_points = project.points.all().get(name=data['default_points']) - project.default_issue_type = project.issue_types.get(name=data['default_issue_type']) - project.default_issue_status = project.issue_statuses.get(name=data['default_issue_status']) - project.default_us_status = project.us_statuses.get(name=data['default_us_status']) - project.default_task_status = project.task_statuses.get(name=data['default_task_status']) - project.default_priority = project.priorities.get(name=data['default_priority']) - project.default_severity = project.severities.get(name=data['default_severity']) - project._importing = True - project.save() - - @transaction.atomic - def store_roles(project, data): - project.roles.all().delete() - for role in data['roles']: - serialized = serializers.RoleExportSerializer(data=role) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - @transaction.atomic - def store_memberships(project, data): - for membership in data['memberships']: - serialized = serializers.MembershipExportSerializer(data=membership, context={"project": project}) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - @transaction.atomic - def store_task(project, us, task): - serialized = serializers.TaskExportSerializer(data=task, context={"project": project}) - serialized.is_valid() - serialized.object.user_story = us - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - for task_attachment in task['attachments']: - store_attachment(project, serialized.object, task_attachment) - - @transaction.atomic - def store_milestones(project, data): - for milestone in data['milestones']: - serialized = serializers.MilestoneExportSerializer(data=milestone) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - for task_without_us in milestone['tasks_without_us']: - store_task(project, None, task_without_us) - - def store_attachment(project, obj, attachment): - serialized = serializers.AttachmentExportSerializer(data=attachment) - serialized.is_valid() - serialized.object.content_type = ContentType.objects.get_for_model(obj.__class__) - serialized.object.object_id = obj.id - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - @transaction.atomic - def store_wiki_pages(project, data): - for wiki_page in data['wiki_pages']: - serialized = serializers.WikiPageExportSerializer(data=wiki_page) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - for attachment in wiki_page['attachments']: - store_attachment(project, serialized.object, attachment) - - @transaction.atomic - def store_wiki_links(project, data): - for wiki_link in data['wiki_links']: - serialized = serializers.WikiLinkExportSerializer(data=wiki_link) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - @transaction.atomic - def store_role_point(project, us, role_point): - serialized = serializers.RolePointsExportSerializer(data=role_point, context={"project": project} ) - serialized.is_valid() - serialized.object.user_story = us - serialized.save() - - @transaction.atomic - def store_user_stories(project, data): - for userstory in data['user_stories']: - userstory_data = {} - for key, value in userstory.items(): - excluded_fields = [ - 'tasks', 'role_points' - ] - if key not in excluded_fields: - userstory_data[key] = value - serialized_us = serializers.UserStoryExportSerializer(data=userstory_data, context={"project": project}) - serialized_us.is_valid() - serialized_us.object.project = project - serialized_us.object._importing = True - serialized_us.save() - - for task in userstory['tasks']: - store_task(project, serialized_us.object, task) - - for us_attachment in userstory['attachments']: - store_attachment(project, serialized_us.object, us_attachment) - - for role_point in userstory['role_points']: - store_role_point(project, serialized_us.object, role_point) - - @transaction.atomic - def store_issues(project, data): - for issue in data['issues']: - serialized = serializers.IssueExportSerializer(data=issue, context={"project": project}) - serialized.is_valid() - serialized.object.project = project - serialized.object._importing = True - serialized.save() - - for attachment in issue['attachments']: - store_attachment(project, serialized.object, attachment) - if owner: data['owner'] = owner - project = store_project(data) - store_choices(project, data, "points", project.points, serializers.PointsExportSerializer, "default_points") - store_choices(project, data, "issue_types", project.issue_types, serializers.IssueTypeExportSerializer, "default_issue_type") - store_choices(project, data, "issue_statuses", project.issue_statuses, serializers.IssueStatusExportSerializer, "default_issue_status") - store_choices(project, data, "us_statuses", project.us_statuses, serializers.UserStoryStatusExportSerializer, "default_us_status") - store_choices(project, data, "task_statuses", project.task_statuses, serializers.TaskStatusExportSerializer, "default_task_status") - store_choices(project, data, "priorities", project.priorities, serializers.PriorityExportSerializer, "default_priority") - store_choices(project, data, "severities", project.severities, serializers.SeverityExportSerializer, "default_severity") - store_default_choices(project, data) - store_roles(project, data) - store_memberships(project, data) - store_milestones(project, data) - store_wiki_pages(project, data) - store_wiki_links(project, data) + project_serialized = store_project(data) + store_choices(project_serialized.object, data, "points", project_serialized.object.points, serializers.PointsExportSerializer, "default_points") + store_choices(project_serialized.object, data, "issue_types", project_serialized.object.issue_types, serializers.IssueTypeExportSerializer, "default_issue_type") + store_choices(project_serialized.object, data, "issue_statuses", project_serialized.object.issue_statuses, serializers.IssueStatusExportSerializer, "default_issue_status") + store_choices(project_serialized.object, data, "us_statuses", project_serialized.object.us_statuses, serializers.UserStoryStatusExportSerializer, "default_us_status") + store_choices(project_serialized.object, data, "task_statuses", project_serialized.object.task_statuses, serializers.TaskStatusExportSerializer, "default_task_status") + store_choices(project_serialized.object, data, "priorities", project_serialized.object.priorities, serializers.PriorityExportSerializer, "default_priority") + store_choices(project_serialized.object, data, "severities", project_serialized.object.severities, serializers.SeverityExportSerializer, "default_severity") + store_default_choices(project_serialized.object, data) + store_roles(project_serialized.object, data) + store_memberships(project_serialized.object, data) + store_milestones(project_serialized.object, data) + store_wiki_pages(project_serialized.object, data) + store_wiki_links(project_serialized.object, data) - store_user_stories(project, data) - store_issues(project, data) + store_user_stories(project_serialized.object, data) + store_issues(project_serialized.object, data) diff --git a/taiga/routers.py b/taiga/routers.py index 69161cc6..7807aa73 100644 --- a/taiga/routers.py +++ b/taiga/routers.py @@ -44,6 +44,12 @@ from taiga.searches.api import SearchViewSet router.register(r"search", SearchViewSet, base_name="search") +# Importer +from taiga.export_import.api import ProjectImporterViewSet + +router.register(r"importer", ProjectImporterViewSet, base_name="importer") + + # Projects & Types from taiga.projects.api import RolesViewSet from taiga.projects.api import ProjectViewSet @@ -58,6 +64,7 @@ from taiga.projects.api import PriorityViewSet from taiga.projects.api import SeverityViewSet from taiga.projects.api import ProjectTemplateViewSet + router.register(r"roles", RolesViewSet, base_name="roles") router.register(r"projects", ProjectViewSet, base_name="projects") router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates") @@ -71,7 +78,6 @@ router.register(r"issue-types", IssueTypeViewSet, base_name="issue-types") router.register(r"priorities", PriorityViewSet, base_name="priorities") router.register(r"severities",SeverityViewSet , base_name="severities") - # Attachments from taiga.projects.attachments.api import UserStoryAttachmentViewSet from taiga.projects.attachments.api import IssueAttachmentViewSet