mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-10-30 10:38:58 +03:00 
			
		
		
		
	Moved retry mechanism to mixin
This commit is contained in:
		| @@ -2,8 +2,9 @@ from .util import get_language_codes | |||||||
| from .forms import LocalizedFieldForm, LocalizedFieldWidget | from .forms import LocalizedFieldForm, LocalizedFieldWidget | ||||||
| from .fields import (LocalizedField, LocalizedBleachField, | from .fields import (LocalizedField, LocalizedBleachField, | ||||||
|                      LocalizedAutoSlugField, LocalizedUniqueSlugField) |                      LocalizedAutoSlugField, LocalizedUniqueSlugField) | ||||||
| from .localized_value import LocalizedValue | from .mixins import AtomicSlugRetryMixin | ||||||
| from .models import LocalizedModel | from .models import LocalizedModel | ||||||
|  | from .localized_value import LocalizedValue | ||||||
|  |  | ||||||
| __all__ = [ | __all__ = [ | ||||||
|     'get_language_codes', |     'get_language_codes', | ||||||
| @@ -14,5 +15,6 @@ __all__ = [ | |||||||
|     'LocalizedBleachField', |     'LocalizedBleachField', | ||||||
|     'LocalizedFieldWidget', |     'LocalizedFieldWidget', | ||||||
|     'LocalizedFieldForm', |     'LocalizedFieldForm', | ||||||
|     'LocalizedModel' |     'LocalizedModel', | ||||||
|  |     'AtomicSlugRetryMixin' | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -34,14 +34,14 @@ def _get_backend_base(): | |||||||
|             '\'%s\' is not a valid database back-end.' |             '\'%s\' is not a valid database back-end.' | ||||||
|             ' The module does not define a DatabaseWrapper class.' |             ' The module does not define a DatabaseWrapper class.' | ||||||
|             ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' |             ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' | ||||||
|         )) |         ) % base_class_name) | ||||||
|  |  | ||||||
|     if isinstance(base_class, Psycopg2DatabaseWrapper): |     if isinstance(base_class, Psycopg2DatabaseWrapper): | ||||||
|         raise ImproperlyConfigured(( |         raise ImproperlyConfigured(( | ||||||
|             '\'%s\' is not a valid database back-end.' |             '\'%s\' is not a valid database back-end.' | ||||||
|             ' It does inherit from the PostgreSQL back-end.' |             ' It does inherit from the PostgreSQL back-end.' | ||||||
|             ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' |             ' Check the value of LOCALIZED_FIELDS_DB_BACKEND_BASE.' | ||||||
|         )) |         ) % base_class_name) | ||||||
|  |  | ||||||
|     return base_class |     return base_class | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.utils.text import slugify | 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_value import LocalizedValue | ||||||
| from .localized_autoslug_field import LocalizedAutoSlugField | from .localized_autoslug_field import LocalizedAutoSlugField | ||||||
| from ..util import get_language_codes |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LocalizedUniqueSlugField(LocalizedAutoSlugField): | class LocalizedUniqueSlugField(LocalizedAutoSlugField): | ||||||
| @@ -17,6 +19,8 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): | |||||||
|         - Improved performance |         - Improved performance | ||||||
|  |  | ||||||
|     When in doubt, use this over :see:LocalizedAutoSlugField. |     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): |     def __init__(self, *args, **kwargs): | ||||||
| @@ -46,6 +50,12 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): | |||||||
|             The localized slug that was generated. |             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() |         slugs = LocalizedValue() | ||||||
|  |  | ||||||
|         for lang_code, _ in settings.LANGUAGES: |         for lang_code, _ in settings.LANGUAGES: | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								localized_fields/mixins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								localized_fields/mixins.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||||
| @@ -34,33 +34,3 @@ class LocalizedModel(models.Model): | |||||||
|                     value = LocalizedValue() |                     value = LocalizedValue() | ||||||
|  |  | ||||||
|             setattr(self, field.name, value) |             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() |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ from django.db import connection, migrations | |||||||
| from django.db.migrations.executor import MigrationExecutor | from django.db.migrations.executor import MigrationExecutor | ||||||
| from django.contrib.postgres.operations import HStoreExtension | 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): | def define_fake_model(name='TestModel', fields=None): | ||||||
| @@ -14,7 +14,7 @@ def define_fake_model(name='TestModel', fields=None): | |||||||
|  |  | ||||||
|     if fields: |     if fields: | ||||||
|         attributes.update(fields) |         attributes.update(fields) | ||||||
|     model = type(name, (LocalizedModel,), attributes) |     model = type(name, (AtomicSlugRetryMixin,LocalizedModel,), attributes) | ||||||
|  |  | ||||||
|     return model |     return model | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user