===================
django-xinclude
===================
.. image:: https://img.shields.io/pypi/v/django-xinclude.svg
:target: https://pypi.org/project/django-xinclude/
:alt: PyPI version
Render a template using htmx with the current context.
----
| ``hx-get`` is often used to delegate potentially computationally expensive template fragments to ``htmx``.
Achieving this sometimes requires more views, each of which needs to inherit from mixins that
provide access to the same context.
| ``django-xinclude`` provides a template tag that aims to make this easier by leveraging the cache,
removing the need for the views.
----
Requirements
------------
* Python 3.10 to 3.12 supported.
* Django 4.2 to 5.0 supported.
* `htmx <https://htmx.org/>`__
Setup
-----
* Install from **pip**:
.. code-block:: sh
python -m pip install django-xinclude
* Add it to your installed apps:
.. code-block:: python
INSTALLED_APPS = [
...,
"django_xinclude",
...,
]
* Include the app URLs in your root URLconf:
.. code-block:: python
from django.urls import include, path
urlpatterns = [
...,
path("__xinclude__/", include("django_xinclude.urls")),
]
You can use a different prefix if required.
Usage
-----
Once installed, load the ``xinclude`` library and use the tag passing the template that you want to include:
.. code-block:: html
{% load xinclude %}
{% xinclude "footer.html" %}{% endxinclude %}
Every feature of the regular |include|__ tag is supported, including the use of ``with`` and ``only``.
.. |include| replace:: ``include``
__ https://docs.djangoproject.com/en/dev/ref/templates/builtins/#include
You can use the following htmx-specific arguments:
* ``hx-trigger``: corresponds to the |hx-trigger|__ htmx attribute. Defaults to ``load once``.
.. |hx-trigger| replace:: ``hx-trigger``
__ https://htmx.org/attributes/hx-trigger/
* ``swap-time``: corresponds to the ``swap`` timing of the |hx-swap|__ htmx attribute.
.. |hx-swap| replace:: ``hx-swap``
__ https://htmx.org/attributes/hx-swap/#timing-swap-settle
* ``settle-time``: corresponds to the ``settle`` timing of the |hx-swap|__ htmx attribute.
__ https://htmx.org/attributes/hx-swap/#timing-swap-settle
"Primary nodes" may be passed along to render initial content prior to htmx swapping. For example:
.. code-block:: html
{% xinclude "footer.html" %}
<div>Loading...</div>
{% endxinclude %}
``django-xinclude`` plays well with the excellent `django-template-partials <https://github.com/carltongibson/django-template-partials/>`__
package, to select specific partials on the target template.
Advanced usage
^^^^^^^^^^^^^^
Below is a more complete example making use of the htmx `transition classes <https://htmx.org/examples/animations/#swapping>`__.
Note the ``intersect once`` trigger, which will fire the request once when the element intersects the viewport.
.. code-block:: html
<style>
.htmx-swapping > #loading {
opacity: 0;
transition: opacity 1s ease-out;
}
</style>
{% xinclude "magic.html" with wand="🪄" hx-trigger="intersect once" swap-time="1s" settle-time="1s" %}
<div id="loading">
Loading...
</div>
{% endxinclude %}
``magic.html``:
.. code-block:: html
<style>
#items.htmx-added {
opacity: 1;
animation: appear ease-in 500ms;
}
</style>
<div id="items">
🔮 {{ wand }}
</div>
----
You can preload the ``xinclude`` libary in every template by appending to your ``TEMPLATES`` ``builtins`` setting.
This way you don't need to repeat the ``{% load xinclude %}`` in every template that you need the tag:
.. code-block:: python
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
...,
"OPTIONS": {
"builtins": [
"django_xinclude.templatetags.xinclude",
],
},
},
]
How It Works
------------
``django-xinclude`` first checks if it needs to render the target template synchronously;
see the `Section below <#rendering-synchronously>`__ for cases where this might be useful.
If this is not the case, it stores the current context and the target template to the cache and constructs a url
with a ``fragment_id`` that targets an internal view. It then renders a parent ``div`` element containing all the
necessary htmx attributes. Once the htmx request fires, the view fetches the cache context and template that match
the passed ``fragment_id`` and uses that context to render the template.
Cache
^^^^^
``django-xinclude`` uses either the cache that corresponds to the ``XINCLUDE_CACHE_ALIAS`` setting, if specified,
or ``CACHES["default"]``.
When setting a new cache key, it finds unpickable values and discards them.
If you want to see which keys get discarded, update your ``settings.LOGGERS`` to include ``"django_xinclude"``
with ``"level": "DEBUG"``.
| All official `Django cache backends <https://docs.djangoproject.com/en/5.0/ref/settings/#backend>`__ should work,
under one **important condition**:
| Your cache should be accessible from all your app instances. If you are using multi-processing for your Django application,
or multiple servers clusters, make sure that your ``django-xinclude`` cache is accessible from all the instances,
otherwise your requests will result in 404s.
Authorization
^^^^^^^^^^^^^
The request user is expected to be the one that initially accessed the original view (and added to cache),
or ``AnonymousUser`` in both cases; otherwise ``django-xinclude`` will return 404 for the htmx requests.
If ``request.user`` is not available, for instance when ``django.contrib.auth`` is not in the ``INSTALLED_APPS``,
then ``django-xinclude`` assumes that the end user can access the data.
Rendering synchronously
^^^^^^^^^^^^^^^^^^^^^^^
There are cases where you might want to conditionally render fragments synchronously (i.e. use the regular ``include``).
For example, you could render synchronously for SEO purposes, when robots are crawling your pages, but still make use
of the htmx functionality for regular users. ``django-xinclude`` supports this, it checks for a ``xinclude_sync``
attribute on the request and renders synchronously if that evaluates to ``True``.
So you can add a custom middleware that sets the ``xinclude_sync`` attribute upon your individual conditions.
See also `Configuration <#configuration>`__ below for the ``XINCLUDE_SYNC_REQUEST_ATTR`` setting.
Configuration
-------------
``XINCLUDE_CACHE_ALIAS: str``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The cache alias that ``django-xinclude`` will use, it defaults to ``CACHES["default"]``.
``XINCLUDE_CACHE_TIMEOUT: int``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The number of seconds that contexts will remain in cache. If the setting is not present, Django will
use the default timeout argument of the appropriate backend in the ``CACHES`` setting.
``XINCLUDE_SYNC_REQUEST_ATTR: str``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The request attribute that ``django-xinclude`` will check on to determine if it needs to render synchronously.
It defaults to ``xinclude_sync``.
Running the tests
-----------------
Fork, then clone the repo:
.. code-block:: sh
git clone git@github.com:your-username/django-xinclude.git
Set up a venv:
.. code-block:: sh
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[tests,dev]'
Set up the |pre-commit|__ hooks:
.. |pre-commit| replace:: ``pre-commit``
__ https://pre-commit.com/
.. code-block:: sh
pre-commit install
Then you can run the tests with the |just|__ command runner:
.. |just| replace:: ``just``
__ https://github.com/casey/just
.. code-block:: sh
just test
Or with coverage:
.. code-block:: sh
just coverage
If you don't have ``just`` installed, you can look in the ``justfile`` for the
commands that are run.
|
Complementary packages
----------------------
* |django-htmx|__: Extensions for using Django with htmx.
* |django-template-partials|__: Reusable named inline partials for the Django Template Language.
.. |django-htmx| replace:: ``django-htmx``
__ https://github.com/adamchainz/django-htmx
.. |django-template-partials| replace:: ``django-template-partials``
__ https://github.com/carltongibson/django-template-partials/
Raw data
{
"_id": null,
"home_page": null,
"name": "django-xinclude",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "django, templates, htmx, xhr, cache",
"author": null,
"author_email": "Giannis Terzopoulos <terzo.giannis@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/be/9f/d20896924a67425f53c87102faccefc5babd8193821e51ebe089c075abd4/django_xinclude-0.1.1.tar.gz",
"platform": null,
"description": "===================\ndjango-xinclude\n===================\n\n.. image:: https://img.shields.io/pypi/v/django-xinclude.svg\n :target: https://pypi.org/project/django-xinclude/\n :alt: PyPI version\n\nRender a template using htmx with the current context.\n\n----\n\n| ``hx-get`` is often used to delegate potentially computationally expensive template fragments to ``htmx``.\n Achieving this sometimes requires more views, each of which needs to inherit from mixins that\n provide access to the same context.\n| ``django-xinclude`` provides a template tag that aims to make this easier by leveraging the cache,\n removing the need for the views.\n\n----\n\nRequirements\n------------\n* Python 3.10 to 3.12 supported.\n* Django 4.2 to 5.0 supported.\n* `htmx <https://htmx.org/>`__\n\nSetup\n-----\n\n* Install from **pip**:\n\n.. code-block:: sh\n\n python -m pip install django-xinclude\n\n* Add it to your installed apps:\n\n.. code-block:: python\n\n INSTALLED_APPS = [\n ...,\n \"django_xinclude\",\n ...,\n ]\n\n* Include the app URLs in your root URLconf:\n\n.. code-block:: python\n\n from django.urls import include, path\n\n urlpatterns = [\n ...,\n path(\"__xinclude__/\", include(\"django_xinclude.urls\")),\n ]\n\nYou can use a different prefix if required.\n\n\nUsage\n-----\n\nOnce installed, load the ``xinclude`` library and use the tag passing the template that you want to include:\n\n.. code-block:: html\n\n {% load xinclude %}\n\n {% xinclude \"footer.html\" %}{% endxinclude %}\n\nEvery feature of the regular |include|__ tag is supported, including the use of ``with`` and ``only``.\n\n.. |include| replace:: ``include``\n__ https://docs.djangoproject.com/en/dev/ref/templates/builtins/#include\n\nYou can use the following htmx-specific arguments:\n\n* ``hx-trigger``: corresponds to the |hx-trigger|__ htmx attribute. Defaults to ``load once``.\n\n.. |hx-trigger| replace:: ``hx-trigger``\n__ https://htmx.org/attributes/hx-trigger/\n\n* ``swap-time``: corresponds to the ``swap`` timing of the |hx-swap|__ htmx attribute.\n\n.. |hx-swap| replace:: ``hx-swap``\n__ https://htmx.org/attributes/hx-swap/#timing-swap-settle\n\n* ``settle-time``: corresponds to the ``settle`` timing of the |hx-swap|__ htmx attribute.\n\n__ https://htmx.org/attributes/hx-swap/#timing-swap-settle\n\n\"Primary nodes\" may be passed along to render initial content prior to htmx swapping. For example:\n\n.. code-block:: html\n\n {% xinclude \"footer.html\" %}\n <div>Loading...</div>\n {% endxinclude %}\n\n``django-xinclude`` plays well with the excellent `django-template-partials <https://github.com/carltongibson/django-template-partials/>`__\npackage, to select specific partials on the target template.\n\nAdvanced usage\n^^^^^^^^^^^^^^\nBelow is a more complete example making use of the htmx `transition classes <https://htmx.org/examples/animations/#swapping>`__.\nNote the ``intersect once`` trigger, which will fire the request once when the element intersects the viewport.\n\n.. code-block:: html\n\n <style>\n .htmx-swapping > #loading {\n opacity: 0;\n transition: opacity 1s ease-out;\n }\n </style>\n\n {% xinclude \"magic.html\" with wand=\"\ud83e\ude84\" hx-trigger=\"intersect once\" swap-time=\"1s\" settle-time=\"1s\" %}\n <div id=\"loading\">\n Loading...\n </div>\n {% endxinclude %}\n\n``magic.html``:\n\n.. code-block:: html\n\n <style>\n #items.htmx-added {\n opacity: 1;\n animation: appear ease-in 500ms;\n }\n </style>\n\n <div id=\"items\">\n \ud83d\udd2e {{ wand }}\n </div>\n\n----\n\nYou can preload the ``xinclude`` libary in every template by appending to your ``TEMPLATES`` ``builtins`` setting.\nThis way you don't need to repeat the ``{% load xinclude %}`` in every template that you need the tag:\n\n.. code-block:: python\n\n TEMPLATES = [\n {\n \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n ...,\n \"OPTIONS\": {\n \"builtins\": [\n \"django_xinclude.templatetags.xinclude\",\n ],\n },\n },\n ]\n\n\nHow It Works\n------------\n``django-xinclude`` first checks if it needs to render the target template synchronously;\nsee the `Section below <#rendering-synchronously>`__ for cases where this might be useful.\nIf this is not the case, it stores the current context and the target template to the cache and constructs a url\nwith a ``fragment_id`` that targets an internal view. It then renders a parent ``div`` element containing all the\nnecessary htmx attributes. Once the htmx request fires, the view fetches the cache context and template that match\nthe passed ``fragment_id`` and uses that context to render the template.\n\nCache\n^^^^^\n``django-xinclude`` uses either the cache that corresponds to the ``XINCLUDE_CACHE_ALIAS`` setting, if specified,\nor ``CACHES[\"default\"]``.\nWhen setting a new cache key, it finds unpickable values and discards them.\nIf you want to see which keys get discarded, update your ``settings.LOGGERS`` to include ``\"django_xinclude\"``\nwith ``\"level\": \"DEBUG\"``.\n\n| All official `Django cache backends <https://docs.djangoproject.com/en/5.0/ref/settings/#backend>`__ should work,\n under one **important condition**:\n| Your cache should be accessible from all your app instances. If you are using multi-processing for your Django application,\n or multiple servers clusters, make sure that your ``django-xinclude`` cache is accessible from all the instances,\n otherwise your requests will result in 404s.\n\nAuthorization\n^^^^^^^^^^^^^\nThe request user is expected to be the one that initially accessed the original view (and added to cache),\nor ``AnonymousUser`` in both cases; otherwise ``django-xinclude`` will return 404 for the htmx requests.\nIf ``request.user`` is not available, for instance when ``django.contrib.auth`` is not in the ``INSTALLED_APPS``,\nthen ``django-xinclude`` assumes that the end user can access the data.\n\nRendering synchronously\n^^^^^^^^^^^^^^^^^^^^^^^\nThere are cases where you might want to conditionally render fragments synchronously (i.e. use the regular ``include``).\nFor example, you could render synchronously for SEO purposes, when robots are crawling your pages, but still make use\nof the htmx functionality for regular users. ``django-xinclude`` supports this, it checks for a ``xinclude_sync``\nattribute on the request and renders synchronously if that evaluates to ``True``.\nSo you can add a custom middleware that sets the ``xinclude_sync`` attribute upon your individual conditions.\n\nSee also `Configuration <#configuration>`__ below for the ``XINCLUDE_SYNC_REQUEST_ATTR`` setting.\n\nConfiguration\n-------------\n\n``XINCLUDE_CACHE_ALIAS: str``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe cache alias that ``django-xinclude`` will use, it defaults to ``CACHES[\"default\"]``.\n\n``XINCLUDE_CACHE_TIMEOUT: int``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe number of seconds that contexts will remain in cache. If the setting is not present, Django will\nuse the default timeout argument of the appropriate backend in the ``CACHES`` setting.\n\n``XINCLUDE_SYNC_REQUEST_ATTR: str``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe request attribute that ``django-xinclude`` will check on to determine if it needs to render synchronously.\nIt defaults to ``xinclude_sync``.\n\nRunning the tests\n-----------------\n\nFork, then clone the repo:\n\n.. code-block:: sh\n\n git clone git@github.com:your-username/django-xinclude.git\n\nSet up a venv:\n\n.. code-block:: sh\n\n python -m venv .venv\n source .venv/bin/activate\n python -m pip install -e '.[tests,dev]'\n\nSet up the |pre-commit|__ hooks:\n\n.. |pre-commit| replace:: ``pre-commit``\n__ https://pre-commit.com/\n\n.. code-block:: sh\n\n pre-commit install\n\nThen you can run the tests with the |just|__ command runner:\n\n.. |just| replace:: ``just``\n__ https://github.com/casey/just\n\n.. code-block:: sh\n\n just test\n\nOr with coverage:\n\n.. code-block:: sh\n\n just coverage\n\nIf you don't have ``just`` installed, you can look in the ``justfile`` for the\ncommands that are run.\n\n|\n\nComplementary packages\n----------------------\n* |django-htmx|__: Extensions for using Django with htmx.\n* |django-template-partials|__: Reusable named inline partials for the Django Template Language.\n\n.. |django-htmx| replace:: ``django-htmx``\n__ https://github.com/adamchainz/django-htmx\n\n.. |django-template-partials| replace:: ``django-template-partials``\n__ https://github.com/carltongibson/django-template-partials/\n",
"bugtrack_url": null,
"license": null,
"summary": "django-xinclude",
"version": "0.1.1",
"project_urls": {
"Changelog": "https://github.com/g-nie/django-xinclude/blob/main/CHANGELOG.rst",
"Repository": "https://github.com/g-nie/django-xinclude"
},
"split_keywords": [
"django",
" templates",
" htmx",
" xhr",
" cache"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "72570e24837b868219e98045dff8095076cc6edb5ecdbda6c98eb1bbc61e1c98",
"md5": "07479bb10cad12fdacac1eb5d37c6832",
"sha256": "aa8a6960266c2ec95220879a83bcd34a4f789e96000484dc5511aee13cefe003"
},
"downloads": -1,
"filename": "django_xinclude-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "07479bb10cad12fdacac1eb5d37c6832",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 12721,
"upload_time": "2024-06-19T20:35:24",
"upload_time_iso_8601": "2024-06-19T20:35:24.248747Z",
"url": "https://files.pythonhosted.org/packages/72/57/0e24837b868219e98045dff8095076cc6edb5ecdbda6c98eb1bbc61e1c98/django_xinclude-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "be9fd20896924a67425f53c87102faccefc5babd8193821e51ebe089c075abd4",
"md5": "d02908c11a6ded4827c0acf24d53b456",
"sha256": "41a251de99c2ef41145c2ab826487d18bd09742fc91bd00286e0de008ba2e66d"
},
"downloads": -1,
"filename": "django_xinclude-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "d02908c11a6ded4827c0acf24d53b456",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 18680,
"upload_time": "2024-06-19T20:35:26",
"upload_time_iso_8601": "2024-06-19T20:35:26.216513Z",
"url": "https://files.pythonhosted.org/packages/be/9f/d20896924a67425f53c87102faccefc5babd8193821e51ebe089c075abd4/django_xinclude-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-19 20:35:26",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "g-nie",
"github_project": "django-xinclude",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "django-xinclude"
}