mirror of
https://github.com/django-components/django-components.git
synced 2025-08-31 19:27:19 +00:00
Merge branch 'master' into dependabot/pip/pyyaml-env-tag-1.1
This commit is contained in:
commit
4f15ad8360
3 changed files with 141 additions and 120 deletions
|
@ -29,7 +29,6 @@ from django.template.context import Context, RequestContext
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.template.loader_tags import BLOCK_CONTEXT_KEY, BlockContext
|
from django.template.loader_tags import BLOCK_CONTEXT_KEY, BlockContext
|
||||||
from django.test.signals import template_rendered
|
from django.test.signals import template_rendered
|
||||||
from django.utils.html import conditional_escape
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from django_components.app_settings import ContextBehavior, app_settings
|
from django_components.app_settings import ContextBehavior, app_settings
|
||||||
|
@ -67,13 +66,11 @@ from django_components.perfutil.provide import register_provide_reference, unreg
|
||||||
from django_components.provide import get_injected_context_var
|
from django_components.provide import get_injected_context_var
|
||||||
from django_components.slots import (
|
from django_components.slots import (
|
||||||
Slot,
|
Slot,
|
||||||
SlotFallback,
|
|
||||||
SlotFunc,
|
|
||||||
SlotInput,
|
|
||||||
SlotIsFilled,
|
SlotIsFilled,
|
||||||
SlotName,
|
SlotName,
|
||||||
SlotResult,
|
SlotResult,
|
||||||
_is_extracting_fill,
|
_is_extracting_fill,
|
||||||
|
normalize_slot_fills,
|
||||||
resolve_fills,
|
resolve_fills,
|
||||||
)
|
)
|
||||||
from django_components.template import cached_template
|
from django_components.template import cached_template
|
||||||
|
@ -2335,7 +2332,7 @@ class Component(metaclass=ComponentMeta):
|
||||||
# without affecting the original values.
|
# without affecting the original values.
|
||||||
args_list = list(args) if args is not None else []
|
args_list = list(args) if args is not None else []
|
||||||
kwargs_dict = to_dict(kwargs) if kwargs is not None else {}
|
kwargs_dict = to_dict(kwargs) if kwargs is not None else {}
|
||||||
slots_dict = self._normalize_slot_fills(
|
slots_dict = normalize_slot_fills(
|
||||||
to_dict(slots) if slots is not None else {},
|
to_dict(slots) if slots is not None else {},
|
||||||
escape_slots_content,
|
escape_slots_content,
|
||||||
)
|
)
|
||||||
|
@ -2704,75 +2701,6 @@ class Component(metaclass=ComponentMeta):
|
||||||
|
|
||||||
return template_data, js_data, css_data
|
return template_data, js_data, css_data
|
||||||
|
|
||||||
def _normalize_slot_fills(
|
|
||||||
self,
|
|
||||||
fills: Mapping[SlotName, SlotInput],
|
|
||||||
escape_content: bool = True,
|
|
||||||
) -> Dict[SlotName, Slot]:
|
|
||||||
# Preprocess slots to escape content if `escape_content=True`
|
|
||||||
norm_fills = {}
|
|
||||||
|
|
||||||
# NOTE: `gen_escaped_content_func` is defined as a separate function, instead of being inlined within
|
|
||||||
# the forloop, because the value the forloop variable points to changes with each loop iteration.
|
|
||||||
def gen_escaped_content_func(content: SlotFunc, slot_name: str) -> Slot:
|
|
||||||
# Case: Already Slot, already escaped, and names assigned, so nothing to do.
|
|
||||||
if isinstance(content, Slot) and content.escaped and content.slot_name and content.component_name:
|
|
||||||
return content
|
|
||||||
|
|
||||||
# Otherwise, we create a new instance of Slot, whether `content` was already Slot or not.
|
|
||||||
# so we can assign metadata to our internal copies.
|
|
||||||
if not isinstance(content, Slot) or not content.escaped:
|
|
||||||
# We wrap the original function so we post-process it by escaping the result.
|
|
||||||
def content_fn(ctx: Context, slot_data: Dict, fallback: SlotFallback) -> SlotResult:
|
|
||||||
rendered = content(ctx, slot_data, fallback)
|
|
||||||
return conditional_escape(rendered) if escape_content else rendered
|
|
||||||
|
|
||||||
content_func = cast(SlotFunc, content_fn)
|
|
||||||
else:
|
|
||||||
content_func = content.content_func
|
|
||||||
|
|
||||||
# Populate potentially missing fields so we can trace the component and slot
|
|
||||||
if isinstance(content, Slot):
|
|
||||||
used_component_name = content.component_name or self.name
|
|
||||||
used_slot_name = content.slot_name or slot_name
|
|
||||||
used_nodelist = content.nodelist
|
|
||||||
used_contents = content.contents if content.contents is not None else content_func
|
|
||||||
else:
|
|
||||||
used_component_name = self.name
|
|
||||||
used_slot_name = slot_name
|
|
||||||
used_nodelist = None
|
|
||||||
used_contents = content_func
|
|
||||||
|
|
||||||
slot = Slot(
|
|
||||||
contents=used_contents,
|
|
||||||
content_func=content_func,
|
|
||||||
component_name=used_component_name,
|
|
||||||
slot_name=used_slot_name,
|
|
||||||
nodelist=used_nodelist,
|
|
||||||
escaped=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return slot
|
|
||||||
|
|
||||||
for slot_name, content in fills.items():
|
|
||||||
# Case: No content, so nothing to do.
|
|
||||||
if content is None:
|
|
||||||
continue
|
|
||||||
# Case: Content is a string / scalar
|
|
||||||
elif not callable(content):
|
|
||||||
escaped_content = conditional_escape(content) if escape_content else content
|
|
||||||
# NOTE: `Slot.content_func` and `Slot.nodelist` are set in `Slot.__init__()`
|
|
||||||
slot: Slot = Slot(
|
|
||||||
contents=escaped_content, component_name=self.name, slot_name=slot_name, escaped=True
|
|
||||||
)
|
|
||||||
# Case: Content is a callable, so either a plain function or a `Slot` instance.
|
|
||||||
else:
|
|
||||||
slot = gen_escaped_content_func(content, slot_name)
|
|
||||||
|
|
||||||
norm_fills[slot_name] = slot
|
|
||||||
|
|
||||||
return norm_fills
|
|
||||||
|
|
||||||
|
|
||||||
# Perf
|
# Perf
|
||||||
# Each component may use different start and end tags. We represent this
|
# Each component may use different start and end tags. We represent this
|
||||||
|
|
|
@ -23,6 +23,7 @@ from typing import (
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.template.base import NodeList, TextNode
|
from django.template.base import NodeList, TextNode
|
||||||
from django.template.exceptions import TemplateSyntaxError
|
from django.template.exceptions import TemplateSyntaxError
|
||||||
|
from django.utils.html import conditional_escape
|
||||||
from django.utils.safestring import SafeString, mark_safe
|
from django.utils.safestring import SafeString, mark_safe
|
||||||
|
|
||||||
from django_components.app_settings import ContextBehavior, app_settings
|
from django_components.app_settings import ContextBehavior, app_settings
|
||||||
|
@ -50,6 +51,7 @@ FILL_FALLBACK_KWARG = "fallback"
|
||||||
|
|
||||||
# Public types
|
# Public types
|
||||||
SlotResult = Union[str, SafeString]
|
SlotResult = Union[str, SafeString]
|
||||||
|
"""Type representing the result of a slot render function."""
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
|
@ -137,6 +139,8 @@ class Slot(Generic[TSlotData]):
|
||||||
# otherwise Pydantic has problem resolving the types.
|
# otherwise Pydantic has problem resolving the types.
|
||||||
SlotInput = Union[SlotResult, SlotFunc[TSlotData], Slot[TSlotData]]
|
SlotInput = Union[SlotResult, SlotFunc[TSlotData], Slot[TSlotData]]
|
||||||
"""
|
"""
|
||||||
|
Type representing all forms in which slot content can be passed to a component.
|
||||||
|
|
||||||
When rendering a component with [`Component.render()`](../api#django_components.Component.render)
|
When rendering a component with [`Component.render()`](../api#django_components.Component.render)
|
||||||
or [`Component.render_to_response()`](../api#django_components.Component.render_to_response),
|
or [`Component.render_to_response()`](../api#django_components.Component.render_to_response),
|
||||||
the slots may be given a strings, functions, or [`Slot`](../api#django_components.Slot) instances.
|
the slots may be given a strings, functions, or [`Slot`](../api#django_components.Slot) instances.
|
||||||
|
@ -163,6 +167,25 @@ class Table(Component):
|
||||||
footer: SlotInput[TableFooterSlotData]
|
footer: SlotInput[TableFooterSlotData]
|
||||||
|
|
||||||
template = "<div>{% slot 'footer' %}</div>"
|
template = "<div>{% slot 'footer' %}</div>"
|
||||||
|
|
||||||
|
html = Table.render(
|
||||||
|
slots={
|
||||||
|
# As a string
|
||||||
|
"header": "Hello, World!",
|
||||||
|
|
||||||
|
# Safe string
|
||||||
|
"header": mark_safe("<i><am><safe>"),
|
||||||
|
|
||||||
|
# Function
|
||||||
|
"footer": lambda ctx, slot_data, slot_ref: f"Page: {slot_data['page_number']}!",
|
||||||
|
|
||||||
|
# Slot instance
|
||||||
|
"footer": Slot(lambda ctx, slot_data, slot_ref: f"Page: {slot_data['page_number']}!"),
|
||||||
|
|
||||||
|
# None (Same as no slot)
|
||||||
|
"header": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
# TODO_V1 - REMOVE, superseded by SlotInput
|
# TODO_V1 - REMOVE, superseded by SlotInput
|
||||||
|
@ -176,21 +199,6 @@ DEPRECATED: Use [`SlotInput`](../api#django_components.SlotInput) instead. Will
|
||||||
SlotName = str
|
SlotName = str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SlotFill(Generic[TSlotData]):
|
|
||||||
"""
|
|
||||||
SlotFill describes what WILL be rendered.
|
|
||||||
|
|
||||||
The fill may be provided by the user from the outside (`is_filled=True`),
|
|
||||||
or it may be the default content of the slot (`is_filled=False`).
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
"""Name of the slot."""
|
|
||||||
is_filled: bool
|
|
||||||
slot: Slot[TSlotData]
|
|
||||||
|
|
||||||
|
|
||||||
class SlotFallback:
|
class SlotFallback:
|
||||||
"""
|
"""
|
||||||
SlotFallback allows to treat a slot fallback as a variable. The slot is rendered only once
|
SlotFallback allows to treat a slot fallback as a variable. The slot is rendered only once
|
||||||
|
@ -551,27 +559,20 @@ class SlotNode(BaseNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
if fill_name in slot_fills:
|
if fill_name in slot_fills:
|
||||||
slot_fill_fn = slot_fills[fill_name]
|
slot_is_filled = True
|
||||||
slot_fill = SlotFill(
|
slot = slot_fills[fill_name]
|
||||||
name=slot_name,
|
|
||||||
is_filled=True,
|
|
||||||
slot=slot_fill_fn,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# No fill was supplied, render the slot's fallback content
|
# No fill was supplied, render the slot's fallback content
|
||||||
slot_fill = SlotFill(
|
slot_is_filled = False
|
||||||
name=slot_name,
|
slot = _nodelist_to_slot(
|
||||||
is_filled=False,
|
component_name=component_name,
|
||||||
slot=_nodelist_to_slot(
|
slot_name=slot_name,
|
||||||
component_name=component_name,
|
nodelist=self.nodelist,
|
||||||
slot_name=slot_name,
|
contents=self.contents,
|
||||||
nodelist=self.nodelist,
|
data_var=None,
|
||||||
contents=self.contents,
|
fallback_var=None,
|
||||||
data_var=None,
|
# Escaped because this was defined in the template
|
||||||
fallback_var=None,
|
escaped=True,
|
||||||
# Escaped because this was defined in the template
|
|
||||||
escaped=True,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check: If a slot is marked as 'required', it must be filled.
|
# Check: If a slot is marked as 'required', it must be filled.
|
||||||
|
@ -584,7 +585,7 @@ class SlotNode(BaseNode):
|
||||||
# Note: Finding a good `cutoff` value may require further trial-and-error.
|
# Note: Finding a good `cutoff` value may require further trial-and-error.
|
||||||
# Higher values make matching stricter. This is probably preferable, as it
|
# Higher values make matching stricter. This is probably preferable, as it
|
||||||
# reduces false positives.
|
# reduces false positives.
|
||||||
if is_required and not slot_fill.is_filled and not component_ctx.is_dynamic_component:
|
if is_required and not slot_is_filled and not component_ctx.is_dynamic_component:
|
||||||
msg = (
|
msg = (
|
||||||
f"Slot '{slot_name}' is marked as 'required' (i.e. non-optional), "
|
f"Slot '{slot_name}' is marked as 'required' (i.e. non-optional), "
|
||||||
f"yet no fill is provided. Check template.'"
|
f"yet no fill is provided. Check template.'"
|
||||||
|
@ -640,7 +641,7 @@ class SlotNode(BaseNode):
|
||||||
|
|
||||||
# For the user-provided slot fill, we want to use the context of where the slot
|
# For the user-provided slot fill, we want to use the context of where the slot
|
||||||
# came from (or current context if configured so)
|
# came from (or current context if configured so)
|
||||||
used_ctx = self._resolve_slot_context(context, slot_fill, component_ctx)
|
used_ctx = self._resolve_slot_context(context, slot_is_filled, component_ctx)
|
||||||
with used_ctx.update(extra_context):
|
with used_ctx.update(extra_context):
|
||||||
# Required for compatibility with Django's {% extends %} tag
|
# Required for compatibility with Django's {% extends %} tag
|
||||||
# This makes sure that the render context used outside of a component
|
# This makes sure that the render context used outside of a component
|
||||||
|
@ -657,7 +658,7 @@ class SlotNode(BaseNode):
|
||||||
# Render slot as a function
|
# Render slot as a function
|
||||||
# NOTE: While `{% fill %}` tag has to opt in for the `fallback` and `data` variables,
|
# NOTE: While `{% fill %}` tag has to opt in for the `fallback` and `data` variables,
|
||||||
# the render function ALWAYS receives them.
|
# the render function ALWAYS receives them.
|
||||||
output = slot_fill.slot(used_ctx, kwargs, slot_ref)
|
output = slot(used_ctx, kwargs, slot_ref)
|
||||||
|
|
||||||
if app_settings.DEBUG_HIGHLIGHT_SLOTS:
|
if app_settings.DEBUG_HIGHLIGHT_SLOTS:
|
||||||
output = apply_component_highlight("slot", output, f"{component_name} - {slot_name}")
|
output = apply_component_highlight("slot", output, f"{component_name} - {slot_name}")
|
||||||
|
@ -676,14 +677,14 @@ class SlotNode(BaseNode):
|
||||||
def _resolve_slot_context(
|
def _resolve_slot_context(
|
||||||
self,
|
self,
|
||||||
context: Context,
|
context: Context,
|
||||||
slot_fill: "SlotFill",
|
slot_is_filled: bool,
|
||||||
component_ctx: "ComponentContext",
|
component_ctx: "ComponentContext",
|
||||||
) -> Context:
|
) -> Context:
|
||||||
"""Prepare the context used in a slot fill based on the settings."""
|
"""Prepare the context used in a slot fill based on the settings."""
|
||||||
# If slot is NOT filled, we use the slot's fallback AKA content between
|
# If slot is NOT filled, we use the slot's fallback AKA content between
|
||||||
# the `{% slot %}` tags. These should be evaluated as if the `{% slot %}`
|
# the `{% slot %}` tags. These should be evaluated as if the `{% slot %}`
|
||||||
# tags weren't even there, which means that we use the current context.
|
# tags weren't even there, which means that we use the current context.
|
||||||
if not slot_fill.is_filled:
|
if not slot_is_filled:
|
||||||
return context
|
return context
|
||||||
|
|
||||||
registry_settings = component_ctx.registry.settings
|
registry_settings = component_ctx.registry.settings
|
||||||
|
@ -887,7 +888,8 @@ class FillNode(BaseNode):
|
||||||
# `{% component %} ... {% endcomponent %}`. Hence we search for the last
|
# `{% component %} ... {% endcomponent %}`. Hence we search for the last
|
||||||
# index of `FILL_GEN_CONTEXT_KEY`.
|
# index of `FILL_GEN_CONTEXT_KEY`.
|
||||||
index_of_new_layers = get_last_index(context.dicts, lambda d: FILL_GEN_CONTEXT_KEY in d)
|
index_of_new_layers = get_last_index(context.dicts, lambda d: FILL_GEN_CONTEXT_KEY in d)
|
||||||
for dict_layer in context.dicts[index_of_new_layers:]:
|
context_dicts: List[Dict[str, Any]] = context.dicts
|
||||||
|
for dict_layer in context_dicts[index_of_new_layers:]:
|
||||||
for key, value in dict_layer.items():
|
for key, value in dict_layer.items():
|
||||||
if not key.startswith("_"):
|
if not key.startswith("_"):
|
||||||
data.extra_context[key] = value
|
data.extra_context[key] = value
|
||||||
|
@ -933,9 +935,33 @@ class FillNode(BaseNode):
|
||||||
class FillWithData(NamedTuple):
|
class FillWithData(NamedTuple):
|
||||||
fill: FillNode
|
fill: FillNode
|
||||||
name: str
|
name: str
|
||||||
|
"""Name of the slot to be filled, as set on the `{% fill %}` tag."""
|
||||||
fallback_var: Optional[str]
|
fallback_var: Optional[str]
|
||||||
|
"""Name of the FALLBACK variable, as set on the `{% fill %}` tag."""
|
||||||
data_var: Optional[str]
|
data_var: Optional[str]
|
||||||
|
"""Name of the DATA variable, as set on the `{% fill %}` tag."""
|
||||||
extra_context: Dict[str, Any]
|
extra_context: Dict[str, Any]
|
||||||
|
"""
|
||||||
|
Extra context variables that will be available inside the `{% fill %}` tag.
|
||||||
|
|
||||||
|
For example, if the `{% fill %}` tags are nested within `{% with %}` or `{% for %}` tags,
|
||||||
|
then the variables defined within those tags will be available inside the `{% fill %}` tags:
|
||||||
|
|
||||||
|
```django
|
||||||
|
{% component "mycomponent" %}
|
||||||
|
{% with extra_var="extra_value" %}
|
||||||
|
{% fill "my_fill" %}
|
||||||
|
{{ extra_var }}
|
||||||
|
{% endfill %}
|
||||||
|
{% endwith %}
|
||||||
|
{% for item in items %}
|
||||||
|
{% fill "my_fill" %}
|
||||||
|
{{ item }}
|
||||||
|
{% endfill %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endcomponent %}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def resolve_fills(
|
def resolve_fills(
|
||||||
|
@ -1083,6 +1109,76 @@ def _extract_fill_content(
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_slot_fills(
|
||||||
|
fills: Mapping[SlotName, SlotInput],
|
||||||
|
escape_content: bool = True,
|
||||||
|
component_name: Optional[str] = None,
|
||||||
|
) -> Dict[SlotName, Slot]:
|
||||||
|
# Preprocess slots to escape content if `escape_content=True`
|
||||||
|
norm_fills = {}
|
||||||
|
|
||||||
|
# NOTE: `gen_escaped_content_func` is defined as a separate function, instead of being inlined within
|
||||||
|
# the forloop, because the value the forloop variable points to changes with each loop iteration.
|
||||||
|
def gen_escaped_content_func(content: SlotFunc, slot_name: str) -> Slot:
|
||||||
|
# Case: Already Slot, already escaped, and names assigned, so nothing to do.
|
||||||
|
if isinstance(content, Slot) and content.escaped and content.slot_name and content.component_name:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# Otherwise, we create a new instance of Slot, whether `content` was already Slot or not.
|
||||||
|
# so we can assign metadata to our internal copies.
|
||||||
|
if not isinstance(content, Slot) or not content.escaped:
|
||||||
|
# We wrap the original function so we post-process it by escaping the result.
|
||||||
|
def content_fn(ctx: Context, slot_data: Dict, fallback: SlotFallback) -> SlotResult:
|
||||||
|
rendered = content(ctx, slot_data, fallback)
|
||||||
|
return conditional_escape(rendered) if escape_content else rendered
|
||||||
|
|
||||||
|
content_func = cast(SlotFunc, content_fn)
|
||||||
|
else:
|
||||||
|
content_func = content.content_func
|
||||||
|
|
||||||
|
# Populate potentially missing fields so we can trace the component and slot
|
||||||
|
if isinstance(content, Slot):
|
||||||
|
used_component_name = content.component_name or component_name
|
||||||
|
used_slot_name = content.slot_name or slot_name
|
||||||
|
used_nodelist = content.nodelist
|
||||||
|
used_contents = content.contents if content.contents is not None else content_func
|
||||||
|
else:
|
||||||
|
used_component_name = component_name
|
||||||
|
used_slot_name = slot_name
|
||||||
|
used_nodelist = None
|
||||||
|
used_contents = content_func
|
||||||
|
|
||||||
|
slot = Slot(
|
||||||
|
contents=used_contents,
|
||||||
|
content_func=content_func,
|
||||||
|
component_name=used_component_name,
|
||||||
|
slot_name=used_slot_name,
|
||||||
|
nodelist=used_nodelist,
|
||||||
|
escaped=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return slot
|
||||||
|
|
||||||
|
for slot_name, content in fills.items():
|
||||||
|
# Case: No content, so nothing to do.
|
||||||
|
if content is None:
|
||||||
|
continue
|
||||||
|
# Case: Content is a string / scalar
|
||||||
|
elif not callable(content):
|
||||||
|
escaped_content = conditional_escape(content) if escape_content else content
|
||||||
|
# NOTE: `Slot.content_func` and `Slot.nodelist` are set in `Slot.__init__()`
|
||||||
|
slot: Slot = Slot(
|
||||||
|
contents=escaped_content, component_name=component_name, slot_name=slot_name, escaped=True
|
||||||
|
)
|
||||||
|
# Case: Content is a callable, so either a plain function or a `Slot` instance.
|
||||||
|
else:
|
||||||
|
slot = gen_escaped_content_func(content, slot_name)
|
||||||
|
|
||||||
|
norm_fills[slot_name] = slot
|
||||||
|
|
||||||
|
return norm_fills
|
||||||
|
|
||||||
|
|
||||||
name_escape_re = re.compile(r"[^\w]")
|
name_escape_re = re.compile(r"[^\w]")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2455,8 +2455,7 @@ class ProjectLayoutTabbed(Component):
|
||||||
"stroke_width": 2,
|
"stroke_width": 2,
|
||||||
"color": "text-gray-400 hover:text-gray-500",
|
"color": "text-gray-400 hover:text-gray-500",
|
||||||
},
|
},
|
||||||
# deps_strategy="ignore",
|
deps_strategy="ignore",
|
||||||
render_dependencies=False,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Breadcrumb(value=data.project["name"], link=curr_project_url),
|
Breadcrumb(value=data.project["name"], link=curr_project_url),
|
||||||
|
@ -4491,8 +4490,7 @@ class Tabs(Component):
|
||||||
"header_attrs": context["header_attrs"],
|
"header_attrs": context["header_attrs"],
|
||||||
"content_attrs": context["content_attrs"],
|
"content_attrs": context["content_attrs"],
|
||||||
},
|
},
|
||||||
# deps_strategy="ignore",
|
deps_strategy="ignore",
|
||||||
render_dependencies=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
template: types.django_html = """
|
template: types.django_html = """
|
||||||
|
@ -5137,8 +5135,7 @@ class ProjectUsers(Component):
|
||||||
"project_id": project_id,
|
"project_id": project_id,
|
||||||
"role_id": role['id'],
|
"role_id": role['id'],
|
||||||
},
|
},
|
||||||
# deps_strategy="ignore",
|
deps_strategy="ignore",
|
||||||
render_dependencies=False,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
delete_action = ""
|
delete_action = ""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue