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)
|
||||
|
||||
|
||||
class EpicHistory(HistoryViewSet):
|
||||
content_type = "epics.epic"
|
||||
permission_classes = (permissions.EpicHistoryPermission,)
|
||||
|
||||
|
||||
class UserStoryHistory(HistoryViewSet):
|
||||
content_type = "userstories.userstory"
|
||||
permission_classes = (permissions.UserStoryHistoryPermission,)
|
||||
|
|
|
@ -106,6 +106,17 @@ def milestone_values(diff):
|
|||
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):
|
||||
values = _common_users_values(diff)
|
||||
|
||||
|
@ -190,6 +201,18 @@ def extract_attachments(obj) -> list:
|
|||
"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
|
||||
def extract_user_story_custom_attributes(obj) -> list:
|
||||
with suppress(ObjectDoesNotExist):
|
||||
|
@ -235,6 +258,7 @@ def project_freezer(project) -> dict:
|
|||
"total_milestones",
|
||||
"total_story_points",
|
||||
"tags",
|
||||
"is_epics_activated",
|
||||
"is_backlog_activated",
|
||||
"is_kanban_activated",
|
||||
"is_wiki_activated",
|
||||
|
@ -256,6 +280,31 @@ def milestone_freezer(milestone) -> dict:
|
|||
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:
|
||||
rp_cls = apps.get_model("userstories", "RolePoints")
|
||||
rpqsd = rp_cls.objects.filter(user_story=us)
|
||||
|
|
|
@ -42,6 +42,14 @@ class IsCommentProjectAdmin(PermissionComponent):
|
|||
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):
|
||||
retrieve_perms = HasProjectPerm('view_project')
|
||||
edit_comment_perms = IsCommentProjectAdmin() | IsCommentOwner()
|
||||
|
|
|
@ -50,6 +50,7 @@ from .models import HistoryType
|
|||
# Freeze implementatitions
|
||||
from .freeze_impl import project_freezer
|
||||
from .freeze_impl import milestone_freezer
|
||||
from .freeze_impl import epic_freezer
|
||||
from .freeze_impl import userstory_freezer
|
||||
from .freeze_impl import issue_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 milestone_values
|
||||
from .freeze_impl import epic_values
|
||||
from .freeze_impl import userstory_values
|
||||
from .freeze_impl import issue_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
|
||||
# not create empty history entry
|
||||
if (not fdiff.diff and not comment
|
||||
and old_fobj is not None
|
||||
and entry_type != HistoryType.delete):
|
||||
|
||||
if (not fdiff.diff and not comment and old_fobj is not None and entry_type != HistoryType.delete):
|
||||
return None
|
||||
|
||||
fvals = make_diff_values(typename, fdiff)
|
||||
|
@ -394,8 +393,10 @@ def prefetch_owners_in_history_queryset(qs):
|
|||
return qs
|
||||
|
||||
|
||||
# Freeze & value register
|
||||
register_freeze_implementation("projects.project", project_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("issues.issue", issue_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("milestones.milestone", milestone_values)
|
||||
register_values_implementation("epics.epic", epic_values)
|
||||
register_values_implementation("userstories.userstory", userstory_values)
|
||||
register_values_implementation("issues.issue", issue_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 . 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
|
||||
|
||||
|
||||
|
@ -222,6 +213,7 @@ class ProjectSerializer(serializers.LightSerializer):
|
|||
members = MethodField()
|
||||
total_milestones = Field()
|
||||
total_story_points = Field()
|
||||
is_epics_activated = Field()
|
||||
is_backlog_activated = Field()
|
||||
is_kanban_activated = Field()
|
||||
is_wiki_activated = Field()
|
||||
|
@ -249,6 +241,7 @@ class ProjectSerializer(serializers.LightSerializer):
|
|||
tags = Field()
|
||||
tags_colors = MethodField()
|
||||
|
||||
default_epic_status = Field(attr="default_epic_status_id")
|
||||
default_points = Field(attr="default_points_id")
|
||||
default_us_status = Field(attr="default_us_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):
|
||||
if "request" in self.context:
|
||||
user = self.context["request"].user
|
||||
return calculate_permissions(
|
||||
is_authenticated=user.is_authenticated(),
|
||||
return calculate_permissions(is_authenticated=user.is_authenticated(),
|
||||
is_superuser=user.is_superuser,
|
||||
is_member=self.get_i_am_member(obj),
|
||||
is_admin=self.get_i_am_admin(obj),
|
||||
|
@ -441,10 +433,8 @@ class ProjectDetailSerializer(ProjectSerializer):
|
|||
return len(obj.members_attr)
|
||||
|
||||
def get_is_out_of_owner_limits(self, obj):
|
||||
assert (hasattr(obj, "private_projects_same_owner_attr"),
|
||||
"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, "private_projects_same_owner_attr"), "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"
|
||||
return services.check_if_project_is_out_of_owner_limits(
|
||||
obj,
|
||||
current_memberships=self.get_total_memberships(obj),
|
||||
|
@ -453,10 +443,8 @@ class ProjectDetailSerializer(ProjectSerializer):
|
|||
)
|
||||
|
||||
def get_is_private_extra_info(self, obj):
|
||||
assert (hasattr(obj, "private_projects_same_owner_attr"),
|
||||
"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, "private_projects_same_owner_attr"), "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"
|
||||
return services.check_if_project_privacity_can_be_changed(
|
||||
obj,
|
||||
current_memberships=self.get_total_memberships(obj),
|
||||
|
|
|
@ -196,11 +196,13 @@ router.register(r"wiki-links", WikiLinkViewSet,
|
|||
|
||||
|
||||
# History & Components
|
||||
from taiga.projects.history.api import EpicHistory
|
||||
from taiga.projects.history.api import UserStoryHistory
|
||||
from taiga.projects.history.api import TaskHistory
|
||||
from taiga.projects.history.api import IssueHistory
|
||||
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/task", TaskHistory, base_name="task-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,
|
||||
tasks.task.change,
|
||||
userstories.userstory.change,
|
||||
epics.epic.change,
|
||||
wiki.wikipage.change}'::text[])
|
||||
)
|
||||
"""])
|
||||
|
@ -92,6 +93,7 @@ class TimelineViewSet(ReadOnlyListViewSet):
|
|||
qs = qs.exclude(event_type__in=["issues.issue.delete",
|
||||
"tasks.task.delete",
|
||||
"userstories.userstory.delete",
|
||||
"epics.epic.delete",
|
||||
"wiki.wikipage.delete",
|
||||
"projects.project.change"])
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ from django.core.management.base import BaseCommand
|
|||
from django.db.models import Model
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from taiga.timeline.service import (_get_impl_key_from_model,
|
||||
_timeline_impl_map, extract_user_info)
|
||||
from taiga.timeline.service import _get_impl_key_from_model,
|
||||
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.signals import _push_to_timelines
|
||||
|
||||
|
@ -54,7 +55,8 @@ class BulkCreator(object):
|
|||
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(instance, Model), "instance must be a instance of Model"
|
||||
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")
|
||||
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
|
||||
|
||||
counter = 1
|
||||
|
|
|
@ -58,7 +58,8 @@ class BulkCreator(object):
|
|||
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(instance, Model), "instance must be a instance of Model"
|
||||
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:
|
||||
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)]
|
||||
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)]
|
||||
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)
|
||||
history_entries = history_entries.filter(key__in=keys)
|
||||
|
@ -121,7 +124,8 @@ def generate_timeline(initial_date, final_date, project_id):
|
|||
"values_diff": {},
|
||||
"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
|
||||
|
||||
for historyEntry in history_entries.iterator():
|
||||
|
|
|
@ -31,7 +31,7 @@ class Command(BaseCommand):
|
|||
total = Project.objects.count()
|
||||
|
||||
for count,project in enumerate(Project.objects.order_by("id")):
|
||||
print("""***********************************
|
||||
%s/%s %s
|
||||
***********************************"""%(count+1, total, project.name))
|
||||
print("***********************************\n",
|
||||
" {}/{} {}\n".format(count+1, total, project.name),
|
||||
"***********************************")
|
||||
call_command("rebuild_timeline", project=project.id)
|
||||
|
|
|
@ -52,7 +52,8 @@ def build_project_namespace(project: object):
|
|||
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(instance, Model), "instance must be a instance of Model"
|
||||
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:
|
||||
_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):
|
||||
_add_to_object_timeline(objects, instance, event_type, created_datetime, namespace, extra_data)
|
||||
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
|
||||
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)
|
||||
try:
|
||||
obj = ObjModel.objects.get(id=obj_id)
|
||||
|
@ -156,6 +160,7 @@ def filter_timeline_for_user(timeline, user):
|
|||
content_types = {
|
||||
"view_project": ContentType.objects.get_by_natural_key("projects", "project"),
|
||||
"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_tasks": ContentType.objects.get_by_natural_key("tasks", "task"),
|
||||
"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:
|
||||
tl_filter |= Q(project=membership.project)
|
||||
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)
|
||||
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):
|
||||
return {
|
||||
"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)
|
||||
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:
|
||||
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):
|
||||
|
|
|
@ -43,6 +43,18 @@ def milestone_timeline(instance, extra_data={}):
|
|||
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", "change")
|
||||
@register_timeline_implementation("userstories.userstory", "delete")
|
||||
|
|
|
@ -389,6 +389,16 @@ class IssueTypeFactory(Factory):
|
|||
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 Meta:
|
||||
model = "custom_attributes.UserStoryCustomAttribute"
|
||||
|
@ -419,6 +429,15 @@ class IssueCustomAttributeFactory(Factory):
|
|||
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 Meta:
|
||||
model = "custom_attributes.UserStoryCustomAttributesValues"
|
||||
|
|
Loading…
Reference in New Issue