File: widgets.py

package info (click to toggle)
python-django-parler 2.3-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,032 kB
  • sloc: python: 4,293; makefile: 164; sh: 6
file content (128 lines) | stat: -rw-r--r-- 3,569 bytes parent folder | download | duplicates (2)
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