From d4b34be47e72baae6714b47f7ff3abd5b98f3fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 22 Sep 2014 17:44:20 +0200 Subject: [PATCH] Refactor signals of tasks, userstories and issues modules --- taiga/projects/issues/__init__.py | 18 +++++ taiga/projects/issues/apps.py | 40 +++++++++++ taiga/projects/issues/models.py | 33 +--------- taiga/projects/issues/signals.py | 28 ++++++++ taiga/projects/milestones/apps.py | 0 taiga/projects/milestones/services.py | 37 +++++++++++ taiga/projects/milestones/signals.py | 0 taiga/projects/signals.py | 37 +++++++++++ taiga/projects/tasks/__init__.py | 17 +++++ taiga/projects/tasks/apps.py | 46 +++++++++++++ taiga/projects/tasks/models.py | 78 +--------------------- taiga/projects/tasks/signals.py | 90 +++++++++++++++++++++++++ taiga/projects/userstories/__init__.py | 17 +++++ taiga/projects/userstories/apps.py | 54 +++++++++++++++ taiga/projects/userstories/models.py | 49 +------------- taiga/projects/userstories/signals.py | 91 ++++++++++++++++++++++++++ 16 files changed, 482 insertions(+), 153 deletions(-) create mode 100644 taiga/projects/issues/apps.py create mode 100644 taiga/projects/issues/signals.py create mode 100644 taiga/projects/milestones/apps.py create mode 100644 taiga/projects/milestones/services.py create mode 100644 taiga/projects/milestones/signals.py create mode 100644 taiga/projects/signals.py create mode 100644 taiga/projects/tasks/apps.py create mode 100644 taiga/projects/tasks/signals.py create mode 100644 taiga/projects/userstories/apps.py create mode 100644 taiga/projects/userstories/signals.py diff --git a/taiga/projects/issues/__init__.py b/taiga/projects/issues/__init__.py index e69de29b..aff90c37 100644 --- a/taiga/projects/issues/__init__.py +++ b/taiga/projects/issues/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +default_app_config = "taiga.projects.issues.apps.IssuesAppConfig" + diff --git a/taiga/projects/issues/apps.py b/taiga/projects/issues/apps.py new file mode 100644 index 00000000..153c2e0b --- /dev/null +++ b/taiga/projects/issues/apps.py @@ -0,0 +1,40 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.apps import AppConfig +from django.apps import apps +from django.db.models import signals + +from taiga.projects import signals as generic_handlers +from . import signals as handlers + + +class IssuesAppConfig(AppConfig): + name = "taiga.projects.issues" + verbose_name = "Issues" + + def ready(self): + # Finixhed date + signals.pre_save.connect(handlers.set_finished_date_when_edit_issue, + sender=apps.get_model("issues", "Issue")) + + # Tags + signals.pre_save.connect(generic_handlers.tags_normalization, + sender=apps.get_model("issues", "Issue")) + signals.post_save.connect(generic_handlers.update_project_tags_when_create_or_edit_taggable_item, + sender=apps.get_model("issues", "Issue")) + signals.post_delete.connect(generic_handlers.update_project_tags_when_delete_taggable_item, + sender=apps.get_model("issues", "Issue")) diff --git a/taiga/projects/issues/models.py b/taiga/projects/issues/models.py index d08b44a3..e43bace3 100644 --- a/taiga/projects/issues/models.py +++ b/taiga/projects/issues/models.py @@ -21,11 +21,11 @@ from django.utils import timezone from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ -from taiga.base.tags import TaggedMixin -from taiga.base.utils.slug import ref_uniquely -from taiga.projects.notifications import WatchedModelMixin from taiga.projects.occ import OCCModelMixin +from taiga.projects.notifications import WatchedModelMixin from taiga.projects.mixins.blocked import BlockedMixin +from taiga.base.tags import TaggedMixin + from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags @@ -67,7 +67,6 @@ class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models. verbose_name = "issue" verbose_name_plural = "issues" ordering = ["project", "-created_date"] - #unique_together = ("ref", "project") permissions = ( ("view_issue", "Can view issue"), ) @@ -96,29 +95,3 @@ class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models. @property def is_closed(self): return self.status.is_closed - - -# Model related signals handlers -@receiver(models.signals.pre_save, sender=Issue, dispatch_uid="issue_finished_date_handler") -def issue_finished_date_handler(sender, instance, **kwargs): - if instance.status.is_closed and not instance.finished_date: - instance.finished_date = timezone.now() - elif not instance.status.is_closed and instance.finished_date: - instance.finished_date = None - - -@receiver(models.signals.pre_save, sender=Issue, dispatch_uid="issue-tags-normalization") -def issue_tags_normalization(sender, instance, **kwargs): - if isinstance(instance.tags, (list, tuple)): - instance.tags = list(map(lambda x: x.lower(), instance.tags)) - - -@receiver(models.signals.post_save, sender=Issue, dispatch_uid="issue_update_project_colors") -def issue_update_project_tags(sender, instance, **kwargs): - update_project_tags_colors_handler(instance) - - -@receiver(models.signals.post_delete, sender=Issue, dispatch_uid="issue_update_project_colors_on_delete") -def issue_update_project_tags_on_delete(sender, instance, **kwargs): - remove_unused_tags(instance.project) - instance.project.save() diff --git a/taiga/projects/issues/signals.py b/taiga/projects/issues/signals.py new file mode 100644 index 00000000..9389d410 --- /dev/null +++ b/taiga/projects/issues/signals.py @@ -0,0 +1,28 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.utils import timezone + + +#################################### +# Signals for set finished date +#################################### + +def set_finished_date_when_edit_issue(sender, instance, **kwargs): + if instance.status.is_closed and not instance.finished_date: + instance.finished_date = timezone.now() + elif not instance.status.is_closed and instance.finished_date: + instance.finished_date = None diff --git a/taiga/projects/milestones/apps.py b/taiga/projects/milestones/apps.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/projects/milestones/services.py b/taiga/projects/milestones/services.py new file mode 100644 index 00000000..cac04870 --- /dev/null +++ b/taiga/projects/milestones/services.py @@ -0,0 +1,37 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.utils import timezone + +from . import models + + + +def calculate_milestone_is_closed(milestone): + return (all([task.status.is_closed for task in milestone.tasks.all()]) and + all([user_story.is_closed for user_story in milestone.user_stories.all()])) + + +def close_milestone(milestone): + if not milestone.closed: + milestone.closed = True + milestone.save(update_fields=["closed",]) + + +def open_milestone(milestone): + if milestone.closed: + milestone.closed = False + milestone.save(update_fields=["closed",]) diff --git a/taiga/projects/milestones/signals.py b/taiga/projects/milestones/signals.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/projects/signals.py b/taiga/projects/signals.py new file mode 100644 index 00000000..e61722cb --- /dev/null +++ b/taiga/projects/signals.py @@ -0,0 +1,37 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags + + +#################################### +# Signals over project items +#################################### + +## TAGS + +def tags_normalization(sender, instance, **kwargs): + if isinstance(instance.tags, (list, tuple)): + instance.tags = list(map(str.lower, instance.tags)) + + +def update_project_tags_when_create_or_edit_taggable_item(sender, instance, **kwargs): + update_project_tags_colors_handler(instance) + + +def update_project_tags_when_delete_taggable_item(sender, instance, **kwargs): + remove_unused_tags(instance.project) + instance.project.save() diff --git a/taiga/projects/tasks/__init__.py b/taiga/projects/tasks/__init__.py index e69de29b..0af24e1d 100644 --- a/taiga/projects/tasks/__init__.py +++ b/taiga/projects/tasks/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +default_app_config = "taiga.projects.tasks.apps.TasksAppConfig" diff --git a/taiga/projects/tasks/apps.py b/taiga/projects/tasks/apps.py new file mode 100644 index 00000000..752560de --- /dev/null +++ b/taiga/projects/tasks/apps.py @@ -0,0 +1,46 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.apps import AppConfig +from django.apps import apps +from django.db.models import signals + +from taiga.projects import signals as generic_handlers +from . import signals as handlers + + +class TasksAppConfig(AppConfig): + name = "taiga.projects.tasks" + verbose_name = "Tasks" + + def ready(self): + # Cached prev object version + signals.pre_save.connect(handlers.cached_prev_task, + sender=apps.get_model("tasks", "Task")) + + # Open/Close US and Milestone + signals.post_save.connect(handlers.try_to_close_or_open_us_and_milestone_when_create_or_edit_task, + sender=apps.get_model("tasks", "Task")) + signals.post_delete.connect(handlers.try_to_close_or_open_us_and_milestone_when_delete_task, + sender=apps.get_model("tasks", "Task")) + + # Tags + signals.pre_save.connect(generic_handlers.tags_normalization, + sender=apps.get_model("tasks", "Task")) + signals.post_save.connect(generic_handlers.update_project_tags_when_create_or_edit_taggable_item, + sender=apps.get_model("tasks", "Task")) + signals.post_delete.connect(generic_handlers.update_project_tags_when_delete_taggable_item, + sender=apps.get_model("tasks", "Task")) diff --git a/taiga/projects/tasks/models.py b/taiga/projects/tasks/models.py index a3708466..9f71dd3b 100644 --- a/taiga/projects/tasks/models.py +++ b/taiga/projects/tasks/models.py @@ -18,18 +18,12 @@ from django.db import models from django.contrib.contenttypes import generic from django.conf import settings from django.utils import timezone -from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ -from taiga.base.tags import TaggedMixin -from taiga.base.utils.slug import ref_uniquely -from taiga.projects.notifications import WatchedModelMixin from taiga.projects.occ import OCCModelMixin -from taiga.projects.userstories.models import UserStory -from taiga.projects.userstories import services as us_service -from taiga.projects.milestones.models import Milestone +from taiga.projects.notifications import WatchedModelMixin from taiga.projects.mixins.blocked import BlockedMixin -from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags +from taiga.base.tags import TaggedMixin class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model): @@ -90,71 +84,3 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.M def __str__(self): return "({1}) {0}".format(self.ref, self.subject) - - -def milestone_has_open_userstories(milestone): - qs = milestone.user_stories.exclude(is_closed=True) - return qs.exists() - - -@receiver(models.signals.post_delete, sender=Task, dispatch_uid="tasks_milestone_close_handler_on_delete") -def tasks_milestone_close_handler_on_delete(sender, instance, **kwargs): - if instance.milestone_id and Milestone.objects.filter(id=instance.milestone_id): - if not milestone_has_open_userstories(instance.milestone): - instance.milestone.closed = True - instance.milestone.save(update_fields=["closed"]) - - -# Define the previous version of the task for use it on the post_save handler -@receiver(models.signals.pre_save, sender=Task, dispatch_uid="tasks_us_close_handler") -def tasks_us_close_handler(sender, instance, **kwargs): - instance.prev = None - if instance.id: - instance.prev = sender.objects.get(id=instance.id) - - -@receiver(models.signals.post_save, sender=Task, dispatch_uid="tasks_us_close_on_create_handler") -def tasks_us_close_on_create_handler(sender, instance, created, **kwargs): - if instance.user_story_id: - if us_service.calculate_userstory_is_closed(instance.user_story): - us_service.close_userstory(instance.user_story) - else: - us_service.open_userstory(instance.user_story) - - if instance.prev and instance.prev.user_story_id: - if us_service.calculate_userstory_is_closed(instance.prev.user_story): - us_service.close_userstory(instance.prev.user_story) - else: - us_service.open_userstory(instance.prev.user_story) - - -@receiver(models.signals.post_delete, sender=Task, dispatch_uid="tasks_us_close_handler_on_delete") -def tasks_us_close_handler_on_delete(sender, instance, **kwargs): - if instance.user_story_id: - if us_service.calculate_userstory_is_closed(instance.user_story): - us_service.close_userstory(instance.user_story) - else: - us_service.open_userstory(instance.user_story) - - -@receiver(models.signals.pre_save, sender=Task, dispatch_uid="tasks_milestone_close_handler") -def tasks_milestone_close_handler(sender, instance, **kwargs): - if instance.milestone_id: - if instance.status.is_closed and not instance.milestone.closed: - if not milestone_has_open_userstories(instance.milestone): - instance.milestone.closed = True - instance.milestone.save(update_fields=["closed"]) - elif not instance.status.is_closed and instance.milestone.closed: - instance.milestone.closed = False - instance.milestone.save(update_fields=["closed"]) - - -@receiver(models.signals.post_save, sender=Task, dispatch_uid="task_update_project_colors") -def task_update_project_tags(sender, instance, **kwargs): - update_project_tags_colors_handler(instance) - - -@receiver(models.signals.post_delete, sender=Task, dispatch_uid="task_update_project_colors_on_delete") -def task_update_project_tags_on_delete(sender, instance, **kwargs): - remove_unused_tags(instance.project) - instance.project.save() diff --git a/taiga/projects/tasks/signals.py b/taiga/projects/tasks/signals.py new file mode 100644 index 00000000..cbc633d1 --- /dev/null +++ b/taiga/projects/tasks/signals.py @@ -0,0 +1,90 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +#################################### +# Signals for cached prev task +#################################### + +# Define the previous version of the task for use it on the post_save handler +def cached_prev_task(sender, instance, **kwargs): + instance.prev = None + if instance.id: + instance.prev = sender.objects.get(id=instance.id) + + +#################################### +# Signals for close US and Milestone +#################################### + +def try_to_close_or_open_us_and_milestone_when_create_or_edit_task(sender, instance, created, **kwargs): + _try_to_close_or_open_us_when_create_or_edit_task(instance) + _try_to_close_or_open_milestone_when_create_or_edit_task(instance) + +def try_to_close_or_open_us_and_milestone_when_delete_task(sender, instance, **kwargs): + _try_to_close_or_open_us_when_delete_task(instance) + _try_to_close_milestone_when_delete_task(instance) + + +# US +def _try_to_close_or_open_us_when_create_or_edit_task(instance): + from taiga.projects.userstories import services as us_service + + if instance.user_story_id: + if us_service.calculate_userstory_is_closed(instance.user_story): + us_service.close_userstory(instance.user_story) + else: + us_service.open_userstory(instance.user_story) + + if instance.prev and instance.prev.user_story_id and instance.prev.user_story_id != instance.user_story_id: + if us_service.calculate_userstory_is_closed(instance.prev.user_story): + us_service.close_userstory(instance.prev.user_story) + else: + us_service.open_userstory(instance.prev.user_story) + + +def _try_to_close_or_open_us_when_delete_task(instance): + from taiga.projects.userstories import services as us_service + + if instance.user_story_id: + if us_service.calculate_userstory_is_closed(instance.user_story): + us_service.close_userstory(instance.user_story) + else: + us_service.open_userstory(instance.user_story) + + +# Milestone +def _try_to_close_or_open_milestone_when_create_or_edit_task(instance): + from taiga.projects.milestones import services as milestone_service + + if instance.milestone_id: + if milestone_service.calculate_milestone_is_closed(instance.milestone): + milestone_service.close_milestone(instance.milestone) + else: + milestone_service.open_milestone(instance.milestone) + + if instance.prev and instance.prev.milestone_id and instance.prev.milestone_id != instance.milestone_id: + if milestone_service.calculate_milestone_is_closed(instance.prev.milestone): + milestone_service.close_milestone(instance.prev.milestone) + else: + milestone_service.open_milestone(instance.prev.milestone) + + +def _try_to_close_milestone_when_delete_task(instance): + from taiga.projects.milestones import services as milestone_service + + if instance.milestone_id and milestone_service.calculate_milestone_is_closed(instance.milestone): + milestone_service.close_milestone(instance.milestone) diff --git a/taiga/projects/userstories/__init__.py b/taiga/projects/userstories/__init__.py index e69de29b..572f9d9a 100644 --- a/taiga/projects/userstories/__init__.py +++ b/taiga/projects/userstories/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +default_app_config = "taiga.projects.userstories.apps.UserStoriesAppConfig" diff --git a/taiga/projects/userstories/apps.py b/taiga/projects/userstories/apps.py new file mode 100644 index 00000000..299f1cfc --- /dev/null +++ b/taiga/projects/userstories/apps.py @@ -0,0 +1,54 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.apps import AppConfig +from django.apps import apps +from django.db.models import signals + +from taiga.projects import signals as generic_handlers +from . import signals as handlers + + +class UserStoriesAppConfig(AppConfig): + name = "taiga.projects.userstories" + verbose_name = "User Stories" + + def ready(self): + # Cached prev object version + signals.pre_save.connect(handlers.cached_prev_us, + sender=apps.get_model("userstories", "UserStory")) + + # Role Points + signals.post_save.connect(handlers.update_role_points_when_create_or_edit_us, + sender=apps.get_model("userstories", "UserStory")) + + # Tasks + signals.post_save.connect(handlers.update_milestone_of_tasks_when_edit_us, + sender=apps.get_model("userstories", "UserStory")) + + # Open/Close US and Milestone + signals.post_save.connect(handlers.try_to_close_or_open_us_and_milestone_when_create_or_edit_us, + sender=apps.get_model("userstories", "UserStory")) + signals.post_delete.connect(handlers.try_to_close_milestone_when_delete_us, + sender=apps.get_model("userstories", "UserStory")) + + # Tags + signals.pre_save.connect(generic_handlers.tags_normalization, + sender=apps.get_model("userstories", "UserStory")) + signals.post_save.connect(generic_handlers.update_project_tags_when_create_or_edit_taggable_item, + sender=apps.get_model("userstories", "UserStory")) + signals.post_delete.connect(generic_handlers.update_project_tags_when_delete_taggable_item, + sender=apps.get_model("userstories", "UserStory")) diff --git a/taiga/projects/userstories/models.py b/taiga/projects/userstories/models.py index e46a1840..377aef52 100644 --- a/taiga/projects/userstories/models.py +++ b/taiga/projects/userstories/models.py @@ -17,16 +17,13 @@ from django.db import models from django.contrib.contenttypes import generic from django.conf import settings -from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from django.utils import timezone -from taiga.base.tags import TaggedMixin -from taiga.base.utils.slug import ref_uniquely -from taiga.projects.notifications import WatchedModelMixin from taiga.projects.occ import OCCModelMixin +from taiga.projects.notifications import WatchedModelMixin from taiga.projects.mixins.blocked import BlockedMixin -from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags +from taiga.base.tags import TaggedMixin class RolePoints(models.Model): @@ -138,45 +135,3 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod total += rp.points.value return total - - -@receiver(models.signals.post_save, sender=UserStory, - dispatch_uid="user_story_create_role_points_handler") -def us_create_role_points_handler(sender, instance, **kwargs): - if instance._importing: - return - instance.project.update_role_points(user_stories=[instance]) - - -@receiver(models.signals.post_save, sender=UserStory, - dispatch_uid="user_story_tasks_reassignation") -def us_task_reassignation(sender, instance, created, **kwargs): - if not created: - instance.tasks.update(milestone=instance.milestone) - - -@receiver(models.signals.pre_save, sender=UserStory, dispatch_uid="us-tags-normalization") -def us_tags_normalization(sender, instance, **kwargs): - if isinstance(instance.tags, (list, tuple)): - instance.tags = list(map(str.lower, instance.tags)) - -@receiver(models.signals.post_save, sender=UserStory, - dispatch_uid="user_story_on_status_change") -def us_close_open_on_status_change(sender, instance, **kwargs): - from taiga.projects.userstories import services as service - - if service.calculate_userstory_is_closed(instance): - service.close_userstory(instance) - else: - service.open_userstory(instance) - - -@receiver(models.signals.post_save, sender=UserStory, dispatch_uid="user_story_update_project_colors") -def us_update_project_tags(sender, instance, **kwargs): - update_project_tags_colors_handler(instance) - - -@receiver(models.signals.post_delete, sender=UserStory, dispatch_uid="user_story_update_project_colors_on_delete") -def us_update_project_tags_on_delete(sender, instance, **kwargs): - remove_unused_tags(instance.project) - instance.project.save() diff --git a/taiga/projects/userstories/signals.py b/taiga/projects/userstories/signals.py new file mode 100644 index 00000000..c1b491a8 --- /dev/null +++ b/taiga/projects/userstories/signals.py @@ -0,0 +1,91 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +#################################### +# Signals for cached prev US +#################################### + +# Define the previous version of the US for use it on the post_save handler +def cached_prev_us(sender, instance, **kwargs): + instance.prev = None + if instance.id: + instance.prev = sender.objects.get(id=instance.id) + + +#################################### +# Signals of role points +#################################### + +def update_role_points_when_create_or_edit_us(sender, instance, **kwargs): + if instance._importing: + return + instance.project.update_role_points(user_stories=[instance]) + + +#################################### +# Signals for update milestone of tasks +#################################### + +def update_milestone_of_tasks_when_edit_us(sender, instance, created, **kwargs): + if not created: + instance.tasks.update(milestone=instance.milestone) + + +#################################### +# Signals for close US and Milestone +#################################### + +def try_to_close_or_open_us_and_milestone_when_create_or_edit_us(sender, instance, created, **kwargs): + _try_to_close_or_open_us_when_create_or_edit_us(instance) + _try_to_close_or_open_milestone_when_create_or_edit_us(instance) + +def try_to_close_milestone_when_delete_us(sender, instance, **kwargs): + _try_to_close_milestone_when_delete_us(instance) + + +# US +def _try_to_close_or_open_us_when_create_or_edit_us(instance): + from . import services as us_service + + if us_service.calculate_userstory_is_closed(instance): + us_service.close_userstory(instance) + else: + us_service.open_userstory(instance) + + +# Milestone +def _try_to_close_or_open_milestone_when_create_or_edit_us(instance): + from taiga.projects.milestones import services as milestone_service + + if instance.milestone_id: + if milestone_service.calculate_milestone_is_closed(instance.milestone): + milestone_service.close_milestone(instance.milestone) + else: + milestone_service.open_milestone(instance.milestone) + + if instance.prev and instance.prev.milestone_id and instance.prev.milestone_id != instance.milestone_id: + if milestone_service.calculate_milestone_is_closed(instance.prev.milestone): + milestone_service.close_milestone(instance.prev.milestone) + else: + milestone_service.open_milestone(instance.prev.milestone) + + +def _try_to_close_milestone_when_delete_us(instance): + from taiga.projects.milestones import services as milestone_service + + if instance.milestone_id and milestone_service.calculate_milestone_is_closed(instance.milestone): + milestone_service.close_milestone(instance.milestone)