input[type=checkbox]
and similar in CSS are not supported by IE so people working with templates and CSS obviously need some other way to select and style specific types of input fields.There are a few ugly ways to do that which violate the DRY principle:
- Encompassing the form element in the template with HTML tag which has a class representing specific type of the input field.
For example:<span class="form_checkbox">
{{ form.is_privacy_policy_confirmed }}
</span> - Defining the specific CSS class for each form field widget in the form.
For example:class FormExample(forms.Form):
is_privacy_policy_confirmed = forms.BooleanField(
required=True,
widget=CheckboxInput(attrs={'class': 'form_checkbox'}),
) - Extending all Fields and all Widgets which use HTML input fields and using the extended versions instead of the originals.
I don't like any of them, because they force me or the template formatters to repeat ourselves and make plenty of replacements in our existing forms.
Although "most sane developers consider it a bad idea", I see the Guerrilla patching of the Widget's render method as the nicest solution to solve this problem.
Guerrilla patch is the modification of the runtime code in dynamic languages without changing the original source code.
The method
render
of the Widget
class draws the input field in HTML. As it takes a parameter attrs
for additional input field attributes, my idea was to create a decorator which modifies the incoming parameters and adds a CSS class "form_TYPE"
, where TYPE
is the input field type.With a little help by Dalius Dobravolskas, I succeeded to code a decorator having an optional parameter which defines the CSS class name for the field. If the class name is not defined, the attribute
input_type
of the Widget class is used for forming the CSS class name (N.B. not all widgets have this attribute).from django.newforms.widgets import Input, CheckboxInput, RadioSelect, CheckboxSelectMultiple
### adding class="form_*" to all html input fields ###
def add_css_class(css_class=""):
def modify_input_class(function):
_css_class = css_class
def new_function(*args, **kwargs):
arg_names = function.func_code.co_varnames
new_kwargs = dict(zip(arg_names, args))
new_kwargs.update(kwargs)
css_class = _css_class or "form_%s" % getattr(
new_kwargs['self'],
"input_type",
"undefined",
)
self = new_kwargs.pop("self")
attrs = getattr(self, "attrs", None) or {}
if "class" in attrs:
css_classes = attrs["class"].split()
if css_class not in css_classes:
css_classes.append(css_class)
attrs["class"] = " ".join(css_classes)
else:
attrs["class"] = css_class
self.attrs = attrs
return function(self, **new_kwargs)
return new_function
return modify_input_class
Input.render = add_css_class()(Input.render)
CheckboxInput.render = add_css_class("form_checkbox")(CheckboxInput.render)
RadioSelect.render = add_css_class("form_radio")(RadioSelect.render)
CheckboxSelectMultiple.render = add_css_class("form_checkbox")(CheckboxSelectMultiple.render)
To use this code, just place it in some
models.py
file in your project.The strange part here was that the variable
css_class
isn't recognized by the sub-child function new_function
directly although the scope of the variable css_class
should let it be accessed there. Anyway, the value got easily accessible when I reassigned it to another variable like _css_class
in the child function modify_input_class
.attrs
argument from the decorated function as it was not clear whether it would be passed as a positional or as a named argument. The first three lines of the function new_function
collects all the incoming arguments to a dictionary new_kwargs
. They can be modified and then passed to the original function to decorate.Ups. I am late to the studio. So see you next time!
@Archatas you should change your font colors
ReplyDelete