mirror of
https://github.com/django-components/django-components.git
synced 2025-09-17 03:15:02 +00:00
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
This commit is contained in:
parent
15a0e66219
commit
0648ad9a93
14 changed files with 983 additions and 432 deletions
|
@ -9,10 +9,16 @@ from django.template.loader import get_template
|
|||
from django.utils.safestring import mark_safe
|
||||
|
||||
# Allow "component.AlreadyRegistered" instead of having to import these everywhere
|
||||
from django_components.component_registry import AlreadyRegistered, ComponentRegistry, NotRegistered # noqa
|
||||
from django_components.component_registry import ( # noqa
|
||||
AlreadyRegistered,
|
||||
ComponentRegistry,
|
||||
NotRegistered,
|
||||
)
|
||||
|
||||
TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get('TEMPLATE_CACHE_SIZE', 128)
|
||||
ACTIVE_SLOT_CONTEXT_KEY = '_DJANGO_COMPONENTS_ACTIVE_SLOTS'
|
||||
TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get(
|
||||
"TEMPLATE_CACHE_SIZE", 128
|
||||
)
|
||||
ACTIVE_SLOT_CONTEXT_KEY = "_DJANGO_COMPONENTS_ACTIVE_SLOTS"
|
||||
|
||||
|
||||
class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass):
|
||||
|
@ -54,7 +60,9 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
|
|||
|
||||
def get_template_name(self, context=None):
|
||||
if not self.template_name:
|
||||
raise ImproperlyConfigured(f'Template name is not set for Component {self.__class__.__name__}')
|
||||
raise ImproperlyConfigured(
|
||||
f"Template name is not set for Component {self.__class__.__name__}"
|
||||
)
|
||||
|
||||
return self.template_name
|
||||
|
||||
|
@ -75,13 +83,19 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
|
|||
|
||||
@staticmethod
|
||||
def slots_in_template(template):
|
||||
return {node.name: node.nodelist for node in template.template.nodelist if Component.is_slot_node(node)}
|
||||
return {
|
||||
node.name: node.nodelist
|
||||
for node in template.template.nodelist
|
||||
if Component.is_slot_node(node)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def is_slot_node(node):
|
||||
return (isinstance(node, Node)
|
||||
and node.token.token_type == TokenType.BLOCK
|
||||
and node.token.split_contents()[0] == "slot")
|
||||
return (
|
||||
isinstance(node, Node)
|
||||
and node.token.token_type == TokenType.BLOCK
|
||||
and node.token.split_contents()[0] == "slot"
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=TEMPLATE_CACHE_SIZE)
|
||||
def get_processed_template(self, template_name):
|
||||
|
@ -117,16 +131,16 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
|
|||
return component_template
|
||||
|
||||
def render(self, context):
|
||||
if hasattr(self, 'context'):
|
||||
if hasattr(self, "context"):
|
||||
warnings.warn(
|
||||
f'{self.__class__.__name__}: `context` method is deprecated, use `get_context` instead',
|
||||
DeprecationWarning
|
||||
f"{self.__class__.__name__}: `context` method is deprecated, use `get_context` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
if hasattr(self, 'template'):
|
||||
if hasattr(self, "template"):
|
||||
warnings.warn(
|
||||
f'{self.__class__.__name__}: `template` method is deprecated, set `template_name` or override `get_template_name` instead',
|
||||
DeprecationWarning
|
||||
f"{self.__class__.__name__}: `template` method is deprecated, set `template_name` or override `get_template_name` instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
template_name = self.template(context)
|
||||
else:
|
||||
|
|
|
@ -12,7 +12,9 @@ class ComponentRegistry(object):
|
|||
|
||||
def register(self, name=None, component=None):
|
||||
if name in self._registry:
|
||||
raise AlreadyRegistered('The component "%s" is already registered' % name)
|
||||
raise AlreadyRegistered(
|
||||
'The component "%s" is already registered' % name
|
||||
)
|
||||
|
||||
self._registry[name] = component
|
||||
|
||||
|
|
|
@ -8,11 +8,13 @@ RENDERED_COMPONENTS_CONTEXT_KEY = "_COMPONENT_DEPENDENCIES"
|
|||
CSS_DEPENDENCY_PLACEHOLDER = '<link name="CSS_PLACEHOLDER">'
|
||||
JS_DEPENDENCY_PLACEHOLDER = '<script name="JS_PLACEHOLDER">'
|
||||
|
||||
SCRIPT_TAG_REGEX = re.compile('<script')
|
||||
COMPONENT_COMMENT_REGEX = re.compile(rb'<!-- _RENDERED (?P<name>\w+?) -->')
|
||||
PLACEHOLDER_REGEX = re.compile(rb'<!-- _RENDERED (?P<name>\w+?) -->'
|
||||
rb'|<link name="CSS_PLACEHOLDER">'
|
||||
rb'|<script name="JS_PLACEHOLDER">')
|
||||
SCRIPT_TAG_REGEX = re.compile("<script")
|
||||
COMPONENT_COMMENT_REGEX = re.compile(rb"<!-- _RENDERED (?P<name>\w+?) -->")
|
||||
PLACEHOLDER_REGEX = re.compile(
|
||||
rb"<!-- _RENDERED (?P<name>\w+?) -->"
|
||||
rb'|<link name="CSS_PLACEHOLDER">'
|
||||
rb'|<script name="JS_PLACEHOLDER">'
|
||||
)
|
||||
|
||||
|
||||
class ComponentDependencyMiddleware:
|
||||
|
@ -25,9 +27,13 @@ class ComponentDependencyMiddleware:
|
|||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
if getattr(settings, "COMPONENTS", {}).get('RENDER_DEPENDENCIES', False)\
|
||||
and not isinstance(response, StreamingHttpResponse)\
|
||||
and response['Content-Type'].startswith('text/html'):
|
||||
if (
|
||||
getattr(settings, "COMPONENTS", {}).get(
|
||||
"RENDER_DEPENDENCIES", False
|
||||
)
|
||||
and not isinstance(response, StreamingHttpResponse)
|
||||
and response["Content-Type"].startswith("text/html")
|
||||
):
|
||||
response.content = process_response_content(response.content)
|
||||
return response
|
||||
|
||||
|
@ -35,12 +41,23 @@ class ComponentDependencyMiddleware:
|
|||
def process_response_content(content):
|
||||
from django_components.component import registry
|
||||
|
||||
component_names_seen = {match.group('name') for match in COMPONENT_COMMENT_REGEX.finditer(content)}
|
||||
all_components = [registry.get(name.decode('utf-8'))('') for name in component_names_seen]
|
||||
component_names_seen = {
|
||||
match.group("name")
|
||||
for match in COMPONENT_COMMENT_REGEX.finditer(content)
|
||||
}
|
||||
all_components = [
|
||||
registry.get(name.decode("utf-8"))("") for name in component_names_seen
|
||||
]
|
||||
all_media = join_media(all_components)
|
||||
js_dependencies = b''.join(media.encode('utf-8') for media in all_media.render_js())
|
||||
css_dependencies = b''.join(media.encode('utf-8') for media in all_media.render_css())
|
||||
return PLACEHOLDER_REGEX.sub(DependencyReplacer(css_dependencies, js_dependencies), content)
|
||||
js_dependencies = b"".join(
|
||||
media.encode("utf-8") for media in all_media.render_js()
|
||||
)
|
||||
css_dependencies = b"".join(
|
||||
media.encode("utf-8") for media in all_media.render_css()
|
||||
)
|
||||
return PLACEHOLDER_REGEX.sub(
|
||||
DependencyReplacer(css_dependencies, js_dependencies), content
|
||||
)
|
||||
|
||||
|
||||
def add_module_attribute_to_scripts(scripts):
|
||||
|
@ -51,8 +68,8 @@ class DependencyReplacer:
|
|||
"""Replacer for use in re.sub that replaces the first placeholder CSS and JS
|
||||
tags it encounters and removes any subsequent ones."""
|
||||
|
||||
CSS_PLACEHOLDER = bytes(CSS_DEPENDENCY_PLACEHOLDER, encoding='utf-8')
|
||||
JS_PLACEHOLDER = bytes(JS_DEPENDENCY_PLACEHOLDER, encoding='utf-8')
|
||||
CSS_PLACEHOLDER = bytes(CSS_DEPENDENCY_PLACEHOLDER, encoding="utf-8")
|
||||
JS_PLACEHOLDER = bytes(JS_DEPENDENCY_PLACEHOLDER, encoding="utf-8")
|
||||
|
||||
def __init__(self, css_string, js_string):
|
||||
self.js_string = js_string
|
||||
|
@ -64,7 +81,7 @@ class DependencyReplacer:
|
|||
elif match[0] == self.JS_PLACEHOLDER:
|
||||
replacement, self.js_string = self.js_string, b""
|
||||
else:
|
||||
replacement = b''
|
||||
replacement = b""
|
||||
return replacement
|
||||
|
||||
|
||||
|
|
|
@ -8,4 +8,4 @@ from django.template.utils import get_app_template_dirs
|
|||
|
||||
class Loader(FilesystemLoader):
|
||||
def get_dirs(self):
|
||||
return get_app_template_dirs('components')
|
||||
return get_app_template_dirs("components")
|
||||
|
|
|
@ -7,7 +7,10 @@ from django.template.library import parse_bits
|
|||
from django.utils.safestring import mark_safe
|
||||
|
||||
from django_components.component import ACTIVE_SLOT_CONTEXT_KEY, registry
|
||||
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
|
||||
from django_components.middleware import (
|
||||
CSS_DEPENDENCY_PLACEHOLDER,
|
||||
JS_DEPENDENCY_PLACEHOLDER,
|
||||
)
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -29,7 +32,9 @@ def component_dependencies_tag():
|
|||
"""Marks location where CSS link and JS script tags should be rendered."""
|
||||
|
||||
if is_dependency_middleware_active():
|
||||
return mark_safe(CSS_DEPENDENCY_PLACEHOLDER + JS_DEPENDENCY_PLACEHOLDER)
|
||||
return mark_safe(
|
||||
CSS_DEPENDENCY_PLACEHOLDER + JS_DEPENDENCY_PLACEHOLDER
|
||||
)
|
||||
else:
|
||||
rendered_dependencies = []
|
||||
for component in get_components_from_registry(registry):
|
||||
|
@ -66,12 +71,19 @@ def component_js_dependencies_tag():
|
|||
return mark_safe("\n".join(rendered_dependencies))
|
||||
|
||||
|
||||
@register.tag(name='component')
|
||||
@register.tag(name="component")
|
||||
def do_component(parser, token):
|
||||
bits = token.split_contents()
|
||||
bits, isolated_context = check_for_isolated_context_keyword(bits)
|
||||
component, context_args, context_kwargs = parse_component_with_args(parser, bits, 'component')
|
||||
return ComponentNode(component, context_args, context_kwargs, isolated_context=isolated_context)
|
||||
component, context_args, context_kwargs = parse_component_with_args(
|
||||
parser, bits, "component"
|
||||
)
|
||||
return ComponentNode(
|
||||
component,
|
||||
context_args,
|
||||
context_kwargs,
|
||||
isolated_context=isolated_context,
|
||||
)
|
||||
|
||||
|
||||
class SlotNode(Node):
|
||||
|
@ -92,17 +104,25 @@ class SlotNode(Node):
|
|||
cloned_node.parent_component = self.parent_component
|
||||
cloned_node.context = context
|
||||
|
||||
with context.update({'slot': cloned_node}):
|
||||
with context.update({"slot": cloned_node}):
|
||||
return self.get_nodelist(context).render(context)
|
||||
|
||||
def get_nodelist(self, context):
|
||||
if ACTIVE_SLOT_CONTEXT_KEY not in context:
|
||||
raise TemplateSyntaxError(f'Attempted to render SlotNode {self.name} outside of a parent Component or '
|
||||
'without access to context provided by its parent Component. This will not'
|
||||
'work properly.')
|
||||
raise TemplateSyntaxError(
|
||||
f"Attempted to render SlotNode {self.name} outside of a parent Component or "
|
||||
"without access to context provided by its parent Component. This will not"
|
||||
"work properly."
|
||||
)
|
||||
|
||||
overriding_nodelist = context[ACTIVE_SLOT_CONTEXT_KEY].get(self.name, None)
|
||||
return overriding_nodelist if overriding_nodelist is not None else self.nodelist
|
||||
overriding_nodelist = context[ACTIVE_SLOT_CONTEXT_KEY].get(
|
||||
self.name, None
|
||||
)
|
||||
return (
|
||||
overriding_nodelist
|
||||
if overriding_nodelist is not None
|
||||
else self.nodelist
|
||||
)
|
||||
|
||||
def super(self):
|
||||
"""Render default slot content."""
|
||||
|
@ -125,9 +145,18 @@ def do_slot(parser, token, component=None):
|
|||
class ComponentNode(Node):
|
||||
class InvalidSlot:
|
||||
def super(self):
|
||||
raise TemplateSyntaxError('slot.super may only be called within a {% slot %}/{% endslot %} block.')
|
||||
raise TemplateSyntaxError(
|
||||
"slot.super may only be called within a {% slot %}/{% endslot %} block."
|
||||
)
|
||||
|
||||
def __init__(self, component, context_args, context_kwargs, slots=None, isolated_context=False):
|
||||
def __init__(
|
||||
self,
|
||||
component,
|
||||
context_args,
|
||||
context_kwargs,
|
||||
slots=None,
|
||||
isolated_context=False,
|
||||
):
|
||||
self.context_args = context_args or []
|
||||
self.context_kwargs = context_kwargs or {}
|
||||
self.component, self.isolated_context = component, isolated_context
|
||||
|
@ -139,17 +168,26 @@ class ComponentNode(Node):
|
|||
self.should_render_dependencies = is_dependency_middleware_active()
|
||||
|
||||
def __repr__(self):
|
||||
return "<Component Node: %s. Contents: %r>" % (self.component,
|
||||
getattr(self.component.instance_template, 'nodelist', None))
|
||||
return "<Component Node: %s. Contents: %r>" % (
|
||||
self.component,
|
||||
getattr(self.component.instance_template, "nodelist", None),
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
self.component.outer_context = context.flatten()
|
||||
|
||||
# Resolve FilterExpressions and Variables that were passed as args to the component, then call component's
|
||||
# context method to get values to insert into the context
|
||||
resolved_context_args = [safe_resolve(arg, context) for arg in self.context_args]
|
||||
resolved_context_kwargs = {key: safe_resolve(kwarg, context) for key, kwarg in self.context_kwargs.items()}
|
||||
component_context = self.component.get_context_data(*resolved_context_args, **resolved_context_kwargs)
|
||||
resolved_context_args = [
|
||||
safe_resolve(arg, context) for arg in self.context_args
|
||||
]
|
||||
resolved_context_kwargs = {
|
||||
key: safe_resolve(kwarg, context)
|
||||
for key, kwarg in self.context_kwargs.items()
|
||||
}
|
||||
component_context = self.component.get_context_data(
|
||||
*resolved_context_args, **resolved_context_kwargs
|
||||
)
|
||||
|
||||
# Create a fresh context if requested
|
||||
if self.isolated_context:
|
||||
|
@ -158,7 +196,10 @@ class ComponentNode(Node):
|
|||
with context.update(component_context):
|
||||
rendered_component = self.component.render(context)
|
||||
if self.should_render_dependencies:
|
||||
return f'<!-- _RENDERED {self.component._component_name} -->' + rendered_component
|
||||
return (
|
||||
f"<!-- _RENDERED {self.component._component_name} -->"
|
||||
+ rendered_component
|
||||
)
|
||||
else:
|
||||
return rendered_component
|
||||
|
||||
|
@ -181,12 +222,20 @@ def do_component_block(parser, token):
|
|||
bits = token.split_contents()
|
||||
bits, isolated_context = check_for_isolated_context_keyword(bits)
|
||||
|
||||
component, context_args, context_kwargs = parse_component_with_args(parser, bits, 'component_block')
|
||||
component, context_args, context_kwargs = parse_component_with_args(
|
||||
parser, bits, "component_block"
|
||||
)
|
||||
|
||||
return ComponentNode(component, context_args, context_kwargs,
|
||||
slots=[do_slot(parser, slot_token, component=component)
|
||||
for slot_token in slot_tokens(parser)],
|
||||
isolated_context=isolated_context)
|
||||
return ComponentNode(
|
||||
component,
|
||||
context_args,
|
||||
context_kwargs,
|
||||
slots=[
|
||||
do_slot(parser, slot_token, component=component)
|
||||
for slot_token in slot_tokens(parser)
|
||||
],
|
||||
isolated_context=isolated_context,
|
||||
)
|
||||
|
||||
|
||||
def slot_tokens(parser):
|
||||
|
@ -195,28 +244,37 @@ def slot_tokens(parser):
|
|||
Raises TemplateSyntaxError if there are other content tokens or if there is no endcomponent_block token."""
|
||||
|
||||
def is_whitespace(token):
|
||||
return token.token_type == TokenType.TEXT and not token.contents.strip()
|
||||
return (
|
||||
token.token_type == TokenType.TEXT and not token.contents.strip()
|
||||
)
|
||||
|
||||
def is_block_tag(token, name):
|
||||
return token.token_type == TokenType.BLOCK and token.split_contents()[0] == name
|
||||
return (
|
||||
token.token_type == TokenType.BLOCK
|
||||
and token.split_contents()[0] == name
|
||||
)
|
||||
|
||||
while True:
|
||||
try:
|
||||
token = parser.next_token()
|
||||
except IndexError:
|
||||
raise TemplateSyntaxError('Unclosed component_block tag')
|
||||
if is_block_tag(token, name='endcomponent_block'):
|
||||
raise TemplateSyntaxError("Unclosed component_block tag")
|
||||
if is_block_tag(token, name="endcomponent_block"):
|
||||
return
|
||||
elif is_block_tag(token, name='slot'):
|
||||
elif is_block_tag(token, name="slot"):
|
||||
yield token
|
||||
elif not is_whitespace(token) and token.token_type != TokenType.COMMENT:
|
||||
raise TemplateSyntaxError(f'Content tokens in component blocks must be inside of slot tags: {token}')
|
||||
elif (
|
||||
not is_whitespace(token) and token.token_type != TokenType.COMMENT
|
||||
):
|
||||
raise TemplateSyntaxError(
|
||||
f"Content tokens in component blocks must be inside of slot tags: {token}"
|
||||
)
|
||||
|
||||
|
||||
def check_for_isolated_context_keyword(bits):
|
||||
"""Return True and strip the last word if token ends with 'only' keyword."""
|
||||
|
||||
if bits[-1] == 'only':
|
||||
if bits[-1] == "only":
|
||||
return bits[:-1], True
|
||||
return bits, False
|
||||
|
||||
|
@ -235,20 +293,26 @@ def parse_component_with_args(parser, bits, tag_name):
|
|||
kwonly_defaults=None,
|
||||
)
|
||||
|
||||
assert tag_name == tag_args[0].token, "Internal error: Expected tag_name to be {}, but it was {}".format(
|
||||
tag_name, tag_args[0].token)
|
||||
if len(tag_args) > 1: # At least one position arg, so take the first as the component name
|
||||
assert (
|
||||
tag_name == tag_args[0].token
|
||||
), "Internal error: Expected tag_name to be {}, but it was {}".format(
|
||||
tag_name, tag_args[0].token
|
||||
)
|
||||
if (
|
||||
len(tag_args) > 1
|
||||
): # At least one position arg, so take the first as the component name
|
||||
component_name = tag_args[1].token
|
||||
context_args = tag_args[2:]
|
||||
context_kwargs = tag_kwargs
|
||||
else: # No positional args, so look for component name as keyword arg
|
||||
try:
|
||||
component_name = tag_kwargs.pop('name').token
|
||||
component_name = tag_kwargs.pop("name").token
|
||||
context_args = []
|
||||
context_kwargs = tag_kwargs
|
||||
except IndexError:
|
||||
raise TemplateSyntaxError(
|
||||
"Call the '%s' tag with a component name as the first parameter" % tag_name
|
||||
"Call the '%s' tag with a component name as the first parameter"
|
||||
% tag_name
|
||||
)
|
||||
|
||||
if not is_wrapped_in_quotes(component_name):
|
||||
|
@ -256,7 +320,7 @@ def parse_component_with_args(parser, bits, tag_name):
|
|||
"Component name '%s' should be in quotes" % component_name
|
||||
)
|
||||
|
||||
trimmed_component_name = component_name[1: -1]
|
||||
trimmed_component_name = component_name[1:-1]
|
||||
component_class = registry.get(trimmed_component_name)
|
||||
component = component_class(trimmed_component_name)
|
||||
|
||||
|
@ -266,7 +330,11 @@ def parse_component_with_args(parser, bits, tag_name):
|
|||
def safe_resolve(context_item, context):
|
||||
"""Resolve FilterExpressions and Variables in context if possible. Return other items unchanged."""
|
||||
|
||||
return context_item.resolve(context) if hasattr(context_item, 'resolve') else context_item
|
||||
return (
|
||||
context_item.resolve(context)
|
||||
if hasattr(context_item, "resolve")
|
||||
else context_item
|
||||
)
|
||||
|
||||
|
||||
def is_wrapped_in_quotes(s):
|
||||
|
@ -274,4 +342,6 @@ def is_wrapped_in_quotes(s):
|
|||
|
||||
|
||||
def is_dependency_middleware_active():
|
||||
return getattr(settings, "COMPONENTS", {}).get('RENDER_DEPENDENCIES', False)
|
||||
return getattr(settings, "COMPONENTS", {}).get(
|
||||
"RENDER_DEPENDENCIES", False
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue