Task #422 - Create issues in bulk
parent
4c2585a891
commit
0b02cc4523
|
@ -24,7 +24,7 @@ from rest_framework import status
|
||||||
|
|
||||||
from taiga.base import filters
|
from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route, list_route
|
||||||
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
|
||||||
from taiga.base import tags
|
from taiga.base import tags
|
||||||
|
|
||||||
|
@ -34,11 +34,12 @@ from taiga.projects.notifications import WatchedResourceMixin
|
||||||
from taiga.projects.occ import OCCResourceMixin
|
from taiga.projects.occ import OCCResourceMixin
|
||||||
from taiga.projects.history import HistoryResourceMixin
|
from taiga.projects.history import HistoryResourceMixin
|
||||||
|
|
||||||
|
from taiga.projects.models import Project
|
||||||
from taiga.projects.votes.utils import attach_votescount_to_queryset
|
from taiga.projects.votes.utils import attach_votescount_to_queryset
|
||||||
from taiga.projects.votes import services as votes_service
|
from taiga.projects.votes import services as votes_service
|
||||||
from taiga.projects.votes import serializers as votes_serializers
|
from taiga.projects.votes import serializers as votes_serializers
|
||||||
from . import models
|
from . import models
|
||||||
|
from . import services
|
||||||
from . import permissions
|
from . import permissions
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
@ -152,6 +153,28 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||||
if obj.type and obj.type.project != obj.project:
|
if obj.type and obj.type.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions to set this type to this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions to set this type to this issue."))
|
||||||
|
|
||||||
|
@list_route(methods=["POST"])
|
||||||
|
def bulk_create(self, request, **kwargs):
|
||||||
|
bulk_issues = request.DATA.get('bulkIssues', None)
|
||||||
|
if bulk_issues is None:
|
||||||
|
raise exc.BadRequest(_('bulkIssues parameter is mandatory'))
|
||||||
|
|
||||||
|
project_id = request.DATA.get('projectId', None)
|
||||||
|
if project_id is None:
|
||||||
|
raise exc.BadRequest(_('projectId parameter is mandatory'))
|
||||||
|
|
||||||
|
project = get_object_or_404(Project, id=project_id)
|
||||||
|
|
||||||
|
self.check_permissions(request, 'bulk_create', project)
|
||||||
|
|
||||||
|
issues = services.create_issues_in_bulk(
|
||||||
|
bulk_issues, callback=self.post_save, project=project, owner=request.user,
|
||||||
|
status=project.default_issue_status, severity=project.default_severity,
|
||||||
|
priority=project.default_priority, type=project.default_issue_type)
|
||||||
|
|
||||||
|
issues_serialized = self.serializer_class(issues, many=True)
|
||||||
|
return Response(data=issues_serialized.data)
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
def upvote(self, request, pk=None):
|
def upvote(self, request, pk=None):
|
||||||
issue = get_object_or_404(models.Issue, pk=pk)
|
issue = get_object_or_404(models.Issue, pk=pk)
|
||||||
|
|
|
@ -30,6 +30,7 @@ class IssuePermission(ResourcePermission):
|
||||||
list_perms = AllowAny()
|
list_perms = AllowAny()
|
||||||
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||||
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||||
|
bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_issue_to_project') | HasProjectPerm('add_issue'))
|
||||||
|
|
||||||
|
|
||||||
class HasIssueIdUrlParam(PermissionComponent):
|
class HasIssueIdUrlParam(PermissionComponent):
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
||||||
|
# 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 taiga.base.utils import db, text
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def get_issues_from_bulk(bulk_data, **additional_fields):
|
||||||
|
"""Convert `bulk_data` into a list of issues.
|
||||||
|
|
||||||
|
:param bulk_data: List of issues in bulk format.
|
||||||
|
:param additional_fields: Additional fields when instantiating each issue.
|
||||||
|
|
||||||
|
:return: List of `Issue` instances.
|
||||||
|
"""
|
||||||
|
return [models.Issue(subject=line, **additional_fields)
|
||||||
|
for line in text.split_in_lines(bulk_data)]
|
||||||
|
|
||||||
|
|
||||||
|
def create_issues_in_bulk(bulk_data, callback=None, **additional_fields):
|
||||||
|
"""Create issues from `bulk_data`.
|
||||||
|
|
||||||
|
:param bulk_data: List of issues in bulk format.
|
||||||
|
:param callback: Callback to execute after each issue save.
|
||||||
|
:param additional_fields: Additional fields when instantiating each issue.
|
||||||
|
|
||||||
|
:return: List of created `Issue` instances.
|
||||||
|
"""
|
||||||
|
issues = get_issues_from_bulk(bulk_data, **additional_fields)
|
||||||
|
db.save_in_bulk(issues, callback)
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def update_issues_order_in_bulk(bulk_data):
|
||||||
|
"""Update the order of some issues.
|
||||||
|
|
||||||
|
`bulk_data` should be a list of tuples with the following format:
|
||||||
|
|
||||||
|
[(<issue id>, <new issue order value>), ...]
|
||||||
|
"""
|
||||||
|
issue_ids = []
|
||||||
|
new_order_values = []
|
||||||
|
for issue_id, new_order_value in bulk_data:
|
||||||
|
issue_ids.append(issue_id)
|
||||||
|
new_order_values.append({"order": new_order_value})
|
||||||
|
db.update_in_bulk_with_ids(issue_ids, new_order_values, model=models.Issue)
|
|
@ -376,3 +376,18 @@ def create_userstory(**kwargs):
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
||||||
return UserStoryFactory(**defaults)
|
return UserStoryFactory(**defaults)
|
||||||
|
|
||||||
|
|
||||||
|
def create_project(**kwargs):
|
||||||
|
"Create a project along with its dependencies"
|
||||||
|
defaults = {}
|
||||||
|
defaults.update(kwargs)
|
||||||
|
|
||||||
|
project = ProjectFactory.create(**defaults)
|
||||||
|
project.default_issue_status = IssueStatusFactory.create(project=project)
|
||||||
|
project.default_severity = SeverityFactory.create(project=project)
|
||||||
|
project.default_priority = PriorityFactory.create(project=project)
|
||||||
|
project.default_issue_type = IssueTypeFactory.create(project=project)
|
||||||
|
project.save()
|
||||||
|
|
||||||
|
return project
|
||||||
|
|
|
@ -1,12 +1,61 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from taiga.projects.issues import services, models
|
||||||
|
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_issues_from_bulk():
|
||||||
|
data = """
|
||||||
|
Issue #1
|
||||||
|
Issue #2
|
||||||
|
"""
|
||||||
|
issues = services.get_issues_from_bulk(data)
|
||||||
|
|
||||||
|
assert len(issues) == 2
|
||||||
|
assert issues[0].subject == "Issue #1"
|
||||||
|
assert issues[1].subject == "Issue #2"
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch("taiga.projects.issues.services.db")
|
||||||
|
def test_create_issues_in_bulk(db):
|
||||||
|
data = """
|
||||||
|
Issue #1
|
||||||
|
Issue #2
|
||||||
|
"""
|
||||||
|
issues = services.create_issues_in_bulk(data)
|
||||||
|
|
||||||
|
db.save_in_bulk.assert_called_once_with(issues, None)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch("taiga.projects.issues.services.db")
|
||||||
|
def test_update_issues_order_in_bulk(db):
|
||||||
|
data = [(1, 1), (2, 2)]
|
||||||
|
services.update_issues_order_in_bulk(data)
|
||||||
|
|
||||||
|
db.update_in_bulk_with_ids.assert_called_once_with([1, 2], [{"order": 1}, {"order": 2}],
|
||||||
|
model=models.Issue)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_create_issues_in_bulk(client):
|
||||||
|
project = f.create_project()
|
||||||
|
|
||||||
|
url = reverse("issues-bulk-create")
|
||||||
|
data = {"bulkIssues": "Issue #1\nIssue #2",
|
||||||
|
"projectId": project.id}
|
||||||
|
|
||||||
|
client.login(project.owner)
|
||||||
|
response = client.json.post(url, data)
|
||||||
|
|
||||||
|
assert response.status_code == 200, response.data
|
||||||
|
|
||||||
|
|
||||||
def test_api_filter_by_subject(client):
|
def test_api_filter_by_subject(client):
|
||||||
f.create_issue()
|
f.create_issue()
|
||||||
issue = f.create_issue(subject="some random subject")
|
issue = f.create_issue(subject="some random subject")
|
||||||
|
|
Loading…
Reference in New Issue