mirror of
https://github.com/django-components/django-components.git
synced 2025-08-10 01:08:00 +00:00
fix: populate RequestContext with context processors (#643)
This commit is contained in:
parent
96a4717631
commit
2d0f270df4
2 changed files with 113 additions and 17 deletions
|
@ -1,6 +1,7 @@
|
||||||
import inspect
|
import inspect
|
||||||
import types
|
import types
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
@ -8,6 +9,7 @@ from typing import (
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Deque,
|
Deque,
|
||||||
Dict,
|
Dict,
|
||||||
|
Generator,
|
||||||
Generic,
|
Generic,
|
||||||
List,
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
|
@ -520,22 +522,7 @@ class Component(Generic[ArgsType, KwargsType, DataType, SlotsType], metaclass=Co
|
||||||
context_data = self.get_context_data(*args, **kwargs)
|
context_data = self.get_context_data(*args, **kwargs)
|
||||||
self._validate_outputs(context_data)
|
self._validate_outputs(context_data)
|
||||||
|
|
||||||
with context.update(context_data):
|
with _prepare_template(self, context, context_data) as template:
|
||||||
template = self.get_template(context)
|
|
||||||
_monkeypatch_template(template)
|
|
||||||
|
|
||||||
if context.template is None:
|
|
||||||
# Associate the newly-created Context with a Template, otherwise we get
|
|
||||||
# an error when we try to use `{% include %}` tag inside the template?
|
|
||||||
# See https://github.com/EmilStenstrom/django-components/issues/580
|
|
||||||
context.template = template
|
|
||||||
context.template_name = template.name
|
|
||||||
|
|
||||||
# Set `Template._dc_is_component_nested` based on whether we're currently INSIDE
|
|
||||||
# the `{% extends %}` tag.
|
|
||||||
# Part of fix for https://github.com/EmilStenstrom/django-components/issues/508
|
|
||||||
template._dc_is_component_nested = bool(context.render_context.get(BLOCK_CONTEXT_KEY))
|
|
||||||
|
|
||||||
# Support passing slots explicitly to `render` method
|
# Support passing slots explicitly to `render` method
|
||||||
if has_slots:
|
if has_slots:
|
||||||
fill_content = self._fills_from_slots_data(
|
fill_content = self._fills_from_slots_data(
|
||||||
|
@ -843,3 +830,35 @@ def _monkeypatch_template(template: Template) -> None:
|
||||||
|
|
||||||
# See https://stackoverflow.com/a/42154067/9788634
|
# See https://stackoverflow.com/a/42154067/9788634
|
||||||
template.render = types.MethodType(_template_render, template)
|
template.render = types.MethodType(_template_render, template)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _maybe_bind_template(context: Context, template: Template) -> Generator[None, Any, None]:
|
||||||
|
if context.template is None:
|
||||||
|
with context.bind_template(template):
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _prepare_template(
|
||||||
|
component: Component,
|
||||||
|
context: Context,
|
||||||
|
context_data: Any,
|
||||||
|
) -> Generator[Template, Any, None]:
|
||||||
|
with context.update(context_data):
|
||||||
|
# Associate the newly-created Context with a Template, otherwise we get
|
||||||
|
# an error when we try to use `{% include %}` tag inside the template?
|
||||||
|
# See https://github.com/EmilStenstrom/django-components/issues/580
|
||||||
|
# And https://github.com/EmilStenstrom/django-components/issues/634
|
||||||
|
template = component.get_template(context)
|
||||||
|
_monkeypatch_template(template)
|
||||||
|
|
||||||
|
# Set `Template._dc_is_component_nested` based on whether we're currently INSIDE
|
||||||
|
# the `{% extends %}` tag.
|
||||||
|
# Part of fix for https://github.com/EmilStenstrom/django-components/issues/508
|
||||||
|
template._dc_is_component_nested = bool(context.render_context.get(BLOCK_CONTEXT_KEY))
|
||||||
|
|
||||||
|
with _maybe_bind_template(context, template):
|
||||||
|
yield template
|
||||||
|
|
|
@ -3,6 +3,7 @@ Tests focusing on the Component class.
|
||||||
For tests focusing on the `component` tag, see `test_templatetags_component.py`
|
For tests focusing on the `component` tag, see `test_templatetags_component.py`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, Tuple, Union, no_type_check
|
from typing import Any, Dict, Tuple, Union, no_type_check
|
||||||
|
|
||||||
|
@ -14,13 +15,16 @@ else:
|
||||||
|
|
||||||
from unittest import skipIf
|
from unittest import skipIf
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.template import Context, RequestContext, Template, TemplateSyntaxError
|
from django.template import Context, RequestContext, Template, TemplateSyntaxError
|
||||||
from django.template.base import TextNode
|
from django.template.base import TextNode
|
||||||
|
from django.test import Client
|
||||||
|
from django.urls import path
|
||||||
from django.utils.safestring import SafeString
|
from django.utils.safestring import SafeString
|
||||||
|
|
||||||
from django_components import Component, SlotFunc, registry, types
|
from django_components import Component, ComponentView, SlotFunc, register, registry, types
|
||||||
from django_components.slots import SlotRef
|
from django_components.slots import SlotRef
|
||||||
|
|
||||||
from .django_test_setup import setup_test_config
|
from .django_test_setup import setup_test_config
|
||||||
|
@ -29,6 +33,21 @@ from .testutils import BaseTestCase, parametrize_context_behavior
|
||||||
setup_test_config({"autodiscover": False})
|
setup_test_config({"autodiscover": False})
|
||||||
|
|
||||||
|
|
||||||
|
# Client for testing endpoints via requests
|
||||||
|
class CustomClient(Client):
|
||||||
|
def __init__(self, urlpatterns=None, *args, **kwargs):
|
||||||
|
import types
|
||||||
|
|
||||||
|
if urlpatterns:
|
||||||
|
urls_module = types.ModuleType("urls")
|
||||||
|
urls_module.urlpatterns = urlpatterns # type: ignore
|
||||||
|
settings.ROOT_URLCONF = urls_module
|
||||||
|
else:
|
||||||
|
settings.ROOT_URLCONF = __name__
|
||||||
|
settings.SECRET_KEY = "secret" # noqa
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Component typings
|
# Component typings
|
||||||
CompArgs = Tuple[int, str]
|
CompArgs = Tuple[int, str]
|
||||||
|
|
||||||
|
@ -772,6 +791,7 @@ class ComponentRenderTest(BaseTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# See https://github.com/EmilStenstrom/django-components/issues/580
|
# See https://github.com/EmilStenstrom/django-components/issues/580
|
||||||
|
# And https://github.com/EmilStenstrom/django-components/issues/634
|
||||||
# And https://github.com/EmilStenstrom/django-components/commit/fee26ec1d8b46b5ee065ca1ce6143889b0f96764
|
# And https://github.com/EmilStenstrom/django-components/commit/fee26ec1d8b46b5ee065ca1ce6143889b0f96764
|
||||||
@parametrize_context_behavior(["django", "isolated"])
|
@parametrize_context_behavior(["django", "isolated"])
|
||||||
def test_render_with_include_and_request_context(self):
|
def test_render_with_include_and_request_context(self):
|
||||||
|
@ -793,6 +813,63 @@ class ComponentRenderTest(BaseTestCase):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# See https://github.com/EmilStenstrom/django-components/issues/580
|
||||||
|
# And https://github.com/EmilStenstrom/django-components/issues/634
|
||||||
|
@parametrize_context_behavior(["django", "isolated"])
|
||||||
|
def test_request_context_is_populated_from_context_processors(self):
|
||||||
|
@register("thing")
|
||||||
|
class Thing(Component):
|
||||||
|
template: types.django_html = """
|
||||||
|
<kbd>Rendered {{ how }}</kbd>
|
||||||
|
<div>
|
||||||
|
CSRF token: {{ csrf_token|default:"<em>No CSRF token</em>" }}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_context_data(self, *args, how: str, **kwargs):
|
||||||
|
return {"how": how}
|
||||||
|
|
||||||
|
class View(ComponentView):
|
||||||
|
def get(self, request):
|
||||||
|
how = "via GET request"
|
||||||
|
|
||||||
|
return self.component.render_to_response(
|
||||||
|
context=RequestContext(self.request),
|
||||||
|
kwargs=self.component.get_context_data(how=how),
|
||||||
|
)
|
||||||
|
|
||||||
|
client = CustomClient(urlpatterns=[path("test_thing/", Thing.as_view())])
|
||||||
|
response = client.get("/test_thing/")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Full response:
|
||||||
|
# """
|
||||||
|
# <kbd>
|
||||||
|
# Rendered via GET request
|
||||||
|
# </kbd>
|
||||||
|
# <div>
|
||||||
|
# CSRF token:
|
||||||
|
# <div>
|
||||||
|
# test_csrf_token
|
||||||
|
# </div>
|
||||||
|
# </div>
|
||||||
|
# """
|
||||||
|
self.assertInHTML(
|
||||||
|
"""
|
||||||
|
<kbd>
|
||||||
|
Rendered via GET request
|
||||||
|
</kbd>
|
||||||
|
""",
|
||||||
|
response.content.decode(),
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
@parametrize_context_behavior(["django", "isolated"])
|
@parametrize_context_behavior(["django", "isolated"])
|
||||||
def test_render_with_extends(self):
|
def test_render_with_extends(self):
|
||||||
class SimpleComponent(Component):
|
class SimpleComponent(Component):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue