mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-10-20 06:48:56 +03:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
97c14fd2ba | ||
|
6cb4cdf52e | ||
|
bcb2ff0092 | ||
|
43a48403e9 | ||
|
f453c44a73 | ||
|
3cb8b04195 | ||
|
3f8fc77c4d | ||
|
9245c85e5d | ||
|
818a0a2fe3 | ||
|
c206005cae | ||
|
3850c34374 | ||
|
99c8830f10 | ||
|
d7bd217a90 |
@@ -1,6 +1,3 @@
|
|||||||
[MASTER]
|
|
||||||
load-plugins=pylint_common, pylint_django
|
|
||||||
|
|
||||||
[FORMAT]
|
[FORMAT]
|
||||||
max-line-length=120
|
max-line-length=120
|
||||||
|
|
||||||
|
31
.scrutinizer.yml
Normal file
31
.scrutinizer.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
checks:
|
||||||
|
python:
|
||||||
|
code_rating: true
|
||||||
|
duplicate_code: true
|
||||||
|
tools:
|
||||||
|
pylint:
|
||||||
|
python_version: '3'
|
||||||
|
config_file: .pylintrc
|
||||||
|
filter:
|
||||||
|
excluded_paths:
|
||||||
|
- '*/tests/*'
|
||||||
|
- '*/migrations/*'
|
||||||
|
build:
|
||||||
|
environment:
|
||||||
|
python: '3.5.0'
|
||||||
|
node: 'v6.2.0'
|
||||||
|
variables:
|
||||||
|
DJANGO_SETTINGS_MODULES: settings
|
||||||
|
DATABASE_URL: postgres://scrutinizer:scrutinizer@localhost:5434/localized_fields
|
||||||
|
postgresql: true
|
||||||
|
redis: true
|
||||||
|
dependencies:
|
||||||
|
override:
|
||||||
|
- 'pip install -r requirements/test.txt'
|
||||||
|
tests:
|
||||||
|
override:
|
||||||
|
-
|
||||||
|
command: coverage run manage.py test
|
||||||
|
coverage:
|
||||||
|
file: '.coverage'
|
||||||
|
format: 'py-cc'
|
40
README.rst
40
README.rst
@@ -7,24 +7,14 @@ django-localized-fields
|
|||||||
.. image:: https://scrutinizer-ci.com/g/SectorLabs/django-localized-fields/badges/coverage.png
|
.. image:: https://scrutinizer-ci.com/g/SectorLabs/django-localized-fields/badges/coverage.png
|
||||||
:target: https://scrutinizer-ci.com/g/SectorLabs/django-localized-fields/
|
:target: https://scrutinizer-ci.com/g/SectorLabs/django-localized-fields/
|
||||||
|
|
||||||
.. image:: https://travis-ci.com/SectorLabs/django-localized-fields.svg?token=sFgxhDFpypxkMcvhRoSz&branch=master
|
.. image:: https://img.shields.io/github/license/SectorLabs/django-localized-fields.svg
|
||||||
:target: https://travis-ci.com/SectorLabs/django-localized-fields
|
|
||||||
|
|
||||||
.. image:: https://badge.fury.io/py/django-localized-fields.svg
|
.. image:: https://badge.fury.io/py/django-localized-fields.svg
|
||||||
:target: https://pypi.python.org/pypi/django-localized-fields
|
:target: https://pypi.python.org/pypi/django-localized-fields
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/license/SectorLabs/django-localized-fields.svg
|
|
||||||
|
|
||||||
``django-localized-fields`` is an implementation of a field class for Django models that allows the field's value to be set in multiple languages. It does this by utilizing the ``hstore`` type (PostgreSQL specific), which is available as ``models.HStoreField`` in Django 1.10.
|
``django-localized-fields`` is an implementation of a field class for Django models that allows the field's value to be set in multiple languages. It does this by utilizing the ``hstore`` type (PostgreSQL specific), which is available as ``models.HStoreField`` in Django 1.10.
|
||||||
|
|
||||||
This package requires Python 3.5 or newer and Django 1.10 or newer.
|
This package requires Python 3.5 or newer, Django 1.10 or newer and PostgreSQL 9.6 or newer.
|
||||||
|
|
||||||
In the pipeline
|
|
||||||
---------------
|
|
||||||
We're working on making this easier to setup and use. Any feedback is apreciated. Here's a short list of things we're working to improve:
|
|
||||||
|
|
||||||
* Make it unnecesarry to add anything to your `INSTALLED_APPS`.
|
|
||||||
* Move generic PostgreSQL code to a separate package.
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
@@ -45,14 +35,14 @@ Installation
|
|||||||
'localized_fields'
|
'localized_fields'
|
||||||
]
|
]
|
||||||
|
|
||||||
3. Set the database engine to ``localized_fields.db_backend``:
|
3. Set the database engine to ``psqlextra.backend``:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
...
|
...
|
||||||
'ENGINE': 'localized_fields.db_backend'
|
'ENGINE': 'psqlextra.backend'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,34 +247,26 @@ Besides ``LocalizedField``, there's also:
|
|||||||
Frequently asked questions (FAQ)
|
Frequently asked questions (FAQ)
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
1. Why do I need to change the database back-end/engine?
|
1. Does this package work with Python 2?
|
||||||
|
|
||||||
We utilize PostgreSQL's `hstore` data type, which allows you to store key-value pairs in a column. In order to create `UNIQUE` constraints on specific key, we need to create a special type of index. We could do this without a custom database back-end, but it would require everyone to manually write their migrations. By using a custom database back-end, we added support for this. When changing the `uniqueness` constraint on a `LocalizedField`, our custom database back-end takes care of creating, updating and deleting these constraints/indexes in the database.
|
|
||||||
|
|
||||||
2. I am already using a custom database back-end, can I still use yours?
|
|
||||||
|
|
||||||
Yes. You can set the ``LOCALIZED_FIELDS_DB_BACKEND_BASE`` setting to your current back-end. This will instruct our custom database back-end to inherit from the database back-end you specified. **Warning**: this will only work if the base you specified indirectly inherits from the standard PostgreSQL database back-end.
|
|
||||||
|
|
||||||
3. Does this package work with Python 2?
|
|
||||||
|
|
||||||
No. Only Python 3.5 or newer is supported. We're using type hints. These do not work well under older versions of Python.
|
No. Only Python 3.5 or newer is supported. We're using type hints. These do not work well under older versions of Python.
|
||||||
|
|
||||||
4. Does this package work with Django 1.X?
|
2. Does this package work with Django 1.X?
|
||||||
|
|
||||||
No. Only Django 1.10 or newer is supported. This is because we rely on Django's ``HStoreField``.
|
No. Only Django 1.10 or newer is supported. This is because we rely on Django's ``HStoreField``.
|
||||||
|
|
||||||
5. Does this package come with support for Django Admin?
|
3. Does this package come with support for Django Admin?
|
||||||
|
|
||||||
Yes. Our custom fields come with a special form that will automatically be used in Django Admin if the field is of ``LocalizedField``.
|
Yes. Our custom fields come with a special form that will automatically be used in Django Admin if the field is of ``LocalizedField``.
|
||||||
|
|
||||||
7. Why should I pick this over any of the other translation packages out there?
|
4. Why should I pick this over any of the other translation packages out there?
|
||||||
|
|
||||||
You should pick whatever you feel comfortable with. This package stores translations in your database without having to have translation tables. It however only works on PostgreSQL.
|
You should pick whatever you feel comfortable with. This package stores translations in your database without having to have translation tables. It however only works on PostgreSQL.
|
||||||
|
|
||||||
8. I am using PostgreSQL <8.4, can I use this?
|
5. I am using PostgreSQL <9.6, can I use this?
|
||||||
|
|
||||||
No. The ``hstore`` data type was introduced in PostgreSQL 8.4.
|
No. The ``hstore`` data type was introduced in PostgreSQL 9.6.
|
||||||
|
|
||||||
9. I am using this package. Can I give you some beer?
|
6. I am using this package. Can I give you some beer?
|
||||||
|
|
||||||
Yes! If you're ever in the area of Cluj-Napoca, Romania, swing by :)
|
Yes! If you're ever in the area of Cluj-Napoca, Romania, swing by :)
|
||||||
|
@@ -1,224 +0,0 @@
|
|||||||
import importlib
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.db.backends.postgresql.base import \
|
|
||||||
DatabaseWrapper as Psycopg2DatabaseWrapper
|
|
||||||
|
|
||||||
from ..fields import LocalizedField
|
|
||||||
|
|
||||||
|
|
||||||
def _get_backend_base():
|
|
||||||
"""Gets the base class for the custom database back-end.
|
|
||||||
|
|
||||||
This should be the Django PostgreSQL back-end. However,
|
|
||||||
some people are already using a custom back-end from
|
|
||||||
another package. We are nice people and expose an option
|
|
||||||
that allows them to configure the back-end we base upon.
|
|
||||||
|
|
||||||
As long as the specified base eventually also has
|
|
||||||
the PostgreSQL back-end as a base, then everything should
|
|
||||||
work as intended.
|
|
||||||
"""
|
|
||||||
base_class_name = getattr(
|
|
||||||
settings,
|
|
||||||
'LOCALIZED_FIELDS_DB_BACKEND_BASE',
|
|
||||||
'django.db.backends.postgresql'
|
|
||||||
)
|
|
||||||
|
|
||||||
base_class_module = importlib.import_module(base_class_name + '.base')
|
|
||||||
base_class = getattr(base_class_module, 'DatabaseWrapper', None)
|
|
||||||
|
|
||||||
if not base_class:
|
|
||||||
raise ImproperlyConfigured((
|
|
||||||
'\'%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
|
|
||||||
|
|
||||||
|
|
||||||
def _get_schema_editor_base():
|
|
||||||
"""Gets the base class for the schema editor.
|
|
||||||
|
|
||||||
We have to use the configured base back-end's
|
|
||||||
schema editor for this."""
|
|
||||||
return _get_backend_base().SchemaEditorClass
|
|
||||||
|
|
||||||
|
|
||||||
class SchemaEditor(_get_schema_editor_base()):
|
|
||||||
"""Custom schema editor for hstore indexes.
|
|
||||||
|
|
||||||
This allows us to put UNIQUE constraints for
|
|
||||||
localized fields."""
|
|
||||||
|
|
||||||
sql_hstore_unique_create = "CREATE UNIQUE INDEX {name} ON {table}{using} ({columns}){extra}"
|
|
||||||
sql_hstore_unique_drop = "DROP INDEX IF EXISTS {name}"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _hstore_unique_name(model, field, keys):
|
|
||||||
"""Gets the name for a UNIQUE INDEX that applies
|
|
||||||
to one or more keys in a hstore field.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
model:
|
|
||||||
The model the field is a part of.
|
|
||||||
|
|
||||||
field:
|
|
||||||
The hstore field to create a
|
|
||||||
UNIQUE INDEX for.
|
|
||||||
|
|
||||||
key:
|
|
||||||
The name of the hstore key
|
|
||||||
to create the name for.
|
|
||||||
|
|
||||||
This can also be a tuple
|
|
||||||
of multiple names.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The name for the UNIQUE index.
|
|
||||||
"""
|
|
||||||
postfix = '_'.join(keys)
|
|
||||||
return '{table_name}_{field_name}_unique_{postfix}'.format(
|
|
||||||
table_name=model._meta.db_table,
|
|
||||||
field_name=field.column,
|
|
||||||
postfix=postfix
|
|
||||||
)
|
|
||||||
|
|
||||||
def _drop_hstore_unique(self, model, field, keys):
|
|
||||||
"""Drops a UNIQUE constraint for the specified hstore keys."""
|
|
||||||
|
|
||||||
name = self._hstore_unique_name(model, field, keys)
|
|
||||||
sql = self.sql_hstore_unique_drop.format(name=name)
|
|
||||||
self.execute(sql)
|
|
||||||
|
|
||||||
def _create_hstore_unique(self, model, field, keys):
|
|
||||||
"""Creates a UNIQUE constraint for the specified hstore keys."""
|
|
||||||
|
|
||||||
name = self._hstore_unique_name(model, field, keys)
|
|
||||||
columns = [
|
|
||||||
'(%s->\'%s\')' % (field.column, key)
|
|
||||||
for key in keys
|
|
||||||
]
|
|
||||||
sql = self.sql_hstore_unique_create.format(
|
|
||||||
name=name,
|
|
||||||
table=model._meta.db_table,
|
|
||||||
using='',
|
|
||||||
columns=','.join(columns),
|
|
||||||
extra=''
|
|
||||||
)
|
|
||||||
self.execute(sql)
|
|
||||||
|
|
||||||
def _apply_hstore_constraints(self, method, model, field):
|
|
||||||
"""Creates/drops UNIQUE constraints for a field."""
|
|
||||||
|
|
||||||
def _compose_keys(constraint):
|
|
||||||
if isinstance(constraint, str):
|
|
||||||
return [constraint]
|
|
||||||
|
|
||||||
return constraint
|
|
||||||
|
|
||||||
uniqueness = getattr(field, 'uniqueness', None)
|
|
||||||
if not uniqueness:
|
|
||||||
return
|
|
||||||
|
|
||||||
for keys in uniqueness:
|
|
||||||
method(
|
|
||||||
model,
|
|
||||||
field,
|
|
||||||
_compose_keys(keys)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _update_hstore_constraints(self, model, old_field, new_field):
|
|
||||||
"""Updates the UNIQUE constraints for the specified field."""
|
|
||||||
|
|
||||||
old_uniqueness = getattr(old_field, 'uniqueness', None)
|
|
||||||
new_uniqueness = getattr(new_field, 'uniqueness', None)
|
|
||||||
|
|
||||||
# drop any old uniqueness constraints
|
|
||||||
if old_uniqueness:
|
|
||||||
self._apply_hstore_constraints(
|
|
||||||
self._drop_hstore_unique,
|
|
||||||
model,
|
|
||||||
old_field
|
|
||||||
)
|
|
||||||
|
|
||||||
# (re-)create uniqueness constraints
|
|
||||||
if new_uniqueness:
|
|
||||||
self._apply_hstore_constraints(
|
|
||||||
self._create_hstore_unique,
|
|
||||||
model,
|
|
||||||
new_field
|
|
||||||
)
|
|
||||||
|
|
||||||
def _alter_field(self, model, old_field, new_field, *args, **kwargs):
|
|
||||||
"""Ran when the configuration on a field changed."""
|
|
||||||
|
|
||||||
super()._alter_field(
|
|
||||||
model, old_field, new_field,
|
|
||||||
*args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
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._apply_hstore_constraints(
|
|
||||||
self._create_hstore_unique,
|
|
||||||
model,
|
|
||||||
field
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete_model(self, model):
|
|
||||||
"""Ran when a model is being deleted."""
|
|
||||||
|
|
||||||
super().delete_model(model)
|
|
||||||
|
|
||||||
for field in model._meta.local_fields:
|
|
||||||
if not isinstance(field, LocalizedField):
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._apply_hstore_constraints(
|
|
||||||
self._drop_hstore_unique,
|
|
||||||
model,
|
|
||||||
field
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseWrapper(_get_backend_base()):
|
|
||||||
"""Wraps the standard PostgreSQL database back-end.
|
|
||||||
|
|
||||||
Overrides the schema editor with our custom
|
|
||||||
schema editor and makes sure the `hstore`
|
|
||||||
extension is enabled."""
|
|
||||||
|
|
||||||
SchemaEditorClass = SchemaEditor
|
|
||||||
|
|
||||||
def prepare_database(self):
|
|
||||||
"""Ran to prepare the configured database.
|
|
||||||
|
|
||||||
This is where we enable the `hstore` extension
|
|
||||||
if it wasn't enabled yet."""
|
|
||||||
|
|
||||||
super().prepare_database()
|
|
||||||
with self.cursor() as cursor:
|
|
||||||
cursor.execute('CREATE EXTENSION IF NOT EXISTS hstore')
|
|
@@ -1,8 +1,9 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.postgres.fields import HStoreField
|
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
|
|
||||||
from localized_fields import LocalizedFieldForm
|
from localized_fields import LocalizedFieldForm
|
||||||
|
from psqlextra.fields import HStoreField
|
||||||
|
|
||||||
from ..localized_value import LocalizedValue
|
from ..localized_value import LocalizedValue
|
||||||
|
|
||||||
|
|
||||||
@@ -14,13 +15,11 @@ class LocalizedField(HStoreField):
|
|||||||
|
|
||||||
Meta = None
|
Meta = None
|
||||||
|
|
||||||
def __init__(self, *args, uniqueness=None, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initializes a new instance of :see:LocalizedValue."""
|
"""Initializes a new instance of :see:LocalizedField."""
|
||||||
|
|
||||||
super(LocalizedField, self).__init__(*args, **kwargs)
|
super(LocalizedField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.uniqueness = uniqueness
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_db_value(value, *_):
|
def from_db_value(value, *_):
|
||||||
"""Turns the specified database value into its Python
|
"""Turns the specified database value into its Python
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
from django.db import models, transaction
|
from psqlextra.models import PostgresModel
|
||||||
from django.db.utils import IntegrityError
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from .fields import LocalizedField
|
from .fields import LocalizedField
|
||||||
from .localized_value import LocalizedValue
|
from .localized_value import LocalizedValue
|
||||||
|
|
||||||
|
|
||||||
class LocalizedModel(models.Model):
|
class LocalizedModel(PostgresModel):
|
||||||
"""A model that contains localized fields."""
|
"""A model that contains localized fields."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
1
requirements/base.txt
Normal file
1
requirements/base.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
django-postgres-extra==1.2
|
@@ -1,3 +1,5 @@
|
|||||||
|
-r base.txt
|
||||||
|
|
||||||
coverage==4.2
|
coverage==4.2
|
||||||
Django==1.10.2
|
Django==1.10.2
|
||||||
django-autoslug==1.9.3
|
django-autoslug==1.9.3
|
||||||
|
@@ -11,7 +11,7 @@ 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'
|
DATABASES['default']['ENGINE'] = 'psqlextra.backend'
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en'
|
LANGUAGE_CODE = 'en'
|
||||||
LANGUAGES = (
|
LANGUAGES = (
|
||||||
|
5
setup.py
5
setup.py
@@ -7,7 +7,7 @@ with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-localized-fields',
|
name='django-localized-fields',
|
||||||
version='2.9.2',
|
version='3.3',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license='MIT License',
|
license='MIT License',
|
||||||
@@ -17,6 +17,9 @@ setup(
|
|||||||
author='Sector Labs',
|
author='Sector Labs',
|
||||||
author_email='open-source@sectorlabs.ro',
|
author_email='open-source@sectorlabs.ro',
|
||||||
keywords=['django', 'localized', 'language', 'models', 'fields'],
|
keywords=['django', 'localized', 'language', 'models', 'fields'],
|
||||||
|
install_requires=[
|
||||||
|
'django-postgres-extra>=1.3'
|
||||||
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Environment :: Web Environment',
|
'Environment :: Web Environment',
|
||||||
'Framework :: Django',
|
'Framework :: Django',
|
||||||
|
@@ -1,87 +0,0 @@
|
|||||||
from unittest import mock
|
|
||||||
|
|
||||||
from django.db import connection
|
|
||||||
from django.apps import apps
|
|
||||||
from django.conf import settings
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
||||||
|
|
||||||
from localized_fields import LocalizedField, get_language_codes
|
|
||||||
|
|
||||||
from .fake_model import define_fake_model
|
|
||||||
|
|
||||||
|
|
||||||
class DBBackendTestCase(TestCase):
|
|
||||||
"""Tests the custom database back-end."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def test_hstore_extension_enabled():
|
|
||||||
"""Tests whether the `hstore` extension was
|
|
||||||
enabled automatically."""
|
|
||||||
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute((
|
|
||||||
'SELECT count(*) FROM pg_extension '
|
|
||||||
'WHERE extname = \'hstore\''
|
|
||||||
))
|
|
||||||
|
|
||||||
assert cursor.fetchone()[0] == 1
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def test_migration_create_drop_model(cls):
|
|
||||||
"""Tests whether models containing a :see:LocalizedField
|
|
||||||
with a `uniqueness` constraint get created properly,
|
|
||||||
with the contraints in the database."""
|
|
||||||
|
|
||||||
model = define_fake_model('NewModel', {
|
|
||||||
'title': LocalizedField(uniqueness=get_language_codes())
|
|
||||||
})
|
|
||||||
|
|
||||||
# create the model in db and verify the indexes are being created
|
|
||||||
with mock.patch.object(BaseDatabaseSchemaEditor, 'execute') as execute:
|
|
||||||
with connection.schema_editor() as schema_editor:
|
|
||||||
schema_editor.create_model(model)
|
|
||||||
|
|
||||||
create_index_calls = [
|
|
||||||
call for call in execute.mock_calls if 'CREATE UNIQUE INDEX' in str(call)
|
|
||||||
]
|
|
||||||
|
|
||||||
assert len(create_index_calls) == len(settings.LANGUAGES)
|
|
||||||
|
|
||||||
# delete the model in the db and verify the indexes are being deleted
|
|
||||||
with mock.patch.object(BaseDatabaseSchemaEditor, 'execute') as execute:
|
|
||||||
with connection.schema_editor() as schema_editor:
|
|
||||||
schema_editor.delete_model(model)
|
|
||||||
|
|
||||||
drop_index_calls = [
|
|
||||||
call for call in execute.mock_calls if 'DROP INDEX' in str(call)
|
|
||||||
]
|
|
||||||
|
|
||||||
assert len(drop_index_calls) == len(settings.LANGUAGES)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def test_migration_alter_field(cls):
|
|
||||||
"""Tests whether the back-end correctly removes and
|
|
||||||
adds `uniqueness` constraints when altering a :see:LocalizedField."""
|
|
||||||
|
|
||||||
define_fake_model('ExistingModel', {
|
|
||||||
'title': LocalizedField(uniqueness=get_language_codes())
|
|
||||||
})
|
|
||||||
|
|
||||||
app_config = apps.get_app_config('tests')
|
|
||||||
|
|
||||||
with mock.patch.object(BaseDatabaseSchemaEditor, 'execute') as execute:
|
|
||||||
with connection.schema_editor() as schema_editor:
|
|
||||||
dynmodel = app_config.get_model('ExistingModel')
|
|
||||||
schema_editor.alter_field(
|
|
||||||
dynmodel,
|
|
||||||
dynmodel._meta.fields[1],
|
|
||||||
dynmodel._meta.fields[1]
|
|
||||||
)
|
|
||||||
|
|
||||||
index_calls = [
|
|
||||||
call for call in execute.mock_calls
|
|
||||||
if 'INDEX' in str(call) and 'title' in str(call)
|
|
||||||
]
|
|
||||||
|
|
||||||
assert len(index_calls) == len(settings.LANGUAGES) * 2
|
|
Reference in New Issue
Block a user