Adding import api
parent
2b36375b6b
commit
6d78c79018
|
@ -290,6 +290,7 @@ REST_FRAMEWORK = {
|
||||||
"PAGINATE_BY": 30,
|
"PAGINATE_BY": 30,
|
||||||
"PAGINATE_BY_PARAM": "page_size",
|
"PAGINATE_BY_PARAM": "page_size",
|
||||||
"MAX_PAGINATE_BY": 1000,
|
"MAX_PAGINATE_BY": 1000,
|
||||||
|
"DATETIME_FORMAT": "%Y-%m-%dT%H:%M:%S%z"
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_PROJECT_TEMPLATE = "scrum"
|
DEFAULT_PROJECT_TEMPLATE = "scrum"
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
# This code is partially taken from django-rest-framework:
|
# This code is partially taken from django-rest-framework:
|
||||||
# Copyright (c) 2011-2014, Tom Christie
|
# Copyright (c) 2011-2014, Tom Christie
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import Http404
|
from django.http import Http404, HttpResponse
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
@ -31,6 +33,9 @@ from rest_framework.utils import formatting
|
||||||
|
|
||||||
from taiga.base.utils.iterators import as_tuple
|
from taiga.base.utils.iterators import as_tuple
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.views.defaults import server_error
|
||||||
|
|
||||||
|
|
||||||
def get_view_name(view_cls, suffix=None):
|
def get_view_name(view_cls, suffix=None):
|
||||||
"""
|
"""
|
||||||
|
@ -436,3 +441,10 @@ class APIView(View):
|
||||||
ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
|
ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
|
||||||
ret['parses'] = [parser.media_type for parser in self.parser_classes]
|
ret['parses'] = [parser.media_type for parser in self.parser_classes]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def api_server_error(request, *args, **kwargs):
|
||||||
|
if settings.DEBUG == False and request.META['CONTENT_TYPE'] == "application/json":
|
||||||
|
return HttpResponse(json.dumps({"error": "Server application error"}),
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
return server_error(request, *args, **kwargs)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def without_signals(*disablers):
|
||||||
|
for disabler in disablers:
|
||||||
|
if not (isinstance(disabler, list) or isinstance(disabler, tuple)) or len(disabler) == 0:
|
||||||
|
raise ValueError("The parameters must be lists of at least one parameter (the signal)")
|
||||||
|
|
||||||
|
signal, *ids = disabler
|
||||||
|
signal.backup_receivers = signal.receivers
|
||||||
|
signal.receivers = list(filter(lambda x: x[0][0] not in ids, signal.receivers))
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
for disabler in disablers:
|
||||||
|
signal, *ids = disabler
|
||||||
|
signal.receivers = signal.backup_receivers
|
||||||
|
|
|
@ -1,65 +1,194 @@
|
||||||
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.db.transaction import atomic
|
||||||
|
from django.db.models import signals
|
||||||
|
|
||||||
from taiga.base.api.mixins import CreateModelMixin
|
from taiga.base.api.mixins import CreateModelMixin
|
||||||
from taiga.base.api.viewsets import GenericViewSet
|
from taiga.base.api.viewsets import GenericViewSet
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.projects.models import Project
|
from taiga.base.utils.signals import without_signals
|
||||||
|
from taiga.projects.models import Project, Membership
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from . import service
|
from . import service
|
||||||
from . import permissions
|
from . import permissions
|
||||||
|
|
||||||
from django.db.models import signals
|
|
||||||
|
|
||||||
def __disconnect_signals():
|
class Http400(APIException):
|
||||||
signals.pre_save.receivers = []
|
status_code = 400
|
||||||
signals.post_save.receivers = []
|
|
||||||
|
|
||||||
class ProjectImporterViewSet(CreateModelMixin, GenericViewSet):
|
class ProjectImporterViewSet(CreateModelMixin, GenericViewSet):
|
||||||
model = Project
|
model = Project
|
||||||
permission_classes = (permissions.ImportPermission, )
|
permission_classes = (permissions.ImportPermission, )
|
||||||
|
|
||||||
|
@method_decorator(atomic)
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
self.check_permissions(request, 'import_project', None)
|
self.check_permissions(request, 'import_project', None)
|
||||||
|
|
||||||
data = request.DATA.copy()
|
data = request.DATA.copy()
|
||||||
data['owner'] = data.get('owner', request.user.email)
|
data['owner'] = data.get('owner', request.user.email)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "project_post_save")):
|
||||||
project_serialized = service.store_project(data)
|
project_serialized = service.store_project(data)
|
||||||
|
|
||||||
if project_serialized:
|
if project_serialized is None:
|
||||||
service.store_choices(project_serialized.object, data, "points", project_serialized.object.points, serializers.PointsExportSerializer, "default_points")
|
raise Http400(service.get_errors())
|
||||||
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")
|
if "points" in data:
|
||||||
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,
|
||||||
service.store_choices(project_serialized.object, data, "task_statuses", project_serialized.object.task_statuses, serializers.TaskStatusExportSerializer, "default_task_status")
|
"points", serializers.PointsExportSerializer)
|
||||||
service.store_choices(project_serialized.object, data, "priorities", project_serialized.object.priorities, serializers.PriorityExportSerializer, "default_priority")
|
if "issue_types" in data:
|
||||||
service.store_choices(project_serialized.object, data, "severities", project_serialized.object.severities, serializers.SeverityExportSerializer, "default_severity")
|
service.store_choices(project_serialized.object, data,
|
||||||
|
"issue_types",
|
||||||
|
serializers.IssueTypeExportSerializer)
|
||||||
|
if "issue_statuses" in data:
|
||||||
|
service.store_choices(project_serialized.object, data,
|
||||||
|
"issue_statuses",
|
||||||
|
serializers.IssueStatusExportSerializer,)
|
||||||
|
if "us_statuses" in data:
|
||||||
|
service.store_choices(project_serialized.object, data,
|
||||||
|
"us_statuses",
|
||||||
|
serializers.UserStoryStatusExportSerializer,)
|
||||||
|
if "task_statuses" in data:
|
||||||
|
service.store_choices(project_serialized.object, data,
|
||||||
|
"task_statuses",
|
||||||
|
serializers.TaskStatusExportSerializer)
|
||||||
|
if "priorities" in data:
|
||||||
|
service.store_choices(project_serialized.object, data,
|
||||||
|
"priorities",
|
||||||
|
serializers.PriorityExportSerializer)
|
||||||
|
if "severities" in data:
|
||||||
|
service.store_choices(project_serialized.object, data,
|
||||||
|
"severities",
|
||||||
|
serializers.SeverityExportSerializer)
|
||||||
|
|
||||||
|
if ("points" in data or "issues_types" in data or
|
||||||
|
"issues_statuses" in data or "us_statuses" in data or
|
||||||
|
"task_statuses" in data or "priorities" in data or
|
||||||
|
"severities" in data):
|
||||||
service.store_default_choices(project_serialized.object, data)
|
service.store_default_choices(project_serialized.object, data)
|
||||||
|
|
||||||
|
if "roles" in data:
|
||||||
service.store_roles(project_serialized.object, data)
|
service.store_roles(project_serialized.object, data)
|
||||||
|
|
||||||
|
if "memberships" in data:
|
||||||
service.store_memberships(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)
|
if project_serialized.object.memberships.filter(user=project_serialized.object.owner).count() == 0:
|
||||||
|
if project_serialized.object.roles.all().count() > 0:
|
||||||
|
Membership.objects.create(
|
||||||
|
project=project_serialized.object,
|
||||||
|
email=project_serialized.object.owner.email,
|
||||||
|
user=project_serialized.object.owner,
|
||||||
|
role=project_serialized.object.roles.all().first(),
|
||||||
|
is_owner=True
|
||||||
|
)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
response_data = project_serialized.data
|
||||||
|
response_data['id'] = project_serialized.object.id
|
||||||
|
headers = self.get_success_headers(response_data)
|
||||||
|
return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
|
@method_decorator(atomic)
|
||||||
def issue(self, request, *args, **kwargs):
|
def issue(self, request, *args, **kwargs):
|
||||||
self.check_permissions(request, 'import_item', serializer.object)
|
project = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'import_item', project)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change", "refissue")):
|
||||||
|
issue = service.store_issue(project, request.DATA)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
headers = self.get_success_headers(issue.data)
|
||||||
|
return Response(issue.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
|
@method_decorator(atomic)
|
||||||
def task(self, request, *args, **kwargs):
|
def task(self, request, *args, **kwargs):
|
||||||
self.check_permissions(request, 'import_item', serializer.object)
|
project = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'import_item', project)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change", "reftask")):
|
||||||
|
task = service.store_task(project, request.DATA)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
headers = self.get_success_headers(task.data)
|
||||||
|
return Response(task.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
|
@method_decorator(atomic)
|
||||||
def us(self, request, *args, **kwargs):
|
def us(self, request, *args, **kwargs):
|
||||||
self.check_permissions(request, 'import_item', serializer.object)
|
project = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'import_item', project)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change", "refus")):
|
||||||
|
us = service.store_user_story(project, request.DATA)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
headers = self.get_success_headers(us.data)
|
||||||
|
return Response(us.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
|
@method_decorator(atomic)
|
||||||
|
def milestone(self, request, *args, **kwargs):
|
||||||
|
project = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'import_item', project)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change")):
|
||||||
|
milestone = service.store_milestone(project, request.DATA)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
headers = self.get_success_headers(milestone.data)
|
||||||
|
return Response(milestone.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
|
@detail_route(methods=['post'])
|
||||||
|
@method_decorator(atomic)
|
||||||
def wiki_page(self, request, *args, **kwargs):
|
def wiki_page(self, request, *args, **kwargs):
|
||||||
self.check_permissions(request, 'import_item', serializer.object)
|
project = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'import_item', project)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change")):
|
||||||
|
wiki_page = service.store_wiki_page(project, request.DATA)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
headers = self.get_success_headers(wiki_page.data)
|
||||||
|
return Response(wiki_page.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
|
@method_decorator(atomic)
|
||||||
def wiki_link(self, request, *args, **kwargs):
|
def wiki_link(self, request, *args, **kwargs):
|
||||||
self.check_permissions(request, 'import_item', serializer.object)
|
project = self.get_object_or_none()
|
||||||
|
self.check_permissions(request, 'import_item', project)
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change")):
|
||||||
|
wiki_link = service.store_wiki_link(project, request.DATA)
|
||||||
|
|
||||||
|
errors = service.get_errors()
|
||||||
|
if errors:
|
||||||
|
raise Http400(errors)
|
||||||
|
|
||||||
|
headers = self.get_success_headers(wiki_link.data)
|
||||||
|
return Response(wiki_link.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 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.models import signals
|
||||||
|
|
||||||
|
from taiga.base.utils.signals import without_signals
|
||||||
|
|
||||||
|
from . import serializers
|
||||||
|
from . import service
|
||||||
|
|
||||||
|
|
||||||
|
class TaigaImportError(Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
def store_milestones(project, data):
|
||||||
|
results = []
|
||||||
|
for milestone_data in data.get('milestones', []):
|
||||||
|
milestone = service.store_milestone(project, milestone_data)
|
||||||
|
results.append(milestone)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def store_tasks(project, data):
|
||||||
|
results = []
|
||||||
|
for task in data.get('tasks', []):
|
||||||
|
task = service.store_task(project, task)
|
||||||
|
results.append(task)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def store_wiki_pages(project, data):
|
||||||
|
results = []
|
||||||
|
for wiki_page in data.get('wiki_pages', []):
|
||||||
|
results.append(service.store_wiki_page(project, wiki_page))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def store_wiki_links(project, data):
|
||||||
|
results = []
|
||||||
|
for wiki_link in data.get('wiki_links', []):
|
||||||
|
results.append(service.store_wiki_link(project, wiki_link))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def store_user_stories(project, data):
|
||||||
|
results = []
|
||||||
|
for userstory in data.get('user_stories', []):
|
||||||
|
us = service.store_user_story(project, userstory)
|
||||||
|
results.append(us)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def store_issues(project, data):
|
||||||
|
issues = []
|
||||||
|
for issue in data.get('issues', []):
|
||||||
|
issues.append(service.store_issue(project, issue))
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def dict_to_project(data, owner=None):
|
||||||
|
if owner:
|
||||||
|
data['owner'] = owner
|
||||||
|
|
||||||
|
with without_signals([signals.post_save, "project_post_save"]):
|
||||||
|
project_serialized = service.store_project(data)
|
||||||
|
|
||||||
|
if not project_serialized:
|
||||||
|
raise TaigaImportError('error importing project')
|
||||||
|
|
||||||
|
proj = project_serialized.object
|
||||||
|
|
||||||
|
service.store_choices(proj, data, "points", serializers.PointsExportSerializer)
|
||||||
|
service.store_choices(proj, data, "issue_types", serializers.IssueTypeExportSerializer)
|
||||||
|
service.store_choices(proj, data, "issue_statuses", serializers.IssueStatusExportSerializer)
|
||||||
|
service.store_choices(proj, data, "us_statuses", serializers.UserStoryStatusExportSerializer)
|
||||||
|
service.store_choices(proj, data, "task_statuses", serializers.TaskStatusExportSerializer)
|
||||||
|
service.store_choices(proj, data, "priorities", serializers.PriorityExportSerializer)
|
||||||
|
service.store_choices(proj, data, "severities", serializers.SeverityExportSerializer)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing choices')
|
||||||
|
|
||||||
|
service.store_default_choices(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing default choices')
|
||||||
|
|
||||||
|
with without_signals([signals.post_save, "role_post_save"]):
|
||||||
|
service.store_roles(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing roles')
|
||||||
|
|
||||||
|
service.store_memberships(proj, data)
|
||||||
|
|
||||||
|
if proj.memberships.filter(user=proj.owner).count() == 0:
|
||||||
|
if proj.roles.all().count() > 0:
|
||||||
|
Membership.objects.create(
|
||||||
|
project=proj,
|
||||||
|
email=proj.owner.email,
|
||||||
|
user=proj.owner,
|
||||||
|
role=proj.roles.all().first(),
|
||||||
|
is_owner=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing memberships')
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change")):
|
||||||
|
store_milestones(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing milestones')
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change")):
|
||||||
|
store_wiki_pages(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing wiki pages')
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change")):
|
||||||
|
store_wiki_links(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing wiki links')
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change", "user_story_create_role_points_handler", "refus")):
|
||||||
|
store_user_stories(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing user stories')
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change", "refissue")):
|
||||||
|
store_issues(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing issues')
|
||||||
|
|
||||||
|
with without_signals((signals.post_save, "events_dispatcher_on_change", "reftask")):
|
||||||
|
store_tasks(proj, data)
|
||||||
|
|
||||||
|
if service.get_errors(clear=False):
|
||||||
|
raise TaigaImportError('error importing issues')
|
|
@ -1,18 +1,49 @@
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models import signals
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import pprint
|
||||||
|
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
from taiga.export_import.renderers import ExportRenderer
|
from taiga.export_import.renderers import ExportRenderer
|
||||||
from taiga.export_import.service import dict_to_project
|
from taiga.export_import.dump_service import dict_to_project, TaigaImportError
|
||||||
|
from taiga.export_import.service import get_errors
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
args = '<dump_file> <owner-email>'
|
args = '<dump_file> <owner-email>'
|
||||||
help = 'Export a project to json'
|
help = 'Export a project to json'
|
||||||
renderer_context = {"indent": 4}
|
renderer_context = {"indent": 4}
|
||||||
renderer = ExportRenderer()
|
renderer = ExportRenderer()
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option('--overwrite',
|
||||||
|
action='store_true',
|
||||||
|
dest='overwrite',
|
||||||
|
default=False,
|
||||||
|
help='Delete project if exists'),
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
data = json.loads(open(args[0], 'r').read())
|
data = json.loads(open(args[0], 'r').read())
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
if options["overwrite"]:
|
||||||
|
receivers_back = signals.post_delete.receivers
|
||||||
|
signals.post_delete.receivers = []
|
||||||
|
try:
|
||||||
|
proj = Project.objects.get(slug=data.get("slug", "not a slug"))
|
||||||
|
proj.tasks.all().delete()
|
||||||
|
proj.user_stories.all().delete()
|
||||||
|
proj.issues.all().delete()
|
||||||
|
proj.memberships.all().delete()
|
||||||
|
proj.roles.all().delete()
|
||||||
|
proj.delete()
|
||||||
|
except Project.DoesNotExist:
|
||||||
|
pass
|
||||||
|
signals.post_delete.receivers = receivers_back
|
||||||
dict_to_project(data, args[1])
|
dict_to_project(data, args[1])
|
||||||
|
except TaigaImportError as e:
|
||||||
|
print("ERROR:", end=" ")
|
||||||
|
print(e.message)
|
||||||
|
print(get_errors())
|
||||||
|
|
|
@ -14,31 +14,30 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.files.base import ContentFile
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
import json
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import io
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.projects import models as projects_models
|
from taiga.projects import models as projects_models
|
||||||
from taiga.projects.userstories import models as userstories_models
|
from taiga.projects.userstories import models as userstories_models
|
||||||
from taiga.projects.tasks import models as tasks_models
|
from taiga.projects.tasks import models as tasks_models
|
||||||
from taiga.projects.issues import models as issues_models
|
from taiga.projects.issues import models as issues_models
|
||||||
from taiga.projects.milestones import models as milestones_models
|
from taiga.projects.milestones import models as milestones_models
|
||||||
from taiga.projects.wiki import models as wiki_models
|
from taiga.projects.wiki import models as wiki_models
|
||||||
from taiga.projects.votes import models as votes_models
|
|
||||||
from taiga.projects.notifications import models as notifications_models
|
|
||||||
from taiga.projects.history import models as history_models
|
from taiga.projects.history import models as history_models
|
||||||
from taiga.projects.attachments import models as attachments_models
|
from taiga.projects.attachments import models as attachments_models
|
||||||
from taiga.users import models as users_models
|
from taiga.users import models as users_models
|
||||||
from taiga.projects.votes import services as votes_service
|
from taiga.projects.votes import services as votes_service
|
||||||
from taiga.projects.history import services as history_service
|
from taiga.projects.history import services as history_service
|
||||||
from taiga.base.serializers import JsonField, PgArrayField
|
from taiga.base.serializers import JsonField, PgArrayField
|
||||||
|
from taiga import mdrender
|
||||||
|
|
||||||
|
|
||||||
class AttachedFileField(serializers.WritableField):
|
class AttachedFileField(serializers.WritableField):
|
||||||
read_only = False
|
read_only = False
|
||||||
|
@ -72,6 +71,15 @@ class UserRelatedField(serializers.RelatedField):
|
||||||
except users_models.User.DoesNotExist:
|
except users_models.User.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class CommentField(serializers.WritableField):
|
||||||
|
read_only = False
|
||||||
|
|
||||||
|
def field_from_native(self, data, files, field_name, into):
|
||||||
|
super().field_from_native(data, files, field_name, into)
|
||||||
|
into["comment_html"] = mdrender.render(self.context['project'], data.get("comment", ""))
|
||||||
|
|
||||||
|
|
||||||
class ProjectRelatedField(serializers.RelatedField):
|
class ProjectRelatedField(serializers.RelatedField):
|
||||||
read_only = False
|
read_only = False
|
||||||
|
|
||||||
|
@ -88,8 +96,99 @@ class ProjectRelatedField(serializers.RelatedField):
|
||||||
try:
|
try:
|
||||||
kwargs = {self.slug_field: data, "project": self.context['project']}
|
kwargs = {self.slug_field: data, "project": self.context['project']}
|
||||||
return self.queryset.get(**kwargs)
|
return self.queryset.get(**kwargs)
|
||||||
except self.parent.opts.model.DoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return None
|
raise ValidationError("{}=\"{}\" not found in this project".format(self.slug_field, data))
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryUserField(JsonField):
|
||||||
|
def to_native(self, obj):
|
||||||
|
if obj is None:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
user = users_models.User.objects.get(pk=obj['pk'])
|
||||||
|
except users_models.User.DoesNotExist:
|
||||||
|
user = None
|
||||||
|
return (UserRelatedField().to_native(user), obj['name'])
|
||||||
|
|
||||||
|
def from_native(self, data):
|
||||||
|
if data is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if len(data) < 2:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return {"pk": UserRelatedField().from_native(data[0]).pk, "name": data[1]}
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryValuesField(JsonField):
|
||||||
|
def to_native(self, obj):
|
||||||
|
if obj is None:
|
||||||
|
return []
|
||||||
|
if "users" in obj:
|
||||||
|
obj['users'] = map(HistoryUserField().to_native, obj['users'])
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def from_native(self, data):
|
||||||
|
if data is None:
|
||||||
|
return []
|
||||||
|
if "users" in data:
|
||||||
|
data['users'] = map(HistoryUserField().from_native, data['users'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryDiffField(JsonField):
|
||||||
|
def to_native(self, obj):
|
||||||
|
if obj is None:
|
||||||
|
return []
|
||||||
|
if "assigned_to" in obj:
|
||||||
|
obj['assigned_to'] = HistoryUserField().to_native(obj['assigned_to'])
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def from_native(self, data):
|
||||||
|
if data is None:
|
||||||
|
return []
|
||||||
|
if "assigned_to" in data:
|
||||||
|
data['assigned_to'] = HistoryUserField().from_native(data['assigned_to'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryExportSerializer(serializers.ModelSerializer):
|
||||||
|
user = HistoryUserField()
|
||||||
|
diff = HistoryDiffField(required=False)
|
||||||
|
snapshot = JsonField(required=False)
|
||||||
|
values = HistoryValuesField(required=False)
|
||||||
|
comment = CommentField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = history_models.HistoryEntry
|
||||||
|
exclude = ("id", "comment_html")
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryExportSerializerMixin(serializers.ModelSerializer):
|
||||||
|
history = serializers.SerializerMethodField("get_history")
|
||||||
|
|
||||||
|
def get_history(self, obj):
|
||||||
|
history_qs = history_service.get_history_queryset_by_model_instance(obj)
|
||||||
|
return HistoryExportSerializer(history_qs, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentExportSerializer(serializers.ModelSerializer):
|
||||||
|
owner = UserRelatedField(required=False)
|
||||||
|
attached_file = AttachedFileField()
|
||||||
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = attachments_models.Attachment
|
||||||
|
exclude = ('id', 'content_type', 'object_id', 'project')
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentExportSerializerMixin(serializers.ModelSerializer):
|
||||||
|
attachments = serializers.SerializerMethodField("get_attachments")
|
||||||
|
|
||||||
|
def get_attachments(self, obj):
|
||||||
|
content_type = ContentType.objects.get_for_model(obj.__class__)
|
||||||
|
attachments_qs = attachments_models.Attachment.objects.filter(object_id=obj.pk, content_type=content_type)
|
||||||
|
return AttachmentExportSerializer(attachments_qs, many=True).data
|
||||||
|
|
||||||
|
|
||||||
class PointsExportSerializer(serializers.ModelSerializer):
|
class PointsExportSerializer(serializers.ModelSerializer):
|
||||||
|
@ -97,36 +196,43 @@ class PointsExportSerializer(serializers.ModelSerializer):
|
||||||
model = projects_models.Points
|
model = projects_models.Points
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class UserStoryStatusExportSerializer(serializers.ModelSerializer):
|
class UserStoryStatusExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.UserStoryStatus
|
model = projects_models.UserStoryStatus
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class TaskStatusExportSerializer(serializers.ModelSerializer):
|
class TaskStatusExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.TaskStatus
|
model = projects_models.TaskStatus
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class IssueStatusExportSerializer(serializers.ModelSerializer):
|
class IssueStatusExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.IssueStatus
|
model = projects_models.IssueStatus
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class PriorityExportSerializer(serializers.ModelSerializer):
|
class PriorityExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.Priority
|
model = projects_models.Priority
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class SeverityExportSerializer(serializers.ModelSerializer):
|
class SeverityExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.Severity
|
model = projects_models.Severity
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class IssueTypeExportSerializer(serializers.ModelSerializer):
|
class IssueTypeExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.IssueType
|
model = projects_models.IssueType
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class RoleExportSerializer(serializers.ModelSerializer):
|
class RoleExportSerializer(serializers.ModelSerializer):
|
||||||
permissions = PgArrayField(required=False)
|
permissions = PgArrayField(required=False)
|
||||||
|
|
||||||
|
@ -134,9 +240,10 @@ class RoleExportSerializer(serializers.ModelSerializer):
|
||||||
model = users_models.Role
|
model = users_models.Role
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class MembershipExportSerializer(serializers.ModelSerializer):
|
class MembershipExportSerializer(serializers.ModelSerializer):
|
||||||
user = UserRelatedField(required=False)
|
user = UserRelatedField(required=False)
|
||||||
role = ProjectRelatedField(slug_field="slug")
|
role = ProjectRelatedField(slug_field="name")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = projects_models.Membership
|
model = projects_models.Membership
|
||||||
|
@ -147,120 +254,62 @@ class MembershipExportSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class RolePointsExportSerializer(serializers.ModelSerializer):
|
class RolePointsExportSerializer(serializers.ModelSerializer):
|
||||||
role = ProjectRelatedField(slug_field="slug")
|
role = ProjectRelatedField(slug_field="name")
|
||||||
points = ProjectRelatedField(slug_field="name")
|
points = ProjectRelatedField(slug_field="name")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = userstories_models.RolePoints
|
model = userstories_models.RolePoints
|
||||||
exclude = ('id', 'user_story')
|
exclude = ('id', 'user_story')
|
||||||
|
|
||||||
|
|
||||||
class MilestoneExportSerializer(serializers.ModelSerializer):
|
class MilestoneExportSerializer(serializers.ModelSerializer):
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
watchers = UserRelatedField(many=True, required=False)
|
watchers = UserRelatedField(many=True, required=False)
|
||||||
tasks_without_us = serializers.SerializerMethodField("get_tasks_without_us")
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = milestones_models.Milestone
|
model = milestones_models.Milestone
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
def get_tasks_without_us(self, obj):
|
|
||||||
queryset = tasks_models.Task.objects.filter(milestone=obj, user_story__isnull=True)
|
|
||||||
return TaskExportSerializer(queryset.order_by('ref'), many=True).data
|
|
||||||
|
|
||||||
class AttachmentExportSerializer(serializers.ModelSerializer):
|
class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||||
owner = UserRelatedField()
|
|
||||||
attached_file = AttachedFileField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = attachments_models.Attachment
|
|
||||||
exclude = ('id', 'content_type', 'object_id', 'project')
|
|
||||||
|
|
||||||
class AttachmentExportSerializerMixin(serializers.ModelSerializer):
|
|
||||||
attachments = serializers.SerializerMethodField("get_attachments")
|
|
||||||
|
|
||||||
def get_attachments(self, obj):
|
|
||||||
content_type = ContentType.objects.get_for_model(obj.__class__)
|
|
||||||
attachments_qs = attachments_models.Attachment.objects.filter(object_id=obj.pk, content_type=content_type)
|
|
||||||
return AttachmentExportSerializer(attachments_qs, many=True).data
|
|
||||||
|
|
||||||
class TaskExportSerializer(AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
status = ProjectRelatedField(slug_field="name", required=False)
|
status = ProjectRelatedField(slug_field="name")
|
||||||
milestone = ProjectRelatedField(slug_field="slug", required=False)
|
user_story = ProjectRelatedField(slug_field="ref", required=False)
|
||||||
|
milestone = ProjectRelatedField(slug_field="name", required=False)
|
||||||
assigned_to = UserRelatedField(required=False)
|
assigned_to = UserRelatedField(required=False)
|
||||||
watchers = UserRelatedField(many=True, required=False)
|
watchers = UserRelatedField(many=True, required=False)
|
||||||
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = tasks_models.Task
|
model = tasks_models.Task
|
||||||
exclude = ('id', 'project', 'user_story')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
class UserStoryExportSerializer(AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
|
||||||
|
class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||||
role_points = RolePointsExportSerializer(many=True, required=False)
|
role_points = RolePointsExportSerializer(many=True, required=False)
|
||||||
generated_from_issue = ProjectRelatedField(slug_field="ref", required=False)
|
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
status = ProjectRelatedField(slug_field="name", required=False)
|
status = ProjectRelatedField(slug_field="name")
|
||||||
tasks = TaskExportSerializer(many=True, required=False)
|
milestone = ProjectRelatedField(slug_field="name", required=False)
|
||||||
milestone = ProjectRelatedField(slug_field="slug", required=False)
|
|
||||||
watchers = UserRelatedField(many=True, required=False)
|
watchers = UserRelatedField(many=True, required=False)
|
||||||
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = userstories_models.UserStory
|
model = userstories_models.UserStory
|
||||||
exclude = ('id', 'project', 'points')
|
exclude = ('id', 'project', 'points', 'tasks')
|
||||||
|
|
||||||
def _convert_user(user_pk):
|
|
||||||
try:
|
|
||||||
user = users_models.User.objects.get(pk=user_pk)
|
|
||||||
except users_models.User.DoesNotExist:
|
|
||||||
return "#imported#{}".format(user_pk)
|
|
||||||
return user.email
|
|
||||||
|
|
||||||
def _convert_user_tuple(user_tuple):
|
|
||||||
return (_convert_user(user_tuple[0]), user_tuple[1])
|
|
||||||
|
|
||||||
class HistoryExportSerializer(serializers.ModelSerializer):
|
|
||||||
user = serializers.SerializerMethodField("get_user")
|
|
||||||
diff = serializers.SerializerMethodField("get_diff")
|
|
||||||
snapshot = JsonField()
|
|
||||||
values = serializers.SerializerMethodField("get_values")
|
|
||||||
|
|
||||||
def get_user(self, obj):
|
|
||||||
return (_convert_user(obj.user['pk']), obj.user['name'])
|
|
||||||
|
|
||||||
def get_values(self, obj):
|
|
||||||
for key, value in obj.values.items():
|
|
||||||
if key == "users":
|
|
||||||
obj.values["users"] = dict(map(_convert_user_tuple, value.items()))
|
|
||||||
|
|
||||||
return obj.values
|
|
||||||
|
|
||||||
def get_diff(self, obj):
|
|
||||||
for key, value in obj.diff.items():
|
|
||||||
if key == "assigned_to":
|
|
||||||
obj.diff["assigned_to"] = map(_convert_user, value)
|
|
||||||
|
|
||||||
return obj.diff
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = history_models.HistoryEntry
|
|
||||||
|
|
||||||
class HistoryExportSerializerMixin(serializers.ModelSerializer):
|
|
||||||
history = serializers.SerializerMethodField("get_history")
|
|
||||||
|
|
||||||
def get_history(self, obj):
|
|
||||||
history_qs = history_service.get_history_queryset_by_model_instance(obj)
|
|
||||||
return HistoryExportSerializer(history_qs, many=True).data
|
|
||||||
|
|
||||||
|
|
||||||
class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
status = ProjectRelatedField(slug_field="name", required=False)
|
status = ProjectRelatedField(slug_field="name")
|
||||||
assigned_to = UserRelatedField(required=False)
|
assigned_to = UserRelatedField(required=False)
|
||||||
priority = ProjectRelatedField(slug_field="name", required=False)
|
priority = ProjectRelatedField(slug_field="name")
|
||||||
severity = ProjectRelatedField(slug_field="name", required=False)
|
severity = ProjectRelatedField(slug_field="name")
|
||||||
type = ProjectRelatedField(slug_field="name", required=False)
|
type = ProjectRelatedField(slug_field="name")
|
||||||
milestone = ProjectRelatedField(slug_field="slug", required=False)
|
milestone = ProjectRelatedField(slug_field="name", required=False)
|
||||||
watchers = UserRelatedField(many=True, required=False)
|
watchers = UserRelatedField(many=True, required=False)
|
||||||
votes = serializers.SerializerMethodField("get_votes")
|
votes = serializers.SerializerMethodField("get_votes")
|
||||||
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
||||||
def get_votes(self, obj):
|
def get_votes(self, obj):
|
||||||
return [x.email for x in votes_service.get_voters(obj)]
|
return [x.email for x in votes_service.get_voters(obj)]
|
||||||
|
@ -269,20 +318,24 @@ class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerial
|
||||||
model = issues_models.Issue
|
model = issues_models.Issue
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
class WikiPageExportSerializer(AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
|
||||||
|
class WikiPageExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
last_modifier = UserRelatedField(required=False)
|
last_modifier = UserRelatedField(required=False)
|
||||||
watchers = UserRelatedField(many=True, required=False)
|
watchers = UserRelatedField(many=True, required=False)
|
||||||
|
modified_date = serializers.DateTimeField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = wiki_models.WikiPage
|
model = wiki_models.WikiPage
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class WikiLinkExportSerializer(serializers.ModelSerializer):
|
class WikiLinkExportSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = wiki_models.WikiLink
|
model = wiki_models.WikiLink
|
||||||
exclude = ('id', 'project')
|
exclude = ('id', 'project')
|
||||||
|
|
||||||
|
|
||||||
class ProjectExportSerializer(serializers.ModelSerializer):
|
class ProjectExportSerializer(serializers.ModelSerializer):
|
||||||
owner = UserRelatedField(required=False)
|
owner = UserRelatedField(required=False)
|
||||||
default_points = serializers.SlugRelatedField(slug_field="name", required=False)
|
default_points = serializers.SlugRelatedField(slug_field="name", required=False)
|
||||||
|
@ -305,6 +358,7 @@ class ProjectExportSerializer(serializers.ModelSerializer):
|
||||||
wiki_pages = WikiPageExportSerializer(many=True, required=False)
|
wiki_pages = WikiPageExportSerializer(many=True, required=False)
|
||||||
wiki_links = WikiLinkExportSerializer(many=True, required=False)
|
wiki_links = WikiLinkExportSerializer(many=True, required=False)
|
||||||
user_stories = UserStoryExportSerializer(many=True, required=False)
|
user_stories = UserStoryExportSerializer(many=True, required=False)
|
||||||
|
tasks = TaskExportSerializer(many=True, required=False)
|
||||||
issues = IssueExportSerializer(many=True, required=False)
|
issues = IssueExportSerializer(many=True, required=False)
|
||||||
tags_colors = JsonField(required=False)
|
tags_colors = JsonField(required=False)
|
||||||
anon_permissions = PgArrayField(required=False)
|
anon_permissions = PgArrayField(required=False)
|
||||||
|
|
|
@ -14,28 +14,37 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models import signals
|
import uuid
|
||||||
from django.db import transaction
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.history.services import make_key_from_model_object
|
||||||
|
from taiga.projects.references import sequences as seq
|
||||||
|
from taiga.projects.references import models as refs
|
||||||
|
from taiga.projects.services import find_invited_user
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
_errors_log = []
|
_errors_log = {}
|
||||||
|
|
||||||
def get_errors():
|
|
||||||
|
def get_errors(clear=True):
|
||||||
_errors = _errors_log.copy()
|
_errors = _errors_log.copy()
|
||||||
|
if clear:
|
||||||
_errors_log.clear()
|
_errors_log.clear()
|
||||||
return _errors
|
return _errors
|
||||||
|
|
||||||
def add_errors(errors):
|
|
||||||
_errors_log.append(errors)
|
def add_errors(section, errors):
|
||||||
|
if section in _errors_log:
|
||||||
|
_errors_log[section].append(errors)
|
||||||
|
else:
|
||||||
|
_errors_log[section] = [errors]
|
||||||
|
|
||||||
def project_to_dict(project):
|
def project_to_dict(project):
|
||||||
return serializers.ProjectExportSerializer(project).data
|
return serializers.ProjectExportSerializer(project).data
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_project(data):
|
def store_project(data):
|
||||||
project_data = {}
|
project_data = {}
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
|
@ -45,7 +54,7 @@ def store_project(data):
|
||||||
"default_issue_type", "memberships", "points", "us_statuses",
|
"default_issue_type", "memberships", "points", "us_statuses",
|
||||||
"task_statuses", "issue_statuses", "priorities", "severities",
|
"task_statuses", "issue_statuses", "priorities", "severities",
|
||||||
"issue_types", "roles", "milestones", "wiki_pages",
|
"issue_types", "roles", "milestones", "wiki_pages",
|
||||||
"wiki_links", "notify_policies", "user_stories", "issues"
|
"wiki_links", "notify_policies", "user_stories", "issues", "tasks",
|
||||||
]
|
]
|
||||||
if key not in excluded_fields:
|
if key not in excluded_fields:
|
||||||
project_data[key] = value
|
project_data[key] = value
|
||||||
|
@ -55,174 +64,288 @@ def store_project(data):
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
serialized.object.save()
|
serialized.object.save()
|
||||||
return serialized
|
return serialized
|
||||||
else:
|
add_errors("project", serialized.errors)
|
||||||
add_errors(serialized.errors)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_choices(project, data, field, relation, serializer, default_field):
|
|
||||||
relation.all().delete()
|
|
||||||
|
|
||||||
for point in data[field]:
|
def store_choice(project, data, field, serializer):
|
||||||
serialized = serializer(data=point)
|
serialized = serializer(data=data)
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
return serialized.object
|
||||||
|
add_errors(field, serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_choices(project, data, field, serializer):
|
||||||
|
result = []
|
||||||
|
for choice_data in data[field]:
|
||||||
|
result.append(store_choice(project, choice_data, field, serializer))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def store_role(project, role):
|
||||||
|
serialized = serializers.RoleExportSerializer(data=role)
|
||||||
|
if serialized.is_valid():
|
||||||
|
serialized.object.project = project
|
||||||
|
serialized.object._importing = True
|
||||||
|
serialized.save()
|
||||||
|
return serialized
|
||||||
|
add_errors("roles", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_roles(project, data):
|
||||||
|
results = []
|
||||||
|
for role in data['roles']:
|
||||||
|
results.append(store_role(project, role))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_default_choices(project, data):
|
def store_default_choices(project, data):
|
||||||
project.default_points = project.points.all().get(name=data['default_points'])
|
def helper(project, field, related, data):
|
||||||
project.default_issue_type = project.issue_types.get(name=data['default_issue_type'])
|
if field in data:
|
||||||
project.default_issue_status = project.issue_statuses.get(name=data['default_issue_status'])
|
value = related.all().get(name=data[field])
|
||||||
project.default_us_status = project.us_statuses.get(name=data['default_us_status'])
|
else:
|
||||||
project.default_task_status = project.task_statuses.get(name=data['default_task_status'])
|
value = related.all().first()
|
||||||
project.default_priority = project.priorities.get(name=data['default_priority'])
|
setattr(project, field, value)
|
||||||
project.default_severity = project.severities.get(name=data['default_severity'])
|
|
||||||
|
helper(project, "default_points", project.points, data)
|
||||||
|
helper(project, "default_issue_type", project.issue_types, data)
|
||||||
|
helper(project, "default_issue_status", project.issue_statuses, data)
|
||||||
|
helper(project, "default_us_status", project.us_statuses, data)
|
||||||
|
helper(project, "default_task_status", project.task_statuses, data)
|
||||||
|
helper(project, "default_priority", project.priorities, data)
|
||||||
|
helper(project, "default_severity", project.severities, data)
|
||||||
project._importing = True
|
project._importing = True
|
||||||
project.save()
|
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_membership(project, membership):
|
||||||
def store_memberships(project, data):
|
|
||||||
for membership in data['memberships']:
|
|
||||||
serialized = serializers.MembershipExportSerializer(data=membership, context={"project": project})
|
serialized = serializers.MembershipExportSerializer(data=membership, context={"project": project})
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
|
if not serialized.object.token:
|
||||||
|
serialized.object.token = str(uuid.uuid1())
|
||||||
|
serialized.object.user = find_invited_user(serialized.object, default=serialized.object.user)
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
add_errors("memberships", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_memberships(project, data):
|
||||||
|
results = []
|
||||||
|
for membership in data.get('memberships', []):
|
||||||
|
results.append(store_membership(project, membership))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def store_task(project, task):
|
||||||
|
if 'status' not in task and project.default_task_status:
|
||||||
|
task['status'] = project.default_task_status.name
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_task(project, us, task):
|
|
||||||
serialized = serializers.TaskExportSerializer(data=task, context={"project": project})
|
serialized = serializers.TaskExportSerializer(data=task, context={"project": project})
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.user_story = us
|
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
|
if serialized.object.owner is None:
|
||||||
|
serialized.object.owner = serialized.object.project.owner
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
|
serialized.object._not_notify = True
|
||||||
|
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
|
||||||
for task_attachment in task['attachments']:
|
if serialized.object.ref:
|
||||||
|
sequence_name = refs.make_sequence_name(project)
|
||||||
|
if not seq.exists(sequence_name):
|
||||||
|
seq.create(sequence_name)
|
||||||
|
seq.set_max(sequence_name, serialized.object.ref)
|
||||||
|
else:
|
||||||
|
serialized.object.ref, _ = refs.make_reference(serialized.object, project)
|
||||||
|
serialized.object.save()
|
||||||
|
|
||||||
|
for task_attachment in task.get('attachments', []):
|
||||||
store_attachment(project, serialized.object, task_attachment)
|
store_attachment(project, serialized.object, task_attachment)
|
||||||
|
|
||||||
@transaction.atomic
|
for history in task.get('history', []):
|
||||||
def store_milestones(project, data):
|
store_history(project, serialized.object, history)
|
||||||
for milestone in data['milestones']:
|
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
add_errors("tasks", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_milestone(project, milestone):
|
||||||
serialized = serializers.MilestoneExportSerializer(data=milestone)
|
serialized = serializers.MilestoneExportSerializer(data=milestone)
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
|
||||||
for task_without_us in milestone['tasks_without_us']:
|
for task_without_us in milestone.get('tasks_without_us', []):
|
||||||
store_task(project, None, task_without_us)
|
task_without_us['user_story'] = None
|
||||||
|
store_task(project, task_without_us)
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
add_errors("milestones", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def store_attachment(project, obj, attachment):
|
def store_attachment(project, obj, attachment):
|
||||||
serialized = serializers.AttachmentExportSerializer(data=attachment)
|
serialized = serializers.AttachmentExportSerializer(data=attachment)
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.content_type = ContentType.objects.get_for_model(obj.__class__)
|
serialized.object.content_type = ContentType.objects.get_for_model(obj.__class__)
|
||||||
serialized.object.object_id = obj.id
|
serialized.object.object_id = obj.id
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
|
if serialized.object.owner is None:
|
||||||
|
serialized.object.owner = serialized.object.project.owner
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
return serialized
|
||||||
|
add_errors("attachments", serialized.errors)
|
||||||
|
return serialized
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_wiki_pages(project, data):
|
def store_history(project, obj, history):
|
||||||
for wiki_page in data['wiki_pages']:
|
serialized = serializers.HistoryExportSerializer(data=history, context={"project": project})
|
||||||
|
if serialized.is_valid():
|
||||||
|
serialized.object.key = make_key_from_model_object(obj)
|
||||||
|
if serialized.object.diff is None:
|
||||||
|
serialized.object.diff = []
|
||||||
|
serialized.object._importing = True
|
||||||
|
serialized.save()
|
||||||
|
return serialized
|
||||||
|
add_errors("history", serialized.errors)
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
|
||||||
|
def store_wiki_page(project, wiki_page):
|
||||||
serialized = serializers.WikiPageExportSerializer(data=wiki_page)
|
serialized = serializers.WikiPageExportSerializer(data=wiki_page)
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
|
if serialized.object.owner is None:
|
||||||
|
serialized.object.owner = serialized.object.project.owner
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
|
serialized.object._not_notify = True
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
|
||||||
for attachment in wiki_page['attachments']:
|
for attachment in wiki_page.get('attachments', []):
|
||||||
store_attachment(project, serialized.object, attachment)
|
store_attachment(project, serialized.object, attachment)
|
||||||
|
|
||||||
@transaction.atomic
|
for history in wiki_page.get('history', []):
|
||||||
def store_wiki_links(project, data):
|
store_history(project, serialized.object, history)
|
||||||
for wiki_link in data['wiki_links']:
|
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
add_errors("wiki_pages", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_wiki_link(project, wiki_link):
|
||||||
serialized = serializers.WikiLinkExportSerializer(data=wiki_link)
|
serialized = serializers.WikiLinkExportSerializer(data=wiki_link)
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
return serialized
|
||||||
|
|
||||||
|
add_errors("wiki_links", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_role_point(project, us, role_point):
|
def store_role_point(project, us, role_point):
|
||||||
serialized = serializers.RolePointsExportSerializer(data=role_point, context={"project": project})
|
serialized = serializers.RolePointsExportSerializer(data=role_point, context={"project": project})
|
||||||
serialized.is_valid()
|
if serialized.is_valid():
|
||||||
serialized.object.user_story = us
|
serialized.object.user_story = us
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
return serialized.object
|
||||||
|
add_errors("role_points", serialized.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_user_story(project, userstory):
|
||||||
|
if 'status' not in userstory and project.default_us_status:
|
||||||
|
userstory['status'] = project.default_us_status.name
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def store_user_stories(project, data):
|
|
||||||
for userstory in data['user_stories']:
|
|
||||||
userstory_data = {}
|
userstory_data = {}
|
||||||
for key, value in userstory.items():
|
for key, value in userstory.items():
|
||||||
excluded_fields = [
|
if key != 'role_points':
|
||||||
'tasks', 'role_points'
|
|
||||||
]
|
|
||||||
if key not in excluded_fields:
|
|
||||||
userstory_data[key] = value
|
userstory_data[key] = value
|
||||||
serialized_us = serializers.UserStoryExportSerializer(data=userstory_data, context={"project": project})
|
serialized_us = serializers.UserStoryExportSerializer(data=userstory_data, context={"project": project})
|
||||||
serialized_us.is_valid()
|
if serialized_us.is_valid():
|
||||||
serialized_us.object.project = project
|
serialized_us.object.project = project
|
||||||
|
if serialized_us.object.owner is None:
|
||||||
|
serialized_us.object.owner = serialized_us.object.project.owner
|
||||||
serialized_us.object._importing = True
|
serialized_us.object._importing = True
|
||||||
|
serialized_us.object._not_notify = True
|
||||||
|
|
||||||
serialized_us.save()
|
serialized_us.save()
|
||||||
|
|
||||||
for task in userstory['tasks']:
|
if serialized_us.object.ref:
|
||||||
store_task(project, serialized_us.object, task)
|
sequence_name = refs.make_sequence_name(project)
|
||||||
|
if not seq.exists(sequence_name):
|
||||||
|
seq.create(sequence_name)
|
||||||
|
seq.set_max(sequence_name, serialized_us.object.ref)
|
||||||
|
else:
|
||||||
|
serialized_us.object.ref, _ = refs.make_reference(serialized_us.object, project)
|
||||||
|
serialized_us.object.save()
|
||||||
|
|
||||||
for us_attachment in userstory['attachments']:
|
for us_attachment in userstory.get('attachments', []):
|
||||||
store_attachment(project, serialized_us.object, us_attachment)
|
store_attachment(project, serialized_us.object, us_attachment)
|
||||||
|
|
||||||
for role_point in userstory['role_points']:
|
for role_point in userstory.get('role_points', []):
|
||||||
store_role_point(project, serialized_us.object, role_point)
|
store_role_point(project, serialized_us.object, role_point)
|
||||||
|
|
||||||
@transaction.atomic
|
for history in userstory.get('history', []):
|
||||||
def store_issues(project, data):
|
store_history(project, serialized_us.object, history)
|
||||||
for issue in data['issues']:
|
|
||||||
serialized = serializers.IssueExportSerializer(data=issue, context={"project": project})
|
return serialized_us
|
||||||
serialized.is_valid()
|
add_errors("user_stories", serialized_us.errors)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_issue(project, data):
|
||||||
|
serialized = serializers.IssueExportSerializer(data=data, context={"project": project})
|
||||||
|
|
||||||
|
if 'type' not in data and project.default_issue_type:
|
||||||
|
data['type'] = project.default_issue_type.name
|
||||||
|
|
||||||
|
if 'status' not in data and project.default_issue_status:
|
||||||
|
data['status'] = project.default_issue_status.name
|
||||||
|
|
||||||
|
if 'priority' not in data and project.default_priority:
|
||||||
|
data['priority'] = project.default_priority.name
|
||||||
|
|
||||||
|
if 'severity' not in data and project.default_severity:
|
||||||
|
data['severity'] = project.default_severity.name
|
||||||
|
|
||||||
|
if serialized.is_valid():
|
||||||
serialized.object.project = project
|
serialized.object.project = project
|
||||||
|
if serialized.object.owner is None:
|
||||||
|
serialized.object.owner = serialized.object.project.owner
|
||||||
serialized.object._importing = True
|
serialized.object._importing = True
|
||||||
|
serialized.object._not_notify = True
|
||||||
|
|
||||||
serialized.save()
|
serialized.save()
|
||||||
|
|
||||||
for attachment in issue['attachments']:
|
if serialized.object.ref:
|
||||||
|
sequence_name = refs.make_sequence_name(project)
|
||||||
|
if not seq.exists(sequence_name):
|
||||||
|
seq.create(sequence_name)
|
||||||
|
seq.set_max(sequence_name, serialized.object.ref)
|
||||||
|
else:
|
||||||
|
serialized.object.ref, _ = refs.make_reference(serialized.object, project)
|
||||||
|
serialized.object.save()
|
||||||
|
|
||||||
|
for attachment in data.get('attachments', []):
|
||||||
store_attachment(project, serialized.object, attachment)
|
store_attachment(project, serialized.object, attachment)
|
||||||
|
for history in data.get('history', []):
|
||||||
|
store_history(project, serialized.object, history)
|
||||||
def dict_to_project(data, owner=None):
|
return serialized
|
||||||
signals.pre_save.receivers = []
|
add_errors("issues", serialized.errors)
|
||||||
signals.post_save.receivers = []
|
return None
|
||||||
signals.pre_delete.receivers = []
|
|
||||||
signals.post_delete.receivers = []
|
|
||||||
|
|
||||||
if owner:
|
|
||||||
data['owner'] = owner
|
|
||||||
|
|
||||||
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_serialized.object, data)
|
|
||||||
store_issues(project_serialized.object, data)
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ def get_attachment_file_path(instance, filename):
|
||||||
|
|
||||||
|
|
||||||
class Attachment(models.Model):
|
class Attachment(models.Model):
|
||||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=False,
|
||||||
related_name="change_attachments",
|
related_name="change_attachments",
|
||||||
verbose_name=_("owner"))
|
verbose_name=_("owner"))
|
||||||
project = models.ForeignKey("projects.Project", null=False, blank=False,
|
project = models.ForeignKey("projects.Project", null=False, blank=False,
|
||||||
|
|
|
@ -76,6 +76,18 @@ class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.
|
||||||
if not self._importing or not self.modified_date:
|
if not self._importing or not self.modified_date:
|
||||||
self.modified_date = timezone.now()
|
self.modified_date = timezone.now()
|
||||||
|
|
||||||
|
if not self.status:
|
||||||
|
self.status = self.project.default_issue_status
|
||||||
|
|
||||||
|
if not self.type:
|
||||||
|
self.type = self.project.default_issue_type
|
||||||
|
|
||||||
|
if not self.severity:
|
||||||
|
self.severity = self.project.default_severity
|
||||||
|
|
||||||
|
if not self.priority:
|
||||||
|
self.priority = self.project.default_priority
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -764,6 +764,9 @@ def project_post_save(sender, instance, created, **kwargs):
|
||||||
if not created:
|
if not created:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if instance._importing:
|
||||||
|
return
|
||||||
|
|
||||||
template = getattr(instance, "creation_template", None)
|
template = getattr(instance, "creation_template", None)
|
||||||
if template is None:
|
if template is None:
|
||||||
template = ProjectTemplate.objects.get(slug=settings.DEFAULT_PROJECT_TEMPLATE)
|
template = ProjectTemplate.objects.get(slug=settings.DEFAULT_PROJECT_TEMPLATE)
|
||||||
|
|
|
@ -37,6 +37,8 @@ class WatchedResourceMixin(object):
|
||||||
after it on inheritance definition.
|
after it on inheritance definition.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_not_notify = False
|
||||||
|
|
||||||
def send_notifications(self, obj, history=None):
|
def send_notifications(self, obj, history=None):
|
||||||
"""
|
"""
|
||||||
Shortcut method for resources with special save
|
Shortcut method for resources with special save
|
||||||
|
@ -50,6 +52,9 @@ class WatchedResourceMixin(object):
|
||||||
if not history:
|
if not history:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self._not_notify:
|
||||||
|
return
|
||||||
|
|
||||||
obj = self.get_object_for_snapshot(obj)
|
obj = self.get_object_for_snapshot(obj)
|
||||||
|
|
||||||
# Process that analizes the corresponding diff and
|
# Process that analizes the corresponding diff and
|
||||||
|
|
|
@ -57,4 +57,9 @@ def next_value(seqname):
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
|
def set_max(seqname, new_value):
|
||||||
|
sql = "SELECT setval(%s, GREATEST(nextval(%s), %s));"
|
||||||
|
with closing(connection.cursor()) as cursor:
|
||||||
|
cursor.execute(sql, [seqname, seqname, new_value])
|
||||||
|
result = cursor.fetchone()
|
||||||
|
return result[0]
|
||||||
|
|
|
@ -77,6 +77,9 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.M
|
||||||
if not self._importing or not self.modified_date:
|
if not self._importing or not self.modified_date:
|
||||||
self.modified_date = timezone.now()
|
self.modified_date = timezone.now()
|
||||||
|
|
||||||
|
if not self.status:
|
||||||
|
self.status = self.project.default_task_status
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -112,6 +112,9 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod
|
||||||
if not self._importing or not self.modified_date:
|
if not self._importing or not self.modified_date:
|
||||||
self.modified_date = timezone.now()
|
self.modified_date = timezone.now()
|
||||||
|
|
||||||
|
if not self.status:
|
||||||
|
self.status = self.project.default_us_status
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -52,3 +52,5 @@ def mediafiles_urlpatterns():
|
||||||
|
|
||||||
urlpatterns += staticfiles_urlpatterns(prefix="/static/")
|
urlpatterns += staticfiles_urlpatterns(prefix="/static/")
|
||||||
urlpatterns += mediafiles_urlpatterns()
|
urlpatterns += mediafiles_urlpatterns()
|
||||||
|
|
||||||
|
handler500 = "taiga.base.api.views.api_server_error"
|
||||||
|
|
|
@ -64,6 +64,9 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
queryset = models.User.objects.all()
|
queryset = models.User.objects.all()
|
||||||
filter_backends = (MembersFilterBackend,)
|
filter_backends = (MembersFilterBackend,)
|
||||||
|
|
||||||
|
def create(self, *args, **kwargs):
|
||||||
|
raise exc.NotSupported()
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
def pre_conditions_on_save(self, obj):
|
||||||
if self.request.user.is_superuser:
|
if self.request.user.is_superuser:
|
||||||
return
|
return
|
||||||
|
|
|
@ -28,7 +28,6 @@ class UserPermission(TaigaResourcePermission):
|
||||||
enought_perms = IsSuperUser()
|
enought_perms = IsSuperUser()
|
||||||
global_perms = None
|
global_perms = None
|
||||||
retrieve_perms = AllowAny()
|
retrieve_perms = AllowAny()
|
||||||
create_perms = AllowAny()
|
|
||||||
update_perms = IsTheSameUser()
|
update_perms = IsTheSameUser()
|
||||||
destroy_perms = IsTheSameUser()
|
destroy_perms = IsTheSameUser()
|
||||||
list_perms = AllowAny()
|
list_perms = AllowAny()
|
||||||
|
|
|
@ -70,7 +70,7 @@ class ProjectFactory(Factory):
|
||||||
class RoleFactory(Factory):
|
class RoleFactory(Factory):
|
||||||
FACTORY_FOR = get_model("users", "Role")
|
FACTORY_FOR = get_model("users", "Role")
|
||||||
|
|
||||||
name = "Tester"
|
name = factory.Sequence(lambda n: "Role {}".format(n))
|
||||||
slug = factory.Sequence(lambda n: "test-role-{}".format(n))
|
slug = factory.Sequence(lambda n: "test-role-{}".format(n))
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class IssueAttachmentFactory(AttachmentFactory):
|
||||||
|
|
||||||
|
|
||||||
class WikiAttachmentFactory(AttachmentFactory):
|
class WikiAttachmentFactory(AttachmentFactory):
|
||||||
content_object = factory.SubFactory("tests.factories.WikiFactory")
|
content_object = factory.SubFactory("tests.factories.WikiPageFactory")
|
||||||
|
|
||||||
|
|
||||||
class RolePointsFactory(Factory):
|
class RolePointsFactory(Factory):
|
||||||
|
|
|
@ -119,7 +119,7 @@ def test_user_create(client, data):
|
||||||
"full_name": "test",
|
"full_name": "test",
|
||||||
})
|
})
|
||||||
results = helper_test_http_method(client, 'post', url, create_data, users)
|
results = helper_test_http_method(client, 'post', url, create_data, users)
|
||||||
assert results == [201]
|
assert results == [405]
|
||||||
|
|
||||||
|
|
||||||
def test_user_patch(client, data):
|
def test_user_patch(client, data):
|
||||||
|
|
|
@ -0,0 +1,639 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 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/>.
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from .. import factories as f
|
||||||
|
|
||||||
|
from taiga.projects.models import Project
|
||||||
|
from taiga.projects.issues.models import Issue
|
||||||
|
from taiga.projects.userstories.models import UserStory
|
||||||
|
from taiga.projects.tasks.models import Task
|
||||||
|
from taiga.projects.wiki.models import WikiPage
|
||||||
|
|
||||||
|
from taiga.export_import.service import project_to_dict
|
||||||
|
from taiga.export_import.dump_service import dict_to_project
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_project_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-list")
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_project_import_without_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-list")
|
||||||
|
data = {
|
||||||
|
"name": "Imported project",
|
||||||
|
"description": "Imported project",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
must_empty_children = [
|
||||||
|
"issues", "user_stories", "roles", "us_statuses", "wiki_pages", "priorities",
|
||||||
|
"severities", "milestones", "points", "issue_types", "task_statuses",
|
||||||
|
"memberships", "issue_statuses", "wiki_links",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert all(map(lambda x: len(response_data[x]) == 0, must_empty_children))
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
|
||||||
|
def test_valid_project_import_with_not_existing_memberships(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-list")
|
||||||
|
data = {
|
||||||
|
"name": "Imported project",
|
||||||
|
"description": "Imported project",
|
||||||
|
"memberships": [{
|
||||||
|
"email": "bad@email.com",
|
||||||
|
"role": "Role",
|
||||||
|
}],
|
||||||
|
"roles": [{"name": "Role"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
# The new membership and the owner membership
|
||||||
|
assert len(response_data["memberships"]) == 2
|
||||||
|
|
||||||
|
def test_valid_project_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-list")
|
||||||
|
data = {
|
||||||
|
"name": "Imported project",
|
||||||
|
"description": "Imported project",
|
||||||
|
"roles": [{
|
||||||
|
"permissions": [],
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"us_statuses": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"severities": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"priorities": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"points": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"issue_types": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"task_statuses": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
"issue_statuses": [{
|
||||||
|
"name": "Test"
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
must_empty_children = [
|
||||||
|
"issues", "user_stories", "wiki_pages", "milestones",
|
||||||
|
"wiki_links",
|
||||||
|
]
|
||||||
|
|
||||||
|
must_one_instance_children = [
|
||||||
|
"roles", "us_statuses", "severities", "priorities", "points",
|
||||||
|
"issue_types", "task_statuses", "issue_statuses", "memberships",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert all(map(lambda x: len(response_data[x]) == 0, must_empty_children))
|
||||||
|
# Allwais is created at least the owner membership
|
||||||
|
assert all(map(lambda x: len(response_data[x]) == 1, must_one_instance_children))
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
|
||||||
|
def test_invalid_project_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-list")
|
||||||
|
data = {
|
||||||
|
"name": "Imported project",
|
||||||
|
"description": "Imported project",
|
||||||
|
"roles": [{ }],
|
||||||
|
"us_statuses": [{ }],
|
||||||
|
"severities": [{ }],
|
||||||
|
"priorities": [{ }],
|
||||||
|
"points": [{ }],
|
||||||
|
"issue_types": [{ }],
|
||||||
|
"task_statuses": [{ }],
|
||||||
|
"issue_statuses": [{ }],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 8
|
||||||
|
assert Project.objects.filter(slug="imported-project").count() == 0
|
||||||
|
|
||||||
|
def test_invalid_issue_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_issue_import_without_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_issue_type = f.IssueTypeFactory.create(project=project)
|
||||||
|
project.default_issue_status = f.IssueStatusFactory.create(project=project)
|
||||||
|
project.default_severity = f.SeverityFactory.create(project=project)
|
||||||
|
project.default_priority = f.PriorityFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Test"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
assert response_data["ref"] is not None
|
||||||
|
|
||||||
|
def test_valid_issue_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_issue_type = f.IssueTypeFactory.create(project=project)
|
||||||
|
project.default_issue_status = f.IssueStatusFactory.create(project=project)
|
||||||
|
project.default_severity = f.SeverityFactory.create(project=project)
|
||||||
|
project.default_priority = f.PriorityFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported issue",
|
||||||
|
"description": "Imported issue",
|
||||||
|
"attachments": [{
|
||||||
|
"owner": user.email,
|
||||||
|
"attached_file": {
|
||||||
|
"name": "imported attachment",
|
||||||
|
"data": base64.b64encode(b"TEST").decode("utf-8")
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data["attachments"]) == 1
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
assert response_data["ref"] is not None
|
||||||
|
|
||||||
|
def test_invalid_issue_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_issue_type = f.IssueTypeFactory.create(project=project)
|
||||||
|
project.default_issue_status = f.IssueStatusFactory.create(project=project)
|
||||||
|
project.default_severity = f.SeverityFactory.create(project=project)
|
||||||
|
project.default_priority = f.PriorityFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported issue",
|
||||||
|
"description": "Imported issue",
|
||||||
|
"attachments": [{ }],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
assert Issue.objects.filter(subject="Imported issue").count() == 0
|
||||||
|
|
||||||
|
def test_invalid_issue_import_with_bad_choices(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_issue_type = f.IssueTypeFactory.create(project=project)
|
||||||
|
project.default_issue_status = f.IssueStatusFactory.create(project=project)
|
||||||
|
project.default_severity = f.SeverityFactory.create(project=project)
|
||||||
|
project.default_priority = f.PriorityFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported issue",
|
||||||
|
"description": "Imported issue",
|
||||||
|
"status": "Not valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported issue",
|
||||||
|
"description": "Imported issue",
|
||||||
|
"priority": "Not valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported issue",
|
||||||
|
"description": "Imported issue",
|
||||||
|
"severity": "Not valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
|
||||||
|
url = reverse("importer-issue", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported issue",
|
||||||
|
"description": "Imported issue",
|
||||||
|
"type": "Not valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
|
||||||
|
def test_invalid_us_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-us", args=[project.pk])
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_us_import_without_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_us_status = f.UserStoryStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-us", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Test"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
assert response_data["ref"] is not None
|
||||||
|
|
||||||
|
def test_valid_us_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_us_status = f.UserStoryStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-us", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported us",
|
||||||
|
"description": "Imported us",
|
||||||
|
"attachments": [{
|
||||||
|
"owner": user.email,
|
||||||
|
"attached_file": {
|
||||||
|
"name": "imported attachment",
|
||||||
|
"data": base64.b64encode(b"TEST").decode("utf-8")
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data["attachments"]) == 1
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
assert response_data["ref"] is not None
|
||||||
|
|
||||||
|
def test_invalid_us_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_us_status = f.UserStoryStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-us", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported us",
|
||||||
|
"description": "Imported us",
|
||||||
|
"attachments": [{ }],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
assert UserStory.objects.filter(subject="Imported us").count() == 0
|
||||||
|
|
||||||
|
def test_invalid_us_import_with_bad_choices(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_us_status = f.UserStoryStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-us", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported us",
|
||||||
|
"description": "Imported us",
|
||||||
|
"status": "Not valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
|
||||||
|
def test_invalid_task_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-task", args=[project.pk])
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_task_import_without_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_task_status = f.TaskStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-task", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Test"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
assert response_data["ref"] is not None
|
||||||
|
|
||||||
|
def test_valid_task_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_task_status = f.TaskStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-task", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported task",
|
||||||
|
"description": "Imported task",
|
||||||
|
"attachments": [{
|
||||||
|
"owner": user.email,
|
||||||
|
"attached_file": {
|
||||||
|
"name": "imported attachment",
|
||||||
|
"data": base64.b64encode(b"TEST").decode("utf-8")
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data["attachments"]) == 1
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
assert response_data["ref"] is not None
|
||||||
|
|
||||||
|
def test_invalid_task_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_task_status = f.TaskStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-task", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported task",
|
||||||
|
"description": "Imported task",
|
||||||
|
"attachments": [{ }],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
assert Task.objects.filter(subject="Imported task").count() == 0
|
||||||
|
|
||||||
|
def test_invalid_task_import_with_bad_choices(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_task_status = f.TaskStatusFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-task", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported task",
|
||||||
|
"description": "Imported task",
|
||||||
|
"status": "Not valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
|
||||||
|
def test_valid_task_with_user_story(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
project.default_task_status = f.TaskStatusFactory.create(project=project)
|
||||||
|
us = f.UserStoryFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-task", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"subject": "Imported task",
|
||||||
|
"description": "Imported task",
|
||||||
|
"user_story": us.ref
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert us.tasks.all().count() == 1
|
||||||
|
|
||||||
|
def test_invalid_wiki_page_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-wiki-page", args=[project.pk])
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_wiki_page_import_without_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-wiki-page", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"slug": "imported-wiki-page",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
|
||||||
|
def test_valid_wiki_page_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-wiki-page", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"slug": "imported-wiki-page",
|
||||||
|
"content": "Imported wiki_page",
|
||||||
|
"attachments": [{
|
||||||
|
"owner": user.email,
|
||||||
|
"attached_file": {
|
||||||
|
"name": "imported attachment",
|
||||||
|
"data": base64.b64encode(b"TEST").decode("utf-8")
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data["attachments"]) == 1
|
||||||
|
assert response_data["owner"] == user.email
|
||||||
|
|
||||||
|
def test_invalid_wiki_page_import_with_extra_data(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-wiki-page", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"slug": "imported-wiki-page",
|
||||||
|
"content": "Imported wiki_page",
|
||||||
|
"attachments": [{ }],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
assert len(response_data) == 1
|
||||||
|
assert WikiPage.objects.filter(slug="imported-wiki-page").count() == 0
|
||||||
|
|
||||||
|
def test_invalid_wiki_link_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-wiki-link", args=[project.pk])
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_wiki_link_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-wiki-link", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"title": "Imported wiki_link",
|
||||||
|
"href": "imported-wiki-link",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
||||||
|
|
||||||
|
def test_invalid_milestone_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-milestone", args=[project.pk])
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
def test_valid_milestone_import(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
url = reverse("importer-milestone", args=[project.pk])
|
||||||
|
data = {
|
||||||
|
"name": "Imported milestone",
|
||||||
|
"estimated_start": "2014-10-10",
|
||||||
|
"estimated_finish": "2014-10-20",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 201
|
||||||
|
response_data = json.loads(response.content.decode("utf-8"))
|
|
@ -10,6 +10,21 @@ from taiga.users import models
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_user_normal_user(client):
|
||||||
|
user = f.UserFactory.create(is_superuser=True)
|
||||||
|
|
||||||
|
url = reverse('users-list')
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 405
|
||||||
|
|
||||||
|
client.login(user)
|
||||||
|
|
||||||
|
response = client.post(url, json.dumps(data), content_type="application/json")
|
||||||
|
assert response.status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_api_user_patch_same_email(client):
|
def test_api_user_patch_same_email(client):
|
||||||
user = f.UserFactory.create(email="same@email.com")
|
user = f.UserFactory.create(email="same@email.com")
|
||||||
url = reverse('users-detail', kwargs={"pk": user.pk})
|
url = reverse('users-detail', kwargs={"pk": user.pk})
|
||||||
|
|
Loading…
Reference in New Issue