mirror of
				https://github.com/SectorLabs/django-localized-fields.git
				synced 2025-10-26 17:08:58 +03:00 
			
		
		
		
	Compare commits
	
		
			19 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fbc1eec754 | ||
|  | 911251ebaa | ||
|  | a9906dd159 | ||
|  | a66b3492cd | ||
|  | bc494694f5 | ||
|  | b0cfaea2b4 | ||
|  | 8c7d0773f7 | ||
|  | cc911d4909 | ||
|  | 0f30cc1493 | ||
|  | 0d1e9510cf | ||
|  | 25c1c24ccb | ||
|  | bd3005a7e9 | ||
|  | 7902d8225a | ||
|  | f024e4feb5 | ||
|  | 92cb5e8b1f | ||
|  | 5c298ef13e | ||
|  | 1b3e5989d3 | ||
|  | d57f9a41bb | ||
|  | bd8924224e | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -15,6 +15,7 @@ reports/ | ||||
| # Ignore build results | ||||
| *.egg-info/ | ||||
| dist/ | ||||
| build/ | ||||
| pip-wheel-metadata | ||||
|  | ||||
| # Ignore stupid .DS_Store | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| | :white_check_mark: | **Tests** | [](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) | | ||||
| | :memo: | **License** | [](http://doge.mit-license.org) | | ||||
| | :package: | **PyPi** | [](https://pypi.python.org/pypi/django-localized-fields) | | ||||
| | <img src="https://icon-library.net/images/django-icon/django-icon-0.jpg" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2, 3.0, 3.1 | | ||||
| | <img src="https://cdn.iconscout.com/icon/free/png-256/django-1-282754.png" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2, 3.0, 3.1, 3.2 | | ||||
| | <img src="http://www.iconarchive.com/download/i73027/cornmanthe3rd/plex/Other-python.ico" width="22px" height="22px" align="center" /> | **Python Versions** | 3.6, 3.7, 3.8, 3.9 | | ||||
| | :book: | **Documentation** | [Read The Docs](https://django-localized-fields.readthedocs.io) | | ||||
| | :warning: | **Upgrade** | [Upgrade fom v5.x](https://django-localized-fields.readthedocs.io/en/latest/releases.html#v6-0) | ||||
| @@ -38,7 +38,7 @@ | ||||
|  | ||||
| 3. Create a postgres user for use in tests (skip if your default user is a postgres superuser): | ||||
|  | ||||
|        λ createuser --superuser psqlextra --pwprompt | ||||
|        λ createuser --superuser localized_fields --pwprompt | ||||
|        λ export DATABASE_URL=postgres://localized_fields:<password>@localhost/localized_fields | ||||
|  | ||||
|    Hint: if you're using virtualenvwrapper, you might find it beneficial to put | ||||
|   | ||||
| @@ -1 +1,4 @@ | ||||
| default_app_config = "localized_fields.apps.LocalizedFieldsConfig" | ||||
| import django | ||||
|  | ||||
| if django.VERSION < (3, 2): | ||||
|     default_app_config = "localized_fields.apps.LocalizedFieldsConfig" | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from . import widgets | ||||
| from .fields import ( | ||||
|     LocalizedBooleanField, | ||||
|     LocalizedCharField, | ||||
|     LocalizedField, | ||||
|     LocalizedFileField, | ||||
| @@ -11,6 +12,7 @@ FORMFIELD_FOR_LOCALIZED_FIELDS_DEFAULTS = { | ||||
|     LocalizedCharField: {"widget": widgets.AdminLocalizedCharFieldWidget}, | ||||
|     LocalizedTextField: {"widget": widgets.AdminLocalizedFieldWidget}, | ||||
|     LocalizedFileField: {"widget": widgets.AdminLocalizedFileFieldWidget}, | ||||
|     LocalizedBooleanField: {"widget": widgets.AdminLocalizedBooleanFieldWidget}, | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| from .autoslug_field import LocalizedAutoSlugField | ||||
| from .boolean_field import LocalizedBooleanField | ||||
| from .char_field import LocalizedCharField | ||||
| from .field import LocalizedField | ||||
| from .file_field import LocalizedFileField | ||||
| @@ -16,6 +17,7 @@ __all__ = [ | ||||
|     "LocalizedFileField", | ||||
|     "LocalizedIntegerField", | ||||
|     "LocalizedFloatField", | ||||
|     "LocalizedBooleanField", | ||||
| ] | ||||
|  | ||||
| try: | ||||
|   | ||||
| @@ -16,14 +16,14 @@ from .field import LocalizedField | ||||
| class LocalizedAutoSlugField(LocalizedField): | ||||
|     """Automatically provides slugs for a localized field upon saving.""" | ||||
|  | ||||
|     warnings.warn( | ||||
|         "LocalizedAutoSlug is deprecated and will be removed in the next major version.", | ||||
|         DeprecationWarning, | ||||
|     ) | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         """Initializes a new instance of :see:LocalizedAutoSlugField.""" | ||||
|  | ||||
|         warnings.warn( | ||||
|             "LocalizedAutoSlug is deprecated and will be removed in the next major version.", | ||||
|             DeprecationWarning, | ||||
|         ) | ||||
|  | ||||
|         self.populate_from = kwargs.pop("populate_from", None) | ||||
|         self.include_time = kwargs.pop("include_time", False) | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import html | ||||
|  | ||||
| from django.conf import settings | ||||
|  | ||||
| from .field import LocalizedField | ||||
| @@ -7,6 +9,13 @@ class LocalizedBleachField(LocalizedField): | ||||
|     """Custom version of :see:BleachField that is actually a | ||||
|     :see:LocalizedField.""" | ||||
|  | ||||
|     def __init__(self, *args, escape=True, **kwargs): | ||||
|         """Initializes a new instance of :see:LocalizedBleachField.""" | ||||
|  | ||||
|         self.escape = escape | ||||
|  | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|     def pre_save(self, instance, add: bool): | ||||
|         """Ran just before the model is saved, allows us to built the slug. | ||||
|  | ||||
| @@ -42,8 +51,14 @@ class LocalizedBleachField(LocalizedField): | ||||
|             if not value: | ||||
|                 continue | ||||
|  | ||||
|             cleaned_value = bleach.clean( | ||||
|                 value if self.escape else html.unescape(value), | ||||
|                 **get_bleach_default_options() | ||||
|             ) | ||||
|  | ||||
|             localized_value.set( | ||||
|                 lang_code, bleach.clean(value, **get_bleach_default_options()) | ||||
|                 lang_code, | ||||
|                 cleaned_value if self.escape else html.unescape(cleaned_value), | ||||
|             ) | ||||
|  | ||||
|         return localized_value | ||||
|   | ||||
							
								
								
									
										110
									
								
								localized_fields/fields/boolean_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								localized_fields/fields/boolean_field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| from typing import Dict, Optional, Union | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.db.utils import IntegrityError | ||||
|  | ||||
| from ..forms import LocalizedBooleanFieldForm | ||||
| from ..value import LocalizedBooleanValue, LocalizedValue | ||||
| from .field import LocalizedField | ||||
|  | ||||
|  | ||||
| class LocalizedBooleanField(LocalizedField): | ||||
|     """Stores booleans as a localized value.""" | ||||
|  | ||||
|     attr_class = LocalizedBooleanValue | ||||
|  | ||||
|     @classmethod | ||||
|     def from_db_value(cls, value, *_) -> Optional[LocalizedBooleanValue]: | ||||
|         db_value = super().from_db_value(value) | ||||
|  | ||||
|         if db_value is None: | ||||
|             return db_value | ||||
|  | ||||
|         if isinstance(db_value, str): | ||||
|             if db_value.lower() == "true": | ||||
|                 return True | ||||
|             return False | ||||
|  | ||||
|         if not isinstance(db_value, LocalizedValue): | ||||
|             return db_value | ||||
|  | ||||
|         return cls._convert_localized_value(db_value) | ||||
|  | ||||
|     def to_python( | ||||
|         self, value: Union[Dict[str, str], str, None] | ||||
|     ) -> LocalizedBooleanValue: | ||||
|         """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: LocalizedBooleanValue) -> dict: | ||||
|         """Gets the value in a format to store into the database.""" | ||||
|  | ||||
|         # apply default values | ||||
|         default_values = LocalizedBooleanValue(self.default) | ||||
|         if isinstance(value, LocalizedBooleanValue): | ||||
|             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 values to be converted to bool | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             local_value = prepped_value[lang_code] | ||||
|  | ||||
|             if local_value is not None and local_value.lower() not in ( | ||||
|                 "false", | ||||
|                 "true", | ||||
|             ): | ||||
|                 raise IntegrityError( | ||||
|                     'non-boolean value in column "%s.%s" violates ' | ||||
|                     "boolean 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": LocalizedBooleanFieldForm} | ||||
|  | ||||
|         defaults.update(kwargs) | ||||
|         return super().formfield(**defaults) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _convert_localized_value( | ||||
|         value: LocalizedValue, | ||||
|     ) -> LocalizedBooleanValue: | ||||
|         """Converts from :see:LocalizedValue to :see:LocalizedBooleanValue.""" | ||||
|  | ||||
|         integer_values = {} | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             local_value = value.get(lang_code, None) | ||||
|  | ||||
|             if isinstance(local_value, str): | ||||
|                 if local_value.lower() == "false": | ||||
|                     local_value = False | ||||
|                 elif local_value.lower() == "true": | ||||
|                     local_value = True | ||||
|                 else: | ||||
|                     raise ValueError( | ||||
|                         f"Could not convert value {local_value} to boolean." | ||||
|                     ) | ||||
|  | ||||
|                 integer_values[lang_code] = local_value | ||||
|             elif local_value is not None: | ||||
|                 raise TypeError( | ||||
|                     f"Expected value of type str instead of {type(local_value)}." | ||||
|                 ) | ||||
|  | ||||
|         return LocalizedBooleanValue(integer_values) | ||||
| @@ -32,6 +32,7 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): | ||||
|  | ||||
|         kwargs["uniqueness"] = kwargs.pop("uniqueness", get_language_codes()) | ||||
|  | ||||
|         self.enabled = kwargs.pop("enabled", True) | ||||
|         self.immutable = kwargs.pop("immutable", False) | ||||
|  | ||||
|         super(LocalizedUniqueSlugField, self).__init__(*args, **kwargs) | ||||
| @@ -49,6 +50,9 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): | ||||
|         kwargs["populate_from"] = self.populate_from | ||||
|         kwargs["include_time"] = self.include_time | ||||
|  | ||||
|         if self.enabled is False: | ||||
|             kwargs["enabled"] = self.enabled | ||||
|  | ||||
|         if self.immutable is True: | ||||
|             kwargs["immutable"] = self.immutable | ||||
|  | ||||
| @@ -69,6 +73,9 @@ class LocalizedUniqueSlugField(LocalizedAutoSlugField): | ||||
|             The localized slug that was generated. | ||||
|         """ | ||||
|  | ||||
|         if not self.enabled: | ||||
|             return getattr(instance, self.name) | ||||
|  | ||||
|         if not isinstance(instance, AtomicSlugRetryMixin): | ||||
|             raise ImproperlyConfigured( | ||||
|                 ( | ||||
|   | ||||
| @@ -6,12 +6,14 @@ from django.core.exceptions import ValidationError | ||||
| from django.forms.widgets import FILE_INPUT_CONTRADICTION | ||||
|  | ||||
| from .value import ( | ||||
|     LocalizedBooleanValue, | ||||
|     LocalizedFileValue, | ||||
|     LocalizedIntegerValue, | ||||
|     LocalizedStringValue, | ||||
|     LocalizedValue, | ||||
| ) | ||||
| from .widgets import ( | ||||
|     AdminLocalizedBooleanFieldWidget, | ||||
|     AdminLocalizedIntegerFieldWidget, | ||||
|     LocalizedCharFieldWidget, | ||||
|     LocalizedFieldWidget, | ||||
| @@ -102,6 +104,15 @@ class LocalizedIntegerFieldForm(LocalizedFieldForm): | ||||
|     value_class = LocalizedIntegerValue | ||||
|  | ||||
|  | ||||
| class LocalizedBooleanFieldForm(LocalizedFieldForm, forms.BooleanField): | ||||
|     """Form for a localized boolean field, allows editing the field in multiple | ||||
|     languages.""" | ||||
|  | ||||
|     widget = AdminLocalizedBooleanFieldWidget | ||||
|     field_class = forms.fields.BooleanField | ||||
|     value_class = LocalizedBooleanValue | ||||
|  | ||||
|  | ||||
| class LocalizedFileFieldForm(LocalizedFieldForm, forms.FileField): | ||||
|     """Form for a localized file field, allows editing the field in multiple | ||||
|     languages.""" | ||||
|   | ||||
| @@ -136,6 +136,15 @@ class LocalizedValue(dict): | ||||
|  | ||||
|         return None | ||||
|  | ||||
|     def is_empty(self) -> bool: | ||||
|         """Gets whether all the languages contain the default value.""" | ||||
|  | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             if self.get(lang_code) != self.default_value: | ||||
|                 return False | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     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.""" | ||||
| @@ -228,6 +237,35 @@ class LocalizedFileValue(LocalizedValue): | ||||
|         return self.get(translation.get_language()) | ||||
|  | ||||
|  | ||||
| class LocalizedBooleanValue(LocalizedValue): | ||||
|     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 | ||||
|  | ||||
|         if isinstance(value, bool): | ||||
|             return value | ||||
|  | ||||
|         if value.lower() == "true": | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def __bool__(self): | ||||
|         """Gets the value in the current language as a boolean.""" | ||||
|         value = self.translate() | ||||
|  | ||||
|         return value | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Returns string representation of value.""" | ||||
|  | ||||
|         value = self.translate() | ||||
|         return str(value) if value is not None else "" | ||||
|  | ||||
|  | ||||
| class LocalizedNumericValue(LocalizedValue): | ||||
|     def __int__(self): | ||||
|         """Gets the value in the current language as an integer.""" | ||||
|   | ||||
| @@ -120,6 +120,18 @@ class AdminLocalizedFieldWidget(LocalizedFieldWidget): | ||||
|     widget = widgets.AdminTextareaWidget | ||||
|  | ||||
|  | ||||
| class AdminLocalizedBooleanFieldWidget(LocalizedFieldWidget): | ||||
|     widget = forms.Select | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         """Initializes a new instance of :see:LocalizedBooleanFieldWidget.""" | ||||
|  | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|         for widget in self.widgets: | ||||
|             widget.choices = [("False", False), ("True", True)] | ||||
|  | ||||
|  | ||||
| class AdminLocalizedCharFieldWidget(AdminLocalizedFieldWidget): | ||||
|     widget = widgets.AdminTextInputWidget | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ SECRET_KEY = 'this is my secret key'  # NOQA | ||||
| TEST_RUNNER = 'django.test.runner.DiscoverRunner' | ||||
|  | ||||
| DATABASES = { | ||||
|     'default': dj_database_url.config(default='postgres:///psqlextra'), | ||||
|     'default': dj_database_url.config(default='postgres:///localized_fields'), | ||||
| } | ||||
|  | ||||
| DATABASES['default']['ENGINE'] = 'psqlextra.backend' | ||||
|   | ||||
| @@ -9,4 +9,4 @@ lines_between_types=1 | ||||
| include_trailing_comma=True | ||||
| not_skip=__init__.py | ||||
| known_standard_library=dataclasses | ||||
| known_third_party=django_bleach,bleach | ||||
| known_third_party=django_bleach,bleach,pytest | ||||
|   | ||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							| @@ -36,7 +36,7 @@ with open( | ||||
|  | ||||
| setup( | ||||
|     name="django-localized-fields", | ||||
|     version="6.2", | ||||
|     version="6.8b1", | ||||
|     packages=find_packages(exclude=["tests"]), | ||||
|     include_package_data=True, | ||||
|     license="MIT License", | ||||
| @@ -64,6 +64,9 @@ setup( | ||||
|         "Operating System :: OS Independent", | ||||
|         "Programming Language :: Python", | ||||
|         "Programming Language :: Python :: 3.6", | ||||
|         "Programming Language :: Python :: 3.7", | ||||
|         "Programming Language :: Python :: 3.8", | ||||
|         "Programming Language :: Python :: 3.9", | ||||
|         "Topic :: Internet :: WWW/HTTP", | ||||
|         "Topic :: Internet :: WWW/HTTP :: Dynamic Content", | ||||
|     ], | ||||
| @@ -87,7 +90,7 @@ setup( | ||||
|             "psycopg2==2.8.4", | ||||
|         ], | ||||
|         "analysis": [ | ||||
|             "black==19.3b0", | ||||
|             "black==22.3.0", | ||||
|             "flake8==3.7.7", | ||||
|             "autoflake==1.3", | ||||
|             "autopep8==1.4.4", | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| """isort:skip_file.""" | ||||
|  | ||||
| import sys | ||||
| import html | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| @@ -66,14 +67,24 @@ class LocalizedBleachFieldTestCase(TestCase): | ||||
|         bleached_value = field.pre_save(model, False) | ||||
|         self._validate(value, bleached_value) | ||||
|  | ||||
|     def test_pre_save_do_not_escape(self): | ||||
|         """Tests whether the :see:pre_save function works properly when field | ||||
|         escape argument is set to False.""" | ||||
|  | ||||
|         value = self._get_test_value() | ||||
|         model, field = self._get_test_model(value, escape=False) | ||||
|  | ||||
|         bleached_value = field.pre_save(model, False) | ||||
|         self._validate(value, bleached_value, False) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _get_test_model(value): | ||||
|         """Gets a test model and a artifically constructed | ||||
|     def _get_test_model(value, escape=True): | ||||
|         """Gets a test model and an artificially constructed | ||||
|         :see:LocalizedBleachField instance to test with.""" | ||||
|  | ||||
|         model = ModelTest(value) | ||||
|  | ||||
|         field = LocalizedBleachField() | ||||
|         field = LocalizedBleachField(escape=escape) | ||||
|         field.attname = "value" | ||||
|         return model, field | ||||
|  | ||||
| @@ -89,7 +100,7 @@ class LocalizedBleachFieldTestCase(TestCase): | ||||
|         return value | ||||
|  | ||||
|     @staticmethod | ||||
|     def _validate(non_bleached_value, bleached_value): | ||||
|     def _validate(non_bleached_value, bleached_value, escaped_value=True): | ||||
|         """Validates whether the specified non-bleached value ended up being | ||||
|         correctly bleached. | ||||
|  | ||||
| @@ -100,14 +111,20 @@ class LocalizedBleachFieldTestCase(TestCase): | ||||
|             bleached_value: | ||||
|                 The value after bleaching. | ||||
|         """ | ||||
|  | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             if not non_bleached_value.get(lang_code): | ||||
|                 assert not bleached_value.get(lang_code) | ||||
|                 continue | ||||
|  | ||||
|             expected_value = bleach.clean( | ||||
|                 non_bleached_value.get(lang_code), get_bleach_default_options() | ||||
|             cleaned_value = bleach.clean( | ||||
|                 non_bleached_value.get(lang_code) | ||||
|                 if escaped_value | ||||
|                 else html.unescape(non_bleached_value.get(lang_code)), | ||||
|                 get_bleach_default_options(), | ||||
|             ) | ||||
|  | ||||
|             expected_value = ( | ||||
|                 cleaned_value if escaped_value else html.unescape(cleaned_value) | ||||
|             ) | ||||
|  | ||||
|             assert bleached_value.get(lang_code) == expected_value | ||||
|   | ||||
							
								
								
									
										211
									
								
								tests/test_boolean_field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								tests/test_boolean_field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| from django.conf import settings | ||||
| from django.db import connection | ||||
| from django.db.utils import IntegrityError | ||||
| from django.test import TestCase | ||||
| from django.utils import translation | ||||
|  | ||||
| from localized_fields.fields import LocalizedBooleanField | ||||
| from localized_fields.value import LocalizedBooleanValue | ||||
|  | ||||
| from .fake_model import get_fake_model | ||||
|  | ||||
|  | ||||
| class LocalizedBooleanFieldTestCase(TestCase): | ||||
|     """Tests whether the :see:LocalizedBooleanField and | ||||
|     :see:LocalizedIntegerValue works properly.""" | ||||
|  | ||||
|     TestModel = None | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         super().setUpClass() | ||||
|  | ||||
|         cls.TestModel = get_fake_model({"translated": LocalizedBooleanField()}) | ||||
|  | ||||
|     def test_basic(self): | ||||
|         """Tests the basics of storing boolean values.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             obj.translated.set(lang_code, False) | ||||
|         obj.save() | ||||
|  | ||||
|         obj = self.TestModel.objects.all().first() | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             assert obj.translated.get(lang_code) is False | ||||
|  | ||||
|     def test_primary_language_required(self): | ||||
|         """Tests whether the primary language is required by default and all | ||||
|         other languages are optional.""" | ||||
|  | ||||
|         # 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.translated.set(lang_code, True) | ||||
|             obj.save() | ||||
|  | ||||
|     def test_default_value_none(self): | ||||
|         """Tests whether the default value for optional languages is | ||||
|         NoneType.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         obj.translated.set(settings.LANGUAGE_CODE, True) | ||||
|         obj.save() | ||||
|  | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             if lang_code == settings.LANGUAGE_CODE: | ||||
|                 continue | ||||
|  | ||||
|             assert obj.translated.get(lang_code) is None | ||||
|  | ||||
|     def test_translate(self): | ||||
|         """Tests whether casting the value to a boolean results in the value | ||||
|         being returned in the currently active language as a boolean.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             obj.translated.set(lang_code, True) | ||||
|         obj.save() | ||||
|  | ||||
|         obj.refresh_from_db() | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             with translation.override(lang_code): | ||||
|                 assert bool(obj.translated) is True | ||||
|                 assert obj.translated.translate() is True | ||||
|  | ||||
|     def test_translate_primary_fallback(self): | ||||
|         """Tests whether casting the value to a boolean results in the value | ||||
|         being returned in the active language and falls back to the primary | ||||
|         language if there is no value in that language.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         obj.translated.set(settings.LANGUAGE_CODE, True) | ||||
|  | ||||
|         secondary_language = settings.LANGUAGES[-1][0] | ||||
|         assert obj.translated.get(secondary_language) is None | ||||
|  | ||||
|         with translation.override(secondary_language): | ||||
|             assert obj.translated.translate() is True | ||||
|             assert bool(obj.translated) is True | ||||
|  | ||||
|     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.translated.set(settings.LANGUAGE_CODE, True) | ||||
|  | ||||
|         secondary_language = settings.LANGUAGES[-1][0] | ||||
|         assert obj.translated.get(secondary_language) is None | ||||
|         assert obj.translated.get(secondary_language, False) is False | ||||
|  | ||||
|     def test_completely_optional(self): | ||||
|         """Tests whether having all languages optional works properly.""" | ||||
|  | ||||
|         model = get_fake_model( | ||||
|             { | ||||
|                 "translated": LocalizedBooleanField( | ||||
|                     null=True, required=[], blank=True | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         obj = model() | ||||
|         obj.save() | ||||
|  | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             assert getattr(obj.translated, lang_code) is None | ||||
|  | ||||
|     def test_store_string(self): | ||||
|         """Tests whether the field properly raises an error when trying to | ||||
|         store a non-boolean.""" | ||||
|  | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             obj = self.TestModel() | ||||
|             with self.assertRaises(IntegrityError): | ||||
|                 obj.translated.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 a boolean.""" | ||||
|  | ||||
|         obj = self.TestModel() | ||||
|         obj.translated.set(settings.LANGUAGE_CODE, False) | ||||
|         obj.save() | ||||
|  | ||||
|         with connection.cursor() as cursor: | ||||
|             table_name = self.TestModel._meta.db_table | ||||
|             cursor.execute("update %s set translated = 'en=>haha'" % table_name) | ||||
|  | ||||
|         with self.assertRaises(ValueError): | ||||
|             obj.refresh_from_db() | ||||
|  | ||||
|     def test_default_value(self): | ||||
|         """Tests whether a default is properly set when specified.""" | ||||
|  | ||||
|         model = get_fake_model( | ||||
|             { | ||||
|                 "translated": LocalizedBooleanField( | ||||
|                     default={settings.LANGUAGE_CODE: True} | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         obj = model.objects.create() | ||||
|         assert obj.translated.get(settings.LANGUAGE_CODE) is True | ||||
|  | ||||
|         obj = model() | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             obj.translated.set(lang_code, None) | ||||
|         obj.save() | ||||
|  | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             if lang_code == settings.LANGUAGE_CODE: | ||||
|                 assert obj.translated.get(lang_code) is True | ||||
|             else: | ||||
|                 assert obj.translated.get(lang_code) is None | ||||
|  | ||||
|     def test_default_value_update(self): | ||||
|         """Tests whether a default is properly set when specified during | ||||
|         updates.""" | ||||
|  | ||||
|         model = get_fake_model( | ||||
|             { | ||||
|                 "translated": LocalizedBooleanField( | ||||
|                     default={settings.LANGUAGE_CODE: True}, null=True | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         obj = model.objects.create( | ||||
|             translated=LocalizedBooleanValue({settings.LANGUAGE_CODE: False}) | ||||
|         ) | ||||
|         assert obj.translated.get(settings.LANGUAGE_CODE) is False | ||||
|  | ||||
|         model.objects.update( | ||||
|             translated=LocalizedBooleanValue({settings.LANGUAGE_CODE: None}) | ||||
|         ) | ||||
|         obj.refresh_from_db() | ||||
|         assert obj.translated.get(settings.LANGUAGE_CODE) is True | ||||
|  | ||||
|     def test_callable_default_value(self): | ||||
|         output = {"en": True} | ||||
|  | ||||
|         def func(): | ||||
|             return output | ||||
|  | ||||
|         model = get_fake_model({"test": LocalizedBooleanField(default=func)}) | ||||
|         obj = model.objects.create() | ||||
|  | ||||
|         assert obj.test["en"] == output["en"] | ||||
| @@ -121,8 +121,8 @@ class LocalizedFieldTestCase(TestCase): | ||||
|  | ||||
|     @staticmethod | ||||
|     def test_get_prep_value(): | ||||
|         """"Tests whether the :see:get_prep_value function produces the | ||||
|         expected dictionary.""" | ||||
|         """Tests whether the :see:get_prep_value function produces the expected | ||||
|         dictionary.""" | ||||
|  | ||||
|         input_data = get_init_values() | ||||
|         localized_value = LocalizedValue(input_data) | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| import copy | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from django import forms | ||||
| from django.conf import settings | ||||
| from django.db import models | ||||
| @@ -215,6 +217,27 @@ class LocalizedSlugFieldTestCase(TestCase): | ||||
|         for lang_code, lang_name in settings.LANGUAGES: | ||||
|             assert obj.slug.get(lang_code) == "title-%s" % lang_name.lower() | ||||
|  | ||||
|     @classmethod | ||||
|     def test_disable(cls): | ||||
|         """Tests whether disabling auto-slugging works.""" | ||||
|  | ||||
|         Model = get_fake_model( | ||||
|             { | ||||
|                 "title": LocalizedField(), | ||||
|                 "slug": LocalizedUniqueSlugField( | ||||
|                     populate_from="title", enabled=False | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         obj = Model() | ||||
|         obj.title = "test" | ||||
|  | ||||
|         # should raise IntegrityError because auto-slugging | ||||
|         # is disabled and the slug field is NULL | ||||
|         with pytest.raises(IntegrityError): | ||||
|             obj.save() | ||||
|  | ||||
|     @classmethod | ||||
|     def test_allows_override_when_immutable(cls): | ||||
|         """Tests whether setting a value manually works and does not get | ||||
| @@ -274,10 +297,13 @@ class LocalizedSlugFieldTestCase(TestCase): | ||||
|         """Tests whether the :see:deconstruct function properly retains options | ||||
|         specified in the constructor.""" | ||||
|  | ||||
|         field = LocalizedUniqueSlugField(populate_from="title") | ||||
|         field = LocalizedUniqueSlugField( | ||||
|             enabled=False, immutable=True, populate_from="title" | ||||
|         ) | ||||
|         _, _, _, kwargs = field.deconstruct() | ||||
|  | ||||
|         assert "populate_from" in kwargs | ||||
|         assert not kwargs["enabled"] | ||||
|         assert kwargs["immutable"] | ||||
|         assert kwargs["populate_from"] == field.populate_from | ||||
|  | ||||
|     @staticmethod | ||||
|   | ||||
| @@ -38,6 +38,17 @@ class LocalizedValueTestCase(TestCase): | ||||
|         for lang_code, _ in settings.LANGUAGES: | ||||
|             assert getattr(value, lang_code) is None | ||||
|  | ||||
|     @staticmethod | ||||
|     def test_is_empty(): | ||||
|         """Tests whether a newly constructed :see:LocalizedValue without any | ||||
|         content is considered "empty".""" | ||||
|  | ||||
|         value = LocalizedValue() | ||||
|         assert value.is_empty() | ||||
|  | ||||
|         value.set(settings.LANGUAGE_CODE, "my value") | ||||
|         assert not value.is_empty() | ||||
|  | ||||
|     @staticmethod | ||||
|     def test_init_array(): | ||||
|         """Tests whether the __init__ function of :see:LocalizedValue properly | ||||
|   | ||||
		Reference in New Issue
	
	Block a user