🚨📢 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 to COMPONENTS.dirs. - You now must define STATICFILES_FINDERS - 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 (See COMPONENTS.app_dirs). - When you call as_view() on a component instance, that instance will be passed to View.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 supersedes get_template_string (will be removed in v1). The new get_template is the same as get_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, or get_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 and on_render_after methods on Component 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 within on_render_before and on_render_after hooks as self.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 raise RuntimeError if accessed outside of render context. Previously it returned None 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 to True 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 and TAG_FORMATTER settings independently from user settings.
🚨📢 Version 0.92 - BREAKING CHANGE: Component class is no longer a subclass of View. To configure the View class, set the Component.View nested class. HTTP methods like get or post can still be defined directly on Component class, and Component.as_view() internally calls Component.View.as_view(). (See Modifying the View class)
The inputs (args, kwargs, slots, context, ...) that you pass to Component.render() can be accessed from within get_context_data, get_template and get_template_name via self.input. (See Accessing data passed to the component)
🚨📢 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 to COMPONENTS.dirs. - You now must define STATICFILES_FINDERS - 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 (See COMPONENTS.app_dirs). - When you call as_view() on a component instance, that instance will be passed to View.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 supersedes get_template_string (will be removed in v1). The new get_template is the same as get_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, or get_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 and on_render_after methods on Component 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 within on_render_before and on_render_after hooks as self.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 raise RuntimeError if accessed outside of render context. Previously it returned None 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 to True 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 and TAG_FORMATTER settings independently from user settings.
🚨📢 Version 0.92 - BREAKING CHANGE: Component class is no longer a subclass of View. To configure the View class, set the Component.View nested class. HTTP methods like get or post can still be defined directly on Component class, and Component.as_view() internally calls Component.View.as_view(). (See Modifying the View class)
The inputs (args, kwargs, slots, context, ...) that you pass to Component.render() can be accessed from within get_context_data, get_template and get_template_name via self.input. (See Accessing data passed to the component)
By default, context variables are passed down the template as in regular Django - deeper scopes can access the variables from the outer scopes. So if you have several nested forloops, then inside the deep-most loop you can access variables defined by all previous loops.
With this in mind, the {% component %} tag behaves similarly to {% include %} tag - inside the component tag, you can access all variables that were defined outside of it.
And just like with {% include %}, if you don't want a specific component template to have access to the parent context, add only to the {% component %} tag:
{%component"calendar"date="2015-06-19"only/%}
-
NOTE: {% csrf_token %} tags need access to the top-level context, and they will not function properly if they are rendered in a component that is called with the only modifier.
If you find yourself using the only modifier often, you can set the context_behavior option to "isolated", which automatically applies the only modifier. This is useful if you want to make sure that components don't accidentally access the outer context.
Components can also access the outer context in their context methods like get_context_data by accessing the property self.outer_context.
Customizing component tags with TagFormatter
New in version 0.89
By default, components are rendered using the pair of {% component %} / {% endcomponent %} template tags:
TagFormatter handles following parts of the process above: - Generates start/end tags, given a component. This is what you then call from within your template as {% component %}.
When you {% component %}, tag formatter pre-processes the tag contents, so it can link back the custom template tag to the right component.
To do so, subclass from TagFormatterABC and implement following method: - start_tag - end_tag - parse
classShorthandComponentFormatter(TagFormatterABC):
-# Given a component name, generate the start template tag
-defstart_tag(self,name:str)->str:
-returnname# e.g. 'button'
-
-# Given a component name, generate the start template tag
-defend_tag(self,name:str)->str:
-returnf"end{name}"# e.g. 'endbutton'
-
-# Given a tag, e.g.
-# `{% button href="..." disabled %}`
-#
-# The parser receives:
-# `['button', 'href="..."', 'disabled']`
-defparse(self,tokens:List[str])->TagResult:
-tokens=[*tokens]
-name=tokens.pop(0)
-returnTagResult(
-name,# e.g. 'button'
-tokens# e.g. ['href="..."', 'disabled']
-)
-
That's it! And once your TagFormatter is ready, don't forget to update the settings!
Defining file paths relative to component or static dirs
As seen in the getting started example, to associate HTML/JS/CSS files with a component, you set them as template_name, Media.js and Media.css respectively:
# In a file [project root]/components/calendar/calendar.py
-fromdjango_componentsimportComponent,register
-
-@register("calendar")
-classCalendar(Component):
-template_name="template.html"
-
-classMedia:
-css="style.css"
-js="script.js"
-
In the example above, the files are defined relative to the directory where component.py is.
Alternatively, you can specify the file paths relative to the directories set in COMPONENTS.dirs or COMPONENTS.app_dirs.
Assuming that COMPONENTS.dirs contains path [project root]/components, we can rewrite the example as:
# In a file [project root]/components/calendar/calendar.py
-fromdjango_componentsimportComponent,register
-
-@register("calendar")
-classCalendar(Component):
-template_name="calendar/template.html"
-
-classMedia:
-css="calendar/style.css"
-js="calendar/script.js"
-
NOTE: In case of conflict, the preference goes to resolving the files relative to the component's directory.
The JS and CSS files included in components are not automatically rendered. Instead, use the following tags to specify where to render the dependencies:
component_dependencies - Renders both JS and CSS
component_js_dependencies - Renders only JS
component_css_dependencies - Reneders only CSS
JS files are rendered as <script> tags. CSS files are rendered as <style> tags.
All library settings are handled from a global COMPONENTS variable that is read from settings.py. By default you don't need it set, there are resonable defaults.
Here's overview of all available settings and their defaults:
If you specify all the component locations with the setting above and have a lot of apps, you can (very) slightly speed things up by disabling autodiscovery.
A list of suffixes that define which files within COMPONENTS.dirs and COMPONENTS.app_dirs will NEVER be treated as static files.
If a file is matched against any of the patterns, it will never be considered a static file, even if the file matches a pattern in COMPONENTS.static_files_allowed.
Use this setting together with COMPONENTS.static_files_allowed for a fine control over what files will be exposed.
You can also pass in compiled regexes (re.Pattern) for more advanced patterns.
By default, any HTML and Python are considered NOT static files:
"django" - Default - The default Django template behavior.
Inside the {% fill %} tag, the context variables you can access are a union of:
All the variables that were OUTSIDE the fill tag, including any loops or with tag
Data returned from get_context_data() of the component that wraps the fill tag.
"isolated" - Similar behavior to Vue or React, this is useful if you want to make sure that components don't accidentally access variables defined outside of the component.
Inside the {% fill %} tag, you can ONLY access variables from 2 places:
get_context_data() of the component which defined the template (AKA the "root" component)
Any loops ({% for ... %}) that the {% fill %} tag is part of.
Then if get_context_data() of the component "my_comp" returns following data:
{"my_var":456}
-
Then the template will be rendered as:
123 # my_var
- # cheese
-
Because variables "my_var" and "cheese" are searched only inside RootComponent.get_context_data(). But since "cheese" is not defined there, it's empty.
Notice that the variables defined with the {% with %} tag are ignored inside the {% fill %} tag with the "isolated" mode.
tag_formatter - Change how components are used in templates¤
NOTE: {% csrf_token %} tags need access to the top-level context, and they will not function properly if they are rendered in a component that is called with the only modifier.
If you find yourself using the only modifier often, you can set the context_behavior option to "isolated", which automatically applies the only modifier. This is useful if you want to make sure that components don't accidentally access the outer context.
Components can also access the outer context in their context methods like get_context_data by accessing the property self.outer_context.
First, let's discuss how TagFormatters work, and how components are rendered in django_components.
When you render a component with {% component %} (or your own tag), the following happens: 1. component must be registered as a Django's template tag 2. Django triggers django_components's tag handler for tag component. 3. The tag handler passes the tag contents for pre-processing to TagFormatter.parse().
Customize how paths are rendered into HTML tags with media_class¤
Sometimes you may need to change how all CSS <link> or JS <script> tags are rendered for a given component. You can achieve this by providing your own subclass of Django's Media class to component's media_class attribute.
Normally, the JS and CSS paths are passed to Media class, which decides how the paths are resolved and how the <link> and <script> tags are constructed.
fromdjango.forms.widgetsimportMedia
+fromdjango_componentsimportComponent,register
+
+classMyMedia(Media):
+# Same as original Media.render_js, except
+# the `<script>` tag has also `type="module"`
+defrender_js(self):
+tags=[]
+forpathinself._js:
+ifhasattr(path,"__html__"):
+tag=path.__html__()
+else:
+tag=format_html(
+'<script type="module" src="{}"></script>',
+self.absolute_path(path)
+)
+returntags
+
+@register("calendar")
+classCalendar(Component):
+template_name="calendar/template.html"
+
+classMedia:
+css="calendar/style.css"
+js="calendar/script.js"
+
+# Override the behavior of Media class
+media_class=MyMedia
+
NOTE: The instance of the Media class (or it's subclass) is available under Component.media after the class creation (__new__).
Setting Up ComponentDependencyMiddleware
ComponentDependencyMiddleware is a Django middleware designed to manage and inject CSS/JS dependencies for rendered components dynamically. It ensures that only the necessary stylesheets and scripts are loaded in your HTML responses, based on the components used in your Django templates.
To set it up, add the middleware to your MIDDLEWARE in settings.py:
MIDDLEWARE=[
+# ... other middleware classes ...
+'django_components.middleware.ComponentDependencyMiddleware'
+# ... other middleware classes ...
+]
+
Then, enable RENDER_DEPENDENCIES in setting.py:
COMPONENTS={
+"RENDER_DEPENDENCIES":True,
+# ... other component settings ...
+}
+
libraries - Load component modules
Configure the locations where components are loaded. To do this, add a COMPONENTS variable to you settings.py with a list of python paths to load. This allows you to build a structure of components that are independent from your apps.
By default, the dynamic component is registered under the name "dynamic". In case of a conflict, use this setting to change the name used for the dynamic components.
This is relevant if you are using the project structure as shown in our examples, where HTML, JS, CSS and Python are separate and nested in a directory.
In this case you may notice that when you are running a development server, the server sometimes does not reload when you change comoponent files.
TL;DR is that the server won't reload if it thinks the changed file is in a templates directory, or in a nested sub directory of a templates directory. This is by design.
To make the dev server reload on all component files, set reload_on_template_change to True. This configures Django to watch for component files too.
NOTE: This setting should be enabled only for the dev environment!
Management Command
You can use the built-in management command startcomponent to create a django component. The command accepts the following arguments and options:
name: The name of the component to create. This is a required argument.
--path: The path to the components directory. This is an optional argument. If not provided, the command will use the BASE_DIR setting from your Django settings.
--js: The name of the JavaScript file. This is an optional argument. The default value is script.js.
--css: The name of the CSS file. This is an optional argument. The default value is style.css.
--template: The name of the template file. This is an optional argument. The default value is template.html.
--force: This option allows you to overwrite existing files if they exist. This is an optional argument.
--verbose: This option allows the command to print additional information during component creation. This is an optional argument.
--dry-run: This option allows you to simulate component creation without actually creating any files. This is an optional argument. The default value is False.
This will create a new component named new_component in the my_components directory. The JavaScript, CSS, and template files will be named my_script.js, my_style.css, and my_template.html, respectively.
As you can see above, this is also the place where we configure how our components should behave, using the settings argument. If omitted, default settings are used.
For library authors, we recommend setting CONTEXT_BEHAVIOR to "isolated", so that the state cannot leak into the components, and so the components' behavior is configured solely through the inputs. This means that the components will be more predictable and easier to debug.
Next, you can decide how will others use your components by settingt the TAG_FORMATTER options.
If omitted or set to "django_components.component_formatter", your components will be used like this:
Either way, these settings will be scoped only to your components. So, in the user code, there may be components side-by-side that use different formatters:
{%loadmytags%}
-
-{# Component from your library "mytags", using the "shorthand" formatter #}
-{%tableitems=itemsheaders=header%}
-{%endtable%}
-
-{# User-created components using the default settings #}
-{%component"my_comp"title="Abc..."%}
-{%endcomponent%}
-
Write your components and register them with your instance of ComponentRegistry
There's one difference when you are writing components that are to be shared, and that's that the components must be explicitly registered with your instance of ComponentRegistry from the previous step.
For better user experience, you can also define the types for the args, kwargs, slots and data.
It's also a good idea to have a common prefix for your components, so they can be easily distinguished from users' components. In the example below, we use the prefix my_ / My.
Normally, users rely on autodiscovery and COMPONENTS.dirs to load the component files.
Since you, as the library author, are not in control of the file system, it is recommended to load the components manually.
We recommend doing this in the AppConfig.ready() hook of your apps.py:
fromdjango.appsimportAppConfig
-
-classMyAppConfig(AppConfig):
-default_auto_field="django.db.models.BigAutoField"
-name="myapp"
-
-# This is the code that gets run when user adds myapp
-# to Django's INSTALLED_APPS
-defready(self)->None:
-# Import the components that you want to make available
-# inside the templates.
-frommyapp.templatesimport(
-menu,
-table,
-)
-
Note that you can also include any other startup logic within AppConfig.ready().
django_components uses a bit of JS code to: - Manage the loading of JS and CSS files used by the components - Allow to pass data from Python to JS
When you make changes to this JS code, you also need to compile it:
Make sure you are inside src/django_components_js:
cdsrc/django_components_js
-
Install the JS dependencies
npminstall
-
Compile the JS/TS code:
pythonbuild.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.
A list of regex patterns (as strings) that define which files within COMPONENTS.dirs and COMPONENTS.app_dirs are treated as static files.
If a file is matched against any of the patterns, it's considered a static file. Such files are collected when running collectstatic, and can be accessed under the static file endpoint.
You can also pass in compiled regexes (re.Pattern) for more advanced patterns.
By default, JS, CSS, and common image and font file formats are considered static files:
Each time a template is rendered it is cached to a global in-memory cache (using Python's lru_cache decorator). This speeds up the next render of the component. As the same component is often used many times on the same page, these savings add up.
By default the cache holds 128 component templates in memory, which should be enough for most sites. But if you have a lot of components, or if you are using the template method of a component to render lots of dynamic templates, you can increase this number. To remove the cache limit altogether and cache everything, set template_cache_size to None.
COMPONENTS={
+"template_cache_size":256,
+}
+
If you want add templates to the cache yourself, you can use cached_template():
fromdjango_componentsimportcached_template
+
+cached_template("Variable: {{ variable }}")
+
+# You can optionally specify Template class, and other Template inputs:
+classMyTemplate(Template):
+pass
+
+cached_template(
+"Variable: {{ variable }}",
+template_cls=MyTemplate,
+name=...
+origin=...
+engine=...
+)
+
To create a component with the default settings, you only need to provide the name of the component:
pythonmanage.pystartcomponentmy_component
+
This will create a new component named my_component in the components directory of your Django project. The JavaScript, CSS, and template files will be named script.js, style.css, and template.html, respectively.
Notes on publishing: - The user of the package NEEDS to have installed and configured django_components. - If you use components where the HTML / CSS / JS files are separate, you may need to define MANIFEST.in to include those files with the distribution (see user guide).
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.
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:
\ No newline at end of file
diff --git a/dev/CODE_OF_CONDUCT/index.html b/dev/CODE_OF_CONDUCT/index.html
index b76254b4..14e40d35 100644
--- a/dev/CODE_OF_CONDUCT/index.html
+++ b/dev/CODE_OF_CONDUCT/index.html
@@ -1 +1 @@
- Code of Conduct - Django-Components
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at emil@emilstenstrom.se. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at emil@emilstenstrom.se. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.