Opened 3 years ago
Last modified 12 months ago
#34034 assigned New feature
Adding a class on ChoiceWidget subwidgets is excessively difficult
| Reported by: | Claude Paroz | Owned by: | Mariana | 
|---|---|---|---|
| Component: | Forms | Version: | dev | 
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no | 
| Needs tests: | no | Patch needs improvement: | yes | 
| Easy pickings: | no | UI/UX: | no | 
Description
Unless someone shows the magical way, adding a class on a subwidget of ChoiceWidget (and not on the widget itself) is too difficult. 
Maybe adding option_attrs (either as class attribute or as __init__ parameter) could be a solution.
Change History (15)
comment:2 by , 3 years ago
That means that you have to subclass the Django widget. Then for subwidgets, you have to override create_option, not get_context. Then you have to take care not overwriting any existing class content. So in some code of mine, I had to write:
    def create_option(self, *args, attrs=None, **kwargs):
        attrs = attrs.copy() if attrs else {}
        if 'class' in attrs:
            attrs['class'] += ' form-check-input'
        else:
            attrs.update({'class': 'form-check-input'})
        return super().create_option(*args, attrs=attrs, **kwargs)
and note that the class name is hardcoded, so if you need another class for another part of a form, you have to set the class name as a subclass attribute, and create other widget subclasses. And as that can touch RadioSelect, CheckboxSelectMultiple, etc., you'll have to create subclasses for all of them (if used, of course).
That's what I mean by "excessingly difficult". But it's doable, of course.
comment:3 by , 3 years ago
| Summary: | Adding a class on ChoiceWidget subwidgets is excessingly difficult → Adding a class on ChoiceWidget subwidgets is excessively difficult | 
|---|---|
| Triage Stage: | Unreviewed → Accepted | 
Hey Claude, 
Maybe adding
option_attrs…
So I'm looking at this in ChoiceWidget: 
    def create_option(
        self, name, value, label, selected, index, subindex=None, attrs=None
    ):
        ...
        option_attrs = (
            self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
        )
        ...
Do you think pulling that into a property (that could then be overridden) would be sufficient? Maybe the else would pull from an __init__ arg. … 🤔
(Implementation TBD)
Seems a reasonable suggestion anyhow. Thanks.
comment:4 by , 3 years ago
I'm not yet sure about the implementation (didn't find time until now), but basically, we should be able to handle the following use cases:
- attrs for parent widget, not on subwidgets (already OK with option_inherits_attrsset toFalse)
- same attrs for parent widget AND subwidgets (already OK with option_inherits_attrsset toTrue)
- attrs for subwidgets but NOT for parent widget (<-- TODO)
Ideally I would like to be able to do that without subclassing, e.g. by Widget(attrs={...}, option_attrs={...}). I guess experiments will tell if it's doable wrt option attrs inheritance.
comment:5 by , 3 years ago
Seems a reasonable suggestion to me. 
One other thought I had was about using templates. A custom template could be provided to option_template_name or "django/forms/widgets/select_option.html" could be overridden. This may help to avoid Widget(attrs={...}, option_attrs={...}) each time this type of widget is used in a project? 
comment:6 by , 3 years ago
The problem with templates is that it's also very difficult to add classes, as they may conflict with other classes already present in attrs. If there was a mean to complement widget/subwidget classes from attrs with custom classes, then templates could be a viable solution.  
comment:7 by , 2 years ago
| Owner: | changed from to | 
|---|---|
| Status: | new → assigned | 
comment:9 by , 2 years ago
| Needs tests: | set | 
|---|---|
| Patch needs improvement: | set | 
comment:10 by , 2 years ago
| Owner: | removed | 
|---|---|
| Status: | assigned → new | 
comment:11 by , 22 months ago
| Owner: | set to | 
|---|---|
| Status: | new → assigned | 
comment:12 by , 17 months ago
| Needs tests: | unset | 
|---|---|
| Patch needs improvement: | unset | 
comment:13 by , 16 months ago
| Patch needs improvement: | set | 
|---|
comment:15 by , 12 months ago
| Patch needs improvement: | set | 
|---|
Could you add an example of the difficulty?
Looking at the
Selectwidget (source), it defines an attributemultiple;Code highlighting:
So I'm assuming this then renders with
<select multiple>.If that's the level you want to add a class to,
context["widget"]["attrs"]["class"] = "my-select-class"should then render your classes.