Refactor signals of tasks, userstories and issues modules
parent
202e7b75a4
commit
d4b34be47e
|
@ -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"
|
||||||
|
|
|
@ -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"))
|
|
@ -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()
|
|
||||||
|
|
|
@ -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
|
|
@ -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",])
|
|
@ -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()
|
|
@ -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"
|
|
@ -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"))
|
|
@ -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()
|
|
||||||
|
|
|
@ -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)
|
|
@ -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"
|
|
@ -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"))
|
|
@ -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()
|
|
||||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue