1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
"""
These widgets perform sorting on the choices within Python.
This is useful when sorting is hard to due translated fields, for example:
* the ORM can't sort it.
* the ordering depends on ``gettext()`` output.
* the model ``__unicode__()`` value depends on translated fields.
Use them like any regular form widget::
from django import forms
from parler.widgets import SortedSelect
class MyModelForm(forms.ModelForm):
class Meta:
# Make sure translated choices are sorted.
model = MyModel
widgets = {
'preferred_language': SortedSelect,
'country': SortedSelect,
}
"""
import copy
from django import forms
from django.utils.encoding import force_str
from django.utils.text import slugify
__all__ = (
"SortedSelect",
"SortedSelectMultiple",
"SortedCheckboxSelectMultiple",
)
class SortedChoiceIterator:
def __init__(self, field):
self.field = field
def __iter__(self):
# Delay sorting until the choices are actually read.
if not self.field._sorted:
self.field._choices = self.field.sort_choices(self.field._choices)
self.field._sorted = True
return iter(self.field._choices)
class SortedSelectMixin:
"""
A mixin to have the choices sorted by (translated) title.
"""
def __init__(self, attrs=None, choices=()):
super().__init__(attrs, choices=())
self._choices = choices # super may set self.choices=()
self._sorted = False
@property
def choices(self):
if not self._sorted:
# Delay evaluation as late as possible.
# For the admins with a LocationSelectFormMixin, this property is read too early.
# The RelatedFieldWidgetWrapper() reads the choices on __init__,
# before the LocationSelectFormMixin can limit the queryset to the current place/region.
return SortedChoiceIterator(self)
return self._choices
@choices.setter
def choices(self, choices):
self._choices = choices
self._sorted = False
def sort_choices(self, choices):
# Also sort optgroups
made_copy = False
for i, choice in enumerate(choices):
if isinstance(choice[1], (list, tuple)):
# An optgroup to sort!
if not made_copy:
# Avoid thread safety issues with other languages.
choices = copy.deepcopy(choices)
made_copy = True
choice = choices[i]
choice[1].sort(key=_choicesorter)
if made_copy:
# avoid another copy.
choices.sort(key=_choicesorter)
return choices
else:
return sorted(choices, key=_choicesorter)
def _choicesorter(choice):
if not choice[0]:
# Allow empty choice to be first
return ""
else:
# Lowercase to have case insensitive sorting.
# For country list, normalize the strings (e.g. Österreich / Oman)
return slugify(force_str(choice[1]))
class SortedSelect(SortedSelectMixin, forms.Select):
"""
A select box which sorts it's options.
"""
pass
class SortedSelectMultiple(SortedSelectMixin, forms.SelectMultiple):
"""
A multiple-select box which sorts it's options.
"""
pass
class SortedCheckboxSelectMultiple(SortedSelectMixin, forms.CheckboxSelectMultiple):
"""
A checkbox group with sorted choices.
"""
pass
|