mirror of
https://github.com/django-components/django-components.git
synced 2025-08-08 16:27:59 +00:00
refactor: rename template_name to template_file (#878)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
b99e32e9d5
commit
d94a459c8d
29 changed files with 251 additions and 138 deletions
|
@ -158,14 +158,32 @@ class ComponentVars(NamedTuple):
|
|||
"""
|
||||
|
||||
|
||||
# Descriptor to pass getting/setting of `template_name` onto `template_file`
|
||||
class ComponentTemplateNameDescriptor:
|
||||
def __get__(self, instance: Optional["Component"], cls: Type["Component"]) -> Any:
|
||||
obj = instance if instance is not None else cls
|
||||
return obj.template_file # type: ignore[attr-defined]
|
||||
|
||||
def __set__(self, instance_or_cls: Union["Component", Type["Component"]], value: Any) -> None:
|
||||
cls = instance_or_cls if isinstance(instance_or_cls, type) else instance_or_cls.__class__
|
||||
cls.template_file = value
|
||||
|
||||
|
||||
class ComponentMeta(ComponentMediaMeta):
|
||||
pass
|
||||
def __new__(mcs, name: Any, bases: Tuple, attrs: Dict) -> Any:
|
||||
# If user set `template_name` on the class, we instead set it to `template_file`,
|
||||
# because we want `template_name` to be the descriptor that proxies to `template_file`.
|
||||
if "template_name" in attrs:
|
||||
attrs["template_file"] = attrs.pop("template_name")
|
||||
attrs["template_name"] = ComponentTemplateNameDescriptor()
|
||||
|
||||
return super().__new__(mcs, name, bases, attrs)
|
||||
|
||||
|
||||
# NOTE: We use metaclass to automatically define the HTTP methods as defined
|
||||
# in `View.http_method_names`.
|
||||
class ComponentViewMeta(type):
|
||||
def __new__(cls, name: str, bases: Any, dct: Dict) -> Any:
|
||||
def __new__(mcs, name: str, bases: Any, dct: Dict) -> Any:
|
||||
# Default implementation shared by all HTTP methods
|
||||
def create_handler(method: str) -> Callable:
|
||||
def handler(self, request: HttpRequest, *args: Any, **kwargs: Any): # type: ignore[no-untyped-def]
|
||||
|
@ -179,7 +197,7 @@ class ComponentViewMeta(type):
|
|||
if method_name not in dct:
|
||||
dct[method_name] = create_handler(method_name)
|
||||
|
||||
return super().__new__(cls, name, bases, dct)
|
||||
return super().__new__(mcs, name, bases, dct)
|
||||
|
||||
|
||||
class ComponentView(View, metaclass=ComponentViewMeta):
|
||||
|
@ -205,14 +223,14 @@ class Component(
|
|||
# PUBLIC API (Configurable by users)
|
||||
# #####################################
|
||||
|
||||
template_name: Optional[str] = None
|
||||
template_file: Optional[str] = None
|
||||
"""
|
||||
Filepath to the Django template associated with this component.
|
||||
|
||||
The filepath must be relative to either the file where the component class was defined,
|
||||
or one of the roots of `STATIFILES_DIRS`.
|
||||
|
||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
||||
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||
|
@ -221,13 +239,27 @@ class Component(
|
|||
|
||||
```py
|
||||
class MyComponent(Component):
|
||||
template_name = "path/to/template.html"
|
||||
template_file = "path/to/template.html"
|
||||
|
||||
def get_context_data(self):
|
||||
return {"name": "World"}
|
||||
```
|
||||
"""
|
||||
|
||||
# NOTE: This attribute is managed by `ComponentTemplateNameDescriptor` that's set in the metaclass.
|
||||
# But we still define it here for documenting and type hinting.
|
||||
template_name: Optional[str]
|
||||
"""
|
||||
Alias for [`template_file`](../api#django_components.Component.template_file).
|
||||
|
||||
For historical reasons, django-components used `template_name` to align with Django's
|
||||
[TemplateView](https://docs.djangoproject.com/en/5.1/ref/class-based-views/base/#django.views.generic.base.TemplateView).
|
||||
|
||||
`template_file` was introduced to align with `js/js_file` and `css/css_file`.
|
||||
|
||||
Setting and accessing this attribute is proxied to `template_file`.
|
||||
"""
|
||||
|
||||
def get_template_name(self, context: Context) -> Optional[str]:
|
||||
"""
|
||||
Filepath to the Django template associated with this component.
|
||||
|
@ -235,7 +267,7 @@ class Component(
|
|||
The filepath must be relative to either the file where the component class was defined,
|
||||
or one of the roots of `STATIFILES_DIRS`.
|
||||
|
||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
||||
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||
|
@ -246,7 +278,7 @@ class Component(
|
|||
"""
|
||||
Inlined Django template associated with this component. Can be a plain string or a Template instance.
|
||||
|
||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
||||
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||
|
@ -266,7 +298,7 @@ class Component(
|
|||
"""
|
||||
Inlined Django template associated with this component. Can be a plain string or a Template instance.
|
||||
|
||||
Only one of [`template_name`](../api#django_components.Component.template_name),
|
||||
Only one of [`template_file`](../api#django_components.Component.template_file),
|
||||
[`get_template_name`](../api#django_components.Component.get_template_name),
|
||||
[`template`](../api#django_components.Component.template)
|
||||
or [`get_template`](../api#django_components.Component.get_template) must be defined.
|
||||
|
@ -596,21 +628,21 @@ class Component(
|
|||
|
||||
return ctx.is_filled
|
||||
|
||||
# NOTE: When the template is taken from a file (AKA specified via `template_name`),
|
||||
# NOTE: When the template is taken from a file (AKA specified via `template_file`),
|
||||
# then we leverage Django's template caching. This means that the same instance
|
||||
# of Template is reused. This is important to keep in mind, because the implication
|
||||
# is that we should treat Templates AND their nodelists as IMMUTABLE.
|
||||
def _get_template(self, context: Context) -> Template:
|
||||
# Resolve template name
|
||||
template_name = self.template_name
|
||||
if self.template_name is not None:
|
||||
template_file = self.template_file
|
||||
if self.template_file is not None:
|
||||
if self.get_template_name(context) is not None:
|
||||
raise ImproperlyConfigured(
|
||||
"Received non-null value from both 'template_name' and 'get_template_name' in"
|
||||
"Received non-null value from both 'template_file' and 'get_template_name' in"
|
||||
f" Component {type(self).__name__}. Only one of the two must be set."
|
||||
)
|
||||
else:
|
||||
template_name = self.get_template_name(context)
|
||||
template_file = self.get_template_name(context)
|
||||
|
||||
# Resolve template str
|
||||
template_input = self.template
|
||||
|
@ -625,14 +657,14 @@ class Component(
|
|||
template_getter = getattr(self, "get_template_string", self.get_template)
|
||||
template_input = template_getter(context)
|
||||
|
||||
if template_name is not None and template_input is not None:
|
||||
if template_file is not None and template_input is not None:
|
||||
raise ImproperlyConfigured(
|
||||
f"Received both 'template_name' and 'template' in Component {type(self).__name__}."
|
||||
f"Received both 'template_file' and 'template' in Component {type(self).__name__}."
|
||||
" Only one of the two must be set."
|
||||
)
|
||||
|
||||
if template_name is not None:
|
||||
return get_template(template_name).template
|
||||
if template_file is not None:
|
||||
return get_template(template_file).template
|
||||
|
||||
elif template_input is not None:
|
||||
# We got template string, so we convert it to Template
|
||||
|
@ -644,7 +676,7 @@ class Component(
|
|||
return template
|
||||
|
||||
raise ImproperlyConfigured(
|
||||
f"Either 'template_name' or 'template' must be set for Component {type(self).__name__}."
|
||||
f"Either 'template_file' or 'template' must be set for Component {type(self).__name__}."
|
||||
)
|
||||
|
||||
def inject(self, key: str, default: Optional[Any] = None) -> Any:
|
||||
|
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
# These are all the attributes that are handled by ComponentMedia and lazily-resolved
|
||||
COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_name", "js", "js_file", "css", "css_file")
|
||||
COMP_MEDIA_LAZY_ATTRS = ("media", "template", "template_file", "js", "js_file", "css", "css_file")
|
||||
|
||||
|
||||
ComponentMediaInputPath = Union[
|
||||
|
@ -176,7 +176,7 @@ class ComponentMedia:
|
|||
media: Optional[MediaCls] = None
|
||||
media_class: Type[MediaCls] = MediaCls
|
||||
template: Optional[str] = None
|
||||
template_name: Optional[str] = None
|
||||
template_file: Optional[str] = None
|
||||
js: Optional[str] = None
|
||||
js_file: Optional[str] = None
|
||||
css: Optional[str] = None
|
||||
|
@ -185,9 +185,9 @@ class ComponentMedia:
|
|||
|
||||
# This metaclass is all about one thing - lazily resolving the media files.
|
||||
#
|
||||
# All the CSS/JS/HTML associated with a component - e.g. the `js`, `js_file`, `template_name` or `Media` class,
|
||||
# All the CSS/JS/HTML associated with a component - e.g. the `js`, `js_file`, `template_file` or `Media` class,
|
||||
# are all class attributes. And some of these attributes need to be resolved, e.g. to find the files
|
||||
# that `js_file`, `css_file` and `template_name` point to.
|
||||
# that `js_file`, `css_file` and `template_file` point to.
|
||||
#
|
||||
# Some of the resolutions we need to do is:
|
||||
# - Component's HTML/JS/CSS files can be defined as relative to the component class file. So for each file,
|
||||
|
@ -240,7 +240,14 @@ class ComponentMediaMeta(type):
|
|||
" already resolved. This may lead to unexpected behavior."
|
||||
)
|
||||
|
||||
super().__setattr__(name, value)
|
||||
# NOTE: When a metaclass specifies a `__setattr__` method, this overrides the normal behavior of
|
||||
# setting an attribute on the class with Descriptors. So we need to call the normal behavior explicitly.
|
||||
# NOTE 2: `__dict__` is used to access the class attributes directly, without triggering the descriptors.
|
||||
desc = cls.__dict__.get(name, None)
|
||||
if hasattr(desc, "__set__"):
|
||||
desc.__set__(cls, value)
|
||||
else:
|
||||
super().__setattr__(name, value)
|
||||
|
||||
|
||||
# This sets up the lazy resolution of the media attributes.
|
||||
|
@ -254,7 +261,7 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any]
|
|||
media=attrs.get("media", None),
|
||||
media_class=attrs.get("media_class", None),
|
||||
template=attrs.get("template", None),
|
||||
template_name=attrs.get("template_name", None),
|
||||
template_file=attrs.get("template_file", None),
|
||||
js=attrs.get("js", None),
|
||||
js_file=attrs.get("js_file", None),
|
||||
css=attrs.get("css", None),
|
||||
|
@ -292,8 +299,8 @@ def _setup_lazy_media_resolve(comp_cls: Type["Component"], attrs: Dict[str, Any]
|
|||
continue
|
||||
else:
|
||||
return value
|
||||
if attr in ("template", "template_name"):
|
||||
if check_pair_empty("template", "template_name"):
|
||||
if attr in ("template", "template_file"):
|
||||
if check_pair_empty("template", "template_file"):
|
||||
continue
|
||||
else:
|
||||
return value
|
||||
|
@ -544,7 +551,7 @@ def _resolve_component_relative_files(
|
|||
# HTML/JS/CSS files, just skip.
|
||||
will_resolve_files = False
|
||||
if (
|
||||
getattr(comp_media, "template_name", None)
|
||||
getattr(comp_media, "template_file", None)
|
||||
or getattr(comp_media, "js_file", None)
|
||||
or getattr(comp_media, "css_file", None)
|
||||
):
|
||||
|
@ -609,8 +616,8 @@ def _resolve_component_relative_files(
|
|||
return filepath
|
||||
|
||||
# Check if template name is a local file or not
|
||||
if getattr(comp_media, "template_name", None):
|
||||
comp_media.template_name = resolve_media_file(comp_media.template_name)
|
||||
if getattr(comp_media, "template_file", None):
|
||||
comp_media.template_file = resolve_media_file(comp_media.template_file)
|
||||
if getattr(comp_media, "js_file", None):
|
||||
comp_media.js_file = resolve_media_file(comp_media.js_file)
|
||||
if getattr(comp_media, "css_file", None):
|
||||
|
|
|
@ -197,7 +197,7 @@ class Command(BaseCommand):
|
|||
|
||||
@register("{name}")
|
||||
class {name.capitalize()}(Component):
|
||||
template_name = "{name}/{template_filename}"
|
||||
template_file = "{name}/{template_filename}"
|
||||
|
||||
def get_context_data(self, value):
|
||||
return {{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue