Skip to content

Commit bb11253

Browse files
committed
Moved retry mechanism to mixin
1 parent 5db8776 commit bb11253

File tree

6 files changed

+57
-37
lines changed

6 files changed

+57
-37
lines changed

localized_fields/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
from .forms import LocalizedFieldForm, LocalizedFieldWidget
33
from .fields import (LocalizedField, LocalizedBleachField,
44
LocalizedAutoSlugField, LocalizedUniqueSlugField)
5-
from .localized_value import LocalizedValue
5+
from .mixins import AtomicSlugRetryMixin
66
from .models import LocalizedModel
7+
from .localized_value import LocalizedValue
78

89
__all__ = [
910
'get_language_codes',
@@ -14,5 +15,6 @@
1415
'LocalizedBleachField',
1516
'LocalizedFieldWidget',
1617
'LocalizedFieldForm',
17-
'LocalizedModel'
18+
'LocalizedModel',
19+
'AtomicSlugRetryMixin'
1820
]

localized_fields/db_backend/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ def _get_backend_base():
3434
'\'%s\' is not a valid database back-end.'
3535
' The module does not define a DatabaseWrapper class.'
3636
' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.'
37-
))
37+
) % base_class_name)
3838

3939
if isinstance(base_class, Psycopg2DatabaseWrapper):
4040
raise ImproperlyConfigured((
4141
'\'%s\' is not a valid database back-end.'
4242
' It does inherit from the PostgreSQL back-end.'
4343
' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.'
44-
))
44+
) % base_class_name)
4545

4646
return base_class
4747

localized_fields/fields/localized_uniqueslug_field.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from django.conf import settings
22
from django.utils.text import slugify
3+
from django.core.exceptions import ImproperlyConfigured
34

5+
from ..util import get_language_codes
6+
from ..mixins import AtomicSlugRetryMixin
47
from ..localized_value import LocalizedValue
58
from .localized_autoslug_field import LocalizedAutoSlugField
6-
from ..util import get_language_codes
79

810

911
class LocalizedUniqueSlugField(LocalizedAutoSlugField):
@@ -17,6 +19,8 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField):
1719
- Improved performance
1820
1921
When in doubt, use this over :see:LocalizedAutoSlugField.
22+
Inherit from :see:AtomicSlugRetryMixin in your model to
23+
make this field work properly.
2024
"""
2125

2226
def __init__(self, *args, **kwargs):
@@ -46,6 +50,12 @@ def pre_save(self, instance, add: bool):
4650
The localized slug that was generated.
4751
"""
4852

53+
if not isinstance(instance, AtomicSlugRetryMixin):
54+
raise ImproperlyConfigured((
55+
'Model \'%s\' does not inherit from AtomicSlugRetryMixin. '
56+
'Without this, the LocalizedUniqueSlugField will not work.'
57+
) % type(instance).__name__)
58+
4959
slugs = LocalizedValue()
5060

5161
for lang_code, _ in settings.LANGUAGES:

localized_fields/mixins.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from django.db import transaction
2+
from django.conf import settings
3+
from django.db.utils import IntegrityError
4+
5+
6+
class AtomicSlugRetryMixin:
7+
"""Makes :see:LocalizedUniqueSlugField work by retrying upon
8+
violation of the UNIQUE constraint."""
9+
10+
def save(self, *args, **kwargs):
11+
"""Saves this model instance to the database."""
12+
13+
max_retries = getattr(
14+
settings,
15+
'LOCALIZED_FIELDS_MAX_RETRIES',
16+
100
17+
)
18+
19+
if not hasattr(self, 'retries'):
20+
self.retries = 0
21+
22+
with transaction.atomic():
23+
try:
24+
return super().save(*args, **kwargs)
25+
except IntegrityError as ex:
26+
# this is as retarded as it looks, there's no
27+
# way we can put the retry logic inside the slug
28+
# field class... we can also not only catch exceptions
29+
# that apply to slug fields... so yea.. this is as
30+
# retarded as it gets... i am sorry :(
31+
if 'slug' not in str(ex):
32+
raise ex
33+
34+
if self.retries >= max_retries:
35+
raise ex
36+
37+
self.retries += 1
38+
return self.save()

localized_fields/models.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,3 @@ def __init__(self, *args, **kwargs):
3434
value = LocalizedValue()
3535

3636
setattr(self, field.name, value)
37-
38-
def save(self, *args, **kwargs):
39-
"""Saves this model instance to the database."""
40-
41-
max_retries = getattr(
42-
settings,
43-
'LOCALIZED_FIELDS_MAX_RETRIES',
44-
100
45-
)
46-
47-
if not hasattr(self, 'retries'):
48-
self.retries = 0
49-
50-
with transaction.atomic():
51-
try:
52-
return super(LocalizedModel, self).save(*args, **kwargs)
53-
except IntegrityError as ex:
54-
# this is as retarded as it looks, there's no
55-
# way we can put the retry logic inside the slug
56-
# field class... we can also not only catch exceptions
57-
# that apply to slug fields... so yea.. this is as
58-
# retarded as it gets... i am sorry :(
59-
if 'slug' not in str(ex):
60-
raise ex
61-
62-
if self.retries >= max_retries:
63-
raise ex
64-
65-
self.retries += 1
66-
return self.save()

tests/fake_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from django.db.migrations.executor import MigrationExecutor
33
from django.contrib.postgres.operations import HStoreExtension
44

5-
from localized_fields import LocalizedModel
5+
from localized_fields import LocalizedModel, AtomicSlugRetryMixin
66

77

88
def define_fake_model(name='TestModel', fields=None):
@@ -14,7 +14,7 @@ def define_fake_model(name='TestModel', fields=None):
1414

1515
if fields:
1616
attributes.update(fields)
17-
model = type(name, (LocalizedModel,), attributes)
17+
model = type(name, (AtomicSlugRetryMixin,LocalizedModel,), attributes)
1818

1919
return model
2020

0 commit comments

Comments
 (0)