A small proposal to form rendering in Django
Title: A small proposal to form rendering in Django
URL Source: https://softwarecrafts.co.uk/100-words/day-308
Markdown Content: It's been a while since my last post, mainly because June saw me start a new client, GSOC really taking off and we have our first real customers in Hamilton Rock with money being deposited and some money being spent, not without its teething issues! Also with a fair amount of social engagements as well!
But anyway, on to today's post. During June I proposed a new feature idea which is an extension to Django's form rendering capabilities to include widgets templates inside a form renderer. Currently, it's only possible to Override widgets at a project level by specifying the template name, or you have to overwrite the widget and then specify your own custom template name and then use that custom widget. It's not possible to customize widgets at the form renderer level.
My idea is to extend the form renderer API. Well actually extends the budget rendering API to check the specified form renderer. It should only be an extension to a private method inside the widget API. Below is the relevant code that I actually got Claude to spit out inside Hamilton Rock today. This is a first iteration which very likely needs some improvement, but it does work!
``` _CAMEL_BOUNDARY = re.compile(r"(?<!^)(?=[A-Z])")
class Widget(metaclass=MediaDefiningClass): ...
def _render(self, template_name, context, renderer=None):
if renderer is None:
renderer = get_default_renderer()
# Walk the widget MRO for a ``<widget>_template_name`` on the renderer.
# A class that defines its own ``template_name`` short-circuits (attribute
# shadowing): a custom widget keeps its template over a base override,
# while an unstyled subclass resolves up to a styled base.
for klass in type(self).__mro__:
slug = _CAMEL_BOUNDARY.sub("_", klass.__name__).lower()
override = getattr(renderer, f"{slug}_template_name", None)
if override is not None:
template_name = override
break
if "template_name" in klass.__dict__:
break
# Same trust posture as Django's own Widget._render.
return mark_safe(renderer.render(template_name, context)) # noqa: S308
```
and here is the current method from the source
def _render(self, template_name, context, renderer=None):
if renderer is None:
renderer = get_default_renderer()
return mark_safe(renderer.render(template_name, context))
There is also some code to allow admin classes to specify a renderer so that your custom renderer doesn't overwrite admin form widgets. In the coming week or so, I will extract this code into a third-party package for others to use.
But what's the real win with this potential change? Honestly I see this unlocking simple packages which unlock custom and complete form rendering packages with Django. Most of these themes would be HTML, CSS & Javascript, with the only python being the declaration of the FormRenderer class like so (pulled from Hamilton Rock):
``` class DrawerFormRenderer(TemplatesSetting): form_template_name = "forms/drawer_form.html#form" field_template_name = "forms/drawer_form.html#field"
text_input_template_name = "forms/drawer_form.html#text_input"
email_input_template_name = "forms/drawer_form.html#text_input"
password_input_template_name = "forms/drawer_form.html#text_input"
date_input_template_name = "forms/drawer_form.html#text_input"
number_input_template_name = "forms/drawer_form.html#number_input"
select_template_name = "forms/drawer_form.html#select"
textarea_template_name = "forms/drawer_form.html#textarea"
checkbox_input_template_name = "forms/drawer_form.html#checkbox"
radio_select_template_name = "forms/drawer_form.html#radio"
```
If you like the look of this, give the feature a thumbs up on the issue and we can hopefully get it progressed. Also do let me know what glaring holes that I have missed in this idea.