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 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):
|
||||
|
|
|
@ -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,)
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
# 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 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')
|
||||
|
|
|
@ -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
|
||||
#####################################################
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue