mirror of
https://github.com/django-components/django-components.git
synced 2025-09-19 04:09:44 +00:00
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:
parent
5535f3bad8
commit
8b5579d2be
7 changed files with 653 additions and 18 deletions
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -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
|
||||
|
|
101
docs/concepts/fundamentals/http_request.md
Normal file
101
docs/concepts/fundamentals/http_request.md
Normal 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,
|
||||
}
|
||||
```
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Subclassing components
|
||||
weight: 11
|
||||
weight: 12
|
||||
---
|
||||
|
||||
In larger projects, you might need to write multiple components with similar behavior.
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue