mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-10-26 08:58:58 +03:00 
			
		
		
		
	Compare commits
	
		
			12 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2393539b44 | ||
|  | e322b3d63b | ||
|  | 1b1d24a460 | ||
|  | fb233e8f25 | ||
|  | bca94a3508 | ||
|  | 8c83fa6b49 | ||
|  | 90597da8fd | ||
|  | 752e17064d | ||
|  | def7dae640 | ||
|  | db4324fbf3 | ||
|  | 9ff0b781ab | ||
|  | a76101c9ad | 
							
								
								
									
										21
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.rst
									
									
									
									
									
								
							| @@ -121,6 +121,7 @@ Or get it in a specific language: | |||||||
|      print(new.title.get('en')) # prints 'english title' |      print(new.title.get('en')) # prints 'english title' | ||||||
|      print(new.title.get('ro')) # prints 'romanian title' |      print(new.title.get('ro')) # prints 'romanian title' | ||||||
|      print(new.title.get()) # whatever language is the primary one |      print(new.title.get()) # whatever language is the primary one | ||||||
|  |      print(new.title.get('ar', 'haha')) # prints 'haha' if there is no value in arabic | ||||||
|  |  | ||||||
| You can also explicitly set a value in a certain language: | You can also explicitly set a value in a certain language: | ||||||
|  |  | ||||||
| @@ -136,7 +137,7 @@ Constraints | |||||||
|  |  | ||||||
| **Required/Optional** | **Required/Optional** | ||||||
|  |  | ||||||
| Constraints is enforced on a database level. | Constraints are enforced on a database level. | ||||||
|  |  | ||||||
| * Optional filling | * Optional filling | ||||||
|  |  | ||||||
| @@ -274,8 +275,24 @@ Besides ``LocalizedField``, there's also: | |||||||
|                    title = LocalizedField() |                    title = LocalizedField() | ||||||
|                    description = LocalizedBleachField() |                    description = LocalizedBleachField() | ||||||
|  |  | ||||||
|  | * ``LocalizedIntegerField`` | ||||||
|  |     This is an experimental field type introduced in version 5.0 and is subject to change. It also has | ||||||
|  |     some pretty major downsides due to the fact that values are stored as strings and are converted | ||||||
|  |     back and forth. | ||||||
|  |  | ||||||
|  |     Allows storing integers in multiple languages. This works exactly like ``LocalizedField`` except that | ||||||
|  |     all values must be integers. Do note that values are stored as strings in your database because | ||||||
|  |     the backing field type is ``hstore``, which only allows storing integers. The ``LocalizedIntegerField`` | ||||||
|  |     takes care of ensuring that all values are integers and converts the stored strings back to integers | ||||||
|  |     when retrieving them from the database. Do not expect to be able to do queries such as: | ||||||
|  |  | ||||||
|  |         .. code-block:: python | ||||||
|  |  | ||||||
|  |             MyModel.objects.filter(score__en__gt=1) | ||||||
|  |  | ||||||
|  |  | ||||||
| * ``LocalizedCharField`` and ``LocalizedTextField`` | * ``LocalizedCharField`` and ``LocalizedTextField`` | ||||||
|     This fields following the Django convention for string-based fields use the empty string as value for “no data”, not NULL. |     These fields following the Django convention for string-based fields use the empty string as value for “no data”, not NULL. | ||||||
|     ``LocalizedCharField`` uses ``TextInput`` (``<input type="text">``) widget for render. |     ``LocalizedCharField`` uses ``TextInput`` (``<input type="text">``) widget for render. | ||||||
|  |  | ||||||
|     Example usage: |     Example usage: | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | from django.contrib.admin import ModelAdmin | ||||||
|  |  | ||||||
| from . import widgets | from . import widgets | ||||||
| from .fields import LocalizedField, LocalizedCharField, LocalizedTextField, \ | from .fields import LocalizedField, LocalizedCharField, LocalizedTextField, \ | ||||||
|     LocalizedFileField |     LocalizedFileField | ||||||
| @@ -11,7 +13,7 @@ FORMFIELD_FOR_LOCALIZED_FIELDS_DEFAULTS = { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class LocalizedFieldsAdminMixin: | class LocalizedFieldsAdminMixin(ModelAdmin): | ||||||
|     """Mixin for making the fancy widgets work in Django Admin.""" |     """Mixin for making the fancy widgets work in Django Admin.""" | ||||||
|  |  | ||||||
|     class Media: |     class Media: | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ from .uniqueslug_field import LocalizedUniqueSlugField | |||||||
| from .char_field import LocalizedCharField | from .char_field import LocalizedCharField | ||||||
| from .text_field import LocalizedTextField | from .text_field import LocalizedTextField | ||||||
| from .file_field import LocalizedFileField | from .file_field import LocalizedFileField | ||||||
|  | from .integer_field import LocalizedIntegerField | ||||||
|  |  | ||||||
|  |  | ||||||
| __all__ = [ | __all__ = [ | ||||||
| @@ -12,7 +13,8 @@ __all__ = [ | |||||||
|     'LocalizedUniqueSlugField', |     'LocalizedUniqueSlugField', | ||||||
|     'LocalizedCharField', |     'LocalizedCharField', | ||||||
|     'LocalizedTextField', |     'LocalizedTextField', | ||||||
|     'LocalizedFileField' |     'LocalizedFileField', | ||||||
|  |     'LocalizedIntegerField' | ||||||
| ] | ] | ||||||
|  |  | ||||||
| try: | try: | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import json | import json | ||||||
|  |  | ||||||
| from typing import Union, List | from typing import Union, List, Optional | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.db.utils import IntegrityError | from django.db.utils import IntegrityError | ||||||
| @@ -53,7 +53,7 @@ class LocalizedField(HStoreField): | |||||||
|         setattr(model, self.name, self.descriptor_class(self)) |         setattr(model, self.name, self.descriptor_class(self)) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def from_db_value(cls, value, *_): |     def from_db_value(cls, value, *_) -> Optional[LocalizedValue]: | ||||||
|         """Turns the specified database value into its Python |         """Turns the specified database value into its Python | ||||||
|         equivalent. |         equivalent. | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										81
									
								
								localized_fields/fields/integer_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								localized_fields/fields/integer_field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | from typing import Optional, Union, Dict | ||||||
|  |  | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db.utils import IntegrityError | ||||||
|  |  | ||||||
|  | from .field import LocalizedField | ||||||
|  | from ..value import LocalizedValue, LocalizedIntegerValue | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalizedIntegerField(LocalizedField): | ||||||
|  |     """Stores integers as a localized value.""" | ||||||
|  |  | ||||||
|  |     attr_class = LocalizedIntegerValue | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def from_db_value(cls, value, *_) -> Optional[LocalizedIntegerValue]: | ||||||
|  |         db_value = super().from_db_value(value) | ||||||
|  |         if db_value is None: | ||||||
|  |             return db_value | ||||||
|  |  | ||||||
|  |         # if we were used in an expression somehow then it might be | ||||||
|  |         # that we're returning an individual value or an array, so | ||||||
|  |         # we should not convert that into an :see:LocalizedIntegerValue | ||||||
|  |         if not isinstance(db_value, LocalizedValue): | ||||||
|  |             return db_value | ||||||
|  |  | ||||||
|  |         return cls._convert_localized_value(db_value) | ||||||
|  |  | ||||||
|  |     def to_python(self, value: Union[Dict[str, int], int, None]) -> LocalizedIntegerValue: | ||||||
|  |         """Converts the value from a database value into a Python value.""" | ||||||
|  |  | ||||||
|  |         db_value = super().to_python(value) | ||||||
|  |         return self._convert_localized_value(db_value) | ||||||
|  |  | ||||||
|  |     def get_prep_value(self, value: LocalizedIntegerValue) -> dict: | ||||||
|  |         """Gets the value in a format to store into the database.""" | ||||||
|  |  | ||||||
|  |         # apply default values | ||||||
|  |         default_values = LocalizedIntegerValue(self.default) | ||||||
|  |         if isinstance(value, LocalizedIntegerValue): | ||||||
|  |             for lang_code, _ in settings.LANGUAGES: | ||||||
|  |                 local_value = value.get(lang_code) | ||||||
|  |                 if local_value is None: | ||||||
|  |                     value.set(lang_code, default_values.get(lang_code, None)) | ||||||
|  |  | ||||||
|  |         prepped_value = super().get_prep_value(value) | ||||||
|  |         if prepped_value is None: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         # make sure all values are proper integers | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             local_value = prepped_value[lang_code] | ||||||
|  |             try: | ||||||
|  |                 if local_value is not None: | ||||||
|  |                     int(local_value) | ||||||
|  |             except (TypeError, ValueError): | ||||||
|  |                 raise IntegrityError('non-integer value in column "%s.%s" violates ' | ||||||
|  |                                      'integer constraint' % (self.name, lang_code)) | ||||||
|  |  | ||||||
|  |             # convert to a string before saving because the underlying | ||||||
|  |             # type is hstore, which only accept strings | ||||||
|  |             prepped_value[lang_code] = str(local_value) if local_value is not None else None | ||||||
|  |  | ||||||
|  |         return prepped_value | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _convert_localized_value(value: LocalizedValue) -> LocalizedIntegerValue: | ||||||
|  |         """Converts from :see:LocalizedValue to :see:LocalizedIntegerValue.""" | ||||||
|  |  | ||||||
|  |         integer_values = {} | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             local_value = value.get(lang_code, None) | ||||||
|  |             if local_value is None or local_value.strip() == '': | ||||||
|  |                 local_value = None | ||||||
|  |  | ||||||
|  |             try: | ||||||
|  |                 integer_values[lang_code] = int(local_value) | ||||||
|  |             except (ValueError, TypeError): | ||||||
|  |                 integer_values[lang_code] = None | ||||||
|  |  | ||||||
|  |         return LocalizedIntegerValue(integer_values) | ||||||
| @@ -1,5 +1,7 @@ | |||||||
|  | import deprecation | ||||||
| import collections | import collections | ||||||
|  |  | ||||||
|  | from typing import Optional | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.utils import translation | from django.utils import translation | ||||||
|  |  | ||||||
| @@ -36,7 +38,8 @@ class LocalizedValue(dict): | |||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         language = language or settings.LANGUAGE_CODE |         language = language or settings.LANGUAGE_CODE | ||||||
|         return super().get(language, default) |         value = super().get(language, default) | ||||||
|  |         return value if value is not None else default | ||||||
|  |  | ||||||
|     def set(self, language: str, value: str): |     def set(self, language: str, value: str): | ||||||
|         """Sets the value in the specified language. |         """Sets the value in the specified language. | ||||||
| @@ -98,10 +101,10 @@ class LocalizedValue(dict): | |||||||
|             for val in value: |             for val in value: | ||||||
|                 self._interpret_value(val) |                 self._interpret_value(val) | ||||||
|  |  | ||||||
|     def __str__(self) -> str: |     def translate(self) -> Optional[str]: | ||||||
|         """Gets the value in the current language, or falls |         """Gets the value in the current language or falls | ||||||
|         back to the primary language if there's no value |         back to the next language if there's no value in the | ||||||
|         in the current language.""" |         current language.""" | ||||||
|  |  | ||||||
|         fallbacks = getattr(settings, 'LOCALIZED_FIELDS_FALLBACKS', {}) |         fallbacks = getattr(settings, 'LOCALIZED_FIELDS_FALLBACKS', {}) | ||||||
|  |  | ||||||
| @@ -112,9 +115,16 @@ class LocalizedValue(dict): | |||||||
|         for lang_code in languages: |         for lang_code in languages: | ||||||
|             value = self.get(lang_code) |             value = self.get(lang_code) | ||||||
|             if value: |             if value: | ||||||
|                 return value or '' |                 return value or None | ||||||
|  |  | ||||||
|         return '' |         return None | ||||||
|  |  | ||||||
|  |     def __str__(self) -> str: | ||||||
|  |         """Gets the value in the current language or falls | ||||||
|  |         back to the next language if there's no value in the | ||||||
|  |         current language.""" | ||||||
|  |  | ||||||
|  |         return self.translate() or '' | ||||||
|  |  | ||||||
|     def __eq__(self, other): |     def __eq__(self, other): | ||||||
|         """Compares :paramref:self to :paramref:other for |         """Compares :paramref:self to :paramref:other for | ||||||
| @@ -183,8 +193,38 @@ class LocalizedFileValue(LocalizedValue): | |||||||
|  |  | ||||||
|     def __str__(self) -> str: |     def __str__(self) -> str: | ||||||
|         """Returns string representation of value""" |         """Returns string representation of value""" | ||||||
|  |  | ||||||
|         return str(super().__str__()) |         return str(super().__str__()) | ||||||
|  |  | ||||||
|  |     @deprecation.deprecated(deprecated_in='4.6', removed_in='5.0', | ||||||
|  |                             current_version='4.6', | ||||||
|  |                             details='Use the translate() function instead.') | ||||||
|     def localized(self): |     def localized(self): | ||||||
|         """Returns value for current language""" |         """Returns value for current language""" | ||||||
|  |  | ||||||
|         return self.get(translation.get_language()) |         return self.get(translation.get_language()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalizedIntegerValue(LocalizedValue): | ||||||
|  |     """All values are integers.""" | ||||||
|  |  | ||||||
|  |     default_value = None | ||||||
|  |  | ||||||
|  |     def translate(self): | ||||||
|  |         """Gets the value in the current language, or | ||||||
|  |         in the configured fallbck language.""" | ||||||
|  |  | ||||||
|  |         value = super().translate() | ||||||
|  |         if value is None or (isinstance(value, str) and value.strip() == ''): | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         return int(value) | ||||||
|  |  | ||||||
|  |     def __int__(self): | ||||||
|  |         """Gets the value in the current language as an integer.""" | ||||||
|  |  | ||||||
|  |         value = self.translate() | ||||||
|  |         if value is None: | ||||||
|  |             return self.default_value | ||||||
|  |  | ||||||
|  |         return int(value) | ||||||
|   | |||||||
| @@ -1 +1,2 @@ | |||||||
| django-postgres-extra==1.21a8 | django-postgres-extra==1.21a9 | ||||||
|  | deprecation==2.0.3 | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							| @@ -7,7 +7,7 @@ with open(os.path.join(os.path.dirname(__file__), 'README.rst'), encoding='utf-8 | |||||||
|  |  | ||||||
| setup( | setup( | ||||||
|     name='django-localized-fields', |     name='django-localized-fields', | ||||||
|     version='4.6a2', |     version='5.0a2', | ||||||
|     packages=find_packages(exclude=['tests']), |     packages=find_packages(exclude=['tests']), | ||||||
|     include_package_data=True, |     include_package_data=True, | ||||||
|     license='MIT License', |     license='MIT License', | ||||||
| @@ -18,8 +18,9 @@ setup( | |||||||
|     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=[ |     install_requires=[ | ||||||
|         'django-postgres-extra>=1.11', |         'django-postgres-extra>=1.21a11', | ||||||
|         'Django>=1.11' |         'Django>=1.11', | ||||||
|  |         'deprecation==2.0.3' | ||||||
|     ], |     ], | ||||||
|     classifiers=[ |     classifiers=[ | ||||||
|         'Environment :: Web Environment', |         'Environment :: Web Environment', | ||||||
|   | |||||||
							
								
								
									
										176
									
								
								tests/test_integer_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								tests/test_integer_field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | |||||||
|  | from django.test import TestCase | ||||||
|  | from django.db.utils import IntegrityError | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import connection | ||||||
|  | from django.utils import translation | ||||||
|  |  | ||||||
|  | from localized_fields.fields import LocalizedIntegerField | ||||||
|  |  | ||||||
|  | from .fake_model import get_fake_model | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LocalizedIntegerFieldTestCase(TestCase): | ||||||
|  |     """Tests whether the :see:LocalizedIntegerField | ||||||
|  |     and :see:LocalizedIntegerValue works properly.""" | ||||||
|  |  | ||||||
|  |     TestModel = None | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def setUpClass(cls): | ||||||
|  |         super().setUpClass() | ||||||
|  |  | ||||||
|  |         cls.TestModel = get_fake_model({ | ||||||
|  |             'score': LocalizedIntegerField() | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |     def test_basic(self): | ||||||
|  |         """Tests the basics of storing integer values.""" | ||||||
|  |  | ||||||
|  |         obj = self.TestModel() | ||||||
|  |         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||||
|  |             obj.score.set(lang_code, index + 1) | ||||||
|  |         obj.save() | ||||||
|  |  | ||||||
|  |         obj = self.TestModel.objects.all().first() | ||||||
|  |         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||||
|  |             assert obj.score.get(lang_code) == index + 1 | ||||||
|  |  | ||||||
|  |     def test_primary_language_required(self): | ||||||
|  |         """Tests whether the primary language is required by | ||||||
|  |         default and all other languages are optiona.""" | ||||||
|  |  | ||||||
|  |         # not filling in anything should raise IntegrityError, | ||||||
|  |         # the primary language is required | ||||||
|  |         with self.assertRaises(IntegrityError): | ||||||
|  |             obj = self.TestModel() | ||||||
|  |             obj.save() | ||||||
|  |  | ||||||
|  |         # when filling all other languages besides the primary language | ||||||
|  |         # should still raise an error because the primary is always required | ||||||
|  |         with self.assertRaises(IntegrityError): | ||||||
|  |             obj = self.TestModel() | ||||||
|  |             for lang_code, _ in settings.LANGUAGES: | ||||||
|  |                 if lang_code == settings.LANGUAGE_CODE: | ||||||
|  |                     continue | ||||||
|  |                 obj.score.set(lang_code, 23) | ||||||
|  |             obj.save() | ||||||
|  |  | ||||||
|  |     def test_default_value_none(self): | ||||||
|  |         """Tests whether the default value for optional languages | ||||||
|  |         is NoneType.""" | ||||||
|  |  | ||||||
|  |         obj = self.TestModel() | ||||||
|  |         obj.score.set(settings.LANGUAGE_CODE, 1234) | ||||||
|  |         obj.save() | ||||||
|  |  | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             if lang_code == settings.LANGUAGE_CODE: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             assert obj.score.get(lang_code) is None | ||||||
|  |  | ||||||
|  |     def test_translate(self): | ||||||
|  |         """Tests whether casting the value to an integer | ||||||
|  |         results in the value being returned in the currently | ||||||
|  |         active language as an integer.""" | ||||||
|  |  | ||||||
|  |         obj = self.TestModel() | ||||||
|  |         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||||
|  |             obj.score.set(lang_code, index + 1) | ||||||
|  |         obj.save() | ||||||
|  |  | ||||||
|  |         obj.refresh_from_db() | ||||||
|  |         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||||
|  |             with translation.override(lang_code): | ||||||
|  |                 assert int(obj.score) == index + 1 | ||||||
|  |                 assert obj.score.translate() == index + 1 | ||||||
|  |  | ||||||
|  |     def test_translate_primary_fallback(self): | ||||||
|  |         """Tests whether casting the value to an integer | ||||||
|  |         results in the value begin returned in the active | ||||||
|  |         language and falls back to the primary language | ||||||
|  |         if there is no value in that language.""" | ||||||
|  |  | ||||||
|  |         obj = self.TestModel() | ||||||
|  |         obj.score.set(settings.LANGUAGE_CODE, 25) | ||||||
|  |  | ||||||
|  |         secondary_language = settings.LANGUAGES[-1][0] | ||||||
|  |         assert obj.score.get(secondary_language) is None | ||||||
|  |  | ||||||
|  |         with translation.override(secondary_language): | ||||||
|  |             assert obj.score.translate() == 25 | ||||||
|  |             assert int(obj.score) == 25 | ||||||
|  |  | ||||||
|  |     def test_get_default_value(self): | ||||||
|  |         """Tests whether getting the value in a specific | ||||||
|  |         language properly returns the specified default | ||||||
|  |         in case it is not available.""" | ||||||
|  |  | ||||||
|  |         obj = self.TestModel() | ||||||
|  |         obj.score.set(settings.LANGUAGE_CODE, 25) | ||||||
|  |  | ||||||
|  |         secondary_language = settings.LANGUAGES[-1][0] | ||||||
|  |         assert obj.score.get(secondary_language) is None | ||||||
|  |         assert obj.score.get(secondary_language, 1337) == 1337 | ||||||
|  |  | ||||||
|  |     def test_completely_optional(self): | ||||||
|  |         """Tests whether having all languages optional | ||||||
|  |         works properly.""" | ||||||
|  |  | ||||||
|  |         model = get_fake_model({ | ||||||
|  |             'score': LocalizedIntegerField(null=True, required=[], blank=True) | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         obj = model() | ||||||
|  |         obj.save() | ||||||
|  |  | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             assert getattr(obj.score, lang_code) is None | ||||||
|  |  | ||||||
|  |     def test_store_string(self): | ||||||
|  |         """Tests whether the field properly raises | ||||||
|  |         an error when trying to store a non-integer.""" | ||||||
|  |  | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             obj = self.TestModel() | ||||||
|  |             with self.assertRaises(IntegrityError): | ||||||
|  |                 obj.score.set(lang_code, 'haha') | ||||||
|  |                 obj.save() | ||||||
|  |  | ||||||
|  |     def test_none_if_illegal_value_stored(self): | ||||||
|  |         """Tests whether None is returned for a language | ||||||
|  |         if the value stored in the database is not an | ||||||
|  |         integer.""" | ||||||
|  |  | ||||||
|  |         obj = self.TestModel() | ||||||
|  |         obj.score.set(settings.LANGUAGE_CODE, 25) | ||||||
|  |         obj.save() | ||||||
|  |  | ||||||
|  |         with connection.cursor() as cursor: | ||||||
|  |             table_name = self.TestModel._meta.db_table | ||||||
|  |             cursor.execute("update %s set score = 'en=>haha'" % table_name); | ||||||
|  |  | ||||||
|  |         obj.refresh_from_db() | ||||||
|  |         assert obj.score.get(settings.LANGUAGE_CODE) is None | ||||||
|  |  | ||||||
|  |     def test_default_value(self): | ||||||
|  |         """Tests whether a default is properly set | ||||||
|  |         when specified.""" | ||||||
|  |  | ||||||
|  |         model = get_fake_model({ | ||||||
|  |             'score': LocalizedIntegerField(default={settings.LANGUAGE_CODE: 75}) | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         obj = model.objects.create() | ||||||
|  |         assert obj.score.get(settings.LANGUAGE_CODE) == 75 | ||||||
|  |  | ||||||
|  |         obj = model() | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             obj.score.set(lang_code, None) | ||||||
|  |         obj.save() | ||||||
|  |  | ||||||
|  |         for lang_code, _ in settings.LANGUAGES: | ||||||
|  |             if lang_code == settings.LANGUAGE_CODE: | ||||||
|  |                 assert obj.score.get(lang_code) == 75 | ||||||
|  |             else: | ||||||
|  |                 assert obj.score.get(lang_code) is None | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| from django.conf import settings |  | ||||||
| from django.test import TestCase |  | ||||||
| from django.utils import translation |  | ||||||
| from django.db.models import F | from django.db.models import F | ||||||
|  | from django.conf import settings | ||||||
|  | from django.utils import translation | ||||||
|  | from django.test import TestCase, override_settings | ||||||
|  |  | ||||||
| from localized_fields.value import LocalizedValue | from localized_fields.value import LocalizedValue | ||||||
|  |  | ||||||
| @@ -90,18 +90,6 @@ class LocalizedValueTestCase(TestCase): | |||||||
|             assert localized_value.get(language) == value |             assert localized_value.get(language) == value | ||||||
|             assert getattr(localized_value, language) == value |             assert getattr(localized_value, language) == value | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def test_str(): |  | ||||||
|         """Tests whether the :see:LocalizedValue |  | ||||||
|         class's __str__ works properly.""" |  | ||||||
|  |  | ||||||
|         keys = get_init_values() |  | ||||||
|         localized_value = LocalizedValue(keys) |  | ||||||
|  |  | ||||||
|         for language, value in keys.items(): |  | ||||||
|             translation.activate(language) |  | ||||||
|             assert str(localized_value) == value |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def test_eq(): |     def test_eq(): | ||||||
|         """Tests whether the __eq__ operator |         """Tests whether the __eq__ operator | ||||||
| @@ -116,9 +104,21 @@ class LocalizedValueTestCase(TestCase): | |||||||
|         assert a != b |         assert a != b | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def test_str_fallback(): |     def test_translate(): | ||||||
|         """Tests whether the :see:LocalizedValue |         """Tests whether the :see:LocalizedValue | ||||||
|         class's __str__'s fallback functionality |         class's __str__ works properly.""" | ||||||
|  |  | ||||||
|  |         keys = get_init_values() | ||||||
|  |         localized_value = LocalizedValue(keys) | ||||||
|  |  | ||||||
|  |         for language, value in keys.items(): | ||||||
|  |             translation.activate(language) | ||||||
|  |             assert localized_value.translate() == value | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def test_translate_fallback(): | ||||||
|  |         """Tests whether the :see:LocalizedValue | ||||||
|  |         class's translate()'s fallback functionality | ||||||
|         works properly.""" |         works properly.""" | ||||||
|  |  | ||||||
|         test_value = 'myvalue' |         test_value = 'myvalue' | ||||||
| @@ -131,13 +131,13 @@ class LocalizedValueTestCase(TestCase): | |||||||
|  |  | ||||||
|         # make sure that, by default it returns |         # make sure that, by default it returns | ||||||
|         # the value in the default language |         # the value in the default language | ||||||
|         assert str(localized_value) == test_value |         assert localized_value.translate() == test_value | ||||||
|  |  | ||||||
|         # make sure that it falls back to the |         # make sure that it falls back to the | ||||||
|         # primary language when there's no value |         # primary language when there's no value | ||||||
|         # available in the current language |         # available in the current language | ||||||
|         translation.activate(other_language) |         translation.activate(other_language) | ||||||
|         assert str(localized_value) == test_value |         assert localized_value.translate() == test_value | ||||||
|  |  | ||||||
|         # make sure that it's just __str__ falling |         # make sure that it's just __str__ falling | ||||||
|         # back and that for the other language |         # back and that for the other language | ||||||
| @@ -145,12 +145,35 @@ class LocalizedValueTestCase(TestCase): | |||||||
|         assert localized_value.get(other_language) != test_value |         assert localized_value.get(other_language) != test_value | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def test_str_fallback_custom_fallback(): |     def test_translate_none(): | ||||||
|  |         """Tests whether the :see:LocalizedValue | ||||||
|  |         class's translate() method properly returns | ||||||
|  |         None when there is no value.""" | ||||||
|  |  | ||||||
|  |         # with no value, we always expect it to return None | ||||||
|  |         localized_value = LocalizedValue() | ||||||
|  |         assert localized_value.translate() == None | ||||||
|  |         assert str(localized_value) == '' | ||||||
|  |  | ||||||
|  |         # with no value for the default language, the default | ||||||
|  |         # behavior is to return None, unless a custom fallback | ||||||
|  |         # chain is configured, which there is not for this test | ||||||
|  |         other_language = settings.LANGUAGES[-1][0] | ||||||
|  |         localized_value = LocalizedValue({ | ||||||
|  |             other_language: 'hey' | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         translation.activate(settings.LANGUAGE_CODE) | ||||||
|  |         assert localized_value.translate() == None | ||||||
|  |         assert str(localized_value) == '' | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def test_translate_fallback_custom_fallback(): | ||||||
|         """Tests whether the :see:LocalizedValue class's |         """Tests whether the :see:LocalizedValue class's | ||||||
|         __str__'s fallback functionality properly respects |         translate()'s fallback functionality properly respects | ||||||
|         the LOCALIZED_FIELDS_FALLBACKS setting.""" |         the LOCALIZED_FIELDS_FALLBACKS setting.""" | ||||||
|  |  | ||||||
|         settings.LOCALIZED_FIELDS_FALLBACKS = { |         fallbacks = { | ||||||
|             'nl': ['ro'] |             'nl': ['ro'] | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -159,8 +182,9 @@ class LocalizedValueTestCase(TestCase): | |||||||
|             'ro': 'ro' |             'ro': 'ro' | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|  |         with override_settings(LOCALIZED_FIELDS_FALLBACKS=fallbacks): | ||||||
|             with translation.override('nl'): |             with translation.override('nl'): | ||||||
|             assert str(localized_value) == 'ro' |                 assert localized_value.translate() == 'ro' | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def test_deconstruct(): |     def test_deconstruct(): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user