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

remotes/origin/enhancement/email-actions
David Barragán Merino 2015-02-17 11:19:31 +01:00
parent 32ff494ddd
commit 56b9fe42e3
3 changed files with 304 additions and 79 deletions

View File

@ -333,6 +333,93 @@ class IssueCustomAttributeExportSerializer(serializers.ModelSerializer):
exclude = ('id', 'project')
class CustomAttributesValuesExportSerializerMixin:
custom_attributes_values = serializers.SerializerMethodField("get_custom_attributes_values")
def custom_attributes_queryset(self, project):
raise NotImplementedError()
def get_custom_attributes_values(self, obj):
def _use_name_instead_id_as_key_in_custom_attributes_values(custom_attributes, values):
ret = {}
for attr in custom_attributes:
value = values.get(str(attr["id"]), None)
if value is not None:
ret[attr["name"]] = value
return ret
try:
values = obj.custom_attributes_values.values
custom_attributes = self.custom_attribute_queryset(obj.project).values('id', 'name')
return _use_name_instead_id_as_key_in_custom_attributes_values(custom_attributes, values)
except ObjectDoesNotExist:
return None
class BaseCustomAttributesValuesExportSerializer:
values = JsonField(source="values", label="values", required=True)
_custom_attribute_model = None
_container_field = None
def validate_values(self, attrs, source):
# values must be a dict
data_values = attrs.get("values", None)
if self.object:
data_values = (data_values or self.object.values)
if type(data_values) is not dict:
raise ValidationError(_("Invalid content. It must be {\"key\": \"value\",...}"))
# Values keys must be in the container object project
data_container = attrs.get(self._container_field, None)
if data_container:
project_id = data_container.project_id
elif self.object:
project_id = getattr(self.object, self._container_field).project_id
else:
project_id = None
values_ids = list(data_values.keys())
qs = self._custom_attribute_model.objects.filter(project=project_id,
id__in=values_ids)
if qs.count() != len(values_ids):
raise ValidationError(_("It contain invalid custom fields."))
return attrs
class UserStoryCustomAttributesValuesExportSerializer(BaseCustomAttributesValuesExportSerializer,
serializers.ModelSerializer):
_custom_attribute_model = custom_attributes_models.UserStoryCustomAttribute
_container_model = "userstories.UserStory"
_container_field = "user_story"
class Meta:
model = custom_attributes_models.UserStoryCustomAttributesValues
exclude = ("id",)
class TaskCustomAttributesValuesExportSerializer(BaseCustomAttributesValuesExportSerializer,
serializers.ModelSerializer):
_custom_attribute_model = custom_attributes_models.TaskCustomAttribute
_container_field = "task"
class Meta:
model = custom_attributes_models.TaskCustomAttributesValues
exclude = ("id",)
class IssueCustomAttributesValuesExportSerializer(BaseCustomAttributesValuesExportSerializer,
serializers.ModelSerializer):
_custom_attribute_model = custom_attributes_models.IssueCustomAttribute
_container_field = "issue"
class Meta:
model = custom_attributes_models.IssueCustomAttributesValues
exclude = ("id",)
class MembershipExportSerializer(serializers.ModelSerializer):
user = UserRelatedField(required=False)
role = ProjectRelatedField(slug_field="name")
@ -382,8 +469,8 @@ class MilestoneExportSerializer(serializers.ModelSerializer):
exclude = ('id', 'project')
class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
serializers.ModelSerializer):
class TaskExportSerializer(CustomAttributesValuesExportSerializerMixin, HistoryExportSerializerMixin,
AttachmentExportSerializerMixin, serializers.ModelSerializer):
owner = UserRelatedField(required=False)
status = ProjectRelatedField(slug_field="name")
user_story = ProjectRelatedField(slug_field="ref", required=False)
@ -396,9 +483,12 @@ class TaskExportSerializer(HistoryExportSerializerMixin, AttachmentExportSeriali
model = tasks_models.Task
exclude = ('id', 'project')
def custom_attributes_queryset(self, project):
return project.taskcustomattributes.all()
class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
serializers.ModelSerializer):
class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin, HistoryExportSerializerMixin,
AttachmentExportSerializerMixin, serializers.ModelSerializer):
role_points = RolePointsExportSerializer(many=True, required=False)
owner = UserRelatedField(required=False)
assigned_to = UserRelatedField(required=False)
@ -412,9 +502,12 @@ class UserStoryExportSerializer(HistoryExportSerializerMixin, AttachmentExportSe
model = userstories_models.UserStory
exclude = ('id', 'project', 'points', 'tasks')
def custom_attributes_queryset(self, project):
return project.userstorycustomattributes.all()
class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
serializers.ModelSerializer):
class IssueExportSerializer(CustomAttributesValuesExportSerializerMixin, HistoryExportSerializerMixin,
AttachmentExportSerializerMixin, serializers.ModelSerializer):
owner = UserRelatedField(required=False)
status = ProjectRelatedField(slug_field="name")
assigned_to = UserRelatedField(required=False)
@ -426,13 +519,16 @@ class IssueExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerial
votes = serializers.SerializerMethodField("get_votes")
modified_date = serializers.DateTimeField(required=False)
def get_votes(self, obj):
return [x.email for x in votes_service.get_voters(obj)]
class Meta:
model = issues_models.Issue
exclude = ('id', 'project')
def get_votes(self, obj):
return [x.email for x in votes_service.get_voters(obj)]
def custom_attributes_queryset(self, project):
return project.issuecustomattributes.all()
class WikiPageExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin,
serializers.ModelSerializer):

View File

@ -108,6 +108,30 @@ def store_custom_attributes(project, data, field, serializer):
result.append(_store_custom_attribute(project, custom_attribute_data, field, serializer))
return result
def store_custom_attributes_values(obj, data_values, obj_field, serializer_class):
data = {
obj_field: obj.id,
"values": data_values,
}
serializer = serializer_class(data=data)
if serializer.is_valid():
serializer.save()
return serializer
add_errors("custom_attributes_values", serializer.errors)
return None
def _use_id_instead_name_as_key_in_custom_attributes_values(custom_attributes, values):
ret = {}
for attr in custom_attributes:
value = values.get(attr["name"], None)
if value is not None:
ret[str(attr["id"])] = value
return ret
def store_role(project, role):
serialized = serializers.RoleExportSerializer(data=role)
@ -122,7 +146,7 @@ def store_role(project, role):
def store_roles(project, data):
results = []
for role in data.get('roles', []):
for role in data.get("roles", []):
results.append(store_role(project, role))
return results
@ -164,16 +188,16 @@ def store_membership(project, membership):
def store_memberships(project, data):
results = []
for membership in data.get('memberships', []):
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
def store_task(project, data):
if "status" not in data and project.default_task_status:
data["status"] = project.default_task_status.name
serialized = serializers.TaskExportSerializer(data=task, context={"project": project})
serialized = serializers.TaskExportSerializer(data=data, context={"project": project})
if serialized.is_valid():
serialized.object.project = project
if serialized.object.owner is None:
@ -192,12 +216,20 @@ def store_task(project, task):
serialized.object.ref, _ = refs.make_reference(serialized.object, project)
serialized.object.save()
for task_attachment in task.get('attachments', []):
for task_attachment in data.get("attachments", []):
store_attachment(project, serialized.object, task_attachment)
for history in task.get('history', []):
for history in data.get("history", []):
store_history(project, serialized.object, history)
custom_attributes_values = data.get("custom_attributes_values", None)
if custom_attributes_values:
custom_attributes = serialized.object.project.taskcustomattributes.all().values('id', 'name')
custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values(custom_attributes,
custom_attributes_values)
store_custom_attributes_values(serialized.object, custom_attributes_values,
"task", serializers.TaskCustomAttributesValuesExportSerializer)
return serialized
add_errors("tasks", serialized.errors)
@ -211,8 +243,8 @@ def store_milestone(project, milestone):
serialized.object._importing = True
serialized.save()
for task_without_us in milestone.get('tasks_without_us', []):
task_without_us['user_story'] = None
for task_without_us in milestone.get("tasks_without_us", []):
task_without_us["user_story"] = None
store_task(project, task_without_us)
return serialized
@ -251,7 +283,7 @@ def store_history(project, obj, history):
def store_wiki_page(project, wiki_page):
wiki_page['slug'] = slugify(unidecode(wiki_page.get('slug', '')))
wiki_page["slug"] = slugify(unidecode(wiki_page.get("slug", "")))
serialized = serializers.WikiPageExportSerializer(data=wiki_page)
if serialized.is_valid():
serialized.object.project = project
@ -261,10 +293,10 @@ def store_wiki_page(project, wiki_page):
serialized.object._not_notify = True
serialized.save()
for attachment in wiki_page.get('attachments', []):
for attachment in wiki_page.get("attachments", []):
store_attachment(project, serialized.object, attachment)
for history in wiki_page.get('history', []):
for history in wiki_page.get("history", []):
store_history(project, serialized.object, history)
return serialized
@ -295,61 +327,12 @@ def store_role_point(project, us, role_point):
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
def store_user_story(project, data):
if "status" not in data and project.default_us_status:
data["status"] = project.default_us_status.name
userstory_data = {}
for key, value in userstory.items():
if key != 'role_points':
userstory_data[key] = value
serialized_us = serializers.UserStoryExportSerializer(data=userstory_data, context={"project": project})
if serialized_us.is_valid():
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._not_notify = True
serialized_us.save()
if serialized_us.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_us.object.ref)
else:
serialized_us.object.ref, _ = refs.make_reference(serialized_us.object, project)
serialized_us.object.save()
for us_attachment in userstory.get('attachments', []):
store_attachment(project, serialized_us.object, us_attachment)
for role_point in userstory.get('role_points', []):
store_role_point(project, serialized_us.object, role_point)
for history in userstory.get('history', []):
store_history(project, serialized_us.object, history)
return serialized_us
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
data_data = {key: value for key, value in data.items() if key not in ["role_points", "custom_attributes_values"]}
serialized = serializers.UserStoryExportSerializer(data=data_data, context={"project": project})
if serialized.is_valid():
serialized.object.project = project
@ -369,10 +352,77 @@ def store_issue(project, data):
serialized.object.ref, _ = refs.make_reference(serialized.object, project)
serialized.object.save()
for attachment in data.get('attachments', []):
store_attachment(project, serialized.object, attachment)
for history in data.get('history', []):
for us_attachment in data.get("attachments", []):
store_attachment(project, serialized.object, us_attachment)
for role_point in data.get("role_points", []):
store_role_point(project, serialized.object, role_point)
for history in data.get("history", []):
store_history(project, serialized.object, history)
custom_attributes_values = data.get("custom_attributes_values", None)
if custom_attributes_values:
custom_attributes = serialized.object.project.userstorycustomattributes.all().values('id', 'name')
custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values(custom_attributes,
custom_attributes_values)
store_custom_attributes_values(serialized.object, custom_attributes_values,
"user_story", serializers.UserStoryCustomAttributesValuesExportSerializer)
return serialized
add_errors("user_stories", serialized.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
if serialized.object.owner is None:
serialized.object.owner = serialized.object.project.owner
serialized.object._importing = True
serialized.object._not_notify = True
serialized.save()
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)
for history in data.get("history", []):
store_history(project, serialized.object, history)
custom_attributes_values = data.get("custom_attributes_values", None)
if custom_attributes_values:
custom_attributes = serialized.object.project.issuecustomattributes.all().values('id', 'name')
custom_attributes_values = _use_id_instead_name_as_key_in_custom_attributes_values(custom_attributes,
custom_attributes_values)
store_custom_attributes_values(serialized.object, custom_attributes_values,
"issue", serializers.IssueCustomAttributesValuesExportSerializer)
return serialized
add_errors("issues", serialized.errors)
return None

View File

@ -22,6 +22,8 @@ from django.core.files.base import ContentFile
from .. import factories as f
from django.apps import apps
from taiga.base.utils import json
from taiga.projects.models import Project
from taiga.projects.issues.models import Issue
@ -256,6 +258,30 @@ def test_valid_user_story_import(client):
assert response_data["finish_date"] == "2014-10-24T00:00:00+0000"
def test_valid_user_story_import_with_custom_attributes_values(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
membership = f.MembershipFactory(project=project, user=user, is_owner=True)
project.default_us_status = f.UserStoryStatusFactory.create(project=project)
project.save()
custom_attr = f.UserStoryCustomAttributeFactory(project=project)
url = reverse("importer-us", args=[project.pk])
data = {
"subject": "Test Custom Attrs Values User Story",
"custom_attributes_values": {
custom_attr.name: "test_value"
}
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
custom_attributes_values = apps.get_model("custom_attributes.UserStoryCustomAttributesValues").objects.get(
user_story__subject=response.data["subject"])
assert custom_attributes_values.values == {str(custom_attr.id): "test_value"}
def test_valid_issue_import_without_extra_data(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
@ -279,6 +305,33 @@ def test_valid_issue_import_without_extra_data(client):
assert response_data["ref"] is not None
def test_valid_issue_import_with_custom_attributes_values(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
membership = f.MembershipFactory(project=project, user=user, is_owner=True)
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()
custom_attr = f.IssueCustomAttributeFactory(project=project)
url = reverse("importer-issue", args=[project.pk])
data = {
"subject": "Test Custom Attrs Values Issues",
"custom_attributes_values": {
custom_attr.name: "test_value"
}
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
custom_attributes_values = apps.get_model("custom_attributes.IssueCustomAttributesValues").objects.get(
issue__subject=response.data["subject"])
assert custom_attributes_values.values == {str(custom_attr.id): "test_value"}
def test_valid_issue_import_with_extra_data(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
@ -536,6 +589,30 @@ def test_valid_task_import_without_extra_data(client):
assert response_data["ref"] is not None
def test_valid_task_import_with_custom_attributes_values(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
membership = f.MembershipFactory(project=project, user=user, is_owner=True)
project.default_task_status = f.TaskStatusFactory.create(project=project)
project.save()
custom_attr = f.TaskCustomAttributeFactory(project=project)
url = reverse("importer-task", args=[project.pk])
data = {
"subject": "Test Custom Attrs Values Tasks",
"custom_attributes_values": {
custom_attr.name: "test_value"
}
}
client.login(user)
response = client.json.post(url, json.dumps(data))
assert response.status_code == 201
custom_attributes_values = apps.get_model("custom_attributes.TaskCustomAttributesValues").objects.get(
task__subject=response.data["subject"])
assert custom_attributes_values.values == {str(custom_attr.id): "test_value"}
def test_valid_task_import_with_extra_data(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
@ -735,6 +812,7 @@ def test_valid_wiki_link_import(client):
json.loads(response.content.decode("utf-8"))
def test_invalid_milestone_import(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
@ -766,6 +844,7 @@ def test_valid_milestone_import(client):
json.loads(response.content.decode("utf-8"))
def test_milestone_import_duplicated_milestone(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)