Skip to content

Commit

Permalink
[WEB-2092] fix: added unique constraints for project, module and stat…
Browse files Browse the repository at this point in the history
…es (makeplane#5281)

* fix: added unique constraints

* chore: migration indetaton
  • Loading branch information
NarayanBavisetti authored Jul 31, 2024
1 parent 67f2e2f commit daaa04c
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 14 deletions.
1 change: 1 addition & 0 deletions apiserver/plane/api/serializers/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Meta:
"workspace",
"project",
"owned_by",
"deleted_at",
]


Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/api/serializers/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Meta:
"updated_by",
"created_at",
"updated_at",
"deleted_at",
]

def to_representation(self, instance):
Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/api/serializers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Meta:
"updated_at",
"created_by",
"updated_by",
"deleted_at",
]

def validate(self, data):
Expand Down
4 changes: 4 additions & 0 deletions apiserver/plane/api/views/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ def delete(self, request, slug, project_id, pk):
)
# Delete the cycle
cycle.delete()
# Delete the cycle issues
CycleIssue.objects.filter(
cycle_id=self.kwargs.get("pk"),
).delete()
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
4 changes: 4 additions & 0 deletions apiserver/plane/api/views/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ def delete(self, request, slug, project_id, pk):
epoch=int(timezone.now().timestamp()),
)
module.delete()
# Delete the module issues
ModuleIssue.objects.filter(
module=pk,
).delete()
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/app/serializers/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Meta:
"created_at",
"updated_at",
"archived_at",
"deleted_at",
]

def to_representation(self, instance):
Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/app/serializers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Meta:
fields = "__all__"
read_only_fields = [
"workspace",
"deleted_at",
]

def create(self, validated_data):
Expand Down
4 changes: 4 additions & 0 deletions apiserver/plane/app/views/cycle/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,10 @@ def destroy(self, request, slug, project_id, pk):
)
# Delete the cycle
cycle.delete()
# Delete the cycle issues
CycleIssue.objects.filter(
cycle_id=self.kwargs.get("pk"),
).delete()
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
4 changes: 2 additions & 2 deletions apiserver/plane/app/views/cycle/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from plane.app.permissions import (
ProjectEntityPermission,
)

# Module imports
from .. import BaseViewSet
from plane.app.serializers import (
Expand All @@ -45,7 +46,6 @@
SubGroupedOffsetPaginator,
)

# Module imports

class CycleIssueViewSet(BaseViewSet):
serializer_class = CycleIssueSerializer
Expand Down Expand Up @@ -334,7 +334,7 @@ def create(self, request, slug, project_id, cycle_id):
return Response({"message": "success"}, status=status.HTTP_201_CREATED)

def destroy(self, request, slug, project_id, cycle_id, issue_id):
cycle_issue = CycleIssue.objects.get(
cycle_issue = CycleIssue.objects.filter(
issue_id=issue_id,
workspace__slug=slug,
project_id=project_id,
Expand Down
15 changes: 14 additions & 1 deletion apiserver/plane/app/views/issue/attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .. import BaseAPIView
from plane.app.serializers import IssueAttachmentSerializer
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import IssueAttachment
from plane.db.models import IssueAttachment, ProjectMember
from plane.bgtasks.issue_activites_task import issue_activity


Expand Down Expand Up @@ -49,6 +49,19 @@ def post(self, request, slug, project_id, issue_id):

def delete(self, request, slug, project_id, issue_id, pk):
issue_attachment = IssueAttachment.objects.get(pk=pk)
if issue_attachment.created_by_id != request.user.id and (
not ProjectMember.objects.filter(
workspace__slug=slug,
member=request.user,
role=20,
project_id=project_id,
is_active=True,
).exists()
):
return Response(
{"error": "Only admin or creator can delete the attachment"},
status=status.HTTP_403_FORBIDDEN,
)
issue_attachment.asset.delete(save=False)
issue_attachment.delete()
issue_activity.delay(
Expand Down
4 changes: 4 additions & 0 deletions apiserver/plane/app/views/module/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,10 @@ def destroy(self, request, slug, project_id, pk):
for issue in module_issues
]
module.delete()
# Delete the module issues
ModuleIssue.objects.filter(
module=pk,
).delete()
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
9 changes: 4 additions & 5 deletions apiserver/plane/app/views/module/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ def create_issue_modules(self, request, slug, project_id, issue_id):
removed_modules = request.data.get("removed_modules", [])
project = Project.objects.get(pk=project_id)


if modules:
_ = ModuleIssue.objects.bulk_create(
[
Expand Down Expand Up @@ -284,7 +283,7 @@ def create_issue_modules(self, request, slug, project_id, issue_id):
]

for module_id in removed_modules:
module_issue = ModuleIssue.objects.get(
module_issue = ModuleIssue.objects.filter(
workspace__slug=slug,
project_id=project_id,
module_id=module_id,
Expand All @@ -297,7 +296,7 @@ def create_issue_modules(self, request, slug, project_id, issue_id):
issue_id=str(issue_id),
project_id=str(project_id),
current_instance=json.dumps(
{"module_name": module_issue.module.name}
{"module_name": module_issue.first().module.name}
),
epoch=int(timezone.now().timestamp()),
notification=True,
Expand All @@ -308,7 +307,7 @@ def create_issue_modules(self, request, slug, project_id, issue_id):
return Response({"message": "success"}, status=status.HTTP_201_CREATED)

def destroy(self, request, slug, project_id, module_id, issue_id):
module_issue = ModuleIssue.objects.get(
module_issue = ModuleIssue.objects.filter(
workspace__slug=slug,
project_id=project_id,
module_id=module_id,
Expand All @@ -321,7 +320,7 @@ def destroy(self, request, slug, project_id, module_id, issue_id):
issue_id=str(issue_id),
project_id=str(project_id),
current_instance=json.dumps(
{"module_name": module_issue.module.name}
{"module_name": module_issue.first().module.name}
),
epoch=int(timezone.now().timestamp()),
notification=True,
Expand Down
3 changes: 1 addition & 2 deletions apiserver/plane/bgtasks/issue_activites_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ def delete_issue_activity(
IssueActivity(
project_id=project_id,
workspace_id=workspace_id,
issue_id=issue_id,
comment="deleted the issue",
verb="deleted",
actor_id=actor_id,
Expand Down Expand Up @@ -879,7 +880,6 @@ def delete_cycle_issue_activity(
cycle_name = requested_data.get("cycle_name", "")
cycle = Cycle.objects.filter(pk=cycle_id).first()
issues = requested_data.get("issues")

for issue in issues:
current_issue = Issue.objects.filter(pk=issue).first()
if issue:
Expand Down Expand Up @@ -1774,4 +1774,3 @@ def issue_activity(
except Exception as e:
log_exception(e)
return

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Generated by Django 4.2.11 on 2024-07-31 12:17

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
(
"db",
"0073_analyticview_deleted_at_apiactivitylog_deleted_at_and_more",
),
]

operations = [
migrations.AlterUniqueTogether(
name="label",
unique_together={("name", "project", "deleted_at")},
),
migrations.AlterUniqueTogether(
name="module",
unique_together={("name", "project", "deleted_at")},
),
migrations.AlterUniqueTogether(
name="project",
unique_together={
("identifier", "workspace", "deleted_at"),
("name", "workspace", "deleted_at"),
},
),
migrations.AlterUniqueTogether(
name="projectidentifier",
unique_together={("name", "workspace", "deleted_at")},
),
migrations.AddConstraint(
model_name="label",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("name", "project"),
name="label_unique_name_project_when_deleted_at_null",
),
),
migrations.AddConstraint(
model_name="module",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("name", "project"),
name="module_unique_name_project_when_deleted_at_null",
),
),
migrations.AddConstraint(
model_name="project",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("identifier", "workspace"),
name="project_unique_identifier_workspace_when_deleted_at_null",
),
),
migrations.AddConstraint(
model_name="project",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("name", "workspace"),
name="project_unique_name_workspace_when_deleted_at_null",
),
),
migrations.AddConstraint(
model_name="projectidentifier",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("name", "workspace"),
name="unique_name_workspace_when_deleted_at_null",
),
),
]
10 changes: 9 additions & 1 deletion apiserver/plane/db/models/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models, transaction
from django.utils import timezone
from django.db.models import Q

# Module imports
from plane.utils.html_processor import strip_tags
Expand Down Expand Up @@ -534,7 +535,14 @@ class Label(ProjectBaseModel):
external_id = models.CharField(max_length=255, blank=True, null=True)

class Meta:
unique_together = ["name", "project"]
unique_together = ["name", "project", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=['name', 'project'],
condition=Q(deleted_at__isnull=True),
name='label_unique_name_project_when_deleted_at_null'
)
]
verbose_name = "Label"
verbose_name_plural = "Labels"
db_table = "labels"
Expand Down
10 changes: 9 additions & 1 deletion apiserver/plane/db/models/module.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Django imports
from django.conf import settings
from django.db import models
from django.db.models import Q

# Module imports
from .project import ProjectBaseModel
Expand Down Expand Up @@ -96,7 +97,14 @@ class Module(ProjectBaseModel):
logo_props = models.JSONField(default=dict)

class Meta:
unique_together = ["name", "project"]
unique_together = ["name", "project", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=['name', 'project'],
condition=Q(deleted_at__isnull=True),
name='module_unique_name_project_when_deleted_at_null'
)
]
verbose_name = "Module"
verbose_name_plural = "Modules"
db_table = "modules"
Expand Down
27 changes: 25 additions & 2 deletions apiserver/plane/db/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.conf import settings
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Q

# Modeule imports
from plane.db.mixins import AuditModel
Expand Down Expand Up @@ -124,7 +125,22 @@ def __str__(self):
return f"{self.name} <{self.workspace.name}>"

class Meta:
unique_together = [["identifier", "workspace"], ["name", "workspace"]]
unique_together = [
["identifier", "workspace", "deleted_at"],
["name", "workspace", "deleted_at"],
]
constraints = [
models.UniqueConstraint(
fields=["identifier", "workspace"],
condition=Q(deleted_at__isnull=True),
name="project_unique_identifier_workspace_when_deleted_at_null",
),
models.UniqueConstraint(
fields=["name", "workspace"],
condition=Q(deleted_at__isnull=True),
name="project_unique_name_workspace_when_deleted_at_null",
),
]
verbose_name = "Project"
verbose_name_plural = "Projects"
db_table = "projects"
Expand Down Expand Up @@ -223,7 +239,14 @@ class ProjectIdentifier(AuditModel):
name = models.CharField(max_length=12, db_index=True)

class Meta:
unique_together = ["name", "workspace"]
unique_together = ["name", "workspace", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["name", "workspace"],
condition=Q(deleted_at__isnull=True),
name="unique_name_workspace_when_deleted_at_null",
)
]
verbose_name = "Project Identifier"
verbose_name_plural = "Project Identifiers"
db_table = "project_identifiers"
Expand Down

0 comments on commit daaa04c

Please sign in to comment.