import json from arches.app.models.models import DDataType from arches.app.datatypes.datatypes import DataTypeFactory from arches.app.models.fields.i18n import ( I18n_String, I18n_TextField, I18n_JSON, I18n_JSONField, ) from tests.base_test import ArchesTestCase from django.contrib.gis.db import models from django.utils import translation from django.db import connection # these tests can be run from the command line via # python manage.py test tests.localization.field_tests --settings="tests.test_settings" class Customi18nTextFieldTests(ArchesTestCase): @classmethod def setUpTestData(cls): super().setUpTestData() sql = """ CREATE TABLE public._localization_test_model ( name jsonb, id integer, PRIMARY KEY (id) ); ALTER TABLE public._localization_test_model OWNER to postgres; CREATE TABLE public._localization_test_model_w_nulls ( name jsonb, id integer, PRIMARY KEY (id) ); ALTER TABLE public._localization_test_model OWNER to postgres; """ with connection.cursor() as cursor: cursor.execute(sql) @classmethod def tearDownClass(cls): with connection.cursor() as cursor: cursor.execute("DROP TABLE public._localization_test_model") super().tearDownClass() class LocalizationTestModel(models.Model): name = I18n_TextField(null=False) id = models.IntegerField(primary_key=True) class Meta: app_label = "_test" managed = True db_table = "_localization_test_model" class LocalizationTestModelWNulls(models.Model): name = I18n_TextField(null=True) id = models.IntegerField(primary_key=True) class Meta: app_label = "_test" managed = True db_table = "_localization_test_model_w_nulls" def test_i18n_text_field(self): m = self.LocalizationTestModel() m.id = 1 translation.activate("en") m.name = "boat" m.save() translation.activate("es") m.name = "barco" m.save() # test that we can save a single localized value translation.activate("en") m = self.LocalizationTestModel.objects.get(pk=1) self.assertEqual(str(m.name), "boat") # test that we can save a different localized value to the same object translation.activate("es") m = self.LocalizationTestModel.objects.get(pk=1) self.assertEqual(str(m.name), "barco") # test that we can access the raw value with all languages self.assertEqual(m.name.raw_value, {"en": "boat", "es": "barco"}) # test that we can update just a single language translation.activate("en") m.name = "ship" m.save() m = self.LocalizationTestModel.objects.get(pk=1) self.assertEqual(str(m.name), "ship") self.assertEqual(m.name.raw_value, {"en": "ship", "es": "barco"}) def test_i18n_text_field_w_blank_strings(self): translation.activate("en") m = self.LocalizationTestModel() m.id = 2 m.save() self.assertEqual(str(m.name), "") self.assertEqual(m.name.raw_value, {"en": ""}) def test_i18n_text_field_w_nulls(self): translation.activate("en") m = self.LocalizationTestModelWNulls() m.id = 3 m.save() self.assertEqual(str(m.name), "") self.assertEqual(m.name.raw_value, {"en": None}) def test_i18n_text_field_return_default_language(self): # test that if the language code requested doesn't exist then return the defualt language instead translation.activate("en") m = self.LocalizationTestModel() m.id = 4 m.name = "one" m.save() translation.activate("de") m = self.LocalizationTestModel.objects.get(pk=4) self.assertEqual(str(m.name), "one") def test_init_i18n_text_field_w_dict(self): m = self.LocalizationTestModel() m.id = 5 m.name = {"en": "one", "es": "uno"} m.save() translation.activate("es") m = self.LocalizationTestModel.objects.get(pk=5) self.assertEqual(str(m.name), "uno") def test_init_i18n_text_field_w_json(self): m = self.LocalizationTestModel.objects.create( id=6, name='{"en": "one", "es": "uno"}' ) m.save() translation.activate("es") m = self.LocalizationTestModel.objects.get(pk=6) self.assertEqual(str(m.name), "uno") def test_init_i18n_text_field_w_i18n_string(self): m = self.LocalizationTestModel() m.name = I18n_String(value='{"en": "one", "es": "uno"}') m.id = 7 m.save() translation.activate("es") m = self.LocalizationTestModel.objects.get(pk=7) self.assertEqual(str(m.name), "uno") def test_init_i18n_text_field_w_i18n_string_dict(self): m = self.LocalizationTestModel() m.name = I18n_String(value={"en": "one", "es": "uno"}) m.id = 8 m.save() translation.activate("es") m = self.LocalizationTestModel.objects.get(pk=8) self.assertEqual(str(m.name), "uno") def test_init_i18n_text_field_w_null_update(self): m = self.LocalizationTestModelWNulls() m.name = I18n_String(value={"en": "one", "es": "uno"}) m.id = 9 m.save() translation.activate("es") m.name = None m.save() m = self.LocalizationTestModelWNulls.objects.get(pk=9) self.assertEqual(str(m.name), "") self.assertEqual(m.name.raw_value, {"en": "one", "es": None}) def test_i18n_text_field_data_consistency_before_and_after_save(self): translation.activate("en") m = self.LocalizationTestModel() m.name = "Marco" m.id = 10 self.assertEqual(str(m.name), "Marco") m.save() # test that post save everything is the same self.assertEqual(str(m.name), "Marco") # test that the object retrieved from the database is the same m = self.LocalizationTestModel.objects.get(pk=10) self.assertEqual(str(m.name), "Marco") self.assertEqual(m.name.raw_value, {"en": "Marco"}) def test_quoted_string_i18n_text_field_data_consistency_before_and_after_save(self): # re https://github.com/archesproject/arches/issues/9623 translation.activate("en") m = self.LocalizationTestModel() m.name = '"Hello World"' m.id = 11 self.assertEqual(str(m.name), '"Hello World"') m.save() # test that post save everything is the same self.assertEqual(str(m.name), '"Hello World"') # test that the object retrieved from the database is the same m = self.LocalizationTestModel.objects.get(pk=11) self.assertEqual(str(m.name), '"Hello World"') self.assertEqual(m.name.raw_value, {"en": '"Hello World"'}) def test_equality(self): value = I18n_String("toast") self.assertEqual(value, "toast") value = I18n_String('{"de": "genau", "en": "precisely"}') translation.activate("en") self.assertEqual(value, "precisely") translation.activate("de") self.assertEqual(value, "genau") class Customi18nJSONFieldTests(ArchesTestCase): @classmethod def setUpTestData(cls): super().setUpTestData() sql = """ CREATE TABLE public._localization_test_json_model ( config jsonb, id integer, PRIMARY KEY (id) ); ALTER TABLE public._localization_test_json_model OWNER to postgres; """ with connection.cursor() as cursor: cursor.execute(sql) @classmethod def tearDownClass(cls): with connection.cursor() as cursor: cursor.execute("DROP TABLE public._localization_test_json_model") super().tearDownClass() class LocalizationTestJsonModel(models.Model): config = I18n_JSONField(null=False) id = models.IntegerField(primary_key=True) class Meta: app_label = "_test" managed = True db_table = "_localization_test_json_model" def test_i18n_json_class(self): test_json = json.dumps( { "i18n_properties": ["placeholder"], "placeholder": {"en": "choose one", "es": "elija uno"}, "min_length": 19, } ) expected_output = json.dumps( { "i18n_properties": ["placeholder"], "placeholder": "choose one", "min_length": 19, } ) translation.activate("en") j = I18n_JSON(test_json) self.assertEqual(str(j), expected_output) def test_i18n_json_field(self): test_json = { "i18n_properties": ["placeholder"], "placeholder": {"en": "choose an option", "es": "elija uno"}, "min_length": 19, } translation.activate("en") m = self.LocalizationTestJsonModel() m.id = 1 m.config = test_json m.save() m = self.LocalizationTestJsonModel.objects.get(pk=1) self.assertEqual(m.config.raw_value, test_json) updated_input = json.dumps( { "i18n_properties": ["placeholder"], "placeholder": "choose one", "min_length": 19, } ) m.config = updated_input m.save() expected_output = { "i18n_properties": ["placeholder"], "placeholder": {"en": "choose one", "es": "elija uno"}, "min_length": 19, } m = self.LocalizationTestJsonModel.objects.get(pk=1) self.assertEqual(m.config.raw_value, expected_output) def test_i18n_json_field_multiple(self): test_json = { "i18n_properties": ["trueLabel", "falseLabel"], "trueLabel": {"en": "true", "es": "verdad"}, "falseLabel": {"en": "false", "es": "falso"}, "min_length": 19, } translation.activate("es") m = self.LocalizationTestJsonModel() m.id = 2 m.config = test_json m.save() m = self.LocalizationTestJsonModel.objects.get(pk=2) self.assertEqual(m.config.raw_value, test_json) self.assertEqual(json.loads(str(m.config))["trueLabel"], "verdad") self.assertEqual(json.loads(str(m.config))["falseLabel"], "falso") self.assertEqual(json.loads(str(m.config))["min_length"], 19) self.assertEqual( json.loads(str(m.config))["i18n_properties"], ["trueLabel", "falseLabel"] ) translation.activate("de") updated_input = json.dumps( { "i18n_properties": ["trueLabel", "falseLabel"], "trueLabel": "wahr", "falseLabel": "falsch", "min_length": 45, } ) m.config = updated_input m.save() expected_output = { "i18n_properties": ["trueLabel", "falseLabel"], "trueLabel": {"en": "true", "es": "verdad", "de": "wahr"}, "falseLabel": {"en": "false", "es": "falso", "de": "falsch"}, "min_length": 45, } m = self.LocalizationTestJsonModel.objects.get(pk=2) self.assertEqual(m.config.raw_value, expected_output) def test_i18n_json_class_as_dict(self): test_json = { "i18n_properties": ["placeholder"], "placeholder": {"en": "choose one", "es": "elija uno"}, "min_length": 19, } translation.activate("en") j = I18n_JSON(test_json) self.assertEqual(j["min_length"], test_json["min_length"]) self.assertTrue("min_length" in j) self.assertEqual(j.get("min_length"), 19) self.assertEqual( list(j.keys()), ["i18n_properties", "placeholder", "min_length"] ) self.assertEqual(j.pop("min_length"), 19) self.assertEqual(list(j.keys()), ["i18n_properties", "placeholder"]) # test item assignment j["new_property"] = "TACO" self.assertEqual( list(j.keys()), ["i18n_properties", "placeholder", "new_property"] ) def test_i18nJSONField_can_handle_different_initial_states(self): initial_json = { "trueLabel": None, "falseLabel": {}, "altLabel": "", "min_length": 19, } json_to_save = { "i18n_properties": ["trueLabel", "falseLabel", "altLabel"], "trueLabel": "YES", "falseLabel": "NO", "altLabel": "taco", "min_length": 19, } expected_output_json = { "i18n_properties": ["trueLabel", "falseLabel", "altLabel"], "trueLabel": {"en": "YES"}, "falseLabel": {"en": "NO"}, "altLabel": {"en": "taco"}, "min_length": 19, } translation.activate("en") m = self.LocalizationTestJsonModel() m.id = 3 m.config = initial_json m.save() m = self.LocalizationTestJsonModel.objects.get(pk=3) self.assertEqual(m.config.raw_value, initial_json) m.config = json_to_save m.save() m = self.LocalizationTestJsonModel.objects.get(pk=3) self.assertEqual(m.config.raw_value, expected_output_json) class I18nJSONFieldBulkUpdateTests(ArchesTestCase): def test_bulk_update_node_config_homogenous_value(self): new_config = I18n_JSON( { "en": "some", "zh": "json", } ) for_bulk_update = [] for dt in DDataType.objects.all()[:3]: dt.defaultconfig = new_config for_bulk_update.append(dt) DDataType.objects.bulk_update(for_bulk_update, fields=["defaultconfig"]) for i, obj in enumerate(for_bulk_update): with self.subTest(obj_index=i): obj.refresh_from_db() self.assertEqual(str(obj.defaultconfig), str(new_config)) def test_bulk_update_heterogenous_values(self): new_configs = [ I18n_JSON( { "en": "some", "zh": "json", } ), I18n_JSON({}), None, ] for_bulk_update = [] for i, dt in enumerate(DDataType.objects.all()[:3]): dt.defaultconfig = new_configs[i] for_bulk_update.append(dt) with self.assertRaises(NotImplementedError): DDataType.objects.bulk_update(for_bulk_update, fields=["defaultconfig"]) # If the above starts failing, it's likely the underlying Django # regression was fixed. # https://code.djangoproject.com/ticket/35167 # In that case, remove the with statement, de-indent the bulk_update, # and comment the following code back in: # for i, obj in enumerate(for_bulk_update): # new_config_as_string = str(new_configs[i]) # with self.subTest(new_config=new_config_as_string): # obj.refresh_from_db() # self.assertEqual(str(obj.defaultconfig), new_config_as_string) # Also consider removing the code at the top of I18n_JSON._parse() class AsSqlTests(ArchesTestCase): def test_domain_datatype(self): datatype = DataTypeFactory().get_instance("domain-value") domain_value = I18n_JSON({"en": "it's a bird"}) # Apostrophe in "it's" is doubly-escaped so it doesn't close the string expected = "jsonb_set(None, array['en'], '\"it''s a bird\"')" self.assertEqual(datatype.i18n_as_sql(domain_value, None, None), expected)