Working in history and timeline for epics (initial version)
parent
389a18026b
commit
a7c262ffdc
|
@ -168,6 +168,11 @@ class HistoryViewSet(ReadOnlyListViewSet):
|
||||||
return self.response_for_queryset(qs)
|
return self.response_for_queryset(qs)
|
||||||
|
|
||||||
|
|
||||||
|
class EpicHistory(HistoryViewSet):
|
||||||
|
content_type = "epics.epic"
|
||||||
|
permission_classes = (permissions.EpicHistoryPermission,)
|
||||||
|
|
||||||
|
|
||||||
class UserStoryHistory(HistoryViewSet):
|
class UserStoryHistory(HistoryViewSet):
|
||||||
content_type = "userstories.userstory"
|
content_type = "userstories.userstory"
|
||||||
permission_classes = (permissions.UserStoryHistoryPermission,)
|
permission_classes = (permissions.UserStoryHistoryPermission,)
|
||||||
|
|
|
@ -106,6 +106,17 @@ def milestone_values(diff):
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def epic_values(diff):
|
||||||
|
values = _common_users_values(diff)
|
||||||
|
|
||||||
|
if "status" in diff:
|
||||||
|
values["status"] = _get_us_status_values(diff["status"])
|
||||||
|
|
||||||
|
# TODO EPICS: What happen with usr stories?
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
def userstory_values(diff):
|
def userstory_values(diff):
|
||||||
values = _common_users_values(diff)
|
values = _common_users_values(diff)
|
||||||
|
|
||||||
|
@ -190,6 +201,18 @@ def extract_attachments(obj) -> list:
|
||||||
"order": attach.order}
|
"order": attach.order}
|
||||||
|
|
||||||
|
|
||||||
|
@as_tuple
|
||||||
|
def extract_epic_custom_attributes(obj) -> list:
|
||||||
|
with suppress(ObjectDoesNotExist):
|
||||||
|
custom_attributes_values = obj.custom_attributes_values.attributes_values
|
||||||
|
for attr in obj.project.epiccustomattributes.all():
|
||||||
|
with suppress(KeyError):
|
||||||
|
value = custom_attributes_values[str(attr.id)]
|
||||||
|
yield {"id": attr.id,
|
||||||
|
"name": attr.name,
|
||||||
|
"value": value}
|
||||||
|
|
||||||
|
|
||||||
@as_tuple
|
@as_tuple
|
||||||
def extract_user_story_custom_attributes(obj) -> list:
|
def extract_user_story_custom_attributes(obj) -> list:
|
||||||
with suppress(ObjectDoesNotExist):
|
with suppress(ObjectDoesNotExist):
|
||||||
|
@ -235,6 +258,7 @@ def project_freezer(project) -> dict:
|
||||||
"total_milestones",
|
"total_milestones",
|
||||||
"total_story_points",
|
"total_story_points",
|
||||||
"tags",
|
"tags",
|
||||||
|
"is_epics_activated",
|
||||||
"is_backlog_activated",
|
"is_backlog_activated",
|
||||||
"is_kanban_activated",
|
"is_kanban_activated",
|
||||||
"is_wiki_activated",
|
"is_wiki_activated",
|
||||||
|
@ -256,6 +280,31 @@ def milestone_freezer(milestone) -> dict:
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
|
def epic_freezer(epic) -> dict:
|
||||||
|
snapshot = {
|
||||||
|
"ref": epic.ref,
|
||||||
|
"owner": epic.owner_id,
|
||||||
|
"status": epic.status.id if epic.status else None,
|
||||||
|
"is_closed": epic.is_closed,
|
||||||
|
"finish_date": str(epic.finish_date),
|
||||||
|
"epics_order": epic.epics_order,
|
||||||
|
"subject": epic.subject,
|
||||||
|
"description": epic.description,
|
||||||
|
"description_html": mdrender(epic.project, epic.description),
|
||||||
|
"assigned_to": epic.assigned_to_id,
|
||||||
|
"client_requirement": epic.client_requirement,
|
||||||
|
"team_requirement": epic.team_requirement,
|
||||||
|
"attachments": extract_attachments(epic),
|
||||||
|
"tags": epic.tags,
|
||||||
|
"is_blocked": epic.is_blocked,
|
||||||
|
"blocked_note": epic.blocked_note,
|
||||||
|
"blocked_note_html": mdrender(epic.project, epic.blocked_note),
|
||||||
|
"custom_attributes": extract_epic_custom_attributes(epic),
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
|
||||||
def userstory_freezer(us) -> dict:
|
def userstory_freezer(us) -> dict:
|
||||||
rp_cls = apps.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)
|
||||||
|
|
|
@ -42,6 +42,14 @@ class IsCommentProjectAdmin(PermissionComponent):
|
||||||
return is_project_admin(request.user, project)
|
return is_project_admin(request.user, project)
|
||||||
|
|
||||||
|
|
||||||
|
class EpicHistoryPermission(TaigaResourcePermission):
|
||||||
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
|
edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
|
||||||
|
delete_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
|
||||||
|
undelete_comment_perms = IsCommentProjectAdmin() | IsCommentDeleter()
|
||||||
|
comment_versions_perms = IsCommentProjectAdmin() | IsCommentOwner()
|
||||||
|
|
||||||
|
|
||||||
class UserStoryHistoryPermission(TaigaResourcePermission):
|
class UserStoryHistoryPermission(TaigaResourcePermission):
|
||||||
retrieve_perms = HasProjectPerm('view_project')
|
retrieve_perms = HasProjectPerm('view_project')
|
||||||
edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
|
edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
|
||||||
|
|
|
@ -50,6 +50,7 @@ from .models import HistoryType
|
||||||
# Freeze implementatitions
|
# Freeze implementatitions
|
||||||
from .freeze_impl import project_freezer
|
from .freeze_impl import project_freezer
|
||||||
from .freeze_impl import milestone_freezer
|
from .freeze_impl import milestone_freezer
|
||||||
|
from .freeze_impl import epic_freezer
|
||||||
from .freeze_impl import userstory_freezer
|
from .freeze_impl import userstory_freezer
|
||||||
from .freeze_impl import issue_freezer
|
from .freeze_impl import issue_freezer
|
||||||
from .freeze_impl import task_freezer
|
from .freeze_impl import task_freezer
|
||||||
|
@ -58,6 +59,7 @@ from .freeze_impl import wikipage_freezer
|
||||||
|
|
||||||
from .freeze_impl import project_values
|
from .freeze_impl import project_values
|
||||||
from .freeze_impl import milestone_values
|
from .freeze_impl import milestone_values
|
||||||
|
from .freeze_impl import epic_values
|
||||||
from .freeze_impl import userstory_values
|
from .freeze_impl import userstory_values
|
||||||
from .freeze_impl import issue_values
|
from .freeze_impl import issue_values
|
||||||
from .freeze_impl import task_values
|
from .freeze_impl import task_values
|
||||||
|
@ -337,10 +339,7 @@ def take_snapshot(obj: object, *, comment: str="", user=None, delete: bool=False
|
||||||
|
|
||||||
# If diff and comment are empty, do
|
# If diff and comment are empty, do
|
||||||
# not create empty history entry
|
# not create empty history entry
|
||||||
if (not fdiff.diff and not comment
|
if (not fdiff.diff and not comment and old_fobj is not None and entry_type != HistoryType.delete):
|
||||||
and old_fobj is not None
|
|
||||||
and entry_type != HistoryType.delete):
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
fvals = make_diff_values(typename, fdiff)
|
fvals = make_diff_values(typename, fdiff)
|
||||||
|
@ -394,8 +393,10 @@ def prefetch_owners_in_history_queryset(qs):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
# Freeze & value register
|
||||||
register_freeze_implementation("projects.project", project_freezer)
|
register_freeze_implementation("projects.project", project_freezer)
|
||||||
register_freeze_implementation("milestones.milestone", milestone_freezer,)
|
register_freeze_implementation("milestones.milestone", milestone_freezer,)
|
||||||
|
register_freeze_implementation("epics.epic", epic_freezer)
|
||||||
register_freeze_implementation("userstories.userstory", userstory_freezer)
|
register_freeze_implementation("userstories.userstory", userstory_freezer)
|
||||||
register_freeze_implementation("issues.issue", issue_freezer)
|
register_freeze_implementation("issues.issue", issue_freezer)
|
||||||
register_freeze_implementation("tasks.task", task_freezer)
|
register_freeze_implementation("tasks.task", task_freezer)
|
||||||
|
@ -403,6 +404,7 @@ register_freeze_implementation("wiki.wikipage", wikipage_freezer)
|
||||||
|
|
||||||
register_values_implementation("projects.project", project_values)
|
register_values_implementation("projects.project", project_values)
|
||||||
register_values_implementation("milestones.milestone", milestone_values)
|
register_values_implementation("milestones.milestone", milestone_values)
|
||||||
|
register_values_implementation("epics.epic", epic_values)
|
||||||
register_values_implementation("userstories.userstory", userstory_values)
|
register_values_implementation("userstories.userstory", userstory_values)
|
||||||
register_values_implementation("issues.issue", issue_values)
|
register_values_implementation("issues.issue", issue_values)
|
||||||
register_values_implementation("tasks.task", task_values)
|
register_values_implementation("tasks.task", task_values)
|
||||||
|
|
|
@ -30,15 +30,6 @@ from taiga.permissions.services import calculate_permissions
|
||||||
from taiga.permissions.services import is_project_admin, is_project_owner
|
from taiga.permissions.services import is_project_admin, is_project_owner
|
||||||
|
|
||||||
from . import services
|
from . import services
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
from .custom_attributes.serializers import EpicCustomAttributeSerializer
|
|
||||||
from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
|
|
||||||
from .custom_attributes.serializers import TaskCustomAttributeSerializer
|
|
||||||
from .custom_attributes.serializers import IssueCustomAttributeSerializer
|
|
||||||
from .likes.mixins.serializers import FanResourceSerializerMixin
|
|
||||||
from .mixins.serializers import ValidateDuplicatedNameInProjectMixin
|
|
||||||
>>>>>>> 5f3559d... Epic custom attributes values
|
|
||||||
from .notifications.choices import NotifyLevel
|
from .notifications.choices import NotifyLevel
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,6 +213,7 @@ class ProjectSerializer(serializers.LightSerializer):
|
||||||
members = MethodField()
|
members = MethodField()
|
||||||
total_milestones = Field()
|
total_milestones = Field()
|
||||||
total_story_points = Field()
|
total_story_points = Field()
|
||||||
|
is_epics_activated = Field()
|
||||||
is_backlog_activated = Field()
|
is_backlog_activated = Field()
|
||||||
is_kanban_activated = Field()
|
is_kanban_activated = Field()
|
||||||
is_wiki_activated = Field()
|
is_wiki_activated = Field()
|
||||||
|
@ -249,6 +241,7 @@ class ProjectSerializer(serializers.LightSerializer):
|
||||||
tags = Field()
|
tags = Field()
|
||||||
tags_colors = MethodField()
|
tags_colors = MethodField()
|
||||||
|
|
||||||
|
default_epic_status = Field(attr="default_epic_status_id")
|
||||||
default_points = Field(attr="default_points_id")
|
default_points = Field(attr="default_points_id")
|
||||||
default_us_status = Field(attr="default_us_status_id")
|
default_us_status = Field(attr="default_us_status_id")
|
||||||
default_task_status = Field(attr="default_task_status_id")
|
default_task_status = Field(attr="default_task_status_id")
|
||||||
|
@ -300,8 +293,7 @@ class ProjectSerializer(serializers.LightSerializer):
|
||||||
def get_my_permissions(self, obj):
|
def get_my_permissions(self, obj):
|
||||||
if "request" in self.context:
|
if "request" in self.context:
|
||||||
user = self.context["request"].user
|
user = self.context["request"].user
|
||||||
return calculate_permissions(
|
return calculate_permissions(is_authenticated=user.is_authenticated(),
|
||||||
is_authenticated=user.is_authenticated(),
|
|
||||||
is_superuser=user.is_superuser,
|
is_superuser=user.is_superuser,
|
||||||
is_member=self.get_i_am_member(obj),
|
is_member=self.get_i_am_member(obj),
|
||||||
is_admin=self.get_i_am_admin(obj),
|
is_admin=self.get_i_am_admin(obj),
|
||||||
|
@ -441,10 +433,8 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
return len(obj.members_attr)
|
return len(obj.members_attr)
|
||||||
|
|
||||||
def get_is_out_of_owner_limits(self, obj):
|
def get_is_out_of_owner_limits(self, obj):
|
||||||
assert (hasattr(obj, "private_projects_same_owner_attr"),
|
assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
|
||||||
"instance must have a private_projects_same_owner_attr attribute"
|
assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
|
||||||
assert (hasattr(obj, "public_projects_same_owner_attr"),
|
|
||||||
"instance must have a public_projects_same_owner_attr attribute"
|
|
||||||
return services.check_if_project_is_out_of_owner_limits(
|
return services.check_if_project_is_out_of_owner_limits(
|
||||||
obj,
|
obj,
|
||||||
current_memberships=self.get_total_memberships(obj),
|
current_memberships=self.get_total_memberships(obj),
|
||||||
|
@ -453,10 +443,8 @@ class ProjectDetailSerializer(ProjectSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_is_private_extra_info(self, obj):
|
def get_is_private_extra_info(self, obj):
|
||||||
assert (hasattr(obj, "private_projects_same_owner_attr"),
|
assert hasattr(obj, "private_projects_same_owner_attr"), "instance must have a private_projects_same_owner_attr attribute"
|
||||||
"instance must have a private_projects_same_owner_attr attribute"
|
assert hasattr(obj, "public_projects_same_owner_attr"), "instance must have a public_projects_same_owner_attr attribute"
|
||||||
assert (hasattr(obj, "public_projects_same_owner_attr"),
|
|
||||||
"instance must have a public_projects_same_owner_attr attribute"
|
|
||||||
return services.check_if_project_privacity_can_be_changed(
|
return services.check_if_project_privacity_can_be_changed(
|
||||||
obj,
|
obj,
|
||||||
current_memberships=self.get_total_memberships(obj),
|
current_memberships=self.get_total_memberships(obj),
|
||||||
|
|
|
@ -196,11 +196,13 @@ router.register(r"wiki-links", WikiLinkViewSet,
|
||||||
|
|
||||||
|
|
||||||
# History & Components
|
# History & Components
|
||||||
|
from taiga.projects.history.api import EpicHistory
|
||||||
from taiga.projects.history.api import UserStoryHistory
|
from taiga.projects.history.api import UserStoryHistory
|
||||||
from taiga.projects.history.api import TaskHistory
|
from taiga.projects.history.api import TaskHistory
|
||||||
from taiga.projects.history.api import IssueHistory
|
from taiga.projects.history.api import IssueHistory
|
||||||
from taiga.projects.history.api import WikiHistory
|
from taiga.projects.history.api import WikiHistory
|
||||||
|
|
||||||
|
router.register(r"history/epic", EpicHistory, base_name="epic-history")
|
||||||
router.register(r"history/userstory", UserStoryHistory, base_name="userstory-history")
|
router.register(r"history/userstory", UserStoryHistory, base_name="userstory-history")
|
||||||
router.register(r"history/task", TaskHistory, base_name="task-history")
|
router.register(r"history/task", TaskHistory, base_name="task-history")
|
||||||
router.register(r"history/issue", IssueHistory, base_name="issue-history")
|
router.register(r"history/issue", IssueHistory, base_name="issue-history")
|
||||||
|
|
|
@ -85,6 +85,7 @@ class TimelineViewSet(ReadOnlyListViewSet):
|
||||||
event_type::text = ANY('{issues.issue.change,
|
event_type::text = ANY('{issues.issue.change,
|
||||||
tasks.task.change,
|
tasks.task.change,
|
||||||
userstories.userstory.change,
|
userstories.userstory.change,
|
||||||
|
epics.epic.change,
|
||||||
wiki.wikipage.change}'::text[])
|
wiki.wikipage.change}'::text[])
|
||||||
)
|
)
|
||||||
"""])
|
"""])
|
||||||
|
@ -92,6 +93,7 @@ class TimelineViewSet(ReadOnlyListViewSet):
|
||||||
qs = qs.exclude(event_type__in=["issues.issue.delete",
|
qs = qs.exclude(event_type__in=["issues.issue.delete",
|
||||||
"tasks.task.delete",
|
"tasks.task.delete",
|
||||||
"userstories.userstory.delete",
|
"userstories.userstory.delete",
|
||||||
|
"epics.epic.delete",
|
||||||
"wiki.wikipage.delete",
|
"wiki.wikipage.delete",
|
||||||
"projects.project.change"])
|
"projects.project.change"])
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ from django.core.management.base import BaseCommand
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
from taiga.timeline.service import (_get_impl_key_from_model,
|
from taiga.timeline.service import _get_impl_key_from_model,
|
||||||
_timeline_impl_map, extract_user_info)
|
from taiga.timeline.service import _timeline_impl_map,
|
||||||
|
from taiga.timeline.service import extract_user_info)
|
||||||
from taiga.timeline.models import Timeline
|
from taiga.timeline.models import Timeline
|
||||||
from taiga.timeline.signals import _push_to_timelines
|
from taiga.timeline.signals import _push_to_timelines
|
||||||
|
|
||||||
|
@ -54,7 +55,8 @@ class BulkCreator(object):
|
||||||
bulk_creator = BulkCreator()
|
bulk_creator = BulkCreator()
|
||||||
|
|
||||||
|
|
||||||
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, namespace:str="default", extra_data:dict={}):
|
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object,
|
||||||
|
namespace:str="default", extra_data:dict={}):
|
||||||
assert isinstance(obj, Model), "obj must be a instance of Model"
|
assert isinstance(obj, Model), "obj must be a instance of Model"
|
||||||
assert isinstance(instance, Model), "instance must be a instance of Model"
|
assert isinstance(instance, Model), "instance must be a instance of Model"
|
||||||
event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
|
event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
|
||||||
|
|
|
@ -40,7 +40,9 @@ def update_timeline(initial_date, final_date):
|
||||||
|
|
||||||
print("Generating tasks indexed by id dict")
|
print("Generating tasks indexed by id dict")
|
||||||
task_ids = timelines.values_list("object_id", flat=True)
|
task_ids = timelines.values_list("object_id", flat=True)
|
||||||
tasks_per_id = {task.id: task for task in Task.objects.filter(id__in=task_ids).select_related("user_story").iterator()}
|
|
||||||
|
tasks_iterator = Task.objects.filter(id__in=task_ids).select_related("user_story").iterator()
|
||||||
|
tasks_per_id = {task.id: task for task in tasks_iterator}
|
||||||
del task_ids
|
del task_ids
|
||||||
|
|
||||||
counter = 1
|
counter = 1
|
||||||
|
|
|
@ -58,7 +58,8 @@ class BulkCreator(object):
|
||||||
bulk_creator = BulkCreator()
|
bulk_creator = BulkCreator()
|
||||||
|
|
||||||
|
|
||||||
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object, namespace:str="default", extra_data:dict={}):
|
def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, created_datetime:object,
|
||||||
|
namespace:str="default", extra_data:dict={}):
|
||||||
assert isinstance(obj, Model), "obj must be a instance of Model"
|
assert isinstance(obj, Model), "obj must be a instance of Model"
|
||||||
assert isinstance(instance, Model), "instance must be a instance of Model"
|
assert isinstance(instance, Model), "instance must be a instance of Model"
|
||||||
event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
|
event_type_key = _get_impl_key_from_model(instance.__class__, event_type)
|
||||||
|
@ -102,11 +103,13 @@ def generate_timeline(initial_date, final_date, project_id):
|
||||||
|
|
||||||
if project_id:
|
if project_id:
|
||||||
project = Project.objects.get(id=project_id)
|
project = Project.objects.get(id=project_id)
|
||||||
us_keys = ['userstories.userstory:%s'%(id) for id in project.user_stories.values_list("id", flat=True)]
|
epic_keys = ['epics.epic:%s'%(id) for id in project.epics.values_list("id", flat=True)]
|
||||||
|
us_keys = ['userstories.userstory:%s'%(id) for id in project.user_stories.values_list("id",
|
||||||
|
flat=True)]
|
||||||
tasks_keys = ['tasks.task:%s'%(id) for id in project.tasks.values_list("id", flat=True)]
|
tasks_keys = ['tasks.task:%s'%(id) for id in project.tasks.values_list("id", flat=True)]
|
||||||
issue_keys = ['issues.issue:%s'%(id) for id in project.issues.values_list("id", flat=True)]
|
issue_keys = ['issues.issue:%s'%(id) for id in project.issues.values_list("id", flat=True)]
|
||||||
wiki_keys = ['wiki.wikipage:%s'%(id) for id in project.wiki_pages.values_list("id", flat=True)]
|
wiki_keys = ['wiki.wikipage:%s'%(id) for id in project.wiki_pages.values_list("id", flat=True)]
|
||||||
keys = us_keys + tasks_keys + issue_keys + wiki_keys
|
keys = epic_keys + us_keys + tasks_keys + issue_keys + wiki_keys
|
||||||
|
|
||||||
projects = projects.filter(id=project_id)
|
projects = projects.filter(id=project_id)
|
||||||
history_entries = history_entries.filter(key__in=keys)
|
history_entries = history_entries.filter(key__in=keys)
|
||||||
|
@ -121,7 +124,8 @@ def generate_timeline(initial_date, final_date, project_id):
|
||||||
"values_diff": {},
|
"values_diff": {},
|
||||||
"user": extract_user_info(project.owner),
|
"user": extract_user_info(project.owner),
|
||||||
}
|
}
|
||||||
_push_to_timelines(project, project.owner, project, "create", project.created_date, extra_data=extra_data)
|
_push_to_timelines(project, project.owner, project, "create", project.created_date,
|
||||||
|
extra_data=extra_data)
|
||||||
del extra_data
|
del extra_data
|
||||||
|
|
||||||
for historyEntry in history_entries.iterator():
|
for historyEntry in history_entries.iterator():
|
||||||
|
|
|
@ -31,7 +31,7 @@ class Command(BaseCommand):
|
||||||
total = Project.objects.count()
|
total = Project.objects.count()
|
||||||
|
|
||||||
for count,project in enumerate(Project.objects.order_by("id")):
|
for count,project in enumerate(Project.objects.order_by("id")):
|
||||||
print("""***********************************
|
print("***********************************\n",
|
||||||
%s/%s %s
|
" {}/{} {}\n".format(count+1, total, project.name),
|
||||||
***********************************"""%(count+1, total, project.name))
|
"***********************************")
|
||||||
call_command("rebuild_timeline", project=project.id)
|
call_command("rebuild_timeline", project=project.id)
|
||||||
|
|
|
@ -52,7 +52,8 @@ def build_project_namespace(project: object):
|
||||||
return "{0}:{1}".format("project", project.id)
|
return "{0}:{1}".format("project", project.id)
|
||||||
|
|
||||||
|
|
||||||
def _add_to_object_timeline(obj: object, instance: object, event_type: str, created_datetime: object, namespace: str="default", extra_data: dict={}):
|
def _add_to_object_timeline(obj: object, instance: object, event_type: str, created_datetime: object,
|
||||||
|
namespace: str="default", extra_data: dict={}):
|
||||||
assert isinstance(obj, Model), "obj must be a instance of Model"
|
assert isinstance(obj, Model), "obj must be a instance of Model"
|
||||||
assert isinstance(instance, Model), "instance must be a instance of Model"
|
assert isinstance(instance, Model), "instance must be a instance of Model"
|
||||||
from .models import Timeline
|
from .models import Timeline
|
||||||
|
@ -74,12 +75,14 @@ def _add_to_object_timeline(obj: object, instance: object, event_type: str, crea
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _add_to_objects_timeline(objects, instance: object, event_type: str, created_datetime: object, namespace: str="default", extra_data: dict={}):
|
def _add_to_objects_timeline(objects, instance: object, event_type: str, created_datetime: object,
|
||||||
|
namespace: str="default", extra_data: dict={}):
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
_add_to_object_timeline(obj, instance, event_type, created_datetime, namespace, extra_data)
|
_add_to_object_timeline(obj, instance, event_type, created_datetime, namespace, extra_data)
|
||||||
|
|
||||||
|
|
||||||
def _push_to_timeline(objects, instance: object, event_type: str, created_datetime: object, namespace: str="default", extra_data: dict={}):
|
def _push_to_timeline(objects, instance: object, event_type: str, created_datetime: object,
|
||||||
|
namespace: str="default", extra_data: dict={}):
|
||||||
if isinstance(objects, Model):
|
if isinstance(objects, Model):
|
||||||
_add_to_object_timeline(objects, instance, event_type, created_datetime, namespace, extra_data)
|
_add_to_object_timeline(objects, instance, event_type, created_datetime, namespace, extra_data)
|
||||||
elif isinstance(objects, QuerySet) or isinstance(objects, list):
|
elif isinstance(objects, QuerySet) or isinstance(objects, list):
|
||||||
|
@ -89,7 +92,8 @@ def _push_to_timeline(objects, instance: object, event_type: str, created_dateti
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def push_to_timelines(project_id, user_id, obj_app_label, obj_model_name, obj_id, event_type, created_datetime, extra_data={}):
|
def push_to_timelines(project_id, user_id, obj_app_label, obj_model_name, obj_id, event_type,
|
||||||
|
created_datetime, extra_data={}):
|
||||||
ObjModel = apps.get_model(obj_app_label, obj_model_name)
|
ObjModel = apps.get_model(obj_app_label, obj_model_name)
|
||||||
try:
|
try:
|
||||||
obj = ObjModel.objects.get(id=obj_id)
|
obj = ObjModel.objects.get(id=obj_id)
|
||||||
|
@ -156,6 +160,7 @@ def filter_timeline_for_user(timeline, user):
|
||||||
content_types = {
|
content_types = {
|
||||||
"view_project": ContentType.objects.get_by_natural_key("projects", "project"),
|
"view_project": ContentType.objects.get_by_natural_key("projects", "project"),
|
||||||
"view_milestones": ContentType.objects.get_by_natural_key("milestones", "milestone"),
|
"view_milestones": ContentType.objects.get_by_natural_key("milestones", "milestone"),
|
||||||
|
"view_epic": ContentType.objects.get_by_natural_key("epics", "epic"),
|
||||||
"view_us": ContentType.objects.get_by_natural_key("userstories", "userstory"),
|
"view_us": ContentType.objects.get_by_natural_key("userstories", "userstory"),
|
||||||
"view_tasks": ContentType.objects.get_by_natural_key("tasks", "task"),
|
"view_tasks": ContentType.objects.get_by_natural_key("tasks", "task"),
|
||||||
"view_issues": ContentType.objects.get_by_natural_key("issues", "issue"),
|
"view_issues": ContentType.objects.get_by_natural_key("issues", "issue"),
|
||||||
|
@ -181,7 +186,8 @@ def filter_timeline_for_user(timeline, user):
|
||||||
if membership.is_admin:
|
if membership.is_admin:
|
||||||
tl_filter |= Q(project=membership.project)
|
tl_filter |= Q(project=membership.project)
|
||||||
else:
|
else:
|
||||||
data_content_types = list(filter(None, [content_types.get(a, None) for a in membership.role.permissions]))
|
data_content_types = list(filter(None, [content_types.get(a, None) for a in
|
||||||
|
membership.role.permissions]))
|
||||||
data_content_types.append(membership_content_type)
|
data_content_types.append(membership_content_type)
|
||||||
tl_filter |= Q(project=membership.project, data_content_type__in=data_content_types)
|
tl_filter |= Q(project=membership.project, data_content_type__in=data_content_types)
|
||||||
|
|
||||||
|
@ -252,6 +258,14 @@ def extract_milestone_info(instance):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_epic_info(instance):
|
||||||
|
return {
|
||||||
|
"id": instance.pk,
|
||||||
|
"ref": instance.ref,
|
||||||
|
"subject": instance.subject,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def extract_userstory_info(instance):
|
def extract_userstory_info(instance):
|
||||||
return {
|
return {
|
||||||
"id": instance.pk,
|
"id": instance.pk,
|
||||||
|
|
|
@ -36,9 +36,23 @@ def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_d
|
||||||
|
|
||||||
ct = ContentType.objects.get_for_model(obj)
|
ct = ContentType.objects.get_for_model(obj)
|
||||||
if settings.CELERY_ENABLED:
|
if settings.CELERY_ENABLED:
|
||||||
connection.on_commit(lambda: push_to_timelines.delay(project_id, user.id, ct.app_label, ct.model, obj.id, event_type, created_datetime, extra_data=extra_data))
|
connection.on_commit(lambda: push_to_timelines.delay(project_id,
|
||||||
|
user.id,
|
||||||
|
ct.app_label,
|
||||||
|
ct.model,
|
||||||
|
obj.id,
|
||||||
|
event_type,
|
||||||
|
created_datetime,
|
||||||
|
extra_data=extra_data))
|
||||||
else:
|
else:
|
||||||
push_to_timelines(project_id, user.id, ct.app_label, ct.model, obj.id, event_type, created_datetime, extra_data=extra_data)
|
push_to_timelines(project_id,
|
||||||
|
user.id,
|
||||||
|
ct.app_label,
|
||||||
|
ct.model,
|
||||||
|
obj.id,
|
||||||
|
event_type,
|
||||||
|
created_datetime,
|
||||||
|
extra_data=extra_data)
|
||||||
|
|
||||||
|
|
||||||
def _clean_description_fields(values_diff):
|
def _clean_description_fields(values_diff):
|
||||||
|
|
|
@ -43,6 +43,18 @@ def milestone_timeline(instance, extra_data={}):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@register_timeline_implementation("epics.epic", "create")
|
||||||
|
@register_timeline_implementation("epics.epic", "change")
|
||||||
|
@register_timeline_implementation("epics.epic", "delete")
|
||||||
|
def epic_timeline(instance, extra_data={}):
|
||||||
|
result ={
|
||||||
|
"epic": service.extract_epic_info(instance),
|
||||||
|
"project": service.extract_project_info(instance.project),
|
||||||
|
}
|
||||||
|
result.update(extra_data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@register_timeline_implementation("userstories.userstory", "create")
|
@register_timeline_implementation("userstories.userstory", "create")
|
||||||
@register_timeline_implementation("userstories.userstory", "change")
|
@register_timeline_implementation("userstories.userstory", "change")
|
||||||
@register_timeline_implementation("userstories.userstory", "delete")
|
@register_timeline_implementation("userstories.userstory", "delete")
|
||||||
|
|
|
@ -389,6 +389,16 @@ class IssueTypeFactory(Factory):
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributeFactory(Factory):
|
||||||
|
class Meta:
|
||||||
|
model = "custom_attributes.EpicCustomAttribute"
|
||||||
|
strategy = factory.CREATE_STRATEGY
|
||||||
|
|
||||||
|
name = factory.Sequence(lambda n: "Epic Custom Attribute {}".format(n))
|
||||||
|
description = factory.Sequence(lambda n: "Description for Epic Custom Attribute {}".format(n))
|
||||||
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributeFactory(Factory):
|
class UserStoryCustomAttributeFactory(Factory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = "custom_attributes.UserStoryCustomAttribute"
|
model = "custom_attributes.UserStoryCustomAttribute"
|
||||||
|
@ -419,6 +429,15 @@ class IssueCustomAttributeFactory(Factory):
|
||||||
project = factory.SubFactory("tests.factories.ProjectFactory")
|
project = factory.SubFactory("tests.factories.ProjectFactory")
|
||||||
|
|
||||||
|
|
||||||
|
class EpicCustomAttributesValuesFactory(Factory):
|
||||||
|
class Meta:
|
||||||
|
model = "custom_attributes.EpicCustomAttributesValues"
|
||||||
|
strategy = factory.CREATE_STRATEGY
|
||||||
|
|
||||||
|
attributes_values = {}
|
||||||
|
epic = factory.SubFactory("tests.factories.EpicFactory")
|
||||||
|
|
||||||
|
|
||||||
class UserStoryCustomAttributesValuesFactory(Factory):
|
class UserStoryCustomAttributesValuesFactory(Factory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = "custom_attributes.UserStoryCustomAttributesValues"
|
model = "custom_attributes.UserStoryCustomAttributesValues"
|
||||||
|
|
Loading…
Reference in New Issue