bb-plane-fork/apiserver/plane/db/models/issue.py
2022-11-30 02:47:42 +05:30

296 lines
8.8 KiB
Python

# 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
)