Как сгруппировать выбор в виджетах Django Select?
Можно ли создать именованные группы выбора в виджетах выбора (выпадающего) Django, когда этот виджет находится в форме, которая автоматически генерируется из модели данных? Могу ли я создать виджет на левом рисунке ниже?
![Two widgets with one grouped]()
Мой первый эксперимент по созданию формы с именованными группами был выполнен вручную, например:
class GroupMenuOrderForm(forms.Form):
food_list = [(1, 'burger'), (2, 'pizza'), (3, 'taco'),]
drink_list = [(4, 'coke'), (5, 'pepsi'), (6, 'root beer'),]
item_list = ( ('food', tuple(food_list)), ('drinks', tuple(drink_list)),)
itemsField = forms.ChoiceField(choices = tuple(item_list))
def GroupMenuOrder(request):
theForm = GroupMenuOrderForm()
return render_to_response(menu_template, {'form': theForm,})
# generates the widget in left-side picture
И он работал красиво, создавая виджет выпадающего слева, с именованными группами.
Затем я создал модель данных, которая имела в основном одну и ту же структуру, и использовала способность Django автоматически генерировать формы из моделей. Он работал - в том смысле, что он показал все варианты. Но варианты не были в названных группах, и до сих пор я не понял, как это сделать - если это даже возможно.
Я нашел несколько вопросов, в которых был ответ: "создайте конструктор формы и выполните любую специальную обработку". Но похоже, что формы .ChoiceField требует кортеж для именованных групп, и я не уверен, как преобразовать кортеж в QuerySet (что, вероятно, невозможно в любом случае, если я правильно понимаю QuerySets как указатель на данные, а не фактические данные).
Код, который я использовал для модели данных:
class ItemDesc(models.Model):
''' one of "food", "drink", where ID of "food" = 1, "drink" = 2 '''
desc = models.CharField(max_length=10, unique=True)
def __unicode__(self):
return self.desc
class MenuItem(models.Model):
''' one of ("burger", 1), ("pizza", 1), ("taco", 1),
("coke", 2), ("pepsi", 2), ("root beer", 2) '''
name = models.CharField(max_length=50, unique=True)
itemDesc = models.ForeignKey(ItemDesc)
def __unicode__(self):
return self.name
class PatronOrder(models.Model):
itemWanted = models.ForeignKey(MenuItem)
class ListMenuOrderForm(forms.ModelForm):
class Meta:
model = PatronOrder
def ListMenuOrder(request):
theForm = ListMenuOrderForm()
return render_to_response(menu_template, {'form': theForm,})
# generates the widget in right-side picture
Я изменил бы модель данных, если понадобится, но это казалось простой структурой. Может быть, слишком много ForeignKeys? Свернуть данные и принять денормализацию?:) Или есть способ конвертировать кортеж в QuerySet или что-то приемлемое для ModelChoiceField?
Обновить: окончательный код на основе meshantz answer
class FooIterator(forms.models.ModelChoiceIterator):
def __init__(self, *args, **kwargs):
super(forms.models.ModelChoiceIterator, self).__init__(*args, **kwargs)
def __iter__(self):
yield ('food', [(1L, u'burger'), (2L, u'pizza')])
yield ('drinks', [(3L, u'coke'), (4L, u'pepsi')])
class ListMenuOrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ListMenuOrderForm, self).__init__(*args, **kwargs)
self.fields['itemWanted'].choices = FooIterator()
class Meta:
model = PatronOrder
(Конечно, фактический код, я кое-что вытащил данные элемента из базы данных.)
Самое большое изменение, которое он связал с djangosnippet, похоже, заключается в том, что Django включил часть кода, позволяя прямо назначать Iterator для выбора, вместо того, чтобы переопределять весь класс. Что очень приятно.
Ответы
Ответ 1
После быстрого просмотра кода ModelChoiceField в django.forms.models, я бы сказал, попробуйте расширить этот класс и переопределить его свойство выбора.
Настройте свойство для возврата пользовательского итератора на основе orignial ModelChoiceIterator в том же модуле (который возвращает кортеж, с которым вы столкнулись) - новый элемент GroupedModelChoiceIterator или некоторые из них.
Мне нужно будет разобраться, как именно написать этот итератор для вас, но я предполагаю, что вам просто нужно заставить его возвращать кортеж кортежей по-своему, а не по умолчанию.
Счастлив ответить на комментарии, так как я уверен, что этот ответ нуждается в небольшой тонкой настройке:)
ИЗМЕНИТЬ НИЖЕ
Просто подумал и проверил djangosnippets, получается, что кто-то сделал именно это:
ModelChoiceField с группами опций. Это год, поэтому для работы с последним django могут потребоваться некоторые настройки, но это именно то, о чем я думал.
Ответ 2
Вот что сработало для меня, не распространяя ни одного из текущих классов django:
У меня есть список типов организмов, учитывая разные королевства как optgroup. В форме OrganismForm вы можете выбрать организм из выпадающего блока выбора, и они упорядочены по optgroup Королевства, а затем все организмы из этого королевства. Например:
[----------------|V]
|Plantae |
| Angiosperm |
| Conifer |
|Animalia |
| Mammal |
| Amphibian |
| Marsupial |
|Fungi |
| Zygomycota |
| Ascomycota |
| Basidiomycota |
| Deuteromycota |
|... |
|________________|
models.py
from django.models import Model
class Kingdom(Model):
name = models.CharField(max_length=16)
class Organism(Model):
kingdom = models.ForeignKeyField(Kingdom)
name = models.CharField(max_length=64)
forms.py:
from models import Kingdom, Organism
class OrganismForm(forms.ModelForm):
organism = forms.ModelChoiceField(
queryset=Organism.objects.all().order_by('kingdom__name', 'name')
)
class Meta:
model = Organism
views.py:
from models import Organism, Kingdom
from forms import OrganismForm
form = OrganismForm()
form.fields['organism'].choices = list()
# Now loop the kingdoms, to get all organisms in each.
for k in Kingdom.objects.all():
# Append the tuple of OptGroup Name, Organism.
form.fields['organism'].choices = form.fields['organism'].choices.append(
(
k.name, # First tuple part is the optgroup name/label
list( # Second tuple part is a list of tuples for each option.
(o.id, o.name) for o in Organism.objects.filter(kingdom=k).order_by('name')
# Each option itself is a tuple of id and name for the label.
)
)
)