USs: class-based to function-based services
parent
40260c82ad
commit
82ddeb69a7
|
@ -46,6 +46,7 @@ def reload_attribute(model_instance, attr_name):
|
||||||
def save_in_bulk(instances, callback=None, **save_options):
|
def save_in_bulk(instances, callback=None, **save_options):
|
||||||
"""Save a list of model instances.
|
"""Save a list of model instances.
|
||||||
|
|
||||||
|
:params instances: List of model instances.
|
||||||
: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.
|
||||||
"""
|
"""
|
||||||
|
@ -55,3 +56,34 @@ def save_in_bulk(instances, callback=None, **save_options):
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
instance.save(**save_options)
|
instance.save(**save_options)
|
||||||
callback(instance)
|
callback(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def update_in_bulk(instances, list_of_new_values, callback=None):
|
||||||
|
"""Update a list of model instances.
|
||||||
|
|
||||||
|
:params instances: List of model instances.
|
||||||
|
:params new_values: List of dicts where each dict is the new data corresponding to the instance
|
||||||
|
in the same index position as the dict.
|
||||||
|
"""
|
||||||
|
if callback is None:
|
||||||
|
callback = functions.identity
|
||||||
|
|
||||||
|
for instance, new_values in zip(instances, list_of_new_values):
|
||||||
|
for attribute, value in new_values.items():
|
||||||
|
setattr(instance, attribute, value)
|
||||||
|
instance.save()
|
||||||
|
callback(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def update_in_bulk_with_ids(ids, list_of_new_values, model):
|
||||||
|
"""Update a table using a list of ids.
|
||||||
|
|
||||||
|
:params ids: List of ids.
|
||||||
|
:params new_values: List of dicts or duples where each dict/duple is the new data corresponding
|
||||||
|
to the instance in the same index position as the dict.
|
||||||
|
:param model: Model of the ids.
|
||||||
|
"""
|
||||||
|
for id, new_values in zip(ids, list_of_new_values):
|
||||||
|
model.objects.filter(id=id).update(**new_values)
|
||||||
|
|
|
@ -25,3 +25,8 @@ def strip_lines(text):
|
||||||
output = output.replace("\n", " ")
|
output = output.replace("\n", " ")
|
||||||
|
|
||||||
return output.strip()
|
return output.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def split_in_lines(text):
|
||||||
|
"""Split a block of text in lines removing unnecessary spaces from each line."""
|
||||||
|
return (line for line in map(str.strip, text.split("\n")) if line)
|
||||||
|
|
|
@ -79,9 +79,8 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
if request.user != project.owner and not has_project_perm(request.user, project, 'add_userstory'):
|
if request.user != project.owner and not has_project_perm(request.user, project, 'add_userstory'):
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
||||||
|
|
||||||
service = services.UserStoriesService()
|
user_stories = services.create_userstories_in_bulk(
|
||||||
user_stories = service.bulk_insert(project, request.user, bulk_stories,
|
bulk_stories, callback=self.post_save, project=project, owner=request.user)
|
||||||
callback_on_success=self.post_save)
|
|
||||||
|
|
||||||
user_stories_serialized = self.serializer_class(user_stories, many=True)
|
user_stories_serialized = self.serializer_class(user_stories, many=True)
|
||||||
return Response(data=user_stories_serialized.data)
|
return Response(data=user_stories_serialized.data)
|
||||||
|
@ -107,8 +106,7 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
if request.user != project.owner and not has_project_perm(request.user, project, 'change_userstory'):
|
if request.user != project.owner and not has_project_perm(request.user, project, 'change_userstory'):
|
||||||
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
||||||
|
|
||||||
service = services.UserStoriesService()
|
services.update_userstories_order_in_bulk(bulk_stories)
|
||||||
service.bulk_update_order(project, request.user, bulk_stories)
|
|
||||||
|
|
||||||
return Response(data=None, status=status.HTTP_204_NO_CONTENT)
|
return Response(data=None, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
|
@ -14,43 +14,47 @@
|
||||||
# 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 django.db import transaction
|
from taiga.base.utils import db, text
|
||||||
from django.db import connection
|
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class UserStoriesService(object):
|
def get_userstories_from_bulk(bulk_data, **additional_fields):
|
||||||
@transaction.atomic
|
"""Convert `bulk_data` into a list of user stories.
|
||||||
def bulk_insert(self, project, user, data, callback_on_success=None):
|
|
||||||
user_stories = []
|
|
||||||
|
|
||||||
items = filter(lambda s: len(s) > 0,
|
:param bulk_data: List of user stories in bulk format.
|
||||||
map(lambda s: s.strip(), data.split("\n")))
|
:param additional_fields: Additional fields when instantiating each user story.
|
||||||
|
|
||||||
for item in items:
|
:return: List of `UserStory` instances.
|
||||||
obj = models.UserStory.objects.create(subject=item, project=project, owner=user,
|
"""
|
||||||
status=project.default_us_status)
|
return [models.UserStory(subject=line, **additional_fields)
|
||||||
user_stories.append(obj)
|
for line in text.split_in_lines(bulk_data)]
|
||||||
|
|
||||||
if callback_on_success:
|
|
||||||
callback_on_success(obj, True)
|
|
||||||
|
|
||||||
return user_stories
|
def create_userstories_in_bulk(bulk_data, callback=None, **additional_fields):
|
||||||
|
"""Create user stories from `bulk_data`.
|
||||||
|
|
||||||
@transaction.atomic
|
:param bulk_data: List of user stories in bulk format.
|
||||||
def bulk_update_order(self, project, user, data):
|
:param callback: Callback to execute after each user story save.
|
||||||
# TODO: Create a history snapshot of all updated USs
|
:param additional_fields: Additional fields when instantiating each user story.
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
sql = """
|
:return: List of created `Task` instances.
|
||||||
prepare bulk_update_order as update userstories_userstory set "order" = $1
|
"""
|
||||||
where userstories_userstory.id = $2 and
|
userstories = get_userstories_from_bulk(bulk_data, **additional_fields)
|
||||||
userstories_userstory.project_id = $3;
|
db.save_in_bulk(userstories, callback)
|
||||||
"""
|
return userstories
|
||||||
|
|
||||||
cursor.execute(sql)
|
|
||||||
for usid, usorder in data:
|
def update_userstories_order_in_bulk(bulk_data):
|
||||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
"""Update the order of some user stories.
|
||||||
(usorder, usid, project.id))
|
|
||||||
cursor.close()
|
`bulk_data` should be a list of tuples with the following format:
|
||||||
|
|
||||||
|
[(<user story id>, <new user story order value>), ...]
|
||||||
|
"""
|
||||||
|
user_story_ids = []
|
||||||
|
new_order_values = []
|
||||||
|
for user_story_id, new_order_value in bulk_data:
|
||||||
|
user_story_ids.append(user_story_id)
|
||||||
|
new_order_values.append({"order": new_order_value})
|
||||||
|
db.update_in_bulk_with_ids(user_story_ids, new_order_values, model=models.UserStory)
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from taiga.projects.userstories import services, models
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_userstories_from_bulk():
|
||||||
|
data = """
|
||||||
|
User Story #1
|
||||||
|
User Story #2
|
||||||
|
"""
|
||||||
|
userstories = services.get_userstories_from_bulk(data)
|
||||||
|
|
||||||
|
assert len(userstories) == 2
|
||||||
|
assert userstories[0].subject == "User Story #1"
|
||||||
|
assert userstories[1].subject == "User Story #2"
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch("taiga.projects.userstories.services.db")
|
||||||
|
def test_create_userstories_in_bulk(db):
|
||||||
|
data = """
|
||||||
|
User Story #1
|
||||||
|
User Story #2
|
||||||
|
"""
|
||||||
|
userstories = services.create_userstories_in_bulk(data)
|
||||||
|
|
||||||
|
db.save_in_bulk.assert_called_once_with(userstories, None)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch("taiga.projects.userstories.services.db")
|
||||||
|
def test_update_userstories_order_in_bulk(db):
|
||||||
|
data = [(1, 1), (2, 2)]
|
||||||
|
services.update_userstories_order_in_bulk(data)
|
||||||
|
|
||||||
|
db.update_in_bulk_with_ids.assert_called_once_with([1, 2], [{"order": 1}, {"order": 2}],
|
||||||
|
model=models.UserStory)
|
|
@ -3,7 +3,7 @@ from unittest import mock
|
||||||
import django_sites as sites
|
import django_sites as sites
|
||||||
|
|
||||||
from taiga.base.utils.urls import get_absolute_url, is_absolute_url, build_url
|
from taiga.base.utils.urls import get_absolute_url, is_absolute_url, build_url
|
||||||
from taiga.base.utils.db import save_in_bulk
|
from taiga.base.utils.db import save_in_bulk, update_in_bulk, update_in_bulk_with_ids
|
||||||
|
|
||||||
|
|
||||||
def test_is_absolute_url():
|
def test_is_absolute_url():
|
||||||
|
@ -35,3 +35,41 @@ def test_save_in_bulk_with_a_callback():
|
||||||
save_in_bulk(instances, callback)
|
save_in_bulk(instances, callback)
|
||||||
|
|
||||||
assert callback.call_count == 2
|
assert callback.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_in_bulk():
|
||||||
|
instance = mock.Mock()
|
||||||
|
instances = [instance, instance]
|
||||||
|
new_values = [{"field1": 1}, {"field2": 2}]
|
||||||
|
|
||||||
|
update_in_bulk(instances, new_values)
|
||||||
|
|
||||||
|
assert instance.save.call_count == 2
|
||||||
|
assert instance.field1 == 1
|
||||||
|
assert instance.field2 == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_in_bulk_with_a_callback():
|
||||||
|
instance = mock.Mock()
|
||||||
|
callback = mock.Mock()
|
||||||
|
instances = [instance, instance]
|
||||||
|
new_values = [{"field1": 1}, {"field2": 2}]
|
||||||
|
|
||||||
|
update_in_bulk(instances, new_values, callback)
|
||||||
|
|
||||||
|
assert callback.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_in_bulk_with_ids():
|
||||||
|
ids = [1, 2]
|
||||||
|
new_values = [{"field1": 1}, {"field2": 2}]
|
||||||
|
model = mock.Mock()
|
||||||
|
|
||||||
|
update_in_bulk_with_ids(ids, new_values, model)
|
||||||
|
|
||||||
|
expected_calls = [
|
||||||
|
mock.call(id=1), mock.call().update(field1=1),
|
||||||
|
mock.call(id=2), mock.call().update(field2=2)
|
||||||
|
]
|
||||||
|
|
||||||
|
model.objects.filter.assert_has_calls(expected_calls)
|
||||||
|
|
Loading…
Reference in New Issue