US #55: Custom fields - Import/Export custom attributes

remotes/origin/enhancement/email-actions
David Barragán Merino 2015-02-05 12:30:13 +01:00
parent 84db9956d5
commit ebc3388d03
5 changed files with 146 additions and 12 deletions

View File

@ -127,6 +127,21 @@ class ProjectImporterViewSet(mixins.ImportThrottlingPolicyMixin, CreateModelMixi
"severities" in data): "severities" in data):
service.store_default_choices(project_serialized.object, 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: if "roles" in data:
service.store_roles(project_serialized.object, data) service.store_roles(project_serialized.object, data)

View File

@ -103,6 +103,16 @@ def dict_to_project(data, owner=None):
if service.get_errors(clear=False): if service.get_errors(clear=False):
raise TaigaImportError('error importing default choices') 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) service.store_roles(proj, data)
if service.get_errors(clear=False): if service.get_errors(clear=False):

View File

@ -20,11 +20,13 @@ from collections import OrderedDict
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.files.base import ContentFile 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 rest_framework import serializers
from taiga.projects import models as projects_models 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.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
@ -81,14 +83,15 @@ class RelatedNoneSafeField(serializers.RelatedField):
return return
value = self.get_default_value() value = self.get_default_value()
key = self.source or field_name
if value in self.null_values: if value in self.null_values:
if self.required: if self.required:
raise ValidationError(self.error_messages['required']) raise ValidationError(self.error_messages['required'])
into[(self.source or field_name)] = None into[key] = None
elif self.many: 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: else:
into[(self.source or field_name)] = self.from_native(value) into[key] = self.from_native(value)
class UserRelatedField(RelatedNoneSafeField): class UserRelatedField(RelatedNoneSafeField):
@ -251,7 +254,8 @@ class AttachmentExportSerializerMixin(serializers.ModelSerializer):
def get_attachments(self, obj): def get_attachments(self, obj):
content_type = ContentType.objects.get_for_model(obj.__class__) 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 return AttachmentExportSerializer(attachments_qs, many=True).data
@ -305,6 +309,30 @@ class RoleExportSerializer(serializers.ModelSerializer):
exclude = ('id', 'project') 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): class MembershipExportSerializer(serializers.ModelSerializer):
user = UserRelatedField(required=False) user = UserRelatedField(required=False)
role = ProjectRelatedField(slug_field="name") role = ProjectRelatedField(slug_field="name")
@ -354,7 +382,8 @@ class MilestoneExportSerializer(serializers.ModelSerializer):
exclude = ('id', 'project') exclude = ('id', 'project')
class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer): class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
serializers.ModelSerializer):
owner = UserRelatedField(required=False) owner = UserRelatedField(required=False)
status = ProjectRelatedField(slug_field="name") status = ProjectRelatedField(slug_field="name")
user_story = ProjectRelatedField(slug_field="ref", required=False) user_story = ProjectRelatedField(slug_field="ref", required=False)
@ -368,7 +397,8 @@ class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSeriali
exclude = ('id', 'project') exclude = ('id', 'project')
class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, serializers.ModelSerializer): class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
serializers.ModelSerializer):
role_points = RolePointsExportSerializer(many=True, required=False) role_points = RolePointsExportSerializer(many=True, required=False)
owner = UserRelatedField(required=False) owner = UserRelatedField(required=False)
assigned_to = UserRelatedField(required=False) assigned_to = UserRelatedField(required=False)
@ -383,7 +413,8 @@ class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSe
exclude = ('id', 'project', 'points', 'tasks') exclude = ('id', 'project', 'points', 'tasks')
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") status = ProjectRelatedField(slug_field="name")
assigned_to = UserRelatedField(required=False) assigned_to = UserRelatedField(required=False)
@ -403,7 +434,8 @@ class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerial
exclude = ('id', 'project') exclude = ('id', 'project')
class WikiPageExportSerializer(HistoryExportSerializerMixin, 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)
@ -437,6 +469,9 @@ class ProjectExportSerializer(serializers.ModelSerializer):
priorities = PriorityExportSerializer(many=True, required=False) priorities = PriorityExportSerializer(many=True, required=False)
severities = SeverityExportSerializer(many=True, required=False) severities = SeverityExportSerializer(many=True, required=False)
issue_types = IssueTypeExportSerializer(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) roles = RoleExportSerializer(many=True, required=False)
milestones = MilestoneExportSerializer(many=True, required=False) milestones = MilestoneExportSerializer(many=True, required=False)
wiki_pages = WikiPageExportSerializer(many=True, required=False) wiki_pages = WikiPageExportSerializer(many=True, required=False)

View File

@ -57,7 +57,8 @@ def store_project(data):
"default_priority", "default_severity", "default_issue_status", "default_priority", "default_severity", "default_issue_status",
"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", "userstorycustomattributes", "taskcustomattributes",
"issuecustomattributes", "roles", "milestones", "wiki_pages",
"wiki_links", "notify_policies", "user_stories", "issues", "tasks", "wiki_links", "notify_policies", "user_stories", "issues", "tasks",
] ]
if key not in excluded_fields: if key not in excluded_fields:
@ -72,7 +73,7 @@ def store_project(data):
return None return None
def store_choice(project, data, field, serializer): def _store_choice(project, data, field, serializer):
serialized = serializer(data=data) serialized = serializer(data=data)
if serialized.is_valid(): if serialized.is_valid():
serialized.object.project = project serialized.object.project = project
@ -86,7 +87,25 @@ def store_choice(project, data, field, serializer):
def store_choices(project, data, field, serializer): def store_choices(project, data, field, serializer):
result = [] result = []
for choice_data in data.get(field, []): 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 return result

View File

@ -167,6 +167,61 @@ def test_invalid_project_import_with_extra_data(client):
assert Project.objects.filter(slug="imported-project").count() == 0 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): def test_invalid_issue_import(client):
user = f.UserFactory.create() user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user) project = f.ProjectFactory.create(owner=user)