build: merge frontend and backend into a single repo

This commit is contained in:
pablohashescobar 2022-11-30 02:47:42 +05:30
parent 10ce333e6f
commit 26ec1e8c15
126 changed files with 8280 additions and 1 deletions

View file

@ -0,0 +1,38 @@
from .base import BaseModel
from .user import User
from .workspace import (
Workspace,
WorkspaceMember,
Team,
WorkspaceMemberInvite,
TeamMember,
)
from .project import Project, ProjectMember, ProjectBaseModel, ProjectMemberInvite, ProjectIdentifier
from .issue import (
Issue,
IssueActivity,
TimelineIssue,
IssueProperty,
IssueComment,
IssueBlocker,
IssueLabel,
IssueAssignee,
Label,
IssueBlocker,
)
from .asset import FileAsset
from .social_connection import SocialLoginConnection
from .state import State
from .cycle import Cycle, CycleIssue
from .shortcut import Shortcut
from .view import View

View file

@ -0,0 +1,24 @@
# Django import
from django.db import models
# Module import
from . import BaseModel
class FileAsset(BaseModel):
"""
A file asset.
"""
attributes = models.JSONField(default=dict)
asset = models.FileField(upload_to="library-assets")
class Meta:
verbose_name = "File Asset"
verbose_name_plural = "File Assets"
db_table = "file_asset"
ordering = ("-created_at",)
def __str__(self):
return self.asset

View file

@ -0,0 +1,39 @@
import uuid
# Django imports
from django.db import models
# Third party imports
from crum import get_current_user
# Module imports
from ..mixins import AuditModel
class BaseModel(AuditModel):
id = models.UUIDField(
default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True
)
class Meta:
abstract = True
def save(self, *args, **kwargs):
user = get_current_user()
if user is None or user.is_anonymous:
self.created_by = None
self.updated_by = None
super(BaseModel, self).save(*args, **kwargs)
else:
# Check if the model is being created or updated
if self._state.adding:
# If created only set created_by value: set updated_by to None
self.created_by = user
self.updated_by = None
# If updated only set updated_by value don't touch created_by
self.updated_by = user
super(BaseModel, self).save(*args, **kwargs)
def __str__(self):
return str(self.id)

View file

@ -0,0 +1,61 @@
# Django imports
from django.db import models
from django.conf import settings
# Module imports
from . import ProjectBaseModel
class Cycle(ProjectBaseModel):
STATUS_CHOICES = (
("draft", "Draft"),
("started", "Started"),
("completed", "Completed"),
)
name = models.CharField(max_length=255, verbose_name="Cycle Name")
description = models.TextField(verbose_name="Cycle Description", blank=True)
start_date = models.DateField(verbose_name="Start Date", blank=True, null=True)
end_date = models.DateField(verbose_name="End Date", blank=True, null=True)
owned_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="owned_by_cycle",
)
status = models.CharField(
max_length=255,
verbose_name="Cycle Status",
choices=STATUS_CHOICES,
default="draft",
)
class Meta:
verbose_name = "Cycle"
verbose_name_plural = "Cycles"
db_table = "cycle"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the cycle"""
return f"{self.name} <{self.project.name}>"
class CycleIssue(ProjectBaseModel):
"""
Cycle Issues
"""
issue = models.OneToOneField(
"db.Issue", on_delete=models.CASCADE, related_name="issue_cycle"
)
cycle = models.ForeignKey(
Cycle, on_delete=models.CASCADE, related_name="issue_cycle"
)
class Meta:
verbose_name = "Cycle Issue"
verbose_name_plural = "Cycle Issues"
db_table = "cycle_issue"
ordering = ("-created_at",)
def __str__(self):
return f"{self.cycle}"

View file

@ -0,0 +1,296 @@
# Django imports
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
# Module imports
from . import ProjectBaseModel
# TODO: Handle identifiers for Bulk Inserts - nk
class Issue(ProjectBaseModel):
PRIORITY_CHOICES = (
("urgent", "Urgent"),
("high", "High"),
("medium", "Medium"),
("low", "Low"),
)
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="parent_issue",
)
state = models.ForeignKey(
"db.State",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="state_issue",
)
name = models.CharField(max_length=255, verbose_name="Issue Name")
description = models.JSONField(verbose_name="Issue Description", blank=True)
priority = models.CharField(
max_length=30,
choices=PRIORITY_CHOICES,
verbose_name="Issue Priority",
null=True,
blank=True,
)
start_date = models.DateField(null=True, blank=True)
target_date = models.DateField(null=True, blank=True)
assignees = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name="assignee",
through="IssueAssignee",
through_fields=("issue", "assignee"),
)
sequence_id = models.IntegerField(default=1, verbose_name="Issue Sequence ID")
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
labels = models.ManyToManyField(
"db.Label", blank=True, related_name="labels", through="IssueLabel"
)
class Meta:
verbose_name = "Issue"
verbose_name_plural = "Issues"
db_table = "issue"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
# This means that the model isn't saved to the database yet
if self._state.adding:
# Get the maximum display_id value from the database
last_id = IssueSequence.objects.filter(project=self.project).aggregate(
largest=models.Max("sequence")
)["largest"]
# aggregate can return None! Check it first.
# If it isn't none, just use the last ID specified (which should be the greatest) and add one to it
if last_id is not None:
self.sequence_id = last_id + 1
if self.state is None:
try:
from plane.db.models import State
self.state, created = State.objects.get_or_create(
project=self.project, name="Backlog"
)
except ImportError:
pass
super(Issue, self).save(*args, **kwargs)
def __str__(self):
"""Return name of the issue"""
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_blocker"
ordering = ("-created_at",)
def __str__(self):
return f"{self.block.name} {self.blocked_by.name}"
class IssueAssignee(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_assignee"
)
assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="issue_assignee",
)
class Meta:
unique_together = ["issue", "assignee"]
verbose_name = "Issue Assignee"
verbose_name_plural = "Issue Assignees"
db_table = "issue_assignee"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.assignee.email}"
class IssueActivity(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_activity"
)
verb = models.CharField(max_length=255, verbose_name="Action", default="created")
field = models.CharField(
max_length=255, verbose_name="Field Name", blank=True, null=True
)
old_value = models.CharField(
max_length=255, verbose_name="Old Value", blank=True, null=True
)
new_value = models.CharField(
max_length=255, verbose_name="New Value", blank=True, null=True
)
comment = models.TextField(verbose_name="Comment", blank=True)
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
issue_comment = models.ForeignKey(
"db.IssueComment",
on_delete=models.SET_NULL,
related_name="issue_comment",
null=True,
)
actor = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name="issue_activities",
)
class Meta:
verbose_name = "Issue Activity"
verbose_name_plural = "Issue Activities"
db_table = "issue_activity"
ordering = ("-created_at",)
def __str__(self):
"""Return issue of the comment"""
return str(self.issue)
class TimelineIssue(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_timeline"
)
sequence_id = models.FloatField(default=1.0)
links = models.JSONField(default=dict, blank=True)
class Meta:
verbose_name = "Timeline Issue"
verbose_name_plural = "Timeline Issues"
db_table = "issue_timeline"
ordering = ("-created_at",)
def __str__(self):
"""Return project of the project member"""
return str(self.issue)
class IssueComment(ProjectBaseModel):
comment = models.TextField(verbose_name="Comment", blank=True)
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
# System can also create comment
actor = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="comments",
null=True,
)
class Meta:
verbose_name = "Issue Comment"
verbose_name_plural = "Issue Comments"
db_table = "issue_comment"
ordering = ("-created_at",)
def __str__(self):
"""Return issue of the comment"""
return str(self.issue)
class IssueProperty(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="issue_property_user",
)
properties = models.JSONField(default=dict)
class Meta:
verbose_name = "Issue Property"
verbose_name_plural = "Issue Properties"
db_table = "issue_property"
ordering = ("-created_at",)
unique_together = ["user", "project"]
def __str__(self):
"""Return properties status of the issue"""
return str(self.user)
class Label(ProjectBaseModel):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="parent_label",
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
colour = models.CharField(max_length=255, blank=True)
class Meta:
verbose_name = "Label"
verbose_name_plural = "Labels"
db_table = "label"
ordering = ("-created_at",)
def __str__(self):
return str(self.name)
class IssueLabel(ProjectBaseModel):
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="label_issue"
)
label = models.ForeignKey(
"db.Label", on_delete=models.CASCADE, related_name="label_issue"
)
class Meta:
verbose_name = "Issue Label"
verbose_name_plural = "Issue Labels"
db_table = "issue_label"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.label.name}"
class IssueSequence(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.SET_NULL, related_name="issue_sequence", null=True
)
sequence = models.PositiveBigIntegerField(default=1)
deleted = models.BooleanField(default=False)
class Meta:
verbose_name = "Issue Sequence"
verbose_name_plural = "Issue Sequences"
db_table = "issue_sequence"
ordering = ("-created_at",)
# TODO: Find a better method to save the model
@receiver(post_save, sender=Issue)
def create_issue_sequence(sender, instance, created, **kwargs):
if created:
IssueSequence.objects.create(
issue=instance, sequence=instance.sequence_id, project=instance.project
)

View file

@ -0,0 +1,142 @@
# Django imports
from django.db import models
from django.conf import settings
from django.template.defaultfilters import slugify
from django.db.models.signals import post_save
from django.dispatch import receiver
# Modeule imports
from plane.db.mixins import AuditModel
# Module imports
from . import BaseModel
ROLE_CHOICES = (
(20, "Admin"),
(15, "Member"),
(10, "Viewer"),
(5, "Guest"),
)
class Project(BaseModel):
NETWORK_CHOICES = ((0, "Secret"), (2, "Public"))
name = models.CharField(max_length=255, verbose_name="Project Name")
description = models.TextField(verbose_name="Project Description", blank=True)
description_text = models.JSONField(
verbose_name="Project Description RT", blank=True, null=True
)
description_html = models.JSONField(
verbose_name="Project Description HTML", blank=True, null=True
)
network = models.PositiveSmallIntegerField(default=2, choices=NETWORK_CHOICES)
workspace = models.ForeignKey(
"db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project"
)
identifier = models.CharField(
max_length=5, verbose_name="Project Identifier", null=True, blank=True
)
slug = models.SlugField(max_length=100, blank=True)
default_assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="default_assignee",
null=True,
blank=True,
)
project_lead = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="project_lead",
null=True,
blank=True,
)
def __str__(self):
"""Return name of the project"""
return f"{self.name} <{self.workspace.name}>"
class Meta:
unique_together = ["name", "workspace"]
verbose_name = "Project"
verbose_name_plural = "Projects"
db_table = "project"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
self.identifier = self.identifier.strip().upper()
return super().save(*args, **kwargs)
class ProjectBaseModel(BaseModel):
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="project_%(class)s"
)
workspace = models.ForeignKey(
"db.Workspace", models.CASCADE, related_name="workspace_%(class)s"
)
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.workspace = self.project.workspace
super(ProjectBaseModel, self).save(*args, **kwargs)
class ProjectMemberInvite(ProjectBaseModel):
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=10)
class Meta:
verbose_name = "Project Member Invite"
verbose_name_plural = "Project Member Invites"
db_table = "project_member_invite"
ordering = ("-created_at",)
def __str__(self):
return f"{self.project.name} {self.email} {self.accepted}"
class ProjectMember(ProjectBaseModel):
member = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="member_project",
)
comment = models.TextField(blank=True, null=True)
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=10)
class Meta:
unique_together = ["project", "member"]
verbose_name = "Project Member"
verbose_name_plural = "Project Members"
db_table = "project_member"
ordering = ("-created_at",)
def __str__(self):
"""Return members of the project"""
return f"{self.member.email} <{self.project.name}>"
class ProjectIdentifier(AuditModel):
project = models.OneToOneField(
Project, on_delete=models.CASCADE, related_name="project_identifier"
)
name = models.CharField(max_length=10)
class Meta:
verbose_name = "Project Identifier"
verbose_name_plural = "Project Identifiers"
db_table = "project_identifier"
ordering = ("-created_at",)

View file

@ -0,0 +1,26 @@
# Django imports
from django.db import models
# Module imports
from . import ProjectBaseModel
class Shortcut(ProjectBaseModel):
TYPE_CHOICES = (("repo", "Repo"), ("direct", "Direct"))
name = models.CharField(max_length=255, verbose_name="Cycle Name")
description = models.TextField(verbose_name="Cycle Description", blank=True)
type = models.CharField(
max_length=255, verbose_name="Shortcut Type", choices=TYPE_CHOICES
)
url = models.URLField(verbose_name="URL", blank=True, null=True)
class Meta:
verbose_name = "Shortcut"
verbose_name_plural = "Shortcuts"
db_table = "shortcut"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the shortcut"""
return f"{self.name} <{self.project.name}>"

View file

@ -0,0 +1,34 @@
# Django imports
from django.db import models
from django.conf import settings
from django.utils import timezone
# Module import
from . import BaseModel
class SocialLoginConnection(BaseModel):
medium = models.CharField(
max_length=20,
choices=(("Google", "google"), ("Github", "github")),
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_connection"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the user and medium"""
return f"{self.medium} <{self.user.email}>"

View file

@ -0,0 +1,29 @@
# Django imports
from django.db import models
from django.template.defaultfilters import slugify
# Module imports
from . import ProjectBaseModel
class State(ProjectBaseModel):
name = models.CharField(max_length=255, verbose_name="State Name")
description = models.TextField(verbose_name="State Description", blank=True)
color = models.CharField(max_length=255, verbose_name="State Color")
slug = models.SlugField(max_length=100, blank=True)
sequence = models.FloatField(default=65535)
def __str__(self):
"""Return name of the state"""
return f"{self.name} <{self.project.name}>"
class Meta:
unique_together = ["name", "project"]
verbose_name = "State"
verbose_name_plural = "States"
db_table = "state"
ordering = ("sequence",)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
return super().save(*args, **kwargs)

View file

@ -0,0 +1,126 @@
# Python imports
from enum import unique
import uuid
# Django imports
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import AbstractBaseUser, UserManager, PermissionsMixin
from django.utils import timezone
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Third party imports
from sentry_sdk import capture_exception
class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(
default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True
)
username = models.CharField(max_length=128, unique=True)
# user fields
mobile_number = models.CharField(max_length=255, blank=True, null=True)
email = models.CharField(max_length=255, null=True, blank=True, unique=True)
first_name = models.CharField(max_length=255, blank=True)
last_name = models.CharField(max_length=255, blank=True)
avatar = models.CharField(max_length=255, blank=True)
# tracking metrics
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="Created At")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Last Modified At")
last_location = models.CharField(max_length=255, blank=True)
created_location = models.CharField(max_length=255, blank=True)
# the is' es
is_superuser = models.BooleanField(default=False)
is_managed = models.BooleanField(default=False)
is_password_expired = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_email_verified = models.BooleanField(default=False)
is_password_autoset = models.BooleanField(default=False)
is_onboarded = models.BooleanField(default=False)
token = models.CharField(max_length=64, blank=True)
billing_address_country = models.CharField(max_length=255, default="INDIA")
billing_address = models.JSONField(null=True)
has_billing_address = models.BooleanField(default=False)
user_timezone = models.CharField(max_length=255, default="Asia/Kolkata")
last_active = models.DateTimeField(default=timezone.now, null=True)
last_login_time = models.DateTimeField(null=True)
last_logout_time = models.DateTimeField(null=True)
last_login_ip = models.CharField(max_length=255, blank=True)
last_logout_ip = models.CharField(max_length=255, blank=True)
last_login_medium = models.CharField(
max_length=20,
default="email",
)
last_login_uagent = models.TextField(blank=True)
token_updated_at = models.DateTimeField(null=True)
last_workspace_id = models.UUIDField(null=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
objects = UserManager()
class Meta:
verbose_name = "User"
verbose_name_plural = "Users"
db_table = "user"
ordering = ("-created_at",)
def __str__(self):
return f"{self.username} <{self.email}>"
def save(self, *args, **kwargs):
self.email = self.email.lower().strip()
self.mobile_number = self.mobile_number
if self.token_updated_at is not None:
self.token = uuid.uuid4().hex + uuid.uuid4().hex
self.token_updated_at = timezone.now()
if self.is_superuser:
self.is_staff = True
super(User, self).save(*args, **kwargs)
@receiver(post_save, sender=User)
def send_welcome_email(sender, instance, created, **kwargs):
try:
if created:
first_name = instance.first_name.capitalize()
to_email = instance.email
from_email_string = f"Team Plane <team@mailer.plane.so>"
subject = f"Welcome {first_name}!"
context = {"first_name": first_name, "email": instance.email}
html_content = render_to_string(
"emails/auth/user_welcome_email.html", context
)
text_content = strip_tags(html_content)
msg = EmailMultiAlternatives(
subject, text_content, from_email_string, [to_email]
)
msg.attach_alternative(html_content, "text/html")
msg.send()
return
except Exception as e:
capture_exception(e)
return

View file

@ -0,0 +1,22 @@
# Django imports
from django.db import models
# Module import
from . import ProjectBaseModel
class View(ProjectBaseModel):
name = models.CharField(max_length=255, verbose_name="View Name")
description = models.TextField(verbose_name="View Description", blank=True)
query = models.JSONField(verbose_name="View Query")
class Meta:
verbose_name = "View"
verbose_name_plural = "Views"
db_table = "view"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the View"""
return f"{self.name} <{self.project.name}>"

View file

@ -0,0 +1,134 @@
# Django imports
from django.db import models
from django.template.defaultfilters import slugify
from django.conf import settings
# Module imports
from . import BaseModel
ROLE_CHOICES = (
(20, "Owner"),
(15, "Admin"),
(10, "Member"),
(5, "Guest"),
)
class Workspace(BaseModel):
name = models.CharField(max_length=255, verbose_name="Workspace Name")
logo = models.URLField(verbose_name="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=100, db_index=True, unique=True)
company_size = models.PositiveIntegerField(default=10)
def __str__(self):
"""Return name of the Workspace"""
return self.name
class Meta:
unique_together = ["name", "owner"]
verbose_name = "Workspace"
verbose_name_plural = "Workspaces"
db_table = "workspace"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
return super().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=10)
company_role = models.TextField(null=True, blank=True)
class Meta:
unique_together = ["workspace", "member"]
verbose_name = "Workspace Member"
verbose_name_plural = "Workspace Members"
db_table = "workspace_member"
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=10)
class Meta:
verbose_name = "Workspace Member Invite"
verbose_name_plural = "Workspace Member Invites"
db_table = "workspace_member_invite"
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)
members = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name="members",
through="TeamMember",
through_fields=("team", "member"),
)
workspace = models.ForeignKey(
Workspace, on_delete=models.CASCADE, related_name="workspace_team"
)
def __str__(self):
"""Return name of the team"""
return f"{self.name} <{self.workspace.name}>"
class Meta:
unique_together = ["name", "workspace"]
verbose_name = "Team"
verbose_name_plural = "Teams"
db_table = "team"
ordering = ("-created_at",)
class TeamMember(BaseModel):
workspace = models.ForeignKey(
Workspace, on_delete=models.CASCADE, related_name="team_member"
)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="team_member")
member = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="team_member"
)
def __str__(self):
return self.team.name
class Meta:
unique_together = ["team", "member"]
verbose_name = "Team Member"
verbose_name_plural = "Team Members"
db_table = "team_member"
ordering = ("-created_at",)