From 46f6fa71e6c99c83f7265965082c33ce495d1cfb Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 2 Aug 2016 12:49:33 +0200 Subject: [PATCH] Adding bulk_create_related_userstories endpoint to epics API --- taiga/base/utils/db.py | 2 ++ taiga/projects/epics/api.py | 29 ++++++++++++++-- taiga/projects/epics/permissions.py | 5 +-- taiga/projects/epics/services.py | 33 ++++++++++++++++++- taiga/projects/epics/validators.py | 5 +++ .../test_epics_resources.py | 28 ++++++++++++++++ tests/integration/test_epics.py | 18 ++++++++++ 7 files changed, 114 insertions(+), 6 deletions(-) diff --git a/taiga/base/utils/db.py b/taiga/base/utils/db.py index 9769abee..a6c21d6b 100644 --- a/taiga/base/utils/db.py +++ b/taiga/base/utils/db.py @@ -83,6 +83,7 @@ def save_in_bulk(instances, callback=None, precall=None, **save_options): :params callback: Callback to call after each save. :params save_options: Additional options to use when saving each instance. """ + ret = [] if callback is None: callback = functions.noop @@ -98,6 +99,7 @@ def save_in_bulk(instances, callback=None, precall=None, **save_options): instance.save(**save_options) callback(instance, created=created) + return ret @transaction.atomic def update_in_bulk(instances, list_of_new_values, callback=None, precall=None): diff --git a/taiga/projects/epics/api.py b/taiga/projects/epics/api.py index 6f4b18e9..5eba3406 100644 --- a/taiga/projects/epics/api.py +++ b/taiga/projects/epics/api.py @@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _ from taiga.base.api.utils import get_object_or_404 from taiga.base import filters, response 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.mixins import BlockedByProjectMixin @@ -201,8 +201,8 @@ class EpicViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, raise exc.Blocked(_("Blocked element")) ret = services.update_epics_order_in_bulk(data["bulk_epics"], - project=project, - field=order_field) + project=project, + field=order_field) return response.Ok(ret) @@ -210,6 +210,29 @@ class EpicViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, def bulk_update_epics_order(self, 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): permission_classes = (permissions.EpicVotersPermission,) diff --git a/taiga/projects/epics/permissions.py b/taiga/projects/epics/permissions.py index 86f2626e..489aeae2 100644 --- a/taiga/projects/epics/permissions.py +++ b/taiga/projects/epics/permissions.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from taiga.base.api.permissions import TaigaResourcePermission, AllowAny, IsAuthenticated, IsSuperUser -from taiga.permissions.permissions import HasProjectPerm, IsProjectAdmin +from taiga.base.api.permissions import TaigaResourcePermission, AllowAny, IsAuthenticated +from taiga.base.api.permissions import IsSuperUser, HasProjectPerm, IsProjectAdmin from taiga.permissions.permissions import CommentAndOrUpdatePerm @@ -35,6 +35,7 @@ class EpicPermission(TaigaResourcePermission): csv_perms = AllowAny() bulk_create_perms = HasProjectPerm('add_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') downvote_perms = IsAuthenticated() & HasProjectPerm('view_epics') watch_perms = IsAuthenticated() & HasProjectPerm('view_epics') diff --git a/taiga/projects/epics/services.py b/taiga/projects/epics/services.py index ea470e8f..145e2339 100644 --- a/taiga/projects/epics/services.py +++ b/taiga/projects/epics/services.py @@ -26,10 +26,12 @@ from django.db import connection from django.utils.translation import ugettext as _ 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.epics.apps import connect_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.projects.votes.utils import attach_total_voters_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 +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 ##################################################### diff --git a/taiga/projects/epics/validators.py b/taiga/projects/epics/validators.py index 9d7a617f..6652928c 100644 --- a/taiga/projects/epics/validators.py +++ b/taiga/projects/epics/validators.py @@ -55,6 +55,11 @@ class EpicsBulkValidator(ProjectExistsValidator, EpicExistsValidator, bulk_epics = serializers.CharField() +class CrateRelatedUserStoriesBulkValidator(ProjectExistsValidator, EpicExistsValidator, + validators.Validator): + userstories = serializers.CharField() + + # Order bulk validators class _EpicOrderBulkValidator(EpicExistsValidator, validators.Validator): diff --git a/tests/integration/resources_permissions/test_epics_resources.py b/tests/integration/resources_permissions/test_epics_resources.py index f4e33813..1d128485 100644 --- a/tests/integration/resources_permissions/test_epics_resources.py +++ b/tests/integration/resources_permissions/test_epics_resources.py @@ -664,6 +664,34 @@ def test_epic_action_bulk_create(client, data): 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): public_url = reverse('epics-upvote', kwargs={"pk": data.public_epic.pk}) private_url1 = reverse('epics-upvote', kwargs={"pk": data.private_epic1.pk}) diff --git a/tests/integration/test_epics.py b/tests/integration/test_epics.py index 5f22f62f..d8e09999 100644 --- a/tests/integration/test_epics.py +++ b/tests/integration/test_epics.py @@ -66,3 +66,21 @@ def test_custom_fields_csv_generation(): assert row[17] == attr.name row = next(reader) 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}