diff --git a/django/core/management/base.py b/django/core/management/base.py index 92a3abb01ec2..db7a01cc4cf0 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -15,6 +15,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style, no_style from django.db import DEFAULT_DB_ALIAS, connections +from django.utils.version import PY314, PY315 ALL_CHECKS = "__all__" @@ -57,6 +58,8 @@ def __init__( ): self.missing_args_message = missing_args_message self.called_from_command_line = called_from_command_line + if PY314 and not PY315: + kwargs.setdefault("suggest_on_error", True) super().__init__(**kwargs) def parse_args(self, args=None, namespace=None): diff --git a/django/db/backends/postgresql/compiler.py b/django/db/backends/postgresql/compiler.py index a07ae3ea9216..48d0ccfd9d06 100644 --- a/django/db/backends/postgresql/compiler.py +++ b/django/db/backends/postgresql/compiler.py @@ -36,6 +36,11 @@ def assemble_as_sql(self, fields, value_rows): # Lack of fields denote the usage of the DEFAULT keyword # for the insertion of empty rows. or any(field is None for field in fields) + # Field.get_placeholder takes value as an argument, so the + # resulting placeholder might be dependent on the value. + # in UNNEST requires a single placeholder to "fit all values" in + # the array. + or any(hasattr(field, "get_placeholder") for field in fields) # Fields that don't use standard internal types might not be # unnest'able (e.g. array and geometry types are known to be # problematic). diff --git a/django/utils/version.py b/django/utils/version.py index 2bb650ac89b1..9f694070c5e8 100644 --- a/django/utils/version.py +++ b/django/utils/version.py @@ -20,6 +20,7 @@ PY312 = sys.version_info >= (3, 12) PY313 = sys.version_info >= (3, 13) PY314 = sys.version_info >= (3, 14) +PY315 = sys.version_info >= (3, 15) def get_version(version=None): diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt index 454abf232c74..b9270695435f 100644 --- a/docs/howto/deployment/wsgi/apache-auth.txt +++ b/docs/howto/deployment/wsgi/apache-auth.txt @@ -128,7 +128,7 @@ In this case, the Apache configuration should look like this: To support the ``WSGIAuthGroupScript`` directive, the same WSGI script ``mysite.wsgi`` must also import the ``groups_for_user`` function which -returns a list groups the given user belongs to. +returns a list of groups the given user belongs to. .. code-block:: python diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt index c1b302ea6af6..887a83b37ffc 100644 --- a/docs/ref/class-based-views/base.txt +++ b/docs/ref/class-based-views/base.txt @@ -113,8 +113,9 @@ ancestor classes are documented under the section title of **Ancestors If the view was called with an HTTP method it doesn't support, this method is called instead. - The default implementation returns ``HttpResponseNotAllowed`` with a - list of allowed methods in plain text. + The default implementation returns ``HttpResponseNotAllowed`` with the + list of allowed methods in the ``Allow`` header, as required by + :rfc:`RFC 7231 <7231#section-6.5.5>`. The response body is empty. .. method:: options(request, *args, **kwargs) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 1190b3f85289..4ec72d70f44b 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -122,7 +122,7 @@ Note: attributes corresponding to ```` and ```` elements, respectively. They can be made callable as functions, as :attr:`~Sitemap.lastmod` was in the example. -* :attr:`~Sitemap.items` is a method that returns a :term:`sequence` or +* :meth:`~Sitemap.items` is a method that returns a :term:`sequence` or ``QuerySet`` of objects. The objects returned will get passed to any callable methods corresponding to a sitemap property (:attr:`~Sitemap.location`, :attr:`~Sitemap.lastmod`, :attr:`~Sitemap.changefreq`, and @@ -140,7 +140,7 @@ Note: A ``Sitemap`` class can define the following methods/attributes: - .. attribute:: Sitemap.items + .. method:: Sitemap.items **Required.** A method that returns a :term:`sequence` or ``QuerySet`` of objects. The framework doesn't care what *type* of objects they are; @@ -153,11 +153,11 @@ Note: **Optional.** Either a method or attribute. If it's a method, it should return the absolute path for a given object - as returned by :attr:`~Sitemap.items`. + as returned by :meth:`~Sitemap.items`. If it's an attribute, its value should be a string representing an absolute path to use for *every* object returned by - :attr:`~Sitemap.items`. + :meth:`~Sitemap.items`. In both cases, "absolute path" means a URL that doesn't include the protocol or domain. Examples: @@ -178,12 +178,12 @@ Note: **Optional.** Either a method or attribute. If it's a method, it should take one argument -- an object as returned - by :attr:`~Sitemap.items` -- and return that object's last-modified + by :meth:`~Sitemap.items` -- and return that object's last-modified date/time as a :class:`~datetime.datetime`. If it's an attribute, its value should be a :class:`~datetime.datetime` representing the last-modified date/time for *every* object returned by - :attr:`~Sitemap.items`. + :meth:`~Sitemap.items`. If all items in a sitemap have a :attr:`~Sitemap.lastmod`, the sitemap generated by :func:`views.sitemap` will have a ``Last-Modified`` @@ -197,7 +197,7 @@ Note: **Optional.** This property returns a :class:`~django.core.paginator.Paginator` for - :attr:`~Sitemap.items`. If you generate sitemaps in a batch you may + :meth:`~Sitemap.items`. If you generate sitemaps in a batch you may want to override this as a cached property in order to avoid multiple ``items()`` calls. @@ -206,11 +206,11 @@ Note: **Optional.** Either a method or attribute. If it's a method, it should take one argument -- an object as returned - by :attr:`~Sitemap.items` -- and return that object's change + by :meth:`~Sitemap.items` -- and return that object's change frequency as a string. If it's an attribute, its value should be a string representing the - change frequency of *every* object returned by :attr:`~Sitemap.items`. + change frequency of *every* object returned by :meth:`~Sitemap.items`. Possible values for :attr:`~Sitemap.changefreq`, whether you use a method or attribute, are: @@ -228,12 +228,12 @@ Note: **Optional.** Either a method or attribute. If it's a method, it should take one argument -- an object as returned - by :attr:`~Sitemap.items` -- and return that object's priority as + by :meth:`~Sitemap.items` -- and return that object's priority as either a string or float. If it's an attribute, its value should be either a string or float representing the priority of *every* object returned by - :attr:`~Sitemap.items`. + :meth:`~Sitemap.items`. Example values for :attr:`~Sitemap.priority`: ``0.4``, ``1.0``. The default priority of a page is ``0.5``. See the `sitemaps.org @@ -308,7 +308,7 @@ Note: :attr:`~Sitemap.lastmod`. * If :attr:`~Sitemap.lastmod` is a method: The latest ``lastmod`` returned by calling the method with all - items returned by :meth:`Sitemap.items`. + items returned by :meth:`~Sitemap.items`. .. method:: Sitemap.get_languages_for_item(item) @@ -456,7 +456,7 @@ both :file:`sitemap-flatpages.xml` and :file:`sitemap-blog.xml`. The dict don't change at all. If all sitemaps have a ``lastmod`` returned by -:meth:`Sitemap.get_latest_lastmod` the sitemap index will have a +:meth:`~Sitemap.get_latest_lastmod` the sitemap index will have a ``Last-Modified`` header equal to the latest ``lastmod``. You should create an index file if one of your sitemaps has more than 50,000 @@ -556,7 +556,7 @@ URL. Each alternate is a dictionary with ``location`` and ``lang_code`` keys. The ``item`` attribute has been added for each URL to allow more flexible customization of the templates, such as `Google news sitemaps`_. Assuming -Sitemap's :attr:`~Sitemap.items` would return a list of items with +Sitemap's :meth:`~Sitemap.items` would return a list of items with ``publication_data`` and a ``tags`` field something like this would generate a Google News compatible sitemap: diff --git a/docs/releases/5.2.8.txt b/docs/releases/5.2.8.txt index 0a0038ba2096..c8987ec819d8 100644 --- a/docs/releases/5.2.8.txt +++ b/docs/releases/5.2.8.txt @@ -32,7 +32,7 @@ Bugfixes * Fixed a bug in Django 5.2 where ``QuerySet.first()`` and ``QuerySet.last()`` raised an error on querysets performing aggregation that selected all fields - of a composite primary key. + of a composite primary key (:ticket:`36648`). * Fixed a bug in Django 5.2 where proxy models having a ``CompositePrimaryKey`` - incorrectly raised a ``models.E042`` system check error. + incorrectly raised a ``models.E042`` system check error (:ticket:`36704`). diff --git a/docs/releases/5.2.9.txt b/docs/releases/5.2.9.txt index 0d726de640ac..588c278be5e9 100644 --- a/docs/releases/5.2.9.txt +++ b/docs/releases/5.2.9.txt @@ -13,3 +13,6 @@ Bugfixes ``django.utils.feedgenerator.Stylesheet.__str__()`` did not escape the ``url``, ``mimetype``, and ``media`` attributes, potentially leading to invalid XML markup (:ticket:`36733`). + +* Fixed a bug in Django 5.2 on PostgreSQL where ``bulk_create()`` did not apply + a field's custom query placeholders (:ticket:`36748`). diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index f3338f8933be..41b554bb5d24 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -228,7 +228,9 @@ Logging Management Commands ~~~~~~~~~~~~~~~~~~~ -* ... +* Management commands now set :class:`~argparse.ArgumentParser`\'s + ``suggest_on_error`` argument to ``True`` by default on Python 3.14, enabling + suggestions for mistyped subcommand names and argument choices. Migrations ~~~~~~~~~~ diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 0c2719456861..19ef99ac49e6 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -38,7 +38,7 @@ from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings from django.test.utils import captured_stderr, captured_stdout from django.urls import path -from django.utils.version import PY313, get_docs_version +from django.utils.version import PY313, PY314, get_docs_version from django.views.static import serve from . import urls @@ -2446,10 +2446,16 @@ def test_precedence(self): class CommandDBOptionChoiceTests(SimpleTestCase): def test_invalid_choice_db_option(self): - expected_error = ( - r"Error: argument --database: invalid choice: 'deflaut' " - r"\(choose from '?default'?, '?other'?\)" - ) + if PY314: + expected_error = ( + r"Error: argument --database: invalid choice: 'deflaut', " + r"maybe you meant 'default'\? \(choose from default, other\)" + ) + else: + expected_error = ( + r"Error: argument --database: invalid choice: 'deflaut' " + r"\(choose from '?default'?, '?other'?\)" + ) args = [ "changepassword", "createsuperuser", diff --git a/tests/postgres_tests/fields.py b/tests/postgres_tests/fields.py index c5dddf197f06..d099effdd56c 100644 --- a/tests/postgres_tests/fields.py +++ b/tests/postgres_tests/fields.py @@ -57,3 +57,8 @@ def deconstruct(self): class EnumField(models.CharField): def get_prep_value(self, value): return value.value if isinstance(value, enum.Enum) else value + + +class OffByOneField(models.IntegerField): + def get_placeholder(self, value, compiler, connection): + return "(%s + 1)" diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 859d45f29dde..d2f9eceb9931 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -9,6 +9,7 @@ EnumField, HStoreField, IntegerRangeField, + OffByOneField, SearchVectorField, ) from ..models import TagField @@ -565,4 +566,26 @@ class Migration(migrations.Migration): "required_db_vendor": "postgresql", }, ), + migrations.CreateModel( + name="OffByOneModel", + fields=[ + ( + "id", + models.BigAutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "one_off", + OffByOneField(), + ), + ], + options={ + "required_db_vendor": "postgresql", + }, + bases=(models.Model,), + ), ] diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index 6a3d25a6af49..ae9518f892c5 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -9,6 +9,7 @@ EnumField, HStoreField, IntegerRangeField, + OffByOneField, SearchVectorField, ) @@ -207,3 +208,7 @@ class HotelReservation(PostgreSQLModel): end = models.DateTimeField() cancelled = models.BooleanField(default=False) requirements = models.JSONField(blank=True, null=True) + + +class OffByOneModel(PostgreSQLModel): + one_off = OffByOneField() diff --git a/tests/postgres_tests/test_bulk_update.py b/tests/postgres_tests/test_bulk_update.py index 85dfcedd09f4..f3e8ac8a4057 100644 --- a/tests/postgres_tests/test_bulk_update.py +++ b/tests/postgres_tests/test_bulk_update.py @@ -6,6 +6,7 @@ IntegerArrayModel, NestedIntegerArrayModel, NullableIntegerArrayModel, + OffByOneModel, OtherTypesArrayModel, RangesModel, ) @@ -44,3 +45,10 @@ def test_bulk_update(self): self.assertSequenceEqual( Model.objects.filter(**{field: new}), instances ) + + def test_bulk_create(self): + OffByOneModel.objects.bulk_create(OffByOneModel(one_off=0) for _ in range(20)) + + self.assertSequenceEqual( + [m.one_off for m in OffByOneModel.objects.all()], 20 * [1] + ) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index e282bd4bc998..afd376307aff 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -1,5 +1,6 @@ import os import sys +import unittest from argparse import ArgumentDefaultsHelpFormatter from io import BytesIO, StringIO, TextIOWrapper from pathlib import Path @@ -24,6 +25,7 @@ from django.test import SimpleTestCase, override_settings from django.test.utils import captured_stderr, extend_sys_path from django.utils import translation +from django.utils.version import PY314, PY315 from .management.commands import dance from .utils import AssertFormatterFailureCaughtContext @@ -454,6 +456,20 @@ def test_outputwrapper_flush(self): self.assertIn("Working...", out.getvalue()) self.assertIs(mocked_flush.called, True) + @unittest.skipUnless(PY314 and not PY315, "Only relevant for Python 3.14") + def test_suggest_on_error_defaults_true(self): + command = BaseCommand() + parser = command.create_parser("prog_name", "subcommand") + self.assertTrue(parser.suggest_on_error) + + @unittest.skipUnless(PY314 and not PY315, "Only relevant for Python 3.14") + def test_suggest_on_error_explicit_false(self): + command = BaseCommand() + parser = command.create_parser( + "prog_name", "subcommand", suggest_on_error=False + ) + self.assertFalse(parser.suggest_on_error) + class CommandRunTests(AdminScriptTestCase): """