diff --git a/AUTHORS b/AUTHORS index 8319353cf5..82aab46439 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1087,6 +1087,6 @@ A big THANK YOU goes to: Ian Bicking for convincing Adrian to ditch code generation. - Mark Pilgrim for "Dive Into Python" (https://www.diveinto.org/python3/). + Mark Pilgrim for "Dive Into Python" (https://diveintopython3.net/). Guido van Rossum for creating Python. diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 516c6c7113..57f81ca476 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -39,6 +39,7 @@ True True """ import sys +import warnings from binascii import b2a_hex from ctypes import byref, c_char_p, c_double, c_ubyte, c_void_p, string_at @@ -50,6 +51,7 @@ from django.contrib.gis.gdal.prototypes import geom as capi from django.contrib.gis.gdal.prototypes import srs as srs_api from django.contrib.gis.gdal.srs import CoordTransform, SpatialReference from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.encoding import force_bytes @@ -206,18 +208,21 @@ class OGRGeometry(GDALBase): "Return 0 for points, 1 for lines, and 2 for surfaces." return capi.get_dims(self.ptr) - def _get_coord_dim(self): + @property + def coord_dim(self): "Return the coordinate dimension of the Geometry." return capi.get_coord_dim(self.ptr) - def _set_coord_dim(self, dim): + # RemovedInDjango60Warning + @coord_dim.setter + def coord_dim(self, dim): "Set the coordinate dimension of this Geometry." + msg = "coord_dim setter is deprecated. Use set_3d() instead." + warnings.warn(msg, RemovedInDjango60Warning, stacklevel=2) if dim not in (2, 3): raise ValueError("Geometry dimension must be either 2 or 3") capi.set_coord_dim(self.ptr, dim) - coord_dim = property(_get_coord_dim, _set_coord_dim) - @property def geom_count(self): "Return the number of elements in this Geometry." diff --git a/django/db/__init__.py b/django/db/__init__.py index f3cf4574a9..eb8118adb5 100644 --- a/django/db/__init__.py +++ b/django/db/__init__.py @@ -17,6 +17,7 @@ from django.db.utils import ( from django.utils.connection import ConnectionProxy __all__ = [ + "close_old_connections", "connection", "connections", "router", diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index 002e03e2b5..47bdf37efa 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -95,12 +95,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): "db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests." "test_trunc_week_before_1000", }, - "Oracle extracts seconds including fractional seconds (#33517).": { - "db_functions.datetime.test_extract_trunc.DateFunctionTests." - "test_extract_second_func_no_fractional", - "db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests." - "test_extract_second_func_no_fractional", - }, "Oracle doesn't support bitwise XOR.": { "expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor", "expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor_null", diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index aedfeea236..541128ec50 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -165,6 +165,9 @@ END; def datetime_extract_sql(self, lookup_type, sql, params, tzname): sql, params = self._convert_sql_to_tz(sql, params, tzname) + if lookup_type == "second": + # Truncate fractional seconds. + return f"FLOOR(EXTRACT(SECOND FROM {sql}))", params return self.date_extract_sql(lookup_type, sql, params) def datetime_trunc_sql(self, lookup_type, sql, params, tzname): @@ -188,6 +191,12 @@ END; return f"CAST({sql} AS DATE)", params return f"TRUNC({sql}, %s)", (*params, trunc_param) + def time_extract_sql(self, lookup_type, sql, params): + if lookup_type == "second": + # Truncate fractional seconds. + return f"FLOOR(EXTRACT(SECOND FROM {sql}))", params + return self.date_extract_sql(lookup_type, sql, params) + def time_trunc_sql(self, lookup_type, sql, params, tzname=None): # The implementation is similar to `datetime_trunc_sql` as both # `DateTimeField` and `TimeField` are stored as TIMESTAMP where diff --git a/django/forms/fields.py b/django/forms/fields.py index fd6900cc65..a425ecad57 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -261,6 +261,10 @@ class Field: result.validators = self.validators[:] return result + def _clean_bound_field(self, bf): + value = bf.initial if self.disabled else bf.data + return self.clean(value) + class CharField(Field): def __init__( @@ -694,6 +698,10 @@ class FileField(Field): def has_changed(self, initial, data): return not self.disabled and data is not None + def _clean_bound_field(self, bf): + value = bf.initial if self.disabled else bf.data + return self.clean(value, bf.initial) + class ImageField(FileField): default_validators = [validators.validate_image_file_extension] diff --git a/django/forms/forms.py b/django/forms/forms.py index 5de14d598a..452f554e1e 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -6,7 +6,7 @@ import copy import datetime from django.core.exceptions import NON_FIELD_ERRORS, ValidationError -from django.forms.fields import Field, FileField +from django.forms.fields import Field from django.forms.utils import ErrorDict, ErrorList, RenderableFormMixin from django.forms.widgets import Media, MediaDefiningClass from django.utils.datastructures import MultiValueDict @@ -329,13 +329,8 @@ class BaseForm(RenderableFormMixin): def _clean_fields(self): for name, bf in self._bound_items(): field = bf.field - value = bf.initial if field.disabled else bf.data try: - if isinstance(field, FileField): - value = field.clean(value, bf.initial) - else: - value = field.clean(value) - self.cleaned_data[name] = value + self.cleaned_data[name] = field._clean_bound_field(bf) if hasattr(self, "clean_%s" % name): value = getattr(self, "clean_%s" % name)() self.cleaned_data[name] = value diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 07e0f46856..e91ac062cb 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -71,6 +71,9 @@ details on these changes. * Support for passing positional arguments to ``Model.save()`` and ``Model.asave()`` will be removed. +* The setter for ``django.contrib.gis.gdal.OGRGeometry.coord_dim`` will be + removed. + .. _deprecation-removed-in-5.1: 5.1 diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 652bca0cb2..06230b8ee3 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -27,8 +27,8 @@ For this tutorial, we expect that you have at least a basic understanding of how Django works. This means you should be comfortable going through the existing tutorials on :doc:`writing your first Django app`. In addition, you should have a good understanding of Python itself. But if you -don't, `Dive Into Python`__ is a fantastic (and free) online book for -beginning Python programmers. +don't, `Dive Into Python`_ is a fantastic (and free) online book for beginning +Python programmers. Those of you who are unfamiliar with version control systems and Trac will find that this tutorial and its links include just enough information to get started. @@ -45,8 +45,8 @@ so that it can be of use to the widest audience. `#django-dev on irc.libera.chat`__ to chat with other Django users who might be able to help. -__ https://diveinto.org/python3/table-of-contents.html __ https://web.libera.chat/#django-dev +.. _Dive Into Python: https://diveintopython3.net/ .. _Django Forum: https://forum.djangoproject.com/ What does this tutorial cover? @@ -350,7 +350,7 @@ This test checks that the ``make_toast()`` returns ``'toast'``. * After reading those, if you want something a little meatier to sink your teeth into, there's always the Python :mod:`unittest` documentation. -__ https://diveinto.org/python3/unit-testing.html +__ https://diveintopython3.net/unit-testing.html Running your new test --------------------- diff --git a/docs/intro/index.txt b/docs/intro/index.txt index ca4836367f..a68d4876b7 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -32,10 +32,12 @@ place: read this material to quickly get up and running. `list of Python resources for non-programmers`_ If you already know a few other languages and want to get up to speed with - Python quickly, we recommend `Dive Into Python`_. If that's not quite your - style, there are many other `books about Python`_. + Python quickly, we recommend referring the official + `Python documentation`_, which provides comprehensive and authoritative + information about the language, as well as links to other resources such as + a list of `books about Python`_. .. _python: https://www.python.org/ .. _list of Python resources for non-programmers: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers - .. _Dive Into Python: https://diveinto.org/python3/table-of-contents.html + .. _Python documentation: https://docs.python.org/3/ .. _books about Python: https://wiki.python.org/moin/PythonBooks diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index f40daf4dfb..0ca63830ea 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -76,7 +76,7 @@ After the previous tutorials, our project should look like this: static/ polls/ images/ - background.gif + background.png style.css templates/ polls/ diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index c98c76cd10..4b7a706fc4 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -553,8 +553,12 @@ coordinate transformation: .. attribute:: coord_dim - Returns or sets the coordinate dimension of this geometry. For example, the - value would be 2 for two-dimensional geometries. + Returns the coordinate dimension of this geometry. For example, the value + would be 2 for two-dimensional geometries. + + .. deprecated:: 5.1 + + The ``coord_dim`` setter is deprecated. Use :meth:`.set_3d` instead. .. attribute:: is_3d diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 5a31ceeaae..d98d523db5 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -99,7 +99,8 @@ connections. If a connection is created in a long-running process, outside of Django’s request-response cycle, the connection will remain open until explicitly -closed, or timeout occurs. +closed, or timeout occurs. You can use ``django.db.close_old_connections()`` to +close all old or unusable connections. Encoding -------- diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7f3fa271c8..3fba67bf20 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1797,9 +1797,9 @@ allows for the following options by default: .. django-admin-option:: --pythonpath PYTHONPATH -Adds the given filesystem path to the Python `import search path`_. If this -isn't provided, ``django-admin`` will use the :envvar:`PYTHONPATH` environment -variable. +Adds the given filesystem path to the Python :py:data:`sys.path` module +attribute. If this isn't provided, ``django-admin`` will use the +:envvar:`PYTHONPATH` environment variable. This option is unnecessary in ``manage.py``, because it takes care of setting the Python path for you. @@ -1810,8 +1810,6 @@ Example usage: django-admin migrate --pythonpath='/home/djangoprojects/myproject' -.. _import search path: https://diveinto.org/python3/your-first-python-program.html#importsearchpath - .. django-admin-option:: --settings SETTINGS Specifies the settings module to use. The settings module should be in Python diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 03c8e61d20..a10af9310f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2484,8 +2484,8 @@ individual elements of the sequence. Returns a slice of the list. -Uses the same syntax as Python's list slicing. See -https://diveinto.org/python3/native-datatypes.html#slicinglists for an +Uses the same syntax as Python's list slicing. See the `Python documentation +`_ for an introduction. Example: diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index be609cea7f..0cf249f3cf 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -378,6 +378,9 @@ Miscellaneous * Passing positional arguments to :meth:`.Model.save` and :meth:`.Model.asave` is deprecated in favor of keyword-only arguments. +* Setting ``django.contrib.gis.gdal.OGRGeometry.coord_dim`` is deprecated. Use + :meth:`~django.contrib.gis.gdal.OGRGeometry.set_3d` instead. + Features removed in 5.1 ======================= diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index a8d32b2fea..75d33b5e7e 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -1608,7 +1608,7 @@ Helper functions Defaults to :setting:`settings.LOGIN_URL ` if not supplied. * ``redirect_field_name``: The name of a ``GET`` field containing the - URL to redirect to after log out. Overrides ``next`` if the given + URL to redirect to after login. Overrides ``next`` if the given ``GET`` parameter is passed. .. _built-in-auth-forms: diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index dc6cd945b3..553f788e61 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -44,9 +44,8 @@ by using an environment variable, :envvar:`DJANGO_SETTINGS_MODULE`. The value of :envvar:`DJANGO_SETTINGS_MODULE` should be in Python path syntax, e.g. ``mysite.settings``. Note that the settings module should be on the -Python `import search path`_. +Python :py:data:`sys.path`. -.. _import search path: https://diveinto.org/python3/your-first-python-program.html#importsearchpath The ``django-admin`` utility ---------------------------- diff --git a/tests/gis_tests/gdal_tests/test_geom.py b/tests/gis_tests/gdal_tests/test_geom.py index eba90504b2..13e7d3e70d 100644 --- a/tests/gis_tests/gdal_tests/test_geom.py +++ b/tests/gis_tests/gdal_tests/test_geom.py @@ -11,6 +11,7 @@ from django.contrib.gis.gdal import ( from django.template import Context from django.template.engine import Engine from django.test import SimpleTestCase +from django.utils.deprecation import RemovedInDjango60Warning from ..test_data import TestDataMixin @@ -727,3 +728,95 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin): msg = "Input to 'set_3d' must be a boolean, got 'None'" with self.assertRaisesMessage(ValueError, msg): geom.set_3d(None) + + def test_wkt_and_wkb_output(self): + tests = [ + # 2D + ("POINT (1 2)", "0101000000000000000000f03f0000000000000040"), + ( + "LINESTRING (30 10,10 30)", + "0102000000020000000000000000003e400000000000002" + "44000000000000024400000000000003e40", + ), + ( + "POLYGON ((30 10,40 40,20 40,30 10))", + "010300000001000000040000000000000000003e400000000000002440000000000000" + "44400000000000004440000000000000344000000000000044400000000000003e4000" + "00000000002440", + ), + ( + "MULTIPOINT (10 40,40 30)", + "0104000000020000000101000000000000000000244000000000000044400101000000" + "00000000000044400000000000003e40", + ), + ( + "MULTILINESTRING ((10 10,20 20),(40 40,30 30,40 20))", + "0105000000020000000102000000020000000000000000002440000000000000244000" + "0000000000344000000000000034400102000000030000000000000000004440000000" + "00000044400000000000003e400000000000003e400000000000004440000000000000" + "3440", + ), + ( + "MULTIPOLYGON (((30 20,45 40,10 40,30 20)),((15 5,40 10,10 20,15 5)))", + "010600000002000000010300000001000000040000000000000000003e400000000000" + "0034400000000000804640000000000000444000000000000024400000000000004440" + "0000000000003e40000000000000344001030000000100000004000000000000000000" + "2e40000000000000144000000000000044400000000000002440000000000000244000" + "000000000034400000000000002e400000000000001440", + ), + ( + "GEOMETRYCOLLECTION (POINT (40 10))", + "010700000001000000010100000000000000000044400000000000002440", + ), + # 3D + ( + "POINT (1 2 3)", + "0101000080000000000000f03f00000000000000400000000000000840", + ), + ( + "LINESTRING (30 10 3,10 30 3)", + "0102000080020000000000000000003e40000000000000244000000000000008400000" + "0000000024400000000000003e400000000000000840", + ), + ( + "POLYGON ((30 10 3,40 40 3,30 10 3))", + "010300008001000000030000000000000000003e400000000000002440000000000000" + "08400000000000004440000000000000444000000000000008400000000000003e4000" + "000000000024400000000000000840", + ), + ( + "MULTIPOINT (10 40 3,40 30 3)", + "0104000080020000000101000080000000000000244000000000000044400000000000" + "000840010100008000000000000044400000000000003e400000000000000840", + ), + ( + "MULTILINESTRING ((10 10 3,20 20 3))", + "0105000080010000000102000080020000000000000000002440000000000000244000" + "00000000000840000000000000344000000000000034400000000000000840", + ), + ( + "MULTIPOLYGON (((30 20 3,45 40 3,30 20 3)))", + "010600008001000000010300008001000000030000000000000000003e400000000000" + "0034400000000000000840000000000080464000000000000044400000000000000840" + "0000000000003e4000000000000034400000000000000840", + ), + ( + "GEOMETRYCOLLECTION (POINT (40 10 3))", + "0107000080010000000101000080000000000000444000000000000024400000000000" + "000840", + ), + ] + for geom, wkb in tests: + with self.subTest(geom=geom): + g = OGRGeometry(geom) + self.assertEqual(g.wkt, geom) + self.assertEqual(g.wkb.hex(), wkb) + + +class DeprecationTests(SimpleTestCase): + def test_coord_setter_deprecation(self): + geom = OGRGeometry("POINT (1 2)") + msg = "coord_dim setter is deprecated. Use set_3d() instead." + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + geom.coord_dim = 3 + self.assertEqual(geom.coord_dim, 3)