Django-components is a package that introduces component-based architecture to Django's server-side rendering. It aims to combine Django's templating system with the modularity seen in modern frontend frameworks.
- ๐งฉ Reusability: Allows creation of self-contained, reusable UI elements.
- ๐ฆ Encapsulation: Each component can include its own HTML, CSS, and JavaScript.
- ๐ Server-side rendering: Components render on the server, improving initial load times and SEO.
- ๐ Django integration: Works within the Django ecosystem, using familiar concepts like template tags.
- โก Asynchronous loading: Components can render independently opening up for integration with JS frameworks like HTMX or AlpineJS.
Potential benefits:
- ๐ Reduced code duplication
- ๐ ๏ธ Improved maintainability through modular design
- ๐ง Easier management of complex UIs
- ๐ค Enhanced collaboration between frontend and backend developers
Django-components can be particularly useful for larger Django projects that require a more structured approach to UI development, without necessitating a shift to a separate frontend framework.
It lets you create "template components", that contains both the template, the Javascript and the CSS needed to generate the front end code you need for a modern app. Use components like this:
{% component "calendar" date="2015-06-19" %}{% endcomponent %}
And this is what gets rendered (plus the CSS and Javascript you've specified):
<div class="calendar-component">Today's date is <span>2015-06-19</span></div>
See the example project or read on to learn about the details!
- Release notes
- Security notes ๐จ
- Installation
- Compatibility
- Create your first component
- Using single-file components
- Use components in templates
- Use components outside of templates
- Use components as views
- Typing and validating components
- Pre-defined components
- Registering components
- Autodiscovery
- Using slots in templates
- Accessing data passed to the component
- Rendering HTML attributes
- Template tag syntax
- Prop drilling and dependency injection (provide / inject)
- Component hooks
- Component context and scope
- Pre-defined template variables
- Customizing component tags with TagFormatter
- Defining HTML/JS/CSS files
- Rendering JS/CSS dependencies
- Available settings
- Running with development server
- Logging and debugging
- Management Command
- Writing and sharing component libraries
- Community examples
- Running django-components project locally
- Development guides
๐จ๐ข Version 0.100
- BREAKING CHANGE:
django_components.safer_staticfiles
app was removed. It is no longer needed.- Installation changes:
- Instead of defining component directories in
STATICFILES_DIRS
, set them toCOMPONENTS.dirs
. - You now must define
STATICFILES_FINDERS
- Instead of defining component directories in
- See here how to migrate your settings.py
- Beside the top-level
/components
directory, you can now define also app-level components dirs, e.g.[app]/components
(SeeCOMPONENTS.app_dirs
). - When you call
as_view()
on a component instance, that instance will be passed toView.as_view()
Version 0.97
- Fixed template caching. You can now also manually create cached templates with
cached_template()
- The previously undocumented
get_template
was made private. - In it's place, there's a new
get_template
, which supersedesget_template_string
(will be removed in v1). The newget_template
is the same asget_template_string
, except it allows to return either a string or a Template instance. - You now must use only one of
template
,get_template
,template_name
, orget_template_name
.
Version 0.96
- Run-time type validation for Python 3.11+ - If the
Component
class is typed, e.g.Component[Args, Kwargs, ...]
, the args, kwargs, slots, and data are validated against the given types. (See Runtime input validation with types) - Render hooks - Set
on_render_before
andon_render_after
methods onComponent
to intercept or modify the template or context before rendering, or the rendered result afterwards. (See Component hooks) component_vars.is_filled
context variable can be accessed from withinon_render_before
andon_render_after
hooks asself.is_filled.my_slot
Version 0.95
- Added support for dynamic components, where the component name is passed as a variable. (See Dynamic components)
- Changed
Component.input
to raiseRuntimeError
if accessed outside of render context. Previously it returnedNone
if unset.
Version 0.94
- django_components now automatically configures Django to support multi-line tags. (See Multi-line tags)
- New setting
reload_on_template_change
. Set this toTrue
to reload the dev server on changes to component template files. (See Reload dev server on component file changes)
Version 0.93
- Spread operator
...dict
inside template tags. (See Spread operator) - Use template tags inside string literals in component inputs. (See Use template tags inside component inputs)
- Dynamic slots, fills and provides - The
name
argument for these can now be a variable, a template expression, or via spread operator - Component library authors can now configure
CONTEXT_BEHAVIOR
andTAG_FORMATTER
settings independently from user settings.
๐จ๐ข Version 0.92
-
BREAKING CHANGE:
Component
class is no longer a subclass ofView
. To configure theView
class, set theComponent.View
nested class. HTTP methods likeget
orpost
can still be defined directly onComponent
class, andComponent.as_view()
internally callsComponent.View.as_view()
. (See Modifying the View class) -
The inputs (args, kwargs, slots, context, ...) that you pass to
Component.render()
can be accessed from withinget_context_data
,get_template
andget_template_name
viaself.input
. (See Accessing data passed to the component) -
Typing:
Component
class supports generics that specify types forComponent.render
(See Adding type hints with Generics)
Version 0.90
-
All tags (
component
,slot
,fill
, ...) now support "self-closing" or "inline" form, where you can omit the closing tag:{# Before #} {% component "button" %}{% endcomponent %} {# After #} {% component "button" / %}
-
All tags now support the "dictionary key" or "aggregate" syntax (
kwarg:key=val
):{% component "button" attrs:class="hidden" %}
-
You can change how the components are written in the template with TagFormatter.
The default is
django_components.component_formatter
:{% component "button" href="..." disabled %} Click me! {% endcomponent %}
While
django_components.shorthand_component_formatter
allows you to write components like so:{% button href="..." disabled %} Click me! {% endbutton %}
๐จ๐ข Version 0.85 Autodiscovery module resolution changed. Following undocumented behavior was removed:
- Previously, autodiscovery also imported any
[app]/components.py
files, and usedSETTINGS_MODULE
to search for component dirs.- To migrate from:
[app]/components.py
- Define each module inCOMPONENTS.libraries
setting, or import each module inside theAppConfig.ready()
hook in respectiveapps.py
files.SETTINGS_MODULE
- Define component dirs usingSTATICFILES_DIRS
- To migrate from:
- Previously, autodiscovery handled relative files in
STATICFILES_DIRS
. To align with Django,STATICFILES_DIRS
now must be full paths (Django docs).
๐จ๐ข Version 0.81 Aligned the render_to_response
method with the (now public) render
method of Component
class. Moreover, slots passed to these can now be rendered also as functions.
- BREAKING CHANGE: The order of arguments to
render_to_response
has changed.
Version 0.80 introduces dependency injection with the {% provide %}
tag and inject()
method.
๐จ๐ข Version 0.79
- BREAKING CHANGE: Default value for the
COMPONENTS.context_behavior
setting was changes from"isolated"
to"django"
. If you did not set this value explicitly before, this may be a breaking change. See the rationale for change here.
๐จ๐ข Version 0.77 CHANGED the syntax for accessing default slot content.
- Previously, the syntax was
{% fill "my_slot" as "alias" %}
and{{ alias.default }}
. - Now, the syntax is
{% fill "my_slot" default="alias" %}
and{{ alias }}
.
Version 0.74 introduces html_attrs
tag and prefix:key=val
construct for passing dicts to components.
๐จ๐ข Version 0.70
{% if_filled "my_slot" %}
tags were replaced with{{ component_vars.is_filled.my_slot }}
variables.- Simplified settings -
slot_context_behavior
andcontext_behavior
were merged. See the documentation for more details.
Version 0.67 CHANGED the default way how context variables are resolved in slots. See the documentation for more details.
๐จ๐ข Version 0.5 CHANGES THE SYNTAX for components. component_block
is now component
, and component
blocks need an ending endcomponent
tag. The new python manage.py upgradecomponent
command can be used to upgrade a directory (use --path argument to point to each dir) of templates that use components to the new syntax automatically.
This change is done to simplify the API in anticipation of a 1.0 release of django_components. After 1.0 we intend to be stricter with big changes like this in point releases.
Version 0.34 adds components as views, which allows you to handle requests and render responses from within a component. See the documentation for more details.
Version 0.28 introduces 'implicit' slot filling and the default
option for slot
tags.
Version 0.27 adds a second installable app: django_components.safer_staticfiles. It provides the same behavior as django.contrib.staticfiles but with extra security guarantees (more info below in Security Notes).
Version 0.26 changes the syntax for {% slot %}
tags. From now on, we separate defining a slot ({% slot %}
) from filling a slot with content ({% fill %}
). This means you will likely need to change a lot of slot tags to fill. We understand this is annoying, but it's the only way we can get support for nested slots that fill in other slots, which is a very nice featuPpre to have access to. Hoping that this will feel worth it!
Version 0.22 starts autoimporting all files inside components subdirectores, to simplify setup. An existing project might start to get AlreadyRegistered-errors because of this. To solve this, either remove your custom loading of components, or set "autodiscover": False in settings.COMPONENTS.
Version 0.17 renames Component.context
and Component.template
to get_context_data
and get_template_name
. The old methods still work, but emit a deprecation warning. This change was done to sync naming with Django's class based views, and make using django-components more familiar to Django users. Component.context
and Component.template
will be removed when version 1.0 is released.
It is strongly recommended to read this section before using django-components in production.
Components can be organized however you prefer. That said, our prefered way is to keep the files of a component close together by bundling them in the same directory.
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
and COMPONENTS.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.
NOTE: If you need to expose different file formats, you can configure these with
COMPONENTS.static_files_allowed
andCOMPONENTS.static_files_forbidden
.
Prior to v0.100, if your were using django.contrib.staticfiles to collect static files, no distinction was made between the different kinds of files.
As a result, your Python code and templates may inadvertently become available on your static file server. You probably don't want this, as parts of your backend logic will be exposed, posing a potential security vulnerability.
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. To use it, add it to INSTALLED_APPS and remove django.contrib.staticfiles.
INSTALLED_APPS = [
# 'django.contrib.staticfiles', # <-- REMOVE
'django_components',
'django_components.safer_staticfiles' # <-- ADD
]
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.
Both routes are described in the official docs of the staticfiles app.
Note that safer_staticfiles
excludes the .py
and .html
files for collectstatic command:
python manage.py collectstatic
but it is ignored on the development server:
python manage.py runserver
For a step-by-step guide on deploying production server with static files, see the demo project.
-
Install
django_components
into your environment:pip install django_components
-
Load
django_components
into Django by adding it intoINSTALLED_APPS
in settings.py:INSTALLED_APPS = [ ..., 'django_components', ]
-
BASE_DIR
setting is required. Ensure that it is defined in settings.py:BASE_DIR = Path(__file__).resolve().parent.parent
-
Add / modify
COMPONENTS.dirs
and / orCOMPONENTS.app_dirs
so django_components knows where to find component HTML, JS and CSS files:COMPONENTS = { "dirs": [ ..., os.path.join(BASE_DIR, "components"), ], }
If
COMPONENTS.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 asmy-app/components/
. The directories within apps are configured withCOMPONENTS.app_dirs
, and the default is[app]/components
.NOTE: The input to
COMPONENTS.dirs
is the same as forSTATICFILES_DIRS
, and the paths must be full paths. See Django docs. -
Next, to make Django load component HTML files as Django templates, 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
- NOTE: Instead of APP_DIRS, for the same effect, we will use
- Add
loaders
toOPTIONS
list and set it to following value:
TEMPLATES = [ { ..., 'OPTIONS': { 'context_processors': [ ... ], 'loaders':[( 'django.template.loaders.cached.Loader', [ # Default Django loader 'django.template.loaders.filesystem.Loader', # Inluding this is the same as APP_DIRS=True 'django.template.loaders.app_directories.Loader', # Components loader 'django_components.template_loader.Loader', ] )], }, }, ]
- Remove
-
Lastly, be able to serve the component JS and CSS files as static files, modify
STATICFILES_FINDERS
section of settings.py as follows:
STATICFILES_FINDERS = [
# Default finders
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
# Django components
"django_components.finders.ComponentsFileSystemFinder",
]
To avoid loading the app in each template using {% load component_tags %}
, you can add the tag as a 'builtin' in settings.py
TEMPLATES = [
{
...,
'OPTIONS': {
'context_processors': [
...
],
'builtins': [
'django_components.templatetags.component_tags',
]
},
},
]
Read on to find out how to build your first component!
Django-components supports all supported combinations versions of Django and Python.
Python version | Django version |
---|---|
3.8 | 4.2 |
3.9 | 4.2 |
3.10 | 4.2, 5.0 |
3.11 | 4.2, 5.0 |
3.12 | 4.2, 5.0 |
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.
/* 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.
/* 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.
{# 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.
# 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.
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:
# In a file called [project root]/components/calendar.py
from django_components import Component, register, types
@register("calendar")
class Calendar(Component):
def get_context_data(self, date):
return {
"date": date,
}
template: types.django_html = """
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
"""
css: types.css = """
.calendar-component { width: 200px; background: pink; }
.calendar-component span { font-weight: bold; }
"""
js: types.js = """
(function(){
if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
}
})()
"""
This makes it easy to create small components without having to create a separate template, CSS, and JS file.
Note, in the above example, that the t.django_html
, t.css
, and t.js
types are used to specify the type of the template, CSS, and JS files, respectively. This is not necessary, but if you're using VSCode with the Python Inline Source Syntax Highlighting extension, it will give you syntax highlighting for the template, CSS, and JS.
If you're a Pycharm user (or any other editor from Jetbrains), you can have coding assistance as well:
from django_components import Component, register
@register("calendar")
class Calendar(Component):
def get_context_data(self, date):
return {
"date": date,
}
# language=HTML
template= """
<div class="calendar-component">Today's date is <span>{{ date }}</span></div>
"""
# language=CSS
css = """
.calendar-component { width: 200px; background: pink; }
.calendar-component span { font-weight: bold; }
"""
# language=JS
js = """
(function(){
if (document.querySelector(".calendar-component")) {
document.querySelector(".calendar-component").onclick = function(){ alert("Clicked calendar!"); };
}
})()
"""
You don't need to use types.django_html
, types.css
, types.js
since Pycharm uses language injections.
You only need to write the comments # language=<lang>
above the variables.
First load the component_tags
tag library, then use the component_[js/css]_dependencies
and component
tags to render the component to the page.
{% 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:
<!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.
New in version 0.81
Components can be rendered outside of Django templates, calling them as regular functions ("React-style").
The component class defines render
and render_to_response
class methods. These methods accept positional args, kwargs, and slots, offering the same flexibility as the {% component %}
tag:
class SimpleComponent(Component):
template = """
{% load component_tags %}
hello: {{ hello }}
foo: {{ foo }}
kwargs: {{ kwargs|safe }}
slot_first: {% slot "first" required / %}
"""
def get_context_data(self, arg1, arg2, **kwargs):
return {
"hello": arg1,
"foo": arg2,
"kwargs": kwargs,
}
rendered = SimpleComponent.render(
args=["world", "bar"],
kwargs={"kw1": "test", "kw2": "ooo"},
slots={"first": "FIRST_SLOT"},
context={"from_context": 98},
)
Renders:
hello: world
foo: bar
kwargs: {'kw1': 'test', 'kw2': 'ooo'}
slot_first: FIRST_SLOT
Both render
and render_to_response
accept the same input:
Component.render(
context: Mapping | django.template.Context | None = None,
args: List[Any] | None = None,
kwargs: Dict[str, Any] | None = None,
slots: Dict[str, str | SafeString | SlotFunc] | None = None,
escape_slots_content: bool = True
) -> str:
-
args
- Positional args for the component. This is the same as calling the component as{% component "my_comp" arg1 arg2 ... %}
-
kwargs
- Keyword args for the component. This is the same as calling the component as{% component "my_comp" key1=val1 key2=val2 ... %}
-
slots
- Component slot fills. This is the same as pasing{% fill %}
tags to the component. Accepts a dictionary of{ slot_name: slot_content }
whereslot_content
can be a string orSlotFunc
. -
escape_slots_content
- Whether the content fromslots
should be escaped.True
by default to prevent XSS attacks. If you disable escaping, you should make sure that any content you pass to the slots is safe, especially if it comes from user input. -
context
- A context (dictionary or Django's Context) within which the component is rendered. The keys on the context can be accessed from within the template.- NOTE: In "isolated" mode, context is NOT accessible, and data MUST be passed via component's args and kwargs.
When rendering components with slots in render
or render_to_response
, you can pass either a string or a function.
The function has following signature:
def render_func(
context: Context,
data: Dict[str, Any],
slot_ref: SlotRef,
) -> str | SafeString:
return nodelist.render(ctx)
context
- Django's Context available to the Slot Node.data
- Data passed to the{% slot %}
tag. See Scoped Slots.slot_ref
- The default slot content. See Accessing original content of slots.- NOTE: The slot is lazily evaluated. To render the slot, convert it to string with
str(slot_ref)
.
- NOTE: The slot is lazily evaluated. To render the slot, convert it to string with
Example:
def footer_slot(ctx, data, slot_ref):
return f"""
SLOT_DATA: {data['abc']}
ORIGINAL: {slot_ref}
"""
MyComponent.render_to_response(
slots={
"footer": footer_slot,
},
)
While render
method returns a plain string, render_to_response
wraps the rendered content in a "Response" class. By default, this is django.http.HttpResponse
.
If you want to use a different Response class in render_to_response
, set the Component.response_class
attribute:
class MyResponse(HttpResponse):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Configure response
self.headers = ...
self.status = ...
class SimpleComponent(Component):
response_class = MyResponse
template: types.django_html = "HELLO"
response = SimpleComponent.render_to_response()
assert isinstance(response, MyResponse)
New in version 0.34
Note: Since 0.92, Component no longer subclasses View. To con