Commits

Mark Lavin committed 9bb76bb Draft

Refactor single and mult-select widgets into common base classes to reduce repeated code. Include POST compatibility for all of these widgets. See #92

Comments (0)

Files changed (1)

selectable/forms/widgets.py

             return [item_value, value]
         return [None, None]
 
-
-class AutoCompleteSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
-
-    def __init__(self, lookup_class, *args, **kwargs):
-        self.lookup_class = import_lookup_class(lookup_class)
-        self.allow_new = kwargs.pop('allow_new', False)
-        self.limit = kwargs.pop('limit', None)
-        query_params = kwargs.pop('query_params', {})
-        widgets = [
-            AutoCompleteWidget(
-                lookup_class, allow_new=self.allow_new,
-                limit=self.limit, query_params=query_params,
-                attrs=kwargs.get('attrs'),
-            ),
-            forms.HiddenInput(attrs={'data-selectable-type': 'hidden'})
-        ]
-        super(AutoCompleteSelectWidget, self).__init__(widgets, *args, **kwargs)
-
     def get_compatible_postdata(self, data, name):
         """Get postdata built for a normal <select> element.
 
 
         This will return ``None`` if a ``<select>``-compatibile post variable
         is not found.
-
         """
         return data.get(name, None)
 
+
+class _BaseSingleSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+    """
+    Common base class for AutoCompleteSelectWidget and AutoComboboxSelectWidget
+    each which use one widget (primary_widget) to select text and a single
+    hidden input to hold the selected id.
+    """
+
+    primary_widget = None
+
+    def __init__(self, lookup_class, *args, **kwargs):
+        self.lookup_class = import_lookup_class(lookup_class)
+        self.allow_new = kwargs.pop('allow_new', False)
+        self.limit = kwargs.pop('limit', None)
+        query_params = kwargs.pop('query_params', {})
+        widgets = [
+            self.primary_widget(
+                lookup_class, allow_new=self.allow_new,
+                limit=self.limit, query_params=query_params,
+                attrs=kwargs.get('attrs'),
+            ),
+            forms.HiddenInput(attrs={'data-selectable-type': 'hidden'})
+        ]
+        super(_BaseSingleSelectWidget, self).__init__(widgets, *args, **kwargs)
+
     def value_from_datadict(self, data, files, name):
-        value = super(AutoCompleteSelectWidget, self).value_from_datadict(data, files, name)
+        value = super(_BaseSingleSelectWidget, self).value_from_datadict(data, files, name)
         if not value[1]:
             compatible_postdata = self.get_compatible_postdata(data, name)
             if compatible_postdata:
         return value
 
 
+class AutoCompleteSelectWidget(_BaseSingleSelectWidget):
+
+    primary_widget = AutoCompleteWidget
+
+
 class AutoComboboxWidget(AutoCompleteWidget, SelectableMediaMixin):
 
     def build_attrs(self, extra_attrs=None, **kwargs):
         return attrs
 
 
-class AutoComboboxSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+class AutoComboboxSelectWidget(_BaseSingleSelectWidget):
 
-    def __init__(self, lookup_class, *args, **kwargs):
-        self.lookup_class = import_lookup_class(lookup_class)
-        self.allow_new = kwargs.pop('allow_new', False)
-        self.limit = kwargs.pop('limit', None)
-        query_params = kwargs.pop('query_params', {})
-        widgets = [
-            AutoComboboxWidget(
-                lookup_class, allow_new=self.allow_new,
-                limit=self.limit, query_params=query_params,
-                attrs=kwargs.get('attrs'),
-            ),
-            forms.HiddenInput(attrs={'data-selectable-type': 'hidden'})
-        ]
-        super(AutoComboboxSelectWidget, self).__init__(widgets, *args, **kwargs)
-
-    def value_from_datadict(self, data, files, name):
-        value = super(AutoComboboxSelectWidget, self).value_from_datadict(data, files, name)
-        if not self.allow_new:
-            return value[1]
-        return value
+    primary_widget = AutoComboboxWidget
 
 
 class LookupMultipleHiddenInput(forms.MultipleHiddenInput):
         return attrs
 
 
-class AutoCompleteSelectMultipleWidget(SelectableMultiWidget, SelectableMediaMixin):
+class _BaseMultipleSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+    """
+    Common base class for AutoCompleteSelectMultipleWidget and AutoComboboxSelectMultipleWidget
+    each which use one widget (primary_widget) to select text and a multiple
+    hidden inputs to hold the selected ids.
+    """
+
+    primary_widget = None
 
     def __init__(self, lookup_class, *args, **kwargs):
         self.lookup_class = import_lookup_class(lookup_class)
         attrs.update(kwargs.get('attrs', {}))
         query_params = kwargs.pop('query_params', {})
         widgets = [
-            AutoCompleteWidget(
+            self.primary_widget(
                 lookup_class, allow_new=False,
                 limit=self.limit, query_params=query_params, attrs=attrs
             ),
             LookupMultipleHiddenInput(lookup_class)
         ]
-        super(AutoCompleteSelectMultipleWidget, self).__init__(widgets, *args, **kwargs)
+        super(_BaseMultipleSelectWidget, self).__init__(widgets, *args, **kwargs)
 
     def value_from_datadict(self, data, files, name):
-        return self.widgets[1].value_from_datadict(data, files, name + '_1')
+        value = self.widgets[1].value_from_datadict(data, files, name + '_1')
+        if not value:
+            # Fall back to the compatible POST name
+            value = self.get_compatible_postdata(data, name)
+        return value
 
     def render(self, name, value, attrs=None):
         if value and not hasattr(value, '__iter__'):
             value = [value]
         value = ['', value]
-        return super(AutoCompleteSelectMultipleWidget, self).render(name, value, attrs)
+        return super(_BaseMultipleSelectWidget, self).render(name, value, attrs)
 
     def _has_changed(self, initial, data):
         """"
         """
         initial = ['', initial]
         data = ['', data]
-        return super(AutoCompleteSelectMultipleWidget, self)._has_changed(initial, data)
+        return super(_BaseMultipleSelectWidget, self)._has_changed(initial, data)
 
 
+class AutoCompleteSelectMultipleWidget(_BaseMultipleSelectWidget):
 
-class AutoComboboxSelectMultipleWidget(SelectableMultiWidget, SelectableMediaMixin):
+    primary_widget = AutoCompleteWidget
 
-    def __init__(self, lookup_class, *args, **kwargs):
-        self.lookup_class = import_lookup_class(lookup_class)
-        self.limit = kwargs.pop('limit', None)
-        position = kwargs.pop('position', 'bottom')
-        attrs = {
-            'data-selectable-multiple': 'true',
-            'data-selectable-position': position
-        }
-        attrs.update(kwargs.get('attrs', {}))
-        query_params = kwargs.pop('query_params', {})
-        widgets = [
-            AutoComboboxWidget(
-                lookup_class, allow_new=False,
-                limit=self.limit, query_params=query_params, attrs=attrs
-            ),
-            LookupMultipleHiddenInput(lookup_class)
-        ]
-        super(AutoComboboxSelectMultipleWidget, self).__init__(widgets, *args, **kwargs)
 
-    def value_from_datadict(self, data, files, name):
-        return self.widgets[1].value_from_datadict(data, files, name + '_1')
+class AutoComboboxSelectMultipleWidget(_BaseMultipleSelectWidget):
 
-    def render(self, name, value, attrs=None):
-        if value and not hasattr(value, '__iter__'):
-            value = [value]
-        value = ['', value]
-        return super(AutoComboboxSelectMultipleWidget, self).render(name, value, attrs)
-
-    def _has_changed(self, initial, data):
-        """"
-        Decects if the widget was changed. This is removed in 1.6.
-
-        For the multi-select case we only care if the hidden inputs changed.
-        """
-        initial = ['', initial]
-        data = ['', data]
-        return super(AutoComboboxSelectMultipleWidget, self)._has_changed(initial, data)
+    primary_widget = AutoComboboxWidget