Refactor signals of tasks, userstories and issues modules

remotes/origin/enhancement/email-actions
David Barragán Merino 2014-09-22 17:44:20 +02:00
parent 202e7b75a4
commit d4b34be47e
16 changed files with 482 additions and 153 deletions

View File

@ -0,0 +1,18 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
default_app_config = "taiga.projects.issues.apps.IssuesAppConfig"

View File

@ -0,0 +1,40 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
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"))

View File

@ -21,11 +21,11 @@ from django.utils import timezone
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ 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.occ import OCCModelMixin
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin 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 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 = "issue"
verbose_name_plural = "issues" verbose_name_plural = "issues"
ordering = ["project", "-created_date"] ordering = ["project", "-created_date"]
#unique_together = ("ref", "project")
permissions = ( permissions = (
("view_issue", "Can view issue"), ("view_issue", "Can view issue"),
) )
@ -96,29 +95,3 @@ class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.
@property @property
def is_closed(self): def is_closed(self):
return self.status.is_closed 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()

View File

@ -0,0 +1,28 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
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

View File

View File

@ -0,0 +1,37 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
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",])

View File

37
taiga/projects/signals.py Normal file
View File

@ -0,0 +1,37 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
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()

View File

@ -0,0 +1,17 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
default_app_config = "taiga.projects.tasks.apps.TasksAppConfig"

View File

@ -0,0 +1,46 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
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"))

View File

@ -18,18 +18,12 @@ from django.db import models
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ 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.occ import OCCModelMixin
from taiga.projects.userstories.models import UserStory from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.userstories import services as us_service
from taiga.projects.milestones.models import Milestone
from taiga.projects.mixins.blocked import BlockedMixin 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): class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
@ -90,71 +84,3 @@ class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.M
def __str__(self): def __str__(self):
return "({1}) {0}".format(self.ref, self.subject) 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()

View File

@ -0,0 +1,90 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
####################################
# 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)

View File

@ -0,0 +1,17 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
default_app_config = "taiga.projects.userstories.apps.UserStoriesAppConfig"

View File

@ -0,0 +1,54 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
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"))

View File

@ -17,16 +17,13 @@
from django.db import models from django.db import models
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.conf import settings from django.conf import settings
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone 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.occ import OCCModelMixin
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin 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): class RolePoints(models.Model):
@ -138,45 +135,3 @@ class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, mod
total += rp.points.value total += rp.points.value
return total 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()

View File

@ -0,0 +1,91 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
####################################
# 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)