mirror of
https://github.com/django-components/django-components.git
synced 2025-08-04 06:18:17 +00:00
Adding request
arg to render (#817)
Co-authored-by: Laurence Hole <laurence@safi.co> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
3f2d92f252
commit
dfd4187192
3 changed files with 90 additions and 6 deletions
|
@ -74,6 +74,12 @@ Component.render(
|
|||
- NOTE: In "isolated" mode, context is NOT accessible, and data MUST be passed via
|
||||
component's args and kwargs.
|
||||
|
||||
- _`request`_ - A Django request object. This is used to enable Django template `context_processors` to run,
|
||||
allowing for template tags like `{% csrf_token %}` and variables like `{{ debug }}`.
|
||||
- Similar behavior can be achieved with [provide / inject](#how-to-use-provide--inject).
|
||||
- This is used internally to convert `context` to a RequestContext. It does nothing if `context` is already
|
||||
a `Context` instance.
|
||||
|
||||
### `SlotFunc`
|
||||
|
||||
When rendering components with slots in `render` or `render_to_response`, you can pass either a string or a function.
|
||||
|
|
|
@ -28,7 +28,7 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
from django.forms.widgets import Media
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.template.base import NodeList, Template, TextNode
|
||||
from django.template.context import Context
|
||||
from django.template.context import Context, RequestContext
|
||||
from django.template.loader import get_template
|
||||
from django.template.loader_tags import BLOCK_CONTEXT_KEY
|
||||
from django.utils.html import conditional_escape
|
||||
|
@ -495,6 +495,7 @@ class Component(
|
|||
args: Optional[ArgsType] = None,
|
||||
kwargs: Optional[KwargsType] = None,
|
||||
type: RenderType = "document",
|
||||
request: Optional[HttpRequest] = None,
|
||||
*response_args: Any,
|
||||
**response_kwargs: Any,
|
||||
) -> HttpResponse:
|
||||
|
@ -523,6 +524,9 @@ class Component(
|
|||
- `"document"` (default) - JS dependencies are inserted into `{% component_js_dependencies %}`,
|
||||
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`
|
||||
|
||||
Any additional args and kwargs are passed to the `response_class`.
|
||||
|
||||
|
@ -553,6 +557,7 @@ class Component(
|
|||
escape_slots_content=escape_slots_content,
|
||||
type=type,
|
||||
render_dependencies=True,
|
||||
request=request,
|
||||
)
|
||||
return cls.response_class(content, *response_args, **response_kwargs)
|
||||
|
||||
|
@ -566,6 +571,7 @@ class Component(
|
|||
escape_slots_content: bool = True,
|
||||
type: RenderType = "document",
|
||||
render_dependencies: bool = True,
|
||||
request: Optional[HttpRequest] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Render the component into a string.
|
||||
|
@ -588,7 +594,9 @@ 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.
|
||||
- `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`
|
||||
Example:
|
||||
```py
|
||||
MyComponent.render(
|
||||
|
@ -611,7 +619,7 @@ class Component(
|
|||
else:
|
||||
comp = cls()
|
||||
|
||||
return comp._render(context, args, kwargs, slots, escape_slots_content, type, render_dependencies)
|
||||
return comp._render(context, args, kwargs, slots, escape_slots_content, type, render_dependencies, request)
|
||||
|
||||
# This is the internal entrypoint for the render function
|
||||
def _render(
|
||||
|
@ -623,9 +631,12 @@ class Component(
|
|||
escape_slots_content: bool = True,
|
||||
type: RenderType = "document",
|
||||
render_dependencies: bool = True,
|
||||
request: Optional[HttpRequest] = None,
|
||||
) -> str:
|
||||
try:
|
||||
return self._render_impl(context, args, kwargs, slots, escape_slots_content, type, render_dependencies)
|
||||
return self._render_impl(
|
||||
context, args, kwargs, slots, escape_slots_content, type, render_dependencies, request
|
||||
)
|
||||
except Exception as err:
|
||||
# Nicely format the error message to include the component path.
|
||||
# E.g.
|
||||
|
@ -662,6 +673,7 @@ class Component(
|
|||
escape_slots_content: bool = True,
|
||||
type: RenderType = "document",
|
||||
render_dependencies: bool = True,
|
||||
request: Optional[HttpRequest] = None,
|
||||
) -> str:
|
||||
# NOTE: We must run validation before we normalize the slots, because the normalization
|
||||
# wraps them in functions.
|
||||
|
@ -672,12 +684,13 @@ class Component(
|
|||
kwargs = cast(KwargsType, kwargs or {})
|
||||
slots_untyped = self._normalize_slot_fills(slots or {}, escape_slots_content)
|
||||
slots = cast(SlotsType, slots_untyped)
|
||||
context = context or Context()
|
||||
context = context or (RequestContext(request) if request else Context())
|
||||
|
||||
# Allow to provide a dict instead of Context
|
||||
# NOTE: This if/else is important to avoid nested Contexts,
|
||||
# See https://github.com/EmilStenstrom/django-components/issues/414
|
||||
context = context if isinstance(context, Context) else Context(context)
|
||||
if not isinstance(context, Context):
|
||||
context = RequestContext(request, context) if request else Context(context)
|
||||
|
||||
# By adding the current input to the stack, we temporarily allow users
|
||||
# to access the provided context, slots, etc. Also required so users can
|
||||
|
|
|
@ -1070,6 +1070,71 @@ class ComponentRenderTest(BaseTestCase):
|
|||
self.assertTrue(token)
|
||||
self.assertEqual(len(token), 64)
|
||||
|
||||
def test_request_context_created_when_no_context(self):
|
||||
@register("thing")
|
||||
class Thing(Component):
|
||||
template: types.django_html = """
|
||||
CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
return self.render_to_response(request=request)
|
||||
|
||||
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
||||
response = client.get("/test_thing/")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
token_re = re.compile(rb"CSRF token:\s+(?P<token>[0-9a-zA-Z]{64})")
|
||||
token = token_re.findall(response.content)[0]
|
||||
|
||||
self.assertTrue(token)
|
||||
self.assertEqual(len(token), 64)
|
||||
|
||||
def test_request_context_created_when_already_a_context_dict(self):
|
||||
@register("thing")
|
||||
class Thing(Component):
|
||||
template: types.django_html = """
|
||||
<p>CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}</p>
|
||||
<p>Existing context: {{ existing_context|default:"<em>No existing context</em>" }}</p>
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
return self.render_to_response(request=request, context={"existing_context": "foo"})
|
||||
|
||||
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
||||
response = client.get("/test_thing/")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
token_re = re.compile(rb"CSRF token:\s+(?P<token>[0-9a-zA-Z]{64})")
|
||||
token = token_re.findall(response.content)[0]
|
||||
|
||||
self.assertTrue(token)
|
||||
self.assertEqual(len(token), 64)
|
||||
self.assertInHTML("Existing context: foo", response.content.decode())
|
||||
|
||||
def request_context_ignores_context_when_already_a_context(self):
|
||||
@register("thing")
|
||||
class Thing(Component):
|
||||
template: types.django_html = """
|
||||
<p>CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}</p>
|
||||
<p>Existing context: {{ existing_context|default:"<em>No existing context</em>" }}</p>
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
return self.render_to_response(request=request, context=Context({"existing_context": "foo"}))
|
||||
|
||||
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
||||
response = client.get("/test_thing/")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
token_re = re.compile(rb"CSRF token:\s+(?P<token>[0-9a-zA-Z]{64})")
|
||||
|
||||
self.assertFalse(token_re.findall(response.content))
|
||||
self.assertInHTML("Existing context: foo", response.content.decode())
|
||||
|
||||
@parametrize_context_behavior(["django", "isolated"])
|
||||
def test_render_with_extends(self):
|
||||
class SimpleComponent(Component):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue