diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index a317b943e5..7892a0ef28 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -6,7 +6,7 @@ from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator -from django.db import models +from django.db import models, transaction from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone @@ -182,7 +182,6 @@ class Issue(ProjectBaseModel): ordering = ("-created_at",) def save(self, *args, **kwargs): - # This means that the model isn't saved to the database yet if self.state is None: try: from plane.db.models import State @@ -192,7 +191,6 @@ class Issue(ProjectBaseModel): project=self.project, default=True, ).first() - # if there is no default state assign any random state if default_state is None: random_state = State.objects.filter( ~models.Q(is_triage=True), project=self.project @@ -206,7 +204,6 @@ class Issue(ProjectBaseModel): try: from plane.db.models import State - # Check if the current issue state group is completed or not if self.state.group == "completed": self.completed_at = timezone.now() else: @@ -215,30 +212,44 @@ class Issue(ProjectBaseModel): pass 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: - self.sequence_id = last_id + 1 - else: - self.sequence_id = 1 + with transaction.atomic(): + last_sequence = ( + IssueSequence.objects.filter(project=self.project) + .select_for_update() + .aggregate(largest=models.Max("sequence"))["largest"] + ) + self.sequence_id = last_sequence + 1 if last_sequence else 1 + # Strip the html tags using html parser + self.description_stripped = ( + None + if ( + self.description_html == "" + or self.description_html is None + ) + else strip_tags(self.description_html) + ) + largest_sort_order = Issue.objects.filter( + project=self.project, state=self.state + ).aggregate(largest=models.Max("sort_order"))["largest"] + if largest_sort_order is not None: + self.sort_order = largest_sort_order + 10000 - largest_sort_order = Issue.objects.filter( - project=self.project, state=self.state - ).aggregate(largest=models.Max("sort_order"))["largest"] - if largest_sort_order is not None: - self.sort_order = largest_sort_order + 10000 + super(Issue, self).save(*args, **kwargs) - # Strip the html tags using html parser - self.description_stripped = ( - None - if (self.description_html == "" or self.description_html is None) - else strip_tags(self.description_html) - ) - super(Issue, self).save(*args, **kwargs) + IssueSequence.objects.create( + issue=self, sequence=self.sequence_id, project=self.project + ) + else: + # Strip the html tags using html parser + self.description_stripped = ( + None + if ( + self.description_html == "" + or self.description_html is None + ) + else strip_tags(self.description_html) + ) + super(Issue, self).save(*args, **kwargs) def __str__(self): """Return name of the issue""" @@ -675,14 +686,3 @@ class IssueVote(ProjectBaseModel): def __str__(self): return f"{self.issue.name} {self.actor.email}" - - -# 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, - )