Add milestones bulk items

stable
Álex Hermida 2018-12-14 13:51:56 +01:00 committed by Alex Hermida
parent 4c74e6182f
commit 39e9de71cf
5 changed files with 119 additions and 5 deletions

View File

@ -27,11 +27,13 @@ from taiga.base.api.mixins import BlockedByProjectMixin
from taiga.base.api.utils import get_object_or_404 from taiga.base.api.utils import get_object_or_404
from taiga.base.utils.db import get_object_or_none from taiga.base.utils.db import get_object_or_none
from taiga.projects.models import Project
from taiga.projects.notifications.mixins import WatchedResourceMixin from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchersViewSetMixin from taiga.projects.notifications.mixins import WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin
from . import serializers from . import serializers
from . import services
from . import validators from . import validators
from . import models from . import models
from . import permissions from . import permissions
@ -143,6 +145,28 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin,
return response.Ok(milestone_stats) return response.Ok(milestone_stats)
@detail_route(methods=["POST"])
def bulk_update_items(self, request, pk=None, **kwargs):
milestone = get_object_or_404(models.Milestone, pk=pk)
self.check_permissions(request, "bulk_update_items", milestone)
validator = validators.UpdateMilestoneBulkValidator(data=request.DATA)
if not validator.is_valid():
return response.BadRequest(validator.errors)
data = validator.data
project = get_object_or_404(Project, pk=data["project_id"])
milestone = get_object_or_404(models.Milestone, pk=data["sprint_id"])
print('data', validator.bulk_stories)
if data["bulk_stories"]:
self.check_permissions(request, "bulk_update_us_milestone", project)
services.update_userstories_milestone_in_bulk(data["bulk_stories"], milestone)
services.snapshot_userstories_in_bulk(data["bulk_stories"], request.user)
return response.NoContent()
class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet): class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.MilestoneWatchersPermission,) permission_classes = (permissions.MilestoneWatchersPermission,)
resource_model = models.Milestone resource_model = models.Milestone

View File

@ -33,6 +33,8 @@ class MilestonePermission(TaigaResourcePermission):
stats_perms = HasProjectPerm('view_milestones') stats_perms = HasProjectPerm('view_milestones')
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones') watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
unwatch_perms = IsAuthenticated() & HasProjectPerm('view_milestones') unwatch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
bulk_update_items_perms = HasProjectPerm('modify_milestone')
bulk_update_us_milestone_perms = HasProjectPerm('modify_us')
class MilestoneWatchersPermission(TaigaResourcePermission): class MilestoneWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectAdmin() | IsSuperUser() enought_perms = IsProjectAdmin() | IsSuperUser()

View File

@ -16,12 +16,16 @@
# 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.utils import timezone from taiga.base.utils import db
from taiga.events import events
from taiga.projects.history.services import take_snapshot
from taiga.projects.services import apply_order_updates
from taiga.projects.tasks.models import Task
from taiga.projects.userstories.models import UserStory
from . import models from . import models
def calculate_milestone_is_closed(milestone): def calculate_milestone_is_closed(milestone):
return (milestone.user_stories.all().count() > 0 and return (milestone.user_stories.all().count() > 0 and
all([task.status is not None and task.status.is_closed for task in milestone.tasks.all()]) and all([task.status is not None and task.status.is_closed for task in milestone.tasks.all()]) and
@ -38,3 +42,49 @@ def open_milestone(milestone):
if milestone.closed: if milestone.closed:
milestone.closed = False milestone.closed = False
milestone.save(update_fields=["closed",]) milestone.save(update_fields=["closed",])
def update_userstories_milestone_in_bulk(bulk_data: list, milestone: object):
"""
Update the milestone and the milestone order of some user stories adding
the extra orders needed to keep consistency.
`bulk_data` should be a list of dicts with the following format:
[{'us_id': <value>, 'order': <value>}, ...]
"""
user_stories = milestone.user_stories.all()
us_orders = {us.id: getattr(us, "sprint_order") for us in user_stories}
new_us_orders = {}
for e in bulk_data:
new_us_orders[e["us_id"]] = e["order"]
# The base orders where we apply the new orders must containg all
# the values
us_orders[e["us_id"]] = e["order"]
apply_order_updates(us_orders, new_us_orders)
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_attr_in_bulk_for_ids(us_milestones, "milestone_id",
model=UserStory)
db.update_attr_in_bulk_for_ids(us_orders, "sprint_order", UserStory)
# Updating the milestone for the tasks
Task.objects.filter(
user_story_id__in=[e["us_id"] for e in bulk_data]).update(
milestone=milestone)
return us_orders
def snapshot_userstories_in_bulk(bulk_data, user):
for us_data in bulk_data:
try:
us = UserStory.objects.get(pk=us_data['us_id'])
take_snapshot(us, user=user)
except UserStory.DoesNotExist:
pass

View File

@ -19,10 +19,12 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from taiga.base.exceptions import ValidationError from taiga.base.exceptions import ValidationError
from taiga.base.api import serializers
from taiga.base.api import validators from taiga.base.api import validators
from taiga.projects.validators import DuplicatedNameInProjectValidator
from taiga.projects.notifications.validators import WatchersValidator from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.userstories.models import UserStory
from taiga.projects.validators import DuplicatedNameInProjectValidator
from taiga.projects.validators import ProjectExistsValidator
from . import models from . import models
@ -39,3 +41,37 @@ class MilestoneValidator(WatchersValidator, DuplicatedNameInProjectValidator, va
class Meta: class Meta:
model = models.Milestone model = models.Milestone
read_only_fields = ("id", "created_date", "modified_date") read_only_fields = ("id", "created_date", "modified_date")
# bulk validators
class _UserStoryMilestoneBulkValidator(validators.Validator):
us_id = serializers.IntegerField()
order = serializers.IntegerField()
class UpdateMilestoneBulkValidator(MilestoneExistsValidator,
ProjectExistsValidator,
validators.Validator):
project_id = serializers.IntegerField()
sprint_id = serializers.IntegerField()
bulk_stories = _UserStoryMilestoneBulkValidator(many=True)
# def validate_milestone_id(self, attrs, source):
# filters = {
# "project__id": attrs["project_id"],
# "id": attrs[source]
# }
# if not Milestone.objects.filter(**filters).exists():
# raise ValidationError(_("The milestone isn't valid for the project"))
# return attrs
def validate_bulk_stories(self, attrs, source):
filters = {
"project__id": attrs["project_id"],
"id__in": [us["us_id"] for us in attrs[source]]
}
if UserStory.objects.filter(**filters).count() != len(filters["id__in"]):
raise ValidationError(_("All the user stories must be from the same project"))
return attrs

View File

@ -202,8 +202,10 @@ def test_api_update_milestone_in_bulk_userstories(client):
} }
client.login(project.owner) client.login(project.owner)
assert project.milestones.get(id=milestone1.id).user_stories.count() == 1 assert project.milestones.get(id=milestone1.id).user_stories.count() == 2
response = client.json.post(url, json.dumps(data)) response = client.json.post(url, json.dumps(data))
assert response.status_code == 204, response.data assert response.status_code == 204, response.data
assert project.milestones.get(id=milestone1.id).user_stories.count() == 1
assert project.milestones.get(id=milestone2.id).user_stories.count() == 1 assert project.milestones.get(id=milestone2.id).user_stories.count() == 1