mirror of
https://github.com/django-components/django-components.git
synced 2025-07-07 17:34:59 +00:00
feat: allow globs when specifynigg additionall JS and CSS (#1043)
* feat: allow globs when specifynigg additionall JS and CSS * refactor: fix tests and linter errors
This commit is contained in:
parent
73e94b6714
commit
ab75cfdb8f
12 changed files with 282 additions and 97 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,5 +1,19 @@
|
|||
# Release notes
|
||||
|
||||
## v0.132
|
||||
|
||||
#### Feat
|
||||
|
||||
- Allow to use glob patterns as paths for additional JS / CSS in
|
||||
`Component.Media.js` and `Component.Media.css`
|
||||
|
||||
```py
|
||||
class MyComponent(Component):
|
||||
class Media:
|
||||
js = ["*.js"]
|
||||
css = ["*.css"]
|
||||
```
|
||||
|
||||
## v0.131
|
||||
|
||||
#### Feat
|
||||
|
|
|
@ -143,17 +143,22 @@ However, there's a few differences from Django's Media class:
|
|||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`,
|
||||
[`SafeString`](https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.SafeString), or a function
|
||||
(See [`ComponentMediaInputPath`](../../../reference/api#django_components.ComponentMediaInputPath)).
|
||||
3. Individual JS / CSS files can be glob patterns, e.g. `*.js` or `styles/**/*.css`.
|
||||
4. If you set [`Media.extend`](../../../reference/api/#django_components.ComponentMediaInput.extend) to a list,
|
||||
it should be a list of [`Component`](../../../reference/api/#django_components.Component) classes.
|
||||
|
||||
```py
|
||||
class MyTable(Component):
|
||||
class Media:
|
||||
js = [
|
||||
"path/to/script.js",
|
||||
"path/to/*.js", # Or as a glob
|
||||
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
|
||||
]
|
||||
css = {
|
||||
"all": [
|
||||
"path/to/style.css",
|
||||
"path/to/*.css", # Or as a glob
|
||||
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # TailwindCSS
|
||||
],
|
||||
"print": ["path/to/style2.css"],
|
||||
|
|
|
@ -230,7 +230,9 @@ with a few differences:
|
|||
|
||||
1. Our Media class accepts various formats for the JS and CSS files: either a single file, a list, or (CSS-only) a dictonary (see below).
|
||||
2. Individual JS / CSS files can be any of `str`, `bytes`, `Path`, [`SafeString`](https://dev.to/doridoro/django-safestring-afj), or a function.
|
||||
3. If you set `Media.extend` to a list, it should be a list of `Component` classes.
|
||||
3. Individual JS / CSS files can be glob patterns, e.g. `*.js` or `styles/**/*.css`.
|
||||
4. If you set [`Media.extend`](../../reference/api/#django_components.ComponentMediaInput.extend) to a list,
|
||||
it should be a list of [`Component`](../../reference/api/#django_components.Component) classes.
|
||||
|
||||
[Learn more](../../concepts/fundamentals/defining_js_css_html_files) about using Media.
|
||||
|
||||
|
@ -245,10 +247,12 @@ class Calendar(Component):
|
|||
class Media: # <--- new
|
||||
js = [
|
||||
"path/to/shared.js",
|
||||
"path/to/*.js", # Or as a glob
|
||||
"https://unpkg.com/alpinejs@3.14.7/dist/cdn.min.js", # AlpineJS
|
||||
]
|
||||
css = [
|
||||
"path/to/shared.css",
|
||||
"path/to/*.css", # Or as a glob
|
||||
"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", # Tailwind
|
||||
]
|
||||
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
import glob
|
||||
import os
|
||||
import sys
|
||||
from collections import deque
|
||||
from copy import copy
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Protocol, Tuple, Type, Union, cast
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Protocol,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from django.contrib.staticfiles import finders
|
||||
|
@ -16,7 +32,7 @@ from django.utils.safestring import SafeData
|
|||
|
||||
from django_components.util.loader import get_component_dirs, resolve_file
|
||||
from django_components.util.logger import logger
|
||||
from django_components.util.misc import get_import_path
|
||||
from django_components.util.misc import flatten, get_import_path, get_module_info, is_glob
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django_components.component import Component
|
||||
|
@ -649,19 +665,19 @@ def _normalize_media(media: Type[ComponentMediaInput]) -> None:
|
|||
_map_media_filepaths(media, _normalize_media_filepath)
|
||||
|
||||
|
||||
def _map_media_filepaths(media: Type[ComponentMediaInput], map_fn: Callable[[Any], Any]) -> None:
|
||||
def _map_media_filepaths(media: Type[ComponentMediaInput], map_fn: Callable[[Sequence[Any]], Iterable[Any]]) -> None:
|
||||
if hasattr(media, "css") and media.css:
|
||||
if not isinstance(media.css, dict):
|
||||
raise ValueError(f"Media.css must be a dict, got {type(media.css)}")
|
||||
|
||||
for media_type, path_list in media.css.items():
|
||||
media.css[media_type] = list(map(map_fn, path_list)) # type: ignore[assignment]
|
||||
media.css[media_type] = list(map_fn(path_list)) # type: ignore[assignment]
|
||||
|
||||
if hasattr(media, "js") and media.js:
|
||||
if not isinstance(media.js, (list, tuple)):
|
||||
raise ValueError(f"Media.css must be a list, got {type(media.css)}")
|
||||
|
||||
media.js = list(map(map_fn, media.js))
|
||||
media.js = list(map_fn(media.js))
|
||||
|
||||
|
||||
def _is_media_filepath(filepath: Any) -> bool:
|
||||
|
@ -683,26 +699,33 @@ def _is_media_filepath(filepath: Any) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _normalize_media_filepath(filepath: ComponentMediaInputPath) -> Union[str, SafeData]:
|
||||
if callable(filepath):
|
||||
filepath = filepath()
|
||||
def _normalize_media_filepath(filepaths: Sequence[ComponentMediaInputPath]) -> List[Union[str, SafeData]]:
|
||||
normalized: List[Union[str, SafeData]] = []
|
||||
for filepath in filepaths:
|
||||
if callable(filepath):
|
||||
filepath = filepath()
|
||||
|
||||
if isinstance(filepath, SafeData) or hasattr(filepath, "__html__"):
|
||||
return filepath
|
||||
if isinstance(filepath, SafeData) or hasattr(filepath, "__html__"):
|
||||
normalized.append(filepath)
|
||||
continue
|
||||
|
||||
if isinstance(filepath, (Path, os.PathLike)) or hasattr(filepath, "__fspath__"):
|
||||
# In case of Windows OS, convert to forward slashes
|
||||
filepath = Path(filepath.__fspath__()).as_posix()
|
||||
if isinstance(filepath, (Path, os.PathLike)) or hasattr(filepath, "__fspath__"):
|
||||
# In case of Windows OS, convert to forward slashes
|
||||
filepath = Path(filepath.__fspath__()).as_posix()
|
||||
|
||||
if isinstance(filepath, bytes):
|
||||
filepath = filepath.decode("utf-8")
|
||||
if isinstance(filepath, bytes):
|
||||
filepath = filepath.decode("utf-8")
|
||||
|
||||
if isinstance(filepath, str):
|
||||
return filepath
|
||||
if isinstance(filepath, str):
|
||||
normalized.append(filepath)
|
||||
continue
|
||||
|
||||
raise ValueError(
|
||||
"Unknown filepath. Must be str, bytes, PathLike, SafeString, or a function that returns one of the former"
|
||||
)
|
||||
raise ValueError(
|
||||
f"Unknown filepath {filepath} of type {type(filepath)}. Must be str, bytes, PathLike, SafeString,"
|
||||
" or a function that returns one of the former"
|
||||
)
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
def _resolve_component_relative_files(
|
||||
|
@ -730,12 +753,9 @@ def _resolve_component_relative_files(
|
|||
return
|
||||
|
||||
component_name = comp_cls.__qualname__
|
||||
# Derive the full path of the file where the component was defined
|
||||
module_name = comp_cls.__module__
|
||||
module_obj = sys.modules[module_name]
|
||||
file_path = module_obj.__file__
|
||||
|
||||
if not file_path:
|
||||
# Get the full path of the file where the component was defined
|
||||
module, module_name, module_file_path = get_module_info(comp_cls)
|
||||
if not module_file_path:
|
||||
logger.debug(
|
||||
f"Could not resolve the path to the file for component '{component_name}'."
|
||||
" Paths for HTML, JS or CSS templates will NOT be resolved relative to the component file."
|
||||
|
@ -743,83 +763,147 @@ def _resolve_component_relative_files(
|
|||
return
|
||||
|
||||
# Get the directory where the component class is defined
|
||||
try:
|
||||
comp_dir_abs, comp_dir_rel = _get_dir_path_from_component_path(file_path, comp_dirs)
|
||||
except RuntimeError:
|
||||
# If no dir was found, we assume that the path is NOT relative to the component dir
|
||||
matched_component_dir = _find_component_dir_containing_file(comp_dirs, module_file_path)
|
||||
|
||||
# If no dir was found (e.g. the component was defined at runtime), we assume that the media paths
|
||||
# are NOT relative.
|
||||
if matched_component_dir is None:
|
||||
logger.debug(
|
||||
f"No component directory found for component '{component_name}' in {file_path}"
|
||||
f"No component directory found for component '{component_name}' in {module_file_path}"
|
||||
" If this component defines HTML, JS or CSS templates relatively to the component file,"
|
||||
" then check that the component's directory is accessible from one of the paths"
|
||||
" specified in the Django's 'COMPONENTS.dirs' settings."
|
||||
)
|
||||
return
|
||||
|
||||
matched_component_dir_abs = os.path.abspath(matched_component_dir)
|
||||
# Derive the path from matched `COMPONENTS.dirs` to the dir where the current component file is.
|
||||
component_module_dir_path_abs = os.path.dirname(module_file_path)
|
||||
|
||||
# Check if filepath refers to a file that's in the same directory as the component class.
|
||||
# If yes, modify the path to refer to the relative file.
|
||||
# If not, don't modify anything.
|
||||
def resolve_media_file(filepath: Union[str, SafeData]) -> Union[str, SafeData]:
|
||||
if isinstance(filepath, str):
|
||||
filepath_abs = os.path.join(comp_dir_abs, filepath)
|
||||
# NOTE: The paths to resources need to use POSIX (forward slashes) for Django to wor
|
||||
# See https://github.com/django-components/django-components/issues/796
|
||||
filepath_rel_to_comp_dir = Path(os.path.join(comp_dir_rel, filepath)).as_posix()
|
||||
|
||||
if os.path.isfile(filepath_abs):
|
||||
# NOTE: It's important to use `repr`, so we don't trigger __str__ on SafeStrings
|
||||
logger.debug(
|
||||
f"Interpreting template '{repr(filepath)}' of component '{module_name}'"
|
||||
" relatively to component file"
|
||||
)
|
||||
|
||||
return filepath_rel_to_comp_dir
|
||||
|
||||
# If resolved absolute path does NOT exist or filepath is NOT a string, then return as is
|
||||
logger.debug(
|
||||
f"Interpreting template '{repr(filepath)}' of component '{module_name}'"
|
||||
" relatively to components directory"
|
||||
def resolve_relative_media_file(
|
||||
filepath: Union[str, SafeData],
|
||||
allow_glob: bool,
|
||||
) -> List[Union[str, SafeData]]:
|
||||
resolved_filepaths, has_matched = resolve_media_file(
|
||||
filepath,
|
||||
allow_glob,
|
||||
static_files_dir=matched_component_dir_abs,
|
||||
media_root_dir=component_module_dir_path_abs,
|
||||
)
|
||||
return filepath
|
||||
|
||||
# NOTE: It's important to use `repr`, so we don't trigger __str__ on SafeStrings
|
||||
if has_matched:
|
||||
logger.debug(
|
||||
f"Interpreting file '{repr(filepath)}' of component '{module_name}'" " relatively to component file"
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"Interpreting file '{repr(filepath)}' of component '{module_name}'"
|
||||
" relatively to components directory"
|
||||
)
|
||||
|
||||
return resolved_filepaths
|
||||
|
||||
# Check if filepath is a glob pattern that points to files relative to the components directory
|
||||
# (as defined by `COMPONENTS.dirs` and `COMPONENTS.app_dirs` settings) in which the component is defined.
|
||||
# If yes, modify the path to refer to the globbed files.
|
||||
# If not, don't modify anything.
|
||||
def resolve_static_media_file(
|
||||
filepath: Union[str, SafeData],
|
||||
allow_glob: bool,
|
||||
) -> List[Union[str, SafeData]]:
|
||||
resolved_filepaths, _ = resolve_media_file(
|
||||
filepath,
|
||||
allow_glob,
|
||||
static_files_dir=matched_component_dir_abs,
|
||||
media_root_dir=matched_component_dir_abs,
|
||||
)
|
||||
return resolved_filepaths
|
||||
|
||||
# Check if template name is a local file or not
|
||||
if getattr(comp_media, "template_file", None):
|
||||
comp_media.template_file = resolve_media_file(comp_media.template_file)
|
||||
comp_media.template_file = resolve_relative_media_file(comp_media.template_file, False)[0]
|
||||
if getattr(comp_media, "js_file", None):
|
||||
comp_media.js_file = resolve_media_file(comp_media.js_file)
|
||||
comp_media.js_file = resolve_relative_media_file(comp_media.js_file, False)[0]
|
||||
if getattr(comp_media, "css_file", None):
|
||||
comp_media.css_file = resolve_media_file(comp_media.css_file)
|
||||
comp_media.css_file = resolve_relative_media_file(comp_media.css_file, False)[0]
|
||||
|
||||
if hasattr(comp_media, "Media") and comp_media.Media:
|
||||
_map_media_filepaths(comp_media.Media, resolve_media_file)
|
||||
|
||||
|
||||
def _get_dir_path_from_component_path(
|
||||
abs_component_file_path: str,
|
||||
candidate_dirs: Union[List[str], List[Path]],
|
||||
) -> Tuple[str, str]:
|
||||
comp_dir_path_abs = os.path.dirname(abs_component_file_path)
|
||||
|
||||
# From all dirs defined in settings.COMPONENTS.dirs, find one that's the parent
|
||||
# to the component file.
|
||||
root_dir_abs = None
|
||||
for candidate_dir in candidate_dirs:
|
||||
candidate_dir_abs = os.path.abspath(candidate_dir)
|
||||
if comp_dir_path_abs.startswith(candidate_dir_abs):
|
||||
root_dir_abs = candidate_dir_abs
|
||||
break
|
||||
|
||||
if root_dir_abs is None:
|
||||
raise RuntimeError(
|
||||
f"Failed to resolve template directory for component file '{abs_component_file_path}'",
|
||||
_map_media_filepaths(
|
||||
comp_media.Media,
|
||||
# Media files can be defined as a glob patterns that match multiple files.
|
||||
# Thus, flatten the list of lists returned by `resolve_relative_media_file`.
|
||||
lambda filepaths: flatten(resolve_relative_media_file(f, True) for f in filepaths),
|
||||
)
|
||||
|
||||
# Derive the path from matched COMPONENTS.dirs to the dir where the current component file is.
|
||||
comp_dir_path_rel = os.path.relpath(comp_dir_path_abs, candidate_dir_abs)
|
||||
# Go over the JS / CSS media files again, but this time, if there are still any globs,
|
||||
# try to resolve them relative to the root directory (as defined by `COMPONENTS.dirs
|
||||
# and `COMPONENTS.app_dirs` settings).
|
||||
_map_media_filepaths(
|
||||
comp_media.Media,
|
||||
# Media files can be defined as a glob patterns that match multiple files.
|
||||
# Thus, flatten the list of lists returned by `resolve_static_media_file`.
|
||||
lambda filepaths: flatten(resolve_static_media_file(f, True) for f in filepaths),
|
||||
)
|
||||
|
||||
# Return both absolute and relative paths:
|
||||
# - Absolute path is used to check if the file exists
|
||||
# - Relative path is used for defining the import on the component class
|
||||
return comp_dir_path_abs, comp_dir_path_rel
|
||||
|
||||
# Check if filepath refers to a file that's in the same directory as the component class.
|
||||
# If yes, modify the path to refer to the relative file.
|
||||
# If not, don't modify anything.
|
||||
def resolve_media_file(
|
||||
filepath: Union[str, SafeData],
|
||||
allow_glob: bool,
|
||||
static_files_dir: str,
|
||||
media_root_dir: str,
|
||||
) -> Tuple[List[Union[str, SafeData]], bool]:
|
||||
# If filepath is NOT a string, then return as is
|
||||
if not isinstance(filepath, str):
|
||||
return [filepath], False
|
||||
|
||||
filepath_abs_or_glob = os.path.join(media_root_dir, filepath)
|
||||
|
||||
# The path may be a glob. So before we check if the file exists,
|
||||
# we need to resolve the glob.
|
||||
if allow_glob and is_glob(filepath_abs_or_glob):
|
||||
matched_abs_filepaths = glob.glob(filepath_abs_or_glob)
|
||||
else:
|
||||
matched_abs_filepaths = [filepath_abs_or_glob]
|
||||
|
||||
# If there are no matches, return the original filepath
|
||||
if not matched_abs_filepaths:
|
||||
return [filepath], False
|
||||
|
||||
resolved_filepaths: List[str] = []
|
||||
for matched_filepath_abs in matched_abs_filepaths:
|
||||
# Derive the path from matched `COMPONENTS.dirs` to the media file.
|
||||
# NOTE: The paths to resources need to use POSIX (forward slashes) for Django to work
|
||||
# See https://github.com/django-components/django-components/issues/796
|
||||
# NOTE: Since these paths matched the glob, we know that these files exist.
|
||||
filepath_rel_to_comp_dir = Path(os.path.relpath(matched_filepath_abs, static_files_dir)).as_posix()
|
||||
resolved_filepaths.append(filepath_rel_to_comp_dir)
|
||||
|
||||
return resolved_filepaths, True
|
||||
|
||||
|
||||
def _find_component_dir_containing_file(
|
||||
component_dirs: Sequence[Union[str, Path]],
|
||||
target_file_path: str,
|
||||
) -> Optional[Union[str, Path]]:
|
||||
"""
|
||||
From all directories that may contain components (such as those defined in `COMPONENTS.dirs`),
|
||||
find the one that's the parent to the given file.
|
||||
"""
|
||||
abs_target_file_path = os.path.abspath(target_file_path)
|
||||
|
||||
for component_dir in component_dirs:
|
||||
component_dir_abs = os.path.abspath(component_dir)
|
||||
if abs_target_file_path.startswith(component_dir_abs):
|
||||
return component_dir
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_asset(
|
||||
|
|
|
@ -2,8 +2,9 @@ import re
|
|||
import sys
|
||||
from hashlib import md5
|
||||
from importlib import import_module
|
||||
from itertools import chain
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Type, TypeVar, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar, Union
|
||||
|
||||
from django_components.util.nanoid import generate
|
||||
|
||||
|
@ -115,3 +116,15 @@ def hash_comp_cls(comp_cls: Type["Component"]) -> str:
|
|||
full_name = get_import_path(comp_cls)
|
||||
comp_cls_hash = md5(full_name.encode()).hexdigest()[0:6]
|
||||
return comp_cls.__name__ + "_" + comp_cls_hash
|
||||
|
||||
|
||||
# String is a glob if it contains at least one of `?`, `*`, or `[`
|
||||
is_glob_re = re.compile(r"[?*[]")
|
||||
|
||||
|
||||
def is_glob(filepath: str) -> bool:
|
||||
return is_glob_re.search(filepath) is not None
|
||||
|
||||
|
||||
def flatten(lst: Iterable[Iterable[T]]) -> List[T]:
|
||||
return list(chain.from_iterable(lst))
|
||||
|
|
23
tests/components/glob/glob.py
Normal file
23
tests/components/glob/glob.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
from django_components import Component
|
||||
|
||||
|
||||
# The Media JS / CSS glob and are relative to the component directory
|
||||
class GlobComponent(Component):
|
||||
template = """
|
||||
{% load component_tags %}
|
||||
{% component_js_dependencies %}
|
||||
{% component_css_dependencies %}
|
||||
"""
|
||||
|
||||
class Media:
|
||||
css = "glob_*.css"
|
||||
js = "glob_*.js"
|
||||
|
||||
|
||||
# The Media JS / CSS glob and are relative to the directory given in
|
||||
# `COMPONENTS.dirs` and `COMPONENTS.app_dirs`
|
||||
class GlobComponentRootDir(GlobComponent):
|
||||
class Media:
|
||||
css = "glob/glob_*.css"
|
||||
js = "glob/glob_*.js"
|
3
tests/components/glob/glob_1.css
Normal file
3
tests/components/glob/glob_1.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.html-css-only {
|
||||
color: blue;
|
||||
}
|
1
tests/components/glob/glob_1.js
Normal file
1
tests/components/glob/glob_1.js
Normal file
|
@ -0,0 +1 @@
|
|||
console.log("JS file");
|
3
tests/components/glob/glob_2.css
Normal file
3
tests/components/glob/glob_2.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.html-css-only {
|
||||
color: blue;
|
||||
}
|
1
tests/components/glob/glob_2.js
Normal file
1
tests/components/glob/glob_2.js
Normal file
|
@ -0,0 +1 @@
|
|||
console.log("JS file");
|
|
@ -386,6 +386,34 @@ class TestComponentMedia:
|
|||
assertInHTML('<link abc href="path/to/style2.css" media="print" rel="stylesheet">', rendered)
|
||||
assertInHTML('<link abc href="path/to/style3.css" media="screen" rel="stylesheet">', rendered)
|
||||
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"INSTALLED_APPS": ("django_components", "tests"),
|
||||
}
|
||||
)
|
||||
def test_glob_pattern_relative_to_component(self):
|
||||
from tests.components.glob.glob import GlobComponent
|
||||
rendered = GlobComponent.render()
|
||||
|
||||
assertInHTML('<link href="glob/glob_1.css" media="all" rel="stylesheet">', rendered)
|
||||
assertInHTML('<link href="glob/glob_2.css" media="all" rel="stylesheet">', rendered)
|
||||
assertInHTML('<script src="glob/glob_1.js"></script>', rendered)
|
||||
assertInHTML('<script src="glob/glob_2.js"></script>', rendered)
|
||||
|
||||
@djc_test(
|
||||
django_settings={
|
||||
"INSTALLED_APPS": ("django_components", "tests"),
|
||||
}
|
||||
)
|
||||
def test_glob_pattern_relative_to_root_dir(self):
|
||||
from tests.components.glob.glob import GlobComponentRootDir
|
||||
rendered = GlobComponentRootDir.render()
|
||||
|
||||
assertInHTML('<link href="glob/glob_1.css" media="all" rel="stylesheet">', rendered)
|
||||
assertInHTML('<link href="glob/glob_2.css" media="all" rel="stylesheet">', rendered)
|
||||
assertInHTML('<script src="glob/glob_1.js"></script>', rendered)
|
||||
assertInHTML('<script src="glob/glob_2.js"></script>', rendered)
|
||||
|
||||
|
||||
@djc_test
|
||||
class TestMediaPathAsObject:
|
||||
|
|
|
@ -247,6 +247,7 @@ class TestComponentFiles:
|
|||
|
||||
assert dot_paths == [
|
||||
"components",
|
||||
"components.glob.glob",
|
||||
"components.multi_file.multi_file",
|
||||
"components.relative_file.relative_file",
|
||||
"components.relative_file_pathobj.relative_file_pathobj",
|
||||
|
@ -260,15 +261,16 @@ class TestComponentFiles:
|
|||
|
||||
# NOTE: Compare parts so that the test works on Windows too
|
||||
assert file_paths[0].parts[-3:] == ("tests", "components", "__init__.py")
|
||||
assert file_paths[1].parts[-4:] == ("tests", "components", "multi_file", "multi_file.py")
|
||||
assert file_paths[2].parts[-4:] == ("tests", "components", "relative_file", "relative_file.py")
|
||||
assert file_paths[3].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.py")
|
||||
assert file_paths[4].parts[-3:] == ("tests", "components", "single_file.py")
|
||||
assert file_paths[5].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.py")
|
||||
assert file_paths[6].parts[-3:] == ("tests", "components", "urls.py")
|
||||
assert file_paths[7].parts[-3:] == ("django_components", "components", "__init__.py")
|
||||
assert file_paths[8].parts[-3:] == ("django_components", "components", "dynamic.py")
|
||||
assert file_paths[9].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.py")
|
||||
assert file_paths[1].parts[-4:] == ("tests", "components", "glob", "glob.py")
|
||||
assert file_paths[2].parts[-4:] == ("tests", "components", "multi_file", "multi_file.py")
|
||||
assert file_paths[3].parts[-4:] == ("tests", "components", "relative_file", "relative_file.py")
|
||||
assert file_paths[4].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.py")
|
||||
assert file_paths[5].parts[-3:] == ("tests", "components", "single_file.py")
|
||||
assert file_paths[6].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.py")
|
||||
assert file_paths[7].parts[-3:] == ("tests", "components", "urls.py")
|
||||
assert file_paths[8].parts[-3:] == ("django_components", "components", "__init__.py")
|
||||
assert file_paths[9].parts[-3:] == ("django_components", "components", "dynamic.py")
|
||||
assert file_paths[10].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.py")
|
||||
|
||||
@djc_test(
|
||||
django_settings={
|
||||
|
@ -282,6 +284,8 @@ class TestComponentFiles:
|
|||
file_paths = [f.filepath for f in files]
|
||||
|
||||
assert dot_paths == [
|
||||
"components.glob.glob_1",
|
||||
"components.glob.glob_2",
|
||||
"components.relative_file.relative_file",
|
||||
"components.relative_file_pathobj.relative_file_pathobj",
|
||||
"components.staticfiles.staticfiles",
|
||||
|
@ -289,10 +293,12 @@ class TestComponentFiles:
|
|||
]
|
||||
|
||||
# NOTE: Compare parts so that the test works on Windows too
|
||||
assert file_paths[0].parts[-4:] == ("tests", "components", "relative_file", "relative_file.js")
|
||||
assert file_paths[1].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.js")
|
||||
assert file_paths[2].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.js")
|
||||
assert file_paths[3].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.js")
|
||||
assert file_paths[0].parts[-4:] == ("tests", "components", "glob", "glob_1.js")
|
||||
assert file_paths[1].parts[-4:] == ("tests", "components", "glob", "glob_2.js")
|
||||
assert file_paths[2].parts[-4:] == ("tests", "components", "relative_file", "relative_file.js")
|
||||
assert file_paths[3].parts[-4:] == ("tests", "components", "relative_file_pathobj", "relative_file_pathobj.js")
|
||||
assert file_paths[4].parts[-4:] == ("tests", "components", "staticfiles", "staticfiles.js")
|
||||
assert file_paths[5].parts[-5:] == ("tests", "test_app", "components", "app_lvl_comp", "app_lvl_comp.js")
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue