mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-10-26 08:58:58 +03:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a21ff53cb9 | ||
|  | e4bd26ece2 | ||
|  | a198440a64 | ||
|  | 5945c3f531 | ||
|  | 3fd862ce4d | ||
|  | dad7f164cd | ||
|  | 6490f3908c | 
							
								
								
									
										12
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.rst
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| django-localized-fields | ||||
| ======================= | ||||
|  | ||||
| .. image:: https://circleci.com/gh/SectorLabs/django-localized-fields.svg?style=svg | ||||
|     :target: https://circleci.com/gh/SectorLabs/django-localized-fields | ||||
| .. image:: https://circleci.com/gh/SectorLabs/django-localized-fields/tree/v5.x.svg?style=svg | ||||
|     :target: https://circleci.com/gh/SectorLabs/django-localized-fields/tree/v5.x | ||||
|  | ||||
| .. image:: https://img.shields.io/github/license/SectorLabs/django-localized-fields.svg | ||||
|     :target: https://github.com/SectorLabs/django-localized-fields/blob/master/LICENSE.md | ||||
| @@ -14,6 +14,12 @@ django-localized-fields | ||||
|  | ||||
| This package requires Python 3.5 or newer, Django 1.11 or newer and PostgreSQL 9.6 or newer. | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **This README is for v5.x. Check out the master branch for v6.x and newer.** | ||||
|  | ||||
| ---- | ||||
|  | ||||
| Contributors | ||||
| ------------ | ||||
|  | ||||
| @@ -26,7 +32,7 @@ Installation | ||||
|  | ||||
|     .. code-block:: bash | ||||
|  | ||||
|         $ pip install django-localized-fields | ||||
|         $ pip install django-localized-fields==5.4.1 | ||||
|  | ||||
| 2. Add ``localized_fields`` and ``django.contrib.postgres`` to your ``INSTALLED_APPS``: | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from .char_field import LocalizedCharField | ||||
| from .text_field import LocalizedTextField | ||||
| from .file_field import LocalizedFileField | ||||
| from .integer_field import LocalizedIntegerField | ||||
| from .float_field import LocalizedFloatField | ||||
|  | ||||
|  | ||||
| __all__ = [ | ||||
| @@ -14,7 +15,8 @@ __all__ = [ | ||||
|     'LocalizedCharField', | ||||
|     'LocalizedTextField', | ||||
|     'LocalizedFileField', | ||||
|     'LocalizedIntegerField' | ||||
|     'LocalizedIntegerField', | ||||
|     'LocalizedFloatField' | ||||
| ] | ||||
|  | ||||
| try: | ||||
|   | ||||
							
								
								
									
										91
									
								
								localized_fields/fields/float_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								localized_fields/fields/float_field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| 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, LocalizedFloatValue | ||||
| from ..forms import LocalizedIntegerFieldForm | ||||
|  | ||||
|  | ||||
| class LocalizedFloatField(LocalizedField): | ||||
|     """Stores float as a localized value.""" | ||||
|  | ||||
|     attr_class = LocalizedFloatValue | ||||
|  | ||||
|     @classmethod | ||||
|     def from_db_value(cls, value, *_) -> Optional[LocalizedFloatValue]: | ||||
|         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:LocalizedFloatValue | ||||
|         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]) -> LocalizedFloatValue: | ||||
|         """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: LocalizedFloatValue) -> dict: | ||||
|         """Gets the value in a format to store into the database.""" | ||||
|  | ||||
|         # apply default values | ||||
|         default_values = LocalizedFloatValue(self.default) | ||||
|         if isinstance(value, LocalizedFloatValue): | ||||
|             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 floats | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             local_value = prepped_value[lang_code] | ||||
|             try: | ||||
|                 if local_value is not None: | ||||
|                     float(local_value) | ||||
|             except (TypeError, ValueError): | ||||
|                 raise IntegrityError('non-float value in column "%s.%s" violates ' | ||||
|                                      'float 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 | ||||
|  | ||||
|     def formfield(self, **kwargs): | ||||
|         """Gets the form field associated with this field.""" | ||||
|         defaults = { | ||||
|             'form_class': LocalizedIntegerFieldForm | ||||
|         } | ||||
|  | ||||
|         defaults.update(kwargs) | ||||
|         return super().formfield(**defaults) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _convert_localized_value(value: LocalizedValue) -> LocalizedFloatValue: | ||||
|         """Converts from :see:LocalizedValue to :see:LocalizedFloatValue.""" | ||||
|  | ||||
|         float_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: | ||||
|                 float_values[lang_code] = float(local_value) | ||||
|             except (ValueError, TypeError): | ||||
|                 float_values[lang_code] = None | ||||
|  | ||||
|         return LocalizedFloatValue(float_values) | ||||
| @@ -206,7 +206,31 @@ class LocalizedFileValue(LocalizedValue): | ||||
|         return self.get(translation.get_language()) | ||||
|  | ||||
|  | ||||
| class LocalizedIntegerValue(LocalizedValue): | ||||
| class LocalizedNumericValue(LocalizedValue): | ||||
|     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) | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         """Returns string representation of value""" | ||||
|  | ||||
|         value = self.translate() | ||||
|         return str(value) if value is not None else '' | ||||
|  | ||||
|     def __float__(self): | ||||
|         """Gets the value in the current language as a float""" | ||||
|         value = self.translate() | ||||
|         if value is None: | ||||
|             return self.default_value | ||||
|  | ||||
|         return float(value) | ||||
|  | ||||
|  | ||||
| class LocalizedIntegerValue(LocalizedNumericValue): | ||||
|     """All values are integers.""" | ||||
|  | ||||
|     default_value = None | ||||
| @@ -221,17 +245,19 @@ class LocalizedIntegerValue(LocalizedValue): | ||||
|  | ||||
|         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 | ||||
| class LocalizedFloatValue(LocalizedNumericValue): | ||||
|     """All values are floats""" | ||||
|  | ||||
|         return int(value) | ||||
|     default_value = None | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         """Returns string representation of value""" | ||||
|     def translate(self): | ||||
|         """ | ||||
|         Gets the value in the current language, or in the configured | ||||
|         fallback language. | ||||
|         """ | ||||
|         value = super().translate() | ||||
|         if value is None or (isinstance(value, str) and value.strip() == ''): | ||||
|             return None | ||||
|  | ||||
|         value = self.translate() | ||||
|         return str(value) if value is not None else '' | ||||
|         return float(value) | ||||
|   | ||||
							
								
								
									
										4
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								setup.py
									
									
									
									
									
								
							| @@ -33,7 +33,7 @@ with open(os.path.join(os.path.dirname(__file__), 'README.rst'), encoding='utf-8 | ||||
|  | ||||
| setup( | ||||
|     name='django-localized-fields', | ||||
|     version='5.4', | ||||
|     version='5.4.2', | ||||
|     packages=find_packages(exclude=['tests']), | ||||
|     include_package_data=True, | ||||
|     license='MIT License', | ||||
| @@ -44,7 +44,7 @@ setup( | ||||
|     author_email='open-source@sectorlabs.ro', | ||||
|     keywords=['django', 'localized', 'language', 'models', 'fields'], | ||||
|     install_requires=[ | ||||
|         'django-postgres-extra>=1.22', | ||||
|         'django-postgres-extra>=1.22,<2.0', | ||||
|         'Django>=1.11', | ||||
|         'deprecation==2.0.7' | ||||
|     ], | ||||
|   | ||||
							
								
								
									
										176
									
								
								tests/test_float_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								tests/test_float_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 LocalizedFloatField | ||||
|  | ||||
| from .fake_model import get_fake_model | ||||
|  | ||||
|  | ||||
| class LocalizedFloatFieldTestCase(TestCase): | ||||
|     """Tests whether the :see:LocalizedFloatField | ||||
|     and :see:LocalizedFloatValue works properly.""" | ||||
|  | ||||
|     TestModel = None | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         super().setUpClass() | ||||
|  | ||||
|         cls.TestModel = get_fake_model({ | ||||
|             'score': LocalizedFloatField() | ||||
|         }) | ||||
|  | ||||
|     def test_basic(self): | ||||
|         """Tests the basics of storing float values.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||
|             obj.score.set(lang_code, index + 1.0) | ||||
|         obj.save() | ||||
|  | ||||
|         obj = self.TestModel.objects.all().first() | ||||
|         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||
|             assert obj.score.get(lang_code) == index + 1.0 | ||||
|  | ||||
|     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.0) | ||||
|             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.0) | ||||
|         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 float | ||||
|         results in the value being returned in the currently | ||||
|         active language as an float.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||
|             obj.score.set(lang_code, index + 1.0) | ||||
|         obj.save() | ||||
|  | ||||
|         obj.refresh_from_db() | ||||
|         for index, (lang_code, _) in enumerate(settings.LANGUAGES): | ||||
|             with translation.override(lang_code): | ||||
|                 assert float(obj.score) == index + 1.0 | ||||
|                 assert obj.score.translate() == index + 1.0 | ||||
|  | ||||
|     def test_translate_primary_fallback(self): | ||||
|         """Tests whether casting the value to an float | ||||
|         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.0) | ||||
|  | ||||
|         secondary_language = settings.LANGUAGES[-1][0] | ||||
|         assert obj.score.get(secondary_language) is None | ||||
|  | ||||
|         with translation.override(secondary_language): | ||||
|             assert obj.score.translate() == 25.0 | ||||
|             assert float(obj.score) == 25.0 | ||||
|  | ||||
|     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.0) | ||||
|  | ||||
|         secondary_language = settings.LANGUAGES[-1][0] | ||||
|         assert obj.score.get(secondary_language) is None | ||||
|         assert obj.score.get(secondary_language, 1337.0) == 1337.0 | ||||
|  | ||||
|     def test_completely_optional(self): | ||||
|         """Tests whether having all languages optional | ||||
|         works properly.""" | ||||
|  | ||||
|         model = get_fake_model({ | ||||
|             'score': LocalizedFloatField(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-float.""" | ||||
|  | ||||
|         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 | ||||
|         float.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         obj.score.set(settings.LANGUAGE_CODE, 25.0) | ||||
|         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': LocalizedFloatField(default={settings.LANGUAGE_CODE: 75.0}) | ||||
|         }) | ||||
|  | ||||
|         obj = model.objects.create() | ||||
|         assert obj.score.get(settings.LANGUAGE_CODE) == 75.0 | ||||
|  | ||||
|         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.0 | ||||
|             else: | ||||
|                 assert obj.score.get(lang_code) is None | ||||
		Reference in New Issue
	
	Block a user