build: merge frontend and backend into a single repo
This commit is contained in:
parent
10ce333e6f
commit
26ec1e8c15
126 changed files with 8280 additions and 1 deletions
38
apiserver/plane/db/models/__init__.py
Normal file
38
apiserver/plane/db/models/__init__.py
Normal 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
|
||||
24
apiserver/plane/db/models/asset.py
Normal file
24
apiserver/plane/db/models/asset.py
Normal 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
|
||||
|
||||
39
apiserver/plane/db/models/base.py
Normal file
39
apiserver/plane/db/models/base.py
Normal 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)
|
||||
61
apiserver/plane/db/models/cycle.py
Normal file
61
apiserver/plane/db/models/cycle.py
Normal 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}"
|
||||
296
apiserver/plane/db/models/issue.py
Normal file
296
apiserver/plane/db/models/issue.py
Normal 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
|
||||
)
|
||||
142
apiserver/plane/db/models/project.py
Normal file
142
apiserver/plane/db/models/project.py
Normal 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",)
|
||||
26
apiserver/plane/db/models/shortcut.py
Normal file
26
apiserver/plane/db/models/shortcut.py
Normal 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}>"
|
||||
34
apiserver/plane/db/models/social_connection.py
Normal file
34
apiserver/plane/db/models/social_connection.py
Normal 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}>"
|
||||
29
apiserver/plane/db/models/state.py
Normal file
29
apiserver/plane/db/models/state.py
Normal 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)
|
||||
126
apiserver/plane/db/models/user.py
Normal file
126
apiserver/plane/db/models/user.py
Normal 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
|
||||
22
apiserver/plane/db/models/view.py
Normal file
22
apiserver/plane/db/models/view.py
Normal 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}>"
|
||||
134
apiserver/plane/db/models/workspace.py
Normal file
134
apiserver/plane/db/models/workspace.py
Normal 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",)
|
||||
Loading…
Add table
Add a link
Reference in a new issue