Refactoring bulk update order API calls

remotes/origin/issue/4795/notification_even_they_are_disabled
Alejandro Alonso 2016-07-20 14:31:48 +02:00 committed by David Barragán Merino
parent 4c84aaa885
commit f66b4c9640
18 changed files with 465 additions and 115 deletions

View File

@ -437,7 +437,8 @@ APP_EXTRA_EXPOSE_HEADERS = [
"taiga-info-total-opened-milestones",
"taiga-info-total-closed-milestones",
"taiga-info-project-memberships",
"taiga-info-project-is-private"
"taiga-info-project-is-private",
"taiga-info-order-updated"
]
DEFAULT_PROJECT_TEMPLATE = "scrum"

View File

@ -25,7 +25,7 @@ COORS_ALLOWED_METHODS = ["POST", "GET", "OPTIONS", "PUT", "DELETE", "PATCH", "HE
COORS_ALLOWED_HEADERS = ["content-type", "x-requested-with",
"authorization", "accept-encoding",
"x-disable-pagination", "x-lazy-pagination",
"x-host", "x-session-id"]
"x-host", "x-session-id", "set-orders"]
COORS_ALLOWED_CREDENTIALS = True
COORS_EXPOSE_HEADERS = ["x-pagination-count", "x-paginated", "x-paginated-by",
"x-pagination-current", "x-pagination-next", "x-pagination-prev",

View File

@ -17,6 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.contenttypes.models import ContentType
from django.db import connection
from django.db import transaction
from django.shortcuts import _get_queryset
@ -26,6 +27,7 @@ from . import functions
import re
def get_object_or_none(klass, *args, **kwargs):
"""
Uses get() to return an object, or None if the object does not exist.
@ -119,19 +121,28 @@ def update_in_bulk(instances, list_of_new_values, callback=None, precall=None):
callback(instance)
def update_in_bulk_with_ids(ids, list_of_new_values, model):
def update_attr_in_bulk_for_ids(values, attr, 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.
:params values: Dict of new values where the key is the pk of the element to update.
:params attr: attr to update
:params model: Model of the ids.
"""
tn = get_typename_for_model_class(model)
for id, new_values in zip(ids, list_of_new_values):
key = "{0}:{1}".format(tn, id)
with advisory_lock(key) as acquired_key_lock:
model.objects.filter(id=id).update(**new_values)
values = [str((id, order)) for id, order in values.items()]
sql = """
UPDATE {tbl}
SET {attr}=update_values.column2
FROM (
VALUES
{values}
) AS update_values
WHERE {tbl}.id=update_values.column1;
""".format(tbl=model._meta.db_table,
values=', '.join(values),
attr=attr)
cursor = connection.cursor()
cursor.execute(sql)
def to_tsquery(term):

View File

@ -72,21 +72,6 @@ def create_issues_in_bulk(bulk_data, callback=None, precall=None, **additional_f
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)
#####################################################
# CSV
#####################################################

View File

@ -27,6 +27,7 @@ from .bulk_update_order import bulk_update_issue_status_order
from .bulk_update_order import bulk_update_task_status_order
from .bulk_update_order import bulk_update_points_order
from .bulk_update_order import bulk_update_userstory_status_order
from .bulk_update_order import apply_order_updates
from .filters import get_all_tags

View File

@ -24,25 +24,66 @@ from taiga.projects import models
from contextlib import suppress
def update_projects_order_in_bulk(bulk_data:list, field:str, user):
def apply_order_updates(base_orders: dict, new_orders: dict):
"""
`base_orders` must be a dict containing all the elements that can be affected by
order modifications.
`new_orders` must be a dict containing the basic order modifications to apply.
The result will a base_orders with the specified order changes in new_orders
and the extra calculated ones applied.
Extra order updates can be needed when moving elements to intermediate positions.
The elements where no order update is needed will be removed.
"""
updated_order_ids = set()
# We will apply the multiple order changes by the new position order
sorted_new_orders = [(k, v) for k, v in new_orders.items()]
sorted_new_orders = sorted(sorted_new_orders, key=lambda e: e[1])
for new_order in sorted_new_orders:
old_order = base_orders[new_order[0]]
new_order = new_order[1]
for id, order in base_orders.items():
# When moving forward only the elements contained in the range new_order - old_order
# positions need to be updated
moving_backward = new_order <= old_order and order >= new_order and order < old_order
# When moving backward all the elements from the new_order position need to bee updated
moving_forward = new_order >= old_order and order >= new_order
if moving_backward or moving_forward:
base_orders[id] += 1
updated_order_ids.add(id)
# Overwritting the orders specified
for id, order in new_orders.items():
if base_orders[id] != order:
base_orders[id] = order
updated_order_ids.add(id)
# Remove not modified elements
removing_keys = [id for id in base_orders if id not in updated_order_ids]
[base_orders.pop(id, None) for id in removing_keys]
def update_projects_order_in_bulk(bulk_data: list, field: str, user):
"""
Update the order of user projects in the user membership.
`bulk_data` should be a list of tuples with the following format:
`bulk_data` should be a list of dicts with the following format:
[(<project id>, {<field>: <value>, ...}), ...]
[{'project_id': <value>, 'order': <value>}, ...]
"""
membership_ids = []
new_order_values = []
memberships_orders = {m.id: getattr(m, field) for m in user.memberships.all()}
new_memberships_orders = {}
for membership_data in bulk_data:
project_id = membership_data["project_id"]
with suppress(ObjectDoesNotExist):
membership = user.memberships.get(project_id=project_id)
membership_ids.append(membership.id)
new_order_values.append({field: membership_data["order"]})
new_memberships_orders[membership.id] = membership_data["order"]
apply_order_updates(memberships_orders, new_memberships_orders)
from taiga.base.utils import db
db.update_in_bulk_with_ids(membership_ids, new_order_values, model=models.Membership)
db.update_attr_in_bulk_for_ids(memberships_orders, field, model=models.Membership)
@transaction.atomic

View File

@ -25,12 +25,15 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.utils import json
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.milestones.models import Milestone
from taiga.projects.models import Project, TaskStatus
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.tagging.api import TaggedResourceMixin
from taiga.projects.userstories.models import UserStory
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from . import models
@ -104,16 +107,74 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin,
if obj.milestone and obj.user_story and obj.milestone != obj.user_story.milestone:
raise exc.WrongArguments(_("You don't have permissions to set this sprint to this task."))
"""
Updating some attributes of the userstory can affect the ordering in the backlog, kanban or taskboard
These two methods generate a key for the task and can be used to be compared before and after
saving
If there is any difference it means an extra ordering update must be done
"""
def _us_order_key(self, obj):
return "{}-{}-{}".format(obj.project_id, obj.user_story_id, obj.us_order)
def _taskboard_order_key(self, obj):
return "{}-{}-{}-{}".format(obj.project_id, obj.user_story_id, obj.status_id, obj.taskboard_order)
def pre_save(self, obj):
if obj.user_story:
obj.milestone = obj.user_story.milestone
if not obj.id:
obj.owner = self.request.user
else:
self._old_us_order_key = self._us_order_key(self.get_object())
self._old_taskboard_order_key = self._taskboard_order_key(self.get_object())
super().pre_save(obj)
def _reorder_if_needed(self, obj, old_order_key, order_key, order_attr,
project, user_story=None, status=None, milestone=None):
# Executes the extra ordering if there is a difference in the ordering keys
if old_order_key != order_key:
extra_orders = json.loads(self.request.META.get("HTTP_SET_ORDERS", "{}"))
data = [{"task_id": obj.id, "order": getattr(obj, order_attr)}]
for id, order in extra_orders.items():
data.append({"task_id": int(id), "order": order})
return services.update_tasks_order_in_bulk(data,
order_attr,
project,
user_story=user_story,
status=status,
milestone=milestone)
return {}
def post_save(self, obj, created=False):
if not created:
# Let's reorder the related stuff after edit the element
orders_updated = {}
updated = self._reorder_if_needed(obj,
self._old_us_order_key,
self._us_order_key(obj),
"us_order",
obj.project,
user_story=obj.user_story)
orders_updated.update(updated)
updated = self._reorder_if_needed(obj,
self._old_taskboard_order_key,
self._taskboard_order_key(obj),
"taskboard_order",
obj.project,
user_story=obj.user_story,
status=obj.status,
milestone=obj.milestone)
orders_updated.update(updated)
self.headers["Taiga-Info-Order-Updated"] = json.dumps(orders_updated)
super().post_save(obj, created)
def update(self, request, *args, **kwargs):
self.object = self.get_object_or_none()
project_id = request.DATA.get('project', None)
if project_id and self.object and self.object.project.id != project_id:
try:
new_project = Project.objects.get(pk=project_id)
@ -223,12 +284,28 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin,
if project.blocked_code is not None:
raise exc.Blocked(_("Blocked element"))
services.update_tasks_order_in_bulk(data["bulk_tasks"],
project=project,
field=order_field)
services.snapshot_tasks_in_bulk(data["bulk_tasks"], request.user)
user_story = None
user_story_id = data.get("user_story_id", None)
if user_story_id is not None:
user_story = get_object_or_404(UserStory, pk=user_story_id)
return response.NoContent()
status = None
status_id = data.get("status_id", None)
if status_id is not None:
status = get_object_or_404(TaskStatus, pk=status_id)
milestone = None
milestone_id = data.get("milestone_id", None)
if milestone_id is not None:
milestone = get_object_or_404(Milestone, pk=milestone_id)
ret = services.update_tasks_order_in_bulk(data["bulk_tasks"],
order_field,
project,
user_story=user_story,
status=status,
milestone=milestone)
return response.Ok(ret)
@list_route(methods=["POST"])
def bulk_update_taskboard_order(self, request, **kwargs):

View File

@ -27,6 +27,7 @@ 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.tasks.apps import connect_tasks_signals
from taiga.projects.tasks.apps import disconnect_tasks_signals
from taiga.events import events
@ -73,24 +74,33 @@ def create_tasks_in_bulk(bulk_data, callback=None, precall=None, **additional_fi
return tasks
def update_tasks_order_in_bulk(bulk_data: list, field: str, project: object):
def update_tasks_order_in_bulk(bulk_data: list, field: str, project: object,
user_story: object=None, status: object=None, milestone: object=None):
"""
Update the order of some tasks.
`bulk_data` should be a list of tuples with the following format:
Updates the order of the tasks specified adding the extra updates needed
to keep consistency.
[(<task id>, {<field>: <value>, ...}), ...]
[{'task_id': <value>, 'order': <value>}, ...]
"""
task_ids = []
new_order_values = []
for task_data in bulk_data:
task_ids.append(task_data["task_id"])
new_order_values.append({field: task_data["order"]})
tasks = project.tasks.all()
if user_story is not None:
tasks = tasks.filter(user_story=user_story)
if status is not None:
tasks = tasks.filter(status=status)
if milestone is not None:
tasks = tasks.filter(milestone=milestone)
task_orders = {task.id: getattr(task, field) for task in tasks}
new_task_orders = {e["task_id"]: e["order"] for e in bulk_data}
apply_order_updates(task_orders, new_task_orders)
task_ids = task_orders.keys()
events.emit_event_for_ids(ids=task_ids,
content_type="tasks.task",
projectid=project.pk)
db.update_in_bulk_with_ids(task_ids, new_order_values, model=models.Task)
db.update_attr_in_bulk_for_ids(task_orders, field, models.Task)
return task_orders
def snapshot_tasks_in_bulk(bulk_data, user):

View File

@ -66,4 +66,7 @@ class _TaskOrderBulkValidator(TaskExistsValidator, validators.Validator):
class UpdateTasksOrderBulkValidator(ProjectExistsValidator, validators.Validator):
project_id = serializers.IntegerField()
milestone_id = serializers.IntegerField(required=False)
status_id = serializers.IntegerField(required=False)
us_id = serializers.IntegerField(required=False)
bulk_tasks = _TaskOrderBulkValidator(many=True)

View File

@ -31,6 +31,7 @@ from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.api import ModelCrudViewSet
from taiga.base.api import ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.base.utils import json
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.history.services import take_snapshot
@ -118,6 +119,21 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
raise exc.PermissionDenied(_("You don't have permissions to set this status "
"to this user story."))
"""
Updating some attributes of the userstory can affect the ordering in the backlog, kanban or taskboard
These three methods generate a key for the user story and can be used to be compared before and after
saving
If there is any difference it means an extra ordering update must be done
"""
def _backlog_order_key(self, obj):
return "{}-{}".format(obj.project_id, obj.backlog_order)
def _kanban_order_key(self, obj):
return "{}-{}-{}".format(obj.project_id, obj.status_id, obj.kanban_order)
def _sprint_order_key(self, obj):
return "{}-{}-{}".format(obj.project_id, obj.milestone_id, obj.sprint_order)
def pre_save(self, obj):
# This is very ugly hack, but having
# restframework is the only way to do it.
@ -129,10 +145,55 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
if not obj.id:
obj.owner = self.request.user
else:
self._old_backlog_order_key = self._backlog_order_key(self.get_object())
self._old_kanban_order_key = self._kanban_order_key(self.get_object())
self._old_sprint_order_key = self._sprint_order_key(self.get_object())
super().pre_save(obj)
def _reorder_if_needed(self, obj, old_order_key, order_key, order_attr,
project, status=None, milestone=None):
# Executes the extra ordering if there is a difference in the ordering keys
if old_order_key != order_key:
extra_orders = json.loads(self.request.META.get("HTTP_SET_ORDERS", "{}"))
data = [{"us_id": obj.id, "order": getattr(obj, order_attr)}]
for id, order in extra_orders.items():
data.append({"us_id": int(id), "order": order})
return services.update_userstories_order_in_bulk(data,
order_attr,
project,
status=status,
milestone=milestone)
return {}
def post_save(self, obj, created=False):
if not created:
# Let's reorder the related stuff after edit the element
orders_updated = {}
updated = self._reorder_if_needed(obj,
self._old_backlog_order_key,
self._backlog_order_key(obj),
"backlog_order",
obj.project)
orders_updated.update(updated)
updated = self._reorder_if_needed(obj,
self._old_kanban_order_key,
self._kanban_order_key(obj),
"kanban_order",
obj.project,
status=obj.status)
orders_updated.update(updated)
updated = self._reorder_if_needed(obj,
self._old_sprint_order_key,
self._sprint_order_key(obj),
"sprint_order",
obj.project,
milestone=obj.milestone)
orders_updated.update(updated)
self.headers["Taiga-Info-Order-Updated"] = json.dumps(orders_updated)
# Code related to the hack of pre_save method.
# Rather, this is the continuation of it.
if self._role_points:
@ -180,6 +241,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
def update(self, request, *args, **kwargs):
self.object = self.get_object_or_none()
project_id = request.DATA.get('project', None)
if project_id and self.object and self.object.project.id != project_id:
try:
new_project = Project.objects.get(pk=project_id)
@ -295,17 +357,26 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
status = None
status_id = data.get("status_id", None)
if status_id is not None:
status = get_object_or_404(UserStoryStatus, pk=status_id)
milestone = None
milestone_id = data.get("milestone_id", None)
if milestone_id is not None:
milestone = get_object_or_404(Milestone, pk=milestone_id)
self.check_permissions(request, "bulk_update_order", project)
if project.blocked_code is not None:
raise exc.Blocked(_("Blocked element"))
services.update_userstories_order_in_bulk(data["bulk_stories"],
project=project,
field=order_field)
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
return response.NoContent()
ret = services.update_userstories_order_in_bulk(data["bulk_stories"],
order_field,
project,
status=status,
milestone=milestone)
return response.Ok(ret)
@list_route(methods=["POST"])
def bulk_update_backlog_order(self, request, **kwargs):

View File

@ -28,9 +28,9 @@ 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.userstories.apps import connect_userstories_signals
from taiga.projects.userstories.apps import disconnect_userstories_signals
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
@ -75,24 +75,32 @@ def create_userstories_in_bulk(bulk_data, callback=None, precall=None, **additio
return userstories
def update_userstories_order_in_bulk(bulk_data: list, field: str, project: object):
def update_userstories_order_in_bulk(bulk_data: list, field: str, project: object,
status: object=None, milestone: object=None):
"""
Update the order of some user stories.
`bulk_data` should be a list of tuples with the following format:
Updates the order of the userstories specified adding the extra updates needed
to keep consistency.
`bulk_data` should be a list of dicts with the following format:
`field` is the order field used
[(<user story id>, {<field>: <value>, ...}), ...]
[{'us_id': <value>, 'order': <value>}, ...]
"""
user_story_ids = []
new_order_values = []
for us_data in bulk_data:
user_story_ids.append(us_data["us_id"])
new_order_values.append({field: us_data["order"]})
user_stories = project.user_stories.all()
if status is not None:
user_stories = user_stories.filter(status=status)
if milestone is not None:
user_stories = user_stories.filter(milestone=milestone)
us_orders = {us.id: getattr(us, field) for us in user_stories}
new_us_orders = {e["us_id"]: e["order"] for e in bulk_data}
apply_order_updates(us_orders, new_us_orders)
user_story_ids = us_orders.keys()
events.emit_event_for_ids(ids=user_story_ids,
content_type="userstories.userstory",
projectid=project.pk)
db.update_in_bulk_with_ids(user_story_ids, new_order_values, model=models.UserStory)
db.update_attr_in_bulk_for_ids(us_orders, field, models.UserStory)
return us_orders
def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
@ -100,14 +108,14 @@ def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
Update the milestone of some user stories.
`bulk_data` should be a list of user story ids:
"""
user_story_ids = [us_data["us_id"] for us_data in bulk_data]
new_milestone_values = [{"milestone": milestone.id}] * len(user_story_ids)
us_milestones = {e["us_id"]: milestone.id for e in bulk_data}
user_story_ids = us_milestones.keys()
events.emit_event_for_ids(ids=user_story_ids,
content_type="userstories.userstory",
projectid=milestone.project.pk)
db.update_in_bulk_with_ids(user_story_ids, new_milestone_values, model=models.UserStory)
db.update_attr_in_bulk_for_ids(us_milestones, "milestone_id", model=models.UserStory)
def snapshot_userstories_in_bulk(bulk_data, user):

View File

@ -64,7 +64,7 @@ class UserStoryValidator(WatchersValidator, EditableWatchedResourceSerializer, v
class Meta:
model = models.UserStory
depth = 0
read_only_fields = ('created_date', 'modified_date', 'owner')
read_only_fields = ('id', 'ref', 'created_date', 'modified_date', 'owner')
class UserStoriesBulkValidator(ProjectExistsValidator, UserStoryStatusExistsValidator,
@ -84,6 +84,8 @@ class _UserStoryOrderBulkValidator(UserStoryExistsValidator, validators.Validato
class UpdateUserStoriesOrderBulkValidator(ProjectExistsValidator, UserStoryStatusExistsValidator,
validators.Validator):
project_id = serializers.IntegerField()
status_id = serializers.IntegerField(required=False)
milestone_id = serializers.IntegerField(required=False)
bulk_stories = _UserStoryOrderBulkValidator(many=True)

View File

@ -659,21 +659,21 @@ def test_user_story_action_bulk_update_order(client, data):
"project_id": data.public_project.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204]
assert results == [401, 403, 403, 200, 200]
post_data = json.dumps({
"bulk_stories": [{"us_id": data.private_user_story1.id, "order": 2}],
"project_id": data.private_project1.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204]
assert results == [401, 403, 403, 200, 200]
post_data = json.dumps({
"bulk_stories": [{"us_id": data.private_user_story2.id, "order": 2}],
"project_id": data.private_project2.pk
})
results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204]
assert results == [401, 403, 403, 200, 200]
post_data = json.dumps({
"bulk_stories": [{"us_id": data.blocked_user_story.id, "order": 2}],

View File

@ -56,15 +56,6 @@ Issue #2
db.save_in_bulk.assert_called_once_with(issues, None, None)
def test_update_issues_order_in_bulk():
data = [(1, 1), (2, 2)]
with mock.patch("taiga.projects.issues.services.db") as db:
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_create_issue_without_status(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)

View File

@ -148,8 +148,8 @@ def test_api_update_order_in_bulk(client):
response1 = client.json.post(url1, json.dumps(data))
response2 = client.json.post(url2, json.dumps(data))
assert response1.status_code == 204, response1.data
assert response2.status_code == 204, response2.data
assert response1.status_code == 200, response1.data
assert response2.status_code == 200, response2.data
def test_get_invalid_csv(client):

View File

@ -50,17 +50,16 @@ def test_create_userstories_in_bulk():
def test_update_userstories_order_in_bulk():
data = [{"us_id": 1, "order": 1}, {"us_id": 2, "order": 2}]
project = mock.Mock()
project.pk = 1
project = f.ProjectFactory.create()
us1 = f.UserStoryFactory.create(project=project, backlog_order=1)
us2 = f.UserStoryFactory.create(project=project, backlog_order=2)
data = [{"us_id": us1.id, "order": 1}, {"us_id": us2.id, "order": 2}]
with mock.patch("taiga.projects.userstories.services.db") as db:
services.update_userstories_order_in_bulk(data, "backlog_order", project)
db.update_in_bulk_with_ids.assert_called_once_with([1, 2],
[{"backlog_order": 1},
{"backlog_order": 2}],
model=models.UserStory)
db.update_attr_in_bulk_for_ids.assert_called_once_with({us1.id: 1, us2.id: 2},
"backlog_order",
models.UserStory)
def test_create_userstory_with_watchers(client):
@ -176,9 +175,9 @@ def test_api_update_orders_in_bulk(client):
response2 = client.json.post(url2, json.dumps(data))
response3 = client.json.post(url3, json.dumps(data))
assert response1.status_code == 204, response1.data
assert response2.status_code == 204, response2.data
assert response3.status_code == 204, response3.data
assert response1.status_code == 200, response1.data
assert response2.status_code == 200, response2.data
assert response3.status_code == 200, response3.data
def test_api_update_milestone_in_bulk(client):

View File

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# 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.projects.services import apply_order_updates
def test_apply_order_updates_one_element_backward():
orders = {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6
}
new_orders = {
"d": 2
}
apply_order_updates(orders, new_orders)
assert orders == {
"d": 2,
"b": 3,
"c": 4
}
def test_apply_order_updates_one_element_forward():
orders = {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6
}
new_orders = {
"a": 3
}
apply_order_updates(orders, new_orders)
assert orders == {
"a": 3,
"c": 4,
"d": 5,
"e": 6,
"f": 7
}
def test_apply_order_updates_multiple_elements_backward():
orders = {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6
}
new_orders = {
"d": 2,
"e": 3
}
apply_order_updates(orders, new_orders)
assert orders == {
"d": 2,
"e": 3,
"b": 4,
"c": 5
}
def test_apply_order_updates_multiple_elements_forward():
orders = {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6
}
new_orders = {
"a": 4,
"b": 5
}
apply_order_updates(orders, new_orders)
assert orders == {
"a": 4,
"b": 5,
"d": 6,
"e": 7,
"f": 8
}
def test_apply_order_updates_two_elements():
orders = {
"a": 0,
"b": 1,
}
new_orders = {
"b": 0
}
apply_order_updates(orders, new_orders)
assert orders == {
"b": 0,
"a": 1
}
def test_apply_order_updates_duplicated_orders():
orders = {
"a": 1,
"b": 2,
"c": 3,
"d": 3,
"e": 3,
"f": 4
}
new_orders = {
"a": 3
}
apply_order_updates(orders, new_orders)
print(orders)
assert orders == {
"a": 3,
"c": 4,
"d": 4,
"e": 4,
"f": 5
}
def test_apply_order_updates_multiple_elements_duplicated_orders():
orders = {
"a": 1,
"b": 2,
"c": 3,
"d": 3,
"e": 3,
"f": 4
}
new_orders = {
"c": 3,
"d": 3,
"a": 4
}
apply_order_updates(orders, new_orders)
print(orders)
assert orders == {
"c": 3,
"d": 3,
"a": 4,
"e": 5,
"f": 6
}

View File

@ -23,7 +23,7 @@ import django_sites as sites
import re
from taiga.base.utils.urls import get_absolute_url, is_absolute_url, build_url
from taiga.base.utils.db import save_in_bulk, update_in_bulk, update_in_bulk_with_ids, to_tsquery
from taiga.base.utils.db import save_in_bulk, update_in_bulk, to_tsquery
pytestmark = pytest.mark.django_db
@ -82,21 +82,6 @@ def test_update_in_bulk_with_a_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)
TS_QUERY_TRANSFORMATIONS = [
("1 OR 2", "1 | 2"),
("(1) 2", "( 1 ) & 2"),