docs: fix links in README and "overview" section, add tutorial (#842)

This commit is contained in:
Juro Oravec 2024-12-16 14:15:02 +01:00 committed by GitHub
parent 6813c9d7aa
commit 789f3807aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1213 additions and 216 deletions

View file

@ -1,10 +1,11 @@
<!-- See Pydantic for inspo https://docs.pydantic.dev/latest -->
- [Get Started](overview/)
- Concepts
- [Getting started](concepts/getting_started/)
- [Fundamentals](concepts/fundamentals/)
- [Advanced](concepts/advanced/)
- Guides
- [Setup](guides/setup/)
- [Cookbook](guides/cookbook/)
- [Dev guides](guides/devguides/)
- [API Documentation](reference/)
- [Release notes](release_notes.md)

View file

@ -235,9 +235,6 @@ After the package has been published, all that remains is to install it in other
{
...,
'OPTIONS': {
'context_processors': [
...
],
'builtins': [
'myapp.templatetags.mytags',
]

View file

@ -1,6 +1,6 @@
---
title: Accessing component inputs
weight: 5
weight: 3
---
When you call `Component.render` or `Component.render_to_response`, the inputs to these methods can be accessed from within the instance under `self.input`.

View file

@ -1,6 +1,6 @@
---
title: Autodiscovery
weight: 11
weight: 9
---
Every component that you want to use in the template with the [`{% component %}`](django_components.templateags.component_tags)

View file

@ -1,6 +1,6 @@
---
title: Component context and scope
weight: 6
weight: 4
---
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.

View file

@ -1,6 +1,6 @@
---
title: Components as views
weight: 12
weight: 10
---
_New in version 0.34_

View file

@ -1,6 +1,6 @@
---
title: Components in Python
weight: 4
weight: 2
---
_New in version 0.81_

View file

@ -1,51 +0,0 @@
---
title: Components in templates
weight: 3
---
First load the `component_tags` tag library, then use the `component_[js/css]_dependencies` and `component` tags to render the component to the page.
```htmldjango
{% load component_tags %}
<!DOCTYPE html>
<html>
<head>
<title>My example calendar</title>
{% component_css_dependencies %}
</head>
<body>
{% component "calendar" date="2015-06-19" %}{% endcomponent %}
{% component_js_dependencies %}
</body>
<html>
```
> NOTE: Instead of writing `{% endcomponent %}` at the end, you can use a self-closing tag:
>
> `{% component "calendar" date="2015-06-19" / %}`
The output from the above template will be:
```html
<!DOCTYPE html>
<html>
<head>
<title>My example calendar</title>
<link
href="/static/calendar/style.css"
type="text/css"
media="all"
rel="stylesheet"
/>
</head>
<body>
<div class="calendar-component">
Today's date is <span>2015-06-19</span>
</div>
<script src="/static/calendar/script.js"></script>
</body>
<html></html>
</html>
```
This makes it possible to organize your front-end around reusable components. Instead of relying on template tags and keeping your CSS and Javascript in the static directory.

View file

@ -1,6 +1,6 @@
---
title: Defining HTML / JS / CSS files
weight: 10
weight: 8
---
django_component's management of files builds on top of [Django's `Media` class](https://docs.djangoproject.com/en/5.0/topics/forms/media/).

View file

@ -1,6 +1,6 @@
---
title: HTML attributes
weight: 9
weight: 7
---
_New in version 0.74_:

View file

@ -1,6 +1,6 @@
---
title: Single-file components
weight: 2
weight: 1
---
Components can also be defined in a single file, which is useful for small components. To do this, you can use the `template`, `js`, and `css` class attributes instead of the `template_name` and `Media`. For example, here's the calendar component from above, defined in a single file:

View file

@ -1,6 +1,6 @@
---
title: Slots
weight: 8
weight: 6
---
_New in version 0.26_:

View file

@ -1,6 +1,6 @@
---
title: Template tag syntax
weight: 7
weight: 5
---
All template tags in django_component, like `{% component %}` or `{% slot %}`, and so on,

View file

@ -1,88 +0,0 @@
---
title: Create your first component
weight: 1
---
A component in django-components is the combination of four things: CSS, Javascript, a Django template, and some Python code to put them all together.
```
sampleproject/
├── calendarapp/
├── components/ 🆕
│ └── calendar/ 🆕
│ ├── calendar.py 🆕
│ ├── script.js 🆕
│ ├── style.css 🆕
│ └── template.html 🆕
├── sampleproject/
├── manage.py
└── requirements.txt
```
Start by creating empty files in the structure above.
First, you need a CSS file. Be sure to prefix all rules with a unique class so they don't clash with other rules.
```css title="[project root]/components/calendar/style.css"
/* In a file called [project root]/components/calendar/style.css */
.calendar-component {
width: 200px;
background: pink;
}
.calendar-component span {
font-weight: bold;
}
```
Then you need a javascript file that specifies how you interact with this component. You are free to use any javascript framework you want. A good way to make sure this component doesn't clash with other components is to define all code inside an anonymous function that calls itself. This makes all variables defined only be defined inside this component and not affect other components.
```js title="[project root]/components/calendar/script.js"
/* In a file called [project root]/components/calendar/script.js */
(function () {
if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function () {
alert("Clicked calendar!");
};
}
})();
```
Now you need a Django template for your component. Feel free to define more variables like `date` in this example. When creating an instance of this component we will send in the values for these variables. The template will be rendered with whatever template backend you've specified in your Django settings file.
```htmldjango title="[project root]/components/calendar/calendar.html"
{# In a file called [project root]/components/calendar/template.html #}
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
```
Finally, we use django-components to tie this together. Start by creating a file called `calendar.py` in your component calendar directory. It will be auto-detected and loaded by the app.
Inside this file we create a Component by inheriting from the Component class and specifying the context method. We also register the global component registry so that we easily can render it anywhere in our templates.
```python title="[project root]/components/calendar/calendar.py"
# In a file called [project root]/components/calendar/calendar.py
from django_components import Component, register
@register("calendar")
class Calendar(Component):
# Templates inside `[your apps]/components` dir and `[project root]/components` dir
# will be automatically found.
#
# `template_name` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
template_name = "template.html"
# Or
def get_template_name(context):
return f"template-{context['name']}.html"
# This component takes one parameter, a date string to show in the template
def get_context_data(self, date):
return {
"date": date,
}
# Both `css` and `js` can be relative to dir where `calendar.py` is, or relative to COMPONENTS.dirs
class Media:
css = "style.css"
js = "script.js"
```
And voilá!! We've created our first component.

View file

@ -0,0 +1,240 @@
---
title: Adding JS and CSS
weight: 2
---
Next we will add CSS and JavaScript to our template.
!!! info
In django-components, using JS and CSS is as simple as defining them on the Component class.
You don't have to insert the `<script>` and `<link>` tags into the HTML manually.
Behind the scenes, django-components keeps track of which components use which JS and CSS
files. Thus, when a component is rendered on the page, the page will contain only the JS
and CSS used by the components, and nothing more!
### 1. Update project structure
Start by creating empty `calendar.js` and `calendar.css` files:
```
sampleproject/
├── calendarapp/
├── components/
│ └── calendar/
│ ├── calendar.py
│ ├── calendar.js 🆕
│ ├── calendar.css 🆕
│ └── calendar.html
├── sampleproject/
├── manage.py
└── requirements.txt
```
### 2. Write CSS
Inside `calendar.css`, write:
```css title="[project root]/components/calendar/calendar.css"
.calendar {
width: 200px;
background: pink;
}
.calendar span {
font-weight: bold;
}
```
Be sure to prefix your rules with unique CSS class like `calendar`, so the CSS doesn't clash with other rules.
<!-- TODO: UPDATE AFTER SCOPED CSS ADDED -->
!!! note
Soon, django-components will automatically scope your CSS by default, so you won't have to worry
about CSS class clashes.
This CSS will be inserted into the page as an inlined `<style>` tag, at the position defined by
[`{% component_css_dependencies %}`](../../reference/template_tags.md#component_css_dependencies),
or at the end of the inside the `<head>` tag (See [JS and CSS output locations](../../advanced/rendering_js_css/#js-and-css-output-locations)).
So in your HTML, you may see something like this:
```html
<html>
<head>
...
<style>
.calendar {
width: 200px;
background: pink;
}
.calendar span {
font-weight: bold;
}
</style>
</head>
<body>
...
</body>
</html>
```
### 3. Write JS
Next we write a JavaScript file that specifies how to interact with this component.
You are free to use any javascript framework you want.
```js title="[project root]/components/calendar/calendar.js"
(function () {
document.querySelector(".calendar").onclick = () => {
alert("Clicked calendar!");
};
})();
```
A good way to make sure the JS of this component doesn't clash with other components is to define all JS code inside
an [anonymous self-invoking function](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) (`(() => { ... })()`).
This makes all variables defined only be defined inside this component and not affect other components.
<!-- TODO: UPDATE AFTER FUNCTIONS WRAPPED -->
!!! note
Soon, django-components will automatically wrap your JS in a self-invoking function by default
(except for JS defined with `<script type="module">`).
Similarly to CSS, JS will be inserted into the page as an inlined `<script>` tag, at the position defined by
[`{% component_js_dependencies %}`](../../reference/template_tags.md#component_js_dependencies),
or at the end of the inside the `<body>` tag (See [JS and CSS output locations](../../advanced/rendering_js_css/#js-and-css-output-locations)).
So in your HTML, you may see something like this:
```html
<html>
<head>
...
</head>
<body>
...
<script>
(function () {
document.querySelector(".calendar").onclick = () => {
alert("Clicked calendar!");
};
})();
</script>
</body>
</html>
```
#### Rules of JS execution
1. **JS is executed in the order in which the components are found in the HTML**
By default, the JS is inserted as a **synchronous** script (`<script> ... </script>`)
So if you define multiple components on the same page, their JS will be
executed in the order in which the components are found in the HTML.
So if we have a template like so:
```htmldjango
<html>
<head>
...
</head>
<body>
{% component "calendar" / %}
{% component "table" / %}
</body>
</html>
```
Then the JS file of the component `calendar` will be executed first, and the JS file
of component `table` will be executed second.
2. **JS will be executed only once, even if there is multiple instances of the same component**
In this case, the JS of `calendar` will STILL execute first (because it was found first),
and will STILL execute only once, even though it's present twice:
```htmldjango
<html>
<head>
...
</head>
<body>
{% component "calendar" / %}
{% component "table" / %}
{% component "calendar" / %}
</body>
</html>
```
### 4. Link JS and CSS to a component
Finally, we return to our Python component in `calendar.py` to tie this together.
To link JS and CSS defined in other files, use the `Media` nested class
([Learn more about using Media](../fundamentals/defining_js_css_html_files.md)).
```python title="[project root]/components/calendar/calendar.py"
from django_components import Component
class Calendar(Component):
template_name = "calendar.html"
class Media: # <--- new
js = "calendar.js"
css = "calendar.css"
def get_context_data(self):
return {
"date": "1970-01-01",
}
```
!!! info
The `Media` nested class is shaped based on [Django's Media class](https://docs.djangoproject.com/en/5.1/topics/forms/media/).
As such, django-components allows multiple formats to define the nested Media class:
```py
# Single files
class Media:
js = "calendar.js"
css = "calendar.css"
# Lists of files
class Media:
js = ["calendar.js", "calendar2.js"]
css = ["calendar.css", "calendar2.css"]
# Dictionary of media types for CSS
class Media:
js = ["calendar.js", "calendar2.js"]
css = {
"all": ["calendar.css", "calendar2.css"],
}
```
If you define a list of JS files, they will be executed one-by-one, left-to-right.
!!! note
Same as with the template file, the file paths for the JS and CSS files can be either:
1. Relative to the Python component file (as seen above),
2. Relative to any of the component directories as defined by
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
(e.g. `[your apps]/components` dir and `[project root]/components`)
And that's it! If you were to embed this component in an HTML, django-components will
automatically embed the associated JS and CSS.
Now that we have a fully-defined component, [next let's use it in a Django template ➡️](./components_in_templates.md).

View file

@ -0,0 +1,295 @@
---
title: Adding slots
weight: 5
---
Our calendar component's looking great! But we just got a new assignment from
our colleague - The calendar date needs to be shown on 3 different pages:
1. On one page, it needs to be shown as is
2. On the second, the date needs to be **bold**
3. On the third, the date needs to be in *italics*
As a reminder, this is what the component's template looks like:
```htmldjango
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
```
There's many ways we could approach this:
- Expose the date in a slot
- Style `.calendar > span` differently on different pages
- Pass a variable to the component that decides how the date is rendered
- Create a new component
First two options are more flexible, because the custom styling is not baked into a component's
implementation. And for the sake of demonstration, we'll solve this challenge with slots.
### 1. What are slots
Components support something called [Slots](../fundamentals/slots.md).
When a component is used inside another template, slots allow the parent template
to override specific parts of the child component by passing in different content.
This mechanism makes components more reusable and composable.
This behavior is similar to [slots in Vue](https://vuejs.org/guide/components/slots.html).
In the example below we introduce two tags that work hand in hand to make this work. These are...
- `{% slot <name> %}`/`{% endslot %}`: Declares a new slot in the component template.
- `{% fill <name> %}`/`{% endfill %}`: (Used inside a [`{% component %}`](../../reference/template_tags.md#component)
tag pair.) Fills a declared slot with the specified content.
### 2. Add a slot tag
Let's update our calendar component to support more customization. We'll add
[`{% slot %}`](../../reference/template_tags.md#slot) tag to the template:
```htmldjango
<div class="calendar">
Today's date is
{% slot "date" default %} {# <--- new #}
<span>{{ date }}</span>
{% endslot %}
</div>
```
Notice that:
1. We named the slot `date` - so we can fill this slot by using `{% fill "date" %}`
2. We also made it the [default slot](../fundamentals/slots.md#default-slot).
3. We placed our original implementation inside the [`{% slot %}`](../../reference/template_tags.md#slot)
tag - this is what will be rendered when the slot is NOT overriden.
### 3. Add fill tag
Now we can use [`{% fill %}`](../../reference/template_tags.md#fill) tags inside the
[`{% component %}`](../../reference/template_tags.md#component) tags to override the `date` slot
to generate the bold and italics variants:
```htmldjango
{# Default #}
{% component "calendar" date="2024-12-13" / %}
{# Bold #}
{% component "calendar" date="2024-12-13" %}
<b> 2024-12-13 </b>
{% endcomponent %}
{# Italics #}
{% component "calendar" date="2024-12-13" %}
<i> 2024-12-13 </i>
{% endcomponent %}
```
Which will render as:
```html
<!-- Default -->
<div class="calendar">
Today's date is <span>2024-12-13</span>
</div>
<!-- Bold -->
<div class="calendar">
Today's date is <b>2024-12-13</b>
</div>
<!-- Italics -->
<div class="calendar">
Today's date is <i>2024-12-13</i>
</div>
```
!!! info
Since we used the `default` flag on `{% slot "date" %}` inside our calendar component,
we can target the `date` component in multiple ways:
1. Explicitly by it's name
```htmldjango
{% component "calendar" date="2024-12-13" %}
{% fill "date" %}
<i> 2024-12-13 </i>
{% endfill %}
{% endcomponent %}
```
2. Implicitly as the [default slot](../fundamentals/slots.md#default-slot) (Omitting the
[`{% fill %}`](../../reference/template_tags.md#fill) tag)
```htmldjango
{% component "calendar" date="2024-12-13" %}
<i> 2024-12-13 </i>
{% endcomponent %}
```
3. Explicitly as the [default slot](../fundamentals/slots.md#default-slot) (Setting fill name to `default`)
```htmldjango
{% component "calendar" date="2024-12-13" %}
{% fill "default" %}
<i> 2024-12-13 </i>
{% endfill %}
{% endcomponent %}
```
### 5. Wait, there's a bug
There is a mistake in our code! `2024-12-13` is Friday, so that's fine. But if we updated
the to `2024-12-14`, which is Saturday, our template from previous step would render this:
```html
<!-- Default -->
<div class="calendar">
Today's date is <span>2024-12-16</span>
</div>
<!-- Bold -->
<div class="calendar">
Today's date is <b>2024-12-14</b>
</div>
<!-- Italics -->
<div class="calendar">
Today's date is <i>2024-12-14</i>
</div>
```
The first instance rendered `2024-12-16`, while the rest rendered `2024-12-14`!
Why? Remember that in the [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
method of our Calendar component, we pre-process the date. If the date falls on Saturday or Sunday, we shift it to next Monday:
```python title="[project root]/components/calendar/calendar.py"
from datetime import date
from django_components import Component, register
# If date is Sat or Sun, shift it to next Mon, so the date is always workweek.
def to_workweek_date(d: date):
...
@register("calendar")
class Calendar(Component):
template_name = "calendar.html"
...
def get_context_data(self, date: date, extra_class: str | None = None):
workweek_date = to_workweek_date(date)
return {
"date": workweek_date,
"extra_class": extra_class,
}
```
And the issue is that in our template, we used the `date` value that we used *as input*,
which is NOT the same as the `date` variable used inside Calendar's template.
### 5. Adding data to slots
We want to use the same `date` variable that's used inside Calendar's template.
Luckily, django-components allows passing data to the slot, also known as [Scoped slots](../fundamentals/slots.md#scoped-slots).
This consists of two steps:
1. Pass the `date` variable to the [`{% slot %}`](../../reference/template_tags.md#slot) tag
2. Access the `date` variable in the [`{% fill %}`](../../reference/template_tags.md#fill)
tag by using the special `data` kwarg
Let's update the Calendar's template:
```htmldjango
<div class="calendar">
Today's date is
{% slot "date" default date=date %} {# <--- changed #}
<span>{{ date }}</span>
{% endslot %}
</div>
```
!!! info
The [`{% slot %}`](../../reference/template_tags.md#slot) tag has one special kwarg, `name`. When you write
```htmldjango
{% slot "date" / %}
```
It's the same as:
```htmldjango
{% slot name="date" / %}
```
Other than the `name` kwarg, you can pass any extra kwargs to the [`{% slot %}`](../../reference/template_tags.md#slot) tag,
and these will be exposed as the slot's data.
```htmldjango
{% slot name="date" kwarg1=123 kwarg2="text" kwarg3=my_var / %}
```
### 6. Accessing slot data in fills
Now, on the [`{% fill %}`](../../reference/template_tags.md#fill) tags, we can use the `data` kwarg to specify the variable under which
the slot data will be available.
The variable from the `data` kwarg contains all the extra kwargs passed to the [`{% slot %}`](../../reference/template_tags.md#slot) tag.
So if we set `data="slot_data"`, then we can access the date variable under `slot_data.date`:
```htmldjango
{# Default #}
{% component "calendar" date="2024-12-13" / %}
{# Bold #}
{% component "calendar" date="2024-12-13" %}
{% fill "date" data="slot_data" %}
<b> {{ slot_data.date }} </b>
{% endfill %}
{% endcomponent %}
{# Italics #}
{% component "calendar" date="2024-12-13" %}
{% fill "date" data="slot_data" %}
<i> {{ slot_data.date }} </i>
{% endfill %}
{% endcomponent %}
```
By using the `date` variable from the slot, we'll render the correct date
each time:
```html
<!-- Default -->
<div class="calendar">
Today's date is <span>2024-12-16</span>
</div>
<!-- Bold -->
<div class="calendar">
Today's date is <b>2024-12-16</b>
</div>
<!-- Italics -->
<div class="calendar">
Today's date is <i>2024-12-16</i>
</div>
```
!!! info
**When to use slots vs variables?**
Generally, slots are more flexible - you can access the slot data, even the original slot content.
Thus, slots behave more like functions that render content based on their context.
On the other hand, variables are static - the variable you pass to a component is what will be used.
Moreover, slots are treated as part of the template - for example the CSS scoping (work in progress)
is applied to the slot content too.

View file

@ -0,0 +1,196 @@
---
title: Components in templates
weight: 3
---
By the end of this section, we want to be able to use our components in Django templates like so:
```htmldjango
{% load component_tags %}
<!DOCTYPE html>
<html>
<head>
<title>My example calendar</title>
</head>
<body>
{% component "calendar" / %}
</body>
<html>
```
### 1. Register component
First, however, we need to register our component class with [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry).
To register a component with a [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry),
we will use the [`@register`](../../../reference/api#django_components.register)
decorator, and give it a name under which the component will be accessible from within the template:
```python title="[project root]/components/calendar/calendar.py"
from django_components import Component, register # <--- new
@register("calendar") # <--- new
class Calendar(Component):
template_name = "calendar.html"
class Media:
js = "calendar.js"
css = "calendar.css"
def get_context_data(self):
return {
"date": "1970-01-01",
}
```
This will register the component to the default registry. Default registry is loaded into the template
by calling `{% load component_tags %}` inside the template.
!!! info
Why do we have to register components?
We want to use our component as a template tag (`{% ... %}`) in Django template.
In Django, template tags are managed by the `Library` instances. Whenever you include `{% load xxx %}`
in your template, you are loading a `Library` instance into your template.
[`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry) acts like a router
and connects the registered components with the associated `Library`.
That way, when you include `{% load component_tags %}` in your template, you are able to "call" components
like `{% component "calendar" / %}`.
`ComponentRegistries` also make it possible to group and share components as standalone packages.
[Learn more here](../advanced/authoring_component_libraries.md).
!!! note
You can create custom [`ComponentRegistry`](../../../reference/api#django_components.ComponentRegistry)
instances, which will use different `Library` instances.
In that case you will have to load different libraries depending on which components you want to use:
Example 1 - Using component defined in the default registry
```htmldjango
{% load component_tags %}
<div>
{% component "calendar" / %}
</div>
```
Example 2 - Using component defined in a custom registry
```htmldjango
{% load my_custom_tags %}
<div>
{% my_component "table" / %}
</div>
```
Note that, because the tag name `component` is use by the default ComponentRegistry,
the custom registry was configured to use the tag `my_component` instead. [Read more here](../advanced/component_registry.md)
### 2. Load and use the component in template
The component is now registered under the name `calendar`. All that remains to do is to load
and render the component inside a template:
```htmldjango
{% load component_tags %} {# Load the default registry #}
<!DOCTYPE html>
<html>
<head>
<title>My example calendar</title>
</head>
<body>
{% component "calendar" / %} {# Render the component #}
</body>
<html>
```
!!! info
Component tags should end with `/` if they do not contain any [Slot fills](../fundamentals/slots.md).
But you can also use `{% endcomponent %}` instead:
```htmldjango
{% component "calendar" %}{% endcomponent %}
```
We defined the Calendar's template as
```htmldjango
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
```
and the variable `date` as `"1970-01-01"`.
Thus, the final output will look something like this:
```htmldjango
<!DOCTYPE html>
<html>
<head>
<title>My example calendar</title>
<style>
.calendar {
width: 200px;
background: pink;
}
.calendar span {
font-weight: bold;
}
</style>
</head>
<body>
<div class="calendar">
Today's date is <span>1970-01-01</span>
</div>
<script>
(function () {
document.querySelector(".calendar").onclick = () => {
alert("Clicked calendar!");
};
})();
</script>
</body>
<html>
```
This makes it possible to organize your front-end around reusable components, instead of relying on template tags
and keeping your CSS and Javascript in the static directory.
!!! info
Remember that you can use
[`{% component_js_dependencies %}`](../../reference/template_tags.md#component_js_dependencies)
and [`{% component_css_dependencies %}`](../../reference/template_tags.md#component_css_dependencies)
to change where the `<script>` and `<style>` tags will be rendered (See [JS and CSS output locations](../../advanced/rendering_js_css/#js-and-css-output-locations)).
!!! info
How does django-components pick up registered components?
Notice that it was enough to add [`@register`](../../../reference/api#django_components.register) to the component.
We didn't need to import the component file anywhere to execute it.
This is because django-components automatically imports all Python files found in the component directories
during an event called [Autodiscovery](../fundamentals/autodiscovery.md).
So with Autodiscovery, it's the same as if you manually imported the component files on the `ready()` hook:
```python
class MyApp(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "myapp"
def ready(self):
import myapp.components.calendar
import myapp.components.table
...
```
You can now render the components! But our component will render the same content now matter where
and how many times we use it. [Let's parametrise some of its state, so that our Calendar component
is configurable from within the template ➡️](./parametrising_components.md)

View file

@ -0,0 +1,225 @@
---
title: Parametrising components
weight: 4
---
So far, our Calendar component will always render the date `1970-01-01`. Let's make it more useful and flexible
by being able to pass in custom date.
What we want is to be able to use the Calendar component within the template like so:
```htmldjango
{% component "calendar" date="2024-12-13" extra_class="text-red" / %}
```
### 1. Understading component inputs
In section [Create your first component](./your_first_component.md), we defined
the [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data) method
that defines what variables will be available within the template:
```python title="[project root]/components/calendar/calendar.py"
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_name = "calendar.html"
...
def get_context_data(self):
return {
"date": "1970-01-01",
}
```
What we didn't say is that [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
actually receives the args and kwargs that were passed to a component.
So if we call a component with a `date` and `extra_class` keywords:
```htmldjango
{% component "calendar" date="2024-12-13" extra_class="text-red" / %}
```
This is the same as calling:
```py
Calendar.get_context_data(date="2024-12-13", extra_class="text-red")
```
And same applies to positional arguments, or mixing args and kwargs, where:
```htmldjango
{% component "calendar" "2024-12-13" extra_class="text-red" / %}
```
is same as
```py
Calendar.get_context_data("2024-12-13", extra_class="text-red")
```
### 2. Define inputs for `get_context_data`
Let's put this to test. We want to pass `date` and `extra_class` kwargs to the component.
And so, we can write the [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
method such that it expects those parameters:
```python title="[project root]/components/calendar/calendar.py"
from datetime import date
from django_components import Component, register
@register("calendar")
class Calendar(Component):
template_name = "calendar.html"
...
def get_context_data(self, date: date, extra_class: str | None = None):
return {
"date": date,
"extra_class": extra_class,
}
```
!!! info
Since [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
is just a regular Python function, type hints annotations work the same way as anywhere else.
!!! warning
Since [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
is just a regular Python function, it will raise TypeError if it receives incorrect parameters.
Since `extra_class` is optional in the function signature, it's optional also in the template.
So both following calls are valid:
```htmldjango
{% component "calendar" "2024-12-13" / %}
{% component "calendar" "2024-12-13" extra_class="text-red" / %}
```
However, `date` is required. Thus we MUST provide it. Same with regular Python functions,
`date` can be set either as positional or keyword argument. But either way it MUST be set:
```htmldjango
{% component "calendar" "2024-12-13" / %}
{% component "calendar" extra_class="text-red" date="2024-12-13" / %}
{% component "calendar" extra_class="text-red" / %}
```
### 3. Process inputs in `get_context_data`
The [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
method is powerful, because it allows us to decouple
component inputs from the template variables. In other words, we can pre-process
the component inputs, and massage them into a shape that's most appropriate for
what the template needs. And it also allows us to pass in static data into the template.
Imagine our component receives data from the database that looks like below
([taken from Django](https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#regroup)).
```py
cities = [
{"name": "Mumbai", "population": "19,000,000", "country": "India"},
{"name": "Calcutta", "population": "15,000,000", "country": "India"},
{"name": "New York", "population": "20,000,000", "country": "USA"},
{"name": "Chicago", "population": "7,000,000", "country": "USA"},
{"name": "Tokyo", "population": "33,000,000", "country": "Japan"},
]
```
We need to group the list items by size into following buckets by population:
- 0-10,000,000
- 10,000,001-20,000,000
- 20,000,001-30,000,000
- +30,000,001
So we want to end up with following data:
```py
cities_by_pop = [
{
"name": "0-10,000,000",
"items": [
{"name": "Chicago", "population": "7,000,000", "country": "USA"},
]
},
{
"name": "10,000,001-20,000,000",
"items": [
{"name": "Calcutta", "population": "15,000,000", "country": "India"},
{"name": "Mumbai", "population": "19,000,000", "country": "India"},
{"name": "New York", "population": "20,000,000", "country": "USA"},
]
},
{
"name": "30,000,001-40,000,000",
"items": [
{"name": "Tokyo", "population": "33,000,000", "country": "Japan"},
]
},
]
```
Without the `get_context_data()` method, we'd have to either:
1. Pre-process the data in Python before passing it to the components.
2. Define a Django filter or template tag to take the data and process it on the spot.
Instead, with `get_context_data()`, we can keep this transformation private to this component,
and keep the rest of the codebase clean.
```py
def group_by_pop(data):
...
@register("population_table")
class PopulationTable(Component):
template_name = "population_table.html"
def get_context_data(self, data):
return {
"data": group_by_pop(data),
}
```
Similarly we can make use of `get_context_data()` to pre-process the date that was given to the component:
```python title="[project root]/components/calendar/calendar.py"
from datetime import date
from django_components import Component, register
# If date is Sat or Sun, shift it to next Mon, so the date is always workweek.
def to_workweek_date(d: date):
...
@register("calendar")
class Calendar(Component):
template_name = "calendar.html"
...
def get_context_data(self, date: date, extra_class: str | None = None):
workweek_date = to_workweek_date(date) # <--- new
return {
"date": workweek_date, # <--- changed
"extra_class": extra_class,
}
```
### 4. Pass inputs to components
Once we're happy with `Calendar.get_contex_data()`, we can update our templates to use
the parametrized version of the component:
```htmldjango
<div>
{% component "calendar" date="2024-12-13" / %}
{% component "calendar" date="1970-01-01" / %}
</div>
```
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)

View file

@ -0,0 +1,157 @@
---
title: Create your first component
weight: 1
---
A component in django-components can be as simple as a Django template and Python code to declare the component:
```py
from django_components import Component
class Calendar(Component):
template = """
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
"""
```
Or a combination of Django template, Python, CSS, and Javascript:
```py
from django_components import Component
class Calendar(Component):
template = """
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
"""
css = """
.calendar {
width: 200px;
background: pink;
}
"""
js = """
document.querySelector(".calendar").onclick = function () {
alert("Clicked calendar!");
};
"""
```
!!! note
With django-components, you can "inline" the HTML, JS and CSS code into the Python class,
as seen above.
You can set up [syntax highlighting](../../guides/setup/syntax_highlight.md),
but autocompletion / intellisense does not yet work.
So, in the example below we define the Django template in a separate file, `calendar.html`,
to allow our IDEs to interpret the file as HTML / Django file.
We'll start by creating a component that defines only a Django template:
### 1. Create project structure
Start by creating empty `calendar.py` and `calendar.html` files:
```
sampleproject/
├── calendarapp/
├── components/ 🆕
│ └── calendar/ 🆕
│ ├── calendar.py 🆕
│ └── calendar.html 🆕
├── sampleproject/
├── manage.py
└── requirements.txt
```
### 2. Write Django template
Inside `calendar.html`, write:
```htmldjango title="[project root]/components/calendar/calendar.html"
<div class="calendar">
Today's date is <span>{{ date }}</span>
</div>
```
In this example we've defined one template variable `date`. You can use any and as many variables as you like. These variables will be
defined in the Python file in [`get_context_data()`](../../../reference/api#django_components.Component.get_context_data)
when creating an instance of this component.
!!! note
The template will be rendered with whatever template backend you've specified in your Django settings file.
Currently django-components supports only the default `"django.template.backends.django.DjangoTemplates"` template backend!
### 3. Create new Component in Python
In `calendar.py`, create a subclass of [Component](../../../reference/api#django_components.Component)
to create a new component.
To link the HTML template with our component, set [`template_name`](../../../reference/api#django_components.Component.template_name)
to the name of the HTML file.
```python title="[project root]/components/calendar/calendar.py"
from django_components import Component
class Calendar(Component):
template_name = "calendar.html"
```
!!! note
The path to the template file can be either:
1. Relative to the component's python file (as seen above),
2. Relative to any of the component directories as defined by
[`COMPONENTS.dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
and/or [`COMPONENTS.app_dirs`](../../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
(e.g. `[your apps]/components` dir and `[project root]/components`)
### 4. Define the template variables
In `calendar.html`, we've used the variable `date`. So we need to define it for the template to work.
This is done using [`Component.get_context_data()`](../../../reference/api#django_components.Component.get_context_data).
It's a function that returns a dictionary. The entries in this dictionary
will become available within the template as variables, e.g. as `{{ date }}`.
```python title="[project root]/components/calendar/calendar.py"
from django_components import Component
class Calendar(Component):
template_name = "calendar.html"
def get_context_data(self):
return {
"date": "1970-01-01",
}
```
Now, when we render the component with [`Component.render()`](../../../reference/api#django_components.Component.render)
method:
```py
Calendar.render()
```
It will output
```html
<div class="calendar">
Today's date is <span>1970-01-01</span>
</div>
```
And voilá!! We've created our first component.
Next, [let's add JS and CSS to this component ➡️](./adding_js_and_css.md).

View file

@ -11,6 +11,8 @@ Please, before opening a new discussion, [check if similar discussion wasn't ope
## Community examples
One of our goals with `django-components` is to make it easy to share components between projects. If you have a set of components that you think would be useful to others, please open a pull request to add them to the list below.
One of our goals with `django-components` is to make it easy to share components between projects
([see how to package components](../concepts/advanced/authoring_component_libraries.md)).
If you have a set of components that you think would be useful to others, please open a pull request to add them to the list below.
- [django-htmx-components](https://github.com/iwanalabs/django-htmx-components): A set of components for use with [htmx](https://htmx.org/). Try out the [live demo](https://dhc.iwanalabs.com/).

View file

@ -5,7 +5,8 @@ weight: 7
## Install locally and run the tests
Start by forking the project by clicking the **Fork button** up in the right corner in the GitHub . This makes a copy of the repository in your own name. Now you can clone this repository locally and start adding features:
Start by forking the project by clicking the **Fork button** up in the right corner in the [GitHub](https://github.com/EmilStenstrom/django-components).
This makes a copy of the repository in your own name. Now you can clone this repository locally and start adding features:
```sh
git clone https://github.com/<your GitHub username>/django-components.git
@ -61,28 +62,30 @@ Use the [sampleproject](https://github.com/EmilStenstrom/django-components/tree/
1. Navigate to [sampleproject](https://github.com/EmilStenstrom/django-components/tree/master/sampleproject/) directory:
```sh
cd sampleproject
```
```sh
cd sampleproject
```
2. Install dependencies from the [requirements.txt](https://github.com/EmilStenstrom/django-components/blob/master/sampleproject/requirements.txt) file:
```sh
pip install -r requirements.txt
```
```sh
pip install -r requirements.txt
```
3. Link to your local version of django-components:
```sh
pip install -e ..
```
```sh
pip install -e ..
```
NOTE: The path (in this case `..`) must point to the directory that has the `setup.py` file.
!!! note
The path to the local version (in this case `..`) must point to the directory that has the `setup.py` file.
4. Start Django server
```sh
python manage.py runserver
```
```sh
python manage.py runserver
```
Once the server is up, it should be available at <http://127.0.0.1:8000>.
@ -99,24 +102,24 @@ When you make changes to this JS code, you also need to compile it:
1. Make sure you are inside `src/django_components_js`:
```sh
cd src/django_components_js
```
```sh
cd src/django_components_js
```
2. Install the JS dependencies
```sh
npm install
```
```sh
npm install
```
3. Compile the JS/TS code:
```sh
python build.py
```
```sh
python build.py
```
The script will combine all JS/TS code into a single `.js` file, minify it,
and copy it to `django_components/static/django_components/django_components.min.js`.
The script will combine all JS/TS code into a single `.js` file, minify it,
and copy it to `django_components/static/django_components/django_components.min.js`.
## Packaging and publishing
@ -139,8 +142,4 @@ twine upload --repository pypi dist/* -u __token__ -p <PyPI_TOKEN>
## Development guides
Deep dive into how django_components' features are implemented.
- [Slot rendering](../devguides/slot_rendering.md)
- [Slots and blocks](../devguides/slots_and_blocks.md)
- [JS and CSS dependency management](../devguides/dependency_mgmt.md)
Head over to [Dev guides](../guides/devguides/dependency_mgmt.md) for a deep dive into how django_components' features are implemented.

View file

@ -9,7 +9,7 @@ weight: 3
pip install django_components
```
2. Load `django_components` into Django by adding it into `INSTALLED_APPS` in settings.py:
2. Load `django_components` into Django by adding it into `INSTALLED_APPS` in in your settings file:
```python
INSTALLED_APPS = [
@ -18,7 +18,7 @@ weight: 3
]
```
3. `BASE_DIR` setting is required. Ensure that it is defined in settings.py:
3. `BASE_DIR` setting is required. Ensure that it is defined:
```python
from pathlib import Path
@ -26,41 +26,52 @@ weight: 3
BASE_DIR = Path(__file__).resolve().parent.parent
```
4. Add / modify [`COMPONENTS.dirs`](#dirs) and / or [`COMPONENTS.app_dirs`](#app_dirs) so django_components knows where to find component HTML, JS and CSS files:
4. Set [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
and/or [`COMPONENTS.app_dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs)
so django_components knows where to find component HTML, JS and CSS files:
```python
from django_components import ComponentsSettings
COMPONENTS = ComponentsSettings(
dirs=[
...,
Path(BASE_DIR) / "components",
],
...,
Path(BASE_DIR) / "components",
],
)
```
If `COMPONENTS.dirs` is omitted, django-components will by default look for a top-level `/components` directory,
If [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
is omitted, django-components will by default look for a top-level `/components` directory,
`{BASE_DIR}/components`.
In addition to `COMPONENTS.dirs`, django_components will also load components from app-level directories, such as `my-app/components/`.
The directories within apps are configured with [`COMPONENTS.app_dirs`](#app_dirs), and the default is `[app]/components`.
In addition to [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs),
django_components will also load components from app-level directories, such as `my-app/components/`.
The directories within apps are configured with
[`COMPONENTS.app_dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs),
and the default is `[app]/components`.
NOTE: The input to `COMPONENTS.dirs` is the same as for `STATICFILES_DIRS`, and the paths must be full paths. [See Django docs](https://docs.djangoproject.com/en/5.0/ref/settings/#staticfiles-dirs).
!!! note
5. Next, to make Django load component HTML files as Django templates, modify `TEMPLATES` section of settings.py as follows:
The input to [`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
is the same as for `STATICFILES_DIRS`, and the paths must be full paths.
[See Django docs](https://docs.djangoproject.com/en/5.0/ref/settings/#staticfiles-dirs).
5. Next, modify `TEMPLATES` section of settings.py as follows:
- _Remove `'APP_DIRS': True,`_
- NOTE: Instead of APP_DIRS, for the same effect, we will use [`django.template.loaders.app_directories.Loader`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.loaders.app_directories.Loader)
- NOTE: Instead of `APP_DIRS: True`, we will use
[`django.template.loaders.app_directories.Loader`](https://docs.djangoproject.com/en/5.1/ref/templates/api/#django.template.loaders.app_directories.Loader),
which has the same effect.
- Add `loaders` to `OPTIONS` list and set it to following value:
This allows Django load component HTML files as Django templates.
```python
TEMPLATES = [
{
...,
'OPTIONS': {
'context_processors': [
...
],
'loaders':[(
'django.template.loaders.cached.Loader', [
# Default Django loader
@ -80,8 +91,9 @@ weight: 3
If you want to use JS or CSS with components, you will need to:
1. Modify `STATICFILES_FINDERS` section of settings.py as follows to be able to serve
the component JS and CSS files as static files:
1. Add `"django_components.finders.ComponentsFileSystemFinder"` to `STATICFILES_FINDERS` in your settings file.
This allows Django to serve component JS and CSS as static files.
```python
STATICFILES_FINDERS = [
@ -93,7 +105,8 @@ If you want to use JS or CSS with components, you will need to:
]
```
2. Add [`ComponentDependencyMiddleware`](#setting-up-componentdependencymiddleware) to `MIDDLEWARE` setting.
2. Add [`ComponentDependencyMiddleware`](../reference/middlewares.md#django_components.dependencies.ComponentDependencyMiddleware)
to `MIDDLEWARE` setting.
The middleware searches the outgoing HTML for all components that were rendered
to generate the HTML, and adds the JS and CSS associated with those components.
@ -105,7 +118,7 @@ If you want to use JS or CSS with components, you will need to:
]
```
Read more in [Rendering JS/CSS dependencies](#rendering-jscss-dependencies).
Read more in [Rendering JS/CSS dependencies](../concepts/advanced/rendering_js_css.md).
3. Add django-component's URL paths to your `urlpatterns`:
@ -119,7 +132,7 @@ If you want to use JS or CSS with components, you will need to:
```
4. _Optional._ If you want to change where the JS and CSS is rendered, use
[`{% component_js_dependencies %}`](../reference/template_tags.md#component_js_dependencies)
[`{% component_js_dependencies %}`](../reference/template_tags.md#component_css_dependencies)
and [`{% component_css_dependencies %}`](../reference/template_tags.md#component_js_dependencies).
By default, the JS `<script>` and CSS `<link>` tags are automatically inserted
@ -148,9 +161,6 @@ TEMPLATES = [
{
...,
'OPTIONS': {
'context_processors': [
...
],
'builtins': [
'django_components.templatetags.component_tags',
]

View file

@ -12,11 +12,18 @@ That said, our prefered way is to keep the files of a component close together b
This means that files containing backend logic, such as Python modules and HTML templates, live in the same directory as static files, e.g. JS and CSS.
From v0.100 onwards, we keep component files (as defined by [`COMPONENTS.dirs`](#dirs) and [`COMPONENTS.app_dirs`](#app_dirs)) separate from the rest of the static
files (defined by `STATICFILES_DIRS`). That way, the Python and HTML files are NOT exposed by the server. Only the static JS, CSS, and [other common formats](#static_files_allowed).
From v0.100 onwards, we keep component files (as defined by
[`COMPONENTS.dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.dirs)
and [`COMPONENTS.app_dirs`](../reference/settings.md#django_components.app_settings.ComponentsSettings.app_dirs))
separate from the rest of the static
files (defined by `STATICFILES_DIRS`). That way, the Python and HTML files are NOT exposed by the server. Only the static JS, CSS, and
[other common formats](../reference/settings.md#django_components.app_settings.ComponentsSettings.static_files_allowed).
> NOTE: If you need to expose different file formats, you can configure these with [`COMPONENTS.static_files_allowed`](#static_files_allowed)
> and [`COMPONENTS.static_files_forbidden`](#static_files_forbidden).
!!! note
If you need to expose different file formats, you can configure these with
[`COMPONENTS.static_files_allowed`](../reference/settings.md#django_components.app_settings.ComponentsSettings.static_files_allowed)
and [`COMPONENTS.static_files_forbidden`](../reference/settings.md#django_components.app_settings.ComponentsSettings.static_files_forbidden).
<!-- # TODO_REMOVE_IN_V1 - Remove mentions of safer_staticfiles in V1 -->
@ -29,7 +36,8 @@ You probably don't want this, as parts of your backend logic will be exposed, po
From _v0.27_ until _v0.100_, django-components shipped with an additional installable app _django_components.**safer_staticfiles**_.
It was 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.
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*.
```python
@ -40,7 +48,11 @@ INSTALLED_APPS = [
]
```
If you are on an older version of django-components, your alternatives are a) passing `--ignore <pattern>` options to the _collecstatic_ CLI command, or b) defining a subclass of StaticFilesConfig.
If you are on an pre-v0.27 version of django-components, your alternatives are:
- a) passing `--ignore <pattern>` options to the _collecstatic_ CLI command,
- b) defining a subclass of StaticFilesConfig.
Both routes are described in the official [docs of the _staticfiles_ app](https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list).
Note that `safer_staticfiles` excludes the `.py` and `.html` files for [collectstatic command](https://docs.djangoproject.com/en/5.0/ref/contrib/staticfiles/#collectstatic):
@ -57,3 +69,5 @@ python manage.py runserver
For a step-by-step guide on deploying production server with static files,
[see the demo project](https://github.com/EmilStenstrom/django-components/tree/master/sampleproject).
See the older versions of the sampleproject for a setup with pre-v0.100 version.

View file

@ -65,7 +65,7 @@ Read on to learn about all the exciting details and configuration possibilities!
## Release notes
Read the [Release Notes](https://EmilStenstrom.github.io/django-components/changelog)
Read the [Release Notes](../release_notes.md)
to see the latest features and fixes.
## Community examples
@ -76,6 +76,6 @@ One of our goals with `django-components` is to make it easy to share components
## Contributing and development
Get involved or sponsor this project - [See here](https://emilstenstrom.github.io/django-components/dev/overview/contributing/)
Get involved or sponsor this project - [See here](./contributing.md)
Running django-components locally for development - [See here](https://emilstenstrom.github.io/django-components/dev/overview/development/)
Running django-components locally for development - [See here](./development.md)