From d3ab2ad081ceab56299a5f6eda640f21a57b76f2 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:57:09 -0300 Subject: [PATCH 01/40] [5.1.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index e0b92dccfb39..9ec8643915ce 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (5, 1, 2, "final", 0) +VERSION = (5, 1, 3, "alpha", 0) __version__ = get_version(VERSION) From 52f2996b9b8829bf6b707a44dcc29dbd04c927ae Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:02:53 -0300 Subject: [PATCH 02/40] [5.1.x] Added stub release notes for 5.1.3. Backport of 4d11402932eca570850bdfa58a71eb59fc62275a from main. --- docs/releases/5.1.3.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/5.1.3.txt diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt new file mode 100644 index 000000000000..9cb48dc2f361 --- /dev/null +++ b/docs/releases/5.1.3.txt @@ -0,0 +1,12 @@ +========================== +Django 5.1.3 release notes +========================== + +*Expected November 5, 2024* + +Django 5.1.3 fixes several bugs in 5.1.2. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 7db747c97f45..06c1461ab7b2 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 5.1.3 5.1.2 5.1.1 5.1 From 379111e14afb2918701b52ea39bc19bab5d14fdd Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 8 Oct 2024 08:10:06 +0200 Subject: [PATCH 03/40] [5.1.x] Refs #34900 -- Added Python 3.12 to classifiers and tox.ini, and used it in GitHub actions. Backport of f07eeff3a2d7ff0955410bf7e3f1ff197ef1ecf7 from main. --- .github/workflows/docs.yml | 4 ++-- .github/workflows/linters.yml | 4 ++-- .github/workflows/schedule_tests.yml | 8 ++++---- .github/workflows/selenium.yml | 4 ++-- .github/workflows/tests.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9b88b9be9316..f61692ef7964 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'docs/requirements.txt' - run: python -m pip install -r docs/requirements.txt @@ -48,7 +48,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - run: python -m pip install blacken-docs - name: Build docs run: | diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 3875e755f912..7c64dc98ff4c 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - run: python -m pip install flake8 - name: flake8 # Pinned to v3.0.0. @@ -44,7 +44,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - run: python -m pip install isort - name: isort # Pinned to v3.0.0. diff --git a/.github/workflows/schedule_tests.yml b/.github/workflows/schedule_tests.yml index c4523af4a030..495a61637846 100644 --- a/.github/workflows/schedule_tests.yml +++ b/.github/workflows/schedule_tests.yml @@ -19,7 +19,7 @@ jobs: - '3.10' - '3.11' - '3.12' - - '3.13-dev' + - '3.13' name: Windows, SQLite, Python ${{ matrix.python-version }} continue-on-error: true steps: @@ -46,7 +46,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' - name: Install libmemcached-dev for pylibmc run: sudo apt-get install libmemcached-dev @@ -145,7 +145,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc @@ -181,7 +181,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc diff --git a/.github/workflows/selenium.yml b/.github/workflows/selenium.yml index fa916a0dedf0..bb82eb4203c5 100644 --- a/.github/workflows/selenium.yml +++ b/.github/workflows/selenium.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc @@ -61,7 +61,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' cache-dependency-path: 'tests/requirements/py3.txt' - name: Install libmemcached-dev for pylibmc diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index abe7c78a2554..5de554721d1f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: python-version: - - '3.12' + - '3.13' name: Windows, SQLite, Python ${{ matrix.python-version }} steps: - name: Checkout From e2551b30adc8016c615761c80301e20f1c75e81d Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 8 Oct 2024 08:14:54 +0200 Subject: [PATCH 04/40] [5.1.x] Refs #34900 -- Doc'd Python 3.13 compatibility. Backport of 2e3bc59fd3760de87952ec8fd6cd3694e8d9dc1c from main. --- docs/faq/install.txt | 2 +- docs/howto/windows.txt | 4 ++-- docs/intro/reusable-apps.txt | 1 + docs/releases/5.1.3.txt | 3 ++- docs/releases/5.1.txt | 5 +++-- pyproject.toml | 1 + tox.ini | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index a89da571a96b..af1da879ec38 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -55,7 +55,7 @@ Django version Python versions 4.1 3.8, 3.9, 3.10, 3.11 (added in 4.1.3) 4.2 3.8, 3.9, 3.10, 3.11, 3.12 (added in 4.2.8) 5.0 3.10, 3.11, 3.12 -5.1 3.10, 3.11, 3.12 +5.1 3.10, 3.11, 3.12, 3.13 (added in 5.1.3) ============== =============== For each version of Python, only the latest micro release (A.B.C) is officially diff --git a/docs/howto/windows.txt b/docs/howto/windows.txt index 83aa8d065573..235b18a24ff3 100644 --- a/docs/howto/windows.txt +++ b/docs/howto/windows.txt @@ -2,7 +2,7 @@ How to install Django on Windows ================================ -This document will guide you through installing Python 3.12 and Django on +This document will guide you through installing Python 3.13 and Django on Windows. It also provides instructions for setting up a virtual environment, which makes it easier to work on Python projects. This is meant as a beginner's guide for users working on Django projects and does not reflect how Django @@ -18,7 +18,7 @@ Install Python ============== Django is a Python web framework, thus requiring Python to be installed on your -machine. At the time of writing, Python 3.12 is the latest version. +machine. At the time of writing, Python 3.13 is the latest version. To install Python on your machine go to https://www.python.org/downloads/. The website should offer you a download button for the latest Python version. diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 98f21c9d91b2..e2c25f3525e9 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -237,6 +237,7 @@ this. For a small app like polls, this process isn't too difficult. "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", ] diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt index 9cb48dc2f361..5541a8824a03 100644 --- a/docs/releases/5.1.3.txt +++ b/docs/releases/5.1.3.txt @@ -4,7 +4,8 @@ Django 5.1.3 release notes *Expected November 5, 2024* -Django 5.1.3 fixes several bugs in 5.1.2. +Django 5.1.3 fixes several bugs in 5.1.2 and adds compatibility with Python +3.13. Bugfixes ======== diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index bc868fddda6e..037c76fd5453 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -18,8 +18,9 @@ project. Python compatibility ==================== -Django 5.1 supports Python 3.10, 3.11, and 3.12. We **highly recommend** and -only officially support the latest release of each series. +Django 5.1 supports Python 3.10, 3.11, 3.12, and 3.13 (as of 5.1.3). We +**highly recommend** and only officially support the latest release of each +series. .. _whats-new-5.1: diff --git a/pyproject.toml b/pyproject.toml index 5c85cf42b618..19bc17ba1a6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", diff --git a/tox.ini b/tox.ini index c635a129b2d1..7a76693f2116 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ setenv = PYTHONDONTWRITEBYTECODE=1 deps = -e . - py{3,310,311,312,py3}: -rtests/requirements/py3.txt + py{3,310,311,312,313,py3}: -rtests/requirements/py3.txt postgres: -rtests/requirements/postgres.txt mysql: -rtests/requirements/mysql.txt oracle: -rtests/requirements/oracle.txt From 2bd546957dcc716fbef6796368e7f5a17bf0a452 Mon Sep 17 00:00:00 2001 From: Meta Date: Wed, 7 Aug 2024 16:07:22 -0300 Subject: [PATCH 05/40] [5.1.x] Fixed #35502 -- Removed duplication of "mysite" directory name in intro docs. Reorganized intro docs when explaining `django-admin startproject` to prevent confusion when using "mysite" as both the top-level directory and the Django project directory name. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Co-authored-by: Carlton Gibson Backport of d2c74cfb48a3d29159732fb98e3c28a53572067f from main. --- docs/intro/overview.txt | 14 +++++++------- docs/intro/reusable-apps.txt | 14 +++++++------- docs/intro/tutorial01.txt | 32 +++++++++++++++++--------------- docs/intro/tutorial05.txt | 2 +- docs/intro/tutorial07.txt | 8 ++++---- docs/spelling_wordlist | 1 + 6 files changed, 37 insertions(+), 34 deletions(-) diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 8314b3d35159..0c41446d010c 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -26,7 +26,7 @@ representing your models -- so far, it's been solving many years' worth of database-schema problems. Here's a quick example: .. code-block:: python - :caption: ``mysite/news/models.py`` + :caption: ``news/models.py`` from django.db import models @@ -151,7 +151,7 @@ a website that lets authenticated users add, change and delete objects. The only step required is to register your model in the admin site: .. code-block:: python - :caption: ``mysite/news/models.py`` + :caption: ``news/models.py`` from django.db import models @@ -163,7 +163,7 @@ only step required is to register your model in the admin site: reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) .. code-block:: python - :caption: ``mysite/news/admin.py`` + :caption: ``news/admin.py`` from django.contrib import admin @@ -195,7 +195,7 @@ Here's what a URLconf might look like for the ``Reporter``/``Article`` example above: .. code-block:: python - :caption: ``mysite/news/urls.py`` + :caption: ``news/urls.py`` from django.urls import path @@ -235,7 +235,7 @@ and renders the template with the retrieved data. Here's an example view for ``year_archive`` from above: .. code-block:: python - :caption: ``mysite/news/views.py`` + :caption: ``news/views.py`` from django.shortcuts import render @@ -265,7 +265,7 @@ Let's say the ``news/year_archive.html`` template was found. Here's what that might look like: .. code-block:: html+django - :caption: ``mysite/news/templates/news/year_archive.html`` + :caption: ``news/templates/news/year_archive.html`` {% extends "base.html" %} @@ -306,7 +306,7 @@ Here's what the "base.html" template, including the use of :doc:`static files `, might look like: .. code-block:: html+django - :caption: ``mysite/templates/base.html`` + :caption: ``templates/base.html`` {% load static %} diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index e2c25f3525e9..a9c0768e3b8e 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -57,7 +57,7 @@ After the previous tutorials, our project should look like this: .. code-block:: text - mysite/ + djangotutorial/ manage.py mysite/ __init__.py @@ -90,12 +90,12 @@ After the previous tutorials, our project should look like this: admin/ base_site.html -You created ``mysite/templates`` in :doc:`Tutorial 7 `, -and ``polls/templates`` in :doc:`Tutorial 3 `. Now perhaps -it is clearer why we chose to have separate template directories for the -project and application: everything that is part of the polls application is in -``polls``. It makes the application self-contained and easier to drop into a -new project. +You created ``djangotutorial/templates`` in :doc:`Tutorial 7 +`, and ``polls/templates`` in +:doc:`Tutorial 3 `. Now perhaps it is clearer why we chose +to have separate template directories for the project and application: +everything that is part of the polls application is in ``polls``. It makes the +application self-contained and easier to drop into a new project. The ``polls`` directory could now be copied into a new Django project and immediately reused. It's not quite ready to be published though. For that, we diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 0af25d209b00..59dc02df7772 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -48,14 +48,21 @@ including database configuration, Django-specific options and application-specific settings. From the command line, ``cd`` into a directory where you'd like to store your -code, then run the following command: +code and create a new directory named ``djangotutorial``. (This directory name +doesn't matter to Django; you can rename it to anything you like.) .. console:: - $ django-admin startproject mysite + $ mkdir djangotutorial -This will create a ``mysite`` directory in your current directory. If it didn't -work, see :ref:`troubleshooting-django-admin`. +Then, run the following command to bootstrap a new Django project: + +.. console:: + + $ django-admin startproject mysite djangotutorial + +This will create a project called ``mysite`` inside the ``djangotutorial`` +directory. If it didn't work, see :ref:`troubleshooting-django-admin`. .. note:: @@ -68,7 +75,7 @@ Let's look at what :djadmin:`startproject` created: .. code-block:: text - mysite/ + djangotutorial/ manage.py mysite/ __init__.py @@ -79,14 +86,11 @@ Let's look at what :djadmin:`startproject` created: These files are: -* The outer :file:`mysite/` root directory is a container for your project. Its - name doesn't matter to Django; you can rename it to anything you like. - * :file:`manage.py`: A command-line utility that lets you interact with this Django project in various ways. You can read all the details about :file:`manage.py` in :doc:`/ref/django-admin`. -* The inner :file:`mysite/` directory is the actual Python package for your +* :file:`mysite/`: A directory that is the actual Python package for your project. Its name is the Python package name you'll need to use to import anything inside it (e.g. ``mysite.urls``). @@ -111,8 +115,8 @@ These files are: The development server ====================== -Let's verify your Django project works. Change into the outer :file:`mysite` directory, if -you haven't already, and run the following commands: +Let's verify your Django project works. Change into the :file:`djangotutorial` +directory, if you haven't already, and run the following commands: .. console:: @@ -179,10 +183,8 @@ rather than creating directories. configuration and apps for a particular website. A project can contain multiple apps. An app can be in multiple projects. -Your apps can live anywhere on your :ref:`Python path `. In -this tutorial, we'll create our poll app in the same directory as your -:file:`manage.py` file so that it can be imported as its own top-level module, -rather than a submodule of ``mysite``. +Your apps can live anywhere in your :ref:`Python path `. In +this tutorial, we'll create our poll app inside the ``djangotutorial`` folder. To create your app, make sure you're in the same directory as :file:`manage.py` and type this command: diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 5f501ce92f5b..28a634b8c381 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -216,7 +216,7 @@ and you'll see something like: FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): - File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question + File "/path/to/djangotutorial/polls/tests.py", line 16, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(), False) AssertionError: True is not False diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt index e0c87be8987c..62eded486488 100644 --- a/docs/intro/tutorial07.txt +++ b/docs/intro/tutorial07.txt @@ -306,10 +306,10 @@ powered by Django itself, and its interfaces use Django's own template system. Customizing your *project's* templates -------------------------------------- -Create a ``templates`` directory in your project directory (the one that -contains ``manage.py``). Templates can live anywhere on your filesystem that -Django can access. (Django runs as whatever user your server runs.) However, -keeping your templates within the project is a good convention to follow. +Create a ``templates`` directory in your ``djangotutorial`` directory. +Templates can live anywhere on your filesystem that Django can access. (Django +runs as whatever user your server runs.) However, keeping your templates within +the project is a good convention to follow. Open your settings file (:file:`mysite/settings.py`, remember) and add a :setting:`DIRS ` option in the :setting:`TEMPLATES` setting: diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index d715e62e054b..d30f2ce440ea 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -141,6 +141,7 @@ Disqus distro django djangoproject +djangotutorial dm docstring docstrings From a313e203e0be7831fa77a6d6b5e29f3b48f11839 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:08:21 -0300 Subject: [PATCH 06/40] [5.1.x] Refs #35502 -- Clarified models.py file path in docs/topics/db/queries.txt. Backport of fc1119e8be705766b0277a0ebf8ad637f9d068c2 from main. --- docs/topics/db/queries.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 45c6183103c3..7e3338eaea33 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -62,7 +62,8 @@ class represents a particular record in the database table. To create an object, instantiate it using keyword arguments to the model class, then call :meth:`~django.db.models.Model.save` to save it to the database. -Assuming models live in a file ``mysite/blog/models.py``, here's an example: +Assuming models live in a ``models.py`` file inside a ``blog`` Django app, here +is an example: .. code-block:: pycon From 13d352eb322a96119ac02817f7f403a8d70e18e9 Mon Sep 17 00:00:00 2001 From: nessita <124304+nessita@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:19:12 -0300 Subject: [PATCH 07/40] [5.1.x] Added GitHub Action workflow to test all Python versions listed in the project config file. Backport of 470f4c2436e00873a31673a5992c5260b2de4e97 from main. --- .github/workflows/python_matrix.yml | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/python_matrix.yml diff --git a/.github/workflows/python_matrix.yml b/.github/workflows/python_matrix.yml new file mode 100644 index 000000000000..ab48c2be8322 --- /dev/null +++ b/.github/workflows/python_matrix.yml @@ -0,0 +1,52 @@ +name: Python Matrix from config file + +on: + pull_request: + types: [labeled, synchronize, opened, reopened] + paths-ignore: + - 'docs/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + define-matrix: + if: contains(github.event.pull_request.labels.*.name, 'python-matrix') + runs-on: ubuntu-latest + outputs: + python_versions_output: ${{ steps.set-matrix.outputs.python_versions }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - id: set-matrix + run: | + python_versions=$(sed -n "s/^.*Programming Language :: Python :: \([[:digit:]]\+\.[[:digit:]]\+\).*$/'\1', /p" pyproject.toml | tr -d '\n' | sed 's/, $//g') + echo "Supported Python versions: $python_versions" + echo "python_versions=[$python_versions]" >> "$GITHUB_OUTPUT" + python: + runs-on: ubuntu-latest + needs: define-matrix + strategy: + matrix: + python-version: ${{ fromJson(needs.define-matrix.outputs.python_versions_output) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: 'tests/requirements/py3.txt' + - name: Install libmemcached-dev for pylibmc + run: sudo apt-get install libmemcached-dev + - 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 tests/runtests.py -v2 From b852989c3073f6748412aac68cbd080c13d0e397 Mon Sep 17 00:00:00 2001 From: Chiara Mezzavilla <2512470+samurang87@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:46:26 +0200 Subject: [PATCH 08/40] [5.1.x] Explained exception to using include() within urlpatterns in tutorial 1. Backport of 40a60d589e1d0d290c3b79c7e97d9cd0c94e52e3 from main. --- docs/intro/tutorial01.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 59dc02df7772..b840eb266025 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -290,7 +290,8 @@ app will still work. .. admonition:: When to use :func:`~django.urls.include()` You should always use ``include()`` when you include other URL patterns. - ``admin.site.urls`` is the only exception to this. + The only exception is ``admin.site.urls``, which is a pre-built URLconf + provided by Django for the default admin site. You have now wired an ``index`` view into the URLconf. Verify it's working with the following command: From a9a7ef7762adf94788425384a2a26ea5cb07462d Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:47:10 +0200 Subject: [PATCH 09/40] [5.1.x] Fixed #35612 -- Added documentation on how the security team evaluates reports. Co-authored-by: Joshua Olatunji Backport of 9423f8b47673779049f603a7da271d183de7dc1d from main. --- docs/internals/security.txt | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/internals/security.txt b/docs/internals/security.txt index 55300b01e170..6aac9a6b660d 100644 --- a/docs/internals/security.txt +++ b/docs/internals/security.txt @@ -38,6 +38,41 @@ action to be taken, you may receive further followup emails. .. _our public Trac instance: https://code.djangoproject.com/query +.. _security-report-evaluation: + +How does Django evaluate a report +================================= + +These are criteria used by the security team when evaluating whether a report +requires a security release: + +* The vulnerability is within a :ref:`supported version ` of + Django. + +* The vulnerability applies to a production-grade Django application. This means + the following do not require a security release: + + * Exploits that only affect local development, for example when using + :djadmin:`runserver`. + * Exploits which fail to follow security best practices, such as failure to + sanitize user input. For other examples, see our :ref:`security + documentation `. + * Exploits in AI generated code that do not adhere to security best practices. + +The security team may conclude that the source of the vulnerability is within +the Python standard library, in which case the reporter will be asked to report +the vulnerability to the Python core team. For further details see the `Python +security guidelines `_. + +On occasion, a security release may be issued to help resolve a security +vulnerability within a popular third-party package. These reports should come +from the package maintainers. + +If you are unsure whether your finding meets these criteria, please still report +it :ref:`privately by emailing security@djangoproject.com +`. The security team will review your report and +recommend the correct course of action. + .. _security-support: Supported versions From 6cb07579662e10e878f740e0cf4d54f3582a4f9a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 11 Oct 2024 13:50:51 +0200 Subject: [PATCH 10/40] [5.1.x] Fixed docs build on Sphinx 8.1+. Sphinx 8.1 added :cve: role, so there is no need to define it in Django: - https://github.com/sphinx-doc/sphinx/pull/11781 This also changes used URL to the one used by Python and soonish to be used by Sphinx itself: - https://github.com/sphinx-doc/sphinx/pull/13006 Backport of 263f7319192b217c4e3b1eea0ea7809836392bbc from main. --- docs/conf.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 7b367e23e15e..047813814cc1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,8 @@ import sys from os.path import abspath, dirname, join +from sphinx import version_info as sphinx_version + # Workaround for sphinx-build recursion limit overflow: # pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) # RuntimeError: maximum recursion depth exceeded while pickling an object @@ -138,13 +140,15 @@ def django_release(): extlinks = { "bpo": ("https://bugs.python.org/issue?@action=redirect&bpo=%s", "bpo-%s"), "commit": ("https://github.com/django/django/commit/%s", "%s"), - "cve": ("https://nvd.nist.gov/vuln/detail/CVE-%s", "CVE-%s"), "pypi": ("https://pypi.org/project/%s/", "%s"), # A file or directory. GitHub redirects from blob to tree if needed. "source": ("https://github.com/django/django/blob/main/%s", "%s"), "ticket": ("https://code.djangoproject.com/ticket/%s", "#%s"), } +if sphinx_version < (8, 1): + extlinks["cve"] = ("https://www.cve.org/CVERecord?id=CVE-%s", "CVE-%s") + # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None From 8ce0039cee6fba82c261037523c5f81a28212341 Mon Sep 17 00:00:00 2001 From: Maryam Yusuf Date: Tue, 13 Aug 2024 00:21:49 +0100 Subject: [PATCH 11/40] [5.1.x] Expanded contributor docs on getting feedback from the wider community. Backport of 438fc42ac667653488200578a47e59f6608f2b0b from main. --- .../writing-code/submitting-patches.txt | 63 ++++++++++++++++--- docs/spelling_wordlist | 1 + 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index c63cc6f9f02c..a002051bbb63 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -111,7 +111,7 @@ requirements: feature, the change should also contain documentation. When you think your work is ready to be reviewed, send :doc:`a GitHub pull -request `. +request `. If you can't send a pull request for some reason, you can also use patches in Trac. When using this style, follow these guidelines. @@ -137,20 +137,63 @@ Regardless of the way you submit your work, follow these steps. .. _ticket tracker: https://code.djangoproject.com/ .. _Development dashboard: https://dashboard.djangoproject.com/ -Non-trivial contributions -========================= +Contributions which require community feedback +============================================== -A "non-trivial" contribution is one that is more than a small bug fix. It's a -change that introduces new Django functionality and makes some sort of design -decision. +A wider community discussion is required when a patch introduces new Django +functionality and makes some sort of design decision. This is especially +important if the approach involves a :ref:`deprecation ` +or introduces breaking changes. -If you provide a non-trivial change, include evidence that alternatives have -been discussed on the `Django Forum`_ or |django-developers| list. +The following are different approaches for gaining feedback from the community. -If you're not sure whether your contribution should be considered non-trivial, -ask on the ticket for opinions. +The Django Forum or django-developers mailing list +-------------------------------------------------- + +You can propose a change on the `Django Forum`_ or |django-developers| mailing +list. You should explain the need for the change, go into details of the +approach and discuss alternatives. + +Please include a link to such discussions in your contributions. + +Third party package +------------------- + +Django does not accept experimental features. All features must follow our +:ref:`deprecation policy `. Hence, it can +take months or years for Django to iterate on an API design. + +If you need user feedback on a public interface, it is better to create a +third-party package first. You can iterate on the public API much faster, while +also validating the need for the feature. + +Once this package becomes stable and there are clear benefits of incorporating +aspects into Django core, starting a discussion on the `Django Forum`_ or +|django-developers| mailing list would be the next step. + +Django Enhancement Proposal (DEP) +--------------------------------- + +Similar to Python’s PEPs, Django has `Django Enhancement Proposals`_ or DEPs. A +DEP is a design document which provides information to the Django community, or +describes a new feature or process for Django. They provide concise technical +specifications of features, along with rationales. DEPs are also the primary +mechanism for proposing and collecting community input on major new features. + +Before considering writing a DEP, it is recommended to first open a discussion +on the `Django Forum`_ or |django-developers| mailing list. This allows the +community to provide feedback and helps refine the proposal. Once the DEP is +ready the :ref:`Steering Council ` votes on whether to accept +it. + +Some examples of DEPs that have been approved and fully implemented: + +* `DEP 181: ORM Expressions `_ +* `DEP 182: Multiple Template Engines `_ +* `DEP 201: Simplified routing syntax `_ .. _Django Forum: https://forum.djangoproject.com/ +.. _Django Enhancement Proposals: https://github.com/django/deps .. _deprecating-a-feature: diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index d30f2ce440ea..747a712a62eb 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -123,6 +123,7 @@ deduplicates deduplication deepcopy deferrable +DEP deprecations deserialization deserialize From 9a5eae0ad8054e9e9d8a43505c52313eecddbc8e Mon Sep 17 00:00:00 2001 From: Clifford Gama <53076065+cliff688@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:39:40 +0200 Subject: [PATCH 12/40] [5.1.x] Fixed #26322 -- Consolidated lazy relationships details in docs/ref/models/fields.txt. Reorganized docs to list and explain the types of lazy relationships that can be defined in related fields. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 65f3cfce59395131f318cf1ecba144530ed6609e from main. --- docs/ref/models/fields.txt | 195 ++++++++++++++++++++++++------------- 1 file changed, 127 insertions(+), 68 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 069ecdbabbd0..0ceb620a9f23 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1634,80 +1634,25 @@ Django also defines a set of fields that represent relations. .. class:: ForeignKey(to, on_delete, **options) A many-to-one relationship. Requires two positional arguments: the class to -which the model is related and the :attr:`~ForeignKey.on_delete` option. - -.. _recursive-relationships: - -To create a recursive relationship -- an object that has a many-to-one -relationship with itself -- use ``models.ForeignKey('self', -on_delete=models.CASCADE)``. - -.. _lazy-relationships: - -If you need to create a relationship on a model that has not yet been defined, -you can use the name of the model, rather than the model object itself:: - - from django.db import models - - - class Car(models.Model): - manufacturer = models.ForeignKey( - "Manufacturer", - on_delete=models.CASCADE, - ) - # ... - - - class Manufacturer(models.Model): - # ... - pass - -Relationships defined this way on :ref:`abstract models -` are resolved when the model is subclassed as a -concrete model and are not relative to the abstract model's ``app_label``: - -.. code-block:: python - :caption: ``products/models.py`` - - from django.db import models - - - class AbstractCar(models.Model): - manufacturer = models.ForeignKey("Manufacturer", on_delete=models.CASCADE) - - class Meta: - abstract = True - -.. code-block:: python - :caption: ``production/models.py`` +which the model is related and the :attr:`~ForeignKey.on_delete` option:: from django.db import models - from products.models import AbstractCar class Manufacturer(models.Model): - pass - - - class Car(AbstractCar): - pass - + name = models.TextField() - # Car.manufacturer will point to `production.Manufacturer` here. - -To refer to models defined in another application, you can explicitly specify -a model with the full application label. For example, if the ``Manufacturer`` -model above is defined in another application called ``production``, you'd -need to use:: class Car(models.Model): - manufacturer = models.ForeignKey( - "production.Manufacturer", - on_delete=models.CASCADE, - ) + manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) -This sort of reference, called a lazy relationship, can be useful when -resolving circular import dependencies between two applications. +The first positional argument can be either a concrete model class or a +:ref:`lazy reference ` to a model class. +:ref:`Recursive relationships `, where a model has a +relationship with itself, are also supported. + +See :attr:`ForeignKey.on_delete` for details on the second positional +argument. A database index is automatically created on the ``ForeignKey``. You can disable this by setting :attr:`~Field.db_index` to ``False``. You may want to @@ -1720,9 +1665,9 @@ Database Representation Behind the scenes, Django appends ``"_id"`` to the field name to create its database column name. In the above example, the database table for the ``Car`` -model will have a ``manufacturer_id`` column. (You can change this explicitly by -specifying :attr:`~Field.db_column`) However, your code should never have to -deal with the database column name, unless you write custom SQL. You'll always +model will have a ``manufacturer_id`` column. You can change this explicitly by +specifying :attr:`~Field.db_column`, however, your code should never have to +deal with the database column name (unless you write custom SQL). You'll always deal with the field names of your model object. .. _foreign-key-arguments: @@ -2272,6 +2217,120 @@ accepted by :class:`ForeignKey`, plus one extra argument: See :doc:`One-to-one relationships ` for usage examples of ``OneToOneField``. +.. _lazy-relationships: + +Lazy relationships +------------------ + +Lazy relationships allow referencing models by their names (as strings) or +creating recursive relationships. Strings can be used as the first argument in +any relationship field to reference models lazily. A lazy reference can be +either :ref:`recursive `, +:ref:`relative ` or +:ref:`absolute `. + +.. _recursive-relationships: + +Recursive +~~~~~~~~~ + +To define a relationship where a model references itself, use ``"self"`` as the +first argument of the relationship field:: + + from django.db import models + + + class Manufacturer(models.Model): + name = models.TextField() + suppliers = models.ManyToManyField("self", symmetrical=False) + + +When used in an :ref:`abstract model `, the recursive +relationship resolves such that each concrete subclass references itself. + +.. _relative-relationships: + +Relative +~~~~~~~~ + +When a relationship needs to be created with a model that has not been defined +yet, it can be referenced by its name rather than the model object itself:: + + from django.db import models + + + class Car(models.Model): + manufacturer = models.ForeignKey( + "Manufacturer", + on_delete=models.CASCADE, + ) + + + class Manufacturer(models.Model): + name = models.TextField() + suppliers = models.ManyToManyField("self", symmetrical=False) + +Relationships defined this way on :ref:`abstract models +` are resolved when the model is subclassed as a +concrete model and are not relative to the abstract model's ``app_label``: + +.. code-block:: python + :caption: ``products/models.py`` + + from django.db import models + + + class AbstractCar(models.Model): + manufacturer = models.ForeignKey("Manufacturer", on_delete=models.CASCADE) + + class Meta: + abstract = True + +.. code-block:: python + :caption: ``production/models.py`` + + from django.db import models + from products.models import AbstractCar + + + class Manufacturer(models.Model): + name = models.TextField() + + + class Car(AbstractCar): + pass + +In this example, the ``Car.manufacturer`` relationship will resolve to +``production.Manufacturer``, as it points to the concrete model defined +within the ``production/models.py`` file. + +.. admonition:: Reusable models with relative references + + Relative references allow the creation of reusable abstract models with + relationships that can resolve to different implementations of the + referenced models in various subclasses across different applications. + +.. _absolute-relationships: + +Absolute +~~~~~~~~ + +Absolute references specify a model using its ``app_label`` and class name, +allowing for model references across different applications. This type of lazy +relationship can also help resolve circular imports. + +For example, if the ``Manufacturer`` model is defined in another application +called ``thirdpartyapp``, it can be referenced as:: + + class Car(models.Model): + manufacturer = models.ForeignKey( + "thirdpartyapp``.Manufacturer", + on_delete=models.CASCADE, + ) + +Absolute references always point to the same model, even when used in an +:ref:`abstract model `. + Field API reference =================== From 3ba8b0dae8cfa8609a525a100ccc7d88859c5c81 Mon Sep 17 00:00:00 2001 From: Justin Thurman Date: Wed, 16 Oct 2024 09:43:02 -0400 Subject: [PATCH 13/40] [5.1.x] Fixed #35845 -- Updated DomainNameValidator to require entire string to be a valid domain name. Bug in 4971a9afe5642569f3dcfcd3972ebb39e88dd457. Thank you to kazet for the report and Claude Paroz for the review. Backport of 99dcc59237f384d7ade98acfd1cae8d90e6d60ab from main. --- django/core/validators.py | 9 ++++++--- docs/releases/5.1.3.txt | 5 ++++- tests/validators/tests.py | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/django/core/validators.py b/django/core/validators.py index b1c5c053b846..8732ddf7adbf 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -101,13 +101,16 @@ def __init__(self, **kwargs): if self.accept_idna: self.regex = _lazy_re_compile( - self.hostname_re + self.domain_re + self.tld_re, re.IGNORECASE + r"^" + self.hostname_re + self.domain_re + self.tld_re + r"$", + re.IGNORECASE, ) else: self.regex = _lazy_re_compile( - self.ascii_only_hostname_re + r"^" + + self.ascii_only_hostname_re + self.ascii_only_domain_re - + self.ascii_only_tld_re, + + self.ascii_only_tld_re + + r"$", re.IGNORECASE, ) super().__init__(**kwargs) diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt index 5541a8824a03..e3c62072b568 100644 --- a/docs/releases/5.1.3.txt +++ b/docs/releases/5.1.3.txt @@ -10,4 +10,7 @@ Django 5.1.3 fixes several bugs in 5.1.2 and adds compatibility with Python Bugfixes ======== -* ... +* Fixed a bug in Django 5.1 where + :class:`~django.core.validators.DomainNameValidator` accepted any input value + that contained a valid domain name, rather than only input values that were a + valid domain name (:ticket:`35845`). diff --git a/tests/validators/tests.py b/tests/validators/tests.py index ba1db5ea46d3..4ae0f6413e5e 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -635,8 +635,8 @@ (validate_domain_name, "python-python.com", None), (validate_domain_name, "python.name.uk", None), (validate_domain_name, "python.tips", None), - (validate_domain_name, "http://例子.测试", None), - (validate_domain_name, "http://dashinpunytld.xn---c", None), + (validate_domain_name, "例子.测试", None), + (validate_domain_name, "dashinpunytld.xn---c", None), (validate_domain_name, "python..org", ValidationError), (validate_domain_name, "python-.org", ValidationError), (validate_domain_name, "too-long-name." * 20 + "com", ValidationError), @@ -652,6 +652,16 @@ ), (DomainNameValidator(accept_idna=False), "ıçğü.com", ValidationError), (DomainNameValidator(accept_idna=False), "not-domain-name", ValidationError), + ( + DomainNameValidator(accept_idna=False), + "not-domain-name, but-has-domain-name-suffix.com", + ValidationError, + ), + ( + DomainNameValidator(accept_idna=False), + "not-domain-name.com, but has domain prefix", + ValidationError, + ), ] # Add valid and invalid URL tests. From d1b63c26cc7962f9401d1650572aeb838892ad55 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 15 Oct 2024 09:26:12 +0100 Subject: [PATCH 14/40] [5.1.x] Refs #35841 -- Updated GeoIP2 test database files. The mmdb files were taken from https://github.com/maxmind/MaxMind-DB/commit/679e37e18a4a3009949c2213ec2c0bb8090c10c7. Backport of c37f249ffa4b735d1492cda11981dedb947ee437 from main. --- .../data/geoip2/GeoIP2-City-Test.mmdb | Bin 22430 -> 22451 bytes .../data/geoip2/GeoIP2-Country-Test.mmdb | Bin 19628 -> 19744 bytes .../data/geoip2/GeoLite2-ASN-Test.mmdb | Bin 12653 -> 12653 bytes .../data/geoip2/GeoLite2-City-Test.mmdb | Bin 21117 -> 21117 bytes .../data/geoip2/GeoLite2-Country-Test.mmdb | Bin 18041 -> 18041 bytes tests/gis_tests/data/geoip2/LICENSE | 4 ---- tests/gis_tests/data/geoip2/README | 3 --- tests/gis_tests/data/geoip2/README.md | 14 ++++++++++++++ 8 files changed, 14 insertions(+), 7 deletions(-) delete mode 100644 tests/gis_tests/data/geoip2/LICENSE delete mode 100644 tests/gis_tests/data/geoip2/README create mode 100644 tests/gis_tests/data/geoip2/README.md diff --git a/tests/gis_tests/data/geoip2/GeoIP2-City-Test.mmdb b/tests/gis_tests/data/geoip2/GeoIP2-City-Test.mmdb index 3197ef122fa3522eb8b4e15ad0e6cf1e0e51e8a0..bf3cbe783593e60c16bf2e97f27ad7b692bb17c9 100644 GIT binary patch literal 22451 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 literal 22430 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 an`rqi_r*?~G6S9xqzOeq?@Uj9@Bafz;CX8R diff --git a/tests/gis_tests/data/geoip2/GeoIP2-Country-Test.mmdb b/tests/gis_tests/data/geoip2/GeoIP2-Country-Test.mmdb index d79c9933bb986e1ea01c7a6faebde862f1c1d4a7..a5989654ebd25f3e9d4c043692cd1e0e66915aac 100644 GIT binary patch literal 19744 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 literal 19628 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 diff --git a/tests/gis_tests/data/geoip2/GeoLite2-ASN-Test.mmdb b/tests/gis_tests/data/geoip2/GeoLite2-ASN-Test.mmdb index afa7e956e4cd55d4398ffb9d4805ad2dde507cf3..fe2123f9e0fc3e1867c9ff5420003e510ee0b4ea 100644 GIT binary patch delta 16 YcmaEx^fqb3UIUhN*8Xdg4;rii07-8MK>z>% delta 16 YcmaEx^fqb3UIUiY=svT_2MyK$07#Pu^Z)<= diff --git a/tests/gis_tests/data/geoip2/GeoLite2-City-Test.mmdb b/tests/gis_tests/data/geoip2/GeoLite2-City-Test.mmdb index 028a6984d93fb2e93c1ac39b828e91484d57a442..9eea131c76b70774e1c81ba78b30f01f33954aad 100644 GIT binary patch delta 18 acmeyngz@hZ#to+fS<+eiuT4H5xDEhW+X+Vi delta 18 acmeyngz@hZ#to+fSyH3>%qE`?Tn7MH{|Nd3 diff --git a/tests/gis_tests/data/geoip2/GeoLite2-Country-Test.mmdb b/tests/gis_tests/data/geoip2/GeoLite2-Country-Test.mmdb index a2cbb0831697342a4b9b136d3c892ec84acc1348..0233bba39b822b43dda9cb44d70b19c00cb0ee84 100644 GIT binary patch delta 18 acmey_!}zm@al=7ZmUPzsYm<+ delta 18 acmey_!}zm@al=7ZmelAzv&lzYw*UZCeF&TY diff --git a/tests/gis_tests/data/geoip2/LICENSE b/tests/gis_tests/data/geoip2/LICENSE deleted file mode 100644 index f86abbd73e1c..000000000000 --- a/tests/gis_tests/data/geoip2/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 -Unported License. To view a copy of this license, visit -http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative -Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. diff --git a/tests/gis_tests/data/geoip2/README b/tests/gis_tests/data/geoip2/README deleted file mode 100644 index b6a21720a38d..000000000000 --- a/tests/gis_tests/data/geoip2/README +++ /dev/null @@ -1,3 +0,0 @@ -These test databases are taken from the following repository: - -https://github.com/maxmind/MaxMind-DB/ diff --git a/tests/gis_tests/data/geoip2/README.md b/tests/gis_tests/data/geoip2/README.md new file mode 100644 index 000000000000..36328671b2e1 --- /dev/null +++ b/tests/gis_tests/data/geoip2/README.md @@ -0,0 +1,14 @@ +# GeoIP2 and GeoLite2 Test Databases + +The following test databases are provided under [this license][0]: + +- `GeoIP2-City-Test.mmdb` +- `GeoIP2-Country-Test.mmdb` +- `GeoLite2-ASN-Test.mmdb` +- `GeoLite2-City-Test.mmdb` +- `GeoLite2-Country-Test.mmdb` + +Updates can be found in [this repository][1]. + +[0]: https://github.com/maxmind/MaxMind-DB/blob/main/LICENSE-MIT +[1]: https://github.com/maxmind/MaxMind-DB/tree/main/test-data From 434cf004b8613908508bee506eab67fbf2374c41 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 15 Oct 2024 09:58:19 +0100 Subject: [PATCH 15/40] [5.1.x] Refs #35841 -- Adjusted GeoIP2 tests for easier test case extension. These changes will make it easier to introduce tests for alternate databases that may have different results without the need to duplicate lots of the tests definition. Backport of 5873f10177ebda66d38e698218cf85dc6397e7d9 from main. --- tests/gis_tests/test_geoip2.py | 104 ++++++++++++++++----------------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index 11c73bec0cd8..f6f6ab039798 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -32,6 +32,33 @@ class GeoLite2Test(SimpleTestCase): ipv6_addr = ipaddress.ip_address(ipv6_str) query_values = (fqdn, ipv4_str, ipv6_str, ipv4_addr, ipv6_addr) + expected_city = { + "accuracy_radius": 100, + "city": "Boxford", + "continent_code": "EU", + "continent_name": "Europe", + "country_code": "GB", + "country_name": "United Kingdom", + "is_in_european_union": False, + "latitude": 51.75, + "longitude": -1.25, + "metro_code": None, + "postal_code": "OX1", + "region_code": "ENG", + "region_name": "England", + "time_zone": "Europe/London", + # Kept for backward compatibility. + "dma_code": None, + "region": "ENG", + } + expected_country = { + "continent_code": "EU", + "continent_name": "Europe", + "country_code": "GB", + "country_name": "United Kingdom", + "is_in_european_union": False, + } + @classmethod def setUpClass(cls): # Avoid referencing __file__ at module level. @@ -100,85 +127,52 @@ def test_country(self): self.assertIs(g._metadata.database_type.endswith("Country"), True) for query in self.query_values: with self.subTest(query=query): + self.assertEqual(g.country(query), self.expected_country) + self.assertEqual( + g.country_code(query), self.expected_country["country_code"] + ) self.assertEqual( - g.country(query), - { - "continent_code": "EU", - "continent_name": "Europe", - "country_code": "GB", - "country_name": "United Kingdom", - "is_in_european_union": False, - }, + g.country_name(query), self.expected_country["country_name"] ) - self.assertEqual(g.country_code(query), "GB") - self.assertEqual(g.country_name(query), "United Kingdom") def test_country_using_city_database(self): g = GeoIP2(country="") self.assertIs(g._metadata.database_type.endswith("City"), True) for query in self.query_values: with self.subTest(query=query): + self.assertEqual(g.country(query), self.expected_country) + self.assertEqual( + g.country_code(query), self.expected_country["country_code"] + ) self.assertEqual( - g.country(query), - { - "continent_code": "EU", - "continent_name": "Europe", - "country_code": "GB", - "country_name": "United Kingdom", - "is_in_european_union": False, - }, + g.country_name(query), self.expected_country["country_name"] ) - self.assertEqual(g.country_code(query), "GB") - self.assertEqual(g.country_name(query), "United Kingdom") def test_city(self): g = GeoIP2(country="") self.assertIs(g._metadata.database_type.endswith("City"), True) for query in self.query_values: with self.subTest(query=query): - self.assertEqual( - g.city(query), - { - "accuracy_radius": 100, - "city": "Boxford", - "continent_code": "EU", - "continent_name": "Europe", - "country_code": "GB", - "country_name": "United Kingdom", - "is_in_european_union": False, - "latitude": 51.75, - "longitude": -1.25, - "metro_code": None, - "postal_code": "OX1", - "region_code": "ENG", - "region_name": "England", - "time_zone": "Europe/London", - # Kept for backward compatibility. - "dma_code": None, - "region": "ENG", - }, - ) + self.assertEqual(g.city(query), self.expected_city) geom = g.geos(query) self.assertIsInstance(geom, GEOSGeometry) self.assertEqual(geom.srid, 4326) - self.assertEqual(geom.tuple, (-1.25, 51.75)) - self.assertEqual(g.lat_lon(query), (51.75, -1.25)) - self.assertEqual(g.lon_lat(query), (-1.25, 51.75)) + expected_lat = self.expected_city["latitude"] + expected_lon = self.expected_city["longitude"] + self.assertEqual(geom.tuple, (expected_lon, expected_lat)) + self.assertEqual(g.lat_lon(query), (expected_lat, expected_lon)) + self.assertEqual(g.lon_lat(query), (expected_lon, expected_lat)) + # Country queries should still work. + self.assertEqual(g.country(query), self.expected_country) + self.assertEqual( + g.country_code(query), self.expected_country["country_code"] + ) self.assertEqual( - g.country(query), - { - "continent_code": "EU", - "continent_name": "Europe", - "country_code": "GB", - "country_name": "United Kingdom", - "is_in_european_union": False, - }, + g.country_name(query), self.expected_country["country_name"] ) - self.assertEqual(g.country_code(query), "GB") - self.assertEqual(g.country_name(query), "United Kingdom") def test_not_found(self): g1 = GeoIP2(city="") From 51eb666758840e0fca15414f1c5826bd20e31153 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 15 Oct 2024 09:48:59 +0100 Subject: [PATCH 16/40] [5.1.x] Fixed #35841 -- Restored support for DB-IP databases in GeoIP2. Thanks Felix Farquharson for the report and Claude Paroz for the review. Regression in 40b5b1596f7505416bd30d5d7582b5a9004ea7d5. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 3fad712a91a8a8f6f6f904aff3d895e3b06b24c7 from main. --- django/contrib/gis/geoip2.py | 28 +++++++++++++--- docs/releases/5.1.3.txt | 3 ++ tests/gis_tests/data/geoip2/README.md | 14 ++++++++ .../data/geoip2/dbip-city-lite-test.mmdb | Bin 0 -> 1481 bytes .../data/geoip2/dbip-country-lite-test.mmdb | Bin 0 -> 1314 bytes tests/gis_tests/test_geoip2.py | 30 ++++++++++++++++-- 6 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 tests/gis_tests/data/geoip2/dbip-city-lite-test.mmdb create mode 100644 tests/gis_tests/data/geoip2/dbip-country-lite-test.mmdb diff --git a/django/contrib/gis/geoip2.py b/django/contrib/gis/geoip2.py index f5058c1c05dc..a5fe429b89ed 100644 --- a/django/contrib/gis/geoip2.py +++ b/django/contrib/gis/geoip2.py @@ -34,6 +34,18 @@ __all__ += ["GeoIP2", "GeoIP2Exception"] +# These are the values stored in the `database_type` field of the metadata. +# See https://maxmind.github.io/MaxMind-DB/#database_type for details. +SUPPORTED_DATABASE_TYPES = { + "DBIP-City-Lite", + "DBIP-Country-Lite", + "GeoIP2-City", + "GeoIP2-Country", + "GeoLite2-City", + "GeoLite2-Country", +} + + class GeoIP2Exception(Exception): pass @@ -106,7 +118,7 @@ def __init__(self, path=None, cache=0, country=None, city=None): ) database_type = self._metadata.database_type - if not database_type.endswith(("City", "Country")): + if database_type not in SUPPORTED_DATABASE_TYPES: raise GeoIP2Exception(f"Unable to handle database edition: {database_type}") def __del__(self): @@ -123,6 +135,14 @@ def __repr__(self): def _metadata(self): return self._reader.metadata() + @cached_property + def is_city(self): + return "City" in self._metadata.database_type + + @cached_property + def is_country(self): + return "Country" in self._metadata.database_type + def _query(self, query, *, require_city=False): if not isinstance(query, (str, ipaddress.IPv4Address, ipaddress.IPv6Address)): raise TypeError( @@ -130,9 +150,7 @@ def _query(self, query, *, require_city=False): "IPv6Address, not type %s" % type(query).__name__, ) - is_city = self._metadata.database_type.endswith("City") - - if require_city and not is_city: + if require_city and not self.is_city: raise GeoIP2Exception(f"Invalid GeoIP city data file: {self._path}") try: @@ -141,7 +159,7 @@ def _query(self, query, *, require_city=False): # GeoIP2 only takes IP addresses, so try to resolve a hostname. query = socket.gethostbyname(query) - function = self._reader.city if is_city else self._reader.country + function = self._reader.city if self.is_city else self._reader.country return function(query) def city(self, query): diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt index e3c62072b568..0dd5b42cb8d5 100644 --- a/docs/releases/5.1.3.txt +++ b/docs/releases/5.1.3.txt @@ -14,3 +14,6 @@ Bugfixes :class:`~django.core.validators.DomainNameValidator` accepted any input value that contained a valid domain name, rather than only input values that were a valid domain name (:ticket:`35845`). + +* Fixed a regression in Django 5.1 that prevented the use of DB-IP databases + with :class:`~django.contrib.gis.geoip2.GeoIP2` (:ticket:`35841`). diff --git a/tests/gis_tests/data/geoip2/README.md b/tests/gis_tests/data/geoip2/README.md index 36328671b2e1..f2a703b45775 100644 --- a/tests/gis_tests/data/geoip2/README.md +++ b/tests/gis_tests/data/geoip2/README.md @@ -12,3 +12,17 @@ Updates can be found in [this repository][1]. [0]: https://github.com/maxmind/MaxMind-DB/blob/main/LICENSE-MIT [1]: https://github.com/maxmind/MaxMind-DB/tree/main/test-data + +# DB-IP Lite Test Databases + +The following test databases are provided under [this license][2]: + +- `dbip-city-lite-test.mmdb` +- `dbip-country-lite-test.mmdb` + +They have been modified to strip them down to a minimal dataset for testing. + +Updates can be found at [this download page][3] from DB-IP. + +[2]: https://creativecommons.org/licenses/by/4.0/ +[3]: https://db-ip.com/db/lite.php diff --git a/tests/gis_tests/data/geoip2/dbip-city-lite-test.mmdb b/tests/gis_tests/data/geoip2/dbip-city-lite-test.mmdb new file mode 100644 index 0000000000000000000000000000000000000000..5f0d657c841ad687085c88a037010b02816e54c8 GIT binary patch literal 1481 zcmZ9}&2tn*7{~D@Mj{{q1$h&2KvV+mk_3qIB9dj7-OVN;Auph))VhS8)?=#x1xNU&C$qI&Q~K zOyCYoVhVR+8Z(&1F3e#!_FymW!anR5;*b!#$pbiuLpY3ka4)`rZ{j|D3*W|f(8hPs z!92R?p~U?-Aw)rlB3V5cIY2mwB`l*B!VsdW3gJB={0Ad}5FxpWBRGn`VN_Kk9L6K~ zK7N2l@k1QLkMJ0NjGy4A_!&mco*c31W9(nF2T@}_QQ`#xZU@{?ff49+|(bnpLqT`Lq zQmG~D8XYPvb@*4(HJ(hQhPn#U)GKV|6+8Fbj+z$2YMQi~cc!YosmQczsyg&*e_I{J z&g7C?W6}Je3T&lq$pS~)RjrH`O&Jv!n{Cqxw&)$wDwLj;Q@Y@pa;9WlCybWf$&{jZ zSoLIad-nJK#eX!}*^<$PsiEt?W{&Op^Pg*JEyGdcAM9<9x3zDKw{^6}Ti3O3=nkrR zPmQP`+9;UTILS%rlrEGU?OnThzsEV$qqG-yP5IuwywZ+ev-can?1XmNIbeKyMEdpG z@rM7;D*bTwYA#<@CC`=>;}#nm3e97edORm|@=hS_uvU@1$wamS2_Xp}D5!|KY_f-Bft-!I8=4J!@0}~T)u-?Q z9ACg225-Fb6E z;{sfWi*PY6!4h1G%WyfC;tDLomADF5;~HFx8m`0jxB)lfCftl$upGDIHr$RoFn|?U ziB(vQHCT%~aTo5!J-8S5;eI@T2k{Ud#v^zX>oAD*7{V|%U<4a6iZP616EZ2_ z6CxwTaqj(;u-A1vv>~AqmJEZU;>k9qQoBbf3TDgX|nZuq?gc#{rCYD z+L*-)=%9-p=5PQ9aR`TT1TUgr>Se+eyo#fE4X@)3yotB)Hr~OzLcAl~6XHJM0Y1b> z_!ytyQ+$Tc@ddubSNIy=;9H#T>a^mYXEK_!m1ilbyvb0~Hf1o}5lcy185yZtX1eaw zSHB=3!czn8L{O@RaL%!_GLp1&%5#RlX(j2pP}!GJe^I;T>Xy_lw~VX)M&Z3 z&e@TmRz9Iswsa`VlcpB8RLZn75hcAr+v#hnsi>~0s_@^+>e`Q=e>59I&6YANl6K~M zSHe<;GpzU6PR8)`jL~a5`hav?{|n>!|Ib?L@9g_{B4_oRy3E?ibU}WqxVxj-G(00= zxKj6qv$8D|jJ5{qr>_%e@+*s*(oH&6*7Faax^ibI7>Kq`)u9EnXshPg+OOp?EBmL4 n!f3ytQaK|f-Mj*SS4{cs^zZGr>C;%;kxAPzb=Mk^") - self.assertIs(g._metadata.database_type.endswith("Country"), True) + self.assertIs(g.is_city, False) + self.assertIs(g.is_country, True) for query in self.query_values: with self.subTest(query=query): self.assertEqual(g.country(query), self.expected_country) @@ -137,7 +138,8 @@ def test_country(self): def test_country_using_city_database(self): g = GeoIP2(country="") - self.assertIs(g._metadata.database_type.endswith("City"), True) + self.assertIs(g.is_city, True) + self.assertIs(g.is_country, False) for query in self.query_values: with self.subTest(query=query): self.assertEqual(g.country(query), self.expected_country) @@ -150,7 +152,8 @@ def test_country_using_city_database(self): def test_city(self): g = GeoIP2(country="") - self.assertIs(g._metadata.database_type.endswith("City"), True) + self.assertIs(g.is_city, True) + self.assertIs(g.is_country, False) for query in self.query_values: with self.subTest(query=query): self.assertEqual(g.city(query), self.expected_city) @@ -224,6 +227,27 @@ class GeoIP2Test(GeoLite2Test): """Non-free GeoIP2 databases are supported.""" +@skipUnless(HAS_GEOIP2, "GeoIP2 is required.") +@override_settings( + GEOIP_CITY="dbip-city-lite-test.mmdb", + GEOIP_COUNTRY="dbip-country-lite-test.mmdb", +) +class DBIPLiteTest(GeoLite2Test): + """DB-IP Lite databases are supported.""" + + expected_city = GeoLite2Test.expected_city | { + "accuracy_radius": None, + "city": "London (Shadwell)", + "latitude": 51.5181, + "longitude": -0.0714189, + "postal_code": None, + "region_code": None, + "time_zone": None, + # Kept for backward compatibility. + "region": None, + } + + @skipUnless(HAS_GEOIP2, "GeoIP2 is required.") class ErrorTest(SimpleTestCase): def test_missing_path(self): From 8d48fc035c1468abf841090d4a8f459d161edfa3 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 21 Oct 2024 19:20:03 +0200 Subject: [PATCH 17/40] [5.1.x] Updated tutorial part count from 7 to 8 in docs/intro/reusable-apps.txt. Backport of f59cdd00093338427acde555c9b687acc5ac67ea from main. --- docs/intro/reusable-apps.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index a9c0768e3b8e..5acf8c2b182a 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -6,7 +6,7 @@ This advanced tutorial begins where :doc:`Tutorial 8 ` left off. We'll be turning our web-poll into a standalone Python package you can reuse in new projects and share with other people. -If you haven't recently completed Tutorials 1–7, we encourage you to review +If you haven't recently completed Tutorials 1–8, we encourage you to review these so that your example project matches the one described below. Reusability matters From 6eb6f236f2836db94a27dff76d1663dfb682b4ea Mon Sep 17 00:00:00 2001 From: amirreza sohrabi far <119850973+amirreza8002@users.noreply.github.com> Date: Tue, 22 Oct 2024 01:01:39 +0330 Subject: [PATCH 18/40] [5.1.x] Updated Hypercorn links in docs/howto/deployment/asgi/hypercorn.txt. Backport of 5a91ad3d7115c692d497663a155edee5ebc8989c from main. --- docs/howto/deployment/asgi/hypercorn.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/deployment/asgi/hypercorn.txt b/docs/howto/deployment/asgi/hypercorn.txt index ea5ce3cc72c6..3abd2d54efcf 100644 --- a/docs/howto/deployment/asgi/hypercorn.txt +++ b/docs/howto/deployment/asgi/hypercorn.txt @@ -17,7 +17,7 @@ You can install Hypercorn with ``pip``: Running Django in Hypercorn =========================== -When Hypercorn is installed, a ``hypercorn`` command is available +When :pypi:`Hypercorn` is installed, a ``hypercorn`` command is available which runs ASGI applications. Hypercorn needs to be called with the location of a module containing an ASGI application object, followed by what the application is called (separated by a colon). @@ -35,4 +35,4 @@ this command from the same directory as your ``manage.py`` file. For more advanced usage, please read the `Hypercorn documentation `_. -.. _Hypercorn: https://pgjones.gitlab.io/hypercorn/ +.. _Hypercorn: https://hypercorn.readthedocs.io/ From 60dc9c288cf0f5e906b022e1ff77bea8a7c4ab19 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 22 Oct 2024 11:06:34 +0200 Subject: [PATCH 19/40] [5.1.x] Fixed example indentation in howto/overriding-templates.txt. Backport of bcb91611eca154f022211633fe485e3e1a3c608d from main. --- docs/howto/overriding-templates.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/howto/overriding-templates.txt b/docs/howto/overriding-templates.txt index f636948a201d..f99a1203a8d6 100644 --- a/docs/howto/overriding-templates.txt +++ b/docs/howto/overriding-templates.txt @@ -111,15 +111,15 @@ reimplement the entire template. For example, you can use this technique to add a custom logo to the ``admin/base_site.html`` template: - .. code-block:: html+django - :caption: ``templates/admin/base_site.html`` +.. code-block:: html+django + :caption: ``templates/admin/base_site.html`` - {% extends "admin/base_site.html" %} + {% extends "admin/base_site.html" %} - {% block branding %} - logo - {{ block.super }} - {% endblock %} + {% block branding %} + logo + {{ block.super }} + {% endblock %} Key points to note: From 8efba533979b55ed8ca45c9dcadd098cc935821d Mon Sep 17 00:00:00 2001 From: ssanger Date: Fri, 18 Oct 2024 18:18:43 -0700 Subject: [PATCH 20/40] [5.1.x] Added missing alt attribute to tag in docs. Backport of df6013b2b4e93ed6d127c2f572e6de0ba46d1d6a from main. --- docs/ref/templates/builtins.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 86841b3dbd38..c00baf3a0779 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -3110,7 +3110,7 @@ slightly different call: {% load static %} {% static "images/hi.jpg" as myphoto %} - + Hi! .. admonition:: Using Jinja2 templates? From d9f37ac118e1717f831394d2be3b64c712d3b40e Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Tue, 22 Oct 2024 20:10:25 +0200 Subject: [PATCH 21/40] [5.1.x] Restructured how-to docs landing page. Previously, this was a bare list of sub-pages, not in any discernible order, and hard to parse. Now the sub-pages are grouped in sections by topic. It's unlikely to be the final word on how this material is arranged, but it's a clear improvement on the existing arrangement and provides a good basis for next steps. Backport of dd0a116b93c40f9febf0e09614ad666af1191744 from main. --- docs/howto/index.txt | 72 ++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/docs/howto/index.txt b/docs/howto/index.txt index 0034032ce25e..d799ca79069d 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -1,38 +1,66 @@ -=============== -"How-to" guides -=============== +============= +How-to guides +============= -Here you'll find short answers to "How do I....?" types of questions. These -how-to guides don't cover topics in depth -- you'll find that material in the -:doc:`/topics/index` and the :doc:`/ref/index`. However, these guides will help -you quickly accomplish common tasks. +Practical guides covering common tasks and problems. + +Models, data and databases +========================== .. toctree:: :maxdepth: 1 - auth-remote-user - csrf - custom-management-commands - custom-model-fields - custom-lookups - custom-template-backend - custom-template-tags - custom-file-storage - deployment/index - upgrade-version - error-reporting initial-data legacy-databases - logging + custom-model-fields + writing-migrations + custom-lookups + +Templates and output +==================== + +.. toctree:: + :maxdepth: 1 + outputting-csv outputting-pdf overriding-templates + custom-template-backend + custom-template-tags + +Project configuration and management +==================================== + +.. toctree:: + :maxdepth: 1 + static-files/index - static-files/deployment - windows - writing-migrations + logging + error-reporting delete-app +Installing, deploying and upgrading +=================================== + +.. toctree:: + :maxdepth: 1 + + upgrade-version + windows + deployment/index + static-files/deployment + +Other guides +============ + +.. toctree:: + :maxdepth: 1 + + auth-remote-user + csrf + custom-management-commands + custom-file-storage + .. seealso:: The `Django community aggregator`_, where we aggregate content from the From 630c9e1f9d05e9d25908a0c5d5f81f1b0b537af0 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 16 Oct 2024 14:38:31 +0530 Subject: [PATCH 22/40] [5.1.x] Fixed #35731 -- Extended db_default docs. This added a missing db_default reference in docs/topics/db/models.txt, and added a reference to the DatabaseDefault object. Backport of 35ab2e018214479fa712d73f070198299ef670a1 from main. --- docs/ref/models/fields.txt | 5 +++++ docs/topics/db/models.txt | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 0ceb620a9f23..745520961dc5 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -434,6 +434,11 @@ precedence when creating instances in Python code. ``db_default`` will still be set at the database level and will be used when inserting rows outside of the ORM or when adding a new field in a migration. +If a field has a ``db_default`` without a ``default`` set and no value is +assigned to the field, a ``DatabaseDefault`` object is returned as the field +value on unsaved model instances. The actual value for the field is determined +by the database when the model instance is saved. + ``db_index`` ------------ diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index ee4bdab7bb38..62488a75f791 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -228,6 +228,15 @@ ones: object. If callable it will be called every time a new object is created. +:attr:`~Field.db_default` + The database-computed default value for the field. This can be a literal + value or a database function. + + If both ``db_default`` and :attr:`Field.default` are set, ``default`` will + take precedence when creating instances in Python code. ``db_default`` will + still be set at the database level and will be used when inserting rows + outside of the ORM or when adding a new field in a migration. + :attr:`~Field.help_text` Extra "help" text to be displayed with the form widget. It's useful for documentation even if your field isn't used on a form. From 8bfa520a01a0156533079b5906aed7ea4629eb72 Mon Sep 17 00:00:00 2001 From: David D Lowe Date: Wed, 23 Oct 2024 18:20:36 +0100 Subject: [PATCH 23/40] [5.1.x] Doc'd that unusable passwords are defined by metadata in the password field. Backport of de2bb73904009313bae3664ef71edfd60df9912b from main. --- docs/ref/contrib/auth.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 036f8d9f7678..9185c9d0f94a 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -54,7 +54,8 @@ Fields Required. A hash of, and metadata about, the password. (Django doesn't store the raw password.) Raw passwords can be arbitrarily long and can - contain any character. See the :doc:`password documentation + contain any character. The metadata in this field may mark the password + as unusable. See the :doc:`password documentation `. .. attribute:: groups @@ -179,8 +180,9 @@ Methods .. method:: set_unusable_password() - Marks the user as having no password set. This isn't the same as - having a blank string for a password. + Marks the user as having no password set by updating the metadata in + the :attr:`~django.contrib.auth.models.User.password` field. This isn't + the same as having a blank string for a password. :meth:`~django.contrib.auth.models.User.check_password()` for this user will never return ``True``. Doesn't save the :class:`~django.contrib.auth.models.User` object. From 34989e076b639ee1c67cbabb426a35ceaaa83760 Mon Sep 17 00:00:00 2001 From: koresi Date: Wed, 25 Sep 2024 03:57:20 +0200 Subject: [PATCH 24/40] [5.1.x] Fixed #22828 -- Warned that ModelAdmin get hooks return the property itself rather a copy. Backport of b8e9cdf13b7ab6621926a5d2aad3e2bb745aae00 from main. --- docs/ref/contrib/admin/index.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index c2061f11ab79..4a8b52c18f66 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1473,6 +1473,27 @@ templates used by the :class:`ModelAdmin` views: See also :ref:`saving-objects-in-the-formset`. +.. warning:: + + All hooks that return a ``ModelAdmin`` property return the property itself + rather than a copy of its value. Dynamically modifying the value can lead + to surprising results. + + Let's take :meth:`ModelAdmin.get_readonly_fields` as an example:: + + class PersonAdmin(admin.ModelAdmin): + readonly_fields = ["name"] + + def get_readonly_fields(self, request, obj=None): + readonly = super().get_readonly_fields(request, obj) + if not request.user.is_superuser: + readonly.append("age") # Edits the class attribute. + return readonly + + This results in ``readonly_fields`` becoming + ``["name", "age", "age", ...]``, even for a superuser, as ``"age"`` is added + each time non-superuser visits the page. + .. method:: ModelAdmin.get_ordering(request) The ``get_ordering`` method takes a ``request`` as parameter and From 6bc36a1d27847411810275796f3c328ce68849fd Mon Sep 17 00:00:00 2001 From: AfiMaameDufie Date: Thu, 24 Oct 2024 09:28:52 +0200 Subject: [PATCH 25/40] [5.1.x] Updated authentication solutions list on using REMOTE_USER how-to. Backport of 6ae0dc89c53e51ec1d74ffba630686ad1988466a from main. --- docs/howto/auth-remote-user.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index 19b25432fe7c..f8492e367ad9 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -6,12 +6,11 @@ This document describes how to make use of external authentication sources (where the web server sets the ``REMOTE_USER`` environment variable) in your Django applications. This type of authentication solution is typically seen on intranet sites, with single sign-on solutions such as IIS and Integrated -Windows Authentication or Apache and `mod_authnz_ldap`_, `CAS`_, `Cosign`_, -`WebAuth`_, `mod_auth_sspi`_, etc. +Windows Authentication or Apache and `mod_authnz_ldap`_, `CAS`_, `WebAuth`_, +`mod_auth_sspi`_, etc. -.. _mod_authnz_ldap: https://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html +.. _mod_authnz_ldap: https://httpd.apache.org/docs/current/mod/mod_authnz_ldap.html .. _CAS: https://www.apereo.org/projects/cas -.. _Cosign: http://weblogin.org .. _WebAuth: https://uit.stanford.edu/service/authentication .. _mod_auth_sspi: https://sourceforge.net/projects/mod-auth-sspi From 56ad530ade53e30596ca95c85169c187b1c33ced Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 24 Oct 2024 16:57:04 +0200 Subject: [PATCH 26/40] [5.1.x] Refs #26029 -- Extended docs for the StorageHandler default instance. Third-party packages that provide storages need to rely on the StorageHandler API in order to allow users to use the `storages` module instance to override defaults. Minimally documenting these methods allows package authors to rely on them. Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Backport of 6dcab75d5d8c2ef18de15323930057e6fa9ec00f from main. --- docs/ref/files/storage.txt | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt index f7c290a15098..52c8f90427d5 100644 --- a/docs/ref/files/storage.txt +++ b/docs/ref/files/storage.txt @@ -11,7 +11,25 @@ Django provides convenient ways to access the default storage class: .. data:: storages - Storage instances as defined by :setting:`STORAGES`. + A dictionary-like object that allows retrieving a storage instance using + its alias as defined by :setting:`STORAGES`. + + ``storages`` has an attribute ``backends``, which defaults to the raw value + provided in :setting:`STORAGES`. + + Additionally, ``storages`` provides a ``create_storage()`` method that + accepts the dictionary used in :setting:`STORAGES` for a backend, and + returns a storage instance based on that backend definition. This may be + useful for third-party packages needing to instantiate storages in tests: + + .. code-block:: pycon + + >>> from django.core.files.storage import storages + >>> storages.backends + {'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'}, + 'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'}, + 'custom': {'BACKEND': 'package.storage.CustomStorage'}} + >>> storage_instance = storages.create_storage({"BACKEND": "package.storage.CustomStorage"}) .. class:: DefaultStorage From 95067098ddba497ad3fd55b1e881ad31cde2ab21 Mon Sep 17 00:00:00 2001 From: mbcodes Date: Wed, 23 Oct 2024 12:22:38 -0700 Subject: [PATCH 27/40] [5.1.x] Improved readability of triage workflow image by increasing its size and color contrast. Backport of c973d9ee82a36419a408b193d4195f69734a8e33 from main. --- docs/internals/_images/triage_process.svg | 40 +++++++++---------- .../contributing/triaging-tickets.txt | 4 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/internals/_images/triage_process.svg b/docs/internals/_images/triage_process.svg index 2b5e0d3cedd3..6fbf1cbcc7f2 100644 --- a/docs/internals/_images/triage_process.svg +++ b/docs/internals/_images/triage_process.svg @@ -232,47 +232,47 @@ - - - The ticket was already reported, was - already rejected, isn't a bug, doesn't contain - enough information, or can't be reproduced. + + + The ticket was already reported, was + already rejected, isn't a bug, doesn't contain + enough information, or can't be reproduced. - + - + - - - The ticket is a - bug and should - be fixed. + + + The ticket is a + bug and should + be fixed. - + - + - - - The ticket has a patch which applies cleanly and includes all - needed tests and docs. A merger can commit it as is. + + + The ticket has a patch which applies cleanly and includes all + needed tests and docs. A merger can commit it as is. - + - + diff --git a/docs/internals/contributing/triaging-tickets.txt b/docs/internals/contributing/triaging-tickets.txt index 852219c96caa..7987d63e9a61 100644 --- a/docs/internals/contributing/triaging-tickets.txt +++ b/docs/internals/contributing/triaging-tickets.txt @@ -49,8 +49,8 @@ attribute easily tells us what and who each ticket is waiting on. Since a picture is worth a thousand words, let's start there: .. image:: /internals/_images/triage_process.* - :height: 501 - :width: 400 + :height: 750 + :width: 600 :alt: Django's ticket triage workflow We've got two roles in this diagram: From e8e503551100af5c29d253870233dee838dbafa3 Mon Sep 17 00:00:00 2001 From: earthyoung Date: Mon, 28 Oct 2024 15:59:18 +0900 Subject: [PATCH 28/40] [5.1.x] Refs #34900 -- Removed usage of deprecated glob.glob1(). Backport of 555f2412cba4c5844408042e92f3bf9fa5c2392c from main. --- django/core/cache/backends/filebased.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 29d49c0ede9f..cbf47e4e1687 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -166,5 +166,5 @@ def _list_cache_files(self): """ return [ os.path.join(self._dir, fname) - for fname in glob.glob1(self._dir, "*%s" % self.cache_suffix) + for fname in glob.glob(f"*{self.cache_suffix}", root_dir=self._dir) ] From a0ea554d24406011474af6be6183807c94d20896 Mon Sep 17 00:00:00 2001 From: antoliny0919 Date: Tue, 29 Oct 2024 08:44:37 +0900 Subject: [PATCH 29/40] [5.1.x] Fixed #35871 -- Corrected example on altering the base_fields attribute in forms docs. Backport of 738e0601d597d4b6bee0000f645994495af984d8 from main. --- docs/ref/forms/api.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 28cd452c4e8b..15fc52f9b976 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -406,8 +406,8 @@ process: .. code-block:: pycon >>> f.base_fields["subject"].label_suffix = "?" - >>> another_f = CommentForm(auto_id=False) - >>> f.as_div().split("")[0] + >>> another_f = ContactForm(auto_id=False) + >>> another_f.as_div().split("")[0] '
' Accessing "clean" data From b8277179d063a92c8216ab5d092836febb7bf465 Mon Sep 17 00:00:00 2001 From: Clifford Gama Date: Sun, 27 Oct 2024 12:12:55 +0200 Subject: [PATCH 30/40] [5.1.x] Fixed typo in ref/models/fields.txt. Backport of 799c3778186167eca3ed43f0e480738a607381de from main. --- docs/ref/models/fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 745520961dc5..b552be50885e 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -2329,7 +2329,7 @@ called ``thirdpartyapp``, it can be referenced as:: class Car(models.Model): manufacturer = models.ForeignKey( - "thirdpartyapp``.Manufacturer", + "thirdpartyapp.Manufacturer", on_delete=models.CASCADE, ) From 0a1091f11835ad817522f1a19528a3d66d18c2e3 Mon Sep 17 00:00:00 2001 From: Maria Hynes Date: Sun, 27 Oct 2024 11:05:49 +0000 Subject: [PATCH 31/40] [5.1.x] Removed unneeded OS reference on running the test suite in contributing docs. This is not needed as the console snippet has buttons that allows the user to choose their OS. Backport of 163e72ebbaa84804877f3d1ae212575e479b533b from main. --- docs/intro/contributing.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 7d590e76a21b..0900fdae37e4 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -217,8 +217,7 @@ a dependency for one or more of the Python packages. Consult the failing package's documentation or search the web with the error message that you encounter. -Now we are ready to run the test suite. If you're using GNU/Linux, macOS, or -some other flavor of Unix, run: +Now we are ready to run the test suite: .. console:: From c5ddc8550c5834e8b1952445ebfb217563de128f Mon Sep 17 00:00:00 2001 From: aruseni Date: Sun, 27 Oct 2024 21:46:13 +0200 Subject: [PATCH 32/40] [5.1.x] Corrected note on importing fields in model field reference docs. Backport of d7f78eb5d6c9250789fb3975b01e2a71d0e39577 from main. --- docs/ref/models/fields.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index b552be50885e..2b1ce96dda5c 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -22,9 +22,9 @@ This document contains all the API references of :class:`Field` including the .. note:: - Technically, these models are defined in :mod:`django.db.models.fields`, but - for convenience they're imported into :mod:`django.db.models`; the standard - convention is to use ``from django.db import models`` and refer to fields as + Fields are defined in :mod:`django.db.models.fields`, but for convenience + they're imported into :mod:`django.db.models`. The standard convention is + to use ``from django.db import models`` and refer to fields as ``models.Field``. .. _common-model-field-options: From b57a8395b58ae47124efcc33455abc7d32553354 Mon Sep 17 00:00:00 2001 From: Tainara Palmeira Date: Mon, 28 Oct 2024 14:46:20 +0100 Subject: [PATCH 33/40] [5.1.x] Refs #35844 -- Expanded compatibility for expected error messages in command tests on Python 3.12 and 3.13. Updated CommandTests.test_subparser_invalid_option and CommandDBOptionChoiceTests.test_invalid_choice_db_option to use assertRaisesRegex() for compatibility with modified error messages in Python 3.12, 3.13, and 3.14+.. Backport of fc22fdd34f1e55adde161f5f2dca8db90bbfce80 from main. --- tests/admin_scripts/tests.py | 6 +++--- tests/user_commands/tests.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 2e77f2c97a62..6878da6f5854 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -2304,8 +2304,8 @@ def test_precedence(self): class CommandDBOptionChoiceTests(SimpleTestCase): def test_invalid_choice_db_option(self): expected_error = ( - "Error: argument --database: invalid choice: " - "'deflaut' (choose from 'default', 'other')" + r"Error: argument --database: invalid choice: 'deflaut' " + r"\(choose from '?default'?, '?other'?\)" ) args = [ "changepassword", @@ -2326,7 +2326,7 @@ def test_invalid_choice_db_option(self): ] for arg in args: - with self.assertRaisesMessage(CommandError, expected_error): + with self.assertRaisesRegex(CommandError, expected_error): call_command(arg, "--database", "deflaut", verbosity=0) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index 65e176620db1..2a1e904f3bda 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -400,8 +400,8 @@ def test_subparser_dest_required_args(self): self.assertIn("bar", out.getvalue()) def test_subparser_invalid_option(self): - msg = "invalid choice: 'test' (choose from 'foo')" - with self.assertRaisesMessage(CommandError, msg): + msg = r"invalid choice: 'test' \(choose from '?foo'?\)" + with self.assertRaisesRegex(CommandError, msg): management.call_command("subparser", "test", 12) msg = "Error: the following arguments are required: subcommand" with self.assertRaisesMessage(CommandError, msg): From 4915feaaf71f011146fc4e2c51e031ad4a80c00b Mon Sep 17 00:00:00 2001 From: antoliny0919 Date: Wed, 30 Oct 2024 07:17:55 +0900 Subject: [PATCH 34/40] [5.1.x] Fixed #35873 -- Corrected Form.as_table() call in form docs. Backport of 8f3dee1dfdc4242348c6cd6ead1c359cda78c2b5 from main. --- docs/ref/forms/api.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 15fc52f9b976..2315757eb75e 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -770,7 +770,7 @@ The template used by ``as_table()``. Default: ``'django/forms/table.html'``. >>> f = ContactForm() >>> f.as_table() '\n\n\n' - >>> print(f) + >>> print(f.as_table()) From ffc67aac1e14360e3c2f3e1de0c64fed289da74c Mon Sep 17 00:00:00 2001 From: Mike Edmunds Date: Mon, 28 Oct 2024 12:54:20 -0700 Subject: [PATCH 35/40] [5.1.x] Fixed #35864 -- Documented EmailMessage.connection is ignored when using send_messages(). Backport of 17c8ee7e3f7bf400128281b4fb283d7c209ca02b from main. --- docs/topics/email.txt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 75a50f40a142..109efe8ad364 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -311,9 +311,11 @@ All parameters are optional and can be set at any time prior to calling the * ``bcc``: A list or tuple of addresses used in the "Bcc" header when sending the email. -* ``connection``: An email backend instance. Use this parameter if - you want to use the same connection for multiple messages. If omitted, a - new connection is created when ``send()`` is called. +* ``connection``: An :ref:`email backend ` instance. Use + this parameter if you are sending the ``EmailMessage`` via ``send()`` and you + want to use the same connection for multiple messages. If omitted, a new + connection is created when ``send()`` is called. This parameter is ignored + when using :ref:`send_messages() `. * ``attachments``: A list of attachments to put on the message. These can be either :class:`~email.mime.base.MIMEBase` instances, or ``(filename, @@ -662,9 +664,10 @@ destroying a connection every time you want to send an email. There are two ways you tell an email backend to reuse a connection. -Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes -a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses), -and sends them all using a single connection. +Firstly, you can use the ``send_messages()`` method on a connection. This takes +a list of :class:`EmailMessage` (or subclass) instances, and sends them all +using that single connection. As a consequence, any :class:`connection +` set on an individual message is ignored. For example, if you have a function called ``get_notification_email()`` that returns a list of :class:`~django.core.mail.EmailMessage` objects representing From 5045dab4f93b64106b3132c3644c06982f824073 Mon Sep 17 00:00:00 2001 From: Johanan-Ayadata Date: Tue, 22 Oct 2024 22:20:55 +0000 Subject: [PATCH 36/40] [5.1.x] Added missing lang attributes to html elements in docs. Backport of 97a6a678c406b0049bd17bcd34f1d71d96141994 from main. --- docs/intro/overview.txt | 2 +- docs/ref/contrib/flatpages.txt | 2 +- docs/topics/http/views.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 0c41446d010c..af87a01bb478 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -309,7 +309,7 @@ Here's what the "base.html" template, including the use of :doc:`static files :caption: ``templates/base.html`` {% load static %} - + {% block title %}{% endblock %} diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index c82fb5de85c0..01e5553ff3cd 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -256,7 +256,7 @@ Here's a sample :file:`flatpages/default.html` template: .. code-block:: html+django - + {{ flatpage.title }} diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 2985bfb72b2c..feb4eaa4ecb7 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -23,7 +23,7 @@ Here's a view that returns the current date and time, as an HTML document:: def current_datetime(request): now = datetime.datetime.now() - html = "It is now %s." % now + html = 'It is now %s.' % now return HttpResponse(html) Let's step through this code one line at a time: @@ -225,7 +225,7 @@ Here's an example of an async view:: async def current_datetime(request): now = datetime.datetime.now() - html = "It is now %s." % now + html = 'It is now %s.' % now return HttpResponse(html) You can read more about Django's async support, and how to best use async From 9fa2d235c9b3ca6b0cd56e06456bf73d02814a8f Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:27:57 +0100 Subject: [PATCH 37/40] [5.1.x] Fixed #35876 -- Displayed non-ASCII fieldset names when rendering ModelAdmin.fieldsets. Thank you to Namhong Kim for the report, and to Mariusz Felisiak and Marijke Luttekes for the review. Regression in 01ed59f753139afb514170ee7f7384c155ecbc2d. Backport of 2c029c718f45341cdd43ee094c24488743c633e6 from main. --- .../admin/templates/admin/includes/fieldset.html | 10 ++++------ docs/releases/5.1.3.txt | 3 +++ tests/admin_inlines/tests.py | 4 ++-- tests/admin_views/admin.py | 1 + tests/admin_views/tests.py | 13 +++++++++++++ 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html index a9d3f927025e..c94d46fd6478 100644 --- a/django/contrib/admin/templates/admin/includes/fieldset.html +++ b/django/contrib/admin/templates/admin/includes/fieldset.html @@ -1,8 +1,7 @@ -{% with name=fieldset.name|default:""|slugify %} -
- {% if name %} +
+ {% if fieldset.name %} {% if fieldset.is_collapsible %}
{% endif %} - {{ fieldset.name }} + {{ fieldset.name }} {% if fieldset.is_collapsible %}{% endif %} {% endif %} {% if fieldset.description %} @@ -36,6 +35,5 @@ {% if not line.fields|length == 1 %}
{% endif %} {% endfor %} - {% if name and fieldset.is_collapsible %}{% endif %} + {% if fieldset.name and fieldset.is_collapsible %}{% endif %} -{% endwith %} diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt index 0dd5b42cb8d5..2ef34bfc8a95 100644 --- a/docs/releases/5.1.3.txt +++ b/docs/releases/5.1.3.txt @@ -17,3 +17,6 @@ Bugfixes * Fixed a regression in Django 5.1 that prevented the use of DB-IP databases with :class:`~django.contrib.gis.geoip2.GeoIP2` (:ticket:`35841`). + +* Fixed a regression in Django 5.1 where non-ASCII fieldset names were not + displayed when rendering admin fieldsets (:ticket:`35876`). diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 2c148a49f0ec..4959afb02d3f 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1785,7 +1785,7 @@ def test_inline_headings(self): # The second and third have the same "Advanced options" name, but the # second one has the "collapse" class. for x, classes in ((1, ""), (2, "collapse")): - heading_id = f"fieldset-0-advanced-options-{x}-heading" + heading_id = f"fieldset-0-{x}-heading" with self.subTest(heading_id=heading_id): self.assertContains( response, @@ -1830,7 +1830,7 @@ def test_inline_headings(self): # Every fieldset defined for an inline's form. for z, fieldset in enumerate(inline_admin_form): if fieldset.name: - heading_id = f"{prefix}-{y}-details-{z}-heading" + heading_id = f"{prefix}-{y}-{z}-heading" self.assertContains( response, f'
' ) + self.assertContains( + response, + '

Some fields

', + ) + self.assertContains( + response, + '

' + "Some other fields

", + ) + self.assertContains( + response, + '

이름

', + ) post = self.client.post( reverse("admin:admin_views_article_add"), add_dict, follow=False ) From 4ae358122b0f5173b08656449ce3eb1e1e57912b Mon Sep 17 00:00:00 2001 From: antoliny0919 Date: Mon, 4 Nov 2024 09:10:58 +0100 Subject: [PATCH 38/40] [5.1.x] Made minor edits to form fields docs. Backport of 4fcbdb11b114bc4d2dc50663f8053de2f18c0770 from main. --- docs/ref/forms/fields.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 3ee33c966109..506947c31d7b 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -112,7 +112,7 @@ validation may not be correct when adding and deleting formsets. The ``label`` argument lets you specify the "human-friendly" label for this field. This is used when the ``Field`` is displayed in a ``Form``. -As explained in "Outputting forms as HTML" above, the default label for a +As explained in :ref:`ref-forms-api-outputting-html`, the default label for a ``Field`` is generated from the field name by converting all underscores to spaces and upper-casing the first letter. Specify ``label`` if that default behavior doesn't result in an adequate label. @@ -226,7 +226,7 @@ validation if a particular field's value is not given. ``initial`` values are >>> f = CommentForm(data) >>> f.is_valid() False - # The form does *not* fall back to using the initial values. + # The form does *not* fallback to using the initial values. >>> f.errors {'url': ['This field is required.'], 'name': ['This field is required.']} @@ -379,7 +379,7 @@ See the :doc:`validators documentation ` for more information. The ``localize`` argument enables the localization of form data input, as well as the rendered output. -See the :doc:`format localization ` documentation for +See the :doc:`format localization documentation ` for more information. ``disabled`` From e3984ca5d12d92610e0a31287d5c497b266dceb7 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 5 Nov 2024 05:55:58 +0100 Subject: [PATCH 39/40] [5.1.x] Added release date for 5.1.3. Backport of ecd81ac8b786ac6f4e8a5626e0d029bcb11064a5 from main --- docs/releases/5.1.3.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/5.1.3.txt b/docs/releases/5.1.3.txt index 2ef34bfc8a95..9e251f221f7c 100644 --- a/docs/releases/5.1.3.txt +++ b/docs/releases/5.1.3.txt @@ -2,7 +2,7 @@ Django 5.1.3 release notes ========================== -*Expected November 5, 2024* +*November 5, 2024* Django 5.1.3 fixes several bugs in 5.1.2 and adds compatibility with Python 3.13. From 69bf08e3a32492998871eb91ad84b3c8d8117180 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 5 Nov 2024 06:02:35 +0100 Subject: [PATCH 40/40] [5.1.x] Bumped version for 5.1.3 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 9ec8643915ce..e3f6bb49533d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (5, 1, 3, "alpha", 0) +VERSION = (5, 1, 3, "final", 0) __version__ = get_version(VERSION)