Выбор поля формы Django, добавление атрибута
PLANNING_CHOICES = (
('0',u'Every morning'),
('1',u'Every night'),
('2',u'Never'),
)
planning = forms.ChoiceField(
required=True,
choices = PLANNING_CHOICES,
)
Имея такое поле формы с именем планирования, мне нужно добавить атрибут заголовка к вариантам и в конце визуализирует как это:
<select>
<option value="0" title="bla1">Every morning</option>
<option value="1" title="bla2">Every night</option>
<option value="2" title="bla3">Never</option>
</select>
Как это может быть достигнуто?
7 ответов
Вам нужно было бы создать подкласс для поля, чтобы использовать любые средства для указания заголовка и виджета для отображения нового атрибута.
Если у вас было что-то вроде этого (примечание: полностью не проверено):
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])
... вы должны быть в состоянии сделать это:
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)
Вот общее решение, позволяющее разрешить атрибуты в параметрах виджета Select, виджета SelectMultiple, а также виджета Select2.
Он был протестирован на Django 2.1 и должен работать и для других версий (расскажите в комментариях).
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
Вот пример, который вы можете попробовать в своем 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)
В colors
поле можно отобразить в оболочке Django, чтобы проверить результат:
(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>'''
Чтобы разрешить множественный выбор, добавьте это:
class SelectMultipleWOA(SelectWOA, SelectMultiple):
"""
SelectMultipleWOA widget works like SelectMultiple, with options attrs.
See SelectWOA.
"""
pass
colors = forms.MultipleChoiceField(
label="Colors",
choices=choices,
widget=SelectMultipleWOA)
Он будет оказывать <select name="mycolors" multiple>...<\select>
.
Вы можете использовать SelectWOA и SelectMultipleWOA для расширения виджетов Select2:
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',}
)
)
Это будет примерно так:
'''<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>'''
render_option
был удален из Django 1.11 и далее. Это то, что я сделал, чтобы добиться этого. Немного копать, и это кажется простым. Работает с 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()
Форма:
class BookingForm(forms.ModelForm):
customer = MyModelChoiceField(required=True,
queryset=Customer.objects.filter(is_active=True).order_by('name'),
widget=CustomSelect(attrs={'class': 'chosen-select'}))
Вывод, который мне нужен был как:
<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>
Вот так я решил проблему
#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>
Ты не можешь По крайней мере, не слишком много взлома.
Вы, вероятно, хорошо используете form_utils.BetterForm, внешний пакет, а не django.forms.Form
если вы хотите, чтобы это и больше контроля над сгенерированной разметкой.
Я улучшил ответ Нихала Шармы для Django 2+, он все еще хакерский, но красивее:
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)
Форма:
class BookingForm(forms.ModelForm):
customer = ModelChoiceFieldWithData(queryset=Customer.objects.filter(is_active=True),
additional_data=('my_field',))
Начиная с Django 3.1, у вас есть доступ кModelChoiceIteratorValue(value, instance)
вcreate_option
метод, когда вы работаете сModelForms
.
Это означает, что вы можете визуализировать атрибуты из экземпляров модели непосредственно в виджете. Следующий фрагмент взят из официальной документации Django :
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}