diff --git a/django_mongodb/base.py b/django_mongodb/base.py index 9b3828cea..a3f4379dc 100644 --- a/django_mongodb/base.py +++ b/django_mongodb/base.py @@ -13,6 +13,7 @@ from .query_utils import regex_match from .schema import DatabaseSchemaEditor from .utils import OperationDebugWrapper +from .validation import DatabaseValidation class Cursor: @@ -128,6 +129,7 @@ def _isnull_operator(a, b): features_class = DatabaseFeatures introspection_class = DatabaseIntrospection ops_class = DatabaseOperations + validation_class = DatabaseValidation def get_collection(self, name, **kwargs): collection = Collection(self.database, name, **kwargs) diff --git a/django_mongodb/fields/auto.py b/django_mongodb/fields/auto.py index 7509d4b42..cc9ebda9d 100644 --- a/django_mongodb/fields/auto.py +++ b/django_mongodb/fields/auto.py @@ -39,6 +39,9 @@ def get_prep_value(self, value): return int(value) raise ValueError(f"Field '{self.name}' expected an ObjectId but got {value!r}.") from e + def get_internal_type(self): + return "ObjectIdAutoField" + def db_type(self, connection): return "objectId" diff --git a/django_mongodb/operations.py b/django_mongodb/operations.py index 04ba5fcbf..a6363bede 100644 --- a/django_mongodb/operations.py +++ b/django_mongodb/operations.py @@ -214,6 +214,17 @@ def explain_query_prefix(self, format=None, **options): super().explain_query_prefix(format, **options) return validated_options + def integer_field_range(self, internal_type): + # MongODB doesn't enforce any integer constraints, but it supports + # integers up to 64 bits. + if internal_type in { + "PositiveBigIntegerField", + "PositiveIntegerField", + "PositiveSmallIntegerField", + }: + return (0, 9223372036854775807) + return (-9223372036854775808, 9223372036854775807) + def prepare_join_on_clause(self, lhs_table, lhs_field, rhs_table, rhs_field): lhs_expr, rhs_expr = super().prepare_join_on_clause( lhs_table, lhs_field, rhs_table, rhs_field diff --git a/django_mongodb/validation.py b/django_mongodb/validation.py new file mode 100644 index 000000000..cb3d0d495 --- /dev/null +++ b/django_mongodb/validation.py @@ -0,0 +1,20 @@ +from django.core import checks +from django.db.backends.base.validation import BaseDatabaseValidation + + +class DatabaseValidation(BaseDatabaseValidation): + prohibited_fields = {"AutoField", "BigAutoField", "SmallAutoField"} + + def check_field_type(self, field, field_type): + """Prohibit AutoField on MongoDB.""" + errors = [] + if field.get_internal_type() in self.prohibited_fields: + errors.append( + checks.Error( + f"{self.connection.display_name} does not support {field.__class__.__name__}.", + obj=field, + hint="Use django_mongodb.fields.ObjectIdAutoField instead.", + id="mongodb.E001", + ) + ) + return errors diff --git a/tests/invalid_models_tests_/__init__.py b/tests/invalid_models_tests_/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/invalid_models_tests_/test_autofield.py b/tests/invalid_models_tests_/test_autofield.py new file mode 100644 index 000000000..4b3ea7e96 --- /dev/null +++ b/tests/invalid_models_tests_/test_autofield.py @@ -0,0 +1,63 @@ +from django.core.checks import Error +from django.db import connection, models +from django.test import SimpleTestCase +from django.test.utils import isolate_apps + +from django_mongodb.validation import DatabaseValidation + + +@isolate_apps("invalid_models_tests") +class ProhibitedFieldTests(SimpleTestCase): + def test_autofield(self): + class Model(models.Model): + id = models.AutoField(primary_key=True) + + field = Model._meta.get_field("id") + validator = DatabaseValidation(connection=connection) + self.assertEqual( + validator.check_field(field), + [ + Error( + "MongoDB does not support AutoField.", + hint="Use django_mongodb.fields.ObjectIdAutoField instead.", + obj=field, + id="mongodb.E001", + ) + ], + ) + + def test_bigautofield(self): + class Model(models.Model): + id = models.BigAutoField(primary_key=True) + + field = Model._meta.get_field("id") + validator = DatabaseValidation(connection=connection) + self.assertEqual( + validator.check_field(field), + [ + Error( + "MongoDB does not support BigAutoField.", + hint="Use django_mongodb.fields.ObjectIdAutoField instead.", + obj=field, + id="mongodb.E001", + ) + ], + ) + + def test_smallautofield(self): + class Model(models.Model): + id = models.SmallAutoField(primary_key=True) + + field = Model._meta.get_field("id") + validator = DatabaseValidation(connection=connection) + self.assertEqual( + validator.check_field(field), + [ + Error( + "MongoDB does not support SmallAutoField.", + hint="Use django_mongodb.fields.ObjectIdAutoField instead.", + obj=field, + id="mongodb.E001", + ) + ], + ) diff --git a/tests/model_fields_/test_autofield.py b/tests/model_fields_/test_autofield.py index 0013b2794..6b0680786 100644 --- a/tests/model_fields_/test_autofield.py +++ b/tests/model_fields_/test_autofield.py @@ -11,6 +11,10 @@ def test_deconstruct(self): self.assertEqual(args, []) self.assertEqual(kwargs, {"primary_key": True}) + def test_get_internal_type(self): + f = ObjectIdAutoField() + self.assertEqual(f.get_internal_type(), "ObjectIdAutoField") + def test_to_python(self): f = ObjectIdAutoField() self.assertEqual(f.to_python("1"), 1)