refactor: Use top-level exports as public API (#562)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Juro Oravec 2024-08-03 08:30:39 +02:00 committed by GitHub
parent d819f3ff49
commit e771a0aaaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 615 additions and 598 deletions

259
README.md
View file

@ -1,4 +1,5 @@
# <img src="logo/logo-black-on-white.svg" alt="django-components" style="max-width: 100%; background: white; color: black;">
<a href="https://github.com/EmilStenstrom/django-components/actions?query=workflow%3A%22Run+tests%22"><img align="right" src="https://github.com/EmilStenstrom/django-components/workflows/Run%20tests/badge.svg" alt="Show test status"></a>
<a href="https://pepy.tech/project/django-components"><img align="right" src="https://pepy.tech/badge/django-components" alt="Show download stats"></a>
@ -47,11 +48,12 @@ And this is what gets rendered (plus the CSS and Javascript you've specified):
## Release notes
🚨📢 **Version 0.85** Autodiscovery module resolution changed. Following undocumented behavior was removed:
- Previously, autodiscovery also imported any `[app]/components.py` files, and used `SETTINGS_MODULE` to search for component dirs.
- To migrate from:
- `[app]/components.py` - Define each module in `COMPONENTS.libraries` setting,
or import each module inside the `AppConfig.ready()` hook in respective `apps.py` files.
- `SETTINGS_MODULE` - Define component dirs using `STATICFILES_DIRS`
- To migrate from:
- `[app]/components.py` - Define each module in `COMPONENTS.libraries` setting,
or import each module inside the `AppConfig.ready()` hook in respective `apps.py` files.
- `SETTINGS_MODULE` - Define component dirs using `STATICFILES_DIRS`
- Previously, autodiscovery handled relative files in `STATICFILES_DIRS`. To align with Django, `STATICFILES_DIRS` now must be full paths ([Django docs](https://docs.djangoproject.com/en/5.0/ref/settings/#std-setting-STATICFILES_DIRS)).
🚨📢 **Version 0.81** Aligned the `render_to_response` method with the (now public) `render` method of `Component` class. Moreover, slots passed to these can now be rendered also as functions.
@ -64,12 +66,12 @@ And this is what gets rendered (plus the CSS and Javascript you've specified):
- BREAKING CHANGE: Default value for the `COMPONENTS.context_behavior` setting was changes from `"isolated"` to `"django"`. If you did not set this value explicitly before, this may be a breaking change. See the rationale for change [here](https://github.com/EmilStenstrom/django-components/issues/498).
🚨📢 **Version 0.77** CHANGED the syntax for accessing default slot content.
- Previously, the syntax was
`{% fill "my_slot" as "alias" %}` and `{{ alias.default }}`.
`{% fill "my_slot" as "alias" %}` and `{{ alias.default }}`.
- Now, the syntax is
`{% fill "my_slot" default="alias" %}` and `{{ alias }}`.
`{% fill "my_slot" default="alias" %}` and `{{ alias }}`.
**Version 0.74** introduces `html_attrs` tag and `prefix:key=val` construct for passing dicts to components.
@ -88,7 +90,7 @@ This change is done to simplify the API in anticipation of a 1.0 release of djan
**Version 0.28** introduces 'implicit' slot filling and the `default` option for `slot` tags.
**Version 0.27** adds a second installable app: *django_components.safer_staticfiles*. It provides the same behavior as *django.contrib.staticfiles* but with extra security guarantees (more info below in Security Notes).
**Version 0.27** adds a second installable app: _django_components.safer_staticfiles_. It provides the same behavior as _django.contrib.staticfiles_ but with extra security guarantees (more info below in Security Notes).
**Version 0.26** changes the syntax for `{% slot %}` tags. From now on, we separate defining a slot (`{% slot %}`) from filling a slot with content (`{% fill %}`). This means you will likely need to change a lot of slot tags to fill. We understand this is annoying, but it's the only way we can get support for nested slots that fill in other slots, which is a very nice featuPpre to have access to. Hoping that this will feel worth it!
@ -98,7 +100,7 @@ This change is done to simplify the API in anticipation of a 1.0 release of djan
## Security notes 🚨
*You are advised to read this section before using django-components in production.*
_You are advised to read this section before using django-components in production._
### Static files
@ -108,12 +110,12 @@ This means that files containing backend logic, such as Python modules and HTML
If your are using _django.contrib.staticfiles_ to collect static files, no distinction is made between the different kinds of files.
As a result, your Python code and templates may inadvertently become available on your static file server.
You probably don't want this, as parts of your backend logic will be exposed, posing a __potential security vulnerability__.
You probably don't want this, as parts of your backend logic will be exposed, posing a **potential security vulnerability**.
As of *v0.27*, django-components ships with an additional installable app *django_components.__safer_staticfiles__*.
It is a drop-in replacement for *django.contrib.staticfiles*.
As of _v0.27_, django-components ships with an additional installable app _django_components.**safer_staticfiles**_.
It is a drop-in replacement for _django.contrib.staticfiles_.
Its behavior is 100% identical except it ignores .py and .html files, meaning these will not end up on your static files server.
To use it, add it to INSTALLED_APPS and remove _django.contrib.staticfiles_.
To use it, add it to INSTALLED*APPS and remove \_django.contrib.staticfiles*.
```python
INSTALLED_APPS = [
@ -163,7 +165,8 @@ For a step-by-step guide on deploying production server with static files,
```
4. Modify `TEMPLATES` section of settings.py as follows:
- *Remove `'APP_DIRS': True,`*
- _Remove `'APP_DIRS': True,`_
- Add `loaders` to `OPTIONS` list and set it to following value:
```python
@ -226,13 +229,13 @@ Read on to find out how to build your first component!
Django-components supports all supported combinations versions of [Django](https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django) and [Python](https://devguide.python.org/versions/#versions).
| Python version | Django version |
|----------------|--------------------------|
| 3.8 | 4.2 |
| 3.9 | 4.2 |
| 3.10 | 4.2, 5.0 |
| 3.11 | 4.2, 5.0 |
| 3.12 | 4.2, 5.0 |
| Python version | Django version |
| -------------- | -------------- |
| 3.8 | 4.2 |
| 3.9 | 4.2 |
| 3.10 | 4.2, 5.0 |
| 3.11 | 4.2, 5.0 |
| 3.12 | 4.2, 5.0 |
## Create your first component
@ -293,10 +296,10 @@ Inside this file we create a Component by inheriting from the Component class an
```python
# In a file called [project root]/components/calendar/calendar.py
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
# will be automatically found. To customize which template to use based on context
# you can override method `get_template_name` instead of specifying `template_name`.
@ -324,26 +327,25 @@ Components can also be defined in a single file, which is useful for small compo
```python
# In a file called [project root]/components/calendar.py
from django_components import component
from django_components import types as t
from django_components import Component, register, types
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
def get_context_data(self, date):
return {
"date": date,
}
template: t.django_html = """
template: types.django_html = """
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
"""
css: t.css = """
css: types.css = """
.calendar-component { width: 200px; background: pink; }
.calendar-component span { font-weight: bold; }
"""
js: t.js = """
js: types.js = """
(function(){
if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
@ -365,10 +367,10 @@ Note, in the above example, that the `t.django_html`, `t.css`, and `t.js` types
If you're a Pycharm user (or any other editor from Jetbrains), you can have coding assistance as well:
```python
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
def get_context_data(self, date):
return {
"date": date,
@ -395,8 +397,8 @@ class Calendar(component.Component):
"""
```
You don't need to use `t.django_html`, `t.css`, `t.js` and `from django_components import types as t` since Pycharm uses [language injections](https://www.jetbrains.com/help/pycharm/using-language-injections.html).
You only need to write the comments `# language=<lang>` above the variables.
You don't need to use `types.django_html`, `types.css`, `types.js` since Pycharm uses [language injections](https://www.jetbrains.com/help/pycharm/using-language-injections.html).
You only need to write the comments `# language=<lang>` above the variables.
## Use components in templates
@ -452,7 +454,7 @@ Components can be rendered outside of Django templates, calling them as regular
The component class defines `render` and `render_to_response` class methods. These methods accept positional args, kwargs, and slots, offering the same flexibility as the `{% component %}` tag:
```py
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template = """
{% load component_tags %}
hello: {{ hello }}
@ -500,21 +502,21 @@ Component.render(
```
- _`args`_ - Positional args for the component. This is the same as calling the component
as `{% component "my_comp" arg1 arg2 ... %}`
as `{% component "my_comp" arg1 arg2 ... %}`
- _`kwargs`_ - Keyword args for the component. This is the same as calling the component
as `{% component "my_comp" key1=val1 key2=val2 ... %}`
as `{% component "my_comp" key1=val1 key2=val2 ... %}`
- _`slots`_ - Component slot fills. This is the same as pasing `{% fill %}` tags to the component.
Accepts a dictionary of `{ slot_name: slot_content }` where `slot_content` can be a string
or [`SlotRenderFunc`](#slotrenderfunc).
Accepts a dictionary of `{ slot_name: slot_content }` where `slot_content` can be a string
or [`SlotRenderFunc`](#slotrenderfunc).
- _`escape_slots_content`_ - Whether the content from `slots` should be escaped. `True` by default to prevent XSS attacks. If you disable escaping, you should make sure that any content you pass to the slots is safe, especially if it comes from user input.
- _`context`_ - A context (dictionary or Django's Context) within which the component
is rendered. The keys on the context can be accessed from within the template.
- NOTE: In "isolated" mode, context is NOT accessible, and data MUST be passed via
component's args and kwargs.
is rendered. The keys on the context can be accessed from within the template.
- NOTE: In "isolated" mode, context is NOT accessible, and data MUST be passed via
component's args and kwargs.
#### `SlotRenderFunc`
@ -534,9 +536,10 @@ def render_func(
- _`context`_ - Django's Context available to the Slot Node.
- _`data`_ - Data passed to the `{% slot %}` tag. See [Scoped Slots](#scoped-slots).
- _`slot_ref`_ - The default slot content. See [Accessing original content of slots](#accessing-original-content-of-slots).
- NOTE: The slot is lazily evaluated. To render the slot, convert it to string with `str(slot_ref)`.
- NOTE: The slot is lazily evaluated. To render the slot, convert it to string with `str(slot_ref)`.
Example:
```py
def footer_slot(ctx, data, slot_ref):
return f"""
@ -565,7 +568,7 @@ class MyResponse(HttpResponse):
self.headers = ...
self.status = ...
class SimpleComponent(component.Component):
class SimpleComponent(Component):
response_class = MyResponse
template: types.django_html = "HELLO"
@ -585,10 +588,10 @@ Here's an example of a calendar component defined as a view:
```python
# In a file called [project root]/components/calendar.py
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
template = """
<div class="calendar-component">
@ -642,13 +645,13 @@ If you're planning on passing an HTML string, check Django's use of [`format_htm
## Autodiscovery
Every component that you want to use in the template with the `{% component %}` tag needs to be registered with the ComponentRegistry. Normally, we use the `@component.register` decorator for that:
Every component that you want to use in the template with the `{% component %}` tag needs to be registered with the ComponentRegistry. Normally, we use the `@register` decorator for that:
```py
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
...
```
@ -679,7 +682,7 @@ Autodiscovery occurs when Django is loaded, during the `ready` hook of the `apps
If you are using autodiscovery, keep a few points in mind:
- Avoid defining any logic on the module-level inside the `components` dir, that you would not want to run anyway.
- Components inside the auto-imported files still need to be registered with `@component.register()`
- Components inside the auto-imported files still need to be registered with `@register()`
- Auto-imported component files must be valid Python modules, they must use suffix `.py`, and module name should follow [PEP-8](https://peps.python.org/pep-0008/#package-and-module-names).
Autodiscovery can be disabled in the [settings](#disable-autodiscovery).
@ -784,12 +787,8 @@ The rendered result (exactly the same as before):
```html
<div class="calendar-component">
<div class="header">
Calendar header
</div>
<div class="body">
Can you believe it's already <span>2020-06-06</span>??
</div>
<div class="header">Calendar header</div>
<div class="body">Can you believe it's already <span>2020-06-06</span>??</div>
</div>
```
@ -897,17 +896,20 @@ Which you can then use are regular default slot:
_Added in version 0.26_
> NOTE: In version 0.77, the syntax was changed from
>
> ```django
> {% fill "my_slot" as "alias" %} {{ alias.default }}
> ```
>
> to
>
> ```django
> {% fill "my_slot" default="slot_default" %} {{ slot_default }}
> ```
Sometimes you may want to keep the original slot, but only wrap or prepend/append content to it. To do so, you can access the default slot via the `default` kwarg.
Similarly to the `data` attribute, you specify the variable name through which the default slot will be made available.
Similarly to the `data` attribute, you specify the variable name through which the default slot will be made available.
For instance, let's say you're filling a slot called 'body'. To render the original slot, assign it to a variable using the `'default'` keyword. You then render this variable to insert the default content:
@ -959,11 +961,8 @@ explicit fills, the div containing the slot is still rendered, as shown below:
```html
<div class="frontmatter-component">
<div class="title">
Title
</div>
<div class="subtitle">
</div>
<div class="title">Title</div>
<div class="subtitle"></div>
</div>
```
@ -1033,8 +1032,8 @@ _Added in version 0.76_:
Consider a component with slot(s). This component may do some processing on the inputs, and then use the processed variable in the slot's default template:
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template = """
<div>
{% slot "content" default %}
@ -1062,8 +1061,8 @@ Using scoped slots consists of two steps:
To pass the data to the `slot` tag, simply pass them as keyword attributes (`key=value`):
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template = """
<div>
{% slot "content" default input=input %}
@ -1153,8 +1152,8 @@ so are still valid:
These can then be accessed inside `get_context_data` so:
```py
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
# Since # . @ - are not valid identifiers, we have to
# use `**kwargs` so the method can accept these args.
def get_context_data(self, **kwargs):
@ -1178,8 +1177,8 @@ In such cases, we may want to define some HTML attributes statically, and other
But for that, we need to define this dictionary on Python side:
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template = """
{% component "other" attrs=attrs %}
{% endcomponent %}
@ -1206,8 +1205,8 @@ we prefix the key with the name of the dict and `:`. So key `class` of input `at
`attrs:class`. And our example becomes:
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template = """
{% component "other"
attrs:class="pa-4 flex"
@ -1296,8 +1295,7 @@ And template:
Then this renders:
```html
<div class="text-green">
</div>
<div class="text-green"></div>
```
### Boolean attributes
@ -1305,8 +1303,7 @@ Then this renders:
In HTML, boolean attributes are usually rendered with no value. Consider the example below where the first button is disabled and the second is not:
```html
<button disabled> Click me! </button>
<button> Click me! </button>
<button disabled>Click me!</button> <button>Click me!</button>
```
HTML rendering with `html_attrs` tag or `attributes_to_string` works the same way, where `key=True` is rendered simply as `key`, and `key=False` is not render at all.
@ -1330,8 +1327,7 @@ And template:
Then this renders:
```html
<div disabled>
</div>
<div disabled></div>
```
### Default attributes
@ -1380,8 +1376,7 @@ And on `html_attrs` tag, we set the key `class`:
Then these will be merged and rendered as:
```html
<div data-value="my-class pa-4 some-class">
</div>
<div data-value="my-class pa-4 some-class"></div>
```
To simplify merging of variables, you can supply the same key multiple times, and these will be all joined together:
@ -1499,8 +1494,8 @@ Then:
### Full example for `html_attrs`
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template: t.django_html = """
<div
{% html_attrs attrs
@ -1521,8 +1516,8 @@ class MyComp(component.Component):
"class_from_var": "extra-class"
}
@component.register("parent")
class Parent(component.Component):
@register("parent")
class Parent(Component):
template: t.django_html = """
{% component "my_comp"
date=date
@ -1555,8 +1550,7 @@ will be appended to it.
So by default, `MyComp` renders:
```html
<div class="pa-4 text-red my-comp-date extra-class" data-id="123">
...
<div class="pa-4 text-red my-comp-date extra-class" data-id="123">...</div>
```
Next, let's consider what will be rendered when we call `MyComp` from `Parent`
@ -1626,6 +1620,7 @@ attributes_to_string(attrs)
_New in version 0.80_:
Django components supports dependency injection with the combination of:
1. `{% provide %}` tag
1. `inject()` method of the `Component` class
@ -1646,6 +1641,7 @@ This feature is inspired by Vue's [Provide / Inject](https://vuejs.org/guide/com
### How to use provide / inject
As the name suggest, using provide / inject consists of 2 steps
1. Providing data
2. Injecting provided data
@ -1668,6 +1664,7 @@ First we use the `{% provide %}` tag to define the data we want to "provide" (ma
Notice that the `provide` tag REQUIRES a name as a first argument. This is the _key_ by which we can then access the data passed to this tag.
`provide` tag _key_, similarly to the _name_ argument in `component` or `slot` tags, has these requirements:
- The _key_ must be a string literal
- It must be a valid identifier (AKA a valid Python variable name)
@ -1675,9 +1672,10 @@ Once you've set the name, you define the data you want to "provide" by passing i
> NOTE: Kwargs passed to `{% provide %}` are NOT added to the context.
> In the example below, the `{{ key }}` won't render anything:
>
> ```django
> {% provide "my_data" key="hi" another=123 %}
> {{ key }}
> {{ key }}
> {% endprovide %}
> ```
@ -1690,7 +1688,7 @@ For a component to be able to "inject" some data, the component (`{% component %
In the example from previous section, we've defined two kwargs: `key="hi" another=123`. That means that if we now inject `"my_data"`, we get an object with 2 attributes - `key` and `another`.
```py
class ChildComponent(component.Component):
class ChildComponent(Component):
def get_context_data(self):
my_data = self.inject("my_data")
print(my_data.key) # hi
@ -1705,7 +1703,7 @@ with given key is found, `inject` raises a `KeyError`.
To avoid the error, you can pass a second argument to `inject` to which will act as a default value, similar to `dict.get(key, default)`:
```py
class ChildComponent(component.Component):
class ChildComponent(Component):
def get_context_data(self):
my_data = self.inject("invalid_key", DEFAULT_DATA)
assert my_data == DEFAUKT_DATA
@ -1717,12 +1715,11 @@ have all the keys that were passed to the `provide` tag.
> NOTE: `inject()` works strictly only in `get_context_data`. If you try to call it from elsewhere, it will raise an error.
### Full example
```py
@component.register("child")
class ChildComponent(component.Component):
@register("child")
class ChildComponent(Component):
template = """
<div> {{ my_data.key }} </div>
<div> {{ my_data.another }} </div>
@ -1744,15 +1741,15 @@ template_str = """
renders:
```html
<div> hi </div>
<div> 123 </div>
<div>hi</div>
<div>123</div>
```
## Component context and scope
By default, context variables are passed down the template as in regular Django - deeper scopes can access the variables from the outer scopes. So if you have several nested forloops, then inside the deep-most loop you can access variables defined by all previous loops.
With this in mind, the `{% component %}` tag behaves similarly to `{% include %}` tag - inside the component tag, you can access all variables that were defined outside of it.
With this in mind, the `{% component %}` tag behaves similarly to `{% include %}` tag - inside the component tag, you can access all variables that were defined outside of it.
And just like with `{% include %}`, if you don't want a specific component template to have access to the parent context, add `only` to the end of the `{% component %}` tag:
@ -1771,6 +1768,7 @@ Components can also access the outer context in their context methods like `get_
django_component's management of files builds on top of [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/).
To be familiar with how Django handles static files, we recommend reading also:
- [How to manage static files (e.g. images, JavaScript, CSS)](https://docs.djangoproject.com/en/5.0/howto/static-files/)
### Defining file paths relative to component or static dirs
@ -1780,10 +1778,10 @@ files with a component, you set them as `template_name`, `Media.js` and `Media.c
```py
# In a file [project root]/components/calendar/calendar.py
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
template_name = "template.html"
class Media:
@ -1799,10 +1797,10 @@ Assuming that `STATICFILES_DIRS` contains path `[project root]/components`, we c
```py
# In a file [project root]/components/calendar/calendar.py
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
template_name = "calendar/template.html"
class Media:
@ -1817,7 +1815,7 @@ NOTE: In case of conflict, the preference goes to resolving the files relative t
Each component can have only a single template. However, you can define as many JS or CSS files as you want using a list.
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
js = ["path/to/script1.js", "path/to/script2.js"]
css = ["path/to/style1.css", "path/to/style2.css"]
@ -1833,7 +1831,7 @@ See the corresponding [Django Documentation](https://docs.djangoproject.com/en/5
Again, you can set either a single file or a list of files per media type:
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
css = {
"all": "path/to/style1.css",
@ -1842,7 +1840,7 @@ class MyComponent(component.Component):
```
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
css = {
"all": ["path/to/style1.css", "path/to/style2.css"],
@ -1852,10 +1850,10 @@ class MyComponent(component.Component):
NOTE: When you define CSS as a string or a list, the `all` media type is implied.
### Supported types for file paths
File paths can be any of:
- `str`
- `bytes`
- `PathLike` (`__fspath__` method)
@ -1867,7 +1865,7 @@ from pathlib import Path
from django.utils.safestring import mark_safe
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = [
mark_safe('<link href="/static/calendar/style.css" rel="stylesheet" />'),
@ -1906,8 +1904,8 @@ class LazyJsPath:
f'<script type="module" src="{full_path}"></script>'
)
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
template_name = "calendar/template.html"
def get_context_data(self, date):
@ -1919,7 +1917,7 @@ class Calendar(component.Component):
css = "calendar/style.css"
js = [
# <script> tag constructed by Media class
"calendar/script1.js",
"calendar/script1.js",
# Custom <script> tag
LazyJsPath("calendar/script2.js"),
]
@ -1935,7 +1933,7 @@ To change how the tags are constructed, you can override the [`Media.render_js`
```py
from django.forms.widgets import Media
from django_components import component
from django_components import Component, register
class MyMedia(Media):
# Same as original Media.render_js, except
@ -1952,14 +1950,14 @@ class MyMedia(Media):
)
return tags
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
template_name = "calendar/template.html"
class Media:
css = "calendar/style.css"
js = "calendar/script.js"
# Override the behavior of Media class
media_class = MyMedia
```
@ -1970,6 +1968,7 @@ NOTE: The instance of the `Media` class (or it's subclass) is available under `C
The JS and CSS files included in components are not automatically rendered.
Instead, use the following tags to specify where to render the dependencies:
- `component_dependencies` - Renders both JS and CSS
- `component_js_dependencies` - Renders only JS
- `component_css_dependencies` - Reneders only CSS
@ -2021,16 +2020,16 @@ COMPONENTS = {
Where `mysite/components/forms.py` may look like this:
```py
@component.register("form_simple")
class FormSimple(component.Component):
@register("form_simple")
class FormSimple(Component):
template = """
<form>
...
</form>
"""
@component.register("form_other")
class FormOther(component.Component):
@register("form_other")
class FormOther(Component):
template = """
<form>
...
@ -2080,15 +2079,17 @@ This has two modes:
- `"django"` - Default - The default Django template behavior.
Inside the `{% fill %}` tag, the context variables you can access are a union of:
- All the variables that were OUTSIDE the fill tag, including any loops or with tag
- Data returned from `get_context_data()` of the component that wraps the fill tag.
Inside the `{% fill %}` tag, the context variables you can access are a union of:
- All the variables that were OUTSIDE the fill tag, including any loops or with tag
- Data returned from `get_context_data()` of the component that wraps the fill tag.
- `"isolated"` - Similar behavior to [Vue](https://vuejs.org/guide/components/slots.html#render-scope) or React, this is useful if you want to make sure that components don't accidentally access variables defined outside of the component.
Inside the `{% fill %}` tag, you can ONLY access variables from 2 places:
- `get_context_data()` of the component which defined the template (AKA the "root" component)
- Any loops (`{% for ... %}`) that the `{% fill %}` tag is part of.
Inside the `{% fill %}` tag, you can ONLY access variables from 2 places:
- `get_context_data()` of the component which defined the template (AKA the "root" component)
- Any loops (`{% for ... %}`) that the `{% fill %}` tag is part of.
```python
COMPONENTS = {
@ -2101,7 +2102,7 @@ COMPONENTS = {
Given this template:
```py
class RootComp(component.Component):
class RootComp(Component):
template = """
{% with cheese="feta" %}
{% component 'my_comp' %}
@ -2138,7 +2139,7 @@ all the data defined in the outer layers, like the `{% with %}` tag.
Given this template:
```py
class RootComp(component.Component):
class RootComp(Component):
template = """
{% with cheese="feta" %}
{% component 'my_comp' %}

View file

@ -3,13 +3,13 @@ from time import perf_counter
from django.template import Context, Template
from django.test import override_settings
from django_components import component, types
from django_components import Component, registry, types
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
from tests.django_test_setup import * # NOQA
from tests.testutils import BaseTestCase, create_and_process_template_response
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -20,7 +20,7 @@ class SlottedComponent(component.Component):
"""
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -36,7 +36,7 @@ class SimpleComponent(component.Component):
js = ["script.js"]
class BreadcrumbComponent(component.Component):
class BreadcrumbComponent(Component):
template_name = "mdn_component_template.html"
LINKS = [
@ -77,10 +77,10 @@ EXPECTED_JS = """<script src="test.js"></script>"""
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class RenderBenchmarks(BaseTestCase):
def setUp(self):
component.registry.clear()
component.registry.register("test_component", SlottedComponent)
component.registry.register("inner_component", SimpleComponent)
component.registry.register("breadcrumb_component", BreadcrumbComponent)
registry.clear()
registry.register("test_component", SlottedComponent)
registry.register("inner_component", SimpleComponent)
registry.register("breadcrumb_component", BreadcrumbComponent)
@staticmethod
def timed_loop(func, iterations=1000):

View file

@ -15,12 +15,14 @@
And components that make use of `abc.html` via `include` or `extends`:
```py
@component.register("my_comp_extends")
class MyCompWithExtends(component.Component):
from django_components import Component, register
@register("my_comp_extends")
class MyCompWithExtends(Component):
template = """{% extends "abc.html" %}"""
@component.register("my_comp_include")
class MyCompWithInclude(component.Component):
@register("my_comp_include")
class MyCompWithInclude(Component):
template = """{% include "abc.html" %}"""
```
@ -61,8 +63,8 @@
and component `my_comp`:
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template_name = "abc.html"
```
@ -108,8 +110,8 @@
`abc.html` will render `OVERRIDEN`:
````py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template_name = """
{% extends "abc.html" %}
@ -125,8 +127,8 @@
new `slots` inside these "overriding" blocks:
```py
@component.register("my_comp")
class MyComp(component.Component):
@register("my_comp")
class MyComp(Component):
template_name = """
{% extends "abc.html" %}

View file

@ -1,8 +1,8 @@
from django_components import component
from django_components import Component, register
@component.register("calendar")
class Calendar(component.Component):
@register("calendar")
class Calendar(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable.
@ -25,8 +25,8 @@ class Calendar(component.Component):
js = "calendar/calendar.js"
@component.register("calendar_relative")
class CalendarRelative(component.Component):
@register("calendar_relative")
class CalendarRelative(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable.

View file

@ -1,11 +1,10 @@
from typing import Any, Dict
from django_components import component
from django_components import types as t
from django_components import Component, register, types
@component.register("greeting")
class Greeting(component.Component):
@register("greeting")
class Greeting(Component):
def get(self, request, *args, **kwargs):
slots = {"message": "Hello, world!"}
context = {"name": request.GET.get("name", "")}
@ -14,12 +13,12 @@ class Greeting(component.Component):
def get_context_data(self, name, *args, **kwargs) -> Dict[str, Any]:
return {"name": name}
template: t.django_html = """
template: types.django_html = """
<div id="greeting">Hello, {{ name }}!</div>
{% slot "message" %}{% endslot %}
"""
css: t.css = """
css: types.css = """
#greeting {
display: inline-block;
color: blue;
@ -27,7 +26,7 @@ class Greeting(component.Component):
}
"""
js: t.js = """
js: types.js = """
document.getElementById("greeting").addEventListener("click", (event) => {
alert("Hello!");
});

View file

@ -1,8 +1,8 @@
from django_components import component
from django_components import Component, register
@component.register("calendar_nested")
class CalendarNested(component.Component):
@register("calendar_nested")
class CalendarNested(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable.

View file

@ -1,8 +1,8 @@
from django_components import component
from django_components import Component, register
@component.register("todo")
class Calendar(component.Component):
@register("todo")
class Calendar(Component):
# Note that Django will look for templates inside `[your apps]/components` dir and
# `[project root]/components` dir. To customize which template to use based on context
# you can override def get_template_name() instead of specifying the below variable.

View file

@ -1,7 +1,19 @@
# flake8: noqa F401
import django
from django_components.autodiscover import autodiscover as autodiscover # NOQA
from django_components.autodiscover import import_libraries as import_libraries # NOQA
# Public API
# isort: off
from django_components.autodiscover import autodiscover as autodiscover
from django_components.autodiscover import import_libraries as import_libraries
from django_components.component import Component as Component
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered
from django_components.component_registry import ComponentRegistry as ComponentRegistry
from django_components.component_registry import NotRegistered as NotRegistered
from django_components.component_registry import register as register
from django_components.component_registry import registry as registry
import django_components.types as types
# isort: on
if django.VERSION < (3, 2):
default_app_config = "django_components.apps.ComponentsConfig"

View file

@ -15,16 +15,7 @@ from django.utils.safestring import SafeString, mark_safe
from django.views import View
from django_components.component_media import ComponentMediaInput, MediaMeta
# Global registry var and register() function moved to separate module.
# Defining them here made little sense, since 1) component_tags.py and component.py
# rely on them equally, and 2) it made it difficult to avoid circularity in the
# way the two modules depend on one another.
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered # NOQA
from django_components.component_registry import ComponentRegistry as ComponentRegistry # NOQA
from django_components.component_registry import NotRegistered as NotRegistered # NOQA
from django_components.component_registry import register as register # NOQA
from django_components.component_registry import registry # NOQA
from django_components.component_registry import registry
from django_components.context import (
_FILLED_SLOTS_CONTENT_CONTEXT_KEY,
_PARENT_COMP_CONTEXT_KEY,
@ -50,6 +41,16 @@ from django_components.slots import (
from django_components.template_parser import process_aggregate_kwargs
from django_components.utils import gen_id
# TODO_DEPRECATE_V1 - REMOVE IN V1, users should use top-level import instead
# isort: off
from django_components.component_registry import AlreadyRegistered as AlreadyRegistered # NOQA
from django_components.component_registry import ComponentRegistry as ComponentRegistry # NOQA
from django_components.component_registry import NotRegistered as NotRegistered # NOQA
from django_components.component_registry import register as register # NOQA
from django_components.component_registry import registry as registry # NOQA
# isort: on
RENDERED_COMMENT_TEMPLATE = "<!-- _RENDERED {name} -->"
@ -202,8 +203,10 @@ class Component(View, metaclass=ComponentMeta):
And given this definition of "my_comp" component:
```py
@component.register("my_comp")
class MyComp(component.Component):
from django_components import Component, register
@register("my_comp")
class MyComp(Component):
template = "hi {{ data.hello }}!"
def get_context_data(self):
data = self.inject("provider")

View file

@ -34,7 +34,7 @@ class MediaMeta(MediaDefiningClass):
1. As plain strings
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
js = "path/to/script.js"
css = "path/to/style.css"
@ -42,7 +42,7 @@ class MediaMeta(MediaDefiningClass):
2. As lists
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
js = ["path/to/script1.js", "path/to/script2.js"]
css = ["path/to/style1.css", "path/to/style2.css"]
@ -50,7 +50,7 @@ class MediaMeta(MediaDefiningClass):
3. [CSS ONLY] Dicts of strings
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
css = {
"all": "path/to/style1.css",
@ -60,7 +60,7 @@ class MediaMeta(MediaDefiningClass):
4. [CSS ONLY] Dicts of lists
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
css = {
"all": ["path/to/style1.css"],
@ -74,7 +74,7 @@ class MediaMeta(MediaDefiningClass):
and `my_comp.py` looks like this:
```py
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
js = "script.js"
```
@ -90,7 +90,7 @@ class MediaMeta(MediaDefiningClass):
# do something
return path
class MyComponent(component.Component):
class MyComponent(Component):
class Media:
js = b"script.js"
css = lazy_eval_css
@ -106,7 +106,7 @@ class MediaMeta(MediaDefiningClass):
def render_js(self):
...
class MyComponent(component.Component):
class MyComponent(Component):
media_class = MyMedia
def get_context_data(self):
assert isinstance(self.media, MyMedia)

View file

@ -125,10 +125,10 @@ class Command(BaseCommand):
with open(os.path.join(component_path, f"{name}.py"), "w") as f:
py_content = dedent(
f"""
from django_components import component
from django_components import Component, register
@component.register("{name}")
class {name.capitalize()}(component.Component):
@register("{name}")
class {name.capitalize()}(Component):
template_name = "{name}/{template_filename}"
def get_context_data(self, value):

View file

@ -2,11 +2,11 @@ from typing import Any, Dict
from django.http import HttpResponse
from django_components import component
from django_components import Component, register
@component.register("multi_file_component")
class MultFileComponent(component.Component):
@register("multi_file_component")
class MultFileComponent(Component):
template_name = "multi_file/multi_file.html"
def post(self, request, *args, **kwargs) -> HttpResponse:

View file

@ -2,11 +2,11 @@ from typing import Any, Dict
from django.http import HttpResponse
from django_components import component
from django_components import Component, register
@component.register("relative_file_component")
class RelativeFileComponent(component.Component):
@register("relative_file_component")
class RelativeFileComponent(Component):
template_name = "relative_file.html"
class Media:

View file

@ -3,7 +3,7 @@ from typing import Any, Dict
from django.templatetags.static import static
from django.utils.html import format_html, html_safe
from django_components import component
from django_components import Component, register
# Format as mentioned in https://github.com/EmilStenstrom/django-components/issues/522#issuecomment-2173577094
@ -21,8 +21,8 @@ class PathObj:
return format_html('<script type="module" src="{}"></script>', static(self.static_path))
@component.register("relative_file_pathobj_component")
class RelativeFileWithPathObjComponent(component.Component):
@register("relative_file_pathobj_component")
class RelativeFileWithPathObjComponent(Component):
template_name = "relative_file_pathobj.html"
class Media:

View file

@ -1,11 +1,11 @@
from typing import Any, Dict
from django_components import component
from django_components import Component, register
# Used for testing the safer_staticfiles app in `test_safer_staticfiles.py`
@component.register("safer_staticfiles_component")
class RelativeFileWithPathObjComponent(component.Component):
@register("safer_staticfiles_component")
class RelativeFileWithPathObjComponent(Component):
template_name = "safer_staticfiles.html"
class Media:

View file

@ -2,11 +2,11 @@ from typing import Any, Dict
from django.http import HttpResponse
from django_components import component, types
from django_components import Component, register, types
@component.register("single_file_component")
class SingleFileComponent(component.Component):
@register("single_file_component")
class SingleFileComponent(Component):
template: types.django_html = """
<form method="post">
{% csrf_token %}

View file

@ -1,7 +1,7 @@
from django.template import Context, Template, TemplateSyntaxError
from django.utils.safestring import SafeString, mark_safe
from django_components import component, types
from django_components import Component, register, types
from django_components.attributes import append_attributes, attributes_to_string
from .django_test_setup import setup_test_config
@ -84,8 +84,8 @@ class HtmlAttrsTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_tag_positional_args(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs attrs defaults class="added_class" class="another-class" data-id=123 %}>
@ -112,8 +112,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered)
def test_tag_raises_on_extra_positional_args(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs attrs defaults class %}>
@ -136,8 +136,8 @@ class HtmlAttrsTests(BaseTestCase):
template.render(Context({"class_var": "padding-top-8"}))
def test_tag_kwargs(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs attrs=attrs defaults=defaults class="added_class" class="another-class" data-id=123 %}>
@ -164,8 +164,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered)
def test_tag_kwargs_2(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs class="added_class" class="another-class" data-id=123 defaults=defaults attrs=attrs %}>
@ -192,8 +192,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered)
def test_tag_aggregate_args(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs attrs:class="from_agg_key" attrs:type="submit" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -219,8 +219,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered)
def test_tag_raises_on_aggregate_and_positional_args_for_attrs(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs attrs attrs:class="from_agg_key" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -237,8 +237,8 @@ class HtmlAttrsTests(BaseTestCase):
template.render(Context({"class_var": "padding-top-8"}))
def test_tag_raises_on_aggregate_and_positional_args_for_defaults(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs defaults=defaults attrs:class="from_agg_key" defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -258,8 +258,8 @@ class HtmlAttrsTests(BaseTestCase):
template.render(Context({"class_var": "padding-top-8"}))
def test_tag_no_attrs(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs defaults:class="override-me" class="added_class" class="another-class" data-id=123 %}>
@ -282,8 +282,8 @@ class HtmlAttrsTests(BaseTestCase):
)
def test_tag_no_defaults(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs attrs class="added_class" class="another-class" data-id=123 %}>
@ -312,8 +312,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered)
def test_tag_no_attrs_no_defaults(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs class="added_class" class="another-class" data-id=123 %}>
@ -337,8 +337,8 @@ class HtmlAttrsTests(BaseTestCase):
self.assertNotIn("override-me", rendered)
def test_tag_empty(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div {% html_attrs %}>

View file

@ -4,7 +4,7 @@ from unittest import TestCase, mock
from django.conf import settings
from django_components import component, component_registry
from django_components import AlreadyRegistered, registry
from django_components.autodiscover import _filepath_to_python_module, autodiscover, import_libraries
from .django_test_setup import setup_test_config
@ -15,14 +15,14 @@ from .django_test_setup import setup_test_config
class _TestCase(TestCase):
def tearDown(self) -> None:
super().tearDown()
component.registry.clear()
registry.clear()
class TestAutodiscover(_TestCase):
def test_autodiscover(self):
setup_test_config({"autodiscover": False})
all_components = component.registry.all().copy()
all_components = registry.all().copy()
self.assertNotIn("single_file_component", all_components)
self.assertNotIn("multi_file_component", all_components)
self.assertNotIn("relative_file_component", all_components)
@ -30,7 +30,7 @@ class TestAutodiscover(_TestCase):
try:
modules = autodiscover(map_module=lambda p: "tests." + p)
except component_registry.AlreadyRegistered:
except AlreadyRegistered:
self.fail("Autodiscover should not raise AlreadyRegistered exception")
self.assertIn("tests.components.single_file", modules)
@ -38,7 +38,7 @@ class TestAutodiscover(_TestCase):
self.assertIn("tests.components.relative_file_pathobj.relative_file_pathobj", modules)
self.assertIn("tests.components.relative_file.relative_file", modules)
all_components = component.registry.all().copy()
all_components = registry.all().copy()
self.assertIn("single_file_component", all_components)
self.assertIn("multi_file_component", all_components)
self.assertIn("relative_file_component", all_components)
@ -56,8 +56,8 @@ class TestImportLibraries(_TestCase):
settings.COMPONENTS["libraries"] = ["tests.components.single_file", "tests.components.multi_file.multi_file"]
# Ensure we start with a clean state
component.registry.clear()
all_components = component.registry.all().copy()
registry.clear()
all_components = registry.all().copy()
self.assertNotIn("single_file_component", all_components)
self.assertNotIn("multi_file_component", all_components)
@ -69,13 +69,13 @@ class TestImportLibraries(_TestCase):
try:
modules = import_libraries()
except component_registry.AlreadyRegistered:
except AlreadyRegistered:
self.fail("Autodiscover should not raise AlreadyRegistered exception")
self.assertIn("tests.components.single_file", modules)
self.assertIn("tests.components.multi_file.multi_file", modules)
all_components = component.registry.all().copy()
all_components = registry.all().copy()
self.assertIn("single_file_component", all_components)
self.assertIn("multi_file_component", all_components)
@ -91,8 +91,8 @@ class TestImportLibraries(_TestCase):
settings.COMPONENTS["libraries"] = ["components.single_file", "components.multi_file.multi_file"]
# Ensure we start with a clean state
component.registry.clear()
all_components = component.registry.all().copy()
registry.clear()
all_components = registry.all().copy()
self.assertNotIn("single_file_component", all_components)
self.assertNotIn("multi_file_component", all_components)
@ -104,13 +104,13 @@ class TestImportLibraries(_TestCase):
try:
modules = import_libraries(map_module=lambda p: "tests." + p)
except component_registry.AlreadyRegistered:
except AlreadyRegistered:
self.fail("Autodiscover should not raise AlreadyRegistered exception")
self.assertIn("tests.components.single_file", modules)
self.assertIn("tests.components.multi_file.multi_file", modules)
all_components = component.registry.all().copy()
all_components = registry.all().copy()
self.assertIn("single_file_component", all_components)
self.assertIn("multi_file_component", all_components)

View file

@ -9,7 +9,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse
from django.template import Context, Template, TemplateSyntaxError
from django_components import component, types
from django_components import Component, registry, types
from django_components.slots import SlotRef
from .django_test_setup import setup_test_config
@ -19,7 +19,7 @@ setup_test_config({"autodiscover": False})
class ComponentTest(BaseTestCase):
class ParentComponent(component.Component):
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -39,7 +39,7 @@ class ComponentTest(BaseTestCase):
def get_context_data(self):
return {"shadowing_variable": "NOT SHADOWED"}
class VariableDisplay(component.Component):
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
@ -56,12 +56,12 @@ class ComponentTest(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="parent_component", component=self.ParentComponent)
component.registry.register(name="variable_display", component=self.VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
registry.register(name="variable_display", component=self.VariableDisplay)
@parametrize_context_behavior(["django", "isolated"])
def test_empty_component(self):
class EmptyComponent(component.Component):
class EmptyComponent(Component):
pass
with self.assertRaises(ImproperlyConfigured):
@ -69,7 +69,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_template_string_static_inlined(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -93,7 +93,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_template_string_dynamic(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
def get_template_string(self, context):
content: types.django_html = """
Variable: <strong>{{ variable }}</strong>
@ -119,7 +119,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_template_name_static(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template_name = "simple_template.html"
def get_context_data(self, variable=None):
@ -141,7 +141,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_template_name_dynamic(self):
class SvgComponent(component.Component):
class SvgComponent(Component):
def get_context_data(self, name, css_class="", title="", **attrs):
return {
"name": name,
@ -168,7 +168,7 @@ class ComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_allows_to_override_get_template(self):
class TestComponent(component.Component):
class TestComponent(Component):
def get_context_data(self, variable, **attrs):
return {
"variable": variable,
@ -190,7 +190,7 @@ class ComponentTest(BaseTestCase):
class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_minimal(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
the_arg2: {{ the_arg2 }}
@ -230,7 +230,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_full(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
the_arg: {{ the_arg }}
@ -283,7 +283,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_to_response_full(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
the_arg: {{ the_arg }}
@ -342,7 +342,7 @@ class ComponentRenderTest(BaseTestCase):
def __init__(self, content: str) -> None:
self.content = bytes(content, "utf-8")
class SimpleComponent(component.Component):
class SimpleComponent(Component):
response_class = MyResponse
template: types.django_html = "HELLO"
@ -358,7 +358,7 @@ class ComponentRenderTest(BaseTestCase):
def test_render_slot_as_func(self, context_behavior_data):
is_isolated = context_behavior_data
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% slot "first" required data1="abc" data2:hello="world" data2:one=123 %}
@ -414,7 +414,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_raises_on_missing_slot(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% slot "first" required %}
@ -430,7 +430,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_with_include(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% include 'slotted_template.html' %}
@ -450,7 +450,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_with_extends(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{% extends 'block.html' %}
{% block body %}
@ -478,7 +478,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_can_access_instance(self):
class TestComponent(component.Component):
class TestComponent(Component):
template = "Variable: <strong>{{ id }}</strong>"
def get_context_data(self, **attrs):
@ -494,7 +494,7 @@ class ComponentRenderTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_render_to_response_can_access_instance(self):
class TestComponent(component.Component):
class TestComponent(Component):
template = "Variable: <strong>{{ id }}</strong>"
def get_context_data(self, **attrs):

View file

@ -6,7 +6,7 @@ from django.template import Context, Template
from django.test import Client
from django.urls import path
from django_components import component
from django_components import Component, register
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -30,8 +30,8 @@ class CustomClient(Client):
class TestComponentAsView(BaseTestCase):
def test_render_component_from_template(self):
@component.register("testcomponent")
class MockComponentRequest(component.Component):
@register("testcomponent")
class MockComponentRequest(Component):
template = """
<form method="post">
{% csrf_token %}
@ -64,7 +64,7 @@ class TestComponentAsView(BaseTestCase):
)
def test_get_request(self):
class MockComponentRequest(component.Component):
class MockComponentRequest(Component):
template = """
<form method="post">
{% csrf_token %}
@ -88,7 +88,7 @@ class TestComponentAsView(BaseTestCase):
)
def test_post_request(self):
class MockComponentRequest(component.Component):
class MockComponentRequest(Component):
template = """
<form method="post">
{% csrf_token %}
@ -114,7 +114,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_replace_slot_in_view(self):
class MockComponentSlot(component.Component):
class MockComponentSlot(Component):
template = """
{% load component_tags %}
<div>
@ -143,7 +143,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_replace_slot_in_view_with_insecure_content(self):
class MockInsecureComponentSlot(component.Component):
class MockInsecureComponentSlot(Component):
template = """
{% load component_tags %}
<div>
@ -165,7 +165,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_replace_context_in_view(self):
class TestComponent(component.Component):
class TestComponent(Component):
template = """
{% load component_tags %}
<div>
@ -186,7 +186,7 @@ class TestComponentAsView(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_replace_context_in_view_with_insecure_content(self):
class MockInsecureComponentContext(component.Component):
class MockInsecureComponentContext(Component):
template = """
{% load component_tags %}
<div>

View file

@ -9,7 +9,7 @@ from django.test import override_settings
from django.utils.html import format_html, html_safe
from django.utils.safestring import mark_safe
from django_components import component, types
from django_components import Component, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, autodiscover_with_cleanup
@ -19,7 +19,7 @@ setup_test_config()
class InlineComponentTest(BaseTestCase):
def test_html(self):
class InlineHTMLComponent(component.Component):
class InlineHTMLComponent(Component):
template = "<div class='inline'>Hello Inline</div>"
comp = InlineHTMLComponent("inline_html_component")
@ -29,7 +29,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_and_css(self):
class HTMLCSSComponent(component.Component):
class HTMLCSSComponent(Component):
template = "<div class='html-css-only'>Content</div>"
css = ".html-css-only { color: blue; }"
@ -44,7 +44,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_and_js(self):
class HTMLJSComponent(component.Component):
class HTMLJSComponent(Component):
template = "<div class='html-js-only'>Content</div>"
js = "console.log('HTML and JS only');"
@ -59,7 +59,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_inline_and_css_js_files(self):
class HTMLStringFileCSSJSComponent(component.Component):
class HTMLStringFileCSSJSComponent(Component):
template = "<div class='html-string-file'>Content</div>"
class Media:
@ -80,7 +80,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_js_inline_and_css_file(self):
class HTMLStringFileCSSJSComponent(component.Component):
class HTMLStringFileCSSJSComponent(Component):
template = "<div class='html-string-file'>Content</div>"
js = "console.log('HTML and JS only');"
@ -101,7 +101,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_css_inline_and_js_file(self):
class HTMLStringFileCSSJSComponent(component.Component):
class HTMLStringFileCSSJSComponent(Component):
template = "<div class='html-string-file'>Content</div>"
css = ".html-string-file { color: blue; }"
@ -121,7 +121,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_variable(self):
class VariableHTMLComponent(component.Component):
class VariableHTMLComponent(Component):
def get_template(self, context):
return Template("<div class='variable-html'>{{ variable }}</div>")
@ -133,7 +133,7 @@ class InlineComponentTest(BaseTestCase):
)
def test_html_variable_filtered(self):
class FilteredComponent(component.Component):
class FilteredComponent(Component):
template: types.django_html = """
Var1: <strong>{{ var1 }}</strong>
Var2 (uppercased): <strong>{{ var2|upper }}</strong>
@ -157,7 +157,7 @@ class InlineComponentTest(BaseTestCase):
class ComponentMediaTests(BaseTestCase):
def test_css_and_js(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -176,7 +176,7 @@ class ComponentMediaTests(BaseTestCase):
)
def test_css_only(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -194,7 +194,7 @@ class ComponentMediaTests(BaseTestCase):
)
def test_js_only(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -212,7 +212,7 @@ class ComponentMediaTests(BaseTestCase):
)
def test_empty_media(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -225,7 +225,7 @@ class ComponentMediaTests(BaseTestCase):
self.assertHTMLEqual(comp.render_dependencies(), "")
def test_missing_media(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -235,7 +235,7 @@ class ComponentMediaTests(BaseTestCase):
self.assertHTMLEqual(comp.render_dependencies(), "")
def test_css_js_as_lists(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = ["path/to/style.css", "path/to/style2.css"]
js = ["path/to/script.js"]
@ -251,7 +251,7 @@ class ComponentMediaTests(BaseTestCase):
)
def test_css_js_as_string(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = "path/to/style.css"
js = "path/to/script.js"
@ -266,7 +266,7 @@ class ComponentMediaTests(BaseTestCase):
)
def test_css_as_dict(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = {
"all": "path/to/style.css",
@ -295,7 +295,7 @@ class ComponentMediaTests(BaseTestCase):
tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>')
return tags
class SimpleComponent(component.Component):
class SimpleComponent(Component):
media_class = MyMedia
class Media:
@ -320,7 +320,7 @@ class ComponentMediaTests(BaseTestCase):
tags.append(f'<my_link href="{path}" media="{medium}" rel="stylesheet" />')
return tags
class SimpleComponent(component.Component):
class SimpleComponent(Component):
media_class = MyMedia
class Media:
@ -376,7 +376,7 @@ class MediaPathAsObjectTests(BaseTestCase):
def __str__(self):
return format_html('<script type="module" src="{}"></script>', static(self.static_path))
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = {
"all": [
@ -424,7 +424,7 @@ class MediaPathAsObjectTests(BaseTestCase):
def __fspath__(self):
return self.path
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = {
"all": [
@ -466,7 +466,7 @@ class MediaPathAsObjectTests(BaseTestCase):
class MyStr(str):
pass
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = {
"all": [
@ -506,7 +506,7 @@ class MediaPathAsObjectTests(BaseTestCase):
class MyBytes(bytes):
pass
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = {
"all": [
@ -538,7 +538,7 @@ class MediaPathAsObjectTests(BaseTestCase):
)
def test_function(self):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = [
lambda: mark_safe('<link hi href="calendar/style.css" rel="stylesheet" />'), # Literal
@ -573,7 +573,7 @@ class MediaPathAsObjectTests(BaseTestCase):
def test_works_with_static(self):
"""Test that all the different ways of defining media files works with Django's staticfiles"""
class SimpleComponent(component.Component):
class SimpleComponent(Component):
class Media:
css = [
mark_safe(f'<link hi href="{static("calendar/style.css")}" rel="stylesheet" />'), # Literal
@ -636,7 +636,7 @@ class MediaStaticfilesTests(BaseTestCase):
tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>')
return tags
class SimpleComponent(component.Component):
class SimpleComponent(Component):
media_class = MyMedia
class Media:
@ -693,7 +693,7 @@ class MediaStaticfilesTests(BaseTestCase):
tags.append(f'<my_script_tag src="{abs_path}"></my_script_tag>')
return tags
class SimpleComponent(component.Component):
class SimpleComponent(Component):
media_class = MyMedia
class Media:
@ -714,7 +714,7 @@ class MediaStaticfilesTests(BaseTestCase):
class MediaRelativePathTests(BaseTestCase):
class ParentComponent(component.Component):
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -734,7 +734,7 @@ class MediaRelativePathTests(BaseTestCase):
def get_context_data(self):
return {"shadowing_variable": "NOT SHADOWED"}
class VariableDisplay(component.Component):
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
@ -751,8 +751,8 @@ class MediaRelativePathTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="parent_component", component=self.ParentComponent)
component.registry.register(name="variable_display", component=self.VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
registry.register(name="variable_display", component=self.VariableDisplay)
# Settings required for autodiscover to work
@override_settings(
@ -771,11 +771,11 @@ class MediaRelativePathTests(BaseTestCase):
# Make sure that only relevant components are registered:
comps_to_remove = [
comp_name
for comp_name in component.registry.all()
for comp_name in registry.all()
if comp_name not in ["relative_file_component", "parent_component", "variable_display"]
]
for comp_name in comps_to_remove:
component.registry.unregister(comp_name)
registry.unregister(comp_name)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -810,7 +810,7 @@ class MediaRelativePathTests(BaseTestCase):
# Fix the paths, since the "components" dir is nested
with autodiscover_with_cleanup(map_module=lambda p: f"tests.{p}"):
component.registry.unregister("relative_file_pathobj_component")
registry.unregister("relative_file_pathobj_component")
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -852,7 +852,7 @@ class MediaRelativePathTests(BaseTestCase):
with autodiscover_with_cleanup(map_module=lambda p: f"tests.{p}"):
# Mark the PathObj instances of 'relative_file_pathobj_component' so they won raise
# error PathObj.__str__ is triggered.
CompCls = component.registry.get("relative_file_pathobj_component")
CompCls = registry.get("relative_file_pathobj_component")
CompCls.Media.js[0].throw_on_calling_str = False # type: ignore
CompCls.Media.css["all"][0].throw_on_calling_str = False # type: ignore

View file

@ -1,6 +1,6 @@
from django.template import Context, Template
from django_components import component, types
from django_components import Component, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -13,7 +13,7 @@ setup_test_config()
#########################
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -26,7 +26,7 @@ class SimpleComponent(component.Component):
return "Variable: < strong > {} < / strong >".format(variable_value)
class VariableDisplay(component.Component):
class VariableDisplay(Component):
template: types.django_html = """
{% load component_tags %}
<h1>Shadowing variable = {{ shadowing_variable }}</h1>
@ -42,7 +42,7 @@ class VariableDisplay(component.Component):
return context
class IncrementerComponent(component.Component):
class IncrementerComponent(Component):
template: types.django_html = """
{% load component_tags %}
<p class="incrementer">value={{ value }};calls={{ calls }}</p>
@ -68,7 +68,7 @@ class IncrementerComponent(component.Component):
class ContextTests(BaseTestCase):
class ParentComponent(component.Component):
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -90,8 +90,8 @@ class ContextTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="variable_display", component=VariableDisplay)
component.registry.register(name="parent_component", component=self.ParentComponent)
registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="parent_component", component=self.ParentComponent)
@parametrize_context_behavior(["django", "isolated"])
def test_nested_component_context_shadows_parent_with_unfilled_slots_and_component_tag(
@ -221,7 +221,7 @@ class ContextTests(BaseTestCase):
class ParentArgsTests(BaseTestCase):
class ParentComponentWithArgs(component.Component):
class ParentComponentWithArgs(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -243,9 +243,9 @@ class ParentArgsTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="incrementer", component=IncrementerComponent)
component.registry.register(name="parent_with_args", component=self.ParentComponentWithArgs)
component.registry.register(name="variable_display", component=VariableDisplay)
registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="parent_with_args", component=self.ParentComponentWithArgs)
registry.register(name="variable_display", component=VariableDisplay)
@parametrize_context_behavior(["django", "isolated"])
def test_parent_args_can_be_drawn_from_context(self):
@ -326,7 +326,7 @@ class ParentArgsTests(BaseTestCase):
class ContextCalledOnceTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="incrementer", component=IncrementerComponent)
registry.register(name="incrementer", component=IncrementerComponent)
@parametrize_context_behavior(["django", "isolated"])
def test_one_context_call_with_simple_component(self):
@ -416,7 +416,7 @@ class ContextCalledOnceTests(BaseTestCase):
class ComponentsCanAccessOuterContext(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=SimpleComponent)
# NOTE: Second arg in tuple is expected value.
@parametrize_context_behavior(
@ -443,7 +443,7 @@ class ComponentsCanAccessOuterContext(BaseTestCase):
class IsolatedContextTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=SimpleComponent)
@parametrize_context_behavior(["django", "isolated"])
def test_simple_component_can_pass_outer_context_in_args(self):
@ -469,7 +469,7 @@ class IsolatedContextTests(BaseTestCase):
class IsolatedContextSettingTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="simple_component", component=SimpleComponent)
registry.register(name="simple_component", component=SimpleComponent)
@parametrize_context_behavior(["isolated"])
def test_component_tag_includes_variable_with_isolated_context_from_settings(
@ -523,7 +523,7 @@ class IsolatedContextSettingTests(BaseTestCase):
class OuterContextPropertyTests(BaseTestCase):
class OuterContextComponent(component.Component):
class OuterContextComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -533,7 +533,7 @@ class OuterContextPropertyTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="outer_context_component", component=self.OuterContextComponent)
registry.register(name="outer_context_component", component=self.OuterContextComponent)
@parametrize_context_behavior(["django", "isolated"])
def test_outer_context_property_with_component(self):
@ -547,7 +547,7 @@ class OuterContextPropertyTests(BaseTestCase):
class ContextVarsIsFilledTests(BaseTestCase):
class IsFilledVarsComponent(component.Component):
class IsFilledVarsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div class="frontmatter-component">
@ -560,7 +560,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
</div>
"""
class ComponentWithConditionalSlots(component.Component):
class ComponentWithConditionalSlots(Component):
template: types.django_html = """
{# Example from django-components/issues/98 #}
{% load component_tags %}
@ -575,7 +575,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
</div>
"""
class ComponentWithComplexConditionalSlots(component.Component):
class ComponentWithComplexConditionalSlots(Component):
template: types.django_html = """
{# Example from django-components/issues/98 #}
{% load component_tags %}
@ -591,7 +591,7 @@ class ContextVarsIsFilledTests(BaseTestCase):
</div>
"""
class ComponentWithNegatedConditionalSlot(component.Component):
class ComponentWithNegatedConditionalSlot(Component):
template: types.django_html = """
{# Example from django-components/issues/98 #}
{% load component_tags %}
@ -607,17 +607,17 @@ class ContextVarsIsFilledTests(BaseTestCase):
def setUp(self) -> None:
super().setUp()
component.registry.register("is_filled_vars", self.IsFilledVarsComponent)
component.registry.register("conditional_slots", self.ComponentWithConditionalSlots)
component.registry.register(
registry.register("is_filled_vars", self.IsFilledVarsComponent)
registry.register("conditional_slots", self.ComponentWithConditionalSlots)
registry.register(
"complex_conditional_slots",
self.ComponentWithComplexConditionalSlots,
)
component.registry.register("negated_conditional_slot", self.ComponentWithNegatedConditionalSlot)
registry.register("negated_conditional_slot", self.ComponentWithNegatedConditionalSlot)
def tearDown(self) -> None:
super().tearDown()
component.registry.clear()
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_is_filled_vars(self):

View file

@ -4,7 +4,7 @@ from django.http import HttpResponseNotModified
from django.template import Template
from django.test import override_settings
from django_components import component, types
from django_components import Component, registry, types
from django_components.middleware import ComponentDependencyMiddleware
from .django_test_setup import setup_test_config
@ -13,7 +13,7 @@ from .testutils import BaseTestCase, create_and_process_template_response
setup_test_config()
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -29,7 +29,7 @@ class SimpleComponent(component.Component):
js = "script.js"
class SimpleComponentAlternate(component.Component):
class SimpleComponentAlternate(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -42,7 +42,7 @@ class SimpleComponentAlternate(component.Component):
js = "script2.js"
class SimpleComponentWithSharedDependency(component.Component):
class SimpleComponentWithSharedDependency(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -55,7 +55,7 @@ class SimpleComponentWithSharedDependency(component.Component):
js = ["script.js", "script2.js"]
class MultistyleComponent(component.Component):
class MultistyleComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
"""
@ -68,11 +68,11 @@ class MultistyleComponent(component.Component):
@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True})
class ComponentMediaRenderingTests(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
# NOTE: registry is global, so need to clear before each test
registry.clear()
def test_no_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -87,7 +87,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_no_js_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %}
@ -97,7 +97,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=0)
def test_no_css_dependencies_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %}
@ -111,7 +111,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_preload_dependencies_render_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies preload='test' %}
@ -126,7 +126,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_preload_css_dependencies_render_when_no_components_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies preload='test' %}
@ -140,7 +140,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_single_component_dependencies_render_when_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -156,7 +156,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_single_component_with_dash_or_slash_in_name(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -172,7 +172,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_preload_dependencies_render_once_when_used(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies preload='test' %}
@ -188,7 +188,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script.js">', rendered, count=1)
def test_placeholder_removed_when_single_component_rendered(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -199,7 +199,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertNotIn("_RENDERED", rendered)
def test_placeholder_removed_when_preload_rendered(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies preload='test' %}
@ -209,7 +209,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertNotIn("_RENDERED", rendered)
def test_single_component_css_dependencies(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %}
@ -224,7 +224,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_single_component_js_dependencies(self):
component.registry.register(name="test", component=SimpleComponent)
registry.register(name="test", component=SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %}
@ -237,7 +237,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
def test_all_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
component.registry.register(name="test", component=MultistyleComponent)
registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
{% component 'test' %}{% endcomponent %}
@ -260,7 +260,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
def test_all_js_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
component.registry.register(name="test", component=MultistyleComponent)
registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %}
{% component 'test' %}{% endcomponent %}
@ -283,7 +283,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
def test_all_css_dependencies_are_rendered_for_component_with_multiple_dependencies(
self,
):
component.registry.register(name="test", component=MultistyleComponent)
registry.register(name="test", component=MultistyleComponent)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %}
{% component 'test' %}{% endcomponent %}
@ -304,8 +304,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_no_dependencies_with_multiple_unused_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
registry.register(name="test1", component=SimpleComponent)
registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -326,8 +326,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_correct_css_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
registry.register(name="test1", component=SimpleComponent)
registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """
{% load component_tags %}{% component_css_dependencies %}
@ -347,8 +347,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_correct_js_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
registry.register(name="test1", component=SimpleComponent)
registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """
{% load component_tags %}{% component_js_dependencies %}
@ -360,8 +360,8 @@ class ComponentMediaRenderingTests(BaseTestCase):
self.assertInHTML('<script src="script2.js">', rendered, count=0)
def test_correct_dependencies_with_multiple_components(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
registry.register(name="test1", component=SimpleComponent)
registry.register(name="test2", component=SimpleComponentAlternate)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -383,9 +383,9 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_shared_dependencies_rendered_once(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(name="test3", component=SimpleComponentWithSharedDependency)
registry.register(name="test1", component=SimpleComponent)
registry.register(name="test2", component=SimpleComponentAlternate)
registry.register(name="test3", component=SimpleComponentWithSharedDependency)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -409,9 +409,9 @@ class ComponentMediaRenderingTests(BaseTestCase):
)
def test_placeholder_removed_when_multiple_component_rendered(self):
component.registry.register(name="test1", component=SimpleComponent)
component.registry.register(name="test2", component=SimpleComponentAlternate)
component.registry.register(name="test3", component=SimpleComponentWithSharedDependency)
registry.register(name="test1", component=SimpleComponent)
registry.register(name="test2", component=SimpleComponentAlternate)
registry.register(name="test3", component=SimpleComponentWithSharedDependency)
template_str: types.django_html = """
{% load component_tags %}{% component_dependencies %}
@ -438,7 +438,7 @@ class ComponentMediaRenderingTests(BaseTestCase):
"test_component",
]
for component_name in component_names:
component.registry.register(name=component_name, component=SimpleComponent)
registry.register(name=component_name, component=SimpleComponent)
template_str: types.django_html = f"""
{{% load component_tags %}}
{{% component_js_dependencies %}}

View file

@ -1,35 +1,35 @@
import unittest
from django_components import component
from django_components import AlreadyRegistered, Component, ComponentRegistry, NotRegistered, register, registry
from .django_test_setup import setup_test_config
setup_test_config()
class MockComponent(component.Component):
class MockComponent(Component):
pass
class MockComponent2(component.Component):
class MockComponent2(Component):
pass
class MockComponentView(component.Component):
class MockComponentView(Component):
def get(self, request, *args, **kwargs):
pass
class ComponentRegistryTest(unittest.TestCase):
def setUp(self):
self.registry = component.ComponentRegistry()
self.registry = ComponentRegistry()
def test_register_class_decorator(self):
@component.register("decorated_component")
class TestComponent(component.Component):
@register("decorated_component")
class TestComponent(Component):
pass
self.assertEqual(component.registry.get("decorated_component"), TestComponent)
self.assertEqual(registry.get("decorated_component"), TestComponent)
def test_simple_register(self):
self.registry.register(name="testcomponent", component=MockComponent)
@ -48,14 +48,14 @@ class ComponentRegistryTest(unittest.TestCase):
def test_prevent_registering_different_components_with_the_same_name(self):
self.registry.register(name="testcomponent", component=MockComponent)
with self.assertRaises(component.AlreadyRegistered):
with self.assertRaises(AlreadyRegistered):
self.registry.register(name="testcomponent", component=MockComponent2)
def test_allow_duplicated_registration_of_the_same_component(self):
try:
self.registry.register(name="testcomponent", component=MockComponentView)
self.registry.register(name="testcomponent", component=MockComponentView)
except component.AlreadyRegistered:
except AlreadyRegistered:
self.fail("Should not raise AlreadyRegistered")
def test_simple_unregister(self):
@ -64,5 +64,5 @@ class ComponentRegistryTest(unittest.TestCase):
self.assertEqual(self.registry.all(), {})
def test_raises_on_failed_unregister(self):
with self.assertRaises(component.NotRegistered):
with self.assertRaises(NotRegistered):
self.registry.unregister(name="testcomponent")

View file

@ -1,7 +1,7 @@
from django.template import Context, Template
from django.template.base import Parser
from django_components import component, types
from django_components import Component, registry, types
from django_components.component import safe_resolve_dict, safe_resolve_list
from django_components.template_parser import process_aggregate_kwargs
from django_components.templatetags.component_tags import _parse_component_with_args
@ -56,7 +56,7 @@ class ParserTest(BaseTestCase):
class ParserComponentTest(BaseTestCase):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template: types.django_html = """
{{ date }}
{{ id }}
@ -72,7 +72,7 @@ class ParserComponentTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_special_chars_accessible_via_kwargs(self):
component.registry.register("test", self.SimpleComponent)
registry.register("test", self.SimpleComponent)
template_str: types.django_html = """
{% load component_tags %}

View file

@ -4,7 +4,7 @@ from typing import Callable
from django.template import Context, Template
from django_components import component, types
from django_components import Component, register, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -12,7 +12,7 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config()
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template_name = "slotted_template.html"
@ -34,11 +34,11 @@ class TemplateInstrumentationTest(BaseTestCase):
self.saved_render_method = Template._render
Template._render = instrumented_test_render
component.registry.clear()
component.registry.register("test_component", SlottedComponent)
registry.clear()
registry.register("test_component", SlottedComponent)
@component.register("inner_component")
class SimpleComponent(component.Component):
@register("inner_component")
class SimpleComponent(Component):
template_name = "simple_template.html"
def get_context_data(self, variable, variable2="default"):
@ -93,19 +93,19 @@ class TemplateInstrumentationTest(BaseTestCase):
class BlockCompatTests(BaseTestCase):
def setUp(self):
component.registry.clear()
registry.clear()
super().setUp()
def tearDown(self):
super().tearDown()
component.registry.clear()
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_extends(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_extends")
class SlotInsideExtendsComponent(component.Component):
@register("slot_inside_extends")
class SlotInsideExtendsComponent(Component):
template: types.django_html = """
{% extends "block_in_slot_in_component.html" %}
"""
@ -135,10 +135,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slots_inside_include(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_include")
class SlotInsideIncludeComponent(component.Component):
@register("slot_inside_include")
class SlotInsideIncludeComponent(Component):
template: types.django_html = """
{% include "block_in_slot_in_component.html" %}
"""
@ -168,7 +168,7 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_component_inside_block(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
template: types.django_html = """
{% extends "block.html" %}
{% load component_tags %}
@ -203,7 +203,7 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
template: types.django_html = """
{% extends "block_in_component.html" %}
@ -233,10 +233,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_block_inside_component_parent(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("block_in_component_parent")
class BlockInCompParent(component.Component):
@register("block_in_component_parent")
class BlockInCompParent(Component):
template_name = "block_in_component_parent.html"
template: types.django_html = """
@ -266,10 +266,10 @@ class BlockCompatTests(BaseTestCase):
Assert that when we call a component with `{% component %}`, that
the `{% block %}` will NOT affect the inner component.
"""
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("block_inside_slot_v1")
class BlockInSlotInComponent(component.Component):
@register("block_inside_slot_v1")
class BlockInSlotInComponent(Component):
template_name = "block_in_slot_in_component.html"
template: types.django_html = """
@ -301,10 +301,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_default(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component):
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
"""
@ -333,11 +333,11 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_default_block_override(self):
component.registry.clear()
component.registry.register("slotted_component", SlottedComponent)
registry.clear()
registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component):
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
{% block inner %}
@ -369,10 +369,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["isolated", "django"])
def test_slot_inside_block__slot_overriden_block_default(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component):
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
"""
@ -405,10 +405,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_inside_block__slot_overriden_block_overriden(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("slot_inside_block")
class _SlotInsideBlockComponent(component.Component):
@register("slot_inside_block")
class _SlotInsideBlockComponent(Component):
template: types.django_html = """
{% extends "slot_inside_block.html" %}
{% block inner %}
@ -451,10 +451,10 @@ class BlockCompatTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inject_inside_block(self):
component.registry.register("slotted_component", SlottedComponent)
registry.register("slotted_component", SlottedComponent)
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""

View file

@ -2,7 +2,7 @@ import textwrap
from django.template import Context, Template, TemplateSyntaxError
from django_components import component, component_registry, types
from django_components import Component, NotRegistered, register, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -10,11 +10,11 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config()
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template_name = "slotted_template.html"
class SlottedComponentWithContext(component.Component):
class SlottedComponentWithContext(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -34,7 +34,7 @@ class SlottedComponentWithContext(component.Component):
class ComponentTemplateTagTest(BaseTestCase):
class SimpleComponent(component.Component):
class SimpleComponent(Component):
template_name = "simple_template.html"
def get_context_data(self, variable, variable2="default"):
@ -48,12 +48,12 @@ class ComponentTemplateTagTest(BaseTestCase):
js = "script.js"
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
# NOTE: registry is global, so need to clear before each test
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_single_component(self):
component.registry.register(name="test", component=self.SimpleComponent)
registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """
{% load component_tags %}
@ -74,12 +74,12 @@ class ComponentTemplateTagTest(BaseTestCase):
"""
template = Template(simple_tag_template)
with self.assertRaises(component_registry.NotRegistered):
with self.assertRaises(NotRegistered):
template.render(Context({}))
@parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_positional_name(self):
component.registry.register(name="test", component=self.SimpleComponent)
registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """
{% load component_tags %}
@ -92,8 +92,8 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_call_component_with_two_variables(self):
@component.register("test")
class IffedComponent(component.Component):
@register("test")
class IffedComponent(Component):
template: types.django_html = """
Variable: <strong>{{ variable }}</strong>
{% if variable2 != "default" %}
@ -123,7 +123,7 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_singlequoted_name(self):
component.registry.register(name="test", component=self.SimpleComponent)
registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """
{% load component_tags %}
@ -136,7 +136,7 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_variable_as_name(self):
component.registry.register(name="test", component=self.SimpleComponent)
registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """
{% load component_tags %}
@ -151,7 +151,7 @@ class ComponentTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_component_called_with_invalid_variable_as_name(self):
component.registry.register(name="test", component=self.SimpleComponent)
registry.register(name="test", component=self.SimpleComponent)
simple_tag_template: types.django_html = """
{% load component_tags %}
@ -161,13 +161,13 @@ class ComponentTemplateTagTest(BaseTestCase):
"""
template = Template(simple_tag_template)
with self.assertRaises(component_registry.NotRegistered):
with self.assertRaises(NotRegistered):
template.render(Context({}))
@parametrize_context_behavior(["django", "isolated"])
def test_component_accepts_provided_and_default_parameters(self):
@component.register("test")
class ComponentWithProvidedAndDefaultParameters(component.Component):
@register("test")
class ComponentWithProvidedAndDefaultParameters(Component):
template: types.django_html = """
Provided variable: <strong>{{ variable }}</strong>
Default: <p>{{ default_param }}</p>
@ -191,11 +191,11 @@ class ComponentTemplateTagTest(BaseTestCase):
class MultiComponentTests(BaseTestCase):
def setUp(self):
component.registry.clear()
registry.clear()
def register_components(self):
component.registry.register("first_component", SlottedComponent)
component.registry.register("second_component", SlottedComponentWithContext)
registry.register("first_component", SlottedComponent)
registry.register("second_component", SlottedComponentWithContext)
def make_template(self, first_slot: str = "", second_slot: str = "") -> Template:
template_str: types.django_html = f"""
@ -266,7 +266,7 @@ class MultiComponentTests(BaseTestCase):
class ComponentIsolationTests(BaseTestCase):
def setUp(self):
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -276,7 +276,7 @@ class ComponentIsolationTests(BaseTestCase):
</custom-template>
"""
component.registry.register("test", SlottedComponent)
registry.register("test", SlottedComponent)
@parametrize_context_behavior(["django", "isolated"])
def test_instances_of_component_do_not_share_slots(self):
@ -322,8 +322,8 @@ class ComponentIsolationTests(BaseTestCase):
class AggregateInputTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_agg_input_accessible_in_get_context_data(self):
@component.register("test")
class AttrsComponent(component.Component):
@register("test")
class AttrsComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -356,11 +356,11 @@ class AggregateInputTests(BaseTestCase):
class ComponentTemplateSyntaxErrorTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register("test", SlottedComponent)
registry.register("test", SlottedComponent)
def tearDown(self) -> None:
super().tearDown()
component.registry.clear()
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_variable_outside_fill_tag_compiles_w_out_error(self):

View file

@ -1,6 +1,6 @@
from django.template import Context, Template, TemplateSyntaxError
from django_components import component, types
from django_components import Component, register, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -11,8 +11,8 @@ setup_test_config()
class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_basic(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -40,8 +40,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_access_keys_in_python(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> key: {{ key }} </div>
<div> another: {{ another }} </div>
@ -74,8 +74,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_access_keys_in_django(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> key: {{ my_provide.key }} </div>
<div> another: {{ my_provide.another }} </div>
@ -107,8 +107,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_does_not_leak(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -138,8 +138,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_empty(self):
"""Check provide tag with no kwargs"""
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -172,8 +172,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_no_inject(self):
"""Check that nothing breaks if we do NOT inject even if some data is provided"""
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div></div>
"""
@ -203,8 +203,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_key_single_quotes(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -235,8 +235,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_no_key_raises(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -259,8 +259,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_key_must_be_string_literal(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -283,8 +283,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_key_must_be_identifier(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -308,8 +308,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_aggregate_dics(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -339,8 +339,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_does_not_expose_kwargs_to_context(self):
"""Check that `provide` tag doesn't assign the keys to the context like `with` tag does"""
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -375,8 +375,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_nested_in_provide_same_key(self):
"""Check that inner `provide` with same key overshadows outer `provide`"""
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -415,8 +415,8 @@ class ProvideTemplateTagTest(BaseTestCase):
def test_provide_nested_in_provide_different_key(self):
"""Check that `provide` tag with different keys don't affect each other"""
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> first_provide: {{ first_provide|safe }} </div>
<div> second_provide: {{ second_provide|safe }} </div>
@ -452,8 +452,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_provide_in_include(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -482,8 +482,8 @@ class ProvideTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_in_provide(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -492,8 +492,8 @@ class ProvideTemplateTagTest(BaseTestCase):
var = self.inject("my_provide", "default")
return {"var": var}
@component.register("parent")
class ParentComponent(component.Component):
@register("parent")
class ParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% provide "my_provide" key="hi" another=123 %}
@ -523,8 +523,8 @@ class ProvideTemplateTagTest(BaseTestCase):
class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inject_basic(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -552,8 +552,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inject_missing_key_raises_without_default(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -574,8 +574,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inject_missing_key_ok_with_default(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -600,8 +600,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inject_empty_string(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""
@ -626,8 +626,8 @@ class InjectTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inject_raises_on_called_outside_get_context_data(self):
@component.register("injectee")
class InjectComponent(component.Component):
@register("injectee")
class InjectComponent(Component):
template: types.django_html = """
<div> injected: {{ var|safe }} </div>
"""

View file

@ -2,7 +2,7 @@ from typing import Any, Dict, List, Optional
from django.template import Context, Template, TemplateSyntaxError
from django_components import component, types
from django_components import Component, register, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -10,7 +10,7 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config()
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -33,15 +33,15 @@ class SlottedComponentWithContext(SlottedComponent):
class ComponentSlottedTemplateTagTest(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
# NOTE: registry is global, so need to clear before each test
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_basic(self):
component.registry.register(name="test1", component=SlottedComponent)
registry.register(name="test1", component=SlottedComponent)
@component.register("test2")
class SimpleComponent(component.Component):
@register("test2")
class SimpleComponent(Component):
template = """Variable: <strong>{{ variable }}</strong>"""
def get_context_data(self, variable, variable2="default"):
@ -82,7 +82,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
# NOTE: Second arg is the expected output of `{{ variable }}`
@parametrize_context_behavior([("django", "test456"), ("isolated", "")])
def test_slotted_template_with_context_var(self, context_behavior_data):
component.registry.register(name="test1", component=SlottedComponentWithContext)
registry.register(name="test1", component=SlottedComponentWithContext)
template_str: types.django_html = """
{% load component_tags %}
@ -113,7 +113,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_no_slots_filled(self):
component.registry.register(name="test", component=SlottedComponent)
registry.register(name="test", component=SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}
@ -135,8 +135,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_without_slots(self):
@component.register("test")
class SlottedComponentNoSlots(component.Component):
@register("test")
class SlottedComponentNoSlots(Component):
template: types.django_html = """
<custom-template></custom-template>
"""
@ -152,8 +152,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_without_slots_and_single_quotes(self):
@component.register("test")
class SlottedComponentNoSlots(component.Component):
@register("test")
class SlottedComponentNoSlots(Component):
template: types.django_html = """
<custom-template></custom-template>
"""
@ -169,7 +169,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_variable_fill_name(self):
component.registry.register(name="test", component=SlottedComponent)
registry.register(name="test", component=SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}
{% with slotname="header" %}
@ -191,7 +191,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_missing_required_slot_raises_error(self):
class Component(component.Component):
class Comp(Component):
template: types.django_html = """
{% load component_tags %}
<div class="header-box">
@ -200,7 +200,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
</div>
"""
component.registry.register("test", Component)
registry.register("test", Comp)
template_str: types.django_html = """
{% load component_tags %}
@ -214,8 +214,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_default_slot_is_fillable_by_implicit_fill_content(self):
@component.register("test_comp")
class ComponentWithDefaultSlot(component.Component):
@register("test_comp")
class ComponentWithDefaultSlot(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -241,8 +241,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_default_slot_is_fillable_by_explicit_fill_content(self):
@component.register("test_comp")
class ComponentWithDefaultSlot(component.Component):
@register("test_comp")
class ComponentWithDefaultSlot(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -267,8 +267,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_error_raised_when_default_and_required_slot_not_filled(self):
@component.register("test_comp")
class ComponentWithDefaultAndRequiredSlot(component.Component):
@register("test_comp")
class ComponentWithDefaultAndRequiredSlot(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -289,10 +289,10 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_fill_tag_can_occur_within_component_nested_in_implicit_fill(self):
component.registry.register("slotted", SlottedComponent)
registry.register("slotted", SlottedComponent)
@component.register("test_comp")
class ComponentWithDefaultSlot(component.Component):
@register("test_comp")
class ComponentWithDefaultSlot(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -327,8 +327,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_error_from_mixed_implicit_and_explicit_fill_content(self):
@component.register("test_comp")
class ComponentWithDefaultSlot(component.Component):
@register("test_comp")
class ComponentWithDefaultSlot(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -348,8 +348,8 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_comments_permitted_inside_implicit_fill_content(self):
@component.register("test_comp")
class ComponentWithDefaultSlot(component.Component):
@register("test_comp")
class ComponentWithDefaultSlot(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -372,7 +372,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_component_without_default_slot_refuses_implicit_fill(self):
component.registry.register("test_comp", SlottedComponent)
registry.register("test_comp", SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test_comp' %}
@ -387,7 +387,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_component_template_cannot_have_multiple_default_slots(self):
class BadComponent(component.Component):
class BadComponent(Component):
def get_template(self, context, template_name: Optional[str] = None) -> Template:
template_str: types.django_html = """
{% load django_components %}
@ -405,7 +405,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_name_fill_typo_gives_helpful_error_message(self):
component.registry.register(name="test1", component=SlottedComponent)
registry.register(name="test1", component=SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}
@ -433,7 +433,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
# NOTE: This is relevant only for the "isolated" mode
@parametrize_context_behavior(["isolated"])
def test_slots_of_top_level_comps_can_access_full_outer_ctx(self):
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -446,7 +446,7 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
"name": name,
}
component.registry.register("test", SlottedComponent)
registry.register("test", SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}
@ -478,13 +478,13 @@ class ComponentSlottedTemplateTagTest(BaseTestCase):
class SlottedTemplateRegressionTests(BaseTestCase):
def setUp(self):
# NOTE: component.registry is global, so need to clear before each test
component.registry.clear()
# NOTE: registry is global, so need to clear before each test
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_slotted_template_that_uses_missing_variable(self):
@component.register("test")
class SlottedComponentWithMissingVariable(component.Component):
@register("test")
class SlottedComponentWithMissingVariable(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -517,12 +517,12 @@ class SlottedTemplateRegressionTests(BaseTestCase):
class SlotDefaultTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.clear()
component.registry.register("test", SlottedComponent)
registry.clear()
registry.register("test", SlottedComponent)
def tearDown(self):
super().tearDown()
component.registry.clear()
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_basic(self):
@ -654,8 +654,8 @@ class SlotDefaultTests(BaseTestCase):
class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -689,8 +689,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data_with_flags(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -724,8 +724,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data_with_slot_default(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -761,8 +761,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data_raises_on_slot_data_and_slot_default_same_var(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -792,8 +792,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data_fill_without_data(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -821,8 +821,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data_fill_without_slot_data(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -844,8 +844,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_slot_data_no_fill(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -870,8 +870,8 @@ class ScopedSlotTest(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_nested_fills(self):
@component.register("test")
class TestComponent(component.Component):
@register("test")
class TestComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -917,7 +917,7 @@ class ScopedSlotTest(BaseTestCase):
class DuplicateSlotTest(BaseTestCase):
class DuplicateSlotComponent(component.Component):
class DuplicateSlotComponent(Component):
template: types.django_html = """
{% load component_tags %}
<header>{% slot "header" %}Default header{% endslot %}</header>
@ -931,7 +931,7 @@ class DuplicateSlotTest(BaseTestCase):
"name": name,
}
class DuplicateSlotNestedComponent(component.Component):
class DuplicateSlotNestedComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% slot "header" %}START{% endslot %}
@ -956,7 +956,7 @@ class DuplicateSlotTest(BaseTestCase):
"items": items,
}
class CalendarComponent(component.Component):
class CalendarComponent(Component):
"""Nested in ComponentWithNestedComponent"""
template: types.django_html = """
@ -975,9 +975,9 @@ class DuplicateSlotTest(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register(name="duplicate_slot", component=self.DuplicateSlotComponent)
component.registry.register(name="duplicate_slot_nested", component=self.DuplicateSlotNestedComponent)
component.registry.register(name="calendar", component=self.CalendarComponent)
registry.register(name="duplicate_slot", component=self.DuplicateSlotComponent)
registry.register(name="duplicate_slot_nested", component=self.DuplicateSlotNestedComponent)
registry.register(name="calendar", component=self.CalendarComponent)
# NOTE: Second arg is the input for the "name" component kwarg
@parametrize_context_behavior(
@ -1113,11 +1113,11 @@ class DuplicateSlotTest(BaseTestCase):
class SlotFillTemplateSyntaxErrorTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.register("test", SlottedComponent)
registry.register("test", SlottedComponent)
def tearDown(self):
super().tearDown()
component.registry.clear()
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_fill_with_no_parent_is_error(self):
@ -1130,8 +1130,8 @@ class SlotFillTemplateSyntaxErrorTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_isolated_slot_is_error(self):
@component.register("broken_component")
class BrokenComponent(component.Component):
@register("broken_component")
class BrokenComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% include 'slotted_template.html' with context=None only %}
@ -1180,7 +1180,7 @@ class SlotBehaviorTests(BaseTestCase):
# NOTE: This is standalone function instead of setUp, so we can configure
# Django settings per test with `@override_settings`
def make_template(self) -> Template:
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -1195,7 +1195,7 @@ class SlotBehaviorTests(BaseTestCase):
"name": name,
}
component.registry.register("test", SlottedComponent)
registry.register("test", SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}

View file

@ -4,7 +4,7 @@ from typing import Any, Dict, Optional
from django.template import Context, Template
from django_components import component, types
from django_components import Component, registry, types
from .django_test_setup import setup_test_config
from .testutils import BaseTestCase, parametrize_context_behavior
@ -12,7 +12,7 @@ from .testutils import BaseTestCase, parametrize_context_behavior
setup_test_config()
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -34,7 +34,7 @@ class SlottedComponentWithContext(SlottedComponent):
class NestedSlotTests(BaseTestCase):
class NestedComponent(component.Component):
class NestedComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% slot 'outer' %}
@ -44,8 +44,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_default_slot_contents_render_correctly(self):
component.registry.clear()
component.registry.register("test", self.NestedComponent)
registry.clear()
registry.register("test", self.NestedComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test' %}{% endcomponent %}
@ -56,8 +56,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_inner_slot_overriden(self):
component.registry.clear()
component.registry.register("test", self.NestedComponent)
registry.clear()
registry.register("test", self.NestedComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test' %}
@ -70,8 +70,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_outer_slot_overriden(self):
component.registry.clear()
component.registry.register("test", self.NestedComponent)
registry.clear()
registry.register("test", self.NestedComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test' %}{% fill 'outer' %}<p>Override</p>{% endfill %}{% endcomponent %}
@ -82,8 +82,8 @@ class NestedSlotTests(BaseTestCase):
@parametrize_context_behavior(["django", "isolated"])
def test_both_overriden_and_inner_removed(self):
component.registry.clear()
component.registry.register("test", self.NestedComponent)
registry.clear()
registry.register("test", self.NestedComponent)
template_str: types.django_html = """
{% load component_tags %}
{% component 'test' %}
@ -100,7 +100,7 @@ class NestedSlotTests(BaseTestCase):
# remain top-level context.
@parametrize_context_behavior([("django", "Joe2"), ("isolated", "Jannete")])
def test_fill_inside_fill_with_same_name(self, context_behavior_data):
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -115,8 +115,8 @@ class NestedSlotTests(BaseTestCase):
"name": name,
}
component.registry.clear()
component.registry.register("test", SlottedComponent)
registry.clear()
registry.register("test", SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}
@ -163,7 +163,7 @@ class NestedSlotTests(BaseTestCase):
# NOTE: This test group are kept for backward compatibility, as the same logic
# as provided by {% if %} tags was previously provided by this library.
class ConditionalSlotTests(BaseTestCase):
class ConditionalComponent(component.Component):
class ConditionalComponent(Component):
template: types.django_html = """
{% load component_tags %}
{% if branch == 'a' %}
@ -178,12 +178,12 @@ class ConditionalSlotTests(BaseTestCase):
def setUp(self):
super().setUp()
component.registry.clear()
component.registry.register("test", self.ConditionalComponent)
registry.clear()
registry.register("test", self.ConditionalComponent)
def tearDown(self):
super().tearDown()
component.registry.clear()
registry.clear()
@parametrize_context_behavior(["django", "isolated"])
def test_no_content_if_branches_are_false(self):
@ -245,7 +245,7 @@ class ConditionalSlotTests(BaseTestCase):
class SlotIterationTest(BaseTestCase):
"""Tests a behaviour of {% fill .. %} tag which is inside a template {% for .. %} loop."""
class ComponentSimpleSlotInALoop(component.Component):
class ComponentSimpleSlotInALoop(Component):
template: types.django_html = """
{% load component_tags %}
{% for object in objects %}
@ -261,7 +261,7 @@ class SlotIterationTest(BaseTestCase):
}
def setUp(self):
component.registry.clear()
registry.clear()
# NOTE: Second arg in tuple is expected result. In isolated mode, loops should NOT leak.
@parametrize_context_behavior(
@ -271,7 +271,7 @@ class SlotIterationTest(BaseTestCase):
]
)
def test_inner_slot_iteration_basic(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
template_str: types.django_html = """
{% load component_tags %}
@ -296,7 +296,7 @@ class SlotIterationTest(BaseTestCase):
]
)
def test_inner_slot_iteration_with_variable_from_outer_scope(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
template_str: types.django_html = """
{% load component_tags %}
@ -328,7 +328,7 @@ class SlotIterationTest(BaseTestCase):
]
)
def test_inner_slot_iteration_nested(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -375,7 +375,7 @@ class SlotIterationTest(BaseTestCase):
]
)
def test_inner_slot_iteration_nested_with_outer_scope_variable(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -417,7 +417,7 @@ class SlotIterationTest(BaseTestCase):
]
)
def test_inner_slot_iteration_nested_with_slot_default(self, context_behavior_data):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -469,7 +469,7 @@ class SlotIterationTest(BaseTestCase):
self,
context_behavior_data,
):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -506,7 +506,7 @@ class SlotIterationTest(BaseTestCase):
def test_inner_slot_iteration_nested_with_slot_default_and_outer_scope_variable__isolated_2(
self,
):
component.registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
registry.register("slot_in_a_loop", self.ComponentSimpleSlotInALoop)
objects = [
{"inner": ["ITER1_OBJ1", "ITER1_OBJ2"]},
@ -559,7 +559,7 @@ class SlotIterationTest(BaseTestCase):
class ComponentNestingTests(BaseTestCase):
class CalendarComponent(component.Component):
class CalendarComponent(Component):
"""Nested in ComponentWithNestedComponent"""
template: types.django_html = """
@ -576,7 +576,7 @@ class ComponentNestingTests(BaseTestCase):
</div>
"""
class DashboardComponent(component.Component):
class DashboardComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div class="dashboard-component">
@ -594,7 +594,7 @@ class ComponentNestingTests(BaseTestCase):
</div>
"""
class ComplexChildComponent(component.Component):
class ComplexChildComponent(Component):
template: types.django_html = """
{% load component_tags %}
<div>
@ -604,7 +604,7 @@ class ComponentNestingTests(BaseTestCase):
</div>
"""
class ComplexParentComponent(component.Component):
class ComplexParentComponent(Component):
template: types.django_html = """
{% load component_tags %}
ITEMS: {{ items|safe }}
@ -622,14 +622,14 @@ class ComponentNestingTests(BaseTestCase):
def setUp(self) -> None:
super().setUp()
component.registry.register("dashboard", self.DashboardComponent)
component.registry.register("calendar", self.CalendarComponent)
component.registry.register("complex_child", self.ComplexChildComponent)
component.registry.register("complex_parent", self.ComplexParentComponent)
registry.register("dashboard", self.DashboardComponent)
registry.register("calendar", self.CalendarComponent)
registry.register("complex_child", self.ComplexChildComponent)
registry.register("complex_parent", self.ComplexParentComponent)
def tearDown(self) -> None:
super().tearDown()
component.registry.clear()
registry.clear()
# NOTE: Second arg in tuple are expected names in nested fills. In "django" mode,
# the value should be overridden by the component, while in "isolated" it should
@ -638,7 +638,7 @@ class ComponentNestingTests(BaseTestCase):
def test_component_inside_slot(self, context_behavior_data):
first_name, second_name = context_behavior_data
class SlottedComponent(component.Component):
class SlottedComponent(Component):
template: types.django_html = """
{% load component_tags %}
<custom-template>
@ -653,7 +653,7 @@ class ComponentNestingTests(BaseTestCase):
"name": name,
}
component.registry.register("test", SlottedComponent)
registry.register("test", SlottedComponent)
template_str: types.django_html = """
{% load component_tags %}