Przypuśćmy, że nasza aplikacja pozwala kupić koszulkę. Dostępne kolory zależą od rozmiaru i trzeba pozwolić je jakoś wybrać. Dwa select
y? Będzie się dało wybrać nieistniejącą kombinację. Łączony select? Nieczytelnie, chyba że…
Poniższe rozwiązanie używa klasy GroupedSelect
pochodzącej z Django Snippets.
from django.forms import ModelChoiceField, ValidationError from django.forms.util import smart_unicode from django.utils.itercompat import groupby from operator import attrgetter class GroupedModelChoiceIterator(object): def __init__(self, field, grouper): self.field = field self.queryset = field.queryset self.grouper = grouper def __iter__(self): if self.field.empty_label is not None: yield (None, ((u'', self.field.empty_label), )) for grouper, items in groupby(self.queryset.all(), attrgetter(self.grouper)): yield (unicode(grouper), tuple((o.pk, unicode(o)) for o in items)) class GroupedModelChoiceField(ModelChoiceField): def __init__(self, field, widget = GroupedSelect, *args, **kwargs): self._field = field super(GroupedModelChoiceField, self).__init__(widget = widget, *args, **kwargs) def _get_choices(self): return GroupedModelChoiceIterator(self, self._field) choices = property(_get_choices, None) def clean(self, value): """ Validates that the input is in self.choices. """ if value in (None, ''): value = u'' value = smart_unicode(value) valid_values = \[\] for group_label, group in self.choices: valid_values += [str(k) for k, v in group] if value not in valid_values: raise ValidationError(u'Select a valid choice. That choice is not one of the available choices.') return super(GroupedModelChoiceField, self).clean(value)
Przykład (zakładając, że color
jest referencją na obiekt, który posiada pole size
):
class SizeAndColorForm(forms.ModelForm):
class Meta:
model = Product
fields = ['color']
color = GroupedModelChoiceField(field = 'size', label = u'size/color')