Initial epic model + epic status + epcic attachments + project template + project defaults

remotes/origin/issue/4795/notification_even_they_are_disabled
David Barragán Merino 2016-06-29 17:25:01 +02:00
parent 75a9751136
commit f70923c064
26 changed files with 931 additions and 84 deletions

View File

@ -300,6 +300,7 @@ INSTALLED_APPS = [
"taiga.projects.likes", "taiga.projects.likes",
"taiga.projects.votes", "taiga.projects.votes",
"taiga.projects.milestones", "taiga.projects.milestones",
"taiga.projects.epics",
"taiga.projects.userstories", "taiga.projects.userstories",
"taiga.projects.tasks", "taiga.projects.tasks",
"taiga.projects.issues", "taiga.projects.issues",

View File

@ -198,6 +198,10 @@ class PermissionBasedAttachmentFilterBackend(PermissionBasedFilterBackend):
return qs.filter(content_type=ct) return qs.filter(content_type=ct)
class CanViewEpicAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
permission = "view_epic"
class CanViewUserStoryAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend): class CanViewUserStoryAttachmentFilterBackend(PermissionBasedAttachmentFilterBackend):
permission = "view_us" permission = "view_us"

View File

@ -22,12 +22,14 @@ from django.utils.translation import ugettext_lazy as _
ANON_PERMISSIONS = [ ANON_PERMISSIONS = [
('view_project', _('View project')), ('view_project', _('View project')),
('view_milestones', _('View milestones')), ('view_milestones', _('View milestones')),
('view_epic', _('View epic')),
('view_us', _('View user stories')), ('view_us', _('View user stories')),
('view_tasks', _('View tasks')), ('view_tasks', _('View tasks')),
('view_issues', _('View issues')), ('view_issues', _('View issues')),
('view_wiki_pages', _('View wiki pages')), ('view_wiki_pages', _('View wiki pages')),
('view_wiki_links', _('View wiki links')), ('view_wiki_links', _('View wiki links')),
] ]
MEMBERS_PERMISSIONS = [ MEMBERS_PERMISSIONS = [
('view_project', _('View project')), ('view_project', _('View project')),
# Milestone permissions # Milestone permissions
@ -36,6 +38,12 @@ MEMBERS_PERMISSIONS = [
('modify_milestone', _('Modify milestone')), ('modify_milestone', _('Modify milestone')),
('delete_milestone', _('Delete milestone')), ('delete_milestone', _('Delete milestone')),
# US permissions # US permissions
('view_epic', _('View epic')),
('add_epic', _('Add epic')),
('modify_epic', _('Modify epic')),
('comment_epic', _('Comment epic')),
('delete_epic', _('Delete epic')),
# US permissions
('view_us', _('View user story')), ('view_us', _('View user story')),
('add_us', _('Add user story')), ('add_us', _('Add user story')),
('modify_us', _('Modify user story')), ('modify_us', _('Modify user story')),

View File

@ -40,6 +40,8 @@ from taiga.base.decorators import detail_route
from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.slug import slugify_uniquely
from taiga.permissions import services as permissions_services from taiga.permissions import services as permissions_services
from taiga.projects.epics.models import Epic
from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.issues.models import Issue from taiga.projects.issues.models import Issue
from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin from taiga.projects.likes.mixins.viewsets import LikedResourceMixin, FansViewSetMixin
@ -49,7 +51,6 @@ from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
from taiga.projects.tasks.models import Task from taiga.projects.tasks.models import Task
from taiga.projects.tagging.api import TagsColorsResourceMixin from taiga.projects.tagging.api import TagsColorsResourceMixin
from taiga.projects.userstories.models import UserStory, RolePoints from taiga.projects.userstories.models import UserStory, RolePoints
from . import filters as project_filters from . import filters as project_filters
@ -449,21 +450,21 @@ class ProjectWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
## Custom values for selectors ## Custom values for selectors
###################################################### ######################################################
class PointsViewSet(MoveOnDestroyMixin, BlockedByProjectMixin, class EpicStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin,
ModelCrudViewSet, BulkUpdateOrderMixin): ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.Points model = models.EpicStatus
serializer_class = serializers.PointsSerializer serializer_class = serializers.EpicStatusSerializer
validator_class = validators.PointsValidator validator_class = validators.EpicStatusValidator
permission_classes = (permissions.PointsPermission,) permission_classes = (permissions.EpicStatusPermission,)
filter_backends = (filters.CanViewProjectFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',) filter_fields = ('project',)
bulk_update_param = "bulk_points" bulk_update_param = "bulk_epic_statuses"
bulk_update_perm = "change_points" bulk_update_perm = "change_epicstatus"
bulk_update_order_action = services.bulk_update_points_order bulk_update_order_action = services.bulk_update_epic_status_order
move_on_destroy_related_class = RolePoints move_on_destroy_related_class = Epic
move_on_destroy_related_field = "points" move_on_destroy_related_field = "status"
move_on_destroy_project_default_field = "default_points" move_on_destroy_project_default_field = "default_epic_status"
class UserStoryStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin, class UserStoryStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin,
@ -483,6 +484,23 @@ class UserStoryStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin,
move_on_destroy_project_default_field = "default_us_status" move_on_destroy_project_default_field = "default_us_status"
class PointsViewSet(MoveOnDestroyMixin, BlockedByProjectMixin,
ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.Points
serializer_class = serializers.PointsSerializer
validator_class = validators.PointsValidator
permission_classes = (permissions.PointsPermission,)
filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',)
bulk_update_param = "bulk_points"
bulk_update_perm = "change_points"
bulk_update_order_action = services.bulk_update_points_order
move_on_destroy_related_class = RolePoints
move_on_destroy_related_field = "points"
move_on_destroy_project_default_field = "default_points"
class TaskStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin, class TaskStatusViewSet(MoveOnDestroyMixin, BlockedByProjectMixin,
ModelCrudViewSet, BulkUpdateOrderMixin): ModelCrudViewSet, BulkUpdateOrderMixin):

View File

@ -83,6 +83,12 @@ class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin,
return obj.content_object return obj.content_object
class EpicAttachmentViewSet(BaseAttachmentViewSet):
permission_classes = (permissions.EpicAttachmentPermission,)
filter_backends = (filters.CanViewEpicAttachmentFilterBackend,)
content_type = "epics.epic"
class UserStoryAttachmentViewSet(BaseAttachmentViewSet): class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
permission_classes = (permissions.UserStoryAttachmentPermission,) permission_classes = (permissions.UserStoryAttachmentPermission,)
filter_backends = (filters.CanViewUserStoryAttachmentFilterBackend,) filter_backends = (filters.CanViewUserStoryAttachmentFilterBackend,)

View File

@ -28,6 +28,15 @@ class IsAttachmentOwnerPerm(PermissionComponent):
return False return False
class EpicAttachmentPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_epic') | IsAttachmentOwnerPerm()
create_perms = HasProjectPerm('modify_epic')
update_perms = HasProjectPerm('modify_epic') | IsAttachmentOwnerPerm()
partial_update_perms = HasProjectPerm('modify_epic') | IsAttachmentOwnerPerm()
destroy_perms = HasProjectPerm('modify_epic') | IsAttachmentOwnerPerm()
list_perms = AllowAny()
class UserStoryAttachmentPermission(TaigaResourcePermission): class UserStoryAttachmentPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_us') | IsAttachmentOwnerPerm() retrieve_perms = HasProjectPerm('view_us') | IsAttachmentOwnerPerm()
create_perms = HasProjectPerm('modify_us') create_perms = HasProjectPerm('modify_us')
@ -67,7 +76,9 @@ class WikiAttachmentPermission(TaigaResourcePermission):
class RawAttachmentPerm(PermissionComponent): class RawAttachmentPerm(PermissionComponent):
def check_permissions(self, request, view, obj=None): def check_permissions(self, request, view, obj=None):
is_owner = IsAttachmentOwnerPerm().check_permissions(request, view, obj) is_owner = IsAttachmentOwnerPerm().check_permissions(request, view, obj)
if obj.content_type.app_label == "userstories" and obj.content_type.model == "userstory": if obj.content_type.app_label == "epics" and obj.content_type.model == "epic":
return EpicAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
elif obj.content_type.app_label == "userstories" and obj.content_type.model == "userstory":
return UserStoryAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner return UserStoryAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner
elif obj.content_type.app_label == "tasks" and obj.content_type.model == "task": elif obj.content_type.app_label == "tasks" and obj.content_type.model == "task":
return TaskAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner return TaskAttachmentPermission(request, view).check_permissions('retrieve', obj) or is_owner

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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.epics.apps.EpicsAppConfig"

View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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
def connect_epics_signals():
from taiga.projects.tagging import signals as tagging_handlers
# Tags
signals.pre_save.connect(tagging_handlers.tags_normalization,
sender=apps.get_model("epics", "Epic"),
dispatch_uid="tags_normalization_epic")
def connect_all_epics_signals():
connect_epics_signals()
def disconnect_epics_signals():
signals.pre_save.disconnect(sender=apps.get_model("epics", "Task"),
dispatch_uid="tags_normalization")
def disconnect_all_epics_signals():
disconnect_epics_signals()
class EpicsAppConfig(AppConfig):
name = "taiga.projects.epics"
verbose_name = "Epics"
def ready(self):
connect_all_epics_signals()

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-06-29 14:43
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import taiga.projects.notifications.mixins
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('projects', '0049_auto_20160629_1443'),
('userstories', '0012_auto_20160614_1201'),
]
operations = [
migrations.CreateModel(
name='Epic',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tags', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=[], null=True, size=None, verbose_name='tags')),
('version', models.IntegerField(default=1, verbose_name='version')),
('is_blocked', models.BooleanField(default=False, verbose_name='is blocked')),
('blocked_note', models.TextField(blank=True, default='', verbose_name='blocked note')),
('ref', models.BigIntegerField(blank=True, db_index=True, default=None, null=True, verbose_name='ref')),
('is_closed', models.BooleanField(default=False)),
('epics_order', models.IntegerField(default=10000, verbose_name='epics order')),
('created_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created date')),
('modified_date', models.DateTimeField(verbose_name='modified date')),
('finish_date', models.DateTimeField(blank=True, null=True, verbose_name='finish date')),
('subject', models.TextField(verbose_name='subject')),
('description', models.TextField(blank=True, verbose_name='description')),
('client_requirement', models.BooleanField(default=False, verbose_name='is client requirement')),
('team_requirement', models.BooleanField(default=False, verbose_name='is team requirement')),
('assigned_to', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='epics_assigned_to_me', to=settings.AUTH_USER_MODEL, verbose_name='assigned to')),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owned_epics', to=settings.AUTH_USER_MODEL, verbose_name='owner')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='epics', to='projects.Project', verbose_name='project')),
('status', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='epics', to='projects.EpicStatus', verbose_name='status')),
('user_stories', models.ManyToManyField(related_name='epics', to='userstories.UserStory', verbose_name='user stories')),
],
options={
'verbose_name_plural': 'epics',
'ordering': ['project', 'ref'],
'verbose_name': 'epic',
},
bases=(taiga.projects.notifications.mixins.WatchedModelMixin, models.Model),
),
# Execute trigger after epic update
migrations.RunSQL(
"""
DROP TRIGGER IF EXISTS update_project_tags_colors_on_epic_update ON epics_epic;
CREATE TRIGGER update_project_tags_colors_on_epic_update
AFTER UPDATE ON epics_epic
FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors();
"""
),
# Execute trigger after epic insert
migrations.RunSQL(
"""
DROP TRIGGER IF EXISTS update_project_tags_colors_on_epic_insert ON epics_epic;
CREATE TRIGGER update_project_tags_colors_on_epic_insert
AFTER INSERT ON epics_epic
FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors();
"""
),
]

View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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.db import models
from django.contrib.contenttypes.fields import GenericRelation
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from taiga.projects.tagging.models import TaggedMixin
from taiga.projects.occ import OCCModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin
class Epic(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
verbose_name=_("ref"))
project = models.ForeignKey("projects.Project", null=False, blank=False,
related_name="epics", verbose_name=_("project"))
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name="owned_epics", verbose_name=_("owner"),
on_delete=models.SET_NULL)
status = models.ForeignKey("projects.EpicStatus", null=True, blank=True,
related_name="epics", verbose_name=_("status"),
on_delete=models.SET_NULL)
is_closed = models.BooleanField(default=False)
epics_order = models.IntegerField(null=False, blank=False, default=10000,
verbose_name=_("epics order"))
created_date = models.DateTimeField(null=False, blank=False,
verbose_name=_("created date"),
default=timezone.now)
modified_date = models.DateTimeField(null=False, blank=False,
verbose_name=_("modified date"))
finish_date = models.DateTimeField(null=True, blank=True,
verbose_name=_("finish date"))
subject = models.TextField(null=False, blank=False,
verbose_name=_("subject"))
description = models.TextField(null=False, blank=True, verbose_name=_("description"))
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
default=None, related_name="epics_assigned_to_me",
verbose_name=_("assigned to"))
client_requirement = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_("is client requirement"))
team_requirement = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_("is team requirement"))
user_stories = models.ManyToManyField("userstories.UserStory", related_name="epics",
verbose_name=_("user stories"))
attachments = GenericRelation("attachments.Attachment")
_importing = None
class Meta:
verbose_name = "epic"
verbose_name_plural = "epics"
ordering = ["project", "ref"]
def save(self, *args, **kwargs):
if not self._importing or not self.modified_date:
self.modified_date = timezone.now()
if not self.status:
self.status = self.project.default_epic_status
super().save(*args, **kwargs)
def __str__(self):
return "({1}) {0}".format(self.ref, self.subject)
def __repr__(self):
return "<Epic %s>" % (self.id)

View File

@ -5,26 +5,28 @@
"fields": { "fields": {
"name": "Scrum", "name": "Scrum",
"slug": "scrum", "slug": "scrum",
"order": 1,
"description": "The agile product backlog in Scrum is a prioritized features list, containing short descriptions of all functionality desired in the product. When applying Scrum, it's not necessary to start a project with a lengthy, upfront effort to document all requirements. The Scrum product backlog is then allowed to grow and change as more is learned about the product and its customers", "description": "The agile product backlog in Scrum is a prioritized features list, containing short descriptions of all functionality desired in the product. When applying Scrum, it's not necessary to start a project with a lengthy, upfront effort to document all requirements. The Scrum product backlog is then allowed to grow and change as more is learned about the product and its customers",
"order": 1,
"created_date": "2014-04-22T14:48:43.596Z", "created_date": "2014-04-22T14:48:43.596Z",
"modified_date": "2014-07-25T10:02:46.479Z", "modified_date": "2016-06-29T14:52:11.273Z",
"default_owner_role": "product-owner", "default_owner_role": "product-owner",
"is_epics_activated": true,
"is_backlog_activated": true, "is_backlog_activated": true,
"is_kanban_activated": false, "is_kanban_activated": false,
"is_wiki_activated": true, "is_wiki_activated": true,
"is_issues_activated": true, "is_issues_activated": true,
"videoconferences": null, "videoconferences": null,
"videoconferences_extra_data": "", "videoconferences_extra_data": "",
"default_options": "{\"severity\": \"Normal\", \"priority\": \"Normal\", \"task_status\": \"New\", \"points\": \"?\", \"us_status\": \"New\", \"issue_type\": \"Bug\", \"issue_status\": \"New\"}", "default_options": "{\"epic_status\": \"New\", \"severity\": \"Normal\", \"issue_type\": \"Bug\", \"us_status\": \"New\", \"points\": \"?\", \"priority\": \"Normal\", \"task_status\": \"New\", \"issue_status\": \"New\"}",
"us_statuses": "[{\"is_archived\": false, \"slug\": \"new\", \"is_closed\": false, \"wip_limit\": null, \"order\": 1, \"name\": \"New\", \"color\": \"#999999\"}, {\"is_archived\": false, \"slug\": \"ready\", \"is_closed\": false, \"wip_limit\": null, \"order\": 2, \"name\": \"Ready\", \"color\": \"#ff8a84\"}, {\"is_archived\": false, \"slug\": \"in-progress\", \"is_closed\": false, \"wip_limit\": null, \"order\": 3, \"name\": \"In progress\", \"color\": \"#ff9900\"}, {\"is_archived\": false, \"slug\": \"ready-for-test\", \"is_closed\": false, \"wip_limit\": null, \"order\": 4, \"name\": \"Ready for test\", \"color\": \"#fcc000\"}, {\"is_archived\": false, \"slug\": \"done\", \"is_closed\": true, \"wip_limit\": null, \"order\": 5, \"name\": \"Done\", \"color\": \"#669900\"}, {\"is_archived\": true, \"slug\": \"archived\", \"is_closed\": true, \"wip_limit\": null, \"order\": 6, \"name\": \"Archived\", \"color\": \"#5c3566\"}]", "epic_statuses": "[{\"order\": 1, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"order\": 2, \"name\": \"Ready\", \"color\": \"#ff8a84\", \"slug\": \"ready\", \"is_closed\": false}, {\"order\": 3, \"name\": \"In progress\", \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"order\": 4, \"name\": \"Ready for test\", \"color\": \"#fcc000\", \"slug\": \"ready-for-test\", \"is_closed\": false}, {\"order\": 5, \"name\": \"Done\", \"color\": \"#669900\", \"slug\": \"done\", \"is_closed\": true}]",
"us_statuses": "[{\"name\": \"New\", \"is_archived\": false, \"wip_limit\": null, \"order\": 1, \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"name\": \"Ready\", \"is_archived\": false, \"wip_limit\": null, \"order\": 2, \"color\": \"#ff8a84\", \"slug\": \"ready\", \"is_closed\": false}, {\"name\": \"In progress\", \"is_archived\": false, \"wip_limit\": null, \"order\": 3, \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"name\": \"Ready for test\", \"is_archived\": false, \"wip_limit\": null, \"order\": 4, \"color\": \"#fcc000\", \"slug\": \"ready-for-test\", \"is_closed\": false}, {\"name\": \"Done\", \"is_archived\": false, \"wip_limit\": null, \"order\": 5, \"color\": \"#669900\", \"slug\": \"done\", \"is_closed\": true}, {\"name\": \"Archived\", \"is_archived\": true, \"wip_limit\": null, \"order\": 6, \"color\": \"#5c3566\", \"slug\": \"archived\", \"is_closed\": true}]",
"points": "[{\"order\": 1, \"name\": \"?\", \"value\": null}, {\"order\": 2, \"name\": \"0\", \"value\": 0.0}, {\"order\": 3, \"name\": \"1/2\", \"value\": 0.5}, {\"order\": 4, \"name\": \"1\", \"value\": 1.0}, {\"order\": 5, \"name\": \"2\", \"value\": 2.0}, {\"order\": 6, \"name\": \"3\", \"value\": 3.0}, {\"order\": 7, \"name\": \"5\", \"value\": 5.0}, {\"order\": 8, \"name\": \"8\", \"value\": 8.0}, {\"order\": 9, \"name\": \"10\", \"value\": 10.0}, {\"order\": 10, \"name\": \"13\", \"value\": 13.0}, {\"order\": 11, \"name\": \"20\", \"value\": 20.0}, {\"order\": 12, \"name\": \"40\", \"value\": 40.0}]", "points": "[{\"order\": 1, \"name\": \"?\", \"value\": null}, {\"order\": 2, \"name\": \"0\", \"value\": 0.0}, {\"order\": 3, \"name\": \"1/2\", \"value\": 0.5}, {\"order\": 4, \"name\": \"1\", \"value\": 1.0}, {\"order\": 5, \"name\": \"2\", \"value\": 2.0}, {\"order\": 6, \"name\": \"3\", \"value\": 3.0}, {\"order\": 7, \"name\": \"5\", \"value\": 5.0}, {\"order\": 8, \"name\": \"8\", \"value\": 8.0}, {\"order\": 9, \"name\": \"10\", \"value\": 10.0}, {\"order\": 10, \"name\": \"13\", \"value\": 13.0}, {\"order\": 11, \"name\": \"20\", \"value\": 20.0}, {\"order\": 12, \"name\": \"40\", \"value\": 40.0}]",
"task_statuses": "[{\"order\": 1, \"name\": \"New\", \"slug\": \"new\", \"color\": \"#999999\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"slug\": \"in-progress\", \"color\": \"#ff9900\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"slug\": \"ready-for-test\", \"color\": \"#ffcc00\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"slug\": \"closed\", \"color\": \"#669900\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"slug\": \"needs-info\", \"color\": \"#999999\", \"is_closed\": false}]", "task_statuses": "[{\"order\": 1, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"color\": \"#ffcc00\", \"slug\": \"ready-for-test\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"color\": \"#669900\", \"slug\": \"closed\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"color\": \"#999999\", \"slug\": \"needs-info\", \"is_closed\": false}]",
"issue_statuses": "[{\"order\": 1, \"name\": \"New\", \"slug\": \"new\", \"color\": \"#8C2318\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"slug\": \"in-progress\", \"color\": \"#5E8C6A\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"slug\": \"ready-for-test\", \"color\": \"#88A65E\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"slug\": \"closed\", \"color\": \"#BFB35A\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"slug\": \"needs-info\", \"color\": \"#89BAB4\", \"is_closed\": false}, {\"order\": 6, \"name\": \"Rejected\", \"slug\": \"rejected\", \"color\": \"#CC0000\", \"is_closed\": true}, {\"order\": 7, \"name\": \"Postponed\", \"slug\": \"posponed\", \"color\": \"#666666\", \"is_closed\": false}]", "issue_statuses": "[{\"order\": 1, \"name\": \"New\", \"color\": \"#8C2318\", \"slug\": \"new\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"color\": \"#5E8C6A\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"color\": \"#88A65E\", \"slug\": \"ready-for-test\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"color\": \"#BFB35A\", \"slug\": \"closed\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"color\": \"#89BAB4\", \"slug\": \"needs-info\", \"is_closed\": false}, {\"order\": 6, \"name\": \"Rejected\", \"color\": \"#CC0000\", \"slug\": \"rejected\", \"is_closed\": true}, {\"order\": 7, \"name\": \"Postponed\", \"color\": \"#666666\", \"slug\": \"posponed\", \"is_closed\": false}]",
"issue_types": "[{\"order\": 1, \"name\": \"Bug\", \"color\": \"#89BAB4\"}, {\"order\": 2, \"name\": \"Question\", \"color\": \"#ba89a8\"}, {\"order\": 3, \"name\": \"Enhancement\", \"color\": \"#89a8ba\"}]", "issue_types": "[{\"order\": 1, \"name\": \"Bug\", \"color\": \"#89BAB4\"}, {\"order\": 2, \"name\": \"Question\", \"color\": \"#ba89a8\"}, {\"order\": 3, \"name\": \"Enhancement\", \"color\": \"#89a8ba\"}]",
"priorities": "[{\"order\": 1, \"name\": \"Low\", \"color\": \"#666666\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#669933\"}, {\"order\": 5, \"name\": \"High\", \"color\": \"#CC0000\"}]", "priorities": "[{\"order\": 1, \"name\": \"Low\", \"color\": \"#666666\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#669933\"}, {\"order\": 5, \"name\": \"High\", \"color\": \"#CC0000\"}]",
"severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#666666\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#669933\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#0000FF\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#FFA500\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]", "severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#666666\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#669933\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#0000FF\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#FFA500\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]",
"roles": "[{\"order\": 10, \"name\": \"UX\", \"slug\": \"ux\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 20, \"name\": \"Design\", \"slug\": \"design\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 30, \"name\": \"Front\", \"slug\": \"front\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 40, \"name\": \"Back\", \"slug\": \"back\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 50, \"name\": \"Product Owner\", \"slug\": \"product-owner\", \"computable\": false, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 60, \"name\": \"Stakeholder\", \"slug\": \"stakeholder\", \"computable\": false, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"view_milestones\", \"view_project\", \"view_tasks\", \"view_us\", \"modify_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}]" "roles": "[{\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 10, \"name\": \"UX\", \"slug\": \"ux\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 20, \"name\": \"Design\", \"slug\": \"design\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 30, \"name\": \"Front\", \"slug\": \"front\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 40, \"name\": \"Back\", \"slug\": \"back\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 50, \"name\": \"Product Owner\", \"slug\": \"product-owner\", \"computable\": false}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"view_milestones\", \"view_project\", \"view_tasks\", \"view_us\", \"modify_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 60, \"name\": \"Stakeholder\", \"slug\": \"stakeholder\", \"computable\": false}]"
} }
}, },
{ {
@ -33,26 +35,28 @@
"fields": { "fields": {
"name": "Kanban", "name": "Kanban",
"slug": "kanban", "slug": "kanban",
"order": 2,
"description": "Kanban is a method for managing knowledge work with an emphasis on just-in-time delivery while not overloading the team members. In this approach, the process, from definition of a task to its delivery to the customer, is displayed for participants to see and team members pull work from a queue.", "description": "Kanban is a method for managing knowledge work with an emphasis on just-in-time delivery while not overloading the team members. In this approach, the process, from definition of a task to its delivery to the customer, is displayed for participants to see and team members pull work from a queue.",
"order": 2,
"created_date": "2014-04-22T14:50:19.738Z", "created_date": "2014-04-22T14:50:19.738Z",
"modified_date": "2014-07-25T13:11:42.754Z", "modified_date": "2016-06-29T14:52:15.232Z",
"default_owner_role": "product-owner", "default_owner_role": "product-owner",
"is_epics_activated": true,
"is_backlog_activated": false, "is_backlog_activated": false,
"is_kanban_activated": true, "is_kanban_activated": true,
"is_wiki_activated": false, "is_wiki_activated": false,
"is_issues_activated": false, "is_issues_activated": false,
"videoconferences": null, "videoconferences": null,
"videoconferences_extra_data": "", "videoconferences_extra_data": "",
"default_options": "{\"severity\": \"Normal\", \"priority\": \"Normal\", \"task_status\": \"New\", \"points\": \"?\", \"us_status\": \"New\", \"issue_type\": \"Bug\", \"issue_status\": \"New\"}", "default_options": "{\"epic_status\": \"New\", \"severity\": \"Normal\", \"issue_type\": \"Bug\", \"us_status\": \"New\", \"points\": \"?\", \"priority\": \"Normal\", \"task_status\": \"New\", \"issue_status\": \"New\"}",
"us_statuses": "[{\"is_archived\": false, \"slug\": \"new\", \"is_closed\": false, \"wip_limit\": null, \"order\": 1, \"name\": \"New\", \"color\": \"#999999\"}, {\"is_archived\": false, \"slug\": \"ready\", \"is_closed\": false, \"wip_limit\": null, \"order\": 2, \"name\": \"Ready\", \"color\": \"#f57900\"}, {\"is_archived\": false, \"slug\": \"in-progress\", \"is_closed\": false, \"wip_limit\": null, \"order\": 3, \"name\": \"In progress\", \"color\": \"#729fcf\"}, {\"is_archived\": false, \"slug\": \"ready-for-test\", \"is_closed\": false, \"wip_limit\": null, \"order\": 4, \"name\": \"Ready for test\", \"color\": \"#4e9a06\"}, {\"is_archived\": false, \"slug\": \"done\", \"is_closed\": true, \"wip_limit\": null, \"order\": 5, \"name\": \"Done\", \"color\": \"#cc0000\"}, {\"is_archived\": true, \"slug\": \"archived\", \"is_closed\": true, \"wip_limit\": null, \"order\": 6, \"name\": \"Archived\", \"color\": \"#5c3566\"}]", "epic_statuses": "[{\"order\": 1, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"order\": 2, \"name\": \"Ready\", \"color\": \"#ff8a84\", \"slug\": \"ready\", \"is_closed\": false}, {\"order\": 3, \"name\": \"In progress\", \"color\": \"#ff9900\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"order\": 4, \"name\": \"Ready for test\", \"color\": \"#fcc000\", \"slug\": \"ready-for-test\", \"is_closed\": false}, {\"order\": 5, \"name\": \"Done\", \"color\": \"#669900\", \"slug\": \"done\", \"is_closed\": true}]",
"us_statuses": "[{\"name\": \"New\", \"is_archived\": false, \"wip_limit\": null, \"order\": 1, \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"name\": \"Ready\", \"is_archived\": false, \"wip_limit\": null, \"order\": 2, \"color\": \"#f57900\", \"slug\": \"ready\", \"is_closed\": false}, {\"name\": \"In progress\", \"is_archived\": false, \"wip_limit\": null, \"order\": 3, \"color\": \"#729fcf\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"name\": \"Ready for test\", \"is_archived\": false, \"wip_limit\": null, \"order\": 4, \"color\": \"#4e9a06\", \"slug\": \"ready-for-test\", \"is_closed\": false}, {\"name\": \"Done\", \"is_archived\": false, \"wip_limit\": null, \"order\": 5, \"color\": \"#cc0000\", \"slug\": \"done\", \"is_closed\": true}, {\"name\": \"Archived\", \"is_archived\": true, \"wip_limit\": null, \"order\": 6, \"color\": \"#5c3566\", \"slug\": \"archived\", \"is_closed\": true}]",
"points": "[{\"order\": 1, \"name\": \"?\", \"value\": null}, {\"order\": 2, \"name\": \"0\", \"value\": 0.0}, {\"order\": 3, \"name\": \"1/2\", \"value\": 0.5}, {\"order\": 4, \"name\": \"1\", \"value\": 1.0}, {\"order\": 5, \"name\": \"2\", \"value\": 2.0}, {\"order\": 6, \"name\": \"3\", \"value\": 3.0}, {\"order\": 7, \"name\": \"5\", \"value\": 5.0}, {\"order\": 8, \"name\": \"8\", \"value\": 8.0}, {\"order\": 9, \"name\": \"10\", \"value\": 10.0}, {\"order\": 10, \"name\": \"13\", \"value\": 13.0}, {\"order\": 11, \"name\": \"20\", \"value\": 20.0}, {\"order\": 12, \"name\": \"40\", \"value\": 40.0}]", "points": "[{\"order\": 1, \"name\": \"?\", \"value\": null}, {\"order\": 2, \"name\": \"0\", \"value\": 0.0}, {\"order\": 3, \"name\": \"1/2\", \"value\": 0.5}, {\"order\": 4, \"name\": \"1\", \"value\": 1.0}, {\"order\": 5, \"name\": \"2\", \"value\": 2.0}, {\"order\": 6, \"name\": \"3\", \"value\": 3.0}, {\"order\": 7, \"name\": \"5\", \"value\": 5.0}, {\"order\": 8, \"name\": \"8\", \"value\": 8.0}, {\"order\": 9, \"name\": \"10\", \"value\": 10.0}, {\"order\": 10, \"name\": \"13\", \"value\": 13.0}, {\"order\": 11, \"name\": \"20\", \"value\": 20.0}, {\"order\": 12, \"name\": \"40\", \"value\": 40.0}]",
"task_statuses": "[{\"order\": 1, \"name\": \"New\", \"slug\": \"new\", \"color\": \"#999999\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"slug\": \"in-progress\", \"color\": \"#729fcf\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"slug\": \"ready-for-test\", \"color\": \"#f57900\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"slug\": \"closed\", \"color\": \"#4e9a06\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"slug\": \"needs-info\", \"color\": \"#cc0000\", \"is_closed\": false}]", "task_statuses": "[{\"order\": 1, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"color\": \"#729fcf\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"color\": \"#f57900\", \"slug\": \"ready-for-test\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"color\": \"#4e9a06\", \"slug\": \"closed\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"color\": \"#cc0000\", \"slug\": \"needs-info\", \"is_closed\": false}]",
"issue_statuses": "[{\"order\": 1, \"name\": \"New\", \"slug\": \"new\", \"color\": \"#999999\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"slug\": \"in-progress\", \"color\": \"#729fcf\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"slug\": \"ready-for-test\", \"color\": \"#f57900\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"slug\": \"closed\", \"color\": \"#4e9a06\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"slug\": \"needs-info\", \"color\": \"#cc0000\", \"is_closed\": false}, {\"order\": 6, \"name\": \"Rejected\", \"slug\": \"rejected\", \"color\": \"#d3d7cf\", \"is_closed\": true}, {\"order\": 7, \"name\": \"Postponed\", \"slug\": \"posponed\", \"color\": \"#75507b\", \"is_closed\": false}]", "issue_statuses": "[{\"order\": 1, \"name\": \"New\", \"color\": \"#999999\", \"slug\": \"new\", \"is_closed\": false}, {\"order\": 2, \"name\": \"In progress\", \"color\": \"#729fcf\", \"slug\": \"in-progress\", \"is_closed\": false}, {\"order\": 3, \"name\": \"Ready for test\", \"color\": \"#f57900\", \"slug\": \"ready-for-test\", \"is_closed\": true}, {\"order\": 4, \"name\": \"Closed\", \"color\": \"#4e9a06\", \"slug\": \"closed\", \"is_closed\": true}, {\"order\": 5, \"name\": \"Needs Info\", \"color\": \"#cc0000\", \"slug\": \"needs-info\", \"is_closed\": false}, {\"order\": 6, \"name\": \"Rejected\", \"color\": \"#d3d7cf\", \"slug\": \"rejected\", \"is_closed\": true}, {\"order\": 7, \"name\": \"Postponed\", \"color\": \"#75507b\", \"slug\": \"posponed\", \"is_closed\": false}]",
"issue_types": "[{\"order\": 1, \"name\": \"Bug\", \"color\": \"#cc0000\"}, {\"order\": 2, \"name\": \"Question\", \"color\": \"#729fcf\"}, {\"order\": 3, \"name\": \"Enhancement\", \"color\": \"#4e9a06\"}]", "issue_types": "[{\"order\": 1, \"name\": \"Bug\", \"color\": \"#cc0000\"}, {\"order\": 2, \"name\": \"Question\", \"color\": \"#729fcf\"}, {\"order\": 3, \"name\": \"Enhancement\", \"color\": \"#4e9a06\"}]",
"priorities": "[{\"order\": 1, \"name\": \"Low\", \"color\": \"#999999\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#4e9a06\"}, {\"order\": 5, \"name\": \"High\", \"color\": \"#CC0000\"}]", "priorities": "[{\"order\": 1, \"name\": \"Low\", \"color\": \"#999999\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#4e9a06\"}, {\"order\": 5, \"name\": \"High\", \"color\": \"#CC0000\"}]",
"severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#999999\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#729fcf\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#4e9a06\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#f57900\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]", "severities": "[{\"order\": 1, \"name\": \"Wishlist\", \"color\": \"#999999\"}, {\"order\": 2, \"name\": \"Minor\", \"color\": \"#729fcf\"}, {\"order\": 3, \"name\": \"Normal\", \"color\": \"#4e9a06\"}, {\"order\": 4, \"name\": \"Important\", \"color\": \"#f57900\"}, {\"order\": 5, \"name\": \"Critical\", \"color\": \"#CC0000\"}]",
"roles": "[{\"order\": 10, \"name\": \"UX\", \"slug\": \"ux\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 20, \"name\": \"Design\", \"slug\": \"design\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 30, \"name\": \"Front\", \"slug\": \"front\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 40, \"name\": \"Back\", \"slug\": \"back\", \"computable\": true, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 50, \"name\": \"Product Owner\", \"slug\": \"product-owner\", \"computable\": false, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}, {\"order\": 60, \"name\": \"Stakeholder\", \"slug\": \"stakeholder\", \"computable\": false, \"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"view_milestones\", \"view_project\", \"view_tasks\", \"view_us\", \"modify_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"]}]" "roles": "[{\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 10, \"name\": \"UX\", \"slug\": \"ux\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 20, \"name\": \"Design\", \"slug\": \"design\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 30, \"name\": \"Front\", \"slug\": \"front\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 40, \"name\": \"Back\", \"slug\": \"back\", \"computable\": true}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"add_milestone\", \"modify_milestone\", \"delete_milestone\", \"view_milestones\", \"view_project\", \"add_task\", \"modify_task\", \"delete_task\", \"view_tasks\", \"add_us\", \"modify_us\", \"delete_us\", \"view_us\", \"add_wiki_page\", \"modify_wiki_page\", \"delete_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 50, \"name\": \"Product Owner\", \"slug\": \"product-owner\", \"computable\": false}, {\"permissions\": [\"add_issue\", \"modify_issue\", \"delete_issue\", \"view_issues\", \"view_milestones\", \"view_project\", \"view_tasks\", \"view_us\", \"modify_wiki_page\", \"view_wiki_pages\", \"add_wiki_link\", \"delete_wiki_link\", \"view_wiki_links\"], \"order\": 60, \"name\": \"Stakeholder\", \"slug\": \"stakeholder\", \"computable\": false}]"
} }
} }
] ]

View File

@ -34,6 +34,7 @@ from taiga.permissions.choices import ANON_PERMISSIONS
from taiga.projects.choices import BLOCKED_BY_STAFF from taiga.projects.choices import BLOCKED_BY_STAFF
from taiga.external_apps.models import Application, ApplicationToken from taiga.external_apps.models import Application, ApplicationToken
from taiga.projects.models import * from taiga.projects.models import *
from taiga.projects.epics.models import *
from taiga.projects.milestones.models import * from taiga.projects.milestones.models import *
from taiga.projects.notifications.choices import NotifyLevel from taiga.projects.notifications.choices import NotifyLevel
from taiga.projects.services.stats import get_stats_for_project from taiga.projects.services.stats import get_stats_for_project
@ -109,6 +110,8 @@ NUM_PROJECTS =getattr(settings, "SAMPLE_DATA_NUM_PROJECTS", 4)
NUM_EMPTY_PROJECTS = getattr(settings, "SAMPLE_DATA_NUM_EMPTY_PROJECTS", 2) NUM_EMPTY_PROJECTS = getattr(settings, "SAMPLE_DATA_NUM_EMPTY_PROJECTS", 2)
NUM_BLOCKED_PROJECTS = getattr(settings, "SAMPLE_DATA_NUM_BLOCKED_PROJECTS", 1) NUM_BLOCKED_PROJECTS = getattr(settings, "SAMPLE_DATA_NUM_BLOCKED_PROJECTS", 1)
NUM_MILESTONES = getattr(settings, "SAMPLE_DATA_NUM_MILESTONES", (1, 5)) NUM_MILESTONES = getattr(settings, "SAMPLE_DATA_NUM_MILESTONES", (1, 5))
NUM_EPICS = getattr(settings, "SAMPLE_DATA_NUM_EPICS", (4, 8))
NUM_USS_EPICS = getattr(settings, "SAMPLE_DATA_NUM_USS_EPICS", (2, 6))
NUM_USS = getattr(settings, "SAMPLE_DATA_NUM_USS", (3, 7)) NUM_USS = getattr(settings, "SAMPLE_DATA_NUM_USS", (3, 7))
NUM_TASKS_FINISHED = getattr(settings, "SAMPLE_DATA_NUM_TASKS_FINISHED", (1, 5)) NUM_TASKS_FINISHED = getattr(settings, "SAMPLE_DATA_NUM_TASKS_FINISHED", (1, 5))
NUM_TASKS = getattr(settings, "SAMPLE_DATA_NUM_TASKS", (0, 4)) NUM_TASKS = getattr(settings, "SAMPLE_DATA_NUM_TASKS", (0, 4))
@ -255,6 +258,11 @@ class Command(BaseCommand):
if self.sd.boolean(): if self.sd.boolean():
self.create_wiki_page(project, wiki_link.href) self.create_wiki_page(project, wiki_link.href)
# create epics
for y in range(self.sd.int(*NUM_EPICS)):
epic = self.create_epic(project)
project.refresh_from_db() project.refresh_from_db()
@ -494,6 +502,59 @@ class Command(BaseCommand):
return milestone return milestone
def create_epic(self, project):
epic = Epic.objects.create(subject=self.sd.choice(SUBJECT_CHOICES),
project=project,
owner=self.sd.db_object_from_queryset(
project.memberships.filter(user__isnull=False)).user,
description=self.sd.paragraph(),
status=self.sd.db_object_from_queryset(project.epic_statuses.filter(
is_closed=False)),
tags=self.sd.words(1, 3).split(" "))
# TODO: Epic custom attributes
#custom_attributes_values = {str(ca.id): self.get_custom_attributes_value(ca.type) for ca
# in project.epiccustomattributes.all() if self.sd.boolean()}
#if custom_attributes_values:
# epic.custom_attributes_values.attributes_values = custom_attributes_values
# epic.custom_attributes_values.save()
for i in range(self.sd.int(*NUM_ATTACHMENTS)):
attachment = self.create_attachment(epic, i+1)
if self.sd.choice([True, True, False, True, True]):
epic.assigned_to = self.sd.db_object_from_queryset(project.memberships.filter(
user__isnull=False)).user
epic.save()
# TODO: Epic history
#take_snapshot(epic,
# comment=self.sd.paragraph(),
# user=epic.owner)
#
# Add history entry
#epic.status=self.sd.db_object_from_queryset(project.epic_statuses.filter(is_closed=False))
#epic.save()
#take_snapshot(epic,
# comment=self.sd.paragraph(),
# user=epic.owner)
# TODO: Epic voters
#self.create_votes(epic)
# TODO: Epic watchers
#self.create_watchers(epic)
if self.sd.choice([True, True, False, True, True]):
filters = {}
if self.sd.choice([True, True, False, True, True]):
filters = {"project": epic.project}
n = self.sd.choice(list(range(self.sd.int(*NUM_USS_EPICS))))
epic.user_stories.add(*UserStory.objects.filter(**filters).order_by("?")[:n])
return epic
def create_project(self, counter, is_private=None, blocked_code=None): def create_project(self, counter, is_private=None, blocked_code=None):
if is_private is None: if is_private is None:
is_private=self.sd.boolean() is_private=self.sd.boolean()

View File

@ -110,6 +110,7 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('projects', '0029_project_is_looking_for_people'), ('projects', '0029_project_is_looking_for_people'),
('likes', '0001_initial'),
('timeline', '0004_auto_20150603_1312'), ('timeline', '0004_auto_20150603_1312'),
('likes', '0001_initial'), ('likes', '0001_initial'),
] ]

View File

@ -9,6 +9,9 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('projects', '0045_merge'), ('projects', '0045_merge'),
('userstories', '0011_userstory_tribe_gig'),
('tasks', '0009_auto_20151104_1131'),
('issues', '0006_remove_issue_watchers'),
] ]
operations = [ operations = [

View File

@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-06-29 14:43
from __future__ import unicode_literals
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import django_pgjson.fields
class Migration(migrations.Migration):
dependencies = [
('projects', '0048_auto_20160615_1508'),
]
operations = [
migrations.CreateModel(
name='EpicStatus',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('slug', models.SlugField(blank=True, max_length=255, verbose_name='slug')),
('order', models.IntegerField(default=10, verbose_name='order')),
('is_closed', models.BooleanField(default=False, verbose_name='is closed')),
('color', models.CharField(default='#999999', max_length=20, verbose_name='color')),
],
options={
'verbose_name_plural': 'epic statuses',
'ordering': ['project', 'order', 'name'],
'verbose_name': 'epic status',
},
),
migrations.AlterModelOptions(
name='issuestatus',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'issue status', 'verbose_name_plural': 'issue statuses'},
),
migrations.AlterModelOptions(
name='issuetype',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'issue type', 'verbose_name_plural': 'issue types'},
),
migrations.AlterModelOptions(
name='membership',
options={'ordering': ['project', 'user__full_name', 'user__username', 'user__email', 'email'], 'verbose_name': 'membership', 'verbose_name_plural': 'memberships'},
),
migrations.AlterModelOptions(
name='points',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'points', 'verbose_name_plural': 'points'},
),
migrations.AlterModelOptions(
name='priority',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'priority', 'verbose_name_plural': 'priorities'},
),
migrations.AlterModelOptions(
name='severity',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'severity', 'verbose_name_plural': 'severities'},
),
migrations.AlterModelOptions(
name='taskstatus',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'task status', 'verbose_name_plural': 'task statuses'},
),
migrations.AlterModelOptions(
name='userstorystatus',
options={'ordering': ['project', 'order', 'name'], 'verbose_name': 'user story status', 'verbose_name_plural': 'user story statuses'},
),
migrations.AddField(
model_name='project',
name='is_epics_activated',
field=models.BooleanField(default=True, verbose_name='active epics panel'),
),
migrations.AddField(
model_name='projecttemplate',
name='epic_statuses',
field=django_pgjson.fields.JsonField(blank=True, null=True, verbose_name='epic statuses'),
),
migrations.AddField(
model_name='projecttemplate',
name='is_epics_activated',
field=models.BooleanField(default=True, verbose_name='active epics panel'),
),
migrations.AlterField(
model_name='project',
name='anon_permissions',
field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('view_epic', 'View epic'), ('view_us', 'View user stories'), ('view_tasks', 'View tasks'), ('view_issues', 'View issues'), ('view_wiki_pages', 'View wiki pages'), ('view_wiki_links', 'View wiki links')]), blank=True, default=[], null=True, size=None, verbose_name='anonymous permissions'),
),
migrations.AlterField(
model_name='project',
name='public_permissions',
field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_epic', 'View epic'), ('add_epic', 'Add epic'), ('modify_epic', 'Modify epic'), ('comment_epic', 'Comment epic'), ('delete_epic', 'Delete epic'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('comment_us', 'Comment user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('comment_task', 'Comment task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('comment_issue', 'Comment issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('comment_wiki_page', 'Comment wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='user permissions'),
),
migrations.AddField(
model_name='epicstatus',
name='project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='epic_statuses', to='projects.Project', verbose_name='project'),
),
migrations.AddField(
model_name='project',
name='default_epic_status',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='projects.EpicStatus', verbose_name='default epic status'),
),
migrations.AlterUniqueTogether(
name='epicstatus',
unique_together=set([('project', 'slug'), ('project', 'name')]),
),
]

View File

@ -102,19 +102,20 @@ class Membership(models.Model):
verbose_name_plural = "memberships" verbose_name_plural = "memberships"
unique_together = ("user", "project",) unique_together = ("user", "project",)
ordering = ["project", "user__full_name", "user__username", "user__email", "email"] ordering = ["project", "user__full_name", "user__username", "user__email", "email"]
permissions = (
("view_membership", "Can view membership"),
)
class ProjectDefaults(models.Model): class ProjectDefaults(models.Model):
default_points = models.OneToOneField("projects.Points", on_delete=models.SET_NULL, default_epic_status = models.OneToOneField("projects.EpicStatus",
related_name="+", null=True, blank=True, on_delete=models.SET_NULL, related_name="+",
verbose_name=_("default points")) null=True, blank=True,
verbose_name=_("default epic status"))
default_us_status = models.OneToOneField("projects.UserStoryStatus", default_us_status = models.OneToOneField("projects.UserStoryStatus",
on_delete=models.SET_NULL, related_name="+", on_delete=models.SET_NULL, related_name="+",
null=True, blank=True, null=True, blank=True,
verbose_name=_("default US status")) verbose_name=_("default US status"))
default_points = models.OneToOneField("projects.Points", on_delete=models.SET_NULL,
related_name="+", null=True, blank=True,
verbose_name=_("default points"))
default_task_status = models.OneToOneField("projects.TaskStatus", default_task_status = models.OneToOneField("projects.TaskStatus",
on_delete=models.SET_NULL, related_name="+", on_delete=models.SET_NULL, related_name="+",
null=True, blank=True, null=True, blank=True,
@ -164,6 +165,8 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model):
verbose_name=_("total of milestones")) verbose_name=_("total of milestones"))
total_story_points = models.FloatField(null=True, blank=True, verbose_name=_("total story points")) total_story_points = models.FloatField(null=True, blank=True, verbose_name=_("total story points"))
is_epics_activated = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("active epics panel"))
is_backlog_activated = models.BooleanField(default=True, null=False, blank=True, is_backlog_activated = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("active backlog panel")) verbose_name=_("active backlog panel"))
is_kanban_activated = models.BooleanField(default=False, null=False, blank=True, is_kanban_activated = models.BooleanField(default=False, null=False, blank=True,
@ -504,6 +507,39 @@ class ProjectModulesConfig(models.Model):
ordering = ["project"] ordering = ["project"]
# Epic common Models
class EpicStatus(models.Model):
name = models.CharField(max_length=255, null=False, blank=False,
verbose_name=_("name"))
slug = models.SlugField(max_length=255, null=False, blank=True,
verbose_name=_("slug"))
order = models.IntegerField(default=10, null=False, blank=False,
verbose_name=_("order"))
is_closed = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_("is closed"))
color = models.CharField(max_length=20, null=False, blank=False, default="#999999",
verbose_name=_("color"))
project = models.ForeignKey("Project", null=False, blank=False,
related_name="epic_statuses", verbose_name=_("project"))
class Meta:
verbose_name = "epic status"
verbose_name_plural = "epic statuses"
ordering = ["project", "order", "name"]
unique_together = (("project", "name"), ("project", "slug"))
def __str__(self):
return self.name
def save(self, *args, **kwargs):
qs = self.project.epic_statuses
if self.id:
qs = qs.exclude(id=self.id)
self.slug = slugify_uniquely_for_queryset(self.name, qs)
return super().save(*args, **kwargs)
# User Stories common Models # User Stories common Models
class UserStoryStatus(models.Model): class UserStoryStatus(models.Model):
name = models.CharField(max_length=255, null=False, blank=False, name = models.CharField(max_length=255, null=False, blank=False,
@ -528,9 +564,6 @@ class UserStoryStatus(models.Model):
verbose_name_plural = "user story statuses" verbose_name_plural = "user story statuses"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = (("project", "name"), ("project", "slug")) unique_together = (("project", "name"), ("project", "slug"))
permissions = (
("view_userstorystatus", "Can view user story status"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -559,9 +592,6 @@ class Points(models.Model):
verbose_name_plural = "points" verbose_name_plural = "points"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = ("project", "name") unique_together = ("project", "name")
permissions = (
("view_points", "Can view points"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -588,9 +618,6 @@ class TaskStatus(models.Model):
verbose_name_plural = "task statuses" verbose_name_plural = "task statuses"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = (("project", "name"), ("project", "slug")) unique_together = (("project", "name"), ("project", "slug"))
permissions = (
("view_taskstatus", "Can view task status"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -621,9 +648,6 @@ class Priority(models.Model):
verbose_name_plural = "priorities" verbose_name_plural = "priorities"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = ("project", "name") unique_together = ("project", "name")
permissions = (
("view_priority", "Can view priority"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -644,9 +668,6 @@ class Severity(models.Model):
verbose_name_plural = "severities" verbose_name_plural = "severities"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = ("project", "name") unique_together = ("project", "name")
permissions = (
("view_severity", "Can view severity"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -671,9 +692,6 @@ class IssueStatus(models.Model):
verbose_name_plural = "issue statuses" verbose_name_plural = "issue statuses"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = (("project", "name"), ("project", "slug")) unique_together = (("project", "name"), ("project", "slug"))
permissions = (
("view_issuestatus", "Can view issue status"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -702,9 +720,6 @@ class IssueType(models.Model):
verbose_name_plural = "issue types" verbose_name_plural = "issue types"
ordering = ["project", "order", "name"] ordering = ["project", "order", "name"]
unique_together = ("project", "name") unique_together = ("project", "name")
permissions = (
("view_issuetype", "Can view issue type"),
)
def __str__(self): def __str__(self):
return self.name return self.name
@ -728,6 +743,8 @@ class ProjectTemplate(models.Model):
blank=False, blank=False,
verbose_name=_("default owner's role")) verbose_name=_("default owner's role"))
is_epics_activated = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("active epics panel"))
is_backlog_activated = models.BooleanField(default=True, null=False, blank=True, is_backlog_activated = models.BooleanField(default=True, null=False, blank=True,
verbose_name=_("active backlog panel")) verbose_name=_("active backlog panel"))
is_kanban_activated = models.BooleanField(default=False, null=False, blank=True, is_kanban_activated = models.BooleanField(default=False, null=False, blank=True,
@ -743,6 +760,7 @@ class ProjectTemplate(models.Model):
verbose_name=_("videoconference extra data")) verbose_name=_("videoconference extra data"))
default_options = JsonField(null=True, blank=True, verbose_name=_("default options")) default_options = JsonField(null=True, blank=True, verbose_name=_("default options"))
epic_statuses = JsonField(null=True, blank=True, verbose_name=_("epic statuses"))
us_statuses = JsonField(null=True, blank=True, verbose_name=_("us statuses")) us_statuses = JsonField(null=True, blank=True, verbose_name=_("us statuses"))
points = JsonField(null=True, blank=True, verbose_name=_("points")) points = JsonField(null=True, blank=True, verbose_name=_("points"))
task_statuses = JsonField(null=True, blank=True, verbose_name=_("task statuses")) task_statuses = JsonField(null=True, blank=True, verbose_name=_("task statuses"))
@ -770,6 +788,7 @@ class ProjectTemplate(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def load_data_from_project(self, project): def load_data_from_project(self, project):
self.is_epics_activated = project.is_epics_activated
self.is_backlog_activated = project.is_backlog_activated self.is_backlog_activated = project.is_backlog_activated
self.is_kanban_activated = project.is_kanban_activated self.is_kanban_activated = project.is_kanban_activated
self.is_wiki_activated = project.is_wiki_activated self.is_wiki_activated = project.is_wiki_activated
@ -779,6 +798,7 @@ class ProjectTemplate(models.Model):
self.default_options = { self.default_options = {
"points": getattr(project.default_points, "name", None), "points": getattr(project.default_points, "name", None),
"epic_status": getattr(project.default_epic_status, "name", None),
"us_status": getattr(project.default_us_status, "name", None), "us_status": getattr(project.default_us_status, "name", None),
"task_status": getattr(project.default_task_status, "name", None), "task_status": getattr(project.default_task_status, "name", None),
"issue_status": getattr(project.default_issue_status, "name", None), "issue_status": getattr(project.default_issue_status, "name", None),
@ -787,6 +807,16 @@ class ProjectTemplate(models.Model):
"severity": getattr(project.default_severity, "name", None) "severity": getattr(project.default_severity, "name", None)
} }
self.epic_statuses = []
for epic_status in project.epic_statuses.all():
self.epic_statuses.append({
"name": epic_status.name,
"slug": epic_status.slug,
"is_closed": epic_status.is_closed,
"color": epic_status.color,
"order": epic_status.order,
})
self.us_statuses = [] self.us_statuses = []
for us_status in project.us_statuses.all(): for us_status in project.us_statuses.all():
self.us_statuses.append({ self.us_statuses.append({
@ -874,6 +904,7 @@ class ProjectTemplate(models.Model):
raise Exception("Project need an id (must be a saved project)") raise Exception("Project need an id (must be a saved project)")
project.creation_template = self project.creation_template = self
project.is_epics_activated = self.is_epics_activated
project.is_backlog_activated = self.is_backlog_activated project.is_backlog_activated = self.is_backlog_activated
project.is_kanban_activated = self.is_kanban_activated project.is_kanban_activated = self.is_kanban_activated
project.is_wiki_activated = self.is_wiki_activated project.is_wiki_activated = self.is_wiki_activated
@ -881,6 +912,16 @@ class ProjectTemplate(models.Model):
project.videoconferences = self.videoconferences project.videoconferences = self.videoconferences
project.videoconferences_extra_data = self.videoconferences_extra_data project.videoconferences_extra_data = self.videoconferences_extra_data
for epic_status in self.epic_statuses:
EpicStatus.objects.create(
name=epic_status["name"],
slug=epic_status["slug"],
is_closed=epic_status["is_closed"],
color=epic_status["color"],
order=epic_status["order"],
project=project
)
for us_status in self.us_statuses: for us_status in self.us_statuses:
UserStoryStatus.objects.create( UserStoryStatus.objects.create(
name=us_status["name"], name=us_status["name"],
@ -955,12 +996,16 @@ class ProjectTemplate(models.Model):
permissions=role['permissions'] permissions=role['permissions']
) )
if self.points: if self.epic_statuses:
project.default_points = Points.objects.get(name=self.default_options["points"], project.default_epic_status = EpicStatus.objects.get(name=self.default_options["epic_status"],
project=project) project=project)
if self.us_statuses: if self.us_statuses:
project.default_us_status = UserStoryStatus.objects.get(name=self.default_options["us_status"], project.default_us_status = UserStoryStatus.objects.get(name=self.default_options["us_status"],
project=project) project=project)
if self.points:
project.default_points = Points.objects.get(name=self.default_options["points"],
project=project)
if self.task_statuses: if self.task_statuses:
project.default_task_status = TaskStatus.objects.get(name=self.default_options["task_status"], project.default_task_status = TaskStatus.objects.get(name=self.default_options["task_status"],

View File

@ -109,6 +109,18 @@ class MembershipPermission(TaigaResourcePermission):
resend_invitation_perms = IsProjectAdmin() resend_invitation_perms = IsProjectAdmin()
# Epics
class EpicStatusPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectAdmin()
update_perms = IsProjectAdmin()
partial_update_perms = IsProjectAdmin()
destroy_perms = IsProjectAdmin()
list_perms = AllowAny()
bulk_update_order_perms = IsProjectAdmin()
# User Stories # User Stories
class PointsPermission(TaigaResourcePermission): class PointsPermission(TaigaResourcePermission):

View File

@ -37,11 +37,13 @@ from .notifications.choices import NotifyLevel
# Custom values for selectors # Custom values for selectors
###################################################### ######################################################
class PointsSerializer(serializers.LightSerializer): class EpicStatusSerializer(serializers.LightSerializer):
id = Field() id = Field()
name = I18NField() name = I18NField()
slug = Field()
order = Field() order = Field()
value = Field() is_closed = Field()
color = Field()
project = Field(attr="project_id") project = Field(attr="project_id")
@ -57,6 +59,14 @@ class UserStoryStatusSerializer(serializers.LightSerializer):
project = Field(attr="project_id") project = Field(attr="project_id")
class PointsSerializer(serializers.LightSerializer):
id = Field()
name = I18NField()
order = Field()
value = Field()
project = Field(attr="project_id")
class TaskStatusSerializer(serializers.LightSerializer): class TaskStatusSerializer(serializers.LightSerializer):
id = Field() id = Field()
name = I18NField() name = I18NField()

View File

@ -19,7 +19,7 @@
# This makes all code that import services works and # This makes all code that import services works and
# is not the baddest practice ;) # is not the baddest practice ;)
from .bulk_update_order import update_projects_order_in_bulk from .bulk_update_order import apply_order_updates
from .bulk_update_order import bulk_update_severity_order from .bulk_update_order import bulk_update_severity_order
from .bulk_update_order import bulk_update_priority_order from .bulk_update_order import bulk_update_priority_order
from .bulk_update_order import bulk_update_issue_type_order from .bulk_update_order import bulk_update_issue_type_order
@ -27,7 +27,8 @@ from .bulk_update_order import bulk_update_issue_status_order
from .bulk_update_order import bulk_update_task_status_order from .bulk_update_order import bulk_update_task_status_order
from .bulk_update_order import bulk_update_points_order from .bulk_update_order import bulk_update_points_order
from .bulk_update_order import bulk_update_userstory_status_order from .bulk_update_order import bulk_update_userstory_status_order
from .bulk_update_order import apply_order_updates from .bulk_update_order import bulk_update_epic_status_order
from .bulk_update_order import update_projects_order_in_bulk
from .filters import get_all_tags from .filters import get_all_tags

View File

@ -86,6 +86,23 @@ def update_projects_order_in_bulk(bulk_data: list, field: str, user):
db.update_attr_in_bulk_for_ids(memberships_orders, field, model=models.Membership) db.update_attr_in_bulk_for_ids(memberships_orders, field, model=models.Membership)
@transaction.atomic
def bulk_update_epic_status_order(project, user, data):
cursor = connection.cursor()
sql = """
prepare bulk_update_order as update projects_epicstatus set "order" = $1
where projects_epicstatus.id = $2 and
projects_epicstatus.project_id = $3;
"""
cursor.execute(sql)
for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id))
cursor.execute("DEALLOCATE bulk_update_order")
cursor.close()
@transaction.atomic @transaction.atomic
def bulk_update_userstory_status_order(project, user, data): def bulk_update_userstory_status_order(project, user, data):
cursor = connection.cursor() cursor = connection.cursor()

View File

@ -117,6 +117,26 @@ def attach_notify_policies(queryset, as_field="notify_policies_attr"):
return queryset return queryset
def attach_epic_statuses(queryset, as_field="epic_statuses_attr"):
"""Attach a json epic statuses representation to each object of the queryset.
:param queryset: A Django projects queryset object.
:param as_field: Attach the epic statuses as an attribute with this name.
:return: Queryset object with the additional `as_field` field.
"""
model = queryset.model
sql = """SELECT json_agg(row_to_json(projects_epicstatus))
FROM projects_epicstatus
WHERE
projects_epicstatus.project_id = {tbl}.id
"""
sql = sql.format(tbl=model._meta.db_table)
queryset = queryset.extra(select={as_field: sql})
return queryset
def attach_userstory_statuses(queryset, as_field="userstory_statuses_attr"): def attach_userstory_statuses(queryset, as_field="userstory_statuses_attr"):
"""Attach a json userstory statuses representation to each object of the queryset. """Attach a json userstory statuses representation to each object of the queryset.
@ -443,6 +463,7 @@ def attach_extra_info(queryset, user=None):
queryset = attach_members(queryset) queryset = attach_members(queryset)
queryset = attach_closed_milestones(queryset) queryset = attach_closed_milestones(queryset)
queryset = attach_notify_policies(queryset) queryset = attach_notify_policies(queryset)
queryset = attach_epic_statuses(queryset)
queryset = attach_userstory_statuses(queryset) queryset = attach_userstory_statuses(queryset)
queryset = attach_points(queryset) queryset = attach_points(queryset)
queryset = attach_task_statuses(queryset) queryset = attach_task_statuses(queryset)

View File

@ -86,9 +86,9 @@ class TaskStatusExistsValidator:
# Custom values for selectors # Custom values for selectors
###################################################### ######################################################
class PointsValidator(DuplicatedNameInProjectValidator, validators.ModelValidator): class EpicStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
class Meta: class Meta:
model = models.Points model = models.EpicStatus
class UserStoryStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator): class UserStoryStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
@ -96,6 +96,11 @@ class UserStoryStatusValidator(DuplicatedNameInProjectValidator, validators.Mode
model = models.UserStoryStatus model = models.UserStoryStatus
class PointsValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
class Meta:
model = models.Points
class TaskStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator): class TaskStatusValidator(DuplicatedNameInProjectValidator, validators.ModelValidator):
class Meta: class Meta:
model = models.TaskStatus model = models.TaskStatus

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.2 on 2016-06-29 14:43
from __future__ import unicode_literals
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0021_auto_20160614_1201'),
]
operations = [
migrations.AlterField(
model_name='role',
name='permissions',
field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('view_project', 'View project'), ('view_milestones', 'View milestones'), ('add_milestone', 'Add milestone'), ('modify_milestone', 'Modify milestone'), ('delete_milestone', 'Delete milestone'), ('view_epic', 'View epic'), ('add_epic', 'Add epic'), ('modify_epic', 'Modify epic'), ('comment_epic', 'Comment epic'), ('delete_epic', 'Delete epic'), ('view_us', 'View user story'), ('add_us', 'Add user story'), ('modify_us', 'Modify user story'), ('comment_us', 'Comment user story'), ('delete_us', 'Delete user story'), ('view_tasks', 'View tasks'), ('add_task', 'Add task'), ('modify_task', 'Modify task'), ('comment_task', 'Comment task'), ('delete_task', 'Delete task'), ('view_issues', 'View issues'), ('add_issue', 'Add issue'), ('modify_issue', 'Modify issue'), ('comment_issue', 'Comment issue'), ('delete_issue', 'Delete issue'), ('view_wiki_pages', 'View wiki pages'), ('add_wiki_page', 'Add wiki page'), ('modify_wiki_page', 'Modify wiki page'), ('comment_wiki_page', 'Comment wiki page'), ('delete_wiki_page', 'Delete wiki page'), ('view_wiki_links', 'View wiki links'), ('add_wiki_link', 'Add wiki link'), ('modify_wiki_link', 'Modify wiki link'), ('delete_wiki_link', 'Delete wiki link')]), blank=True, default=[], null=True, size=None, verbose_name='permissions'),
),
]

View File

@ -229,22 +229,17 @@ class StorageEntryFactory(Factory):
value = factory.Sequence(lambda n: {"value": "value-{}".format(n)}) value = factory.Sequence(lambda n: {"value": "value-{}".format(n)})
class UserStoryStatusFactory(Factory): class EpicFactory(Factory):
class Meta: class Meta:
model = "projects.UserStoryStatus" model = "epics.Epic"
strategy = factory.CREATE_STRATEGY strategy = factory.CREATE_STRATEGY
name = factory.Sequence(lambda n: "User Story status {}".format(n)) ref = factory.Sequence(lambda n: n)
project = factory.SubFactory("tests.factories.ProjectFactory")
class TaskStatusFactory(Factory):
class Meta:
model = "projects.TaskStatus"
strategy = factory.CREATE_STRATEGY
name = factory.Sequence(lambda n: "Task status {}".format(n))
project = factory.SubFactory("tests.factories.ProjectFactory") project = factory.SubFactory("tests.factories.ProjectFactory")
owner = factory.SubFactory("tests.factories.UserFactory")
subject = factory.Sequence(lambda n: "User Story {}".format(n))
description = factory.Sequence(lambda n: "User Story {} description".format(n))
status = factory.SubFactory("tests.factories.EpicStatusFactory")
class MilestoneFactory(Factory): class MilestoneFactory(Factory):
@ -330,6 +325,33 @@ class WikiLinkFactory(Factory):
order = factory.Sequence(lambda n: n) order = factory.Sequence(lambda n: n)
class EpicStatusFactory(Factory):
class Meta:
model = "projects.EpicStatus"
strategy = factory.CREATE_STRATEGY
name = factory.Sequence(lambda n: "Epic status {}".format(n))
project = factory.SubFactory("tests.factories.ProjectFactory")
class UserStoryStatusFactory(Factory):
class Meta:
model = "projects.UserStoryStatus"
strategy = factory.CREATE_STRATEGY
name = factory.Sequence(lambda n: "User Story status {}".format(n))
project = factory.SubFactory("tests.factories.ProjectFactory")
class TaskStatusFactory(Factory):
class Meta:
model = "projects.TaskStatus"
strategy = factory.CREATE_STRATEGY
name = factory.Sequence(lambda n: "Task status {}".format(n))
project = factory.SubFactory("tests.factories.ProjectFactory")
class IssueStatusFactory(Factory): class IssueStatusFactory(Factory):
class Meta: class Meta:
model = "projects.IssueStatus" model = "projects.IssueStatus"

View File

@ -115,6 +115,11 @@ def data():
user=m.project_owner, user=m.project_owner,
is_admin=True) is_admin=True)
m.public_epic_status = f.EpicStatusFactory(project=m.public_project)
m.private_epic_status1 = f.EpicStatusFactory(project=m.private_project1)
m.private_epic_status2 = f.EpicStatusFactory(project=m.private_project2)
m.blocked_epic_status = f.EpicStatusFactory(project=m.blocked_project)
m.public_points = f.PointsFactory(project=m.public_project) m.public_points = f.PointsFactory(project=m.public_project)
m.private_points1 = f.PointsFactory(project=m.private_project1) m.private_points1 = f.PointsFactory(project=m.private_project1)
m.private_points2 = f.PointsFactory(project=m.private_project2) m.private_points2 = f.PointsFactory(project=m.private_project2)
@ -155,6 +160,10 @@ def data():
return m return m
#####################################################
# Roles
#####################################################
def test_roles_retrieve(client, data): def test_roles_retrieve(client, data):
public_url = reverse('roles-detail', kwargs={"pk": data.public_project.roles.all()[0].pk}) public_url = reverse('roles-detail', kwargs={"pk": data.public_project.roles.all()[0].pk})
private1_url = reverse('roles-detail', kwargs={"pk": data.private_project1.roles.all()[0].pk}) private1_url = reverse('roles-detail', kwargs={"pk": data.private_project1.roles.all()[0].pk})
@ -299,6 +308,198 @@ def test_roles_patch(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Epic Status
#####################################################
def test_epic_status_retrieve(client, data):
public_url = reverse('epic-statuses-detail', kwargs={"pk": data.public_epic_status.pk})
private1_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status1.pk})
private2_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status2.pk})
blocked_url = reverse('epic-statuses-detail', kwargs={"pk": data.blocked_epic_status.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [401, 403, 403, 200, 200]
results = helper_test_http_method(client, 'get', blocked_url, None, users)
assert results == [401, 403, 403, 200, 200]
def test_epic_status_update(client, data):
public_url = reverse('epic-statuses-detail', kwargs={"pk": data.public_epic_status.pk})
private1_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status1.pk})
private2_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status2.pk})
blocked_url = reverse('epic-statuses-detail', kwargs={"pk": data.blocked_epic_status.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
epic_status_data = serializers.EpicStatusSerializer(data.public_epic_status).data
epic_status_data["name"] = "test"
epic_status_data = json.dumps(epic_status_data)
results = helper_test_http_method(client, 'put', public_url, epic_status_data, users)
assert results == [401, 403, 403, 403, 200]
epic_status_data = serializers.EpicStatusSerializer(data.private_epic_status1).data
epic_status_data["name"] = "test"
epic_status_data = json.dumps(epic_status_data)
results = helper_test_http_method(client, 'put', private1_url, epic_status_data, users)
assert results == [401, 403, 403, 403, 200]
epic_status_data = serializers.EpicStatusSerializer(data.private_epic_status2).data
epic_status_data["name"] = "test"
epic_status_data = json.dumps(epic_status_data)
results = helper_test_http_method(client, 'put', private2_url, epic_status_data, users)
assert results == [401, 403, 403, 403, 200]
epic_status_data = serializers.EpicStatusSerializer(data.blocked_epic_status).data
epic_status_data["name"] = "test"
epic_status_data = json.dumps(epic_status_data)
results = helper_test_http_method(client, 'put', blocked_url, epic_status_data, users)
assert results == [401, 403, 403, 403, 451]
def test_epic_status_delete(client, data):
public_url = reverse('epic-statuses-detail', kwargs={"pk": data.public_epic_status.pk})
private1_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status1.pk})
private2_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status2.pk})
blocked_url = reverse('epic-statuses-detail', kwargs={"pk": data.blocked_epic_status.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'delete', public_url, None, users)
assert results == [401, 403, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private1_url, None, users)
assert results == [401, 403, 403, 403, 204]
results = helper_test_http_method(client, 'delete', private2_url, None, users)
assert results == [401, 403, 403, 403, 204]
results = helper_test_http_method(client, 'delete', blocked_url, None, users)
assert results == [401, 403, 403, 403, 451]
def test_epic_status_list(client, data):
url = reverse('epic-statuses-list')
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 2
assert response.status_code == 200
client.login(data.registered_user)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 2
assert response.status_code == 200
client.login(data.project_member_without_perms)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 2
assert response.status_code == 200
client.login(data.project_member_with_perms)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 4
assert response.status_code == 200
client.login(data.project_owner)
response = client.get(url)
projects_data = json.loads(response.content.decode('utf-8'))
assert len(projects_data) == 4
assert response.status_code == 200
def test_epic_status_patch(client, data):
public_url = reverse('epic-statuses-detail', kwargs={"pk": data.public_epic_status.pk})
private1_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status1.pk})
private2_url = reverse('epic-statuses-detail', kwargs={"pk": data.private_epic_status2.pk})
blocked_url = reverse('epic-statuses-detail', kwargs={"pk": data.blocked_epic_status.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'patch', public_url, '{"name": "Test"}', users)
assert results == [401, 403, 403, 403, 200]
results = helper_test_http_method(client, 'patch', private1_url, '{"name": "Test"}', users)
assert results == [401, 403, 403, 403, 200]
results = helper_test_http_method(client, 'patch', private2_url, '{"name": "Test"}', users)
assert results == [401, 403, 403, 403, 200]
results = helper_test_http_method(client, 'patch', blocked_url, '{"name": "Test"}', users)
assert results == [401, 403, 403, 403, 451]
def test_epic_status_action_bulk_update_order(client, data):
url = reverse('epic-statuses-bulk-update-order')
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
post_data = json.dumps({
"bulk_epic_statuses": [(1, 2)],
"project": data.public_project.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 403, 204]
post_data = json.dumps({
"bulk_epic_statuses": [(1, 2)],
"project": data.private_project1.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 403, 204]
post_data = json.dumps({
"bulk_epic_statuses": [(1, 2)],
"project": data.private_project2.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 403, 204]
post_data = json.dumps({
"bulk_epic_statuses": [(1, 2)],
"project": data.blocked_project.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 403, 451]
#####################################################
# Points
#####################################################
def test_points_retrieve(client, data): def test_points_retrieve(client, data):
public_url = reverse('points-detail', kwargs={"pk": data.public_points.pk}) public_url = reverse('points-detail', kwargs={"pk": data.public_points.pk})
private1_url = reverse('points-detail', kwargs={"pk": data.private_points1.pk}) private1_url = reverse('points-detail', kwargs={"pk": data.private_points1.pk})
@ -483,6 +684,10 @@ def test_points_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# User Story Status
#####################################################
def test_user_story_status_retrieve(client, data): def test_user_story_status_retrieve(client, data):
public_url = reverse('userstory-statuses-detail', kwargs={"pk": data.public_user_story_status.pk}) public_url = reverse('userstory-statuses-detail', kwargs={"pk": data.public_user_story_status.pk})
private1_url = reverse('userstory-statuses-detail', kwargs={"pk": data.private_user_story_status1.pk}) private1_url = reverse('userstory-statuses-detail', kwargs={"pk": data.private_user_story_status1.pk})
@ -570,7 +775,6 @@ def test_user_story_status_delete(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
def test_user_story_status_list(client, data): def test_user_story_status_list(client, data):
url = reverse('userstory-statuses-list') url = reverse('userstory-statuses-list')
@ -668,6 +872,10 @@ def test_user_story_status_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Task Status
#####################################################
def test_task_status_retrieve(client, data): def test_task_status_retrieve(client, data):
public_url = reverse('task-statuses-detail', kwargs={"pk": data.public_task_status.pk}) public_url = reverse('task-statuses-detail', kwargs={"pk": data.public_task_status.pk})
private1_url = reverse('task-statuses-detail', kwargs={"pk": data.private_task_status1.pk}) private1_url = reverse('task-statuses-detail', kwargs={"pk": data.private_task_status1.pk})
@ -755,7 +963,6 @@ def test_task_status_delete(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
def test_task_status_list(client, data): def test_task_status_list(client, data):
url = reverse('task-statuses-list') url = reverse('task-statuses-list')
@ -853,6 +1060,10 @@ def test_task_status_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Issue Status
#####################################################
def test_issue_status_retrieve(client, data): def test_issue_status_retrieve(client, data):
public_url = reverse('issue-statuses-detail', kwargs={"pk": data.public_issue_status.pk}) public_url = reverse('issue-statuses-detail', kwargs={"pk": data.public_issue_status.pk})
private1_url = reverse('issue-statuses-detail', kwargs={"pk": data.private_issue_status1.pk}) private1_url = reverse('issue-statuses-detail', kwargs={"pk": data.private_issue_status1.pk})
@ -1037,6 +1248,10 @@ def test_issue_status_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Issue Type
#####################################################
def test_issue_type_retrieve(client, data): def test_issue_type_retrieve(client, data):
public_url = reverse('issue-types-detail', kwargs={"pk": data.public_issue_type.pk}) public_url = reverse('issue-types-detail', kwargs={"pk": data.public_issue_type.pk})
private1_url = reverse('issue-types-detail', kwargs={"pk": data.private_issue_type1.pk}) private1_url = reverse('issue-types-detail', kwargs={"pk": data.private_issue_type1.pk})
@ -1221,6 +1436,10 @@ def test_issue_type_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Priority
#####################################################
def test_priority_retrieve(client, data): def test_priority_retrieve(client, data):
public_url = reverse('priorities-detail', kwargs={"pk": data.public_priority.pk}) public_url = reverse('priorities-detail', kwargs={"pk": data.public_priority.pk})
private1_url = reverse('priorities-detail', kwargs={"pk": data.private_priority1.pk}) private1_url = reverse('priorities-detail', kwargs={"pk": data.private_priority1.pk})
@ -1283,6 +1502,7 @@ def test_priority_update(client, data):
results = helper_test_http_method(client, 'put', blocked_url, priority_data, users) results = helper_test_http_method(client, 'put', blocked_url, priority_data, users)
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
def test_priority_delete(client, data): def test_priority_delete(client, data):
public_url = reverse('priorities-detail', kwargs={"pk": data.public_priority.pk}) public_url = reverse('priorities-detail', kwargs={"pk": data.public_priority.pk})
private1_url = reverse('priorities-detail', kwargs={"pk": data.private_priority1.pk}) private1_url = reverse('priorities-detail', kwargs={"pk": data.private_priority1.pk})
@ -1404,6 +1624,10 @@ def test_priority_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Severity
#####################################################
def test_severity_retrieve(client, data): def test_severity_retrieve(client, data):
public_url = reverse('severities-detail', kwargs={"pk": data.public_severity.pk}) public_url = reverse('severities-detail', kwargs={"pk": data.public_severity.pk})
private1_url = reverse('severities-detail', kwargs={"pk": data.private_severity1.pk}) private1_url = reverse('severities-detail', kwargs={"pk": data.private_severity1.pk})
@ -1588,6 +1812,10 @@ def test_severity_action_bulk_update_order(client, data):
assert results == [401, 403, 403, 403, 451] assert results == [401, 403, 403, 403, 451]
#####################################################
# Memberships
#####################################################
def test_membership_retrieve(client, data): def test_membership_retrieve(client, data):
public_url = reverse('memberships-detail', kwargs={"pk": data.public_membership.pk}) public_url = reverse('memberships-detail', kwargs={"pk": data.public_membership.pk})
private1_url = reverse('memberships-detail', kwargs={"pk": data.private_membership1.pk}) private1_url = reverse('memberships-detail', kwargs={"pk": data.private_membership1.pk})
@ -1859,6 +2087,10 @@ def test_membership_action_resend_invitation(client, data):
assert results == [404, 404, 404, 403, 451] assert results == [404, 404, 404, 403, 451]
#####################################################
# Project Templates
#####################################################
def test_project_template_retrieve(client, data): def test_project_template_retrieve(client, data):
url = reverse('project-templates-detail', kwargs={"pk": data.project_template.pk}) url = reverse('project-templates-detail', kwargs={"pk": data.project_template.pk})
@ -1935,6 +2167,10 @@ def test_project_template_patch(client, data):
assert results == [401, 403, 200] assert results == [401, 403, 200]
#####################################################
# Tags
#####################################################
def test_create_tag(client, data): def test_create_tag(client, data):
users = [ users = [
None, None,