mirror of
https://github.com/makeplane/plane.git
synced 2026-07-13 14:01:45 +02:00
feat: project grouping migrations (#691)
* feat: project grouping migrations * chore: updated migration file * chore: migration file changes * chore: replaced base modal with project base modal in project attribute * chore: project attribute migration * chore: unique constraints migration * dev: soft delete for project states * chore: removed db dependency --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com>
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
# Generated by Django 4.2.14 on 2024-08-02 09:52
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("ee", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ProjectAttribute",
|
||||
fields=[
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="Created At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"deleted_at",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
db_index=True,
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"priority",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("none", "None"),
|
||||
("low", "Low"),
|
||||
("medium", "Medium"),
|
||||
("high", "High"),
|
||||
("urgent", "Urgent"),
|
||||
],
|
||||
default="none",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
("start_date", models.DateTimeField(blank=True, null=True)),
|
||||
("target_date", models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Project Attribute",
|
||||
"verbose_name_plural": "Project Attributes",
|
||||
"db_table": "project_attributes",
|
||||
"ordering": ("created_at",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ProjectState",
|
||||
fields=[
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="Created At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"deleted_at",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
db_index=True,
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=50)),
|
||||
("description", models.TextField(blank=True)),
|
||||
("color", models.CharField(max_length=255)),
|
||||
("sequence", models.FloatField(default=65535)),
|
||||
(
|
||||
"group",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("draft", "Draft"),
|
||||
("planning", "Planning"),
|
||||
("execution", "Execution"),
|
||||
("monitoring", "Monitoring"),
|
||||
("completed", "Completed"),
|
||||
("cancelled", "Cancelled"),
|
||||
],
|
||||
default="draft",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
("default", models.BooleanField(default=False)),
|
||||
(
|
||||
"external_source",
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
(
|
||||
"external_id",
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Project State",
|
||||
"verbose_name_plural": "Project States",
|
||||
"db_table": "project_states",
|
||||
"ordering": ("sequence",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="WorkspaceFeature",
|
||||
fields=[
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="Created At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"deleted_at",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
db_index=True,
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_project_grouping_enabled",
|
||||
models.BooleanField(default=False),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Workspace Feature",
|
||||
"verbose_name_plural": "Workspace Features",
|
||||
"db_table": "workspace_features",
|
||||
"ordering": ("-created_at",),
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="issueproperty",
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="issuepropertyoption",
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="issueproperty",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="issuepropertyactivity",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="issuepropertyoption",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="issuepropertyvalue",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="issueworklog",
|
||||
name="deleted_at",
|
||||
field=models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="Deleted At"
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="issueproperty",
|
||||
unique_together={("name", "issue_type", "deleted_at")},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="issuepropertyoption",
|
||||
unique_together={("name", "property", "deleted_at")},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="issueproperty",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("deleted_at__isnull", True)),
|
||||
fields=("name", "issue_type"),
|
||||
name="issue_property_unique_name_project_when_deleted_at_null",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="issuepropertyoption",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("deleted_at__isnull", True)),
|
||||
fields=("name", "property"),
|
||||
name="issue_property_option_unique_name_project_when_deleted_at_null",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workspacefeature",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_created_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Created By",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workspacefeature",
|
||||
name="updated_by",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_updated_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Last Modified By",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workspacefeature",
|
||||
name="workspace",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="features",
|
||||
to="db.workspace",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectstate",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_created_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Created By",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectstate",
|
||||
name="updated_by",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_updated_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Last Modified By",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectstate",
|
||||
name="workspace",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="project_states",
|
||||
to="db.workspace",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectattribute",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_created_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Created By",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectattribute",
|
||||
name="project",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="project_%(class)s",
|
||||
to="db.project",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectattribute",
|
||||
name="state",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="project_attributes",
|
||||
to="ee.projectstate",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectattribute",
|
||||
name="updated_by",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_updated_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Last Modified By",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projectattribute",
|
||||
name="workspace",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="workspace_%(class)s",
|
||||
to="db.workspace",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="projectstate",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("deleted_at__isnull", True)),
|
||||
fields=("name", "workspace"),
|
||||
name="project_state_unique_name_project_when_deleted_at_null",
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="projectstate",
|
||||
unique_together={("name", "workspace", "deleted_at")},
|
||||
),
|
||||
]
|
||||
@@ -5,3 +5,5 @@ from .issue_properties import (
|
||||
IssuePropertyActivity,
|
||||
)
|
||||
from .issue import IssueWorkLog
|
||||
from .project import ProjectState, ProjectAttribute
|
||||
from .workspace import WorkspaceFeature
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Django imports
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
|
||||
@@ -54,7 +55,14 @@ class IssueProperty(WorkspaceBaseModel):
|
||||
|
||||
class Meta:
|
||||
ordering = ["sort_order"]
|
||||
unique_together = ["name", "issue_type"]
|
||||
unique_together = ["name", "issue_type", "deleted_at"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["name", "issue_type"],
|
||||
condition=Q(deleted_at__isnull=True),
|
||||
name="issue_property_unique_name_project_when_deleted_at_null",
|
||||
)
|
||||
]
|
||||
db_table = "issue_properties"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -95,7 +103,14 @@ class IssuePropertyOption(WorkspaceBaseModel):
|
||||
|
||||
class Meta:
|
||||
ordering = ["sort_order"]
|
||||
unique_together = ["name", "property"]
|
||||
unique_together = ["name", "property", "deleted_at"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["name", "property"],
|
||||
condition=Q(deleted_at__isnull=True),
|
||||
name="issue_property_option_unique_name_project_when_deleted_at_null",
|
||||
)
|
||||
]
|
||||
db_table = "issue_property_options"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
91
apiserver/plane/ee/models/project.py
Normal file
91
apiserver/plane/ee/models/project.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# Django imports
|
||||
from django.db import models
|
||||
|
||||
# Module imports
|
||||
from plane.db.models.base import BaseModel
|
||||
from plane.db.models.project import ProjectBaseModel
|
||||
|
||||
|
||||
class GroupChoices(models.TextChoices):
|
||||
DRAFT = "draft", "Draft"
|
||||
PLANNING = "planning", "Planning"
|
||||
EXECUTION = "execution", "Execution"
|
||||
MONITORING = "monitoring", "Monitoring"
|
||||
COMPLETED = "completed", "Completed"
|
||||
CANCELLED = "cancelled", "Cancelled"
|
||||
|
||||
|
||||
class ProjectState(BaseModel):
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", on_delete=models.CASCADE, related_name="project_states"
|
||||
)
|
||||
name = models.CharField(max_length=50)
|
||||
description = models.TextField(blank=True)
|
||||
color = models.CharField(max_length=255)
|
||||
sequence = models.FloatField(default=65535)
|
||||
group = models.CharField(
|
||||
max_length=20, choices=GroupChoices.choices, default=GroupChoices.DRAFT
|
||||
)
|
||||
default = 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)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the state"""
|
||||
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="project_state_unique_name_project_when_deleted_at_null",
|
||||
)
|
||||
]
|
||||
verbose_name = "Project State"
|
||||
verbose_name_plural = "Project States"
|
||||
db_table = "project_states"
|
||||
ordering = ("sequence",)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self._state.adding:
|
||||
# Get the maximum sequence value from the database
|
||||
last_id = ProjectState.objects.filter(
|
||||
workspace=self.workspace
|
||||
).aggregate(largest=models.Max("sequence"))["largest"]
|
||||
# if last_id is not None
|
||||
if last_id is not None:
|
||||
self.sequence = last_id + 15000
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class PriorityChoices(models.TextChoices):
|
||||
NONE = "none", "None"
|
||||
LOW = "low", "Low"
|
||||
MEDIUM = "medium", "Medium"
|
||||
HIGH = "high", "High"
|
||||
URGENT = "urgent", "Urgent"
|
||||
|
||||
|
||||
class ProjectAttribute(ProjectBaseModel):
|
||||
priority = models.CharField(
|
||||
max_length=50,
|
||||
choices=PriorityChoices.choices,
|
||||
default=PriorityChoices.NONE,
|
||||
)
|
||||
state = models.ForeignKey(
|
||||
ProjectState,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="project_attributes",
|
||||
null=True,
|
||||
)
|
||||
start_date = models.DateTimeField(null=True, blank=True)
|
||||
target_date = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Project Attribute"
|
||||
verbose_name_plural = "Project Attributes"
|
||||
db_table = "project_attributes"
|
||||
ordering = ("created_at",)
|
||||
18
apiserver/plane/ee/models/workspace.py
Normal file
18
apiserver/plane/ee/models/workspace.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Django imports
|
||||
from django.db import models
|
||||
|
||||
# Module imports
|
||||
from plane.db.models.base import BaseModel
|
||||
|
||||
|
||||
class WorkspaceFeature(BaseModel):
|
||||
workspace = models.OneToOneField(
|
||||
"db.Workspace", on_delete=models.CASCADE, related_name="features"
|
||||
)
|
||||
is_project_grouping_enabled = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Workspace Feature"
|
||||
verbose_name_plural = "Workspace Features"
|
||||
db_table = "workspace_features"
|
||||
ordering = ("-created_at",)
|
||||
Reference in New Issue
Block a user