bb-plane-fork/apps/api/plane/db/models/workspace.py
sriram veeraghanta 9237f568dd
[WEB-5044] fix: ruff lint and format errors (#7868)
* fix: lint errors

* fix: file formatting

* fix: code refactor
2025-09-29 19:15:32 +05:30

437 lines
15 KiB
Python

# Python imports
import pytz
from typing import Optional, Any
# Django imports
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
# Module imports
from .base import BaseModel
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
from plane.utils.color import get_random_color
ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest"))
def get_default_props():
return {
"filters": {
"priority": None,
"state": None,
"state_group": None,
"assignees": None,
"created_by": None,
"labels": None,
"start_date": None,
"target_date": None,
"subscriber": None,
},
"display_filters": {
"group_by": None,
"order_by": "-created_at",
"type": None,
"sub_issue": True,
"show_empty_groups": True,
"layout": "list",
"calendar_date_range": "",
},
"display_properties": {
"assignee": True,
"attachment_count": True,
"created_on": True,
"due_date": True,
"estimate": True,
"key": True,
"labels": True,
"link": True,
"priority": True,
"start_date": True,
"state": True,
"sub_issue_count": True,
"updated_on": True,
},
}
def get_default_filters():
return {
"priority": None,
"state": None,
"state_group": None,
"assignees": None,
"created_by": None,
"labels": None,
"start_date": None,
"target_date": None,
"subscriber": None,
}
def get_default_display_filters():
return {
"display_filters": {
"group_by": None,
"order_by": "-created_at",
"type": None,
"sub_issue": True,
"show_empty_groups": True,
"layout": "list",
"calendar_date_range": "",
}
}
def get_default_display_properties():
return {
"display_properties": {
"assignee": True,
"attachment_count": True,
"created_on": True,
"due_date": True,
"estimate": True,
"key": True,
"labels": True,
"link": True,
"priority": True,
"start_date": True,
"state": True,
"sub_issue_count": True,
"updated_on": True,
}
}
def get_issue_props():
return {"subscribed": True, "assigned": True, "created": True, "all_issues": True}
def slug_validator(value):
if value in RESTRICTED_WORKSPACE_SLUGS:
raise ValidationError("Slug is not valid")
class Workspace(BaseModel):
TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones))
name = models.CharField(max_length=80, verbose_name="Workspace Name")
logo = models.TextField(verbose_name="Logo", blank=True, null=True)
logo_asset = models.ForeignKey(
"db.FileAsset",
on_delete=models.SET_NULL,
related_name="workspace_logo",
blank=True,
null=True,
)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="owner_workspace",
)
slug = models.SlugField(max_length=48, db_index=True, unique=True, validators=[slug_validator])
organization_size = models.CharField(max_length=20, blank=True, null=True)
timezone = models.CharField(max_length=255, default="UTC", choices=TIMEZONE_CHOICES)
background_color = models.CharField(max_length=255, default=get_random_color)
def __str__(self):
"""Return name of the Workspace"""
return self.name
@property
def logo_url(self):
# Return the logo asset url if it exists
if self.logo_asset:
return self.logo_asset.asset_url
# Return the logo url if it exists
if self.logo:
return self.logo
return None
def delete(self, using: Optional[str] = None, soft: bool = True, *args: Any, **kwargs: Any):
"""
Override the delete method to append epoch timestamp to the slug when soft deleting.
Args:
using: The database alias to use for the deletion.
soft: Whether to perform a soft delete (True) or hard delete (False).
*args: Additional positional arguments.
**kwargs: Additional keyword arguments.
"""
# Call the parent class's delete method first
result = super().delete(using=using, soft=soft, *args, **kwargs)
# If it's a soft delete and the model still exists (not hard deleted)
if soft and hasattr(self, "deleted_at") and self.deleted_at:
# Use the deleted_at timestamp to update the slug
deletion_timestamp: int = int(self.deleted_at.timestamp())
self.slug = f"{self.slug}__{deletion_timestamp}"
self.save(update_fields=["slug"])
return result
class Meta:
verbose_name = "Workspace"
verbose_name_plural = "Workspaces"
db_table = "workspaces"
ordering = ("-created_at",)
class WorkspaceBaseModel(BaseModel):
workspace = models.ForeignKey("db.Workspace", models.CASCADE, related_name="workspace_%(class)s")
project = models.ForeignKey("db.Project", models.CASCADE, related_name="project_%(class)s", null=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.project:
self.workspace = self.project.workspace
super(WorkspaceBaseModel, self).save(*args, **kwargs)
class WorkspaceMember(BaseModel):
workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_member")
member = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="member_workspace",
)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=5)
company_role = models.TextField(null=True, blank=True)
view_props = models.JSONField(default=get_default_props)
default_props = models.JSONField(default=get_default_props)
issue_props = models.JSONField(default=get_issue_props)
is_active = models.BooleanField(default=True)
class Meta:
unique_together = ["workspace", "member", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["workspace", "member"],
condition=models.Q(deleted_at__isnull=True),
name="workspace_member_unique_workspace_member_when_deleted_at_null",
)
]
verbose_name = "Workspace Member"
verbose_name_plural = "Workspace Members"
db_table = "workspace_members"
ordering = ("-created_at",)
def __str__(self):
"""Return members of the workspace"""
return f"{self.member.email} <{self.workspace.name}>"
class WorkspaceMemberInvite(BaseModel):
workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="workspace_member_invite")
email = models.CharField(max_length=255)
accepted = models.BooleanField(default=False)
token = models.CharField(max_length=255)
message = models.TextField(null=True)
responded_at = models.DateTimeField(null=True)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=5)
class Meta:
unique_together = ["email", "workspace", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["email", "workspace"],
condition=models.Q(deleted_at__isnull=True),
name="workspace_member_invite_unique_email_workspace_when_deleted_at_null",
)
]
verbose_name = "Workspace Member Invite"
verbose_name_plural = "Workspace Member Invites"
db_table = "workspace_member_invites"
ordering = ("-created_at",)
def __str__(self):
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):
workspace = models.ForeignKey(
"db.Workspace",
on_delete=models.CASCADE,
related_name="workspace_user_properties",
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="workspace_user_properties",
)
filters = models.JSONField(default=get_default_filters)
display_filters = models.JSONField(default=get_default_display_filters)
display_properties = models.JSONField(default=get_default_display_properties)
rich_filters = models.JSONField(default=dict)
class Meta:
unique_together = ["workspace", "user", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["workspace", "user"],
condition=models.Q(deleted_at__isnull=True),
name="workspace_user_properties_unique_workspace_user_when_deleted_at_null",
)
]
verbose_name = "Workspace User Property"
verbose_name_plural = "Workspace User Property"
db_table = "workspace_user_properties"
ordering = ("-created_at",)
def __str__(self):
return f"{self.workspace.name} {self.user.email}"
class WorkspaceUserLink(WorkspaceBaseModel):
title = models.CharField(max_length=255, null=True, blank=True)
url = models.TextField()
metadata = models.JSONField(default=dict)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="owner_workspace_user_link",
)
class Meta:
verbose_name = "Workspace User Link"
verbose_name_plural = "Workspace User Links"
db_table = "workspace_user_links"
ordering = ("-created_at",)
def __str__(self):
return f"{self.workspace.id} {self.url}"
class WorkspaceHomePreference(BaseModel):
"""Preference for the home page of a workspace for a user"""
class HomeWidgetKeys(models.TextChoices):
QUICK_LINKS = "quick_links", "Quick Links"
RECENTS = "recents", "Recents"
MY_STICKIES = "my_stickies", "My Stickies"
NEW_AT_PLANE = "new_at_plane", "New at Plane"
QUICK_TUTORIAL = "quick_tutorial", "Quick Tutorial"
workspace = models.ForeignKey(
"db.Workspace",
on_delete=models.CASCADE,
related_name="workspace_user_home_preferences",
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="workspace_user_home_preferences",
)
key = models.CharField(max_length=255)
is_enabled = models.BooleanField(default=True)
config = models.JSONField(default=dict)
sort_order = models.FloatField(default=65535)
class Meta:
unique_together = ["workspace", "user", "key", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["workspace", "user", "key"],
condition=models.Q(deleted_at__isnull=True),
name="workspace_user_home_preferences_unique_workspace_user_key_when_deleted_at_null",
)
]
verbose_name = "Workspace Home Preference"
verbose_name_plural = "Workspace Home Preferences"
db_table = "workspace_home_preferences"
ordering = ("-created_at",)
def __str__(self):
return f"{self.workspace.name} {self.user.email} {self.key}"
class WorkspaceUserPreference(BaseModel):
"""Preference for the workspace for a user"""
class UserPreferenceKeys(models.TextChoices):
VIEWS = "views", "Views"
ACTIVE_CYCLES = "active_cycles", "Active Cycles"
ANALYTICS = "analytics", "Analytics"
DRAFTS = "drafts", "Drafts"
YOUR_WORK = "your_work", "Your Work"
ARCHIVES = "archives", "Archives"
workspace = models.ForeignKey(
"db.Workspace",
on_delete=models.CASCADE,
related_name="workspace_user_preferences",
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="workspace_user_preferences",
)
key = models.CharField(max_length=255)
is_pinned = models.BooleanField(default=False)
sort_order = models.FloatField(default=65535)
class Meta:
unique_together = ["workspace", "user", "key", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["workspace", "user", "key"],
condition=models.Q(deleted_at__isnull=True),
name="workspace_user_preferences_unique_workspace_user_key_when_deleted_at_null",
)
]
verbose_name = "Workspace User Preference"
verbose_name_plural = "Workspace User Preferences"
db_table = "workspace_user_preferences"
ordering = ("-created_at",)