Align radio buttons horizontally in django forms
HI
I want to align the radio buttons horizontall开发者_C百科y. 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
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),
)
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.
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'})
)
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")],)},
}
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>
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
and the Django-Suit(http://djangosuit.com/) (it use Bootstrap`s 2 styles)
Not tested for the Django-grappelli, yet.
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'
})
)
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.
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>
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"}),
)
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 }}
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)
精彩评论