From 86108b0314c7dd148d14072de608130237bb0a2c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 21 Apr 2015 13:36:23 +0200 Subject: [PATCH] Enabling ordering in user projects --- taiga/projects/api.py | 16 +++++++++++- taiga/projects/filters.py | 25 +++++++++++++++++++ .../migrations/0020_membership_user_order.py | 20 +++++++++++++++ taiga/projects/models.py | 7 ++++++ taiga/projects/serializers.py | 8 ++++++ taiga/projects/services/__init__.py | 1 + taiga/projects/services/bulk_update_order.py | 20 +++++++++++++++ tests/integration/test_projects.py | 18 +++++++++++++ 8 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 taiga/projects/filters.py create mode 100644 taiga/projects/migrations/0020_membership_user_order.py diff --git a/taiga/projects/api.py b/taiga/projects/api.py index 4833f749..283ad269 100644 --- a/taiga/projects/api.py +++ b/taiga/projects/api.py @@ -30,6 +30,7 @@ from taiga.base.api.permissions import AllowAnyPermission from taiga.base.api.utils import get_object_or_404 from taiga.base.utils.slug import slugify_uniquely +from taiga.projects import filters as project_filters from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.mixins.ordering import BulkUpdateOrderMixin from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin @@ -57,9 +58,22 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet): admin_serializer_class = serializers.ProjectDetailAdminSerializer list_serializer_class = serializers.ProjectSerializer permission_classes = (permissions.ProjectPermission, ) - filter_backends = (filters.CanViewProjectObjFilterBackend,) + filter_backends = (filters.CanViewProjectObjFilterBackend, project_filters.ProjectsFilterBackend) filter_fields = (('member', 'members'),) + @list_route(methods=["POST"]) + def bulk_update_order(self, request, **kwargs): + if self.request.user.is_anonymous(): + return response.Unauthorized() + + serializer = serializers.UpdateProjectOrderBulkSerializer(data=request.DATA, many=True) + if not serializer.is_valid(): + return response.BadRequest(serializer.errors) + + data = serializer.data + services.update_projects_order_in_bulk(data, "user_order", request.user) + return response.NoContent(data=None) + def get_queryset(self): qs = models.Project.objects.all() return attach_votescount_to_queryset(qs, as_field="stars_count") diff --git a/taiga/projects/filters.py b/taiga/projects/filters.py new file mode 100644 index 00000000..ab8cb8b2 --- /dev/null +++ b/taiga/projects/filters.py @@ -0,0 +1,25 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# 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 . + +from taiga.base import filters + +class ProjectsFilterBackend(filters.FilterBackend): + def filter_queryset(self, request, queryset, view): + queryset = super().filter_queryset(request, queryset, view) + if "user_order" in request.QUERY_PARAMS and "member" in request.QUERY_PARAMS: + queryset = queryset.order_by("memberships__user_order") + + return queryset diff --git a/taiga/projects/migrations/0020_membership_user_order.py b/taiga/projects/migrations/0020_membership_user_order.py new file mode 100644 index 00000000..17ae559e --- /dev/null +++ b/taiga/projects/migrations/0020_membership_user_order.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0019_auto_20150311_0821'), + ] + + operations = [ + migrations.AddField( + model_name='membership', + name='user_order', + field=models.IntegerField(default=10000, verbose_name='user order'), + preserve_default=True, + ), + ] diff --git a/taiga/projects/models.py b/taiga/projects/models.py index 1dc89966..df4e432f 100644 --- a/taiga/projects/models.py +++ b/taiga/projects/models.py @@ -68,6 +68,9 @@ class Membership(models.Model): invitation_extra_text = models.TextField(null=True, blank=True, verbose_name=_("invitation extra text")) + user_order = models.IntegerField(default=10000, null=False, blank=False, + verbose_name=_("user order")) + def clean(self): # TODO: Review and do it more robust memberships = Membership.objects.filter(user=self.user, project=self.project) @@ -282,6 +285,10 @@ class Project(ProjectDefaults, TaggedMixin, models.Model): return self._get_user_stories_points(user_stories) + @property + def project(self): + return self + @property def project(self): return self diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index 28f9cad5..2a6fcf34 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -389,3 +389,11 @@ class ProjectTemplateSerializer(serializers.ModelSerializer): model = models.ProjectTemplate read_only_fields = ("created_date", "modified_date") i18n_fields = ("name", "description") + +###################################################### +## Project order bulk serializers +###################################################### + +class UpdateProjectOrderBulkSerializer(ProjectExistsValidator, serializers.Serializer): + project_id = serializers.IntegerField() + order = serializers.IntegerField() diff --git a/taiga/projects/services/__init__.py b/taiga/projects/services/__init__.py index bf1aac00..12b4276c 100644 --- a/taiga/projects/services/__init__.py +++ b/taiga/projects/services/__init__.py @@ -17,6 +17,7 @@ # This makes all code that import services works and # is not the baddest practice ;) +from .bulk_update_order import update_projects_order_in_bulk from .bulk_update_order import bulk_update_severity_order from .bulk_update_order import bulk_update_priority_order from .bulk_update_order import bulk_update_issue_type_order diff --git a/taiga/projects/services/bulk_update_order.py b/taiga/projects/services/bulk_update_order.py index 47c277a4..40499346 100644 --- a/taiga/projects/services/bulk_update_order.py +++ b/taiga/projects/services/bulk_update_order.py @@ -16,6 +16,26 @@ from django.db import transaction from django.db import connection +from taiga.projects import models + +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: + + [(, {: , ...}), ...] + """ + membership_ids = [] + new_order_values = [] + for membership_data in bulk_data: + project_id = membership_data["project_id"] + membership = user.memberships.get(project_id=project_id) + membership_ids.append(membership.id) + new_order_values.append({field: membership_data["order"]}) + + from taiga.base.utils import db + + db.update_in_bulk_with_ids(membership_ids, new_order_values, model=models.Membership) @transaction.atomic diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py index 6efa9a65..88eb5195 100644 --- a/tests/integration/test_projects.py +++ b/tests/integration/test_projects.py @@ -277,3 +277,21 @@ def test_destroy_point_and_reassign(client): assert user_story.role_points.all()[0].points.id == p2.id project = Project.objects.get(id=project.id) assert project.default_points.id == p2.id + +def test_update_projects_order_in_bulk(client): + user = f.create_user() + client.login(user) + membership_1 = f.MembershipFactory(user=user) + membership_2 = f.MembershipFactory(user=user) + + url = reverse("projects-bulk-update-order") + data = [ + {"project_id": membership_1.project.id, "order":100}, + {"project_id": membership_2.project.id, "order":200} + ] + + response = client.json.post(url, json.dumps(data)) + + assert response.status_code == 204 + assert user.memberships.get(project=membership_1.project).user_order == 100 + assert user.memberships.get(project=membership_2.project).user_order == 200