mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-10-30 10:38:58 +03:00 
			
		
		
		
	Add a new auto-slug field that is concurrency resistent
This commit is contained in:
		| @@ -5,6 +5,8 @@ from django.core.exceptions import ImproperlyConfigured | |||||||
| from django.db.backends.postgresql.base import \ | from django.db.backends.postgresql.base import \ | ||||||
|     DatabaseWrapper as Psycopg2DatabaseWrapper |     DatabaseWrapper as Psycopg2DatabaseWrapper | ||||||
|  |  | ||||||
|  | from ..fields import LocalizedField | ||||||
|  |  | ||||||
|  |  | ||||||
| def _get_backend_base(): | def _get_backend_base(): | ||||||
|     """Gets the base class for the custom database back-end. |     """Gets the base class for the custom database back-end. | ||||||
| @@ -153,7 +155,22 @@ class SchemaEditor(_get_schema_editor_base()): | |||||||
|             *args, **kwargs |             *args, **kwargs | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         self._update_hstore_constraints(model, old_field, new_field) |         is_old_field_localized = isinstance(old_field, LocalizedField) | ||||||
|  |         is_new_field_localized = isinstance(new_field, LocalizedField) | ||||||
|  |  | ||||||
|  |         if is_old_field_localized or is_new_field_localized: | ||||||
|  |             self._update_hstore_constraints(model, old_field, new_field) | ||||||
|  |  | ||||||
|  |     def create_model(self, model): | ||||||
|  |         """Ran when a new model is created.""" | ||||||
|  |  | ||||||
|  |         super().create_model(model) | ||||||
|  |  | ||||||
|  |         for field in model._meta.local_fields: | ||||||
|  |             if not isinstance(field, LocalizedField): | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             self._update_hstore_constraints(model, field, field) | ||||||
|  |  | ||||||
|  |  | ||||||
| class DatabaseWrapper(_get_backend_base()): | class DatabaseWrapper(_get_backend_base()): | ||||||
|   | |||||||
| @@ -59,6 +59,9 @@ class LocalizedMagicSlugField(LocalizedAutoSlugField): | |||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             slug = slugify(value, allow_unicode=True) |             slug = slugify(value, allow_unicode=True) | ||||||
|  |             if instance.retries > 0: | ||||||
|  |                 slug += '-%d' % instance.retries | ||||||
|  |  | ||||||
|             slugs.set(lang_code, slug) |             slugs.set(lang_code, slug) | ||||||
|  |  | ||||||
|         setattr(instance, self.name, slugs) |         setattr(instance, self.name, slugs) | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| from django.db import models | from django.db import models | ||||||
|  | from django.db.utils import IntegrityError | ||||||
|  | from django.db import transaction | ||||||
|  |  | ||||||
| from .fields import LocalizedField | from .fields import LocalizedField | ||||||
| from .localized_value import LocalizedValue | from .localized_value import LocalizedValue | ||||||
| @@ -32,3 +34,30 @@ 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.""" | ||||||
|  |  | ||||||
|  |         if not hasattr(self, 'retries'): | ||||||
|  |             self.retries = 0 | ||||||
|  |  | ||||||
|  |         error = None | ||||||
|  |         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 | ||||||
|  |  | ||||||
|  |                 error = ex | ||||||
|  |  | ||||||
|  |         if self.retries >= 100: | ||||||
|  |             raise error | ||||||
|  |  | ||||||
|  |         self.retries += 1 | ||||||
|  |         return self.save() | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import dj_database_url | |||||||
| DEBUG = True | DEBUG = True | ||||||
| TEMPLATE_DEBUG = True | TEMPLATE_DEBUG = True | ||||||
|  |  | ||||||
| SECRET_KEY = 'this is my secret key' | SECRET_KEY = 'this is my secret key'  # NOQA | ||||||
|  |  | ||||||
| TEST_RUNNER = 'django.test.runner.DiscoverRunner' | TEST_RUNNER = 'django.test.runner.DiscoverRunner' | ||||||
|  |  | ||||||
| @@ -11,6 +11,8 @@ DATABASES = { | |||||||
|     'default': dj_database_url.config(default='postgres:///localized_fields') |     'default': dj_database_url.config(default='postgres:///localized_fields') | ||||||
| } | } | ||||||
|  |  | ||||||
|  | DATABASES['default']['ENGINE'] = 'localized_fields.db_backend' | ||||||
|  |  | ||||||
| LANGUAGE_CODE = 'en' | LANGUAGE_CODE = 'en' | ||||||
| LANGUAGES = ( | LANGUAGES = ( | ||||||
|     ('en', 'English'), |     ('en', 'English'), | ||||||
|   | |||||||
| @@ -1,10 +1,7 @@ | |||||||
| from django.contrib.postgres.operations import HStoreExtension |  | ||||||
| from django.db import connection, migrations | from django.db import connection, migrations | ||||||
|  | from localized_fields import LocalizedModel | ||||||
| from django.db.migrations.executor import MigrationExecutor | from django.db.migrations.executor import MigrationExecutor | ||||||
| import sys | from django.contrib.postgres.operations import HStoreExtension | ||||||
|  |  | ||||||
| from localized_fields import (LocalizedAutoSlugField, LocalizedField, |  | ||||||
|                               LocalizedModel, LocalizedMagicSlugField) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_fake_model(name='TestModel', fields={}): | def get_fake_model(name='TestModel', fields={}): | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| from django import forms | from django import forms | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
|  | from localized_fields import (LocalizedField, LocalizedAutoSlugField, | ||||||
|  |                               LocalizedMagicSlugField) | ||||||
| from django.utils.text import slugify | from django.utils.text import slugify | ||||||
|  |  | ||||||
| from localized_fields import LocalizedField, LocalizedAutoSlugField, LocalizedMagicSlugField |  | ||||||
|  |  | ||||||
| from .fake_model import get_fake_model | from .fake_model import get_fake_model | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -71,7 +71,6 @@ class LocalizedSlugFieldTestCase(TestCase): | |||||||
|     @classmethod |     @classmethod | ||||||
|     def test_deconstruct_auto(cls): |     def test_deconstruct_auto(cls): | ||||||
|         cls._test_deconstruct(LocalizedAutoSlugField) |         cls._test_deconstruct(LocalizedAutoSlugField) | ||||||
|         cls._test_deconstruct(LocalizedMagicSlugField) |  | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def test_deconstruct_magic(cls): |     def test_deconstruct_magic(cls): | ||||||
| @@ -93,7 +92,7 @@ class LocalizedSlugFieldTestCase(TestCase): | |||||||
|         obj.title.en = 'this is my title' |         obj.title.en = 'this is my title' | ||||||
|         obj.save() |         obj.save() | ||||||
|  |  | ||||||
|         assert obj.slug.get('en') == slugify(obj.title.en) |         assert obj.slug.get('en') == slugify(obj.title) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _test_populate_multiple_languages(model): |     def _test_populate_multiple_languages(model): | ||||||
| @@ -117,11 +116,12 @@ class LocalizedSlugFieldTestCase(TestCase): | |||||||
|         obj.title.en = 'title' |         obj.title.en = 'title' | ||||||
|         obj.save() |         obj.save() | ||||||
|  |  | ||||||
|         another_obj = model() |         for i in range(1, 90): | ||||||
|         another_obj.title.en = 'title' |             another_obj = model() | ||||||
|         another_obj.save() |             another_obj.title.en = 'title' | ||||||
|  |             another_obj.save() | ||||||
|  |  | ||||||
|         assert another_obj.slug.en == 'title-1' |             assert another_obj.slug.en == 'title-%d' % i | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _test_unique_slug_utf(model): |     def _test_unique_slug_utf(model): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user