mirror of
https://github.com/django-components/django-components.git
synced 2025-07-14 20:24:59 +00:00
91 lines
3.3 KiB
Python
91 lines
3.3 KiB
Python
import warnings
|
|
from copy import copy
|
|
from itertools import chain
|
|
|
|
from django.conf import settings
|
|
from django.forms.widgets import MediaDefiningClass
|
|
from django.template.base import NodeList, TokenType
|
|
from django.template.loader import get_template
|
|
from django.utils.safestring import mark_safe
|
|
from six import with_metaclass
|
|
|
|
# Allow "component.AlreadyRegistered" instead of having to import these everywhere
|
|
from django_components.component_registry import AlreadyRegistered, ComponentRegistry, NotRegistered # noqa
|
|
|
|
|
|
class Component(with_metaclass(MediaDefiningClass)):
|
|
|
|
def __init__(self, component_name):
|
|
self.__component_name = component_name
|
|
self.instance_template = None
|
|
|
|
def context(self):
|
|
return {}
|
|
|
|
def template(self, context):
|
|
raise NotImplementedError("Missing template() method on component")
|
|
|
|
def render_dependencies(self):
|
|
"""Helper function to access media.render()"""
|
|
|
|
return self.media.render()
|
|
|
|
def render_css_dependencies(self):
|
|
"""Render only CSS dependencies available in the media class."""
|
|
|
|
return mark_safe("\n".join(self.media.render_css()))
|
|
|
|
def render_js_dependencies(self):
|
|
"""Render only JS dependencies available in the media class."""
|
|
|
|
return mark_safe("\n".join(self.media.render_js()))
|
|
|
|
@staticmethod
|
|
def slots_in_template(template):
|
|
return {node.name: node.nodelist for node in template.template.nodelist if is_slot_node(node)}
|
|
|
|
def compile_instance_template(self, slots_for_instance):
|
|
"""Use component's base template and the slots used for this instance to compile
|
|
a unified template for this instance."""
|
|
|
|
component_template = get_template(self.template({}))
|
|
slots_in_template = self.slots_in_template(component_template)
|
|
|
|
defined_slot_names = set(slots_in_template.keys())
|
|
filled_slot_names = set(slots_for_instance.keys())
|
|
unexpected_slots = filled_slot_names - defined_slot_names
|
|
if unexpected_slots:
|
|
if settings.DEBUG:
|
|
warnings.warn(
|
|
"Component {} was provided with unexpected slots: {}".format(
|
|
self.__component_name, unexpected_slots
|
|
)
|
|
)
|
|
for unexpected_slot in unexpected_slots:
|
|
del slots_for_instance[unexpected_slot]
|
|
|
|
combined_slots = dict(slots_in_template, **slots_for_instance)
|
|
if combined_slots:
|
|
# Replace slot nodes with their nodelists, then combine into a single, flat nodelist
|
|
node_iterator = ([node] if not is_slot_node(node) else combined_slots[node.name]
|
|
for node in component_template.template.nodelist)
|
|
|
|
self.instance_template = copy(component_template.template)
|
|
self.instance_template.nodelist = NodeList(chain.from_iterable(node_iterator))
|
|
else:
|
|
self.instance_template = component_template.template
|
|
|
|
def render(self, context):
|
|
return self.instance_template.render(context)
|
|
|
|
class Media:
|
|
css = {}
|
|
js = []
|
|
|
|
|
|
def is_slot_node(node):
|
|
return node.token.token_type == TokenType.BLOCK and node.token.split_contents()[0] == "slot"
|
|
|
|
|
|
# This variable represents the global component registry
|
|
registry = ComponentRegistry()
|