From 879330b391982cfe2223986e09224425a626dedf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 4 Jan 2025 19:52:10 -0500 Subject: [PATCH] edits --- .pre-commit-config.yaml | 2 +- django_mongodb/forms/array.py | 6 +- django_mongodb/utils.py | 6 +- django_mongodb/validators.py | 18 ++++ docs/source/_ext/djangodocs.py | 5 ++ docs/source/forms.rst | 7 ++ tests/model_fields_/test_arrayfield.py | 110 +++++++++++++------------ 7 files changed, 92 insertions(+), 62 deletions(-) create mode 100644 django_mongodb/validators.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30ae1ea4..2ceb70d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: hooks: - id: rstcheck additional_dependencies: [sphinx] - args: ["--ignore-directives=fieldlookup", "--ignore-roles=lookup"] + args: ["--ignore-directives=fieldlookup,setting", "--ignore-roles=lookup,setting"] # We use the Python version instead of the original version which seems to require Docker # https://github.com/koalaman/shellcheck-precommit diff --git a/django_mongodb/forms/array.py b/django_mongodb/forms/array.py index cd45dff4..1caa97af 100644 --- a/django_mongodb/forms/array.py +++ b/django_mongodb/forms/array.py @@ -2,13 +2,11 @@ from itertools import chain from django import forms -from django.contrib.postgres.validators import ( - ArrayMaxLengthValidator, - ArrayMinLengthValidator, -) from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from django_mongodb.validators import ArrayMaxLengthValidator, ArrayMinLengthValidator + from ..utils import prefix_validation_error diff --git a/django_mongodb/utils.py b/django_mongodb/utils.py index c62ae318..54a9212f 100644 --- a/django_mongodb/utils.py +++ b/django_mongodb/utils.py @@ -68,9 +68,9 @@ def prefix_validation_error(error, prefix, code, params): if error.error_list == [error]: error_params = error.params or {} return ValidationError( - # We can't simply concatenate messages since they might require - # their associated parameters to be expressed correctly which - # is not something `format_lazy` does. For example, proxied + # Messages can't simply be concatenated since they might require + # their associated parameters to be expressed correctly which is + # not something format_lazy() does. For example, proxied # ngettext calls require a count parameter and are converted # to an empty string if they are missing it. message=format_lazy( diff --git a/django_mongodb/validators.py b/django_mongodb/validators.py new file mode 100644 index 00000000..6005152e --- /dev/null +++ b/django_mongodb/validators.py @@ -0,0 +1,18 @@ +from django.core.validators import MaxLengthValidator, MinLengthValidator +from django.utils.translation import ngettext_lazy + + +class ArrayMaxLengthValidator(MaxLengthValidator): + message = ngettext_lazy( + "List contains %(show_value)d item, it should contain no more than %(limit_value)d.", + "List contains %(show_value)d items, it should contain no more than %(limit_value)d.", + "show_value", + ) + + +class ArrayMinLengthValidator(MinLengthValidator): + message = ngettext_lazy( + "List contains %(show_value)d item, it should contain no fewer than %(limit_value)d.", + "List contains %(show_value)d items, it should contain no fewer than %(limit_value)d.", + "show_value", + ) diff --git a/docs/source/_ext/djangodocs.py b/docs/source/_ext/djangodocs.py index 4828fdac..fda464d8 100644 --- a/docs/source/_ext/djangodocs.py +++ b/docs/source/_ext/djangodocs.py @@ -4,3 +4,8 @@ def setup(app): rolename="lookup", indextemplate="pair: %s; field lookup type", ) + app.add_crossref_type( + directivename="setting", + rolename="setting", + indextemplate="pair: %s; setting", + ) diff --git a/docs/source/forms.rst b/docs/source/forms.rst index 5d940960..c82cdd6d 100644 --- a/docs/source/forms.rst +++ b/docs/source/forms.rst @@ -97,6 +97,13 @@ Stores an :class:`~bson.objectid.ObjectId`. This field handles arrays by reproducing the underlying field a fixed number of times. + The template for this widget is located in + ``django_mongodb/templates/mongodb/widgets``. Don't forget to configure + template loading appropriately, for example, by using a + :class:`~django.template.backends.django.DjangoTemplates` engine with + :setting:`APP_DIRS=True ` and ``"django_mongodb"`` in + :setting:`INSTALLED_APPS`. + .. attribute:: base_field This is a required argument. It specifies the form field to be diff --git a/tests/model_fields_/test_arrayfield.py b/tests/model_fields_/test_arrayfield.py index e79319fa..9aa46f80 100644 --- a/tests/model_fields_/test_arrayfield.py +++ b/tests/model_fields_/test_arrayfield.py @@ -81,6 +81,34 @@ class MyModel(models.Model): instance = MyModel(field=value) self.assertEqual(instance.get_field_display(), display) + def test_deconstruct(self): + field = ArrayField(models.IntegerField()) + name, path, args, kwargs = field.deconstruct() + new = ArrayField(*args, **kwargs) + self.assertEqual(type(new.base_field), type(field.base_field)) + self.assertIsNot(new.base_field, field.base_field) + + def test_deconstruct_with_size(self): + field = ArrayField(models.IntegerField(), size=3) + name, path, args, kwargs = field.deconstruct() + new = ArrayField(*args, **kwargs) + self.assertEqual(new.size, field.size) + + def test_deconstruct_args(self): + field = ArrayField(models.CharField(max_length=20)) + name, path, args, kwargs = field.deconstruct() + new = ArrayField(*args, **kwargs) + self.assertEqual(new.base_field.max_length, field.base_field.max_length) + + def test_subclass_deconstruct(self): + field = ArrayField(models.IntegerField()) + name, path, args, kwargs = field.deconstruct() + self.assertEqual(path, "django_mongodb.fields.ArrayField") + + field = ArrayFieldSubclass() + name, path, args, kwargs = field.deconstruct() + self.assertEqual(path, "model_fields_.models.ArrayFieldSubclass") + class SaveLoadTests(TestCase): def test_integer(self): @@ -613,53 +641,41 @@ class MyModel(models.Model): self.assertEqual(MyModel._meta.get_field("field").check(), []) +@isolate_apps("model_fields_") class MigrationsTests(TransactionTestCase): available_apps = ["model_fields_"] - def test_deconstruct(self): - field = ArrayField(models.IntegerField()) - name, path, args, kwargs = field.deconstruct() - new = ArrayField(*args, **kwargs) - self.assertEqual(type(new.base_field), type(field.base_field)) - self.assertIsNot(new.base_field, field.base_field) - - def test_deconstruct_with_size(self): - field = ArrayField(models.IntegerField(), size=3) - name, path, args, kwargs = field.deconstruct() - new = ArrayField(*args, **kwargs) - self.assertEqual(new.size, field.size) - - def test_deconstruct_args(self): - field = ArrayField(models.CharField(max_length=20)) - name, path, args, kwargs = field.deconstruct() - new = ArrayField(*args, **kwargs) - self.assertEqual(new.base_field.max_length, field.base_field.max_length) - - def test_subclass_deconstruct(self): - field = ArrayField(models.IntegerField()) - name, path, args, kwargs = field.deconstruct() - self.assertEqual(path, "django_mongodb.fields.ArrayField") - - field = ArrayFieldSubclass() - name, path, args, kwargs = field.deconstruct() - self.assertEqual(path, "model_fields_.models.ArrayFieldSubclass") - @override_settings( MIGRATION_MODULES={ "model_fields_": "model_fields_.array_default_migrations", } ) def test_adding_field_with_default(self): - # See #22962 + class IntegerArrayDefaultModel(models.Model): + field = ArrayField(models.IntegerField(), size=None) + table_name = "model_fields__integerarraydefaultmodel" - with connection.cursor() as cursor: - self.assertNotIn(table_name, connection.introspection.table_names(cursor)) - call_command("migrate", "model_fields_", verbosity=0) - with connection.cursor() as cursor: - self.assertIn(table_name, connection.introspection.table_names(cursor)) + self.assertNotIn(table_name, connection.introspection.table_names(None)) + # Create collection + call_command("migrate", "model_fields_", "0001", verbosity=0) + self.assertIn(table_name, connection.introspection.table_names(None)) + obj = IntegerArrayDefaultModel.objects.create(field=[1, 2, 3]) + # Add `field2 to IntegerArrayDefaultModel. + call_command("migrate", "model_fields_", "0002", verbosity=0) + + class UpdatedIntegerArrayDefaultModel(models.Model): + field = ArrayField(models.IntegerField(), size=None) + field_2 = ArrayField(models.IntegerField(), default=[], size=None) + + class Meta: + db_table = "model_fields__integerarraydefaultmodel" + + obj = UpdatedIntegerArrayDefaultModel.objects.get() + # The default is populated on existing documents. + self.assertEqual(obj.field_2, []) + # Cleanup. call_command("migrate", "model_fields_", "zero", verbosity=0) - with connection.cursor() as cursor: - self.assertNotIn(table_name, connection.introspection.table_names(cursor)) + self.assertNotIn(table_name, connection.introspection.table_names(None)) @override_settings( MIGRATION_MODULES={ @@ -669,7 +685,7 @@ def test_adding_field_with_default(self): def test_adding_arrayfield_with_index(self): table_name = "model_fields__chartextarrayindexmodel" call_command("migrate", "model_fields_", verbosity=0) - # All fields should have regular indexes. + # All fields should have indexes. indexes = [ c["columns"][0] for c in connection.introspection.get_constraints(None, table_name).values() @@ -779,11 +795,7 @@ class AdminUtilsTests(SimpleTestCase): def test_array_display_for_field(self): array_field = ArrayField(models.IntegerField()) - display_value = display_for_field( - [1, 2], - array_field, - self.empty_value, - ) + display_value = display_for_field([1, 2], array_field, self.empty_value) self.assertEqual(display_value, "1, 2") def test_array_with_choices_display_for_field(self): @@ -794,17 +806,7 @@ def test_array_with_choices_display_for_field(self): ([1, 2], "2nd choice"), ], ) - - display_value = display_for_field( - [1, 2], - array_field, - self.empty_value, - ) + display_value = display_for_field([1, 2], array_field, self.empty_value) self.assertEqual(display_value, "2nd choice") - - display_value = display_for_field( - [99, 99], - array_field, - self.empty_value, - ) + display_value = display_for_field([99, 99], array_field, self.empty_value) self.assertEqual(display_value, self.empty_value)