mirror of
https://github.com/django-components/django-components.git
synced 2025-10-09 21:41:59 +00:00
docs: self-contained examples (#1436)
This commit is contained in:
parent
48adaf98f1
commit
9877cf30ed
71 changed files with 673 additions and 361 deletions
|
@ -219,6 +219,43 @@ The CI workflow runs when:
|
|||
- A new commit is pushed to the `master` branch - This updates the `dev` version
|
||||
- A new tag is pushed - This updates the `latest` version and the version specified in the tag name
|
||||
|
||||
### Examples
|
||||
|
||||
The [examples page](../../examples) is populated from entries in `docs/examples/`.
|
||||
|
||||
These examples have special folder layout:
|
||||
|
||||
```txt
|
||||
|- docs/
|
||||
|- examples/
|
||||
|- <example_name>/
|
||||
|- component.py - The component definition
|
||||
|- page.py - The page view for the example
|
||||
|- test_example_<example_name>.py - Tests
|
||||
|- README.md - Component documentation
|
||||
|- images/ - Images used in README
|
||||
```
|
||||
|
||||
This allows us to keep the examples in one place, and define, test, and document them.
|
||||
|
||||
**Previews** - There's a script in `sampleproject/examples/utils.py` that picks up the `component.py` and `page.py` files, making them previewable in the dev server (`http://localhost:8000/examples/<example_name>`).
|
||||
|
||||
To see all available examples, go to `http://localhost:8000/examples/`.
|
||||
|
||||
**Tests** - Use the file format `test_example_<example_name>.py` to define tests for the example. These tests are picked up when you run pytest.
|
||||
|
||||
#### Adding examples
|
||||
|
||||
Let's say we want to add an example called `form`:
|
||||
|
||||
1. Create a new directory in `docs/examples/form/`
|
||||
2. Add actual implementation in `component.py`
|
||||
3. Add a live demo page in `page.py`
|
||||
4. Add tests in `test_example_form.py`
|
||||
5. Write up the documentation in `README.md`
|
||||
6. Link to that new page from `docs/examples/index.md`.
|
||||
7. Update `docs/examples/.nav.yml` to update the navigation.
|
||||
|
||||
### People page
|
||||
|
||||
The [people page](https://django-components.github.io/django-components/dev/community/people/) is regularly updated with stats about the contributors and authors. This is triggered automatically once a month or manually via the Actions tab.
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
nav:
|
||||
- index.md
|
||||
- Components:
|
||||
- ./form.md
|
||||
- ./tabs.md
|
||||
- Form: ./form
|
||||
- Tabs (AlpineJS): ./tabs
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# Adding examples
|
||||
|
||||
Rule of thumb:
|
||||
|
||||
If the example is a 3rd party package or it lives under a different URL, only link to it in `overview.md`.
|
||||
|
||||
If the example is file(s) that we wrote:
|
||||
|
||||
1. Define the component in `sampleproject/examples/components/<component_name>/<component_name>.py`.
|
||||
2. Define a view / page component in `sampleproject/examples/pages/<component_name>/<component_name>.py`.
|
||||
3. Add a new page here in the documentation named `<component_name>.md`, similarly to [Tabs](./alpine/tabs.md).
|
||||
4. Link to that new page from `index.md`.
|
||||
5. Update `.nav.yml` if needed.
|
||||
6. Add a corresponding test file in `tests/test_example_<component_name>.py`.
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
A `Form` component that automatically generates labels and arranges fields in a grid. It simplifies form creation by handling the layout for you.
|
||||
|
||||

|
||||

|
||||
|
||||
To get started, use the following example to create a simple form with 2 fields - `project` and `option`:
|
||||
|
||||
|
@ -45,7 +45,7 @@ you can use the `label:<field_name>` slot.
|
|||
|
||||
Whether you define custom labels or not, the form will have the following structure:
|
||||
|
||||

|
||||

|
||||
|
||||
## API
|
||||
|
||||
|
@ -90,14 +90,18 @@ The `FormLabel` component accepts the following arguments:
|
|||
This will render:
|
||||
|
||||
```html
|
||||
<label for="user_name" class="font-semibold text-gray-700">
|
||||
Your Name
|
||||
</label>
|
||||
<label for="user_name" class="font-semibold text-gray-700"> Your Name </label>
|
||||
```
|
||||
|
||||
If `title` is not provided, `field_name="user_name"` would automatically generate the title "User Name",
|
||||
converting snake_case to "Title Case".
|
||||
|
||||
## Definition
|
||||
|
||||
```djc_py
|
||||
--8<-- "docs/examples/form/component.py"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
To see the component in action, you can set up a view and a URL pattern as shown below.
|
||||
|
@ -105,7 +109,7 @@ To see the component in action, you can set up a view and a URL pattern as shown
|
|||
### `views.py`
|
||||
|
||||
```djc_py
|
||||
--8<-- "sampleproject/examples/pages/form.py"
|
||||
--8<-- "docs/examples/form/page.py"
|
||||
```
|
||||
|
||||
### `urls.py`
|
||||
|
@ -119,18 +123,3 @@ urlpatterns = [
|
|||
path("examples/form", FormPage.as_view(), name="form"),
|
||||
]
|
||||
```
|
||||
|
||||
## Definition
|
||||
|
||||
### `form.py`
|
||||
|
||||
```djc_py
|
||||
--8<-- "sampleproject/examples/components/form/form.py"
|
||||
```
|
||||
|
||||
### `form.html`
|
||||
|
||||
```django
|
||||
{% load component_tags %}
|
||||
--8<-- "sampleproject/examples/components/form/form.html"
|
||||
```
|
|
@ -5,8 +5,6 @@ from django_components import Component, Slot, register, types
|
|||
|
||||
@register("form")
|
||||
class Form(Component):
|
||||
template_file = "form.html"
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
editable: bool = True
|
||||
method: str = "post"
|
||||
|
@ -24,6 +22,28 @@ class Form(Component):
|
|||
"fields": fields,
|
||||
}
|
||||
|
||||
template: types.django_html = """
|
||||
<form
|
||||
{% if submit_href and editable %} action="{{ submit_href }}" {% endif %}
|
||||
method="{{ method }}"
|
||||
{% html_attrs attrs %}
|
||||
>
|
||||
{% slot "prepend" / %}
|
||||
|
||||
<div {% html_attrs form_content_attrs %}>
|
||||
{# Generate a grid of fields and labels out of given slots #}
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-4 gap-y-2 items-center">
|
||||
{% for field_name, label in fields %}
|
||||
{{ label }}
|
||||
{% slot name=field_name / %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% slot "append" / %}
|
||||
</form>
|
||||
"""
|
||||
|
||||
|
||||
# Users of this component can define form fields as slots.
|
||||
#
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
@ -1,41 +1,24 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from django.template import Context, Template
|
||||
from pytest_django.asserts import assertHTMLEqual
|
||||
|
||||
from django_components import registry, types
|
||||
from django_components.testing import djc_test
|
||||
from tests.testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
|
||||
|
||||
# Instead of having to re-define the components from the examples section in documentation,
|
||||
# we import them directly from sampleproject.
|
||||
def _create_tab_components():
|
||||
# Imported lazily, so we import it only once settings are set
|
||||
from sampleproject.examples.components.form.form import Form, FormLabel
|
||||
# Imported lazily, so we import components only once settings are set
|
||||
def _create_form_components():
|
||||
from docs.examples.form.component import Form, FormLabel # noqa: PLC0415
|
||||
|
||||
# NOTE: We're importing the component classes from the sampleproject, so we're
|
||||
# testing the actual implementation.
|
||||
registry.register("form", Form)
|
||||
registry.register("form_label", FormLabel)
|
||||
|
||||
|
||||
@djc_test(
|
||||
parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR,
|
||||
components_settings={
|
||||
"dirs": [
|
||||
Path(__file__).parent / "components",
|
||||
# Include the directory where example components are defined
|
||||
Path(__file__).parent.parent / "sampleproject/examples/components",
|
||||
],
|
||||
},
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
@djc_test
|
||||
class TestExampleForm:
|
||||
def test_render_simple_form(self, components_settings):
|
||||
_create_tab_components()
|
||||
def test_render_simple_form(self):
|
||||
_create_form_components()
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "form" %}
|
||||
|
@ -66,9 +49,9 @@ class TestExampleForm:
|
|||
""",
|
||||
)
|
||||
|
||||
def test_custom_label(self, components_settings):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
def test_custom_label(self):
|
||||
_create_form_components()
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "form" %}
|
||||
{% fill "label:project" %}<strong>Custom Project Label</strong>{% endfill %}
|
||||
|
@ -81,9 +64,9 @@ class TestExampleForm:
|
|||
assert "<strong>Custom Project Label</strong>" in rendered
|
||||
assert '<label for="project"' not in rendered
|
||||
|
||||
def test_unused_label_raises_error(self, components_settings):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
def test_unused_label_raises_error(self):
|
||||
_create_form_components()
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "form" %}
|
||||
{% fill "label:project" %}Custom Project Label{% endfill %}
|
||||
|
@ -94,9 +77,9 @@ class TestExampleForm:
|
|||
with pytest.raises(ValueError, match=r"Unused labels: {'label:project'}"):
|
||||
template.render(Context({}))
|
||||
|
||||
def test_prepend_append_slots(self, components_settings):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
def test_prepend_append_slots(self):
|
||||
_create_form_components()
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "form" %}
|
||||
{% fill "prepend" %}<div>Prepended content</div>{% endfill %}
|
|
@ -9,12 +9,12 @@ If you have components that would be useful to others, open a [pull request](htt
|
|||
|
||||
## Components
|
||||
|
||||
- [Form](./form.md) - A form component that automatically generates labels and arranges fields in a grid.
|
||||
- [Tabs](./tabs.md) - Dynamic tabs with [AlpineJS](https://alpinejs.dev/).
|
||||
- [Form](./form) - A form component that automatically generates labels and arranges fields in a grid.
|
||||
- [Tabs (AlpineJS)](./tabs) - Dynamic tabs with [AlpineJS](https://alpinejs.dev/).
|
||||
|
||||
## Packages
|
||||
|
||||
Packages or projects that define components for django-components:
|
||||
|
||||
- [djc-heroicons](https://pypi.org/project/djc-heroicons/) - Icons from HeroIcons.com for django-components.
|
||||
- [`djc-heroicons`](https://pypi.org/project/djc-heroicons/) - Icons from HeroIcons.com for django-components.
|
||||
- [`django-htmx-components`](https://github.com/iwanalabs/django-htmx-components) - A set of components for use with [htmx](https://htmx.org/).
|
||||
|
|
|
@ -18,7 +18,7 @@ To get started, use the following example to create a simple container with 2 ta
|
|||
{% endcomponent %}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
## API
|
||||
|
||||
|
@ -45,6 +45,22 @@ The `Tab` component defines an individual tab. It MUST be nested inside a `Tabli
|
|||
|
||||
Use the `Tab`'s default slot to define the content of the tab.
|
||||
|
||||
## How it works
|
||||
|
||||
At the start of rendering, `Tablist` defines special context that `Tab`s recognize.
|
||||
|
||||
When a `Tab` component is nested and rendered inside a `Tablist`, it registers itself with the parent `Tablist` component.
|
||||
|
||||
After the rendering of `Tablist`'s body is done, we end up with list of rendered `Tabs` that were encountered.
|
||||
|
||||
`Tablist` then uses this information to dynamically render the tab HTML.
|
||||
|
||||
## Definition
|
||||
|
||||
```djc_py
|
||||
--8<-- "docs/examples/tabs/component.py"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
To see the component in action, you can set up a view and a URL pattern as shown below.
|
||||
|
@ -54,7 +70,7 @@ To see the component in action, you can set up a view and a URL pattern as shown
|
|||
This example shows how to render a full page with the tab component.
|
||||
|
||||
```djc_py
|
||||
--8<-- "sampleproject/examples/pages/tabs.py"
|
||||
--8<-- "docs/examples/tabs/page.py"
|
||||
```
|
||||
|
||||
### `urls.py`
|
||||
|
@ -68,33 +84,3 @@ urlpatterns = [
|
|||
path("examples/tabs", TabsPage.as_view(), name="tabs"),
|
||||
]
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
At the start of rendering, `Tablist` defines special context that `Tab`s recognize.
|
||||
|
||||
When a `Tab` component is nested and rendered inside a `Tablist`, it registers itself with the parent `Tablist` component.
|
||||
|
||||
After the rendering of `Tablist`'s body is done, we end up with list of rendered `Tabs` that were encountered.
|
||||
|
||||
`Tablist` then uses this information to dynamically render the tab HTML.
|
||||
|
||||
## Definition
|
||||
|
||||
### `tabs.py`
|
||||
|
||||
```djc_py
|
||||
--8<-- "sampleproject/examples/components/tabs/tabs.py"
|
||||
```
|
||||
|
||||
### `tabs.html`
|
||||
|
||||
```django
|
||||
--8<-- "sampleproject/examples/components/tabs/tabs.html"
|
||||
```
|
||||
|
||||
### `tabs.css`
|
||||
|
||||
```css
|
||||
--8<-- "sampleproject/examples/components/tabs/tabs.css"
|
||||
```
|
|
@ -37,9 +37,6 @@ class _TablistImpl(Component):
|
|||
Refer to `Tablist` API below.
|
||||
"""
|
||||
|
||||
template_file = "tabs.html"
|
||||
css_file = "tabs.css"
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
# `mark_safe` is used so the script tag is usd as is, so we can add `defer` flag.
|
||||
|
@ -75,6 +72,160 @@ class _TablistImpl(Component):
|
|||
"selected_tab": selected_tab,
|
||||
}
|
||||
|
||||
template: t.django_html = """
|
||||
{% load component_tags %}
|
||||
<div
|
||||
x-data="{
|
||||
selectedTab: '{{ selected_tab }}',
|
||||
}"
|
||||
{% html_attrs
|
||||
container_attrs
|
||||
id=id
|
||||
%}
|
||||
>
|
||||
<div
|
||||
{% html_attrs
|
||||
tablist_attrs
|
||||
role="tablist"
|
||||
aria-label=name
|
||||
%}
|
||||
>
|
||||
{% for tab_datum, is_hidden in tab_data %}
|
||||
<button
|
||||
:aria-selected="selectedTab === '{{ tab_datum.tab_id }}'"
|
||||
@click="selectedTab = '{{ tab_datum.tab_id }}'"
|
||||
{% html_attrs
|
||||
tab_attrs
|
||||
id=tab_datum.tab_id
|
||||
role="tab"
|
||||
aria-controls=tab_datum.tabpanel_id
|
||||
disabled=tab_datum.disabled
|
||||
%}
|
||||
>
|
||||
{{ tab_datum.header }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% for tab_datum, is_hidden in tab_data %}
|
||||
<article
|
||||
:hidden="selectedTab != '{{ tab_datum.tab_id }}'"
|
||||
{% html_attrs
|
||||
tabpanel_attrs
|
||||
hidden=is_hidden
|
||||
role="tabpanel"
|
||||
id=tab_datum.tabpanel_id
|
||||
aria-labelledby=tab_datum.tab_id
|
||||
%}
|
||||
>
|
||||
{{ tab_datum.content }}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
"""
|
||||
|
||||
css: t.css = """
|
||||
/* based on https://codepen.io/brettsmason/pen/zYGEgZP */
|
||||
|
||||
[role="tablist"] {
|
||||
margin: 0 0 -0.1em;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[role="tab"] {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0.3em 0.5em 0.4em;
|
||||
border: 1px solid hsl(219, 1%, 72%);
|
||||
border-radius: 0.2em 0.2em 0 0;
|
||||
box-shadow: 0 0 0.2em hsl(219, 1%, 72%);
|
||||
overflow: visible;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
background: hsl(220, 20%, 94%);
|
||||
}
|
||||
|
||||
[role="tab"]:hover::before,
|
||||
[role="tab"]:focus::before,
|
||||
[role="tab"][aria-selected="true"]::before {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: -1px;
|
||||
left: -1px;
|
||||
border-radius: 0.2em 0.2em 0 0;
|
||||
border-top: 3px solid LinkText;
|
||||
content: '';
|
||||
}
|
||||
|
||||
[role="tab"][aria-selected="true"] {
|
||||
border-radius: 0;
|
||||
background: hsl(220, 43%, 99%);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
[role="tab"][aria-selected="true"]:not(:focus):not(:hover)::before {
|
||||
border-top: 5px solid SelectedItem;
|
||||
}
|
||||
|
||||
[role="tab"][aria-selected="true"]::after {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
bottom: -1px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 0.3em;
|
||||
background: hsl(220, 43%, 99%);
|
||||
box-shadow: none;
|
||||
content: '';
|
||||
}
|
||||
|
||||
[role="tab"]:hover,
|
||||
[role="tab"]:focus,
|
||||
[role="tab"]:active {
|
||||
outline: 0;
|
||||
border-radius: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
[role="tab"]:hover::before,
|
||||
[role="tab"]:focus::before {
|
||||
border-color: LinkText;
|
||||
}
|
||||
|
||||
[role="tabpanel"] {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.5em 0.5em 0.7em;
|
||||
border: 1px solid hsl(219, 1%, 72%);
|
||||
border-radius: 0 0.2em 0.2em 0.2em;
|
||||
box-shadow: 0 0 0.2em hsl(219, 1%, 72%);
|
||||
background: hsl(220, 43%, 99%);
|
||||
}
|
||||
|
||||
[role="tabpanel"]:focus {
|
||||
border-color: LinkText;
|
||||
box-shadow: 0 0 0.2em LinkText;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
[role="tabpanel"]:focus::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -1px;
|
||||
left: -1px;
|
||||
border-bottom: 3px solid LinkText;
|
||||
border-radius: 0 0 0.2em 0.2em;
|
||||
content: '';
|
||||
}
|
||||
|
||||
[role="tabpanel"] p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[role="tabpanel"] * + p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@register("Tablist")
|
||||
class Tablist(Component):
|
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 259 KiB |
|
@ -1,4 +1,4 @@
|
|||
from django.http import HttpRequest
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from django_components import Component, types
|
||||
|
||||
|
@ -35,5 +35,5 @@ class TabsPage(Component):
|
|||
"""
|
||||
|
||||
class View:
|
||||
def get(self, request: HttpRequest):
|
||||
def get(self, request: HttpRequest) -> HttpResponse:
|
||||
return TabsPage.render_to_response(request=request)
|
|
@ -1,41 +1,24 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from django.template import Context, Template
|
||||
from pytest_django.asserts import assertHTMLEqual
|
||||
|
||||
from django_components import registry, types
|
||||
from django_components.testing import djc_test
|
||||
from tests.testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
|
||||
|
||||
# Instead of having to re-define the components from the examples section in documentation,
|
||||
# we import them directly from sampleproject.
|
||||
def _create_tab_components():
|
||||
# Imported lazily, so we import it only once settings are set
|
||||
from sampleproject.examples.components.tabs.tabs import Tab, Tablist, _TablistImpl
|
||||
def _create_tab_components() -> None:
|
||||
from docs.examples.tabs.component import Tab, Tablist, _TablistImpl # noqa: PLC0415
|
||||
|
||||
# NOTE: We're importing the component classes from the sampleproject, so we're
|
||||
# testing the actual implementation.
|
||||
registry.register("Tab", Tab)
|
||||
registry.register("Tablist", Tablist)
|
||||
registry.register("_tabset", _TablistImpl)
|
||||
|
||||
|
||||
@djc_test(
|
||||
parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR,
|
||||
components_settings={
|
||||
"dirs": [
|
||||
Path(__file__).parent / "components",
|
||||
# Include the directory where example components are defined
|
||||
Path(__file__).parent.parent / "sampleproject/examples/components",
|
||||
],
|
||||
},
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
@djc_test
|
||||
class TestExampleTabs:
|
||||
def test_render_simple_tabs(self, components_settings):
|
||||
def test_render_simple_tabs(self):
|
||||
_create_tab_components()
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
|
@ -47,15 +30,13 @@ class TestExampleTabs:
|
|||
template = Template(template_str)
|
||||
rendered = template.render(Context({}))
|
||||
|
||||
if components_settings["context_behavior"] == "django":
|
||||
comp_id = "ca1bc4b"
|
||||
else:
|
||||
comp_id = "ca1bc47"
|
||||
|
||||
assertHTMLEqual(
|
||||
rendered,
|
||||
f"""
|
||||
<div x-data="{{ selectedTab: 'my-tabs_tab-1_tab' }}" id="my-tabs" data-djc-id-{comp_id}>
|
||||
"""
|
||||
<div x-data="{
|
||||
selectedTab: 'my-tabs_tab-1_tab',
|
||||
}"
|
||||
id="my-tabs" data-djc-id-ca1bc4b>
|
||||
<div role="tablist" aria-label="My Tabs">
|
||||
<button
|
||||
:aria-selected="selectedTab === 'my-tabs_tab-1_tab'"
|
||||
|
@ -93,9 +74,9 @@ class TestExampleTabs:
|
|||
""",
|
||||
)
|
||||
|
||||
def test_disabled_tab(self, components_settings):
|
||||
def test_disabled_tab(self):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "Tablist" name="My Tabs" %}
|
||||
{% component "Tab" header="Tab 1" %}Content 1{% endcomponent %}
|
||||
|
@ -108,9 +89,9 @@ class TestExampleTabs:
|
|||
assert "disabled" in rendered
|
||||
assert "Content 2" in rendered
|
||||
|
||||
def test_custom_ids(self, components_settings):
|
||||
def test_custom_ids(self):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "Tablist" id="custom-list" name="My Tabs" %}
|
||||
{% component "Tab" id="custom-tab" header="Tab 1" %}Content 1{% endcomponent %}
|
||||
|
@ -125,9 +106,9 @@ class TestExampleTabs:
|
|||
assert 'id="custom-tab_content"' in rendered
|
||||
assert 'aria-labelledby="custom-tab_tab"' in rendered
|
||||
|
||||
def test_tablist_in_tab_raise_error(self, components_settings):
|
||||
def test_tablist_in_tab_raise_error(self):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "Tablist" name="Outer Tabs" %}
|
||||
{% component "Tab" header="Outer 1" %}
|
||||
|
@ -145,9 +126,9 @@ class TestExampleTabs:
|
|||
|
||||
assert "Inner Content" in rendered
|
||||
|
||||
def test_tab_in_tab_raise_error(self, components_settings):
|
||||
def test_tab_in_tab_raise_error(self):
|
||||
_create_tab_components()
|
||||
template_str = """
|
||||
template_str: types.django_html = """
|
||||
{% load component_tags %}
|
||||
{% component "Tablist" name="Outer Tabs" %}
|
||||
{% component "Tab" header="Outer 1" %}
|
|
@ -134,6 +134,7 @@ ignore = [
|
|||
"SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
"SLF001", # Private member accessed: `_registry`
|
||||
"TRY300", # Consider moving this statement to an `else` block
|
||||
"TRY301", # Abstract `raise` to an inner function
|
||||
|
||||
# TODO: Following could be useful to start using, but might require more changes.
|
||||
"C420", # Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead
|
||||
|
@ -173,6 +174,13 @@ ignore = [
|
|||
"T201", # `print` found
|
||||
"DTZ", # `datetime` found
|
||||
]
|
||||
"docs/examples/*" = [
|
||||
"ARG002", # Unused method argument
|
||||
"ANN", # Annotations are not needed for tests
|
||||
"S101", # Use of `assert` detected
|
||||
"T201", # `print` found
|
||||
"DTZ", # `datetime` found
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["django_components"]
|
||||
|
@ -216,5 +224,6 @@ reportIncompatibleVariableOverride = "none"
|
|||
[tool.pytest.ini_options]
|
||||
testpaths = [
|
||||
"tests",
|
||||
"docs/examples",
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
|
|
|
@ -23,6 +23,8 @@ python manage.py runserver
|
|||
|
||||
The app will be available at http://localhost:8000/.
|
||||
|
||||
To see more examples, visit http://localhost:8000/examples/.
|
||||
|
||||
### Serving static files
|
||||
|
||||
Assuming that you're running the dev server with `DEBUG=True` setting, ALL
|
||||
|
|
|
@ -1,3 +1,47 @@
|
|||
# Examples
|
||||
|
||||
This directory contains example components from the "Examples" documentation section.
|
||||
This Django app dynamically discovers and registers example components from the documentation (`docs/examples/`).
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Discovery**: At startup, the app scans `docs/examples/*/` directories for:
|
||||
|
||||
- `component.py` - Component definitions with inline templates
|
||||
- `page.py` - Page views for live demos
|
||||
|
||||
2. **Registration**: Found modules are imported as:
|
||||
|
||||
- `examples.dynamic.<example_name>.component`
|
||||
- `examples.dynamic.<example_name>.page`
|
||||
|
||||
3. **Components**: Are automatically registered with django-components registry via `@register()` decorators
|
||||
|
||||
4. **URLs**: Page views are automatically registered as URL patterns at `examples/<example_name>`
|
||||
|
||||
## Structure
|
||||
|
||||
Each example in `docs/examples/` follows this structure:
|
||||
|
||||
```
|
||||
docs/examples/form/
|
||||
├── README.md # Documentation
|
||||
├── component.py # Component with inline templates
|
||||
├── page.py # Page view for live demo
|
||||
├── test.py # Tests
|
||||
└── images/ # Screenshots/assets
|
||||
```
|
||||
|
||||
## Live examples
|
||||
|
||||
All examples are available as live demos:
|
||||
|
||||
- **Index page**: [http://localhost:8000/examples/](http://localhost:8000/examples/) - Lists all available examples
|
||||
- **Individual examples**: `http://localhost:8000/examples/<example_name>`
|
||||
- [http://localhost:8000/examples/form](http://localhost:8000/examples/form)
|
||||
- [http://localhost:8000/examples/tabs](http://localhost:8000/examples/tabs)
|
||||
|
||||
## Adding new examples
|
||||
|
||||
1. Create a new directory in `docs/examples/<example_name>/`
|
||||
2. Add `component.py`, `page.py`, and other files as seen above.
|
||||
3. Start the server and open `http://localhost:8000/examples/<example_name>` to see the example.
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
from .utils import discover_example_modules
|
||||
|
||||
|
||||
# This adds finds all examples defined in `docs/examples/` and for each of them
|
||||
# adds a URL that renders that example's live demo. These are available under
|
||||
# `http://localhost:8000/examples/<example_name>`.
|
||||
#
|
||||
# Overview of all examples is available under `http://localhost:8000/examples/`.
|
||||
class ExamplesConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "examples"
|
||||
|
||||
def ready(self):
|
||||
# Auto-discover and register example components and pages
|
||||
discover_example_modules()
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<form
|
||||
{% if submit_href and editable %} action="{{ submit_href }}" {% endif %}
|
||||
method="{{ method }}"
|
||||
{% html_attrs attrs %}
|
||||
>
|
||||
{% slot "prepend" / %}
|
||||
|
||||
<div {% html_attrs form_content_attrs %}>
|
||||
{# Generate a grid of fields and labels out of given slots #}
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-4 gap-y-2 items-center">
|
||||
{% for field_name, label in fields %}
|
||||
{{ label }}
|
||||
{% slot name=field_name / %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% slot "append" / %}
|
||||
</form>
|
|
@ -1,100 +0,0 @@
|
|||
/* based on https://codepen.io/brettsmason/pen/zYGEgZP */
|
||||
|
||||
[role="tablist"] {
|
||||
margin: 0 0 -0.1em;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[role="tab"] {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0.3em 0.5em 0.4em;
|
||||
border: 1px solid hsl(219, 1%, 72%);
|
||||
border-radius: 0.2em 0.2em 0 0;
|
||||
box-shadow: 0 0 0.2em hsl(219, 1%, 72%);
|
||||
overflow: visible;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
background: hsl(220, 20%, 94%);
|
||||
}
|
||||
|
||||
[role="tab"]:hover::before,
|
||||
[role="tab"]:focus::before,
|
||||
[role="tab"][aria-selected="true"]::before {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: -1px;
|
||||
left: -1px;
|
||||
border-radius: 0.2em 0.2em 0 0;
|
||||
border-top: 3px solid LinkText;
|
||||
content: '';
|
||||
}
|
||||
|
||||
[role="tab"][aria-selected="true"] {
|
||||
border-radius: 0;
|
||||
background: hsl(220, 43%, 99%);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
[role="tab"][aria-selected="true"]:not(:focus):not(:hover)::before {
|
||||
border-top: 5px solid SelectedItem;
|
||||
}
|
||||
|
||||
[role="tab"][aria-selected="true"]::after {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
bottom: -1px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 0.3em;
|
||||
background: hsl(220, 43%, 99%);
|
||||
box-shadow: none;
|
||||
content: '';
|
||||
}
|
||||
|
||||
[role="tab"]:hover,
|
||||
[role="tab"]:focus,
|
||||
[role="tab"]:active {
|
||||
outline: 0;
|
||||
border-radius: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
[role="tab"]:hover::before,
|
||||
[role="tab"]:focus::before {
|
||||
border-color: LinkText;
|
||||
}
|
||||
|
||||
[role="tabpanel"] {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.5em 0.5em 0.7em;
|
||||
border: 1px solid hsl(219, 1%, 72%);
|
||||
border-radius: 0 0.2em 0.2em 0.2em;
|
||||
box-shadow: 0 0 0.2em hsl(219, 1%, 72%);
|
||||
background: hsl(220, 43%, 99%);
|
||||
}
|
||||
|
||||
[role="tabpanel"]:focus {
|
||||
border-color: LinkText;
|
||||
box-shadow: 0 0 0.2em LinkText;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
[role="tabpanel"]:focus::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -1px;
|
||||
left: -1px;
|
||||
border-bottom: 3px solid LinkText;
|
||||
border-radius: 0 0 0.2em 0.2em;
|
||||
content: '';
|
||||
}
|
||||
|
||||
[role="tabpanel"] p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[role="tabpanel"] * + p {
|
||||
margin-top: 1em;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
{% load component_tags %}
|
||||
<div
|
||||
x-data="{ selectedTab: '{{ selected_tab }}' }"
|
||||
{% html_attrs
|
||||
container_attrs
|
||||
id=id
|
||||
%}
|
||||
>
|
||||
<div
|
||||
{% html_attrs
|
||||
tablist_attrs
|
||||
role="tablist"
|
||||
aria-label=name
|
||||
%}>
|
||||
{% for tab_datum, is_hidden in tab_data %}
|
||||
<button
|
||||
:aria-selected="selectedTab === '{{ tab_datum.tab_id }}'"
|
||||
@click="selectedTab = '{{ tab_datum.tab_id }}'"
|
||||
{% html_attrs
|
||||
tab_attrs
|
||||
id=tab_datum.tab_id
|
||||
role="tab"
|
||||
aria-controls=tab_datum.tabpanel_id
|
||||
disabled=tab_datum.disabled
|
||||
%}>
|
||||
{{ tab_datum.header }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% for tab_datum, is_hidden in tab_data %}
|
||||
<article
|
||||
:hidden="selectedTab != '{{ tab_datum.tab_id }}'"
|
||||
{% html_attrs
|
||||
tabpanel_attrs
|
||||
hidden=is_hidden
|
||||
role="tabpanel"
|
||||
id=tab_datum.tabpanel_id
|
||||
aria-labelledby=tab_datum.tab_id
|
||||
%}>
|
||||
{{ tab_datum.content }}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
|
@ -1,9 +1,57 @@
|
|||
import importlib
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from examples.pages.form import FormPage
|
||||
from examples.pages.tabs import TabsPage
|
||||
from django_components.component import Component
|
||||
|
||||
from .utils import discover_example_modules
|
||||
from .views import ExamplesIndexPage
|
||||
|
||||
|
||||
# For each example in `docs/examples/*`, register a URL pattern that points to the example's view.
|
||||
# The example will be available at `http://localhost:8000/examples/<example_name>`.
|
||||
# The view is the first Component class that we find in example's `page.py` module.
|
||||
#
|
||||
# So if we have an example called `form`:
|
||||
# 1. We look for a module `examples.dynamic.form.page`,
|
||||
# 2. We find the first Component class in that module (in this case `FormPage`),
|
||||
# 3. We register a URL pattern that points to that view (in this case `http://localhost:8000/examples/form`).
|
||||
def get_example_urls():
|
||||
# First, ensure all example modules are discovered and imported
|
||||
examples_names = discover_example_modules()
|
||||
|
||||
urlpatterns = [
|
||||
path("examples/tabs", TabsPage.as_view(), name="tabs"),
|
||||
path("examples/form", FormPage.as_view(), name="form"),
|
||||
# Index page that lists all examples
|
||||
path("examples/", ExamplesIndexPage.as_view(), name="examples_index"),
|
||||
]
|
||||
for example_name in examples_names:
|
||||
try:
|
||||
# Import the page module (should already be loaded by discover_example_modules)
|
||||
module_name = f"examples.dynamic.{example_name}.page"
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
# Find the view class (assume it's the first Component class)
|
||||
view_class = None
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
if issubclass(attr, Component) and attr_name != "Component":
|
||||
view_class = attr
|
||||
break
|
||||
|
||||
if not view_class:
|
||||
raise ValueError(f"No Component class found in {module_name}")
|
||||
|
||||
# Make the example availble under localhost:8000/examples/<example_name>
|
||||
url_pattern = f"examples/{example_name}"
|
||||
view_name = example_name
|
||||
|
||||
urlpatterns.append(path(url_pattern, view_class.as_view(), name=view_name))
|
||||
print(f"Registered URL: {url_pattern} -> {view_class.__name__}")
|
||||
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"Failed to register URL for {example_name}: {e}")
|
||||
|
||||
return urlpatterns
|
||||
|
||||
|
||||
urlpatterns = get_example_urls()
|
||||
|
|
93
sampleproject/examples/utils.py
Normal file
93
sampleproject/examples/utils.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Set
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
# Keep track of what we've already discovered to make subsequent calls a noop
|
||||
_discovered_examples: Set[str] = set()
|
||||
|
||||
|
||||
def discover_example_modules() -> List[str]:
|
||||
"""
|
||||
Find and import `component.py` and `page.py` files from example directories
|
||||
`docs/examples/*/` (e.g. `docs/examples/form/component.py`).
|
||||
|
||||
These files will be importable from other modules like:
|
||||
|
||||
```python
|
||||
from examples.dynamic.form.component import Form
|
||||
# or
|
||||
from examples.dynamic.form.page import FormPage
|
||||
```
|
||||
|
||||
Components will be also registered with the ComponentRegistry, so they can be used
|
||||
in the templates via the `{% component %}` tag like:
|
||||
|
||||
```django
|
||||
{% component "form" / %}
|
||||
```
|
||||
|
||||
This function is idempotent - calling it multiple times will not re-import modules.
|
||||
"""
|
||||
# Skip if we've already discovered examples
|
||||
if _discovered_examples:
|
||||
return list(_discovered_examples)
|
||||
|
||||
docs_examples_dir: Path = settings.EXAMPLES_DIR
|
||||
if not docs_examples_dir.exists():
|
||||
raise FileNotFoundError(f"Docs examples directory not found: {docs_examples_dir}")
|
||||
|
||||
for example_dir in docs_examples_dir.iterdir():
|
||||
if not example_dir.is_dir():
|
||||
continue
|
||||
|
||||
example_name = example_dir.name
|
||||
|
||||
component_file = example_dir / "component.py"
|
||||
if component_file.exists():
|
||||
_import_module_file(component_file, example_name, "component")
|
||||
|
||||
page_file = example_dir / "page.py"
|
||||
if page_file.exists():
|
||||
_import_module_file(page_file, example_name, "page")
|
||||
|
||||
# Mark this example as discovered
|
||||
_discovered_examples.add(example_name)
|
||||
|
||||
return list(_discovered_examples)
|
||||
|
||||
|
||||
def _import_module_file(py_file: Path, example_name: str, module_type: str):
|
||||
"""
|
||||
Dynamically import a python file as a module.
|
||||
|
||||
This file will then be importable from other modules like:
|
||||
|
||||
```python
|
||||
from examples.dynamic.form.component import Form
|
||||
# or
|
||||
from examples.dynamic.form.page import FormPage
|
||||
```
|
||||
"""
|
||||
module_name = f"examples.dynamic.{example_name}.{module_type}"
|
||||
|
||||
# Skip if module is already imported
|
||||
if module_name in sys.modules:
|
||||
return
|
||||
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(module_name, py_file)
|
||||
if not spec or not spec.loader:
|
||||
raise ValueError(f"Failed to load {module_type} {example_name}/{py_file.name}")
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
# Add to sys.modules so the contents can be imported from other modules
|
||||
# via Python import system.
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
print(f"Loaded example {module_type}: {example_name}/{py_file.name}")
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"Failed to load {module_type} {example_name}/{py_file.name}: {e}")
|
122
sampleproject/examples/views.py
Normal file
122
sampleproject/examples/views.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
from django.http import HttpRequest
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from django_components import Component, types
|
||||
|
||||
from .utils import discover_example_modules
|
||||
|
||||
|
||||
class ExamplesIndexPage(Component):
|
||||
"""Index page that lists all available examples"""
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
mark_safe(
|
||||
'<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp,container-queries"></script>'
|
||||
),
|
||||
)
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Get the list of discovered examples
|
||||
example_names = discover_example_modules()
|
||||
|
||||
# Convert example names to display format
|
||||
examples = []
|
||||
for name in sorted(example_names):
|
||||
# Convert snake_case to PascalCase (e.g. error_fallback -> ErrorFallback)
|
||||
display_name = "".join(word.capitalize() for word in name.split("_"))
|
||||
examples.append(
|
||||
{
|
||||
"name": name, # Original name for URLs
|
||||
"display_name": display_name, # PascalCase for display
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"examples": examples,
|
||||
}
|
||||
|
||||
class View:
|
||||
def get(self, request: HttpRequest):
|
||||
return ExamplesIndexPage.render_to_response(request=request)
|
||||
|
||||
template: types.django_html = """
|
||||
<html>
|
||||
<head>
|
||||
<title>Django Components Examples</title>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<div class="max-w-4xl mx-auto py-12 px-6">
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-4">
|
||||
Django Components Examples
|
||||
</h1>
|
||||
<p class="text-xl text-gray-600">
|
||||
Interactive examples showcasing django-components features
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if examples %}
|
||||
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for example in examples %}
|
||||
<div class="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 flex flex-col">
|
||||
<div class="p-6 flex flex-col flex-grow">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-2">
|
||||
{{ example.display_name }}
|
||||
</h2>
|
||||
<p class="text-gray-600 mb-4 flex-grow">
|
||||
{{ example.display_name }} component example
|
||||
</p>
|
||||
<a
|
||||
href="/examples/{{ example.name }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors duration-200 self-start"
|
||||
>
|
||||
View Example
|
||||
<svg class="ml-2 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<div class="text-gray-400 mb-4">
|
||||
<svg class="mx-auto w-16 h-16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No examples found</h3>
|
||||
<p class="text-gray-600">
|
||||
No example components were discovered in the docs/examples/ directory.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-12 text-center">
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
About these examples
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
These examples are dynamically discovered from the <code class="bg-gray-100 px-2 py-1 rounded text-sm">docs/examples/</code> directory.
|
||||
Each example includes a component definition, live demo page, and tests.
|
||||
</p>
|
||||
<a
|
||||
href="https://github.com/django-components/django-components"
|
||||
class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
View on GitHub
|
||||
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""" # noqa: E501
|
|
@ -169,3 +169,9 @@ LOGGING = {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
#####################
|
||||
# CUSTOM SETTINGS
|
||||
#####################
|
||||
|
||||
EXAMPLES_DIR = BASE_DIR.parent / "docs" / "examples"
|
||||
|
|
|
@ -11,7 +11,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -8,7 +8,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -4,7 +4,7 @@ from django_components.util.cache import LRUCache
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -7,7 +7,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -12,7 +12,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -11,7 +11,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
# NOTE: Argparse changed how the optional args are displayed in Python 3.11+
|
||||
if sys.version_info >= (3, 10):
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# Either back or forward slash
|
||||
|
|
|
@ -34,7 +34,7 @@ from django_components.urls import urlpatterns as dc_urlpatterns
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# Client for testing endpoints via requests
|
||||
|
|
|
@ -11,7 +11,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# Common settings for all tests
|
||||
|
|
|
@ -7,7 +7,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -7,7 +7,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def _prepare_template() -> Template:
|
||||
|
|
|
@ -20,7 +20,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# "Main media" refer to the HTML, JS, and CSS set on the Component class itself
|
||||
|
|
|
@ -11,7 +11,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -33,7 +33,7 @@ class ComponentBeforeReady(Component):
|
|||
template = "Hello"
|
||||
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
class CustomClient(Client):
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.util.misc import gen_id
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# Context processor that generates a unique ID. This is used to test that the context
|
||||
|
|
|
@ -16,7 +16,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
class SimpleComponent(Component):
|
||||
|
|
|
@ -12,7 +12,6 @@ if TYPE_CHECKING:
|
|||
from django_components import types
|
||||
|
||||
setup_test_config(
|
||||
components={"autodiscover": False},
|
||||
extra_settings={
|
||||
"ROOT_URLCONF": "tests.test_dependency_manager",
|
||||
},
|
||||
|
|
|
@ -15,7 +15,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def to_spaces(s: str):
|
||||
|
|
|
@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
|||
|
||||
from django_components import types
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# NOTE: All views, components, and associated JS and CSS are defined in
|
||||
|
|
|
@ -14,7 +14,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
engine = Template("").engine
|
||||
|
|
|
@ -37,7 +37,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def dummy_view(request: HttpRequest):
|
||||
|
|
|
@ -8,7 +8,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# This subclass allows us to call the `collectstatic` command from within Python.
|
||||
|
|
|
@ -5,7 +5,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# This same set of tests is also found in djc_html_parser, to ensure that
|
||||
|
|
|
@ -25,7 +25,7 @@ except ImportError:
|
|||
TemplateProxy = None
|
||||
|
||||
|
||||
setup_test_config(components={"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# See https://github.com/django-components/django-components/issues/1323#issuecomment-3156654329
|
||||
|
|
|
@ -11,7 +11,7 @@ from django_components.util.loader import _filepath_to_python_module, get_compon
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -17,7 +17,7 @@ from django_components.util.tag_parser import TagAttr
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -21,7 +21,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
class MockComponent(Component):
|
||||
|
|
|
@ -9,7 +9,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config(components={"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -7,7 +7,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def _get_templates_used_to_render(subject_template, render_context=None):
|
||||
|
|
|
@ -18,7 +18,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# Test interaction of the `Slot` instances with Component rendering
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
class MultiwordStartTagFormatter(ShorthandComponentFormatter):
|
||||
|
|
|
@ -12,7 +12,7 @@ from django_components.util.tag_parser import TagAttr, TagValue, TagValuePart, T
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# NOTE: We have to define the parser to be able to resolve filters
|
||||
|
|
|
@ -6,7 +6,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -8,7 +8,7 @@ from django_components.util.template_parser import parse_template
|
|||
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def token2tuple(token: Token):
|
||||
|
|
|
@ -8,7 +8,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
#######################
|
||||
|
|
|
@ -9,7 +9,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def gen_slotted_component():
|
||||
|
|
|
@ -8,7 +8,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def gen_slotted_component():
|
||||
|
|
|
@ -14,7 +14,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
# NOTE: By running garbage collection and then checking for empty caches,
|
||||
|
@ -714,6 +714,9 @@ class TestProvideTemplateTag:
|
|||
# Ensure all caches are properly cleaned up even with multiple component instances
|
||||
_assert_clear_cache()
|
||||
|
||||
# TODO - Enable once globals and finalizers are scoped to a single DJC instance")
|
||||
# See https://github.com/django-components/django-components/issues/1413
|
||||
@pytest.mark.skip("#TODO")
|
||||
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
||||
def test_provide_component_inside_nested_forloop(self, components_settings):
|
||||
@register("nested_loop_component")
|
||||
|
@ -758,6 +761,9 @@ class TestProvideTemplateTag:
|
|||
# Ensure all caches are properly cleaned up even with many component instances
|
||||
_assert_clear_cache()
|
||||
|
||||
# TODO - Enable once globals and finalizers are scoped to a single DJC instance")
|
||||
# See https://github.com/django-components/django-components/issues/1413
|
||||
@pytest.mark.skip("#TODO")
|
||||
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
||||
def test_provide_component_forloop_with_error(self, components_settings):
|
||||
@register("error_loop_component")
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
def _gen_slotted_component():
|
||||
|
|
|
@ -10,7 +10,7 @@ from django_components.testing import djc_test
|
|||
|
||||
from .testutils import PARAMETRIZE_CONTEXT_BEHAVIOR, setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
setup_test_config()
|
||||
|
||||
|
||||
@djc_test
|
||||
|
|
|
@ -50,6 +50,7 @@ def setup_test_config(
|
|||
},
|
||||
],
|
||||
"COMPONENTS": {
|
||||
"autodiscover": False,
|
||||
**(components or {}),
|
||||
},
|
||||
"MIDDLEWARE": [],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue