From bb1125320716193da88d6b2d9993257101b75d96 Mon Sep 17 00:00:00 2001 From: Swen Kooij Date: Fri, 3 Feb 2017 10:35:39 +0200 Subject: [PATCH] Moved retry mechanism to mixin --- localized_fields/__init__.py | 6 ++- localized_fields/db_backend/base.py | 4 +- .../fields/localized_uniqueslug_field.py | 12 +++++- localized_fields/mixins.py | 38 +++++++++++++++++++ localized_fields/models.py | 30 --------------- tests/fake_model.py | 4 +- 6 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 localized_fields/mixins.py diff --git a/localized_fields/__init__.py b/localized_fields/__init__.py index d8c8d05..1385f00 100644 --- a/localized_fields/__init__.py +++ b/localized_fields/__init__.py @@ -2,8 +2,9 @@ from .util import get_language_codes from .forms import LocalizedFieldForm, LocalizedFieldWidget from .fields import (LocalizedField, LocalizedBleachField, LocalizedAutoSlugField, LocalizedUniqueSlugField) -from .localized_value import LocalizedValue +from .mixins import AtomicSlugRetryMixin from .models import LocalizedModel +from .localized_value import LocalizedValue __all__ = [ 'get_language_codes', @@ -14,5 +15,6 @@ __all__ = [ 'LocalizedBleachField', 'LocalizedFieldWidget', 'LocalizedFieldForm', - 'LocalizedModel' + 'LocalizedModel', + 'AtomicSlugRetryMixin' ] diff --git a/localized_fields/db_backend/base.py b/localized_fields/db_backend/base.py index b20bf86..afed196 100644 --- a/localized_fields/db_backend/base.py +++ b/localized_fields/db_backend/base.py @@ -34,14 +34,14 @@ def _get_backend_base(): '\'%s\' is not a valid database back-end.' ' The module does not define a DatabaseWrapper class.' ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' - )) + ) % base_class_name) if isinstance(base_class, Psycopg2DatabaseWrapper): raise ImproperlyConfigured(( '\'%s\' is not a valid database back-end.' ' It does inherit from the PostgreSQL back-end.' ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' - )) + ) % base_class_name) return base_class diff --git a/localized_fields/fields/localized_uniqueslug_field.py b/localized_fields/fields/localized_uniqueslug_field.py index 05a8c6d..0506907 100644 --- a/localized_fields/fields/localized_uniqueslug_field.py +++ b/localized_fields/fields/localized_uniqueslug_field.py @@ -1,9 +1,11 @@ from django.conf import settings from django.utils.text import slugify +from django.core.exceptions import ImproperlyConfigured +from ..util import get_language_codes +from ..mixins import AtomicSlugRetryMixin from ..localized_value import LocalizedValue from .localized_autoslug_field import LocalizedAutoSlugField -from ..util import get_language_codes class LocalizedUniqueSlugField(LocalizedAutoSlugField): @@ -17,6 +19,8 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): - Improved performance When in doubt, use this over :see:LocalizedAutoSlugField. + Inherit from :see:AtomicSlugRetryMixin in your model to + make this field work properly. """ def __init__(self, *args, **kwargs): @@ -46,6 +50,12 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): The localized slug that was generated. """ + if not isinstance(instance, AtomicSlugRetryMixin): + raise ImproperlyConfigured(( + 'Model \'%s\' does not inherit from AtomicSlugRetryMixin. ' + 'Without this, the LocalizedUniqueSlugField will not work.' + ) % type(instance).__name__) + slugs = LocalizedValue() for lang_code, _ in settings.LANGUAGES: diff --git a/localized_fields/mixins.py b/localized_fields/mixins.py new file mode 100644 index 0000000..1402715 --- /dev/null +++ b/localized_fields/mixins.py @@ -0,0 +1,38 @@ +from django.db import transaction +from django.conf import settings +from django.db.utils import IntegrityError + + +class AtomicSlugRetryMixin: + """Makes :see:LocalizedUniqueSlugField work by retrying upon + violation of the UNIQUE constraint.""" + + def save(self, *args, **kwargs): + """Saves this model instance to the database.""" + + max_retries = getattr( + settings, + 'LOCALIZED_FIELDS_MAX_RETRIES', + 100 + ) + + if not hasattr(self, 'retries'): + self.retries = 0 + + with transaction.atomic(): + try: + return super().save(*args, **kwargs) + except IntegrityError as ex: + # this is as retarded as it looks, there's no + # way we can put the retry logic inside the slug + # field class... we can also not only catch exceptions + # that apply to slug fields... so yea.. this is as + # retarded as it gets... i am sorry :( + if 'slug' not in str(ex): + raise ex + + if self.retries >= max_retries: + raise ex + + self.retries += 1 + return self.save() diff --git a/localized_fields/models.py b/localized_fields/models.py index 3e8fdcd..fb70e08 100644 --- a/localized_fields/models.py +++ b/localized_fields/models.py @@ -34,33 +34,3 @@ class LocalizedModel(models.Model): value = LocalizedValue() setattr(self, field.name, value) - - def save(self, *args, **kwargs): - """Saves this model instance to the database.""" - - max_retries = getattr( - settings, - 'LOCALIZED_FIELDS_MAX_RETRIES', - 100 - ) - - if not hasattr(self, 'retries'): - self.retries = 0 - - with transaction.atomic(): - try: - return super(LocalizedModel, self).save(*args, **kwargs) - except IntegrityError as ex: - # this is as retarded as it looks, there's no - # way we can put the retry logic inside the slug - # field class... we can also not only catch exceptions - # that apply to slug fields... so yea.. this is as - # retarded as it gets... i am sorry :( - if 'slug' not in str(ex): - raise ex - - if self.retries >= max_retries: - raise ex - - self.retries += 1 - return self.save() diff --git a/tests/fake_model.py b/tests/fake_model.py index 1ae002e..128d77e 100644 --- a/tests/fake_model.py +++ b/tests/fake_model.py @@ -2,7 +2,7 @@ from django.db import connection, migrations from django.db.migrations.executor import MigrationExecutor from django.contrib.postgres.operations import HStoreExtension -from localized_fields import LocalizedModel +from localized_fields import LocalizedModel, AtomicSlugRetryMixin def define_fake_model(name='TestModel', fields=None): @@ -14,7 +14,7 @@ def define_fake_model(name='TestModel', fields=None): if fields: attributes.update(fields) - model = type(name, (LocalizedModel,), attributes) + model = type(name, (AtomicSlugRetryMixin,LocalizedModel,), attributes) return model