Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions django/core/management/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__"

Expand Down Expand Up @@ -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):
Expand Down
5 changes: 5 additions & 0 deletions django/db/backends/postgresql/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions django/utils/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion docs/howto/deployment/wsgi/apache-auth.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions docs/ref/class-based-views/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
28 changes: 14 additions & 14 deletions docs/ref/contrib/sitemaps.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Note:
attributes corresponding to ``<changefreq>`` and ``<priority>`` 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
Expand All @@ -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;
Expand All @@ -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:
Expand All @@ -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``
Expand All @@ -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.

Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
4 changes: 2 additions & 2 deletions docs/releases/5.2.8.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
3 changes: 3 additions & 0 deletions docs/releases/5.2.9.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
4 changes: 3 additions & 1 deletion docs/releases/6.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~
Expand Down
16 changes: 11 additions & 5 deletions tests/admin_scripts/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions tests/postgres_tests/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
23 changes: 23 additions & 0 deletions tests/postgres_tests/migrations/0002_create_test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
EnumField,
HStoreField,
IntegerRangeField,
OffByOneField,
SearchVectorField,
)
from ..models import TagField
Expand Down Expand Up @@ -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,),
),
]
5 changes: 5 additions & 0 deletions tests/postgres_tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
EnumField,
HStoreField,
IntegerRangeField,
OffByOneField,
SearchVectorField,
)

Expand Down Expand Up @@ -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()
8 changes: 8 additions & 0 deletions tests/postgres_tests/test_bulk_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
IntegerArrayModel,
NestedIntegerArrayModel,
NullableIntegerArrayModel,
OffByOneModel,
OtherTypesArrayModel,
RangesModel,
)
Expand Down Expand Up @@ -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]
)
16 changes: 16 additions & 0 deletions tests/user_commands/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import sys
import unittest
from argparse import ArgumentDefaultsHelpFormatter
from io import BytesIO, StringIO, TextIOWrapper
from pathlib import Path
Expand All @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down