refactor: do not call URLResolver._populate() if not needed

This commit is contained in:
Juro Oravec 2025-11-09 18:51:41 +00:00
parent 39e4d78dc5
commit 77df93b5b1
6 changed files with 38 additions and 11 deletions

View file

@ -1,5 +1,11 @@
# Release notes
## v0.143.1
#### Fix
- Make django-component's position in Django's `INSTALLED_APPS` more lenient by not calling Django's `URLResolver._populate()` if `URLResolver` hasn't been resolved before ([See thread](https://discord.com/channels/1417824875023700000/1417825089675853906/1437034834118840411)).
## v0.143.0
#### Feat

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "django_components"
version = "0.143.0"
version = "0.143.1"
requires-python = ">=3.8, <4.0"
description = "A way to create simple reusable template components in Django."
keywords = ["django", "components", "css", "js", "html"]

View file

@ -12,8 +12,6 @@ asv==0.6.5
# via -r requirements-dev.in
asv-runner==0.2.1
# via asv
backports-asyncio-runner==1.2.0
# via pytest-asyncio
build==1.3.0
# via asv
cachetools==6.2.0

View file

@ -1163,9 +1163,7 @@ class ExtensionManager:
extensions_url_resolver.url_patterns = urls
# Rebuild URL resolver cache to be able to resolve the new routes by their names.
urlconf = get_urlconf()
resolver = get_resolver(urlconf)
resolver._populate()
self._lazy_populate_resolver()
# Flush stored events
#
@ -1190,6 +1188,22 @@ class ExtensionManager:
getattr(self, hook)(data)
self._events = []
# Django processes the paths from `urlpatterns` only once.
# This is at conflict with how we handle URL paths introduced by extensions,
# which may happen AFTER Django processes `urlpatterns`.
# If that happens, we need to force Django to re-process `urlpatterns`.
# If we don't do it, then the new paths added by our extensions won't work
# with e.g. `django.url.reverse()`.
# See https://discord.com/channels/1417824875023700000/1417825089675853906/1437034834118840411
def _lazy_populate_resolver(self) -> None:
urlconf = get_urlconf()
root_resolver = get_resolver(urlconf)
# However, if Django has NOT yet processed the `urlpatterns`, then do nothing.
# If we called `_populate()` in such case, we may break people's projects
# as the values may be resolved prematurely, before all the needed code is loaded.
if root_resolver._populated:
root_resolver._populate()
def get_extension(self, name: str) -> ComponentExtension:
for extension in self.extensions:
if extension.name == name:
@ -1221,12 +1235,8 @@ class ExtensionManager:
all_urls.append(urlpattern)
did_add_urls = True
# Force Django's URLResolver to update its lookups, so things like `reverse()` work
if did_add_urls:
# Django's root URLResolver
urlconf = get_urlconf()
root_resolver = get_resolver(urlconf)
root_resolver._populate()
self._lazy_populate_resolver()
def remove_extension_urls(self, name: str, urls: List[URLRoute]) -> None:
if not self._initialized:

View file

@ -585,5 +585,11 @@ def _clear_djc_global_state(
if gc_collect:
gc.collect()
# Clear Django's URL resolver cache, so that any URLs that were added
# during tests are removed.
from django.urls.resolvers import _get_cached_resolver # noqa: PLC0415
_get_cached_resolver.cache_clear()
global IS_TESTING # noqa: PLW0603
IS_TESTING = False

View file

@ -5,6 +5,7 @@ import pytest
from django.http import HttpRequest, HttpResponse
from django.template import Context, Origin, Template
from django.test import Client
from django.urls import get_resolver, get_urlconf
from django_components import Component, Slot, SlotNode, register, registry
from django_components.app_settings import app_settings
@ -649,6 +650,12 @@ class TestExtensionHooks:
@djc_test
class TestExtensionViews:
@djc_test(components_settings={"extensions": [DummyExtension]})
def test_resolver_not_populated_needlessly(self):
urlconf = get_urlconf()
resolver = get_resolver(urlconf)
assert not resolver._populated
@djc_test(components_settings={"extensions": [DummyExtension]})
def test_views(self):
client = Client()