mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-04-25 11:42:54 +03:00
Add a new auto-slug field that is concurrency resistent
This commit is contained in:
parent
1adcd5a399
commit
a48eb12370
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user