Fixing slug duplications on race conditions
parent
5eb6bfe1e0
commit
3e9bfd7523
|
@ -18,6 +18,7 @@
|
|||
|
||||
from django_pglocks import advisory_lock
|
||||
|
||||
|
||||
def detail_route(methods=['get'], **kwargs):
|
||||
"""
|
||||
Used to mark a method on a ViewSet that should be routed for detail requests.
|
||||
|
@ -51,12 +52,11 @@ def model_pk_lock(func):
|
|||
"""
|
||||
def decorator(self, *args, **kwargs):
|
||||
from taiga.base.utils.db import get_typename_for_model_class
|
||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||
pk = self.kwargs.get(self.pk_url_kwarg, None)
|
||||
tn = get_typename_for_model_class(self.get_queryset().model)
|
||||
key = "{0}:{1}".format(tn, pk)
|
||||
|
||||
with advisory_lock(key) as acquired_key_lock:
|
||||
with advisory_lock(key):
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
return decorator
|
||||
|
|
|
@ -26,6 +26,8 @@ from django.http import Http404
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.utils import timezone
|
||||
|
||||
from django_pglocks import advisory_lock
|
||||
|
||||
from taiga.base import filters
|
||||
from taiga.base import exceptions as exc
|
||||
from taiga.base import response
|
||||
|
@ -214,6 +216,7 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
|||
if not template_description:
|
||||
raise response.BadRequest(_("Not valid template description"))
|
||||
|
||||
with advisory_lock("create-project-template") as acquired_key_lock:
|
||||
template_slug = slugify_uniquely(template_name, models.ProjectTemplate)
|
||||
|
||||
project = self.get_object()
|
||||
|
@ -227,6 +230,7 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin,
|
|||
)
|
||||
|
||||
template.load_data_from_project(project)
|
||||
|
||||
template.save()
|
||||
return response.Created(serializers.ProjectTemplateSerializer(template).data)
|
||||
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.apps import apps
|
||||
from django.db import models
|
||||
from django.db.models import Prefetch, Count
|
||||
from django.db.models import Count
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
@ -28,7 +27,7 @@ from django.utils.functional import cached_property
|
|||
from taiga.base.utils.slug import slugify_uniquely
|
||||
from taiga.base.utils.dicts import dict_sum
|
||||
from taiga.projects.notifications.mixins import WatchedModelMixin
|
||||
from taiga.projects.userstories.models import UserStory
|
||||
from django_pglocks import advisory_lock
|
||||
|
||||
import itertools
|
||||
import datetime
|
||||
|
@ -84,8 +83,10 @@ class Milestone(WatchedModelMixin, models.Model):
|
|||
if not self._importing or not self.modified_date:
|
||||
self.modified_date = timezone.now()
|
||||
if not self.slug:
|
||||
with advisory_lock("milestone-creation-{}".format(self.project_id)):
|
||||
self.slug = slugify_uniquely(self.name, self.__class__)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
else:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
|
|
|
@ -16,27 +16,23 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import itertools
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import signals, Q
|
||||
from django.db.models import Q
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from django_pglocks import advisory_lock
|
||||
|
||||
from django_pgjson.fields import JsonField
|
||||
|
||||
from taiga.projects.tagging.models import TaggedMixin
|
||||
from taiga.projects.tagging.models import TagsColorsdMixin
|
||||
from taiga.base.utils.dicts import dict_sum
|
||||
from taiga.base.utils.files import get_file_path
|
||||
from taiga.base.utils.sequence import arithmetic_progression
|
||||
from taiga.base.utils.slug import slugify_uniquely
|
||||
|
@ -270,16 +266,6 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model):
|
|||
if not self._importing or not self.modified_date:
|
||||
self.modified_date = timezone.now()
|
||||
|
||||
if not self.slug:
|
||||
base_name = "{}-{}".format(self.owner.username, self.name)
|
||||
base_slug = slugify_uniquely(base_name, self.__class__)
|
||||
slug = base_slug
|
||||
for i in arithmetic_progression():
|
||||
if not type(self).objects.filter(slug=slug).exists() or i > 100:
|
||||
break
|
||||
slug = "{}-{}".format(base_slug, i)
|
||||
self.slug = slug
|
||||
|
||||
if not self.is_backlog_activated:
|
||||
self.total_milestones = None
|
||||
self.total_story_points = None
|
||||
|
@ -290,12 +276,24 @@ class Project(ProjectDefaults, TaggedMixin, TagsColorsdMixin, models.Model):
|
|||
if not self.is_looking_for_people:
|
||||
self.looking_for_people_note = ""
|
||||
|
||||
if self.anon_permissions == None:
|
||||
if self.anon_permissions is None:
|
||||
self.anon_permissions = []
|
||||
|
||||
if self.public_permissions == None:
|
||||
if self.public_permissions is None:
|
||||
self.public_permissions = []
|
||||
|
||||
if not self.slug:
|
||||
with advisory_lock("project-creation"):
|
||||
base_name = "{}-{}".format(self.owner.username, self.name)
|
||||
base_slug = slugify_uniquely(base_name, self.__class__)
|
||||
slug = base_slug
|
||||
for i in arithmetic_progression():
|
||||
if not type(self).objects.filter(slug=slug).exists() or i > 100:
|
||||
break
|
||||
slug = "{}-{}".format(base_slug, i)
|
||||
self.slug = slug
|
||||
super().save(*args, **kwargs)
|
||||
else:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def refresh_totals(self, save=True):
|
||||
|
|
|
@ -21,6 +21,8 @@ from django.contrib.contenttypes.fields import GenericRelation
|
|||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django_pglocks import advisory_lock
|
||||
|
||||
from taiga.base.utils.slug import slugify_uniquely_for_queryset
|
||||
from taiga.projects.notifications.mixins import WatchedModelMixin
|
||||
from taiga.projects.occ import OCCModelMixin
|
||||
|
@ -84,7 +86,9 @@ class WikiLink(models.Model):
|
|||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.href:
|
||||
with advisory_lock("wiki-page-creation-{}".format(self.project_id)):
|
||||
wl_qs = self.project.wiki_links.all()
|
||||
self.href = slugify_uniquely_for_queryset(self.title, wl_qs, slugfield="href")
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
else:
|
||||
super().save(*args, **kwargs)
|
||||
|
|
|
@ -35,6 +35,7 @@ from django.utils import timezone
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django_pgjson.fields import JsonField
|
||||
from django_pglocks import advisory_lock
|
||||
|
||||
from taiga.auth.tokens import get_token_for_user
|
||||
from taiga.base.utils.slug import slugify_uniquely
|
||||
|
@ -265,6 +266,7 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||
super().save(*args, **kwargs)
|
||||
|
||||
def cancel(self):
|
||||
with advisory_lock("delete-user"):
|
||||
self.username = slugify_uniquely("deleted-user", User, slugfield="username")
|
||||
self.email = "{}@taiga.io".format(self.username)
|
||||
self.is_active = False
|
||||
|
|
Loading…
Reference in New Issue