Merge pull request #72 from taigaio/refactor/signals
Refactor signals of tasks, userstories and issues modules (and fix bug #1061)remotes/origin/enhancement/email-actions
commit
7bc9cb1703
|
@ -177,16 +177,16 @@ INSTALLED_APPS = [
|
||||||
"taiga.users",
|
"taiga.users",
|
||||||
"taiga.userstorage",
|
"taiga.userstorage",
|
||||||
"taiga.projects",
|
"taiga.projects",
|
||||||
|
"taiga.projects.references",
|
||||||
|
"taiga.projects.history",
|
||||||
|
"taiga.projects.notifications",
|
||||||
"taiga.projects.attachments",
|
"taiga.projects.attachments",
|
||||||
|
"taiga.projects.votes",
|
||||||
"taiga.projects.milestones",
|
"taiga.projects.milestones",
|
||||||
"taiga.projects.userstories",
|
"taiga.projects.userstories",
|
||||||
"taiga.projects.tasks",
|
"taiga.projects.tasks",
|
||||||
"taiga.projects.issues",
|
"taiga.projects.issues",
|
||||||
"taiga.projects.references",
|
|
||||||
"taiga.projects.wiki",
|
"taiga.projects.wiki",
|
||||||
"taiga.projects.history",
|
|
||||||
"taiga.projects.notifications",
|
|
||||||
"taiga.projects.votes",
|
|
||||||
"taiga.searches",
|
"taiga.searches",
|
||||||
"taiga.timeline",
|
"taiga.timeline",
|
||||||
"taiga.mdrender",
|
"taiga.mdrender",
|
||||||
|
|
|
@ -36,7 +36,7 @@ import base64
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.db.models import get_model
|
from django.apps import apps
|
||||||
from rest_framework.authentication import BaseAuthentication
|
from rest_framework.authentication import BaseAuthentication
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ def get_user_for_token(token):
|
||||||
except signing.BadSignature:
|
except signing.BadSignature:
|
||||||
raise exc.NotAuthenticated("Invalid token")
|
raise exc.NotAuthenticated("Invalid token")
|
||||||
|
|
||||||
model_cls = get_model("users", "User")
|
model_cls = apps.get_model("users", "User")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = model_cls.objects.get(pk=data["user_id"])
|
user = model_cls.objects.get(pk=data["user_id"])
|
||||||
|
|
|
@ -23,7 +23,7 @@ should be contained in a class". Because of that, it
|
||||||
not uses clasess and uses simple functions.
|
not uses clasess and uses simple functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db import transaction as tx
|
from django.db import transaction as tx
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
|
@ -68,7 +68,7 @@ def is_user_already_registred(*, username:str, email:str, github_id:int=None) ->
|
||||||
Checks if a specified user is already registred.
|
Checks if a specified user is already registred.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user_model = get_model("users", "User")
|
user_model = apps.get_model("users", "User")
|
||||||
|
|
||||||
or_expr = Q(username=username) | Q(email=email)
|
or_expr = Q(username=username) | Q(email=email)
|
||||||
if github_id:
|
if github_id:
|
||||||
|
@ -86,7 +86,7 @@ def get_membership_by_token(token:str):
|
||||||
If not matches with any membership NotFound exception
|
If not matches with any membership NotFound exception
|
||||||
is raised.
|
is raised.
|
||||||
"""
|
"""
|
||||||
membership_model = get_model("projects", "Membership")
|
membership_model = apps.get_model("projects", "Membership")
|
||||||
qs = membership_model.objects.filter(token=token)
|
qs = membership_model.objects.filter(token=token)
|
||||||
if len(qs) == 0:
|
if len(qs) == 0:
|
||||||
raise exc.NotFound("Token not matches any valid invitation.")
|
raise exc.NotFound("Token not matches any valid invitation.")
|
||||||
|
@ -108,7 +108,7 @@ def public_register(username:str, password:str, email:str, full_name:str):
|
||||||
if is_user_already_registred(username=username, email=email):
|
if is_user_already_registred(username=username, email=email):
|
||||||
raise exc.IntegrityError("User is already registred.")
|
raise exc.IntegrityError("User is already registred.")
|
||||||
|
|
||||||
user_model = get_model("users", "User")
|
user_model = apps.get_model("users", "User")
|
||||||
user = user_model(username=username,
|
user = user_model(username=username,
|
||||||
email=email,
|
email=email,
|
||||||
full_name=full_name)
|
full_name=full_name)
|
||||||
|
@ -149,7 +149,7 @@ def private_register_for_new_user(token:str, username:str, email:str,
|
||||||
if is_user_already_registred(username=username, email=email):
|
if is_user_already_registred(username=username, email=email):
|
||||||
raise exc.WrongArguments(_("Username or Email is already in use."))
|
raise exc.WrongArguments(_("Username or Email is already in use."))
|
||||||
|
|
||||||
user_model = get_model("users", "User")
|
user_model = apps.get_model("users", "User")
|
||||||
user = user_model(username=username,
|
user = user_model(username=username,
|
||||||
email=email,
|
email=email,
|
||||||
full_name=full_name)
|
full_name=full_name)
|
||||||
|
@ -177,7 +177,7 @@ def github_register(username:str, email:str, full_name:str, github_id:int, bio:s
|
||||||
|
|
||||||
:returns: User
|
:returns: User
|
||||||
"""
|
"""
|
||||||
user_model = get_model("users", "User")
|
user_model = apps.get_model("users", "User")
|
||||||
user, created = user_model.objects.get_or_create(github_id=github_id,
|
user, created = user_model.objects.get_or_create(github_id=github_id,
|
||||||
defaults={"username": username,
|
defaults={"username": username,
|
||||||
"email": email,
|
"email": email,
|
||||||
|
|
|
@ -18,7 +18,7 @@ import abc
|
||||||
|
|
||||||
from taiga.base.utils import sequence as sq
|
from taiga.base.utils import sequence as sq
|
||||||
from taiga.permissions.service import user_has_perm, is_project_owner
|
from taiga.permissions.service import user_has_perm, is_project_owner
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -181,7 +181,7 @@ class HasProjectParamAndPerm(PermissionComponent):
|
||||||
super().__init__(*components)
|
super().__init__(*components)
|
||||||
|
|
||||||
def check_permissions(self, request, view, obj=None):
|
def check_permissions(self, request, view, obj=None):
|
||||||
Project = get_model('projects', 'Project')
|
Project = apps.get_model('projects', 'Project')
|
||||||
project_id = request.QUERY_PARAMS.get("project", None)
|
project_id = request.QUERY_PARAMS.get("project", None)
|
||||||
try:
|
try:
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from taiga.base.utils.iterators import as_tuple
|
from taiga.base.utils.iterators import as_tuple
|
||||||
from taiga.base.utils.iterators import as_dict
|
from taiga.base.utils.iterators import as_dict
|
||||||
|
@ -41,7 +41,7 @@ def _get_generic_values(ids:tuple, *, typename=None, attr:str="name") -> tuple:
|
||||||
|
|
||||||
@as_dict
|
@as_dict
|
||||||
def _get_users_values(ids:set) -> dict:
|
def _get_users_values(ids:set) -> dict:
|
||||||
user_model = get_model("users", "User")
|
user_model = apps.get_model("users", "User")
|
||||||
ids = filter(lambda x: x is not None, ids)
|
ids = filter(lambda x: x is not None, ids)
|
||||||
qs = user_model.objects.filter(pk__in=tuple(ids))
|
qs = user_model.objects.filter(pk__in=tuple(ids))
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ def milestone_freezer(milestone) -> dict:
|
||||||
|
|
||||||
|
|
||||||
def userstory_freezer(us) -> dict:
|
def userstory_freezer(us) -> dict:
|
||||||
rp_cls = get_model("userstories", "RolePoints")
|
rp_cls = apps.get_model("userstories", "RolePoints")
|
||||||
rpqsd = rp_cls.objects.filter(user_story=us)
|
rpqsd = rp_cls.objects.filter(user_story=us)
|
||||||
|
|
||||||
points = {}
|
points = {}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import uuid
|
||||||
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 django.db import models
|
from django.db import models
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django_pgjson.fields import JsonField
|
from django_pgjson.fields import JsonField
|
||||||
|
@ -79,7 +79,7 @@ class HistoryEntry(models.Model):
|
||||||
@cached_property
|
@cached_property
|
||||||
def owner(self):
|
def owner(self):
|
||||||
pk = self.user["pk"]
|
pk = self.user["pk"]
|
||||||
model = get_model("users", "User")
|
model = apps.get_model("users", "User")
|
||||||
return model.objects.get(pk=pk)
|
return model.objects.get(pk=pk)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
|
@ -35,7 +35,7 @@ from functools import lru_cache
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.paginator import Paginator, InvalidPage
|
from django.core.paginator import Paginator, InvalidPage
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.db import transaction as tx
|
from django.db import transaction as tx
|
||||||
|
|
||||||
from taiga.mdrender.service import render as mdrender
|
from taiga.mdrender.service import render as mdrender
|
||||||
|
@ -207,7 +207,7 @@ def _rebuild_snapshot_from_diffs(keysnapshot, partials):
|
||||||
|
|
||||||
|
|
||||||
def get_last_snapshot_for_key(key:str) -> FrozenObj:
|
def get_last_snapshot_for_key(key:str) -> FrozenObj:
|
||||||
entry_model = get_model("history", "HistoryEntry")
|
entry_model = apps.get_model("history", "HistoryEntry")
|
||||||
|
|
||||||
# Search last snapshot
|
# Search last snapshot
|
||||||
qs = (entry_model.objects
|
qs = (entry_model.objects
|
||||||
|
@ -251,7 +251,7 @@ def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False):
|
||||||
new_fobj = freeze_model_instance(obj)
|
new_fobj = freeze_model_instance(obj)
|
||||||
old_fobj, need_real_snapshot = get_last_snapshot_for_key(key)
|
old_fobj, need_real_snapshot = get_last_snapshot_for_key(key)
|
||||||
|
|
||||||
entry_model = get_model("history", "HistoryEntry")
|
entry_model = apps.get_model("history", "HistoryEntry")
|
||||||
user_id = None if user is None else user.id
|
user_id = None if user is None else user.id
|
||||||
user_name = "" if user is None else user.get_full_name()
|
user_name = "" if user is None else user.get_full_name()
|
||||||
|
|
||||||
|
@ -306,7 +306,7 @@ def get_history_queryset_by_model_instance(obj:object, types=(HistoryType.change
|
||||||
Get one page of history for specified object.
|
Get one page of history for specified object.
|
||||||
"""
|
"""
|
||||||
key = make_key_from_model_object(obj)
|
key = make_key_from_model_object(obj)
|
||||||
history_entry_model = get_model("history", "HistoryEntry")
|
history_entry_model = apps.get_model("history", "HistoryEntry")
|
||||||
|
|
||||||
qs = history_entry_model.objects.filter(key=key, type__in=types)
|
qs = history_entry_model.objects.filter(key=key, type__in=types)
|
||||||
if not include_hidden:
|
if not include_hidden:
|
||||||
|
|
|
@ -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",])
|
|
@ -19,7 +19,7 @@ import itertools
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
@ -211,8 +211,8 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
|
||||||
return user_model.objects.filter(id__in=list(members))
|
return user_model.objects.filter(id__in=list(members))
|
||||||
|
|
||||||
def update_role_points(self, user_stories=None):
|
def update_role_points(self, user_stories=None):
|
||||||
RolePoints = get_model("userstories", "RolePoints")
|
RolePoints = apps.get_model("userstories", "RolePoints")
|
||||||
Role = get_model("users", "Role")
|
Role = apps.get_model("users", "Role")
|
||||||
|
|
||||||
# Get all available roles on this project
|
# Get all available roles on this project
|
||||||
roles = self.get_roles().filter(computable=True)
|
roles = self.get_roles().filter(computable=True)
|
||||||
|
@ -251,7 +251,7 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
|
||||||
return dict_sum(*flat_role_dicts)
|
return dict_sum(*flat_role_dicts)
|
||||||
|
|
||||||
def _get_points_increment(self, client_requirement, team_requirement):
|
def _get_points_increment(self, client_requirement, team_requirement):
|
||||||
userstory_model = get_model("userstories", "UserStory")
|
userstory_model = apps.get_model("userstories", "UserStory")
|
||||||
user_stories = userstory_model.objects.none()
|
user_stories = userstory_model.objects.none()
|
||||||
last_milestones = self.milestones.order_by('-estimated_finish')
|
last_milestones = self.milestones.order_by('-estimated_finish')
|
||||||
last_milestone = last_milestones[0] if last_milestones else None
|
last_milestone = last_milestones[0] if last_milestones else None
|
||||||
|
@ -742,9 +742,9 @@ def membership_post_delete(sender, instance, using, **kwargs):
|
||||||
# On membership object is deleted, update watchers of all objects relation.
|
# On membership object is deleted, update watchers of all objects relation.
|
||||||
@receiver(signals.post_delete, sender=Membership, dispatch_uid='update_watchers_on_membership_post_delete')
|
@receiver(signals.post_delete, sender=Membership, dispatch_uid='update_watchers_on_membership_post_delete')
|
||||||
def update_watchers_on_membership_post_delete(sender, instance, using, **kwargs):
|
def update_watchers_on_membership_post_delete(sender, instance, using, **kwargs):
|
||||||
models = [get_model("userstories", "UserStory"),
|
models = [apps.get_model("userstories", "UserStory"),
|
||||||
get_model("tasks", "Task"),
|
apps.get_model("tasks", "Task"),
|
||||||
get_model("issues", "Issue")]
|
apps.get_model("issues", "Issue")]
|
||||||
|
|
||||||
# `user_id` is used beacuse in some momments
|
# `user_id` is used beacuse in some momments
|
||||||
# instance.user can contain pointer to now
|
# instance.user can contain pointer to now
|
||||||
|
|
|
@ -18,12 +18,9 @@ from functools import partial
|
||||||
from operator import is_not
|
from operator import is_not
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models.loading import get_model
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from taiga.projects.history.models import HistoryType
|
|
||||||
from taiga.projects.notifications import services
|
from taiga.projects.notifications import services
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,18 +141,3 @@ class WatchedModelMixin(models.Model):
|
||||||
self.get_owner(),)
|
self.get_owner(),)
|
||||||
is_not_none = partial(is_not, None)
|
is_not_none = partial(is_not, None)
|
||||||
return frozenset(filter(is_not_none, participants))
|
return frozenset(filter(is_not_none, participants))
|
||||||
|
|
||||||
|
|
||||||
# class WatcherValidationSerializerMixin(object):
|
|
||||||
# def validate_watchers(self, attrs, source):
|
|
||||||
# values = set(attrs.get(source, []))
|
|
||||||
# if values:
|
|
||||||
# project = None
|
|
||||||
# if "project" in attrs and attrs["project"]:
|
|
||||||
# project = attrs["project"]
|
|
||||||
# elif self.object:
|
|
||||||
# project = self.object.project
|
|
||||||
# model_cls = get_model("projects", "Membership")
|
|
||||||
# if len(values) != model_cls.objects.filter(project=project, user__in=values).count():
|
|
||||||
# raise serializers.ValidationError("Error, some watcher user is not a member of the project")
|
|
||||||
# return attrs
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ def notify_policy_exists(project, user) -> bool:
|
||||||
Check if policy exists for specified project
|
Check if policy exists for specified project
|
||||||
and user.
|
and user.
|
||||||
"""
|
"""
|
||||||
model_cls = get_model("notifications", "NotifyPolicy")
|
model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
qs = model_cls.objects.filter(project=project,
|
qs = model_cls.objects.filter(project=project,
|
||||||
user=user)
|
user=user)
|
||||||
return qs.exists()
|
return qs.exists()
|
||||||
|
@ -43,7 +43,7 @@ def create_notify_policy(project, user, level=NotifyLevel.notwatch):
|
||||||
"""
|
"""
|
||||||
Given a project and user, create notification policy for it.
|
Given a project and user, create notification policy for it.
|
||||||
"""
|
"""
|
||||||
model_cls = get_model("notifications", "NotifyPolicy")
|
model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
try:
|
try:
|
||||||
return model_cls.objects.create(project=project,
|
return model_cls.objects.create(project=project,
|
||||||
user=user,
|
user=user,
|
||||||
|
@ -56,7 +56,7 @@ def create_notify_policy_if_not_exists(project, user, level=NotifyLevel.notwatch
|
||||||
"""
|
"""
|
||||||
Given a project and user, create notification policy for it.
|
Given a project and user, create notification policy for it.
|
||||||
"""
|
"""
|
||||||
model_cls = get_model("notifications", "NotifyPolicy")
|
model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
try:
|
try:
|
||||||
result = model_cls.objects.get_or_create(project=project,
|
result = model_cls.objects.get_or_create(project=project,
|
||||||
user=user,
|
user=user,
|
||||||
|
@ -70,7 +70,7 @@ def get_notify_policy(project, user):
|
||||||
"""
|
"""
|
||||||
Get notification level for specified project and user.
|
Get notification level for specified project and user.
|
||||||
"""
|
"""
|
||||||
model_cls = get_model("notifications", "NotifyPolicy")
|
model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
instance, _ = model_cls.objects.get_or_create(project=project, user=user,
|
instance, _ = model_cls.objects.get_or_create(project=project, user=user,
|
||||||
defaults={"notify_level": NotifyLevel.notwatch})
|
defaults={"notify_level": NotifyLevel.notwatch})
|
||||||
return instance
|
return instance
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -37,7 +37,7 @@ class ResolverViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
|
|
||||||
project_model = get_model("projects", "Project")
|
project_model = apps.get_model("projects", "Project")
|
||||||
project = get_object_or_404(project_model, slug=data["project"])
|
project = get_object_or_404(project_model, slug=data["project"])
|
||||||
|
|
||||||
self.check_permissions(request, "list", project)
|
self.check_permissions(request, "list", project)
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
def get_instance_by_ref(project_id, obj_ref):
|
def get_instance_by_ref(project_id, obj_ref):
|
||||||
model_cls = get_model("references", "Reference")
|
model_cls = apps.get_model("references", "Reference")
|
||||||
try:
|
try:
|
||||||
instance = model_cls.objects.get(project_id=project_id, ref=obj_ref)
|
instance = model_cls.objects.get(project_id=project_id, ref=obj_ref)
|
||||||
except model_cls.DoesNotExist:
|
except model_cls.DoesNotExist:
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from django.db.models import get_model
|
from django.apps import apps
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin
|
from taiga.base.serializers import Serializer, PickleField, NeighborsSerializerMixin
|
||||||
|
@ -56,7 +56,7 @@ class UserStorySerializer(serializers.ModelSerializer):
|
||||||
role_points = obj._related_data.pop("role_points", None)
|
role_points = obj._related_data.pop("role_points", None)
|
||||||
super().save_object(obj, **kwargs)
|
super().save_object(obj, **kwargs)
|
||||||
|
|
||||||
points_modelcls = get_model("projects", "Points")
|
points_modelcls = apps.get_model("projects", "Points")
|
||||||
|
|
||||||
if role_points:
|
if role_points:
|
||||||
for role_id, points_id in role_points.items():
|
for role_id, points_id in role_points.items():
|
||||||
|
|
|
@ -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)
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from .models import Votes, Vote
|
from .models import Votes, Vote
|
||||||
|
@ -32,7 +32,7 @@ def add_vote(obj, user):
|
||||||
:param obj: Any Django model instance.
|
:param obj: Any Django model instance.
|
||||||
:param user: User adding the vote. :class:`~taiga.users.models.User` instance.
|
:param user: User adding the vote. :class:`~taiga.users.models.User` instance.
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
with atomic():
|
with atomic():
|
||||||
vote, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
|
vote, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ def remove_vote(obj, user):
|
||||||
:param obj: Any Django model instance.
|
:param obj: Any Django model instance.
|
||||||
:param user: User removing her vote. :class:`~taiga.users.models.User` instance.
|
:param user: User removing her vote. :class:`~taiga.users.models.User` instance.
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
with atomic():
|
with atomic():
|
||||||
qs = Vote.objects.filter(content_type=obj_type, object_id=obj.id, user=user)
|
qs = Vote.objects.filter(content_type=obj_type, object_id=obj.id, user=user)
|
||||||
if not qs.exists():
|
if not qs.exists():
|
||||||
|
@ -74,7 +74,7 @@ def get_voters(obj):
|
||||||
|
|
||||||
:return: User queryset object representing the users that voted the object.
|
:return: User queryset object representing the users that voted the object.
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id)
|
return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ def get_votes(obj):
|
||||||
|
|
||||||
:return: Number of votes or `0` if the object has no votes at all.
|
:return: Number of votes or `0` if the object has no votes at all.
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return Votes.objects.get(content_type=obj_type, object_id=obj.id).count
|
return Votes.objects.get(content_type=obj_type, object_id=obj.id).count
|
||||||
|
@ -101,7 +101,7 @@ def get_voted(user_or_id, model):
|
||||||
|
|
||||||
:return: Queryset of objects representing the votes of the user.
|
:return: Queryset of objects representing the votes of the user.
|
||||||
"""
|
"""
|
||||||
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
||||||
conditions = ('votes_vote.content_type_id = %s',
|
conditions = ('votes_vote.content_type_id = %s',
|
||||||
'%s.id = votes_vote.object_id' % model._meta.db_table,
|
'%s.id = votes_vote.object_id' % model._meta.db_table,
|
||||||
'votes_vote.user_id = %s')
|
'votes_vote.user_id = %s')
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
def attach_votescount_to_queryset(queryset, as_field="votes_count"):
|
def attach_votescount_to_queryset(queryset, as_field="votes_count"):
|
||||||
|
@ -33,7 +33,7 @@ def attach_votescount_to_queryset(queryset, as_field="votes_count"):
|
||||||
:return: Queryset object with the additional `as_field` field.
|
:return: Queryset object with the additional `as_field` field.
|
||||||
"""
|
"""
|
||||||
model = queryset.model
|
model = queryset.model
|
||||||
type = get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
||||||
sql = ("SELECT coalesce(votes_votes.count, 0) FROM votes_votes "
|
sql = ("SELECT coalesce(votes_votes.count, 0) FROM votes_votes "
|
||||||
"WHERE votes_votes.content_type_id = {type_id} AND votes_votes.object_id = {tbl}.id")
|
"WHERE votes_votes.content_type_id = {type_id} AND votes_votes.object_id = {tbl}.id")
|
||||||
sql = sql.format(type_id=type.id, tbl=model._meta.db_table)
|
sql = sql.format(type_id=type.id, tbl=model._meta.db_table)
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
@ -31,7 +31,7 @@ from . import services
|
||||||
|
|
||||||
class SearchViewSet(viewsets.ViewSet):
|
class SearchViewSet(viewsets.ViewSet):
|
||||||
def list(self, request, **kwargs):
|
def list(self, request, **kwargs):
|
||||||
project_model = get_model("projects", "Project")
|
project_model = apps.get_model("projects", "Project")
|
||||||
|
|
||||||
text = request.QUERY_PARAMS.get('text', "")
|
text = request.QUERY_PARAMS.get('text', "")
|
||||||
project_id = request.QUERY_PARAMS.get('project', None)
|
project_id = request.QUERY_PARAMS.get('project', None)
|
||||||
|
@ -55,7 +55,7 @@ class SearchViewSet(viewsets.ViewSet):
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
def _get_project(self, project_id):
|
def _get_project(self, project_id):
|
||||||
project_model = get_model("projects", "Project")
|
project_model = apps.get_model("projects", "Project")
|
||||||
return project_model.objects.get(pk=project_id)
|
return project_model.objects.get(pk=project_id)
|
||||||
|
|
||||||
def _search_user_stories(self, project, text):
|
def _search_user_stories(self, project, text):
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ MAX_RESULTS = getattr(settings, "SEARCHES_MAX_RESULTS", 150)
|
||||||
|
|
||||||
|
|
||||||
def search_user_stories(project, text):
|
def search_user_stories(project, text):
|
||||||
model_cls = get_model("userstories", "UserStory")
|
model_cls = apps.get_model("userstories", "UserStory")
|
||||||
where_clause = ("to_tsvector(coalesce(userstories_userstory.subject) || ' ' || "
|
where_clause = ("to_tsvector(coalesce(userstories_userstory.subject) || ' ' || "
|
||||||
"coalesce(userstories_userstory.description)) @@ plainto_tsquery(%s)")
|
"coalesce(userstories_userstory.description)) @@ plainto_tsquery(%s)")
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ def search_user_stories(project, text):
|
||||||
|
|
||||||
|
|
||||||
def search_tasks(project, text):
|
def search_tasks(project, text):
|
||||||
model_cls = get_model("tasks", "Task")
|
model_cls = apps.get_model("tasks", "Task")
|
||||||
where_clause = ("to_tsvector(coalesce(tasks_task.subject, '') || ' ' || coalesce(tasks_task.description, '')) "
|
where_clause = ("to_tsvector(coalesce(tasks_task.subject, '') || ' ' || coalesce(tasks_task.description, '')) "
|
||||||
"@@ plainto_tsquery(%s)")
|
"@@ plainto_tsquery(%s)")
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ def search_tasks(project, text):
|
||||||
|
|
||||||
|
|
||||||
def search_issues(project, text):
|
def search_issues(project, text):
|
||||||
model_cls = get_model("issues", "Issue")
|
model_cls = apps.get_model("issues", "Issue")
|
||||||
where_clause = ("to_tsvector(coalesce(issues_issue.subject) || ' ' || coalesce(issues_issue.description)) "
|
where_clause = ("to_tsvector(coalesce(issues_issue.subject) || ' ' || coalesce(issues_issue.description)) "
|
||||||
"@@ plainto_tsquery(%s)")
|
"@@ plainto_tsquery(%s)")
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ def search_issues(project, text):
|
||||||
|
|
||||||
|
|
||||||
def search_wiki_pages(project, text):
|
def search_wiki_pages(project, text):
|
||||||
model_cls = get_model("wiki", "WikiPage")
|
model_cls = apps.get_model("wiki", "WikiPage")
|
||||||
where_clause = ("to_tsvector(coalesce(wiki_wikipage.slug) || ' ' || coalesce(wiki_wikipage.content)) "
|
where_clause = ("to_tsvector(coalesce(wiki_wikipage.slug) || ' ' || coalesce(wiki_wikipage.content)) "
|
||||||
"@@ plainto_tsquery(%s)")
|
"@@ plainto_tsquery(%s)")
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,6 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
|
||||||
from django.db.models import signals
|
|
||||||
|
|
||||||
from taiga.timeline.service import push_to_timeline
|
from taiga.timeline.service import push_to_timeline
|
||||||
|
|
||||||
# TODO: Add events to followers timeline when followers are implemented.
|
# TODO: Add events to followers timeline when followers are implemented.
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -48,7 +48,7 @@ class MembersFilterBackend(BaseFilterBackend):
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
project_id = request.QUERY_PARAMS.get('project', None)
|
project_id = request.QUERY_PARAMS.get('project', None)
|
||||||
if project_id:
|
if project_id:
|
||||||
Project = get_model('projects', 'Project')
|
Project = apps.get_model('projects', 'Project')
|
||||||
project = get_object_or_404(Project, pk=project_id)
|
project = get_object_or_404(Project, pk=project_id)
|
||||||
if project.memberships.filter(user=request.user).exists() or project.owner == request.user:
|
if project.memberships.filter(user=request.user).exists() or project.owner == request.user:
|
||||||
return queryset.filter(Q(memberships__project=project) | Q(id=project.owner.id)).distinct()
|
return queryset.filter(Q(memberships__project=project) | Q(id=project.owner.id)).distinct()
|
||||||
|
@ -201,7 +201,7 @@ class UsersViewSet(ModelCrudViewSet):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
self.check_permissions(request, 'starred', user)
|
self.check_permissions(request, 'starred', user)
|
||||||
|
|
||||||
stars = votes_service.get_voted(user.pk, model=get_model('projects', 'Project'))
|
stars = votes_service.get_voted(user.pk, model=apps.get_model('projects', 'Project'))
|
||||||
stars_data = StarredSerializer(stars, many=True)
|
stars_data = StarredSerializer(stars, many=True)
|
||||||
return Response(stars_data.data)
|
return Response(stars_data.data)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
This model contains a domain logic for users application.
|
This model contains a domain logic for users application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ def get_and_validate_user(*, username:str, password:str) -> bool:
|
||||||
exception is raised.
|
exception is raised.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user_model = get_model("users", "User")
|
user_model = apps.get_model("users", "User")
|
||||||
qs = user_model.objects.filter(Q(username=username) |
|
qs = user_model.objects.filter(Q(username=username) |
|
||||||
Q(email=username))
|
Q(email=username))
|
||||||
if len(qs) == 0:
|
if len(qs) == 0:
|
||||||
|
|
|
@ -19,7 +19,6 @@ import uuid
|
||||||
import threading
|
import threading
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
import factory
|
import factory
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.projects.serializers import ProjectDetailSerializer
|
from taiga.projects.serializers import ProjectDetailSerializer
|
||||||
|
@ -53,8 +53,8 @@ def data():
|
||||||
role__project=m.private_project2,
|
role__project=m.private_project2,
|
||||||
role__permissions=[])
|
role__permissions=[])
|
||||||
|
|
||||||
ContentType = get_model("contenttypes", "ContentType")
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
Project = get_model("projects", "Project")
|
Project = apps.get_model("projects", "Project")
|
||||||
|
|
||||||
project_ct = ContentType.objects.get_for_model(Project)
|
project_ct = ContentType.objects.get_for_model(Project)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from .. import factories
|
from .. import factories
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ def test_response_200_in_registration_with_github_account(client):
|
||||||
|
|
||||||
|
|
||||||
def test_response_200_in_registration_with_github_account_in_a_project(client):
|
def test_response_200_in_registration_with_github_account_in_a_project(client):
|
||||||
membership_model = get_model("projects", "Membership")
|
membership_model = apps.get_model("projects", "Membership")
|
||||||
membership = factories.MembershipFactory(user=None)
|
membership = factories.MembershipFactory(user=None)
|
||||||
form = {"type": "github",
|
form = {"type": "github",
|
||||||
"code": "xxxxxx",
|
"code": "xxxxxx",
|
||||||
|
|
|
@ -21,7 +21,6 @@ from unittest.mock import MagicMock
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db.models.loading import get_model
|
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
from taiga.projects.history import services
|
from taiga.projects.history import services
|
||||||
|
|
|
@ -20,7 +20,7 @@ import pytest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db.models.loading import get_model
|
from django.apps import apps
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
from taiga.projects.notifications import services
|
from taiga.projects.notifications import services
|
||||||
|
@ -59,7 +59,7 @@ def test_attach_notify_policy_to_project_queryset():
|
||||||
def test_create_retrieve_notify_policy():
|
def test_create_retrieve_notify_policy():
|
||||||
project = f.ProjectFactory.create()
|
project = f.ProjectFactory.create()
|
||||||
|
|
||||||
policy_model_cls = get_model("notifications", "NotifyPolicy")
|
policy_model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
current_number = policy_model_cls.objects.all().count()
|
current_number = policy_model_cls.objects.all().count()
|
||||||
assert current_number == 0
|
assert current_number == 0
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ def test_users_to_notify():
|
||||||
member2 = f.MembershipFactory.create(project=project)
|
member2 = f.MembershipFactory.create(project=project)
|
||||||
member3 = f.MembershipFactory.create(project=project)
|
member3 = f.MembershipFactory.create(project=project)
|
||||||
|
|
||||||
policy_model_cls = get_model("notifications", "NotifyPolicy")
|
policy_model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
|
|
||||||
policy1 = policy_model_cls.objects.get(user=member1.user)
|
policy1 = policy_model_cls.objects.get(user=member1.user)
|
||||||
policy2 = policy_model_cls.objects.get(user=member2.user)
|
policy2 = policy_model_cls.objects.get(user=member2.user)
|
||||||
|
|
Loading…
Reference in New Issue