USs: class-based to function-based services

remotes/origin/enhancement/email-actions
Anler Hp 2014-07-11 01:02:52 +02:00
parent 40260c82ad
commit 82ddeb69a7
6 changed files with 151 additions and 35 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)