Adding bulk_create_related_userstories endpoint to epics API
parent
dd3b098d4e
commit
46f6fa71e6
|
@ -83,6 +83,7 @@ def save_in_bulk(instances, callback=None, precall=None, **save_options):
|
||||||
:params callback: Callback to call after each save.
|
:params callback: Callback to call after each save.
|
||||||
:params save_options: Additional options to use when saving each instance.
|
:params save_options: Additional options to use when saving each instance.
|
||||||
"""
|
"""
|
||||||
|
ret = []
|
||||||
if callback is None:
|
if callback is None:
|
||||||
callback = functions.noop
|
callback = functions.noop
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ def save_in_bulk(instances, callback=None, precall=None, **save_options):
|
||||||
instance.save(**save_options)
|
instance.save(**save_options)
|
||||||
callback(instance, created=created)
|
callback(instance, created=created)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def update_in_bulk(instances, list_of_new_values, callback=None, precall=None):
|
def update_in_bulk(instances, list_of_new_values, callback=None, precall=None):
|
||||||
|
|
|
@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _
|
||||||
from taiga.base.api.utils import get_object_or_404
|
from taiga.base.api.utils import get_object_or_404
|
||||||
from taiga.base import filters, response
|
from taiga.base import filters, response
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route, detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||||
from taiga.base.api.mixins import BlockedByProjectMixin
|
from taiga.base.api.mixins import BlockedByProjectMixin
|
||||||
|
|
||||||
|
@ -201,8 +201,8 @@ class EpicViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin,
|
||||||
raise exc.Blocked(_("Blocked element"))
|
raise exc.Blocked(_("Blocked element"))
|
||||||
|
|
||||||
ret = services.update_epics_order_in_bulk(data["bulk_epics"],
|
ret = services.update_epics_order_in_bulk(data["bulk_epics"],
|
||||||
project=project,
|
project=project,
|
||||||
field=order_field)
|
field=order_field)
|
||||||
|
|
||||||
return response.Ok(ret)
|
return response.Ok(ret)
|
||||||
|
|
||||||
|
@ -210,6 +210,29 @@ class EpicViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin,
|
||||||
def bulk_update_epics_order(self, request, **kwargs):
|
def bulk_update_epics_order(self, request, **kwargs):
|
||||||
return self._bulk_update_order("epics_order", request, **kwargs)
|
return self._bulk_update_order("epics_order", request, **kwargs)
|
||||||
|
|
||||||
|
@detail_route(methods=["POST"])
|
||||||
|
def bulk_create_related_userstories(self, request, **kwargs):
|
||||||
|
validator = validators.CrateRelatedUserStoriesBulkValidator(data=request.DATA)
|
||||||
|
if validator.is_valid():
|
||||||
|
data = validator.data
|
||||||
|
obj = self.get_object()
|
||||||
|
project = obj.project
|
||||||
|
self.check_permissions(request, 'bulk_create_userstories', project)
|
||||||
|
if project.blocked_code is not None:
|
||||||
|
raise exc.Blocked(_("Blocked element"))
|
||||||
|
|
||||||
|
services.create_related_userstories_in_bulk(
|
||||||
|
data["userstories"],
|
||||||
|
obj,
|
||||||
|
project=project,
|
||||||
|
owner=request.user
|
||||||
|
)
|
||||||
|
obj = self.get_queryset().get(id=obj.id)
|
||||||
|
epic_serialized = self.get_serializer_class()(obj)
|
||||||
|
return response.Ok(epic_serialized.data)
|
||||||
|
|
||||||
|
return response.BadRequest(validator.errors)
|
||||||
|
|
||||||
|
|
||||||
class EpicVotersViewSet(VotersViewSetMixin, ModelListViewSet):
|
class EpicVotersViewSet(VotersViewSetMixin, ModelListViewSet):
|
||||||
permission_classes = (permissions.EpicVotersPermission,)
|
permission_classes = (permissions.EpicVotersPermission,)
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from taiga.base.api.permissions import TaigaResourcePermission, AllowAny, IsAuthenticated, IsSuperUser
|
from taiga.base.api.permissions import TaigaResourcePermission, AllowAny, IsAuthenticated
|
||||||
from taiga.permissions.permissions import HasProjectPerm, IsProjectAdmin
|
from taiga.base.api.permissions import IsSuperUser, HasProjectPerm, IsProjectAdmin
|
||||||
|
|
||||||
from taiga.permissions.permissions import CommentAndOrUpdatePerm
|
from taiga.permissions.permissions import CommentAndOrUpdatePerm
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ class EpicPermission(TaigaResourcePermission):
|
||||||
csv_perms = AllowAny()
|
csv_perms = AllowAny()
|
||||||
bulk_create_perms = HasProjectPerm('add_epic')
|
bulk_create_perms = HasProjectPerm('add_epic')
|
||||||
bulk_update_order_perms = HasProjectPerm('modify_epic')
|
bulk_update_order_perms = HasProjectPerm('modify_epic')
|
||||||
|
bulk_create_userstories_perms = HasProjectPerm('modify_epic') & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us'))
|
||||||
upvote_perms = IsAuthenticated() & HasProjectPerm('view_epics')
|
upvote_perms = IsAuthenticated() & HasProjectPerm('view_epics')
|
||||||
downvote_perms = IsAuthenticated() & HasProjectPerm('view_epics')
|
downvote_perms = IsAuthenticated() & HasProjectPerm('view_epics')
|
||||||
watch_perms = IsAuthenticated() & HasProjectPerm('view_epics')
|
watch_perms = IsAuthenticated() & HasProjectPerm('view_epics')
|
||||||
|
|
|
@ -26,10 +26,12 @@ from django.db import connection
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from taiga.base.utils import db, text
|
from taiga.base.utils import db, text
|
||||||
from taiga.projects.history.services import take_snapshot
|
|
||||||
from taiga.projects.services import apply_order_updates
|
from taiga.projects.services import apply_order_updates
|
||||||
from taiga.projects.epics.apps import connect_epics_signals
|
from taiga.projects.epics.apps import connect_epics_signals
|
||||||
from taiga.projects.epics.apps import disconnect_epics_signals
|
from taiga.projects.epics.apps import disconnect_epics_signals
|
||||||
|
from taiga.projects.userstories.apps import connect_userstories_signals
|
||||||
|
from taiga.projects.userstories.apps import disconnect_userstories_signals
|
||||||
|
from taiga.projects.userstories.services import get_userstories_from_bulk
|
||||||
from taiga.events import events
|
from taiga.events import events
|
||||||
from taiga.projects.votes.utils import attach_total_voters_to_queryset
|
from taiga.projects.votes.utils import attach_total_voters_to_queryset
|
||||||
from taiga.projects.notifications.utils import attach_watchers_to_queryset
|
from taiga.projects.notifications.utils import attach_watchers_to_queryset
|
||||||
|
@ -96,6 +98,35 @@ def update_epics_order_in_bulk(bulk_data: list, field: str, project: object):
|
||||||
return epic_orders
|
return epic_orders
|
||||||
|
|
||||||
|
|
||||||
|
def create_related_userstories_in_bulk(bulk_data, epic, **additional_fields):
|
||||||
|
"""Create user stories from `bulk_data`.
|
||||||
|
|
||||||
|
:param epic: Element where all the user stories will be contained
|
||||||
|
:param bulk_data: List of user stories in bulk format.
|
||||||
|
:param additional_fields: Additional fields when instantiating each user story.
|
||||||
|
|
||||||
|
:return: List of created `Task` instances.
|
||||||
|
"""
|
||||||
|
userstories = get_userstories_from_bulk(bulk_data, **additional_fields)
|
||||||
|
disconnect_userstories_signals()
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.save_in_bulk(userstories)
|
||||||
|
related_userstories = []
|
||||||
|
for userstory in userstories:
|
||||||
|
related_userstories.append(
|
||||||
|
models.RelatedUserStory(
|
||||||
|
user_story=userstory,
|
||||||
|
epic=epic
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.save_in_bulk(related_userstories)
|
||||||
|
finally:
|
||||||
|
connect_userstories_signals()
|
||||||
|
|
||||||
|
return userstories
|
||||||
|
|
||||||
|
|
||||||
#####################################################
|
#####################################################
|
||||||
# CSV
|
# CSV
|
||||||
#####################################################
|
#####################################################
|
||||||
|
|
|
@ -55,6 +55,11 @@ class EpicsBulkValidator(ProjectExistsValidator, EpicExistsValidator,
|
||||||
bulk_epics = serializers.CharField()
|
bulk_epics = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class CrateRelatedUserStoriesBulkValidator(ProjectExistsValidator, EpicExistsValidator,
|
||||||
|
validators.Validator):
|
||||||
|
userstories = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
# Order bulk validators
|
# Order bulk validators
|
||||||
|
|
||||||
class _EpicOrderBulkValidator(EpicExistsValidator, validators.Validator):
|
class _EpicOrderBulkValidator(EpicExistsValidator, validators.Validator):
|
||||||
|
|
|
@ -664,6 +664,34 @@ def test_epic_action_bulk_create(client, data):
|
||||||
assert results == [401, 403, 403, 451, 451]
|
assert results == [401, 403, 403, 451, 451]
|
||||||
|
|
||||||
|
|
||||||
|
def test_bulk_create_related_userstories(client, data):
|
||||||
|
public_url = reverse('epics-bulk-create-related-userstories', kwargs={"pk": data.public_epic.pk})
|
||||||
|
private_url1 = reverse('epics-bulk-create-related-userstories', kwargs={"pk": data.private_epic1.pk})
|
||||||
|
private_url2 = reverse('epics-bulk-create-related-userstories', kwargs={"pk": data.private_epic2.pk})
|
||||||
|
blocked_url = reverse('epics-bulk-create-related-userstories', kwargs={"pk": data.blocked_epic.pk})
|
||||||
|
|
||||||
|
users = [
|
||||||
|
None,
|
||||||
|
data.registered_user,
|
||||||
|
data.project_member_without_perms,
|
||||||
|
data.project_member_with_perms,
|
||||||
|
data.project_owner
|
||||||
|
]
|
||||||
|
|
||||||
|
bulk_data = json.dumps({
|
||||||
|
"userstories": "test1\ntest2",
|
||||||
|
})
|
||||||
|
|
||||||
|
results = helper_test_http_method(client, 'post', public_url, bulk_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', private_url1, bulk_data, users)
|
||||||
|
assert results == [401, 403, 403, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', private_url2, bulk_data, users)
|
||||||
|
assert results == [404, 404, 404, 200, 200]
|
||||||
|
results = helper_test_http_method(client, 'post', blocked_url, bulk_data, users)
|
||||||
|
assert results == [404, 404, 404, 451, 451]
|
||||||
|
|
||||||
|
|
||||||
def test_epic_action_upvote(client, data):
|
def test_epic_action_upvote(client, data):
|
||||||
public_url = reverse('epics-upvote', kwargs={"pk": data.public_epic.pk})
|
public_url = reverse('epics-upvote', kwargs={"pk": data.public_epic.pk})
|
||||||
private_url1 = reverse('epics-upvote', kwargs={"pk": data.private_epic1.pk})
|
private_url1 = reverse('epics-upvote', kwargs={"pk": data.private_epic1.pk})
|
||||||
|
|
|
@ -66,3 +66,21 @@ def test_custom_fields_csv_generation():
|
||||||
assert row[17] == attr.name
|
assert row[17] == attr.name
|
||||||
row = next(reader)
|
row = next(reader)
|
||||||
assert row[17] == "val1"
|
assert row[17] == "val1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bulk_create_related_userstories(client):
|
||||||
|
user = f.UserFactory.create()
|
||||||
|
project = f.ProjectFactory.create(owner=user)
|
||||||
|
epic = f.EpicFactory.create(project=project)
|
||||||
|
f.MembershipFactory.create(project=project, user=user, is_admin=True)
|
||||||
|
|
||||||
|
url = reverse('epics-bulk-create-related-userstories', kwargs={"pk": epic.pk})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"userstories": "test1\ntest2"
|
||||||
|
}
|
||||||
|
client.login(user)
|
||||||
|
response = client.json.post(url, json.dumps(data))
|
||||||
|
print(response.data)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.data['user_stories_counts'] == {'opened': 2, 'closed': 0}
|
||||||
|
|
Loading…
Reference in New Issue