diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 641edb07c..fa97c5a6d 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -93,6 +93,7 @@ class ProjectDetailSerializer(BaseSerializer): total_cycles = serializers.IntegerField(read_only=True) total_modules = serializers.IntegerField(read_only=True) is_member = serializers.BooleanField(read_only=True) + sort_order = serializers.FloatField(read_only=True) class Meta: model = Project diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index d8da4f7dd..26064d331 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -5,7 +5,7 @@ from datetime import datetime # Django imports from django.core.exceptions import ValidationError from django.db import IntegrityError -from django.db.models import Q, Exists, OuterRef, Func, F +from django.db.models import Q, Exists, OuterRef, Func, F, Min, Subquery from django.core.validators import validate_email from django.conf import settings @@ -120,9 +120,15 @@ class ProjectViewSet(BaseViewSet): project_id=OuterRef("pk"), workspace__slug=self.kwargs.get("slug"), ) + sort_order_query = ProjectMember.objects.filter( + member=request.user, + project_id=OuterRef("pk"), + workspace__slug=self.kwargs.get("slug"), + ).values("sort_order") projects = ( self.get_queryset() .annotate(is_favorite=Exists(subquery)) + .annotate(sort_order=Subquery(sort_order_query)) .order_by("sort_order", "name") .annotate( total_members=ProjectMember.objects.filter( @@ -592,17 +598,26 @@ class AddMemberToProjectEndpoint(BaseAPIView): {"error": "Atleast one member is required"}, status=status.HTTP_400_BAD_REQUEST, ) + bulk_project_members = [] - project_members = ProjectMember.objects.bulk_create( - [ + project_members = ProjectMember.objects.filter( + workspace=self.workspace, member_id__in=[member.get("member_id") for member in members] + ).values("member_id").annotate(sort_order_min=Min("sort_order")) + + for member in members: + sort_order = [project_member.get("sort_order") for project_member in project_members] + bulk_project_members.append( ProjectMember( member_id=member.get("member_id"), role=member.get("role", 10), project_id=project_id, workspace_id=project.workspace_id, + sort_order=sort_order[0] - 10000 if len(sort_order) else 65535 ) - for member in members - ], + ) + + project_members = ProjectMember.objects.bulk_create( + bulk_project_members, batch_size=10, ignore_conflicts=True, ) @@ -845,12 +860,14 @@ class ProjectUserViewsEndpoint(BaseAPIView): view_props = project_member.view_props default_props = project_member.default_props preferences = project_member.preferences + sort_order = project_member.sort_order project_member.view_props = request.data.get("view_props", view_props) project_member.default_props = request.data.get( "default_props", default_props ) project_member.preferences = request.data.get("preferences", preferences) + project_member.sort_order = request.data.get("sort_order", sort_order) project_member.save() diff --git a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py index 78b77521c..8f8700b5d 100644 --- a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py +++ b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py @@ -58,16 +58,16 @@ def update_workspace_member_props(apps, schema_editor): Model.objects.bulk_update(updated_workspace_member, ["view_props"], batch_size=100) -def update_project_sort_order(apps, schema_editor): - Model = apps.get_model("db", "Project") +def update_project_member_sort_order(apps, schema_editor): + Model = apps.get_model("db", "ProjectMember") - updated_projects = [] + updated_project_members = [] for obj in Model.objects.all(): obj.sort_order = random.randint(1, 65536) - updated_projects.append(obj) + updated_project_members.append(obj) - Model.objects.bulk_update(updated_projects, ["sort_order"], batch_size=100) + Model.objects.bulk_update(updated_project_members, ["sort_order"], batch_size=100) class Migration(migrations.Migration): @@ -93,5 +93,5 @@ class Migration(migrations.Migration): name='sort_order', field=models.FloatField(default=65535), ), - migrations.RunPython(update_project_sort_order), + migrations.RunPython(update_project_member_sort_order), ] diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index f95a8c2f0..7049e3ced 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -91,7 +91,6 @@ class Project(BaseModel): default_state = models.ForeignKey( "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" ) - sort_order = models.FloatField(default=65535) def __str__(self): """Return name of the project""" @@ -156,6 +155,20 @@ class ProjectMember(ProjectBaseModel): view_props = models.JSONField(default=get_default_props) default_props = models.JSONField(default=get_default_props) preferences = models.JSONField(default=get_default_preferences) + sort_order = models.FloatField(default=65535) + + + def save(self, *args, **kwargs): + if self._state.adding: + smallest_sort_order = ProjectMember.objects.filter( + workspace=self.workspace, member=self.member + ).aggregate(smallest=models.Min("sort_order"))["smallest"] + + # Project ordering + if smallest_sort_order is not None: + self.sort_order = smallest_sort_order - 10000 + + super(ProjectMember, self).save(*args, **kwargs) class Meta: unique_together = ["project", "member"]