From 2a88a925a15477b556e24d9e8e5ac9eb7340f177 Mon Sep 17 00:00:00 2001 From: Giannis Terzopoulos Date: Wed, 20 Nov 2024 14:37:33 +0100 Subject: [PATCH] update README --- .pre-commit-config.yaml | 1 + README.md | 394 +++++++++++++++++++--------------------- 2 files changed, 191 insertions(+), 204 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e860dc7..57da2ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ repos: - id: end-of-file-fixer exclude_types: [text] - id: trailing-whitespace + exclude_types: [markdown] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.7.4 hooks: diff --git a/README.md b/README.md index 7166e4b..4f3b301 100644 --- a/README.md +++ b/README.md @@ -1,271 +1,257 @@ -=================== -django-xinclude -=================== +# django-xinclude -.. image:: https://img.shields.io/pypi/v/django-xinclude.svg - :target: https://pypi.org/project/django-xinclude/ - :alt: PyPI version +[![PyPI version](https://img.shields.io/pypi/v/django-xinclude.svg)](https://pypi.org/project/django-xinclude/) Render a template using htmx with the current context. > [!IMPORTANT] > This package currently contains minimal features and is a work-in-progress. ----- +------------------------------------------------------------------------ -| ``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. +`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. +## Requirements -Requirements ------------- -* Python 3.10 to 3.12 supported. -* Django 4.2 to 5.0 supported. -* `htmx `__ +- Python 3.10 to 3.12 supported. +- Django 4.2 to 5.0 supported. +- [htmx](https://htmx.org/) -Setup ------ +## Setup -* Install from **pip**: +- Install from **pip**: -.. code-block:: sh +``` sh +python -m pip install django-xinclude +``` - python -m pip install django-xinclude +- Add it to your installed apps: -* Add it to your installed apps: +``` python +INSTALLED_APPS = [ + ..., + "django_xinclude", + ..., +] +``` -.. code-block:: python +- Include the app URLs in your root URLconf: - INSTALLED_APPS = [ - ..., - "django_xinclude", - ..., - ] +``` python +from django.urls import include, path -* Include the app URLs in your root URLconf: - -.. code-block:: python - - from django.urls import include, path - - urlpatterns = [ - ..., - path("__xinclude__/", include("django_xinclude.urls")), - ] +urlpatterns = [ + ..., + path("__xinclude__/", include("django_xinclude.urls")), +] +``` You can use a different prefix if required. +## Usage -Usage ------ - -Once installed, load the ``xinclude`` library and use the tag passing the template that you want to include: - -.. code-block:: html - - {% load xinclude %} +Once installed, load the `xinclude` library and use the tag passing the +template that you want to include: - {% xinclude "footer.html" %}{% endxinclude %} +``` html +{% load xinclude %} -Every feature of the regular |include|__ tag is supported, including the use of ``with`` and ``only``. +{% xinclude "footer.html" %}{% endxinclude %} +``` -.. |include| replace:: ``include`` -__ https://docs.djangoproject.com/en/dev/ref/templates/builtins/#include +Every feature of the regular `include`\_\_ tag is supported, including +the use of `with` and `only`. You can use the following htmx-specific arguments: -* ``hx-trigger``: corresponds to the |hx-trigger|__ htmx attribute. Defaults to ``load once``. +- `hx-trigger`: corresponds to the `hx-trigger`\_\_ htmx attribute. + Defaults to `load once`. +- `swap-time`: corresponds to the `swap` timing of the `hx-swap`\_\_ + htmx attribute. +- `settle-time`: corresponds to the `settle` timing of the + `hx-swap`\_\_ htmx attribute. -.. |hx-trigger| replace:: ``hx-trigger`` -__ https://htmx.org/attributes/hx-trigger/ +\"Primary nodes\" may be passed along to render initial content prior to +htmx swapping. For example: -* ``swap-time``: corresponds to the ``swap`` timing of the |hx-swap|__ htmx attribute. +``` html +{% xinclude "footer.html" %} +
Loading...
+{% endxinclude %} +``` -.. |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 +`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. -"Primary nodes" may be passed along to render initial content prior to htmx swapping. For example: +### Advanced usage -.. code-block:: html +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. - {% xinclude "footer.html" %} -
Loading...
- {% endxinclude %} +``` html + -``django-xinclude`` plays well with the excellent `django-template-partials `__ -package, to select specific partials on the target template. +{% xinclude "magic.html" with wand="🪄" hx-trigger="intersect once" swap-time="1s" settle-time="1s" %} +
+ Loading... +
+{% endxinclude %} +``` + +`magic.html`: + +``` html + + +
+ 🔮 {{ wand }} +
+``` + +------------------------------------------------------------------------ + +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: + +``` python +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + # ..., + "OPTIONS": { + "builtins": [ + "django_xinclude.templatetags.xinclude", + ], + }, + }, +] +``` -Advanced usage -^^^^^^^^^^^^^^ -Below is a more complete example making use of the htmx `transition classes `__. -Note the ``intersect once`` trigger, which will fire the request once when the element intersects the viewport. +## How It Works -.. code-block:: html +`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 - {% xinclude "magic.html" with wand="🪄" hx-trigger="intersect once" swap-time="1s" settle-time="1s" %} -
- Loading... -
- {% endxinclude %} +`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 unpicklable values and discards +them. If you want to see which keys get discarded, update your +`settings.LOGGERS` to include `"django_xinclude"` with +`"level": "DEBUG"`. -``magic.html``: +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. -.. code-block:: html +### 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. -
- 🔮 {{ wand }} -
+### 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. -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: +See also [Configuration](#configuration) below for the +`XINCLUDE_SYNC_REQUEST_ATTR` setting. -.. code-block:: python +## Configuration - 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 unpicklable 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 `__ 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 ------------------ +### `XINCLUDE_CACHE_ALIAS: str` -Fork, then clone the repo: +The cache alias that `django-xinclude` will use, it defaults to +`CACHES["default"]`. -.. code-block:: sh +### `XINCLUDE_CACHE_TIMEOUT: int` - git clone git@github.com:your-username/django-xinclude.git +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. -Set up a venv: +### `XINCLUDE_SYNC_REQUEST_ATTR: str` -.. code-block:: sh +The request attribute that `django-xinclude` will check on to determine +if it needs to render synchronously. It defaults to `xinclude_sync`. - python -m venv .venv - source .venv/bin/activate - python -m pip install -e '.[tests,dev]' +## Running the tests -Set up the |pre-commit|__ hooks: +Fork, then clone the repo: -.. |pre-commit| replace:: ``pre-commit`` -__ https://pre-commit.com/ +``` sh +git clone git@github.com:your-username/django-xinclude.git +``` -.. code-block:: sh +Set up a venv: - pre-commit install +``` sh +python -m venv .venv +source .venv/bin/activate +python -m pip install -e '.[tests,dev]' +``` -Then you can run the tests with the |just|__ command runner: +Set up the `pre-commit`\_\_ hooks: -.. |just| replace:: ``just`` -__ https://github.com/casey/just +``` sh +pre-commit install +``` -.. code-block:: sh +Then you can run the tests with the `just`\_\_ command runner: - just test +``` 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. - -| +``` sh +just coverage +``` -Complementary packages ----------------------- -* |django-htmx|__: Extensions for using Django with htmx. -* |django-template-partials|__: Reusable named inline partials for the Django Template Language. +If you don't have `just` installed, you can look in the `justfile` for +the commands that are run. -.. |django-htmx| replace:: ``django-htmx`` -__ https://github.com/adamchainz/django-htmx +## Complementary packages -.. |django-template-partials| replace:: ``django-template-partials`` -__ https://github.com/carltongibson/django-template-partials/ +- `django-htmx`\_\_: Extensions for using Django with htmx. +- `django-template-partials`\_\_: Reusable named inline partials for + the Django Template Language.