Align radio buttons horizontally in django forms
Asked Answered
H

13

17

HI

I want to align the radio buttons horizontally. By default django forms displays in vertical format.

feature_type  = forms.TypedChoiceField(choices = formfields.FeatureType, widget = forms.RadioSelect)

Is there any special parameter that we can pass for radio buttons alignment?

Thanks in advance

Heady answered 9/5, 2011 at 10:26 Comment(2)
I think this is more of a CSS problem than Django. You can perhaps add a class to the radio buttons and say float:left; in your css for radio button class.Elaelaborate
I have googled it and I have found one link where I could not able to make it work. wikis.utexas.edu/display/~bm6432/…. I will try to add the css and will let you know if it fixes. thanks geekamHeady
B
20

Thats the behavior of the RadioField. If you want it rendered horizontally, create a horizontal renderer, like something as follows:

from django.utils.safestring import mark_safe

class HorizontalRadioRenderer(forms.RadioSelect.renderer):
  def render(self):
    return mark_safe(u'\n'.join([u'%s\n' % w for w in self]))


class ApprovalForm(forms.Form):
    approval = forms.ChoiceField(choices=APPROVAL_CHOICES,
                 initial=0,
                 widget=forms.RadioSelect(renderer=HorizontalRadioRenderer),
                                 )
Bodhisattva answered 9/5, 2011 at 11:39 Comment(6)
Hey lakshman, when I execute the above code its getting error mark_safe is not defined. If you can help this that would be agreat helpHeady
You should import that from Django.templatesBodhisattva
Cool its working fine laxman. Thanks for the help :-) for mark_safe to work I have imported: from django.utils.safestring import mark_safeHeady
Since Django 1.11, this gives me an error at forms.RadioSelect.renderer: "type object 'RadioSelect' has no attribute 'renderer'".Ytterbite
I got the same error "type object 'RadioSelect' has no attribute 'renderer'" .any help pleaseDada
this does not work in newer versions of Django and it isn't the right way to go anyway. Check out the answer below that changes the styling of ul -> li which should be selected as the correct answer.Job
M
6

Another way out is to changing the style of ul->li list to display:inline-block. You can do something like that

 <style>
 ul#youelementId li{
  display: inline-block;
  }
</style>

hope this would help next reader.

Morelli answered 13/12, 2018 at 7:21 Comment(1)
the only way that worked for me in Django 3.1.4Gnu
M
6

I've come up with an alternative solution. If you are using bootstrap to render your forms, you can add the .form-check-inline class to the input and the field will display horizontally. Listed below is code that shows what I'm describing. I hope this helps someone from reinventing the wheel. Thanks for reading. Take care and have a good day.

                feature_type = forms.MultipleChoiceField(
                required=False,
                ...
                widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-inline'})
                )

This image shows a set of radio buttons displayed horizontally.

Microbicide answered 30/4, 2020 at 19:52 Comment(4)
This is a very nice and clean solution.Strata
What's the necessity to turn required off (=False)?Sadi
this is for checkboxes, and the OP asked re: Radio Buttons -- does it work for those as well, or only with CheckboxSelectMultiple?Katowice
I tried this with Django 4.2 and bootstrap 5. I does not work. The check boxes are still aligned vertically.Smokeproof
R
3

I don't know why people suggest so difficult decisions, that don'ts work for my Django 4.1 xD. Solution:

{% for choice in form.feature_type %}
    {{ choice }}
{% endfor %}

# Also you can use:
{{ choice.tag }}
{{ choice.choice_label }}
Raver answered 10/1, 2023 at 3:9 Comment(0)
G
2

In forms.py for your field make widget with attr class="inline"

widgets ='your_name': forms.RadioSelect(choices=your_CHOICES,attrs={"class": "inline"})

In CSS make this class inline-block

.inline div{
display: inline-block;
}
Gstring answered 18/9, 2023 at 3:49 Comment(0)
T
1

On my Django 2.2.6 above solutions did not worked well, so I post my solution after many tries and following the breadcrumbs until the django forms widget templates used.

I had to override 2 templates, and heritage my own widget class and then point it.

The modified default django templates have been:

  • django/forms/templates/django/forms/widgets/input_option.html
  • django/forms/templates/django/forms/widgets/multiple_input.html

Now they are:

PROJECT_NAME/PROJECT_APP/templates/admin/horizontal_option.html

{% if widget.wrap_label %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} class="radio-inline">{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>{% endif %}

PROJECT_NAME/PROJECT_APP/templates/admin/horizontal_radios.html

{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
  <li>{{ group }}
    <ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
    {% include option.template_name with widget=option %}{% endfor %}{% if group %}
  </ul></li>{% endif %}{% endfor %}
</ul>{% endwith %}
  • The first one includes a hardcoded class: class="radio-inline" at labels, which in default Django had nothing
  • The second one the rendering of the set of radios, I removed the extra HTML li tags they were rendered inside the internal ul tag.

Then you need to create your own widget class:

from django.forms import RadioSelect


class HorizontalRadioSelect(RadioSelect):
    template_name = 'admin/horizontal_radios.html'
    option_template_name = 'admin/horizontal_inputs.html'

And finally, in my case, I pointed to it overriding formfield_overrides class attribute in my admin. But you can do this in your models too I think:

    formfield_overrides = {
        models.BooleanField: {'widget': HorizontalRadioSelect(choices=[(True, "Yes"), (False, "No"), (None, "Unknown")],)},
    }
Tsunami answered 2/3, 2020 at 12:41 Comment(1)
This is the correct solution for versions Django 2.0+ Thank you!Table
W
1

Adding this for those using django-crispy-forms and rendering fields with |as_crispy_field. None of the solutions above worked for me, but this did:

Assuming you have something like this in your form:

class MyModelForm(forms.ModelForm):
    class Meta: 
        model = MyModel
        fields = ['your_field_name']

    your_field_name = forms.ChoiceField(widget=forms.RadioSelect(), choices=YOUR_CHOICE_LIST)

...and this in your template:

<div class="row g-3">
    <div class="col-md-12">
        {{ form.your_field_name|as_crispy_field }}
    </div>
</div>

The following CSS does the trick:

<style>
    #div_id_your_field_name div {
        display:inline-flex;
        width: 100%;
    } /* Renders the radio buttons inline and spreads across your div */
    
    #div_id_your_field_name .form-check-label {
        margin-left: 10px;

    } /* puts a bit of space between the radio and label */
    
    #div_id_your_field_name .form-label {
        display: none;

    } /* Optional, if you want to suppress the default label for some reason */
</style>
Wile answered 21/3, 2022 at 16:58 Comment(2)
thanks, this worked (unlike every other solution presented on this page -- I assume because I'm using Crispy Forms)Katowice
This is probably the reason, yes. Glad it helped! (And best wishes on the career transition - you won't regret it!)Wile
R
0

Modified forms.RadioSelect:

class HorizontalRadioSelect(forms.RadioSelect):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        css_style = 'style="display: inline-block; margin-right: 10px;"'

        self.renderer.inner_html = '<li ' + css_style + '>{choice_value}{sub_widgets}</li>'

Working with on the Python 3.4 with the standart admin of the Django 1.10 enter image description here

and the Django-Suit(http://djangosuit.com/) (it use Bootstrap`s 2 styles)

enter image description here

Not tested for the Django-grappelli, yet.

Ratafia answered 16/9, 2016 at 19:19 Comment(2)
This answer isn't directly applicable to the example code provided by the user and, as such, doesn't answer their question.Chilpancingo
On Django 4.2 (and possibly before), this says, 'HorizontalRadioSelect' object has no attribute 'renderer'Yoder
A
0

According to the Django docs 'attrs' is a dictionary containing HTML attributes to be set on the rendered widget.

With that in mind a simple form based solution would be something like the following:

feature_type  = forms.TypedChoiceField(
    choices = formfields.FeatureType,
    widget = forms.RadioSelect(attrs={
        'style': 'display: inline-block'
    })
)
Andersonandert answered 21/8, 2017 at 22:38 Comment(1)
There is no "display" attribute, this should be "style": "display: inline-block" and it will not work this way. display: inline-block will be applied to the radio button instead of <li>Hopple
T
0

There is actually no need to override the widget, template or anything else in admin. At least since 2008 (see forms.css), the easiest way is to pass a class attribute inline: attrs={'class': 'inline'}

In a custom form, it may look like:

field = forms.ChoiceField(choices=(('1', 'one'), ('2', 'two')),
                          widget=forms.RadioSelect(attrs={'class': 'inline'}))

… and it works the very same for checkboxes:

field = forms.MultipleChoiceField(choices=(('1', 'one'), ('2', 'two')),
                                  widget=forms.CheckboxSelectMultiple(attrs={'class': 'inline'}))

And it should be the very same for formfield overrides, either via ModelAdmin formfield_overrides or formfield_for_* functions.

Taimi answered 20/8, 2020 at 14:36 Comment(1)
This does not work for me, applying the attrs dict with class: inline has no effect on either RadioSelect nor CheckboxSelectMultiple.Katowice
C
0

as renerer raise this error for me:

AttributeError: type object 'RadioSelect' has no attribute 'renderer'

I come up with this code:

<form method="post">
    {% csrf_token %}
    {% for question in form %}
        <p>{{ question.label }}: </p>
        {% for choice in question %}
            {{ choice }}
        {% endfor %}
    {% endfor %}
    <br><br>
    <button type="submit">
            submit
    </button>

</form>
Candis answered 17/5, 2021 at 11:27 Comment(0)
O
0

You can use this approach. In my opinion, it is the simplest

class YourAdminForm(forms.ModelForm):

    class Meta:
        model = models.YourModel
        widgets = {
            "your_field": forms.RadioSelect(attrs={"class": "inline"}),
        }

or just define your field something like this

    your_field = forms.ChoiceField(
        choices=(...),
        widget=forms.RadioSelect(attrs={"class": "inline"}),
    )
Overstreet answered 13/1, 2022 at 9:38 Comment(1)
tried this exactly as shown, the radio select buttons remain stacked vertically. the attrs dict is being ignored, and I have yet to figure out why.Katowice
K
0

I had to figure this out for a CMS I manage. Some of the answers here are close, but aren't completely right. You need to provide the class in the attrs parameter, as at least one other response has mentioned, but you should provide inline li. If you provide style, then the style will get applied to the the input element, instead of the li.

I couldn't find anything in their documentation that lists the available CSS classes for their components, so I discovered this by using DevTools in Firefox to look at the available styles, and then finding inline li in their forms.css.

This solution makes it possible change the alignment without needing to mess with templates or making my own CSS file.

from django import forms

from .models import MyModel

MyForm = forms.modelform_factory(
    MyModel,
    widgets={"field_name": forms.RadioSelect(attrs={"class": "inline li"})},
    fields="__all__",
)

To figure out what other classes are available so you can more conveniently modify the the layout of various form elements, run python manage.py collectstatic (make sure STATIC_ROOT is defined), and you can find forms.css under STATIC_ROOT/static/admin/css.

In this particular repo, I have STATIC_ROOT set to ./lib in my local_settings.py:

$ python manage.py collectstatic

1408 static files copied to './lib/static'.

$ ls -1 lib/static/admin/css/
autocomplete.css
base.css
changelists.css
dashboard.css
fonts.css
forms.css
login.css
nav_sidebar.css
responsive.css
responsive_rtl.css
rtl.css
vendor/
widgets.css

Once I actually got it displaying horizontally with a clean solution, I also wanted to hide the blank option from the list of choices, so ended up creating my own CSS file to do that, since the documentation for that was easy to find

/* BASE_DIR/static/css/my_customizations.css */
#id_field_name li:has(input[value='']) {
  visibility: hidden;
  padding: 0;
  margin: 0;
  height: 0;
}
from django import forms

from .models import MyModel

class MyForm(
    forms.modelform_factory(
        MyModel,
        widgets={"field_name": forms.RadioSelect(attrs={"class": "inline li"})},
        fields="__all__",
    )
):
    class Media:
        css = {"all": ("css/my_customizations.css",)}

(Though, as of January 2023, :has is not yet supported by Firefox, but will hopefully be supported sometime this year)

Knotted answered 13/1, 2023 at 4:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.