diff --git a/CHANGELOG.md b/CHANGELOG.md index 1558cd3b..3a4e2a58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - 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)). +#### Refactor + +- Components now raise error if template data overwrites variables from `context_processors` ([#1482](https://github.com/django-components/django-components/issues/1482)) + ## v0.143.0 #### Feat diff --git a/src/django_components/component.py b/src/django_components/component.py index 5103a940..6eec353a 100644 --- a/src/django_components/component.py +++ b/src/django_components/component.py @@ -3569,6 +3569,8 @@ class Component(metaclass=ComponentMeta): # 2. Prepare component state ###################################### + context_processors_data = component.context_processors_data + # Required for compatibility with Django's {% extends %} tag # See https://github.com/django-components/django-components/pull/859 context.render_context.push( # type: ignore[union-attr] @@ -3653,6 +3655,17 @@ class Component(metaclass=ComponentMeta): ), ) + # Check if template_data doesn't conflict with context_processors_data + # See https://github.com/django-components/django-components/issues/1482 + # NOTE: This is done after on_component_data so extensions can modify the data first. + if context_processors_data: + for key in template_data: + if key in context_processors_data: + raise ValueError( + f"Variable '{key}' defined in component '{component_name}' conflicts " + "with the same variable from context processors. Rename the variable in the component." + ) + # Cache component's JS and CSS scripts, in case they have been evicted from the cache. cache_component_js(comp_cls, force=False) cache_component_css(comp_cls, force=False) @@ -3697,7 +3710,7 @@ class Component(metaclass=ComponentMeta): with context.update( # type: ignore[union-attr] { # Make data from context processors available inside templates - **component.context_processors_data, + **context_processors_data, # Private context fields _COMPONENT_CONTEXT_KEY: render_id, COMPONENT_IS_NESTED_KEY: comp_is_nested, diff --git a/tests/test_context.py b/tests/test_context.py index 777a8758..e0e3d33c 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,5 +1,6 @@ from typing import Dict, Optional, cast +import pytest from django.http import HttpRequest from django.template import Context, RequestContext, Template from pytest_django.asserts import assertHTMLEqual, assertInHTML @@ -969,6 +970,43 @@ class TestContextProcessors: assert component.request == request + @djc_test( + # Same as settings in testutils.py, but also sets context_processors + django_settings={ + "TEMPLATES": [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + "tests/templates/", + "tests/components/", + ], + "OPTIONS": { + "builtins": [ + "django_components.templatetags.component_tags", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + "django_components.template_loader.Loader", + ], + "context_processors": [ + "django.template.context_processors.request", + ], + }, + }, + ], + } + ) + def test_raises_on_conflict_with_template_data(self): + class TestComponent(Component): + def get_template_data(self, args, kwargs, slots, context): + return { + "request": "OVERWRITTEN", + } + + with pytest.raises(ValueError, match="Variable 'request' defined in component 'TestComponent' conflicts"): + TestComponent.render(request=HttpRequest()) + @djc_test class TestOuterContextProperty: