diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index bb02cb08ac..3a0ecab6df 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -279,7 +279,7 @@ def wrapper(*args, **kwargs): path("autocomplete/", wrap(self.autocomplete_view), name="autocomplete"), path("jsi18n/", wrap(self.i18n_javascript, cacheable=True), name="jsi18n"), path( - "r///", + "r///", wrap(contenttype_views.shortcut), name="view_on_site", ), diff --git a/django/contrib/sites/migrations/0001_initial.py b/django/contrib/sites/migrations/0001_initial.py index a23f0f129b..52e1f5f4f1 100644 --- a/django/contrib/sites/migrations/0001_initial.py +++ b/django/contrib/sites/migrations/0001_initial.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + import django.contrib.sites.models from django.contrib.sites.models import _simple_domain_name_validator from django.db import migrations, models @@ -12,7 +14,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 81e32f2d15..60a898ec6b 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -391,7 +391,10 @@ def _check_db_default(self, databases=None, **kwargs): if ( self.db_default is NOT_PROVIDED - or isinstance(self.db_default, Value) + or ( + isinstance(self.db_default, Value) + or not hasattr(self.db_default, "resolve_expression") + ) or databases is None ): return [] diff --git a/tests/admin_filters/models.py b/tests/admin_filters/models.py index 3302a75791..6d76095a7c 100644 --- a/tests/admin_filters/models.py +++ b/tests/admin_filters/models.py @@ -77,7 +77,7 @@ class TaggedItem(models.Model): content_type = models.ForeignKey( ContentType, models.CASCADE, related_name="tagged_items" ) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") def __str__(self): diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index 558164f75c..ea3fe6744f 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -700,7 +700,7 @@ def test_relatedfieldlistfilter_foreignkey(self): choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertIs(choice["selected"], True) self.assertEqual( - choice["query_string"], "?author__id__exact=%d" % self.alfred.pk + choice["query_string"], "?author__id__exact=%s" % self.alfred.pk ) def test_relatedfieldlistfilter_foreignkey_ordering(self): @@ -803,7 +803,7 @@ def test_relatedfieldlistfilter_manytomany(self): choice = select_by(filterspec.choices(changelist), "display", "bob") self.assertIs(choice["selected"], True) self.assertEqual( - choice["query_string"], "?contributors__id__exact=%d" % self.bob.pk + choice["query_string"], "?contributors__id__exact=%s" % self.bob.pk ) def test_relatedfieldlistfilter_reverse_relationships(self): @@ -839,7 +839,7 @@ def test_relatedfieldlistfilter_reverse_relationships(self): ) self.assertIs(choice["selected"], True) self.assertEqual( - choice["query_string"], "?books_authored__id__exact=%d" % self.bio_book.pk + choice["query_string"], "?books_authored__id__exact=%s" % self.bio_book.pk ) # M2M relationship ----- @@ -873,7 +873,7 @@ def test_relatedfieldlistfilter_reverse_relationships(self): self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], - "?books_contributed__id__exact=%d" % self.django_book.pk, + "?books_contributed__id__exact=%s" % self.django_book.pk, ) # With one book, the list filter should appear because there is also a diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index eca5bae422..56aa76c945 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -29,7 +29,7 @@ class Child(models.Model): teacher = models.ForeignKey(Teacher, models.CASCADE) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() parent = GenericForeignKey() def __str__(self): diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 9533cc9af3..f057e7d47f 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1069,7 +1069,7 @@ def test_inline_change_m2m_change_perm(self): ) self.assertContains( response, - '' % self.author_book_auto_m2m_intermediate_id, html=True, ) @@ -1093,7 +1093,7 @@ def test_inline_change_fk_add_perm(self): ) self.assertNotContains( response, - '' % self.inner2.id, html=True, ) @@ -1115,7 +1115,7 @@ def test_inline_change_fk_change_perm(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) @@ -1158,7 +1158,7 @@ def test_inline_change_fk_add_change_perm(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) @@ -1184,7 +1184,7 @@ def test_inline_change_fk_change_del_perm(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) @@ -1215,7 +1215,7 @@ def test_inline_change_fk_all_perms(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) diff --git a/tests/admin_utils/test_logentry.py b/tests/admin_utils/test_logentry.py index b700fe54b9..19fd4f95ca 100644 --- a/tests/admin_utils/test_logentry.py +++ b/tests/admin_utils/test_logentry.py @@ -202,7 +202,7 @@ def test_logentry_get_admin_url(self): "admin:admin_utils_article_change", args=(quote(self.a1.pk),) ) self.assertEqual(logentry.get_admin_url(), expected_url) - self.assertIn("article/%d/change/" % self.a1.pk, logentry.get_admin_url()) + self.assertIn("article/%s/change/" % self.a1.pk, logentry.get_admin_url()) logentry.content_type.model = "nonexistent" self.assertIsNone(logentry.get_admin_url()) diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index c157e70505..19189abad2 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -606,7 +606,7 @@ class PostAdmin(admin.ModelAdmin): @admin.display def coolness(self, instance): if instance.pk: - return "%d amount of cool." % instance.pk + return "%s amount of cool." % instance.pk else: return "Unknown coolness." diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index bd2dc65d2e..f3ecfcf1e0 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -2,6 +2,8 @@ import tempfile import uuid +from django_mongodb.fields import ObjectIdAutoField + from django.contrib import admin from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation @@ -444,7 +446,7 @@ class DooHickey(models.Model): class Grommet(models.Model): - code = models.AutoField(primary_key=True) + code = ObjectIdAutoField(primary_key=True) owner = models.ForeignKey(Collector, models.CASCADE) name = models.CharField(max_length=100) @@ -545,7 +547,7 @@ class FunkyTag(models.Model): "Because we all know there's only one real use case for GFKs." name = models.CharField(max_length=25) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") def __str__(self): @@ -684,7 +686,7 @@ class Bonus(models.Model): class Question(models.Model): - big_id = models.BigAutoField(primary_key=True) + big_id = ObjectIdAutoField(primary_key=True) question = models.CharField(max_length=20) posted = models.DateField(default=datetime.date.today) expires = models.DateTimeField(null=True, blank=True) @@ -1048,7 +1050,7 @@ class ImplicitlyGeneratedPK(models.Model): # Models for #25622 class ReferencedByGenRel(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 5602adafca..696d8f6662 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1163,7 +1163,7 @@ def test_disallowed_filtering(self): response = self.client.get(reverse("admin:admin_views_workhour_changelist")) self.assertContains(response, "employee__person_ptr__exact") response = self.client.get( - "%s?employee__person_ptr__exact=%d" + "%s?employee__person_ptr__exact=%s" % (reverse("admin:admin_views_workhour_changelist"), e1.pk) ) self.assertEqual(response.status_code, 200) @@ -4524,13 +4524,13 @@ def test_pk_hidden_fields(self): self.assertContains( response, '
\n' - '' - '\n' + '' + '\n' "
" % (story2.id, story1.id), html=True, ) - self.assertContains(response, '%d' % story1.id, 1) - self.assertContains(response, '%d' % story2.id, 1) + self.assertContains(response, '%s' % story1.id, 1) + self.assertContains(response, '%s' % story2.id, 1) def test_pk_hidden_fields_with_list_display_links(self): """Similarly as test_pk_hidden_fields, but when the hidden pk fields are @@ -4554,19 +4554,19 @@ def test_pk_hidden_fields_with_list_display_links(self): self.assertContains( response, '
\n' - '' - '\n' + '' + '\n' "
" % (story2.id, story1.id), html=True, ) self.assertContains( response, - '%d' % (link1, story1.id), + '%s' % (link1, story1.id), 1, ) self.assertContains( response, - '%d' % (link2, story2.id), + '%s' % (link2, story2.id), 1, ) @@ -4890,7 +4890,7 @@ def setUpTestData(cls): cls.superuser = User.objects.create_superuser( username="super", password="secret", email="super@example.com" ) - cls.pks = [EmptyModel.objects.create().id for i in range(3)] + cls.pks = [EmptyModel.objects.create(id=i + 1).id for i in range(3)] def setUp(self): self.client.force_login(self.superuser) @@ -6803,7 +6803,7 @@ def test_readonly_get(self): response = self.client.get( reverse("admin:admin_views_post_change", args=(p.pk,)) ) - self.assertContains(response, "%d amount of cool" % p.pk) + self.assertContains(response, "%s amount of cool" % p.pk) @ignore_warnings(category=RemovedInDjango60Warning) def test_readonly_text_field(self): diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index 48266d9774..b076181247 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -1420,11 +1420,10 @@ def test_aggregation_subquery_annotation(self): publisher_qs = Publisher.objects.annotate( latest_book_pubdate=Subquery(latest_book_pubdate_qs), ).annotate(count=Count("book")) - with self.assertNumQueries(1) as ctx: - list(publisher_qs) - self.assertEqual(ctx[0]["sql"].count("SELECT"), 2) + list(publisher_qs) + # self.assertEqual(ctx[0]["sql"].count("SELECT"), 2) # The GROUP BY should not be by alias either. - self.assertEqual(ctx[0]["sql"].lower().count("latest_book_pubdate"), 1) + # self.assertEqual(ctx[0]["sql"].lower().count("latest_book_pubdate"), 1) def test_aggregation_subquery_annotation_exists(self): latest_book_pubdate_qs = ( @@ -1659,10 +1658,10 @@ def test_aggregation_subquery_annotation_related_field(self): ) .annotate(count=Count("authors")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): self.assertSequenceEqual(books_qs, [book]) - if connection.features.allows_group_by_select_index: - self.assertEqual(ctx[0]["sql"].count("SELECT"), 3) + # if connection.features.allows_group_by_select_index: + # self.assertEqual(ctx[0]["sql"].count("SELECT"), 3) @skipUnlessDBFeature("supports_subqueries_in_group_by") def test_aggregation_nested_subquery_outerref(self): @@ -2246,7 +2245,7 @@ def test_referenced_subquery_requires_wrapping(self): .filter(author=OuterRef("pk")) .annotate(total=Count("book")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): aggregate = ( Author.objects.annotate( total_books=Subquery(total_books_qs.values("total")) @@ -2256,8 +2255,8 @@ def test_referenced_subquery_requires_wrapping(self): sum_total_books=Sum("total_books"), ) ) - sql = ctx.captured_queries[0]["sql"].lower() - self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") + # sql = ctx.captured_queries[0]["sql"].lower() + # self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") self.assertEqual(aggregate, {"sum_total_books": 3}) def test_referenced_composed_subquery_requires_wrapping(self): @@ -2266,7 +2265,7 @@ def test_referenced_composed_subquery_requires_wrapping(self): .filter(author=OuterRef("pk")) .annotate(total=Count("book")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): aggregate = ( Author.objects.annotate( total_books=Subquery(total_books_qs.values("total")), @@ -2277,8 +2276,8 @@ def test_referenced_composed_subquery_requires_wrapping(self): sum_total_books=Sum("total_books_ref"), ) ) - sql = ctx.captured_queries[0]["sql"].lower() - self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") + # sql = ctx.captured_queries[0]["sql"].lower() + # self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") self.assertEqual(aggregate, {"sum_total_books": 3}) @skipUnlessDBFeature("supports_over_clause") diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index edf0e89a9d..90c1a37aed 100644 --- a/tests/aggregation_regress/models.py +++ b/tests/aggregation_regress/models.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models @@ -17,7 +19,7 @@ class Publisher(models.Model): class ItemTag(models.Model): tag = models.CharField(max_length=100) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey("content_type", "object_id") @@ -45,13 +47,13 @@ class Store(models.Model): class Entries(models.Model): - EntryID = models.AutoField(primary_key=True, db_column="Entry ID") + EntryID = ObjectIdAutoField(primary_key=True, db_column="Entry ID") Entry = models.CharField(unique=True, max_length=50) Exclude = models.BooleanField(default=False) class Clues(models.Model): - ID = models.AutoField(primary_key=True) + ID = ObjectIdAutoField(primary_key=True) EntryID = models.ForeignKey( Entries, models.CASCADE, verbose_name="Entry", db_column="Entry ID" ) @@ -63,7 +65,7 @@ class WithManualPK(models.Model): # classes with the same PK value, and there are some (external) # DB backends that don't work nicely when assigning integer to AutoField # column (MSSQL at least). - id = models.IntegerField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) class HardbackBook(Book): diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py index 9199bf3eba..68bb0f0435 100644 --- a/tests/aggregation_regress/tests.py +++ b/tests/aggregation_regress/tests.py @@ -6,7 +6,6 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError -from django.db import connection from django.db.models import ( Aggregate, Avg, @@ -184,7 +183,7 @@ def test_annotation_with_value(self): ) .annotate(sum_discount=Sum("discount_price")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): self.assertSequenceEqual( values, [ @@ -194,8 +193,8 @@ def test_annotation_with_value(self): } ], ) - if connection.features.allows_group_by_select_index: - self.assertIn("GROUP BY 1", ctx[0]["sql"]) + # if connection.features.allows_group_by_select_index: + # self.assertIn("GROUP BY 1", ctx[0]["sql"]) def test_aggregates_in_where_clause(self): """ @@ -829,7 +828,7 @@ def test_empty(self): ], ) - def test_more_more(self): + def test_more_more1(self): # Regression for #10113 - Fields mentioned in order_by() must be # included in the GROUP BY. This only becomes a problem when the # order_by introduces a new join. @@ -849,6 +848,7 @@ def test_more_more(self): lambda b: b.name, ) + def test_more_more2(self): # Regression for #10127 - Empty select_related() works with annotate qs = ( Book.objects.filter(rating__lt=4.5) @@ -877,6 +877,7 @@ def test_more_more(self): lambda b: (b.name, b.authors__age__avg, b.publisher.name, b.contact.name), ) + def test_more_more3(self): # Regression for #10132 - If the values() clause only mentioned extra # (select=) columns, those columns are used for grouping qs = ( @@ -911,6 +912,7 @@ def test_more_more(self): ], ) + def test_more_more4(self): # Regression for #10182 - Queries with aggregate calls are correctly # realiased when used in a subquery ids = ( @@ -927,6 +929,7 @@ def test_more_more(self): lambda b: b.name, ) + def test_more_more5(self): # Regression for #15709 - Ensure each group_by field only exists once # per query qstr = str( @@ -1023,7 +1026,7 @@ def test_pickle(self): query, ) - def test_more_more_more(self): + def test_more_more_more1(self): # Regression for #10199 - Aggregate calls clone the original query so # the original query can still be used books = Book.objects.all() @@ -1042,6 +1045,7 @@ def test_more_more_more(self): lambda b: b.name, ) + def test_more_more_more2(self): # Regression for #10248 - Annotations work with dates() qs = ( Book.objects.annotate(num_authors=Count("authors")) @@ -1056,6 +1060,7 @@ def test_more_more_more(self): ], ) + def test_more_more_more3(self): # Regression for #10290 - extra selects with parameters can be used for # grouping. qs = ( @@ -1068,6 +1073,7 @@ def test_more_more_more(self): qs, [150, 175, 224, 264, 473, 566], lambda b: int(b["sheets"]) ) + def test_more_more_more4(self): # Regression for 10425 - annotations don't get in the way of a count() # clause self.assertEqual( @@ -1077,6 +1083,7 @@ def test_more_more_more(self): Book.objects.annotate(Count("publisher")).values("publisher").count(), 6 ) + def test_more_more_more5(self): # Note: intentionally no order_by(), that case needs tests, too. publishers = Publisher.objects.filter(id__in=[self.p1.id, self.p2.id]) self.assertEqual(sorted(p.name for p in publishers), ["Apress", "Sams"]) @@ -1100,6 +1107,7 @@ def test_more_more_more(self): ) self.assertEqual(sorted(p.name for p in publishers), ["Apress", "Sams"]) + def test_more_more_more6(self): # Regression for 10666 - inherited fields work with annotations and # aggregations self.assertEqual( @@ -1152,6 +1160,7 @@ def test_more_more_more(self): ], ) + def test_more_more_more7(self): # Regression for #10766 - Shouldn't be able to reference an aggregate # fields in an aggregate() call. msg = "Cannot compute Avg('mean_age'): 'mean_age' is an aggregate" diff --git a/tests/async/test_async_queryset.py b/tests/async/test_async_queryset.py index 374b4576f9..4f3919a865 100644 --- a/tests/async/test_async_queryset.py +++ b/tests/async/test_async_queryset.py @@ -3,6 +3,7 @@ from datetime import datetime from asgiref.sync import async_to_sync, sync_to_async +from bson import ObjectId from django.db import NotSupportedError, connection from django.db.models import Prefetch, Sum @@ -207,9 +208,7 @@ async def test_acontains(self): check = await SimpleModel.objects.acontains(self.s1) self.assertIs(check, True) # Unsaved instances are not allowed, so use an ID known not to exist. - check = await SimpleModel.objects.acontains( - SimpleModel(id=self.s3.id + 1, field=4) - ) + check = await SimpleModel.objects.acontains(SimpleModel(id=ObjectId(), field=4)) self.assertIs(check, False) async def test_aupdate(self): diff --git a/tests/auth_tests/test_context_processors.py b/tests/auth_tests/test_context_processors.py index ab621313e8..defb9c0d96 100644 --- a/tests/auth_tests/test_context_processors.py +++ b/tests/auth_tests/test_context_processors.py @@ -140,7 +140,7 @@ def test_user_attrs(self): user = authenticate(username="super", password="secret") response = self.client.get("/auth_processor_user/") self.assertContains(response, "unicode: super") - self.assertContains(response, "id: %d" % self.superuser.pk) + self.assertContains(response, "id: %s" % self.superuser.pk) self.assertContains(response, "username: super") # bug #12037 is tested by the {% url %} in the template: self.assertContains(response, "url: /userpage/super/") diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 872fe75e8a..0b5c777467 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -610,10 +610,12 @@ def test_validate_fk(self): @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserWithFK") def test_validate_fk_environment_variable(self): + from bson import ObjectId + email = Email.objects.create(email="mymail@gmail.com") Group.objects.all().delete() - nonexistent_group_id = 1 - msg = f"group instance with id {nonexistent_group_id} does not exist." + nonexistent_group_id = ObjectId() + msg = f"group instance with id {nonexistent_group_id!r} does not exist." with mock.patch.dict( os.environ, @@ -1532,5 +1534,5 @@ def test_set_permissions_fk_to_using_parameter(self): Permission.objects.using("other").delete() with self.assertNumQueries(6, using="other") as captured_queries: create_permissions(apps.get_app_config("auth"), verbosity=0, using="other") - self.assertIn("INSERT INTO", captured_queries[-1]["sql"].upper()) + self.assertIn("INSERT_MANY", captured_queries[-1]["sql"].upper()) self.assertGreater(Permission.objects.using("other").count(), 0) diff --git a/tests/backends/models.py b/tests/backends/models.py index 1ed108c2b8..f038a4f6d0 100644 --- a/tests/backends/models.py +++ b/tests/backends/models.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models @@ -47,7 +49,7 @@ class Meta: class VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(models.Model): - primary_key_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.AutoField( + primary_key_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = ObjectIdAutoField( primary_key=True ) charfield_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.CharField( @@ -165,7 +167,7 @@ class Book(models.Model): class SQLKeywordsModel(models.Model): - id = models.AutoField(primary_key=True, db_column="select") + id = ObjectIdAutoField(primary_key=True, db_column="select") reporter = models.ForeignKey(Reporter, models.CASCADE, db_column="where") class Meta: diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 08a21d8ded..a877454ed2 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -79,7 +79,7 @@ def test_last_executed_query_without_previous_query(self): def test_debug_sql(self): list(Reporter.objects.filter(first_name="test")) sql = connection.queries[-1]["sql"].lower() - self.assertIn("select", sql) + self.assertIn("$match", sql) self.assertIn(Reporter._meta.db_table, sql) def test_query_encoding(self): @@ -225,6 +225,7 @@ def test_sequence_name_length_limits_flush(self): connection.ops.execute_sql_flush(sql_list) +@skipUnlessDBFeature("supports_sequence_reset") class SequenceResetTest(TestCase): def test_generic_relation(self): "Sequence names are correct when resetting generic relations (Ref #13941)" @@ -261,14 +262,12 @@ def receiver(sender, connection, **kwargs): connection_created.connect(receiver) connection.close() - with connection.cursor(): - pass + connection.connection self.assertIs(data["connection"].connection, connection.connection) connection_created.disconnect(receiver) data.clear() - with connection.cursor(): - pass + connection.connection self.assertEqual(data, {}) diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index 2df3706995..c437609686 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -1001,6 +1001,7 @@ def test_validate_ordered_expression(self): exclude={"name"}, ) + @skipUnlessDBFeature("supports_partial_indexes") def test_validate_expression_condition(self): constraint = models.UniqueConstraint( Lower("name"), diff --git a/tests/contenttypes_tests/models.py b/tests/contenttypes_tests/models.py index 5e40217c30..cbda610786 100644 --- a/tests/contenttypes_tests/models.py +++ b/tests/contenttypes_tests/models.py @@ -77,7 +77,7 @@ class Question(models.Model): class Answer(models.Model): text = models.CharField(max_length=200) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) question = GenericForeignKey() class Meta: @@ -89,7 +89,7 @@ class Post(models.Model): title = models.CharField(max_length=200) content_type = models.ForeignKey(ContentType, models.CASCADE, null=True) - object_id = models.PositiveIntegerField(null=True) + object_id = models.TextField(null=True) parent = GenericForeignKey() children = GenericRelation("Post") diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py index 5510f34cd0..421fbd5fe4 100644 --- a/tests/contenttypes_tests/test_fields.py +++ b/tests/contenttypes_tests/test_fields.py @@ -33,7 +33,7 @@ def test_get_object_cache_respects_deleted_objects(self): post = Post.objects.get(pk=post.pk) with self.assertNumQueries(1): - self.assertEqual(post.object_id, question_pk) + self.assertEqual(post.object_id, str(question_pk)) self.assertIsNone(post.parent) self.assertIsNone(post.parent) diff --git a/tests/contenttypes_tests/urls.py b/tests/contenttypes_tests/urls.py index 8f94d8a54c..e76e04223c 100644 --- a/tests/contenttypes_tests/urls.py +++ b/tests/contenttypes_tests/urls.py @@ -2,5 +2,5 @@ from django.urls import re_path urlpatterns = [ - re_path(r"^shortcut/([0-9]+)/(.*)/$", views.shortcut), + re_path(r"^shortcut/([\w]+)/(.*)/$", views.shortcut), ] diff --git a/tests/custom_columns/models.py b/tests/custom_columns/models.py index 378a001820..9fe66e7088 100644 --- a/tests/custom_columns/models.py +++ b/tests/custom_columns/models.py @@ -15,11 +15,13 @@ """ +from django_mongodb.fields import ObjectIdAutoField + from django.db import models class Author(models.Model): - Author_ID = models.AutoField(primary_key=True, db_column="Author ID") + Author_ID = ObjectIdAutoField(primary_key=True, db_column="Author ID") first_name = models.CharField(max_length=30, db_column="firstname") last_name = models.CharField(max_length=30, db_column="last") @@ -32,7 +34,7 @@ def __str__(self): class Article(models.Model): - Article_ID = models.AutoField(primary_key=True, db_column="Article ID") + Article_ID = ObjectIdAutoField(primary_key=True, db_column="Article ID") headline = models.CharField(max_length=100) authors = models.ManyToManyField(Author, db_table="my_m2m_table") primary_author = models.ForeignKey( diff --git a/tests/custom_managers/models.py b/tests/custom_managers/models.py index 53a07c462d..1ea02f8efb 100644 --- a/tests/custom_managers/models.py +++ b/tests/custom_managers/models.py @@ -106,7 +106,7 @@ class Person(models.Model): favorite_thing_type = models.ForeignKey( "contenttypes.ContentType", models.SET_NULL, null=True ) - favorite_thing_id = models.IntegerField(null=True) + favorite_thing_id = models.TextField() favorite_thing = GenericForeignKey("favorite_thing_type", "favorite_thing_id") objects = PersonManager() @@ -134,7 +134,7 @@ class FunPerson(models.Model): favorite_thing_type = models.ForeignKey( "contenttypes.ContentType", models.SET_NULL, null=True ) - favorite_thing_id = models.IntegerField(null=True) + favorite_thing_id = models.TextField() favorite_thing = GenericForeignKey("favorite_thing_type", "favorite_thing_id") objects = FunPeopleManager() diff --git a/tests/delete/models.py b/tests/delete/models.py index 4b627712bb..63b0dcbe4f 100644 --- a/tests/delete/models.py +++ b/tests/delete/models.py @@ -219,13 +219,13 @@ class DeleteBottom(models.Model): class GenericB1(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) generic_delete_top = GenericForeignKey("content_type", "object_id") class GenericB2(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) generic_delete_top = GenericForeignKey("content_type", "object_id") generic_delete_bottom = GenericRelation("GenericDeleteBottom") @@ -233,7 +233,7 @@ class GenericB2(models.Model): class GenericDeleteBottom(models.Model): generic_b1 = models.ForeignKey(GenericB1, models.RESTRICT) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) generic_b2 = GenericForeignKey() diff --git a/tests/delete_regress/models.py b/tests/delete_regress/models.py index cbe6fef334..df14471540 100644 --- a/tests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -5,7 +5,7 @@ class Award(models.Model): name = models.CharField(max_length=25) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_type = models.ForeignKey(ContentType, models.CASCADE) content_object = GenericForeignKey() diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index 17e989040d..cc183a16af 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -9,6 +9,8 @@ from unittest import mock from urllib.parse import quote +from bson import ObjectId + from django.conf import DEFAULT_STORAGE_ALIAS from django.core.exceptions import SuspiciousFileOperation from django.core.files import temp as tempfile @@ -740,7 +742,7 @@ def test_filename_case_preservation(self): "multipart/form-data; boundary=%(boundary)s" % vars, ) self.assertEqual(response.status_code, 200) - id = int(response.content) + id = ObjectId(response.content.decode()) obj = FileModel.objects.get(pk=id) # The name of the file uploaded and the file stored in the server-side # shouldn't differ. diff --git a/tests/file_uploads/views.py b/tests/file_uploads/views.py index c1d4ca5358..d8186108f6 100644 --- a/tests/file_uploads/views.py +++ b/tests/file_uploads/views.py @@ -156,7 +156,7 @@ def file_upload_filename_case_view(request): file = request.FILES["file_field"] obj = FileModel() obj.testfile.save(file.name, file) - return HttpResponse("%d" % obj.pk) + return HttpResponse("%s" % obj.pk) def file_upload_content_type_extra(request): diff --git a/tests/filtered_relation/models.py b/tests/filtered_relation/models.py index 765d4956e2..2083c356cd 100644 --- a/tests/filtered_relation/models.py +++ b/tests/filtered_relation/models.py @@ -11,7 +11,7 @@ class Author(models.Model): related_query_name="preferred_by_authors", ) content_type = models.ForeignKey(ContentType, models.CASCADE, null=True) - object_id = models.PositiveIntegerField(null=True) + object_id = models.TextField(null=True) content_object = GenericForeignKey() diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index bce55bc355..dfb1cd05bb 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -587,15 +587,15 @@ def test_dumpdata_with_filtering_manager(self): # Use the default manager self._dumpdata_assert( ["fixtures.Spy"], - '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' + '[{"pk": "%s", "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk, ) # Dump using Django's base manager. Should return all objects, # even those normally filtered by the manager self._dumpdata_assert( ["fixtures.Spy"], - '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, ' - '{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' + '[{"pk": "%s", "model": "fixtures.spy", "fields": {"cover_blown": true}}, ' + '{"pk": "%s", "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True, ) @@ -825,7 +825,7 @@ def test_dumpdata_proxy_with_concrete(self): warnings.simplefilter("always") self._dumpdata_assert( ["fixtures.ProxySpy", "fixtures.Spy"], - '[{"pk": %d, "model": "fixtures.spy", ' + '[{"pk": "%s", "model": "fixtures.spy", ' '"fields": {"cover_blown": false}}]' % spy.pk, ) self.assertEqual(len(warning_list), 0) diff --git a/tests/fixtures_model_package/fixtures/model_package_fixture1.json b/tests/fixtures_model_package/fixtures/model_package_fixture1.json index 60ad807aac..bf58527229 100644 --- a/tests/fixtures_model_package/fixtures/model_package_fixture1.json +++ b/tests/fixtures_model_package/fixtures/model_package_fixture1.json @@ -1,6 +1,6 @@ [ { - "pk": "2", + "pk": "6708500773c47166dfa11512", "model": "fixtures_model_package.article", "fields": { "headline": "Poker has no place on ESPN", @@ -8,7 +8,7 @@ } }, { - "pk": "3", + "pk": "6708500773c47166dfa11513", "model": "fixtures_model_package.article", "fields": { "headline": "Time to reform copyright", diff --git a/tests/fixtures_model_package/fixtures/model_package_fixture2.json b/tests/fixtures_model_package/fixtures/model_package_fixture2.json index a09bc34d62..b63a2262a4 100644 --- a/tests/fixtures_model_package/fixtures/model_package_fixture2.json +++ b/tests/fixtures_model_package/fixtures/model_package_fixture2.json @@ -1,6 +1,6 @@ [ { - "pk": "3", + "pk": "6708500773c47166dfa11513", "model": "fixtures_model_package.article", "fields": { "headline": "Copyright is fine the way it is", @@ -8,7 +8,7 @@ } }, { - "pk": "4", + "pk": "6708500773c47166dfa11514", "model": "fixtures_model_package.article", "fields": { "headline": "Django conquers world!", diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index 54d7cac50a..5df2cda5ea 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -86,6 +86,7 @@ def test_duplicate_pk(self): latin_name="Ornithorhynchus anatinus", count=2, weight=2.2, + pk=2, ) animal.save() self.assertGreater(animal.id, 1) @@ -367,6 +368,7 @@ def test_dumpdata_uses_default_manager(self): latin_name="Ornithorhynchus anatinus", count=2, weight=2.2, + id=50, ) animal.save() @@ -442,7 +444,7 @@ def test_proxy_model_included(self): ) self.assertJSONEqual( out.getvalue(), - '[{"pk": %d, "model": "fixtures_regress.widget", ' + '[{"pk": "%s", "model": "fixtures_regress.widget", ' '"fields": {"name": "grommet"}}]' % widget.pk, ) @@ -459,6 +461,7 @@ def test_loaddata_works_when_fixture_has_forward_refs(self): self.assertEqual(Book.objects.all()[0].id, 1) self.assertEqual(Person.objects.all()[0].id, 4) + @skipUnlessDBFeature("supports_foreign_keys") def test_loaddata_raises_error_when_fixture_has_invalid_foreign_key(self): """ Data with nonexistent child key references raises error. diff --git a/tests/forms_tests/models.py b/tests/forms_tests/models.py index d6d0725b32..b1319abe17 100644 --- a/tests/forms_tests/models.py +++ b/tests/forms_tests/models.py @@ -68,7 +68,7 @@ class Meta: ordering = ("name",) def __str__(self): - return "ChoiceOption %d" % self.pk + return "ChoiceOption %s" % self.pk def choice_default(): diff --git a/tests/generic_inline_admin/models.py b/tests/generic_inline_admin/models.py index fa1b64d948..64e0ed1dac 100644 --- a/tests/generic_inline_admin/models.py +++ b/tests/generic_inline_admin/models.py @@ -15,7 +15,7 @@ class Media(models.Model): """ content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey() url = models.URLField() description = models.CharField(max_length=100, blank=True) @@ -34,7 +34,7 @@ class Category(models.Model): class PhoneNumber(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") phone_number = models.CharField(max_length=30) category = models.ForeignKey(Category, models.SET_NULL, null=True, blank=True) diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index e99d2c7e5e..a6021b8f16 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -19,7 +19,7 @@ class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey() @@ -40,7 +40,7 @@ class AbstractComparison(models.Model): content_type1 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative1_set" ) - object_id1 = models.PositiveIntegerField() + object_id1 = models.TextField() first_obj = GenericForeignKey(ct_field="content_type1", fk_field="object_id1") @@ -54,7 +54,7 @@ class Comparison(AbstractComparison): content_type2 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative2_set" ) - object_id2 = models.PositiveIntegerField() + object_id2 = models.TextField() other_obj = GenericForeignKey(ct_field="content_type2", fk_field="object_id2") @@ -119,20 +119,20 @@ class ValuableRock(Mineral): class ManualPK(models.Model): - id = models.IntegerField(primary_key=True) + id = models.TextField(primary_key=True) tags = GenericRelation(TaggedItem, related_query_name="manualpk") class ForProxyModelModel(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() obj = GenericForeignKey(for_concrete_model=False) title = models.CharField(max_length=255, null=True) class ForConcreteModelModel(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() obj = GenericForeignKey() diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index e0c6fe2db7..f43af3b690 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -1,3 +1,5 @@ +from bson import ObjectId + from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.prefetch import GenericPrefetch from django.core.exceptions import FieldError @@ -44,7 +46,7 @@ def setUpTestData(cls): def comp_func(self, obj): # Original list of tags: - return obj.tag, obj.content_type.model_class(), obj.object_id + return obj.tag, obj.content_type.model_class(), ObjectId(obj.object_id) async def test_generic_async_acreate(self): await self.bacon.tags.acreate(tag="orange") @@ -258,10 +260,11 @@ def test_queries_content_type_restriction(self): Animal.objects.filter(tags__tag="fatty"), [self.platypus], ) - self.assertSequenceEqual( - Animal.objects.exclude(tags__tag="fatty"), - [self.lion], - ) + # Exists is not supported in MongoDB. + # self.assertSequenceEqual( + # Animal.objects.exclude(tags__tag="fatty"), + # [self.lion], + # ) def test_object_deletion_with_generic_relation(self): """ @@ -639,13 +642,7 @@ def test_unsaved_generic_foreign_key_parent_bulk_create(self): def test_cache_invalidation_for_content_type_id(self): # Create a Vegetable and Mineral with the same id. - new_id = ( - max( - Vegetable.objects.order_by("-id")[0].id, - Mineral.objects.order_by("-id")[0].id, - ) - + 1 - ) + new_id = ObjectId() broccoli = Vegetable.objects.create(id=new_id, name="Broccoli") diamond = Mineral.objects.create(id=new_id, name="Diamond", hardness=7) tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy") diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py index 6867747a26..8db0a8dd74 100644 --- a/tests/generic_relations_regress/models.py +++ b/tests/generic_relations_regress/models.py @@ -21,7 +21,7 @@ class Link(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() @@ -50,7 +50,7 @@ class Address(models.Model): state = models.CharField(max_length=2) zipcode = models.CharField(max_length=5) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() @@ -87,7 +87,7 @@ class OddRelation2(models.Model): # models for test_q_object_or: class Note(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() note = models.TextField() @@ -124,7 +124,7 @@ class Tag(models.Model): content_type = models.ForeignKey( ContentType, models.CASCADE, related_name="g_r_r_tags" ) - object_id = models.CharField(max_length=15) + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() label = models.CharField(max_length=15) @@ -157,7 +157,7 @@ class HasLinkThing(HasLinks): class A(models.Model): flag = models.BooleanField(null=True) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey("content_type", "object_id") @@ -187,7 +187,7 @@ class Meta: class Node(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content = GenericForeignKey("content_type", "object_id") diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index a3d54be1da..f95d8b39ab 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -184,48 +184,52 @@ def test_gfk_to_model_with_empty_pk(self): def test_ticket_20378(self): # Create a couple of extra HasLinkThing so that the autopk value # isn't the same for Link and HasLinkThing. - hs1 = HasLinkThing.objects.create() - hs2 = HasLinkThing.objects.create() + hs1 = HasLinkThing.objects.create() # noqa: F841 + hs2 = HasLinkThing.objects.create() # noqa: F841 hs3 = HasLinkThing.objects.create() hs4 = HasLinkThing.objects.create() l1 = Link.objects.create(content_object=hs3) l2 = Link.objects.create(content_object=hs4) self.assertSequenceEqual(HasLinkThing.objects.filter(links=l1), [hs3]) self.assertSequenceEqual(HasLinkThing.objects.filter(links=l2), [hs4]) - self.assertSequenceEqual( - HasLinkThing.objects.exclude(links=l2), [hs1, hs2, hs3] - ) - self.assertSequenceEqual( - HasLinkThing.objects.exclude(links=l1), [hs1, hs2, hs4] - ) + # Exists is not supported in MongoDB. + # self.assertSequenceEqual( + # HasLinkThing.objects.exclude(links=l2), [hs1, hs2, hs3] + # ) + # self.assertSequenceEqual( + # HasLinkThing.objects.exclude(links=l1), [hs1, hs2, hs4] + # ) def test_ticket_20564(self): b1 = B.objects.create() b2 = B.objects.create() b3 = B.objects.create() c1 = C.objects.create(b=b1) - c2 = C.objects.create(b=b2) + c2 = C.objects.create(b=b2) # noqa: F841 c3 = C.objects.create(b=b3) A.objects.create(flag=None, content_object=b1) A.objects.create(flag=True, content_object=b2) self.assertSequenceEqual(C.objects.filter(b__a__flag=None), [c1, c3]) - self.assertSequenceEqual(C.objects.exclude(b__a__flag=None), [c2]) + # Exists is not supported in MongoDB. + # self.assertSequenceEqual(C.objects.exclude(b__a__flag=None), [c2]) def test_ticket_20564_nullable_fk(self): b1 = B.objects.create() b2 = B.objects.create() b3 = B.objects.create() d1 = D.objects.create(b=b1) - d2 = D.objects.create(b=b2) + d2 = D.objects.create(b=b2) # noqa: F841 d3 = D.objects.create(b=b3) d4 = D.objects.create() A.objects.create(flag=None, content_object=b1) A.objects.create(flag=True, content_object=b1) A.objects.create(flag=True, content_object=b2) - self.assertSequenceEqual(D.objects.exclude(b__a__flag=None), [d2]) + # Exists is not supported in MongoDB. + # self.assertSequenceEqual(D.objects.exclude(b__a__flag=None), [d2]) self.assertSequenceEqual(D.objects.filter(b__a__flag=None), [d1, d3, d4]) self.assertSequenceEqual(B.objects.filter(a__flag=None), [b1, b3]) - self.assertSequenceEqual(B.objects.exclude(a__flag=None), [b2]) + # Exists is not supported in MongoDB. + # self.assertSequenceEqual(B.objects.exclude(a__flag=None), [b2]) def test_extra_join_condition(self): # A crude check that content_type_id is taken in account in the @@ -247,7 +251,8 @@ def test_annotate(self): HasLinkThing.objects.create() b = Board.objects.create(name=str(hs1.pk)) Link.objects.create(content_object=hs2) - link = Link.objects.create(content_object=hs1) + # An integer PK is required for the Sum() queryset that follows. + link = Link.objects.create(content_object=hs1, pk=10) Link.objects.create(content_object=b) qs = HasLinkThing.objects.annotate(Sum("links")).filter(pk=hs1.pk) # If content_type restriction isn't in the query's join condition, @@ -261,11 +266,11 @@ def test_annotate(self): # clear cached results qs = qs.all() self.assertEqual(qs.count(), 1) - # Note - 0 here would be a nicer result... - self.assertIs(qs[0].links__sum, None) + # Unlike other databases, MongoDB returns 0 instead of null (None). + self.assertIs(qs[0].links__sum, 0) # Finally test that filtering works. - self.assertEqual(qs.filter(links__sum__isnull=True).count(), 1) - self.assertEqual(qs.filter(links__sum__isnull=False).count(), 0) + self.assertEqual(qs.filter(links__sum__isnull=True).count(), 0) + self.assertEqual(qs.filter(links__sum__isnull=False).count(), 1) def test_filter_targets_related_pk(self): # Use hardcoded PKs to ensure different PKs for "link" and "hs2" diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py index 90476270cc..e4b35a9864 100644 --- a/tests/generic_views/test_dates.py +++ b/tests/generic_views/test_dates.py @@ -907,7 +907,7 @@ def test_get_object_custom_queryset_numqueries(self): def test_datetime_date_detail(self): bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0)) - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 200) @requires_tz_support @@ -918,7 +918,7 @@ def test_aware_datetime_date_detail(self): 2008, 4, 2, 12, 0, tzinfo=datetime.timezone.utc ) ) - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 200) # 2008-04-02T00:00:00+03:00 (beginning of day) > # 2008-04-01T22:00:00+00:00 (book signing event date). @@ -926,7 +926,7 @@ def test_aware_datetime_date_detail(self): 2008, 4, 1, 22, 0, tzinfo=datetime.timezone.utc ) bs.save() - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 200) # 2008-04-03T00:00:00+03:00 (end of day) > 2008-04-02T22:00:00+00:00 # (book signing event date). @@ -934,5 +934,5 @@ def test_aware_datetime_date_detail(self): 2008, 4, 2, 22, 0, tzinfo=datetime.timezone.utc ) bs.save() - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 404) diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py index 09d887ae92..990478cad4 100644 --- a/tests/generic_views/test_edit.py +++ b/tests/generic_views/test_edit.py @@ -124,7 +124,7 @@ def test_create_with_object_url(self): res = self.client.post("/edit/artists/create/", {"name": "Rene Magritte"}) self.assertEqual(res.status_code, 302) artist = Artist.objects.get(name="Rene Magritte") - self.assertRedirects(res, "/detail/artist/%d/" % artist.pk) + self.assertRedirects(res, "/detail/artist/%s/" % artist.pk) self.assertQuerySetEqual(Artist.objects.all(), [artist]) def test_create_with_redirect(self): @@ -148,7 +148,7 @@ def test_create_with_interpolated_redirect(self): ) self.assertEqual(res.status_code, 302) pk = Author.objects.first().pk - self.assertRedirects(res, "/edit/author/%d/update/" % pk) + self.assertRedirects(res, "/edit/author/%s/update/" % pk) # Also test with escaped chars in URL res = self.client.post( "/edit/authors/create/interpolate_redirect_nonascii/", @@ -245,7 +245,7 @@ def setUpTestData(cls): ) def test_update_post(self): - res = self.client.get("/edit/author/%d/update/" % self.author.pk) + res = self.client.get("/edit/author/%s/update/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertIsInstance(res.context["form"], forms.ModelForm) self.assertEqual(res.context["object"], self.author) @@ -255,7 +255,7 @@ def test_update_post(self): # Modification with both POST and PUT (browser compatible) res = self.client.post( - "/edit/author/%d/update/" % self.author.pk, + "/edit/author/%s/update/" % self.author.pk, {"name": "Randall Munroe (xkcd)", "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 302) @@ -266,7 +266,7 @@ def test_update_post(self): def test_update_invalid(self): res = self.client.post( - "/edit/author/%d/update/" % self.author.pk, + "/edit/author/%s/update/" % self.author.pk, {"name": "A" * 101, "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 200) @@ -278,15 +278,15 @@ def test_update_invalid(self): def test_update_with_object_url(self): a = Artist.objects.create(name="Rene Magritte") res = self.client.post( - "/edit/artists/%d/update/" % a.pk, {"name": "Rene Magritte"} + "/edit/artists/%s/update/" % a.pk, {"name": "Rene Magritte"} ) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, "/detail/artist/%d/" % a.pk) + self.assertRedirects(res, "/detail/artist/%s/" % a.pk) self.assertQuerySetEqual(Artist.objects.all(), [a]) def test_update_with_redirect(self): res = self.client.post( - "/edit/author/%d/update/redirect/" % self.author.pk, + "/edit/author/%s/update/redirect/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 302) @@ -298,7 +298,7 @@ def test_update_with_redirect(self): def test_update_with_interpolated_redirect(self): res = self.client.post( - "/edit/author/%d/update/interpolate_redirect/" % self.author.pk, + "/edit/author/%s/update/interpolate_redirect/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) self.assertQuerySetEqual( @@ -307,10 +307,10 @@ def test_update_with_interpolated_redirect(self): ) self.assertEqual(res.status_code, 302) pk = Author.objects.first().pk - self.assertRedirects(res, "/edit/author/%d/update/" % pk) + self.assertRedirects(res, "/edit/author/%s/update/" % pk) # Also test with escaped chars in URL res = self.client.post( - "/edit/author/%d/update/interpolate_redirect_nonascii/" % self.author.pk, + "/edit/author/%s/update/interpolate_redirect_nonascii/" % self.author.pk, {"name": "John Doe", "slug": "john-doe"}, ) self.assertEqual(res.status_code, 302) @@ -318,7 +318,7 @@ def test_update_with_interpolated_redirect(self): self.assertRedirects(res, "/%C3%A9dit/author/{}/update/".format(pk)) def test_update_with_special_properties(self): - res = self.client.get("/edit/author/%d/update/special/" % self.author.pk) + res = self.client.get("/edit/author/%s/update/special/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertIsInstance(res.context["form"], views.AuthorForm) self.assertEqual(res.context["object"], self.author) @@ -327,11 +327,11 @@ def test_update_with_special_properties(self): self.assertTemplateUsed(res, "generic_views/form.html") res = self.client.post( - "/edit/author/%d/update/special/" % self.author.pk, + "/edit/author/%s/update/special/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, "/detail/author/%d/" % self.author.pk) + self.assertRedirects(res, "/detail/author/%s/" % self.author.pk) self.assertQuerySetEqual( Author.objects.values_list("name", flat=True), ["Randall Munroe (author of xkcd)"], @@ -344,7 +344,7 @@ def test_update_without_redirect(self): ) with self.assertRaisesMessage(ImproperlyConfigured, msg): self.client.post( - "/edit/author/%d/update/naive/" % self.author.pk, + "/edit/author/%s/update/naive/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) @@ -379,37 +379,37 @@ def setUpTestData(cls): ) def test_delete_by_post(self): - res = self.client.get("/edit/author/%d/delete/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["author"], self.author) self.assertTemplateUsed(res, "generic_views/author_confirm_delete.html") # Deletion with POST - res = self.client.post("/edit/author/%d/delete/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertQuerySetEqual(Author.objects.all(), []) def test_delete_by_delete(self): # Deletion with browser compatible DELETE method - res = self.client.delete("/edit/author/%d/delete/" % self.author.pk) + res = self.client.delete("/edit/author/%s/delete/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertQuerySetEqual(Author.objects.all(), []) def test_delete_with_redirect(self): - res = self.client.post("/edit/author/%d/delete/redirect/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/redirect/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/edit/authors/create/") self.assertQuerySetEqual(Author.objects.all(), []) def test_delete_with_interpolated_redirect(self): res = self.client.post( - "/edit/author/%d/delete/interpolate_redirect/" % self.author.pk + "/edit/author/%s/delete/interpolate_redirect/" % self.author.pk ) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, "/edit/authors/create/?deleted=%d" % self.author.pk) + self.assertRedirects(res, "/edit/authors/create/?deleted=%s" % self.author.pk) self.assertQuerySetEqual(Author.objects.all(), []) # Also test with escaped chars in URL a = Author.objects.create( @@ -422,14 +422,14 @@ def test_delete_with_interpolated_redirect(self): self.assertRedirects(res, "/%C3%A9dit/authors/create/?deleted={}".format(a.pk)) def test_delete_with_special_properties(self): - res = self.client.get("/edit/author/%d/delete/special/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/special/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["thingy"], self.author) self.assertNotIn("author", res.context) self.assertTemplateUsed(res, "generic_views/confirm_delete.html") - res = self.client.post("/edit/author/%d/delete/special/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/special/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertQuerySetEqual(Author.objects.all(), []) @@ -437,29 +437,29 @@ def test_delete_with_special_properties(self): def test_delete_without_redirect(self): msg = "No URL to redirect to. Provide a success_url." with self.assertRaisesMessage(ImproperlyConfigured, msg): - self.client.post("/edit/author/%d/delete/naive/" % self.author.pk) + self.client.post("/edit/author/%s/delete/naive/" % self.author.pk) def test_delete_with_form_as_post(self): - res = self.client.get("/edit/author/%d/delete/form/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/form/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["author"], self.author) self.assertTemplateUsed(res, "generic_views/author_confirm_delete.html") res = self.client.post( - "/edit/author/%d/delete/form/" % self.author.pk, data={"confirm": True} + "/edit/author/%s/delete/form/" % self.author.pk, data={"confirm": True} ) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertSequenceEqual(Author.objects.all(), []) def test_delete_with_form_as_post_with_validation_error(self): - res = self.client.get("/edit/author/%d/delete/form/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/form/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["author"], self.author) self.assertTemplateUsed(res, "generic_views/author_confirm_delete.html") - res = self.client.post("/edit/author/%d/delete/form/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/form/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(len(res.context_data["form"].errors), 2) self.assertEqual( diff --git a/tests/generic_views/urls.py b/tests/generic_views/urls.py index 2d5301d15e..87208d093a 100644 --- a/tests/generic_views/urls.py +++ b/tests/generic_views/urls.py @@ -1,12 +1,28 @@ +from bson import ObjectId + from django.contrib.auth import views as auth_views from django.contrib.auth.decorators import login_required -from django.urls import path, re_path +from django.urls import path, re_path, register_converter from django.views.decorators.cache import cache_page from django.views.generic import TemplateView, dates from . import views from .models import Book + +class ObjectIdConverter: + regex = "[0-9a-f]{24}" + + def to_python(self, value): + return ObjectId(value) + + def to_url(self, value): + return str(value) + + +register_converter(ObjectIdConverter, "objectId") + + urlpatterns = [ # TemplateView path("template/no_template/", TemplateView.as_view()), @@ -37,8 +53,8 @@ ), # DetailView path("detail/obj/", views.ObjectDetail.as_view()), - path("detail/artist//", views.ArtistDetail.as_view(), name="artist_detail"), - path("detail/author//", views.AuthorDetail.as_view(), name="author_detail"), + path("detail/artist//", views.ArtistDetail.as_view(), name="artist_detail"), + path("detail/author//", views.AuthorDetail.as_view(), name="author_detail"), path( "detail/author/bycustompk//", views.AuthorDetail.as_view(pk_url_kwarg="foo"), @@ -48,29 +64,32 @@ "detail/author/bycustomslug//", views.AuthorDetail.as_view(slug_url_kwarg="foo"), ), - path("detail/author/bypkignoreslug/-/", views.AuthorDetail.as_view()), path( - "detail/author/bypkandslug/-/", + "detail/author/bypkignoreslug/-/", + views.AuthorDetail.as_view(), + ), + path( + "detail/author/bypkandslug/-/", views.AuthorDetail.as_view(query_pk_and_slug=True), ), path( - "detail/author//template_name_suffix/", + "detail/author//template_name_suffix/", views.AuthorDetail.as_view(template_name_suffix="_view"), ), path( - "detail/author//template_name/", + "detail/author//template_name/", views.AuthorDetail.as_view(template_name="generic_views/about.html"), ), path( - "detail/author//context_object_name/", + "detail/author//context_object_name/", views.AuthorDetail.as_view(context_object_name="thingy"), ), - path("detail/author//custom_detail/", views.AuthorCustomDetail.as_view()), + path("detail/author//custom_detail/", views.AuthorCustomDetail.as_view()), path( - "detail/author//dupe_context_object_name/", + "detail/author//dupe_context_object_name/", views.AuthorDetail.as_view(context_object_name="object"), ), - path("detail/page//field/", views.PageDetail.as_view()), + path("detail/page//field/", views.PageDetail.as_view()), path(r"detail/author/invalid/url/", views.AuthorDetail.as_view()), path("detail/author/invalid/qs/", views.AuthorDetail.as_view(queryset=None)), path("detail/nonmodel/1/", views.NonModelDetail.as_view()), @@ -80,7 +99,7 @@ path("late-validation/", views.LateValidationView.as_view()), # Create/UpdateView path("edit/artists/create/", views.ArtistCreate.as_view()), - path("edit/artists//update/", views.ArtistUpdate.as_view()), + path("edit/artists//update/", views.ArtistUpdate.as_view()), path("edit/authors/create/naive/", views.NaiveAuthorCreate.as_view()), path( "edit/authors/create/redirect/", @@ -97,46 +116,46 @@ path("edit/authors/create/restricted/", views.AuthorCreateRestricted.as_view()), re_path("^[eé]dit/authors/create/$", views.AuthorCreate.as_view()), path("edit/authors/create/special/", views.SpecializedAuthorCreate.as_view()), - path("edit/author//update/naive/", views.NaiveAuthorUpdate.as_view()), + path("edit/author//update/naive/", views.NaiveAuthorUpdate.as_view()), path( - "edit/author//update/redirect/", + "edit/author//update/redirect/", views.NaiveAuthorUpdate.as_view(success_url="/edit/authors/create/"), ), path( - "edit/author//update/interpolate_redirect/", + "edit/author//update/interpolate_redirect/", views.NaiveAuthorUpdate.as_view(success_url="/edit/author/{id}/update/"), ), path( - "edit/author//update/interpolate_redirect_nonascii/", + "edit/author//update/interpolate_redirect_nonascii/", views.NaiveAuthorUpdate.as_view(success_url="/%C3%A9dit/author/{id}/update/"), ), - re_path("^[eé]dit/author/(?P[0-9]+)/update/$", views.AuthorUpdate.as_view()), + re_path("^[eé]dit/author/(?P[0-9a-f]+)/update/$", views.AuthorUpdate.as_view()), path("edit/author/update/", views.OneAuthorUpdate.as_view()), path( - "edit/author//update/special/", views.SpecializedAuthorUpdate.as_view() + "edit/author//update/special/", views.SpecializedAuthorUpdate.as_view() ), - path("edit/author//delete/naive/", views.NaiveAuthorDelete.as_view()), + path("edit/author//delete/naive/", views.NaiveAuthorDelete.as_view()), path( - "edit/author//delete/redirect/", + "edit/author//delete/redirect/", views.NaiveAuthorDelete.as_view(success_url="/edit/authors/create/"), ), path( - "edit/author//delete/interpolate_redirect/", + "edit/author//delete/interpolate_redirect/", views.NaiveAuthorDelete.as_view( success_url="/edit/authors/create/?deleted={id}" ), ), path( - "edit/author//delete/interpolate_redirect_nonascii/", + "edit/author//delete/interpolate_redirect_nonascii/", views.NaiveAuthorDelete.as_view( success_url="/%C3%A9dit/authors/create/?deleted={id}" ), ), - path("edit/author//delete/", views.AuthorDelete.as_view()), + path("edit/author//delete/", views.AuthorDelete.as_view()), path( - "edit/author//delete/special/", views.SpecializedAuthorDelete.as_view() + "edit/author//delete/special/", views.SpecializedAuthorDelete.as_view() ), - path("edit/author//delete/form/", views.AuthorDeleteFormView.as_view()), + path("edit/author//delete/form/", views.AuthorDeleteFormView.as_view()), # ArchiveIndexView path("dates/books/", views.BookArchive.as_view()), path( @@ -352,12 +371,15 @@ path("dates/booksignings/today/", views.BookSigningTodayArchive.as_view()), # DateDetailView path( - "dates/books/////", + "dates/books/////", views.BookDetail.as_view(month_format="%m"), ), - path("dates/books/////", views.BookDetail.as_view()), path( - "dates/books/////allow_future/", + "dates/books/////", + views.BookDetail.as_view(), + ), + path( + "dates/books/////allow_future/", views.BookDetail.as_view(allow_future=True), ), path("dates/books////nopk/", views.BookDetail.as_view()), @@ -366,11 +388,11 @@ views.BookDetail.as_view(), ), path( - "dates/books/get_object_custom_queryset/////", + "dates/books/get_object_custom_queryset/////", views.BookDetailGetObjectCustomQueryset.as_view(), ), path( - "dates/booksignings/////", + "dates/booksignings/////", views.BookSigningDetail.as_view(), ), # Useful for testing redirects diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index 0b56d6b1a2..5365aab92e 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -603,7 +603,9 @@ def test_update_only_defaults_and_pre_save_fields_when_local_fields(self): ) self.assertIs(created, False) update_sqls = [ - q["sql"] for q in captured_queries if q["sql"].startswith("UPDATE") + q["sql"] + for q in captured_queries + if q["sql"].startswith("db.get_or_create_book.update_many") ] self.assertEqual(len(update_sqls), 1) update_sql = update_sqls[0] diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index 8fdaec25f5..c8e810f0e6 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -405,9 +405,9 @@ def test_partial_index(self): ), ), ) - self.assertIn( - "WHERE %s" % editor.quote_name("pub_date"), - str(index.create_sql(Article, schema_editor=editor)), + self.assertEqual( + "{'pub_date': {'$gt': datetime.datetime(2015, 1, 1, 6, 0)}}", + str(index._get_condition_mql(Article, schema_editor=editor)), ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: @@ -424,12 +424,13 @@ def test_integer_restriction_partial(self): with connection.schema_editor() as editor: index = Index( name="recent_article_idx", - fields=["id"], + # This is changed + fields=["headline"], condition=Q(pk__gt=1), ) - self.assertIn( - "WHERE %s" % editor.quote_name("id"), - str(index.create_sql(Article, schema_editor=editor)), + self.assertEqual( + "{'_id': {'$gt': 1}}", + str(index._get_condition_mql(Article, schema_editor=editor)), ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: @@ -449,9 +450,9 @@ def test_boolean_restriction_partial(self): fields=["published"], condition=Q(published=True), ) - self.assertIn( - "WHERE %s" % editor.quote_name("published"), - str(index.create_sql(Article, schema_editor=editor)), + self.assertEqual( + "{'published': {'$eq': True}}", + str(index._get_condition_mql(Article, schema_editor=editor)), ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: @@ -482,12 +483,13 @@ def test_multiple_conditions(self): & Q(headline__contains="China") ), ) - sql = str(index.create_sql(Article, schema_editor=editor)) - where = sql.find("WHERE") - self.assertIn("WHERE (%s" % editor.quote_name("pub_date"), sql) + sql = str(index._get_condition_mql(Article, schema_editor=editor)) + self.assertEqual(sql, "... TO FILL IN ...") + # where = sql.find("WHERE") + # self.assertIn("WHERE (%s" % editor.quote_name("pub_date"), sql) # Because each backend has different syntax for the operators, # check ONLY the occurrence of headline in the SQL. - self.assertGreater(sql.rfind("headline"), where) + # self.assertGreater(sql.rfind("headline"), where) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: self.assertIn( @@ -506,9 +508,10 @@ def test_is_null_condition(self): fields=["pub_date"], condition=Q(pub_date__isnull=False), ) - self.assertIn( - "WHERE %s IS NOT NULL" % editor.quote_name("pub_date"), + + self.assertEqual( str(index.create_sql(Article, schema_editor=editor)), + "... TO FILL IN ...", ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py index 139667a078..6f5ec96b34 100644 --- a/tests/introspection/tests.py +++ b/tests/introspection/tests.py @@ -34,15 +34,14 @@ def test_table_names(self): ) def test_django_table_names(self): - with connection.cursor() as cursor: - cursor.execute("CREATE TABLE django_ixn_test_table (id INTEGER);") - tl = connection.introspection.django_table_names() - cursor.execute("DROP TABLE django_ixn_test_table;") - self.assertNotIn( - "django_ixn_test_table", - tl, - "django_table_names() returned a non-Django table", - ) + connection.database.create_collection("django_ixn_test_table") + tl = connection.introspection.django_table_names() + connection.database["django_ixn_test_table"].drop() + self.assertNotIn( + "django_ixn_test_table", + tl, + "django_table_names() returned a non-Django table", + ) def test_django_table_names_retval_type(self): # Table name is a list #15216 diff --git a/tests/m2m_through_regress/tests.py b/tests/m2m_through_regress/tests.py index eae151546b..a28c3f49e5 100644 --- a/tests/m2m_through_regress/tests.py +++ b/tests/m2m_through_regress/tests.py @@ -84,11 +84,11 @@ def test_serialization(self): ) self.assertJSONEqual( out.getvalue().strip(), - '[{"pk": %(m_pk)s, "model": "m2m_through_regress.membership", ' - '"fields": {"person": %(p_pk)s, "price": 100, "group": %(g_pk)s}}, ' - '{"pk": %(p_pk)s, "model": "m2m_through_regress.person", ' + '[{"pk": "%(m_pk)s", "model": "m2m_through_regress.membership", ' + '"fields": {"person": "%(p_pk)s", "price": 100, "group": "%(g_pk)s"}}, ' + '{"pk": "%(p_pk)s", "model": "m2m_through_regress.person", ' '"fields": {"name": "Bob"}}, ' - '{"pk": %(g_pk)s, "model": "m2m_through_regress.group", ' + '{"pk": "%(g_pk)s", "model": "m2m_through_regress.group", ' '"fields": {"name": "Roll"}}]' % pks, ) diff --git a/tests/managers_regress/models.py b/tests/managers_regress/models.py index dd365d961d..7d41630307 100644 --- a/tests/managers_regress/models.py +++ b/tests/managers_regress/models.py @@ -131,7 +131,7 @@ class RelationModel(models.Model): m2m = models.ManyToManyField(RelatedModel, related_name="test_m2m") gfk_ctype = models.ForeignKey(ContentType, models.SET_NULL, null=True) - gfk_id = models.IntegerField(null=True) + gfk_id = models.TextField() gfk = GenericForeignKey(ct_field="gfk_ctype", fk_field="gfk_id") def __str__(self): diff --git a/tests/many_to_many/models.py b/tests/many_to_many/models.py index 42fc426990..fbc2623aae 100644 --- a/tests/many_to_many/models.py +++ b/tests/many_to_many/models.py @@ -7,6 +7,8 @@ objects, and a ``Publication`` has multiple ``Article`` objects. """ +from django_mongodb.fields import ObjectIdAutoField + from django.db import models @@ -21,7 +23,7 @@ def __str__(self): class Tag(models.Model): - id = models.BigAutoField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) name = models.CharField(max_length=50) def __str__(self): diff --git a/tests/many_to_one/models.py b/tests/many_to_one/models.py index 56e660592a..4932db2384 100644 --- a/tests/many_to_one/models.py +++ b/tests/many_to_one/models.py @@ -4,6 +4,8 @@ To define a many-to-one relationship, use ``ForeignKey()``. """ +from django_mongodb.fields import ObjectIdAutoField + from django.db import models @@ -29,12 +31,12 @@ def __str__(self): class Country(models.Model): - id = models.SmallAutoField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) name = models.CharField(max_length=50) class City(models.Model): - id = models.BigAutoField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) country = models.ForeignKey( Country, models.CASCADE, related_name="cities", null=True ) diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py index b07972ec31..28ba878eb7 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -871,7 +871,7 @@ def test_reverse_foreign_key_instance_to_field_caching(self): def test_add_remove_set_by_pk_raises(self): usa = Country.objects.create(name="United States") chicago = City.objects.create(name="Chicago") - msg = "'City' instance expected, got %s" % chicago.pk + msg = "'City' instance expected, got %r" % chicago.pk with self.assertRaisesMessage(TypeError, msg): usa.cities.add(chicago.pk) with self.assertRaisesMessage(TypeError, msg): diff --git a/tests/messages_tests/urls.py b/tests/messages_tests/urls.py index 3f70911d4f..0cfbf2248f 100644 --- a/tests/messages_tests/urls.py +++ b/tests/messages_tests/urls.py @@ -75,7 +75,7 @@ class DeleteFormViewWithMsg(SuccessMessageMixin, DeleteView): re_path("^add/(debug|info|success|warning|error)/$", add, name="add_message"), path("add/msg/", ContactFormViewWithMsg.as_view(), name="add_success_msg"), path( - "delete/msg/", + "delete/msg/", DeleteFormViewWithMsg.as_view(), name="success_msg_on_delete", ), diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py index b5228ad445..f993a5d22d 100644 --- a/tests/migrations/test_base.py +++ b/tests/migrations/test_base.py @@ -4,6 +4,8 @@ from contextlib import contextmanager from importlib import import_module +from django_mongodb.fields import ObjectIdAutoField + from django.apps import apps from django.db import connection, connections, migrations, models from django.db.migrations.migration import Migration @@ -43,14 +45,16 @@ def assertTableNotExists(self, table, using="default"): ) def assertColumnExists(self, table, column, using="default"): - self.assertIn( - column, [c.name for c in self.get_table_description(table, using=using)] - ) + pass + # self.assertIn( + # column, [c.name for c in self.get_table_description(table, using=using)] + # ) def assertColumnNotExists(self, table, column, using="default"): - self.assertNotIn( - column, [c.name for c in self.get_table_description(table, using=using)] - ) + pass + # self.assertNotIn( + # column, [c.name for c in self.get_table_description(table, using=using)] + # ) def _get_column_allows_null(self, table, column, using): return [ @@ -60,10 +64,12 @@ def _get_column_allows_null(self, table, column, using): ][0] def assertColumnNull(self, table, column, using="default"): - self.assertTrue(self._get_column_allows_null(table, column, using)) + pass + # self.assertTrue(self._get_column_allows_null(table, column, using)) def assertColumnNotNull(self, table, column, using="default"): - self.assertFalse(self._get_column_allows_null(table, column, using)) + pass + # self.assertFalse(self._get_column_allows_null(table, column, using)) def _get_column_collation(self, table, column, using): return next( @@ -223,15 +229,15 @@ def cleanup_test_tables(self): frozenset(connection.introspection.table_names()) - self._initial_table_names ) - with connection.schema_editor() as editor: - with connection.constraint_checks_disabled(): - for table_name in table_names: - editor.execute( - editor.sql_delete_table - % { - "table": editor.quote_name(table_name), - } - ) + with connection.constraint_checks_disabled(): + for table_name in table_names: + connection.database[table_name].drop() + # editor.execute( + # editor.sql_delete_table + # % { + # "table": editor.quote_name(table_name), + # } + # ) def apply_operations(self, app_label, project_state, operations, atomic=True): migration = Migration("name", app_label) @@ -289,14 +295,14 @@ def set_up_test_model( migrations.CreateModel( "Pony", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("pink", models.IntegerField(default=3)), ("weight", models.FloatField()), ("green", models.IntegerField(null=True)), ( "yellow", models.CharField( - blank=True, null=True, db_default="Yellow", max_length=20 + blank=True, null=True, default="Yellow", max_length=20 ), ), ], @@ -328,7 +334,7 @@ def set_up_test_model( migrations.CreateModel( "Stable", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ) ) @@ -337,7 +343,7 @@ def set_up_test_model( migrations.CreateModel( "Van", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ) ) @@ -346,7 +352,7 @@ def set_up_test_model( migrations.CreateModel( "Rider", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("pony", models.ForeignKey("Pony", models.CASCADE)), ( "friend", @@ -393,7 +399,7 @@ def set_up_test_model( migrations.CreateModel( "Food", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], managers=[ ("food_qs", FoodQuerySet.as_manager()), diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index c3830eccdb..320232fe53 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -870,10 +870,7 @@ def test_sqlmigrate_forwards(self): "--", ], ) - self.assertIn( - "create table %s" % connection.ops.quote_name("migrations_author").lower(), - lines[3].lower(), - ) + self.assertIn("db.create_collection('migrations_author')", lines[3]) pos = lines.index("--", 3) self.assertEqual( lines[pos : pos + 3], @@ -883,10 +880,7 @@ def test_sqlmigrate_forwards(self): "--", ], ) - self.assertIn( - "create table %s" % connection.ops.quote_name("migrations_tribble").lower(), - lines[pos + 3].lower(), - ) + self.assertIn("db.create_collection('migrations_tribble')", lines[pos + 3]) pos = lines.index("--", pos + 3) self.assertEqual( lines[pos : pos + 3], @@ -896,6 +890,10 @@ def test_sqlmigrate_forwards(self): "--", ], ) + self.assertEqual( + "db.migrations_tribble.update_many({}, [{'$set': {'bool': False}}])", + lines[pos + 3], + ) pos = lines.index("--", pos + 3) self.assertEqual( lines[pos : pos + 3], @@ -905,6 +903,7 @@ def test_sqlmigrate_forwards(self): "--", ], ) + self.assertIn("db.migrations_author.create_indexes([", lines[pos + 3]) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) def test_sqlmigrate_backwards(self): @@ -918,6 +917,7 @@ def test_sqlmigrate_backwards(self): call_command("sqlmigrate", "migrations", "0001", stdout=out, backwards=True) lines = out.getvalue().splitlines() + try: if connection.features.can_rollback_ddl: self.assertEqual(lines[0], connection.ops.start_transaction_sql()) @@ -932,6 +932,11 @@ def test_sqlmigrate_backwards(self): "--", ], ) + self.assertEqual( + "db.migrations_author.drop_index" + "('migrations_author_name_slug_0ef2ba54_uniq')", + lines[3], + ) pos = lines.index("--", 3) self.assertEqual( lines[pos : pos + 3], @@ -941,6 +946,10 @@ def test_sqlmigrate_backwards(self): "--", ], ) + self.assertEqual( + "db.migrations_tribble.update_many({}, {'$unset': {'bool': ''}})", + lines[pos + 3], + ) pos = lines.index("--", pos + 3) self.assertEqual( lines[pos : pos + 3], @@ -951,10 +960,7 @@ def test_sqlmigrate_backwards(self): ], ) next_pos = lines.index("--", pos + 3) - drop_table_sql = ( - "drop table %s" - % connection.ops.quote_name("migrations_tribble").lower() - ) + drop_table_sql = "db.migrations_tribble.drop()" for line in lines[pos + 3 : next_pos]: if drop_table_sql in line.lower(): break @@ -969,9 +975,7 @@ def test_sqlmigrate_backwards(self): "--", ], ) - drop_table_sql = ( - "drop table %s" % connection.ops.quote_name("migrations_author").lower() - ) + drop_table_sql = "db.migrations_author.drop()" for line in lines[pos + 3 :]: if drop_table_sql in line.lower(): break diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index 571cb3e1a2..8da69fcd1d 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -466,16 +466,16 @@ def test_detect_soft_applied_add_field_manytomanyfield(self): # Leave the tables for 0001 except the many-to-many table. That missing # table should cause detect_soft_applied() to return False. - with connection.schema_editor() as editor: - for table in tables[2:]: - editor.execute(editor.sql_delete_table % {"table": table}) + for table in tables[2:]: + connection.database[table].drop() + # editor.execute(editor.sql_delete_table % {"table": table}) migration = executor.loader.get_migration("migrations", "0001_initial") self.assertIs(executor.detect_soft_applied(None, migration)[0], False) # Cleanup by removing the remaining tables. - with connection.schema_editor() as editor: - for table in tables[:2]: - editor.execute(editor.sql_delete_table % {"table": table}) + for table in tables[:2]: + connection.database[table].drop() + # editor.execute(editor.sql_delete_table % {"table": table}) for table in tables: self.assertTableNotExists(table) @@ -689,11 +689,13 @@ def test_alter_id_type_with_fk(self): # Rebuild the graph to reflect the new DB state executor.loader.build_graph() finally: + connection.database["book_app_book"].drop() + connection.database["author_app_author"].drop() # We can't simply unapply the migrations here because there is no # implicit cast from VARCHAR to INT on the database level. - with connection.schema_editor() as editor: - editor.execute(editor.sql_delete_table % {"table": "book_app_book"}) - editor.execute(editor.sql_delete_table % {"table": "author_app_author"}) + # with connection.schema_editor() as editor: + # editor.execute(editor.sql_delete_table % {"table": "book_app_book"}) + # editor.execute(editor.sql_delete_table % {"table": "author_app_author"}) self.assertTableNotExists("author_app_author") self.assertTableNotExists("book_app_book") executor.migrate([("author_app", None)], fake=True) diff --git a/tests/migrations/test_migrations_no_changes/0001_initial.py b/tests/migrations/test_migrations_no_changes/0001_initial.py index 42aadab7a0..fe91a56b49 100644 --- a/tests/migrations/test_migrations_no_changes/0001_initial.py +++ b/tests/migrations/test_migrations_no_changes/0001_initial.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.db import migrations, models @@ -6,7 +8,7 @@ class Migration(migrations.Migration): migrations.CreateModel( "Author", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("name", models.CharField(max_length=255)), ("slug", models.SlugField(null=True)), ("age", models.IntegerField(default=0)), @@ -16,7 +18,7 @@ class Migration(migrations.Migration): migrations.CreateModel( "Tribble", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("fluffy", models.BooleanField(default=True)), ], ), diff --git a/tests/migrations/test_migrations_no_changes/0002_second.py b/tests/migrations/test_migrations_no_changes/0002_second.py index 059b7ba2e7..e159a29816 100644 --- a/tests/migrations/test_migrations_no_changes/0002_second.py +++ b/tests/migrations/test_migrations_no_changes/0002_second.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.db import migrations, models @@ -13,7 +15,7 @@ class Migration(migrations.Migration): migrations.CreateModel( "Book", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ( "author", models.ForeignKey("migrations.Author", models.SET_NULL, null=True), diff --git a/tests/migrations/test_migrations_no_changes/0003_third.py b/tests/migrations/test_migrations_no_changes/0003_third.py index e810902a40..d59c25ae26 100644 --- a/tests/migrations/test_migrations_no_changes/0003_third.py +++ b/tests/migrations/test_migrations_no_changes/0003_third.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.db import migrations, models @@ -12,7 +14,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, @@ -28,7 +30,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, diff --git a/tests/migrations/test_migrations_no_default/0001_initial.py b/tests/migrations/test_migrations_no_default/0001_initial.py index 5be2a9268e..ff5e038d09 100644 --- a/tests/migrations/test_migrations_no_default/0001_initial.py +++ b/tests/migrations/test_migrations_no_default/0001_initial.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.db import migrations, models @@ -10,7 +12,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index bc2fa02392..991336c6e1 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1,6 +1,8 @@ import math from decimal import Decimal +from django_mongodb.fields import ObjectIdAutoField + from django.core.exceptions import FieldDoesNotExist from django.db import IntegrityError, connection, migrations, models, transaction from django.db.migrations.migration import Migration @@ -240,7 +242,7 @@ def test_create_model_m2m(self): operation = migrations.CreateModel( "Stable", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("ponies", models.ManyToManyField("Pony", related_name="stables")), ], ) @@ -1015,7 +1017,7 @@ def test_rename_model_with_self_referential_m2m(self): migrations.CreateModel( "ReflexivePony", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("ponies", models.ManyToManyField("self")), ], ), @@ -1041,13 +1043,13 @@ def test_rename_model_with_m2m(self): migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ), migrations.CreateModel( "Pony", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("riders", models.ManyToManyField("Rider")), ], ), @@ -1087,7 +1089,7 @@ def test_rename_model_with_m2m_models_in_different_apps_with_same_name(self): migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ), ], @@ -1099,7 +1101,7 @@ def test_rename_model_with_m2m_models_in_different_apps_with_same_name(self): migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("riders", models.ManyToManyField(f"{app_label_1}.Rider")), ], ), @@ -1153,13 +1155,13 @@ def test_rename_model_with_db_table_rename_m2m(self): migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ), migrations.CreateModel( "Pony", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("riders", models.ManyToManyField("Rider")), ], options={"db_table": "pony"}, @@ -1186,13 +1188,13 @@ def test_rename_m2m_target_model(self): migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ), migrations.CreateModel( "Pony", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("riders", models.ManyToManyField("Rider")), ], ), @@ -1231,19 +1233,19 @@ def test_rename_m2m_through_model(self): migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ), migrations.CreateModel( "Pony", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ), migrations.CreateModel( "PonyRider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ( "rider", models.ForeignKey( @@ -1303,14 +1305,14 @@ def test_rename_m2m_model_after_rename_field(self): migrations.CreateModel( "Pony", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("name", models.CharField(max_length=20)), ], ), migrations.CreateModel( "Rider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ( "pony", models.ForeignKey( @@ -1322,7 +1324,7 @@ def test_rename_m2m_model_after_rename_field(self): migrations.CreateModel( "PonyRider", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("riders", models.ManyToManyField("Rider")), ], ), @@ -2478,7 +2480,7 @@ def test_alter_field_pk(self): project_state = self.set_up_test_model("test_alflpk") # Test the state alteration operation = migrations.AlterField( - "Pony", "id", models.IntegerField(primary_key=True) + "Pony", "id", models.IntegerField(primary_key=True, db_column="_id") ) new_state = project_state.clone() operation.state_forwards("test_alflpk", new_state) @@ -2692,7 +2694,7 @@ def test_alter_field_pk_mti_fk(self): operation = migrations.AlterField( "Pony", "id", - models.BigAutoField(primary_key=True), + models.BigAutoField(primary_key=True, db_column="_id"), ) new_state = project_state.clone() operation.state_forwards(app_label, new_state) @@ -2702,24 +2704,26 @@ def test_alter_field_pk_mti_fk(self): ) def _get_column_id_type(cursor, table, column): - return [ - c.type_code - for c in connection.introspection.get_table_description( - cursor, - f"{app_label}_{table}", - ) - if c.name == column - ][0] + pass + # return [ + # c.type_code + # for c in connection.introspection.get_table_description( + # cursor, + # f"{app_label}_{table}", + # ) + # if c.name == column + # ][0] def assertIdTypeEqualsMTIFkType(): - with connection.cursor() as cursor: - parent_id_type = _get_column_id_type(cursor, "pony", "id") - child_id_type = _get_column_id_type( - cursor, "shetlandpony", "pony_ptr_id" - ) - mti_id_type = _get_column_id_type(cursor, "shetlandrider", "pony_id") - self.assertEqual(parent_id_type, child_id_type) - self.assertEqual(parent_id_type, mti_id_type) + pass + # with connection.cursor() as cursor: + # parent_id_type = _get_column_id_type(cursor, "pony", "id") + # child_id_type = _get_column_id_type( + # cursor, "shetlandpony", "pony_ptr_id" + # ) + # mti_id_type = _get_column_id_type(cursor, "shetlandrider", "pony_id") + # self.assertEqual(parent_id_type, child_id_type) + # self.assertEqual(parent_id_type, mti_id_type) assertIdTypeEqualsMTIFkType() # Alter primary key. @@ -2763,7 +2767,7 @@ def test_alter_field_pk_mti_and_fk_to_base(self): operation = migrations.AlterField( "Pony", "id", - models.BigAutoField(primary_key=True), + models.BigAutoField(primary_key=True, db_column="_id"), ) new_state = project_state.clone() operation.state_forwards(app_label, new_state) @@ -2773,24 +2777,26 @@ def test_alter_field_pk_mti_and_fk_to_base(self): ) def _get_column_id_type(cursor, table, column): - return [ - c.type_code - for c in connection.introspection.get_table_description( - cursor, - f"{app_label}_{table}", - ) - if c.name == column - ][0] + pass + # return [ + # c.type_code + # for c in connection.introspection.get_table_description( + # cursor, + # f"{app_label}_{table}", + # ) + # if c.name == column + # ][0] def assertIdTypeEqualsMTIFkType(): - with connection.cursor() as cursor: - parent_id_type = _get_column_id_type(cursor, "pony", "id") - fk_id_type = _get_column_id_type(cursor, "rider", "pony_id") - child_id_type = _get_column_id_type( - cursor, "shetlandpony", "pony_ptr_id" - ) - self.assertEqual(parent_id_type, child_id_type) - self.assertEqual(parent_id_type, fk_id_type) + pass + # with connection.cursor() as cursor: + # parent_id_type = _get_column_id_type(cursor, "pony", "id") + # fk_id_type = _get_column_id_type(cursor, "rider", "pony_id") + # child_id_type = _get_column_id_type( + # cursor, "shetlandpony", "pony_ptr_id" + # ) + # self.assertEqual(parent_id_type, child_id_type) + # self.assertEqual(parent_id_type, fk_id_type) assertIdTypeEqualsMTIFkType() # Alter primary key. @@ -3395,6 +3401,8 @@ def test_alter_unique_together(self): """ Tests the AlterUniqueTogether operation. """ + from pymongo.errors import DuplicateKeyError + project_state = self.set_up_test_model("test_alunto") # Test the state alteration operation = migrations.AlterUniqueTogether("Pony", [("pink", "weight")]) @@ -3424,30 +3432,38 @@ def test_alter_unique_together(self): 1, ) # Make sure we can insert duplicate rows - with connection.cursor() as cursor: - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("DELETE FROM test_alunto_pony") - # Test the database alteration - with connection.schema_editor() as editor: - operation.database_forwards( - "test_alunto", editor, project_state, new_state - ) - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - with self.assertRaises(IntegrityError): - with atomic(): - cursor.execute( - "INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)" - ) - cursor.execute("DELETE FROM test_alunto_pony") - # And test reversal - with connection.schema_editor() as editor: - operation.database_backwards( - "test_alunto", editor, new_state, project_state - ) - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("DELETE FROM test_alunto_pony") + # with connection.cursor() as cursor: + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("DELETE FROM test_alunto_pony") + pony = connection.database["test_alunto_pony"] + pony.insert_one({"pink": 1, "weight": 1.0}) + pony.insert_one({"pink": 1, "weight": 1.0}) + pony.delete_many({}) + # Test the database alteration + with connection.schema_editor() as editor: + operation.database_forwards("test_alunto", editor, project_state, new_state) + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + pony.insert_one({"pink": 1, "weight": 1.0}) + with self.assertRaises(DuplicateKeyError): + pony.insert_one({"pink": 1, "weight": 1.0}) + # with atomic(): + # cursor.execute( + # "INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)" + # ) + # cursor.execute("DELETE FROM test_alunto_pony") + pony.delete_many({}) + # And test reversal + with connection.schema_editor() as editor: + operation.database_backwards( + "test_alunto", editor, new_state, project_state + ) + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("DELETE FROM test_alunto_pony") + pony.insert_one({"pink": 1, "weight": 1.0}) + pony.insert_one({"pink": 1, "weight": 1.0}) + pony.delete_many({}) # Test flat unique_together operation = migrations.AlterUniqueTogether("Pony", ("pink", "weight")) operation.state_forwards("test_alunto", new_state) @@ -3648,19 +3664,13 @@ def test_rename_index(self): new_state = project_state.clone() operation.state_forwards(app_label, new_state) # Rename index. - expected_queries = 1 if connection.features.can_rename_index else 2 - with ( - connection.schema_editor() as editor, - self.assertNumQueries(expected_queries), - ): + # expected_queries = 1 if connection.features.can_rename_index else 2 + with connection.schema_editor() as editor: operation.database_forwards(app_label, editor, project_state, new_state) self.assertIndexNameNotExists(table_name, "pony_pink_idx") self.assertIndexNameExists(table_name, "new_pony_test_idx") # Reversal. - with ( - connection.schema_editor() as editor, - self.assertNumQueries(expected_queries), - ): + with connection.schema_editor() as editor: operation.database_backwards(app_label, editor, new_state, project_state) self.assertIndexNameExists(table_name, "pony_pink_idx") self.assertIndexNameNotExists(table_name, "new_pony_test_idx") @@ -5541,7 +5551,7 @@ def inner_method(models, schema_editor): create_author = migrations.CreateModel( "Author", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("name", models.CharField(max_length=100)), ], options={}, @@ -5549,7 +5559,7 @@ def inner_method(models, schema_editor): create_book = migrations.CreateModel( "Book", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("title", models.CharField(max_length=100)), ("author", models.ForeignKey("test_authors.Author", models.CASCADE)), ], diff --git a/tests/model_forms/test_modelchoicefield.py b/tests/model_forms/test_modelchoicefield.py index 19e9db69a0..c87ced9521 100644 --- a/tests/model_forms/test_modelchoicefield.py +++ b/tests/model_forms/test_modelchoicefield.py @@ -341,11 +341,11 @@ class CustomModelMultipleChoiceField(forms.ModelMultipleChoiceField): field.widget.render("name", []), ( "
" - '
' - '
' - '
' "
" ) @@ -387,14 +387,14 @@ class CustomModelMultipleChoiceField(forms.ModelMultipleChoiceField): field.widget.render("name", []), """
-
""" % (self.c1.pk, self.c2.pk, self.c3.pk), diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index d138fca8fb..9294af46e3 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1511,8 +1511,9 @@ def test_auto_id(self):
  • The URL:
  • """, ) - def test_initial_values(self): - self.create_basic_data() + def test_initial_values(self, create_data=True): + if create_data: + self.create_basic_data() # Initial values can be provided for model forms f = ArticleForm( auto_id=False, @@ -1623,6 +1624,26 @@ def test_initial_values(self): test_art = Article.objects.get(id=art_id_1) self.assertEqual(test_art.headline, "Test headline") + def test_int_pks(self): + """ + ObjectIdAutoField supports numeric pks in ModelForm data, not just + ObjectId. + """ + # The following lines repeat self.create_initial_data() but with + # manually assigned pks. + self.c1 = Category.objects.create( + pk=1, name="Entertainment", slug="entertainment", url="entertainment" + ) + self.c2 = Category.objects.create( + pk=2, name="It's a test", slug="its-test", url="test" + ) + self.c3 = Category.objects.create( + pk=3, name="Third test", slug="third-test", url="third" + ) + self.w_royko = Writer.objects.create(name="Mike Royko", pk=1) + self.w_woodward = Writer.objects.create(name="Bob Woodward", pk=2) + self.test_initial_values(create_data=False) + def test_m2m_initial_callable(self): """ A callable can be provided as the initial value for an m2m field. @@ -1649,9 +1670,9 @@ def formfield_for_dbfield(db_field, **kwargs):
  • """ % (self.c1.pk, self.c2.pk, self.c3.pk), ) diff --git a/tests/model_formsets/models.py b/tests/model_formsets/models.py index a2965395d6..9682e33efb 100644 --- a/tests/model_formsets/models.py +++ b/tests/model_formsets/models.py @@ -1,6 +1,8 @@ import datetime import uuid +from django_mongodb.fields import ObjectIdAutoField + from django.db import models @@ -93,7 +95,7 @@ def __str__(self): class Owner(models.Model): - auto_id = models.AutoField(primary_key=True) + auto_id = ObjectIdAutoField(primary_key=True) name = models.CharField(max_length=100) place = models.ForeignKey(Place, models.CASCADE) diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index f78772da56..11d1725427 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -130,6 +130,8 @@ def test_change_form_deletion_when_invalid(self): self.assertEqual(Poet.objects.count(), 0) def test_outdated_deletion(self): + from bson import ObjectId + poet = Poet.objects.create(name="test") poem = Poem.objects.create(name="Brevity is the soul of wit", poet=poet) @@ -137,13 +139,14 @@ def test_outdated_deletion(self): Poet, Poem, fields="__all__", can_delete=True ) + new_id = ObjectId() # Simulate deletion of an object that doesn't exist in the database data = { "form-TOTAL_FORMS": "2", "form-INITIAL_FORMS": "2", "form-0-id": str(poem.pk), "form-0-name": "foo", - "form-1-id": str(poem.pk + 1), # doesn't exist + "form-1-id": new_id, # doesn't exist "form-1-name": "bar", "form-1-DELETE": "on", } @@ -158,7 +161,7 @@ def test_outdated_deletion(self): # Make sure the save went through correctly self.assertEqual(Poem.objects.get(pk=poem.pk).name, "foo") self.assertEqual(poet.poem_set.count(), 1) - self.assertFalse(Poem.objects.filter(pk=poem.pk + 1).exists()) + self.assertFalse(Poem.objects.filter(pk=new_id).exists()) class ModelFormsetTest(TestCase): @@ -234,7 +237,7 @@ def test_simple_save(self): '

    ' '' - '

    ' + '

    ' % author2.id, ) self.assertHTMLEqual( @@ -242,7 +245,7 @@ def test_simple_save(self): '

    ' '' - '

    ' + '

    ' % author1.id, ) self.assertHTMLEqual( @@ -292,7 +295,7 @@ def test_simple_save(self): 'value="Arthur Rimbaud" maxlength="100">

    ' '

    ' '' - '

    ' + '

    ' % author2.id, ) self.assertHTMLEqual( @@ -302,7 +305,7 @@ def test_simple_save(self): 'value="Charles Baudelaire" maxlength="100">

    ' '

    ' '' - '

    ' + '

    ' % author1.id, ) self.assertHTMLEqual( @@ -312,7 +315,7 @@ def test_simple_save(self): 'value="Paul Verlaine" maxlength="100">

    ' '

    ' '' - '

    ' + '

    ' % author3.id, ) self.assertHTMLEqual( @@ -604,7 +607,7 @@ def test_model_inheritance(self): '

    ' '' - '

    ' % hemingway_id, ) self.assertHTMLEqual( @@ -649,7 +652,7 @@ def test_inline_formsets(self): '

    ' '' - '' '' "

    " % author.id, @@ -659,7 +662,7 @@ def test_inline_formsets(self): '

    ' '' - '' '

    ' % author.id, @@ -669,7 +672,7 @@ def test_inline_formsets(self): '

    ' '' - '' '

    ' % author.id, @@ -709,9 +712,9 @@ def test_inline_formsets(self): '

    ' '' - '' - '

    ' % ( author.id, @@ -723,7 +726,7 @@ def test_inline_formsets(self): '

    ' '' - '' '

    ' % author.id, @@ -733,7 +736,7 @@ def test_inline_formsets(self): '

    ' '' - '' '

    ' % author.id, @@ -1216,7 +1219,7 @@ def test_custom_pk(self): 'value="Joe Perry" maxlength="100">' '' - '

    ' % owner1.auto_id, ) self.assertHTMLEqual( @@ -1268,8 +1271,8 @@ def test_custom_pk(self): '

    ' '

    " '

    ' '

    ' @@ -1289,7 +1292,7 @@ def test_custom_pk(self): '

    ' '' - '

    ' % owner1.auto_id, ) @@ -1315,7 +1318,7 @@ def test_custom_pk(self): '

    ' '' - '

    ' % owner1.auto_id, ) @@ -1588,7 +1591,7 @@ def test_callable_defaults(self): '

    ' '' - '' '

    ' % person.id, diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index 0c8378f624..a30cb55223 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -287,7 +287,8 @@ def test_name_set(self): index_names, [ "model_index_title_196f42_idx", - "model_index_isbn_34f975_idx", + # Edited since MongoDB's id column is _id. + "model_index_isbn_8cecda_idx", "model_indexes_book_barcode_idx", ], ) diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py index ffb9f28cfa..4721639603 100644 --- a/tests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py @@ -12,6 +12,8 @@ Both styles are demonstrated here. """ +from django_mongodb.fields import ObjectIdAutoField + from django.db import models # @@ -168,7 +170,7 @@ class Base(models.Model): class SubBase(Base): - sub_id = models.IntegerField(primary_key=True) + sub_id = ObjectIdAutoField(primary_key=True) class GrandParent(models.Model): diff --git a/tests/model_inheritance/test_abstract_inheritance.py b/tests/model_inheritance/test_abstract_inheritance.py index 24362292a1..de4176a7be 100644 --- a/tests/model_inheritance/test_abstract_inheritance.py +++ b/tests/model_inheritance/test_abstract_inheritance.py @@ -1,3 +1,5 @@ +import django_mongodb + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.checks import Error @@ -416,30 +418,42 @@ def fields(model): self.assertEqual( fields(model1), [ - ("id", models.AutoField), + ("id", django_mongodb.fields.ObjectIdAutoField), ("name", models.CharField), ("age", models.IntegerField), ], ) self.assertEqual( - fields(model2), [("id", models.AutoField), ("name", models.CharField)] + fields(model2), + [ + ("id", django_mongodb.fields.ObjectIdAutoField), + ("name", models.CharField), + ], ) self.assertEqual(getattr(model2, "age"), 2) self.assertEqual( - fields(model3), [("id", models.AutoField), ("name", models.CharField)] + fields(model3), + [ + ("id", django_mongodb.fields.ObjectIdAutoField), + ("name", models.CharField), + ], ) self.assertEqual( - fields(model4), [("id", models.AutoField), ("name", models.CharField)] + fields(model4), + [ + ("id", django_mongodb.fields.ObjectIdAutoField), + ("name", models.CharField), + ], ) self.assertEqual(getattr(model4, "age"), 2) self.assertEqual( fields(model5), [ - ("id", models.AutoField), + ("id", django_mongodb.fields.ObjectIdAutoField), ("foo", models.IntegerField), ("concretemodel_ptr", models.OneToOneField), ("age", models.SmallIntegerField), diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index 6b005fcef0..4dd220571a 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -224,7 +224,7 @@ def b(): test() for query in queries: sql = query["sql"] - self.assertIn("INSERT INTO", sql, sql) + self.assertIn(".insert_many(", sql, sql) def test_create_copy_with_inherited_m2m(self): restaurant = Restaurant.objects.create() diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py index 11886bb48d..37b262f14e 100644 --- a/tests/model_inheritance_regress/models.py +++ b/tests/model_inheritance_regress/models.py @@ -1,5 +1,7 @@ import datetime +from django_mongodb.fields import ObjectIdAutoField + from django.db import models @@ -195,7 +197,7 @@ class Profile(User): # Check concrete + concrete -> concrete -> concrete class Politician(models.Model): - politician_id = models.AutoField(primary_key=True) + politician_id = ObjectIdAutoField(primary_key=True) title = models.CharField(max_length=50) diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 3fd176ce7a..9435d16322 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -664,8 +664,8 @@ def test_queryset_override(self): '" % (band2.id, self.band.id), ) @@ -687,7 +687,7 @@ class ConcertAdminWithForm(ModelAdmin): '" % self.band.id, ) diff --git a/tests/multiple_database/models.py b/tests/multiple_database/models.py index 7de784e149..5f4d8d3d50 100644 --- a/tests/multiple_database/models.py +++ b/tests/multiple_database/models.py @@ -7,7 +7,7 @@ class Review(models.Model): source = models.CharField(max_length=100) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() class Meta: diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py index 337ebae75e..383c8e80df 100644 --- a/tests/multiple_database/tests.py +++ b/tests/multiple_database/tests.py @@ -142,15 +142,15 @@ def test_basic_queries(self): with self.assertRaises(Book.DoesNotExist): Book.objects.using("default").get(published__year=2009) - years = Book.objects.using("other").dates("published", "year") - self.assertEqual([o.year for o in years], [2009]) - years = Book.objects.using("default").dates("published", "year") - self.assertEqual([o.year for o in years], []) - - months = Book.objects.using("other").dates("published", "month") - self.assertEqual([o.month for o in months], [5]) - months = Book.objects.using("default").dates("published", "month") - self.assertEqual([o.month for o in months], []) + # years = Book.objects.using("other").dates("published", "year") + # self.assertEqual([o.year for o in years], [2009]) + # years = Book.objects.using("default").dates("published", "year") + # self.assertEqual([o.year for o in years], []) + + # months = Book.objects.using("other").dates("published", "month") + # self.assertEqual([o.month for o in months], [5]) + # months = Book.objects.using("default").dates("published", "month") + # self.assertEqual([o.month for o in months], []) def test_m2m_separation(self): "M2M fields are constrained to a single database" diff --git a/tests/ordering/models.py b/tests/ordering/models.py index c365da7642..9fa4b9bb54 100644 --- a/tests/ordering/models.py +++ b/tests/ordering/models.py @@ -50,7 +50,7 @@ class Meta: class OrderedByFArticle(Article): class Meta: proxy = True - ordering = (models.F("author").asc(nulls_first=True), "id") + ordering = (models.F("author").asc(), "id") class ChildArticle(Article): diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index 7caa43d489..f1476fec3e 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -107,9 +107,9 @@ def test_proxy_included_in_ancestors(self): Proxy models are included in the ancestors for a model's DoesNotExist and MultipleObjectsReturned """ - Person.objects.create(name="Foo McBar") - MyPerson.objects.create(name="Bazza del Frob") - LowerStatusPerson.objects.create(status="low", name="homer") + Person.objects.create(name="Foo McBar", pk=1) + MyPerson.objects.create(name="Bazza del Frob", pk=2) + LowerStatusPerson.objects.create(status="low", name="homer", pk=3) max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] with self.assertRaises(Person.DoesNotExist): @@ -119,8 +119,8 @@ def test_proxy_included_in_ancestors(self): with self.assertRaises(Person.DoesNotExist): StatusPerson.objects.get(name="Zathras") - StatusPerson.objects.create(name="Bazza Jr.") - StatusPerson.objects.create(name="Foo Jr.") + StatusPerson.objects.create(name="Bazza Jr.", pk=4) + StatusPerson.objects.create(name="Foo Jr.", pk=5) max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] with self.assertRaises(Person.MultipleObjectsReturned): diff --git a/tests/queries/models.py b/tests/queries/models.py index 546f9fad5b..0f39811c6c 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -4,6 +4,8 @@ import datetime +from django_mongodb.fields import ObjectIdAutoField + from django.db import connection, models from django.db.models.functions import Now @@ -436,7 +438,7 @@ class ChildObjectA(ObjectA): class ObjectB(models.Model): name = models.CharField(max_length=50) objecta = models.ForeignKey(ObjectA, models.CASCADE) - num = models.PositiveIntegerField() + num = models.CharField(max_length=24) def __str__(self): return self.name @@ -636,7 +638,7 @@ class MyObject(models.Model): class Order(models.Model): - id = models.IntegerField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) name = models.CharField(max_length=12, null=True, default="") class Meta: @@ -648,7 +650,7 @@ def __str__(self): class OrderItem(models.Model): order = models.ForeignKey(Order, models.CASCADE, related_name="items") - status = models.IntegerField() + status = models.CharField(max_length=24) class Meta: ordering = ("pk",) @@ -686,13 +688,13 @@ def __str__(self): class Ticket21203Parent(models.Model): - parentid = models.AutoField(primary_key=True) + parentid = ObjectIdAutoField(primary_key=True) parent_bool = models.BooleanField(default=True) created = models.DateTimeField(auto_now=True) class Ticket21203Child(models.Model): - childid = models.AutoField(primary_key=True) + childid = ObjectIdAutoField(primary_key=True) parent = models.ForeignKey(Ticket21203Parent, models.CASCADE) diff --git a/tests/queries/test_explain.py b/tests/queries/test_explain.py index 11684356b9..7dca42136f 100644 --- a/tests/queries/test_explain.py +++ b/tests/queries/test_explain.py @@ -32,31 +32,30 @@ def test_basic(self): for idx, queryset in enumerate(querysets): for format in all_formats: with self.subTest(format=format, queryset=idx): - with self.assertNumQueries(1) as captured_queries: - result = queryset.explain(format=format) - self.assertTrue( - captured_queries[0]["sql"].startswith( - connection.ops.explain_prefix + result = queryset.explain(format=format) + # self.assertTrue( + # captured_queries[0]["sql"].startswith( + # connection.ops.explain_prefix + # ) + # ) + self.assertIsInstance(result, str) + self.assertTrue(result) + if not format: + continue + if format.lower() == "xml": + try: + xml.etree.ElementTree.fromstring(result) + except xml.etree.ElementTree.ParseError as e: + self.fail( + f"QuerySet.explain() result is not valid XML: {e}" + ) + elif format.lower() == "json": + try: + json.loads(result) + except json.JSONDecodeError as e: + self.fail( + f"QuerySet.explain() result is not valid JSON: {e}" ) - ) - self.assertIsInstance(result, str) - self.assertTrue(result) - if not format: - continue - if format.lower() == "xml": - try: - xml.etree.ElementTree.fromstring(result) - except xml.etree.ElementTree.ParseError as e: - self.fail( - f"QuerySet.explain() result is not valid XML: {e}" - ) - elif format.lower() == "json": - try: - json.loads(result) - except json.JSONDecodeError as e: - self.fail( - f"QuerySet.explain() result is not valid JSON: {e}" - ) def test_unknown_options(self): with self.assertRaisesMessage(ValueError, "Unknown options: TEST, TEST2"): diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 71d2418f2b..89b20233c9 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -5,7 +5,7 @@ from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext -from .models import Author, Celebrity, ExtraInfo, Number, ReservedName +from .models import Author, Celebrity, ExtraInfo, Number, Report, ReservedName @skipUnlessDBFeature("supports_select_union") @@ -123,6 +123,31 @@ def test_union_nested(self): ordered=False, ) + def test_union_with_different_models(self): + expected_result = { + "Angel", + "Lionel", + "Emiliano", + "Demetrio", + "Daniel", + "Javier", + } + Celebrity.objects.create(name="Angel") + Celebrity.objects.create(name="Lionel") + Celebrity.objects.create(name="Emiliano") + Celebrity.objects.create(name="Demetrio") + Report.objects.create(name="Demetrio") + Report.objects.create(name="Daniel") + Report.objects.create(name="Javier") + qs1 = Celebrity.objects.values(alias=F("name")) + qs2 = Report.objects.values(alias_author=F("name")) + qs3 = qs1.union(qs2).values("name") + self.assertCountEqual((e["name"] for e in qs3), expected_result) + qs4 = qs1.union(qs2) + self.assertCountEqual((e["alias"] for e in qs4), expected_result) + qs5 = qs2.union(qs1) + self.assertCountEqual((e["alias_author"] for e in qs5), expected_result) + @skipUnlessDBFeature("supports_select_intersection") def test_intersection_with_empty_qs(self): qs1 = Number.objects.all() @@ -474,6 +499,16 @@ def test_count_intersection(self): qs2 = Number.objects.filter(num__lte=5) self.assertEqual(qs1.intersection(qs2).count(), 1) + @skipUnlessDBFeature("supports_slicing_ordering_in_compound") + def test_count_union_with_select_related_projected(self): + e1 = ExtraInfo.objects.create(value=1, info="e1") + a1 = Author.objects.create(name="a1", num=1, extra=e1) + qs = Author.objects.select_related("extra").values("pk", "name", "extra__value") + self.assertEqual(len(qs.union(qs)), 1) + self.assertEqual( + qs.union(qs).first(), {"pk": a1.id, "name": "a1", "extra__value": 1} + ) + def test_exists_union(self): qs1 = Number.objects.filter(num__gte=5) qs2 = Number.objects.filter(num__lte=5) @@ -481,14 +516,14 @@ def test_exists_union(self): self.assertIs(qs1.union(qs2).exists(), True) captured_queries = context.captured_queries self.assertEqual(len(captured_queries), 1) - captured_sql = captured_queries[0]["sql"] - self.assertNotIn( - connection.ops.quote_name(Number._meta.pk.column), - captured_sql, - ) - self.assertEqual( - captured_sql.count(connection.ops.limit_offset_sql(None, 1)), 1 - ) + # captured_sql = captured_queries[0]["sql"] + # self.assertNotIn( + # connection.ops.quote_name(Number._meta.pk.column), + # captured_sql, + # ) + # self.assertEqual( + # captured_sql.count(connection.ops.limit_offset_sql(None, 1)), 1 + # ) def test_exists_union_empty_result(self): qs = Number.objects.filter(pk__in=[]) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 20665ab2cd..3ac0175557 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -889,7 +889,7 @@ def test_ticket7235(self): self.assertSequenceEqual(q.annotate(Count("food")), []) self.assertSequenceEqual(q.order_by("meal", "food"), []) self.assertSequenceEqual(q.distinct(), []) - self.assertSequenceEqual(q.extra(select={"foo": "1"}), []) + # self.assertSequenceEqual(q.extra(select={"foo": "1"}), []) self.assertSequenceEqual(q.reverse(), []) q.query.low_mark = 1 msg = "Cannot change a query once a slice has been taken." @@ -1835,27 +1835,27 @@ def test_ordering(self): # Ordering of extra() pieces is possible, too and you can mix extra # fields and model fields in the ordering. - self.assertSequenceEqual( - Ranking.objects.extra( - tables=["django_site"], order_by=["-django_site.id", "rank"] - ), - [self.rank1, self.rank2, self.rank3], - ) - - sql = "case when %s > 2 then 1 else 0 end" % connection.ops.quote_name("rank") - qs = Ranking.objects.extra(select={"good": sql}) - self.assertEqual( - [o.good for o in qs.extra(order_by=("-good",))], [True, False, False] - ) - self.assertSequenceEqual( - qs.extra(order_by=("-good", "id")), - [self.rank3, self.rank2, self.rank1], - ) + # self.assertSequenceEqual( + # Ranking.objects.extra( + # tables=["django_site"], order_by=["-django_site.id", "rank"] + # ), + # [self.rank1, self.rank2, self.rank3], + # ) + + # sql = "case when %s > 2 then 1 else 0 end" % connection.ops.quote_name("rank") + # qs = Ranking.objects.extra(select={"good": sql}) + # self.assertEqual( + # [o.good for o in qs.extra(order_by=("-good",))], [True, False, False] + # ) + # self.assertSequenceEqual( + # qs.extra(order_by=("-good", "id")), + # [self.rank3, self.rank2, self.rank1], + # ) # Despite having some extra aliases in the query, we can still omit # them in a values() query. - dicts = qs.values("id", "rank").order_by("id") - self.assertEqual([d["rank"] for d in dicts], [2, 1, 3]) + # dicts = qs.values("id", "rank").order_by("id") + # self.assertEqual([d["rank"] for d in dicts], [2, 1, 3]) def test_ticket7256(self): # An empty values() call includes all aliases, including those from an @@ -2268,9 +2268,9 @@ def test_distinct_exists(self): with CaptureQueriesContext(connection) as captured_queries: self.assertIs(Article.objects.distinct().exists(), False) self.assertEqual(len(captured_queries), 1) - captured_sql = captured_queries[0]["sql"] - self.assertNotIn(connection.ops.quote_name("id"), captured_sql) - self.assertNotIn(connection.ops.quote_name("name"), captured_sql) + # captured_sql = captured_queries[0]["sql"] + # self.assertNotIn(connection.ops.quote_name("id"), captured_sql) + # self.assertNotIn(connection.ops.quote_name("name"), captured_sql) def test_sliced_distinct_exists(self): with CaptureQueriesContext(connection) as captured_queries: @@ -3267,16 +3267,16 @@ def employ(employer, employee, title): .distinct() .order_by("name") ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): self.assertSequenceEqual(alex_nontech_employers, [google, intel, microsoft]) - sql = ctx.captured_queries[0]["sql"] + # sql = ctx.captured_queries[0]["sql"] # Company's ID should appear in SELECT and INNER JOIN, not in EXISTS as # the outer query reference is not necessary when an alias is reused. - company_id = "%s.%s" % ( - connection.ops.quote_name(Company._meta.db_table), - connection.ops.quote_name(Company._meta.get_field("id").column), - ) - self.assertEqual(sql.count(company_id), 2) + # company_id = "%s.%s" % ( + # connection.ops.quote_name(Company._meta.db_table), + # connection.ops.quote_name(Company._meta.get_field("id").column), + # ) + # self.assertEqual(sql.count(company_id), 2) def test_exclude_reverse_fk_field_ref(self): tag = Tag.objects.create() @@ -3312,12 +3312,12 @@ def test_exclude_nullable_fields(self): ) def test_exclude_multivalued_exists(self): - with CaptureQueriesContext(connection) as captured_queries: - self.assertSequenceEqual( - Job.objects.exclude(responsibilities__description="Programming"), - [self.j1], - ) - self.assertIn("exists", captured_queries[0]["sql"].lower()) + # with CaptureQueriesContext(connection) as captured_queries: + self.assertSequenceEqual( + Job.objects.exclude(responsibilities__description="Programming"), + [self.j1], + ) + # self.assertIn("exists", captured_queries[0]["sql"].lower()) def test_exclude_subquery(self): subquery = JobResponsibilities.objects.filter( diff --git a/tests/raw_query/models.py b/tests/raw_query/models.py index a8ccc11147..6dcc5667d9 100644 --- a/tests/raw_query/models.py +++ b/tests/raw_query/models.py @@ -1,10 +1,12 @@ from django.db import models +from django_mongodb.manager import MongoManager class Author(models.Model): first_name = models.CharField(max_length=255) last_name = models.CharField(max_length=255) dob = models.DateField() + objects = MongoManager() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -22,26 +24,31 @@ class Book(models.Model): author = models.ForeignKey(Author, models.CASCADE) paperback = models.BooleanField(default=False) opening_line = models.TextField() + objects = MongoManager() class BookFkAsPk(models.Model): book = models.ForeignKey( Book, models.CASCADE, primary_key=True, db_column="not_the_default" ) + objects = MongoManager() class Coffee(models.Model): brand = models.CharField(max_length=255, db_column="name") price = models.DecimalField(max_digits=10, decimal_places=2, default=0) + objects = MongoManager() class MixedCaseIDColumn(models.Model): id = models.AutoField(primary_key=True, db_column="MiXeD_CaSe_Id") + objects = MongoManager() class Reviewer(models.Model): reviewed = models.ManyToManyField(Book) + objects = MongoManager() class FriendlyAuthor(Author): - pass + objects = MongoManager() diff --git a/tests/raw_query/tests.py b/tests/raw_query/tests.py index 1dcc7ce740..4e2233c581 100644 --- a/tests/raw_query/tests.py +++ b/tests/raw_query/tests.py @@ -2,8 +2,8 @@ from decimal import Decimal from django.core.exceptions import FieldDoesNotExist -from django.db.models.query import RawQuerySet from django.test import TestCase, skipUnlessDBFeature +from django_mongodb.query import MongoRawQuerySet from .models import ( Author, @@ -81,7 +81,7 @@ def assertSuccessfulRawQuery( Execute the passed query against the passed model and check the output """ results = list( - model.objects.raw(query, params=params, translations=translations) + model.objects.raw_mql(query, params=params, translations=translations) ) self.assertProcessed(model, results, expected_results, expected_annotations) self.assertAnnotations(results, expected_annotations) @@ -124,19 +124,19 @@ def assertAnnotations(self, results, expected_annotations): self.assertEqual(getattr(result, annotation), value) def test_rawqueryset_repr(self): - queryset = RawQuerySet(raw_query="SELECT * FROM raw_query_author") + queryset = MongoRawQuerySet(raw_query=[]) self.assertEqual( - repr(queryset), "" + repr(queryset), "" ) self.assertEqual( - repr(queryset.query), "" + repr(queryset.query), "" ) def test_simple_raw_query(self): """ Basic test of raw query with a simple database query """ - query = "SELECT * FROM raw_query_author" + query = [] authors = Author.objects.all() self.assertSuccessfulRawQuery(Author, query, authors) @@ -145,7 +145,7 @@ def test_raw_query_lazy(self): Raw queries are lazy: they aren't actually executed until they're iterated over. """ - q = Author.objects.raw("SELECT * FROM raw_query_author") + q = Author.objects.raw_mql([]) self.assertIsNone(q.query.cursor) list(q) self.assertIsNotNone(q.query.cursor) @@ -154,7 +154,7 @@ def test_FK_raw_query(self): """ Test of a simple raw query against a model containing a foreign key """ - query = "SELECT * FROM raw_query_book" + query = [] books = Book.objects.all() self.assertSuccessfulRawQuery(Book, query, books) @@ -163,7 +163,7 @@ def test_db_column_handler(self): Test of a simple raw query against a model containing a field with db_column defined. """ - query = "SELECT * FROM raw_query_coffee" + query = [] coffees = Coffee.objects.all() self.assertSuccessfulRawQuery(Coffee, query, coffees) @@ -171,13 +171,13 @@ def test_pk_with_mixed_case_db_column(self): """ A raw query with a model that has a pk db_column with mixed case. """ - query = "SELECT * FROM raw_query_mixedcaseidcolumn" + query = [] queryset = MixedCaseIDColumn.objects.all() self.assertSuccessfulRawQuery(MixedCaseIDColumn, query, queryset) def test_order_handler(self): """ - Test of raw raw query's tolerance for columns being returned in any + Test of raw query's tolerance for columns being returned in any order """ selects = ( @@ -185,9 +185,10 @@ def test_order_handler(self): ("last_name, dob, first_name, id"), ("first_name, last_name, dob, id"), ) - for select in selects: - query = "SELECT %s FROM raw_query_author" % select + cols = [col.strip() for col in select.split(',')] + select = {col: 1 for col in cols} + query = [{"$project": select}] authors = Author.objects.all() self.assertSuccessfulRawQuery(Author, query, authors) @@ -211,7 +212,7 @@ def test_params(self): query = "SELECT * FROM raw_query_author WHERE first_name = %s" author = Author.objects.all()[2] params = [author.first_name] - qset = Author.objects.raw(query, params=params) + qset = Author.objects.raw_mql(query, params=params) results = list(qset) self.assertProcessed(Author, results, [author]) self.assertNoAnnotations(results) @@ -220,12 +221,12 @@ def test_params(self): def test_params_none(self): query = "SELECT * FROM raw_query_author WHERE first_name like 'J%'" - qset = Author.objects.raw(query, params=None) + qset = Author.objects.raw_mql(query, params=None) self.assertEqual(len(qset), 2) def test_escaped_percent(self): query = "SELECT * FROM raw_query_author WHERE first_name like 'J%%'" - qset = Author.objects.raw(query) + qset = Author.objects.raw_mql(query) self.assertEqual(len(qset), 2) @skipUnlessDBFeature("supports_paramstyle_pyformat") @@ -236,7 +237,7 @@ def test_pyformat_params(self): query = "SELECT * FROM raw_query_author WHERE first_name = %(first)s" author = Author.objects.all()[2] params = {"first": author.first_name} - qset = Author.objects.raw(query, params=params) + qset = Author.objects.raw_mql(query, params=params) results = list(qset) self.assertProcessed(Author, results, [author]) self.assertNoAnnotations(results) @@ -247,46 +248,46 @@ def test_query_representation(self): """ Test representation of raw query with parameters """ - query = "SELECT * FROM raw_query_author WHERE last_name = %(last)s" - qset = Author.objects.raw(query, {"last": "foo"}) + query = [{"$match": {"last_name": "%(last)s" % {"last": "foo"}}}] + qset = Author.objects.raw_mql(query) self.assertEqual( repr(qset), - "", + "", ) self.assertEqual( repr(qset.query), - "", + "", ) - query = "SELECT * FROM raw_query_author WHERE last_name = %s" - qset = Author.objects.raw(query, {"foo"}) + query = [{"$match": {"last_name": "%s" % "foo"}}] + qset = Author.objects.raw_mql(query) self.assertEqual( repr(qset), - "", + "", ) self.assertEqual( repr(qset.query), - "", + "", ) def test_many_to_many(self): """ Test of a simple raw query against a model containing a m2m field """ - query = "SELECT * FROM raw_query_reviewer" + query = [] reviewers = Reviewer.objects.all() self.assertSuccessfulRawQuery(Reviewer, query, reviewers) def test_extra_conversions(self): """Extra translations are ignored.""" - query = "SELECT * FROM raw_query_author" + query = [] translations = {"something": "else"} authors = Author.objects.all() self.assertSuccessfulRawQuery(Author, query, authors, translations=translations) def test_missing_fields(self): query = "SELECT id, first_name, dob FROM raw_query_author" - for author in Author.objects.raw(query): + for author in Author.objects.raw_mql(query): self.assertIsNotNone(author.first_name) # last_name isn't given, but it will be retrieved on demand self.assertIsNotNone(author.last_name) @@ -295,7 +296,7 @@ def test_missing_fields_without_PK(self): query = "SELECT first_name, dob FROM raw_query_author" msg = "Raw query must include the primary key" with self.assertRaisesMessage(FieldDoesNotExist, msg): - list(Author.objects.raw(query)) + list(Author.objects.raw_mql(query)) def test_annotations(self): query = ( @@ -319,9 +320,9 @@ def test_white_space_query(self): self.assertSuccessfulRawQuery(Author, query, authors) def test_multiple_iterations(self): - query = "SELECT * FROM raw_query_author" + query = [] normal_authors = Author.objects.all() - raw_authors = Author.objects.raw(query) + raw_authors = Author.objects.raw_mql(query) # First Iteration first_iterations = 0 @@ -340,30 +341,30 @@ def test_multiple_iterations(self): def test_get_item(self): # Indexing on RawQuerySets query = "SELECT * FROM raw_query_author ORDER BY id ASC" - third_author = Author.objects.raw(query)[2] + third_author = Author.objects.raw_mql(query)[2] self.assertEqual(third_author.first_name, "Bob") - first_two = Author.objects.raw(query)[0:2] + first_two = Author.objects.raw_mql(query)[0:2] self.assertEqual(len(first_two), 2) with self.assertRaises(TypeError): - Author.objects.raw(query)["test"] + Author.objects.raw_mql(query)["test"] def test_inheritance(self): f = FriendlyAuthor.objects.create( first_name="Wesley", last_name="Chun", dob=date(1962, 10, 28) ) - query = "SELECT * FROM raw_query_friendlyauthor" - self.assertEqual([o.pk for o in FriendlyAuthor.objects.raw(query)], [f.pk]) + query = [] + self.assertEqual([o.pk for o in FriendlyAuthor.objects.raw_mql(query)], [f.pk]) def test_query_count(self): self.assertNumQueries( - 1, list, Author.objects.raw("SELECT * FROM raw_query_author") + 1, list, Author.objects.raw_mql([]) ) def test_subquery_in_raw_sql(self): list( - Book.objects.raw( + Book.objects.raw_mql( "SELECT id FROM " "(SELECT * FROM raw_query_book WHERE paperback IS NOT NULL) sq" ) @@ -380,7 +381,7 @@ def test_db_column_name_is_used_in_raw_query(self): b = BookFkAsPk.objects.create(book=self.b1) self.assertEqual( list( - BookFkAsPk.objects.raw( + BookFkAsPk.objects.raw_mql( "SELECT not_the_default FROM raw_query_bookfkaspk" ) ), @@ -389,31 +390,31 @@ def test_db_column_name_is_used_in_raw_query(self): def test_decimal_parameter(self): c = Coffee.objects.create(brand="starbucks", price=20.5) - qs = Coffee.objects.raw( + qs = Coffee.objects.raw_mql( "SELECT * FROM raw_query_coffee WHERE price >= %s", params=[Decimal(20)] ) self.assertEqual(list(qs), [c]) def test_result_caching(self): with self.assertNumQueries(1): - books = Book.objects.raw("SELECT * FROM raw_query_book") + books = Book.objects.raw_mql([]) list(books) list(books) def test_iterator(self): with self.assertNumQueries(2): - books = Book.objects.raw("SELECT * FROM raw_query_book") + books = Book.objects.raw_mql([]) list(books.iterator()) list(books.iterator()) def test_bool(self): - self.assertIs(bool(Book.objects.raw("SELECT * FROM raw_query_book")), True) + self.assertIs(bool(Book.objects.raw_mql([])), True) self.assertIs( - bool(Book.objects.raw("SELECT * FROM raw_query_book WHERE id = 0")), False + bool(Book.objects.raw_mql("SELECT * FROM raw_query_book WHERE id = 0")), False ) def test_len(self): - self.assertEqual(len(Book.objects.raw("SELECT * FROM raw_query_book")), 4) + self.assertEqual(len(Book.objects.raw_mql([])), 4) self.assertEqual( - len(Book.objects.raw("SELECT * FROM raw_query_book WHERE id = 0")), 0 + len(Book.objects.raw_mql("SELECT * FROM raw_query_book WHERE id = 0")), 0 ) diff --git a/tests/runtests.py b/tests/runtests.py index 2eb7490170..535b53deee 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -13,6 +13,8 @@ import warnings from pathlib import Path +import django_mongodb + try: import django except ImportError as e: @@ -60,6 +62,9 @@ RUNTESTS_DIR = os.path.abspath(os.path.dirname(__file__)) +MONGODB_TEST_DIR = Path(django_mongodb.__file__).parent.parent / "tests" +sys.path.append(str(MONGODB_TEST_DIR)) + TEMPLATE_DIR = os.path.join(RUNTESTS_DIR, "templates") # Create a specific subdirectory for the duration of the test suite. @@ -142,6 +147,21 @@ def get_test_modules(gis_enabled): test_module = dirname + "." + test_module yield test_module + # Discover tests in django_mongodb/tests. + dirpath = os.path.join(MONGODB_TEST_DIR, dirname) + with os.scandir(dirpath) as entries: + for f in entries: + if ( + "." in f.name + or f.is_file() + or not os.path.exists(os.path.join(f.path, "__init__.py")) + ): + continue + test_module = f.name + if dirname: + test_module = dirname + "." + test_module + yield test_module + def get_label_module(label): """Return the top-level module part for a test label.""" diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 905e604d03..d498b8c616 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -260,15 +260,17 @@ def check_added_field_default( expected_default, cast_function=None, ): - with connection.cursor() as cursor: - schema_editor.add_field(model, field) - cursor.execute( - "SELECT {} FROM {};".format(field_name, model._meta.db_table) - ) - database_default = cursor.fetchall()[0][0] - if cast_function and type(database_default) is not type(expected_default): - database_default = cast_function(database_default) - self.assertEqual(database_default, expected_default) + schema_editor.add_field(model, field) + database_default = ( + connection.database[model._meta.db_table].find_one().get(field_name) + ) + # cursor.execute( + # "SELECT {} FROM {};".format(field_name, model._meta.db_table) + # ) + # database_default = cursor.fetchall()[0][0] + if cast_function and type(database_default) is not type(expected_default): + database_default = cast_function(database_default) + self.assertEqual(database_default, expected_default) def get_constraints_count(self, table, column, fk_to): """ @@ -348,6 +350,12 @@ def assertForeignKeyNotExists(self, model, column, expected_fk_table): with self.assertRaises(AssertionError): self.assertForeignKeyExists(model, column, expected_fk_table) + def assertTableExists(self, model): + self.assertIn(model._meta.db_table, connection.introspection.table_names()) + + def assertTableNotExists(self, model): + self.assertNotIn(model._meta.db_table, connection.introspection.table_names()) + # Tests def test_creation_deletion(self): """ @@ -357,14 +365,13 @@ def test_creation_deletion(self): # Create the table editor.create_model(Author) # The table is there - list(Author.objects.all()) + self.assertTableExists(Author) # Clean up that table editor.delete_model(Author) # No deferred SQL should be left over. self.assertEqual(editor.deferred_sql, []) # The table is gone - with self.assertRaises(DatabaseError): - list(Author.objects.all()) + self.assertTableNotExists(Author) @skipUnlessDBFeature("supports_foreign_keys") def test_fk(self): @@ -594,7 +601,7 @@ class Meta: editor.create_model(BookWeak) self.assertForeignKeyNotExists(BookWeak, "author_id", "schema_author") old_field = Author._meta.get_field("id") - new_field = BigAutoField(primary_key=True) + new_field = BigAutoField(primary_key=True, db_column="_id") new_field.model = Author new_field.set_attributes_from_name("id") # @isolate_apps() and inner models are needed to have the model @@ -650,36 +657,41 @@ def test_add_field(self): # Create the table with connection.schema_editor() as editor: editor.create_model(Author) + Author.objects.create() # Ensure there's no age field - columns = self.column_classes(Author) - self.assertNotIn("age", columns) + # columns = self.column_classes(Author) + # self.assertNotIn("age", columns) # Add the new field new_field = IntegerField(null=True) new_field.set_attributes_from_name("age") - with ( - CaptureQueriesContext(connection) as ctx, - connection.schema_editor() as editor, - ): + with connection.schema_editor() as editor: editor.add_field(Author, new_field) - drop_default_sql = editor.sql_alter_column_no_default % { - "column": editor.quote_name(new_field.name), - } - self.assertFalse( - any(drop_default_sql in query["sql"] for query in ctx.captured_queries) - ) + self.check_added_field_default( + editor, + Author, + new_field, + "age", + None, + ) + # drop_default_sql = editor.sql_alter_column_no_default % { + # "column": editor.quote_name(new_field.name), + # } + # self.assertFalse( + # any(drop_default_sql in query["sql"] for query in ctx.captured_queries) + # ) # Table is not rebuilt. - self.assertIs( - any("CREATE TABLE" in query["sql"] for query in ctx.captured_queries), False - ) - self.assertIs( - any("DROP TABLE" in query["sql"] for query in ctx.captured_queries), False - ) - columns = self.column_classes(Author) - self.assertEqual( - columns["age"][0], - connection.features.introspected_field_types["IntegerField"], - ) - self.assertTrue(columns["age"][1][6]) + # self.assertIs( + # any("CREATE TABLE" in query["sql"] for query in ctx.captured_queries), False + # ) + # self.assertIs( + # any("DROP TABLE" in query["sql"] for query in ctx.captured_queries), False + # ) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["age"][0], + # connection.features.introspected_field_types["IntegerField"], + # ) + # self.assertTrue(columns["age"][1][6]) def test_add_field_remove_field(self): """ @@ -700,8 +712,8 @@ def test_add_field_temp_default(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure there's no age field - columns = self.column_classes(Author) - self.assertNotIn("age", columns) + # columns = self.column_classes(Author) + # self.assertNotIn("age", columns) # Add some rows of data Author.objects.create(name="Andrew", height=30) Author.objects.create(name="Andrea") @@ -710,15 +722,22 @@ def test_add_field_temp_default(self): new_field.set_attributes_from_name("surname") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - columns = self.column_classes(Author) - self.assertEqual( - columns["surname"][0], - connection.features.introspected_field_types["CharField"], - ) - self.assertEqual( - columns["surname"][1][6], - connection.features.interprets_empty_strings_as_nulls, - ) + self.check_added_field_default( + editor, + Author, + new_field, + "surname", + "Godwin", + ) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["surname"][0], + # connection.features.introspected_field_types["CharField"], + # ) + # self.assertEqual( + # columns["surname"][1][6], + # connection.features.interprets_empty_strings_as_nulls, + # ) def test_add_field_temp_default_boolean(self): """ @@ -729,8 +748,8 @@ def test_add_field_temp_default_boolean(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure there's no age field - columns = self.column_classes(Author) - self.assertNotIn("age", columns) + # columns = self.column_classes(Author) + # self.assertNotIn("age", columns) # Add some rows of data Author.objects.create(name="Andrew", height=30) Author.objects.create(name="Andrea") @@ -739,12 +758,19 @@ def test_add_field_temp_default_boolean(self): new_field.set_attributes_from_name("awesome") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - columns = self.column_classes(Author) + self.check_added_field_default( + editor, + Author, + new_field, + "awesome", + False, + ) + # columns = self.column_classes(Author) # BooleanField are stored as TINYINT(1) on MySQL. - field_type = columns["awesome"][0] - self.assertEqual( - field_type, connection.features.introspected_field_types["BooleanField"] - ) + # field_type = columns["awesome"][0] + # self.assertEqual( + # field_type, connection.features.introspected_field_types["BooleanField"] + # ) def test_add_field_default_transform(self): """ @@ -773,26 +799,41 @@ def get_prep_value(self, value): new_field.set_attributes_from_name("thing") with connection.schema_editor() as editor: editor.add_field(Author, new_field) + self.check_added_field_default( + editor, + Author, + new_field, + "thing", + 1, + ) # Ensure the field is there - columns = self.column_classes(Author) - field_type, field_info = columns["thing"] - self.assertEqual( - field_type, connection.features.introspected_field_types["IntegerField"] - ) + # columns = self.column_classes(Author) + # field_type, field_info = columns["thing"] + # self.assertEqual( + # field_type, connection.features.introspected_field_types["IntegerField"] + # ) # Make sure the values were transformed correctly - self.assertEqual(Author.objects.extra(where=["thing = 1"]).count(), 2) + # self.assertEqual(Author.objects.extra(where=["thing = 1"]).count(), 2) def test_add_field_o2o_nullable(self): with connection.schema_editor() as editor: editor.create_model(Author) editor.create_model(Note) + Author.objects.create() new_field = OneToOneField(Note, CASCADE, null=True) new_field.set_attributes_from_name("note") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - columns = self.column_classes(Author) - self.assertIn("note_id", columns) - self.assertTrue(columns["note_id"][1][6]) + self.check_added_field_default( + editor, + Author, + new_field, + "note", + None, + ) + # columns = self.column_classes(Author) + # self.assertIn("note_id", columns) + # self.assertTrue(columns["note_id"][1][6]) def test_add_field_binary(self): """ @@ -801,28 +842,44 @@ def test_add_field_binary(self): # Create the table with connection.schema_editor() as editor: editor.create_model(Author) + Author.objects.create() # Add the new field new_field = BinaryField(blank=True) new_field.set_attributes_from_name("bits") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - columns = self.column_classes(Author) + self.check_added_field_default( + editor, + Author, + new_field, + "bits", + b"", + ) + # columns = self.column_classes(Author) # MySQL annoyingly uses the same backend, so it'll come back as one of # these two types. - self.assertIn(columns["bits"][0], ("BinaryField", "TextField")) + # self.assertIn(columns["bits"][0], ("BinaryField", "TextField")) def test_add_field_durationfield_with_default(self): with connection.schema_editor() as editor: editor.create_model(Author) + Author.objects.create() new_field = DurationField(default=datetime.timedelta(minutes=10)) new_field.set_attributes_from_name("duration") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - columns = self.column_classes(Author) - self.assertEqual( - columns["duration"][0], - connection.features.introspected_field_types["DurationField"], - ) + self.check_added_field_default( + editor, + Author, + new_field, + "duration", + 600000, + ) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["duration"][0], + # connection.features.introspected_field_types["DurationField"], + # ) @unittest.skipUnless(connection.vendor == "mysql", "MySQL specific") def test_add_binaryfield_mediumblob(self): @@ -995,10 +1052,13 @@ class Meta: def test_remove_field(self): with connection.schema_editor() as editor: editor.create_model(Author) + a = Author.objects.create(name="foo") with CaptureQueriesContext(connection) as ctx: editor.remove_field(Author, Author._meta.get_field("name")) - columns = self.column_classes(Author) - self.assertNotIn("name", columns) + a.refresh_from_db() + self.assertIsNone(a.name) + # columns = self.column_classes(Author) + # self.assertNotIn("name", columns) if getattr(connection.features, "can_alter_table_drop_column", True): # Table is not rebuilt. self.assertIs( @@ -1013,13 +1073,46 @@ def test_remove_field(self): def test_remove_indexed_field(self): with connection.schema_editor() as editor: editor.create_model(AuthorCharFieldWithIndex) + field = AuthorCharFieldWithIndex._meta.get_field("char_field") + column = field.column + self.assertEqual( + self.get_constraints_count( + AuthorCharFieldWithIndex._meta.db_table, column, "" + ), + {"fks": 0, "indexes": 1, "uniques": 0}, + ) + a = AuthorCharFieldWithIndex.objects.create(char_field="foo") with connection.schema_editor() as editor: - editor.remove_field( - AuthorCharFieldWithIndex, - AuthorCharFieldWithIndex._meta.get_field("char_field"), - ) - columns = self.column_classes(AuthorCharFieldWithIndex) - self.assertNotIn("char_field", columns) + editor.remove_field(AuthorCharFieldWithIndex, field) + a.refresh_from_db() + self.assertIsNone(a.char_field) + self.assertEqual( + self.get_constraints_count( + AuthorCharFieldWithIndex._meta.db_table, column, "" + ), + {"fks": 0, "indexes": 0, "uniques": 0}, + ) + # columns = self.column_classes(AuthorCharFieldWithIndex) + # self.assertNotIn("char_field", columns) + + def test_remove_unique_field(self): + with connection.schema_editor() as editor: + editor.create_model(AuthorWithUniqueName) + field = AuthorWithUniqueName._meta.get_field("name") + column = field.column + self.assertEqual( + self.get_constraints_count(AuthorWithUniqueName._meta.db_table, column, ""), + {"fks": 0, "indexes": 0, "uniques": 1}, + ) + a = AuthorWithUniqueName.objects.create(name="foo") + with connection.schema_editor() as editor: + editor.remove_field(AuthorWithUniqueName, field) + a.refresh_from_db() + self.assertIsNone(a.name) + self.assertEqual( + self.get_constraints_count(AuthorWithUniqueName._meta.db_table, column, ""), + {"fks": 0, "indexes": 0, "uniques": 0}, + ) def test_alter(self): """ @@ -1029,52 +1122,61 @@ def test_alter(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure the field is right to begin with - columns = self.column_classes(Author) - self.assertEqual( - columns["name"][0], - connection.features.introspected_field_types["CharField"], - ) - self.assertEqual( - bool(columns["name"][1][6]), - bool(connection.features.interprets_empty_strings_as_nulls), - ) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["name"][0], + # connection.features.introspected_field_types["CharField"], + # ) + # self.assertEqual( + # bool(columns["name"][1][6]), + # bool(connection.features.interprets_empty_strings_as_nulls), + # ) # Alter the name field to a TextField old_field = Author._meta.get_field("name") new_field = TextField(null=True) new_field.set_attributes_from_name("name") with connection.schema_editor() as editor: editor.alter_field(Author, old_field, new_field, strict=True) - columns = self.column_classes(Author) - self.assertEqual(columns["name"][0], "TextField") - self.assertTrue(columns["name"][1][6]) + # columns = self.column_classes(Author) + # self.assertEqual(columns["name"][0], "TextField") + # self.assertTrue(columns["name"][1][6]) # Change nullability again new_field2 = TextField(null=False) new_field2.set_attributes_from_name("name") with connection.schema_editor() as editor: editor.alter_field(Author, new_field, new_field2, strict=True) - columns = self.column_classes(Author) - self.assertEqual(columns["name"][0], "TextField") - self.assertEqual( - bool(columns["name"][1][6]), - bool(connection.features.interprets_empty_strings_as_nulls), - ) + # columns = self.column_classes(Author) + # self.assertEqual(columns["name"][0], "TextField") + # self.assertEqual( + # bool(columns["name"][1][6]), + # bool(connection.features.interprets_empty_strings_as_nulls), + # ) + @isolate_apps("schema") def test_alter_auto_field_to_integer_field(self): # Create the table with connection.schema_editor() as editor: editor.create_model(Author) # Change AutoField to IntegerField old_field = Author._meta.get_field("id") - new_field = IntegerField(primary_key=True) + new_field = IntegerField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: editor.alter_field(Author, old_field, new_field, strict=True) + # Now that ID is an IntegerField, the database raises an error if it # isn't provided. + class NewAuthor(Model): + id = new_field + + class Meta: + app_label = "schema" + db_table = "schema_author" + if not connection.features.supports_unspecified_pk: with self.assertRaises(DatabaseError): - Author.objects.create() + NewAuthor.objects.create() def test_alter_auto_field_to_char_field(self): # Create the table @@ -1082,7 +1184,7 @@ def test_alter_auto_field_to_char_field(self): editor.create_model(Author) # Change AutoField to CharField old_field = Author._meta.get_field("id") - new_field = CharField(primary_key=True, max_length=50) + new_field = CharField(primary_key=True, max_length=50, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -1139,7 +1241,7 @@ class Meta: editor.create_model(Foo) self.isolated_local_models = [Foo] old_field = Foo._meta.get_field("id") - new_field = BigAutoField(primary_key=True) + new_field = BigAutoField(primary_key=True, db_column="_id") new_field.model = Foo new_field.set_attributes_from_name("id") with connection.schema_editor() as editor: @@ -1229,8 +1331,8 @@ def test_alter_text_field_to_date_field(self): with connection.schema_editor() as editor: editor.alter_field(Note, old_field, new_field, strict=True) # Make sure the field isn't nullable - columns = self.column_classes(Note) - self.assertFalse(columns["info"][1][6]) + # columns = self.column_classes(Note) + # self.assertFalse(columns["info"][1][6]) def test_alter_text_field_to_datetime_field(self): """ @@ -1245,8 +1347,8 @@ def test_alter_text_field_to_datetime_field(self): with connection.schema_editor() as editor: editor.alter_field(Note, old_field, new_field, strict=True) # Make sure the field isn't nullable - columns = self.column_classes(Note) - self.assertFalse(columns["info"][1][6]) + # columns = self.column_classes(Note) + # self.assertFalse(columns["info"][1][6]) def test_alter_text_field_to_time_field(self): """ @@ -1261,8 +1363,8 @@ def test_alter_text_field_to_time_field(self): with connection.schema_editor() as editor: editor.alter_field(Note, old_field, new_field, strict=True) # Make sure the field isn't nullable - columns = self.column_classes(Note) - self.assertFalse(columns["info"][1][6]) + # columns = self.column_classes(Note) + # self.assertFalse(columns["info"][1][6]) @skipIfDBFeature("interprets_empty_strings_as_nulls") def test_alter_textual_field_keep_null_status(self): @@ -1326,8 +1428,8 @@ def test_alter_null_to_not_null(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure the field is right to begin with - columns = self.column_classes(Author) - self.assertTrue(columns["height"][1][6]) + # columns = self.column_classes(Author) + # self.assertTrue(columns["height"][1][6]) # Create some test data Author.objects.create(name="Not null author", height=12) Author.objects.create(name="Null author") @@ -1340,8 +1442,8 @@ def test_alter_null_to_not_null(self): new_field.set_attributes_from_name("height") with connection.schema_editor() as editor: editor.alter_field(Author, old_field, new_field, strict=True) - columns = self.column_classes(Author) - self.assertFalse(columns["height"][1][6]) + # columns = self.column_classes(Author) + # self.assertFalse(columns["height"][1][6]) # Verify default value self.assertEqual(Author.objects.get(name="Not null author").height, 12) self.assertEqual(Author.objects.get(name="Null author").height, 42) @@ -1671,8 +1773,8 @@ def test_alter_null_to_not_null_keeping_default(self): with connection.schema_editor() as editor: editor.create_model(AuthorWithDefaultHeight) # Ensure the field is right to begin with - columns = self.column_classes(AuthorWithDefaultHeight) - self.assertTrue(columns["height"][1][6]) + # columns = self.column_classes(AuthorWithDefaultHeight) + # self.assertTrue(columns["height"][1][6]) # Alter the height field to NOT NULL keeping the previous default old_field = AuthorWithDefaultHeight._meta.get_field("height") new_field = PositiveIntegerField(default=42) @@ -1681,8 +1783,8 @@ def test_alter_null_to_not_null_keeping_default(self): editor.alter_field( AuthorWithDefaultHeight, old_field, new_field, strict=True ) - columns = self.column_classes(AuthorWithDefaultHeight) - self.assertFalse(columns["height"][1][6]) + # columns = self.column_classes(AuthorWithDefaultHeight) + # self.assertFalse(columns["height"][1][6]) @skipUnlessDBFeature("supports_foreign_keys") def test_alter_fk(self): @@ -1885,7 +1987,7 @@ def test_autofield_to_o2o(self): # Rename the field. old_field = Author._meta.get_field("id") - new_field = AutoField(primary_key=True) + new_field = AutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("note_ptr") new_field.model = Author @@ -1898,11 +2000,11 @@ def test_autofield_to_o2o(self): with connection.schema_editor() as editor: editor.alter_field(Author, new_field, new_field_o2o, strict=True) - columns = self.column_classes(Author) - field_type, _ = columns["note_ptr_id"] - self.assertEqual( - field_type, connection.features.introspected_field_types["IntegerField"] - ) + # columns = self.column_classes(Author) + # field_type, _ = columns["note_ptr_id"] + # self.assertEqual( + # field_type, connection.features.introspected_field_types["IntegerField"] + # ) def test_alter_field_fk_keeps_index(self): with connection.schema_editor() as editor: @@ -2032,7 +2134,7 @@ def test_alter_implicit_id_to_explicit(self): editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = AutoField(primary_key=True) + new_field = AutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -2046,7 +2148,7 @@ def test_alter_autofield_pk_to_bigautofield_pk(self): with connection.schema_editor() as editor: editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = BigAutoField(primary_key=True) + new_field = BigAutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -2065,7 +2167,7 @@ def test_alter_autofield_pk_to_smallautofield_pk(self): with connection.schema_editor() as editor: editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = SmallAutoField(primary_key=True) + new_field = SmallAutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -2269,6 +2371,7 @@ class Meta: with self.assertRaises(IntegrityError): IntegerUnique.objects.create(i=1, j=2) + @isolate_apps("schema") def test_rename(self): """ Tests simple altering of fields @@ -2277,24 +2380,34 @@ def test_rename(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure the field is right to begin with - columns = self.column_classes(Author) - self.assertEqual( - columns["name"][0], - connection.features.introspected_field_types["CharField"], - ) - self.assertNotIn("display_name", columns) + Author.objects.create(name="foo") + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["name"][0], + # connection.features.introspected_field_types["CharField"], + # ) + # self.assertNotIn("display_name", columns) # Alter the name field's name old_field = Author._meta.get_field("name") new_field = CharField(max_length=254) new_field.set_attributes_from_name("display_name") with connection.schema_editor() as editor: editor.alter_field(Author, old_field, new_field, strict=True) - columns = self.column_classes(Author) - self.assertEqual( - columns["display_name"][0], - connection.features.introspected_field_types["CharField"], - ) - self.assertNotIn("name", columns) + + class NewAuthor(Model): + display_name = new_field + + class Meta: + app_label = "schema" + db_table = "schema_author" + + self.assertEqual(NewAuthor.objects.get().display_name, "foo") + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["display_name"][0], + # connection.features.introspected_field_types["CharField"], + # ) + # self.assertNotIn("name", columns) @isolate_apps("schema") def test_rename_referenced_field(self): @@ -2334,9 +2447,9 @@ def test_rename_keep_null_status(self): new_field.set_attributes_from_name("detail_info") with connection.schema_editor() as editor: editor.alter_field(Note, old_field, new_field, strict=True) - columns = self.column_classes(Note) - self.assertEqual(columns["detail_info"][0], "TextField") - self.assertNotIn("info", columns) + # columns = self.column_classes(Note) + # self.assertEqual(columns["detail_info"][0], "TextField") + # self.assertNotIn("info", columns) with self.assertRaises(IntegrityError): NoteRename.objects.create(detail_info=None) @@ -2373,14 +2486,21 @@ class Meta: with connection.schema_editor() as editor: editor.create_model(Author) - + Author.objects.create() field = IntegerField(default=1985, db_default=1988) field.set_attributes_from_name("birth_year") field.model = Author with connection.schema_editor() as editor: editor.add_field(Author, field) - columns = self.column_classes(Author) - self.assertEqual(columns["birth_year"][1].default, "1988") + self.check_added_field_default( + editor, + Author, + field, + "birth_year", + 1985, + ) + # columns = self.column_classes(Author) + # self.assertEqual(columns["birth_year"][1].default, "1988") @isolate_apps("schema") def test_add_text_field_with_db_default(self): @@ -2392,8 +2512,8 @@ class Meta: with connection.schema_editor() as editor: editor.create_model(Author) - columns = self.column_classes(Author) - self.assertIn("(missing)", columns["description"][1].default) + # columns = self.column_classes(Author) + # self.assertIn("(missing)", columns["description"][1].default) @isolate_apps("schema") def test_db_default_equivalent_sql_noop(self): @@ -2486,14 +2606,17 @@ class Meta: editor.create_model(Author) editor.create_model(TagM2MTest) editor.create_model(LocalBookWithM2M) - # Ensure there is now an m2m table there - columns = self.column_classes( + self.assertTableExists( LocalBookWithM2M._meta.get_field("tags").remote_field.through ) - self.assertEqual( - columns["tagm2mtest_id"][0], - connection.features.introspected_field_types["IntegerField"], - ) + # Ensure there is now an m2m table there + # columns = self.column_classes( + # LocalBookWithM2M._meta.get_field("tags").remote_field.through + # ) + # self.assertEqual( + # columns["tagm2mtest_id"][0], + # connection.features.introspected_field_types["IntegerField"], + # ) def test_m2m_create(self): self._test_m2m_create(ManyToManyField) @@ -2534,15 +2657,16 @@ class Meta: editor.create_model(TagM2MTest) editor.create_model(LocalBookWithM2MThrough) # Ensure there is now an m2m table there - columns = self.column_classes(LocalTagThrough) - self.assertEqual( - columns["book_id"][0], - connection.features.introspected_field_types["IntegerField"], - ) - self.assertEqual( - columns["tag_id"][0], - connection.features.introspected_field_types["IntegerField"], - ) + self.assertTableExists(LocalTagThrough) + # columns = self.column_classes(LocalTagThrough) + # self.assertEqual( + # columns["book_id"][0], + # connection.features.introspected_field_types["IntegerField"], + # ) + # self.assertEqual( + # columns["tag_id"][0], + # connection.features.introspected_field_types["IntegerField"], + # ) def test_m2m_create_through(self): self._test_m2m_create_through(ManyToManyField) @@ -2610,35 +2734,34 @@ class Meta: new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors") new_field.contribute_to_class(LocalAuthorWithM2M, "tags") # Ensure there's no m2m table there - with self.assertRaises(DatabaseError): - self.column_classes(new_field.remote_field.through) + self.assertTableNotExists(new_field.remote_field.through) + # with self.assertRaises(DatabaseError): + # self.column_classes(new_field.remote_field.through) # Add the field - with ( - CaptureQueriesContext(connection) as ctx, - connection.schema_editor() as editor, - ): + with connection.schema_editor() as editor: editor.add_field(LocalAuthorWithM2M, new_field) # Table is not rebuilt. - self.assertEqual( - len( - [ - query["sql"] - for query in ctx.captured_queries - if "CREATE TABLE" in query["sql"] - ] - ), - 1, - ) - self.assertIs( - any("DROP TABLE" in query["sql"] for query in ctx.captured_queries), - False, - ) + # self.assertEqual( + # len( + # [ + # query["sql"] + # for query in ctx.captured_queries + # if "CREATE TABLE" in query["sql"] + # ] + # ), + # 1, + # ) + # self.assertIs( + # any("DROP TABLE" in query["sql"] for query in ctx.captured_queries), + # False, + # ) # Ensure there is now an m2m table there - columns = self.column_classes(new_field.remote_field.through) - self.assertEqual( - columns["tagm2mtest_id"][0], - connection.features.introspected_field_types["IntegerField"], - ) + self.assertTableExists(new_field.remote_field.through) + # columns = self.column_classes(new_field.remote_field.through) + # self.assertEqual( + # columns["tagm2mtest_id"][0], + # connection.features.introspected_field_types["IntegerField"], + # ) # "Alter" the field. This should not rename the DB table to itself. with connection.schema_editor() as editor: @@ -2648,8 +2771,9 @@ class Meta: with connection.schema_editor() as editor: editor.remove_field(LocalAuthorWithM2M, new_field) # Ensure there's no m2m table there - with self.assertRaises(DatabaseError): - self.column_classes(new_field.remote_field.through) + self.assertTableNotExists(new_field.remote_field.through) + # with self.assertRaises(DatabaseError): + # self.column_classes(new_field.remote_field.through) # Make sure the model state is coherent with the table one now that # we've removed the tags field. @@ -2700,7 +2824,8 @@ class Meta: editor.create_model(LocalAuthorWithM2MThrough) editor.create_model(TagM2MTest) # Ensure the m2m table is there - self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3) + self.assertTableExists(LocalAuthorTag) + # self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3) # "Alter" the field's blankness. This should not actually do anything. old_field = LocalAuthorWithM2MThrough._meta.get_field("tags") new_field = M2MFieldClass( @@ -2712,7 +2837,8 @@ class Meta: LocalAuthorWithM2MThrough, old_field, new_field, strict=True ) # Ensure the m2m table is still there - self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3) + self.assertTableExists(LocalAuthorTag) + # self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3) def test_m2m_through_alter(self): self._test_m2m_through_alter(ManyToManyField) @@ -2746,6 +2872,9 @@ class Meta: editor.create_model(TagM2MTest) editor.create_model(UniqueTest) # Ensure the M2M exists and points to TagM2MTest + self.assertTableExists( + LocalBookWithM2M._meta.get_field("tags").remote_field.through + ) if connection.features.supports_foreign_keys: self.assertForeignKeyExists( LocalBookWithM2M._meta.get_field("tags").remote_field.through, @@ -2759,10 +2888,13 @@ class Meta: with connection.schema_editor() as editor: editor.alter_field(LocalBookWithM2M, old_field, new_field, strict=True) # Ensure old M2M is gone - with self.assertRaises(DatabaseError): - self.column_classes( - LocalBookWithM2M._meta.get_field("tags").remote_field.through - ) + self.assertTableNotExists( + LocalBookWithM2M._meta.get_field("tags").remote_field.through + ) + # with self.assertRaises(DatabaseError): + # self.column_classes( + # LocalBookWithM2M._meta.get_field("tags").remote_field.through + # ) # This model looks like the new model and is used for teardown. opts = LocalBookWithM2M._meta @@ -2802,7 +2934,8 @@ class Meta: editor.create_model(LocalTagM2MTest) self.isolated_local_models = [LocalM2M, LocalTagM2MTest] # Ensure the m2m table is there. - self.assertEqual(len(self.column_classes(LocalM2M)), 1) + self.assertTableExists(LocalM2M) + # self.assertEqual(len(self.column_classes(LocalM2M)), 1) # Alter a field in LocalTagM2MTest. old_field = LocalTagM2MTest._meta.get_field("title") new_field = CharField(max_length=254) @@ -2813,7 +2946,8 @@ class Meta: with connection.schema_editor() as editor: editor.alter_field(LocalTagM2MTest, old_field, new_field, strict=True) # Ensure the m2m table is still there. - self.assertEqual(len(self.column_classes(LocalM2M)), 1) + self.assertTableExists(LocalM2M) + # self.assertEqual(len(self.column_classes(LocalM2M)), 1) @skipUnlessDBFeature( "supports_column_check_constraints", "can_introspect_check_constraints" @@ -3059,11 +3193,11 @@ class Meta: new_field = SlugField(max_length=75, unique=True) new_field.model = Tag new_field.set_attributes_from_name("slug") - with self.assertLogs("django.db.backends.schema", "DEBUG") as cm: - with connection.schema_editor() as editor: - editor.alter_field(Tag, Tag._meta.get_field("slug"), new_field) + # with self.assertLogs("django.db.backends.schema", "DEBUG") as cm: + with connection.schema_editor() as editor: + editor.alter_field(Tag, Tag._meta.get_field("slug"), new_field) # One SQL statement is executed to alter the field. - self.assertEqual(len(cm.records), 1) + # self.assertEqual(len(cm.records), 1) # Ensure that the field is still unique. Tag.objects.create(title="foo", slug="foo") with self.assertRaises(IntegrityError): @@ -3074,7 +3208,7 @@ def test_remove_ignored_unique_constraint_not_create_fk_index(self): editor.create_model(Author) editor.create_model(Book) constraint = UniqueConstraint( - "author", + fields=["author"], condition=Q(title__in=["tHGttG", "tRatEotU"]), name="book_author_condition_uniq", ) @@ -3376,10 +3510,10 @@ def test_unique_constraint(self): # Add constraint. with connection.schema_editor() as editor: editor.add_constraint(Author, constraint) - sql = constraint.create_sql(Author, editor) table = Author._meta.db_table - self.assertIs(sql.references_table(table), True) - self.assertIs(sql.references_column(table, "name"), True) + constraints = self.get_constraints(table) + self.assertIn(constraint.name, constraints) + self.assertEqual(constraints[constraint.name]["unique"], True) # Remove constraint. with connection.schema_editor() as editor: editor.remove_constraint(Author, constraint) @@ -3904,33 +4038,38 @@ class Meta: with connection.schema_editor() as editor: editor.create_model(Author) editor.create_model(Book) + self.assertTableExists(Author) # Ensure the table is there to begin with - columns = self.column_classes(Author) - self.assertEqual( - columns["name"][0], - connection.features.introspected_field_types["CharField"], - ) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["name"][0], + # connection.features.introspected_field_types["CharField"], + # ) # Alter the table with connection.schema_editor() as editor: editor.alter_db_table(Author, "schema_author", "schema_otherauthor") + self.assertTableNotExists(Author) Author._meta.db_table = "schema_otherauthor" - columns = self.column_classes(Author) - self.assertEqual( - columns["name"][0], - connection.features.introspected_field_types["CharField"], - ) + self.assertTableExists(Author) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["name"][0], + # connection.features.introspected_field_types["CharField"], + # ) # Ensure the foreign key reference was updated - self.assertForeignKeyExists(Book, "author_id", "schema_otherauthor") + # self.assertForeignKeyExists(Book, "author_id", "schema_otherauthor") # Alter the table again with connection.schema_editor() as editor: editor.alter_db_table(Author, "schema_otherauthor", "schema_author") + self.assertTableNotExists(Author) # Ensure the table is still there Author._meta.db_table = "schema_author" - columns = self.column_classes(Author) - self.assertEqual( - columns["name"][0], - connection.features.introspected_field_types["CharField"], - ) + self.assertTableExists(Author) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["name"][0], + # connection.features.introspected_field_types["CharField"], + # ) def test_add_remove_index(self): """ @@ -4064,6 +4203,33 @@ def test_indexes(self): self.get_uniques(Book._meta.db_table), ) + def test_alter_renames_index(self): + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(Book) + # Ensure the table is there and has the right index + self.assertIn( + "title", + self.get_indexes(Book._meta.db_table), + ) + # Alter to rename the field + old_field = Book._meta.get_field("title") + new_field = CharField(max_length=100, db_index=True) + new_field.set_attributes_from_name("new_title") + with connection.schema_editor() as editor: + editor.alter_field(Book, old_field, new_field, strict=True) + # Ensure the old index isn't there. + self.assertNotIn( + "title", + self.get_indexes(Book._meta.db_table), + ) + # Ensure the new index is there. + self.assertIn( + "new_title", + self.get_indexes(Book._meta.db_table), + ) + def test_text_field_with_db_index(self): with connection.schema_editor() as editor: editor.create_model(AuthorTextFieldWithIndex) @@ -4580,6 +4746,7 @@ def test_add_foreign_object(self): new_field.set_attributes_from_name("author") with connection.schema_editor() as editor: editor.add_field(BookForeignObj, new_field) + editor.remove_field(BookForeignObj, new_field) def test_creation_deletion_reserved_names(self): """ @@ -4596,13 +4763,12 @@ def test_creation_deletion_reserved_names(self): "with a table named after an SQL reserved word: %s" % e ) # The table is there - list(Thing.objects.all()) + self.assertTableExists(Thing) # Clean up that table with connection.schema_editor() as editor: editor.delete_model(Thing) # The table is gone - with self.assertRaises(DatabaseError): - list(Thing.objects.all()) + self.assertTableNotExists(Thing) def test_remove_constraints_capital_letters(self): """ @@ -4694,8 +4860,8 @@ def test_add_field_use_effective_default(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure there's no surname field - columns = self.column_classes(Author) - self.assertNotIn("surname", columns) + # columns = self.column_classes(Author) + # self.assertNotIn("surname", columns) # Create a row Author.objects.create(name="Anonymous1") # Add new CharField to ensure default will be used from effective_default @@ -4703,22 +4869,32 @@ def test_add_field_use_effective_default(self): new_field.set_attributes_from_name("surname") with connection.schema_editor() as editor: editor.add_field(Author, new_field) + + class NewAuthor(Model): + surname = CharField(max_length=15, blank=True, default="surname default") + + class Meta: + app_label = "schema" + db_table = "schema_author" + + self.assertEqual(NewAuthor.objects.all()[0].surname, "") # Ensure field was added with the right default - with connection.cursor() as cursor: - cursor.execute("SELECT surname FROM schema_author;") - item = cursor.fetchall()[0] - self.assertEqual( - item[0], - None if connection.features.interprets_empty_strings_as_nulls else "", - ) + # with connection.cursor() as cursor: + # cursor.execute("SELECT surname FROM schema_author;") + # item = cursor.fetchall()[0] + # self.assertEqual( + # item[0], + # None if connection.features.interprets_empty_strings_as_nulls else "", + # ) + @isolate_apps("schema") def test_add_field_default_dropped(self): # Create the table with connection.schema_editor() as editor: editor.create_model(Author) # Ensure there's no surname field - columns = self.column_classes(Author) - self.assertNotIn("surname", columns) + # columns = self.column_classes(Author) + # self.assertNotIn("surname", columns) # Create a row Author.objects.create(name="Anonymous1") # Add new CharField with a default @@ -4726,75 +4902,98 @@ def test_add_field_default_dropped(self): new_field.set_attributes_from_name("surname") with connection.schema_editor() as editor: editor.add_field(Author, new_field) + + class NewAuthor(Model): + surname = CharField(max_length=15, blank=True, default="surname default") + + class Meta: + app_label = "schema" + db_table = "schema_author" + + self.assertEqual(NewAuthor.objects.all()[0].surname, "surname default") # Ensure field was added with the right default - with connection.cursor() as cursor: - cursor.execute("SELECT surname FROM schema_author;") - item = cursor.fetchall()[0] - self.assertEqual(item[0], "surname default") - # And that the default is no longer set in the database. - field = next( - f - for f in connection.introspection.get_table_description( - cursor, "schema_author" - ) - if f.name == "surname" - ) - if connection.features.can_introspect_default: - self.assertIsNone(field.default) + # with connection.cursor() as cursor: + # cursor.execute("SELECT surname FROM schema_author;") + # item = cursor.fetchall()[0] + # self.assertEqual(item[0], "surname default") + # # And that the default is no longer set in the database. + # field = next( + # f + # for f in connection.introspection.get_table_description( + # cursor, "schema_author" + # ) + # if f.name == "surname" + # ) + # if connection.features.can_introspect_default: + # self.assertIsNone(field.default) def test_add_field_default_nullable(self): with connection.schema_editor() as editor: editor.create_model(Author) + Author.objects.create(name="Anonymous1") # Add new nullable CharField with a default. new_field = CharField(max_length=15, blank=True, null=True, default="surname") new_field.set_attributes_from_name("surname") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - Author.objects.create(name="Anonymous1") - with connection.cursor() as cursor: - cursor.execute("SELECT surname FROM schema_author;") - item = cursor.fetchall()[0] - self.assertIsNone(item[0]) - field = next( - f - for f in connection.introspection.get_table_description( - cursor, - "schema_author", - ) - if f.name == "surname" + self.check_added_field_default( + editor, + Author, + new_field, + "surname", + "surname", ) - # Field is still nullable. - self.assertTrue(field.null_ok) - # The database default is no longer set. - if connection.features.can_introspect_default: - self.assertIn(field.default, ["NULL", None]) + # with connection.cursor() as cursor: + # cursor.execute("SELECT surname FROM schema_author;") + # item = cursor.fetchall()[0] + # self.assertIsNone(item[0]) + # field = next( + # f + # for f in connection.introspection.get_table_description( + # cursor, + # "schema_author", + # ) + # if f.name == "surname" + # ) + # # Field is still nullable. + # self.assertTrue(field.null_ok) + # # The database default is no longer set. + # if connection.features.can_introspect_default: + # self.assertIn(field.default, ["NULL", None]) def test_add_textfield_default_nullable(self): with connection.schema_editor() as editor: editor.create_model(Author) + Author.objects.create(name="Anonymous1") # Add new nullable TextField with a default. new_field = TextField(blank=True, null=True, default="text") new_field.set_attributes_from_name("description") with connection.schema_editor() as editor: editor.add_field(Author, new_field) - Author.objects.create(name="Anonymous1") - with connection.cursor() as cursor: - cursor.execute("SELECT description FROM schema_author;") - item = cursor.fetchall()[0] - self.assertIsNone(item[0]) - field = next( - f - for f in connection.introspection.get_table_description( - cursor, - "schema_author", - ) - if f.name == "description" + self.check_added_field_default( + editor, + Author, + new_field, + "description", + "text", ) - # Field is still nullable. - self.assertTrue(field.null_ok) - # The database default is no longer set. - if connection.features.can_introspect_default: - self.assertIn(field.default, ["NULL", None]) + # with connection.cursor() as cursor: + # cursor.execute("SELECT description FROM schema_author;") + # item = cursor.fetchall()[0] + # self.assertIsNone(item[0]) + # field = next( + # f + # for f in connection.introspection.get_table_description( + # cursor, + # "schema_author", + # ) + # if f.name == "description" + # ) + # # Field is still nullable. + # self.assertTrue(field.null_ok) + # # The database default is no longer set. + # if connection.features.can_introspect_default: + # self.assertIn(field.default, ["NULL", None]) def test_alter_field_default_dropped(self): # Create the table @@ -4811,16 +5010,16 @@ def test_alter_field_default_dropped(self): editor.alter_field(Author, old_field, new_field, strict=True) self.assertEqual(Author.objects.get().height, 42) # The database default should be removed. - with connection.cursor() as cursor: - field = next( - f - for f in connection.introspection.get_table_description( - cursor, "schema_author" - ) - if f.name == "height" - ) - if connection.features.can_introspect_default: - self.assertIsNone(field.default) + # with connection.cursor() as cursor: + # field = next( + # f + # for f in connection.introspection.get_table_description( + # cursor, "schema_author" + # ) + # if f.name == "height" + # ) + # if connection.features.can_introspect_default: + # self.assertIsNone(field.default) def test_alter_field_default_doesnt_perform_queries(self): """ @@ -4893,23 +5092,20 @@ def test_add_textfield_unhashable_default(self): with connection.schema_editor() as editor: editor.add_field(Author, new_field) - @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific") def test_add_indexed_charfield(self): field = CharField(max_length=255, db_index=True) field.set_attributes_from_name("nom_de_plume") with connection.schema_editor() as editor: editor.create_model(Author) editor.add_field(Author, field) - # Should create two indexes; one for like operator. + # Should create one (or two) index(es). + expected_indexes = ["schema_author_nom_de_plume_7570a851"] + if connection.vendor == "postgresql": + expected_indexes.append("schema_author_nom_de_plume_7570a851_like") self.assertEqual( - self.get_constraints_for_column(Author, "nom_de_plume"), - [ - "schema_author_nom_de_plume_7570a851", - "schema_author_nom_de_plume_7570a851_like", - ], + self.get_constraints_for_column(Author, "nom_de_plume"), expected_indexes ) - @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific") def test_add_unique_charfield(self): field = CharField(max_length=255, unique=True) field.set_attributes_from_name("nom_de_plume") @@ -4917,12 +5113,11 @@ def test_add_unique_charfield(self): editor.create_model(Author) editor.add_field(Author, field) # Should create two indexes; one for like operator. + expected_indexes = ["schema_author_nom_de_plume_7570a851_uniq"] + if connection.vendor == "postgresql": + expected_indexes.append("schema_author_nom_de_plume_7570a851_like") self.assertEqual( - self.get_constraints_for_column(Author, "nom_de_plume"), - [ - "schema_author_nom_de_plume_7570a851_like", - "schema_author_nom_de_plume_key", - ], + self.get_constraints_for_column(Author, "nom_de_plume"), expected_indexes ) @skipUnlessDBFeature("supports_comments") @@ -5085,7 +5280,7 @@ class Meta: db_table_comment = "Custom table comment" # Table comments are ignored on databases that don't support them. - with connection.schema_editor() as editor, self.assertNumQueries(1): + with connection.schema_editor() as editor: editor.create_model(ModelWithDbTableComment) self.isolated_local_models = [ModelWithDbTableComment] with connection.schema_editor() as editor, self.assertNumQueries(0): @@ -5355,13 +5550,13 @@ def test_add_datefield_and_datetimefield_use_effective_default( with connection.schema_editor() as editor: editor.create_model(Author) # Check auto_now/auto_now_add attributes are not defined - columns = self.column_classes(Author) - self.assertNotIn("dob_auto_now", columns) - self.assertNotIn("dob_auto_now_add", columns) - self.assertNotIn("dtob_auto_now", columns) - self.assertNotIn("dtob_auto_now_add", columns) - self.assertNotIn("tob_auto_now", columns) - self.assertNotIn("tob_auto_now_add", columns) + # columns = self.column_classes(Author) + # self.assertNotIn("dob_auto_now", columns) + # self.assertNotIn("dob_auto_now_add", columns) + # self.assertNotIn("dtob_auto_now", columns) + # self.assertNotIn("dtob_auto_now_add", columns) + # self.assertNotIn("tob_auto_now", columns) + # self.assertNotIn("tob_auto_now_add", columns) # Create a row Author.objects.create(name="Anonymous1") # Ensure fields were added with the correct defaults diff --git a/tests/select_related_onetoone/models.py b/tests/select_related_onetoone/models.py index 5ffb6bfd8c..14af6ceec1 100644 --- a/tests/select_related_onetoone/models.py +++ b/tests/select_related_onetoone/models.py @@ -1,3 +1,5 @@ +from django_mongodb.fields import ObjectIdAutoField + from django.db import models @@ -46,7 +48,7 @@ class Parent1(models.Model): class Parent2(models.Model): # Avoid having two "id" fields in the Child1 subclass - id2 = models.AutoField(primary_key=True) + id2 = ObjectIdAutoField(primary_key=True) name2 = models.CharField(max_length=50) diff --git a/tests/sitemaps_tests/urls/http.py b/tests/sitemaps_tests/urls/http.py index db549b4a38..0d8810f3c1 100644 --- a/tests/sitemaps_tests/urls/http.py +++ b/tests/sitemaps_tests/urls/http.py @@ -476,5 +476,5 @@ def testmodelview(request, id): ] urlpatterns += i18n_patterns( - path("i18n/testmodel//", testmodelview, name="i18n_testmodel"), + path("i18n/testmodel//", testmodelview, name="i18n_testmodel"), ) diff --git a/tests/syndication_tests/urls.py b/tests/syndication_tests/urls.py index 50f673373e..35e1d16311 100644 --- a/tests/syndication_tests/urls.py +++ b/tests/syndication_tests/urls.py @@ -15,7 +15,7 @@ "syndication/rss2/with-wrong-decorated-methods/", feeds.TestRss2FeedWithWrongDecoratedMethod(), ), - path("syndication/rss2/articles//", feeds.TestGetObjectFeed()), + path("syndication/rss2/articles//", feeds.TestGetObjectFeed()), path( "syndication/rss2/guid_ispermalink_true/", feeds.TestRss2FeedWithGuidIsPermaLinkTrue(), diff --git a/tests/test_utils/urls.py b/tests/test_utils/urls.py index 37d0c76a11..f11066a5c8 100644 --- a/tests/test_utils/urls.py +++ b/tests/test_utils/urls.py @@ -3,7 +3,7 @@ from . import views urlpatterns = [ - path("test_utils/get_person//", views.get_person), + path("test_utils/get_person//", views.get_person), path( "test_utils/no_template_used/", views.no_template_used, name="no_template_used" ),