feat:forward context processors variables in context in ISOLATED mode (#975)

* feat:forward context processors variables in context in ISOLATED mode

	provide context_processors_data property to Component to access those variables in Component

* refactor: internalize RequestContext and pass HttpRequest internally

* docs: document HttpRequest and context processors

---------

Co-authored-by: Juro Oravec <juraj.oravec.josefson@gmail.com>
This commit is contained in:
lilian D 2025-02-20 11:06:49 +01:00 committed by GitHub
parent 5535f3bad8
commit 8b5579d2be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 653 additions and 18 deletions

View file

@ -1,5 +1,29 @@
# Release notes
## v0.130
#### Feat
- Access the HttpRequest object under `Component.request`.
To pass the request object to a component, either:
- Render a template or component with `RequestContext`,
- Or set the `request` kwarg to `Component.render()` or `Component.render_to_response()`.
Read more on [HttpRequest](https://django-components.github.io/django-components/0.130/concepts/fundamentals/http_request/).
- Access the context processors data under `Component.context_processors_data`.
Context processors data is available only when the component has access to the `request` object,
either by:
- Passing the request to `Component.render()` or `Component.render_to_response()`,
- Or by rendering a template or component with `RequestContext`,
- Or being nested in another component that has access to the request object.
The data from context processors is automatically available within the component's template.
Read more on [HttpRequest](https://django-components.github.io/django-components/0.130/concepts/fundamentals/http_request/).
## v0.129
#### Fix

View file

@ -0,0 +1,101 @@
---
title: HTTP Request
weight: 11
---
The most common use of django-components is to render HTML for a given request. As such,
there are a few features that are dependent on the request object.
## Passing and accessing HttpRequest
In regular Django templates, the request object is available only within the `RequestContext`.
In Components, you can either use `RequestContext`, or pass the `request` object
explicitly to [`Component.render()`](../../../reference/api#django_components.Component.render) and
[`Component.render_to_response()`](../../../reference/api#django_components.Component.render_to_response).
When a component is nested in another, the child component uses parent's `request` object.
You can access the request object under [`Component.request`](../../../reference/api#django_components.Component.request):
```python
class MyComponent(Component):
def get_context_data(self):
return {
'user_id': self.request.GET['user_id'],
}
# ✅ With request
MyComponent.render(request=request)
MyComponent.render(context=RequestContext(request, {}))
# ❌ Without request
MyComponent.render()
MyComponent.render(context=Context({}))
```
## Context Processors
Components support Django's [context processors](https://docs.djangoproject.com/en/5.1/ref/templates/api/#using-requestcontext).
In regular Django templates, the context processors are applied only when the template is rendered with `RequestContext`.
Components allow you to pass the `request` object explicitly. Thus, the context processors are applied to components either when:
- The component is rendered with `RequestContext` (Regular Django behavior)
- The component is rendered with a regular `Context` (or none), but you set the `request` kwarg
of [`Component.render()`](../../../reference/api#django_components.Component.render).
- The component is nested in another component that matches one of the two conditions above.
```python
# ❌ No context processors
rendered = MyComponent.render()
rendered = MyComponent.render(Context({}))
# ✅ With context processors
rendered = MyComponent.render(request=request)
rendered = MyComponent.render(Context({}), request=request)
rendered = MyComponent.render(RequestContext(request, {}))
```
When a component is rendered within a template with [`{% component %}`](../../../reference/template_tags#component) tag, context processors are available depending on whether the template is rendered with `RequestContext` or not.
```python
template = Template("""
<div>
{% component "MyComponent" / %}
</div>
""")
# ❌ No context processors
rendered = template.render(Context({}))
# ✅ With context processors
rendered = template.render(RequestContext(request, {}))
```
### Accessing context processors data
The data from context processors is automatically available within the component's template.
```python
class MyComponent(Component):
template = """
<div>
{{ csrf_token }}
</div>
"""
MyComponent.render(request=request)
```
You can also access the context processors data from within [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data) and other methods under [`Component.context_processors_data`](../../../reference/api#django_components.Component.context_processors_data).
```python
class MyComponent(Component):
def get_context_data(self):
csrf_token = self.context_processors_data['csrf_token']
return {
'csrf_token': csrf_token,
}
```

View file

@ -1,6 +1,6 @@
---
title: Subclassing components
weight: 11
weight: 12
---
In larger projects, you might need to write multiple components with similar behavior.

View file

@ -67,7 +67,7 @@ If you insert this tag multiple times, ALL JS scripts will be duplicately insert
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1434" target="_blank">See source code</a>
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L1549" target="_blank">See source code</a>
@ -175,7 +175,7 @@ can access only the data that was explicitly passed to it:
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L577" target="_blank">See source code</a>
<a href="https://github.com/django-components/django-components/tree/master/src/django_components/templatetags/component_tags.py#L612" target="_blank">See source code</a>

View file

@ -70,7 +70,7 @@ from django_components.slots import (
)
from django_components.template import cached_template
from django_components.util.component_highlight import apply_component_highlight
from django_components.util.context import snapshot_context
from django_components.util.context import gen_context_processors_data, snapshot_context
from django_components.util.django_monkeypatch import is_template_cls_patched
from django_components.util.exception import component_error_message
from django_components.util.logger import trace_component_msg
@ -114,6 +114,7 @@ class MetadataItem(Generic[ArgsType, KwargsType, SlotsType]):
render_id: str
input: RenderInput[ArgsType, KwargsType, SlotsType]
is_filled: Optional[SlotIsFilled]
request: Optional[HttpRequest]
class ViewFn(Protocol):
@ -233,6 +234,7 @@ class ComponentContext:
fills: Dict[SlotName, Slot]
outer_context: Optional[Context]
registry: ComponentRegistry
request: Optional[HttpRequest]
# When we render a component, the root component, together with all the nested Components,
# shares this dictionary for storing callbacks that are called from within `component_post_render`.
# This is so that we can pass them all in when the root component is passed to `component_post_render`.
@ -663,6 +665,8 @@ class Component(
"""
Input holds the data (like arg, kwargs, slots) that were passed to
the current execution of the `render` method.
Raises `RuntimeError` if accessed outside of rendering execution.
"""
if not len(self._metadata_stack):
raise RuntimeError(f"{self.name}: Tried to access Component input while outside of rendering execution")
@ -678,6 +682,8 @@ class Component(
This attribute is available for use only within the template as `{{ component_vars.is_filled.slot_name }}`,
and within `on_render_before` and `on_render_after` hooks.
Raises `RuntimeError` if accessed outside of rendering execution.
"""
if not len(self._metadata_stack):
raise RuntimeError(
@ -693,6 +699,93 @@ class Component(
return ctx.is_filled
@property
def request(self) -> Optional[HttpRequest]:
"""
[HTTPRequest](https://docs.djangoproject.com/en/5.1/ref/request-response/#django.http.HttpRequest)
object passed to this component.
In regular Django templates, you have to use
[`RequestContext`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.RequestContext)
to pass the `HttpRequest` object to the template.
But in Components, you can either use `RequestContext`, or pass the `request` object
explicitly via [`Component.render()`](../api#django_components.Component.render) and
[`Component.render_to_response()`](../api#django_components.Component.render_to_response).
When a component is nested in another, the child component uses parent's `request` object.
Raises `RuntimeError` if accessed outside of rendering execution.
**Example:**
```py
class MyComponent(Component):
def get_context_data(self):
user_id = self.request.GET['user_id']
return {
'user_id': user_id,
}
```
"""
if not len(self._metadata_stack):
raise RuntimeError(
f"{self.name}: Tried to access Component's `request` attribute " "while outside of rendering execution"
)
ctx = self._metadata_stack[-1]
return ctx.request
@property
def context_processors_data(self) -> Dict:
"""
Retrieve data injected by
[`context_processors`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#configuring-an-engine).
This data is also available from within the component's template, without having to
return this data from
[`get_context_data()`](../api#django_components.Component.get_context_data).
Unlike regular Django templates, the context processors are applied to components either when:
- The component is rendered with `RequestContext` (Regular Django behavior)
- The component is rendered with a regular `Context` (or none), but the `request` kwarg
of [`Component.render()`](../api#django_components.Component.render) is set.
- The component is nested in another component that matches one of the two conditions above.
See
[`Component.request`](../api#django_components.Component.request)
on how the `request`
([HTTPRequest](https://docs.djangoproject.com/en/5.1/ref/request-response/#django.http.HttpRequest))
object is passed to and within the components.
Raises `RuntimeError` if accessed outside of rendering execution.
**Example:**
```py
class MyComponent(Component):
def get_context_data(self):
user = self.context_processors_data['user']
return {
'is_logged_in': user.is_authenticated,
}
```
"""
if not len(self._metadata_stack):
raise RuntimeError(
f"{self.name}: Tried to access Component's `context_processors_data` attribute "
"while outside of rendering execution"
)
context = self.input.context
request = self.request
if request is None:
return {}
else:
return gen_context_processors_data(context, request)
# NOTE: We cache the Template instance. When the template is taken from a file
# via `get_template_name`, then we leverage Django's template caching with `get_template()`.
# Otherwise, we use our own `cached_template()` to cache the template.
@ -859,8 +952,7 @@ class Component(
or to the end of the `<body>` tag. CSS dependencies are inserted into
`{% component_css_dependencies %}`, or the end of the `<head>` tag.
- `request` - The request object. This is only required when needing to use RequestContext,
e.g. to enable template `context_processors`. Unused if context is already an instance
of `Context`
e.g. to enable template `context_processors`.
Any additional args and kwargs are passed to the `response_class`.
@ -929,8 +1021,7 @@ class Component(
`{% component_css_dependencies %}`, or the end of the `<head>` tag.
- `render_dependencies` - Set this to `False` if you want to insert the resulting HTML into another component.
- `request` - The request object. This is only required when needing to use RequestContext,
e.g. to enable template `context_processors`. Unused if context is already an instance of
`Context`
e.g. to enable template `context_processors`.
Example:
```py
MyComponent.render(
@ -991,11 +1082,26 @@ class Component(
# wraps them in functions.
self._validate_inputs(args or (), kwargs or {}, slots or {})
# Allow to pass down Request object via context.
# `context` may be passed explicitly via `Component.render()` and `Component.render_to_response()`,
# or implicitly via `{% component %}` tag.
if request is None and context:
# If the context is `RequestContext`, it has `request` attribute
request = getattr(context, "request", None)
# Check if this is a nested component and whether parent has request
if request is None:
parent_id = context.get(_COMPONENT_CONTEXT_KEY, None)
if parent_id:
parent_comp_ctx = component_context_cache[parent_id]
request = parent_comp_ctx.request
# Allow to provide no args/kwargs/slots/context
args = cast(ArgsType, args or ())
kwargs = cast(KwargsType, kwargs or {})
slots_untyped = self._normalize_slot_fills(slots or {}, escape_slots_content)
slots = cast(SlotsType, slots_untyped)
# Use RequestContext if request is provided, so that child non-component template tags
# can access the request object too.
context = context or (RequestContext(request) if request else Context())
# Allow to provide a dict instead of Context
@ -1027,6 +1133,7 @@ class Component(
render_dependencies=render_dependencies,
),
is_filled=None,
request=request,
)
# We pass down the components the info about the component's parent.
@ -1071,6 +1178,8 @@ class Component(
default_slot=None,
outer_context=snapshot_context(self.outer_context) if self.outer_context is not None else None,
registry=self.registry,
# Pass down Request object via context
request=request,
post_render_callbacks=post_render_callbacks,
)
@ -1083,6 +1192,7 @@ class Component(
# Allow to access component input and metadata like component ID from within these hook
with self._with_metadata(metadata):
context_processors_data = self.context_processors_data
context_data = self.get_context_data(*args, **kwargs)
# TODO - enable JS and CSS vars - EXPOSE AND DOCUMENT AND MAKE NON-NULL
js_data = self.get_js_data(*args, **kwargs) if hasattr(self, "get_js_data") else {} # type: ignore
@ -1107,6 +1217,8 @@ class Component(
with context.update(
{
# Make data from context processors available inside templates
**context_processors_data,
# Private context fields
_COMPONENT_CONTEXT_KEY: render_id,
# NOTE: Public API for variables accessible from within a component's template

View file

@ -1,9 +1,21 @@
import copy
from typing import List
import sys
from typing import Any, Callable, Dict, List, Tuple
from weakref import WeakKeyDictionary
from django.template import Context
from django.http import HttpRequest
from django.template import Engine
from django.template.context import BaseContext, Context
from django.template.loader_tags import BlockContext
# We cache the context processors data for each request, so that we don't have to
# generate it for each component.
# NOTE: Can't be used as generic in Python 3.8
if sys.version_info >= (3, 9):
context_processors_data: WeakKeyDictionary[HttpRequest, Dict[str, Any]] = WeakKeyDictionary()
else:
context_processors_data = WeakKeyDictionary()
class CopiedDict(dict):
"""Dict subclass to identify dictionaries that have been copied with `snapshot_context`"""
@ -112,3 +124,33 @@ def _copy_block_context(block_context: BlockContext) -> BlockContext:
# need to modify the Nodes, but we need to make a copy of the lists.
block_context_copy.blocks[key] = val.copy()
return block_context_copy
# Django's logic for generating context processors data. The gist is the same as
# `RequestContext.bind_template()`, but without depending on a Template object.
# See https://github.com/django/django/blame/2d34ebe49a25d0974392583d5bbd954baf742a32/django/template/context.py#L255
def gen_context_processors_data(context: BaseContext, request: HttpRequest) -> Dict[str, Any]:
if request in context_processors_data:
return context_processors_data[request]
# TODO_REMOVE_IN_V2 - In v2, if we still support context processors,
# it should be set on our settings, so we wouldn't have to get the Engine for that.
# In v2 it should be also possible to remove RequestContext, and use only Context,
# since we're internalized the behaviour of RequestContext.
default_engine = Engine.get_default()
# NOTE: Compatibility with `RequestContext`, which accepts an optional
# `processors` argument.
request_context_processors: Tuple[Callable[..., Any], ...] = getattr(context, "_processors", ())
# This part is same as in `RequestContext.bind_template()`
processors = default_engine.template_context_processors + request_context_processors
processors_data = {}
for processor in processors:
data = processor(request)
try:
processors_data.update(data)
except TypeError as e:
raise TypeError(f"Context processor {processor.__qualname__} didn't return a " "dictionary.") from e
return processors_data

View file

@ -1,4 +1,7 @@
from django.template import Context, Template
from typing import Dict, Optional
from django.http import HttpRequest
from django.template import Context, RequestContext, Template
from django_components import Component, register, registry, types
@ -437,7 +440,7 @@ class IsolatedContextTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"})).strip()
self.assertIn("outer_value", rendered, rendered)
self.assertIn("outer_value", rendered)
@parametrize_context_behavior(["django", "isolated"])
def test_simple_component_cannot_use_outer_context(self):
@ -447,7 +450,7 @@ class IsolatedContextTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"})).strip()
self.assertNotIn("outer_value", rendered, rendered)
self.assertNotIn("outer_value", rendered)
class IsolatedContextSettingTests(BaseTestCase):
@ -465,7 +468,7 @@ class IsolatedContextSettingTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"}))
self.assertIn("outer_value", rendered, rendered)
self.assertIn("outer_value", rendered)
@parametrize_context_behavior(["isolated"])
def test_component_tag_excludes_variable_with_isolated_context_from_settings(
@ -477,7 +480,7 @@ class IsolatedContextSettingTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"}))
self.assertNotIn("outer_value", rendered, rendered)
self.assertNotIn("outer_value", rendered)
@parametrize_context_behavior(["isolated"])
def test_component_includes_variable_with_isolated_context_from_settings(
@ -490,7 +493,7 @@ class IsolatedContextSettingTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"}))
self.assertIn("outer_value", rendered, rendered)
self.assertIn("outer_value", rendered)
@parametrize_context_behavior(["isolated"])
def test_component_excludes_variable_with_isolated_context_from_settings(
@ -503,7 +506,360 @@ class IsolatedContextSettingTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"}))
self.assertNotIn("outer_value", rendered, rendered)
self.assertNotIn("outer_value", rendered)
class ContextProcessorsTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_request_context_in_template(self):
context_processors_data: Optional[Dict] = None
inner_request: Optional[HttpRequest] = None
@register("test")
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal inner_request
context_processors_data = self.context_processors_data
inner_request = self.request
return {}
template_str: types.django_html = """
{% load component_tags %}
{% component "test" %}
{% endcomponent %}
"""
request = HttpRequest()
request_context = RequestContext(request)
template = Template(template_str)
rendered = template.render(request_context)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(inner_request, request)
@parametrize_context_behavior(["django", "isolated"])
def test_request_context_in_template_nested(self):
context_processors_data = None
context_processors_data_child = None
parent_request: Optional[HttpRequest] = None
child_request: Optional[HttpRequest] = None
@register("test_parent")
class TestParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% component "test_child" / %}
"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal parent_request
context_processors_data = self.context_processors_data
parent_request = self.request
return {}
@register("test_child")
class TestChildComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data_child
nonlocal child_request
context_processors_data_child = self.context_processors_data
child_request = self.request
return {}
template_str: types.django_html = """
{% load component_tags %}
{% component "test_parent" / %}
"""
request = HttpRequest()
request_context = RequestContext(request)
template = Template(template_str)
rendered = template.render(request_context)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(list(context_processors_data_child.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(parent_request, request)
self.assertEqual(child_request, request)
@parametrize_context_behavior(["django", "isolated"])
def test_request_context_in_template_slot(self):
context_processors_data = None
context_processors_data_child = None
parent_request: Optional[HttpRequest] = None
child_request: Optional[HttpRequest] = None
@register("test_parent")
class TestParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% component "test_child" %}
{% slot "content" default / %}
{% endcomponent %}
"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal parent_request
context_processors_data = self.context_processors_data
parent_request = self.request
return {}
@register("test_child")
class TestChildComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data_child
nonlocal child_request
context_processors_data_child = self.context_processors_data
child_request = self.request
return {}
template_str: types.django_html = """
{% load component_tags %}
{% component "test_parent" %}
{% component "test_child" / %}
{% endcomponent %}
"""
request = HttpRequest()
request_context = RequestContext(request)
template = Template(template_str)
rendered = template.render(request_context)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(list(context_processors_data_child.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(parent_request, request)
self.assertEqual(child_request, request)
@parametrize_context_behavior(["django", "isolated"])
def test_request_context_in_python(self):
context_processors_data = None
inner_request: Optional[HttpRequest] = None
@register("test")
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal inner_request
context_processors_data = self.context_processors_data
inner_request = self.request
return {}
request = HttpRequest()
request_context = RequestContext(request)
rendered = TestComponent.render(request_context)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(inner_request, request)
@parametrize_context_behavior(["django", "isolated"])
def test_request_context_in_python_nested(self):
context_processors_data: Optional[Dict] = None
context_processors_data_child: Optional[Dict] = None
parent_request: Optional[HttpRequest] = None
child_request: Optional[HttpRequest] = None
@register("test_parent")
class TestParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% component "test_child" / %}
"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal parent_request
context_processors_data = self.context_processors_data
parent_request = self.request
return {}
@register("test_child")
class TestChildComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data_child
nonlocal child_request
context_processors_data_child = self.context_processors_data
child_request = self.request
return {}
request = HttpRequest()
request_context = RequestContext(request)
rendered = TestParentComponent.render(request_context)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(list(context_processors_data_child.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(parent_request, request)
self.assertEqual(child_request, request)
@parametrize_context_behavior(["django", "isolated"])
def test_request_in_python(self):
context_processors_data: Optional[Dict] = None
inner_request: Optional[HttpRequest] = None
@register("test")
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal inner_request
context_processors_data = self.context_processors_data
inner_request = self.request
return {}
request = HttpRequest()
rendered = TestComponent.render(request=request)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(inner_request, request)
@parametrize_context_behavior(["django", "isolated"])
def test_request_in_python_nested(self):
context_processors_data: Optional[Dict] = None
context_processors_data_child: Optional[Dict] = None
parent_request: Optional[HttpRequest] = None
child_request: Optional[HttpRequest] = None
@register("test_parent")
class TestParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% component "test_child" / %}
"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal parent_request
context_processors_data = self.context_processors_data
parent_request = self.request
return {}
@register("test_child")
class TestChildComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data_child
nonlocal child_request
context_processors_data_child = self.context_processors_data
child_request = self.request
return {}
request = HttpRequest()
rendered = TestParentComponent.render(request=request)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(list(context_processors_data_child.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(parent_request, request)
self.assertEqual(child_request, request)
# No request, regular Context
@parametrize_context_behavior(["django", "isolated"])
def test_no_context_processor_when_non_request_context_in_python(self):
context_processors_data: Optional[Dict] = None
inner_request: Optional[HttpRequest] = None
@register("test")
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal inner_request
context_processors_data = self.context_processors_data
inner_request = self.request
return {}
rendered = TestComponent.render(context=Context())
self.assertNotIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), []) # type: ignore[union-attr]
self.assertEqual(inner_request, None)
# No request, no Context
@parametrize_context_behavior(["django", "isolated"])
def test_no_context_processor_when_non_request_context_in_python_2(self):
context_processors_data: Optional[Dict] = None
inner_request: Optional[HttpRequest] = None
@register("test")
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal inner_request
context_processors_data = self.context_processors_data
inner_request = self.request
return {}
rendered = TestComponent.render()
self.assertNotIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), []) # type: ignore[union-attr]
self.assertEqual(inner_request, None)
# Yes request, regular Context
@parametrize_context_behavior(["django", "isolated"])
def test_context_processor_when_regular_context_and_request_in_python(self):
context_processors_data: Optional[Dict] = None
inner_request: Optional[HttpRequest] = None
@register("test")
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
def get_context_data(self):
nonlocal context_processors_data
nonlocal inner_request
context_processors_data = self.context_processors_data
inner_request = self.request
return {}
request = HttpRequest()
rendered = TestComponent.render(Context(), request=request)
self.assertIn("csrfmiddlewaretoken", rendered)
self.assertEqual(list(context_processors_data.keys()), ["csrf_token"]) # type: ignore[union-attr]
self.assertEqual(inner_request, request)
def test_raises_on_accessing_context_processors_data_outside_of_rendering(self):
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
with self.assertRaisesMessage(
RuntimeError,
"Tried to access Component's `context_processors_data` attribute while outside of rendering execution",
):
TestComponent().context_processors_data
def test_raises_on_accessing_request_outside_of_rendering(self):
class TestComponent(Component):
template: types.django_html = """{% csrf_token %}"""
with self.assertRaisesMessage(
RuntimeError,
"Tried to access Component's `request` attribute while outside of rendering execution",
):
TestComponent().request
class OuterContextPropertyTests(BaseTestCase):
@ -527,7 +883,7 @@ class OuterContextPropertyTests(BaseTestCase):
"""
template = Template(template_str)
rendered = template.render(Context({"variable": "outer_value"})).strip()
self.assertIn("outer_value", rendered, rendered)
self.assertIn("outer_value", rendered)
class ContextVarsIsFilledTests(BaseTestCase):