First working form generation
This commit is contained in:
parent
a885cb39a0
commit
c86b826e09
6 changed files with 140 additions and 20 deletions
|
@ -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()
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in a new issue