From c86b826e0943bbdd8b926418947c7238a47b3abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20K=C3=A1rolyi?= Date: Sun, 8 Sep 2024 23:55:05 +0200 Subject: [PATCH] First working form generation --- src/ktools/django/jinja/templatetags.py | 125 +++++++++++++++--- .../ktools/render-content/attrs.html.jinja | 1 - .../bootstrap5/floating/attrs.html.jinja | 5 + .../floating/input-option.html.jinja | 1 + .../bootstrap5/floating/input.html.jinja | 12 ++ .../floating/multiple-input.html.jinja | 16 +++ 6 files changed, 140 insertions(+), 20 deletions(-) delete mode 100644 src/ktools/django/ktools-jinjatemplates/ktools/render-content/attrs.html.jinja create mode 100644 src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/attrs.html.jinja create mode 100644 src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input-option.html.jinja create mode 100644 src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input.html.jinja create mode 100644 src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/multiple-input.html.jinja diff --git a/src/ktools/django/jinja/templatetags.py b/src/ktools/django/jinja/templatetags.py index 3c8a6a6..1c42d62 100644 --- a/src/ktools/django/jinja/templatetags.py +++ b/src/ktools/django/jinja/templatetags.py @@ -1,11 +1,21 @@ +from __future__ import annotations + +from typing import Any + from django.forms.boundfield import BoundField +from django.forms.renderers import BaseRenderer +from django.forms.widgets import Widget from django.http.request import HttpRequest +from django.template import engines from django.template.loader import render_to_string from django.utils.safestring import SafeString +from django_jinja.backend import Template from jinja2.exceptions import TemplateNotFound from jinja2.runtime import Context from jinja2.utils import pass_context +from ktools.django.jinja.backend import KtoolsJinjaBackend + from ..conf.constants import TEMPLATE_ENGINE_NAME @@ -29,8 +39,86 @@ def ktools_render_messages( ) -_TEXT_INPUT_TYPES = set( - ('TextInput', 'RegionalPhoneNumberWidget', 'EmailInput', 'PasswordInput')) +_TEMPLATE_MAPPINGS = { + 'bootstrap5-floating': { + 'TextInput': { + 'template_name': ( + 'ktools/widgets/bootstrap5/floating/input.html.jinja') + }, + 'EmailInput': { + 'template_name': ( + 'ktools/widgets/bootstrap5/floating/input.html.jinja') + }, + 'RegionalPhoneNumberWidget': { + 'template_name': ( + 'ktools/widgets/bootstrap5/floating/input.html.jinja') + }, + 'PasswordInput': { + 'template_name': ( + 'ktools/widgets/bootstrap5/floating/input.html.jinja') + }, + 'CheckboxSelectMultiple': { + 'template_name': ( + 'ktools/widgets/bootstrap5/floating/multiple-input.html' + '.jinja'), + 'option_template_name': ( + 'ktools/widgets/bootstrap5/floating/input-option.html' + '.jinja') + }, + } +} + + +class _KTemplatesSetting(BaseRenderer): + + def __init__(self, boundfield: _KBoundField): + self._k_boundfield = boundfield + + def render( + self, template_name: str, context: dict[str, Any], + request: HttpRequest | None = None): + """ + Add the `_KBoundField` back to the context so that the template + can use it. + """ + template = self.get_template(template_name) + context['_k_boundfield'] = self._k_boundfield + return template.render(context=context, request=request).strip() + + def get_template(self, template_name: str) -> Template: + 'Find our templates faster.' + my_engine: KtoolsJinjaBackend = engines[TEMPLATE_ENGINE_NAME] + return my_engine.get_template(template_name=template_name) + + +class _KBoundField(BoundField): + + def __init__(self): + 'Placeholder to override parent\'s `__init__`.' + + @staticmethod + def wrap(boundfield: BoundField) -> _KBoundField: + obj = _KBoundField() + obj.__dict__ = boundfield.__dict__ + return obj + + def as_widget( + self, widget: Widget | None = None, + attrs: dict[str, str | bool] | None = None, only_initial: bool = False + ) -> SafeString: + renderer = self.form.renderer + self.form.renderer = _KTemplatesSetting(boundfield=self) + result = super().as_widget( + widget=widget, attrs=attrs, only_initial=only_initial) + self.form.renderer = renderer + return result + + def _adjust_data(self, data: dict[str, str]): + widget = self.field.widget + widget.template_name = data['template_name'] + if option_template_name := data.get('option_template_name'): + if hasattr(widget, 'option_template_name'): + widget.option_template_name = option_template_name @pass_context @@ -39,20 +127,19 @@ def ktools_render_widget( variant: str = 'bootstrap5-floating' ) -> SafeString: 'Rendering a HTML message in various formats from ktools templates.' - match variant: - case 'bootstrap5-floating': - context = context.derived(locals=dict( - boundfield=boundfield, _text_types=_TEXT_INPUT_TYPES - )) - return render_to_string( - template_name=( - 'ktools/render-content/widget-bootstrap5-floating.' - 'html.jinja'), - context=context, using=TEMPLATE_ENGINE_NAME - ) - case _: - raise TemplateNotFound( - name='no-such-variant', - message=( - f'Variant {variant} is not an acceptable variant option.') - ) + if not (mappings := _TEMPLATE_MAPPINGS.get(variant)): + raise TemplateNotFound( + name='no-such-variant', + message=(f'Variant {variant} is not an acceptable variant option.') + ) + widget_class = boundfield.field.widget.__class__.__name__ + if not (template_data := mappings.get(widget_class)): + raise TemplateNotFound( + name='no-such-template-mapping', + message=( + f'No mapping found for widget class {widget_class!r}. Its ' + 'original template path is ' + + repr(boundfield.field.widget.template_name))) + k_boundfield = _KBoundField.wrap(boundfield=boundfield) + k_boundfield._adjust_data(data=template_data) + return k_boundfield.as_widget() diff --git a/src/ktools/django/ktools-jinjatemplates/ktools/render-content/attrs.html.jinja b/src/ktools/django/ktools-jinjatemplates/ktools/render-content/attrs.html.jinja deleted file mode 100644 index 3d2fdd0..0000000 --- a/src/ktools/django/ktools-jinjatemplates/ktools/render-content/attrs.html.jinja +++ /dev/null @@ -1 +0,0 @@ -{% for name, value in widget.attrs.items() %}{% if value is not sameas False %} {{ name }}{% if value is not sameas True %}="{{ value|e }}"{% endif %}{% endif %}{% endfor %} diff --git a/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/attrs.html.jinja b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/attrs.html.jinja new file mode 100644 index 0000000..9b994c2 --- /dev/null +++ b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/attrs.html.jinja @@ -0,0 +1,5 @@ +{% for name, value in widget.attrs.items() %} + {%- if value is not sameas False %} {{ name }} + {%- if value is not sameas True %}="{{ value|e }}"{% endif %} + {%- endif %} +{%- endfor %} diff --git a/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input-option.html.jinja b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input-option.html.jinja new file mode 100644 index 0000000..50845bd --- /dev/null +++ b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input-option.html.jinja @@ -0,0 +1 @@ +{% if widget.wrap_label %}{% endif %}{% include "ktools/widgets/bootstrap5/floating/input.html.jinja" %}{% if widget.wrap_label %} {{ widget.label }}{% endif %} diff --git a/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input.html.jinja b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input.html.jinja new file mode 100644 index 0000000..05be238 --- /dev/null +++ b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/input.html.jinja @@ -0,0 +1,12 @@ +
+ + + {% if _k_boundfield.errors %} +
+ {{ '
\n '.join(_k_boundfield.errors|map('e')) }} +
+ {% endif %} + {%- if _k_boundfield.help_text %} + {{ _k_boundfield.help_text }} + {%- endif %} +
diff --git a/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/multiple-input.html.jinja b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/multiple-input.html.jinja new file mode 100644 index 0000000..3b1ec15 --- /dev/null +++ b/src/ktools/django/ktools-jinjatemplates/ktools/widgets/bootstrap5/floating/multiple-input.html.jinja @@ -0,0 +1,16 @@ +{% set id = widget.attrs.id -%} + + {%- for group, options, index in widget.optgroups %} + {%- if group %} +
+ {%- endif %} + {%- for widget in options -%} +
+ {%- include widget.template_name -%} +
+ {%- endfor %} + {%- if group %} +
+ {%- endif %} +{%- endfor %} +