Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6a09e51
refactor: rename IssueUserProperty to ProjectUserProperty and update …
pablohashescobar Dec 1, 2025
e43d9b6
migrate: move issue user properties to project user properties and up…
pablohashescobar Dec 1, 2025
f75d40f
Merge branch 'preview' of github.com:makeplane/plane into chore-user-…
pablohashescobar Dec 1, 2025
d874227
refactor: rename IssueUserPropertySerializer and IssueUserDisplayProp…
pablohashescobar Dec 1, 2025
280c3ce
fix: enhance ProjectUserDisplayPropertyEndpoint to handle missing pro…
pablohashescobar Dec 1, 2025
e7cf8fa
fix: correct formatting in migration for ProjectUserProperty model op…
pablohashescobar Dec 3, 2025
2f0ea09
migrate: add migration to update existing non-service API tokens to r…
pablohashescobar Dec 8, 2025
ab9a6b4
migrate: refine migration to update existing non-service API tokens b…
pablohashescobar Dec 8, 2025
d75efb3
chore: changed the project sort order in project user property
NarayanBavisetti Dec 18, 2025
3d7212b
chore: remove allowed_rate_limit from APIToken
sangeethailango Dec 24, 2025
a5e9954
Merge branch 'preview' of github.com:makeplane/plane into chore-user-…
vamsikrishnamathala Dec 30, 2025
93c863b
Merge branch 'chore-user-property-migrations' of github.com:makeplane…
vamsikrishnamathala Jan 2, 2026
da31afa
chore: updated user-properties endpoint for frontend
vamsikrishnamathala Jan 2, 2026
0ffb3e0
Merge branch 'preview' of github.com:makeplane/plane into chore-user-…
NarayanBavisetti Jan 5, 2026
b39c769
chore: removed the extra projectuserproperty
NarayanBavisetti Jan 5, 2026
d3a50b7
chore: updated the migration file
NarayanBavisetti Jan 5, 2026
2ce67a1
chore: code refactor
anmolsinghbhatia Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions apps/api/plane/api/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from plane.db.models import (
Cycle,
Intake,
IssueUserProperty,
ProjectUserProperty,
Module,
Project,
DeployBoard,
Expand Down Expand Up @@ -218,8 +218,6 @@ def post(self, request, slug):

# Add the user as Administrator to the project
_ = ProjectMember.objects.create(project_id=serializer.instance.id, member=request.user, role=20)
# Also create the issue property for the user
_ = IssueUserProperty.objects.create(project_id=serializer.instance.id, user=request.user)

if serializer.instance.project_lead is not None and str(serializer.instance.project_lead) != str(
request.user.id
Expand All @@ -229,11 +227,6 @@ def post(self, request, slug):
member_id=serializer.instance.project_lead,
role=20,
)
# Also create the issue property for the user
IssueUserProperty.objects.create(
project_id=serializer.instance.id,
user_id=serializer.instance.project_lead,
)

State.objects.bulk_create(
[
Expand Down
2 changes: 1 addition & 1 deletion apps/api/plane/app/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
IssueCreateSerializer,
IssueActivitySerializer,
IssueCommentSerializer,
IssueUserPropertySerializer,
ProjectUserPropertySerializer,
IssueAssigneeSerializer,
LabelSerializer,
IssueSerializer,
Expand Down
6 changes: 3 additions & 3 deletions apps/api/plane/app/serializers/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
Issue,
IssueActivity,
IssueComment,
IssueUserProperty,
ProjectUserProperty,
IssueAssignee,
IssueSubscriber,
IssueLabel,
Expand Down Expand Up @@ -346,9 +346,9 @@ class Meta:
fields = "__all__"


class IssueUserPropertySerializer(BaseSerializer):
class ProjectUserPropertySerializer(BaseSerializer):
class Meta:
model = IssueUserProperty
model = ProjectUserProperty
fields = "__all__"
read_only_fields = ["user", "workspace", "project"]

Expand Down
8 changes: 4 additions & 4 deletions apps/api/plane/app/urls/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
IssueReactionViewSet,
IssueRelationViewSet,
IssueSubscriberViewSet,
IssueUserDisplayPropertyEndpoint,
ProjectUserDisplayPropertyEndpoint,
IssueViewSet,
LabelViewSet,
BulkArchiveIssuesEndpoint,
Expand Down Expand Up @@ -208,13 +208,13 @@
name="project-issue-comment-reactions",
),
## End Comment Reactions
## IssueUserProperty
## ProjectUserProperty
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-properties/",
IssueUserDisplayPropertyEndpoint.as_view(),
ProjectUserDisplayPropertyEndpoint.as_view(),
name="project-issue-display-properties",
),
## IssueUserProperty End
## ProjectUserProperty End
## Issue Archives
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/plane/app/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
from .issue.base import (
IssueListEndpoint,
IssueViewSet,
IssueUserDisplayPropertyEndpoint,
ProjectUserDisplayPropertyEndpoint,
BulkDeleteIssuesEndpoint,
DeletedIssuesListViewSet,
IssuePaginatedViewSet,
Expand Down
36 changes: 23 additions & 13 deletions apps/api/plane/app/views/issue/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
IssueDetailSerializer,
IssueListDetailSerializer,
IssueSerializer,
IssueUserPropertySerializer,
ProjectUserPropertySerializer,
)
from plane.bgtasks.issue_activities_task import issue_activity
from plane.bgtasks.issue_description_version_task import issue_description_version_task
Expand All @@ -51,7 +51,7 @@
IssueReaction,
IssueRelation,
IssueSubscriber,
IssueUserProperty,
ProjectUserProperty,
ModuleIssue,
Project,
ProjectMember,
Expand Down Expand Up @@ -723,23 +723,33 @@ def destroy(self, request, slug, project_id, pk=None):
return Response(status=status.HTTP_204_NO_CONTENT)


class IssueUserDisplayPropertyEndpoint(BaseAPIView):
class ProjectUserDisplayPropertyEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def patch(self, request, slug, project_id):
issue_property = IssueUserProperty.objects.get(user=request.user, project_id=project_id)
try:
issue_property = ProjectUserProperty.objects.get(
user=request.user,
project_id=project_id
)
except ProjectUserProperty.DoesNotExist:
issue_property = ProjectUserProperty.objects.create(
user=request.user,
project_id=project_id
)

issue_property.rich_filters = request.data.get("rich_filters", issue_property.rich_filters)
issue_property.filters = request.data.get("filters", issue_property.filters)
issue_property.display_filters = request.data.get("display_filters", issue_property.display_filters)
issue_property.display_properties = request.data.get("display_properties", issue_property.display_properties)
issue_property.save()
serializer = IssueUserPropertySerializer(issue_property)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer = ProjectUserPropertySerializer(
issue_property,
data=request.data,
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)

@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id):
issue_property, _ = IssueUserProperty.objects.get_or_create(user=request.user, project_id=project_id)
serializer = IssueUserPropertySerializer(issue_property)
issue_property, _ = ProjectUserProperty.objects.get_or_create(user=request.user, project_id=project_id)
serializer = ProjectUserPropertySerializer(issue_property)
return Response(serializer.data, status=status.HTTP_200_OK)


Expand Down
10 changes: 2 additions & 8 deletions apps/api/plane/app/views/project/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@
from plane.db.models import (
UserFavorite,
DeployBoard,
ProjectUserProperty,
Intake,
IssueUserProperty,
Project,
ProjectIdentifier,
ProjectMember,
ProjectNetwork,
State,
DEFAULT_STATES,
UserFavorite,
Workspace,
WorkspaceMember,
)
Expand Down Expand Up @@ -250,8 +251,6 @@ def create(self, request, slug):
member=request.user,
role=ROLE.ADMIN.value,
)
# Also create the issue property for the user
_ = IssueUserProperty.objects.create(project_id=serializer.data["id"], user=request.user)

if serializer.data["project_lead"] is not None and str(serializer.data["project_lead"]) != str(
request.user.id
Expand All @@ -261,11 +260,6 @@ def create(self, request, slug):
member_id=serializer.data["project_lead"],
role=ROLE.ADMIN.value,
)
# Also create the issue property for the user
IssueUserProperty.objects.create(
project_id=serializer.data["id"],
user_id=serializer.data["project_lead"],
)

State.objects.bulk_create(
[
Expand Down
8 changes: 4 additions & 4 deletions apps/api/plane/app/views/project/invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
User,
WorkspaceMember,
Project,
IssueUserProperty,
ProjectUserProperty,
)
from plane.db.models.project import ProjectNetwork
from plane.utils.host import base_host
Expand Down Expand Up @@ -160,9 +160,9 @@ def create(self, request, slug):
ignore_conflicts=True,
)

IssueUserProperty.objects.bulk_create(
ProjectUserProperty.objects.bulk_create(
[
IssueUserProperty(
ProjectUserProperty(
project_id=project_id,
user=request.user,
workspace=workspace,
Expand Down Expand Up @@ -220,7 +220,7 @@ def post(self, request, slug, project_id, pk):
if project_member is None:
# Create a Project Member
_ = ProjectMember.objects.create(
workspace_id=project_invite.workspace_id,
project_id=project_id,
member=user,
role=project_invite.role,
)
Expand Down
32 changes: 16 additions & 16 deletions apps/api/plane/app/views/project/member.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from django.db.models import Min

# Module imports
from .base import BaseViewSet, BaseAPIView
Expand All @@ -13,7 +14,7 @@

from plane.app.permissions import WorkspaceUserPermission

from plane.db.models import Project, ProjectMember, IssueUserProperty, WorkspaceMember
from plane.db.models import Project, ProjectMember, ProjectUserProperty, WorkspaceMember
from plane.bgtasks.project_add_user_email_task import project_add_user_email
from plane.utils.host import base_host
from plane.app.permissions.base import allow_permission, ROLE
Expand Down Expand Up @@ -89,47 +90,46 @@ def create(self, request, slug, project_id):
# Update the roles of the existing members
ProjectMember.objects.bulk_update(bulk_project_members, ["is_active", "role"], batch_size=100)

# Get the list of project members of the requested workspace with the given slug
project_members = (
ProjectMember.objects.filter(
# Get the minimum sort_order for each member in the workspace
member_sort_orders = (
ProjectUserProperty.objects.filter(
workspace__slug=slug,
member_id__in=[member.get("member_id") for member in members],
user_id__in=[member.get("member_id") for member in members],
)
.values("member_id", "sort_order")
.order_by("sort_order")
.values("user_id")
.annotate(min_sort_order=Min("sort_order"))
)
# Convert to dictionary for easy lookup: {user_id: min_sort_order}
sort_order_map = {str(item["user_id"]): item["min_sort_order"] for item in member_sort_orders}

# Loop through requested members
for member in members:
# Get the sort orders of the member
sort_order = [
project_member.get("sort_order")
for project_member in project_members
if str(project_member.get("member_id")) == str(member.get("member_id"))
]
member_id = str(member.get("member_id"))
# Get the minimum sort_order for this member, or use default
min_sort_order = sort_order_map.get(member_id)
# Create a new project member
bulk_project_members.append(
ProjectMember(
member_id=member.get("member_id"),
role=member.get("role", 5),
project_id=project_id,
workspace_id=project.workspace_id,
sort_order=(sort_order[0] - 10000 if len(sort_order) else 65535),
)
)
# Create a new issue property
bulk_issue_props.append(
IssueUserProperty(
ProjectUserProperty(
user_id=member.get("member_id"),
project_id=project_id,
workspace_id=project.workspace_id,
sort_order=(min_sort_order - 10000 if min_sort_order is not None else 65535),
)
)

# Bulk create the project members and issue properties
project_members = ProjectMember.objects.bulk_create(bulk_project_members, batch_size=10, ignore_conflicts=True)

_ = IssueUserProperty.objects.bulk_create(bulk_issue_props, batch_size=10, ignore_conflicts=True)
_ = ProjectUserProperty.objects.bulk_create(bulk_issue_props, batch_size=10, ignore_conflicts=True)

project_members = ProjectMember.objects.filter(
project_id=project_id,
Expand Down
6 changes: 3 additions & 3 deletions apps/api/plane/bgtasks/workspace_seed_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
WorkspaceMember,
Project,
ProjectMember,
IssueUserProperty,
ProjectUserProperty,
State,
Label,
Issue,
Expand Down Expand Up @@ -122,9 +122,9 @@ def create_project_and_member(workspace: Workspace, bot_user: User) -> Dict[int,
)

# Create issue user properties
IssueUserProperty.objects.bulk_create(
ProjectUserProperty.objects.bulk_create(
[
IssueUserProperty(
ProjectUserProperty(
project=project,
user_id=workspace_member["member_id"],
workspace_id=workspace.id,
Expand Down
17 changes: 4 additions & 13 deletions apps/api/plane/db/management/commands/create_project_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
WorkspaceMember,
ProjectMember,
Project,
IssueUserProperty,
ProjectUserProperty,
)


Expand Down Expand Up @@ -47,27 +47,18 @@ def handle(self, *args: Any, **options: Any):
if not WorkspaceMember.objects.filter(workspace=project.workspace, member=user, is_active=True).exists():
raise CommandError("User not member in workspace")

# Get the smallest sort order
smallest_sort_order = (
ProjectMember.objects.filter(workspace_id=project.workspace_id).order_by("sort_order").first()
)

if smallest_sort_order:
sort_order = smallest_sort_order.sort_order - 1000
else:
sort_order = 65535

if ProjectMember.objects.filter(project=project, member=user).exists():
# Update the project member
ProjectMember.objects.filter(project=project, member=user).update(
is_active=True, sort_order=sort_order, role=role
is_active=True, role=role
)
else:
# Create the project member
ProjectMember.objects.create(project=project, member=user, role=role, sort_order=sort_order)
ProjectMember.objects.create(project=project, member=user, role=role)

# Issue Property
IssueUserProperty.objects.get_or_create(user=user, project=project)
ProjectUserProperty.objects.get_or_create(user=user, project=project)

# Success message
self.stdout.write(self.style.SUCCESS(f"User {user_email} added to project {project_id}"))
Expand Down
Loading
Loading