mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-10-28 01:48:57 +03:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a21ff53cb9 | ||
|  | e4bd26ece2 | 
| @@ -5,6 +5,7 @@ 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 | from .integer_field import LocalizedIntegerField | ||||||
|  | from .float_field import LocalizedFloatField | ||||||
|  |  | ||||||
|  |  | ||||||
| __all__ = [ | __all__ = [ | ||||||
| @@ -14,7 +15,8 @@ __all__ = [ | |||||||
|     'LocalizedCharField', |     'LocalizedCharField', | ||||||
|     'LocalizedTextField', |     'LocalizedTextField', | ||||||
|     'LocalizedFileField', |     'LocalizedFileField', | ||||||
|     'LocalizedIntegerField' |     'LocalizedIntegerField', | ||||||
|  |     'LocalizedFloatField' | ||||||
| ] | ] | ||||||
|  |  | ||||||
| try: | 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()) |         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.""" |     """All values are integers.""" | ||||||
|  |  | ||||||
|     default_value = None |     default_value = None | ||||||
| @@ -221,17 +245,19 @@ class LocalizedIntegerValue(LocalizedValue): | |||||||
|  |  | ||||||
|         return int(value) |         return int(value) | ||||||
|  |  | ||||||
|     def __int__(self): |  | ||||||
|         """Gets the value in the current language as an integer.""" |  | ||||||
|  |  | ||||||
|         value = self.translate() | class LocalizedFloatValue(LocalizedNumericValue): | ||||||
|         if value is None: |     """All values are floats""" | ||||||
|             return self.default_value |  | ||||||
|  |  | ||||||
|         return int(value) |     default_value = None | ||||||
|  |  | ||||||
|     def __str__(self) -> str: |     def translate(self): | ||||||
|         """Returns string representation of value""" |         """ | ||||||
|  |         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 float(value) | ||||||
|         return str(value) if value is not None else '' |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -33,7 +33,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='5.4.1', |     version='5.4.2', | ||||||
|     packages=find_packages(exclude=['tests']), |     packages=find_packages(exclude=['tests']), | ||||||
|     include_package_data=True, |     include_package_data=True, | ||||||
|     license='MIT License', |     license='MIT License', | ||||||
|   | |||||||
							
								
								
									
										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