From d26ebb922bdb28fb2de6b455bc8954e1f1367b5b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 16 Jul 2024 21:08:15 -0400 Subject: [PATCH 01/23] Update GenericForeignKey object_id to CharField MongoDB uses ObjectId rather than integer --- tests/delete/models.py | 6 +++--- tests/delete_regress/models.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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() From eb2edbd5ad238528086f23ae42910f1b2c8caf5c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 16 Jul 2024 21:21:08 -0400 Subject: [PATCH 02/23] use ObjectIdAutoField in test models --- django/contrib/sites/migrations/0001_initial.py | 4 +++- tests/aggregation_regress/models.py | 8 +++++--- tests/backends/models.py | 6 ++++-- tests/many_to_many/models.py | 4 +++- tests/many_to_one/models.py | 6 ++++-- tests/many_to_one/tests.py | 2 +- tests/model_forms/test_modelchoicefield.py | 12 ++++++------ tests/model_forms/tests.py | 6 +++--- tests/queries/models.py | 12 +++++++----- tests/select_related_onetoone/models.py | 4 +++- 10 files changed, 39 insertions(+), 25 deletions(-) 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/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index edf0e89a9d..edafab037a 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 @@ -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/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/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/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..d181585ca3 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1649,9 +1649,9 @@ def formfield_for_dbfield(db_field, **kwargs):
  • """ % (self.c1.pk, self.c2.pk, self.c3.pk), ) 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/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) From 1ca24f835f61e46156570cd60bcd4f4c39661c59 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 18 Jul 2024 11:10:18 -0400 Subject: [PATCH 03/23] update serializer output for MongoAutoField --- tests/m2m_through_regress/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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, ) From acc5a8ae7e24ed24d363562a1a0fa9c7a60aad3a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 18 Jul 2024 15:44:03 -0400 Subject: [PATCH 04/23] comment out usage of QuerySet.extra() and distinct() --- tests/queries/tests.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 20665ab2cd..a98f95a5da 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -888,8 +888,8 @@ def test_ticket7235(self): self.assertSequenceEqual(q.select_related("food"), []) 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.distinct(), []) + # 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 From eb877079f4e25c1334571644aa64e5c2e6fc967e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 22 Jul 2024 13:14:30 -0400 Subject: [PATCH 05/23] remove unsupported usage of nulls_first --- tests/ordering/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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): From a87e9ff7fe96a8ad553dd38f57f8594839d643a3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 19 Aug 2024 19:38:20 -0400 Subject: [PATCH 06/23] aggregation_regress edits --- tests/aggregation_regress/models.py | 2 +- tests/aggregation_regress/tests.py | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index edafab037a..90c1a37aed 100644 --- a/tests/aggregation_regress/models.py +++ b/tests/aggregation_regress/models.py @@ -19,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") 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" From 44d8244bbecec7329340295476c9620eb6f4da58 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 15 Aug 2024 21:01:34 -0400 Subject: [PATCH 07/23] add test for ModelForm data using numeric pks --- tests/model_forms/tests.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index d181585ca3..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. From 100a3799d529b57e6170992a743bfd3715979aac Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 16 Aug 2024 18:02:38 -0400 Subject: [PATCH 08/23] drop requirement that QuerySet.explain() log a query --- tests/queries/test_explain.py | 47 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 24 deletions(-) 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"): From 3dd74857d6607e51f1c38e9fb41c1dcceb2e150f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Aug 2024 20:25:07 -0400 Subject: [PATCH 09/23] schema and migrations test edits --- tests/migrations/test_base.py | 46 +-- tests/schema/tests.py | 683 ++++++++++++++++++++-------------- 2 files changed, 433 insertions(+), 296 deletions(-) diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py index b5228ad445..6440d13a63 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 [ @@ -223,15 +227,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 +293,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 +332,7 @@ def set_up_test_model( migrations.CreateModel( "Stable", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ) ) @@ -337,7 +341,7 @@ def set_up_test_model( migrations.CreateModel( "Van", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ) ) @@ -346,7 +350,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 +397,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/schema/tests.py b/tests/schema/tests.py index 905e604d03..e1c11b6019 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): @@ -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,16 @@ def test_remove_field(self): def test_remove_indexed_field(self): with connection.schema_editor() as editor: editor.create_model(AuthorCharFieldWithIndex) + 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) + a.refresh_from_db() + self.assertIsNone(a.char_field) + # columns = self.column_classes(AuthorCharFieldWithIndex) + # self.assertNotIn("char_field", columns) def test_alter(self): """ @@ -1029,35 +1092,35 @@ 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), + # ) def test_alter_auto_field_to_integer_field(self): # Create the table @@ -1229,8 +1292,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 +1308,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 +1324,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 +1389,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 +1403,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 +1734,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 +1744,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): @@ -2269,6 +2332,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 +2341,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 +2408,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 +2447,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 +2473,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 +2567,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 +2618,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 +2695,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 +2732,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 +2785,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 +2798,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 +2833,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 +2849,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 +2895,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 +2907,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" @@ -3376,10 +3471,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 +3999,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): """ @@ -4580,6 +4680,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 +4697,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 +4794,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 +4803,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 +4836,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 +4944,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): """ @@ -5085,7 +5218,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 +5488,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 From 3211b6cac3df23dc6bb0093ab4a996773ee7dd5d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 29 Aug 2024 12:21:33 -0400 Subject: [PATCH 10/23] backends edits --- tests/backends/tests.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 08a21d8ded..8c09ac0187 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): @@ -261,14 +261,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, {}) From 971a089c93c640cc7bfd684825ebbad6d1ef00e3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Aug 2024 21:06:56 -0400 Subject: [PATCH 11/23] introspection test edits --- tests/introspection/tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 From 73f6eeb39ae92f6cf1c6aac11924f42b8aadc4d8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Aug 2024 17:48:33 -0400 Subject: [PATCH 12/23] Added supports_sequence_reset skip in backends tests. --- tests/backends/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 8c09ac0187..a877454ed2 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -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)" From e486811ea2b919f4b3bbcd87143c85c4c0baea8f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Sep 2024 15:22:42 -0400 Subject: [PATCH 13/23] remove SQL introspection from queries test --- tests/queries/test_qs_combinators.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 71d2418f2b..d9e0ceb8eb 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -481,14 +481,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=[]) From ab7e2e46f29860b6d1dfc89c13c26ae142f3f3ee Mon Sep 17 00:00:00 2001 From: Emanuel Lupi Date: Sat, 7 Sep 2024 12:57:59 -0300 Subject: [PATCH 14/23] Added QuerySet.union() test with renames. --- tests/queries/test_qs_combinators.py | 37 +++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index d9e0ceb8eb..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) From f5fd65f6bb4e384b90b3ef4f62c755938450dd91 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Aug 2024 20:25:07 -0400 Subject: [PATCH 15/23] schema and migrations test edits --- tests/migrations/test_base.py | 52 ++- tests/migrations/test_operations.py | 118 ++--- tests/schema/tests.py | 694 +++++++++++++++++----------- 3 files changed, 506 insertions(+), 358 deletions(-) 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_operations.py b/tests/migrations/test_operations.py index bc2fa02392..2f0b1aeb8a 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")), ], ), @@ -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. @@ -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. @@ -3648,19 +3654,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 +5541,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 +5549,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/schema/tests.py b/tests/schema/tests.py index 905e604d03..ed99bd242c 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): @@ -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,16 @@ def test_remove_field(self): def test_remove_indexed_field(self): with connection.schema_editor() as editor: editor.create_model(AuthorCharFieldWithIndex) + 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) + a.refresh_from_db() + self.assertIsNone(a.char_field) + # columns = self.column_classes(AuthorCharFieldWithIndex) + # self.assertNotIn("char_field", columns) def test_alter(self): """ @@ -1029,36 +1092,37 @@ 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: @@ -1070,11 +1134,19 @@ def test_alter_auto_field_to_integer_field(self): 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 @@ -1229,8 +1301,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 +1317,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 +1333,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 +1398,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 +1412,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 +1743,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 +1753,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): @@ -2269,6 +2341,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 +2350,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 +2417,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 +2456,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 +2482,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 +2576,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 +2627,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 +2704,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 +2741,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 +2794,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 +2807,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 +2842,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 +2858,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 +2904,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 +2916,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" @@ -3376,10 +3480,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 +4008,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): """ @@ -4580,6 +4689,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 +4706,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 +4803,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 +4812,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 +4845,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 +4953,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): """ @@ -5085,7 +5227,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 +5497,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 From 2062d1b1182daee2b468a02ec82b562e92d60528 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 29 Aug 2024 12:21:33 -0400 Subject: [PATCH 16/23] backends edits --- tests/backends/tests.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 08a21d8ded..8c09ac0187 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): @@ -261,14 +261,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, {}) From be9bb06bb8bc3c9d90fdcc2c065cb315368c481b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Aug 2024 21:06:56 -0400 Subject: [PATCH 17/23] introspection test edits --- tests/introspection/tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 From 831a01eb80209f731494e0547c4724a1c6b874ba Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Aug 2024 17:48:33 -0400 Subject: [PATCH 18/23] Added supports_sequence_reset skip in backends tests. --- tests/backends/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 8c09ac0187..a877454ed2 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -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)" From 4f60fb2285935945b3f99354b474fcc843415831 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Sep 2024 15:22:42 -0400 Subject: [PATCH 19/23] remove SQL introspection from queries test --- tests/queries/test_qs_combinators.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 71d2418f2b..d9e0ceb8eb 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -481,14 +481,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=[]) From 9e610190087d459502e6ac6d5e1acf5fea135c74 Mon Sep 17 00:00:00 2001 From: Emanuel Lupi Date: Sat, 7 Sep 2024 12:57:59 -0300 Subject: [PATCH 20/23] Added QuerySet.union() test with renames. --- tests/queries/test_qs_combinators.py | 37 +++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index d9e0ceb8eb..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) From a5860219cf6226486b668864463e340df35c4556 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 16 Sep 2024 09:35:30 -0400 Subject: [PATCH 21/23] prevent basic test from requiring DatabaseFeatures.supports_expression_defaults --- tests/basic/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/basic/models.py b/tests/basic/models.py index cd89643140..49779ff2fd 100644 --- a/tests/basic/models.py +++ b/tests/basic/models.py @@ -51,7 +51,7 @@ class PrimaryKeyWithDefault(models.Model): class PrimaryKeyWithDbDefault(models.Model): - uuid = models.IntegerField(primary_key=True, db_default=1) + uuid = models.IntegerField(primary_key=True, db_default=models.Value(1)) class ChildPrimaryKeyWithDefault(PrimaryKeyWithDefault): From 640aef4857badeac97d78aac2dd8225e3862ee0f Mon Sep 17 00:00:00 2001 From: Emanuel Lupi Date: Tue, 17 Sep 2024 06:09:57 -0400 Subject: [PATCH 22/23] Edit test models, uses ObjectId instead of positiveIntegerField. --- django/contrib/admin/models.py | 3 ++- tests/generic_relations/models.py | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 021984160a..7e21f5971d 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -9,6 +9,7 @@ from django.utils.text import get_text_list from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ +from django_mongodb.fields import ObjectIdField ADDITION = 1 CHANGE = 2 @@ -63,7 +64,7 @@ class LogEntry(models.Model): blank=True, null=True, ) - object_id = models.TextField(_("object id"), blank=True, null=True) + object_id = ObjectIdField(_("object id"), blank=True, null=True) # Translators: 'repr' means representation # (https://docs.python.org/library/functions.html#repr) object_repr = models.CharField(_("object repr"), max_length=200) diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index e99d2c7e5e..8a1d36e790 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -12,6 +12,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models +from django_mongodb.fields import ObjectIdField class TaggedItem(models.Model): @@ -19,7 +20,7 @@ class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = ObjectIdField() content_object = GenericForeignKey() @@ -40,7 +41,7 @@ class AbstractComparison(models.Model): content_type1 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative1_set" ) - object_id1 = models.PositiveIntegerField() + object_id1 = ObjectIdField() first_obj = GenericForeignKey(ct_field="content_type1", fk_field="object_id1") @@ -54,7 +55,7 @@ class Comparison(AbstractComparison): content_type2 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative2_set" ) - object_id2 = models.PositiveIntegerField() + object_id2 = ObjectIdField() other_obj = GenericForeignKey(ct_field="content_type2", fk_field="object_id2") @@ -119,20 +120,20 @@ class ValuableRock(Mineral): class ManualPK(models.Model): - id = models.IntegerField(primary_key=True) + id = ObjectIdField(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 = ObjectIdField() 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 = ObjectIdField() obj = GenericForeignKey() From 8bf77cd71d861694ebf5c905e6d1707dcc323729 Mon Sep 17 00:00:00 2001 From: Emanuel Lupi Date: Sun, 22 Sep 2024 21:10:35 -0500 Subject: [PATCH 23/23] Using textfield instead of ObjectIdField. --- django/contrib/admin/models.py | 3 +-- tests/generic_relations/models.py | 13 ++++++------- tests/generic_relations/tests.py | 3 ++- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index 7e21f5971d..021984160a 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -9,7 +9,6 @@ from django.utils.text import get_text_list from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ -from django_mongodb.fields import ObjectIdField ADDITION = 1 CHANGE = 2 @@ -64,7 +63,7 @@ class LogEntry(models.Model): blank=True, null=True, ) - object_id = ObjectIdField(_("object id"), blank=True, null=True) + object_id = models.TextField(_("object id"), blank=True, null=True) # Translators: 'repr' means representation # (https://docs.python.org/library/functions.html#repr) object_repr = models.CharField(_("object repr"), max_length=200) diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index 8a1d36e790..a6021b8f16 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -12,7 +12,6 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models -from django_mongodb.fields import ObjectIdField class TaggedItem(models.Model): @@ -20,7 +19,7 @@ class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = ObjectIdField() + object_id = models.TextField() content_object = GenericForeignKey() @@ -41,7 +40,7 @@ class AbstractComparison(models.Model): content_type1 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative1_set" ) - object_id1 = ObjectIdField() + object_id1 = models.TextField() first_obj = GenericForeignKey(ct_field="content_type1", fk_field="object_id1") @@ -55,7 +54,7 @@ class Comparison(AbstractComparison): content_type2 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative2_set" ) - object_id2 = ObjectIdField() + object_id2 = models.TextField() other_obj = GenericForeignKey(ct_field="content_type2", fk_field="object_id2") @@ -120,20 +119,20 @@ class ValuableRock(Mineral): class ManualPK(models.Model): - id = ObjectIdField(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 = ObjectIdField() + 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 = ObjectIdField() + object_id = models.TextField() obj = GenericForeignKey() diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index e0c6fe2db7..46da6a39a1 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -1,3 +1,4 @@ +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 +45,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")