22👍
You’d have to subclass the field to take whatever means of specifying the title you’d like and the widget to display the new attribute.
If you had something like this (note: entirely untested):
from django import forms
from django.utils.html import escape
from django.utils.encoding import force_unicode
class SelectWithTitles(forms.Select):
def __init__(self, *args, **kwargs):
super(SelectWithTitles, self).__init__(*args, **kwargs)
# Ensure the titles dict exists
self.titles = {}
def render_option(self, selected_choices, option_value, option_label):
title_html = (option_label in self.titles) and \
u' title="%s" ' % escape(force_unicode(self.titles[option_label])) or ''
option_value = force_unicode(option_value)
selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
return u'<option value="%s"%s%s>%s</option>' % (
escape(option_value), title_html, selected_html,
conditional_escape(force_unicode(option_label)))
class ChoiceFieldWithTitles(forms.ChoiceField):
widget = SelectWithTitles
def __init__(self, choices=(), *args, **kwargs):
choice_pairs = [(c[0], c[1]) for c in choices]
super(ChoiceFieldWithTitles, self).__init__(choices=choice_pairs, *args, **kwargs)
self.widget.titles = dict([(c[1], c[2]) for c in choices])
…you should be able to do this:
PLANNING_CHOICES_WITH_TITLES = (
('0', 'Every morning', 'bla1'),
('1', 'Every night', 'bla2'),
('2', 'Never', 'bla3'),
)
planning = forms.ChoiceFieldWithTitles(
required=True, choices=PLANNING_CHOICES_WITH_TITLES)
24👍
Here is a general solution to allow attributes in options in a Select widget, SelectMultiple widget and also in Select2 widgets.
It has been tested on Django 2.1 and should work for other versions as well (tell me in comments).
from django.forms.widgets import Select, SelectMultiple
class SelectWOA(Select):
"""
Select With Option Attributes:
subclass of Django's Select widget that allows attributes in options,
like disabled="disabled", title="help text", class="some classes",
style="background: color;"...
Pass a dict instead of a string for its label:
choices = [ ('value_1', 'label_1'),
...
('value_k', {'label': 'label_k', 'foo': 'bar', ...}),
... ]
The option k will be rendered as:
<option value="value_k" foo="bar" ...>label_k</option>
"""
def create_option(self, name, value, label, selected, index,
subindex=None, attrs=None):
if isinstance(label, dict):
opt_attrs = label.copy()
label = opt_attrs.pop('label')
else:
opt_attrs = {}
option_dict = super(SelectWOA, self).create_option(name, value,
label, selected, index, subindex=subindex, attrs=attrs)
for key,val in opt_attrs.items():
option_dict['attrs'][key] = val
return option_dict
Here is an example that you can try in your forms.py:
choices = [('b', 'blue'),
('g', {'label': 'green', 'disabled': 'disabled'}),
('c', {'label': 'cyan',
'title': 'Kind of violet',
'style': 'background: cyan;',
}),
('r', 'red'), ]
colors = forms.ChoiceField(
label="Colors",
choices=choices,
widget=SelectWOA)
The colors
field can be rendered in Django shell to check the result:
(myvenv) $ ./manage.py shell
>>> from myapp.forms import *
>>> choices = ...
>>> colors = ...
>>> colors.widget.render('mycolors','')
'''<select name="mycolors">
<option value="b">blue</option>
<option value="g" disabled="disabled">green</option>
<option value="c" title="Kind of violet" style="background: cyan;">cyan</option>
<option value="r">red</option>
</select>'''
To allow multiple selections, add this:
class SelectMultipleWOA(SelectWOA, SelectMultiple):
"""
SelectMultipleWOA widget works like SelectMultiple, with options attrs.
See SelectWOA.
"""
pass
colors = forms.MultipleChoiceField(
label="Colors",
choices=choices,
widget=SelectMultipleWOA)
It will render <select name="mycolors" multiple>...<\select>
.
You can use SelectWOA and SelectMultipleWOA to extend the Select2 widgets:
from django_select2.forms import Select2Mixin
class Select2MultipleWidgetWOA(Select2Mixin, SelectMultipleWOA):
"""
Select2 drop in widget for multiple select.
Works just like Select2MultipleWidget but with options attrs.
"""
pass
colors = forms.MultipleChoiceField(
label="Colors",
choices=choices,
widget=Select2MultipleWidgetWOA(
attrs={'data-placeholder': 'Any color',
'data-close-on-select': 'false',
'style': 'width:280px; height:36px;',
'title': 'Type a word to filter the menu',}
)
)
It will render something like:
'''<select name="mycolors" data-placeholder="Any color"
class="django-select2" data-minimum-input-length="0" multiple
style="width:280px; height:36px;" data-close-on-select="false"
data-allow-clear="false" title="Type a word to filter the menu">
<option value="b">blue</option>
<option value="g" disabled="disabled">green</option>
<option value="c" title="Kind of violet" style="background: cyan;">cyan</option>
<option value="r">red</option>
</select>'''
- [Django]-Examples of Django and Celery: Periodic Tasks
- [Django]-'RelatedManager' object has no attribute
- [Django]-How to csrf_token protection in jinja2 template engine?
7👍
render_option
has been removed from Django 1.11 onwards. This is what I did to achieve this. A little bit of digging and this seems straightforward. Works with Django 2.0+
class CustomSelect(forms.Select):
def __init__(self, attrs=None, choices=()):
self.custom_attrs = {}
super().__init__(attrs, choices)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
# setting the attributes here for the option
if len(self.custom_attrs) > 0:
if value in self.custom_attrs:
custom_attr = self.custom_attrs[value]
for k, v in custom_attr.items():
option_attrs.update({k: v})
return {
'name': name,
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
}
class MyModelChoiceField(ModelChoiceField):
# custom method to label the option field
def label_from_instance(self, obj):
# since the object is accessible here you can set the extra attributes
if hasattr(obj, 'type'):
self.widget.custom_attrs.update({obj.pk: {'type': obj.type}})
return obj.get_display_name()
The form:
class BookingForm(forms.ModelForm):
customer = MyModelChoiceField(required=True,
queryset=Customer.objects.filter(is_active=True).order_by('name'),
widget=CustomSelect(attrs={'class': 'chosen-select'}))
The output which I needed is as:
<select name="customer" class="chosen-select" required="" id="id_customer">
<option value="" selected="">---------</option>
<option value="242" type="CNT">AEC Transcolutions Private Limited</option>
<option value="243" type="CNT">BBC FREIGHT CARRIER</option>
<option value="244" type="CNT">Blue Dart Express Limited</option>
- [Django]-Could not parse the remainder: '[0]' from 'item[0]' Django
- [Django]-Django: from django.urls import reverse; ImportError: No module named urls
- [Django]-Django – query filter on manytomany is empty
6👍
This is how I solved the problem
#models.py
class Publisher(models.Model):
slug = models.Slugfield()
class Book(forms.ModelForm):
publisher = models.ForeignKey(Publisher)
#forms.py
from django import forms
from django.utils.encoding import force_unicode
from django.utils.html import escape, conditional_escape
class SelectWithData(forms.Select):
def render_option(self, selected_choices, option_value, option_label):
obj_data = {
obj.id: {
data_attr: getattr(obj, data_attr) for data_attr in self.data_attrs
} for obj in self.queryset
}
data_text = u''
for data_attr in self.data_attrs:
data_text += u' data-{}="{}" '.format(
data_attr,
escape(force_unicode(obj_data[option_value][data_attr]))
)
option_value = force_unicode(option_value)
selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
return u'<option value="{}"{}{}>{}</option>'.format(
escape(option_value),
data_text,
selected_html,
conditional_escape(force_unicode(option_label))
)
class ModelChoiceFieldWithData(forms.ModelChoiceField):
widget = SelectWithData
def __init__(self, queryset, **kwargs):
data_attrs = kwargs.pop('data_attrs')
super(ModelChoiceFieldWithData, self).__init__(queryset, **kwargs)
self.widget.queryset = queryset
self.widget.data_attrs = data_attrs
class BookForm(forms.ModelForm):
publisher = ModelChoiceFieldWithData(
queryset=Publisher.objects.all(),
data_attrs=('slug',),
)
#html
<select id="id_publisher" name="publisher" required="required" title="">
<option value="1" data-slug="first" selected="selected">First Publisher</option>
<option value="2" data-slug="second">Second Publisher</option>
</select>
- [Django]-How to call function that takes an argument in a Django template?
- [Django]-How to exclude two conditions in query via Django ORM?
- [Django]-How to configure X-Frame-Options in Django to allow iframe embedding of one view?
3👍
You can’t. Not without too much hackery, at least.
You are probably good using form_utils.BetterForm, an external package rather than django.forms.Form
if you want to have this and more control over the markup generated.
- [Django]-How to make an auto-filled and auto-incrementing field in django admin
- [Django]-Simple way for QuerySet union and subtraction in django?
- [Django]-ProgrammingError: relation "django_session" does not exist error after installing Psycopg2
3👍
I improved a little Nihal Sharma’s answer for Django 2+, it’s still hacky, but prettier:
class CustomSelect(forms.Select):
def __init__(self, attrs=None, choices=()):
self.custom_attrs = {}
super().__init__(attrs, choices)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
# setting the attributes here for the option
if value in self.custom_attrs:
option['attrs'].update({k: v for k, v in self.custom_attrs[value].items()})
return option
class ModelChoiceFieldWithData(ModelChoiceField):
widget = CustomSelect
def __init__(self, *args, **kwargs):
self.additional_data = kwargs.pop('additional_data')
super().__init__(*args, **kwargs)
# custom method to label the option field
def label_from_instance(self, obj):
# since the object is accessible here you can set the extra attributes
self.widget.custom_attrs[obj.pk] = {f'data-{attr}': getattr(obj, attr) for attr in self.additional_data}
return super().label_from_instance(obj)
The form:
class BookingForm(forms.ModelForm):
customer = ModelChoiceFieldWithData(queryset=Customer.objects.filter(is_active=True),
additional_data=('my_field',))
- [Django]-How to use "select_for_update()" to get an object in Django?
- [Django]-How to get logged in user's uid from session in Django?
- [Django]-Django: Arbitrary number of unnamed urls.py parameters
1👍
Since Django 3.1, you have access to a ModelChoiceIteratorValue(value, instance)
in the create_option
method when you are working with ModelForms
.
This means you can render attributes from model instances directly in the widget. The following snippet is taken from the official Django documentation:
from django import forms
class ToppingSelect(forms.Select):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
if value:
option['attrs']['data-price'] = value.instance.price
return option
class PizzaForm(forms.ModelForm):
class Meta:
model = Pizza
fields = ['topping']
widgets = {'topping': ToppingSelect}
- [Django]-Django template convert to string
- [Django]-PyCharm Not Properly Recognizing Requirements – Python, Django
- [Django]-Django Admin nested inline