diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 910c489a377b..a9fe3cf01d2d 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -416,27 +416,17 @@ def _get_model_from_node(self, node, attr): def getInnerText(node): - """Get all the inner text of a DOM node (recursively).""" - inner_text_list = getInnerTextList(node) - return "".join(inner_text_list) - - -def getInnerTextList(node): - """Return a list of the inner texts of a DOM node (recursively).""" + """Get the inner text of a DOM node and any children one level deep.""" # inspired by # https://mail.python.org/pipermail/xml-sig/2005-March/011022.html - result = [] - for child in node.childNodes: - if ( - child.nodeType == child.TEXT_NODE - or child.nodeType == child.CDATA_SECTION_NODE - ): - result.append(child.data) - elif child.nodeType == child.ELEMENT_NODE: - result.extend(getInnerTextList(child)) - else: - pass - return result + return "".join( + [ + element.data + for child in node.childNodes + for element in (child, *child.childNodes) + if element.nodeType in (element.TEXT_NODE, element.CDATA_SECTION_NODE) + ] + ) # Below code based on Christian Heimes' defusedxml diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index c5f53c5e071b..533f460e4ac4 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -111,7 +111,7 @@ instead of :class:`django.contrib.auth.middleware.RemoteUserMiddleware`:: This warning doesn't apply to ``RemoteUserMiddleware`` in its default configuration with ``header = 'REMOTE_USER'``, since a key that doesn't start with ``HTTP_`` in ``request.META`` can only be set by your WSGI - server, not directly from an HTTP request header. + or ASGI server, not directly from an HTTP request header. If you need more control, you can create your own authentication backend that inherits from :class:`~django.contrib.auth.backends.RemoteUserBackend` and diff --git a/docs/internals/_images/contribution_process.pdf b/docs/internals/_images/contribution_process.pdf new file mode 100644 index 000000000000..19378575f0a6 Binary files /dev/null and b/docs/internals/_images/contribution_process.pdf differ diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 9a6b14ef4c15..961e4548325a 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -181,8 +181,16 @@ The ``ContentTypeManager`` :class:`~django.contrib.contenttypes.models.ContentType` to keep track of models for which it has created :class:`~django.contrib.contenttypes.models.ContentType` instances. You - probably won't ever need to call this method yourself; Django will call - it automatically when it's needed. + probably won't need to call this method in application code yourself; + Django will call it automatically when it's needed. + + You may need to clear the cache when testing, to reset between tests, + or after preparing test state. For example:: + + class ContentTypesTests(TestCase): + def setUp(self): + ContentType.objects.clear_cache() + self.addCleanup(ContentType.objects.clear_cache) .. method:: get_for_id(id) diff --git a/docs/ref/contrib/postgres/aggregates.txt b/docs/ref/contrib/postgres/aggregates.txt index 1825ec2fc504..0ca144bb6623 100644 --- a/docs/ref/contrib/postgres/aggregates.txt +++ b/docs/ref/contrib/postgres/aggregates.txt @@ -182,6 +182,9 @@ General-purpose aggregation functions .. deprecated:: 6.0 + The PostgreSQL ``StringAgg`` class is deprecated in favor of the + generally available :class:`~django.db.models.StringAgg` class. + Returns the input values concatenated into a string, separated by the ``delimiter`` string, or ``default`` if there are no values. diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 5a2696993c0b..1f3e27414129 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -1058,7 +1058,7 @@ Methods .. method:: HttpResponse.close() This method is called at the end of the request directly by the WSGI - server. + or ASGI server. .. method:: HttpResponse.write(content) diff --git a/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml b/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml new file mode 100644 index 000000000000..cd164d3d2601 --- /dev/null +++ b/tests/fixtures/fixtures/invalid_deeply_nested_elements.xml @@ -0,0 +1,10 @@ + + + + + + Django pony + + + + diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py index 48e3182b59aa..7511569d2102 100644 --- a/tests/fixtures/tests.py +++ b/tests/fixtures/tests.py @@ -23,6 +23,7 @@ CircularA, CircularB, NaturalKeyThing, + Person, PrimaryKeyUUIDModel, ProxySpy, Spy, @@ -520,6 +521,14 @@ def test_loading_and_dumping(self): natural_foreign_keys=True, ) + def test_deeply_nested_elements(self): + """Text inside deeply-nested tags is skipped.""" + management.call_command( + "loaddata", "invalid_deeply_nested_elements.xml", verbosity=0 + ) + person = Person.objects.get(pk=1) + self.assertEqual(person.name, "Django") # not "Django pony" + def test_dumpdata_with_excludes(self): # Load fixture1 which has a site, two articles, and a category Site.objects.all().delete() diff --git a/tests/runtests.py b/tests/runtests.py index 974ec257d51c..60dc03fea39b 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -61,15 +61,6 @@ TEMPLATE_DIR = os.path.join(RUNTESTS_DIR, "templates") -# Create a specific subdirectory for the duration of the test suite. -TMPDIR = tempfile.mkdtemp(prefix="django_") -# Set the TMPDIR environment variable in addition to tempfile.tempdir -# so that children processes inherit it. -tempfile.tempdir = os.environ["TMPDIR"] = TMPDIR - -# Removing the temporary TMPDIR. -atexit.register(shutil.rmtree, TMPDIR) - # Add variables enabling coverage to trace code in subprocesses. os.environ["RUNTESTS_DIR"] = RUNTESTS_DIR os.environ["COVERAGE_PROCESS_START"] = os.path.join(RUNTESTS_DIR, ".coveragerc") @@ -197,6 +188,7 @@ def _module_match_label(module_name, label): def setup_collect_tests(start_at, start_after, test_labels=None): + TMPDIR = os.environ["TMPDIR"] state = { "INSTALLED_APPS": settings.INSTALLED_APPS, "ROOT_URLCONF": getattr(settings, "ROOT_URLCONF", ""), @@ -324,13 +316,6 @@ def no_available_apps(cls): def teardown_run_tests(state): teardown_collect_tests(state) - # Discard the multiprocessing.util finalizer that tries to remove a - # temporary directory that's already removed by this script's - # atexit.register(shutil.rmtree, TMPDIR) handler. Prevents - # FileNotFoundError at the end of a test run (#27890). - from multiprocessing.util import _finalizer_registry - - _finalizer_registry.pop((-100, 0), None) del os.environ["RUNNING_DJANGOS_TEST_SUITE"] @@ -531,6 +516,14 @@ def paired_tests(paired_test, options, test_labels, start_at, start_after): if __name__ == "__main__": + # Create a specific subdirectory for the duration of the test suite. + TMPDIR = tempfile.mkdtemp(prefix="django_") + # Set the TMPDIR environment variable in addition to tempfile.tempdir + # so that children processes inherit it. + tempfile.tempdir = os.environ["TMPDIR"] = TMPDIR + # Remove the temporary TMPDIR. + atexit.register(shutil.rmtree, TMPDIR) + parser = argparse.ArgumentParser(description="Run the Django test suite.") parser.add_argument( "modules", diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 7412c2624c73..ee115aaf1cf2 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -1,3 +1,4 @@ +import math import os import sys from datetime import datetime @@ -122,14 +123,25 @@ def test_strip_tags(self): # The check below is temporary until all supported Python versions and # CI workers include the fix. See: # https://github.com/python/cpython/commit/6eb6c5db - min_fixed = { + min_fixed_security = { (3, 13): (3, 13, 6), (3, 12): (3, 12, 12), } + # Similarly, there was a fix for terminating incomplete entities. See: + # https://github.com/python/cpython/commit/95296a9d + min_fixed_incomplete_entities = { + (3, 14): (3, 14, 1), + (3, 13): (3, 13, 10), + (3, 12): (3, 12, math.inf), # not fixed in 3.12. + } major_version = sys.version_info[:2] - htmlparser_fixed = sys.version_info >= min_fixed.get( + htmlparser_fixed_security = sys.version_info >= min_fixed_security.get( major_version, major_version ) + htmlparser_fixed_incomplete_entities = ( + sys.version_info + >= min_fixed_incomplete_entities.get(major_version, major_version) + ) items = ( ( "

See: 'é is an apostrophe followed by e acute

", @@ -156,16 +168,19 @@ def test_strip_tags(self): # https://bugs.python.org/issue20288 ("&gotcha&#;<>", "&gotcha&#;<>"), ("ript>test</script>", "ript>test"), - ("&h", "alert()h"), + ( + "&h", + "alert()&h;" if htmlparser_fixed_incomplete_entities else "alert()h", + ), ( ">" if htmlparser_fixed else ">" if htmlparser_fixed_security else ">br>br>br>X", "XX"), ("<" * 50 + "a>" * 50, ""), ( ">" + "" if htmlparser_fixed else ">" + "" if htmlparser_fixed_security else ">" + "