diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f61692ef7964..46c2cf870724 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -21,8 +21,7 @@ permissions: jobs: docs: - # OS must be the same as on djangoproject.com. - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 name: docs steps: - name: Checkout diff --git a/.readthedocs.yml b/.readthedocs.yml index bde8b64da0f0..915d51de46f9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,12 +4,13 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.8" + python: "3.12" sphinx: configuration: docs/conf.py + fail_on_warning: true python: install: diff --git a/django/__init__.py b/django/__init__.py index e3f6bb49533d..543877d59f41 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (5, 1, 3, "final", 0) +VERSION = (5, 1, 4, "final", 0) __version__ = get_version(VERSION) diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index c40f2aa69dd2..a8639cb25858 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -115,10 +115,12 @@ def get_system_username(): """ try: result = getpass.getuser() - except (ImportError, KeyError): - # KeyError will be raised by os.getpwuid() (called by getuser()) - # if there is no corresponding entry in the /etc/passwd file - # (a very restricted chroot environment, for example). + except (ImportError, KeyError, OSError): + # TODO: Drop ImportError and KeyError when dropping support for PY312. + # KeyError (Python <3.13) or OSError (Python 3.13+) will be raised by + # os.getpwuid() (called by getuser()) if there is no corresponding + # entry in the /etc/passwd file (for example, in a very restricted + # chroot environment). return "" return result diff --git a/django/db/models/base.py b/django/db/models/base.py index 9bc39aeb227a..a866bc02ad6a 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -717,12 +717,13 @@ def refresh_from_db(self, using=None, fields=None, from_queryset=None): if fields is not None: db_instance_qs = db_instance_qs.only(*fields) elif deferred_fields: - fields = { - f.attname - for f in self._meta.concrete_fields - if f.attname not in deferred_fields - } - db_instance_qs = db_instance_qs.only(*fields) + db_instance_qs = db_instance_qs.only( + *{ + f.attname + for f in self._meta.concrete_fields + if f.attname not in deferred_fields + } + ) db_instance = db_instance_qs.get() non_loaded_fields = db_instance.get_deferred_fields() @@ -739,9 +740,9 @@ def refresh_from_db(self, using=None, fields=None, from_queryset=None): field.delete_cached_value(self) # Clear cached relations. - for field in self._meta.related_objects: - if (fields is None or field.name in fields) and field.is_cached(self): - field.delete_cached_value(self) + for rel in self._meta.related_objects: + if (fields is None or rel.name in fields) and rel.is_cached(self): + rel.delete_cached_value(self) # Clear cached private relations. for field in self._meta.private_fields: diff --git a/django/db/models/fields/json.py b/django/db/models/fields/json.py index 1b219e620c9a..608da6036f87 100644 --- a/django/db/models/fields/json.py +++ b/django/db/models/fields/json.py @@ -193,20 +193,18 @@ def compile_json_path_final_key(self, key_transform): # Compile the final key without interpreting ints as array elements. return ".%s" % json.dumps(key_transform) - def as_sql(self, compiler, connection, template=None): + def _as_sql_parts(self, compiler, connection): # Process JSON path from the left-hand side. if isinstance(self.lhs, KeyTransform): - lhs, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs( + lhs_sql, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs( compiler, connection ) lhs_json_path = compile_json_path(lhs_key_transforms) else: - lhs, lhs_params = self.process_lhs(compiler, connection) + lhs_sql, lhs_params = self.process_lhs(compiler, connection) lhs_json_path = "$" - sql = template % lhs # Process JSON path from the right-hand side. rhs = self.rhs - rhs_params = [] if not isinstance(rhs, (list, tuple)): rhs = [rhs] for key in rhs: @@ -217,24 +215,43 @@ def as_sql(self, compiler, connection, template=None): *rhs_key_transforms, final_key = rhs_key_transforms rhs_json_path = compile_json_path(rhs_key_transforms, include_root=False) rhs_json_path += self.compile_json_path_final_key(final_key) - rhs_params.append(lhs_json_path + rhs_json_path) + yield lhs_sql, lhs_params, lhs_json_path + rhs_json_path + + def _combine_sql_parts(self, parts): # Add condition for each key. if self.logical_operator: - sql = "(%s)" % self.logical_operator.join([sql] * len(rhs_params)) - return sql, tuple(lhs_params) + tuple(rhs_params) + return "(%s)" % self.logical_operator.join(parts) + return "".join(parts) + + def as_sql(self, compiler, connection, template=None): + sql_parts = [] + params = [] + for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts( + compiler, connection + ): + sql_parts.append(template % (lhs_sql, "%s")) + params.extend(lhs_params + [rhs_json_path]) + return self._combine_sql_parts(sql_parts), tuple(params) def as_mysql(self, compiler, connection): return self.as_sql( - compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %%s)" + compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %s)" ) def as_oracle(self, compiler, connection): - sql, params = self.as_sql( - compiler, connection, template="JSON_EXISTS(%s, '%%s')" - ) - # Add paths directly into SQL because path expressions cannot be passed - # as bind variables on Oracle. - return sql % tuple(params), [] + template = "JSON_EXISTS(%s, '%s')" + sql_parts = [] + params = [] + for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts( + compiler, connection + ): + # Add right-hand-side directly into SQL because it cannot be passed + # as bind variables to JSON_EXISTS. It might result in invalid + # queries but it is assumed that it cannot be evaded because the + # path is JSON serialized. + sql_parts.append(template % (lhs_sql, rhs_json_path)) + params.extend(lhs_params) + return self._combine_sql_parts(sql_parts), tuple(params) def as_postgresql(self, compiler, connection): if isinstance(self.rhs, KeyTransform): @@ -246,7 +263,7 @@ def as_postgresql(self, compiler, connection): def as_sqlite(self, compiler, connection): return self.as_sql( - compiler, connection, template="JSON_TYPE(%s, %%s) IS NOT NULL" + compiler, connection, template="JSON_TYPE(%s, %s) IS NOT NULL" ) @@ -455,9 +472,9 @@ def as_oracle(self, compiler, connection): return "(NOT %s OR %s IS NULL)" % (sql, lhs), tuple(params) + tuple(lhs_params) def as_sqlite(self, compiler, connection): - template = "JSON_TYPE(%s, %%s) IS NULL" + template = "JSON_TYPE(%s, %s) IS NULL" if not self.rhs: - template = "JSON_TYPE(%s, %%s) IS NOT NULL" + template = "JSON_TYPE(%s, %s) IS NOT NULL" return HasKeyOrArrayIndex(self.lhs.lhs, self.lhs.key_name).as_sql( compiler, connection, diff --git a/django/utils/html.py b/django/utils/html.py index d9513fc75848..ff8684f5a974 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -7,6 +7,7 @@ from html.parser import HTMLParser from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit +from django.core.exceptions import SuspiciousOperation from django.utils.deprecation import RemovedInDjango60Warning from django.utils.encoding import punycode from django.utils.functional import Promise, cached_property, keep_lazy, keep_lazy_text @@ -39,6 +40,7 @@ ) MAX_URL_LENGTH = 2048 +MAX_STRIP_TAGS_DEPTH = 50 @keep_lazy(SafeString) @@ -205,15 +207,19 @@ def _strip_once(value): @keep_lazy_text def strip_tags(value): """Return the given HTML with all tags stripped.""" - # Note: in typical case this loop executes _strip_once once. Loop condition - # is redundant, but helps to reduce number of executions of _strip_once. value = str(value) + # Note: in typical case this loop executes _strip_once twice (the second + # execution does not remove any more tags). + strip_tags_depth = 0 while "<" in value and ">" in value: + if strip_tags_depth >= MAX_STRIP_TAGS_DEPTH: + raise SuspiciousOperation new_value = _strip_once(value) if value.count("<") == new_value.count("<"): # _strip_once wasn't able to detect more tags. break value = new_value + strip_tags_depth += 1 return value diff --git a/docs/faq/contributing.txt b/docs/faq/contributing.txt index 71a6a7a47688..d281ce8b7574 100644 --- a/docs/faq/contributing.txt +++ b/docs/faq/contributing.txt @@ -53,8 +53,8 @@ To determine the right time, you need to keep an eye on the schedule. If you post your message right before a release deadline, you're not likely to get the sort of attention you require. -Gentle IRC reminders can also work -- again, strategically timed if possible. -During a bug sprint would be a very good time, for example. +Gentle reminders in the ``#contributing-getting-started`` channel in the +`Django Discord server`_ can work. Another way to get traction is to pull several related tickets together. When someone sits down to review a bug in an area they haven't touched for @@ -68,6 +68,8 @@ issue over and over again. This sort of behavior will not gain you any additional attention -- certainly not the attention that you need in order to get your issue addressed. +.. _`Django Discord server`: https://discord.gg/xcRH6mN4fa + But I've reminded you several times and you keep ignoring my contribution! ========================================================================== diff --git a/docs/howto/static-files/deployment.txt b/docs/howto/static-files/deployment.txt index d6d1158249d4..19b7c9df826a 100644 --- a/docs/howto/static-files/deployment.txt +++ b/docs/howto/static-files/deployment.txt @@ -15,7 +15,7 @@ Serving static files in production The basic outline of putting static files into production consists of two steps: run the :djadmin:`collectstatic` command when static files change, then arrange for the collected static files directory (:setting:`STATIC_ROOT`) to be -moved to the static file server and served. Depending the ``staticfiles`` +moved to the static file server and served. Depending on the ``staticfiles`` :setting:`STORAGES` alias, files may need to be moved to a new location manually or the :func:`post_process ` method of diff --git a/docs/internals/contributing/index.txt b/docs/internals/contributing/index.txt index 6e3fd948ee26..b547e468b713 100644 --- a/docs/internals/contributing/index.txt +++ b/docs/internals/contributing/index.txt @@ -46,7 +46,6 @@ a great ecosystem to work in: .. _posting guidelines: https://code.djangoproject.com/wiki/UsingTheMailingList .. _#django IRC channel: https://web.libera.chat/#django -.. _#django-dev IRC channel: https://web.libera.chat/#django-dev .. _community page: https://www.djangoproject.com/community/ .. _Django Discord server: https://discord.gg/xcRH6mN4fa .. _Django forum: https://forum.djangoproject.com/ diff --git a/docs/internals/contributing/new-contributors.txt b/docs/internals/contributing/new-contributors.txt index 8e81031b3247..201fe4afc2aa 100644 --- a/docs/internals/contributing/new-contributors.txt +++ b/docs/internals/contributing/new-contributors.txt @@ -21,53 +21,55 @@ First steps Start with these steps to discover Django's development process. -* **Triage tickets** +Triage tickets +-------------- - If an `unreviewed ticket`_ reports a bug, try and reproduce it. If you - can reproduce it and it seems valid, make a note that you confirmed the bug - and accept the ticket. Make sure the ticket is filed under the correct - component area. Consider writing a patch that adds a test for the bug's - behavior, even if you don't fix the bug itself. See more at - :ref:`how-can-i-help-with-triaging` +If an `unreviewed ticket`_ reports a bug, try and reproduce it. If you can +reproduce it and it seems valid, make a note that you confirmed the bug and +accept the ticket. Make sure the ticket is filed under the correct component +area. Consider writing a patch that adds a test for the bug's behavior, even if +you don't fix the bug itself. See more at :ref:`how-can-i-help-with-triaging` -* **Look for tickets that are accepted and review patches to build familiarity - with the codebase and the process** +Review patches of accepted tickets +---------------------------------- - Mark the appropriate flags if a patch needs docs or tests. Look through the - changes a patch makes, and keep an eye out for syntax that is incompatible - with older but still supported versions of Python. :doc:`Run the tests - ` and make sure they pass. - Where possible and relevant, try them out on a database other than SQLite. - Leave comments and feedback! +This will help you build familiarity with the codebase and processes. Mark the +appropriate flags if a patch needs docs or tests. Look through the changes a +patch makes, and keep an eye out for syntax that is incompatible with older but +still supported versions of Python. :doc:`Run the tests +` and make sure they pass. +Where possible and relevant, try them out on a database other than SQLite. +Leave comments and feedback! -* **Keep old patches up to date** +Keep old patches up-to-date +--------------------------- - Oftentimes the codebase will change between a patch being submitted and the - time it gets reviewed. Make sure it still applies cleanly and functions as - expected. Updating a patch is both useful and important! See more on - :doc:`writing-code/submitting-patches`. +Oftentimes the codebase will change between a patch being submitted and the +time it gets reviewed. Make sure it still applies cleanly and functions as +expected. Updating a patch is both useful and important! See more on +:doc:`writing-code/submitting-patches`. -* **Write some documentation** +Write some documentation +------------------------ - Django's documentation is great but it can always be improved. Did you find - a typo? Do you think that something should be clarified? Go ahead and - suggest a documentation patch! See also the guide on - :doc:`writing-documentation`. +Django's documentation is great but it can always be improved. Did you find a +typo? Do you think that something should be clarified? Go ahead and suggest a +documentation patch! See also the guide on :doc:`writing-documentation`. - .. note:: +.. note:: - The `reports page`_ contains links to many useful Trac queries, including - several that are useful for triaging tickets and reviewing patches as - suggested above. + The `reports page`_ contains links to many useful Trac queries, including + several that are useful for triaging tickets and reviewing patches as + suggested above. - .. _reports page: https://code.djangoproject.com/wiki/Reports + .. _reports page: https://code.djangoproject.com/wiki/Reports -* **Sign the Contributor License Agreement** +Sign the Contributor License Agreement +-------------------------------------- - The code that you write belongs to you or your employer. If your - contribution is more than one or two lines of code, you need to sign the - `CLA`_. See the `Contributor License Agreement FAQ`_ for a more thorough - explanation. +The code that you write belongs to you or your employer. If your contribution +is more than one or two lines of code, you need to sign the `CLA`_. See the +`Contributor License Agreement FAQ`_ for a more thorough explanation. .. _CLA: https://www.djangoproject.com/foundation/cla/ .. _Contributor License Agreement FAQ: https://www.djangoproject.com/foundation/cla/faq/ @@ -80,78 +82,89 @@ Guidelines As a newcomer on a large project, it's easy to experience frustration. Here's some advice to make your work on Django more useful and rewarding. -* **Pick a subject area that you care about, that you are familiar with, or - that you want to learn about** +Pick a subject area +------------------- - You don't already have to be an expert on the area you want to work on; you - become an expert through your ongoing contributions to the code. +This should be something that you care about, that you are familiar with or +that you want to learn about. You don't already have to be an expert on the +area you want to work on; you become an expert through your ongoing +contributions to the code. -* **Analyze tickets' context and history** +Analyze tickets' context and history +------------------------------------ - Trac isn't an absolute; the context is just as important as the words. - When reading Trac, you need to take into account who says things, and when - they were said. Support for an idea two years ago doesn't necessarily mean - that the idea will still have support. You also need to pay attention to who - *hasn't* spoken -- for example, if an experienced contributor hasn't been - recently involved in a discussion, then a ticket may not have the support - required to get into Django. +Trac isn't an absolute; the context is just as important as the words. When +reading Trac, you need to take into account who says things, and when they were +said. Support for an idea two years ago doesn't necessarily mean that the idea +will still have support. You also need to pay attention to who *hasn't* spoken +-- for example, if an experienced contributor hasn't been recently involved in +a discussion, then a ticket may not have the support required to get into +Django. -* **Start small** +Start small +----------- - It's easier to get feedback on a little issue than on a big one. See the - `easy pickings`_. +It's easier to get feedback on a little issue than on a big one. See the +`easy pickings`_. -* **If you're going to engage in a big task, make sure that your idea has - support first** +Confirm support before engaging in a big task +--------------------------------------------- - This means getting someone else to confirm that a bug is real before you fix - the issue, and ensuring that there's consensus on a proposed feature before - you go implementing it. +This means getting someone else to confirm that a bug is real before you fix +the issue, and ensuring that there's consensus on a proposed feature before you +go implementing it. -* **Be bold! Leave feedback!** +Be bold! Leave feedback! +------------------------ - Sometimes it can be scary to put your opinion out to the world and say "this - ticket is correct" or "this patch needs work", but it's the only way the - project moves forward. The contributions of the broad Django community - ultimately have a much greater impact than that of any one person. We can't - do it without **you**! +Sometimes it can be scary to put your opinion out to the world and say "this +ticket is correct" or "this patch needs work", but it's the only way the +project moves forward. The contributions of the broad Django community +ultimately have a much greater impact than that of any one person. We can't do +it without **you**! -* **Err on the side of caution when marking things Ready For Check-in** +Be cautious when marking things "Ready For Check-in" +---------------------------------------------------- - If you're really not certain if a ticket is ready, don't mark it as - such. Leave a comment instead, letting others know your thoughts. If you're - mostly certain, but not completely certain, you might also try asking on IRC - to see if someone else can confirm your suspicions. +If you're really not certain if a ticket is ready, don't mark it as such. Leave +a comment instead, letting others know your thoughts. If you're mostly certain, +but not completely certain, you might also try asking on the +``#contributing-getting-started`` channel in the `Django Discord server`_ to +see if someone else can confirm your suspicions. -* **Wait for feedback, and respond to feedback that you receive** +.. _`Django Discord server`: https://discord.gg/xcRH6mN4fa - Focus on one or two tickets, see them through from start to finish, and - repeat. The shotgun approach of taking on lots of tickets and letting some - fall by the wayside ends up doing more harm than good. +Wait for feedback, and respond to feedback that you receive +----------------------------------------------------------- -* **Be rigorous** +Focus on one or two tickets, see them through from start to finish, and repeat. +The shotgun approach of taking on lots of tickets and letting some fall by the +wayside ends up doing more harm than good. - When we say ":pep:`8`, and must have docs and tests", we mean it. If a patch - doesn't have docs and tests, there had better be a good reason. Arguments - like "I couldn't find any existing tests of this feature" don't carry much - weight--while it may be true, that means you have the extra-important job of - writing the very first tests for that feature, not that you get a pass from - writing tests altogether. +Be rigorous +----------- -* **Be patient** +When we say ":pep:`8`, and must have docs and tests", we mean it. If a patch +doesn't have docs and tests, there had better be a good reason. Arguments like +"I couldn't find any existing tests of this feature" don't carry much weight. +While it may be true, that means you have the extra-important job of writing +the very first tests for that feature, not that you get a pass from writing +tests altogether. - It's not always easy for your ticket or your patch to be reviewed quickly. - This isn't personal. There are a lot of tickets and pull requests to get - through. +Be patient +---------- - Keeping your patch up to date is important. Review the ticket on Trac to - ensure that the *Needs tests*, *Needs documentation*, and *Patch needs - improvement* flags are unchecked once you've addressed all review comments. +It's not always easy for your ticket or your patch to be reviewed quickly. This +isn't personal. There are a lot of tickets and pull requests to get through. - Remember that Django has an eight-month release cycle, so there's plenty of - time for your patch to be reviewed. +Keeping your patch up to date is important. Review the ticket on Trac to ensure +that the *Needs tests*, *Needs documentation*, and *Patch needs improvement* +flags are unchecked once you've addressed all review comments. - Finally, a well-timed reminder can help. See :ref:`contributing code FAQ - ` for ideas here. +Remember that Django has an eight-month release cycle, so there's plenty of +time for your patch to be reviewed. + +Finally, a well-timed reminder can help. See :ref:`contributing code FAQ +` for ideas here. .. _easy pickings: https://code.djangoproject.com/query?status=!closed&easy=1 diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index a002051bbb63..c2081a0e0a66 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -45,10 +45,14 @@ and time availability), claim it by following these steps: any activity, it's probably safe to reassign it to yourself. * Log into your account, if you haven't already, by clicking "GitHub Login" - or "DjangoProject Login" in the upper left of the ticket page. + or "DjangoProject Login" in the upper left of the ticket page. Once logged + in, you can then click the "Modify Ticket" button near the bottom of the + page. -* Claim the ticket by clicking the "assign to myself" radio button under - "Action" near the bottom of the page, then click "Submit changes." +* Claim the ticket by clicking the "assign to" radio button in the "Action" + section. Your username will be filled in the text box by default. + +* Finally click the "Submit changes" button at the bottom to save. .. note:: The Django software foundation requests that anyone contributing more than diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 3641bfb8cc53..76f4a9e7542d 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -322,7 +322,6 @@ dependencies: * :pypi:`numpy` * :pypi:`Pillow` 6.2.1+ * :pypi:`PyYAML` -* :pypi:`pytz` (required) * :pypi:`pywatchman` * :pypi:`redis` 3.4+ * :pypi:`setuptools` diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index c0a8ab8ab14e..131c60fec8c5 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -624,9 +624,9 @@ need to be done by the releaser. message, add a "refs #XXXX" to the original ticket where the deprecation began if possible. -#. Remove ``.. versionadded::``, ``.. versionadded::``, and ``.. deprecated::`` - annotations in the documentation from two releases ago. For example, in - Django 4.2, notes for 4.0 will be removed. +#. Remove ``.. versionadded::``, ``.. versionchanged::``, and + ``.. deprecated::`` annotations in the documentation from two releases ago. + For example, in Django 4.2, notes for 4.0 will be removed. #. Add the new branch to `Read the Docs `_. Since the automatically diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index b840eb266025..00d1627c24d8 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -30,9 +30,6 @@ of this page, or update Django to the newest version. If you're using an older version of Python, check :ref:`faq-python-version-support` to find a compatible version of Django. -See :doc:`How to install Django ` for advice on how to remove -older versions of Django and install a newer one. - .. admonition:: Where to get help: If you're having trouble going through this tutorial, please head over to diff --git a/docs/intro/tutorial08.txt b/docs/intro/tutorial08.txt index 463db3221ea3..98bf70d330bd 100644 --- a/docs/intro/tutorial08.txt +++ b/docs/intro/tutorial08.txt @@ -22,10 +22,11 @@ Installing Django Debug Toolbar =============================== Django Debug Toolbar is a useful tool for debugging Django web applications. -It's a third-party package maintained by the `Jazzband -`_ organization. The toolbar helps you understand how your -application functions and to identify problems. It does so by providing panels -that provide debug information about the current request and response. +It's a third-party package that is maintained by the community organization +`Django Commons `_. The toolbar helps you +understand how your application functions and to identify problems. It does so +by providing panels that provide debug information about the current request +and response. To install a third-party application like the toolbar, you need to install the package by running the below command within an activated virtual @@ -67,7 +68,7 @@ resolve the issue yourself, there are options available to you. `_ that outlines troubleshooting options. #. Search for similar issues on the package's issue tracker. Django Debug - Toolbar’s is `on GitHub `_. + Toolbar’s is `on GitHub `_. #. Consult the `Django Forum `_. #. Join the `Django Discord server `_. #. Join the #Django IRC channel on `Libera.chat `_. diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 173e51979ccd..0900bebed8ae 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -34,8 +34,7 @@ features include: may be used outside of a Django project/application. In other words, no need to have :envvar:`DJANGO_SETTINGS_MODULE` set or use a database, etc. * Mutability: :class:`GEOSGeometry` objects may be modified. -* Cross-platform and tested; compatible with Windows, Linux, Solaris, and - macOS platforms. +* Cross-platform tested. .. _geos-tutorial: diff --git a/docs/ref/contrib/postgres/indexes.txt b/docs/ref/contrib/postgres/indexes.txt index 73ef195309bb..107d9c278d43 100644 --- a/docs/ref/contrib/postgres/indexes.txt +++ b/docs/ref/contrib/postgres/indexes.txt @@ -34,14 +34,14 @@ available from the ``django.contrib.postgres.indexes`` module. .. class:: BrinIndex(*expressions, autosummarize=None, pages_per_range=None, **options) Creates a `BRIN index - `_. + `_. Set the ``autosummarize`` parameter to ``True`` to enable `automatic summarization`_ to be performed by autovacuum. The ``pages_per_range`` argument takes a positive integer. - .. _automatic summarization: https://www.postgresql.org/docs/current/brin-intro.html#BRIN-OPERATION + .. _automatic summarization: https://www.postgresql.org/docs/current/brin.html#BRIN-OPERATION ``BTreeIndex`` ============== diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 506947c31d7b..21ff2ada66bb 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -65,8 +65,6 @@ an empty value -- either ``None`` or the empty string (``""``) -- then Traceback (most recent call last): ... ValidationError: ['This field is required.'] - >>> f.clean(" ") - ' ' >>> f.clean(0) '0' >>> f.clean(True) diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 7a037eaf756c..614b345b5a43 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -254,7 +254,7 @@ Common cases such as validating against an email or a regular expression can be handled using existing validator classes available in Django. For example, ``validators.validate_slug`` is an instance of a :class:`~django.core.validators.RegexValidator` constructed with the first -argument being the pattern: ``^[-a-zA-Z0-9_]+$``. See the section on +argument being the pattern: ``^[-a-zA-Z0-9_]+\Z``. See the section on :doc:`writing validators ` to see a list of what is already available and for an example of how to write a validator. diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index f76759b25454..807cee8f8ae2 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -142,9 +142,9 @@ For example, take the following form:: url = forms.URLField() comment = forms.CharField() -This form will include three default :class:`TextInput` widgets, with default -rendering -- no CSS class, no extra attributes. This means that the input boxes -provided for each widget will be rendered exactly the same: +This form will include :class:`TextInput` widgets for the name and comment +fields, and a :class:`URLInput` widget for the url field. Each has default +rendering - no CSS class, no extra attributes: .. code-block:: pycon @@ -154,11 +154,11 @@ provided for each widget will be rendered exactly the same:
Url:
Comment:
-On a real web page, you probably don't want every widget to look the same. You -might want a larger input element for the comment, and you might want the -'name' widget to have some special CSS class. It is also possible to specify -the 'type' attribute to take advantage of the new HTML5 input types. To do -this, you use the :attr:`Widget.attrs` argument when creating the widget:: +On a real web page, you probably want to customize this. You might want a +larger input element for the comment, and you might want the 'name' widget to +have some special CSS class. It is also possible to specify the 'type' +attribute to use a different HTML5 input type. To do this, you use the +:attr:`Widget.attrs` argument when creating the widget:: class CommentForm(forms.Form): name = forms.CharField(widget=forms.TextInput(attrs={"class": "special"})) diff --git a/docs/releases/4.2.17.txt b/docs/releases/4.2.17.txt new file mode 100644 index 000000000000..9a6aee3db6ef --- /dev/null +++ b/docs/releases/4.2.17.txt @@ -0,0 +1,33 @@ +=========================== +Django 4.2.17 release notes +=========================== + +*December 4, 2024* + +Django 4.2.17 fixes one security issue with severity "high" and one security +issue with severity "moderate" in 4.2.16. + +CVE-2024-53907: Denial-of-service possibility in ``strip_tags()`` +================================================================= + +:func:`~django.utils.html.strip_tags` would be extremely slow to evaluate +certain inputs containing large sequences of nested incomplete HTML entities. +The ``strip_tags()`` method is used to implement the corresponding +:tfilter:`striptags` template filter, which was thus also vulnerable. + +``strip_tags()`` now has an upper limit of recursive calls to ``HTMLParser`` +before raising a :exc:`.SuspiciousOperation` exception. + +Remember that absolutely NO guarantee is provided about the results of +``strip_tags()`` being HTML safe. So NEVER mark safe the result of a +``strip_tags()`` call without escaping it first, for example with +:func:`django.utils.html.escape`. + +CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle +========================================================================== + +Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle +was subject to SQL injection if untrusted data was used as a ``lhs`` value. + +Applications that use the :lookup:`has_key ` lookup through +the ``__`` syntax are unaffected. diff --git a/docs/releases/5.0.10.txt b/docs/releases/5.0.10.txt new file mode 100644 index 000000000000..ae1fbf99e40a --- /dev/null +++ b/docs/releases/5.0.10.txt @@ -0,0 +1,33 @@ +=========================== +Django 5.0.10 release notes +=========================== + +*December 4, 2024* + +Django 5.0.10 fixes one security issue with severity "high" and one security +issue with severity "moderate" in 5.0.9. + +CVE-2024-53907: Denial-of-service possibility in ``strip_tags()`` +================================================================= + +:func:`~django.utils.html.strip_tags` would be extremely slow to evaluate +certain inputs containing large sequences of nested incomplete HTML entities. +The ``strip_tags()`` method is used to implement the corresponding +:tfilter:`striptags` template filter, which was thus also vulnerable. + +``strip_tags()`` now has an upper limit of recursive calls to ``HTMLParser`` +before raising a :exc:`.SuspiciousOperation` exception. + +Remember that absolutely NO guarantee is provided about the results of +``strip_tags()`` being HTML safe. So NEVER mark safe the result of a +``strip_tags()`` call without escaping it first, for example with +:func:`django.utils.html.escape`. + +CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle +========================================================================== + +Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle +was subject to SQL injection if untrusted data was used as a ``lhs`` value. + +Applications that use the :lookup:`has_key ` lookup through +the ``__`` syntax are unaffected. diff --git a/docs/releases/5.1.4.txt b/docs/releases/5.1.4.txt new file mode 100644 index 000000000000..e7687256887d --- /dev/null +++ b/docs/releases/5.1.4.txt @@ -0,0 +1,43 @@ +========================== +Django 5.1.4 release notes +========================== + +*December 4, 2024* + +Django 5.1.4 fixes one security issue with severity "high", one security issue +with severity "moderate", and several bugs in 5.1.3. + +CVE-2024-53907: Denial-of-service possibility in ``strip_tags()`` +================================================================= + +:func:`~django.utils.html.strip_tags` would be extremely slow to evaluate +certain inputs containing large sequences of nested incomplete HTML entities. +The ``strip_tags()`` method is used to implement the corresponding +:tfilter:`striptags` template filter, which was thus also vulnerable. + +``strip_tags()`` now has an upper limit of recursive calls to ``HTMLParser`` +before raising a :exc:`.SuspiciousOperation` exception. + +Remember that absolutely NO guarantee is provided about the results of +``strip_tags()`` being HTML safe. So NEVER mark safe the result of a +``strip_tags()`` call without escaping it first, for example with +:func:`django.utils.html.escape`. + +CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle +========================================================================== + +Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle +was subject to SQL injection if untrusted data was used as a ``lhs`` value. + +Applications that use the :lookup:`has_key ` lookup through +the ``__`` syntax are unaffected. + +Bugfixes +======== + +* Fixed a crash in ``createsuperuser`` on Python 3.13+ caused by an unhandled + ``OSError`` when the username could not be determined (:ticket:`35942`). + +* Fixed a regression in Django 5.1 where relational fields were not updated + when calling ``Model.refresh_from_db()`` on instances with deferred fields + (:ticket:`35950`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 06c1461ab7b2..81c2bb69aad8 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.4 5.1.3 5.1.2 5.1.1 @@ -35,6 +36,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 5.0.10 5.0.9 5.0.8 5.0.7 @@ -52,6 +54,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 4.2.17 4.2.16 4.2.15 4.2.14 diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index 3839c8608b5b..e9e4e78b7930 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -127,15 +127,19 @@ wasn't provided to :func:`~django.contrib.auth.authenticate` (which passes it on to the backend). The Django admin is tightly coupled to the Django :ref:`User object -`. The best way to deal with this is to create a Django ``User`` -object for each user that exists for your backend (e.g., in your LDAP -directory, your external SQL database, etc.) You can either write a script to -do this in advance, or your ``authenticate`` method can do it the first time a -user logs in. +`. For example, for a user to access the admin, +:attr:`.User.is_staff` and :attr:`.User.is_active` must be ``True`` (see +:meth:`.AdminSite.has_permission` for details). + +The best way to deal with this is to create a Django ``User`` object for each +user that exists for your backend (e.g., in your LDAP directory, your external +SQL database, etc.). You can either write a script to do this in advance, or +your ``authenticate`` method can do it the first time a user logs in. Here's an example backend that authenticates against a username and password variable defined in your ``settings.py`` file and creates a Django ``User`` -object the first time a user authenticates:: +object the first time a user authenticates. In this example, the created Django +``User`` object is a superuser who will have full access to the admin:: from django.conf import settings from django.contrib.auth.backends import BaseBackend @@ -162,7 +166,7 @@ object the first time a user authenticates:: except User.DoesNotExist: # Create a new user. There's no need to set a password # because only the password from settings.py is checked. - user = User(username=username) + user = User(username=username) # is_active defaults to True. user.is_staff = True user.is_superuser = True user.save() diff --git a/docs/topics/db/fixtures.txt b/docs/topics/db/fixtures.txt index ac5b34dae0d7..6066d34f8e66 100644 --- a/docs/topics/db/fixtures.txt +++ b/docs/topics/db/fixtures.txt @@ -4,28 +4,25 @@ Fixtures ======== -.. seealso:: - - * :doc:`/howto/initial-data` - -What is a fixture? -================== - A *fixture* is a collection of files that contain the serialized contents of the database. Each fixture has a unique name, and the files that comprise the fixture can be distributed over multiple directories, in multiple applications. -How to produce a fixture? -========================= +.. seealso:: + + * :doc:`/howto/initial-data` + +How to produce a fixture +======================== Fixtures can be generated by :djadmin:`manage.py dumpdata `. It's also possible to generate custom fixtures by directly using :doc:`serialization tools ` or even by handwriting them. -How to use a fixture? -===================== +How to use a fixture +==================== -Fixtures can be used to pre-populate database with data for +Fixtures can be used to pre-populate the database with data for :ref:`tests `: .. code-block:: python @@ -40,8 +37,8 @@ or to provide some :ref:`initial data ` using the django-admin loaddata -Where Django looks for fixtures? -================================ +How fixtures are discovered +=========================== Django will search in these locations for fixtures: @@ -116,8 +113,8 @@ example). .. _MySQL: https://dev.mysql.com/doc/refman/en/constraint-foreign-key.html -How fixtures are saved to the database? -======================================= +How fixtures are saved to the database +====================================== When fixture files are processed, the data is saved to the database as is. Model defined :meth:`~django.db.models.Model.save` methods are not called, and diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 1f49044e6e8c..14d4962eb671 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -48,13 +48,10 @@ following example will create a formset class to display two blank forms: >>> ArticleFormSet = formset_factory(ArticleForm, extra=2) -Iterating over a formset will render the forms in the order they were -created. You can change this order by providing an alternate implementation for -the ``__iter__()`` method. - -Formsets can also be indexed into, which returns the corresponding form. If you -override ``__iter__``, you will need to also override ``__getitem__`` to have -matching behavior. +Formsets can be iterated and indexed, accessing forms in the order they were +created. You can reorder the forms by overriding the default +:py:meth:`iteration ` and +:py:meth:`indexing ` behavior if needed. .. _formsets-initial-data: @@ -1008,10 +1005,11 @@ deal with the management form: The above ends up calling the :meth:`BaseFormSet.render` method on the formset class. This renders the formset using the template specified by the :attr:`~BaseFormSet.template_name` attribute. Similar to forms, by default the -formset will be rendered ``as_table``, with other helper methods of ``as_p`` -and ``as_ul`` being available. The rendering of the formset can be customized -by specifying the ``template_name`` attribute, or more generally by -:ref:`overriding the default template `. +formset will be rendered ``as_div``, with other helper methods of ``as_p``, +``as_ul``, and ``as_table`` being available. The rendering of the formset can +be customized by specifying the ``template_name`` attribute, or more generally +by :ref:`overriding the default template +`. .. _manually-rendered-can-delete-and-can-order: diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt index cedd824e300f..4e23d1b6bc21 100644 --- a/docs/topics/performance.txt +++ b/docs/topics/performance.txt @@ -137,7 +137,7 @@ one that it is comfortable to code for. Firstly, in a real-life case you need to consider what is happening before and after your count to work out what's an optimal way of doing it *in that - particular context*. The database optimization documents describes :ref:`a + particular context*. The database optimization document describes :ref:`a case where counting in the template would be better `. diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 5765c500346a..0ef85a7299c7 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -126,6 +126,13 @@ def tearDown(self): def test_actual_implementation(self): self.assertIsInstance(management.get_system_username(), str) + def test_getuser_raises_exception(self): + # TODO: Drop ImportError and KeyError when dropping support for PY312. + for exc in (ImportError, KeyError, OSError): + with self.subTest(exc=str(exc)): + with mock.patch("getpass.getuser", side_effect=exc): + self.assertEqual(management.get_system_username(), "") + def test_simple(self): management.get_system_username = lambda: "joe" self.assertEqual(management.get_default_username(), "joe") diff --git a/tests/contenttypes_tests/test_fields.py b/tests/contenttypes_tests/test_fields.py index ab16324fb681..fc49d59b2775 100644 --- a/tests/contenttypes_tests/test_fields.py +++ b/tests/contenttypes_tests/test_fields.py @@ -57,6 +57,15 @@ def test_clear_cached_generic_relation_explicit_fields(self): self.assertIsNot(answer.question, old_question_obj) self.assertEqual(answer.question, old_question_obj) + def test_clear_cached_generic_relation_when_deferred(self): + question = Question.objects.create(text="question") + Answer.objects.create(text="answer", question=question) + answer = Answer.objects.defer("text").get() + old_question_obj = answer.question + # The reverse relation is refreshed even when the text field is deferred. + answer.refresh_from_db() + self.assertIsNot(answer.question, old_question_obj) + class GenericRelationTests(TestCase): def test_value_to_string(self): diff --git a/tests/defer/tests.py b/tests/defer/tests.py index 3945b667bad5..989b5c63d788 100644 --- a/tests/defer/tests.py +++ b/tests/defer/tests.py @@ -290,6 +290,14 @@ def test_custom_refresh_on_deferred_loading(self): self.assertEqual(rf2.name, "new foo") self.assertEqual(rf2.value, "new bar") + def test_refresh_when_one_field_deferred(self): + s = Secondary.objects.create() + PrimaryOneToOne.objects.create(name="foo", value="bar", related=s) + s = Secondary.objects.defer("first").get() + p_before = s.primary_o2o + s.refresh_from_db() + self.assertIsNot(s.primary_o2o, p_before) + class InvalidDeferTests(SimpleTestCase): def test_invalid_defer(self): diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py index ff42b1a14c38..e517ef682675 100644 --- a/tests/model_fields/test_jsonfield.py +++ b/tests/model_fields/test_jsonfield.py @@ -29,6 +29,7 @@ from django.db.models.expressions import RawSQL from django.db.models.fields.json import ( KT, + HasKey, KeyTextTransform, KeyTransform, KeyTransformFactory, @@ -582,6 +583,14 @@ def test_has_key_deep(self): [expected], ) + def test_has_key_literal_lookup(self): + self.assertSequenceEqual( + NullableJSONModel.objects.filter( + HasKey(Value({"foo": "bar"}, JSONField()), "foo") + ).order_by("id"), + self.objs, + ) + def test_has_key_list(self): obj = NullableJSONModel.objects.create(value=[{"a": 1}, {"b": "x"}]) tests = [ diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 9bee483dc7ff..75873061de41 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -1,6 +1,7 @@ import os from datetime import datetime +from django.core.exceptions import SuspiciousOperation from django.core.serializers.json import DjangoJSONEncoder from django.test import SimpleTestCase from django.utils.deprecation import RemovedInDjango60Warning @@ -124,12 +125,18 @@ def test_strip_tags(self): ("&h", "alert()h"), (">br>br>br>X", "XX"), + ("<" * 50 + "a>" * 50, ""), ) for value, output in items: with self.subTest(value=value, output=output): self.check_output(strip_tags, value, output) self.check_output(strip_tags, lazystr(value), output) + def test_strip_tags_suspicious_operation(self): + value = "<" * 51 + "a>" * 51, "" + with self.assertRaises(SuspiciousOperation): + strip_tags(value) + def test_strip_tags_files(self): # Test with more lengthy content (also catching performance regressions) for filename in ("strip_tags1.html", "strip_tags2.txt"):