First working form generation

This commit is contained in:
László Károlyi 2024-09-08 23:55:05 +02:00
parent a885cb39a0
commit c86b826e09
Signed by: karolyi
GPG key ID: 2DCAF25E55735BFE
6 changed files with 140 additions and 20 deletions

View file

@ -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 _:
if not (mappings := _TEMPLATE_MAPPINGS.get(variant)):
raise TemplateNotFound(
name='no-such-variant',
message=(
f'Variant {variant} is not an acceptable variant option.')
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()

View file

@ -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 %}

View file

@ -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 %}

View file

@ -0,0 +1 @@
{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "ktools/widgets/bootstrap5/floating/input.html.jinja" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}

View file

@ -0,0 +1,12 @@
<div class="form-floating mb-3">
<input type="{{ widget.type }}" name="{{ widget.name|e }}"{% if widget.value != None %} value="{{ widget.value|e }}"{% endif %}{% include 'ktools/widgets/bootstrap5/floating/attrs.html.jinja' %}>
<label for="{{ _k_boundfield.auto_id }}">{{ _k_boundfield.label }}</label>
{% if _k_boundfield.errors %}
<div id="{{ form.errorfeedback_prefix }}{{ _k_boundfield.auto_id }}" class="invalid-feedback">
{{ '<br>\n '.join(_k_boundfield.errors|map('e')) }}
</div>
{% endif %}
{%- if _k_boundfield.help_text %}
<small class="text-muted">{{ _k_boundfield.help_text }}</small>
{%- endif %}
</div>

View file

@ -0,0 +1,16 @@
{% set id = widget.attrs.id -%}
<div{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>
{%- for group, options, index in widget.optgroups %}
{%- if group %}
<div><label>{{ group }}</label>
{%- endif %}
{%- for widget in options -%}
<div>
{%- include widget.template_name -%}
</div>
{%- endfor %}
{%- if group %}
</div>
{%- endif %}
{%- endfor %}
</div>