From 602a66dd3c9002ac3520efb7dc5659212b7b658b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 11 Feb 2025 10:57:19 -0500 Subject: [PATCH 01/35] disable JavaScript/Windows tests --- .github/workflows/tests.yml | 56 ------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 3373f82e0a..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Tests - -on: - pull_request: - paths-ignore: - - 'docs/**' - push: - branches: - - main - paths-ignore: - - 'docs/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - windows: - runs-on: windows-latest - strategy: - matrix: - python-version: - - '3.13' - name: Windows, SQLite, Python ${{ matrix.python-version }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: 'tests/requirements/py3.txt' - - name: Install and upgrade packaging tools - run: python -m pip install --upgrade pip setuptools wheel - - run: python -m pip install -r tests/requirements/py3.txt -e . - - name: Run tests - run: python -Wall tests/runtests.py -v2 - - javascript-tests: - runs-on: ubuntu-latest - name: JavaScript tests - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: '**/package.json' - - run: npm install - - run: npm test From 948e0f802999c2b19a548db4a792fd4fb2c53578 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 17 Apr 2024 10:00:08 -0400 Subject: [PATCH 02/35] use ObjectIdAutoField for contrib apps' default_auto_field --- django/contrib/admin/apps.py | 2 +- django/contrib/auth/apps.py | 2 +- django/contrib/contenttypes/apps.py | 2 +- django/contrib/flatpages/apps.py | 2 +- django/contrib/gis/apps.py | 2 +- django/contrib/redirects/apps.py | 2 +- django/contrib/sites/apps.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/django/contrib/admin/apps.py b/django/contrib/admin/apps.py index 08a9e0d832..f35149bc20 100644 --- a/django/contrib/admin/apps.py +++ b/django/contrib/admin/apps.py @@ -7,7 +7,7 @@ class SimpleAdminConfig(AppConfig): """Simple AppConfig which does not do automatic discovery.""" - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" default_site = "django.contrib.admin.sites.AdminSite" name = "django.contrib.admin" verbose_name = _("Administration") diff --git a/django/contrib/auth/apps.py b/django/contrib/auth/apps.py index ad6f816809..555a2aaeba 100644 --- a/django/contrib/auth/apps.py +++ b/django/contrib/auth/apps.py @@ -11,7 +11,7 @@ class AuthConfig(AppConfig): - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" name = "django.contrib.auth" verbose_name = _("Authentication and Authorization") diff --git a/django/contrib/contenttypes/apps.py b/django/contrib/contenttypes/apps.py index 11dfb91010..7cba23bdd8 100644 --- a/django/contrib/contenttypes/apps.py +++ b/django/contrib/contenttypes/apps.py @@ -11,7 +11,7 @@ class ContentTypesConfig(AppConfig): - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" name = "django.contrib.contenttypes" verbose_name = _("Content Types") diff --git a/django/contrib/flatpages/apps.py b/django/contrib/flatpages/apps.py index eb9f470b59..8fc2f9d434 100644 --- a/django/contrib/flatpages/apps.py +++ b/django/contrib/flatpages/apps.py @@ -3,6 +3,6 @@ class FlatPagesConfig(AppConfig): - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" name = "django.contrib.flatpages" verbose_name = _("Flat Pages") diff --git a/django/contrib/gis/apps.py b/django/contrib/gis/apps.py index 6282501056..b51c1f4516 100644 --- a/django/contrib/gis/apps.py +++ b/django/contrib/gis/apps.py @@ -4,7 +4,7 @@ class GISConfig(AppConfig): - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" name = "django.contrib.gis" verbose_name = _("GIS") diff --git a/django/contrib/redirects/apps.py b/django/contrib/redirects/apps.py index d7706711b7..55a5145f9c 100644 --- a/django/contrib/redirects/apps.py +++ b/django/contrib/redirects/apps.py @@ -3,6 +3,6 @@ class RedirectsConfig(AppConfig): - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" name = "django.contrib.redirects" verbose_name = _("Redirects") diff --git a/django/contrib/sites/apps.py b/django/contrib/sites/apps.py index ac51a84e18..758d3a365c 100644 --- a/django/contrib/sites/apps.py +++ b/django/contrib/sites/apps.py @@ -8,7 +8,7 @@ class SitesConfig(AppConfig): - default_auto_field = "django.db.models.AutoField" + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" name = "django.contrib.sites" verbose_name = _("Sites") From 6d39ce095fdf996dba305a2cb2b22b6463d4a92c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 24 Apr 2024 22:01:10 -0400 Subject: [PATCH 03/35] Fixed #35402 -- Fixed (naively) django_test_skips crash when referencing a class --- django/db/backends/base/creation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py index 6856fdb596..f6cc270b16 100644 --- a/django/db/backends/base/creation.py +++ b/django/db/backends/base/creation.py @@ -350,6 +350,9 @@ def mark_expected_failures_and_skips(self): test_app = test_name.split(".")[0] # Importing a test app that isn't installed raises RuntimeError. if test_app in settings.INSTALLED_APPS: + # If this is a a test class, it may need to be imported. + if test_name.count(".") == 2: + import_string(test_name) test_case = import_string(test_case_name) test_method = getattr(test_case, test_method_name) setattr(test_case, test_method_name, skip(reason)(test_method)) From 4dd7ec9665d31796e6d23d1bd5c9a894125b4e4e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 29 May 2024 11:26:54 -0400 Subject: [PATCH 04/35] Added DatabaseFeatures.supports_microsecond_precision. This reverts commit a80903b7114c984b5087597e8c34750e7bb44f51. --- django/db/backends/base/features.py | 3 + tests/basic/tests.py | 40 +++++++- .../db_functions/comparison/test_greatest.py | 14 ++- tests/db_functions/comparison/test_least.py | 14 ++- .../datetime/test_extract_trunc.py | 99 ++++++++++--------- tests/expressions/tests.py | 82 +++++++++------ tests/model_fields/test_datetimefield.py | 1 + tests/model_fields/test_durationfield.py | 7 +- tests/queries/models.py | 13 ++- tests/queryset_pickle/tests.py | 14 ++- tests/timezones/tests.py | 43 ++++++++ 11 files changed, 243 insertions(+), 87 deletions(-) diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index de5dc12768..ddf701be36 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -87,6 +87,9 @@ class BaseDatabaseFeatures: # by returning the type used to store duration field? supports_temporal_subtraction = False + # Do time/datetime fields have microsecond precision? + supports_microsecond_precision = True + # Does the __regex lookup support backreferencing and grouping? supports_regex_backreferencing = True diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 6c2f9f2bd2..89676ec3cf 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -18,6 +18,7 @@ SimpleTestCase, TestCase, TransactionTestCase, + skipIfDBFeature, skipUnlessDBFeature, ) from django.test.utils import CaptureQueriesContext, ignore_warnings @@ -388,6 +389,7 @@ def test_not_equal_and_equal_operators_behave_as_expected_on_instances(self): Article.objects.get(id__exact=a1.id), Article.objects.get(id__exact=a2.id) ) + @skipUnlessDBFeature("supports_microsecond_precision") def test_microsecond_precision(self): a9 = Article( headline="Article 9", @@ -399,6 +401,33 @@ def test_microsecond_precision(self): datetime(2005, 7, 31, 12, 30, 45, 180), ) + @skipIfDBFeature("supports_microsecond_precision") + def test_microsecond_precision_not_supported(self): + # In MySQL, microsecond-level precision isn't always available. You'll + # lose microsecond-level precision once the data is saved. + a9 = Article( + headline="Article 9", + pub_date=datetime(2005, 7, 31, 12, 30, 45, 180), + ) + a9.save() + self.assertEqual( + Article.objects.get(id__exact=a9.id).pub_date, + datetime(2005, 7, 31, 12, 30, 45), + ) + + @skipIfDBFeature("supports_microsecond_precision") + def test_microsecond_precision_not_supported_edge_case(self): + # If microsecond-level precision isn't available, you'll lose + # microsecond-level precision once the data is saved. + a = Article.objects.create( + headline="Article", + pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999), + ) + self.assertEqual( + Article.objects.get(pk=a.pk).pub_date, + datetime(2008, 12, 31, 23, 59, 59, 999000), + ) + def test_manually_specify_primary_key(self): # You can manually specify the primary key when creating a new object. a101 = Article( @@ -1007,6 +1036,13 @@ def _update(self, *args, **kwargs): class ModelRefreshTests(TestCase): + def _truncate_ms(self, val): + # Some databases don't support microseconds in datetimes which causes + # problems when comparing the original value to that loaded from the DB. + if connection.features.supports_microsecond_precision: + return val + return val - timedelta(microseconds=val.microsecond) + def test_refresh(self): a = Article.objects.create(pub_date=datetime.now()) Article.objects.create(pub_date=datetime.now()) @@ -1066,7 +1102,7 @@ def test_refresh_null_fk(self): self.assertEqual(s2.selfref, s1) def test_refresh_unsaved(self): - pub_date = datetime.now() + pub_date = self._truncate_ms(datetime.now()) a = Article.objects.create(pub_date=pub_date) a2 = Article(id=a.pk) with self.assertNumQueries(1): @@ -1166,7 +1202,7 @@ def test_refresh_for_update(self): ) def test_refresh_with_related(self): - a = Article.objects.create(pub_date=datetime.now()) + a = Article.objects.create(pub_date=self._truncate_ms(datetime.now())) fa = FeaturedArticle.objects.create(article=a) from_queryset = FeaturedArticle.objects.select_related("article") diff --git a/tests/db_functions/comparison/test_greatest.py b/tests/db_functions/comparison/test_greatest.py index c37514adf7..cdc4206f55 100644 --- a/tests/db_functions/comparison/test_greatest.py +++ b/tests/db_functions/comparison/test_greatest.py @@ -11,9 +11,17 @@ from ..models import Article, Author, DecimalModel, Fan +def microsecond_support(value): + return ( + value + if connection.features.supports_microsecond_precision + else value.replace(microsecond=0) + ) + + class GreatestTests(TestCase): def test_basic(self): - now = timezone.now() + now = microsecond_support(timezone.now()) before = now - timedelta(hours=1) Article.objects.create( title="Testing with Django", written=before, published=now @@ -25,7 +33,7 @@ def test_basic(self): @skipUnlessDBFeature("greatest_least_ignores_nulls") def test_ignores_null(self): - now = timezone.now() + now = microsecond_support(timezone.now()) Article.objects.create(title="Testing with Django", written=now) articles = Article.objects.annotate( last_updated=Greatest("written", "published") @@ -42,7 +50,7 @@ def test_propagates_null(self): def test_coalesce_workaround(self): past = datetime(1900, 1, 1) - now = timezone.now() + now = microsecond_support(timezone.now()) Article.objects.create(title="Testing with Django", written=now) articles = Article.objects.annotate( last_updated=Greatest( diff --git a/tests/db_functions/comparison/test_least.py b/tests/db_functions/comparison/test_least.py index eb7514187a..a39ed42985 100644 --- a/tests/db_functions/comparison/test_least.py +++ b/tests/db_functions/comparison/test_least.py @@ -11,9 +11,17 @@ from ..models import Article, Author, DecimalModel, Fan +def microsecond_support(value): + return ( + value + if connection.features.supports_microsecond_precision + else value.replace(microsecond=0) + ) + + class LeastTests(TestCase): def test_basic(self): - now = timezone.now() + now = microsecond_support(timezone.now()) before = now - timedelta(hours=1) Article.objects.create( title="Testing with Django", written=before, published=now @@ -23,7 +31,7 @@ def test_basic(self): @skipUnlessDBFeature("greatest_least_ignores_nulls") def test_ignores_null(self): - now = timezone.now() + now = microsecond_support(timezone.now()) Article.objects.create(title="Testing with Django", written=now) articles = Article.objects.annotate( first_updated=Least("written", "published"), @@ -38,7 +46,7 @@ def test_propagates_null(self): def test_coalesce_workaround(self): future = datetime(2100, 1, 1) - now = timezone.now() + now = microsecond_support(timezone.now()) Article.objects.create(title="Testing with Django", written=now) articles = Article.objects.annotate( last_updated=Least( diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py index 3f13ca7989..d51b941ea2 100644 --- a/tests/db_functions/datetime/test_extract_trunc.py +++ b/tests/db_functions/datetime/test_extract_trunc.py @@ -3,7 +3,7 @@ from datetime import timezone as datetime_timezone from django.conf import settings -from django.db import DataError, OperationalError +from django.db import DataError, NotSupportedError, OperationalError, connection from django.db.models import ( DateField, DateTimeField, @@ -50,6 +50,14 @@ from ..models import Author, DTModel, Fan +def microsecond_support(value): + return ( + value + if connection.features.supports_microsecond_precision + else value.replace(microsecond=0) + ) + + def truncate_to(value, kind, tzinfo=None): # Convert to target timezone before truncation if tzinfo is not None: @@ -222,7 +230,7 @@ def test_extract_lookup_name_sql_injection(self): self.create_model(start_datetime, end_datetime) self.create_model(end_datetime, start_datetime) - with self.assertRaises((OperationalError, ValueError)): + with self.assertRaises((NotSupportedError, OperationalError, ValueError)): DTModel.objects.filter( start_datetime__year=Extract( "start_datetime", "day' FROM start_datetime)) OR 1=1;--" @@ -230,8 +238,8 @@ def test_extract_lookup_name_sql_injection(self): ).exists() def test_extract_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -435,8 +443,8 @@ def test_extract_duration_unsupported_lookups(self): DTModel.objects.annotate(extracted=Extract("duration", lookup)) def test_extract_year_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -464,8 +472,8 @@ def test_extract_year_func(self): ) def test_extract_iso_year_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -528,6 +536,7 @@ def test_extract_iso_year_func_boundaries(self): qs = DTModel.objects.filter( start_datetime__iso_year=2015, ).order_by("start_datetime") + self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015]) qs = DTModel.objects.filter( start_datetime__iso_year__gt=2014, @@ -539,8 +548,8 @@ def test_extract_iso_year_func_boundaries(self): self.assertSequenceEqual(qs, [obj_1_iso_2014]) def test_extract_month_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -574,8 +583,8 @@ def test_extract_month_func(self): ) def test_extract_day_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -603,8 +612,8 @@ def test_extract_day_func(self): ) def test_extract_week_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -725,8 +734,8 @@ def test_extract_week_func_boundaries(self): ) def test_extract_weekday_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -760,8 +769,8 @@ def test_extract_weekday_func(self): ) def test_extract_iso_weekday_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -795,8 +804,8 @@ def test_extract_iso_weekday_func(self): ) def test_extract_hour_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -824,8 +833,8 @@ def test_extract_hour_func(self): ) def test_extract_minute_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -859,8 +868,8 @@ def test_extract_minute_func(self): ) def test_extract_second_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -925,14 +934,14 @@ def test_trunc_lookup_name_sql_injection(self): "year', start_datetime)) OR 1=1;--", ) ).exists() - except (DataError, OperationalError): + except (DataError, NotSupportedError, OperationalError): pass else: self.assertIs(exists, False) def test_trunc_func(self): - start_datetime = datetime(999, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(999, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -1016,6 +1025,8 @@ def assertDatetimeToTimeKind(kind): self.assertEqual(qs.count(), 2) def _test_trunc_week(self, start_datetime, end_datetime): + start_datetime = microsecond_support(start_datetime) + end_datetime = microsecond_support(end_datetime) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -1108,7 +1119,7 @@ def test_trunc_none(self): ) def test_trunc_year_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "year") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1155,7 +1166,7 @@ def test_trunc_year_func(self): ) def test_trunc_quarter_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 10, 15, 14, 10, 50, 123), "quarter") last_quarter_2015 = truncate_to( datetime(2015, 12, 31, 14, 10, 50, 123), "quarter" @@ -1212,7 +1223,7 @@ def test_trunc_quarter_func(self): ) def test_trunc_month_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "month") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1259,7 +1270,7 @@ def test_trunc_month_func(self): ) def test_trunc_week_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "week") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1296,8 +1307,8 @@ def test_trunc_week_func(self): ) def test_trunc_date_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -1343,8 +1354,8 @@ def test_trunc_date_none(self): ) def test_trunc_time_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) @@ -1417,7 +1428,7 @@ def test_trunc_time_comparison(self): ) def test_trunc_day_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "day") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1453,7 +1464,7 @@ def test_trunc_day_func(self): ) def test_trunc_hour_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "hour") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1500,7 +1511,7 @@ def test_trunc_hour_func(self): ) def test_trunc_minute_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "minute") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1549,7 +1560,7 @@ def test_trunc_minute_func(self): ) def test_trunc_second_func(self): - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "second") if settings.USE_TZ: start_datetime = timezone.make_aware(start_datetime) @@ -1580,7 +1591,7 @@ def test_trunc_second_func(self): DTModel.objects.filter( start_datetime=TruncSecond("start_datetime") ).count(), - 1, + 1 if connection.features.supports_microsecond_precision else 2, ) with self.assertRaisesMessage( @@ -1785,8 +1796,8 @@ def test_extract_invalid_field_with_timezone(self): ).get() def test_trunc_timezone_applied_before_truncation(self): - start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2016, 1, 1, 1, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) self.create_model(start_datetime, end_datetime) @@ -1826,8 +1837,8 @@ def test_trunc_func_with_timezone(self): If the truncated datetime transitions to a different offset (daylight saving) then the returned value will have that new timezone/offset. """ - start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) - end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123) + start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321)) + end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)) start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) self.create_model(start_datetime, end_datetime) diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 5f61f65ac0..a60fa245f6 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -1844,20 +1844,24 @@ def setUpTestData(cls): # e1: started one day after assigned, tiny duration, data # set so that end time has no fractional seconds, which - # tests an edge case on sqlite. - delay = datetime.timedelta(1) - end = stime + delay + delta1 - e1 = Experiment.objects.create( - name="e1", - assigned=sday, - start=stime + delay, - end=end, - completed=end.date(), - estimated_time=delta1, - ) - cls.deltas.append(delta1) - cls.delays.append(e1.start - datetime.datetime.combine(e1.assigned, midnight)) - cls.days_long.append(e1.completed - e1.assigned) + # tests an edge case on sqlite. This Experiment is only included in + # the test data when the DB supports microsecond precision. + if connection.features.supports_microsecond_precision: + delay = datetime.timedelta(1) + end = stime + delay + delta1 + e1 = Experiment.objects.create( + name="e1", + assigned=sday, + start=stime + delay, + end=end, + completed=end.date(), + estimated_time=delta1, + ) + cls.deltas.append(delta1) + cls.delays.append( + e1.start - datetime.datetime.combine(e1.assigned, midnight) + ) + cls.days_long.append(e1.completed - e1.assigned) # e2: started three days after assigned, small duration end = stime + delta2 @@ -2166,7 +2170,10 @@ def test_date_subtraction(self): e.name for e in queryset.filter(completion_duration__lt=datetime.timedelta(days=5)) } - self.assertEqual(less_than_5_days, {"e0", "e1", "e2"}) + expected = {"e0", "e2"} + if connection.features.supports_microsecond_precision: + expected.add("e1") + self.assertEqual(less_than_5_days, expected) queryset = Experiment.objects.annotate( difference=F("completed") - Value(None, output_field=DateField()), @@ -2206,14 +2213,19 @@ def test_date_case_subtraction(self): @skipUnlessDBFeature("supports_temporal_subtraction") def test_time_subtraction(self): - Time.objects.create(time=datetime.time(12, 30, 15, 2345)) + if connection.features.supports_microsecond_precision: + time = datetime.time(12, 30, 15, 2345) + timedelta = datetime.timedelta( + hours=1, minutes=15, seconds=15, microseconds=2345 + ) + else: + time = datetime.time(12, 30, 15) + timedelta = datetime.timedelta(hours=1, minutes=15, seconds=15) + Time.objects.create(time=time) queryset = Time.objects.annotate( difference=F("time") - Value(datetime.time(11, 15, 0)), ) - self.assertEqual( - queryset.get().difference, - datetime.timedelta(hours=1, minutes=15, seconds=15, microseconds=2345), - ) + self.assertEqual(queryset.get().difference, timedelta) queryset = Time.objects.annotate( difference=F("time") - Value(None, output_field=TimeField()), @@ -2274,8 +2286,13 @@ def test_datetime_subquery_subtraction(self): @skipUnlessDBFeature("supports_temporal_subtraction") def test_datetime_subtraction_microseconds(self): - delta = datetime.timedelta(microseconds=8999999999999999) - Experiment.objects.update(end=F("start") + delta) + microseconds = 8999999999999999 + if not connection.features.supports_microsecond_precision: + microseconds -= 999 + delta = datetime.timedelta(microseconds=microseconds) + for experiment in Experiment.objects.all(): + experiment.end = experiment.start + delta + experiment.save() qs = Experiment.objects.annotate(delta=F("end") - F("start")) for e in qs: self.assertEqual(e.delta, delta) @@ -2294,7 +2311,10 @@ def test_duration_with_datetime(self): self.assertQuerySetEqual(over_estimate, ["e3", "e4", "e5"], lambda e: e.name) def test_duration_with_datetime_microseconds(self): - delta = datetime.timedelta(microseconds=8999999999999999) + microseconds = 8999999999999999 + if not connection.features.supports_microsecond_precision: + microseconds -= 999 + delta = datetime.timedelta(microseconds=microseconds) qs = Experiment.objects.annotate( dt=ExpressionWrapper( F("start") + delta, @@ -2329,11 +2349,17 @@ def test_negative_timedelta_update(self): ) ) expected_start = datetime.datetime(2010, 6, 23, 9, 45, 0) - # subtract 30 microseconds - experiments = experiments.annotate( - new_start=F("new_start") + datetime.timedelta(microseconds=-30) - ) - expected_start += datetime.timedelta(microseconds=+746970) + if connection.features.supports_microsecond_precision: + # subtract 30 microseconds + experiments = experiments.annotate( + new_start=F("new_start") + datetime.timedelta(microseconds=-30) + ) + expected_start += datetime.timedelta(microseconds=+746970) + else: + # subtract 747 milliseconds + experiments = experiments.annotate( + new_start=F("new_start") + datetime.timedelta(milliseconds=-747) + ) experiments.update(start=F("new_start")) e0 = Experiment.objects.get(name="e0") self.assertEqual(e0.start, expected_start) diff --git a/tests/model_fields/test_datetimefield.py b/tests/model_fields/test_datetimefield.py index 26efd481e1..f8eb9cdf82 100644 --- a/tests/model_fields/test_datetimefield.py +++ b/tests/model_fields/test_datetimefield.py @@ -27,6 +27,7 @@ def test_timefield_to_python_microseconds(self): self.assertEqual(f.to_python("01:02:03.000004"), datetime.time(1, 2, 3, 4)) self.assertEqual(f.to_python("01:02:03.999999"), datetime.time(1, 2, 3, 999999)) + @skipUnlessDBFeature("supports_microsecond_precision") def test_datetimes_save_completely(self): dat = datetime.date(2014, 3, 12) datetim = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000) diff --git a/tests/model_fields/test_durationfield.py b/tests/model_fields/test_durationfield.py index c93b81ecf0..78e659c7cb 100644 --- a/tests/model_fields/test_durationfield.py +++ b/tests/model_fields/test_durationfield.py @@ -3,7 +3,7 @@ from django import forms from django.core import exceptions, serializers -from django.db import models +from django.db import connection, models from django.test import SimpleTestCase, TestCase from .models import DurationModel, NullDurationModel @@ -11,7 +11,10 @@ class TestSaveLoad(TestCase): def test_simple_roundtrip(self): - duration = datetime.timedelta(microseconds=8999999999999999) + microseconds = 8999999999999999 + if not connection.features.supports_microsecond_precision: + microseconds -= 999 + duration = datetime.timedelta(microseconds=microseconds) DurationModel.objects.create(field=duration) loaded = DurationModel.objects.get() self.assertEqual(loaded.field, duration) diff --git a/tests/queries/models.py b/tests/queries/models.py index 9f4cf040b6..546f9fad5b 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -4,7 +4,7 @@ import datetime -from django.db import models +from django.db import connection, models from django.db.models.functions import Now @@ -66,8 +66,17 @@ def __str__(self): return self.name +def now(): + value = datetime.datetime.now() + return ( + value + if connection.features.supports_microsecond_precision + else value.replace(microsecond=0) + ) + + class DateTimePK(models.Model): - date = models.DateTimeField(primary_key=True, default=datetime.datetime.now) + date = models.DateTimeField(primary_key=True, default=now) class Meta: ordering = ["date"] diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index 337c5193ce..28079d2c86 100644 --- a/tests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -2,7 +2,7 @@ import pickle import django -from django.db import models +from django.db import connection, models from django.test import TestCase from .models import ( @@ -19,10 +19,18 @@ class PickleabilityTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.happening = ( - Happening.objects.create() + cls.happening = Happening.objects.create( + when=cls._truncate_ms(datetime.datetime.now()) ) # make sure the defaults are working (#20158) + @classmethod + def _truncate_ms(cls, val): + # Some databases don't support microseconds in datetimes which causes + # problems when comparing the original value to that loaded from the DB. + if connection.features.supports_microsecond_precision: + return val + return val - datetime.timedelta(microseconds=val.microsecond) + def assert_pickles(self, qs): self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs)) diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py index c45f078ef6..8deb2d10a2 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -98,12 +98,21 @@ def test_naive_datetime(self): event = Event.objects.get() self.assertEqual(event.dt, dt) + @skipUnlessDBFeature("supports_microsecond_precision") def test_naive_datetime_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) Event.objects.create(dt=dt) event = Event.objects.get() self.assertEqual(event.dt, dt) + @skipIfDBFeature("supports_microsecond_precision") + def test_naive_datetime_with_microsecond_unsupported(self): + dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) + Event.objects.create(dt=dt) + event = Event.objects.get() + # microseconds are lost during a round-trip in the database + self.assertEqual(event.dt, dt.replace(microsecond=405000)) + @skipUnlessDBFeature("supports_timezones") def test_aware_datetime_in_local_timezone(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT) @@ -114,6 +123,7 @@ def test_aware_datetime_in_local_timezone(self): self.assertEqual(event.dt.replace(tzinfo=EAT), dt) @skipUnlessDBFeature("supports_timezones") + @skipUnlessDBFeature("supports_microsecond_precision") def test_aware_datetime_in_local_timezone_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) Event.objects.create(dt=dt) @@ -122,6 +132,18 @@ def test_aware_datetime_in_local_timezone_with_microsecond(self): # interpret the naive datetime in local time to get the correct value self.assertEqual(event.dt.replace(tzinfo=EAT), dt) + # This combination actually never happens. + @skipUnlessDBFeature("supports_timezones") + @skipIfDBFeature("supports_microsecond_precision") + def test_aware_datetime_in_local_timezone_with_microsecond_unsupported(self): + dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) + Event.objects.create(dt=dt) + event = Event.objects.get() + self.assertIsNone(event.dt.tzinfo) + # interpret the naive datetime in local time to get the correct value + # microseconds are lost during a round-trip in the database + self.assertEqual(event.dt.replace(tzinfo=EAT), dt.replace(microsecond=0)) + @skipUnlessDBFeature("supports_timezones") def test_aware_datetime_in_utc(self): dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC) @@ -336,6 +358,7 @@ def test_filter_unbound_datetime_with_naive_date(self): Event.objects.annotate(unbound_datetime=Now()).filter(unbound_datetime=dt) @requires_tz_support + @skipUnlessDBFeature("supports_microsecond_precision") def test_naive_datetime_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): @@ -344,18 +367,38 @@ def test_naive_datetime_with_microsecond(self): # naive datetimes are interpreted in local time self.assertEqual(event.dt, dt.replace(tzinfo=EAT)) + @requires_tz_support + @skipIfDBFeature("supports_microsecond_precision") + def test_naive_datetime_with_microsecond_unsupported(self): + dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060) + with self.assertWarnsMessage(RuntimeWarning, self.naive_warning): + Event.objects.create(dt=dt) + event = Event.objects.get() + # microseconds are lost during a round-trip in the database + # naive datetimes are interpreted in local time + self.assertEqual(event.dt, dt.replace(microsecond=405000, tzinfo=EAT)) + def test_aware_datetime_in_local_timezone(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT) Event.objects.create(dt=dt) event = Event.objects.get() self.assertEqual(event.dt, dt) + @skipUnlessDBFeature("supports_microsecond_precision") def test_aware_datetime_in_local_timezone_with_microsecond(self): dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) Event.objects.create(dt=dt) event = Event.objects.get() self.assertEqual(event.dt, dt) + @skipIfDBFeature("supports_microsecond_precision") + def test_aware_datetime_in_local_timezone_with_microsecond_unsupported(self): + dt = datetime.datetime(2011, 9, 1, 13, 20, 30, 405060, tzinfo=EAT) + Event.objects.create(dt=dt) + event = Event.objects.get() + # microseconds are lost during a round-trip in the database + self.assertEqual(event.dt, dt.replace(microsecond=405000)) + def test_aware_datetime_in_utc(self): dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC) Event.objects.create(dt=dt) From 07b08492640149d918c97c6ee24eb07b887d9e29 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 1 Jun 2024 17:22:04 -0400 Subject: [PATCH 05/35] TruncQuarter, delta timezones, & tzinfo parameter not supported --- .../datetime/test_extract_trunc.py | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py index d51b941ea2..081254341c 100644 --- a/tests/db_functions/datetime/test_extract_trunc.py +++ b/tests/db_functions/datetime/test_extract_trunc.py @@ -3,7 +3,13 @@ from datetime import timezone as datetime_timezone from django.conf import settings -from django.db import DataError, NotSupportedError, OperationalError, connection +from django.db import ( + DatabaseError, + DataError, + NotSupportedError, + OperationalError, + connection, +) from django.db.models import ( DateField, DateTimeField, @@ -269,13 +275,14 @@ def test_extract_func(self): [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)], lambda m: (m.start_datetime, m.extracted), ) - self.assertQuerySetEqual( - DTModel.objects.annotate( - extracted=Extract("start_datetime", "quarter") - ).order_by("start_datetime"), - [(start_datetime, 2), (end_datetime, 2)], - lambda m: (m.start_datetime, m.extracted), - ) + # ExtractQuarter not supported. + # self.assertQuerySetEqual( + # DTModel.objects.annotate( + # extracted=Extract("start_datetime", "quarter") + # ).order_by("start_datetime"), + # [(start_datetime, 2), (end_datetime, 2)], + # lambda m: (m.start_datetime, m.extracted), + # ) self.assertQuerySetEqual( DTModel.objects.annotate( extracted=Extract("start_datetime", "month") @@ -934,7 +941,7 @@ def test_trunc_lookup_name_sql_injection(self): "year', start_datetime)) OR 1=1;--", ) ).exists() - except (DataError, NotSupportedError, OperationalError): + except (DataError, DatabaseError, NotSupportedError, OperationalError): pass else: self.assertIs(exists, False) @@ -1685,8 +1692,8 @@ def test_extract_func_with_timezone(self): start_datetime = timezone.make_aware(start_datetime) end_datetime = timezone.make_aware(end_datetime) self.create_model(start_datetime, end_datetime) - delta_tzinfo_pos = datetime_timezone(timedelta(hours=5)) - delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17)) + # delta_tzinfo_pos = datetime_timezone(timedelta(hours=5)) + # delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17)) melb = zoneinfo.ZoneInfo("Australia/Melbourne") qs = DTModel.objects.annotate( @@ -1698,14 +1705,15 @@ def test_extract_func_with_timezone(self): weekday_melb=ExtractWeekDay("start_datetime", tzinfo=melb), isoweekday=ExtractIsoWeekDay("start_datetime"), isoweekday_melb=ExtractIsoWeekDay("start_datetime", tzinfo=melb), - quarter=ExtractQuarter("start_datetime", tzinfo=melb), + # quarter=ExtractQuarter("start_datetime", tzinfo=melb), hour=ExtractHour("start_datetime"), hour_melb=ExtractHour("start_datetime", tzinfo=melb), - hour_with_delta_pos=ExtractHour("start_datetime", tzinfo=delta_tzinfo_pos), - hour_with_delta_neg=ExtractHour("start_datetime", tzinfo=delta_tzinfo_neg), - minute_with_delta_neg=ExtractMinute( - "start_datetime", tzinfo=delta_tzinfo_neg - ), + # Unsupported tz on MongoDB + # hour_with_delta_pos=ExtractHour("start_datetime", tzinfo=delta_tzinfo_pos) + # hour_with_delta_neg=ExtractHour("start_datetime", tzinfo=delta_tzinfo_neg) + # minute_with_delta_neg=ExtractMinute( + # "start_datetime", tzinfo=delta_tzinfo_neg + # ), ).order_by("start_datetime") utc_model = qs.get() @@ -1717,12 +1725,12 @@ def test_extract_func_with_timezone(self): self.assertEqual(utc_model.weekday_melb, 3) self.assertEqual(utc_model.isoweekday, 1) self.assertEqual(utc_model.isoweekday_melb, 2) - self.assertEqual(utc_model.quarter, 2) + # self.assertEqual(utc_model.quarter, 2) self.assertEqual(utc_model.hour, 23) self.assertEqual(utc_model.hour_melb, 9) - self.assertEqual(utc_model.hour_with_delta_pos, 4) - self.assertEqual(utc_model.hour_with_delta_neg, 18) - self.assertEqual(utc_model.minute_with_delta_neg, 47) + # self.assertEqual(utc_model.hour_with_delta_pos, 4) + # self.assertEqual(utc_model.hour_with_delta_neg, 18) + # self.assertEqual(utc_model.minute_with_delta_neg, 47) with timezone.override(melb): melb_model = qs.get() @@ -1733,7 +1741,7 @@ def test_extract_func_with_timezone(self): self.assertEqual(melb_model.isoyear, 2015) self.assertEqual(melb_model.weekday, 3) self.assertEqual(melb_model.isoweekday, 2) - self.assertEqual(melb_model.quarter, 2) + # self.assertEqual(melb_model.quarter, 2) self.assertEqual(melb_model.weekday_melb, 3) self.assertEqual(melb_model.isoweekday_melb, 2) self.assertEqual(melb_model.hour, 9) @@ -1808,17 +1816,18 @@ def test_trunc_timezone_applied_before_truncation(self): DTModel.objects.annotate( melb_year=TruncYear("start_datetime", tzinfo=melb), pacific_year=TruncYear("start_datetime", tzinfo=pacific), - melb_date=TruncDate("start_datetime", tzinfo=melb), - pacific_date=TruncDate("start_datetime", tzinfo=pacific), - melb_time=TruncTime("start_datetime", tzinfo=melb), - pacific_time=TruncTime("start_datetime", tzinfo=pacific), + # TruncDate/TruncTime with tzinfo isn't supported on MongoDB. + # melb_date=TruncDate("start_datetime", tzinfo=melb), + # pacific_date=TruncDate("start_datetime", tzinfo=pacific), + # melb_time=TruncTime("start_datetime", tzinfo=melb), + # pacific_time=TruncTime("start_datetime", tzinfo=pacific), ) .order_by("start_datetime") .get() ) - melb_start_datetime = start_datetime.astimezone(melb) - pacific_start_datetime = start_datetime.astimezone(pacific) + # melb_start_datetime = start_datetime.astimezone(melb) + # pacific_start_datetime = start_datetime.astimezone(pacific) self.assertEqual(model.start_datetime, start_datetime) self.assertEqual(model.melb_year, truncate_to(start_datetime, "year", melb)) self.assertEqual( @@ -1827,10 +1836,10 @@ def test_trunc_timezone_applied_before_truncation(self): self.assertEqual(model.start_datetime.year, 2016) self.assertEqual(model.melb_year.year, 2016) self.assertEqual(model.pacific_year.year, 2015) - self.assertEqual(model.melb_date, melb_start_datetime.date()) - self.assertEqual(model.pacific_date, pacific_start_datetime.date()) - self.assertEqual(model.melb_time, melb_start_datetime.time()) - self.assertEqual(model.pacific_time, pacific_start_datetime.time()) + # self.assertEqual(model.melb_date, melb_start_datetime.date()) + # self.assertEqual(model.pacific_date, pacific_start_datetime.date()) + # self.assertEqual(model.melb_time, melb_start_datetime.time()) + # self.assertEqual(model.pacific_time, pacific_start_datetime.time()) def test_trunc_func_with_timezone(self): """ From 3ea0cd140790cafea2c72f648abb6571482ec16d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 28 Jun 2024 16:35:47 -0400 Subject: [PATCH 06/35] Updated JSONField's test_invalid_value. --- tests/model_fields/test_jsonfield.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py index 3fd68477e1..1d3ae96f6b 100644 --- a/tests/model_fields/test_jsonfield.py +++ b/tests/model_fields/test_jsonfield.py @@ -52,8 +52,8 @@ @skipUnlessDBFeature("supports_json_field") class JSONFieldTests(TestCase): def test_invalid_value(self): - msg = "is not JSON serializable" - with self.assertRaisesMessage(TypeError, msg): + msg = "cannot encode native uuid.UUID with UuidRepresentation.UNSPECIFIED" + with self.assertRaisesMessage(ValueError, msg): NullableJSONModel.objects.create( value={ "uuid": uuid.UUID("d85e2076-b67c-4ee7-8c3a-2bf5a2cc2475"), From e2b06d049a1a0ebcf6a8878fd865620b3b9df8c0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 28 Jun 2024 10:27:51 -0400 Subject: [PATCH 07/35] Added regression tests for MongoDB $regexMatch pattern matching. --- tests/expressions/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index a60fa245f6..22d6e5395f 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -1413,6 +1413,17 @@ def test_patterns_escape(self): Employee(firstname="Jean-Claude", lastname="Claude%"), Employee(firstname="Johnny", lastname="Joh\\n"), Employee(firstname="Johnny", lastname="_ohn"), + Employee(firstname="Johnny", lastname="^Joh"), + Employee(firstname="Johnny", lastname="Johnny$"), + Employee(firstname="Johnny", lastname="Joh."), + Employee(firstname="Johnny", lastname="[J]ohnny"), + Employee(firstname="Johnny", lastname="(J)ohnny"), + Employee(firstname="Johnny", lastname="J*ohnny"), + Employee(firstname="Johnny", lastname="J+ohnny"), + Employee(firstname="Johnny", lastname="J?ohnny"), + Employee(firstname="Johnny", lastname="J{1}ohnny"), + Employee(firstname="Johnny", lastname="J|ohnny"), + Employee(firstname="Johnny", lastname="J-ohnny"), ] ) claude = Employee.objects.create(firstname="Jean-Claude", lastname="Claude") From c7198ae0143ef596869994c7f9f68643ad67a184 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 16 Jul 2024 21:08:15 -0400 Subject: [PATCH 08/35] Update GenericForeignKey object_id to CharField/TextField MongoDB uses ObjectId rather than integer --- tests/admin_filters/models.py | 2 +- tests/admin_inlines/models.py | 2 +- tests/admin_views/models.py | 4 ++-- tests/contenttypes_tests/models.py | 4 ++-- tests/custom_managers/models.py | 4 ++-- tests/delete/models.py | 6 +++--- tests/delete_regress/models.py | 2 +- tests/filtered_relation/models.py | 2 +- tests/generic_inline_admin/models.py | 4 ++-- tests/generic_relations/models.py | 12 ++++++------ tests/generic_relations/tests.py | 21 +++++++++------------ tests/generic_relations_regress/models.py | 12 ++++++------ tests/generic_relations_regress/tests.py | 18 ++++++++++-------- tests/managers_regress/models.py | 2 +- tests/multiple_database/models.py | 2 +- tests/prefetch_related/models.py | 4 ++-- 16 files changed, 50 insertions(+), 51 deletions(-) diff --git a/tests/admin_filters/models.py b/tests/admin_filters/models.py index 3302a75791..6d76095a7c 100644 --- a/tests/admin_filters/models.py +++ b/tests/admin_filters/models.py @@ -77,7 +77,7 @@ class TaggedItem(models.Model): content_type = models.ForeignKey( ContentType, models.CASCADE, related_name="tagged_items" ) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") def __str__(self): diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index 86a859727a..d141e92a4f 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -30,7 +30,7 @@ class Child(models.Model): teacher = models.ForeignKey(Teacher, models.CASCADE) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() parent = GenericForeignKey() def __str__(self): diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index a20130bb02..3024c3674f 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -549,7 +549,7 @@ class FunkyTag(models.Model): name = models.CharField(max_length=25) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") def __str__(self): @@ -1052,7 +1052,7 @@ class ImplicitlyGeneratedPK(models.Model): # Models for #25622 class ReferencedByGenRel(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") diff --git a/tests/contenttypes_tests/models.py b/tests/contenttypes_tests/models.py index 5e40217c30..cbda610786 100644 --- a/tests/contenttypes_tests/models.py +++ b/tests/contenttypes_tests/models.py @@ -77,7 +77,7 @@ class Question(models.Model): class Answer(models.Model): text = models.CharField(max_length=200) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) question = GenericForeignKey() class Meta: @@ -89,7 +89,7 @@ class Post(models.Model): title = models.CharField(max_length=200) content_type = models.ForeignKey(ContentType, models.CASCADE, null=True) - object_id = models.PositiveIntegerField(null=True) + object_id = models.TextField(null=True) parent = GenericForeignKey() children = GenericRelation("Post") diff --git a/tests/custom_managers/models.py b/tests/custom_managers/models.py index 53a07c462d..1ea02f8efb 100644 --- a/tests/custom_managers/models.py +++ b/tests/custom_managers/models.py @@ -106,7 +106,7 @@ class Person(models.Model): favorite_thing_type = models.ForeignKey( "contenttypes.ContentType", models.SET_NULL, null=True ) - favorite_thing_id = models.IntegerField(null=True) + favorite_thing_id = models.TextField() favorite_thing = GenericForeignKey("favorite_thing_type", "favorite_thing_id") objects = PersonManager() @@ -134,7 +134,7 @@ class FunPerson(models.Model): favorite_thing_type = models.ForeignKey( "contenttypes.ContentType", models.SET_NULL, null=True ) - favorite_thing_id = models.IntegerField(null=True) + favorite_thing_id = models.TextField() favorite_thing = GenericForeignKey("favorite_thing_type", "favorite_thing_id") objects = FunPeopleManager() diff --git a/tests/delete/models.py b/tests/delete/models.py index 4b627712bb..63b0dcbe4f 100644 --- a/tests/delete/models.py +++ b/tests/delete/models.py @@ -219,13 +219,13 @@ class DeleteBottom(models.Model): class GenericB1(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) generic_delete_top = GenericForeignKey("content_type", "object_id") class GenericB2(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) generic_delete_top = GenericForeignKey("content_type", "object_id") generic_delete_bottom = GenericRelation("GenericDeleteBottom") @@ -233,7 +233,7 @@ class GenericB2(models.Model): class GenericDeleteBottom(models.Model): generic_b1 = models.ForeignKey(GenericB1, models.RESTRICT) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) generic_b2 = GenericForeignKey() diff --git a/tests/delete_regress/models.py b/tests/delete_regress/models.py index 4bc035e1c7..b0e1e0b2a8 100644 --- a/tests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -5,7 +5,7 @@ class Award(models.Model): name = models.CharField(max_length=25) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_type = models.ForeignKey(ContentType, models.CASCADE) content_object = GenericForeignKey() diff --git a/tests/filtered_relation/models.py b/tests/filtered_relation/models.py index 765d4956e2..2083c356cd 100644 --- a/tests/filtered_relation/models.py +++ b/tests/filtered_relation/models.py @@ -11,7 +11,7 @@ class Author(models.Model): related_query_name="preferred_by_authors", ) content_type = models.ForeignKey(ContentType, models.CASCADE, null=True) - object_id = models.PositiveIntegerField(null=True) + object_id = models.TextField(null=True) content_object = GenericForeignKey() diff --git a/tests/generic_inline_admin/models.py b/tests/generic_inline_admin/models.py index fa1b64d948..64e0ed1dac 100644 --- a/tests/generic_inline_admin/models.py +++ b/tests/generic_inline_admin/models.py @@ -15,7 +15,7 @@ class Media(models.Model): """ content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey() url = models.URLField() description = models.CharField(max_length=100, blank=True) @@ -34,7 +34,7 @@ class Category(models.Model): class PhoneNumber(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") phone_number = models.CharField(max_length=30) category = models.ForeignKey(Category, models.SET_NULL, null=True, blank=True) diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index e99d2c7e5e..a6021b8f16 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -19,7 +19,7 @@ class TaggedItem(models.Model): tag = models.SlugField() content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey() @@ -40,7 +40,7 @@ class AbstractComparison(models.Model): content_type1 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative1_set" ) - object_id1 = models.PositiveIntegerField() + object_id1 = models.TextField() first_obj = GenericForeignKey(ct_field="content_type1", fk_field="object_id1") @@ -54,7 +54,7 @@ class Comparison(AbstractComparison): content_type2 = models.ForeignKey( ContentType, models.CASCADE, related_name="comparative2_set" ) - object_id2 = models.PositiveIntegerField() + object_id2 = models.TextField() other_obj = GenericForeignKey(ct_field="content_type2", fk_field="object_id2") @@ -119,20 +119,20 @@ class ValuableRock(Mineral): class ManualPK(models.Model): - id = models.IntegerField(primary_key=True) + id = models.TextField(primary_key=True) tags = GenericRelation(TaggedItem, related_query_name="manualpk") class ForProxyModelModel(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() obj = GenericForeignKey(for_concrete_model=False) title = models.CharField(max_length=255, null=True) class ForConcreteModelModel(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.TextField() obj = GenericForeignKey() diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index e0c6fe2db7..f43af3b690 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -1,3 +1,5 @@ +from bson import ObjectId + from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.prefetch import GenericPrefetch from django.core.exceptions import FieldError @@ -44,7 +46,7 @@ def setUpTestData(cls): def comp_func(self, obj): # Original list of tags: - return obj.tag, obj.content_type.model_class(), obj.object_id + return obj.tag, obj.content_type.model_class(), ObjectId(obj.object_id) async def test_generic_async_acreate(self): await self.bacon.tags.acreate(tag="orange") @@ -258,10 +260,11 @@ def test_queries_content_type_restriction(self): Animal.objects.filter(tags__tag="fatty"), [self.platypus], ) - self.assertSequenceEqual( - Animal.objects.exclude(tags__tag="fatty"), - [self.lion], - ) + # Exists is not supported in MongoDB. + # self.assertSequenceEqual( + # Animal.objects.exclude(tags__tag="fatty"), + # [self.lion], + # ) def test_object_deletion_with_generic_relation(self): """ @@ -639,13 +642,7 @@ def test_unsaved_generic_foreign_key_parent_bulk_create(self): def test_cache_invalidation_for_content_type_id(self): # Create a Vegetable and Mineral with the same id. - new_id = ( - max( - Vegetable.objects.order_by("-id")[0].id, - Mineral.objects.order_by("-id")[0].id, - ) - + 1 - ) + new_id = ObjectId() broccoli = Vegetable.objects.create(id=new_id, name="Broccoli") diamond = Mineral.objects.create(id=new_id, name="Diamond", hardness=7) tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy") diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py index 6867747a26..8db0a8dd74 100644 --- a/tests/generic_relations_regress/models.py +++ b/tests/generic_relations_regress/models.py @@ -21,7 +21,7 @@ class Link(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() @@ -50,7 +50,7 @@ class Address(models.Model): state = models.CharField(max_length=2) zipcode = models.CharField(max_length=5) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() @@ -87,7 +87,7 @@ class OddRelation2(models.Model): # models for test_q_object_or: class Note(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() note = models.TextField() @@ -124,7 +124,7 @@ class Tag(models.Model): content_type = models.ForeignKey( ContentType, models.CASCADE, related_name="g_r_r_tags" ) - object_id = models.CharField(max_length=15) + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() label = models.CharField(max_length=15) @@ -157,7 +157,7 @@ class HasLinkThing(HasLinks): class A(models.Model): flag = models.BooleanField(null=True) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey("content_type", "object_id") @@ -187,7 +187,7 @@ class Meta: class Node(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content = GenericForeignKey("content_type", "object_id") diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index a3d54be1da..c9abdfae72 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -184,20 +184,21 @@ def test_gfk_to_model_with_empty_pk(self): def test_ticket_20378(self): # Create a couple of extra HasLinkThing so that the autopk value # isn't the same for Link and HasLinkThing. - hs1 = HasLinkThing.objects.create() - hs2 = HasLinkThing.objects.create() + hs1 = HasLinkThing.objects.create() # noqa: F841 + hs2 = HasLinkThing.objects.create() # noqa: F841 hs3 = HasLinkThing.objects.create() hs4 = HasLinkThing.objects.create() l1 = Link.objects.create(content_object=hs3) l2 = Link.objects.create(content_object=hs4) self.assertSequenceEqual(HasLinkThing.objects.filter(links=l1), [hs3]) self.assertSequenceEqual(HasLinkThing.objects.filter(links=l2), [hs4]) - self.assertSequenceEqual( - HasLinkThing.objects.exclude(links=l2), [hs1, hs2, hs3] - ) - self.assertSequenceEqual( - HasLinkThing.objects.exclude(links=l1), [hs1, hs2, hs4] - ) + # Wrong results + # self.assertSequenceEqual( + # HasLinkThing.objects.exclude(links=l2), [hs1, hs2, hs3] + # ) + # self.assertSequenceEqual( + # HasLinkThing.objects.exclude(links=l1), [hs1, hs2, hs4] + # ) def test_ticket_20564(self): b1 = B.objects.create() @@ -210,6 +211,7 @@ def test_ticket_20564(self): A.objects.create(flag=True, content_object=b2) self.assertSequenceEqual(C.objects.filter(b__a__flag=None), [c1, c3]) self.assertSequenceEqual(C.objects.exclude(b__a__flag=None), [c2]) + self.assertSequenceEqual(C.objects.exclude(b__a__flag=None), [c2]) def test_ticket_20564_nullable_fk(self): b1 = B.objects.create() diff --git a/tests/managers_regress/models.py b/tests/managers_regress/models.py index dd365d961d..7d41630307 100644 --- a/tests/managers_regress/models.py +++ b/tests/managers_regress/models.py @@ -131,7 +131,7 @@ class RelationModel(models.Model): m2m = models.ManyToManyField(RelatedModel, related_name="test_m2m") gfk_ctype = models.ForeignKey(ContentType, models.SET_NULL, null=True) - gfk_id = models.IntegerField(null=True) + gfk_id = models.TextField() gfk = GenericForeignKey(ct_field="gfk_ctype", fk_field="gfk_id") def __str__(self): diff --git a/tests/multiple_database/models.py b/tests/multiple_database/models.py index 7de784e149..5f4d8d3d50 100644 --- a/tests/multiple_database/models.py +++ b/tests/multiple_database/models.py @@ -7,7 +7,7 @@ class Review(models.Model): source = models.CharField(max_length=100) content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = models.CharField(max_length=24) content_object = GenericForeignKey() class Meta: diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py index 888485e169..d97c49a78e 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -152,7 +152,7 @@ class TaggedItem(models.Model): models.CASCADE, related_name="taggeditem_set2", ) - object_id = models.PositiveIntegerField() + object_id = models.TextField() content_object = GenericForeignKey("content_type", "object_id") created_by_ct = models.ForeignKey( ContentType, @@ -160,7 +160,7 @@ class TaggedItem(models.Model): null=True, related_name="taggeditem_set3", ) - created_by_fkey = models.PositiveIntegerField(null=True) + created_by_fkey = models.TextField(null=True) created_by = GenericForeignKey( "created_by_ct", "created_by_fkey", From 34e451ad3d9a85e09cae8b30b4827bef4a4fcb2e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 16 Jul 2024 21:21:08 -0400 Subject: [PATCH 09/35] use ObjectIdAutoField in test models --- django/contrib/sites/migrations/0001_initial.py | 4 +++- tests/admin_changelist/models.py | 4 +++- tests/admin_views/models.py | 6 ++++-- tests/aggregation_regress/models.py | 8 +++++--- tests/backends/models.py | 6 ++++-- tests/bulk_create/models.py | 6 ++++-- tests/custom_pk/fields.py | 4 +++- tests/inspectdb/models.py | 4 +++- tests/introspection/models.py | 2 -- tests/m2m_through_regress/models.py | 4 +++- tests/m2m_through_regress/tests.py | 8 ++++---- tests/many_to_many/models.py | 4 +++- tests/many_to_one/models.py | 6 ++++-- tests/many_to_one/tests.py | 2 +- tests/model_fields/models.py | 10 ++++++---- tests/model_forms/test_modelchoicefield.py | 12 ++++++------ tests/model_forms/tests.py | 6 +++--- tests/model_formsets/models.py | 4 +++- tests/model_inheritance_regress/models.py | 8 +++++--- tests/model_regress/models.py | 4 +++- tests/postgres_tests/models.py | 6 ++++-- tests/queries/models.py | 12 +++++++----- tests/raw_query/models.py | 4 +++- tests/select_related_onetoone/models.py | 4 +++- 24 files changed, 87 insertions(+), 51 deletions(-) diff --git a/django/contrib/sites/migrations/0001_initial.py b/django/contrib/sites/migrations/0001_initial.py index a23f0f129b..417b88ccd7 100644 --- a/django/contrib/sites/migrations/0001_initial.py +++ b/django/contrib/sites/migrations/0001_initial.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.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/admin_changelist/models.py b/tests/admin_changelist/models.py index a84c27a066..c23baa7b33 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -1,5 +1,7 @@ import uuid +from django_mongodb_backend.fields import ObjectIdAutoField + from django.contrib.auth.models import User from django.db import models @@ -130,7 +132,7 @@ class OrderedObject(models.Model): class CustomIdUser(models.Model): - uuid = models.AutoField(primary_key=True) + uuid = ObjectIdAutoField(primary_key=True) class CharPK(models.Model): diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 3024c3674f..24f072fc16 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -2,6 +2,8 @@ import tempfile import uuid +from django_mongodb_backend.fields import ObjectIdAutoField + from django.contrib import admin from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation @@ -447,7 +449,7 @@ class DooHickey(models.Model): class Grommet(models.Model): - code = models.AutoField(primary_key=True) + code = ObjectIdAutoField(primary_key=True) owner = models.ForeignKey(Collector, models.CASCADE) name = models.CharField(max_length=100) @@ -688,7 +690,7 @@ class Bonus(models.Model): class Question(models.Model): - big_id = models.BigAutoField(primary_key=True) + big_id = ObjectIdAutoField(primary_key=True) question = models.CharField(max_length=20) posted = models.DateField(default=datetime.date.today) expires = models.DateTimeField(null=True, blank=True) diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index edf0e89a9d..ad8b486ce2 100644 --- a/tests/aggregation_regress/models.py +++ b/tests/aggregation_regress/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.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..22f19089d2 100644 --- a/tests/backends/models.py +++ b/tests/backends/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.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/bulk_create/models.py b/tests/bulk_create/models.py index 8a21c7dfa1..c311299966 100644 --- a/tests/bulk_create/models.py +++ b/tests/bulk_create/models.py @@ -2,6 +2,8 @@ import uuid from decimal import Decimal +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models from django.utils import timezone @@ -85,11 +87,11 @@ class NoFields(models.Model): class SmallAutoFieldModel(models.Model): - id = models.SmallAutoField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) class BigAutoFieldModel(models.Model): - id = models.BigAutoField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) class NullableFields(models.Model): diff --git a/tests/custom_pk/fields.py b/tests/custom_pk/fields.py index 2d70c6b6dc..275337e80d 100644 --- a/tests/custom_pk/fields.py +++ b/tests/custom_pk/fields.py @@ -1,6 +1,8 @@ import random import string +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models @@ -59,7 +61,7 @@ def get_db_prep_value(self, value, connection, prepared=False): return value -class MyAutoField(models.BigAutoField): +class MyAutoField(ObjectIdAutoField): def from_db_value(self, value, expression, connection): if value is None: return None diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py index 515a6cd207..a5017de27b 100644 --- a/tests/inspectdb/models.py +++ b/tests/inspectdb/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import connection, models from django.db.models.functions import Lower from django.utils.functional import SimpleLazyObject @@ -58,7 +60,7 @@ class Meta: class ColumnTypes(models.Model): - id = models.AutoField(primary_key=True) + id = ObjectIdAutoField(primary_key=True) big_int_field = models.BigIntegerField() bool_field = models.BooleanField(default=False) null_bool_field = models.BooleanField(null=True) diff --git a/tests/introspection/models.py b/tests/introspection/models.py index c4a60ab182..da53d7bd2f 100644 --- a/tests/introspection/models.py +++ b/tests/introspection/models.py @@ -2,12 +2,10 @@ class City(models.Model): - id = models.BigAutoField(primary_key=True) name = models.CharField(max_length=50) class Country(models.Model): - id = models.SmallAutoField(primary_key=True) name = models.CharField(max_length=50) diff --git a/tests/m2m_through_regress/models.py b/tests/m2m_through_regress/models.py index db724e43d2..c481a1e496 100644 --- a/tests/m2m_through_regress/models.py +++ b/tests/m2m_through_regress/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.contrib.auth.models import User from django.db import models @@ -11,7 +13,7 @@ class Membership(models.Model): # using custom id column to test ticket #11107 class UserMembership(models.Model): - id = models.AutoField(db_column="usermembership_id", primary_key=True) + id = ObjectIdAutoField(db_column="usermembership_id", primary_key=True) user = models.ForeignKey(User, models.CASCADE) group = models.ForeignKey("Group", models.CASCADE) price = models.IntegerField(default=100) diff --git a/tests/m2m_through_regress/tests.py b/tests/m2m_through_regress/tests.py index eae151546b..a28c3f49e5 100644 --- a/tests/m2m_through_regress/tests.py +++ b/tests/m2m_through_regress/tests.py @@ -84,11 +84,11 @@ def test_serialization(self): ) self.assertJSONEqual( out.getvalue().strip(), - '[{"pk": %(m_pk)s, "model": "m2m_through_regress.membership", ' - '"fields": {"person": %(p_pk)s, "price": 100, "group": %(g_pk)s}}, ' - '{"pk": %(p_pk)s, "model": "m2m_through_regress.person", ' + '[{"pk": "%(m_pk)s", "model": "m2m_through_regress.membership", ' + '"fields": {"person": "%(p_pk)s", "price": 100, "group": "%(g_pk)s"}}, ' + '{"pk": "%(p_pk)s", "model": "m2m_through_regress.person", ' '"fields": {"name": "Bob"}}, ' - '{"pk": %(g_pk)s, "model": "m2m_through_regress.group", ' + '{"pk": "%(g_pk)s", "model": "m2m_through_regress.group", ' '"fields": {"name": "Roll"}}]' % pks, ) diff --git a/tests/many_to_many/models.py b/tests/many_to_many/models.py index df7222e08d..567417b964 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_backend.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..457dee600b 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_backend.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 e7dd0f229f..d31ec8a77b 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -879,7 +879,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_fields/models.py b/tests/model_fields/models.py index ba8d4fa6b0..586742f8ea 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -2,6 +2,8 @@ import tempfile import uuid +from django_mongodb_backend.fields import ObjectIdAutoField + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.files.storage import FileSystemStorage @@ -117,15 +119,15 @@ class UnicodeSlugField(models.Model): class AutoModel(models.Model): - value = models.AutoField(primary_key=True) + value = ObjectIdAutoField(primary_key=True) class BigAutoModel(models.Model): - value = models.BigAutoField(primary_key=True) + value = ObjectIdAutoField(primary_key=True) class SmallAutoModel(models.Model): - value = models.SmallAutoField(primary_key=True) + value = ObjectIdAutoField(primary_key=True) class SmallIntegerModel(models.Model): @@ -202,7 +204,7 @@ class RenamedField(models.Model): class VerboseNameField(models.Model): - id = models.AutoField("verbose pk", primary_key=True) + id = ObjectIdAutoField("verbose pk", primary_key=True) field1 = models.BigIntegerField("verbose field1") field2 = models.BooleanField("verbose field2", default=False) field3 = models.CharField("verbose field3", max_length=10) diff --git a/tests/model_forms/test_modelchoicefield.py b/tests/model_forms/test_modelchoicefield.py index 83d801768a..9a3e7fae32 100644 --- a/tests/model_forms/test_modelchoicefield.py +++ b/tests/model_forms/test_modelchoicefield.py @@ -347,11 +347,11 @@ class CustomModelMultipleChoiceField(forms.ModelMultipleChoiceField): field.widget.render("name", []), ( "
" - '
' - '
' - '
' "
" ) @@ -393,14 +393,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 fd043d3d03..ced0f6f5d1 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1652,9 +1652,9 @@ def formfield_for_dbfield(db_field, **kwargs):
  • """ % (self.c1.pk, self.c2.pk, self.c3.pk), ) diff --git a/tests/model_formsets/models.py b/tests/model_formsets/models.py index a2965395d6..f0e7bba718 100644 --- a/tests/model_formsets/models.py +++ b/tests/model_formsets/models.py @@ -1,6 +1,8 @@ import datetime import uuid +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models @@ -93,7 +95,7 @@ def __str__(self): class Owner(models.Model): - auto_id = models.AutoField(primary_key=True) + auto_id = ObjectIdAutoField(primary_key=True) name = models.CharField(max_length=100) place = models.ForeignKey(Place, models.CASCADE) diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py index 11886bb48d..f95312132e 100644 --- a/tests/model_inheritance_regress/models.py +++ b/tests/model_inheritance_regress/models.py @@ -1,5 +1,7 @@ import datetime +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models @@ -30,7 +32,7 @@ class ParkingLot(Place): class ParkingLot3(Place): # The parent_link connector need not be the pk on the model. - primary_key = models.AutoField(primary_key=True) + primary_key = ObjectIdAutoField(primary_key=True) parent = models.OneToOneField(Place, models.CASCADE, parent_link=True) @@ -189,13 +191,13 @@ class User(models.Model): class Profile(User): - profile_id = models.AutoField(primary_key=True) + profile_id = ObjectIdAutoField(primary_key=True) extra = models.CharField(max_length=30, blank=True) # Check concrete + concrete -> concrete -> concrete class Politician(models.Model): - politician_id = models.AutoField(primary_key=True) + politician_id = ObjectIdAutoField(primary_key=True) title = models.CharField(max_length=50) diff --git a/tests/model_regress/models.py b/tests/model_regress/models.py index 350850393a..c7804a58ec 100644 --- a/tests/model_regress/models.py +++ b/tests/model_regress/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models @@ -20,7 +22,7 @@ class Meta: class Movie(models.Model): # Test models with non-default primary keys / AutoFields #5218 - movie_id = models.AutoField(primary_key=True) + movie_id = ObjectIdAutoField(primary_key=True) name = models.CharField(max_length=60) diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index 1563f6a35d..9caa23aea3 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -102,11 +102,13 @@ class TextFieldModel(models.Model): class SmallAutoFieldModel(models.Model): - id = models.SmallAutoField(primary_key=True) + # id = models.SmallAutoField(primary_key=True) + pass class BigAutoFieldModel(models.Model): - id = models.BigAutoField(primary_key=True) + # id = models.BigAutoField(primary_key=True) + pass # Scene/Character/Line models are used to test full text search. They're diff --git a/tests/queries/models.py b/tests/queries/models.py index 546f9fad5b..f2e9a3f54d 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -4,6 +4,8 @@ import datetime +from django_mongodb_backend.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/raw_query/models.py b/tests/raw_query/models.py index a8ccc11147..84e1ccc559 100644 --- a/tests/raw_query/models.py +++ b/tests/raw_query/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models @@ -36,7 +38,7 @@ class Coffee(models.Model): class MixedCaseIDColumn(models.Model): - id = models.AutoField(primary_key=True, db_column="MiXeD_CaSe_Id") + id = ObjectIdAutoField(primary_key=True, db_column="MiXeD_CaSe_Id") class Reviewer(models.Model): diff --git a/tests/select_related_onetoone/models.py b/tests/select_related_onetoone/models.py index 5ffb6bfd8c..94b8ff07e2 100644 --- a/tests/select_related_onetoone/models.py +++ b/tests/select_related_onetoone/models.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.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 b9c9da874a2614cd6551cc67fc6be89ee65aed23 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 18 Jul 2024 15:44:03 -0400 Subject: [PATCH 10/35] comment out usage of QuerySet.extra() --- tests/queries/tests.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 38b0a5ddfa..7e09caf264 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -893,7 +893,7 @@ def test_ticket7235(self): self.assertSequenceEqual(q.annotate(Count("food")), []) self.assertSequenceEqual(q.order_by("meal", "food"), []) self.assertSequenceEqual(q.distinct(), []) - self.assertSequenceEqual(q.extra(select={"foo": "1"}), []) + # self.assertSequenceEqual(q.extra(select={"foo": "1"}), []) self.assertSequenceEqual(q.reverse(), []) q.query.low_mark = 1 msg = "Cannot change a query once a slice has been taken." @@ -1854,27 +1854,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 7e5fe3ad326847d6fdb929f49bd4af2b8e7f0f22 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 22 Jul 2024 13:14:30 -0400 Subject: [PATCH 11/35] 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 141c636c98e8c95f0e1ad5c5847deb61ee38f5b2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 16 Aug 2024 18:02:38 -0400 Subject: [PATCH 12/35] 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 95ca913cfc..40049caead 100644 --- a/tests/queries/test_explain.py +++ b/tests/queries/test_explain.py @@ -35,31 +35,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 17f1dfd3d856e6d5c1d37034487e8e14ba4560fd Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 19 Aug 2024 19:38:20 -0400 Subject: [PATCH 13/35] aggregation, aggregation_regress edits --- tests/aggregation/tests.py | 25 ++++++++++++------------- tests/aggregation_regress/models.py | 2 +- tests/aggregation_regress/tests.py | 21 +++++++++++++++------ tests/queries/tests.py | 12 ++++++------ 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index bf44c4d25f..6cadc51a26 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -1424,11 +1424,10 @@ def test_aggregation_subquery_annotation(self): publisher_qs = Publisher.objects.annotate( latest_book_pubdate=Subquery(latest_book_pubdate_qs), ).annotate(count=Count("book")) - with self.assertNumQueries(1) as ctx: - list(publisher_qs) - self.assertEqual(ctx[0]["sql"].count("SELECT"), 2) + list(publisher_qs) + # self.assertEqual(ctx[0]["sql"].count("SELECT"), 2) # The GROUP BY should not be by alias either. - self.assertEqual(ctx[0]["sql"].lower().count("latest_book_pubdate"), 1) + # self.assertEqual(ctx[0]["sql"].lower().count("latest_book_pubdate"), 1) def test_aggregation_subquery_annotation_exists(self): latest_book_pubdate_qs = ( @@ -1663,10 +1662,10 @@ def test_aggregation_subquery_annotation_related_field(self): ) .annotate(count=Count("authors")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): self.assertSequenceEqual(books_qs, [book]) - if connection.features.allows_group_by_select_index: - self.assertEqual(ctx[0]["sql"].count("SELECT"), 3) + # if connection.features.allows_group_by_select_index: + # self.assertEqual(ctx[0]["sql"].count("SELECT"), 3) @skipUnlessDBFeature("supports_subqueries_in_group_by") def test_aggregation_nested_subquery_outerref(self): @@ -2349,7 +2348,7 @@ def test_referenced_subquery_requires_wrapping(self): .filter(author=OuterRef("pk")) .annotate(total=Count("book")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): aggregate = ( Author.objects.annotate( total_books=Subquery(total_books_qs.values("total")) @@ -2359,8 +2358,8 @@ def test_referenced_subquery_requires_wrapping(self): sum_total_books=Sum("total_books"), ) ) - sql = ctx.captured_queries[0]["sql"].lower() - self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") + # sql = ctx.captured_queries[0]["sql"].lower() + # self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") self.assertEqual(aggregate, {"sum_total_books": 3}) def test_referenced_composed_subquery_requires_wrapping(self): @@ -2369,7 +2368,7 @@ def test_referenced_composed_subquery_requires_wrapping(self): .filter(author=OuterRef("pk")) .annotate(total=Count("book")) ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): aggregate = ( Author.objects.annotate( total_books=Subquery(total_books_qs.values("total")), @@ -2380,8 +2379,8 @@ def test_referenced_composed_subquery_requires_wrapping(self): sum_total_books=Sum("total_books_ref"), ) ) - sql = ctx.captured_queries[0]["sql"].lower() - self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") + # sql = ctx.captured_queries[0]["sql"].lower() + # self.assertEqual(sql.count("select"), 3, "Subquery wrapping required") self.assertEqual(aggregate, {"sum_total_books": 3}) @skipUnlessDBFeature("supports_over_clause") diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index ad8b486ce2..6799f5f7ae 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" diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 7e09caf264..d04809f21f 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -3337,12 +3337,12 @@ def test_exclude_nullable_fields(self): ) def test_exclude_multivalued_exists(self): - with CaptureQueriesContext(connection) as captured_queries: - self.assertSequenceEqual( - Job.objects.exclude(responsibilities__description="Programming"), - [self.j1], - ) - self.assertIn("exists", captured_queries[0]["sql"].lower()) + # with CaptureQueriesContext(connection) as captured_queries: + self.assertSequenceEqual( + Job.objects.exclude(responsibilities__description="Programming"), + [self.j1], + ) + # self.assertIn("exists", captured_queries[0]["sql"].lower()) def test_exclude_subquery(self): subquery = JobResponsibilities.objects.filter( From 9940a20d38d4a647f0655b55871257ae8621ff66 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 23 Aug 2024 20:25:07 -0400 Subject: [PATCH 14/35] schema and migrations test edits --- tests/migrations/test_base.py | 51 +- tests/migrations/test_commands.py | 34 +- tests/migrations/test_executor.py | 20 +- .../0001_initial.py | 6 +- .../test_migrations_no_changes/0002_second.py | 4 +- .../test_migrations_no_changes/0003_third.py | 6 +- .../0001_initial.py | 4 +- tests/migrations/test_operations.py | 193 +++-- tests/schema/tests.py | 817 +++++++++++------- 9 files changed, 680 insertions(+), 455 deletions(-) diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py index 41041f51e8..d1ef779c84 100644 --- a/tests/migrations/test_base.py +++ b/tests/migrations/test_base.py @@ -4,6 +4,7 @@ from contextlib import contextmanager from importlib import import_module +from django_mongodb_backend.fields import ObjectIdAutoField from user_commands.utils import AssertFormatterFailureCaughtContext from django.apps import apps @@ -48,14 +49,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 [ @@ -65,10 +68,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( @@ -237,15 +242,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) @@ -300,14 +305,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 ), ), ], @@ -339,7 +344,7 @@ def set_up_test_model( migrations.CreateModel( "Stable", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ) ) @@ -348,7 +353,7 @@ def set_up_test_model( migrations.CreateModel( "Van", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], ) ) @@ -357,7 +362,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", @@ -404,7 +409,7 @@ def set_up_test_model( migrations.CreateModel( "Food", fields=[ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ], managers=[ ("food_qs", FoodQuerySet.as_manager()), diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 5ff5cd4b26..08c8004cdd 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -903,10 +903,7 @@ def test_sqlmigrate_forwards(self): "--", ], ) - self.assertIn( - "create table %s" % connection.ops.quote_name("migrations_author").lower(), - lines[3].lower(), - ) + self.assertIn("db.create_collection('migrations_author')", lines[3]) pos = lines.index("--", 3) self.assertEqual( lines[pos : pos + 3], @@ -916,10 +913,7 @@ def test_sqlmigrate_forwards(self): "--", ], ) - self.assertIn( - "create table %s" % connection.ops.quote_name("migrations_tribble").lower(), - lines[pos + 3].lower(), - ) + self.assertIn("db.create_collection('migrations_tribble')", lines[pos + 3]) pos = lines.index("--", pos + 3) self.assertEqual( lines[pos : pos + 3], @@ -929,6 +923,10 @@ def test_sqlmigrate_forwards(self): "--", ], ) + self.assertEqual( + "db.migrations_tribble.update_many({}, [{'$set': {'bool': False}}])", + lines[pos + 3], + ) pos = lines.index("--", pos + 3) self.assertEqual( lines[pos : pos + 3], @@ -938,6 +936,7 @@ def test_sqlmigrate_forwards(self): "--", ], ) + self.assertIn("db.migrations_author.create_indexes([", lines[pos + 3]) @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) def test_sqlmigrate_backwards(self): @@ -958,6 +957,7 @@ def test_sqlmigrate_backwards(self): ) lines = out.getvalue().splitlines() + try: if connection.features.can_rollback_ddl: self.assertEqual(lines[0], connection.ops.start_transaction_sql()) @@ -972,6 +972,11 @@ def test_sqlmigrate_backwards(self): "--", ], ) + self.assertEqual( + "db.migrations_author.drop_index" + "('migrations_author_name_slug_0ef2ba54_uniq')", + lines[3], + ) pos = lines.index("--", 3) self.assertEqual( lines[pos : pos + 3], @@ -981,6 +986,10 @@ def test_sqlmigrate_backwards(self): "--", ], ) + self.assertEqual( + "db.migrations_tribble.update_many({}, {'$unset': {'bool': ''}})", + lines[pos + 3], + ) pos = lines.index("--", pos + 3) self.assertEqual( lines[pos : pos + 3], @@ -991,10 +1000,7 @@ def test_sqlmigrate_backwards(self): ], ) next_pos = lines.index("--", pos + 3) - drop_table_sql = ( - "drop table %s" - % connection.ops.quote_name("migrations_tribble").lower() - ) + drop_table_sql = "db.migrations_tribble.drop()" for line in lines[pos + 3 : next_pos]: if drop_table_sql in line.lower(): break @@ -1009,9 +1015,7 @@ def test_sqlmigrate_backwards(self): "--", ], ) - drop_table_sql = ( - "drop table %s" % connection.ops.quote_name("migrations_author").lower() - ) + drop_table_sql = "db.migrations_author.drop()" for line in lines[pos + 3 :]: if drop_table_sql in line.lower(): break diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index 571cb3e1a2..8da69fcd1d 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -466,16 +466,16 @@ def test_detect_soft_applied_add_field_manytomanyfield(self): # Leave the tables for 0001 except the many-to-many table. That missing # table should cause detect_soft_applied() to return False. - with connection.schema_editor() as editor: - for table in tables[2:]: - editor.execute(editor.sql_delete_table % {"table": table}) + for table in tables[2:]: + connection.database[table].drop() + # editor.execute(editor.sql_delete_table % {"table": table}) migration = executor.loader.get_migration("migrations", "0001_initial") self.assertIs(executor.detect_soft_applied(None, migration)[0], False) # Cleanup by removing the remaining tables. - with connection.schema_editor() as editor: - for table in tables[:2]: - editor.execute(editor.sql_delete_table % {"table": table}) + for table in tables[:2]: + connection.database[table].drop() + # editor.execute(editor.sql_delete_table % {"table": table}) for table in tables: self.assertTableNotExists(table) @@ -689,11 +689,13 @@ def test_alter_id_type_with_fk(self): # Rebuild the graph to reflect the new DB state executor.loader.build_graph() finally: + connection.database["book_app_book"].drop() + connection.database["author_app_author"].drop() # We can't simply unapply the migrations here because there is no # implicit cast from VARCHAR to INT on the database level. - with connection.schema_editor() as editor: - editor.execute(editor.sql_delete_table % {"table": "book_app_book"}) - editor.execute(editor.sql_delete_table % {"table": "author_app_author"}) + # with connection.schema_editor() as editor: + # editor.execute(editor.sql_delete_table % {"table": "book_app_book"}) + # editor.execute(editor.sql_delete_table % {"table": "author_app_author"}) self.assertTableNotExists("author_app_author") self.assertTableNotExists("book_app_book") executor.migrate([("author_app", None)], fake=True) diff --git a/tests/migrations/test_migrations_no_changes/0001_initial.py b/tests/migrations/test_migrations_no_changes/0001_initial.py index 42aadab7a0..9d8b13ebaf 100644 --- a/tests/migrations/test_migrations_no_changes/0001_initial.py +++ b/tests/migrations/test_migrations_no_changes/0001_initial.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import migrations, models @@ -6,7 +8,7 @@ class Migration(migrations.Migration): migrations.CreateModel( "Author", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("name", models.CharField(max_length=255)), ("slug", models.SlugField(null=True)), ("age", models.IntegerField(default=0)), @@ -16,7 +18,7 @@ class Migration(migrations.Migration): migrations.CreateModel( "Tribble", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ("fluffy", models.BooleanField(default=True)), ], ), diff --git a/tests/migrations/test_migrations_no_changes/0002_second.py b/tests/migrations/test_migrations_no_changes/0002_second.py index 059b7ba2e7..60baa50986 100644 --- a/tests/migrations/test_migrations_no_changes/0002_second.py +++ b/tests/migrations/test_migrations_no_changes/0002_second.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import migrations, models @@ -13,7 +15,7 @@ class Migration(migrations.Migration): migrations.CreateModel( "Book", [ - ("id", models.AutoField(primary_key=True)), + ("id", ObjectIdAutoField(primary_key=True)), ( "author", models.ForeignKey("migrations.Author", models.SET_NULL, null=True), diff --git a/tests/migrations/test_migrations_no_changes/0003_third.py b/tests/migrations/test_migrations_no_changes/0003_third.py index e810902a40..0ea7b162e1 100644 --- a/tests/migrations/test_migrations_no_changes/0003_third.py +++ b/tests/migrations/test_migrations_no_changes/0003_third.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import migrations, models @@ -12,7 +14,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, @@ -28,7 +30,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, diff --git a/tests/migrations/test_migrations_no_default/0001_initial.py b/tests/migrations/test_migrations_no_default/0001_initial.py index 5be2a9268e..043fe2c282 100644 --- a/tests/migrations/test_migrations_no_default/0001_initial.py +++ b/tests/migrations/test_migrations_no_default/0001_initial.py @@ -1,3 +1,5 @@ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import migrations, models @@ -10,7 +12,7 @@ class Migration(migrations.Migration): fields=[ ( "id", - models.AutoField( + ObjectIdAutoField( verbose_name="ID", serialize=False, auto_created=True, diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index e92b1c4506..189b96b9a0 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1,6 +1,9 @@ import math from decimal import Decimal +from bson import Int64 +from django_mongodb_backend.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 +243,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")), ], ) @@ -551,9 +554,9 @@ def test_create_model_with_partial_unique_constraint(self): self.assertTableExists("test_crmo_pony") # Test constraint works Pony = new_state.apps.get_model("test_crmo", "Pony") - Pony.objects.create(pink=1, weight=4.0) - Pony.objects.create(pink=1, weight=4.0) - Pony.objects.create(pink=1, weight=6.0) + Pony.objects.create(pink=Int64(1), weight=4.0) + Pony.objects.create(pink=Int64(1), weight=4.0) + Pony.objects.create(pink=Int64(1), weight=6.0) if connection.features.supports_partial_indexes: with self.assertRaises(IntegrityError): Pony.objects.create(pink=1, weight=7.0) @@ -1019,7 +1022,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")), ], ), @@ -1045,13 +1048,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")), ], ), @@ -1091,7 +1094,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)), ], ), ], @@ -1103,7 +1106,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")), ], ), @@ -1157,13 +1160,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"}, @@ -1190,13 +1193,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")), ], ), @@ -1235,19 +1238,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( @@ -1307,14 +1310,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( @@ -1326,7 +1329,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")), ], ), @@ -1356,7 +1359,7 @@ def test_rename_m2m_field_with_2_references(self): fields=[ ( "id", - models.BigAutoField( + ObjectIdAutoField( auto_created=True, primary_key=True, serialize=False, @@ -1371,7 +1374,7 @@ def test_rename_m2m_field_with_2_references(self): fields=[ ( "id", - models.BigAutoField( + ObjectIdAutoField( auto_created=True, primary_key=True, serialize=False, @@ -2587,7 +2590,7 @@ def test_alter_field_pk(self): project_state = self.set_up_test_model("test_alflpk") # Test the state alteration operation = migrations.AlterField( - "Pony", "id", models.IntegerField(primary_key=True) + "Pony", "id", models.IntegerField(primary_key=True, db_column="_id") ) new_state = project_state.clone() operation.state_forwards("test_alflpk", new_state) @@ -2801,7 +2804,7 @@ def test_alter_field_pk_mti_fk(self): operation = migrations.AlterField( "Pony", "id", - models.BigAutoField(primary_key=True), + models.BigAutoField(primary_key=True, db_column="_id"), ) new_state = project_state.clone() operation.state_forwards(app_label, new_state) @@ -2811,24 +2814,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. @@ -2872,7 +2877,7 @@ def test_alter_field_pk_mti_and_fk_to_base(self): operation = migrations.AlterField( "Pony", "id", - models.BigAutoField(primary_key=True), + models.BigAutoField(primary_key=True, db_column="_id"), ) new_state = project_state.clone() operation.state_forwards(app_label, new_state) @@ -2882,24 +2887,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. @@ -3594,6 +3601,8 @@ def test_alter_unique_together(self): """ Tests the AlterUniqueTogether operation. """ + from pymongo.errors import DuplicateKeyError + project_state = self.set_up_test_model("test_alunto") # Test the state alteration operation = migrations.AlterUniqueTogether("Pony", [("pink", "weight")]) @@ -3627,30 +3636,38 @@ def test_alter_unique_together(self): 1, ) # Make sure we can insert duplicate rows - with connection.cursor() as cursor: - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("DELETE FROM test_alunto_pony") - # Test the database alteration - with connection.schema_editor() as editor: - operation.database_forwards( - "test_alunto", editor, project_state, new_state - ) - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - with self.assertRaises(IntegrityError): - with atomic(): - cursor.execute( - "INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)" - ) - cursor.execute("DELETE FROM test_alunto_pony") - # And test reversal - with connection.schema_editor() as editor: - operation.database_backwards( - "test_alunto", editor, new_state, project_state - ) - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") - cursor.execute("DELETE FROM test_alunto_pony") + # with connection.cursor() as cursor: + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("DELETE FROM test_alunto_pony") + pony = connection.database["test_alunto_pony"] + pony.insert_one({"pink": Int64(1), "weight": 1.0}) + pony.insert_one({"pink": Int64(1), "weight": 1.0}) + pony.delete_many({}) + # Test the database alteration + with connection.schema_editor() as editor: + operation.database_forwards("test_alunto", editor, project_state, new_state) + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + pony.insert_one({"pink": Int64(1), "weight": 1.0}) + with self.assertRaises(DuplicateKeyError): + pony.insert_one({"pink": Int64(1), "weight": 1.0}) + # with atomic(): + # cursor.execute( + # "INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)" + # ) + # cursor.execute("DELETE FROM test_alunto_pony") + pony.delete_many({}) + # And test reversal + with connection.schema_editor() as editor: + operation.database_backwards( + "test_alunto", editor, new_state, project_state + ) + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("INSERT INTO test_alunto_pony (pink, weight) VALUES (1, 1)") + # cursor.execute("DELETE FROM test_alunto_pony") + pony.insert_one({"pink": Int64(1), "weight": 1.0}) + pony.insert_one({"pink": Int64(1), "weight": 1.0}) + pony.delete_many({}) # Test flat unique_together operation = migrations.AlterUniqueTogether("Pony", ("pink", "weight")) operation.state_forwards("test_alunto", new_state) @@ -3862,19 +3879,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") @@ -5906,7 +5917,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={}, @@ -5914,7 +5925,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 935267c2d6..e541914a7c 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -254,15 +254,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): """ @@ -342,6 +344,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): """ @@ -351,14 +359,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): @@ -588,7 +595,7 @@ class Meta: editor.create_model(BookWeak) self.assertForeignKeyNotExists(BookWeak, "author_id", "schema_author") old_field = Author._meta.get_field("id") - new_field = BigAutoField(primary_key=True) + new_field = BigAutoField(primary_key=True, db_column="_id") new_field.model = Author new_field.set_attributes_from_name("id") # @isolate_apps() and inner models are needed to have the model @@ -644,36 +651,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): """ @@ -694,8 +706,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") @@ -704,15 +716,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): """ @@ -723,8 +742,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") @@ -733,12 +752,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): """ @@ -767,26 +793,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): """ @@ -795,28 +836,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): @@ -989,10 +1046,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( @@ -1007,13 +1067,46 @@ def test_remove_field(self): def test_remove_indexed_field(self): with connection.schema_editor() as editor: editor.create_model(AuthorCharFieldWithIndex) + field = AuthorCharFieldWithIndex._meta.get_field("char_field") + column = field.column + self.assertEqual( + self.get_constraints_count( + AuthorCharFieldWithIndex._meta.db_table, column, "" + ), + {"fks": 0, "indexes": 1, "uniques": 0}, + ) + a = AuthorCharFieldWithIndex.objects.create(char_field="foo") with connection.schema_editor() as editor: - editor.remove_field( - AuthorCharFieldWithIndex, - AuthorCharFieldWithIndex._meta.get_field("char_field"), - ) - columns = self.column_classes(AuthorCharFieldWithIndex) - self.assertNotIn("char_field", columns) + editor.remove_field(AuthorCharFieldWithIndex, field) + a.refresh_from_db() + self.assertIsNone(a.char_field) + self.assertEqual( + self.get_constraints_count( + AuthorCharFieldWithIndex._meta.db_table, column, "" + ), + {"fks": 0, "indexes": 0, "uniques": 0}, + ) + # columns = self.column_classes(AuthorCharFieldWithIndex) + # self.assertNotIn("char_field", columns) + + def test_remove_unique_field(self): + with connection.schema_editor() as editor: + editor.create_model(AuthorWithUniqueName) + field = AuthorWithUniqueName._meta.get_field("name") + column = field.column + self.assertEqual( + self.get_constraints_count(AuthorWithUniqueName._meta.db_table, column, ""), + {"fks": 0, "indexes": 0, "uniques": 1}, + ) + a = AuthorWithUniqueName.objects.create(name="foo") + with connection.schema_editor() as editor: + editor.remove_field(AuthorWithUniqueName, field) + a.refresh_from_db() + self.assertIsNone(a.name) + self.assertEqual( + self.get_constraints_count(AuthorWithUniqueName._meta.db_table, column, ""), + {"fks": 0, "indexes": 0, "uniques": 0}, + ) def test_alter(self): """ @@ -1023,52 +1116,61 @@ def test_alter(self): with connection.schema_editor() as editor: editor.create_model(Author) # Ensure the field is right to begin with - columns = self.column_classes(Author) - self.assertEqual( - columns["name"][0], - connection.features.introspected_field_types["CharField"], - ) - self.assertEqual( - bool(columns["name"][1][6]), - bool(connection.features.interprets_empty_strings_as_nulls), - ) + # columns = self.column_classes(Author) + # self.assertEqual( + # columns["name"][0], + # connection.features.introspected_field_types["CharField"], + # ) + # self.assertEqual( + # bool(columns["name"][1][6]), + # bool(connection.features.interprets_empty_strings_as_nulls), + # ) # Alter the name field to a TextField old_field = Author._meta.get_field("name") new_field = TextField(null=True) new_field.set_attributes_from_name("name") with connection.schema_editor() as editor: editor.alter_field(Author, old_field, new_field, strict=True) - columns = self.column_classes(Author) - self.assertEqual(columns["name"][0], "TextField") - self.assertTrue(columns["name"][1][6]) + # columns = self.column_classes(Author) + # self.assertEqual(columns["name"][0], "TextField") + # self.assertTrue(columns["name"][1][6]) # Change nullability again new_field2 = TextField(null=False) new_field2.set_attributes_from_name("name") with connection.schema_editor() as editor: editor.alter_field(Author, new_field, new_field2, strict=True) - columns = self.column_classes(Author) - self.assertEqual(columns["name"][0], "TextField") - self.assertEqual( - bool(columns["name"][1][6]), - bool(connection.features.interprets_empty_strings_as_nulls), - ) + # columns = self.column_classes(Author) + # self.assertEqual(columns["name"][0], "TextField") + # self.assertEqual( + # bool(columns["name"][1][6]), + # bool(connection.features.interprets_empty_strings_as_nulls), + # ) + @isolate_apps("schema") def test_alter_auto_field_to_integer_field(self): # Create the table with connection.schema_editor() as editor: editor.create_model(Author) # Change AutoField to IntegerField old_field = Author._meta.get_field("id") - new_field = IntegerField(primary_key=True) + new_field = IntegerField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: editor.alter_field(Author, old_field, new_field, strict=True) + # Now that ID is an IntegerField, the database raises an error if it # isn't provided. + class NewAuthor(Model): + id = new_field + + class Meta: + app_label = "schema" + db_table = "schema_author" + if not connection.features.supports_unspecified_pk: with self.assertRaises(DatabaseError): - Author.objects.create() + NewAuthor.objects.create() def test_alter_auto_field_to_char_field(self): # Create the table @@ -1076,7 +1178,7 @@ def test_alter_auto_field_to_char_field(self): editor.create_model(Author) # Change AutoField to CharField old_field = Author._meta.get_field("id") - new_field = CharField(primary_key=True, max_length=50) + new_field = CharField(primary_key=True, max_length=50, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -1133,7 +1235,7 @@ class Meta: editor.create_model(Foo) self.isolated_local_models = [Foo] old_field = Foo._meta.get_field("id") - new_field = BigAutoField(primary_key=True) + new_field = BigAutoField(primary_key=True, db_column="_id") new_field.model = Foo new_field.set_attributes_from_name("id") with connection.schema_editor() as editor: @@ -1226,8 +1328,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): """ @@ -1242,8 +1344,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): """ @@ -1258,8 +1360,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): @@ -1323,8 +1425,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") @@ -1337,8 +1439,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) @@ -1668,8 +1770,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) @@ -1678,8 +1780,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): @@ -1882,7 +1984,7 @@ def test_autofield_to_o2o(self): # Rename the field. old_field = Author._meta.get_field("id") - new_field = AutoField(primary_key=True) + new_field = AutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("note_ptr") new_field.model = Author @@ -1895,11 +1997,11 @@ def test_autofield_to_o2o(self): with connection.schema_editor() as editor: editor.alter_field(Author, new_field, new_field_o2o, strict=True) - columns = self.column_classes(Author) - field_type, _ = columns["note_ptr_id"] - self.assertEqual( - field_type, connection.features.introspected_field_types["IntegerField"] - ) + # columns = self.column_classes(Author) + # field_type, _ = columns["note_ptr_id"] + # self.assertEqual( + # field_type, connection.features.introspected_field_types["IntegerField"] + # ) def test_alter_field_fk_keeps_index(self): with connection.schema_editor() as editor: @@ -2029,7 +2131,7 @@ def test_alter_implicit_id_to_explicit(self): editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = AutoField(primary_key=True) + new_field = AutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -2043,7 +2145,7 @@ def test_alter_autofield_pk_to_bigautofield_pk(self): with connection.schema_editor() as editor: editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = BigAutoField(primary_key=True) + new_field = BigAutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -2062,7 +2164,7 @@ def test_alter_autofield_pk_to_smallautofield_pk(self): with connection.schema_editor() as editor: editor.create_model(Author) old_field = Author._meta.get_field("id") - new_field = SmallAutoField(primary_key=True) + new_field = SmallAutoField(primary_key=True, db_column="_id") new_field.set_attributes_from_name("id") new_field.model = Author with connection.schema_editor() as editor: @@ -2279,6 +2381,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 @@ -2287,24 +2390,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): @@ -2344,9 +2457,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) @@ -2383,14 +2496,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): @@ -2402,8 +2522,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): @@ -2496,14 +2616,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) @@ -2544,15 +2667,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) @@ -2620,35 +2744,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: @@ -2658,8 +2781,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. @@ -2710,7 +2834,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( @@ -2722,7 +2847,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) @@ -2756,6 +2882,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, @@ -2769,10 +2898,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 @@ -2812,7 +2944,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) @@ -2823,7 +2956,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" @@ -3103,11 +3237,11 @@ class Meta: new_field = SlugField(max_length=75, unique=True) new_field.model = Tag new_field.set_attributes_from_name("slug") - with self.assertLogs("django.db.backends.schema", "DEBUG") as cm: - with connection.schema_editor() as editor: - editor.alter_field(Tag, Tag._meta.get_field("slug"), new_field) + # with self.assertLogs("django.db.backends.schema", "DEBUG") as cm: + with connection.schema_editor() as editor: + editor.alter_field(Tag, Tag._meta.get_field("slug"), new_field) # One SQL statement is executed to alter the field. - self.assertEqual(len(cm.records), 1) + # self.assertEqual(len(cm.records), 1) # Ensure that the field is still unique. Tag.objects.create(title="foo", slug="foo") with self.assertRaises(IntegrityError): @@ -3118,7 +3252,7 @@ def test_remove_ignored_unique_constraint_not_create_fk_index(self): editor.create_model(Author) editor.create_model(Book) constraint = UniqueConstraint( - "author", + fields=["author"], condition=Q(title__in=["tHGttG", "tRatEotU"]), name="book_author_condition_uniq", ) @@ -3422,10 +3556,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) @@ -3827,33 +3961,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): """ @@ -3987,6 +4126,33 @@ def test_indexes(self): self.get_uniques(Book._meta.db_table), ) + def test_alter_renames_index(self): + # Create the table + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(Book) + # Ensure the table is there and has the right index + self.assertIn( + "title", + self.get_indexes(Book._meta.db_table), + ) + # Alter to rename the field + old_field = Book._meta.get_field("title") + new_field = CharField(max_length=100, db_index=True) + new_field.set_attributes_from_name("new_title") + with connection.schema_editor() as editor: + editor.alter_field(Book, old_field, new_field, strict=True) + # Ensure the old index isn't there. + self.assertNotIn( + "title", + self.get_indexes(Book._meta.db_table), + ) + # Ensure the new index is there. + self.assertIn( + "new_title", + self.get_indexes(Book._meta.db_table), + ) + def test_text_field_with_db_index(self): with connection.schema_editor() as editor: editor.create_model(AuthorTextFieldWithIndex) @@ -4503,6 +4669,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): """ @@ -4519,13 +4686,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): """ @@ -4617,8 +4783,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 @@ -4626,22 +4792,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 @@ -4649,75 +4825,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 @@ -4734,16 +4933,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): """ @@ -4816,23 +5015,20 @@ def test_add_textfield_unhashable_default(self): with connection.schema_editor() as editor: editor.add_field(Author, new_field) - @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific") def test_add_indexed_charfield(self): field = CharField(max_length=255, db_index=True) field.set_attributes_from_name("nom_de_plume") with connection.schema_editor() as editor: editor.create_model(Author) editor.add_field(Author, field) - # Should create two indexes; one for like operator. + # Should create one (or two) index(es). + expected_indexes = ["schema_author_nom_de_plume_7570a851"] + if connection.vendor == "postgresql": + expected_indexes.append("schema_author_nom_de_plume_7570a851_like") self.assertEqual( - self.get_constraints_for_column(Author, "nom_de_plume"), - [ - "schema_author_nom_de_plume_7570a851", - "schema_author_nom_de_plume_7570a851_like", - ], + self.get_constraints_for_column(Author, "nom_de_plume"), expected_indexes ) - @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific") def test_add_unique_charfield(self): field = CharField(max_length=255, unique=True) field.set_attributes_from_name("nom_de_plume") @@ -4840,12 +5036,11 @@ def test_add_unique_charfield(self): editor.create_model(Author) editor.add_field(Author, field) # Should create two indexes; one for like operator. + expected_indexes = ["schema_author_nom_de_plume_7570a851_uniq"] + if connection.vendor == "postgresql": + expected_indexes.append("schema_author_nom_de_plume_7570a851_like") self.assertEqual( - self.get_constraints_for_column(Author, "nom_de_plume"), - [ - "schema_author_nom_de_plume_7570a851_like", - "schema_author_nom_de_plume_key", - ], + self.get_constraints_for_column(Author, "nom_de_plume"), expected_indexes ) @skipUnlessDBFeature("supports_comments") @@ -5008,7 +5203,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): @@ -5323,13 +5518,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 088579ac4eb47b3ddb95df89d36fbfd6ae0d32ac Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 29 Aug 2024 12:21:33 -0400 Subject: [PATCH 15/35] backends edits --- tests/backends/base/test_base.py | 2 ++ tests/backends/tests.py | 10 ++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/backends/base/test_base.py b/tests/backends/base/test_base.py index 4418d010ea..05577aaa6a 100644 --- a/tests/backends/base/test_base.py +++ b/tests/backends/base/test_base.py @@ -393,6 +393,8 @@ def test_multi_database_init_connection_state_called_once(self): connections[db], "check_database_version_supported", ) as mocked_check_database_version_supported: + if connections[db].connection is None: + connections[db].connection.connect() connections[db].init_connection_state() after_first_calls = len( mocked_check_database_version_supported.mock_calls diff --git a/tests/backends/tests.py b/tests/backends/tests.py index b955e8aad4..ab703c3e69 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -85,7 +85,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): @@ -268,14 +268,12 @@ def receiver(sender, connection, **kwargs): connection_created.connect(receiver) connection.close() - with connection.cursor(): - pass + connection.connect() self.assertIs(data["connection"].connection, connection.connection) - + connection.close() connection_created.disconnect(receiver) data.clear() - with connection.cursor(): - pass + connection.connect() self.assertEqual(data, {}) From 3f63a6b5afe4ee3f7918dfe188c2d83a1a89c6a4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 Aug 2024 21:06:56 -0400 Subject: [PATCH 16/35] 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 633747b4b81260d1e974916b82ab3c37049b9179 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Sep 2024 15:22:42 -0400 Subject: [PATCH 17/35] remove SQL introspection from queries tests --- tests/queries/test_qs_combinators.py | 16 ++++++++-------- tests/queries/tests.py | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index e329d0c4f0..f16ac4a500 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -603,14 +603,14 @@ def test_exists_union(self): self.assertIs(qs1.union(qs2).exists(), True) captured_queries = context.captured_queries self.assertEqual(len(captured_queries), 1) - captured_sql = captured_queries[0]["sql"] - self.assertNotIn( - connection.ops.quote_name(Number._meta.pk.column), - captured_sql, - ) - self.assertEqual( - captured_sql.count(connection.ops.limit_offset_sql(None, 1)), 1 - ) + # captured_sql = captured_queries[0]["sql"] + # self.assertNotIn( + # connection.ops.quote_name(Number._meta.pk.column), + # captured_sql, + # ) + # self.assertEqual( + # captured_sql.count(connection.ops.limit_offset_sql(None, 1)), 1 + # ) def test_exists_union_empty_result(self): qs = Number.objects.filter(pk__in=[]) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index d04809f21f..e1a883e540 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2287,9 +2287,9 @@ def test_distinct_exists(self): with CaptureQueriesContext(connection) as captured_queries: self.assertIs(Article.objects.distinct().exists(), False) self.assertEqual(len(captured_queries), 1) - captured_sql = captured_queries[0]["sql"] - self.assertNotIn(connection.ops.quote_name("id"), captured_sql) - self.assertNotIn(connection.ops.quote_name("name"), captured_sql) + # captured_sql = captured_queries[0]["sql"] + # self.assertNotIn(connection.ops.quote_name("id"), captured_sql) + # self.assertNotIn(connection.ops.quote_name("name"), captured_sql) def test_sliced_distinct_exists(self): with CaptureQueriesContext(connection) as captured_queries: @@ -3292,16 +3292,16 @@ def employ(employer, employee, title): .distinct() .order_by("name") ) - with self.assertNumQueries(1) as ctx: + with self.assertNumQueries(1): self.assertSequenceEqual(alex_nontech_employers, [google, intel, microsoft]) - sql = ctx.captured_queries[0]["sql"] + # sql = ctx.captured_queries[0]["sql"] # Company's ID should appear in SELECT and INNER JOIN, not in EXISTS as # the outer query reference is not necessary when an alias is reused. - company_id = "%s.%s" % ( - connection.ops.quote_name(Company._meta.db_table), - connection.ops.quote_name(Company._meta.get_field("id").column), - ) - self.assertEqual(sql.count(company_id), 2) + # company_id = "%s.%s" % ( + # connection.ops.quote_name(Company._meta.db_table), + # connection.ops.quote_name(Company._meta.get_field("id").column), + # ) + # self.assertEqual(sql.count(company_id), 2) def test_exclude_reverse_fk_field_ref(self): tag = Tag.objects.create() From afabcfd35c1eee120a85a4262422dd365fea8cb2 Mon Sep 17 00:00:00 2001 From: Emanuel Lupi Date: Sat, 7 Sep 2024 12:57:59 -0300 Subject: [PATCH 18/35] Added QuerySet.union() test with renames. --- tests/queries/test_qs_combinators.py | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index f16ac4a500..41aae117cd 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -24,6 +24,7 @@ ExtraInfo, Note, Number, + Report, ReservedName, Tag, ) @@ -157,6 +158,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() @@ -596,6 +622,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 678c3ca57c9286257be75c0ddf4b37bc024a5942 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 25 Sep 2024 20:08:15 -0400 Subject: [PATCH 19/35] edits for many test apps --- tests/admin_changelist/tests.py | 18 ++-- tests/admin_filters/tests.py | 8 +- tests/admin_inlines/tests.py | 12 +-- tests/admin_utils/test_logentry.py | 2 +- tests/admin_views/admin.py | 2 +- tests/admin_views/tests.py | 22 ++--- tests/async/test_async_queryset.py | 5 +- tests/auth_tests/test_context_processors.py | 2 +- tests/auth_tests/test_management.py | 8 +- tests/contenttypes_tests/test_fields.py | 2 +- tests/contenttypes_tests/urls.py | 2 +- tests/custom_columns/models.py | 6 +- tests/file_uploads/tests.py | 4 +- tests/file_uploads/views.py | 2 +- tests/fixtures/tests.py | 8 +- .../fixtures/model_package_fixture1.json | 4 +- .../fixtures/model_package_fixture2.json | 4 +- tests/fixtures_regress/tests.py | 5 +- tests/forms_tests/models.py | 2 +- tests/generic_relations_regress/tests.py | 11 +-- tests/generic_views/test_dates.py | 8 +- tests/generic_views/test_edit.py | 56 ++++++------- tests/generic_views/urls.py | 82 ++++++++++++------- tests/get_or_create/tests.py | 4 +- tests/messages_tests/urls.py | 2 +- tests/model_formsets/tests.py | 45 +++++----- tests/model_indexes/tests.py | 3 +- tests/model_inheritance/models.py | 4 +- .../test_abstract_inheritance.py | 24 ++++-- tests/model_inheritance/tests.py | 2 +- tests/modeladmin/tests.py | 6 +- tests/multiple_database/tests.py | 18 ++-- tests/prefetch_related/tests.py | 4 +- tests/proxy_models/tests.py | 10 +-- tests/serializers/test_json.py | 8 +- tests/serializers/test_jsonl.py | 6 +- tests/serializers/tests.py | 5 +- tests/servers/test_liveserverthread.py | 1 + tests/servers/tests.py | 1 + tests/sitemaps_tests/urls/http.py | 2 +- tests/syndication_tests/urls.py | 2 +- tests/test_utils/test_testcase.py | 2 +- tests/test_utils/test_transactiontestcase.py | 2 +- tests/test_utils/tests.py | 9 +- tests/test_utils/urls.py | 2 +- 45 files changed, 248 insertions(+), 189 deletions(-) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 6003ce47d8..16ebc38a1c 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -206,7 +206,7 @@ def test_many_search_terms(self): with CaptureQueriesContext(connection) as context: object_count = cl.queryset.count() self.assertEqual(object_count, 1) - self.assertEqual(context.captured_queries[0]["sql"].count("JOIN"), 1) + self.assertEqual(context.captured_queries[0]["sql"].count("$lookup"), 1) def test_related_field_multiple_search_terms(self): """ @@ -421,7 +421,7 @@ def test_result_list_editable_html(self): # make sure that hidden fields are in the correct place hiddenfields_div = ( '
    ' - '' + '' "
    " ) % new_child.id self.assertInHTML( @@ -781,7 +781,9 @@ def test_pk_in_search_fields(self): cl = m.get_changelist_instance(request) self.assertEqual(cl.queryset.count(), 1) - request = self.factory.get("/concert/", data={SEARCH_VAR: band.pk + 5}) + request = self.factory.get( + "/concert/", data={SEARCH_VAR: "6722e37ac32eaa8ecf4eec61"} + ) request.user = self.superuser cl = m.get_changelist_instance(request) self.assertEqual(cl.queryset.count(), 0) @@ -1330,10 +1332,12 @@ def test_changelist_view_list_editable_changed_objects_uses_filter(self): with CaptureQueriesContext(connection) as context: response = self.client.post(changelist_url, data=data) self.assertEqual(response.status_code, 200) - self.assertIn("WHERE", context.captured_queries[4]["sql"]) - self.assertIn("IN", context.captured_queries[4]["sql"]) - # Check only the first few characters since the UUID may have dashes. - self.assertIn(str(a.pk)[:8], context.captured_queries[4]["sql"]) + # Check only the first few characters of the pk since the UUID has + # dashes. + self.assertIn( + "{'$match': {'$expr': {'$in': ['$uuid', ('%s" % str(a.pk)[:8], + context.captured_queries[4]["sql"], + ) def test_list_editable_error_title(self): a = Swallow.objects.create(origin="Swallow A", load=4, speed=1) diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index 558164f75c..ea3fe6744f 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -700,7 +700,7 @@ def test_relatedfieldlistfilter_foreignkey(self): choice = select_by(filterspec.choices(changelist), "display", "alfred") self.assertIs(choice["selected"], True) self.assertEqual( - choice["query_string"], "?author__id__exact=%d" % self.alfred.pk + choice["query_string"], "?author__id__exact=%s" % self.alfred.pk ) def test_relatedfieldlistfilter_foreignkey_ordering(self): @@ -803,7 +803,7 @@ def test_relatedfieldlistfilter_manytomany(self): choice = select_by(filterspec.choices(changelist), "display", "bob") self.assertIs(choice["selected"], True) self.assertEqual( - choice["query_string"], "?contributors__id__exact=%d" % self.bob.pk + choice["query_string"], "?contributors__id__exact=%s" % self.bob.pk ) def test_relatedfieldlistfilter_reverse_relationships(self): @@ -839,7 +839,7 @@ def test_relatedfieldlistfilter_reverse_relationships(self): ) self.assertIs(choice["selected"], True) self.assertEqual( - choice["query_string"], "?books_authored__id__exact=%d" % self.bio_book.pk + choice["query_string"], "?books_authored__id__exact=%s" % self.bio_book.pk ) # M2M relationship ----- @@ -873,7 +873,7 @@ def test_relatedfieldlistfilter_reverse_relationships(self): self.assertIs(choice["selected"], True) self.assertEqual( choice["query_string"], - "?books_contributed__id__exact=%d" % self.django_book.pk, + "?books_contributed__id__exact=%s" % self.django_book.pk, ) # With one book, the list filter should appear because there is also a diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 89f43300d7..f8056ec6a2 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1193,7 +1193,7 @@ def test_inline_change_m2m_change_perm(self): ) self.assertContains( response, - '' % self.author_book_auto_m2m_intermediate_id, html=True, ) @@ -1221,7 +1221,7 @@ def test_inline_change_fk_add_perm(self): ) self.assertNotContains( response, - '' % self.inner2.id, html=True, ) @@ -1252,7 +1252,7 @@ def test_inline_change_fk_change_perm(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) @@ -1299,7 +1299,7 @@ def test_inline_change_fk_add_change_perm(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) @@ -1329,7 +1329,7 @@ def test_inline_change_fk_change_del_perm(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) @@ -1369,7 +1369,7 @@ def test_inline_change_fk_all_perms(self): ) self.assertContains( response, - '' % self.inner2.id, html=True, ) diff --git a/tests/admin_utils/test_logentry.py b/tests/admin_utils/test_logentry.py index 43b6cf5573..890c6797df 100644 --- a/tests/admin_utils/test_logentry.py +++ b/tests/admin_utils/test_logentry.py @@ -224,7 +224,7 @@ def test_logentry_get_admin_url(self): "admin:admin_utils_article_change", args=(quote(self.a1.pk),) ) self.assertEqual(logentry.get_admin_url(), expected_url) - self.assertIn("article/%d/change/" % self.a1.pk, logentry.get_admin_url()) + self.assertIn("article/%s/change/" % self.a1.pk, logentry.get_admin_url()) logentry.content_type.model = "nonexistent" self.assertIsNone(logentry.get_admin_url()) diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 5e14069bae..566ee96a30 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -609,7 +609,7 @@ class PostAdmin(admin.ModelAdmin): @admin.display def coolness(self, instance): if instance.pk: - return "%d amount of cool." % instance.pk + return "%s amount of cool." % instance.pk else: return "Unknown coolness." diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index f0d7b41b64..a2a368492f 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1173,7 +1173,7 @@ def test_disallowed_filtering(self): response = self.client.get(reverse("admin:admin_views_workhour_changelist")) self.assertContains(response, "employee__person_ptr__exact") response = self.client.get( - "%s?employee__person_ptr__exact=%d" + "%s?employee__person_ptr__exact=%s" % (reverse("admin:admin_views_workhour_changelist"), e1.pk) ) self.assertEqual(response.status_code, 200) @@ -4588,13 +4588,13 @@ def test_pk_hidden_fields(self): self.assertContains( response, '
    \n' - '' - '\n' + '' + '\n' "
    " % (story2.id, story1.id), html=True, ) - self.assertContains(response, '%d' % story1.id, 1) - self.assertContains(response, '%d' % story2.id, 1) + self.assertContains(response, '%s' % story1.id, 1) + self.assertContains(response, '%s' % story2.id, 1) def test_pk_hidden_fields_with_list_display_links(self): """Similarly as test_pk_hidden_fields, but when the hidden pk fields are @@ -4618,19 +4618,19 @@ def test_pk_hidden_fields_with_list_display_links(self): self.assertContains( response, '
    \n' - '' - '\n' + '' + '\n' "
    " % (story2.id, story1.id), html=True, ) self.assertContains( response, - '%d' % (link1, story1.id), + '%s' % (link1, story1.id), 1, ) self.assertContains( response, - '%d' % (link2, story2.id), + '%s' % (link2, story2.id), 1, ) @@ -4954,7 +4954,7 @@ def setUpTestData(cls): cls.superuser = User.objects.create_superuser( username="super", password="secret", email="super@example.com" ) - cls.pks = [EmptyModel.objects.create().id for i in range(3)] + cls.pks = [EmptyModel.objects.create(id=i + 1).id for i in range(3)] def setUp(self): self.client.force_login(self.superuser) @@ -6929,7 +6929,7 @@ def test_readonly_get(self): response = self.client.get( reverse("admin:admin_views_post_change", args=(p.pk,)) ) - self.assertContains(response, "%d amount of cool" % p.pk) + self.assertContains(response, "%s amount of cool" % p.pk) @ignore_warnings(category=RemovedInDjango60Warning) def test_readonly_text_field(self): diff --git a/tests/async/test_async_queryset.py b/tests/async/test_async_queryset.py index 374b4576f9..4f3919a865 100644 --- a/tests/async/test_async_queryset.py +++ b/tests/async/test_async_queryset.py @@ -3,6 +3,7 @@ from datetime import datetime from asgiref.sync import async_to_sync, sync_to_async +from bson import ObjectId from django.db import NotSupportedError, connection from django.db.models import Prefetch, Sum @@ -207,9 +208,7 @@ async def test_acontains(self): check = await SimpleModel.objects.acontains(self.s1) self.assertIs(check, True) # Unsaved instances are not allowed, so use an ID known not to exist. - check = await SimpleModel.objects.acontains( - SimpleModel(id=self.s3.id + 1, field=4) - ) + check = await SimpleModel.objects.acontains(SimpleModel(id=ObjectId(), field=4)) self.assertIs(check, False) async def test_aupdate(self): diff --git a/tests/auth_tests/test_context_processors.py b/tests/auth_tests/test_context_processors.py index ab621313e8..defb9c0d96 100644 --- a/tests/auth_tests/test_context_processors.py +++ b/tests/auth_tests/test_context_processors.py @@ -140,7 +140,7 @@ def test_user_attrs(self): user = authenticate(username="super", password="secret") response = self.client.get("/auth_processor_user/") self.assertContains(response, "unicode: super") - self.assertContains(response, "id: %d" % self.superuser.pk) + self.assertContains(response, "id: %s" % self.superuser.pk) self.assertContains(response, "username: super") # bug #12037 is tested by the {% url %} in the template: self.assertContains(response, "url: /userpage/super/") diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 9f12e631cc..8be6e73276 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -615,10 +615,12 @@ def test_validate_fk(self): @override_settings(AUTH_USER_MODEL="auth_tests.CustomUserWithFK") def test_validate_fk_environment_variable(self): + from bson import ObjectId + email = Email.objects.create(email="mymail@gmail.com") Group.objects.all().delete() - nonexistent_group_id = 1 - msg = f"group instance with id {nonexistent_group_id} is not a valid choice." + nonexistent_group_id = ObjectId() + msg = f"group instance with id {nonexistent_group_id!r} is not a valid choice." with mock.patch.dict( os.environ, @@ -1537,5 +1539,5 @@ def test_set_permissions_fk_to_using_parameter(self): Permission.objects.using("other").delete() with self.assertNumQueries(4, using="other") as captured_queries: create_permissions(apps.get_app_config("auth"), verbosity=0, using="other") - self.assertIn("INSERT INTO", captured_queries[-1]["sql"].upper()) + self.assertIn("INSERT_MANY", captured_queries[-1]["sql"].upper()) self.assertGreater(Permission.objects.using("other").count(), 0) diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py index fc49d59b27..19a3ca543f 100644 --- a/tests/contenttypes_tests/test_fields.py +++ b/tests/contenttypes_tests/test_fields.py @@ -33,7 +33,7 @@ def test_get_object_cache_respects_deleted_objects(self): post = Post.objects.get(pk=post.pk) with self.assertNumQueries(1): - self.assertEqual(post.object_id, question_pk) + self.assertEqual(post.object_id, str(question_pk)) self.assertIsNone(post.parent) self.assertIsNone(post.parent) diff --git a/tests/contenttypes_tests/urls.py b/tests/contenttypes_tests/urls.py index 8f94d8a54c..e76e04223c 100644 --- a/tests/contenttypes_tests/urls.py +++ b/tests/contenttypes_tests/urls.py @@ -2,5 +2,5 @@ from django.urls import re_path urlpatterns = [ - re_path(r"^shortcut/([0-9]+)/(.*)/$", views.shortcut), + re_path(r"^shortcut/([\w]+)/(.*)/$", views.shortcut), ] diff --git a/tests/custom_columns/models.py b/tests/custom_columns/models.py index 378a001820..1a2c99e431 100644 --- a/tests/custom_columns/models.py +++ b/tests/custom_columns/models.py @@ -15,11 +15,13 @@ """ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models class Author(models.Model): - Author_ID = models.AutoField(primary_key=True, db_column="Author ID") + Author_ID = ObjectIdAutoField(primary_key=True, db_column="Author ID") first_name = models.CharField(max_length=30, db_column="firstname") last_name = models.CharField(max_length=30, db_column="last") @@ -32,7 +34,7 @@ def __str__(self): class Article(models.Model): - Article_ID = models.AutoField(primary_key=True, db_column="Article ID") + Article_ID = ObjectIdAutoField(primary_key=True, db_column="Article ID") headline = models.CharField(max_length=100) authors = models.ManyToManyField(Author, db_table="my_m2m_table") primary_author = models.ForeignKey( diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py index c46f5a490b..004e45ab9f 100644 --- a/tests/file_uploads/tests.py +++ b/tests/file_uploads/tests.py @@ -9,6 +9,8 @@ from unittest import mock from urllib.parse import quote +from bson import ObjectId + from django.conf import DEFAULT_STORAGE_ALIAS from django.core.exceptions import SuspiciousFileOperation from django.core.files import temp as tempfile @@ -792,7 +794,7 @@ def test_filename_case_preservation(self): "multipart/form-data; boundary=%(boundary)s" % vars, ) self.assertEqual(response.status_code, 200) - id = int(response.content) + id = ObjectId(response.content.decode()) obj = FileModel.objects.get(pk=id) # The name of the file uploaded and the file stored in the server-side # shouldn't differ. diff --git a/tests/file_uploads/views.py b/tests/file_uploads/views.py index c1d4ca5358..d8186108f6 100644 --- a/tests/file_uploads/views.py +++ b/tests/file_uploads/views.py @@ -156,7 +156,7 @@ def file_upload_filename_case_view(request): file = request.FILES["file_field"] obj = FileModel() obj.testfile.save(file.name, file) - return HttpResponse("%d" % obj.pk) + return HttpResponse("%s" % obj.pk) def file_upload_content_type_extra(request): diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index bce55bc355..dfb1cd05bb 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -587,15 +587,15 @@ def test_dumpdata_with_filtering_manager(self): # Use the default manager self._dumpdata_assert( ["fixtures.Spy"], - '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' + '[{"pk": "%s", "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk, ) # Dump using Django's base manager. Should return all objects, # even those normally filtered by the manager self._dumpdata_assert( ["fixtures.Spy"], - '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, ' - '{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' + '[{"pk": "%s", "model": "fixtures.spy", "fields": {"cover_blown": true}}, ' + '{"pk": "%s", "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True, ) @@ -825,7 +825,7 @@ def test_dumpdata_proxy_with_concrete(self): warnings.simplefilter("always") self._dumpdata_assert( ["fixtures.ProxySpy", "fixtures.Spy"], - '[{"pk": %d, "model": "fixtures.spy", ' + '[{"pk": "%s", "model": "fixtures.spy", ' '"fields": {"cover_blown": false}}]' % spy.pk, ) self.assertEqual(len(warning_list), 0) diff --git a/tests/fixtures_model_package/fixtures/model_package_fixture1.json b/tests/fixtures_model_package/fixtures/model_package_fixture1.json index 60ad807aac..bf58527229 100644 --- a/tests/fixtures_model_package/fixtures/model_package_fixture1.json +++ b/tests/fixtures_model_package/fixtures/model_package_fixture1.json @@ -1,6 +1,6 @@ [ { - "pk": "2", + "pk": "6708500773c47166dfa11512", "model": "fixtures_model_package.article", "fields": { "headline": "Poker has no place on ESPN", @@ -8,7 +8,7 @@ } }, { - "pk": "3", + "pk": "6708500773c47166dfa11513", "model": "fixtures_model_package.article", "fields": { "headline": "Time to reform copyright", diff --git a/tests/fixtures_model_package/fixtures/model_package_fixture2.json b/tests/fixtures_model_package/fixtures/model_package_fixture2.json index a09bc34d62..b63a2262a4 100644 --- a/tests/fixtures_model_package/fixtures/model_package_fixture2.json +++ b/tests/fixtures_model_package/fixtures/model_package_fixture2.json @@ -1,6 +1,6 @@ [ { - "pk": "3", + "pk": "6708500773c47166dfa11513", "model": "fixtures_model_package.article", "fields": { "headline": "Copyright is fine the way it is", @@ -8,7 +8,7 @@ } }, { - "pk": "4", + "pk": "6708500773c47166dfa11514", "model": "fixtures_model_package.article", "fields": { "headline": "Django conquers world!", diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index 4a982c7262..ab46e64023 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -94,6 +94,7 @@ def test_duplicate_pk(self): latin_name="Ornithorhynchus anatinus", count=2, weight=2.2, + pk=2, ) animal.save() self.assertGreater(animal.id, 1) @@ -412,6 +413,7 @@ def test_dumpdata_uses_default_manager(self): latin_name="Ornithorhynchus anatinus", count=2, weight=2.2, + id=50, ) animal.save() @@ -487,7 +489,7 @@ def test_proxy_model_included(self): ) self.assertJSONEqual( out.getvalue(), - '[{"pk": %d, "model": "fixtures_regress.widget", ' + '[{"pk": "%s", "model": "fixtures_regress.widget", ' '"fields": {"name": "grommet"}}]' % widget.pk, ) @@ -504,6 +506,7 @@ def test_loaddata_works_when_fixture_has_forward_refs(self): self.assertEqual(Book.objects.all()[0].id, 1) self.assertEqual(Person.objects.all()[0].id, 4) + @skipUnlessDBFeature("supports_foreign_keys") def test_loaddata_raises_error_when_fixture_has_invalid_foreign_key(self): """ Data with nonexistent child key references raises error. diff --git a/tests/forms_tests/models.py b/tests/forms_tests/models.py index d6d0725b32..b1319abe17 100644 --- a/tests/forms_tests/models.py +++ b/tests/forms_tests/models.py @@ -68,7 +68,7 @@ class Meta: ordering = ("name",) def __str__(self): - return "ChoiceOption %d" % self.pk + return "ChoiceOption %s" % self.pk def choice_default(): diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index c9abdfae72..ef5d45104a 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -249,7 +249,8 @@ def test_annotate(self): HasLinkThing.objects.create() b = Board.objects.create(name=str(hs1.pk)) Link.objects.create(content_object=hs2) - link = Link.objects.create(content_object=hs1) + # An integer PK is required for the Sum() queryset that follows. + link = Link.objects.create(content_object=hs1, pk=10) Link.objects.create(content_object=b) qs = HasLinkThing.objects.annotate(Sum("links")).filter(pk=hs1.pk) # If content_type restriction isn't in the query's join condition, @@ -263,11 +264,11 @@ def test_annotate(self): # clear cached results qs = qs.all() self.assertEqual(qs.count(), 1) - # Note - 0 here would be a nicer result... - self.assertIs(qs[0].links__sum, None) + # Unlike other databases, MongoDB returns 0 instead of null (None). + self.assertIs(qs[0].links__sum, 0) # Finally test that filtering works. - self.assertEqual(qs.filter(links__sum__isnull=True).count(), 1) - self.assertEqual(qs.filter(links__sum__isnull=False).count(), 0) + self.assertEqual(qs.filter(links__sum__isnull=True).count(), 0) + self.assertEqual(qs.filter(links__sum__isnull=False).count(), 1) def test_filter_targets_related_pk(self): # Use hardcoded PKs to ensure different PKs for "link" and "hs2" diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py index fc680f4209..49bda6a610 100644 --- a/tests/generic_views/test_dates.py +++ b/tests/generic_views/test_dates.py @@ -907,7 +907,7 @@ def test_get_object_custom_queryset_numqueries(self): def test_datetime_date_detail(self): bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0)) - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 200) @requires_tz_support @@ -918,7 +918,7 @@ def test_aware_datetime_date_detail(self): 2008, 4, 2, 12, 0, tzinfo=datetime.timezone.utc ) ) - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 200) # 2008-04-02T00:00:00+03:00 (beginning of day) > # 2008-04-01T22:00:00+00:00 (book signing event date). @@ -926,7 +926,7 @@ def test_aware_datetime_date_detail(self): 2008, 4, 1, 22, 0, tzinfo=datetime.timezone.utc ) bs.save() - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 200) # 2008-04-03T00:00:00+03:00 (end of day) > 2008-04-02T22:00:00+00:00 # (book signing event date). @@ -934,5 +934,5 @@ def test_aware_datetime_date_detail(self): 2008, 4, 2, 22, 0, tzinfo=datetime.timezone.utc ) bs.save() - res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk) + res = self.client.get("/dates/booksignings/2008/apr/2/%s/" % bs.pk) self.assertEqual(res.status_code, 404) diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py index 09d887ae92..990478cad4 100644 --- a/tests/generic_views/test_edit.py +++ b/tests/generic_views/test_edit.py @@ -124,7 +124,7 @@ def test_create_with_object_url(self): res = self.client.post("/edit/artists/create/", {"name": "Rene Magritte"}) self.assertEqual(res.status_code, 302) artist = Artist.objects.get(name="Rene Magritte") - self.assertRedirects(res, "/detail/artist/%d/" % artist.pk) + self.assertRedirects(res, "/detail/artist/%s/" % artist.pk) self.assertQuerySetEqual(Artist.objects.all(), [artist]) def test_create_with_redirect(self): @@ -148,7 +148,7 @@ def test_create_with_interpolated_redirect(self): ) self.assertEqual(res.status_code, 302) pk = Author.objects.first().pk - self.assertRedirects(res, "/edit/author/%d/update/" % pk) + self.assertRedirects(res, "/edit/author/%s/update/" % pk) # Also test with escaped chars in URL res = self.client.post( "/edit/authors/create/interpolate_redirect_nonascii/", @@ -245,7 +245,7 @@ def setUpTestData(cls): ) def test_update_post(self): - res = self.client.get("/edit/author/%d/update/" % self.author.pk) + res = self.client.get("/edit/author/%s/update/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertIsInstance(res.context["form"], forms.ModelForm) self.assertEqual(res.context["object"], self.author) @@ -255,7 +255,7 @@ def test_update_post(self): # Modification with both POST and PUT (browser compatible) res = self.client.post( - "/edit/author/%d/update/" % self.author.pk, + "/edit/author/%s/update/" % self.author.pk, {"name": "Randall Munroe (xkcd)", "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 302) @@ -266,7 +266,7 @@ def test_update_post(self): def test_update_invalid(self): res = self.client.post( - "/edit/author/%d/update/" % self.author.pk, + "/edit/author/%s/update/" % self.author.pk, {"name": "A" * 101, "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 200) @@ -278,15 +278,15 @@ def test_update_invalid(self): def test_update_with_object_url(self): a = Artist.objects.create(name="Rene Magritte") res = self.client.post( - "/edit/artists/%d/update/" % a.pk, {"name": "Rene Magritte"} + "/edit/artists/%s/update/" % a.pk, {"name": "Rene Magritte"} ) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, "/detail/artist/%d/" % a.pk) + self.assertRedirects(res, "/detail/artist/%s/" % a.pk) self.assertQuerySetEqual(Artist.objects.all(), [a]) def test_update_with_redirect(self): res = self.client.post( - "/edit/author/%d/update/redirect/" % self.author.pk, + "/edit/author/%s/update/redirect/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 302) @@ -298,7 +298,7 @@ def test_update_with_redirect(self): def test_update_with_interpolated_redirect(self): res = self.client.post( - "/edit/author/%d/update/interpolate_redirect/" % self.author.pk, + "/edit/author/%s/update/interpolate_redirect/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) self.assertQuerySetEqual( @@ -307,10 +307,10 @@ def test_update_with_interpolated_redirect(self): ) self.assertEqual(res.status_code, 302) pk = Author.objects.first().pk - self.assertRedirects(res, "/edit/author/%d/update/" % pk) + self.assertRedirects(res, "/edit/author/%s/update/" % pk) # Also test with escaped chars in URL res = self.client.post( - "/edit/author/%d/update/interpolate_redirect_nonascii/" % self.author.pk, + "/edit/author/%s/update/interpolate_redirect_nonascii/" % self.author.pk, {"name": "John Doe", "slug": "john-doe"}, ) self.assertEqual(res.status_code, 302) @@ -318,7 +318,7 @@ def test_update_with_interpolated_redirect(self): self.assertRedirects(res, "/%C3%A9dit/author/{}/update/".format(pk)) def test_update_with_special_properties(self): - res = self.client.get("/edit/author/%d/update/special/" % self.author.pk) + res = self.client.get("/edit/author/%s/update/special/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertIsInstance(res.context["form"], views.AuthorForm) self.assertEqual(res.context["object"], self.author) @@ -327,11 +327,11 @@ def test_update_with_special_properties(self): self.assertTemplateUsed(res, "generic_views/form.html") res = self.client.post( - "/edit/author/%d/update/special/" % self.author.pk, + "/edit/author/%s/update/special/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, "/detail/author/%d/" % self.author.pk) + self.assertRedirects(res, "/detail/author/%s/" % self.author.pk) self.assertQuerySetEqual( Author.objects.values_list("name", flat=True), ["Randall Munroe (author of xkcd)"], @@ -344,7 +344,7 @@ def test_update_without_redirect(self): ) with self.assertRaisesMessage(ImproperlyConfigured, msg): self.client.post( - "/edit/author/%d/update/naive/" % self.author.pk, + "/edit/author/%s/update/naive/" % self.author.pk, {"name": "Randall Munroe (author of xkcd)", "slug": "randall-munroe"}, ) @@ -379,37 +379,37 @@ def setUpTestData(cls): ) def test_delete_by_post(self): - res = self.client.get("/edit/author/%d/delete/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["author"], self.author) self.assertTemplateUsed(res, "generic_views/author_confirm_delete.html") # Deletion with POST - res = self.client.post("/edit/author/%d/delete/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertQuerySetEqual(Author.objects.all(), []) def test_delete_by_delete(self): # Deletion with browser compatible DELETE method - res = self.client.delete("/edit/author/%d/delete/" % self.author.pk) + res = self.client.delete("/edit/author/%s/delete/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertQuerySetEqual(Author.objects.all(), []) def test_delete_with_redirect(self): - res = self.client.post("/edit/author/%d/delete/redirect/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/redirect/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/edit/authors/create/") self.assertQuerySetEqual(Author.objects.all(), []) def test_delete_with_interpolated_redirect(self): res = self.client.post( - "/edit/author/%d/delete/interpolate_redirect/" % self.author.pk + "/edit/author/%s/delete/interpolate_redirect/" % self.author.pk ) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, "/edit/authors/create/?deleted=%d" % self.author.pk) + self.assertRedirects(res, "/edit/authors/create/?deleted=%s" % self.author.pk) self.assertQuerySetEqual(Author.objects.all(), []) # Also test with escaped chars in URL a = Author.objects.create( @@ -422,14 +422,14 @@ def test_delete_with_interpolated_redirect(self): self.assertRedirects(res, "/%C3%A9dit/authors/create/?deleted={}".format(a.pk)) def test_delete_with_special_properties(self): - res = self.client.get("/edit/author/%d/delete/special/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/special/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["thingy"], self.author) self.assertNotIn("author", res.context) self.assertTemplateUsed(res, "generic_views/confirm_delete.html") - res = self.client.post("/edit/author/%d/delete/special/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/special/" % self.author.pk) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertQuerySetEqual(Author.objects.all(), []) @@ -437,29 +437,29 @@ def test_delete_with_special_properties(self): def test_delete_without_redirect(self): msg = "No URL to redirect to. Provide a success_url." with self.assertRaisesMessage(ImproperlyConfigured, msg): - self.client.post("/edit/author/%d/delete/naive/" % self.author.pk) + self.client.post("/edit/author/%s/delete/naive/" % self.author.pk) def test_delete_with_form_as_post(self): - res = self.client.get("/edit/author/%d/delete/form/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/form/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["author"], self.author) self.assertTemplateUsed(res, "generic_views/author_confirm_delete.html") res = self.client.post( - "/edit/author/%d/delete/form/" % self.author.pk, data={"confirm": True} + "/edit/author/%s/delete/form/" % self.author.pk, data={"confirm": True} ) self.assertEqual(res.status_code, 302) self.assertRedirects(res, "/list/authors/") self.assertSequenceEqual(Author.objects.all(), []) def test_delete_with_form_as_post_with_validation_error(self): - res = self.client.get("/edit/author/%d/delete/form/" % self.author.pk) + res = self.client.get("/edit/author/%s/delete/form/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["author"], self.author) self.assertTemplateUsed(res, "generic_views/author_confirm_delete.html") - res = self.client.post("/edit/author/%d/delete/form/" % self.author.pk) + res = self.client.post("/edit/author/%s/delete/form/" % self.author.pk) self.assertEqual(res.status_code, 200) self.assertEqual(len(res.context_data["form"].errors), 2) self.assertEqual( diff --git a/tests/generic_views/urls.py b/tests/generic_views/urls.py index 277b2c4c1b..a0144dea2a 100644 --- a/tests/generic_views/urls.py +++ b/tests/generic_views/urls.py @@ -1,12 +1,28 @@ +from bson import ObjectId + from django.contrib.auth import views as auth_views from django.contrib.auth.decorators import login_required -from django.urls import path, re_path +from django.urls import path, re_path, register_converter from django.views.decorators.cache import cache_page from django.views.generic import TemplateView, dates from . import views from .models import Book + +class ObjectIdConverter: + regex = "[0-9a-f]{24}" + + def to_python(self, value): + return ObjectId(value) + + def to_url(self, value): + return str(value) + + +register_converter(ObjectIdConverter, "objectId") + + urlpatterns = [ # TemplateView path("template/no_template/", TemplateView.as_view()), @@ -37,8 +53,8 @@ ), # DetailView path("detail/obj/", views.ObjectDetail.as_view()), - path("detail/artist//", views.ArtistDetail.as_view(), name="artist_detail"), - path("detail/author//", views.AuthorDetail.as_view(), name="author_detail"), + path("detail/artist//", views.ArtistDetail.as_view(), name="artist_detail"), + path("detail/author//", views.AuthorDetail.as_view(), name="author_detail"), path( "detail/author/bycustompk//", views.AuthorDetail.as_view(pk_url_kwarg="foo"), @@ -48,29 +64,32 @@ "detail/author/bycustomslug//", views.AuthorDetail.as_view(slug_url_kwarg="foo"), ), - path("detail/author/bypkignoreslug/-/", views.AuthorDetail.as_view()), path( - "detail/author/bypkandslug/-/", + "detail/author/bypkignoreslug/-/", + views.AuthorDetail.as_view(), + ), + path( + "detail/author/bypkandslug/-/", views.AuthorDetail.as_view(query_pk_and_slug=True), ), path( - "detail/author//template_name_suffix/", + "detail/author//template_name_suffix/", views.AuthorDetail.as_view(template_name_suffix="_view"), ), path( - "detail/author//template_name/", + "detail/author//template_name/", views.AuthorDetail.as_view(template_name="generic_views/about.html"), ), path( - "detail/author//context_object_name/", + "detail/author//context_object_name/", views.AuthorDetail.as_view(context_object_name="thingy"), ), - path("detail/author//custom_detail/", views.AuthorCustomDetail.as_view()), + path("detail/author//custom_detail/", views.AuthorCustomDetail.as_view()), path( - "detail/author//dupe_context_object_name/", + "detail/author//dupe_context_object_name/", views.AuthorDetail.as_view(context_object_name="object"), ), - path("detail/page//field/", views.PageDetail.as_view()), + path("detail/page//field/", views.PageDetail.as_view()), path(r"detail/author/invalid/url/", views.AuthorDetail.as_view()), path("detail/author/invalid/qs/", views.AuthorDetail.as_view(queryset=None)), path("detail/nonmodel/1/", views.NonModelDetail.as_view()), @@ -80,7 +99,7 @@ path("late-validation/", views.LateValidationView.as_view()), # Create/UpdateView path("edit/artists/create/", views.ArtistCreate.as_view()), - path("edit/artists//update/", views.ArtistUpdate.as_view()), + path("edit/artists//update/", views.ArtistUpdate.as_view()), path("edit/authors/create/naive/", views.NaiveAuthorCreate.as_view()), path( "edit/authors/create/redirect/", @@ -97,46 +116,46 @@ path("edit/authors/create/restricted/", views.AuthorCreateRestricted.as_view()), re_path("^[eé]dit/authors/create/$", views.AuthorCreate.as_view()), path("edit/authors/create/special/", views.SpecializedAuthorCreate.as_view()), - path("edit/author//update/naive/", views.NaiveAuthorUpdate.as_view()), + path("edit/author//update/naive/", views.NaiveAuthorUpdate.as_view()), path( - "edit/author//update/redirect/", + "edit/author//update/redirect/", views.NaiveAuthorUpdate.as_view(success_url="/edit/authors/create/"), ), path( - "edit/author//update/interpolate_redirect/", + "edit/author//update/interpolate_redirect/", views.NaiveAuthorUpdate.as_view(success_url="/edit/author/{id}/update/"), ), path( - "edit/author//update/interpolate_redirect_nonascii/", + "edit/author//update/interpolate_redirect_nonascii/", views.NaiveAuthorUpdate.as_view(success_url="/%C3%A9dit/author/{id}/update/"), ), - re_path("^[eé]dit/author/(?P[0-9]+)/update/$", views.AuthorUpdate.as_view()), + re_path("^[eé]dit/author/(?P[0-9a-f]+)/update/$", views.AuthorUpdate.as_view()), path("edit/author/update/", views.OneAuthorUpdate.as_view()), path( - "edit/author//update/special/", views.SpecializedAuthorUpdate.as_view() + "edit/author//update/special/", views.SpecializedAuthorUpdate.as_view() ), - path("edit/author//delete/naive/", views.NaiveAuthorDelete.as_view()), + path("edit/author//delete/naive/", views.NaiveAuthorDelete.as_view()), path( - "edit/author//delete/redirect/", + "edit/author//delete/redirect/", views.NaiveAuthorDelete.as_view(success_url="/edit/authors/create/"), ), path( - "edit/author//delete/interpolate_redirect/", + "edit/author//delete/interpolate_redirect/", views.NaiveAuthorDelete.as_view( success_url="/edit/authors/create/?deleted={id}" ), ), path( - "edit/author//delete/interpolate_redirect_nonascii/", + "edit/author//delete/interpolate_redirect_nonascii/", views.NaiveAuthorDelete.as_view( success_url="/%C3%A9dit/authors/create/?deleted={id}" ), ), - path("edit/author//delete/", views.AuthorDelete.as_view()), + path("edit/author//delete/", views.AuthorDelete.as_view()), path( - "edit/author//delete/special/", views.SpecializedAuthorDelete.as_view() + "edit/author//delete/special/", views.SpecializedAuthorDelete.as_view() ), - path("edit/author//delete/form/", views.AuthorDeleteFormView.as_view()), + path("edit/author//delete/form/", views.AuthorDeleteFormView.as_view()), # ArchiveIndexView path("dates/books/", views.BookArchive.as_view()), path( @@ -352,12 +371,15 @@ path("dates/booksignings/today/", views.BookSigningTodayArchive.as_view()), # DateDetailView path( - "dates/books/////", + "dates/books/////", views.BookDetail.as_view(month_format="%m"), ), - path("dates/books/////", views.BookDetail.as_view()), path( - "dates/books/////allow_future/", + "dates/books/////", + views.BookDetail.as_view(), + ), + path( + "dates/books/////allow_future/", views.BookDetail.as_view(allow_future=True), ), path("dates/books////nopk/", views.BookDetail.as_view()), @@ -366,11 +388,11 @@ views.BookDetail.as_view(), ), path( - "dates/books/get_object_custom_queryset/////", + "dates/books/get_object_custom_queryset/////", views.BookDetailGetObjectCustomQueryset.as_view(), ), path( - "dates/booksignings/////", + "dates/booksignings/////", views.BookSigningDetail.as_view(), ), # Useful for testing redirects diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index 59f84be221..bfe93e604c 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -591,7 +591,9 @@ def test_update_only_defaults_and_pre_save_fields_when_local_fields(self): ) self.assertIs(created, False) update_sqls = [ - q["sql"] for q in captured_queries if q["sql"].startswith("UPDATE") + q["sql"] + for q in captured_queries + if q["sql"].startswith("db.get_or_create_book.update_many") ] self.assertEqual(len(update_sqls), 1) update_sql = update_sqls[0] diff --git a/tests/messages_tests/urls.py b/tests/messages_tests/urls.py index 3f70911d4f..0cfbf2248f 100644 --- a/tests/messages_tests/urls.py +++ b/tests/messages_tests/urls.py @@ -75,7 +75,7 @@ class DeleteFormViewWithMsg(SuccessMessageMixin, DeleteView): re_path("^add/(debug|info|success|warning|error)/$", add, name="add_message"), path("add/msg/", ContactFormViewWithMsg.as_view(), name="add_success_msg"), path( - "delete/msg/", + "delete/msg/", DeleteFormViewWithMsg.as_view(), name="success_msg_on_delete", ), diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index e5c026cee6..8b109fce4a 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -130,6 +130,8 @@ def test_change_form_deletion_when_invalid(self): self.assertEqual(Poet.objects.count(), 0) def test_outdated_deletion(self): + from bson import ObjectId + poet = Poet.objects.create(name="test") poem = Poem.objects.create(name="Brevity is the soul of wit", poet=poet) @@ -137,13 +139,14 @@ def test_outdated_deletion(self): Poet, Poem, fields="__all__", can_delete=True ) + new_id = ObjectId() # Simulate deletion of an object that doesn't exist in the database data = { "form-TOTAL_FORMS": "2", "form-INITIAL_FORMS": "2", "form-0-id": str(poem.pk), "form-0-name": "foo", - "form-1-id": str(poem.pk + 1), # doesn't exist + "form-1-id": new_id, # doesn't exist "form-1-name": "bar", "form-1-DELETE": "on", } @@ -158,7 +161,7 @@ def test_outdated_deletion(self): # Make sure the save went through correctly self.assertEqual(Poem.objects.get(pk=poem.pk).name, "foo") self.assertEqual(poet.poem_set.count(), 1) - self.assertFalse(Poem.objects.filter(pk=poem.pk + 1).exists()) + self.assertFalse(Poem.objects.filter(pk=new_id).exists()) class ModelFormsetTest(TestCase): @@ -234,7 +237,7 @@ def test_simple_save(self): '

    ' '' - '

    ' + '

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

    ' '' - '

    ' + '

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

    ' '

    ' '' - '

    ' + '

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

    ' '

    ' '' - '

    ' + '

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

    ' '

    ' '' - '

    ' + '

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

    ' '' - '

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

    ' '' - '' '' "

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

    ' '' - '' '

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

    ' '' - '' '

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

    ' '' - '' - '

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

    ' '' - '' '

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

    ' '' - '' '

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

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

    ' '

    " '

    ' '

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

    ' '' - '

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

    ' '' - '

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

    ' '' - '' '

    ' % person.id, diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index 0c8378f624..a30cb55223 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -287,7 +287,8 @@ def test_name_set(self): index_names, [ "model_index_title_196f42_idx", - "model_index_isbn_34f975_idx", + # Edited since MongoDB's id column is _id. + "model_index_isbn_8cecda_idx", "model_indexes_book_barcode_idx", ], ) diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py index ffb9f28cfa..3952b07537 100644 --- a/tests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py @@ -12,6 +12,8 @@ Both styles are demonstrated here. """ +from django_mongodb_backend.fields import ObjectIdAutoField + from django.db import models # @@ -168,7 +170,7 @@ class Base(models.Model): class SubBase(Base): - sub_id = models.IntegerField(primary_key=True) + sub_id = ObjectIdAutoField(primary_key=True) class GrandParent(models.Model): diff --git a/tests/model_inheritance/test_abstract_inheritance.py b/tests/model_inheritance/test_abstract_inheritance.py index 24362292a1..b691c14024 100644 --- a/tests/model_inheritance/test_abstract_inheritance.py +++ b/tests/model_inheritance/test_abstract_inheritance.py @@ -1,3 +1,5 @@ +import django_mongodb_backend + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.checks import Error @@ -416,30 +418,42 @@ def fields(model): self.assertEqual( fields(model1), [ - ("id", models.AutoField), + ("id", django_mongodb_backend.fields.ObjectIdAutoField), ("name", models.CharField), ("age", models.IntegerField), ], ) self.assertEqual( - fields(model2), [("id", models.AutoField), ("name", models.CharField)] + fields(model2), + [ + ("id", django_mongodb_backend.fields.ObjectIdAutoField), + ("name", models.CharField), + ], ) self.assertEqual(getattr(model2, "age"), 2) self.assertEqual( - fields(model3), [("id", models.AutoField), ("name", models.CharField)] + fields(model3), + [ + ("id", django_mongodb_backend.fields.ObjectIdAutoField), + ("name", models.CharField), + ], ) self.assertEqual( - fields(model4), [("id", models.AutoField), ("name", models.CharField)] + fields(model4), + [ + ("id", django_mongodb_backend.fields.ObjectIdAutoField), + ("name", models.CharField), + ], ) self.assertEqual(getattr(model4, "age"), 2) self.assertEqual( fields(model5), [ - ("id", models.AutoField), + ("id", django_mongodb_backend.fields.ObjectIdAutoField), ("foo", models.IntegerField), ("concretemodel_ptr", models.OneToOneField), ("age", models.SmallIntegerField), diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index cc333a9ac2..0c29dc444b 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -224,7 +224,7 @@ def b(): test() for query in queries: sql = query["sql"] - self.assertIn("INSERT INTO", sql, sql) + self.assertIn(".insert_many(", sql, sql) def test_create_copy_with_inherited_m2m(self): restaurant = Restaurant.objects.create() diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 062368d94e..f27a57ff3c 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -665,8 +665,8 @@ def test_queryset_override(self): '" % (band2.id, self.band.id), ) @@ -689,7 +689,7 @@ class ConcertAdminWithForm(ModelAdmin): '" % self.band.id, ) diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py index 9587030a46..23d2f37f65 100644 --- a/tests/multiple_database/tests.py +++ b/tests/multiple_database/tests.py @@ -142,15 +142,15 @@ def test_basic_queries(self): with self.assertRaises(Book.DoesNotExist): Book.objects.using("default").get(published__year=2009) - years = Book.objects.using("other").dates("published", "year") - self.assertEqual([o.year for o in years], [2009]) - years = Book.objects.using("default").dates("published", "year") - self.assertEqual([o.year for o in years], []) - - months = Book.objects.using("other").dates("published", "month") - self.assertEqual([o.month for o in months], [5]) - months = Book.objects.using("default").dates("published", "month") - self.assertEqual([o.month for o in months], []) + # years = Book.objects.using("other").dates("published", "year") + # self.assertEqual([o.year for o in years], [2009]) + # years = Book.objects.using("default").dates("published", "year") + # self.assertEqual([o.year for o in years], []) + + # months = Book.objects.using("other").dates("published", "month") + # self.assertEqual([o.month for o in months], [5]) + # months = Book.objects.using("default").dates("published", "month") + # self.assertEqual([o.month for o in months], []) def test_m2m_separation(self): "M2M fields are constrained to a single database" diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index 3c0b5f4505..4f41e17215 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -1305,8 +1305,8 @@ def test_deleted_GFK(self): self.assertEqual( result, [ - (book1_pk, ct.pk, None), - (self.book2.pk, ct.pk, self.book2), + (str(book1_pk), ct.pk, None), + (str(self.book2.pk), ct.pk, self.book2), ], ) diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index 7caa43d489..f1476fec3e 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -107,9 +107,9 @@ def test_proxy_included_in_ancestors(self): Proxy models are included in the ancestors for a model's DoesNotExist and MultipleObjectsReturned """ - Person.objects.create(name="Foo McBar") - MyPerson.objects.create(name="Bazza del Frob") - LowerStatusPerson.objects.create(status="low", name="homer") + Person.objects.create(name="Foo McBar", pk=1) + MyPerson.objects.create(name="Bazza del Frob", pk=2) + LowerStatusPerson.objects.create(status="low", name="homer", pk=3) max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] with self.assertRaises(Person.DoesNotExist): @@ -119,8 +119,8 @@ def test_proxy_included_in_ancestors(self): with self.assertRaises(Person.DoesNotExist): StatusPerson.objects.get(name="Zathras") - StatusPerson.objects.create(name="Bazza Jr.") - StatusPerson.objects.create(name="Foo Jr.") + StatusPerson.objects.create(name="Bazza Jr.", pk=4) + StatusPerson.objects.create(name="Foo Jr.", pk=5) max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] with self.assertRaises(Person.MultipleObjectsReturned): diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py index 65d521faac..6d67bfdb43 100644 --- a/tests/serializers/test_json.py +++ b/tests/serializers/test_json.py @@ -29,14 +29,14 @@ class JsonSerializerTestCase(SerializersTestBase, TestCase): mapping_ordering_str = """[ { "model": "serializers.article", - "pk": %(article_pk)s, + "pk": "%(article_pk)s", "fields": { - "author": %(author_pk)s, + "author": "%(author_pk)s", "headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T11:00:00", "categories": [ - %(first_category_pk)s, - %(second_category_pk)s + "%(first_category_pk)s", + "%(second_category_pk)s" ], "meta_data": [], "topics": [] diff --git a/tests/serializers/test_jsonl.py b/tests/serializers/test_jsonl.py index 3137b037a9..73fe725602 100644 --- a/tests/serializers/test_jsonl.py +++ b/tests/serializers/test_jsonl.py @@ -21,12 +21,12 @@ class JsonlSerializerTestCase(SerializersTestBase, TestCase): pkless_str = "\n".join([s.replace("\n", "") for s in pkless_str]) mapping_ordering_str = ( - '{"model": "serializers.article","pk": %(article_pk)s,' + '{"model": "serializers.article","pk": "%(article_pk)s",' '"fields": {' - '"author": %(author_pk)s,' + '"author": "%(author_pk)s",' '"headline": "Poker has no place on ESPN",' '"pub_date": "2006-06-16T11:00:00",' - '"categories": [%(first_category_pk)s,%(second_category_pk)s],' + '"categories": ["%(first_category_pk)s","%(second_category_pk)s"],' '"meta_data": [],' '"topics": []}}\n' ) diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index 9e6bb762c9..cb6df67f00 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -463,8 +463,9 @@ def test_serialize_no_only_pk_with_natural_keys(self): categories_sql = ctx[1]["sql"] self.assertNotIn(connection.ops.quote_name("meta_data_id"), categories_sql) # CategoryMetaData has natural_key(). - meta_data_sql = ctx[2]["sql"] - self.assertIn(connection.ops.quote_name("kind"), meta_data_sql) + # MongoDB has no "SELECT" clause. + # meta_data_sql = ctx[2]["sql"] + # self.assertIn(connection.ops.quote_name("kind"), meta_data_sql) topics_data_sql = ctx[3]["sql"] self.assertNotIn(connection.ops.quote_name("category_id"), topics_data_sql) diff --git a/tests/servers/test_liveserverthread.py b/tests/servers/test_liveserverthread.py index 8ed70f3202..9710786af4 100644 --- a/tests/servers/test_liveserverthread.py +++ b/tests/servers/test_liveserverthread.py @@ -20,6 +20,7 @@ def test_closes_connections(self): conn = connections[DEFAULT_DB_ALIAS] # Pass a connection to the thread to check they are being closed. connections_override = {DEFAULT_DB_ALIAS: conn} + conn.close() # Open a connection to the database. conn.connect() conn.inc_thread_sharing() diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 05898009d5..f3d27c6a4b 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -93,6 +93,7 @@ def test_closes_connections(self): # its database connections. closed_event = self.server_thread.httpd._connections_closed conn = self.conn + conn.close() # Open a connection to the database. conn.connect() self.assertIsNotNone(conn.connection) diff --git a/tests/sitemaps_tests/urls/http.py b/tests/sitemaps_tests/urls/http.py index db549b4a38..0d8810f3c1 100644 --- a/tests/sitemaps_tests/urls/http.py +++ b/tests/sitemaps_tests/urls/http.py @@ -476,5 +476,5 @@ def testmodelview(request, id): ] urlpatterns += i18n_patterns( - path("i18n/testmodel//", testmodelview, name="i18n_testmodel"), + path("i18n/testmodel//", testmodelview, name="i18n_testmodel"), ) diff --git a/tests/syndication_tests/urls.py b/tests/syndication_tests/urls.py index bb1d3d990d..a840a2a8ba 100644 --- a/tests/syndication_tests/urls.py +++ b/tests/syndication_tests/urls.py @@ -15,7 +15,7 @@ "syndication/rss2/with-wrong-decorated-methods/", feeds.TestRss2FeedWithWrongDecoratedMethod(), ), - path("syndication/rss2/articles//", feeds.TestGetObjectFeed()), + path("syndication/rss2/articles//", feeds.TestGetObjectFeed()), path( "syndication/rss2/guid_ispermalink_true/", feeds.TestRss2FeedWithGuidIsPermaLinkTrue(), diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py index 866e0dccc6..3dcbb229ab 100644 --- a/tests/test_utils/test_testcase.py +++ b/tests/test_utils/test_testcase.py @@ -56,7 +56,7 @@ def test_disallowed_database_connection(self): def test_disallowed_database_queries(self): message = ( - "Database queries to 'other' are not allowed in this test. " + "Database connections to 'other' are not allowed in this test. " "Add 'other' to test_utils.test_testcase.TestTestCase.databases to " "ensure proper test isolation and silence this failure." ) diff --git a/tests/test_utils/test_transactiontestcase.py b/tests/test_utils/test_transactiontestcase.py index 12ef4c9a1c..d76dfabdd1 100644 --- a/tests/test_utils/test_transactiontestcase.py +++ b/tests/test_utils/test_transactiontestcase.py @@ -61,7 +61,7 @@ class DisallowedDatabaseQueriesTests(TransactionTestCase): def test_disallowed_database_queries(self): message = ( - "Database queries to 'other' are not allowed in this test. " + "Database connections to 'other' are not allowed in this test. " "Add 'other' to test_utils.test_transactiontestcase." "DisallowedDatabaseQueriesTests.databases to ensure proper test " "isolation and silence this failure." diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 359cf07402..63c494fc64 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -255,10 +255,9 @@ def make_configuration_query(): real_ensure_connection() if is_opening_connection: - # Avoid infinite recursion. Creating a cursor calls + # Avoid infinite recursion. get_autocommit() calls # ensure_connection() which is currently mocked by this method. - with connection.cursor() as cursor: - cursor.execute("SELECT 1" + connection.features.bare_select_suffix) + connection.get_autocommit() ensure_connection = ( "django.db.backends.base.base.BaseDatabaseWrapper.ensure_connection" @@ -2092,7 +2091,7 @@ def test_disallowed_database_connections(self): def test_disallowed_database_queries(self): expected_message = ( - "Database queries to 'default' are not allowed in SimpleTestCase " + "Database connections to 'default' are not allowed in SimpleTestCase " "subclasses. Either subclass TestCase or TransactionTestCase to " "ensure proper test isolation or add 'default' to " "test_utils.tests.DisallowedDatabaseQueriesTests.databases to " @@ -2103,7 +2102,7 @@ def test_disallowed_database_queries(self): def test_disallowed_database_chunked_cursor_queries(self): expected_message = ( - "Database queries to 'default' are not allowed in SimpleTestCase " + "Database connections to 'default' are not allowed in SimpleTestCase " "subclasses. Either subclass TestCase or TransactionTestCase to " "ensure proper test isolation or add 'default' to " "test_utils.tests.DisallowedDatabaseQueriesTests.databases to " diff --git a/tests/test_utils/urls.py b/tests/test_utils/urls.py index 37d0c76a11..f11066a5c8 100644 --- a/tests/test_utils/urls.py +++ b/tests/test_utils/urls.py @@ -3,7 +3,7 @@ from . import views urlpatterns = [ - path("test_utils/get_person//", views.get_person), + path("test_utils/get_person//", views.get_person), path( "test_utils/no_template_used/", views.no_template_used, name="no_template_used" ), From e129fe5cd619d5728ea63f82b99d0e73a50d1656 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 14 Oct 2024 19:03:25 -0400 Subject: [PATCH 20/35] indexes --- tests/indexes/tests.py | 73 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index 0c4158a886..f19d6ff516 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -2,7 +2,7 @@ from unittest import skipUnless from django.conf import settings -from django.db import connection +from django.db import NotSupportedError, connection from django.db.models import CASCADE, CharField, ForeignKey, Index, Q from django.db.models.functions import Lower from django.test import ( @@ -398,9 +398,9 @@ def test_partial_index(self): ), ), ) - self.assertIn( - "WHERE %s" % editor.quote_name("pub_date"), - str(index.create_sql(Article, schema_editor=editor)), + self.assertEqual( + {"pub_date": {"$gt": datetime.datetime(2015, 1, 1, 6, 0)}}, + index._get_condition_mql(Article, schema_editor=editor), ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: @@ -417,12 +417,13 @@ def test_integer_restriction_partial(self): with connection.schema_editor() as editor: index = Index( name="recent_article_idx", - fields=["id"], + # This is changed + fields=["headline"], condition=Q(pk__gt=1), ) - self.assertIn( - "WHERE %s" % editor.quote_name("id"), - str(index.create_sql(Article, schema_editor=editor)), + self.assertEqual( + {"_id": {"$gt": 1}}, + index._get_condition_mql(Article, schema_editor=editor), ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: @@ -442,9 +443,9 @@ def test_boolean_restriction_partial(self): fields=["published"], condition=Q(published=True), ) - self.assertIn( - "WHERE %s" % editor.quote_name("published"), - str(index.create_sql(Article, schema_editor=editor)), + self.assertEqual( + {"published": {"$eq": True}}, + index._get_condition_mql(Article, schema_editor=editor), ) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: @@ -472,15 +473,24 @@ def test_multiple_conditions(self): tzinfo=timezone.get_current_timezone(), ) ) - & Q(headline__contains="China") + & Q(headline="China") ), ) - sql = str(index.create_sql(Article, schema_editor=editor)) - where = sql.find("WHERE") - self.assertIn("WHERE (%s" % editor.quote_name("pub_date"), sql) + sql = index._get_condition_mql(Article, schema_editor=editor) + self.assertEqual( + sql, + { + "$and": [ + {"pub_date": {"$gt": datetime.datetime(2015, 1, 1, 6, 0)}}, + {"headline": {"$eq": "China"}}, + ] + }, + ) + # where = sql.find("WHERE") + # self.assertIn("WHERE (%s" % editor.quote_name("pub_date"), sql) # Because each backend has different syntax for the operators, # check ONLY the occurrence of headline in the SQL. - self.assertGreater(sql.rfind("headline"), where) + # self.assertGreater(sql.rfind("headline"), where) editor.add_index(index=index, model=Article) with connection.cursor() as cursor: self.assertIn( @@ -493,26 +503,17 @@ def test_multiple_conditions(self): editor.remove_index(index=index, model=Article) def test_is_null_condition(self): - with connection.schema_editor() as editor: - index = Index( - name="recent_article_idx", - fields=["pub_date"], - condition=Q(pub_date__isnull=False), - ) - self.assertIn( - "WHERE %s IS NOT NULL" % editor.quote_name("pub_date"), - str(index.create_sql(Article, schema_editor=editor)), - ) - editor.add_index(index=index, model=Article) - with connection.cursor() as cursor: - self.assertIn( - index.name, - connection.introspection.get_constraints( - cursor=cursor, - table_name=Article._meta.db_table, - ), - ) - editor.remove_index(index=index, model=Article) + msg = "MongoDB does not support the 'isnull' lookup in indexes." + index = Index( + name="recent_article_idx", + fields=["pub_date"], + condition=Q(pub_date__isnull=False), + ) + with ( + self.assertRaisesMessage(NotSupportedError, msg), + connection.schema_editor() as editor, + ): + index._get_condition_mql(Article, schema_editor=editor) @skipUnlessDBFeature("supports_expression_indexes") def test_partial_func_index(self): From a571a2ca49b834bfefcba3b2cd5eec1c4ea08b82 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 22 Oct 2024 10:55:13 -0400 Subject: [PATCH 21/35] allow runtests.py to discover tests in django_mongodb/tests --- tests/runtests.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/runtests.py b/tests/runtests.py index 57d4fcea72..f6cb580aee 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -13,6 +13,8 @@ import warnings from pathlib import Path +import django_mongodb_backend + try: import django except ImportError as e: @@ -63,6 +65,9 @@ RUNTESTS_DIR = os.path.abspath(os.path.dirname(__file__)) +MONGODB_TEST_DIR = Path(django_mongodb_backend.__file__).parent.parent / "tests" +sys.path.append(str(MONGODB_TEST_DIR)) + TEMPLATE_DIR = os.path.join(RUNTESTS_DIR, "templates") # Create a specific subdirectory for the duration of the test suite. @@ -145,6 +150,21 @@ def get_test_modules(gis_enabled): test_module = dirname + "." + test_module yield test_module + # Discover tests in django_mongodb_backend/tests. + dirpath = os.path.join(MONGODB_TEST_DIR, dirname) + with os.scandir(dirpath) as entries: + for f in entries: + if ( + "." in f.name + or f.is_file() + or not os.path.exists(os.path.join(f.path, "__init__.py")) + ): + continue + test_module = f.name + if dirname: + test_module = dirname + "." + test_module + yield test_module + def get_label_module(label): """Return the top-level module part for a test label.""" From 5aa4390f0d15dc8d2d49f1b14f539c048b53840c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 7 Nov 2024 14:44:22 -0500 Subject: [PATCH 22/35] constraints edits for partial indexes MongoDB doesn't support isnull constraints. --- tests/constraints/models.py | 2 +- tests/constraints/tests.py | 29 ++++++++++++++++------------ tests/introspection/models.py | 2 +- tests/validation/models.py | 2 +- tests/validation/test_constraints.py | 8 ++++---- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/tests/constraints/models.py b/tests/constraints/models.py index 41b827640e..a0379df466 100644 --- a/tests/constraints/models.py +++ b/tests/constraints/models.py @@ -98,7 +98,7 @@ class Meta: models.UniqueConstraint( fields=["name"], name="name_without_color_uniq", - condition=models.Q(color__isnull=True), + condition=models.Q(color="blue"), ), ] diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py index 96cd1c25ef..f93f9f228c 100644 --- a/tests/constraints/tests.py +++ b/tests/constraints/tests.py @@ -868,10 +868,10 @@ def test_database_constraint(self): @skipUnlessDBFeature("supports_partial_indexes") def test_database_constraint_with_condition(self): - UniqueConstraintConditionProduct.objects.create(name="p1") - UniqueConstraintConditionProduct.objects.create(name="p2") + UniqueConstraintConditionProduct.objects.create(name="p1", color="blue") + UniqueConstraintConditionProduct.objects.create(name="p2", color="blue") with self.assertRaises(IntegrityError): - UniqueConstraintConditionProduct.objects.create(name="p1") + UniqueConstraintConditionProduct.objects.create(name="p1", color="blue") def test_model_validation(self): msg = "Unique constraint product with this Name and Color already exists." @@ -887,13 +887,14 @@ def test_model_validation_with_condition(self): Model.validate_constraints(). """ obj1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="red") - obj2 = UniqueConstraintConditionProduct.objects.create(name="p2") + obj2 = UniqueConstraintConditionProduct.objects.create(name="p2", color="blue") UniqueConstraintConditionProduct( name=obj1.name, color="blue" ).validate_constraints() msg = "Constraint “name_without_color_uniq” is violated." with self.assertRaisesMessage(ValidationError, msg): - UniqueConstraintConditionProduct(name=obj2.name).validate_constraints() + p = UniqueConstraintConditionProduct(name=obj2.name, color="blue") + p.validate_constraints() def test_model_validation_constraint_no_code_error(self): class ValidateNoCodeErrorConstraint(UniqueConstraint): @@ -999,13 +1000,13 @@ def test_validate_fields_unattached(self): @skipUnlessDBFeature("supports_partial_indexes") def test_validate_condition(self): - p1 = UniqueConstraintConditionProduct.objects.create(name="p1") + p1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="blue") constraint = UniqueConstraintConditionProduct._meta.constraints[0] msg = "Constraint “name_without_color_uniq” is violated." with self.assertRaisesMessage(ValidationError, msg): constraint.validate( UniqueConstraintConditionProduct, - UniqueConstraintConditionProduct(name=p1.name, color=None), + UniqueConstraintConditionProduct(name=p1.name, color="blue"), ) # Values not matching condition are ignored. constraint.validate( @@ -1023,11 +1024,11 @@ def test_validate_condition(self): @skipUnlessDBFeature("supports_partial_indexes") def test_validate_condition_custom_error(self): - p1 = UniqueConstraintConditionProduct.objects.create(name="p1") + p1 = UniqueConstraintConditionProduct.objects.create(name="p1", color="blue") constraint = models.UniqueConstraint( fields=["name"], name="name_without_color_uniq", - condition=models.Q(color__isnull=True), + condition=models.Q(color="blue"), violation_error_code="custom_code", violation_error_message="Custom message", ) @@ -1035,7 +1036,7 @@ def test_validate_condition_custom_error(self): with self.assertRaisesMessage(ValidationError, msg) as cm: constraint.validate( UniqueConstraintConditionProduct, - UniqueConstraintConditionProduct(name=p1.name, color=None), + UniqueConstraintConditionProduct(name=p1.name, color="blue"), ) self.assertEqual(cm.exception.code, "custom_code") @@ -1121,9 +1122,13 @@ def test_validate_expression_condition(self): constraint = models.UniqueConstraint( Lower("name"), name="name_lower_without_color_uniq", - condition=models.Q(color__isnull=True), + condition=models.Q(color="blue"), + ) + p2 = UniqueConstraintProduct.objects.create(name="p2", color="blue") + non_unique_product = UniqueConstraintProduct( + name=p2.name.upper(), + color=p2.color, ) - non_unique_product = UniqueConstraintProduct(name=self.p2.name.upper()) msg = "Constraint “name_lower_without_color_uniq” is violated." with self.assertRaisesMessage(ValidationError, msg): constraint.validate(UniqueConstraintProduct, non_unique_product) diff --git a/tests/introspection/models.py b/tests/introspection/models.py index da53d7bd2f..ab16cdbf7f 100644 --- a/tests/introspection/models.py +++ b/tests/introspection/models.py @@ -97,7 +97,7 @@ class Meta: models.UniqueConstraint( fields=["name"], name="cond_name_without_color_uniq", - condition=models.Q(color__isnull=True), + condition=models.Q(color="blue"), ), ] diff --git a/tests/validation/models.py b/tests/validation/models.py index ed88750364..d188b0e57e 100644 --- a/tests/validation/models.py +++ b/tests/validation/models.py @@ -214,6 +214,6 @@ class Meta: models.UniqueConstraint( fields=["name"], name="name_without_color_uniq_validation", - condition=models.Q(color__isnull=True), + condition=models.Q(color="blue"), ), ] diff --git a/tests/validation/test_constraints.py b/tests/validation/test_constraints.py index 0b1ee6518e..ffbcc801a9 100644 --- a/tests/validation/test_constraints.py +++ b/tests/validation/test_constraints.py @@ -75,8 +75,8 @@ def test_full_clean_with_unique_constraints_disabled(self): @skipUnlessDBFeature("supports_partial_indexes") def test_full_clean_with_partial_unique_constraints(self): - UniqueConstraintConditionProduct.objects.create(name="product") - product = UniqueConstraintConditionProduct(name="product") + UniqueConstraintConditionProduct.objects.create(name="product", color="blue") + product = UniqueConstraintConditionProduct(name="product", color="blue") with self.assertRaises(ValidationError) as cm: product.full_clean() self.assertEqual( @@ -90,6 +90,6 @@ def test_full_clean_with_partial_unique_constraints(self): @skipUnlessDBFeature("supports_partial_indexes") def test_full_clean_with_partial_unique_constraints_disabled(self): - UniqueConstraintConditionProduct.objects.create(name="product") - product = UniqueConstraintConditionProduct(name="product") + UniqueConstraintConditionProduct.objects.create(name="product", color="blue") + product = UniqueConstraintConditionProduct(name="product", color="blue") product.full_clean(validate_constraints=False) From 377ad5341220ab9c43f3746343a99c3a70b26626 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 14 Dec 2024 11:16:18 -0500 Subject: [PATCH 23/35] fix test_model_admin_default_delete_action --- tests/admin_views/test_actions.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py index 467fe046ef..08e670a0ea 100644 --- a/tests/admin_views/test_actions.py +++ b/tests/admin_views/test_actions.py @@ -84,13 +84,7 @@ def test_model_admin_default_delete_action(self): ) # Log entries are inserted in bulk. self.assertEqual( - len( - [ - q["sql"] - for q in ctx.captured_queries - if q["sql"].startswith("INSERT") - ] - ), + len([q["sql"] for q in ctx.captured_queries if "insert_many" in q["sql"]]), 1, ) self.assertEqual(Subscriber.objects.count(), 0) From cc1a56b897a040e0a15438dd3a6decbca831f61c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 10 Feb 2025 19:01:12 -0500 Subject: [PATCH 24/35] adapt tests for ObjectIdAutoField --- tests/admin_changelist/tests.py | 19 +- tests/admin_checks/tests.py | 6 +- tests/admin_inlines/tests.py | 8 +- tests/admin_views/admin.py | 12 +- tests/admin_views/models.py | 2 +- tests/admin_views/test_actions.py | 10 +- tests/admin_views/tests.py | 228 +++++++++----- tests/admin_widgets/models.py | 2 +- tests/aggregation/tests.py | 7 +- tests/aggregation_regress/tests.py | 2 +- tests/auth_tests/fixtures/natural.json | 4 +- tests/auth_tests/fixtures/regular.json | 6 +- tests/auth_tests/test_management.py | 21 +- tests/auth_tests/test_views.py | 3 +- tests/auth_tests/urls_custom_user_admin.py | 4 +- tests/backends/base/test_creation.py | 8 +- tests/backends/tests.py | 22 +- tests/basic/tests.py | 6 +- tests/bulk_create/tests.py | 6 +- tests/check_framework/test_model_checks.py | 20 +- tests/contenttypes_tests/test_views.py | 6 +- .../db_functions/comparison/test_coalesce.py | 10 +- tests/delete_regress/models.py | 6 +- tests/delete_regress/tests.py | 2 +- tests/expressions_case/tests.py | 8 +- .../fixtures/fixtures/circular_reference.json | 8 +- .../fixtures/db_fixture_1.default.json | 4 +- .../fixtures/db_fixture_2.default.json.gz | Bin 175 -> 180 bytes tests/fixtures/fixtures/fixture1.json | 8 +- tests/fixtures/fixtures/fixture2.json | 4 +- tests/fixtures/fixtures/fixture3.xml | 6 +- tests/fixtures/fixtures/fixture4.json.zip | Bin 282 -> 286 bytes tests/fixtures/fixtures/fixture5.json.bz2 | Bin 166 -> 169 bytes tests/fixtures/fixtures/fixture5.json.gz | Bin 169 -> 173 bytes tests/fixtures/fixtures/fixture5.json.lzma | Bin 157 -> 155 bytes tests/fixtures/fixtures/fixture5.json.xz | Bin 200 -> 200 bytes tests/fixtures/fixtures/fixture5.json.zip | Bin 295 -> 301 bytes tests/fixtures/fixtures/fixture6.json | 14 +- tests/fixtures/fixtures/fixture8.json | 6 +- .../fixtures/fixture_with[special]chars.json | 2 +- .../fixtures/forward_reference_fk.json | 8 +- .../fixtures/forward_reference_m2m.json | 8 +- tests/fixtures/fixtures/invalid.json | 2 +- .../null_character_in_field_value.json | 2 +- tests/fixtures/models.py | 4 +- tests/fixtures/tests.py | 295 +++++++++++------- tests/fixtures_regress/fixtures/absolute.json | 2 +- tests/fixtures_regress/fixtures/animal.xml | 4 +- .../fixtures/big-fixture.json | 40 +-- tests/fixtures_regress/fixtures/feature.json | 4 +- .../fixtures/forward_ref.json | 8 +- .../fixtures/forward_ref_bad_data.json | 4 +- .../fixtures/forward_ref_lookup.json | 8 +- .../fixtures_regress/fixtures/m2mtoself.json | 2 +- .../fixtures/model-inheritance.json | 4 +- .../fixtures/nk-inheritance.json | 4 +- .../fixtures/nk-inheritance2.xml | 8 +- .../fixtures/non_natural_1.json | 12 +- .../fixtures/non_natural_2.xml | 12 +- .../fixtures/path.containing.dots.json | 2 +- tests/fixtures_regress/fixtures/pretty.xml | 4 +- tests/fixtures_regress/fixtures/sequence.json | 2 +- .../fixtures/sequence_empty_lines_jsonl.jsonl | 2 +- .../fixtures/sequence_extra.json | 4 +- .../fixtures/sequence_extra_jsonl.jsonl | 4 +- .../fixtures/sequence_extra_xml.xml | 2 +- .../fixtures/sequence_extra_yaml.yaml | 4 +- .../fixtures/special-article.json | 4 +- tests/fixtures_regress/fixtures/thingy.json | 2 +- .../fixtures_1/forward_ref_1.json | 4 +- .../fixtures_1/inner/absolute.json | 2 +- .../fixtures_2/forward_ref_2.json | 2 +- tests/fixtures_regress/tests.py | 72 +++-- tests/flatpages_tests/test_csrf.py | 5 +- tests/flatpages_tests/test_forms.py | 5 +- tests/flatpages_tests/test_middleware.py | 6 +- tests/flatpages_tests/test_sitemaps.py | 1 - tests/flatpages_tests/test_templatetags.py | 4 +- tests/flatpages_tests/test_views.py | 6 +- tests/force_insert_update/tests.py | 48 ++- tests/forms_tests/models.py | 4 +- .../forms_tests/tests/test_error_messages.py | 18 +- tests/forms_tests/tests/tests.py | 133 +++++--- tests/forms_tests/urls.py | 2 +- tests/generic_relations_regress/tests.py | 11 +- tests/generic_views/test_dates.py | 8 +- tests/generic_views/test_detail.py | 4 +- tests/generic_views/test_edit.py | 2 +- tests/generic_views/views.py | 2 +- tests/get_or_create/tests.py | 42 ++- tests/gis_tests/distapp/fixtures/initial.json | 30 +- tests/gis_tests/geogapp/fixtures/initial.json | 12 +- .../relatedapp/fixtures/initial.json | 26 +- tests/indexes/tests.py | 6 +- tests/inline_formsets/tests.py | 2 +- tests/lookup/tests.py | 2 +- .../fixtures/m2m_through.json | 8 +- tests/model_fields/models.py | 2 +- tests/model_fields/test_foreignkey.py | 4 +- tests/model_fields/test_jsonfield.py | 2 +- tests/model_forms/tests.py | 2 +- tests/model_formsets/tests.py | 138 ++++---- tests/model_formsets_regress/tests.py | 16 +- tests/model_inheritance_regress/tests.py | 8 +- .../fixtures/multidb-common.json | 4 +- .../fixtures/multidb.default.json | 6 +- .../fixtures/multidb.other.json | 8 +- tests/multiple_database/fixtures/pets.json | 10 +- tests/multiple_database/tests.py | 23 +- tests/or_lookups/tests.py | 4 +- tests/order_with_respect_to/base_tests.py | 12 +- tests/prefetch_related/tests.py | 6 +- tests/proxy_models/fixtures/mypeople.json | 4 +- tests/proxy_models/tests.py | 22 +- tests/queries/test_bulk_update.py | 2 +- tests/queries/tests.py | 95 ++++-- tests/queryset_pickle/tests.py | 10 +- tests/redirects_tests/tests.py | 3 +- tests/runtests.py | 4 +- tests/serializers/models/data.py | 4 +- tests/serializers/test_data.py | 47 ++- tests/serializers/test_deserialization.py | 46 ++- tests/serializers/test_json.py | 64 ++-- tests/serializers/test_jsonl.py | 59 ++-- tests/serializers/test_natural.py | 14 +- tests/serializers/test_xml.py | 10 +- tests/serializers/test_yaml.py | 10 +- tests/serializers/tests.py | 6 +- tests/servers/fixtures/testdata.json | 6 +- tests/signals/tests.py | 8 +- tests/sites_framework/tests.py | 6 +- tests/sites_tests/tests.py | 6 +- tests/syndication_tests/tests.py | 4 +- tests/test_utils/fixtures/person.json | 3 +- tests/validation/test_unique.py | 4 +- tests/validation/tests.py | 6 +- tests/view_tests/tests/test_defaults.py | 4 +- 137 files changed, 1258 insertions(+), 842 deletions(-) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 16ebc38a1c..1de13c6b7e 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -916,7 +916,7 @@ def test_no_distinct_for_m2m_in_list_filter_without_params(self): self.assertIs(cl.queryset.query.distinct, False) # A ManyToManyField in params does have distinct applied. - request = self.factory.get("/band/", {"genres": "0"}) + request = self.factory.get("/band/", {"genres": "000000000000000000000000"}) request.user = self.superuser cl = m.get_changelist_instance(request) self.assertIs(cl.queryset.query.distinct, True) @@ -1034,14 +1034,19 @@ def test_dynamic_list_display_links(self): """ parent = Parent.objects.create(name="parent") for i in range(1, 10): - Child.objects.create(id=i, name="child %s" % i, parent=parent, age=i) + Child.objects.create( + id=f"{i:024}", + name="child %s" % i, + parent=parent, + age=i, + ) m = DynamicListDisplayLinksChildAdmin(Child, custom_site) superuser = self._create_superuser("superuser") request = self._mocked_authenticated_request("/child/", superuser) response = m.changelist_view(request) for i in range(1, 10): - link = reverse("admin:admin_changelist_child_change", args=(i,)) + link = reverse("admin:admin_changelist_child_change", args=(f"{i:024}",)) self.assertContains(response, '%s' % (link, i)) list_display = m.get_list_display(request) @@ -1366,7 +1371,7 @@ def test_deterministic_order_for_unordered_model(self): superuser = self._create_superuser("superuser") for counter in range(1, 51): - UnorderedObject.objects.create(id=counter, bool=True) + UnorderedObject.objects.create(id=f"{counter:024}", bool=True) class UnorderedObjectAdmin(admin.ModelAdmin): list_per_page = 10 @@ -1382,7 +1387,7 @@ def check_results_order(ascending=False): response = model_admin.changelist_view(request) for result in response.context_data["cl"].result_list: counter += 1 if ascending else -1 - self.assertEqual(result.id, counter) + self.assertEqual(str(result.id), f"{counter:024}") custom_site.unregister(UnorderedObject) # When no order is defined at all, everything is ordered by '-pk'. @@ -1426,7 +1431,7 @@ def test_deterministic_order_for_model_ordered_by_its_manager(self): superuser = self._create_superuser("superuser") for counter in range(1, 51): - OrderedObject.objects.create(id=counter, bool=True, number=counter) + OrderedObject.objects.create(id=f"{counter:024}", bool=True, number=counter) class OrderedObjectAdmin(admin.ModelAdmin): list_per_page = 10 @@ -1442,7 +1447,7 @@ def check_results_order(ascending=False): response = model_admin.changelist_view(request) for result in response.context_data["cl"].result_list: counter += 1 if ascending else -1 - self.assertEqual(result.id, counter) + self.assertEqual(str(result.id), f"{counter:024}") custom_site.unregister(OrderedObject) # When no order is defined at all, use the model's default ordering diff --git a/tests/admin_checks/tests.py b/tests/admin_checks/tests.py index 6ca5d6d925..40758832f2 100644 --- a/tests/admin_checks/tests.py +++ b/tests/admin_checks/tests.py @@ -76,8 +76,7 @@ def test_checks_are_performed(self): admin.site.register(Song, MyAdmin) try: errors = checks.run_checks() - expected = ["error!"] - self.assertEqual(errors, expected) + self.assertIn("error!", errors) finally: admin.site.unregister(Song) @@ -267,8 +266,7 @@ class CustomAdminSite(admin.AdminSite): custom_site.register(Song, MyAdmin) try: errors = checks.run_checks() - expected = ["error!"] - self.assertEqual(errors, expected) + self.assertIn("error!", errors) finally: custom_site.unregister(Song) diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index f8056ec6a2..dbbefd89bc 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -518,8 +518,10 @@ def test_localize_pk_shortcut(self): The "View on Site" link is correct for locales that use thousand separators. """ - holder = Holder.objects.create(pk=123456789, dummy=42) - inner = Inner.objects.create(pk=987654321, holder=holder, dummy=42, readonly="") + holder = Holder.objects.create(pk="000000000000000123456789", dummy=42) + inner = Inner.objects.create( + pk="000000000000000987654321", holder=holder, dummy=42, readonly="" + ) response = self.client.get( reverse("admin:admin_inlines_holder_change", args=(holder.id,)) ) @@ -953,7 +955,7 @@ def setUpTestData(cls): ) cls.user.user_permissions.add(permission) - author = Author.objects.create(pk=1, name="The Author") + author = Author.objects.create(pk="000000000000000000000001", name="The Author") cls.book = author.books.create(name="The inline Book") cls.author_change_url = reverse( "admin:admin_inlines_author_change", args=(author.id,) diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 566ee96a30..312ad314d8 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -288,11 +288,13 @@ def has_module_permission(self, request): class RowLevelChangePermissionModelAdmin(admin.ModelAdmin): def has_change_permission(self, request, obj=None): """Only allow changing objects with even id number""" - return request.user.is_staff and (obj is not None) and (obj.id % 2 == 0) + return ( + request.user.is_staff and (obj is not None) and (int(str(obj.id)) % 2 == 0) + ) def has_view_permission(self, request, obj=None): """Only allow viewing objects if id is a multiple of 3.""" - return request.user.is_staff and obj is not None and obj.id % 3 == 0 + return request.user.is_staff and obj is not None and int(str(obj.id)) % 3 == 0 class CustomArticleAdmin(admin.ModelAdmin): @@ -467,7 +469,7 @@ def save_related(self, request, form, formsets, change): class EmptyModelAdmin(admin.ModelAdmin): def get_queryset(self, request): - return super().get_queryset(request).filter(pk__gt=1) + return super().get_queryset(request).filter(pk__gt="000000000000000000000001") class OldSubscriberAdmin(admin.ModelAdmin): @@ -644,7 +646,9 @@ class FieldOverridePostAdmin(PostAdmin): class CustomChangeList(ChangeList): def get_queryset(self, request): - return self.root_queryset.order_by("pk").filter(pk=9999) # Doesn't exist + return self.root_queryset.order_by("pk").filter( + pk="000000000000000000000000" + ) # Doesn't exist class GadgetAdmin(admin.ModelAdmin): diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 24f072fc16..64d12a9869 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -941,7 +941,7 @@ def get_queryset(self): class FilteredManager(models.Model): def __str__(self): - return "PK=%d" % self.pk + return "PK=%s" % self.pk pk_gt_1 = _Manager() objects = models.Manager() diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py index 08e670a0ea..876e7a43b4 100644 --- a/tests/admin_views/test_actions.py +++ b/tests/admin_views/test_actions.py @@ -90,9 +90,11 @@ def test_model_admin_default_delete_action(self): self.assertEqual(Subscriber.objects.count(), 0) def test_default_delete_action_nonexistent_pk(self): - self.assertFalse(Subscriber.objects.filter(id=9998).exists()) + self.assertFalse( + Subscriber.objects.filter(id="000000000000000000009998").exists() + ) action_data = { - ACTION_CHECKBOX_NAME: ["9998"], + ACTION_CHECKBOX_NAME: ["000000000000000000009998"], "action": "delete_selected", "index": 0, } @@ -110,7 +112,7 @@ def test_non_localized_pk(self): If USE_THOUSAND_SEPARATOR is set, the ids for the objects selected for deletion are rendered without separators. """ - s = ExternalSubscriber.objects.create(id=9999) + s = ExternalSubscriber.objects.create(id="000000000000000000009999") action_data = { ACTION_CHECKBOX_NAME: [s.pk, self.s2.pk], "action": "delete_selected", @@ -120,7 +122,7 @@ def test_non_localized_pk(self): reverse("admin:admin_views_subscriber_changelist"), action_data ) self.assertTemplateUsed(response, "admin/delete_selected_confirmation.html") - self.assertContains(response, 'value="9999"') # Instead of 9,999 + self.assertContains(response, 'value="000000000000000000009999"') self.assertContains(response, 'value="%s"' % self.s2.pk) def test_model_admin_default_delete_action_protected(self): diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index a2a368492f..5de0ac99d0 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1734,7 +1734,7 @@ def test_custom_model_admin_templates(self): data={ "index": 0, "action": ["delete_selected"], - "_selected_action": ["1"], + "_selected_action": [str(article_pk)], }, ) self.assertTemplateUsed( @@ -2737,10 +2737,18 @@ def test_change_view(self): self.client.post(reverse("admin:logout")) # Test redirection when using row-level change permissions. Refs #11513. - r1 = RowLevelChangePermissionModel.objects.create(id=1, name="odd id") - r2 = RowLevelChangePermissionModel.objects.create(id=2, name="even id") - r3 = RowLevelChangePermissionModel.objects.create(id=3, name="odd id mult 3") - r6 = RowLevelChangePermissionModel.objects.create(id=6, name="even id mult 3") + r1 = RowLevelChangePermissionModel.objects.create( + id="000000000000000000000001", name="odd id" + ) + r2 = RowLevelChangePermissionModel.objects.create( + id="000000000000000000000002", name="even id" + ) + r3 = RowLevelChangePermissionModel.objects.create( + id="000000000000000000000003", name="odd id mult 3" + ) + r6 = RowLevelChangePermissionModel.objects.create( + id="000000000000000000000006", name="even id mult 3" + ) change_url_1 = reverse( "admin:admin_views_rowlevelchangepermissionmodel_change", args=(r1.pk,) ) @@ -2767,14 +2775,20 @@ def test_change_view(self): self.assertEqual(response.status_code, 403) response = self.client.post(change_url_1, {"name": "changed"}) self.assertEqual( - RowLevelChangePermissionModel.objects.get(id=1).name, "odd id" + RowLevelChangePermissionModel.objects.get( + id="000000000000000000000001" + ).name, + "odd id", ) self.assertEqual(response.status_code, 403) response = self.client.get(change_url_2) self.assertEqual(response.status_code, 200) response = self.client.post(change_url_2, {"name": "changed"}) self.assertEqual( - RowLevelChangePermissionModel.objects.get(id=2).name, "changed" + RowLevelChangePermissionModel.objects.get( + id="000000000000000000000002" + ).name, + "changed", ) self.assertRedirects(response, self.index_url) response = self.client.get(change_url_3) @@ -2782,14 +2796,19 @@ def test_change_view(self): response = self.client.post(change_url_3, {"name": "changed"}) self.assertEqual(response.status_code, 403) self.assertEqual( - RowLevelChangePermissionModel.objects.get(id=3).name, + RowLevelChangePermissionModel.objects.get( + id="000000000000000000000003" + ).name, "odd id mult 3", ) response = self.client.get(change_url_6) self.assertEqual(response.status_code, 200) response = self.client.post(change_url_6, {"name": "changed"}) self.assertEqual( - RowLevelChangePermissionModel.objects.get(id=6).name, "changed" + RowLevelChangePermissionModel.objects.get( + id="000000000000000000000006" + ).name, + "changed", ) self.assertRedirects(response, self.index_url) @@ -2804,7 +2823,10 @@ def test_change_view(self): change_url_1, {"name": "changed"}, follow=True ) self.assertEqual( - RowLevelChangePermissionModel.objects.get(id=1).name, "odd id" + RowLevelChangePermissionModel.objects.get( + id="000000000000000000000001" + ).name, + "odd id", ) self.assertContains(response, "login-form") response = self.client.get(change_url_2, follow=True) @@ -2813,7 +2835,10 @@ def test_change_view(self): change_url_2, {"name": "changed again"}, follow=True ) self.assertEqual( - RowLevelChangePermissionModel.objects.get(id=2).name, "changed" + RowLevelChangePermissionModel.objects.get( + id="000000000000000000000002" + ).name, + "changed", ) self.assertContains(response, "login-form") self.client.post(reverse("admin:logout")) @@ -3109,8 +3134,12 @@ def test_history_view(self): self.assertEqual(response.status_code, 200) # Test redirection when using row-level change permissions. Refs #11513. - rl1 = RowLevelChangePermissionModel.objects.create(id=1, name="odd id") - rl2 = RowLevelChangePermissionModel.objects.create(id=2, name="even id") + rl1 = RowLevelChangePermissionModel.objects.create( + id="000000000000000000000001", name="odd id" + ) + rl2 = RowLevelChangePermissionModel.objects.create( + id="000000000000000000000002", name="even id" + ) logins = [ self.superuser, self.viewuser, @@ -3591,8 +3620,12 @@ def setUpTestData(cls): cls.ssh1 = SuperSecretHideout.objects.create( location="super floating castle!", supervillain=cls.sv1 ) - cls.cy1 = CyclicOne.objects.create(pk=1, name="I am recursive", two_id=1) - cls.cy2 = CyclicTwo.objects.create(pk=1, name="I am recursive too", one_id=1) + cls.cy1 = CyclicOne.objects.create( + pk="000000000000000000000001", name="I am recursive", two_id=1 + ) + cls.cy2 = CyclicTwo.objects.create( + pk="000000000000000000000001", name="I am recursive too", one_id=1 + ) def setUp(self): self.client.force_login(self.superuser) @@ -4451,12 +4484,22 @@ def test_non_form_errors_is_errorlist(self): ) def test_list_editable_ordering(self): - collector = Collector.objects.create(id=1, name="Frederick Clegg") + collector = Collector.objects.create( + id="000000000000000000000001", name="Frederick Clegg" + ) - Category.objects.create(id=1, order=1, collector=collector) - Category.objects.create(id=2, order=2, collector=collector) - Category.objects.create(id=3, order=0, collector=collector) - Category.objects.create(id=4, order=0, collector=collector) + Category.objects.create( + id="000000000000000000000001", order=1, collector=collector + ) + Category.objects.create( + id="000000000000000000000002", order=2, collector=collector + ) + Category.objects.create( + id="000000000000000000000003", order=0, collector=collector + ) + Category.objects.create( + id="000000000000000000000004", order=0, collector=collector + ) # NB: The order values must be changed so that the items are reordered. data = { @@ -4464,16 +4507,16 @@ def test_list_editable_ordering(self): "form-INITIAL_FORMS": "4", "form-MAX_NUM_FORMS": "0", "form-0-order": "14", - "form-0-id": "1", + "form-0-id": "000000000000000000000001", "form-0-collector": "1", "form-1-order": "13", - "form-1-id": "2", + "form-1-id": "000000000000000000000002", "form-1-collector": "1", "form-2-order": "1", - "form-2-id": "3", + "form-2-id": "000000000000000000000003", "form-2-collector": "1", "form-3-order": "0", - "form-3-id": "4", + "form-3-id": "000000000000000000000004", "form-3-collector": "1", # The form processing understands this as a list_editable "Save" # and not an action "Go". @@ -4486,18 +4529,24 @@ def test_list_editable_ordering(self): self.assertEqual(response.status_code, 302) # The order values have been applied to the right objects - self.assertEqual(Category.objects.get(id=1).order, 14) - self.assertEqual(Category.objects.get(id=2).order, 13) - self.assertEqual(Category.objects.get(id=3).order, 1) - self.assertEqual(Category.objects.get(id=4).order, 0) + self.assertEqual(Category.objects.get(id="000000000000000000000001").order, 14) + self.assertEqual(Category.objects.get(id="000000000000000000000002").order, 13) + self.assertEqual(Category.objects.get(id="000000000000000000000003").order, 1) + self.assertEqual(Category.objects.get(id="000000000000000000000004").order, 0) def test_list_editable_pagination(self): """ Pagination works for list_editable items. """ - UnorderedObject.objects.create(id=1, name="Unordered object #1") - UnorderedObject.objects.create(id=2, name="Unordered object #2") - UnorderedObject.objects.create(id=3, name="Unordered object #3") + UnorderedObject.objects.create( + id="000000000000000000000001", name="Unordered object #1" + ) + UnorderedObject.objects.create( + id="000000000000000000000002", name="Unordered object #2" + ) + UnorderedObject.objects.create( + id="000000000000000000000003", name="Unordered object #3" + ) response = self.client.get( reverse("admin:admin_views_unorderedobject_changelist") ) @@ -4954,7 +5003,7 @@ def setUpTestData(cls): cls.superuser = User.objects.create_superuser( username="super", password="secret", email="super@example.com" ) - cls.pks = [EmptyModel.objects.create(id=i + 1).id for i in range(3)] + cls.pks = [EmptyModel.objects.create(id=f"{i+1:024}").id for i in range(3)] def setUp(self): self.client.force_login(self.superuser) @@ -4967,7 +5016,7 @@ def setUp(self): def test_changelist_view(self): response = self.client.get(reverse("admin:admin_views_emptymodel_changelist")) for i in self.pks: - if i > 1: + if str(i) > "000000000000000000000001": self.assertContains(response, "Primary key = %s" % i) else: self.assertNotContains(response, "Primary key = %s" % i) @@ -5004,13 +5053,16 @@ def test_change_view(self): for i in self.pks: url = reverse("admin:admin_views_emptymodel_change", args=(i,)) response = self.client.get(url, follow=True) - if i > 1: + if str(i) > "000000000000000000000001": self.assertEqual(response.status_code, 200) else: self.assertRedirects(response, reverse("admin:index")) self.assertEqual( [m.message for m in response.context["messages"]], - ["empty model with ID “1” doesn’t exist. Perhaps it was deleted?"], + [ + "empty model with ID “000000000000000000000001” doesn’t " + "exist. Perhaps it was deleted?" + ], ) def test_add_model_modeladmin_defer_qs(self): @@ -5228,22 +5280,28 @@ def test_history_view_custom_qs(self): Custom querysets are considered for the admin history view. """ self.client.post(reverse("admin:login"), self.super_login) - FilteredManager.objects.create(pk=1) - FilteredManager.objects.create(pk=2) + FilteredManager.objects.create(pk="000000000000000000000001") + FilteredManager.objects.create(pk="000000000000000000000002") response = self.client.get( reverse("admin:admin_views_filteredmanager_changelist") ) - self.assertContains(response, "PK=1") - self.assertContains(response, "PK=2") + self.assertContains(response, "PK=000000000000000000000001") + self.assertContains(response, "PK=000000000000000000000002") self.assertEqual( self.client.get( - reverse("admin:admin_views_filteredmanager_history", args=(1,)) + reverse( + "admin:admin_views_filteredmanager_history", + args=("000000000000000000000001",), + ) ).status_code, 200, ) self.assertEqual( self.client.get( - reverse("admin:admin_views_filteredmanager_history", args=(2,)) + reverse( + "admin:admin_views_filteredmanager_history", + args=("000000000000000000000002",), + ) ).status_code, 200, ) @@ -5309,7 +5367,9 @@ def setUpTestData(cls): cls.superuser = User.objects.create_superuser( username="super", password="secret", email="super@example.com" ) - cls.collector = Collector.objects.create(pk=1, name="John Fowles") + cls.collector = Collector.objects.create( + id="000000000000000000000001", name="John Fowles" + ) def setUp(self): self.post_data = { @@ -5318,59 +5378,59 @@ def setUp(self): "widget_set-INITIAL_FORMS": "0", "widget_set-MAX_NUM_FORMS": "0", "widget_set-0-id": "", - "widget_set-0-owner": "1", + "widget_set-0-owner": str(self.collector.pk), "widget_set-0-name": "", "widget_set-1-id": "", - "widget_set-1-owner": "1", + "widget_set-1-owner": str(self.collector.pk), "widget_set-1-name": "", "widget_set-2-id": "", - "widget_set-2-owner": "1", + "widget_set-2-owner": str(self.collector.pk), "widget_set-2-name": "", "doohickey_set-TOTAL_FORMS": "3", "doohickey_set-INITIAL_FORMS": "0", "doohickey_set-MAX_NUM_FORMS": "0", - "doohickey_set-0-owner": "1", + "doohickey_set-0-owner": str(self.collector.pk), "doohickey_set-0-code": "", "doohickey_set-0-name": "", - "doohickey_set-1-owner": "1", + "doohickey_set-1-owner": str(self.collector.pk), "doohickey_set-1-code": "", "doohickey_set-1-name": "", - "doohickey_set-2-owner": "1", + "doohickey_set-2-owner": str(self.collector.pk), "doohickey_set-2-code": "", "doohickey_set-2-name": "", "grommet_set-TOTAL_FORMS": "3", "grommet_set-INITIAL_FORMS": "0", "grommet_set-MAX_NUM_FORMS": "0", "grommet_set-0-code": "", - "grommet_set-0-owner": "1", + "grommet_set-0-owner": str(self.collector.pk), "grommet_set-0-name": "", "grommet_set-1-code": "", - "grommet_set-1-owner": "1", + "grommet_set-1-owner": str(self.collector.pk), "grommet_set-1-name": "", "grommet_set-2-code": "", - "grommet_set-2-owner": "1", + "grommet_set-2-owner": str(self.collector.pk), "grommet_set-2-name": "", "whatsit_set-TOTAL_FORMS": "3", "whatsit_set-INITIAL_FORMS": "0", "whatsit_set-MAX_NUM_FORMS": "0", - "whatsit_set-0-owner": "1", + "whatsit_set-0-owner": str(self.collector.pk), "whatsit_set-0-index": "", "whatsit_set-0-name": "", - "whatsit_set-1-owner": "1", + "whatsit_set-1-owner": str(self.collector.pk), "whatsit_set-1-index": "", "whatsit_set-1-name": "", - "whatsit_set-2-owner": "1", + "whatsit_set-2-owner": str(self.collector.pk), "whatsit_set-2-index": "", "whatsit_set-2-name": "", "fancydoodad_set-TOTAL_FORMS": "3", "fancydoodad_set-INITIAL_FORMS": "0", "fancydoodad_set-MAX_NUM_FORMS": "0", "fancydoodad_set-0-doodad_ptr": "", - "fancydoodad_set-0-owner": "1", + "fancydoodad_set-0-owner": str(self.collector.pk), "fancydoodad_set-0-name": "", "fancydoodad_set-0-expensive": "on", "fancydoodad_set-1-doodad_ptr": "", - "fancydoodad_set-1-owner": "1", + "fancydoodad_set-1-owner": str(self.collector.pk), "fancydoodad_set-1-name": "", "fancydoodad_set-1-expensive": "on", "fancydoodad_set-2-doodad_ptr": "", @@ -5382,13 +5442,13 @@ def setUp(self): "category_set-MAX_NUM_FORMS": "0", "category_set-0-order": "", "category_set-0-id": "", - "category_set-0-collector": "1", + "category_set-0-collector": str(self.collector.pk), "category_set-1-order": "", "category_set-1-id": "", - "category_set-1-collector": "1", + "category_set-1-collector": str(self.collector.pk), "category_set-2-order": "", "category_set-2-id": "", - "category_set-2-collector": "1", + "category_set-2-collector": str(self.collector.pk), } self.client.force_login(self.superuser) @@ -5578,10 +5638,18 @@ def test_ordered_inline(self): An inline with an editable ordering fields is updated correctly. """ # Create some objects with an initial ordering - Category.objects.create(id=1, order=1, collector=self.collector) - Category.objects.create(id=2, order=2, collector=self.collector) - Category.objects.create(id=3, order=0, collector=self.collector) - Category.objects.create(id=4, order=0, collector=self.collector) + Category.objects.create( + id="000000000000000000000001", order=1, collector=self.collector + ) + Category.objects.create( + id="000000000000000000000002", order=2, collector=self.collector + ) + Category.objects.create( + id="000000000000000000000003", order=0, collector=self.collector + ) + Category.objects.create( + id="000000000000000000000004", order=0, collector=self.collector + ) # NB: The order values must be changed so that the items are reordered. self.post_data.update( @@ -5591,26 +5659,26 @@ def test_ordered_inline(self): "category_set-INITIAL_FORMS": "4", "category_set-MAX_NUM_FORMS": "0", "category_set-0-order": "14", - "category_set-0-id": "1", - "category_set-0-collector": "1", + "category_set-0-id": "000000000000000000000001", + "category_set-0-collector": str(self.collector.pk), "category_set-1-order": "13", - "category_set-1-id": "2", - "category_set-1-collector": "1", + "category_set-1-id": "000000000000000000000002", + "category_set-1-collector": str(self.collector.pk), "category_set-2-order": "1", - "category_set-2-id": "3", - "category_set-2-collector": "1", + "category_set-2-id": "000000000000000000000003", + "category_set-2-collector": str(self.collector.pk), "category_set-3-order": "0", - "category_set-3-id": "4", - "category_set-3-collector": "1", + "category_set-3-id": "000000000000000000000004", + "category_set-3-collector": str(self.collector.pk), "category_set-4-order": "", "category_set-4-id": "", - "category_set-4-collector": "1", + "category_set-4-collector": str(self.collector.pk), "category_set-5-order": "", "category_set-5-id": "", - "category_set-5-collector": "1", + "category_set-5-collector": str(self.collector.pk), "category_set-6-order": "", "category_set-6-id": "", - "category_set-6-collector": "1", + "category_set-6-collector": str(self.collector.pk), } ) collector_url = reverse( @@ -5622,10 +5690,10 @@ def test_ordered_inline(self): # The order values have been applied to the right objects self.assertEqual(self.collector.category_set.count(), 4) - self.assertEqual(Category.objects.get(id=1).order, 14) - self.assertEqual(Category.objects.get(id=2).order, 13) - self.assertEqual(Category.objects.get(id=3).order, 1) - self.assertEqual(Category.objects.get(id=4).order, 0) + self.assertEqual(Category.objects.get(id="000000000000000000000001").order, 14) + self.assertEqual(Category.objects.get(id="000000000000000000000002").order, 13) + self.assertEqual(Category.objects.get(id="000000000000000000000003").order, 1) + self.assertEqual(Category.objects.get(id="000000000000000000000004").order, 0) @override_settings(ROOT_URLCONF="admin_views.urls") @@ -8067,7 +8135,7 @@ def send_message(self, level): message with the level has appeared in the response. """ action_data = { - ACTION_CHECKBOX_NAME: [1], + ACTION_CHECKBOX_NAME: ["000000000000000000000001"], "action": "message_%s" % level, "index": 0, } @@ -8099,7 +8167,7 @@ def test_message_error(self): def test_message_extra_tags(self): action_data = { - ACTION_CHECKBOX_NAME: [1], + ACTION_CHECKBOX_NAME: ["000000000000000000000001"], "action": "message_extra_tags", "index": 0, } diff --git a/tests/admin_widgets/models.py b/tests/admin_widgets/models.py index 0113ecb7c8..fb55c870db 100644 --- a/tests/admin_widgets/models.py +++ b/tests/admin_widgets/models.py @@ -108,7 +108,7 @@ class Event(models.Model): main_band = models.ForeignKey( Band, models.CASCADE, - limit_choices_to=models.Q(pk__gt=0), + limit_choices_to=models.Q(pk__gt="000000000000000000000000"), related_name="events_main_band_at", ) supporting_bands = models.ManyToManyField( diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py index 6cadc51a26..b1920ab128 100644 --- a/tests/aggregation/tests.py +++ b/tests/aggregation/tests.py @@ -2412,7 +2412,12 @@ def test_aggregate_reference_lookup_rhs(self): def test_aggregate_reference_lookup_rhs_iter(self): aggregates = Author.objects.annotate( max_book_author=Max("book__authors"), - ).aggregate(count=Count("id", filter=Q(id__in=[F("max_book_author"), 0]))) + ).aggregate( + count=Count( + "id", + filter=Q(id__in=[F("max_book_author"), "000000000000000000000000"]), + ) + ) self.assertEqual(aggregates, {"count": 1}) @skipUnlessDBFeature("supports_select_union") diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py index 68bb0f0435..b4c79d6482 100644 --- a/tests/aggregation_regress/tests.py +++ b/tests/aggregation_regress/tests.py @@ -1432,7 +1432,7 @@ def test_annotate_joins(self): qs = Book.objects.annotate(n=Count("pk")) self.assertIs(qs.query.alias_map["aggregation_regress_book"].join_type, None) # The query executes without problems. - self.assertEqual(len(qs.exclude(publisher=-1)), 6) + self.assertEqual(len(qs.exclude(publisher="000000000000000000000001")), 6) @skipUnlessDBFeature("allows_group_by_selected_pks") def test_aggregate_duplicate_columns(self): diff --git a/tests/auth_tests/fixtures/natural.json b/tests/auth_tests/fixtures/natural.json index 7811c7a548..1e1ccca690 100644 --- a/tests/auth_tests/fixtures/natural.json +++ b/tests/auth_tests/fixtures/natural.json @@ -1,6 +1,6 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "auth.group", "fields": { "name": "my_group", @@ -8,7 +8,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "auth.user", "fields": { "username": "my_username", diff --git a/tests/auth_tests/fixtures/regular.json b/tests/auth_tests/fixtures/regular.json index b9f2680766..781898a5bd 100644 --- a/tests/auth_tests/fixtures/regular.json +++ b/tests/auth_tests/fixtures/regular.json @@ -1,6 +1,6 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "auth.group", "fields": { "name": "my_group", @@ -8,7 +8,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "auth.user", "fields": { "username": "my_username", @@ -19,7 +19,7 @@ "is_staff": true, "last_login": "2012-01-13 00:14:00", "groups": [ - 1 + "000000000000000000000001" ], "user_permissions": [], "password": "pbkdf2_sha256$10000$LUyhxJjuLwXF$f6Zbpnx1L5dPze8m0itBaHMDyZ/n6JyhuavQy2RrBIM=", diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 8be6e73276..3ae34590eb 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -600,9 +600,11 @@ def test(self): def test_validate_fk(self): email = Email.objects.create(email="mymail@gmail.com") Group.objects.all().delete() - nonexistent_group_id = 1 - msg = f"group instance with id {nonexistent_group_id} is not a valid choice." - + nonexistent_group_id = "000000000000000000000001" + msg = ( + f"group instance with id ObjectId('{nonexistent_group_id}') is " + "not a valid choice." + ) with self.assertRaisesMessage(CommandError, msg): call_command( "createsuperuser", @@ -620,8 +622,10 @@ def test_validate_fk_environment_variable(self): email = Email.objects.create(email="mymail@gmail.com") Group.objects.all().delete() nonexistent_group_id = ObjectId() - msg = f"group instance with id {nonexistent_group_id!r} is not a valid choice." - + msg = ( + f"group instance with id ObjectId('{nonexistent_group_id}') is " + "not a valid choice." + ) with mock.patch.dict( os.environ, {"DJANGO_SUPERUSER_GROUP": str(nonexistent_group_id)}, @@ -639,8 +643,11 @@ def test_validate_fk_environment_variable(self): def test_validate_fk_via_option_interactive(self): email = Email.objects.create(email="mymail@gmail.com") Group.objects.all().delete() - nonexistent_group_id = 1 - msg = f"group instance with id {nonexistent_group_id} is not a valid choice." + nonexistent_group_id = "000000000000000000000001" + msg = ( + f"group instance with id ObjectId('{nonexistent_group_id}') is " + "not a valid choice." + ) @mock_inputs( { diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index 1583f8ffd7..1d7fe73979 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -1761,7 +1761,8 @@ def test_admin_password_change(self): ) self.assertRedirects(response, user_change_url) row = LogEntry.objects.latest("id") - self.assertEqual(row.user_id, 1) # hardcoded in CustomUserAdmin.log_change() + # hardcoded in CustomUserAdmin.log_change() + self.assertEqual(str(row.user_id), "000000000000000000000001") self.assertEqual(row.object_id, str(u.pk)) self.assertEqual(row.get_change_message(), "Changed password.") diff --git a/tests/auth_tests/urls_custom_user_admin.py b/tests/auth_tests/urls_custom_user_admin.py index 1c7ce1eb42..46caeadaf3 100644 --- a/tests/auth_tests/urls_custom_user_admin.py +++ b/tests/auth_tests/urls_custom_user_admin.py @@ -9,9 +9,9 @@ class CustomUserAdmin(UserAdmin): def log_change(self, request, obj, message): # LogEntry.user column doesn't get altered to expect a UUID, so set an - # integer manually to avoid causing an error. + # ObjectId manually to avoid causing an error. original_pk = request.user.pk - request.user.pk = 1 + request.user.pk = "000000000000000000000001" super().log_change(request, obj, message) request.user.pk = original_pk diff --git a/tests/backends/base/test_creation.py b/tests/backends/base/test_creation.py index 7e760e8884..1664c15bfc 100644 --- a/tests/backends/base/test_creation.py +++ b/tests/backends/base/test_creation.py @@ -179,13 +179,13 @@ def test_circular_reference(self): [ { "model": "backends.object", - "pk": 1, - "fields": {"obj_ref": 1, "related_objects": []} + "pk": "000000000000000000000001", + "fields": {"obj_ref": "000000000000000000000001", "related_objects": []} }, { "model": "backends.objectreference", - "pk": 1, - "fields": {"obj": 1} + "pk": "000000000000000000000001", + "fields": {"obj": "000000000000000000000001"} } ] """ diff --git a/tests/backends/tests.py b/tests/backends/tests.py index ab703c3e69..acd4be0923 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -616,7 +616,7 @@ def test_integrity_checks_on_creation(self): a1 = Article( headline="This is a test", pub_date=datetime.datetime(2005, 7, 27), - reporter_id=30, + reporter_id="000000000000000000000030", ) try: a1.save() @@ -648,7 +648,7 @@ def test_integrity_checks_on_update(self): ) # Retrieve it from the DB a1 = Article.objects.get(headline="Test article") - a1.reporter_id = 30 + a1.reporter_id = "000000000000000000000030" try: a1.save() except IntegrityError: @@ -685,7 +685,7 @@ def test_disable_constraint_checks_manually(self): ) # Retrieve it from the DB a = Article.objects.get(headline="Test article") - a.reporter_id = 30 + a.reporter_id = "000000000000000000000030" try: connection.disable_constraint_checking() a.save() @@ -708,7 +708,7 @@ def test_disable_constraint_checks_context_manager(self): ) # Retrieve it from the DB a = Article.objects.get(headline="Test article") - a.reporter_id = 30 + a.reporter_id = "000000000000000000000030" try: with connection.constraint_checks_disabled(): a.save() @@ -729,7 +729,7 @@ def test_check_constraints(self): ) # Retrieve it from the DB a = Article.objects.get(headline="Test article") - a.reporter_id = 30 + a.reporter_id = "000000000000000000000030" with connection.constraint_checks_disabled(): a.save() try: @@ -744,7 +744,7 @@ def test_check_constraints_sql_keywords(self): with transaction.atomic(): obj = SQLKeywordsModel.objects.create(reporter=self.r) obj.refresh_from_db() - obj.reporter_id = 30 + obj.reporter_id = "000000000000000000000030" with connection.constraint_checks_disabled(): obj.save() try: @@ -966,9 +966,9 @@ def test_can_reference_existent(self): self.assertEqual(ref.obj, obj) def test_can_reference_non_existent(self): - self.assertFalse(Object.objects.filter(id=12345).exists()) - ref = ObjectReference.objects.create(obj_id=12345) - ref_new = ObjectReference.objects.get(obj_id=12345) + self.assertFalse(Object.objects.filter(id="000000000000000000012345").exists()) + ref = ObjectReference.objects.create(obj_id="000000000000000000012345") + ref_new = ObjectReference.objects.get(obj_id="000000000000000000012345") self.assertEqual(ref, ref_new) with self.assertRaises(Object.DoesNotExist): @@ -983,6 +983,8 @@ def test_many_to_many(self): intermediary_model = Object._meta.get_field( "related_objects" ).remote_field.through - intermediary_model.objects.create(from_object_id=obj.id, to_object_id=12345) + intermediary_model.objects.create( + from_object_id=obj.id, to_object_id="000000000000000000012345" + ) self.assertEqual(obj.related_objects.count(), 1) self.assertEqual(intermediary_model.objects.count(), 2) diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 89676ec3cf..8f31e0adbb 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -431,12 +431,12 @@ def test_microsecond_precision_not_supported_edge_case(self): def test_manually_specify_primary_key(self): # You can manually specify the primary key when creating a new object. a101 = Article( - id=101, + id="000000000000000000000101", headline="Article 101", pub_date=datetime(2005, 7, 31, 12, 30, 45), ) a101.save() - a101 = Article.objects.get(pk=101) + a101 = Article.objects.get(pk="000000000000000000000101") self.assertEqual(a101.headline, "Article 101") def test_create_method(self): @@ -798,7 +798,7 @@ def test_does_not_exist(self): ObjectDoesNotExist, "Article matching query does not exist." ): Article.objects.get( - id__exact=2000, + id__exact="000000000000000000002000", ) # To avoid dict-ordering related errors check only one lookup # in single assert. diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py index 7b86a2def5..e5dfacd6f6 100644 --- a/tests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py @@ -226,14 +226,14 @@ def test_large_batch_mixed(self): """ TwoFields.objects.bulk_create( [ - TwoFields(id=i if i % 2 == 0 else None, f1=i, f2=i + 1) + TwoFields(id=f"{i:024}" if i % 2 == 0 else None, f1=i, f2=i + 1) for i in range(100000, 101000) ] ) self.assertEqual(TwoFields.objects.count(), 1000) # We can't assume much about the ID's created, except that the above # created IDs must exist. - id_range = range(100000, 101000, 2) + id_range = [f"{i:024}" for i in range(100000, 101000, 2)] self.assertEqual(TwoFields.objects.filter(id__in=id_range).count(), 500) self.assertEqual(TwoFields.objects.exclude(id__in=id_range).count(), 500) @@ -247,7 +247,7 @@ def test_large_batch_mixed_efficiency(self): connection.queries_log.clear() TwoFields.objects.bulk_create( [ - TwoFields(id=i if i % 2 == 0 else None, f1=i, f2=i + 1) + TwoFields(id=f"{i:024}" if i % 2 == 0 else None, f1=i, f2=i + 1) for i in range(100000, 101000) ] ) diff --git a/tests/check_framework/test_model_checks.py b/tests/check_framework/test_model_checks.py index be504f9c2d..97b0373585 100644 --- a/tests/check_framework/test_model_checks.py +++ b/tests/check_framework/test_model_checks.py @@ -69,7 +69,9 @@ class Meta: ], ) - @modify_settings(INSTALLED_APPS={"append": "basic"}) + @modify_settings( + INSTALLED_APPS={"append": "basic", "remove": "django.contrib.sites"} + ) @isolate_apps("basic", "check_framework", kwarg_name="apps") def test_collision_across_apps(self, apps): class Model1(models.Model): @@ -94,7 +96,9 @@ class Meta: ], ) - @modify_settings(INSTALLED_APPS={"append": "basic"}) + @modify_settings( + INSTALLED_APPS={"append": "basic", "remove": "django.contrib.sites"} + ) @override_settings( DATABASE_ROUTERS=["check_framework.test_model_checks.EmptyRouter"] ) @@ -235,7 +239,9 @@ class Model2(AbstractModel): self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), []) - @modify_settings(INSTALLED_APPS={"append": "basic"}) + @modify_settings( + INSTALLED_APPS={"append": "basic", "remove": "django.contrib.sites"} + ) @isolate_apps("basic", "check_framework", kwarg_name="apps") def test_collision_across_apps(self, apps): index = models.Index(fields=["id"], name="foo") @@ -261,7 +267,9 @@ class Meta: ], ) - @modify_settings(INSTALLED_APPS={"append": "basic"}) + @modify_settings( + INSTALLED_APPS={"append": "basic", "remove": "django.contrib.sites"} + ) @isolate_apps("basic", "check_framework", kwarg_name="apps") def test_no_collision_across_apps_interpolation(self, apps): index = models.Index(fields=["id"], name="%(app_label)s_%(class)s_foo") @@ -367,7 +375,9 @@ class Model2(AbstractModel): self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), []) - @modify_settings(INSTALLED_APPS={"append": "basic"}) + @modify_settings( + INSTALLED_APPS={"append": "basic", "remove": "django.contrib.sites"} + ) @isolate_apps("basic", "check_framework", kwarg_name="apps") def test_collision_across_apps(self, apps): constraint = models.CheckConstraint(condition=models.Q(id__gt=0), name="foo") diff --git a/tests/contenttypes_tests/test_views.py b/tests/contenttypes_tests/test_views.py index 75f39a7bab..7d3034e1aa 100644 --- a/tests/contenttypes_tests/test_views.py +++ b/tests/contenttypes_tests/test_views.py @@ -27,7 +27,9 @@ class ContentTypesViewsTests(TestCase): def setUpTestData(cls): # Don't use the manager to ensure the site exists with pk=1, regardless # of whether or not it already exists. - cls.site1 = Site(pk=1, domain="testserver", name="testserver") + cls.site1 = Site( + pk="000000000000000000000001", domain="testserver", name="testserver" + ) cls.site1.save() cls.author1 = Author.objects.create(name="Boris") cls.article1 = Article.objects.create( @@ -178,7 +180,7 @@ def test_shortcut_view_with_site_m2m(self, get_model): # domains in the MockSite model. MockSite.objects.bulk_create( [ - MockSite(pk=1, domain="example.com"), + MockSite(pk="000000000000000000000001", domain="example.com"), MockSite(pk=self.site_2.pk, domain=self.site_2.domain), MockSite(pk=self.site_3.pk, domain=self.site_3.domain), ] diff --git a/tests/db_functions/comparison/test_coalesce.py b/tests/db_functions/comparison/test_coalesce.py index b08ae742df..cbb7bed1aa 100644 --- a/tests/db_functions/comparison/test_coalesce.py +++ b/tests/db_functions/comparison/test_coalesce.py @@ -67,9 +67,15 @@ def test_empty_queryset(self): queryset = Author.objects.values("id") tests = [ (queryset.none(), "QuerySet.none()"), - (queryset.filter(id=0), "QuerySet.filter(id=0)"), + ( + queryset.filter(id="000000000000000000000000"), + "QuerySet.filter(id=000000000000000000000000)", + ), (Subquery(queryset.none()), "Subquery(QuerySet.none())"), - (Subquery(queryset.filter(id=0)), "Subquery(Queryset.filter(id=0)"), + ( + Subquery(queryset.filter(id="000000000000000000000000")), + "Subquery(Queryset.filter(id000000000000000000000000)", + ), ] for empty_query, description in tests: with self.subTest(description), self.assertNumQueries(1): diff --git a/tests/delete_regress/models.py b/tests/delete_regress/models.py index b0e1e0b2a8..316a2dccf8 100644 --- a/tests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -91,7 +91,11 @@ class Item(models.Model): version = models.ForeignKey(Version, models.CASCADE) location = models.ForeignKey(Location, models.SET_NULL, blank=True, null=True) location_value = models.ForeignKey( - Location, models.SET(42), default=1, db_constraint=False, related_name="+" + Location, + models.SET("000000000000000000000042"), + default="000000000000000000000001", + db_constraint=False, + related_name="+", ) diff --git a/tests/delete_regress/tests.py b/tests/delete_regress/tests.py index ce5a0db8ab..2e2da1777a 100644 --- a/tests/delete_regress/tests.py +++ b/tests/delete_regress/tests.py @@ -115,7 +115,7 @@ def test_fk_to_m2m_through(self): self.assertEqual(PlayedWithNote.objects.count(), 0) def test_15776(self): - policy = Policy.objects.create(pk=1, policy_number="1234") + policy = Policy.objects.create(policy_number="1234") version = Version.objects.create(policy=policy) location = Location.objects.create(version=version) Item.objects.create(version=version, location=location) diff --git a/tests/expressions_case/tests.py b/tests/expressions_case/tests.py index 8704a7b991..d215a4fa1c 100644 --- a/tests/expressions_case/tests.py +++ b/tests/expressions_case/tests.py @@ -466,7 +466,7 @@ def test_condition_with_lookups(self): def test_case_reuse(self): SOME_CASE = Case( - When(pk=0, then=Value("0")), + When(pk="000000000000000000000000", then=Value("0")), default=Value("1"), ) self.assertQuerySetEqual( @@ -1360,7 +1360,7 @@ def test_join_promotion(self): self.assertQuerySetEqual( CaseTestModel.objects.filter(pk=o.pk).annotate( foo=Case( - When(fk_rel__pk=1, then=2), + When(fk_rel__pk="000000000000000000000001", then=2), default=3, ), ), @@ -1390,11 +1390,11 @@ def test_join_promotion_multiple_annotations(self): self.assertQuerySetEqual( CaseTestModel.objects.filter(pk=o.pk).annotate( foo=Case( - When(fk_rel__pk=1, then=2), + When(fk_rel__pk="000000000000000000000001", then=2), default=3, ), bar=Case( - When(fk_rel__pk=1, then=4), + When(fk_rel__pk="000000000000000000000001", then=4), default=5, ), ), diff --git a/tests/fixtures/fixtures/circular_reference.json b/tests/fixtures/fixtures/circular_reference.json index 0656c30c93..1ac092e251 100644 --- a/tests/fixtures/fixtures/circular_reference.json +++ b/tests/fixtures/fixtures/circular_reference.json @@ -1,18 +1,18 @@ [ { "model": "fixtures.circulara", - "pk": 1, + "pk": "000000000000000000000001", "fields": { "key": "x", - "obj": 1 + "obj": "000000000000000000000001" } }, { "model": "fixtures.circularb", - "pk": 1, + "pk": "000000000000000000000001", "fields": { "key": "y", - "obj": 1 + "obj": "000000000000000000000001" } } ] diff --git a/tests/fixtures/fixtures/db_fixture_1.default.json b/tests/fixtures/fixtures/db_fixture_1.default.json index 9bb39e400f..8d002bab44 100644 --- a/tests/fixtures/fixtures/db_fixture_1.default.json +++ b/tests/fixtures/fixtures/db_fixture_1.default.json @@ -1,10 +1,10 @@ [ { - "pk": "6", + "pk": "000000000000000000000006", "model": "fixtures.article", "fields": { "headline": "Who needs more than one database?", "pub_date": "2006-06-16 14:00:00" } } -] \ No newline at end of file +] diff --git a/tests/fixtures/fixtures/db_fixture_2.default.json.gz b/tests/fixtures/fixtures/db_fixture_2.default.json.gz index 80e4ba139f96c029cdb069b070f5b85998c5d395..2255f615123c369bcde2bf3f2250f9ec59237a19 100644 GIT binary patch literal 180 zcmV;l089TLiwFqxv8rbP17>M>bairNH!fslW?^+~bS`RhZ*BmK=28HHYA^``N(I?U zRtic6*nzo{4nk#aeoAT%NO4+bMM-HuH}M|v0MPh1)@gi0001Nr%P=B literal 175 zcmV;g08sxQiwFp}j|NKs17>M>bairNH!fslW?^+~bS`RhZ*BlhPC*L7Fc7@=70aGg zQjrS1_zVvsC3d$om^P(JpdkL;O$xTdFo)ThVIKtuK3NlRdSeZE#lvO|j@Tx*GfRjw z`;(r7X)W(VoncE}QrlMcd)8#l$f@L~d!7kM2YuTOuFu3*BZpi* dD^(qZWd-G>R!WHf^tV{``2!#v85__5002&SN+AFM diff --git a/tests/fixtures/fixtures/fixture1.json b/tests/fixtures/fixtures/fixture1.json index 332feaef77..aa2ea28eac 100644 --- a/tests/fixtures/fixtures/fixture1.json +++ b/tests/fixtures/fixtures/fixture1.json @@ -1,6 +1,6 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "sites.site", "fields": { "domain": "example.com", @@ -8,7 +8,7 @@ } }, { - "pk": "2", + "pk": "000000000000000000000002", "model": "fixtures.article", "fields": { "headline": "Poker has no place on ESPN", @@ -16,7 +16,7 @@ } }, { - "pk": "3", + "pk": "000000000000000000000003", "model": "fixtures.article", "fields": { "headline": "Time to reform copyright", @@ -24,7 +24,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures.category", "fields": { "description": "Latest news stories", diff --git a/tests/fixtures/fixtures/fixture2.json b/tests/fixtures/fixtures/fixture2.json index a697448327..e69148be9e 100644 --- a/tests/fixtures/fixtures/fixture2.json +++ b/tests/fixtures/fixtures/fixture2.json @@ -1,6 +1,6 @@ [ { - "pk": "3", + "pk": "000000000000000000000003", "model": "fixtures.article", "fields": { "headline": "Copyright is fine the way it is", @@ -8,7 +8,7 @@ } }, { - "pk": "4", + "pk": "000000000000000000000004", "model": "fixtures.article", "fields": { "headline": "Django conquers world!", diff --git a/tests/fixtures/fixtures/fixture3.xml b/tests/fixtures/fixtures/fixture3.xml index 9ced78162e..1f0325d768 100644 --- a/tests/fixtures/fixtures/fixture3.xml +++ b/tests/fixtures/fixtures/fixture3.xml @@ -1,11 +1,11 @@ - + Poker on TV is great! 2006-06-16 11:00:00 - + XML identified as leading cause of cancer 2006-06-16 16:00:00 - \ No newline at end of file + diff --git a/tests/fixtures/fixtures/fixture4.json.zip b/tests/fixtures/fixtures/fixture4.json.zip index 270cccb3ff71e61eb319978b1397ef95ef47388b..9b834cc53b89cc67ed5b1eb54aef809e361233d7 100644 GIT binary patch literal 286 zcmWIWW@Zs#W?r0VO(BpHyUS7;&V#PtMkg@^C*l;bM?#&%-R^26zG{&M`ls}8Q7^Ky^5su1)|IM9xo*x>Rb5$m z!p%V@V3x@INfAt8c_|4A%eY}>~C;$LB`+!7H2mnwAagYFtzi-~kAOMmPOieH&5ugAA z5K5Xfrkg0yjYHJYp^@q`JPUQK)(F`xl>o?rcwJ3C^nToDR1B2OR!PaCUdso;kwwAI znk8?+POWnXaHFJnwD0RmQK@!O%$XQ#1*L#F-7t*=3N?gI<9Jq^4k`jDni#5%eNr z>3}mN1InrIJz_k1y=rAIZ`e>?+S%=ZJS2i$m{*X#F=M+oX{SB`sl}-gfO>OUl)d52 U@R*M7_FV09aW^tpET3 diff --git a/tests/fixtures/fixtures/fixture5.json.gz b/tests/fixtures/fixtures/fixture5.json.gz index bb6baca66d2601108e7cbb69d20b00a796b0f2d2..b41a6d7cdf4774a29f4ae3eff29348b0fda95f7b 100644 GIT binary patch literal 173 zcmV;e08;-SiwFpIv8rbP17>M>bairNH7;s%Z*Bl>j@=5vFbsw7`xK$qDxDV_>_v7V zGTJr6V%JK4P!QkUv;(&b58*(jTm8zu6aw1`>;M1&<3CVc literal 169 zcmV;a09OAWiwFpXx8g_u17>M>bairNH7;s%Z*Blpj?D_fFc5_AeTrqzDyfGS`XW7u zNYZREm?n}R6vTITlY-S@7}#O>*i!?nSIr{4-*5ozaR*zsculs{vw5Z*C>~@;X0r6R znhjYUdA8ANeVhLKG>qT)#tYQ~V2d?pHc oveDfEgh!7V%&u`lOW; z_5C)nwW>LvM4RP}vfmUAgxh+oPsgnehXSf za74w6ckC0Em_sZai!|nOh*a&pm@tNsO*^9ENfTg literal 200 zcmV;(05|{rH+ooF000E$*0e?f03iVk0001wm_eN20M-DAT>veDfEgh!7V%&u`lOW; z_6t~{K~CS6{X!LBkL<#(ThQ-Dr<3>6bc)Y^)d&sd9ut{p|Jb9E0sL~JKvtDXCcX#G z1DJqetZxO8F}85JeCe1a^C2e)AqZ^6C(yC4#b9gV>^4{6_((>q{u)Fg+<=upA)x%a z`MD~(XYq+IiZm4E=I8q`mP=dG0h0i*%f0RRAkMft|D#Ao{g000001X)_3 CXj#4h diff --git a/tests/fixtures/fixtures/fixture5.json.zip b/tests/fixtures/fixtures/fixture5.json.zip index 9380cef608e4fa5f102203f56973d5b24f381492..8c10891cf6e16228bdb9c31ace149209f3af00c9 100644 GIT binary patch literal 301 zcmWIWW@Zs#W?20X{! z*LGdl?DagcdG856rAzAsyJNNgHaGovw=C@1X*(x{llt*RNe)%P>mK!6%QtrC^445G zaWs1i>%Ljxrx+hQwxy(X^J*Dyu@VaPU-MVmE4TOXCi|b9NnwS2ZiyWa{s`Cj>FI|! zDNNc`fB9Q_n7Hoc^A9v!_Ah@?^`b41gKOU<`LNG3YZ(K)8JX;vak*Rt=yniLXjsw+ qqEY<8#h?HZV_;;EU|`7N)zGTB&4^A1c(bxW)G#u{0~tF&90mZsqhcNa literal 295 zcmWIWW@Zs#U|`^2@SkF5Ir;G8noc0^IuP?Rh%%&QR+N+$rJCwx73b%LhHx@4w{Cyr zp}ysjM`#5L!&gQThS0vVT!#zhb3I>J%Z&o&t3PvFG0MaKx90mY`=x9p- diff --git a/tests/fixtures/fixtures/fixture6.json b/tests/fixtures/fixtures/fixture6.json index 60e4733c71..32a0c1f66a 100644 --- a/tests/fixtures/fixtures/fixture6.json +++ b/tests/fixtures/fixtures/fixture6.json @@ -1,38 +1,38 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures.tag", "fields": { "name": "copyright", "tagged_type": ["fixtures", "article"], - "tagged_id": "3" + "tagged_id": "000000000000000000000003" } }, { - "pk": "2", + "pk": "000000000000000000000002", "model": "fixtures.tag", "fields": { "name": "law", "tagged_type": ["fixtures", "article"], - "tagged_id": "3" + "tagged_id": "000000000000000000000003" } }, { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures.person", "fields": { "name": "Django Reinhardt" } }, { - "pk": "2", + "pk": "000000000000000000000002", "model": "fixtures.person", "fields": { "name": "Stephane Grappelli" } }, { - "pk": "3", + "pk": "000000000000000000000003", "model": "fixtures.person", "fields": { "name": "Prince" diff --git a/tests/fixtures/fixtures/fixture8.json b/tests/fixtures/fixtures/fixture8.json index bc113aa00e..51aad74e87 100644 --- a/tests/fixtures/fixtures/fixture8.json +++ b/tests/fixtures/fixtures/fixture8.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures.visa", "fields": { "person": ["Django Reinhardt"], @@ -12,7 +12,7 @@ } }, { - "pk": "2", + "pk": "000000000000000000000002", "model": "fixtures.visa", "fields": { "person": ["Stephane Grappelli"], @@ -22,7 +22,7 @@ } }, { - "pk": "3", + "pk": "000000000000000000000003", "model": "fixtures.visa", "fields": { "person": ["Prince"], diff --git a/tests/fixtures/fixtures/fixture_with[special]chars.json b/tests/fixtures/fixtures/fixture_with[special]chars.json index b6b7ad2a7c..1e01f6aa88 100644 --- a/tests/fixtures/fixtures/fixture_with[special]chars.json +++ b/tests/fixtures/fixtures/fixture_with[special]chars.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures.article", "fields": { "headline": "How To Deal With Special Characters", diff --git a/tests/fixtures/fixtures/forward_reference_fk.json b/tests/fixtures/fixtures/forward_reference_fk.json index c553d2b487..6c20c7fab9 100644 --- a/tests/fixtures/fixtures/forward_reference_fk.json +++ b/tests/fixtures/fixtures/forward_reference_fk.json @@ -1,18 +1,18 @@ [ { "model": "fixtures.naturalkeything", - "pk": 1, + "pk": "000000000000000000000001", "fields": { "key": "t1", - "other_thing": 2 + "other_thing": "000000000000000000000002" } }, { "model": "fixtures.naturalkeything", - "pk": 2, + "pk": "000000000000000000000002", "fields": { "key": "t2", - "other_thing": 1 + "other_thing": "000000000000000000000001" } } ] diff --git a/tests/fixtures/fixtures/forward_reference_m2m.json b/tests/fixtures/fixtures/forward_reference_m2m.json index 927bac62b6..b91f6dfae9 100644 --- a/tests/fixtures/fixtures/forward_reference_m2m.json +++ b/tests/fixtures/fixtures/forward_reference_m2m.json @@ -1,22 +1,22 @@ [ { "model": "fixtures.naturalkeything", - "pk": 1, + "pk": "000000000000000000000001", "fields": { "key": "t1", - "other_things": [2, 3] + "other_things": ["000000000000000000000002", "000000000000000000000003"] } }, { "model": "fixtures.naturalkeything", - "pk": 2, + "pk": "000000000000000000000002", "fields": { "key": "t2" } }, { "model": "fixtures.naturalkeything", - "pk": 3, + "pk": "000000000000000000000003", "fields": { "key": "t3" } diff --git a/tests/fixtures/fixtures/invalid.json b/tests/fixtures/fixtures/invalid.json index fb69f7c949..61f2a7908c 100644 --- a/tests/fixtures/fixtures/invalid.json +++ b/tests/fixtures/fixtures/invalid.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures.article", "fields": { "headline": "Breaking news", diff --git a/tests/fixtures/fixtures/null_character_in_field_value.json b/tests/fixtures/fixtures/null_character_in_field_value.json index 7b246a0544..9092a27a74 100644 --- a/tests/fixtures/fixtures/null_character_in_field_value.json +++ b/tests/fixtures/fixtures/null_character_in_field_value.json @@ -1,6 +1,6 @@ [ { - "pk": "2", + "pk": "000000000000000000000002", "model": "fixtures.article", "fields": { "headline": "Poker has no place on ESPN\u0000", diff --git a/tests/fixtures/models.py b/tests/fixtures/models.py index c87e170afc..b0f1adbfa7 100644 --- a/tests/fixtures/models.py +++ b/tests/fixtures/models.py @@ -10,6 +10,8 @@ import uuid +from django_mongodb_backend.fields import ObjectIdField + from django.contrib.auth.models import Permission from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -56,7 +58,7 @@ class Tag(models.Model): tagged_type = models.ForeignKey( ContentType, models.CASCADE, related_name="fixtures_tag_set" ) - tagged_id = models.PositiveIntegerField(default=0) + tagged_id = ObjectIdField(default="000000000000000000000000") tagged = GenericForeignKey(ct_field="tagged_type", fk_field="tagged_id") def __str__(self): diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index dfb1cd05bb..aa1b1df404 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -145,12 +145,15 @@ def test_loading_and_dumping(self): # Dump the current contents of the database as a JSON fixture self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -158,17 +161,20 @@ def test_loading_and_dumping(self): # Try just dumping the contents of fixtures.Category self._dumpdata_assert( ["fixtures.Category"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}]', ) # ...and just fixtures.Article self._dumpdata_assert( ["fixtures.Article"], - '[{"pk": 2, "model": "fixtures.article", "fields": ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -176,12 +182,15 @@ def test_loading_and_dumping(self): # ...and both self._dumpdata_assert( ["fixtures.Category", "fixtures.Article"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -190,10 +199,12 @@ def test_loading_and_dumping(self): self._dumpdata_assert( ["fixtures.Article", "fixtures.Article"], ( - '[{"pk": 2, "model": "fixtures.article", "fields": ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]' ), @@ -202,12 +213,15 @@ def test_loading_and_dumping(self): # Specify a dump that specifies Article both explicitly and implicitly self._dumpdata_assert( ["fixtures.Article", "fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -216,12 +230,15 @@ def test_loading_and_dumping(self): # but lists the app first (#22025). self._dumpdata_assert( ["fixtures", "fixtures.Article"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -229,12 +246,15 @@ def test_loading_and_dumping(self): # Same again, but specify in the reverse order self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -242,9 +262,10 @@ def test_loading_and_dumping(self): # Specify one model from one application, and an entire other application. self._dumpdata_assert( ["fixtures.Category", "sites"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 1, "model": "sites.site", "fields": ' + '{"pk": "000000000000000000000001", "model": "sites.site", "fields": ' '{"domain": "example.com", "name": "example.com"}}]', ) @@ -340,14 +361,14 @@ def test_loading_and_dumping(self): # By default, you get raw keys on dumpdata self._dumpdata_assert( ["fixtures.book"], - '[{"pk": 1, "model": "fixtures.book", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.book", "fields": ' '{"name": "Music for all ages", "authors": [3, 1]}}]', ) # But you can get natural keys if you ask for them and they are available self._dumpdata_assert( ["fixtures.book"], - '[{"pk": 1, "model": "fixtures.book", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.book", "fields": ' '{"name": "Music for all ages", "authors": ' '[["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_foreign_keys=True, @@ -367,49 +388,59 @@ def test_loading_and_dumping(self): # Dump the current contents of the database as a JSON fixture self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker on TV is great!", ' '"pub_date": "2006-06-16T11:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Copyright is fine the way it is", ' '"pub_date": "2006-06-16T14:00:00"}}, ' - '{"pk": 4, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000004", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Django conquers world!", ' '"pub_date": "2006-06-16T15:00:00"}}, ' '{"pk": 5, "model": "fixtures.article", "fields": ' '{"headline": "XML identified as leading cause of cancer", ' '"pub_date": "2006-06-16T16:00:00"}}, ' - '{"pk": 1, "model": "fixtures.tag", "fields": ' + '{"pk": "000000000000000000000001", "model": "fixtures.tag",' + ' "fields": ' '{"tagged_type": ["fixtures", "article"], "name": "copyright", ' '"tagged_id": 3}}, ' - '{"pk": 2, "model": "fixtures.tag", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.tag",' + ' "fields": ' '{"tagged_type": ["fixtures", "article"], "name": "legal", ' '"tagged_id": 3}}, ' - '{"pk": 3, "model": "fixtures.tag", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.tag",' + ' "fields": ' '{"tagged_type": ["fixtures", "article"], "name": "django", ' '"tagged_id": 4}}, ' - '{"pk": 4, "model": "fixtures.tag", "fields": ' + '{"pk": "000000000000000000000004", "model": "fixtures.tag",' + ' "fields": ' '{"tagged_type": ["fixtures", "article"], "name": "world domination", ' '"tagged_id": 4}}, ' - '{"pk": 1, "model": "fixtures.person", ' + '{"pk": "000000000000000000000001", "model": "fixtures.person", ' '"fields": {"name": "Django Reinhardt"}}, ' - '{"pk": 2, "model": "fixtures.person", ' + '{"pk": "000000000000000000000002", "model": "fixtures.person", ' '"fields": {"name": "Stephane Grappelli"}}, ' - '{"pk": 3, "model": "fixtures.person", ' + '{"pk": "000000000000000000000003", "model": "fixtures.person", ' '"fields": {"name": "Artist formerly known as \\"Prince\\""}}, ' - '{"pk": 1, "model": "fixtures.visa", ' + '{"pk": "000000000000000000000001", "model": "fixtures.visa", ' '"fields": {"person": ["Django Reinhardt"], "permissions": ' '[["add_user", "auth", "user"], ["change_user", "auth", "user"], ' '["delete_user", "auth", "user"]]}}, ' - '{"pk": 2, "model": "fixtures.visa", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.visa",' + ' "fields": ' '{"person": ["Stephane Grappelli"], "permissions": ' '[["add_user", "auth", "user"], ["delete_user", "auth", "user"]]}}, ' - '{"pk": 3, "model": "fixtures.visa", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.visa", "fields": ' '{"person": ["Artist formerly known as \\"Prince\\""], "permissions": ' '[["change_user", "auth", "user"]]}}, ' - '{"pk": 1, "model": "fixtures.book", "fields": ' + '{"pk": "000000000000000000000001", "model": "fixtures.book",' + ' "fields": ' '{"name": "Music for all ages", "authors": ' '[["Artist formerly known as \\"Prince\\""], ["Django Reinhardt"]]}}]', natural_foreign_keys=True, @@ -522,7 +553,7 @@ def test_dumpdata_with_excludes(self): # Excluding fixtures app should only leave sites self._dumpdata_assert( ["sites", "fixtures"], - '[{"pk": 1, "model": "sites.site", "fields": ' + '[{"pk": "000000000000000000000001", "model": "sites.site", "fields": ' '{"domain": "example.com", "name": "example.com"}}]', exclude_list=["fixtures"], ) @@ -530,9 +561,10 @@ def test_dumpdata_with_excludes(self): # Excluding fixtures.Article/Book should leave fixtures.Category self._dumpdata_assert( ["sites", "fixtures"], - '[{"pk": 1, "model": "sites.site", ' + '[{"pk": "000000000000000000000001", "model": "sites.site", ' '"fields": {"domain": "example.com", "name": "example.com"}}, ' - '{"pk": 1, "model": "fixtures.category", "fields": ' + '{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}]', exclude_list=["fixtures.Article", "fixtures.Book"], ) @@ -540,9 +572,9 @@ def test_dumpdata_with_excludes(self): # Excluding fixtures and fixtures.Article/Book should be a no-op self._dumpdata_assert( ["sites", "fixtures"], - '[{"pk": 1, "model": "sites.site", ' + '[{"pk": "000000000000000000000001", "model": "sites.site", ' '"fields": {"domain": "example.com", "name": "example.com"}}, ' - '{"pk": 1, "model": "fixtures.category", ' + '{"pk": "000000000000000000000001", "model": "fixtures.category", ' '"fields": {"description": "Latest news stories", ' '"title": "News Stories"}}]', exclude_list=["fixtures.Article", "fixtures.Book"], @@ -551,7 +583,8 @@ def test_dumpdata_with_excludes(self): # Excluding sites and fixtures.Article/Book should only leave fixtures.Category self._dumpdata_assert( ["sites", "fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}]', exclude_list=["fixtures.Article", "fixtures.Book", "sites"], ) @@ -605,21 +638,21 @@ def test_dumpdata_with_pks(self): management.call_command("loaddata", "fixture2.json", verbosity=0) self._dumpdata_assert( ["fixtures.Article"], - '[{"pk": 2, "model": "fixtures.article", ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article", ' '"fields": {"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article", "fields": ' '{"headline": "Copyright is fine the way it is", ' '"pub_date": "2006-06-16T14:00:00"}}]', - primary_keys="2,3", + primary_keys="000000000000000000000002,000000000000000000000003", ) self._dumpdata_assert( ["fixtures.Article"], - '[{"pk": 2, "model": "fixtures.article", ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article", ' '"fields": {"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}]', - primary_keys="2", + primary_keys="000000000000000000000002", ) with self.assertRaisesMessage( @@ -627,10 +660,12 @@ def test_dumpdata_with_pks(self): ): self._dumpdata_assert( ["fixtures"], - '[{"pk": 2, "model": "fixtures.article", "fields": ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Copyright is fine the way it is", ' '"pub_date": "2006-06-16T14:00:00"}}]', primary_keys="2,3", @@ -641,10 +676,12 @@ def test_dumpdata_with_pks(self): ): self._dumpdata_assert( "", - '[{"pk": 2, "model": "fixtures.article", "fields": ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Copyright is fine the way it is", ' '"pub_date": "2006-06-16T14:00:00"}}]', primary_keys="2,3", @@ -655,10 +692,12 @@ def test_dumpdata_with_pks(self): ): self._dumpdata_assert( ["fixtures.Article", "fixtures.category"], - '[{"pk": 2, "model": "fixtures.article", "fields": ' + '[{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Copyright is fine the way it is", ' '"pub_date": "2006-06-16T14:00:00"}}]', primary_keys="2,3", @@ -683,12 +722,15 @@ def test_dumpdata_with_file_output(self): management.call_command("loaddata", "fixture1.json", verbosity=0) self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', filename="dumpdata.json", @@ -698,12 +740,15 @@ def test_dumpdata_with_file_gzip_output(self): management.call_command("loaddata", "fixture1.json", verbosity=0) self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', filename="dumpdata.json.gz", @@ -714,12 +759,15 @@ def test_dumpdata_with_file_bz2_output(self): management.call_command("loaddata", "fixture1.json", verbosity=0) self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', filename="dumpdata.json.bz2", @@ -730,12 +778,15 @@ def test_dumpdata_with_file_lzma_output(self): management.call_command("loaddata", "fixture1.json", verbosity=0) self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', filename="dumpdata.json.lzma", @@ -746,12 +797,15 @@ def test_dumpdata_with_file_xz_output(self): management.call_command("loaddata", "fixture1.json", verbosity=0) self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', filename="dumpdata.json.xz", @@ -763,12 +817,15 @@ def test_dumpdata_with_file_zip_output(self): with self.assertWarnsMessage(RuntimeWarning, msg): self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', filename="dumpdata.json.zip", @@ -928,14 +985,14 @@ def test_loaddata_error_message(self): if connection.vendor == "mysql": with connection.cursor() as cursor: cursor.execute("SET sql_mode = 'TRADITIONAL'") - msg = "Could not load fixtures.Article(pk=1):" + msg = "Could not load fixtures.Article(pk=000000000000000000000001):" with self.assertRaisesMessage(IntegrityError, msg): management.call_command("loaddata", "invalid.json", verbosity=0) @skipUnlessDBFeature("prohibits_null_characters_in_text_exception") def test_loaddata_null_characters_on_postgresql(self): error, msg = connection.features.prohibits_null_characters_in_text_exception - msg = f"Could not load fixtures.Article(pk=2): {msg}" + msg = f"Could not load fixtures.Article(pk=000000000000000000000002): {msg}" with self.assertRaisesMessage(error, msg): management.call_command("loaddata", "null_character_in_field_value.json") @@ -1018,24 +1075,32 @@ def test_output_formats(self): # Dump the current contents of the database as a JSON fixture self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}, ' - '{"pk": 1, "model": "fixtures.tag", "fields": ' + '{"pk": "000000000000000000000001", "model": "fixtures.tag", "fields": ' '{"tagged_type": ["fixtures", "article"], "name": "copyright", ' - '"tagged_id": 3}}, ' - '{"pk": 2, "model": "fixtures.tag", "fields": ' - '{"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, ' - '{"pk": 1, "model": "fixtures.person", "fields": ' + '"tagged_id": "000000000000000000000003"}}, ' + '{"pk": "000000000000000000000002", "model": "fixtures.tag", "fields": ' + '{"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": ' + '"000000000000000000000003"}}, ' + '{"pk": "000000000000000000000001", "model": "fixtures.person",' + ' "fields": ' '{"name": "Django Reinhardt"}}, ' - '{"pk": 2, "model": "fixtures.person", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.person",' + ' "fields": ' '{"name": "Stephane Grappelli"}}, ' - '{"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}]', + '{"pk": "000000000000000000000003", "model": "fixtures.person",' + ' "fields": ' + '{"name": "Prince"}}]', natural_foreign_keys=True, ) @@ -1043,39 +1108,41 @@ def test_output_formats(self): self._dumpdata_assert( ["fixtures"], '' - '' + '' 'News Stories' 'Latest news stories' "" - '' + '' 'Poker has no place on ESPN' '2006-06-16T12:00:00' "" - '' + '' 'Time to reform copyright' '2006-06-16T13:00:00' "" - '' + '' 'copyright' 'fixtures' "article" - '3' + '000000000000000000000003' + "" "" - '' + '' 'law' 'fixtures' "article" - '3' + '000000000000000000000003' + "" "" - '' + '' 'Django Reinhardt' "" - '' + '' 'Stephane Grappelli' "" - '' + '' 'Prince' "", format="xml", @@ -1212,12 +1279,15 @@ def test_format_discovery(self): # Dump the current contents of the database as a JSON fixture self._dumpdata_assert( ["fixtures"], - '[{"pk": 1, "model": "fixtures.category", "fields": ' + '[{"pk": "000000000000000000000001", "model": "fixtures.category",' + ' "fields": ' '{"description": "Latest news stories", "title": "News Stories"}}, ' - '{"pk": 2, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000002", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Poker has no place on ESPN", ' '"pub_date": "2006-06-16T12:00:00"}}, ' - '{"pk": 3, "model": "fixtures.article", "fields": ' + '{"pk": "000000000000000000000003", "model": "fixtures.article",' + ' "fields": ' '{"headline": "Time to reform copyright", ' '"pub_date": "2006-06-16T13:00:00"}}]', ) @@ -1242,10 +1312,12 @@ def test_forward_reference_fk(self): self.assertEqual(t2.other_thing, t1) self._dumpdata_assert( ["fixtures"], - '[{"model": "fixtures.naturalkeything", "pk": 1, ' - '"fields": {"key": "t1", "other_thing": 2, "other_things": []}}, ' - '{"model": "fixtures.naturalkeything", "pk": 2, ' - '"fields": {"key": "t2", "other_thing": 1, "other_things": []}}]', + '[{"model": "fixtures.naturalkeything", "pk": "000000000000000000000001", ' + '"fields": {"key": "t1", "other_thing": "000000000000000000000002",' + ' "other_things": []}}, ' + '{"model": "fixtures.naturalkeything", "pk": "000000000000000000000002", ' + '"fields": {"key": "t2", "other_thing": "000000000000000000000001",' + ' "other_things": []}}]', ) def test_forward_reference_fk_natural_key(self): @@ -1277,11 +1349,12 @@ def test_forward_reference_m2m(self): ) self._dumpdata_assert( ["fixtures"], - '[{"model": "fixtures.naturalkeything", "pk": 1, ' - '"fields": {"key": "t1", "other_thing": null, "other_things": [2, 3]}}, ' - '{"model": "fixtures.naturalkeything", "pk": 2, ' + '[{"model": "fixtures.naturalkeything", "pk": "000000000000000000000001", ' + '"fields": {"key": "t1", "other_thing": null, "other_things": ' + '["000000000000000000000002", "000000000000000000000003"]}}, ' + '{"model": "fixtures.naturalkeything", "pk": "000000000000000000000002", ' '"fields": {"key": "t2", "other_thing": null, "other_things": []}}, ' - '{"model": "fixtures.naturalkeything", "pk": 3, ' + '{"model": "fixtures.naturalkeything", "pk": "000000000000000000000003", ' '"fields": {"key": "t3", "other_thing": null, "other_things": []}}]', ) @@ -1320,10 +1393,10 @@ def test_circular_reference(self): self.assertEqual(obj_b.obj, obj_a) self._dumpdata_assert( ["fixtures"], - '[{"model": "fixtures.circulara", "pk": 1, ' - '"fields": {"key": "x", "obj": 1}}, ' - '{"model": "fixtures.circularb", "pk": 1, ' - '"fields": {"key": "y", "obj": 1}}]', + '[{"model": "fixtures.circulara", "pk": "000000000000000000000001", ' + '"fields": {"key": "x", "obj": "000000000000000000000001"}}, ' + '{"model": "fixtures.circularb", "pk": "000000000000000000000001", ' + '"fields": {"key": "y", "obj": "000000000000000000000001"}}]', ) def test_circular_reference_natural_key(self): diff --git a/tests/fixtures_regress/fixtures/absolute.json b/tests/fixtures_regress/fixtures/absolute.json index bdf889d333..213e47b1ab 100644 --- a/tests/fixtures_regress/fixtures/absolute.json +++ b/tests/fixtures_regress/fixtures/absolute.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.absolute", "fields": { "name": "Load Absolute Path Test" diff --git a/tests/fixtures_regress/fixtures/animal.xml b/tests/fixtures_regress/fixtures/animal.xml index 0383c60fc1..b657e691aa 100644 --- a/tests/fixtures_regress/fixtures/animal.xml +++ b/tests/fixtures_regress/fixtures/animal.xml @@ -1,9 +1,9 @@ - + Emu Dromaius novaehollandiae 42 1.2 - \ No newline at end of file + diff --git a/tests/fixtures_regress/fixtures/big-fixture.json b/tests/fixtures_regress/fixtures/big-fixture.json index 41bd33c6b5..4c4ed56d5a 100644 --- a/tests/fixtures_regress/fixtures/big-fixture.json +++ b/tests/fixtures_regress/fixtures/big-fixture.json @@ -1,6 +1,6 @@ [ { - "pk": 6, + "pk": "000000000000000000000006", "model": "fixtures_regress.channel", "fields": { "name": "Business" @@ -8,76 +8,76 @@ }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.article", "fields": { "title": "Article Title 1", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "fixtures_regress.article", "fields": { "title": "Article Title 2", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "fixtures_regress.article", "fields": { "title": "Article Title 3", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "fixtures_regress.article", "fields": { "title": "Article Title 4", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "fixtures_regress.article", "fields": { "title": "Article Title 5", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 6, + "pk": "000000000000000000000006", "model": "fixtures_regress.article", "fields": { "title": "Article Title 6", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 7, + "pk": "000000000000000000000007", "model": "fixtures_regress.article", "fields": { "title": "Article Title 7", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 8, + "pk": "000000000000000000000008", "model": "fixtures_regress.article", "fields": { "title": "Article Title 8", - "channels": [6] + "channels": ["000000000000000000000006"] } }, { - "pk": 9, + "pk": "000000000000000000000009", "model": "fixtures_regress.article", "fields": { "title": "Yet Another Article", - "channels": [6] + "channels": ["000000000000000000000006"] } } -] \ No newline at end of file +] diff --git a/tests/fixtures_regress/fixtures/feature.json b/tests/fixtures_regress/fixtures/feature.json index 84aa2adcf4..43d1b1c27f 100644 --- a/tests/fixtures_regress/fixtures/feature.json +++ b/tests/fixtures_regress/fixtures/feature.json @@ -5,13 +5,13 @@ "title": "Title of this feature article" }, "model": "fixtures_regress.article", - "pk": 1 + "pk": "000000000000000000000001" }, { "fields": { "channels": [] }, "model": "fixtures_regress.feature", - "pk": 1 + "pk": "000000000000000000000001" } ] diff --git a/tests/fixtures_regress/fixtures/forward_ref.json b/tests/fixtures_regress/fixtures/forward_ref.json index 237b076243..2370126efc 100644 --- a/tests/fixtures_regress/fixtures/forward_ref.json +++ b/tests/fixtures_regress/fixtures/forward_ref.json @@ -1,17 +1,17 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.book", "fields": { "name": "Cryptonomicon", - "author": 4 + "author": "000000000000000000000004" } }, { - "pk": "4", + "pk": "000000000000000000000004", "model": "fixtures_regress.person", "fields": { "name": "Neal Stephenson" } } -] \ No newline at end of file +] diff --git a/tests/fixtures_regress/fixtures/forward_ref_bad_data.json b/tests/fixtures_regress/fixtures/forward_ref_bad_data.json index 3a3fb64360..e36f73786e 100644 --- a/tests/fixtures_regress/fixtures/forward_ref_bad_data.json +++ b/tests/fixtures_regress/fixtures/forward_ref_bad_data.json @@ -1,6 +1,6 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.book", "fields": { "name": "Cryptonomicon", @@ -8,7 +8,7 @@ } }, { - "pk": "4", + "pk": "000000000000000000000004", "model": "fixtures_regress.person", "fields": { "name": "Neal Stephenson" diff --git a/tests/fixtures_regress/fixtures/forward_ref_lookup.json b/tests/fixtures_regress/fixtures/forward_ref_lookup.json index 42e8ec0877..5336a1dcda 100644 --- a/tests/fixtures_regress/fixtures/forward_ref_lookup.json +++ b/tests/fixtures_regress/fixtures/forward_ref_lookup.json @@ -1,13 +1,13 @@ [ { - "pk": "4", + "pk": "000000000000000000000004", "model": "fixtures_regress.person", "fields": { "name": "Neal Stephenson" } }, { - "pk": "2", + "pk": "000000000000000000000002", "model": "fixtures_regress.store", "fields": { "main": null, @@ -15,7 +15,7 @@ } }, { - "pk": "3", + "pk": "000000000000000000000003", "model": "fixtures_regress.store", "fields": { "main": null, @@ -23,7 +23,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.book", "fields": { "name": "Cryptonomicon", diff --git a/tests/fixtures_regress/fixtures/m2mtoself.json b/tests/fixtures_regress/fixtures/m2mtoself.json index b904ba36e0..592b8f0a9f 100644 --- a/tests/fixtures_regress/fixtures/m2mtoself.json +++ b/tests/fixtures_regress/fixtures/m2mtoself.json @@ -1 +1 @@ -[{"fields": {"parent": [1]}, "model": "fixtures_regress.m2mtoself", "pk": 1}] +[{"fields": {"parent": ["000000000000000000000001"]}, "model": "fixtures_regress.m2mtoself", "pk": "000000000000000000000001"}] diff --git a/tests/fixtures_regress/fixtures/model-inheritance.json b/tests/fixtures_regress/fixtures/model-inheritance.json index 00c482b3dd..304ad6eb5f 100644 --- a/tests/fixtures_regress/fixtures/model-inheritance.json +++ b/tests/fixtures_regress/fixtures/model-inheritance.json @@ -1,4 +1,4 @@ [ - {"pk": 1, "model": "fixtures_regress.parent", "fields": {"name": "fred"}}, - {"pk": 1, "model": "fixtures_regress.child", "fields": {"data": "apple"}} + {"pk": "000000000000000000000001", "model": "fixtures_regress.parent", "fields": {"name": "fred"}}, + {"pk": "000000000000000000000001", "model": "fixtures_regress.child", "fields": {"data": "apple"}} ] diff --git a/tests/fixtures_regress/fixtures/nk-inheritance.json b/tests/fixtures_regress/fixtures/nk-inheritance.json index 08e5d4feee..eb654f25e1 100644 --- a/tests/fixtures_regress/fixtures/nk-inheritance.json +++ b/tests/fixtures_regress/fixtures/nk-inheritance.json @@ -1,13 +1,13 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.nkchild", "fields": { "data": "apple" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.reftonkchild", "fields": { "text": "my text", diff --git a/tests/fixtures_regress/fixtures/nk-inheritance2.xml b/tests/fixtures_regress/fixtures/nk-inheritance2.xml index 7eb17a6b7e..c792359114 100644 --- a/tests/fixtures_regress/fixtures/nk-inheritance2.xml +++ b/tests/fixtures_regress/fixtures/nk-inheritance2.xml @@ -1,12 +1,12 @@ - + james - + banana - + other text apple @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/tests/fixtures_regress/fixtures/non_natural_1.json b/tests/fixtures_regress/fixtures/non_natural_1.json index 4bce792e35..1c43677d49 100644 --- a/tests/fixtures_regress/fixtures/non_natural_1.json +++ b/tests/fixtures_regress/fixtures/non_natural_1.json @@ -1,25 +1,25 @@ [ { - "pk": 12, + "pk": "000000000000000000000012", "model": "fixtures_regress.person", "fields": { "name": "Greg Egan" } }, { - "pk": 11, + "pk": "000000000000000000000011", "model": "fixtures_regress.store", "fields": { "name": "Angus and Robertson" } }, { - "pk": 10, + "pk": "000000000000000000000010", "model": "fixtures_regress.book", "fields": { "name": "Permutation City", - "author": 12, - "stores": [11] + "author": "000000000000000000000012", + "stores": ["000000000000000000000011"] } } -] \ No newline at end of file +] diff --git a/tests/fixtures_regress/fixtures/non_natural_2.xml b/tests/fixtures_regress/fixtures/non_natural_2.xml index 280ad3758b..a1de7907c0 100644 --- a/tests/fixtures_regress/fixtures/non_natural_2.xml +++ b/tests/fixtures_regress/fixtures/non_natural_2.xml @@ -1,16 +1,16 @@ - + Orson Scott Card - + Collins Bookstore - + Ender's Game - 22 + 000000000000000000000022 - + - \ No newline at end of file + diff --git a/tests/fixtures_regress/fixtures/path.containing.dots.json b/tests/fixtures_regress/fixtures/path.containing.dots.json index d62ac03fff..9f55585f44 100644 --- a/tests/fixtures_regress/fixtures/path.containing.dots.json +++ b/tests/fixtures_regress/fixtures/path.containing.dots.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.absolute", "fields": { "name": "Load Absolute Path Test" diff --git a/tests/fixtures_regress/fixtures/pretty.xml b/tests/fixtures_regress/fixtures/pretty.xml index 68e5710c6a..dc7545cb54 100644 --- a/tests/fixtures_regress/fixtures/pretty.xml +++ b/tests/fixtures_regress/fixtures/pretty.xml @@ -1,6 +1,6 @@ - + @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/tests/fixtures_regress/fixtures/sequence.json b/tests/fixtures_regress/fixtures/sequence.json index c45ea9420c..bdac5a0550 100644 --- a/tests/fixtures_regress/fixtures/sequence.json +++ b/tests/fixtures_regress/fixtures/sequence.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.animal", "fields": { "name": "Lion", diff --git a/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl b/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl index c8ac372cab..785e58dcd9 100644 --- a/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl +++ b/tests/fixtures_regress/fixtures/sequence_empty_lines_jsonl.jsonl @@ -1,3 +1,3 @@ -{"pk": "1", "model": "fixtures_regress.animal", "fields": {"name": "Eagle", "latin_name": "Aquila", "count": 3, "weight": 1.2}} +{"pk": "000000000000000000000001", "model": "fixtures_regress.animal", "fields": {"name": "Eagle", "latin_name": "Aquila", "count": 3, "weight": 1.2}} diff --git a/tests/fixtures_regress/fixtures/sequence_extra.json b/tests/fixtures_regress/fixtures/sequence_extra.json index 880aff8c24..fc4705c98b 100644 --- a/tests/fixtures_regress/fixtures/sequence_extra.json +++ b/tests/fixtures_regress/fixtures/sequence_extra.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.animal", "fields": { "name": "Lion", @@ -11,7 +11,7 @@ } }, { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.animal_extra", "fields": { "name": "Nonexistent model", diff --git a/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl b/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl index 6644eaf95d..db5e381123 100644 --- a/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl +++ b/tests/fixtures_regress/fixtures/sequence_extra_jsonl.jsonl @@ -1,2 +1,2 @@ -{"pk": "1", "model": "fixtures_regress.animal", "fields": {"name": "Eagle", "extra_name": "Super Eagle", "latin_name": "Aquila", "count": 3, "weight": 1.2}} -{"pk": "1", "model": "fixtures_regress.animal_extra", "fields": {"name": "Nonexistent model", "extra_name": "test for ticket #29522", "latin_name": "Aquila", "count": 3, "weight": 1.2}} +{"pk": "000000000000000000000001", "model": "fixtures_regress.animal", "fields": {"name": "Eagle", "extra_name": "Super Eagle", "latin_name": "Aquila", "count": 3, "weight": 1.2}} +{"pk": "000000000000000000000001", "model": "fixtures_regress.animal_extra", "fields": {"name": "Nonexistent model", "extra_name": "test for ticket #29522", "latin_name": "Aquila", "count": 3, "weight": 1.2}} diff --git a/tests/fixtures_regress/fixtures/sequence_extra_xml.xml b/tests/fixtures_regress/fixtures/sequence_extra_xml.xml index dd2ee7c28f..710501d6a5 100644 --- a/tests/fixtures_regress/fixtures/sequence_extra_xml.xml +++ b/tests/fixtures_regress/fixtures/sequence_extra_xml.xml @@ -1,6 +1,6 @@ - + Wolf Super Wolf Canis lupus diff --git a/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml b/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml index 760b2d4275..5840a3e89f 100644 --- a/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml +++ b/tests/fixtures_regress/fixtures/sequence_extra_yaml.yaml @@ -1,4 +1,4 @@ -- pk: "1" +- pk: "000000000000000000000001" model: fixtures_regress.animal fields: name: Cat @@ -7,7 +7,7 @@ count: 3 weight: 1.2 -- pk: "1" +- pk: "000000000000000000000001" model: fixtures_regress.animal_extra fields: name: Nonexistent model diff --git a/tests/fixtures_regress/fixtures/special-article.json b/tests/fixtures_regress/fixtures/special-article.json index a36244acc1..a670ca8ece 100644 --- a/tests/fixtures_regress/fixtures/special-article.json +++ b/tests/fixtures_regress/fixtures/special-article.json @@ -1,12 +1,12 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.article", "fields": {"title": "foof" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.specialarticle", "fields": { "title": "Article Title 1", diff --git a/tests/fixtures_regress/fixtures/thingy.json b/tests/fixtures_regress/fixtures/thingy.json index 1693177b98..d06e63085e 100644 --- a/tests/fixtures_regress/fixtures/thingy.json +++ b/tests/fixtures_regress/fixtures/thingy.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.thingy", "fields": { "name": "Whatchamacallit" diff --git a/tests/fixtures_regress/fixtures_1/forward_ref_1.json b/tests/fixtures_regress/fixtures_1/forward_ref_1.json index 1a75037b48..03e3fe6b2f 100644 --- a/tests/fixtures_regress/fixtures_1/forward_ref_1.json +++ b/tests/fixtures_regress/fixtures_1/forward_ref_1.json @@ -1,10 +1,10 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.book", "fields": { "name": "Cryptonomicon", - "author": 4 + "author": "000000000000000000000004" } } ] diff --git a/tests/fixtures_regress/fixtures_1/inner/absolute.json b/tests/fixtures_regress/fixtures_1/inner/absolute.json index d62ac03fff..9f55585f44 100644 --- a/tests/fixtures_regress/fixtures_1/inner/absolute.json +++ b/tests/fixtures_regress/fixtures_1/inner/absolute.json @@ -1,6 +1,6 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "fixtures_regress.absolute", "fields": { "name": "Load Absolute Path Test" diff --git a/tests/fixtures_regress/fixtures_2/forward_ref_2.json b/tests/fixtures_regress/fixtures_2/forward_ref_2.json index 9cb63085a4..0d9e8a9750 100644 --- a/tests/fixtures_regress/fixtures_2/forward_ref_2.json +++ b/tests/fixtures_regress/fixtures_2/forward_ref_2.json @@ -1,6 +1,6 @@ [ { - "pk": "4", + "pk": "000000000000000000000004", "model": "fixtures_regress.person", "fields": { "name": "Neal Stephenson" diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py index ab46e64023..585fc7dda7 100644 --- a/tests/fixtures_regress/tests.py +++ b/tests/fixtures_regress/tests.py @@ -1,11 +1,12 @@ # Unittests for fixtures. import json import os -import re import unittest from io import StringIO from pathlib import Path +from bson import ObjectId + from django.core import management, serializers from django.core.exceptions import ImproperlyConfigured from django.core.serializers.base import DeserializationError @@ -94,10 +95,10 @@ def test_duplicate_pk(self): latin_name="Ornithorhynchus anatinus", count=2, weight=2.2, - pk=2, + pk="000000000000000000000002", ) animal.save() - self.assertGreater(animal.id, 1) + self.assertGreater(animal.id, ObjectId("000000000000000000000001")) def test_loaddata_not_found_fields_not_ignore(self): """ @@ -345,8 +346,12 @@ def test_pg_sequence_resetting_checks(self): "model-inheritance.json", verbosity=0, ) - self.assertEqual(Parent.objects.all()[0].id, 1) - self.assertEqual(Child.objects.all()[0].id, 1) + self.assertEqual( + Parent.objects.all()[0].id, ObjectId("000000000000000000000001") + ) + self.assertEqual( + Child.objects.all()[0].id, ObjectId("000000000000000000000001") + ) def test_close_connection_after_loaddata(self): """ @@ -361,15 +366,17 @@ def test_close_connection_after_loaddata(self): "big-fixture.json", verbosity=0, ) - articles = Article.objects.exclude(id=9) + articles = Article.objects.exclude(id="000000000000000000000009") self.assertEqual( - list(articles.values_list("id", flat=True)), [1, 2, 3, 4, 5, 6, 7, 8] + list(articles.values_list("id", flat=True)), + [ObjectId(f"{i:024}") for i in range(1, 9)], ) # Just for good measure, run the same query again. # Under the influence of ticket #7572, this will # give a different result to the previous call. self.assertEqual( - list(articles.values_list("id", flat=True)), [1, 2, 3, 4, 5, 6, 7, 8] + list(articles.values_list("id", flat=True)), + [ObjectId(f"{i:024}") for i in range(1, 9)], ) def test_field_value_coerce(self): @@ -413,7 +420,7 @@ def test_dumpdata_uses_default_manager(self): latin_name="Ornithorhynchus anatinus", count=2, weight=2.2, - id=50, + id="000000000000000000000050", ) animal.save() @@ -427,15 +434,10 @@ def test_dumpdata_uses_default_manager(self): # Output order isn't guaranteed, so check for parts data = out.getvalue() - - # Get rid of artifacts like '000000002' to eliminate the differences - # between different Python versions. - data = re.sub("0{6,}[0-9]", "", data) - animals_data = sorted( [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "fixtures_regress.animal", "fields": { "count": 3, @@ -445,7 +447,7 @@ def test_dumpdata_uses_default_manager(self): }, }, { - "pk": 10, + "pk": "000000000000000000000010", "model": "fixtures_regress.animal", "fields": { "count": 42, @@ -455,7 +457,7 @@ def test_dumpdata_uses_default_manager(self): }, }, { - "pk": animal.pk, + "pk": str(animal.pk), "model": "fixtures_regress.animal", "fields": { "count": 2, @@ -503,8 +505,10 @@ def test_loaddata_works_when_fixture_has_forward_refs(self): "forward_ref.json", verbosity=0, ) - self.assertEqual(Book.objects.all()[0].id, 1) - self.assertEqual(Person.objects.all()[0].id, 4) + self.assertEqual(Book.objects.all()[0].id, ObjectId("000000000000000000000001")) + self.assertEqual( + Person.objects.all()[0].id, ObjectId("000000000000000000000004") + ) @skipUnlessDBFeature("supports_foreign_keys") def test_loaddata_raises_error_when_fixture_has_invalid_foreign_key(self): @@ -536,8 +540,10 @@ def test_loaddata_forward_refs_split_fixtures(self): "forward_ref_2.json", verbosity=0, ) - self.assertEqual(Book.objects.all()[0].id, 1) - self.assertEqual(Person.objects.all()[0].id, 4) + self.assertEqual(Book.objects.all()[0].id, ObjectId("000000000000000000000001")) + self.assertEqual( + Person.objects.all()[0].id, ObjectId("000000000000000000000004") + ) def test_loaddata_no_fixture_specified(self): """ @@ -647,7 +653,11 @@ def test_loaddata_with_valid_fixture_dirs(self): @override_settings(FIXTURE_DIRS=[Path(_cur_dir) / "fixtures_1"]) def test_fixtures_dir_pathlib(self): management.call_command("loaddata", "inner/absolute.json", verbosity=0) - self.assertQuerySetEqual(Absolute.objects.all(), [1], transform=lambda o: o.pk) + self.assertQuerySetEqual( + Absolute.objects.all(), + [ObjectId("000000000000000000000001")], + transform=lambda o: o.pk, + ) class NaturalKeyFixtureTests(TestCase): @@ -666,9 +676,13 @@ def test_nk_deserialize(self): "nk-inheritance.json", verbosity=0, ) - self.assertEqual(NKChild.objects.get(pk=1).data, "apple") + self.assertEqual( + NKChild.objects.get(pk="000000000000000000000001").data, "apple" + ) - self.assertEqual(RefToNKChild.objects.get(pk=1).nk_fk.data, "apple") + self.assertEqual( + RefToNKChild.objects.get(pk="000000000000000000000001").nk_fk.data, "apple" + ) def test_nk_deserialize_xml(self): """ @@ -690,8 +704,12 @@ def test_nk_deserialize_xml(self): "nk-inheritance2.xml", verbosity=0, ) - self.assertEqual(NKChild.objects.get(pk=2).data, "banana") - self.assertEqual(RefToNKChild.objects.get(pk=2).nk_fk.data, "apple") + self.assertEqual( + NKChild.objects.get(pk="000000000000000000000002").data, "banana" + ) + self.assertEqual( + RefToNKChild.objects.get(pk="000000000000000000000002").nk_fk.data, "apple" + ) def test_nk_on_serialize(self): """ @@ -723,7 +741,7 @@ def test_nk_on_serialize(self): {"fields": {"main": null, "name": "Borders"}, "model": "fixtures_regress.store"}, {"fields": {"name": "Neal Stephenson"}, "model": "fixtures_regress.person"}, - {"pk": 1, "model": "fixtures_regress.book", + {"pk": "000000000000000000000001", "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}] """, diff --git a/tests/flatpages_tests/test_csrf.py b/tests/flatpages_tests/test_csrf.py index 62ac5f9a14..ad2a952069 100644 --- a/tests/flatpages_tests/test_csrf.py +++ b/tests/flatpages_tests/test_csrf.py @@ -20,14 +20,15 @@ ROOT_URLCONF="flatpages_tests.urls", CSRF_FAILURE_VIEW="django.views.csrf.csrf_failure", TEMPLATES=FLATPAGES_TEMPLATES, - SITE_ID=1, ) class FlatpageCSRFTests(TestCase): @classmethod def setUpTestData(cls): # don't use the manager because we want to ensure the site exists # with pk=1, regardless of whether or not it already exists. - cls.site1 = Site(pk=1, domain="example.com", name="example.com") + cls.site1 = Site( + pk="000000000000000000000001", domain="example.com", name="example.com" + ) cls.site1.save() cls.fp1 = FlatPage.objects.create( url="/flatpage/", diff --git a/tests/flatpages_tests/test_forms.py b/tests/flatpages_tests/test_forms.py index 00caf01960..410e007831 100644 --- a/tests/flatpages_tests/test_forms.py +++ b/tests/flatpages_tests/test_forms.py @@ -7,13 +7,14 @@ @modify_settings(INSTALLED_APPS={"append": ["django.contrib.flatpages"]}) -@override_settings(SITE_ID=1) class FlatpageAdminFormTests(TestCase): @classmethod def setUpTestData(cls): # don't use the manager because we want to ensure the site exists # with pk=1, regardless of whether or not it already exists. - cls.site1 = Site(pk=1, domain="example.com", name="example.com") + cls.site1 = Site( + pk="000000000000000000000001", domain="example.com", name="example.com" + ) cls.site1.save() def setUp(self): diff --git a/tests/flatpages_tests/test_middleware.py b/tests/flatpages_tests/test_middleware.py index 581947e9f6..61a79edbeb 100644 --- a/tests/flatpages_tests/test_middleware.py +++ b/tests/flatpages_tests/test_middleware.py @@ -12,7 +12,9 @@ class TestDataMixin: def setUpTestData(cls): # don't use the manager because we want to ensure the site exists # with pk=1, regardless of whether or not it already exists. - cls.site1 = Site(pk=1, domain="example.com", name="example.com") + cls.site1 = Site( + pk="000000000000000000000001", domain="example.com", name="example.com" + ) cls.site1.save() cls.fp1 = FlatPage.objects.create( url="/flatpage/", @@ -65,7 +67,6 @@ def setUpTestData(cls): ], ROOT_URLCONF="flatpages_tests.urls", TEMPLATES=FLATPAGES_TEMPLATES, - SITE_ID=1, ) class FlatpageMiddlewareTests(TestDataMixin, TestCase): def test_view_flatpage(self): @@ -144,7 +145,6 @@ def test_fallback_flatpage_special_chars(self): ], ROOT_URLCONF="flatpages_tests.urls", TEMPLATES=FLATPAGES_TEMPLATES, - SITE_ID=1, ) class FlatpageMiddlewareAppendSlashTests(TestDataMixin, TestCase): def test_redirect_view_flatpage(self): diff --git a/tests/flatpages_tests/test_sitemaps.py b/tests/flatpages_tests/test_sitemaps.py index abb3e9dba6..9546ed28b9 100644 --- a/tests/flatpages_tests/test_sitemaps.py +++ b/tests/flatpages_tests/test_sitemaps.py @@ -6,7 +6,6 @@ @override_settings( ROOT_URLCONF="flatpages_tests.urls", - SITE_ID=1, ) @modify_settings( INSTALLED_APPS={ diff --git a/tests/flatpages_tests/test_templatetags.py b/tests/flatpages_tests/test_templatetags.py index eb36ee375b..c6bc1c290b 100644 --- a/tests/flatpages_tests/test_templatetags.py +++ b/tests/flatpages_tests/test_templatetags.py @@ -10,7 +10,9 @@ class FlatpageTemplateTagTests(TestCase): def setUpTestData(cls): # don't use the manager because we want to ensure the site exists # with pk=1, regardless of whether or not it already exists. - cls.site1 = Site(pk=1, domain="example.com", name="example.com") + cls.site1 = Site( + pk="000000000000000000000001", domain="example.com", name="example.com" + ) cls.site1.save() cls.fp1 = FlatPage.objects.create( url="/flatpage/", diff --git a/tests/flatpages_tests/test_views.py b/tests/flatpages_tests/test_views.py index 24ad07d35a..a4fa1373b9 100644 --- a/tests/flatpages_tests/test_views.py +++ b/tests/flatpages_tests/test_views.py @@ -12,7 +12,9 @@ class TestDataMixin: def setUpTestData(cls): # don't use the manager because we want to ensure the site exists # with pk=1, regardless of whether or not it already exists. - cls.site1 = Site(pk=1, domain="example.com", name="example.com") + cls.site1 = Site( + pk="000000000000000000000001", domain="example.com", name="example.com" + ) cls.site1.save() cls.fp1 = FlatPage.objects.create( url="/flatpage/", @@ -65,7 +67,6 @@ def setUpTestData(cls): ], ROOT_URLCONF="flatpages_tests.urls", TEMPLATES=FLATPAGES_TEMPLATES, - SITE_ID=1, ) class FlatpageViewTests(TestDataMixin, TestCase): def test_view_flatpage(self): @@ -129,7 +130,6 @@ def test_view_flatpage_special_chars(self): ], ROOT_URLCONF="flatpages_tests.urls", TEMPLATES=FLATPAGES_TEMPLATES, - SITE_ID=1, ) class FlatpageViewAppendSlashTests(TestDataMixin, TestCase): def test_redirect_view_flatpage(self): diff --git a/tests/force_insert_update/tests.py b/tests/force_insert_update/tests.py index cc223cf3ea..460f1deccb 100644 --- a/tests/force_insert_update/tests.py +++ b/tests/force_insert_update/tests.py @@ -103,7 +103,7 @@ def test_force_insert_not_base(self): def test_force_insert_false(self): with self.assertNumQueries(3): - obj = SubCounter.objects.create(pk=1, value=0) + obj = SubCounter.objects.create(pk="000000000000000000000001", value=0) with self.assertNumQueries(2): SubCounter(pk=obj.pk, value=1).save() obj.refresh_from_db() @@ -118,65 +118,79 @@ def test_force_insert_false(self): self.assertEqual(obj.value, 3) def test_force_insert_false_with_existing_parent(self): - parent = Counter.objects.create(pk=1, value=1) + parent = Counter.objects.create(pk="000000000000000000000001", value=1) with self.assertNumQueries(2): SubCounter.objects.create(pk=parent.pk, value=2) def test_force_insert_parent(self): with self.assertNumQueries(3): - SubCounter(pk=1, value=1).save(force_insert=True) + SubCounter(pk="000000000000000000000001", value=1).save(force_insert=True) # Force insert a new parent and don't UPDATE first. with self.assertNumQueries(2): - SubCounter(pk=2, value=1).save(force_insert=(Counter,)) + SubCounter(pk="000000000000000000000002", value=1).save( + force_insert=(Counter,) + ) with self.assertNumQueries(2): - SubCounter(pk=3, value=1).save(force_insert=(models.Model,)) + SubCounter(pk="000000000000000000000003", value=1).save( + force_insert=(models.Model,) + ) def test_force_insert_with_grandparent(self): with self.assertNumQueries(4): - SubSubCounter(pk=1, value=1).save(force_insert=True) + SubSubCounter(pk="000000000000000000000001", value=1).save( + force_insert=True + ) # Force insert parents on all levels and don't UPDATE first. with self.assertNumQueries(3): - SubSubCounter(pk=2, value=1).save(force_insert=(models.Model,)) + SubSubCounter(pk="000000000000000000000002", value=1).save( + force_insert=(models.Model,) + ) with self.assertNumQueries(3): - SubSubCounter(pk=3, value=1).save(force_insert=(Counter,)) + SubSubCounter(pk="000000000000000000000003", value=1).save( + force_insert=(Counter,) + ) # Force insert only the last parent. with self.assertNumQueries(4): - SubSubCounter(pk=4, value=1).save(force_insert=(SubCounter,)) + SubSubCounter(pk="000000000000000000000004", value=1).save( + force_insert=(SubCounter,) + ) def test_force_insert_with_existing_grandparent(self): # Force insert only the last child. - grandparent = Counter.objects.create(pk=1, value=1) + grandparent = Counter.objects.create(pk="000000000000000000000001", value=1) with self.assertNumQueries(4): SubSubCounter(pk=grandparent.pk, value=1).save(force_insert=True) # Force insert a parent, and don't force insert a grandparent. - grandparent = Counter.objects.create(pk=2, value=1) + grandparent = Counter.objects.create(pk="000000000000000000000002", value=1) with self.assertNumQueries(3): SubSubCounter(pk=grandparent.pk, value=1).save(force_insert=(SubCounter,)) # Force insert parents on all levels, grandparent conflicts. - grandparent = Counter.objects.create(pk=3, value=1) + grandparent = Counter.objects.create(pk="000000000000000000000003", value=1) with self.assertRaises(IntegrityError), transaction.atomic(): SubSubCounter(pk=grandparent.pk, value=1).save(force_insert=(Counter,)) def test_force_insert_diamond_mti(self): # Force insert all parents. with self.assertNumQueries(4): - DiamondSubSubCounter(pk=1, value=1).save( + DiamondSubSubCounter(pk="000000000000000000000001", value=1).save( force_insert=(Counter, SubCounter, OtherSubCounter) ) with self.assertNumQueries(4): - DiamondSubSubCounter(pk=2, value=1).save(force_insert=(models.Model,)) + DiamondSubSubCounter(pk="000000000000000000000002", value=1).save( + force_insert=(models.Model,) + ) # Force insert parents, and don't force insert a common grandparent. with self.assertNumQueries(5): - DiamondSubSubCounter(pk=3, value=1).save( + DiamondSubSubCounter(pk="000000000000000000000003", value=1).save( force_insert=(SubCounter, OtherSubCounter) ) - grandparent = Counter.objects.create(pk=4, value=1) + grandparent = Counter.objects.create(pk="000000000000000000000004", value=1) with self.assertNumQueries(4): DiamondSubSubCounter(pk=grandparent.pk, value=1).save( force_insert=(SubCounter, OtherSubCounter), ) # Force insert all parents, grandparent conflicts. - grandparent = Counter.objects.create(pk=5, value=1) + grandparent = Counter.objects.create(pk="000000000000000000000005", value=1) with self.assertRaises(IntegrityError), transaction.atomic(): DiamondSubSubCounter(pk=grandparent.pk, value=1).save( force_insert=(models.Model,) diff --git a/tests/forms_tests/models.py b/tests/forms_tests/models.py index b1319abe17..738bbc2645 100644 --- a/tests/forms_tests/models.py +++ b/tests/forms_tests/models.py @@ -80,11 +80,11 @@ def choice_default_list(): def int_default(): - return 1 + return "000000000000000000000001" def int_list_default(): - return [1] + return ["000000000000000000000001"] class ChoiceFieldModel(models.Model): diff --git a/tests/forms_tests/tests/test_error_messages.py b/tests/forms_tests/tests/test_error_messages.py index f4f5700107..d0a0aac461 100644 --- a/tests/forms_tests/tests/test_error_messages.py +++ b/tests/forms_tests/tests/test_error_messages.py @@ -312,9 +312,9 @@ class SomeForm(Form): class ModelChoiceFieldErrorMessagesTestCase(TestCase, AssertFormErrorsMixin): def test_modelchoicefield(self): # Create choices for the model choice field tests below. - ChoiceModel.objects.create(pk=1, name="a") - ChoiceModel.objects.create(pk=2, name="b") - ChoiceModel.objects.create(pk=3, name="c") + ChoiceModel.objects.create(pk="000000000000000000000001", name="a") + ChoiceModel.objects.create(pk="000000000000000000000002", name="b") + ChoiceModel.objects.create(pk="000000000000000000000003", name="c") # ModelChoiceField e = { @@ -323,7 +323,7 @@ def test_modelchoicefield(self): } f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) self.assertFormErrors(["REQUIRED"], f.clean, "") - self.assertFormErrors(["INVALID CHOICE"], f.clean, "4") + self.assertFormErrors(["INVALID CHOICE"], f.clean, "000000000000000000000004") # ModelMultipleChoiceField e = { @@ -335,8 +335,14 @@ def test_modelchoicefield(self): queryset=ChoiceModel.objects.all(), error_messages=e ) self.assertFormErrors(["REQUIRED"], f.clean, "") - self.assertFormErrors(["NOT A LIST OF VALUES"], f.clean, "3") - self.assertFormErrors(["4 IS INVALID CHOICE"], f.clean, ["4"]) + self.assertFormErrors( + ["NOT A LIST OF VALUES"], f.clean, "000000000000000000000003" + ) + self.assertFormErrors( + ["000000000000000000000004 IS INVALID CHOICE"], + f.clean, + ["000000000000000000000004"], + ) def test_modelchoicefield_value_placeholder(self): f = ModelChoiceField( diff --git a/tests/forms_tests/tests/tests.py b/tests/forms_tests/tests/tests.py index 38735bfb78..51a175a1e9 100644 --- a/tests/forms_tests/tests/tests.py +++ b/tests/forms_tests/tests/tests.py @@ -100,55 +100,73 @@ def test_callable_initial_value(self): The initial value for a callable default returning a queryset is the pk. """ - ChoiceOptionModel.objects.create(id=1, name="default") - ChoiceOptionModel.objects.create(id=2, name="option 2") - ChoiceOptionModel.objects.create(id=3, name="option 3") + ChoiceOptionModel.objects.create(id="000000000000000000000001", name="default") + ChoiceOptionModel.objects.create(id="000000000000000000000002", name="option 2") + ChoiceOptionModel.objects.create(id="000000000000000000000003", name="option 3") + self.maxDiff = None self.assertHTMLEqual( ChoiceFieldForm().as_p(), """

    - +

    - +

    - +

    - +

    """, ) def test_initial_instance_value(self): "Initial instances for model fields may also be instances (refs #7287)" - ChoiceOptionModel.objects.create(id=1, name="default") - obj2 = ChoiceOptionModel.objects.create(id=2, name="option 2") - obj3 = ChoiceOptionModel.objects.create(id=3, name="option 3") + ChoiceOptionModel.objects.create(id="000000000000000000000001", name="default") + obj2 = ChoiceOptionModel.objects.create( + id="000000000000000000000002", name="option 2" + ) + obj3 = ChoiceOptionModel.objects.create( + id="000000000000000000000003", name="option 3" + ) self.assertHTMLEqual( ChoiceFieldForm( initial={ @@ -163,42 +181,55 @@ def test_initial_instance_value(self): """

    - +

    - +

    - - + +

    - - + +

    """, ) @@ -371,9 +402,9 @@ class Meta: class ManyToManyExclusionTestCase(TestCase): def test_m2m_field_exclusion(self): # Issue 12337. save_instance should honor the passed-in exclude keyword. - opt1 = ChoiceOptionModel.objects.create(id=1, name="default") - opt2 = ChoiceOptionModel.objects.create(id=2, name="option 2") - opt3 = ChoiceOptionModel.objects.create(id=3, name="option 3") + opt1 = ChoiceOptionModel.objects.create(name="default") + opt2 = ChoiceOptionModel.objects.create(name="option 2") + opt3 = ChoiceOptionModel.objects.create(name="option 3") initial = { "choice": opt1, "choice_int": opt1, diff --git a/tests/forms_tests/urls.py b/tests/forms_tests/urls.py index 4063568a81..5015083185 100644 --- a/tests/forms_tests/urls.py +++ b/tests/forms_tests/urls.py @@ -4,5 +4,5 @@ urlpatterns = [ path("form_view/", form_view, name="form_view"), - path("model_form//", ArticleFormView.as_view(), name="article_form"), + path("model_form//", ArticleFormView.as_view(), name="article_form"), ] diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index ef5d45104a..06bfea34b6 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -250,14 +250,15 @@ def test_annotate(self): b = Board.objects.create(name=str(hs1.pk)) Link.objects.create(content_object=hs2) # An integer PK is required for the Sum() queryset that follows. - link = Link.objects.create(content_object=hs1, pk=10) + # Removed since not supported on MongoDB. + link = Link.objects.create(content_object=hs1) Link.objects.create(content_object=b) qs = HasLinkThing.objects.annotate(Sum("links")).filter(pk=hs1.pk) # If content_type restriction isn't in the query's join condition, # then wrong results are produced here as the link to b will also match # (b and hs1 have equal pks). self.assertEqual(qs.count(), 1) - self.assertEqual(qs[0].links__sum, link.id) + self.assertEqual(qs[0].links__sum, 0) # Modified for MongoDB. link.delete() # Now if we don't have proper left join, we will not produce any # results at all here. @@ -273,9 +274,9 @@ def test_annotate(self): def test_filter_targets_related_pk(self): # Use hardcoded PKs to ensure different PKs for "link" and "hs2" # objects. - HasLinkThing.objects.create(pk=1) - hs2 = HasLinkThing.objects.create(pk=2) - link = Link.objects.create(content_object=hs2, pk=1) + HasLinkThing.objects.create(pk="000000000000000000000001") + hs2 = HasLinkThing.objects.create(pk="000000000000000000000002") + link = Link.objects.create(content_object=hs2, pk="000000000000000000000001") self.assertNotEqual(link.object_id, link.pk) self.assertSequenceEqual(HasLinkThing.objects.filter(links=link.pk), [hs2]) diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py index 49bda6a610..a55300455e 100644 --- a/tests/generic_views/test_dates.py +++ b/tests/generic_views/test_dates.py @@ -897,13 +897,17 @@ def test_get_object_custom_queryset(self): self.assertTemplateUsed(res, "generic_views/book_detail.html") res = self.client.get( - "/dates/books/get_object_custom_queryset/2008/oct/01/9999999/" + "/dates/books/get_object_custom_queryset/2008/oct/01/" + "000000000000000009999999/" ) self.assertEqual(res.status_code, 404) def test_get_object_custom_queryset_numqueries(self): with self.assertNumQueries(1): - self.client.get("/dates/books/get_object_custom_queryset/2006/may/01/2/") + self.client.get( + "/dates/books/get_object_custom_queryset/2006/may/01/" + "000000000000000000000002/" + ) def test_datetime_date_detail(self): bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0)) diff --git a/tests/generic_views/test_detail.py b/tests/generic_views/test_detail.py index 7203100576..ca37dafd43 100644 --- a/tests/generic_views/test_detail.py +++ b/tests/generic_views/test_detail.py @@ -51,12 +51,12 @@ def test_detail_by_pk(self): self.assertTemplateUsed(res, "generic_views/author_detail.html") def test_detail_missing_object(self): - res = self.client.get("/detail/author/500/") + res = self.client.get("/detail/author/000000000000000000000500/") self.assertEqual(res.status_code, 404) def test_detail_object_does_not_exist(self): with self.assertRaises(ObjectDoesNotExist): - self.client.get("/detail/doesnotexist/1/") + self.client.get("/detail/doesnotexist/000000000000000000000500/") def test_detail_by_custom_pk(self): res = self.client.get("/detail/author/bycustompk/%s/" % self.author1.pk) diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py index 990478cad4..df9b685291 100644 --- a/tests/generic_views/test_edit.py +++ b/tests/generic_views/test_edit.py @@ -239,7 +239,7 @@ class UpdateViewTests(TestCase): @classmethod def setUpTestData(cls): cls.author = Author.objects.create( - pk=1, # Required for OneAuthorUpdate. + pk="000000000000000000000001", # Required for OneAuthorUpdate. name="Randall Munroe", slug="randall-munroe", ) diff --git a/tests/generic_views/views.py b/tests/generic_views/views.py index 5348c67632..f3e26e4a4d 100644 --- a/tests/generic_views/views.py +++ b/tests/generic_views/views.py @@ -169,7 +169,7 @@ class OneAuthorUpdate(generic.UpdateView): fields = "__all__" def get_object(self): - return Author.objects.get(pk=1) + return Author.objects.get(pk="000000000000000000000001") class SpecializedAuthorUpdate(generic.UpdateView): diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index bfe93e604c..a82749a8f0 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -80,12 +80,13 @@ def test_get_or_create_with_pk_property(self): """ Using the pk property of a model is allowed. """ - Thing.objects.get_or_create(pk=1) + Thing.objects.get_or_create(pk="000000000000000000000001") def test_get_or_create_with_model_property_defaults(self): """Using a property with a setter implemented is allowed.""" t, _ = Thing.objects.get_or_create( - defaults={"capitalized_name_property": "annie"}, pk=1 + defaults={"capitalized_name_property": "annie"}, + pk="000000000000000000000001", ) self.assertEqual(t.name, "Annie") @@ -215,9 +216,11 @@ def raise_exception(): class GetOrCreateTestsWithManualPKs(TestCase): + id = "000000000000000000000001" + @classmethod def setUpTestData(cls): - ManualPrimaryKeyTest.objects.create(id=1, data="Original") + ManualPrimaryKeyTest.objects.create(id=cls.id, data="Original") def test_create_with_duplicate_primary_key(self): """ @@ -225,8 +228,8 @@ def test_create_with_duplicate_primary_key(self): then you will get an error and data will not be updated. """ with self.assertRaises(IntegrityError): - ManualPrimaryKeyTest.objects.get_or_create(id=1, data="Different") - self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original") + ManualPrimaryKeyTest.objects.get_or_create(id=self.id, data="Different") + self.assertEqual(ManualPrimaryKeyTest.objects.get(id=self.id).data, "Original") def test_savepoint_rollback(self): """ @@ -237,7 +240,8 @@ def test_savepoint_rollback(self): with self.assertRaises(DatabaseError): # pk 123456789 doesn't exist, so the tag object will be created. # Saving triggers a unique constraint violation on 'text'. - Tag.objects.get_or_create(pk=123456789, defaults={"text": "foo"}) + pk = "000000000000000123456789" + Tag.objects.get_or_create(pk=pk, defaults={"text": "foo"}) # Tag objects can be created after the error. Tag.objects.create(text="bar") @@ -259,7 +263,7 @@ def test_get_or_create_integrityerror(self): otherwise the exception is never raised. """ try: - Profile.objects.get_or_create(person=Person(id=1)) + Profile.objects.get_or_create(person=Person(id="000000000000000000000001")) except IntegrityError: pass else: @@ -350,21 +354,23 @@ def test_manual_primary_key_test(self): If you specify an existing primary key, but different other fields, then you will get an error and data will not be updated. """ - ManualPrimaryKeyTest.objects.create(id=1, data="Original") + id = "000000000000000000000001" + ManualPrimaryKeyTest.objects.create(id=id, data="Original") with self.assertRaises(IntegrityError): - ManualPrimaryKeyTest.objects.update_or_create(id=1, data="Different") - self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original") + ManualPrimaryKeyTest.objects.update_or_create(id=id, data="Different") + self.assertEqual(ManualPrimaryKeyTest.objects.get(id=id).data, "Original") def test_with_pk_property(self): """ Using the pk property of a model is allowed. """ - Thing.objects.update_or_create(pk=1) + Thing.objects.update_or_create(pk="000000000000000000000001") def test_update_or_create_with_model_property_defaults(self): """Using a property with a setter implemented is allowed.""" t, _ = Thing.objects.update_or_create( - defaults={"capitalized_name_property": "annie"}, pk=1 + defaults={"capitalized_name_property": "annie"}, + pk="000000000000000000000001", ) self.assertEqual(t.name, "Annie") @@ -375,8 +381,9 @@ def test_error_contains_full_traceback(self): We cannot use assertRaises/assertRaises here because we need to inspect the actual traceback. Refs #16340. """ + id = "000000000000000000000001" try: - ManualPrimaryKeyTest.objects.update_or_create(id=1, data="Different") + ManualPrimaryKeyTest.objects.update_or_create(id=id, data="Different") except IntegrityError: formatted_traceback = traceback.format_exc() self.assertIn("obj.save", formatted_traceback) @@ -610,12 +617,13 @@ class UpdateOrCreateTestsWithManualPKs(TestCase): def test_create_with_duplicate_primary_key(self): """ If an existing primary key is specified with different values for other - fields, then IntegrityError is raised and data isn't updated. + fields, then Integritrror is raised and data isn't updated. """ - ManualPrimaryKeyTest.objects.create(id=1, data="Original") + id = "000000000000000000000001" + ManualPrimaryKeyTest.objects.create(id=id, data="Original") with self.assertRaises(IntegrityError): - ManualPrimaryKeyTest.objects.update_or_create(id=1, data="Different") - self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original") + ManualPrimaryKeyTest.objects.update_or_create(id=id, data="Different") + self.assertEqual(ManualPrimaryKeyTest.objects.get(id=id).data, "Original") class UpdateOrCreateTransactionTests(TransactionTestCase): diff --git a/tests/gis_tests/distapp/fixtures/initial.json b/tests/gis_tests/distapp/fixtures/initial.json index 6cd67c7fea..b8632d342e 100644 --- a/tests/gis_tests/distapp/fixtures/initial.json +++ b/tests/gis_tests/distapp/fixtures/initial.json @@ -8,7 +8,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "distapp.southtexascity", "fields": { "name": "West University Place", @@ -16,7 +16,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "distapp.southtexascity", "fields": { "name": "Southside Place", @@ -24,7 +24,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "distapp.southtexascity", "fields": { "name": "Bellaire", @@ -80,7 +80,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "distapp.southtexascityft", "fields": { "name": "West University Place", @@ -88,7 +88,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "distapp.southtexascityft", "fields": { "name": "Southside Place", @@ -96,7 +96,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "distapp.southtexascityft", "fields": { "name": "Bellaire", @@ -152,7 +152,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "distapp.australiacity", "fields": { "name": "Shellharbour", @@ -160,7 +160,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "distapp.australiacity", "fields": { "name": "Thirroul", @@ -168,7 +168,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "distapp.australiacity", "fields": { "name": "Mittagong", @@ -240,7 +240,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "distapp.censuszipcode", "fields": { "name": "77005", @@ -248,7 +248,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "distapp.censuszipcode", "fields": { "name": "77025", @@ -256,7 +256,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "distapp.censuszipcode", "fields": { "name": "77401", @@ -272,7 +272,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "distapp.southtexaszipcode", "fields": { "name": "77005", @@ -280,7 +280,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "distapp.southtexaszipcode", "fields": { "name": "77025", @@ -288,7 +288,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "distapp.southtexaszipcode", "fields": { "name": "77401", diff --git a/tests/gis_tests/geogapp/fixtures/initial.json b/tests/gis_tests/geogapp/fixtures/initial.json index f0f0374d47..442f31c39d 100644 --- a/tests/gis_tests/geogapp/fixtures/initial.json +++ b/tests/gis_tests/geogapp/fixtures/initial.json @@ -8,7 +8,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "geogapp.city", "fields": { "name": "Dallas", @@ -16,7 +16,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "geogapp.city", "fields": { "name": "Oklahoma City", @@ -24,7 +24,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "geogapp.city", "fields": { "name": "Wellington", @@ -72,7 +72,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "geogapp.zipcode", "fields" : { "code" : "77005", @@ -80,7 +80,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "geogapp.zipcode", "fields" : { "code" : "77025", @@ -88,7 +88,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "geogapp.zipcode", "fields" : { "code" : "77401", diff --git a/tests/gis_tests/relatedapp/fixtures/initial.json b/tests/gis_tests/relatedapp/fixtures/initial.json index 4adf9ef854..3a2e4c19b4 100644 --- a/tests/gis_tests/relatedapp/fixtures/initial.json +++ b/tests/gis_tests/relatedapp/fixtures/initial.json @@ -7,21 +7,21 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "relatedapp.location", "fields": { "point": "SRID=4326;POINT (-104.528056 33.387222)" } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "relatedapp.location", "fields": { "point": "SRID=4326;POINT (-79.460734 40.18476)" } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "relatedapp.location", "fields": { "point": "SRID=4326;POINT (-95.363151 29.763374)" @@ -44,7 +44,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "relatedapp.city", "fields": { "name": "Roswell", @@ -53,7 +53,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "relatedapp.city", "fields": { "name": "Kecksburg", @@ -62,7 +62,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "relatedapp.city", "fields": { "name": "Dallas", @@ -97,7 +97,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "relatedapp.Author", "fields": { "name": "William Patry", @@ -113,7 +113,7 @@ } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "relatedapp.Book", "fields": { "title": "I Could Tell You But Then You Would Have to be Destroyed by Me", @@ -121,7 +121,7 @@ } }, { - "pk": 3, + "pk": "000000000000000000000003", "model": "relatedapp.Book", "fields": { "title": "Blank Spots on the Map", @@ -129,7 +129,7 @@ } }, { - "pk": 4, + "pk": "000000000000000000000004", "model": "relatedapp.Book", "fields": { "title": "Patry on Copyright", @@ -150,7 +150,7 @@ }, { "model": "relatedapp.parcel", - "pk": 2, + "pk": "000000000000000000000002", "fields": { "name": "Aurora Parcel Beta", "city": 1, @@ -162,7 +162,7 @@ }, { "model": "relatedapp.parcel", - "pk": 3, + "pk": "000000000000000000000003", "fields": { "name": "Aurora Parcel Ignore", "city": 1, @@ -174,7 +174,7 @@ }, { "model": "relatedapp.parcel", - "pk": 4, + "pk": "000000000000000000000004", "fields": { "name": "Roswell Parcel Ignore", "city": 2, diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index f19d6ff516..c4e2649f8e 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -1,6 +1,8 @@ import datetime from unittest import skipUnless +from bson import ObjectId + from django.conf import settings from django.db import NotSupportedError, connection from django.db.models import CASCADE, CharField, ForeignKey, Index, Q @@ -419,10 +421,10 @@ def test_integer_restriction_partial(self): name="recent_article_idx", # This is changed fields=["headline"], - condition=Q(pk__gt=1), + condition=Q(pk__gt="000000000000000000000001"), ) self.assertEqual( - {"_id": {"$gt": 1}}, + {"_id": {"$gt": ObjectId("000000000000000000000001")}}, index._get_condition_mql(Article, schema_editor=editor), ) editor.add_index(index=index, model=Article) diff --git a/tests/inline_formsets/tests.py b/tests/inline_formsets/tests.py index 1ae9b3f760..7de9cc7f6c 100644 --- a/tests/inline_formsets/tests.py +++ b/tests/inline_formsets/tests.py @@ -162,7 +162,7 @@ def test_any_iterable_allowed_as_argument_to_exclude(self): @skipUnlessDBFeature("allows_auto_pk_0") def test_zero_primary_key(self): # Regression test for #21472 - poet = Poet.objects.create(id=0, name="test") + poet = Poet.objects.create(id="000000000000000000000000", name="test") poet.poem_set.create(name="test poem") PoemFormSet = inlineformset_factory(Poet, Poem, fields="__all__", extra=0) formset = PoemFormSet(None, instance=poet) diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index e19fbca521..50c37c8288 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -196,7 +196,7 @@ def test_in_bulk(self): Article.objects.in_bulk(frozenset([self.a3.id])), {self.a3.id: self.a3} ) self.assertEqual(Article.objects.in_bulk((self.a3.id,)), {self.a3.id: self.a3}) - self.assertEqual(Article.objects.in_bulk([1000]), {}) + self.assertEqual(Article.objects.in_bulk(["000000000000000000001000"]), {}) self.assertEqual(Article.objects.in_bulk([]), {}) self.assertEqual( Article.objects.in_bulk(iter([self.a1.id])), {self.a1.id: self.a1} diff --git a/tests/m2m_through_regress/fixtures/m2m_through.json b/tests/m2m_through_regress/fixtures/m2m_through.json index 6f24886f02..ae6898ea45 100644 --- a/tests/m2m_through_regress/fixtures/m2m_through.json +++ b/tests/m2m_through_regress/fixtures/m2m_through.json @@ -1,13 +1,13 @@ [ { - "pk": "1", + "pk": "000000000000000000000001", "model": "m2m_through_regress.person", "fields": { "name": "Guido" } }, { - "pk": "1", + "pk": "000000000000000000000001", "model": "auth.user", "fields": { "username": "Guido", @@ -16,14 +16,14 @@ } }, { - "pk": "1", + "pk": "000000000000000000000001", "model": "m2m_through_regress.group", "fields": { "name": "Python Core Group" } }, { - "pk": "1", + "pk": "000000000000000000000001", "model": "m2m_through_regress.usermembership", "fields": { "user": "1", diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index 586742f8ea..f4fef1868b 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -38,7 +38,7 @@ class Foo(models.Model): def get_foo(): - return Foo.objects.get(id=1).pk + return Foo.objects.get(id="000000000000000000000001").pk class Bar(models.Model): diff --git a/tests/model_fields/test_foreignkey.py b/tests/model_fields/test_foreignkey.py index ca8eff3540..ba545d5eed 100644 --- a/tests/model_fields/test_foreignkey.py +++ b/tests/model_fields/test_foreignkey.py @@ -13,7 +13,9 @@ class ForeignKeyTests(TestCase): def test_callable_default(self): """A lazy callable may be used for ForeignKey.default.""" - a = Foo.objects.create(id=1, a="abc", d=Decimal("12.34")) + a = Foo.objects.create( + id="000000000000000000000001", a="abc", d=Decimal("12.34") + ) b = Bar.objects.create(b="bcd") self.assertEqual(b.a, a) diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py index 1d3ae96f6b..c9c5b8e09c 100644 --- a/tests/model_fields/test_jsonfield.py +++ b/tests/model_fields/test_jsonfield.py @@ -307,7 +307,7 @@ def test_realistic_object(self): @skipUnlessDBFeature("supports_primitives_in_json_field") def test_bulk_update_custom_get_prep_value(self): objs = CustomSerializationJSONModel.objects.bulk_create( - [CustomSerializationJSONModel(pk=1, json_field={"version": "1"})] + [CustomSerializationJSONModel(json_field={"version": "1"})] ) objs[0].json_field["version"] = "1-alpha" CustomSerializationJSONModel.objects.bulk_update(objs, ["json_field"]) diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index ced0f6f5d1..80f5206cfb 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -2176,7 +2176,7 @@ def test_model_multiple_choice_field(self): # Note, we are using an id of 1006 here since tests that run before # this may create categories with primary keys up to 6. Use # a number that will not conflict. - c6 = Category.objects.create(id=1006, name="Sixth", url="6th") + c6 = Category.objects.create(name="Sixth", url="6th") self.assertCountEqual(f.clean([c6.id]), [c6]) # Delete a Category object *after* the ModelMultipleChoiceField has already been diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 8b109fce4a..b7ff2919d7 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -830,7 +830,7 @@ def test_inline_formsets_with_custom_pk(self): AuthorBooksFormSet2 = inlineformset_factory( Author, BookWithCustomPK, can_delete=False, extra=1, fields="__all__" ) - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create(name="Charles Baudelaire") formset = AuthorBooksFormSet2(instance=author) self.assertEqual(len(formset.forms), 1) @@ -843,7 +843,7 @@ def test_inline_formsets_with_custom_pk(self): '' '

    ', + f'value="{author.pk}" id="id_bookwithcustompk_set-0-author">

    ', ) data = { @@ -863,7 +863,7 @@ def test_inline_formsets_with_custom_pk(self): saved = formset.save() self.assertEqual(len(saved), 1) (book1,) = saved - self.assertEqual(book1.pk, 77777) + self.assertEqual(str(book1.pk), "77777") book1 = author.bookwithcustompk_set.get() self.assertEqual(book1.title, "Les Fleurs du Mal") @@ -875,7 +875,7 @@ def test_inline_formsets_with_multi_table_inheritance(self): AuthorBooksFormSet3 = inlineformset_factory( Author, AlternateBook, can_delete=False, extra=1, fields="__all__" ) - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create(name="Charles Baudelaire") formset = AuthorBooksFormSet3(instance=author) self.assertEqual(len(formset.forms), 1) @@ -887,8 +887,8 @@ def test_inline_formsets_with_multi_table_inheritance(self): '

    ' '' - '' + '' '

    ', ) @@ -925,7 +925,9 @@ def test_inline_formsets_with_nullable_unique_together(self): extra=2, fields="__all__", ) - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create( + pk="000000000000000000000001", name="Charles Baudelaire" + ) data = { # The number of forms rendered. @@ -934,9 +936,9 @@ def test_inline_formsets_with_nullable_unique_together(self): "bookwithoptionalalteditor_set-INITIAL_FORMS": "0", # The max number of forms. "bookwithoptionalalteditor_set-MAX_NUM_FORMS": "", - "bookwithoptionalalteditor_set-0-author": "1", + "bookwithoptionalalteditor_set-0-author": "000000000000000000000001", "bookwithoptionalalteditor_set-0-title": "Les Fleurs du Mal", - "bookwithoptionalalteditor_set-1-author": "1", + "bookwithoptionalalteditor_set-1-author": "000000000000000000000001", "bookwithoptionalalteditor_set-1-title": "Les Fleurs du Mal", } formset = AuthorBooksFormSet4(data, instance=author) @@ -945,21 +947,29 @@ def test_inline_formsets_with_nullable_unique_together(self): saved = formset.save() self.assertEqual(len(saved), 2) book1, book2 = saved - self.assertEqual(book1.author_id, 1) + self.assertEqual(str(book1.author_id), "000000000000000000000001") self.assertEqual(book1.title, "Les Fleurs du Mal") - self.assertEqual(book2.author_id, 1) + self.assertEqual(str(book1.author_id), "000000000000000000000001") self.assertEqual(book2.title, "Les Fleurs du Mal") def test_inline_formsets_with_custom_save_method(self): AuthorBooksFormSet = inlineformset_factory( Author, Book, can_delete=False, extra=2, fields="__all__" ) - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create( + pk="000000000000000000000001", name="Charles Baudelaire" + ) book1 = Book.objects.create( - pk=1, author=author, title="Les Paradis Artificiels" + pk="000000000000000000000001", + author=author, + title="Les Paradis Artificiels", + ) + book2 = Book.objects.create( + pk="000000000000000000000002", author=author, title="Les Fleurs du Mal" + ) + book3 = Book.objects.create( + pk="000000000000000000000003", author=author, title="Flowers of Evil" ) - book2 = Book.objects.create(pk=2, author=author, title="Les Fleurs du Mal") - book3 = Book.objects.create(pk=3, author=author, title="Flowers of Evil") class PoemForm(forms.ModelForm): def save(self, commit=True): @@ -1001,9 +1011,10 @@ def save(self, commit=True): '

    ' '' - '' - '' + '' + '' "

    ", ) self.assertHTMLEqual( @@ -1011,9 +1022,10 @@ def save(self, commit=True): '

    ' '' - '' - '' + '' + '' "

    ", ) self.assertHTMLEqual( @@ -1021,18 +1033,18 @@ def save(self, commit=True): '

    ' '' - '' - '

    ', + '' + '

    ', ) self.assertHTMLEqual( formset.forms[3].as_p(), '

    ' '' - '' + '' '

    ', ) self.assertHTMLEqual( @@ -1040,8 +1052,8 @@ def save(self, commit=True): '

    ' '' - '' + '' '

    ', ) @@ -1068,18 +1080,18 @@ def save(self, commit=True): '

    ' '' - '' - '

    ', + '' + '

    ', ) self.assertHTMLEqual( formset.forms[1].as_p(), '

    ' '' - '' + '' '

    ', ) self.assertHTMLEqual( @@ -1087,8 +1099,8 @@ def save(self, commit=True): '

    ' '' - '' + '' '

    ', ) @@ -1165,7 +1177,9 @@ def test_custom_pk(self): # Custom primary keys with ForeignKey, OneToOneField and AutoField ############ - place = Place.objects.create(pk=1, name="Giordanos", city="Chicago") + place = Place.objects.create( + pk="000000000000000000000001", name="Giordanos", city="Chicago" + ) FormSet = inlineformset_factory( Place, Owner, extra=2, can_delete=False, fields="__all__" @@ -1177,8 +1191,8 @@ def test_custom_pk(self): '

    ' '' - '' + '' '

    ', ) @@ -1187,8 +1201,8 @@ def test_custom_pk(self): '

    ' '' - '' + '' '

    ', ) @@ -1217,8 +1231,8 @@ def test_custom_pk(self): '

    ' '' - '' + '' '

    ' % owner1.auto_id, ) @@ -1227,8 +1241,8 @@ def test_custom_pk(self): '

    ' '' - '' + '' '

    ', ) @@ -1237,8 +1251,8 @@ def test_custom_pk(self): '

    ' '' - '' + '' '

    ', ) @@ -1340,7 +1354,9 @@ def test_custom_pk(self): def test_unique_true_enforces_max_num_one(self): # ForeignKey with unique=True should enforce max_num=1 - place = Place.objects.create(pk=1, name="Giordanos", city="Chicago") + place = Place.objects.create( + pk="000000000000000000000001", name="Giordanos", city="Chicago" + ) FormSet = inlineformset_factory( Place, Location, can_delete=False, fields="__all__" @@ -1357,8 +1373,8 @@ def test_unique_true_enforces_max_num_one(self): '

    ' '' - '' + '' '

    ', ) @@ -1760,7 +1776,7 @@ def test_model_formset_with_initial_queryset(self): # has_changed should work with queryset and list of pk's # see #18898 FormSet = modelformset_factory(AuthorMeeting, fields="__all__") - Author.objects.create(pk=1, name="Charles Baudelaire") + Author.objects.create(pk="000000000000000000000001", name="Charles Baudelaire") data = { "form-TOTAL_FORMS": 1, "form-INITIAL_FORMS": 0, @@ -1822,10 +1838,12 @@ def test_prevent_duplicates_from_with_the_same_formset(self): self.assertTrue(formset.is_valid()) FormSet = inlineformset_factory(Author, Book, extra=0, fields="__all__") - author = Author.objects.create(pk=1, name="Charles Baudelaire") - Book.objects.create(pk=1, author=author, title="Les Paradis Artificiels") - Book.objects.create(pk=2, author=author, title="Les Fleurs du Mal") - Book.objects.create(pk=3, author=author, title="Flowers of Evil") + author = Author.objects.create( + pk="000000000000000000000001", name="Charles Baudelaire" + ) + Book.objects.create(author=author, title="Les Paradis Artificiels") + Book.objects.create(author=author, title="Les Fleurs du Mal") + Book.objects.create(author=author, title="Flowers of Evil") book_ids = author.book_set.order_by("id").values_list("id", flat=True) data = { @@ -2191,7 +2209,7 @@ def test_inlineformset_factory_help_text_overrides(self): self.assertEqual(form["title"].help_text, "Choose carefully.") def test_modelformset_factory_error_messages_overrides(self): - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create(name="Charles Baudelaire") BookFormSet = modelformset_factory( Book, fields="__all__", @@ -2202,7 +2220,7 @@ def test_modelformset_factory_error_messages_overrides(self): self.assertEqual(form.errors, {"title": ["Title too long!!"]}) def test_inlineformset_factory_error_messages_overrides(self): - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create(name="Charles Baudelaire") BookFormSet = inlineformset_factory( Author, Book, @@ -2214,7 +2232,7 @@ def test_inlineformset_factory_error_messages_overrides(self): self.assertEqual(form.errors, {"title": ["Title too long!!"]}) def test_modelformset_factory_field_class_overrides(self): - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create(name="Charles Baudelaire") BookFormSet = modelformset_factory( Book, fields="__all__", @@ -2227,7 +2245,7 @@ def test_modelformset_factory_field_class_overrides(self): self.assertIsInstance(form.fields["title"], forms.SlugField) def test_inlineformset_factory_field_class_overrides(self): - author = Author.objects.create(pk=1, name="Charles Baudelaire") + author = Author.objects.create(name="Charles Baudelaire") BookFormSet = inlineformset_factory( Author, Book, diff --git a/tests/model_formsets_regress/tests.py b/tests/model_formsets_regress/tests.py index 0ccc2c0490..2618bbcf05 100644 --- a/tests/model_formsets_regress/tests.py +++ b/tests/model_formsets_regress/tests.py @@ -201,15 +201,21 @@ def test_inline_model_with_to_field_to_rel(self): """ FormSet = inlineformset_factory(UserProfile, ProfileNetwork, exclude=[]) - user = User.objects.create(username="guido", serial=1337, pk=1) - self.assertEqual(user.pk, 1) - profile = UserProfile.objects.create(user=user, about="about", pk=2) - self.assertEqual(profile.pk, 2) + user = User.objects.create( + username="guido", serial=1337, pk="000000000000000000000001" + ) + self.assertEqual(str(user.pk), "000000000000000000000001") + profile = UserProfile.objects.create( + user=user, about="about", pk="000000000000000000000002" + ) + self.assertEqual(str(profile.pk), "000000000000000000000002") ProfileNetwork.objects.create(profile=profile, network=10, identifier=10) formset = FormSet(instance=profile) # Testing the inline model's relation - self.assertEqual(formset[0].instance.profile_id, 1) + self.assertEqual( + str(formset[0].instance.profile_id), "000000000000000000000001" + ) def test_formset_with_none_instance(self): "A formset with instance=None can be created. Regression for #11872" diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py index ba31048ac2..0a0502ae11 100644 --- a/tests/model_inheritance_regress/tests.py +++ b/tests/model_inheritance_regress/tests.py @@ -431,10 +431,14 @@ def test_abstract_verbose_name_plural_inheritance(self): def test_inherited_nullable_exclude(self): obj = SelfRefChild.objects.create(child_data=37, parent_data=42) self.assertQuerySetEqual( - SelfRefParent.objects.exclude(self_data=72), [obj.pk], attrgetter("pk") + SelfRefParent.objects.exclude(self_data="000000000000000000000072"), + [obj.pk], + attrgetter("pk"), ) self.assertQuerySetEqual( - SelfRefChild.objects.exclude(self_data=72), [obj.pk], attrgetter("pk") + SelfRefChild.objects.exclude(self_data="000000000000000000000072"), + [obj.pk], + attrgetter("pk"), ) def test_concrete_abstract_concrete_pk(self): diff --git a/tests/multiple_database/fixtures/multidb-common.json b/tests/multiple_database/fixtures/multidb-common.json index 33134173b9..02aad4cdc0 100644 --- a/tests/multiple_database/fixtures/multidb-common.json +++ b/tests/multiple_database/fixtures/multidb-common.json @@ -1,10 +1,10 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "multiple_database.book", "fields": { "title": "The Definitive Guide to Django", "published": "2009-7-8" } } -] \ No newline at end of file +] diff --git a/tests/multiple_database/fixtures/multidb.default.json b/tests/multiple_database/fixtures/multidb.default.json index 379b18a803..f57c87daff 100644 --- a/tests/multiple_database/fixtures/multidb.default.json +++ b/tests/multiple_database/fixtures/multidb.default.json @@ -1,20 +1,20 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "multiple_database.person", "fields": { "name": "Marty Alchin" } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "multiple_database.person", "fields": { "name": "George Vilches" } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "multiple_database.book", "fields": { "title": "Pro Django", diff --git a/tests/multiple_database/fixtures/multidb.other.json b/tests/multiple_database/fixtures/multidb.other.json index c64f490201..f67ac0e906 100644 --- a/tests/multiple_database/fixtures/multidb.other.json +++ b/tests/multiple_database/fixtures/multidb.other.json @@ -1,20 +1,20 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "multiple_database.person", "fields": { "name": "Mark Pilgrim" } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "multiple_database.person", "fields": { "name": "Chris Mills" } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "multiple_database.book", "fields": { "title": "Dive into Python", @@ -23,4 +23,4 @@ "editor": ["Chris Mills"] } } -] \ No newline at end of file +] diff --git a/tests/multiple_database/fixtures/pets.json b/tests/multiple_database/fixtures/pets.json index 89756a3e5b..c6f059de48 100644 --- a/tests/multiple_database/fixtures/pets.json +++ b/tests/multiple_database/fixtures/pets.json @@ -1,18 +1,18 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "multiple_database.pet", "fields": { "name": "Mr Bigglesworth", - "owner": 1 + "owner": "000000000000000000000001" } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "multiple_database.pet", "fields": { "name": "Spot", - "owner": 2 + "owner": "000000000000000000000002" } } -] \ No newline at end of file +] diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py index 23d2f37f65..a790a442a0 100644 --- a/tests/multiple_database/tests.py +++ b/tests/multiple_database/tests.py @@ -884,7 +884,7 @@ def test_o2o_cross_database_protection(self): new_bob_profile = UserProfile(flavor="spring surprise") # assigning a profile requires an explicit pk as the object isn't saved - charlie = User(pk=51, username="charlie", email="charlie@example.com") + charlie = User(username="charlie", email="charlie@example.com") charlie.set_unusable_password() # initially, no db assigned @@ -1645,16 +1645,16 @@ def test_m2m_cross_database_protection(self): "M2M relations can cross databases if the database share a source" # Create books and authors on the inverse to the usual database pro = Book.objects.using("other").create( - pk=1, title="Pro Django", published=datetime.date(2008, 12, 16) + title="Pro Django", published=datetime.date(2008, 12, 16) ) - marty = Person.objects.using("other").create(pk=1, name="Marty Alchin") + marty = Person.objects.using("other").create(name="Marty Alchin") dive = Book.objects.using("default").create( - pk=2, title="Dive into Python", published=datetime.date(2009, 5, 4) + title="Dive into Python", published=datetime.date(2009, 5, 4) ) - mark = Person.objects.using("default").create(pk=2, name="Mark Pilgrim") + mark = Person.objects.using("default").create(name="Mark Pilgrim") # Now save back onto the usual database. # This simulates primary/replica - the objects exist on both database, @@ -1737,14 +1737,16 @@ def test_m2m_cross_database_protection(self): # If you create an object through a M2M relation, it will be # written to the write database, even if the original object # was on the read database - alice = dive.authors.create(name="Alice", pk=3) + alice = dive.authors.create(name="Alice") self.assertEqual(alice._state.db, "default") # Same goes for get_or_create, regardless of whether getting or creating alice, created = dive.authors.get_or_create(name="Alice") self.assertEqual(alice._state.db, "default") - bob, created = dive.authors.get_or_create(name="Bob", defaults={"pk": 4}) + bob, created = dive.authors.get_or_create( + name="Bob", defaults={"pk": "000000000000000000000004"} + ) self.assertEqual(bob._state.db, "default") def test_o2o_cross_database_protection(self): @@ -1848,10 +1850,10 @@ def test_generic_key_cross_database_protection(self): def test_m2m_managers(self): "M2M relations are represented by managers, and can be controlled like managers" pro = Book.objects.using("other").create( - pk=1, title="Pro Django", published=datetime.date(2008, 12, 16) + title="Pro Django", published=datetime.date(2008, 12, 16) ) - marty = Person.objects.using("other").create(pk=1, name="Marty Alchin") + marty = Person.objects.using("other").create(name="Marty Alchin") self.assertEqual(pro.authors.db, "other") self.assertEqual(pro.authors.db_manager("default").db, "default") @@ -1866,9 +1868,8 @@ def test_foreign_key_managers(self): FK reverse relations are represented by managers, and can be controlled like managers. """ - marty = Person.objects.using("other").create(pk=1, name="Marty Alchin") + marty = Person.objects.using("other").create(name="Marty Alchin") Book.objects.using("other").create( - pk=1, title="Pro Django", published=datetime.date(2008, 12, 16), editor=marty, diff --git a/tests/or_lookups/tests.py b/tests/or_lookups/tests.py index bfcb32bea7..9fc6379f39 100644 --- a/tests/or_lookups/tests.py +++ b/tests/or_lookups/tests.py @@ -95,7 +95,9 @@ def test_pk_in(self): ) self.assertQuerySetEqual( - Article.objects.filter(pk__in=[self.a1, self.a2, self.a3, 40000]), + Article.objects.filter( + pk__in=[self.a1, self.a2, self.a3, "000000000000000000040000"] + ), ["Hello", "Goodbye", "Hello and goodbye"], attrgetter("headline"), ) diff --git a/tests/order_with_respect_to/base_tests.py b/tests/order_with_respect_to/base_tests.py index 5170c6d957..2a2ce9657a 100644 --- a/tests/order_with_respect_to/base_tests.py +++ b/tests/order_with_respect_to/base_tests.py @@ -19,10 +19,10 @@ def setUpTestData(cls): cls.q1 = cls.Question.objects.create( text="Which Beatle starts with the letter 'R'?" ) - cls.Answer.objects.create(text="John", question=cls.q1) - cls.Answer.objects.create(text="Paul", question=cls.q1) - cls.Answer.objects.create(text="George", question=cls.q1) - cls.Answer.objects.create(text="Ringo", question=cls.q1) + cls.a1 = cls.Answer.objects.create(text="John", question=cls.q1) + cls.a2 = cls.Answer.objects.create(text="Paul", question=cls.q1) + cls.a3 = cls.Answer.objects.create(text="George", question=cls.q1) + cls.a4 = cls.Answer.objects.create(text="Ringo", question=cls.q1) def test_default_to_insertion_order(self): # Answers will always be ordered in the order they were inserted. @@ -125,4 +125,6 @@ def db_for_write(self, model, **hints): using="other", ), ): - self.q1.set_answer_order([3, 1, 2, 4]) + self.q1.set_answer_order( + [self.a3.pk, self.a1.pk, self.a2.pk, self.a4.pk] + ) diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index 4f41e17215..1313680921 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -1706,14 +1706,16 @@ class Ticket19607Tests(TestCase): @classmethod def setUpTestData(cls): LessonEntry.objects.bulk_create( - LessonEntry(id=id_, name1=name1, name2=name2) + LessonEntry(id=f"{id_:024}", name1=name1, name2=name2) for id_, name1, name2 in [ (1, "einfach", "simple"), (2, "schwierig", "difficult"), ] ) WordEntry.objects.bulk_create( - WordEntry(id=id_, lesson_entry_id=lesson_entry_id, name=name) + WordEntry( + id=f"{id_:024}", lesson_entry_id=f"{lesson_entry_id:024}", name=name + ) for id_, lesson_entry_id, name in [ (1, 1, "einfach"), (2, 1, "simple"), diff --git a/tests/proxy_models/fixtures/mypeople.json b/tests/proxy_models/fixtures/mypeople.json index d20c8f2a6e..1414ad57bd 100644 --- a/tests/proxy_models/fixtures/mypeople.json +++ b/tests/proxy_models/fixtures/mypeople.json @@ -1,9 +1,9 @@ [ { - "pk": 100, + "pk": "000000000000000000000100", "model": "proxy_models.myperson", "fields": { "name": "Elvis Presley" } } -] \ No newline at end of file +] diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py index f1476fec3e..a9bd288a74 100644 --- a/tests/proxy_models/tests.py +++ b/tests/proxy_models/tests.py @@ -107,24 +107,26 @@ def test_proxy_included_in_ancestors(self): Proxy models are included in the ancestors for a model's DoesNotExist and MultipleObjectsReturned """ - Person.objects.create(name="Foo McBar", pk=1) - MyPerson.objects.create(name="Bazza del Frob", pk=2) - LowerStatusPerson.objects.create(status="low", name="homer", pk=3) - max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] + Person.objects.create(name="Foo McBar", pk="000000000000000000000001") + MyPerson.objects.create(name="Bazza del Frob", pk="000000000000000000000002") + LowerStatusPerson.objects.create( + status="low", name="homer", pk="000000000000000000000002" + ) + max_id = int(str(Person.objects.aggregate(max_id=models.Max("id"))["max_id"])) with self.assertRaises(Person.DoesNotExist): MyPersonProxy.objects.get(name="Zathras") with self.assertRaises(Person.MultipleObjectsReturned): - MyPersonProxy.objects.get(id__lt=max_id + 1) + MyPersonProxy.objects.get(id__lt=f"{max_id + 1:024}") with self.assertRaises(Person.DoesNotExist): StatusPerson.objects.get(name="Zathras") - StatusPerson.objects.create(name="Bazza Jr.", pk=4) - StatusPerson.objects.create(name="Foo Jr.", pk=5) - max_id = Person.objects.aggregate(max_id=models.Max("id"))["max_id"] + StatusPerson.objects.create(name="Bazza Jr.", pk="000000000000000000000004") + StatusPerson.objects.create(name="Foo Jr.", pk="000000000000000000000005") + max_id = int(str(Person.objects.aggregate(max_id=models.Max("id"))["max_id"])) with self.assertRaises(Person.MultipleObjectsReturned): - StatusPerson.objects.get(id__lt=max_id + 1) + StatusPerson.objects.get(id__lt=f"{max_id + 1:024}") def test_abstract_base_with_model_fields(self): msg = ( @@ -392,7 +394,7 @@ def test_proxy_bug(self): def test_proxy_load_from_fixture(self): management.call_command("loaddata", "mypeople.json", verbosity=0) - p = MyPerson.objects.get(pk=100) + p = MyPerson.objects.get(pk="000000000000000000000100") self.assertEqual(p.name, "Elvis Presley") def test_select_related_only(self): diff --git a/tests/queries/test_bulk_update.py b/tests/queries/test_bulk_update.py index 956edecbd6..66e085501e 100644 --- a/tests/queries/test_bulk_update.py +++ b/tests/queries/test_bulk_update.py @@ -202,7 +202,7 @@ def test_custom_pk(self): ) def test_falsey_pk_value(self): - order = Order.objects.create(pk=0, name="test") + order = Order.objects.create(pk="000000000000000000000000", name="test") order.name = "updated" Order.objects.bulk_update([order], ["name"]) order.refresh_from_db() diff --git a/tests/queries/tests.py b/tests/queries/tests.py index e1a883e540..d514d767c0 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -127,9 +127,9 @@ def setUpTestData(cls): cls.t4 = Tag.objects.create(name="t4", parent=cls.t3) cls.t5 = Tag.objects.create(name="t5", parent=cls.t3) - cls.n1 = Note.objects.create(note="n1", misc="foo", id=1) - cls.n2 = Note.objects.create(note="n2", misc="bar", id=2) - cls.n3 = Note.objects.create(note="n3", misc="foo", id=3, negate=False) + cls.n1 = Note.objects.create(note="n1", misc="foo") + cls.n2 = Note.objects.create(note="n2", misc="bar") + cls.n3 = Note.objects.create(note="n3", misc="foo", negate=False) cls.ann1 = Annotation.objects.create(name="a1", tag=cls.t1) cls.ann1.notes.add(cls.n1) @@ -184,7 +184,7 @@ def setUpTestData(cls): cls.c2 = Cover.objects.create(title="second", item=cls.i2) def test_subquery_condition(self): - qs1 = Tag.objects.filter(pk__lte=0) + qs1 = Tag.objects.filter(pk__lte="000000000000000000000000") qs2 = Tag.objects.filter(parent__in=qs1) qs3 = Tag.objects.filter(parent__in=qs2) self.assertEqual(qs3.query.subq_aliases, {"T", "U", "V"}) @@ -447,7 +447,9 @@ def test_get_clears_ordering(self): def test_tickets_4088_4306(self): self.assertSequenceEqual(Report.objects.filter(creator=1001), [self.r1]) self.assertSequenceEqual(Report.objects.filter(creator__num=1001), [self.r1]) - self.assertSequenceEqual(Report.objects.filter(creator__id=1001), []) + self.assertSequenceEqual( + Report.objects.filter(creator__id="000000000000000000001001"), [] + ) self.assertSequenceEqual( Report.objects.filter(creator__id=self.a1.id), [self.r1] ) @@ -547,7 +549,7 @@ def test_ticket2091(self): self.assertSequenceEqual(Item.objects.filter(tags__in=[t]), [self.i4]) def test_avoid_infinite_loop_on_too_many_subqueries(self): - x = Tag.objects.filter(pk=1) + x = Tag.objects.filter(pk="000000000000000000000001") local_recursion_limit = sys.getrecursionlimit() // 16 msg = "Maximum recursion depth exceeded: too many subqueries." with self.assertRaisesMessage(RecursionError, msg): @@ -555,7 +557,7 @@ def test_avoid_infinite_loop_on_too_many_subqueries(self): x = Tag.objects.filter(pk__in=x) def test_reasonable_number_of_subq_aliases(self): - x = Tag.objects.filter(pk=1) + x = Tag.objects.filter(pk="000000000000000000000001") for _ in range(20): x = Tag.objects.filter(pk__in=x) self.assertEqual( @@ -700,11 +702,13 @@ def test_ticket4358(self): self.assertIn("note_id", ExtraInfo.objects.values()[0]) # You can also pass it in explicitly. self.assertSequenceEqual( - ExtraInfo.objects.values("note_id"), [{"note_id": 1}, {"note_id": 2}] + ExtraInfo.objects.values("note_id"), + [{"note_id": self.n1.pk}, {"note_id": self.n2.pk}], ) # ...or use the field name. self.assertSequenceEqual( - ExtraInfo.objects.values("note"), [{"note": 1}, {"note": 2}] + ExtraInfo.objects.values("note"), + [{"note": self.n1.pk}, {"note": self.n2.pk}], ) def test_ticket6154(self): @@ -888,7 +892,9 @@ def test_ticket7235(self): self.assertSequenceEqual(q.all(), []) self.assertSequenceEqual(q.filter(meal="m"), []) self.assertSequenceEqual(q.exclude(meal="m"), []) - self.assertSequenceEqual(q.complex_filter({"pk": 1}), []) + self.assertSequenceEqual( + q.complex_filter({"pk": "000000000000000000000001"}), [] + ) self.assertSequenceEqual(q.select_related("food"), []) self.assertSequenceEqual(q.annotate(Count("food")), []) self.assertSequenceEqual(q.order_by("meal", "food"), []) @@ -926,7 +932,7 @@ def test_ticket9985(self): # qs.values_list(...).values(...) combinations should work. self.assertSequenceEqual( Note.objects.values_list("note", flat=True).values("id").order_by("id"), - [{"id": 1}, {"id": 2}, {"id": 3}], + [{"id": self.n1.pk}, {"id": self.n2.pk}, {"id": self.n3.pk}], ) self.assertSequenceEqual( Annotation.objects.filter( @@ -1830,8 +1836,8 @@ class Queries5Tests(TestCase): def setUpTestData(cls): # Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the # Meta.ordering will be rank3, rank2, rank1. - cls.n1 = Note.objects.create(note="n1", misc="foo", id=1) - cls.n2 = Note.objects.create(note="n2", misc="bar", id=2) + cls.n1 = Note.objects.create(note="n1", misc="foo") + cls.n2 = Note.objects.create(note="n2", misc="bar") e1 = ExtraInfo.objects.create(info="e1", note=cls.n1) e2 = ExtraInfo.objects.create(info="e2", note=cls.n2) a1 = Author.objects.create(name="a1", num=1001, extra=e1) @@ -2045,7 +2051,7 @@ def test_join_already_in_query(self): class DisjunctiveFilterTests(TestCase): @classmethod def setUpTestData(cls): - cls.n1 = Note.objects.create(note="n1", misc="foo", id=1) + cls.n1 = Note.objects.create(note="n1", misc="foo") cls.e1 = ExtraInfo.objects.create(info="e1", note=cls.n1) def test_ticket7872(self): @@ -2087,7 +2093,7 @@ def setUpTestData(cls): cls.t3 = Tag.objects.create(name="t3", parent=cls.t1) cls.t4 = Tag.objects.create(name="t4", parent=cls.t3) cls.t5 = Tag.objects.create(name="t5", parent=cls.t3) - n1 = Note.objects.create(note="n1", misc="foo", id=1) + n1 = Note.objects.create(note="n1", misc="foo") cls.ann1 = Annotation.objects.create(name="a1", tag=cls.t1) cls.ann1.notes.add(n1) cls.ann2 = Annotation.objects.create(name="a2", tag=cls.t4) @@ -2119,10 +2125,16 @@ def test_tickets_8921_9188(self): # preemptively discovered cases). self.assertSequenceEqual( - PointerA.objects.filter(connection__pointerb__id=1), [] + PointerA.objects.filter( + connection__pointerb__id="000000000000000000000001" + ), + [], ) self.assertSequenceEqual( - PointerA.objects.exclude(connection__pointerb__id=1), [] + PointerA.objects.exclude( + connection__pointerb__id="000000000000000000000001" + ), + [], ) self.assertSequenceEqual( @@ -2212,7 +2224,7 @@ def test_xor_subquery(self): class RawQueriesTests(TestCase): @classmethod def setUpTestData(cls): - Note.objects.create(note="n1", misc="foo", id=1) + Note.objects.create(note="n1", misc="foo") def test_ticket14729(self): # Test representation of raw query with one or few parameters passed as list @@ -2242,7 +2254,7 @@ def test_ticket10432(self): class ComparisonTests(TestCase): @classmethod def setUpTestData(cls): - cls.n1 = Note.objects.create(note="n1", misc="foo", id=1) + cls.n1 = Note.objects.create(note="n1", misc="foo") e1 = ExtraInfo.objects.create(info="e1", note=cls.n1) cls.a2 = Author.objects.create(name="a2", num=2002, extra=e1) @@ -2884,7 +2896,7 @@ def test_slicing_can_slice_again_after_slicing(self): def test_slicing_cannot_filter_queryset_once_sliced(self): msg = "Cannot filter a query once a slice has been taken." with self.assertRaisesMessage(TypeError, msg): - Article.objects.all()[0:5].filter(id=1) + Article.objects.all()[0:5].filter(name="foo") def test_slicing_cannot_reorder_queryset_once_sliced(self): msg = "Cannot reorder a query once a slice has been taken." @@ -3377,9 +3389,9 @@ class ExcludeTest17600(TestCase): @classmethod def setUpTestData(cls): # Create a few Orders. - cls.o1 = Order.objects.create(pk=1) - cls.o2 = Order.objects.create(pk=2) - cls.o3 = Order.objects.create(pk=3) + cls.o1 = Order.objects.create() + cls.o2 = Order.objects.create() + cls.o3 = Order.objects.create() # Create some OrderItems for the first order with homogeneous # status_id values @@ -3911,7 +3923,7 @@ class DisjunctionPromotionTests(TestCase): def test_disjunction_promotion_select_related(self): fk1 = FK1.objects.create(f1="f1", f2="f2") basea = BaseA.objects.create(a=fk1) - qs = BaseA.objects.filter(Q(a=fk1) | Q(b=2)) + qs = BaseA.objects.filter(Q(a=fk1) | Q(b="000000000000000000000002")) self.assertEqual(str(qs.query).count(" JOIN "), 0) qs = qs.select_related("a", "b") self.assertEqual(str(qs.query).count(" INNER JOIN "), 0) @@ -3967,7 +3979,9 @@ def test_disjunction_promotion3_demote(self): self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 1) def test_disjunction_promotion4_demote(self): - qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + qs = BaseA.objects.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) self.assertEqual(str(qs.query).count("JOIN"), 0) # Demote needed for the "a" join. It is marked as outer join by # above filter (even if it is trimmed away). @@ -3977,11 +3991,15 @@ def test_disjunction_promotion4_demote(self): def test_disjunction_promotion4(self): qs = BaseA.objects.filter(a__f1="foo") self.assertEqual(str(qs.query).count("INNER JOIN"), 1) - qs = qs.filter(Q(a=1) | Q(a=2)) + qs = qs.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) self.assertEqual(str(qs.query).count("INNER JOIN"), 1) def test_disjunction_promotion5_demote(self): - qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + qs = BaseA.objects.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) # Note that the above filters on a force the join to an # inner join even if it is trimmed. self.assertEqual(str(qs.query).count("JOIN"), 0) @@ -3993,12 +4011,16 @@ def test_disjunction_promotion5_demote(self): qs = BaseA.objects.filter(Q(a__f1="foo") | Q(b__f1="foo")) # Now the join to a is created as LOUTER self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 2) - qs = qs.filter(Q(a=1) | Q(a=2)) + qs = qs.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) self.assertEqual(str(qs.query).count("INNER JOIN"), 1) self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 1) def test_disjunction_promotion6(self): - qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + qs = BaseA.objects.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) self.assertEqual(str(qs.query).count("JOIN"), 0) qs = BaseA.objects.filter(Q(a__f1="foo") & Q(b__f1="foo")) self.assertEqual(str(qs.query).count("INNER JOIN"), 2) @@ -4007,12 +4029,16 @@ def test_disjunction_promotion6(self): qs = BaseA.objects.filter(Q(a__f1="foo") & Q(b__f1="foo")) self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 0) self.assertEqual(str(qs.query).count("INNER JOIN"), 2) - qs = qs.filter(Q(a=1) | Q(a=2)) + qs = qs.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) self.assertEqual(str(qs.query).count("INNER JOIN"), 2) self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 0) def test_disjunction_promotion7(self): - qs = BaseA.objects.filter(Q(a=1) | Q(a=2)) + qs = BaseA.objects.filter( + Q(a="000000000000000000000001") | Q(a="000000000000000000000002") + ) self.assertEqual(str(qs.query).count("JOIN"), 0) qs = BaseA.objects.filter(Q(a__f1="foo") | (Q(b__f1="foo") & Q(a__f1="bar"))) self.assertEqual(str(qs.query).count("INNER JOIN"), 1) @@ -4038,7 +4064,10 @@ def test_disjunction_promotion_fexpression(self): Q(a__f1=F("b__f1")) | Q(a__f2=F("b__f2")) | Q(c__f1="foo") ) self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 3) - qs = BaseA.objects.filter(Q(a__f1=F("c__f1")) | (Q(pk=1) & Q(pk=2))) + qs = BaseA.objects.filter( + Q(a__f1=F("c__f1")) + | (Q(pk="000000000000000000000001") & Q(pk="000000000000000000000002")) + ) self.assertEqual(str(qs.query).count("LEFT OUTER JOIN"), 2) self.assertEqual(str(qs.query).count("INNER JOIN"), 0) @@ -4400,7 +4429,7 @@ def test_ticket_21376(self): Q(objecta=a) | Q(objectb__objecta=a), ) qs = qs.filter( - Q(objectb=1) | Q(objecta=a), + Q(objectb="000000000000000000000001") | Q(objecta=a), ) self.assertEqual(qs.count(), 1) tblname = connection.ops.quote_name(ObjectB._meta.db_table) diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index 28079d2c86..9450a88239 100644 --- a/tests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -58,7 +58,7 @@ def test_staticmethod_as_default(self): self.assert_pickles(Happening.objects.filter(number2=1)) def test_filter_reverse_fk(self): - self.assert_pickles(Group.objects.filter(event=1)) + self.assert_pickles(Group.objects.filter(event="000000000000000000000001")) def test_doesnotexist_exception(self): # Ticket #17776 @@ -97,7 +97,7 @@ def test_model_pickle(self): """ A model not defined on module level is picklable. """ - original = Container.SomeModel(pk=1) + original = Container.SomeModel(pk="000000000000000000000001") dumped = pickle.dumps(original) reloaded = pickle.loads(dumped) self.assertEqual(original, reloaded) @@ -176,7 +176,9 @@ def test_pickle_prefetch_queryset_still_usable(self): models.Prefetch("event_set", queryset=Event.objects.order_by("id")) ) groups2 = pickle.loads(pickle.dumps(groups)) - self.assertSequenceEqual(groups2.filter(id__gte=0), [g]) + self.assertSequenceEqual( + groups2.filter(id__gte="000000000000000000000000"), [g] + ) def test_pickle_prefetch_queryset_not_evaluated(self): Group.objects.create(name="foo") @@ -327,7 +329,7 @@ def test_annotation_values_list(self): def test_filter_deferred(self): qs = Happening.objects.all() qs._defer_next_filter = True - qs = qs.filter(id=0) + qs = qs.filter(id="000000000000000000000000") self.assert_pickles(qs) def test_missing_django_version_unpickling(self): diff --git a/tests/redirects_tests/tests.py b/tests/redirects_tests/tests.py index d175be62fb..0ca35ce720 100644 --- a/tests/redirects_tests/tests.py +++ b/tests/redirects_tests/tests.py @@ -12,7 +12,7 @@ "append": "django.contrib.redirects.middleware.RedirectFallbackMiddleware" } ) -@override_settings(APPEND_SLASH=False, ROOT_URLCONF="redirects_tests.urls", SITE_ID=1) +@override_settings(APPEND_SLASH=False, ROOT_URLCONF="redirects_tests.urls") class RedirectTests(TestCase): @classmethod def setUpTestData(cls): @@ -95,7 +95,6 @@ class OverriddenRedirectFallbackMiddleware(RedirectFallbackMiddleware): @modify_settings( MIDDLEWARE={"append": "redirects_tests.tests.OverriddenRedirectFallbackMiddleware"} ) -@override_settings(SITE_ID=1) class OverriddenRedirectMiddlewareTests(TestCase): @classmethod def setUpTestData(cls): diff --git a/tests/runtests.py b/tests/runtests.py index f6cb580aee..734f27310f 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -14,6 +14,7 @@ from pathlib import Path import django_mongodb_backend +from bson import ObjectId try: import django @@ -251,7 +252,7 @@ def setup_collect_tests(start_at, start_after, test_labels=None): } ] settings.LANGUAGE_CODE = "en" - settings.SITE_ID = 1 + settings.SITE_ID = ObjectId("000000000000000000000001") settings.MIDDLEWARE = ALWAYS_MIDDLEWARE settings.MIGRATION_MODULES = { # This lets us skip creating migrations for the test models as many of @@ -267,6 +268,7 @@ def setup_collect_tests(start_at, start_after, test_labels=None): settings.LOGGING = log_config settings.SILENCED_SYSTEM_CHECKS = [ "fields.W342", # ForeignKey(unique=True) -> OneToOneField + "sites.E101", # SITE_ID must be an ObjectId for MongoDB. ] # Load all the ALWAYS_INSTALLED_APPS. diff --git a/tests/serializers/models/data.py b/tests/serializers/models/data.py index 77625c05e9..77ad596699 100644 --- a/tests/serializers/models/data.py +++ b/tests/serializers/models/data.py @@ -7,6 +7,8 @@ import uuid +from django_mongodb_backend.fields import ObjectIdField + from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models @@ -108,7 +110,7 @@ class Tag(models.Model): data = models.SlugField() content_type = models.ForeignKey(ContentType, models.CASCADE) - object_id = models.PositiveIntegerField() + object_id = ObjectIdField() content_object = GenericForeignKey() diff --git a/tests/serializers/test_data.py b/tests/serializers/test_data.py index 6a6de18033..90079f004d 100644 --- a/tests/serializers/test_data.py +++ b/tests/serializers/test_data.py @@ -12,6 +12,8 @@ import uuid from collections import namedtuple +from bson import ObjectId + from django.core import serializers from django.db import connection, models from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature @@ -79,6 +81,16 @@ ) from .tests import register_tests + +def prep_value(value): + """Format a data value for MongoDB (convert int to ObjectId).""" + return f"{value:024}" if isinstance(value, int) else value + + +def value_to_object_id(value): + return ObjectId(f"{value:024}") if isinstance(value, int) else value + + # A set of functions that can be used to recreate # test data objects of various kinds. # The save method is a raw base model save, to make @@ -104,7 +116,7 @@ def generic_create(pk, klass, data): def fk_create(pk, klass, data): instance = klass(id=pk) - setattr(instance, "data_id", data) + setattr(instance, "data_id", prep_value(data)) models.Model.save_base(instance, raw=True) return [instance] @@ -112,7 +124,7 @@ def fk_create(pk, klass, data): def m2m_create(pk, klass, data): instance = klass(id=pk) models.Model.save_base(instance, raw=True) - instance.data.set(data) + instance.data.set([f"{d:024}" for d in data]) return [instance] @@ -124,8 +136,10 @@ def im2m_create(pk, klass, data): def im_create(pk, klass, data): instance = klass(id=pk) - instance.right_id = data["right"] - instance.left_id = data["left"] + instance.right_id = ( + f'{data["right"]:024}' # if data is not None else data # data["right"] + ) + instance.left_id = f'{data["left"]:024}' if "extra" in data: instance.extra = data["extra"] models.Model.save_base(instance, raw=True) @@ -134,7 +148,7 @@ def im_create(pk, klass, data): def o2o_create(pk, klass, data): instance = klass() - instance.data_id = data + instance.data_id = f"{data:024}" if data is not None else data models.Model.save_base(instance, raw=True) return [instance] @@ -170,7 +184,7 @@ def data_compare(testcase, pk, klass, data): testcase.assertEqual( bytes(data), bytes(instance.data), - "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" + "Objects with PK=%s not equal; expected '%s' (%s), got '%s' (%s)" % ( pk, repr(bytes(data)), @@ -183,7 +197,7 @@ def data_compare(testcase, pk, klass, data): testcase.assertEqual( data, instance.data, - "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" + "Objects with PK=%s not equal; expected '%s' (%s), got '%s' (%s)" % ( pk, data, @@ -202,12 +216,15 @@ def generic_compare(testcase, pk, klass, data): def fk_compare(testcase, pk, klass, data): instance = klass.objects.get(id=pk) - testcase.assertEqual(data, instance.data_id) + testcase.assertEqual(value_to_object_id(data), instance.data_id) def m2m_compare(testcase, pk, klass, data): instance = klass.objects.get(id=pk) - testcase.assertEqual(data, [obj.id for obj in instance.data.order_by("id")]) + testcase.assertEqual( + [value_to_object_id(d) for d in data], + [obj.id for obj in instance.data.order_by("id")], + ) def im2m_compare(testcase, pk, klass, data): @@ -217,8 +234,8 @@ def im2m_compare(testcase, pk, klass, data): def im_compare(testcase, pk, klass, data): instance = klass.objects.get(id=pk) - testcase.assertEqual(data["left"], instance.left_id) - testcase.assertEqual(data["right"], instance.right_id) + testcase.assertEqual(value_to_object_id(data["left"]), instance.left_id) + testcase.assertEqual(value_to_object_id(data["right"]), instance.right_id) if "extra" in data: testcase.assertEqual(data["extra"], instance.extra) else: @@ -226,8 +243,8 @@ def im_compare(testcase, pk, klass, data): def o2o_compare(testcase, pk, klass, data): - instance = klass.objects.get(data=data) - testcase.assertEqual(data, instance.data_id) + instance = klass.objects.get(data=prep_value(data)) + testcase.assertEqual(value_to_object_id(data), instance.data_id) def pk_compare(testcase, pk, klass, data): @@ -424,7 +441,7 @@ def assert_serializer(self, format, data): objects = [] for test_helper, pk, model, data_value in data: with connection.constraint_checks_disabled(): - objects.extend(test_helper.create_object(pk, model, data_value)) + objects.extend(test_helper.create_object(prep_value(pk), model, data_value)) # Get a count of the number of objects created for each model class. instance_counts = {} @@ -444,7 +461,7 @@ def assert_serializer(self, format, data): # Assert that the deserialized data is the same as the original source. for test_helper, pk, model, data_value in data: with self.subTest(model=model, data_value=data_value): - test_helper.compare_object(self, pk, model, data_value) + test_helper.compare_object(self, prep_value(pk), model, data_value) # Assert no new objects were created. for model, count in instance_counts.items(): diff --git a/tests/serializers/test_deserialization.py b/tests/serializers/test_deserialization.py index 0bbb46b7ce..142680b610 100644 --- a/tests/serializers/test_deserialization.py +++ b/tests/serializers/test_deserialization.py @@ -1,6 +1,8 @@ import json import unittest +from bson import ObjectId + from django.core.serializers.base import DeserializationError, DeserializedObject from django.core.serializers.json import Deserializer as JsonDeserializer from django.core.serializers.jsonl import Deserializer as JsonlDeserializer @@ -20,17 +22,26 @@ class TestDeserializer(SimpleTestCase): def setUp(self): self.object_list = [ - {"pk": 1, "model": "serializers.author", "fields": {"name": "Jane"}}, - {"pk": 2, "model": "serializers.author", "fields": {"name": "Joe"}}, + { + "pk": "000000000000000000000001", + "model": "serializers.author", + "fields": {"name": "Jane"}, + }, + { + "pk": "000000000000000000000002", + "model": "serializers.author", + "fields": {"name": "Joe"}, + }, ] self.deserializer = Deserializer(self.object_list) - self.jane = Author(name="Jane", pk=1) - self.joe = Author(name="Joe", pk=2) + self.jane = Author(name="Jane", pk=ObjectId("000000000000000000000001")) + self.joe = Author(name="Joe", pk=ObjectId("000000000000000000000002")) def test_deserialized_object_repr(self): deserial_obj = DeserializedObject(obj=self.jane) self.assertEqual( - repr(deserial_obj), "" + repr(deserial_obj), + "", ) def test_next_functionality(self): @@ -46,7 +57,11 @@ def test_next_functionality(self): def test_invalid_model_identifier(self): invalid_object_list = [ - {"pk": 1, "model": "serializers.author2", "fields": {"name": "Jane"}} + { + "pk": "000000000000000000000001", + "model": "serializers.author2", + "fields": {"name": "Jane"}, + } ] self.deserializer = Deserializer(invalid_object_list) with self.assertRaises(DeserializationError): @@ -87,11 +102,12 @@ def test_json_bytes_input(self): self.assertEqual(second_item.object, self.joe) def test_jsonl_bytes_input(self): - test_string = """ - {"pk": 1, "model": "serializers.author", "fields": {"name": "Jane"}} - {"pk": 2, "model": "serializers.author", "fields": {"name": "Joe"}} - {"pk": 3, "model": "serializers.author", "fields": {"name": "John"}} - {"pk": 4, "model": "serializers.author", "fields": {"name": "Smith"}}""" + zeros = "00000000000000000000000" + test_string = f""" +{{"pk": "{zeros}1", "model": "serializers.author", "fields": {{"name": "Jane"}}}} +{{"pk": "{zeros}2", "model": "serializers.author", "fields": {{"name": "Joe"}}}} +{{"pk": "{zeros}3", "model": "serializers.author", "fields": {{"name": "John"}}}} +{{"pk": "{zeros}4", "model": "serializers.author", "fields": {{"name": "Smith"}}}}""" stream = test_string.encode("utf-8") deserializer = JsonlDeserializer(stream_or_string=stream) @@ -105,22 +121,22 @@ def test_jsonl_bytes_input(self): def test_yaml_bytes_input(self): from django.core.serializers.pyyaml import Deserializer as YamlDeserializer - test_string = """- pk: 1 + test_string = """- pk: "000000000000000000000001" model: serializers.author fields: name: Jane -- pk: 2 +- pk: "000000000000000000000002" model: serializers.author fields: name: Joe -- pk: 3 +- pk: "000000000000000000000003" model: serializers.author fields: name: John -- pk: 4 +- pk: "000000000000000000000004" model: serializers.author fields: name: Smith diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py index 6d67bfdb43..42afec4ca7 100644 --- a/tests/serializers/test_json.py +++ b/tests/serializers/test_json.py @@ -121,8 +121,9 @@ def test_helpful_error_message_invalid_field(self): If there is an invalid field value, the error message should contain the model associated with it. """ + pk = "000000000000000000000001" test_string = """[{ - "pk": "1", + "pk": "000000000000000000000001", "model": "serializers.player", "fields": { "name": "Bob", @@ -130,7 +131,7 @@ def test_helpful_error_message_invalid_field(self): "team": "Team" } }]""" - expected = "(serializers.player:pk=1) field_value was 'invalidint'" + expected = f"(serializers.player:pk={pk}) field_value was 'invalidint'" with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize("json", test_string)) @@ -139,8 +140,9 @@ def test_helpful_error_message_for_foreign_keys(self): Invalid foreign keys with a natural key should throw a helpful error message, such as what the failing key is. """ + pk = "000000000000000000000001" test_string = """[{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.category", "fields": { "name": "Unknown foreign key", @@ -151,7 +153,7 @@ def test_helpful_error_message_for_foreign_keys(self): } }]""" key = ["doesnotexist", "metadata"] - expected = "(serializers.category:pk=1) field_value was '%r'" % key + expected = f"(serializers.category:pk={pk}) field_value was '%r'" % key with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize("json", test_string)) @@ -159,29 +161,30 @@ def test_helpful_error_message_for_many2many_non_natural(self): """ Invalid many-to-many keys should throw a helpful error message. """ + pk = "000000000000000000000001" test_string = """[{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { - "author": 1, + "author": "000000000000000000000001", "headline": "Unknown many to many", "pub_date": "2014-09-15T10:35:00", - "categories": [1, "doesnotexist"] + "categories": ["000000000000000000000001", "doesnotexist"] } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": { "name": "Agnes" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.category", "fields": { "name": "Reference" } }]""" - expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" + expected = f"(serializers.article:pk={pk}) field_value was 'doesnotexist'" with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize("json", test_string)) @@ -191,7 +194,7 @@ def test_helpful_error_message_for_many2many_natural1(self): This tests the code path where one of a list of natural keys is invalid. """ test_string = """[{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.categorymetadata", "fields": { "kind": "author", @@ -199,10 +202,10 @@ def test_helpful_error_message_for_many2many_natural1(self): "value": "Agnes" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { - "author": 1, + "author": "000000000000000000000001", "headline": "Unknown many to many", "pub_date": "2014-09-15T10:35:00", "meta_data": [ @@ -212,14 +215,17 @@ def test_helpful_error_message_for_many2many_natural1(self): ] } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": { "name": "Agnes" } }]""" key = ["doesnotexist", "meta1"] - expected = "(serializers.article:pk=1) field_value was '%r'" % key + expected = ( + "(serializers.article:pk=000000000000000000000001) field_value was '%r'" + % key + ) with self.assertRaisesMessage(DeserializationError, expected): for obj in serializers.deserialize("json", test_string): obj.save() @@ -230,17 +236,18 @@ def test_helpful_error_message_for_many2many_natural2(self): tests the code path where a natural many-to-many key has only a single value. """ + pk = "000000000000000000000001" test_string = """[{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { - "author": 1, + "author": "000000000000000000000001", "headline": "Unknown many to many", "pub_date": "2014-09-15T10:35:00", - "meta_data": [1, "doesnotexist"] + "meta_data": ["000000000000000000000001", "doesnotexist"] } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.categorymetadata", "fields": { "kind": "author", @@ -248,13 +255,13 @@ def test_helpful_error_message_for_many2many_natural2(self): "value": "Agnes" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": { "name": "Agnes" } }]""" - expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" + expected = f"(serializers.article:pk={pk}) field_value was 'doesnotexist'" with self.assertRaisesMessage(DeserializationError, expected): for obj in serializers.deserialize("json", test_string, ignore=False): obj.save() @@ -263,13 +270,14 @@ def test_helpful_error_message_for_many2many_not_iterable(self): """ Not iterable many-to-many field value throws a helpful error message. """ + pk = "000000000000000000000001" test_string = """[{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.m2mdata", "fields": {"data": null} }]""" - expected = "(serializers.m2mdata:pk=1) field_value was 'None'" + expected = f"(serializers.m2mdata:pk={pk}) field_value was 'None'" with self.assertRaisesMessage(DeserializationError, expected): next(serializers.deserialize("json", test_string, ignore=False)) @@ -280,24 +288,24 @@ class JsonSerializerTransactionTestCase( serializer_name = "json" fwd_ref_str = """[ { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { "headline": "Forward references pose no problem", "pub_date": "2006-06-16T15:00:00", - "categories": [1], - "author": 1 + "categories": ["000000000000000000000001"], + "author": "000000000000000000000001" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.category", "fields": { "name": "Reference" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": { "name": "Agnes" diff --git a/tests/serializers/test_jsonl.py b/tests/serializers/test_jsonl.py index 73fe725602..bb6e861df6 100644 --- a/tests/serializers/test_jsonl.py +++ b/tests/serializers/test_jsonl.py @@ -104,11 +104,12 @@ def test_helpful_error_message_invalid_field(self): If there is an invalid field value, the error message contains the model associated with it. """ + pk = "000000000000000000000001" test_string = ( - '{"pk": "1","model": "serializers.player",' - '"fields": {"name": "Bob","rank": "invalidint","team": "Team"}}' + '{"pk": "%s","model": "serializers.player",' + '"fields": {"name": "Bob","rank": "invalidint","team": "Team"}}' % pk ) - expected = "(serializers.player:pk=1) field_value was 'invalidint'" + expected = f"(serializers.player:pk={pk}) field_value was 'invalidint'" with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize("jsonl", test_string)) @@ -117,14 +118,15 @@ def test_helpful_error_message_for_foreign_keys(self): Invalid foreign keys with a natural key throws a helpful error message, such as what the failing key is. """ + pk = "000000000000000000000001" test_string = ( - '{"pk": 1, "model": "serializers.category",' + '{"pk": "000000000000000000000001", "model": "serializers.category",' '"fields": {' '"name": "Unknown foreign key",' '"meta_data": ["doesnotexist","metadata"]}}' ) key = ["doesnotexist", "metadata"] - expected = "(serializers.category:pk=1) field_value was '%r'" % key + expected = f"(serializers.category:pk={pk}) field_value was '%r'" % key with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize("jsonl", test_string)) @@ -132,30 +134,31 @@ def test_helpful_error_message_for_many2many_non_natural(self): """ Invalid many-to-many keys throws a helpful error message. """ + pk = "000000000000000000000001" test_strings = [ """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { - "author": 1, + "author": "000000000000000000000001", "headline": "Unknown many to many", "pub_date": "2014-09-15T10:35:00", - "categories": [1, "doesnotexist"] + "categories": ["000000000000000000000001", "doesnotexist"] } }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": {"name": "Agnes"} }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.category", "fields": {"name": "Reference"} }""", ] test_string = "\n".join([s.replace("\n", "") for s in test_strings]) - expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" + expected = f"(serializers.article:pk={pk}) field_value was 'doesnotexist'" with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize("jsonl", test_string)) @@ -164,17 +167,18 @@ def test_helpful_error_message_for_many2many_natural1(self): Invalid many-to-many keys throws a helpful error message where one of a list of natural keys is invalid. """ + pk = "000000000000000000000001" test_strings = [ """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.categorymetadata", "fields": {"kind": "author","name": "meta1","value": "Agnes"} }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { - "author": 1, + "author": "000000000000000000000001", "headline": "Unknown many to many", "pub_date": "2014-09-15T10:35:00", "meta_data": [ @@ -185,14 +189,14 @@ def test_helpful_error_message_for_many2many_natural1(self): } }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": {"name": "Agnes"} }""", ] test_string = "\n".join([s.replace("\n", "") for s in test_strings]) key = ["doesnotexist", "meta1"] - expected = "(serializers.article:pk=1) field_value was '%r'" % key + expected = f"(serializers.article:pk={pk}) field_value was '%r'" % key with self.assertRaisesMessage(DeserializationError, expected): for obj in serializers.deserialize("jsonl", test_string): obj.save() @@ -202,30 +206,31 @@ def test_helpful_error_message_for_many2many_natural2(self): Invalid many-to-many keys throws a helpful error message where a natural many-to-many key has only a single value. """ + pk = "000000000000000000000001" test_strings = [ """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { - "author": 1, + "author": "000000000000000000000001", "headline": "Unknown many to many", "pub_date": "2014-09-15T10:35:00", - "meta_data": [1, "doesnotexist"] + "meta_data": ["000000000000000000000001", "doesnotexist"] } }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.categorymetadata", "fields": {"kind": "author","name": "meta1","value": "Agnes"} }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": {"name": "Agnes"} }""", ] test_string = "\n".join([s.replace("\n", "") for s in test_strings]) - expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" + expected = f"(serializers.article:pk={pk}) field_value was 'doesnotexist'" with self.assertRaisesMessage(DeserializationError, expected): for obj in serializers.deserialize("jsonl", test_string, ignore=False): obj.save() @@ -248,22 +253,22 @@ class JsonSerializerTransactionTestCase( serializer_name = "jsonl" fwd_ref_str = [ """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.article", "fields": { "headline": "Forward references pose no problem", "pub_date": "2006-06-16T15:00:00", - "categories": [1], - "author": 1 + "categories": ["000000000000000000000001"], + "author": "000000000000000000000001" } }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.category", "fields": {"name": "Reference"} }""", """{ - "pk": 1, + "pk": "000000000000000000000001", "model": "serializers.author", "fields": {"name": "Agnes"} }""", diff --git a/tests/serializers/test_natural.py b/tests/serializers/test_natural.py index b5b35708c6..cf9d292374 100644 --- a/tests/serializers/test_natural.py +++ b/tests/serializers/test_natural.py @@ -21,9 +21,15 @@ def natural_key_serializer_test(self, format): # Create all the objects defined in the test data with connection.constraint_checks_disabled(): objects = [ - NaturalKeyAnchor.objects.create(id=1100, data="Natural Key Anghor"), - FKDataNaturalKey.objects.create(id=1101, data_id=1100), - FKDataNaturalKey.objects.create(id=1102, data_id=None), + NaturalKeyAnchor.objects.create( + id="000000000000000000001100", data="Natural Key Anghor" + ), + FKDataNaturalKey.objects.create( + id="000000000000000000001101", data_id="000000000000000000001100" + ), + FKDataNaturalKey.objects.create( + id="000000000000000000001102", data_id=None + ), ] # Serialize the test database serialized_data = serializers.serialize( @@ -40,7 +46,7 @@ def natural_key_serializer_test(self, format): self.assertEqual( obj.data, instance.data, - "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" + "Objects with PK=%s not equal; expected '%s' (%s), got '%s' (%s)" % ( obj.pk, obj.data, diff --git a/tests/serializers/test_xml.py b/tests/serializers/test_xml.py index 0ae66f77d0..38bf9b7b49 100644 --- a/tests/serializers/test_xml.py +++ b/tests/serializers/test_xml.py @@ -97,18 +97,18 @@ class XmlSerializerTransactionTestCase( serializer_name = "xml" fwd_ref_str = """ - - 1 + + 000000000000000000000001 Forward references pose no problem 2006-06-16T15:00:00 - + - + Agnes - + Reference """ # NOQA diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py index 6db6f046fd..8069534b45 100644 --- a/tests/serializers/test_yaml.py +++ b/tests/serializers/test_yaml.py @@ -162,17 +162,17 @@ class YamlSerializerTransactionTestCase( ): serializer_name = "yaml" fwd_ref_str = """- model: serializers.article - pk: 1 + pk: "000000000000000000000001" fields: headline: Forward references pose no problem pub_date: 2006-06-16 15:00:00 - categories: [1] - author: 1 + categories: ["000000000000000000000001"] + author: "000000000000000000000001" - model: serializers.category - pk: 1 + pk: "000000000000000000000001" fields: name: Reference - model: serializers.author - pk: 1 + pk: "000000000000000000000001" fields: name: Agnes""" diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py index cb6df67f00..0733c81503 100644 --- a/tests/serializers/tests.py +++ b/tests/serializers/tests.py @@ -486,11 +486,13 @@ class Serializer(serializers.json.Serializer): stream_class = File serializer = Serializer() - data = serializer.serialize([Score(id=1, score=3.4)]) + data = serializer.serialize([Score(id="000000000000000000000001", score=3.4)]) self.assertIs(serializer.stream_class, File) self.assertIsInstance(serializer.stream, File) self.assertEqual( - data, '[{"model": "serializers.score", "pk": 1, "fields": {"score": 3.4}}]' + data, + '[{"model": "serializers.score", "pk": "000000000000000000000001", ' + '"fields": {"score": 3.4}}]', ) diff --git a/tests/servers/fixtures/testdata.json b/tests/servers/fixtures/testdata.json index d81b2253d2..644f1b5aba 100644 --- a/tests/servers/fixtures/testdata.json +++ b/tests/servers/fixtures/testdata.json @@ -1,16 +1,16 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "servers.person", "fields": { "name": "jane" } }, { - "pk": 2, + "pk": "000000000000000000000002", "model": "servers.person", "fields": { "name": "robert" } } -] \ No newline at end of file +] diff --git a/tests/signals/tests.py b/tests/signals/tests.py index 6c90c6aa52..9217c31354 100644 --- a/tests/signals/tests.py +++ b/tests/signals/tests.py @@ -100,7 +100,7 @@ def post_save_handler(signal, sender, instance, **kwargs): data[:] = [] p2 = Person(first_name="James", last_name="Jones") - p2.id = 99999 + p2.id = "000000000000000000099999" p2.save() self.assertEqual( data, @@ -110,7 +110,7 @@ def post_save_handler(signal, sender, instance, **kwargs): ], ) data[:] = [] - p2.id = 99998 + p2.id = "000000000000000000099998" p2.save() self.assertEqual( data, @@ -167,9 +167,9 @@ def __call__(self, signal, sender, instance, origin, **kwargs): data[:] = [] p2 = Person(first_name="James", last_name="Jones") - p2.id = 99999 + p2.id = "000000000000000000099999" p2.save() - p2.id = 99998 + p2.id = "000000000000000000099998" p2.save() p2.delete() self.assertEqual( diff --git a/tests/sites_framework/tests.py b/tests/sites_framework/tests.py index 4a297a9243..af29e41b5a 100644 --- a/tests/sites_framework/tests.py +++ b/tests/sites_framework/tests.py @@ -16,7 +16,7 @@ def setUpTestData(cls): id=settings.SITE_ID, domain="example.com", name="example.com" ) Site.objects.create( - id=settings.SITE_ID + 1, domain="example2.com", name="example2.com" + id="000000000000000000000002", domain="example2.com", name="example2.com" ) def test_site_fk(self): @@ -28,9 +28,9 @@ def test_site_fk(self): def test_sites_m2m(self): article = SyndicatedArticle.objects.create(title="Fresh News!") article.sites.add(Site.objects.get(id=settings.SITE_ID)) - article.sites.add(Site.objects.get(id=settings.SITE_ID + 1)) + article.sites.add(Site.objects.get(id="000000000000000000000002")) article2 = SyndicatedArticle.objects.create(title="More News!") - article2.sites.add(Site.objects.get(id=settings.SITE_ID + 1)) + article2.sites.add(Site.objects.get(id="000000000000000000000002")) self.assertEqual(SyndicatedArticle.on_site.get(), article) def test_custom_named_field(self): diff --git a/tests/sites_tests/tests.py b/tests/sites_tests/tests.py index 4f5b07ee8f..f0eeafec41 100644 --- a/tests/sites_tests/tests.py +++ b/tests/sites_tests/tests.py @@ -1,3 +1,5 @@ +from bson import ObjectId + from django.apps import apps from django.apps.registry import Apps from django.conf import settings @@ -316,13 +318,13 @@ def test_signal(self): ) self.assertTrue(Site.objects.exists()) - @override_settings(SITE_ID=35696) + @override_settings(SITE_ID="000000000000000000035696") def test_custom_site_id(self): """ #23945 - The configured ``SITE_ID`` should be respected. """ create_default_site(self.app_config, verbosity=0) - self.assertEqual(Site.objects.get().pk, 35696) + self.assertEqual(Site.objects.get().pk, ObjectId("000000000000000000035696")) @override_settings() # Restore original ``SITE_ID`` afterward. def test_no_site_id(self): diff --git a/tests/syndication_tests/tests.py b/tests/syndication_tests/tests.py index 6403f7461a..872193dc9f 100644 --- a/tests/syndication_tests/tests.py +++ b/tests/syndication_tests/tests.py @@ -841,5 +841,7 @@ def test_get_object(self): ) def test_get_non_existent_object(self): - response = self.client.get("/syndication/rss2/articles/0/") + response = self.client.get( + "/syndication/rss2/articles/000000000000000000000000/" + ) self.assertEqual(response.status_code, 404) diff --git a/tests/test_utils/fixtures/person.json b/tests/test_utils/fixtures/person.json index 9f4df38996..dc6a67e415 100644 --- a/tests/test_utils/fixtures/person.json +++ b/tests/test_utils/fixtures/person.json @@ -1,10 +1,9 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "test_utils.person", "fields": { "name": "Elvis Presley" } } ] - diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py index 36ee6e9da0..4121f58f1d 100644 --- a/tests/validation/test_unique.py +++ b/tests/validation/test_unique.py @@ -136,7 +136,9 @@ def test_primary_key_unique_check_not_performed_when_adding_and_pk_not_specified def test_primary_key_unique_check_performed_when_adding_and_pk_specified(self): # Regression test for #12560 with self.assertNumQueries(1): - mtv = ModelToValidate(number=10, name="Some Name", id=123) + mtv = ModelToValidate( + number=10, name="Some Name", id="000000000000000000000123" + ) setattr(mtv, "_adding", True) mtv.full_clean() diff --git a/tests/validation/tests.py b/tests/validation/tests.py index 494310e553..5fae830390 100644 --- a/tests/validation/tests.py +++ b/tests/validation/tests.py @@ -1,3 +1,5 @@ +from bson import ObjectId + from django import forms from django.core.exceptions import NON_FIELD_ERRORS from django.test import TestCase @@ -27,7 +29,9 @@ def test_custom_validate_method(self): self.assertFailsValidation(mtv.full_clean, [NON_FIELD_ERRORS, "name"]) def test_wrong_FK_value_raises_error(self): - mtv = ModelToValidate(number=10, name="Some Name", parent_id=3) + mtv = ModelToValidate( + number=10, name="Some Name", parent_id=ObjectId("000000000000000000000003") + ) self.assertFieldFailsValidationWithMessage( mtv.full_clean, "parent", diff --git a/tests/view_tests/tests/test_defaults.py b/tests/view_tests/tests/test_defaults.py index 66bc1da168..48af13119b 100644 --- a/tests/view_tests/tests/test_defaults.py +++ b/tests/view_tests/tests/test_defaults.py @@ -52,7 +52,9 @@ def setUpTestData(cls): author=author, date_created=datetime.datetime(2001, 1, 1, 21, 22, 23), ) - Site(id=1, domain="testserver", name="testserver").save() + Site( + id="000000000000000000000001", domain="testserver", name="testserver" + ).save() def test_page_not_found(self): "A 404 status is returned by the page_not_found view" From 7ad6880f1a005f1029b14c5e784310dc0733228f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 20 Feb 2025 11:19:36 -0500 Subject: [PATCH 25/35] Fixed #36201 -- Fixed `ModelChoiceField/ModelMultipleChoiceField.clean()` to catch `ValidationError` from queryset operations. --- django/forms/models.py | 9 +++++++-- tests/model_forms/test_uuid.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/django/forms/models.py b/django/forms/models.py index be59dbe4a0..1543b21c8b 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -1562,7 +1562,12 @@ def to_python(self, value): if isinstance(value, self.queryset.model): value = getattr(value, key) value = self.queryset.get(**{key: value}) - except (ValueError, TypeError, self.queryset.model.DoesNotExist): + except ( + ValueError, + TypeError, + ValidationError, + self.queryset.model.DoesNotExist, + ): raise ValidationError( self.error_messages["invalid_choice"], code="invalid_choice", @@ -1640,7 +1645,7 @@ def _check_values(self, value): self.validate_no_null_characters(pk) try: self.queryset.filter(**{key: pk}) - except (ValueError, TypeError): + except (ValueError, TypeError, ValidationError): raise ValidationError( self.error_messages["invalid_pk_value"], code="invalid_pk_value", diff --git a/tests/model_forms/test_uuid.py b/tests/model_forms/test_uuid.py index 583b3fea94..8bf2d87a4b 100644 --- a/tests/model_forms/test_uuid.py +++ b/tests/model_forms/test_uuid.py @@ -30,6 +30,6 @@ def test_update_save_error(self): def test_model_multiple_choice_field_uuid_pk(self): f = forms.ModelMultipleChoiceField(UUIDPK.objects.all()) with self.assertRaisesMessage( - ValidationError, "“invalid_uuid” is not a valid UUID." + ValidationError, "“invalid_uuid” is not a valid value." ): f.clean(["invalid_uuid"]) From fd544e8662f867d82edcb88fbe005aeec3fafc24 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 20 Feb 2025 11:21:53 -0500 Subject: [PATCH 26/35] Fixed shortcut() crash on ObjectId pk --- django/contrib/contenttypes/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index bfde73c567..fac15df107 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -1,7 +1,7 @@ from django.apps import apps from django.contrib.contenttypes.models import ContentType from django.contrib.sites.shortcuts import get_current_site -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.http import Http404, HttpResponseRedirect from django.utils.translation import gettext as _ @@ -19,7 +19,7 @@ def shortcut(request, content_type_id, object_id): % {"ct_id": content_type_id} ) obj = content_type.get_object_for_this_type(pk=object_id) - except (ObjectDoesNotExist, ValueError): + except (ObjectDoesNotExist, ValidationError, ValueError): raise Http404( _("Content type %(ct_id)s object %(obj_id)s doesn’t exist") % {"ct_id": content_type_id, "obj_id": object_id} From 40458208d082affde84e22b086b58f1b308132c9 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 May 2025 09:55:17 -0400 Subject: [PATCH 27/35] mock close_pool() in test_creation.py --- tests/backends/base/test_creation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/backends/base/test_creation.py b/tests/backends/base/test_creation.py index 1664c15bfc..970bd41783 100644 --- a/tests/backends/base/test_creation.py +++ b/tests/backends/base/test_creation.py @@ -76,6 +76,9 @@ def test_migrate_test_setting_false( if connection.vendor == "oracle": # Don't close connection on Oracle. creation.connection.close = mock.Mock() + if connection.vendor == "mongodb": + # Don't close connection pool on MongoDB. + creation.connection.close_pool = mock.Mock() old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): @@ -106,6 +109,9 @@ def test_migrate_test_setting_false_ensure_schema( if connection.vendor == "oracle": # Don't close connection on Oracle. creation.connection.close = mock.Mock() + if connection.vendor == "mongodb": + # Don't close connection pool on MongoDB. + creation.connection.close_pool = mock.Mock() old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): @@ -130,6 +136,9 @@ def test_migrate_test_setting_true( if connection.vendor == "oracle": # Don't close connection on Oracle. creation.connection.close = mock.Mock() + if connection.vendor == "mongodb": + # Don't close connection pool on MongoDB. + creation.connection.close_pool = mock.Mock() old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): @@ -160,6 +169,9 @@ def test_mark_expected_failures_and_skips_call( if connection.vendor == "oracle": # Don't close connection on Oracle. creation.connection.close = mock.Mock() + if connection.vendor == "mongodb": + # Don't close connection pool on MongoDB. + creation.connection.close_pool = mock.Mock() old_database_name = test_connection.settings_dict["NAME"] try: with mock.patch.object(creation, "_create_test_db"): From 843107a432cee10c411a36d5cb6ffbec288b8612 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 8 May 2025 11:05:30 -0400 Subject: [PATCH 28/35] Removed hardcoded pks in admin selenium tests. --- tests/admin_views/tests.py | 20 ++++++++++++-------- tests/admin_widgets/tests.py | 22 ++++++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 5de0ac99d0..9e9df24c62 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -71,6 +71,7 @@ Collector, Color, ComplexSortedPerson, + Country, CoverLetter, CustomArticle, CyclicOne, @@ -6758,11 +6759,12 @@ def _get_text_inside_element_by_selector(selector): self.wait_until(lambda d: len(d.window_handles) == 1, 1) self.selenium.switch_to.window(self.selenium.window_handles[0]) + argentina = Country.objects.get(name="Argentina") self.assertHTMLEqual( _get_HTML_inside_element_by_id(born_country_select_id), - """ + f""" - + """, ) # Argentina isn't added to the living_country select nor selected by @@ -6796,12 +6798,13 @@ def _get_text_inside_element_by_selector(selector): self.wait_until(lambda d: len(d.window_handles) == 1, 1) self.selenium.switch_to.window(self.selenium.window_handles[0]) + spain = Country.objects.get(name="Spain") self.assertHTMLEqual( _get_HTML_inside_element_by_id(born_country_select_id), - """ + f""" - - + + """, ) @@ -6838,12 +6841,13 @@ def _get_text_inside_element_by_selector(selector): self.wait_until(lambda d: len(d.window_handles) == 1, 1) self.selenium.switch_to.window(self.selenium.window_handles[0]) + italy = spain self.assertHTMLEqual( _get_HTML_inside_element_by_id(born_country_select_id), - """ + f""" - - + + """, ) # Italy is added to the living_country select and it's also selected by diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 7720ec04c0..ac90644303 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -1787,8 +1787,8 @@ def test_form_submission_via_enter_key_with_filter_horizontal(self): class AdminRawIdWidgetSeleniumTests(AdminWidgetSeleniumTestCase): def setUp(self): super().setUp() - Band.objects.create(id=42, name="Bogey Blues") - Band.objects.create(id=98, name="Green Potatoes") + self.blues = Band.objects.create(name="Bogey Blues") + self.potatoes = Band.objects.create(name="Green Potatoes") @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) def test_ForeignKey(self): @@ -1810,23 +1810,23 @@ def test_ForeignKey(self): self.selenium.find_element(By.ID, "lookup_id_main_band").click() self.wait_for_and_switch_to_popup() link = self.selenium.find_element(By.LINK_TEXT, "Bogey Blues") - self.assertIn("/band/42/", link.get_attribute("href")) + self.assertIn(f"/band/{self.blues.pk}/", link.get_attribute("href")) link.click() # The field now contains the selected band's id self.selenium.switch_to.window(main_window) - self.wait_for_value("#id_main_band", "42") + self.wait_for_value("#id_main_band", str(self.blues.pk)) # Reopen the popup window and click on another band self.selenium.find_element(By.ID, "lookup_id_main_band").click() self.wait_for_and_switch_to_popup() link = self.selenium.find_element(By.LINK_TEXT, "Green Potatoes") - self.assertIn("/band/98/", link.get_attribute("href")) + self.assertIn(f"/band/{self.potatoes.pk}/", link.get_attribute("href")) link.click() # The field now contains the other selected band's id self.selenium.switch_to.window(main_window) - self.wait_for_value("#id_main_band", "98") + self.wait_for_value("#id_main_band", str(self.potatoes.pk)) def test_many_to_many(self): from selenium.webdriver.common.by import By @@ -1857,23 +1857,25 @@ def test_many_to_many(self): self.selenium.find_element(By.ID, "lookup_id_supporting_bands").click() self.wait_for_and_switch_to_popup() link = self.selenium.find_element(By.LINK_TEXT, "Bogey Blues") - self.assertIn("/band/42/", link.get_attribute("href")) + self.assertIn(f"/band/{self.blues.pk}/", link.get_attribute("href")) link.click() # The field now contains the selected band's id self.selenium.switch_to.window(main_window) - self.wait_for_value("#id_supporting_bands", "42") + self.wait_for_value("#id_supporting_bands", str(self.blues.pk)) # Reopen the popup window and click on another band self.selenium.find_element(By.ID, "lookup_id_supporting_bands").click() self.wait_for_and_switch_to_popup() link = self.selenium.find_element(By.LINK_TEXT, "Green Potatoes") - self.assertIn("/band/98/", link.get_attribute("href")) + self.assertIn(f"/band/{self.potatoes.pk}/", link.get_attribute("href")) link.click() # The field now contains the two selected bands' ids self.selenium.switch_to.window(main_window) - self.wait_for_value("#id_supporting_bands", "42,98") + self.wait_for_value( + "#id_supporting_bands", f"{self.blues.pk},{self.potatoes.pk}" + ) class RelatedFieldWidgetSeleniumTests(AdminWidgetSeleniumTestCase): From 7b83596e4000836b4f2d1cdedf0858e0b4481c08 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 8 May 2025 09:26:05 -0400 Subject: [PATCH 29/35] Refs #34488 -- Fixed file path for ClearableFileInput selenium tests. The file path should not be based on the directory that the tests are run from. --- tests/admin_widgets/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index ac90644303..e15bd6233e 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -4,6 +4,7 @@ import zoneinfo from datetime import datetime, timedelta from importlib import import_module +from pathlib import Path from unittest import skipUnless from django import forms @@ -1955,7 +1956,7 @@ def test_ForeignKey_using_to_field(self): class ImageFieldWidgetsSeleniumTests(AdminWidgetSeleniumTestCase): name_input_id = "id_name" photo_input_id = "id_photo" - tests_files_folder = "%s/files" % os.getcwd() + tests_files_folder = "%s/files" % Path(__file__).parent.parent clear_checkbox_id = "photo-clear_id" def _submit_and_wait(self): From 3803ba486dc750a519c2f1b5f382f81fd8cddabe Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 7 Jun 2025 10:32:19 -0400 Subject: [PATCH 30/35] adaptions for transactions supports --- tests/admin_changelist/tests.py | 2 +- tests/async/tests.py | 6 ++-- tests/backends/base/test_base.py | 22 ++++++++++---- tests/force_insert_update/tests.py | 6 ++-- tests/get_or_create/tests.py | 17 +++++++---- tests/migrations/test_executor.py | 1 + tests/migrations/test_operations.py | 1 + tests/sessions_tests/tests.py | 47 +++++++++++++++++++++++++++++ tests/test_utils/test_testcase.py | 6 ++-- tests/test_utils/tests.py | 3 +- tests/transaction_hooks/tests.py | 6 +++- tests/transactions/tests.py | 2 ++ 12 files changed, 97 insertions(+), 22 deletions(-) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 1de13c6b7e..58a21b5cbe 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -457,7 +457,7 @@ def test_result_list_editable(self): with self.assertRaises(IncorrectLookupParameters): m.get_changelist_instance(request) - @skipUnlessDBFeature("supports_transactions") + @skipUnlessDBFeature("uses_savepoints") def test_list_editable_atomicity(self): a = Swallow.objects.create(origin="Swallow A", load=4, speed=1) b = Swallow.objects.create(origin="Swallow B", load=2, speed=2) diff --git a/tests/async/tests.py b/tests/async/tests.py index 6ca5c989b0..a0855c5041 100644 --- a/tests/async/tests.py +++ b/tests/async/tests.py @@ -7,7 +7,7 @@ from django.core.cache import DEFAULT_CACHE_ALIAS, caches from django.core.exceptions import ImproperlyConfigured, SynchronousOnlyOperation from django.http import HttpResponse, HttpResponseNotAllowed -from django.test import RequestFactory, SimpleTestCase +from django.test import RequestFactory, SimpleTestCase, TestCase from django.utils.asyncio import async_unsafe from django.views.generic.base import View @@ -25,7 +25,9 @@ async def async_cache(): self.assertIs(cache_1, cache_2) -class DatabaseConnectionTest(SimpleTestCase): +# Changed from SimpleTestCase to TestCase for MongoDB since +# DatabaseFeatures.supports_transactions establishes a connection. +class DatabaseConnectionTest(TestCase): """A database connection cannot be used in an async context.""" async def test_get_async_connection(self): diff --git a/tests/backends/base/test_base.py b/tests/backends/base/test_base.py index 05577aaa6a..dfae61d76f 100644 --- a/tests/backends/base/test_base.py +++ b/tests/backends/base/test_base.py @@ -61,6 +61,7 @@ def test_check_database_version_supported_with_none_as_database_version(self): connection.check_database_version_supported() +@skipUnlessDBFeature("supports_transactions") class DatabaseWrapperLoggingTests(TransactionTestCase): available_apps = ["backends"] @@ -73,17 +74,23 @@ def test_commit_debug_log(self): Person.objects.create(first_name="first", last_name="last") self.assertGreaterEqual(len(conn.queries_log), 3) - self.assertEqual(conn.queries_log[-3]["sql"], "BEGIN") + self.assertEqual( + conn.queries_log[-3]["sql"], "session.start_transaction()" + ) self.assertRegex( cm.output[0], r"DEBUG:django.db.backends:\(\d+.\d{3}\) " - rf"BEGIN; args=None; alias={DEFAULT_DB_ALIAS}", + r"session.start_transaction\(\); args=None; " + f"alias={DEFAULT_DB_ALIAS}", + ) + self.assertEqual( + conn.queries_log[-1]["sql"], "session.commit_transaction()" ) - self.assertEqual(conn.queries_log[-1]["sql"], "COMMIT") self.assertRegex( cm.output[-1], r"DEBUG:django.db.backends:\(\d+.\d{3}\) " - rf"COMMIT; args=None; alias={DEFAULT_DB_ALIAS}", + r"session.commit_transaction\(\); args=None; " + f"alias={DEFAULT_DB_ALIAS}", ) @override_settings(DEBUG=True) @@ -95,11 +102,14 @@ def test_rollback_debug_log(self): Person.objects.create(first_name="first", last_name="last") raise Exception("Force rollback") - self.assertEqual(conn.queries_log[-1]["sql"], "ROLLBACK") + self.assertEqual( + conn.queries_log[-1]["sql"], "session.abort_transaction()" + ) self.assertRegex( cm.output[-1], r"DEBUG:django.db.backends:\(\d+.\d{3}\) " - rf"ROLLBACK; args=None; alias={DEFAULT_DB_ALIAS}", + r"session.abort_transaction\(\); args=None; " + f"alias={DEFAULT_DB_ALIAS}", ) def test_no_logs_without_debug(self): diff --git a/tests/force_insert_update/tests.py b/tests/force_insert_update/tests.py index 460f1deccb..59cc1ba1f4 100644 --- a/tests/force_insert_update/tests.py +++ b/tests/force_insert_update/tests.py @@ -1,5 +1,5 @@ from django.db import DatabaseError, IntegrityError, models, transaction -from django.test import TestCase +from django.test import TestCase, TransactionTestCase from .models import ( Counter, @@ -13,7 +13,9 @@ ) -class ForceTests(TestCase): +class ForceTests(TransactionTestCase): + available_apps = ["force_insert_update"] + def test_force_update(self): c = Counter.objects.create(name="one", value=1) diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index a82749a8f0..169c3fb905 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -215,12 +215,13 @@ def raise_exception(): self.assertFalse(created) -class GetOrCreateTestsWithManualPKs(TestCase): +class GetOrCreateTestsWithManualPKs(TransactionTestCase): + available_apps = ["get_or_create"] + id = "000000000000000000000001" - @classmethod - def setUpTestData(cls): - ManualPrimaryKeyTest.objects.create(id=cls.id, data="Original") + def setUp(self): + ManualPrimaryKeyTest.objects.create(id=self.id, data="Original") def test_create_with_duplicate_primary_key(self): """ @@ -270,7 +271,9 @@ def test_get_or_create_integrityerror(self): self.skipTest("This backend does not support integrity checks.") -class GetOrCreateThroughManyToMany(TestCase): +class GetOrCreateThroughManyToMany(TransactionTestCase): + available_apps = ["get_or_create"] + def test_get_get_or_create(self): tag = Tag.objects.create(text="foo") a_thing = Thing.objects.create(name="a") @@ -613,7 +616,9 @@ def test_update_only_defaults_and_pre_save_fields_when_local_fields(self): self.assertNotIn(connection.ops.quote_name("name"), update_sql) -class UpdateOrCreateTestsWithManualPKs(TestCase): +class UpdateOrCreateTestsWithManualPKs(TransactionTestCase): + available_apps = ["get_or_create"] + def test_create_with_duplicate_primary_key(self): """ If an existing primary key is specified with different values for other diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index 8da69fcd1d..c68c234d63 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -161,6 +161,7 @@ def test_non_atomic_migration(self): self.assertTrue(Publisher.objects.exists()) self.assertTableNotExists("migrations_book") + @skipUnlessDBFeature("supports_transactions") @override_settings( MIGRATION_MODULES={"migrations": "migrations.test_migrations_atomic_operation"} ) diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 189b96b9a0..2141eb1404 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -5768,6 +5768,7 @@ def test_run_python_invalid_reverse_code(self): with self.assertRaisesMessage(ValueError, msg): migrations.RunPython(code=migrations.RunPython.noop, reverse_code="invalid") + @skipUnlessDBFeature("supports_transactions") def test_run_python_atomic(self): """ Tests the RunPython operation correctly handles the "atomic" keyword diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py index 9eabb933a8..378ca404e5 100644 --- a/tests/sessions_tests/tests.py +++ b/tests/sessions_tests/tests.py @@ -9,6 +9,8 @@ from pathlib import Path from unittest import mock +from asgiref.sync import sync_to_async + from django.conf import settings from django.contrib.sessions.backends.base import SessionBase, UpdateError from django.contrib.sessions.backends.cache import SessionStore as CacheSession @@ -27,6 +29,7 @@ from django.core.cache.backends.base import InvalidCacheBackendError from django.core.exceptions import ImproperlyConfigured from django.core.signing import TimestampSigner +from django.db import connection from django.http import HttpResponse from django.test import ( RequestFactory, @@ -748,6 +751,28 @@ async def test_aclear_expired(self): await other_session.aclear_expired() self.assertEqual(await self.model.objects.acount(), 1) + def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self): + f = connection.features + if f.supports_transactions and not f.uses_savepoints: + raise self.skipTest("Requires savepoints if transactions are supported.") + super().test_session_save_does_not_resurrect_session_logged_out_in_other_context() # noqa: E501 + + async def test_session_asave_does_not_resurrect_session_logged_out_in_other_context( + self, + ): + # Unsure if this is the best way to make sure connection.features is + # usable. + await sync_to_async(connection.ensure_connection)() + + @sync_to_async + def should_skip(): + f = connection.features + return f.supports_transactions and not f.uses_savepoints + + if await should_skip(): + raise self.skipTest("Requires savepoints if transactions are supported.") + await super().test_session_asave_does_not_resurrect_session_logged_out_in_other_context() # noqa: E501 + @override_settings(USE_TZ=True) class DatabaseSessionWithTimeZoneTests(DatabaseSessionTests): @@ -858,6 +883,28 @@ async def test_cache_async_set_failure_non_fatal(self): self.assertEqual(log.message, f"Error saving to cache ({session._cache})") self.assertEqual(str(log.exc_info[1]), "Faked exception saving to cache") + def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self): + f = connection.features + if f.supports_transactions and not f.uses_savepoints: + raise self.skipTest("Requires savepoints if transactions are supported.") + super().test_session_save_does_not_resurrect_session_logged_out_in_other_context() # noqa: E501 + + async def test_session_asave_does_not_resurrect_session_logged_out_in_other_context( + self, + ): + # Unsure if this is the best way to make sure connection.features is + # usable. + await sync_to_async(connection.ensure_connection)() + + @sync_to_async + def should_skip(): + f = connection.features + return f.supports_transactions and not f.uses_savepoints + + if await should_skip(): + raise self.skipTest("Requires savepoints if transactions are supported.") + await super().test_session_asave_does_not_resurrect_session_logged_out_in_other_context() # noqa: E501 + @override_settings(USE_TZ=True) class CacheDBSessionWithTimeZoneTests(CacheDBSessionTests): diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py index 3dcbb229ab..e1f1781b07 100644 --- a/tests/test_utils/test_testcase.py +++ b/tests/test_utils/test_testcase.py @@ -83,9 +83,9 @@ def inner(self): # On databases with no transaction support (for instance, MySQL with the MyISAM -# engine), setUpTestData() is called before each test, so there is no need to -# clone class level test data. -@skipUnlessDBFeature("supports_transactions") +# engine) or no savepoints support, setUpTestData() is called before each test, +# so there is no need to clone class level test data. +@skipUnlessDBFeature("supports_transactions", "uses_savepoints") class TestDataTests(TestCase): # setUpTestData re-assignment are also wrapped in TestData. jim_douglas = None diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 63c494fc64..5552ffc47b 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1889,7 +1889,7 @@ def test_override_staticfiles_dirs(self): self.assertIn(expected_location, finder.locations) -@skipUnlessDBFeature("supports_transactions") +@skipUnlessDBFeature("supports_transactions", "uses_savepoints") class TestBadSetUpTestData(TestCase): """ An exception in setUpTestData() shouldn't leak a transaction which would @@ -1977,6 +1977,7 @@ def pre_hook(): self.assertEqual(len(callbacks), 1) self.assertNotEqual(callbacks[0], pre_hook) + @skipUnlessDBFeature("uses_savepoints") def test_with_rolled_back_savepoint(self): with self.captureOnCommitCallbacks() as callbacks: try: diff --git a/tests/transaction_hooks/tests.py b/tests/transaction_hooks/tests.py index 938e92575f..9e4096956b 100644 --- a/tests/transaction_hooks/tests.py +++ b/tests/transaction_hooks/tests.py @@ -108,6 +108,7 @@ def test_executes_only_after_final_transaction_committed(self): self.assertNotified([]) self.assertDone([1]) + @skipUnlessDBFeature("uses_savepoints") def test_discards_hooks_from_rolled_back_savepoint(self): with transaction.atomic(): # one successful savepoint @@ -139,6 +140,7 @@ def test_no_hooks_run_from_failed_transaction(self): self.assertDone([]) + @skipUnlessDBFeature("uses_savepoints") def test_inner_savepoint_rolled_back_with_outer(self): with transaction.atomic(): try: @@ -164,6 +166,7 @@ def test_no_savepoints_atomic_merged_with_outer(self): self.assertDone([]) + @skipUnlessDBFeature("uses_savepoints") def test_inner_savepoint_does_not_affect_outer(self): with transaction.atomic(): with transaction.atomic(): @@ -210,7 +213,7 @@ def test_hooks_cleared_after_rollback(self): def test_hooks_cleared_on_reconnect(self): with transaction.atomic(): self.do(1) - connection.close() + connection.close_pool() connection.connect() @@ -275,6 +278,7 @@ def should_never_be_called(): with self.assertRaisesMessage(transaction.TransactionManagementError, msg): transaction.on_commit(should_never_be_called) finally: + connection.commit() # Prevent transaction from leaking. connection.set_autocommit(True) def test_raises_exception_non_callable(self): diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py index 9fe8c58593..745a5b5f93 100644 --- a/tests/transactions/tests.py +++ b/tests/transactions/tests.py @@ -460,6 +460,7 @@ def test_atomic_does_not_leak_savepoints_on_failure(self): # This is expected to fail because the savepoint no longer exists. connection.savepoint_rollback(sid) + @skipUnlessDBFeature("supports_transactions") def test_mark_for_rollback_on_error_in_transaction(self): with transaction.atomic(savepoint=False): # Swallow the intentional error raised. @@ -505,6 +506,7 @@ def test_mark_for_rollback_on_error_in_autocommit(self): Reporter.objects.create() +@skipUnlessDBFeature("supports_transactions") class NonAutocommitTests(TransactionTestCase): available_apps = [] From 57a8df919ea09a6a7ecc592bc7ce2ee4930fb3cf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 26 May 2025 22:31:27 -0400 Subject: [PATCH 31/35] use skipUnlessGISLookup --- .../contrib/gis/db/backends/base/features.py | 12 ------- tests/gis_tests/distapp/tests.py | 9 ++--- tests/gis_tests/geoapp/test_regress.py | 2 ++ tests/gis_tests/geoapp/tests.py | 36 +++++++++++-------- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/django/contrib/gis/db/backends/base/features.py b/django/contrib/gis/db/backends/base/features.py index cc4ce1046b..db198604a7 100644 --- a/django/contrib/gis/db/backends/base/features.py +++ b/django/contrib/gis/db/backends/base/features.py @@ -58,14 +58,6 @@ class BaseSpatialFeatures: # for empty results? empty_intersection_returns_none = True - @property - def supports_bbcontains_lookup(self): - return "bbcontains" in self.connection.ops.gis_operators - - @property - def supports_contained_lookup(self): - return "contained" in self.connection.ops.gis_operators - @property def supports_crosses_lookup(self): return "crosses" in self.connection.ops.gis_operators @@ -74,10 +66,6 @@ def supports_crosses_lookup(self): def supports_distances_lookups(self): return self.has_Distance_function - @property - def supports_dwithin_lookup(self): - return "dwithin" in self.connection.ops.gis_operators - @property def supports_relate_lookup(self): return "relate" in self.connection.ops.gis_operators diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index 84b58b345b..85505047ac 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -22,7 +22,7 @@ ) from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature -from ..utils import FuncTestMixin +from ..utils import FuncTestMixin, skipUnlessGISLookup from .models import ( AustraliaCity, CensusZipcode, @@ -65,7 +65,7 @@ def test_init(self): self.assertEqual(1, Interstate.objects.count()) self.assertEqual(1, SouthTexasInterstate.objects.count()) - @skipUnlessDBFeature("supports_dwithin_lookup") + @skipUnlessGISLookup("dwithin") def test_dwithin(self): """ Test the `dwithin` lookup type. @@ -322,7 +322,7 @@ def test_mysql_geodetic_distance_error(self): point__distance_lte=(Point(0, 0), D(m=100)) ).exists() - @skipUnlessDBFeature("supports_dwithin_lookup") + @skipUnlessGISLookup("dwithin") def test_dwithin_subquery(self): """dwithin lookup in a subquery using OuterRef as a parameter.""" qs = CensusZipcode.objects.annotate( @@ -334,7 +334,8 @@ def test_dwithin_subquery(self): ).filter(annotated_value=True) self.assertEqual(self.get_names(qs), ["77002", "77025", "77401"]) - @skipUnlessDBFeature("supports_dwithin_lookup", "supports_dwithin_distance_expr") + @skipUnlessGISLookup("dwithin") + @skipUnlessDBFeature("supports_dwithin_distance_expr") def test_dwithin_with_expression_rhs(self): # LineString of Wollongong and Adelaide coords. ls = LineString(((150.902, -34.4245), (138.6, -34.9258)), srid=4326) diff --git a/tests/gis_tests/geoapp/test_regress.py b/tests/gis_tests/geoapp/test_regress.py index 9a9226f341..9f14f56b81 100644 --- a/tests/gis_tests/geoapp/test_regress.py +++ b/tests/gis_tests/geoapp/test_regress.py @@ -5,6 +5,7 @@ from django.db.models import Count, Min from django.test import TestCase, skipUnlessDBFeature +from ..utils import skipUnlessGISLookup from .models import City, PennsylvaniaCity, State, Truth @@ -66,6 +67,7 @@ def test_unicode_date(self): founded, PennsylvaniaCity.objects.aggregate(Min("founded"))["founded__min"] ) + @skipUnlessGISLookup("contains") def test_empty_count(self): "Testing that PostGISAdapter.__eq__ does check empty strings. See #13670." # contrived example, but need a geo lookup paired with an id__in lookup diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index 962d4f2217..f9abae129f 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -272,6 +272,7 @@ def test_empty_geometries(self): class GeoLookupTest(TestCase): fixtures = ["initial"] + @skipUnlessGISLookup("disjoint") def test_disjoint_lookup(self): "Testing the `disjoint` lookup type." ptown = City.objects.get(name="Pueblo") @@ -281,22 +282,22 @@ def test_disjoint_lookup(self): self.assertEqual(1, qs2.count()) self.assertEqual("Kansas", qs2[0].name) - def test_contains_contained_lookups(self): - "Testing the 'contained', 'contains', and 'bbcontains' lookup types." + @skipUnlessGISLookup("contained") + def test_contained(self): # Getting Texas, yes we were a country -- once ;) texas = Country.objects.get(name="Texas") # Seeing what cities are in Texas, should get Houston and Dallas, # and Oklahoma City because 'contained' only checks on the # _bounding box_ of the Geometries. - if connection.features.supports_contained_lookup: - qs = City.objects.filter(point__contained=texas.mpoly) - self.assertEqual(3, qs.count()) - cities = ["Houston", "Dallas", "Oklahoma City"] - for c in qs: - self.assertIn(c.name, cities) - - # Pulling out some cities. + qs = City.objects.filter(point__contained=texas.mpoly) + self.assertEqual(3, qs.count()) + cities = ["Houston", "Dallas", "Oklahoma City"] + for c in qs: + self.assertIn(c.name, cities) + + @skipUnlessGISLookup("contains") + def test_contains(self): houston = City.objects.get(name="Houston") wellington = City.objects.get(name="Wellington") pueblo = City.objects.get(name="Pueblo") @@ -325,13 +326,15 @@ def test_contains_contained_lookups(self): len(Country.objects.filter(mpoly__contains=okcity.point.wkt)), 0 ) # Query w/WKT + @skipUnlessGISLookup("bbcontains") + def test_bbcontains(self): # OK City is contained w/in bounding box of Texas. - if connection.features.supports_bbcontains_lookup: - qs = Country.objects.filter(mpoly__bbcontains=okcity.point) - self.assertEqual(1, len(qs)) - self.assertEqual("Texas", qs[0].name) + okcity = City.objects.get(name="Oklahoma City") + qs = Country.objects.filter(mpoly__bbcontains=okcity.point) + self.assertEqual(1, len(qs)) + self.assertEqual("Texas", qs[0].name) - @skipUnlessDBFeature("supports_crosses_lookup") + @skipUnlessGISLookup("crosses") def test_crosses_lookup(self): Track.objects.create(name="Line1", line=LineString([(-95, 29), (-60, 0)])) self.assertEqual( @@ -422,6 +425,7 @@ def test_strictly_above_below_lookups(self): lambda b: b.name, ) + @skipUnlessGISLookup("same_as", "equals") def test_equals_lookups(self): "Testing the 'same_as' and 'equals' lookup types." pnt = fromstr("POINT (-95.363151 29.763374)", srid=4326) @@ -617,6 +621,7 @@ def test_gis_lookups_with_complex_expressions(self): **{"point__" + lookup: functions.Union("point", "point")} ).exists() + @skipUnlessGISLookup("within") def test_subquery_annotation(self): multifields = MultiFields.objects.create( city=City.objects.create(point=Point(1, 1)), @@ -769,6 +774,7 @@ def test_unionagg_tolerance_escaping(self): Union("point", tolerance="0.05))), (((1"), ) + @skipUnlessGISLookup("within") def test_within_subquery(self): """ Using a queryset inside a geo lookup is working (using a subquery) From 3d1d7c868fd2a1c3e1c28fcc74fc183b02c09c8e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 25 Jul 2025 15:41:50 -0400 Subject: [PATCH 32/35] Add tests for MultiPointField, MultiLineStringField, and GeometryCollectionField These should be contributed upstream to Django. --- tests/gis_tests/geoapp/models.py | 12 +++++++++ tests/gis_tests/geoapp/tests.py | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/tests/gis_tests/geoapp/models.py b/tests/gis_tests/geoapp/models.py index 2c13c827c6..c7acb653d3 100644 --- a/tests/gis_tests/geoapp/models.py +++ b/tests/gis_tests/geoapp/models.py @@ -102,3 +102,15 @@ class ManyPointModel(NamedModel): point1 = models.PointField() point2 = models.PointField() point3 = models.PointField(srid=3857) + + +class Points(models.Model): + geom = models.MultiPointField() + + +class Lines(models.Model): + geom = models.MultiLineStringField() + + +class GeometryCollections(models.Model): + geom = models.GeometryCollectionField() diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index f9abae129f..945d80a563 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -26,10 +26,13 @@ City, Country, Feature, + GeometryCollections, + Lines, MinusOneSRID, MultiFields, NonConcreteModel, PennsylvaniaCity, + Points, State, ThreeDimensionalFeature, Track, @@ -269,6 +272,47 @@ def test_empty_geometries(self): self.assertEqual(feature.geom.srid, g.srid) +# TODO: contribute these tests added to the MongoDB fork upstream to Django. +class SaveLoadTests(TestCase): + def test_multi_line_string_field(self): + geom = MultiLineString( + LineString((0, 0), (1, 1), (5, 5)), + LineString((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)), + ) + obj = Lines.objects.create(geom=geom) + obj.refresh_from_db() + self.assertEqual(obj.geom.tuple, geom.tuple) + + def test_multi_line_string_with_linear_ring(self): + # LinearRings are transformed to LineString + geom = MultiLineString( + LineString((0, 0), (1, 1), (5, 5)), + LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)), + ) + obj = Lines.objects.create(geom=geom) + obj.refresh_from_db() + self.assertEqual(obj.geom.tuple, geom.tuple) + self.assertEqual(obj.geom[0].tuple, geom[0].tuple) + self.assertEqual(obj.geom[1].__class__.__name__, "LineString") + self.assertEqual(obj.geom[1].tuple, geom[1].tuple) + + def test_multi_point_field(self): + geom = MultiPoint(Point(1, 1), Point(0, 0)) + obj = Points.objects.create(geom=geom) + obj.refresh_from_db() + self.assertEqual(obj.geom, geom) + + def test_geometry_collection_field(self): + geom = GeometryCollection( + Point(2, 2), + LineString((0, 0), (2, 2)), + Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))), + ) + obj = GeometryCollections.objects.create(geom=geom) + obj.refresh_from_db() + self.assertEqual(obj.geom, geom) + + class GeoLookupTest(TestCase): fixtures = ["initial"] From 0f6a6e789f26c2cf65f72a6eaa169a3c6999c117 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 24 May 2025 13:23:43 -0400 Subject: [PATCH 33/35] adapt GIS tests for MongoDB --- .../gis/db/backends/base/operations.py | 3 ++ django/core/management/commands/loaddata.py | 18 +++++++ tests/gis_tests/distapp/fixtures/initial.json | 48 +++++++++--------- .../gis_tests/geoapp/fixtures/initial.json.gz | Bin 131247 -> 131254 bytes tests/gis_tests/geoapp/test_expressions.py | 1 + tests/gis_tests/geoapp/test_functions.py | 4 +- tests/gis_tests/geoapp/tests.py | 5 +- tests/gis_tests/geogapp/fixtures/initial.json | 12 ++--- .../gis_migrations/test_operations.py | 22 ++++++-- tests/gis_tests/layermap/tests.py | 13 +++-- .../relatedapp/fixtures/initial.json | 46 ++++++++--------- tests/gis_tests/relatedapp/tests.py | 11 ++-- tests/runtests.py | 11 ++-- 13 files changed, 122 insertions(+), 72 deletions(-) diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index fafdf60743..f7f54289a1 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -39,6 +39,8 @@ def select_extent(self): "AsGML", "AsKML", "AsSVG", + "AsWKB", + "AsWKT", "Azimuth", "BoundingCircle", "Centroid", @@ -46,6 +48,7 @@ def select_extent(self): "Difference", "Distance", "Envelope", + "ForcePolygonCW", "FromWKB", "FromWKT", "GeoHash", diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 8c76e52633..ca91cd0d0f 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -18,6 +18,7 @@ DEFAULT_DB_ALIAS, DatabaseError, IntegrityError, + connection, connections, router, transaction, @@ -251,7 +252,24 @@ def load_label(self, fixture_label): for obj in objects: objects_in_fixture += 1 + + # Workaround for MongoDB to ignore unsupported SRIDs in test + # fixtures. + if connection.features.gis_enabled: + from django.contrib.gis.db.models import GeometryField + + invalid_srid = False + for field in obj.object._meta.fields: + if isinstance(field, GeometryField): + val = getattr(obj.object, field.name) + if val and val.srid in {32140, 2278}: + invalid_srid = True + break + if invalid_srid: + continue + if self.save_obj(obj): + loaded_objects_in_fixture += 1 if show_progress: self.stdout.write( diff --git a/tests/gis_tests/distapp/fixtures/initial.json b/tests/gis_tests/distapp/fixtures/initial.json index b8632d342e..4ab4f095a9 100644 --- a/tests/gis_tests/distapp/fixtures/initial.json +++ b/tests/gis_tests/distapp/fixtures/initial.json @@ -1,6 +1,6 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.southtexascity", "fields": { "name": "Downtown Houston", @@ -32,7 +32,7 @@ } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "distapp.southtexascity", "fields": { "name": "Pearland", @@ -40,7 +40,7 @@ } }, { - "pk": 6, + "pk": "000000000000000000000006", "model": "distapp.southtexascity", "fields": { "name": "Galveston", @@ -48,7 +48,7 @@ } }, { - "pk": 7, + "pk": "000000000000000000000007", "model": "distapp.southtexascity", "fields": { "name": "Sealy", @@ -56,7 +56,7 @@ } }, { - "pk": 8, + "pk": "000000000000000000000008", "model": "distapp.southtexascity", "fields": { "name": "San Antonio", @@ -64,7 +64,7 @@ } }, { - "pk": 9, + "pk": "000000000000000000000009", "model": "distapp.southtexascity", "fields": { "name": "Saint Hedwig", @@ -72,7 +72,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.southtexascityft", "fields": { "name": "Downtown Houston", @@ -104,7 +104,7 @@ } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "distapp.southtexascityft", "fields": { "name": "Pearland", @@ -112,7 +112,7 @@ } }, { - "pk": 6, + "pk": "000000000000000000000006", "model": "distapp.southtexascityft", "fields": { "name": "Galveston", @@ -120,7 +120,7 @@ } }, { - "pk": 7, + "pk": "000000000000000000000007", "model": "distapp.southtexascityft", "fields": { "name": "Sealy", @@ -128,7 +128,7 @@ } }, { - "pk": 8, + "pk": "000000000000000000000008", "model": "distapp.southtexascityft", "fields": { "name": "San Antonio", @@ -136,7 +136,7 @@ } }, { - "pk": 9, + "pk": "000000000000000000000009", "model": "distapp.southtexascityft", "fields": { "name": "Saint Hedwig", @@ -144,7 +144,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.australiacity", "fields": { "name": "Wollongong", @@ -176,7 +176,7 @@ } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "distapp.australiacity", "fields": { "name": "Batemans Bay", @@ -184,7 +184,7 @@ } }, { - "pk": 6, + "pk": "000000000000000000000006", "model": "distapp.australiacity", "fields": { "name": "Canberra", @@ -192,7 +192,7 @@ } }, { - "pk": 7, + "pk": "000000000000000000000007", "model": "distapp.australiacity", "fields": { "name": "Melbourne", @@ -200,7 +200,7 @@ } }, { - "pk": 8, + "pk": "000000000000000000000008", "model": "distapp.australiacity", "fields": { "name": "Sydney", @@ -208,7 +208,7 @@ } }, { - "pk": 9, + "pk": "000000000000000000000009", "model": "distapp.australiacity", "fields": { "name": "Hobart", @@ -216,7 +216,7 @@ } }, { - "pk": 10, + "pk": "000000000000000000000011", "model": "distapp.australiacity", "fields": { "name": "Adelaide", @@ -224,7 +224,7 @@ } }, { - "pk": 11, + "pk": "000000000000000000000012", "model": "distapp.australiacity", "fields": { "name": "Hillsdale", @@ -232,7 +232,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.censuszipcode", "fields": { "name": "77002", @@ -264,7 +264,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.southtexaszipcode", "fields": { "name": "77002", @@ -296,7 +296,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.interstate", "fields": { "path": "SRID=4326;LINESTRING (-104.4780170766107972 36.6669879187069370, -104.4468522338494978 36.7992540939338610, -104.4621269262599981 36.9372149776075034, -104.5126119783767962 37.0816326882088703, -104.5247764602161027 37.2930049989204804, -104.7084397427667994 37.4915025992539768, -104.8126599016281943 37.6951428562186308, -104.8452887035466006 37.8761339565947921, -104.7160169341002955 38.0595176333779932, -104.6165437927668052 38.3043204585510608, -104.6437227858174026 38.5397998656473675, -104.7596170387259065 38.7322907594295032, -104.8380078676821938 38.8999846060434109, -104.8501253693505930 39.0998018921335770, -104.8791648316464062 39.2436877645750286, -104.8635041274215070 39.3785278162751027, -104.8894471170052043 39.5929228239604996, -104.9721242843343987 39.6952848241968468, -105.0112104500356054 39.7273080432393968, -105.0010368577104032 39.7667760781157114, -104.9818356189999946 39.8146650412196692, -104.9858891550477011 39.8880691125083189, -104.9873548059578070 39.9811723457101635, -104.9766220487419019 40.0979642345069180, -104.9818565932953049 40.3605653066288426, -104.9912746373996981 40.7490448444765576)", @@ -304,7 +304,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "distapp.southtexasinterstate", "fields": { "path": "SRID=32140;LINESTRING (924952.5000000000000000 4220931.5999999996274710, 925065.3000000000465661 4220931.5999999996274710, 929568.4000000000232831 4221057.7999999998137355)", diff --git a/tests/gis_tests/geoapp/fixtures/initial.json.gz b/tests/gis_tests/geoapp/fixtures/initial.json.gz index df9243a479a2eb0f925196e1adfc2b803ab49f10..9305ed1af54a5263fb20df0f45f1bab799cb8886 100644 GIT binary patch literal 131254 zcmV(sK<&RDiwFoJaf@gG18Ht)bZKF1E^2dcZUBt^OYb&Iu;mBM`4mSz>Or%W_sjw@ zLqe7eMqmbrQ42Q+VY^!xBrLu>@mmp5PgT8pANPA&ea?O={&mSqWL#FP_`m$i|L=eH zFZS=>{HOotzxiMM%fBgq|F8a=f9F#F-GBUV{=CH1 z|A+tLfBnB2y{-S{|Mvg%U;fws#sB`l`Y->_|C|5nfA=r{_TT>7|9LI_uTt9R{qle3 zU;6rMmeQ~N@9b*(>nfvF`~T`S_8Irp-bcIEw*PUlGfM08nr)mpZ>e&vGUw^n`AY3; z*Y%$xH~RR`x!*c{wf`Ksy4!!w{p{EC&vN@}W$!UoJ7f3zJI+aYtg*VnRp^!YcZ9z&EJr>KmOR}WVUnJ4eE1A0#?_ihP>|<10XWPC$7v-?gKDFCgTs!3IxZ2h2*>l&~e~Y)CJ-bavbGg&jT=}@8 z``Lf3d4JtcZd`eJ_IVFdT(0)cy*B%wao=UKmM^CI#M|2_)Ov61o=@B7{wd|Ut+v+~ zncObdA9D3NJ1^Ihzm`?<{ZGrwErw?AYy3Xo{_A_EU$

    nzx}n<+^XvZCGt|i%oOL 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

    x+(XF+7YihmMgSCy}ZqUbBUXW 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�%9UNS66?q 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 diff --git a/tests/gis_tests/geoapp/test_expressions.py b/tests/gis_tests/geoapp/test_expressions.py index b56832bb6f..a93bffdbbc 100644 --- a/tests/gis_tests/geoapp/test_expressions.py +++ b/tests/gis_tests/geoapp/test_expressions.py @@ -54,6 +54,7 @@ def test_update_from_other_field(self): obj.point3.equals_exact(p1.transform(3857, clone=True), 0.1) ) + @skipUnlessDBFeature("has_Distance_function") def test_multiple_annotation(self): multi_field = MultiFields.objects.create( point=Point(1, 1), diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 80b08f8d39..907b90db7c 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -559,7 +559,7 @@ def test_memsize(self): # Exact value depends on database and version. self.assertTrue(20 <= ptown.size <= 105) - @skipUnlessDBFeature("has_NumGeom_function") + @skipUnlessDBFeature("has_NumGeometries_function") def test_num_geom(self): # Both 'countries' only have two geometries. for c in Country.objects.annotate(num_geom=functions.NumGeometries("mpoly")): @@ -576,7 +576,7 @@ def test_num_geom(self): else: self.assertEqual(1, city.num_geom) - @skipUnlessDBFeature("has_NumPoint_function") + @skipUnlessDBFeature("has_NumPoints_function") def test_num_points(self): coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)] Track.objects.create(name="Foo", line=LineString(coords)) diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index 945d80a563..bf1822eb79 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -507,7 +507,10 @@ def test_null_geometries(self): self.assertIsNone(nmi.poly) # Assigning a geometry and saving -- then UPDATE back to NULL. - nmi.poly = "POLYGON((0 0,1 0,1 1,1 0,0 0))" + + # Edited from "POLYGON((0 0,1 0,1 1,1 0,0 0))" + # MongoDB: Duplicate vertices: 1 and 3 + nmi.poly = "POLYGON((0 0,1 0,1 1,0 0))" nmi.save() State.objects.filter(name="Northern Mariana Islands").update(poly=None) self.assertIsNone(State.objects.get(name="Northern Mariana Islands").poly) diff --git a/tests/gis_tests/geogapp/fixtures/initial.json b/tests/gis_tests/geogapp/fixtures/initial.json index 442f31c39d..5c4c4ad41c 100644 --- a/tests/gis_tests/geogapp/fixtures/initial.json +++ b/tests/gis_tests/geogapp/fixtures/initial.json @@ -1,6 +1,6 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "geogapp.city", "fields": { "name": "Houston", @@ -32,7 +32,7 @@ } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "geogapp.city", "fields": { "name": "Pueblo", @@ -40,7 +40,7 @@ } }, { - "pk": 6, + "pk": "000000000000000000000006", "model": "geogapp.city", "fields": { "name": "Lawrence", @@ -48,7 +48,7 @@ } }, { - "pk": 7, + "pk": "000000000000000000000007", "model": "geogapp.city", "fields": { "name": "Chicago", @@ -56,7 +56,7 @@ } }, { - "pk": 8, + "pk": "000000000000000000000008", "model": "geogapp.city", "fields": { "name": "Victoria", @@ -64,7 +64,7 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "geogapp.zipcode", "fields" : { "code" : "77002", diff --git a/tests/gis_tests/gis_migrations/test_operations.py b/tests/gis_tests/gis_migrations/test_operations.py index 98201ed3f7..d040c4954e 100644 --- a/tests/gis_tests/gis_migrations/test_operations.py +++ b/tests/gis_tests/gis_migrations/test_operations.py @@ -46,10 +46,12 @@ def get_table_description(self, table): return connection.introspection.get_table_description(cursor, table) def assertColumnExists(self, table, column): - self.assertIn(column, [c.name for c in self.get_table_description(table)]) + pass + # self.assertIn(column, [c.name for c in self.get_table_description(table)]) def assertColumnNotExists(self, table, column): - self.assertNotIn(column, [c.name for c in self.get_table_description(table)]) + pass + # self.assertNotIn(column, [c.name for c in self.get_table_description(table)]) def apply_operations(self, app_label, project_state, operations): migration = Migration("name", app_label) @@ -90,7 +92,11 @@ def assertSpatialIndexExists(self, table, column, raster=False): ) ) else: - self.assertIn([column], [c["columns"] for c in constraints.values()]) + # MongoDB edit: added `c["orders"] == ["GEO"]` to verify index type. + self.assertIn( + [column], + [c["columns"] for c in constraints.values() if c["orders"] == ["GEO"]], + ) def assertSpatialIndexNotExists(self, table, column, raster=False): with connection.cursor() as cursor: @@ -231,6 +237,9 @@ def test_remove_geom_field(self): """ self.alter_gis_model(migrations.RemoveField, "Neighborhood", "geom") self.assertColumnNotExists("gis_neighborhood", "geom") + # Most databases drop a column index along with the column so this check isn't + # needed, but that's not the case with MongoDB. + self.assertSpatialIndexNotExists("gis_neighborhood", "geom") # Test GeometryColumns when available if HAS_GEOMETRY_COLUMNS: @@ -424,6 +433,13 @@ def test_create_raster_model_on_db_without_raster_support(self): with self.assertRaisesMessage(ImproperlyConfigured, msg): self.set_up_test_model(force_raster_creation=True) + class Neighborhood(models.Model): + class Meta: + db_table = "gis_neighborhood" + + with connection.schema_editor() as editor: + editor.delete_model(Neighborhood) + def test_add_raster_field_on_db_without_raster_support(self): msg = "Raster fields require backends with raster support." with self.assertRaisesMessage(ImproperlyConfigured, msg): diff --git a/tests/gis_tests/layermap/tests.py b/tests/gis_tests/layermap/tests.py index 6cc903f3ad..34d9e4f76c 100644 --- a/tests/gis_tests/layermap/tests.py +++ b/tests/gis_tests/layermap/tests.py @@ -14,7 +14,7 @@ MissingForeignKey, ) from django.db import connection -from django.test import TestCase, override_settings +from django.test import TestCase, TransactionTestCase, override_settings from .models import ( City, @@ -47,7 +47,9 @@ STATES = ["Texas", "Texas", "Texas", "Hawaii", "Colorado"] -class LayerMapTest(TestCase): +class LayerMapTest(TransactionTestCase): + available_apps = ["gis_tests.layermap"] + def test_init(self): "Testing LayerMapping initialization." @@ -412,7 +414,12 @@ def test_null_number_imported_not_allowed(self): # transaction. You can't execute queries until the end of the 'atomic' # block." On Oracle and MySQL, the one object that did load appears in # this count. On other databases, no records appear. - self.assertLessEqual(DoesNotAllowNulls.objects.count(), 1) + if connection.features.supports_transactions: + self.assertLessEqual(DoesNotAllowNulls.objects.count(), 1) + else: + # When transactions aren't supported, so "An error occurred..." + # doesn't happen and all valid objects are created. + self.assertEqual(DoesNotAllowNulls.objects.count(), 2) class OtherRouter: diff --git a/tests/gis_tests/relatedapp/fixtures/initial.json b/tests/gis_tests/relatedapp/fixtures/initial.json index 3a2e4c19b4..1f657cd6c9 100644 --- a/tests/gis_tests/relatedapp/fixtures/initial.json +++ b/tests/gis_tests/relatedapp/fixtures/initial.json @@ -1,9 +1,9 @@ [ { - "pk": 1, + "pk": "000000000000000000000001", "model": "relatedapp.location", "fields": { - "point": "SRID=4326;POINT (-97.516111 33.058333)" + "point": "POINT (-97.516111 33.058333)" } }, { @@ -28,19 +28,19 @@ } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "relatedapp.location", "fields": { "point": "SRID=4326;POINT (-96.801611 32.782057)" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "relatedapp.city", "fields": { "name": "Aurora", "state": "TX", - "location": 1 + "location": "000000000000000000000001" } }, { @@ -49,7 +49,7 @@ "fields": { "name": "Roswell", "state": "NM", - "location": 2 + "location": "000000000000000000000002" } }, { @@ -58,7 +58,7 @@ "fields": { "name": "Kecksburg", "state": "PA", - "location": 3 + "location": "000000000000000000000003" } }, { @@ -67,29 +67,29 @@ "fields": { "name": "Dallas", "state": "TX", - "location": 5 + "location": "000000000000000000000005" } }, { - "pk": 5, + "pk": "000000000000000000000005", "model": "relatedapp.city", "fields": { "name": "Houston", "state": "TX", - "location": 4 + "location": "000000000000000000000004" } }, { - "pk": 6, + "pk": "000000000000000000000006", "model": "relatedapp.city", "fields": { "name": "Fort Worth", "state": "TX", - "location": 5 + "location": "000000000000000000000005" } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "relatedapp.Author", "fields": { "name": "Trevor Paglen", @@ -105,11 +105,11 @@ } }, { - "pk": 1, + "pk": "000000000000000000000001", "model": "relatedapp.Book", "fields": { "title": "Torture Taxi", - "author": 1 + "author": "000000000000000000000001" } }, { @@ -117,7 +117,7 @@ "model": "relatedapp.Book", "fields": { "title": "I Could Tell You But Then You Would Have to be Destroyed by Me", - "author": 1 + "author": "000000000000000000000001" } }, { @@ -125,7 +125,7 @@ "model": "relatedapp.Book", "fields": { "title": "Blank Spots on the Map", - "author": 1 + "author": "000000000000000000000001" } }, { @@ -133,15 +133,15 @@ "model": "relatedapp.Book", "fields": { "title": "Patry on Copyright", - "author": 2 + "author": "000000000000000000000002" } }, { "model": "relatedapp.parcel", - "pk": 1, + "pk": "000000000000000000000001", "fields": { "name": "Aurora Parcel Alpha", - "city": 1, + "city": "000000000000000000000001", "center1": "POINT (1.7128 -2.0060)", "center2": "POINT (3.7128 -5.0060)", "border1": "POLYGON((0 0, 5 5, 12 12, 0 0))", @@ -153,7 +153,7 @@ "pk": "000000000000000000000002", "fields": { "name": "Aurora Parcel Beta", - "city": 1, + "city": "000000000000000000000001", "center1": "POINT (4.7128 5.0060)", "center2": "POINT (12.75 10.05)", "border1": "POLYGON((10 10, 15 15, 22 22, 10 10))", @@ -165,7 +165,7 @@ "pk": "000000000000000000000003", "fields": { "name": "Aurora Parcel Ignore", - "city": 1, + "city": "000000000000000000000001", "center1": "POINT (9.7128 12.0060)", "center2": "POINT (1.7128 -2.0060)", "border1": "POLYGON ((24 23, 25 25, 32 32, 24 23))", @@ -177,7 +177,7 @@ "pk": "000000000000000000000004", "fields": { "name": "Roswell Parcel Ignore", - "city": 2, + "city": "000000000000000000000002", "center1": "POINT (-9.7128 -12.0060)", "center2": "POINT (-1.7128 2.0060)", "border1": "POLYGON ((30 30, 35 35, 42 32, 30 30))", diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py index 303d357705..81191aa489 100644 --- a/tests/gis_tests/relatedapp/tests.py +++ b/tests/gis_tests/relatedapp/tests.py @@ -6,6 +6,7 @@ from django.test.utils import override_settings from django.utils import timezone +from ..utils import skipUnlessGISLookup from .models import Article, Author, Book, City, DirectoryEntry, Event, Location, Parcel @@ -189,6 +190,7 @@ def test07_values(self): for m, d, t in zip(gqs, gvqs, gvlqs): # The values should be Geometry objects and not raw strings returned # by the spatial database. + self.assertEqual(m.id, d["id"]) self.assertIsInstance(d["point"], GEOSGeometry) self.assertIsInstance(t[1], GEOSGeometry) self.assertEqual(m.point, d["point"]) @@ -213,13 +215,16 @@ def test09_pk_relations(self): # are out of order. Dallas and Houston have location IDs that differ # from their PKs -- this is done to ensure that the related location # ID column is selected instead of ID column for the city. + from bson import ObjectId + city_ids = (1, 2, 3, 4, 5) loc_ids = (1, 2, 3, 5, 4) ids_qs = City.objects.order_by("id").values("id", "location__id") for val_dict, c_id, l_id in zip(ids_qs, city_ids, loc_ids): - self.assertEqual(val_dict["id"], c_id) - self.assertEqual(val_dict["location__id"], l_id) + self.assertEqual(val_dict["id"], ObjectId(f"{c_id:024}")) + self.assertEqual(val_dict["location__id"], ObjectId(f"{l_id:024}")) + @skipUnlessGISLookup("within") def test10_combine(self): "Testing the combination of two QuerySets (#10807)." buf1 = City.objects.get(name="Aurora").location.point.buffer(0.1) @@ -264,7 +269,7 @@ def test12b_count(self): def test13c_count(self): "Testing `Count` aggregate with `.values()`. See #15305." qs = ( - Location.objects.filter(id=5) + Location.objects.filter(id="000000000000000000000005") .annotate(num_cities=Count("city")) .values("id", "point", "num_cities") ) diff --git a/tests/runtests.py b/tests/runtests.py index 734f27310f..359dc63460 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -132,7 +132,7 @@ def get_test_modules(gis_enabled): # GIS tests are in nested apps discovery_dirs.append("gis_tests") else: - SUBDIRS_TO_SKIP[""].add("gis_tests") + SUBDIRS_TO_SKIP[""].update({"gis_tests", "gis_tests_"}) for dirname in discovery_dirs: dirpath = os.path.join(RUNTESTS_DIR, dirname) @@ -152,19 +152,16 @@ def get_test_modules(gis_enabled): yield test_module # Discover tests in django_mongodb_backend/tests. - dirpath = os.path.join(MONGODB_TEST_DIR, dirname) - with os.scandir(dirpath) as entries: + with os.scandir(MONGODB_TEST_DIR) as entries: for f in entries: if ( "." in f.name + or os.path.basename(f.name) in subdirs_to_skip or f.is_file() or not os.path.exists(os.path.join(f.path, "__init__.py")) ): continue - test_module = f.name - if dirname: - test_module = dirname + "." + test_module - yield test_module + yield f.name def get_label_module(label): From d8f1da8cdb94edd7ec1ba2f7e6fb8cf6bf233323 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 6 Aug 2025 17:22:45 -0400 Subject: [PATCH 34/35] AutoField unsupported in Cast tests --- tests/db_functions/comparison/test_cast.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/db_functions/comparison/test_cast.py b/tests/db_functions/comparison/test_cast.py index 49cabdbd21..fe53c061b9 100644 --- a/tests/db_functions/comparison/test_cast.py +++ b/tests/db_functions/comparison/test_cast.py @@ -63,9 +63,10 @@ def test_cast_to_decimal_field(self): def test_cast_to_integer(self): for field_class in ( - models.AutoField, - models.BigAutoField, - models.SmallAutoField, + # Unsuppported on MongoDB + # models.AutoField, + # models.BigAutoField, + # models.SmallAutoField, models.IntegerField, models.BigIntegerField, models.SmallIntegerField, From 0cc3c700e80a6b2aa443f34d4ba5da516ad4cffa Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 4 Sep 2025 11:45:51 -0400 Subject: [PATCH 35/35] INTPYTHON-736 - Update test to not expect --- tests/admin_changelist/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 58a21b5cbe..c20e124cd2 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1340,7 +1340,7 @@ def test_changelist_view_list_editable_changed_objects_uses_filter(self): # Check only the first few characters of the pk since the UUID has # dashes. self.assertIn( - "{'$match': {'$expr': {'$in': ['$uuid', ('%s" % str(a.pk)[:8], + "{'$match': {'uuid': {'$in': ('%s" % str(a.pk)[:8], context.captured_queries[4]["sql"], )