US #55: Custom fields - Import/Export custom attributes
parent
84db9956d5
commit
ebc3388d03
|
@ -127,6 +127,21 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
|
|||
"severities" in data):
|
||||
service.store_default_choices(project_serialized.object, data)
|
||||
|
||||
if "userstorycustomattributes" in data:
|
||||
service.store_custom_attributes(project_serialized.object, data,
|
||||
"userstorycustomattributes",
|
||||
serializers.UserStoryCustomAttributeExportSerializer)
|
||||
|
||||
if "taskcustomattributes" in data:
|
||||
service.store_custom_attributes(project_serialized.object, data,
|
||||
"taskcustomattributes",
|
||||
serializers.TaskCustomAttributeExportSerializer)
|
||||
|
||||
if "issuecustomattributes" in data:
|
||||
service.store_custom_attributes(project_serialized.object, data,
|
||||
"issuecustomattributes",
|
||||
serializers.IssueCustomAttributeExportSerializer)
|
||||
|
||||
if "roles" in data:
|
||||
service.store_roles(project_serialized.object, data)
|
||||
|
||||
|
|
|
@ -103,6 +103,16 @@ def dict_to_project(data, owner=None):
|
|||
if service.get_errors(clear=False):
|
||||
raise TaigaImportError('error importing default choices')
|
||||
|
||||
service.store_custom_attributes(proj, data, "userstorycustomattributes",
|
||||
serializers.UserStoryCustomAttributeExportSerializer)
|
||||
service.store_custom_attributes(proj, data, "taskcustomattributes",
|
||||
serializers.TaskCustomAttributeExportSerializer)
|
||||
service.store_custom_attributes(proj, data, "issuecustomattributes",
|
||||
serializers.IssueCustomAttributeExportSerializer)
|
||||
|
||||
if service.get_errors(clear=False):
|
||||
raise TaigaImportError('error importing custom attributes')
|
||||
|
||||
service.store_roles(proj, data)
|
||||
|
||||
if service.get_errors(clear=False):
|
||||
|
|
|
@ -20,11 +20,13 @@ 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 django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from taiga.projects import models as projects_models
|
||||
from taiga.projects.custom_attributes import models as custom_attributes_models
|
||||
from taiga.projects.userstories import models as userstories_models
|
||||
from taiga.projects.tasks import models as tasks_models
|
||||
from taiga.projects.issues import models as issues_models
|
||||
|
@ -81,14 +83,15 @@ class RelatedNoneSafeField(serializers.RelatedField):
|
|||
return
|
||||
value = self.get_default_value()
|
||||
|
||||
key = self.source or field_name
|
||||
if value in self.null_values:
|
||||
if self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
into[(self.source or field_name)] = None
|
||||
into[key] = None
|
||||
elif self.many:
|
||||
into[(self.source or field_name)] = [self.from_native(item) for item in value if self.from_native(item) is not None]
|
||||
into[key] = [self.from_native(item) for item in value if self.from_native(item) is not None]
|
||||
else:
|
||||
into[(self.source or field_name)] = self.from_native(value)
|
||||
into[key] = self.from_native(value)
|
||||
|
||||
|
||||
class UserRelatedField(RelatedNoneSafeField):
|
||||
|
@ -251,7 +254,8 @@ class AttachmentExportSerializerMixin(serializers.ModelSerializer):
|
|||
|
||||
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)
|
||||
attachments_qs = attachments_models.Attachment.objects.filter(object_id=obj.pk,
|
||||
content_type=content_type)
|
||||
return AttachmentExportSerializer(attachments_qs, many=True).data
|
||||
|
||||
|
||||
|
@ -305,6 +309,30 @@ class RoleExportSerializer(serializers.ModelSerializer):
|
|||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class UserStoryCustomAttributeExportSerializer(serializers.ModelSerializer):
|
||||
modified_date = serializers.DateTimeField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = custom_attributes_models.UserStoryCustomAttribute
|
||||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class TaskCustomAttributeExportSerializer(serializers.ModelSerializer):
|
||||
modified_date = serializers.DateTimeField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = custom_attributes_models.TaskCustomAttribute
|
||||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class IssueCustomAttributeExportSerializer(serializers.ModelSerializer):
|
||||
modified_date = serializers.DateTimeField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = custom_attributes_models.IssueCustomAttribute
|
||||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class MembershipExportSerializer(serializers.ModelSerializer):
|
||||
user = UserRelatedField(required=False)
|
||||
role = ProjectRelatedField(slug_field="name")
|
||||
|
@ -354,7 +382,8 @@ class MilestoneExportSerializer(serializers.ModelSerializer):
|
|||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||
class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
|
||||
serializers.ModelSerializer):
|
||||
owner = UserRelatedField(required=False)
|
||||
status = ProjectRelatedField(slug_field="name")
|
||||
user_story = ProjectRelatedField(slug_field="ref", required=False)
|
||||
|
@ -368,7 +397,8 @@ class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSeriali
|
|||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||
class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
|
||||
serializers.ModelSerializer):
|
||||
role_points = RolePointsExportSerializer(many=True, required=False)
|
||||
owner = UserRelatedField(required=False)
|
||||
assigned_to = UserRelatedField(required=False)
|
||||
|
@ -383,7 +413,8 @@ class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSe
|
|||
exclude = ('id', 'project', 'points', 'tasks')
|
||||
|
||||
|
||||
class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||
class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
|
||||
serializers.ModelSerializer):
|
||||
owner = UserRelatedField(required=False)
|
||||
status = ProjectRelatedField(slug_field="name")
|
||||
assigned_to = UserRelatedField(required=False)
|
||||
|
@ -403,7 +434,8 @@ class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerial
|
|||
exclude = ('id', 'project')
|
||||
|
||||
|
||||
class WikiPageExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer):
|
||||
class WikiPageExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
|
||||
serializers.ModelSerializer):
|
||||
owner = UserRelatedField(required=False)
|
||||
last_modifier = UserRelatedField(required=False)
|
||||
watchers = UserRelatedField(many=True, required=False)
|
||||
|
@ -437,6 +469,9 @@ class ProjectExportSerializer(serializers.ModelSerializer):
|
|||
priorities = PriorityExportSerializer(many=True, required=False)
|
||||
severities = SeverityExportSerializer(many=True, required=False)
|
||||
issue_types = IssueTypeExportSerializer(many=True, required=False)
|
||||
userstorycustomattributes = UserStoryCustomAttributeExportSerializer(many=True, required=False)
|
||||
taskcustomattributes = TaskCustomAttributeExportSerializer(many=True, required=False)
|
||||
issuecustomattributes = IssueCustomAttributeExportSerializer(many=True, required=False)
|
||||
roles = RoleExportSerializer(many=True, required=False)
|
||||
milestones = MilestoneExportSerializer(many=True, required=False)
|
||||
wiki_pages = WikiPageExportSerializer(many=True, required=False)
|
||||
|
|
|
@ -57,7 +57,8 @@ def store_project(data):
|
|||
"default_priority", "default_severity", "default_issue_status",
|
||||
"default_issue_type", "memberships", "points", "us_statuses",
|
||||
"task_statuses", "issue_statuses", "priorities", "severities",
|
||||
"issue_types", "roles", "milestones", "wiki_pages",
|
||||
"issue_types", "userstorycustomattributes", "taskcustomattributes",
|
||||
"issuecustomattributes", "roles", "milestones", "wiki_pages",
|
||||
"wiki_links", "notify_policies", "user_stories", "issues", "tasks",
|
||||
]
|
||||
if key not in excluded_fields:
|
||||
|
@ -72,7 +73,7 @@ def store_project(data):
|
|||
return None
|
||||
|
||||
|
||||
def store_choice(project, data, field, serializer):
|
||||
def _store_choice(project, data, field, serializer):
|
||||
serialized = serializer(data=data)
|
||||
if serialized.is_valid():
|
||||
serialized.object.project = project
|
||||
|
@ -86,7 +87,25 @@ def store_choice(project, data, field, serializer):
|
|||
def store_choices(project, data, field, serializer):
|
||||
result = []
|
||||
for choice_data in data.get(field, []):
|
||||
result.append(store_choice(project, choice_data, field, serializer))
|
||||
result.append(_store_choice(project, choice_data, field, serializer))
|
||||
return result
|
||||
|
||||
|
||||
def _store_custom_attribute(project, data, field, serializer):
|
||||
serialized = serializer(data=data)
|
||||
if serialized.is_valid():
|
||||
serialized.object.project = project
|
||||
serialized.object._importing = True
|
||||
serialized.save()
|
||||
return serialized.object
|
||||
add_errors(field, serialized.errors)
|
||||
return None
|
||||
|
||||
|
||||
def store_custom_attributes(project, data, field, serializer):
|
||||
result = []
|
||||
for custom_attribute_data in data.get(field, []):
|
||||
result.append(_store_custom_attribute(project, custom_attribute_data, field, serializer))
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
@ -167,6 +167,61 @@ def test_invalid_project_import_with_extra_data(client):
|
|||
assert Project.objects.filter(slug="imported-project").count() == 0
|
||||
|
||||
|
||||
def test_valid_project_import_with_custom_attributes(client):
|
||||
user = f.UserFactory.create()
|
||||
|
||||
url = reverse("importer-list")
|
||||
data = {
|
||||
"name": "Imported project",
|
||||
"description": "Imported project",
|
||||
"userstorycustomattributes": [{
|
||||
"name": "custom attribute example 1",
|
||||
"description": "short description 1",
|
||||
"order": 1
|
||||
}],
|
||||
"taskcustomattributes": [{
|
||||
"name": "custom attribute example 1",
|
||||
"description": "short description 1",
|
||||
"order": 1
|
||||
}],
|
||||
"issuecustomattributes": [{
|
||||
"name": "custom attribute example 1",
|
||||
"description": "short description 1",
|
||||
"order": 1
|
||||
}]
|
||||
}
|
||||
|
||||
must_empty_children = ["issues", "user_stories", "wiki_pages", "milestones", "wiki_links"]
|
||||
must_one_instance_children = ["userstorycustomattributes", "taskcustomattributes", "issuecustomattributes"]
|
||||
|
||||
client.login(user)
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
assert response.status_code == 201
|
||||
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_custom_attributes(client):
|
||||
user = f.UserFactory.create()
|
||||
|
||||
url = reverse("importer-list")
|
||||
data = {
|
||||
"name": "Imported project",
|
||||
"description": "Imported project",
|
||||
"userstorycustomattributes": [{ }],
|
||||
"taskcustomattributes": [{ }],
|
||||
"issuecustomattributes": [{ }]
|
||||
}
|
||||
|
||||
client.login(user)
|
||||
response = client.json.post(url, json.dumps(data))
|
||||
assert response.status_code == 400
|
||||
assert len(response.data) == 3
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue