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):
|
||||
"""Save a list of model instances.
|
||||
|
||||
:params instances: List of model instances.
|
||||
:params callback: Callback to call after each save.
|
||||
: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:
|
||||
instance.save(**save_options)
|
||||
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", " ")
|
||||
|
||||
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'):
|
||||
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
||||
|
||||
service = services.UserStoriesService()
|
||||
user_stories = service.bulk_insert(project, request.user, bulk_stories,
|
||||
callback_on_success=self.post_save)
|
||||
user_stories = services.create_userstories_in_bulk(
|
||||
bulk_stories, callback=self.post_save, project=project, owner=request.user)
|
||||
|
||||
user_stories_serialized = self.serializer_class(user_stories, many=True)
|
||||
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'):
|
||||
raise exc.PermissionDenied(_("You don't have permisions to create user stories."))
|
||||
|
||||
service = services.UserStoriesService()
|
||||
service.bulk_update_order(project, request.user, bulk_stories)
|
||||
services.update_userstories_order_in_bulk(bulk_stories)
|
||||
|
||||
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
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.db import transaction
|
||||
from django.db import connection
|
||||
from taiga.base.utils import db, text
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class UserStoriesService(object):
|
||||
@transaction.atomic
|
||||
def bulk_insert(self, project, user, data, callback_on_success=None):
|
||||
user_stories = []
|
||||
def get_userstories_from_bulk(bulk_data, **additional_fields):
|
||||
"""Convert `bulk_data` into a list of user stories.
|
||||
|
||||
items = filter(lambda s: len(s) > 0,
|
||||
map(lambda s: s.strip(), data.split("\n")))
|
||||
:param bulk_data: List of user stories in bulk format.
|
||||
:param additional_fields: Additional fields when instantiating each user story.
|
||||
|
||||
for item in items:
|
||||
obj = models.UserStory.objects.create(subject=item, project=project, owner=user,
|
||||
status=project.default_us_status)
|
||||
user_stories.append(obj)
|
||||
|
||||
if callback_on_success:
|
||||
callback_on_success(obj, True)
|
||||
|
||||
return user_stories
|
||||
|
||||
@transaction.atomic
|
||||
def bulk_update_order(self, project, user, data):
|
||||
# TODO: Create a history snapshot of all updated USs
|
||||
cursor = connection.cursor()
|
||||
|
||||
sql = """
|
||||
prepare bulk_update_order as update userstories_userstory set "order" = $1
|
||||
where userstories_userstory.id = $2 and
|
||||
userstories_userstory.project_id = $3;
|
||||
:return: List of `UserStory` instances.
|
||||
"""
|
||||
return [models.UserStory(subject=line, **additional_fields)
|
||||
for line in text.split_in_lines(bulk_data)]
|
||||
|
||||
cursor.execute(sql)
|
||||
for usid, usorder in data:
|
||||
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
|
||||
(usorder, usid, project.id))
|
||||
cursor.close()
|
||||
|
||||
def create_userstories_in_bulk(bulk_data, callback=None, **additional_fields):
|
||||
"""Create user stories from `bulk_data`.
|
||||
|
||||
:param bulk_data: List of user stories in bulk format.
|
||||
:param callback: Callback to execute after each user story save.
|
||||
: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)
|
||||
db.save_in_bulk(userstories, callback)
|
||||
return userstories
|
||||
|
||||
|
||||
def update_userstories_order_in_bulk(bulk_data):
|
||||
"""Update the order of some user stories.
|
||||
|
||||
`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
|
||||
|
||||
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():
|
||||
|
@ -35,3 +35,41 @@ def test_save_in_bulk_with_a_callback():
|
|||
save_in_bulk(instances, callback)
|
||||
|
||||
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