Merge pull request #994 from taigaio/issue/5137/imported-projects-notify-incorrect-users-in-some-cases

Imported projects notify incorrect users in some cases
remotes/origin/fix-throttling-bug
David Barragán Merino 2017-05-30 13:14:04 +00:00 committed by GitHub
commit e7c58fa670
7 changed files with 109 additions and 17 deletions

View File

@ -24,6 +24,10 @@ _custom_tasks_attributes_cache = {}
_custom_issues_attributes_cache = {} _custom_issues_attributes_cache = {}
_custom_userstories_attributes_cache = {} _custom_userstories_attributes_cache = {}
_custom_epics_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): def cached_get_user_by_pk(pk):
if pk not in _cache_user_by_pk: if pk not in _cache_user_by_pk:

View File

@ -17,6 +17,7 @@
# 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.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from taiga.base.api import serializers 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.attachments import models as attachments_models
from taiga.projects.history import services as history_service from taiga.projects.history import services as history_service
from .fields import (UserRelatedField, HistoryUserField, HistoryDiffField, from .cache import cached_get_user_by_pk
HistoryValuesField, FileField) from .fields import (UserRelatedField, HistoryUserField,
HistoryDiffField, HistoryValuesField,
SlugRelatedField, FileField)
class HistoryExportSerializer(serializers.LightSerializer): class HistoryExportSerializer(serializers.LightSerializer):
user = HistoryUserField() user = HistoryUserField()
diff = HistoryDiffField() diff = HistoryDiffField()
snapshot = Field() snapshot = MethodField()
values = HistoryValuesField() values = HistoryValuesField()
comment = Field() comment = Field()
delete_comment_date = DateTimeField() delete_comment_date = DateTimeField()
@ -44,17 +46,49 @@ class HistoryExportSerializer(serializers.LightSerializer):
is_snapshot = Field() is_snapshot = Field()
type = 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): class HistoryExportSerializerMixin(serializers.LightSerializer):
history = MethodField("get_history") history = MethodField("get_history")
def statuses_queryset(self, project):
raise NotImplementedError()
def get_history(self, obj): def get_history(self, obj):
history_qs = history_service.get_history_queryset_by_model_instance( history_qs = history_service.get_history_queryset_by_model_instance(
obj, obj,
types=(history_models.HistoryType.change, history_models.HistoryType.create,) types=(history_models.HistoryType.change, history_models.HistoryType.create,)
) )
return HistoryExportSerializer(history_qs, many=True, statuses_queryset=self.statuses_queryset(obj.project)).data
return HistoryExportSerializer(history_qs, many=True).data
class AttachmentExportSerializer(serializers.LightSerializer): class AttachmentExportSerializer(serializers.LightSerializer):

View File

@ -30,7 +30,11 @@ from .mixins import (HistoryExportSerializerMixin,
from .cache import (_custom_tasks_attributes_cache, from .cache import (_custom_tasks_attributes_cache,
_custom_userstories_attributes_cache, _custom_userstories_attributes_cache,
_custom_epics_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): class RelatedExportSerializer(serializers.LightSerializer):
@ -217,6 +221,11 @@ class TaskExportSerializer(CustomAttributesValuesExportSerializerMixin,
_custom_tasks_attributes_cache[project.id] = list(project.taskcustomattributes.all().values('id', 'name')) _custom_tasks_attributes_cache[project.id] = list(project.taskcustomattributes.all().values('id', 'name'))
return _custom_tasks_attributes_cache[project.id] 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, class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin,
HistoryExportSerializerMixin, HistoryExportSerializerMixin,
@ -255,6 +264,10 @@ class UserStoryExportSerializer(CustomAttributesValuesExportSerializerMixin,
) )
return _custom_userstories_attributes_cache[project.id] 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): class EpicRelatedUserStoryExportSerializer(RelatedExportSerializer):
user_story = SlugRelatedField(slug_field="ref") user_story = SlugRelatedField(slug_field="ref")
@ -285,15 +298,20 @@ class EpicExportSerializer(CustomAttributesValuesExportSerializerMixin,
related_user_stories = MethodField() related_user_stories = MethodField()
def get_related_user_stories(self, obj): 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): def custom_attributes_queryset(self, project):
if project.id not in _custom_epics_attributes_cache: if project.id not in _custom_epics_attributes_cache:
_custom_epics_attributes_cache[project.id] = list( _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] 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, class IssueExportSerializer(CustomAttributesValuesExportSerializerMixin,
HistoryExportSerializerMixin, HistoryExportSerializerMixin,
@ -329,6 +347,10 @@ class IssueExportSerializer(CustomAttributesValuesExportSerializerMixin,
_custom_issues_attributes_cache[project.id] = list(project.issuecustomattributes.all().values('id', 'name')) _custom_issues_attributes_cache[project.id] = list(project.issuecustomattributes.all().values('id', 'name'))
return _custom_issues_attributes_cache[project.id] 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, class WikiPageExportSerializer(HistoryExportSerializerMixin,
AttachmentExportSerializerMixin, AttachmentExportSerializerMixin,
@ -342,6 +364,8 @@ class WikiPageExportSerializer(HistoryExportSerializerMixin,
content = Field() content = Field()
version = Field() version = Field()
def statuses_queryset(self, project):
return {}
class WikiLinkExportSerializer(RelatedExportSerializer): class WikiLinkExportSerializer(RelatedExportSerializer):
title = Field() title = Field()

View File

@ -155,8 +155,8 @@ def _store_attachment(project, obj, attachment):
return validator return validator
def _store_history(project, obj, history): def _store_history(project, obj, history, statuses={}):
validator = validators.HistoryExportValidator(data=history, context={"project": project}) validator = validators.HistoryExportValidator(data=history, context={"project": project, "statuses": statuses})
if validator.is_valid(): if validator.is_valid():
validator.object.key = make_key_from_model_object(obj) validator.object.key = make_key_from_model_object(obj)
if validator.object.diff is None: if validator.object.diff is None:
@ -360,8 +360,9 @@ def store_user_story(project, data):
_store_role_point(project, validator.object, role_point) _store_role_point(project, validator.object, role_point)
history_entries = data.get("history", []) history_entries = data.get("history", [])
statuses = {s.name: s.id for s in project.us_statuses.all()}
for history in history_entries: for history in history_entries:
_store_history(project, validator.object, history) _store_history(project, validator.object, history, statuses)
if not history_entries: if not history_entries:
take_snapshot(validator.object, user=validator.object.owner) 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) _store_epic_related_user_story(project, validator.object, related_user_story)
history_entries = data.get("history", []) history_entries = data.get("history", [])
statuses = {s.name: s.id for s in project.epic_statuses.all()}
for history in history_entries: for history in history_entries:
_store_history(project, validator.object, history) _store_history(project, validator.object, history, statuses)
if not history_entries: if not history_entries:
take_snapshot(validator.object, user=validator.object.owner) take_snapshot(validator.object, user=validator.object.owner)
@ -496,8 +498,9 @@ def store_task(project, data):
_store_attachment(project, validator.object, task_attachment) _store_attachment(project, validator.object, task_attachment)
history_entries = data.get("history", []) history_entries = data.get("history", [])
statuses = {s.name: s.id for s in project.task_statuses.all()}
for history in history_entries: for history in history_entries:
_store_history(project, validator.object, history) _store_history(project, validator.object, history, statuses)
if not history_entries: if not history_entries:
take_snapshot(validator.object, user=validator.object.owner) take_snapshot(validator.object, user=validator.object.owner)
@ -567,8 +570,9 @@ def store_issue(project, data):
_store_attachment(project, validator.object, attachment) _store_attachment(project, validator.object, attachment)
history_entries = data.get("history", []) history_entries = data.get("history", [])
statuses = {s.name: s.id for s in project.issue_statuses.all()}
for history in history_entries: for history in history_entries:
_store_history(project, validator.object, history) _store_history(project, validator.object, history, statuses)
if not history_entries: if not history_entries:
take_snapshot(validator.object, user=validator.object.owner) take_snapshot(validator.object, user=validator.object.owner)

View File

@ -12,6 +12,7 @@ from .validators import UserStoryCustomAttributeExportValidator
from .validators import TaskCustomAttributeExportValidator from .validators import TaskCustomAttributeExportValidator
from .validators import IssueCustomAttributeExportValidator from .validators import IssueCustomAttributeExportValidator
from .validators import BaseCustomAttributesValuesExportValidator from .validators import BaseCustomAttributesValuesExportValidator
from .validators import EpicCustomAttributesValuesExportValidator
from .validators import UserStoryCustomAttributesValuesExportValidator from .validators import UserStoryCustomAttributesValuesExportValidator
from .validators import TaskCustomAttributesValuesExportValidator from .validators import TaskCustomAttributesValuesExportValidator
from .validators import IssueCustomAttributesValuesExportValidator from .validators import IssueCustomAttributesValuesExportValidator

View File

@ -144,6 +144,21 @@ class ProjectRelatedField(serializers.RelatedField):
raise ValidationError(_("{}=\"{}\" not found in this project".format(self.slug_field, data))) 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): class HistoryUserField(JSONField):
def from_native(self, data): def from_native(self, data):
if data is None: if data is None:

View File

@ -28,13 +28,14 @@ from taiga.projects.notifications import services as notifications_services
from taiga.projects.history import services as history_service from taiga.projects.history import services as history_service
from .fields import (UserRelatedField, HistoryUserField, HistoryDiffField, from .fields import (UserRelatedField, HistoryUserField, HistoryDiffField,
JSONField, HistoryValuesField, CommentField, FileField) JSONField, HistorySnapshotField,
HistoryValuesField, CommentField, FileField)
class HistoryExportValidator(validators.ModelValidator): class HistoryExportValidator(validators.ModelValidator):
user = HistoryUserField() user = HistoryUserField()
diff = HistoryDiffField(required=False) diff = HistoryDiffField(required=False)
snapshot = JSONField(required=False) snapshot = HistorySnapshotField(required=False)
values = HistoryValuesField(required=False) values = HistoryValuesField(required=False)
comment = CommentField(required=False) comment = CommentField(required=False)
delete_comment_date = serializers.DateTimeField(required=False) delete_comment_date = serializers.DateTimeField(required=False)
@ -44,6 +45,15 @@ class HistoryExportValidator(validators.ModelValidator):
model = history_models.HistoryEntry model = history_models.HistoryEntry
exclude = ("id", "comment_html", "key", "project") 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): class AttachmentExportValidator(validators.ModelValidator):
owner = UserRelatedField(required=False) owner = UserRelatedField(required=False)