Compare commits

...
Author SHA1 Message Date
sriramveeraghanta a02cef11a6 chore: remove unused models 2025-12-30 18:31:02 +05:30
21 changed files with 12 additions and 472 deletions
+1 -12
View File
@@ -9,7 +9,6 @@ from rest_framework import serializers
# Module imports
from plane.db.models import (
Issue,
IssueType,
IssueActivity,
IssueAssignee,
FileAsset,
@@ -58,9 +57,6 @@ class IssueSerializer(BaseSerializer):
write_only=True,
required=False,
)
type_id = serializers.PrimaryKeyRelatedField(
source="type", queryset=IssueType.objects.all(), required=False, allow_null=True
)
class Meta:
model = Issue
@@ -151,14 +147,7 @@ class IssueSerializer(BaseSerializer):
workspace_id = self.context["workspace_id"]
default_assignee_id = self.context["default_assignee_id"]
issue_type = validated_data.pop("type", None)
if not issue_type:
# Get default issue type
issue_type = IssueType.objects.filter(project_issue_types__project_id=project_id, is_default=True).first()
issue_type = issue_type
issue = Issue.objects.create(**validated_data, project_id=project_id, type=issue_type)
issue = Issue.objects.create(**validated_data, project_id=project_id)
# Issue Audit Users
created_by_id = issue.created_by_id
@@ -15,7 +15,6 @@ from .workspace import (
WorkSpaceMemberSerializer,
WorkSpaceMemberInviteSerializer,
WorkspaceLiteSerializer,
WorkspaceThemeSerializer,
WorkspaceMemberAdminSerializer,
WorkspaceMemberMeSerializer,
WorkspaceUserPropertiesSerializer,
@@ -89,7 +88,6 @@ from .module import (
from .api import APITokenSerializer, APITokenReadSerializer
from .importer import ImporterSerializer
from .page import (
PageSerializer,
@@ -114,8 +112,6 @@ from .intake import (
IntakeIssueDetailSerializer,
)
from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer, UserNotificationPreferenceSerializer
from .exporter import ExporterHistorySerializer
@@ -1,27 +0,0 @@
from .base import BaseSerializer
from plane.db.models import AnalyticView
from plane.utils.issue_filters import issue_filters
class AnalyticViewSerializer(BaseSerializer):
class Meta:
model = AnalyticView
fields = "__all__"
read_only_fields = ["workspace", "query"]
def create(self, validated_data):
query_params = validated_data.get("query_dict", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = {}
return AnalyticView.objects.create(**validated_data)
def update(self, instance, validated_data):
query_params = validated_data.get("query_data", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = {}
validated_data["query"] = issue_filters(query_params, "PATCH")
return super().update(instance, validated_data)
@@ -1,16 +0,0 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import Importer
class ImporterSerializer(BaseSerializer):
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = Importer
fields = "__all__"
-34
View File
@@ -10,7 +10,6 @@ from plane.utils.content_validator import (
)
from plane.db.models import (
Page,
PageLabel,
Label,
ProjectPage,
Project,
@@ -55,7 +54,6 @@ class PageSerializer(BaseSerializer):
read_only_fields = ["workspace", "owned_by"]
def create(self, validated_data):
labels = validated_data.pop("labels", None)
project_id = self.context["project_id"]
owned_by_id = self.context["owned_by_id"]
description = self.context["description"]
@@ -84,41 +82,9 @@ class PageSerializer(BaseSerializer):
updated_by_id=page.updated_by_id,
)
# Create page labels
if labels is not None:
PageLabel.objects.bulk_create(
[
PageLabel(
label=label,
page=page,
workspace_id=page.workspace_id,
created_by_id=page.created_by_id,
updated_by_id=page.updated_by_id,
)
for label in labels
],
batch_size=10,
)
return page
def update(self, instance, validated_data):
labels = validated_data.pop("labels", None)
if labels is not None:
PageLabel.objects.filter(page=instance).delete()
PageLabel.objects.bulk_create(
[
PageLabel(
label=label,
page=instance,
workspace_id=instance.workspace_id,
created_by_id=instance.created_by_id,
updated_by_id=instance.updated_by_id,
)
for label in labels
],
batch_size=10,
)
return super().update(instance, validated_data)
@@ -10,7 +10,6 @@ from plane.db.models import (
Workspace,
WorkspaceMember,
WorkspaceMemberInvite,
WorkspaceTheme,
WorkspaceUserProperties,
WorkspaceUserLink,
UserRecentVisit,
@@ -125,13 +124,6 @@ class WorkSpaceMemberInviteSerializer(BaseSerializer):
]
class WorkspaceThemeSerializer(BaseSerializer):
class Meta:
model = WorkspaceTheme
fields = "__all__"
read_only_fields = ["workspace", "actor"]
class WorkspaceUserPropertiesSerializer(BaseSerializer):
class Meta:
model = WorkspaceUserProperties
-17
View File
@@ -3,8 +3,6 @@ from django.urls import path
from plane.app.views import (
AnalyticsEndpoint,
AnalyticViewViewset,
SavedAnalyticEndpoint,
ExportAnalyticsEndpoint,
AdvanceAnalyticsEndpoint,
AdvanceAnalyticsStatsEndpoint,
@@ -23,21 +21,6 @@ urlpatterns = [
AnalyticsEndpoint.as_view(),
name="plane-analytics",
),
path(
"workspaces/<str:slug>/analytic-view/",
AnalyticViewViewset.as_view({"get": "list", "post": "create"}),
name="analytic-view",
),
path(
"workspaces/<str:slug>/analytic-view/<uuid:pk>/",
AnalyticViewViewset.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}),
name="analytic-view",
),
path(
"workspaces/<str:slug>/saved-analytic-view/<uuid:analytic_id>/",
SavedAnalyticEndpoint.as_view(),
name="saved-analytic-view",
),
path(
"workspaces/<str:slug>/export-analytics/",
ExportAnalyticsEndpoint.as_view(),
-11
View File
@@ -11,7 +11,6 @@ from plane.app.views import (
WorkspaceMemberUserViewsEndpoint,
WorkSpaceAvailabilityCheckEndpoint,
UserLastProjectWithWorkspaceEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
@@ -115,16 +114,6 @@ urlpatterns = [
WorkspaceMemberUserViewsEndpoint.as_view(),
name="workspace-member-views-details",
),
path(
"workspaces/<str:slug>/workspace-themes/",
WorkspaceThemeViewSet.as_view({"get": "list", "post": "create"}),
name="workspace-themes",
),
path(
"workspaces/<str:slug>/workspace-themes/<uuid:pk>/",
WorkspaceThemeViewSet.as_view({"get": "retrieve", "patch": "partial_update", "delete": "destroy"}),
name="workspace-themes",
),
path(
"workspaces/<str:slug>/user-stats/<uuid:user_id>/",
WorkspaceUserProfileStatsEndpoint.as_view(),
-3
View File
@@ -35,7 +35,6 @@ from .workspace.base import (
UserWorkSpacesEndpoint,
WorkSpaceAvailabilityCheckEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
ExportWorkspaceUserActivityEndpoint,
)
@@ -194,8 +193,6 @@ from .intake.base import (
from .analytic.base import (
AnalyticsEndpoint,
AnalyticViewViewset,
SavedAnalyticEndpoint,
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
ProjectStatsEndpoint,
+1 -44
View File
@@ -11,14 +11,10 @@ from rest_framework import status
from rest_framework.response import Response
# Module imports
from plane.app.permissions import WorkSpaceAdminPermission
from plane.app.serializers import AnalyticViewSerializer
from plane.app.views.base import BaseAPIView, BaseViewSet
from plane.app.views.base import BaseAPIView
from plane.bgtasks.analytic_plot_export import analytic_export_task
from plane.db.models import (
AnalyticView,
Issue,
Workspace,
Project,
ProjectMember,
Cycle,
@@ -186,45 +182,6 @@ class AnalyticsEndpoint(BaseAPIView):
)
class AnalyticViewViewset(BaseViewSet):
permission_classes = [WorkSpaceAdminPermission]
model = AnalyticView
serializer_class = AnalyticViewSerializer
def perform_create(self, serializer):
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
serializer.save(workspace_id=workspace.id)
def get_queryset(self):
return self.filter_queryset(super().get_queryset().filter(workspace__slug=self.kwargs.get("slug")))
class SavedAnalyticEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def get(self, request, slug, analytic_id):
analytic_view = AnalyticView.objects.get(pk=analytic_id, workspace__slug=slug)
filter = analytic_view.query
queryset = Issue.issue_objects.filter(**filter)
x_axis = analytic_view.query_dict.get("x_axis", False)
y_axis = analytic_view.query_dict.get("y_axis", False)
if not x_axis or not y_axis:
return Response(
{"error": "x-axis and y-axis dimensions are required"},
status=status.HTTP_400_BAD_REQUEST,
)
segment = request.GET.get("segment", False)
distribution = build_graph_plot(queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment)
total_issues = queryset.count()
return Response(
{"total": total_issues, "distribution": distribution},
status=status.HTTP_200_OK,
)
class ExportAnalyticsEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def post(self, request, slug):
+9 -25
View File
@@ -28,14 +28,13 @@ from plane.app.permissions import (
)
# Module imports
from plane.app.serializers import WorkSpaceSerializer, WorkspaceThemeSerializer
from plane.app.serializers import WorkSpaceSerializer
from plane.app.views.base import BaseAPIView, BaseViewSet
from plane.db.models import (
Issue,
IssueActivity,
Workspace,
WorkspaceMember,
WorkspaceTheme,
Profile,
)
from plane.app.permissions import ROLE, allow_permission
@@ -77,12 +76,14 @@ class WorkSpaceViewSet(BaseViewSet):
def create(self, request):
try:
(DISABLE_WORKSPACE_CREATION,) = get_configuration_value([
{
"key": "DISABLE_WORKSPACE_CREATION",
"default": os.environ.get("DISABLE_WORKSPACE_CREATION", "0"),
}
])
(DISABLE_WORKSPACE_CREATION,) = get_configuration_value(
[
{
"key": "DISABLE_WORKSPACE_CREATION",
"default": os.environ.get("DISABLE_WORKSPACE_CREATION", "0"),
}
]
)
if DISABLE_WORKSPACE_CREATION == "1":
return Response(
@@ -341,23 +342,6 @@ class UserWorkspaceDashboardEndpoint(BaseAPIView):
)
class WorkspaceThemeViewSet(BaseViewSet):
permission_classes = [WorkSpaceAdminPermission]
model = WorkspaceTheme
serializer_class = WorkspaceThemeSerializer
def get_queryset(self):
return super().get_queryset().filter(workspace__slug=self.kwargs.get("slug"))
def create(self, request, slug):
workspace = Workspace.objects.get(slug=slug)
serializer = WorkspaceThemeSerializer(data=request.data)
if serializer.is_valid():
serializer.save(workspace=workspace, actor=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ExportWorkspaceUserActivityEndpoint(BaseAPIView):
permission_classes = [WorkspaceEntityPermission]
-22
View File
@@ -30,7 +30,6 @@ from plane.db.models import (
ModuleIssue,
Page,
ProjectPage,
PageLabel,
Intake,
IntakeIssue,
)
@@ -242,24 +241,6 @@ def create_pages(workspace, project, user_id, pages_count):
)
def create_page_labels(workspace, project, user_id, pages_count):
# labels
labels = Label.objects.filter(project=project).values_list("id", flat=True)
pages = random.sample(
list(Page.objects.filter(projects__id=project.id).values_list("id", flat=True)),
int(pages_count / 2),
)
# Bulk page labels
bulk_page_labels = []
for page in pages:
for label in random.sample(list(labels), random.randint(0, len(labels) - 1)):
bulk_page_labels.append(PageLabel(page_id=page, label_id=label, workspace=workspace))
# Page labels
PageLabel.objects.bulk_create(bulk_page_labels, batch_size=1000, ignore_conflicts=True)
def create_issues(workspace, project, user_id, issue_count):
fake = Faker()
Faker.seed(0)
@@ -517,9 +498,6 @@ def create_dummy_data(
# create pages
create_pages(workspace=workspace, project=project, user_id=user_id, pages_count=pages_count)
# create page labels
create_page_labels(workspace=workspace, project=project, user_id=user_id, pages_count=pages_count)
# create issues
create_issues(workspace=workspace, project=project, user_id=user_id, issue_count=issue_count)
+1 -8
View File
@@ -1,4 +1,3 @@
from .analytic import AnalyticView
from .api import APIActivityLog, APIToken
from .asset import FileAsset
from .base import BaseModel
@@ -13,7 +12,6 @@ from .draft import (
)
from .estimate import Estimate, EstimatePoint
from .exporter import ExporterHistory
from .importer import Importer
from .intake import Intake, IntakeIssue
from .integration import (
GithubCommentSync,
@@ -29,7 +27,6 @@ from .issue import (
Issue,
IssueActivity,
IssueAssignee,
IssueBlocker,
IssueComment,
IssueLabel,
IssueLink,
@@ -45,7 +42,7 @@ from .issue import (
)
from .module import Module, ModuleIssue, ModuleLink, ModuleMember, ModuleUserProperties
from .notification import EmailNotificationLog, Notification, UserNotificationPreference
from .page import Page, PageLabel, PageLog, ProjectPage, PageVersion
from .page import Page, PageLog, ProjectPage, PageVersion
from .project import (
Project,
ProjectBaseModel,
@@ -56,7 +53,6 @@ from .project import (
ProjectPublicMember,
)
from .session import Session
from .social_connection import SocialLoginConnection
from .state import State, StateGroup, DEFAULT_STATES
from .user import Account, Profile, User, BotTypeEnum
from .view import IssueView
@@ -66,7 +62,6 @@ from .workspace import (
WorkspaceBaseModel,
WorkspaceMember,
WorkspaceMemberInvite,
WorkspaceTheme,
WorkspaceUserProperties,
WorkspaceUserLink,
WorkspaceHomePreference,
@@ -75,8 +70,6 @@ from .workspace import (
from .favorite import UserFavorite
from .issue_type import IssueType
from .recent_visit import UserRecentVisit
from .label import Label
-22
View File
@@ -1,22 +0,0 @@
# Django models
from django.db import models
from .base import BaseModel
class AnalyticView(BaseModel):
workspace = models.ForeignKey("db.Workspace", related_name="analytics", on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
query = models.JSONField()
query_dict = models.JSONField(default=dict)
class Meta:
verbose_name = "Analytic"
verbose_name_plural = "Analytics"
db_table = "analytic_views"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the analytic view"""
return f"{self.name} <{self.workspace.name}>"
-7
View File
@@ -63,13 +63,6 @@ class DraftIssue(WorkspaceBaseModel):
completed_at = models.DateTimeField(null=True)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
type = models.ForeignKey(
"db.IssueType",
on_delete=models.SET_NULL,
related_name="draft_issue_type",
null=True,
blank=True,
)
class Meta:
verbose_name = "DraftIssue"
-36
View File
@@ -1,36 +0,0 @@
# Django imports
from django.conf import settings
from django.db import models
# Module imports
from .project import ProjectBaseModel
class Importer(ProjectBaseModel):
service = models.CharField(max_length=50, choices=(("github", "GitHub"), ("jira", "Jira")))
status = models.CharField(
max_length=50,
choices=(
("queued", "Queued"),
("processing", "Processing"),
("completed", "Completed"),
("failed", "Failed"),
),
default="queued",
)
initiated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="imports")
metadata = models.JSONField(default=dict)
config = models.JSONField(default=dict)
data = models.JSONField(default=dict)
token = models.ForeignKey("db.APIToken", on_delete=models.CASCADE, related_name="importer")
imported_data = models.JSONField(null=True)
class Meta:
verbose_name = "Importer"
verbose_name_plural = "Importers"
db_table = "importers"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the service"""
return f"{self.service} <{self.project.name}>"
-21
View File
@@ -155,13 +155,6 @@ class Issue(ProjectBaseModel):
is_draft = models.BooleanField(default=False)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
type = models.ForeignKey(
"db.IssueType",
on_delete=models.SET_NULL,
related_name="issue_type",
null=True,
blank=True,
)
issue_objects = IssueManager()
@@ -242,20 +235,6 @@ class Issue(ProjectBaseModel):
return f"{self.name} <{self.project.name}>"
class IssueBlocker(ProjectBaseModel):
block = models.ForeignKey(Issue, related_name="blocker_issues", on_delete=models.CASCADE)
blocked_by = models.ForeignKey(Issue, related_name="blocked_issues", on_delete=models.CASCADE)
class Meta:
verbose_name = "Issue Blocker"
verbose_name_plural = "Issue Blockers"
db_table = "issue_blockers"
ordering = ("-created_at",)
def __str__(self):
return f"{self.block.name} {self.blocked_by.name}"
class IssueRelationChoices(models.TextChoices):
DUPLICATE = "duplicate", "Duplicate"
RELATES_TO = "relates_to", "Relates To"
-51
View File
@@ -1,51 +0,0 @@
# Django imports
from django.db import models
from django.db.models import Q
# Module imports
from .project import ProjectBaseModel
from .base import BaseModel
class IssueType(BaseModel):
workspace = models.ForeignKey("db.Workspace", related_name="issue_types", on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
logo_props = models.JSONField(default=dict)
is_epic = models.BooleanField(default=False)
is_default = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
level = models.FloatField(default=0)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
class Meta:
verbose_name = "Issue Type"
verbose_name_plural = "Issue Types"
db_table = "issue_types"
def __str__(self):
return self.name
class ProjectIssueType(ProjectBaseModel):
issue_type = models.ForeignKey("db.IssueType", related_name="project_issue_types", on_delete=models.CASCADE)
level = models.PositiveIntegerField(default=0)
is_default = models.BooleanField(default=False)
class Meta:
unique_together = ["project", "issue_type", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["project", "issue_type"],
condition=Q(deleted_at__isnull=True),
name="project_issue_type_unique_project_issue_type_when_deleted_at_null",
)
]
verbose_name = "Project Issue Type"
verbose_name_plural = "Project Issue Types"
db_table = "project_issue_types"
ordering = ("project", "issue_type")
def __str__(self):
return f"{self.project} - {self.issue_type}"
-16
View File
@@ -32,7 +32,6 @@ class Page(BaseModel):
owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="pages")
access = models.PositiveSmallIntegerField(choices=((0, "Public"), (1, "Private")), default=0)
color = models.CharField(max_length=255, blank=True)
labels = models.ManyToManyField("db.Label", blank=True, related_name="pages", through="db.PageLabel")
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
@@ -113,21 +112,6 @@ class PageLog(BaseModel):
return f"{self.page.name} {self.entity_name}"
class PageLabel(BaseModel):
label = models.ForeignKey("db.Label", on_delete=models.CASCADE, related_name="page_labels")
page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="page_labels")
workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_page_label")
class Meta:
verbose_name = "Page Label"
verbose_name_plural = "Page Labels"
db_table = "page_labels"
ordering = ("-created_at",)
def __str__(self):
return f"{self.page.name} {self.label.name}"
class ProjectPage(BaseModel):
project = models.ForeignKey("db.Project", on_delete=models.CASCADE, related_name="project_pages")
page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="project_pages")
@@ -1,39 +0,0 @@
# Django imports
from django.conf import settings
from django.db import models
from django.utils import timezone
# Module import
from .base import BaseModel
class SocialLoginConnection(BaseModel):
medium = models.CharField(
max_length=20,
choices=(
("Google", "google"),
("Github", "github"),
("GitLab", "gitlab"),
("Jira", "jira"),
),
default=None,
)
last_login_at = models.DateTimeField(default=timezone.now, null=True)
last_received_at = models.DateTimeField(default=timezone.now, null=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="user_login_connections",
)
token_data = models.JSONField(null=True)
extra_data = models.JSONField(null=True)
class Meta:
verbose_name = "Social Login Connection"
verbose_name_plural = "Social Login Connections"
db_table = "social_login_connections"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the user and medium"""
return f"{self.medium} <{self.user.email}>"
-49
View File
@@ -261,55 +261,6 @@ class WorkspaceMemberInvite(BaseModel):
return f"{self.workspace.name} {self.email} {self.accepted}"
class Team(BaseModel):
name = models.CharField(max_length=255, verbose_name="Team Name")
description = models.TextField(verbose_name="Team Description", blank=True)
workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE, related_name="workspace_team")
logo_props = models.JSONField(default=dict)
def __str__(self):
"""Return name of the team"""
return f"{self.name} <{self.workspace.name}>"
class Meta:
unique_together = ["name", "workspace", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["name", "workspace"],
condition=models.Q(deleted_at__isnull=True),
name="team_unique_name_workspace_when_deleted_at_null",
)
]
verbose_name = "Team"
verbose_name_plural = "Teams"
db_table = "teams"
ordering = ("-created_at",)
class WorkspaceTheme(BaseModel):
workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="themes")
name = models.CharField(max_length=300)
actor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="themes")
colors = models.JSONField(default=dict)
def __str__(self):
return str(self.name) + str(self.actor.email)
class Meta:
unique_together = ["workspace", "name", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["workspace", "name"],
condition=models.Q(deleted_at__isnull=True),
name="workspace_theme_unique_workspace_name_when_deleted_at_null",
)
]
verbose_name = "Workspace Theme"
verbose_name_plural = "Workspace Themes"
db_table = "workspace_themes"
ordering = ("-created_at",)
class WorkspaceUserProperties(BaseModel):
class NavigationControlPreference(models.TextChoices):
ACCORDION = "ACCORDION", "Accordion"