From a2d1547fae6672c5b9f70bace49f9e2861316044 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 17 May 2017 09:32:31 +0200 Subject: [PATCH] Imported projects notify incorrect users in some cases --- taiga/export_import/serializers/cache.py | 4 ++ taiga/export_import/serializers/mixins.py | 46 ++++++++++++++++--- .../export_import/serializers/serializers.py | 30 ++++++++++-- taiga/export_import/services/store.py | 16 ++++--- taiga/export_import/validators/__init__.py | 1 + taiga/export_import/validators/fields.py | 15 ++++++ taiga/export_import/validators/mixins.py | 14 +++++- 7 files changed, 109 insertions(+), 17 deletions(-) diff --git a/taiga/export_import/serializers/cache.py b/taiga/export_import/serializers/cache.py index 4dfb76ad..909a079e 100644 --- a/taiga/export_import/serializers/cache.py +++ b/taiga/export_import/serializers/cache.py @@ -24,6 +24,10 @@ _custom_tasks_attributes_cache = {} _custom_issues_attributes_cache = {} _custom_userstories_attributes_cache = {} _custom_epics_attributes_cache = {} +_tasks_statuses_cache = {} +_issues_statuses_cache = {} +_userstories_statuses_cache = {} +_epics_statuses_cache = {} def cached_get_user_by_pk(pk): if pk not in _cache_user_by_pk: diff --git a/taiga/export_import/serializers/mixins.py b/taiga/export_import/serializers/mixins.py index 0df95a5f..30e9d511 100644 --- a/taiga/export_import/serializers/mixins.py +++ b/taiga/export_import/serializers/mixins.py @@ -17,6 +17,7 @@ # along with this program. If not, see . from django.core.exceptions import ObjectDoesNotExist +from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from taiga.base.api import serializers @@ -25,14 +26,15 @@ from taiga.projects.history import models as history_models from taiga.projects.attachments import models as attachments_models from taiga.projects.history import services as history_service -from .fields import (UserRelatedField, HistoryUserField, HistoryDiffField, - HistoryValuesField, FileField) - +from .cache import cached_get_user_by_pk +from .fields import (UserRelatedField, HistoryUserField, + HistoryDiffField, HistoryValuesField, + SlugRelatedField, FileField) class HistoryExportSerializer(serializers.LightSerializer): user = HistoryUserField() diff = HistoryDiffField() - snapshot = Field() + snapshot = MethodField() values = HistoryValuesField() comment = Field() delete_comment_date = DateTimeField() @@ -44,17 +46,49 @@ class HistoryExportSerializer(serializers.LightSerializer): is_snapshot = Field() type = Field() + def __init__(self, *args, **kwargs): + # Don't pass the extra ids args up to the superclass + self.statuses_queryset = kwargs.pop("statuses_queryset", {}) + + # Instantiate the superclass normally + super().__init__(*args, **kwargs) + + def get_snapshot(self, obj): + user_model_cls = get_user_model() + + snapshot = obj.snapshot + if snapshot is None: + return None + + try: + owner = cached_get_user_by_pk(snapshot.get("owner", None)) + snapshot["owner"] = owner.email + except user_model_cls.DoesNotExist: + pass + + try: + assigned_to = cached_get_user_by_pk(snapshot.get("assigned_to", None)) + snapshot["assigned_to"] = assigned_to.email + except user_model_cls.DoesNotExist: + pass + + if "status" in snapshot: + snapshot["status"] = self.statuses_queryset.get(snapshot["status"]) + + return snapshot class HistoryExportSerializerMixin(serializers.LightSerializer): history = MethodField("get_history") + def statuses_queryset(self, project): + raise NotImplementedError() + def get_history(self, obj): history_qs = history_service.get_history_queryset_by_model_instance( obj, types=(history_models.HistoryType.change, history_models.HistoryType.create,) ) - - return HistoryExportSerializer(history_qs, many=True).data + return HistoryExportSerializer(history_qs, many=True, statuses_queryset=self.statuses_queryset(obj.project)).data class AttachmentExportSerializer(serializers.LightSerializer): diff --git a/taiga/export_import/serializers/serializers.py b/taiga/export_import/serializers/serializers.py index 1f00fbf7..a9bbe6e3 100644 --- a/taiga/export_import/serializers/serializers.py +++ b/taiga/export_import/serializers/serializers.py @@ -30,7 +30,11 @@ from .mixins import (HistoryExportSerializerMixin, from .cache import (_custom_tasks_attributes_cache, _custom_userstories_attributes_cache, _custom_epics_attributes_cache, - _custom_issues_attributes_cache) + _custom_issues_attributes_cache, + _tasks_statuses_cache, + _issues_statuses_cache, + _userstories_statuses_cache, + _epics_statuses_cache) class RelatedExportSerializer(serializers.LightSerializer): @@ -217,6 +221,11 @@ class TaskExportSerializer(CustomAttributesValuesExportSerializerMixin, _custom_tasks_attributes_cache[project.id] = list(project.taskcustomattributes.all().values('id', 'name')) return _custom_tasks_attributes_cache[project.id] + def statuses_queryset(self, project): + if project.id not in _tasks_statuses_cache: + _tasks_statuses_cache[project.id] = {s.id: s.name for s in project.task_statuses.all()} + return _tasks_statuses_cache[project.id] + class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin, HistoryExportSerializerMixin, @@ -255,6 +264,10 @@ class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin, ) return _custom_userstories_attributes_cache[project.id] + def statuses_queryset(self, project): + if project.id not in _userstories_statuses_cache: + _userstories_statuses_cache[project.id] = {s.id: s.name for s in project.us_statuses.all()} + return _userstories_statuses_cache[project.id] class EpicRelatedUserStoryExportSerializer(RelatedExportSerializer): user_story = SlugRelatedField(slug_field="ref") @@ -285,15 +298,20 @@ class EpicExportSerializer(CustomAttributesValuesExportSerializerMixin, related_user_stories = MethodField() def get_related_user_stories(self, obj): - return EpicRelatedUserStoryExportSerializer(obj.relateduserstory_set.all(), many=True).data + return EpicRelatedUserStoryExportSerializer(obj.relateduserstory_set.filter(epic__project=obj.project), many=True).data def custom_attributes_queryset(self, project): if project.id not in _custom_epics_attributes_cache: _custom_epics_attributes_cache[project.id] = list( - project.userstorycustomattributes.all().values('id', 'name') + project.epiccustomattributes.all().values('id', 'name') ) return _custom_epics_attributes_cache[project.id] + def statuses_queryset(self, project): + if project.id not in _epics_statuses_cache: + _epics_statuses_cache[project.id] = {s.id: s.name for s in project.epic_statuses.all()} + return _epics_statuses_cache[project.id] + class IssueExportSerializer(CustomAttributesValuesExportSerializerMixin, HistoryExportSerializerMixin, @@ -329,6 +347,10 @@ class IssueExportSerializer(CustomAttributesValuesExportSerializerMixin, _custom_issues_attributes_cache[project.id] = list(project.issuecustomattributes.all().values('id', 'name')) return _custom_issues_attributes_cache[project.id] + def statuses_queryset(self, project): + if project.id not in _issues_statuses_cache: + _issues_statuses_cache[project.id] = {s.id: s.name for s in project.issue_statuses.all()} + return _issues_statuses_cache[project.id] class WikiPageExportSerializer(HistoryExportSerializerMixin, AttachmentExportSerializerMixin, @@ -342,6 +364,8 @@ class WikiPageExportSerializer(HistoryExportSerializerMixin, content = Field() version = Field() + def statuses_queryset(self, project): + return {} class WikiLinkExportSerializer(RelatedExportSerializer): title = Field() diff --git a/taiga/export_import/services/store.py b/taiga/export_import/services/store.py index 1944912d..a20b9329 100644 --- a/taiga/export_import/services/store.py +++ b/taiga/export_import/services/store.py @@ -155,8 +155,8 @@ def _store_attachment(project, obj, attachment): return validator -def _store_history(project, obj, history): - validator = validators.HistoryExportValidator(data=history, context={"project": project}) +def _store_history(project, obj, history, statuses={}): + validator = validators.HistoryExportValidator(data=history, context={"project": project, "statuses": statuses}) if validator.is_valid(): validator.object.key = make_key_from_model_object(obj) if validator.object.diff is None: @@ -360,8 +360,9 @@ def store_user_story(project, data): _store_role_point(project, validator.object, role_point) history_entries = data.get("history", []) + statuses = {s.name: s.id for s in project.us_statuses.all()} for history in history_entries: - _store_history(project, validator.object, history) + _store_history(project, validator.object, history, statuses) if not history_entries: take_snapshot(validator.object, user=validator.object.owner) @@ -436,8 +437,9 @@ def store_epic(project, data): _store_epic_related_user_story(project, validator.object, related_user_story) history_entries = data.get("history", []) + statuses = {s.name: s.id for s in project.epic_statuses.all()} for history in history_entries: - _store_history(project, validator.object, history) + _store_history(project, validator.object, history, statuses) if not history_entries: take_snapshot(validator.object, user=validator.object.owner) @@ -496,8 +498,9 @@ def store_task(project, data): _store_attachment(project, validator.object, task_attachment) history_entries = data.get("history", []) + statuses = {s.name: s.id for s in project.task_statuses.all()} for history in history_entries: - _store_history(project, validator.object, history) + _store_history(project, validator.object, history, statuses) if not history_entries: take_snapshot(validator.object, user=validator.object.owner) @@ -567,8 +570,9 @@ def store_issue(project, data): _store_attachment(project, validator.object, attachment) history_entries = data.get("history", []) + statuses = {s.name: s.id for s in project.issue_statuses.all()} for history in history_entries: - _store_history(project, validator.object, history) + _store_history(project, validator.object, history, statuses) if not history_entries: take_snapshot(validator.object, user=validator.object.owner) diff --git a/taiga/export_import/validators/__init__.py b/taiga/export_import/validators/__init__.py index 0948ade0..dd286efa 100644 --- a/taiga/export_import/validators/__init__.py +++ b/taiga/export_import/validators/__init__.py @@ -12,6 +12,7 @@ from .validators import UserStoryCustomAttributeExportValidator from .validators import TaskCustomAttributeExportValidator from .validators import IssueCustomAttributeExportValidator from .validators import BaseCustomAttributesValuesExportValidator +from .validators import EpicCustomAttributesValuesExportValidator from .validators import UserStoryCustomAttributesValuesExportValidator from .validators import TaskCustomAttributesValuesExportValidator from .validators import IssueCustomAttributesValuesExportValidator diff --git a/taiga/export_import/validators/fields.py b/taiga/export_import/validators/fields.py index d93a3677..fda4ad5d 100644 --- a/taiga/export_import/validators/fields.py +++ b/taiga/export_import/validators/fields.py @@ -144,6 +144,21 @@ class ProjectRelatedField(serializers.RelatedField): raise ValidationError(_("{}=\"{}\" not found in this project".format(self.slug_field, data))) +class HistorySnapshotField(JSONField): + def from_native(self, data): + if data is None: + return {} + + owner = UserRelatedField().from_native(data.get("owner")) + if owner: + data["owner"] = owner.pk + + assigned_to = UserRelatedField().from_native(data.get("assigned_to")) + if assigned_to: + data["assigned_to"] = assigned_to.pk + + return data + class HistoryUserField(JSONField): def from_native(self, data): if data is None: diff --git a/taiga/export_import/validators/mixins.py b/taiga/export_import/validators/mixins.py index 44027c90..5e634054 100644 --- a/taiga/export_import/validators/mixins.py +++ b/taiga/export_import/validators/mixins.py @@ -28,13 +28,14 @@ from taiga.projects.notifications import services as notifications_services from taiga.projects.history import services as history_service from .fields import (UserRelatedField, HistoryUserField, HistoryDiffField, - JSONField, HistoryValuesField, CommentField, FileField) + JSONField, HistorySnapshotField, + HistoryValuesField, CommentField, FileField) class HistoryExportValidator(validators.ModelValidator): user = HistoryUserField() diff = HistoryDiffField(required=False) - snapshot = JSONField(required=False) + snapshot = HistorySnapshotField(required=False) values = HistoryValuesField(required=False) comment = CommentField(required=False) delete_comment_date = serializers.DateTimeField(required=False) @@ -44,6 +45,15 @@ class HistoryExportValidator(validators.ModelValidator): model = history_models.HistoryEntry exclude = ("id", "comment_html", "key", "project") + def restore_object(self, attrs, instance=None): + snapshot = attrs["snapshot"] + statuses = self.context.get("statuses", {}) + if "status" in snapshot: + status_id = statuses.get(snapshot["status"], None) + attrs["snapshot"]["status"] = status_id + + instance = super(HistoryExportValidator, self).restore_object(attrs, instance) + return instance class AttachmentExportValidator(validators.ModelValidator): owner = UserRelatedField(required=False)