Source code for autocomplete_light.widgets
from __future__ import unicode_literals
from django import forms
from django.template.loader import render_to_string
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
"""
The provided widgets are meant to rely on an Autocomplete class.
- :py:class:`ChoiceWidget` :py:class:`django:django.forms.Select`
ChoiceWidget is intended to work as a replacement for django's Select widget,
and MultipleChoiceWidget for django's SelectMultiple,
Constructing a widget needs an Autocomplete class or registered autocomplete
name.
The choice autocomplete widget renders from autocomplete_light/widget.html
template.
"""
try:
from django.forms.utils import flatatt
except ImportError:
from django.forms.util import flatatt
__all__ = ['WidgetBase', 'ChoiceWidget', 'MultipleChoiceWidget', 'TextWidget']
[docs]class WidgetBase(object):
"""
Base widget for autocompletes.
.. py:attribute:: attrs
HTML ``<input />`` attributes, such as class, placeholder, etc ... Note
that any ``data-autocomplete-*`` attribute will be parsed as an option
for ``yourlabs.Autocomplete`` js object. For example::
attrs={
'placeholder': 'foo',
'data-autocomplete-minimum-characters': 0
'class': 'bar',
}
Will render like::
<input
placeholder="foo"
data-autocomplete-minimum-characters="0"
class="autocomplete bar"
/>
Which will set by the way ``yourlabs.Autocomplete.minimumCharacters``
option - the naming conversion is handled by jQuery.
.. py:attribute:: widget_attrs
HTML widget container attributes. Note that any ``data-widget-*``
attribute will be parsed as an option for ``yourlabs.Widget`` js
object. For example::
widget_attrs={
'data-widget-maximum-values': 6,
'class': 'country-autocomplete',
}
Will render like::
<span
id="country-wrapper"
data-widget-maximum-values="6"
class="country-autocomplete autcomplete-light-widget"
/>
Which will set by the way ``yourlabs.Widget.maximumValues`` - note that
the naming conversion is handled by jQuery.
.. py:attribute:: widget_js_attributes
**DEPRECATED** in favor of :py:attr::`widget_attrs`.
A dict of options that will override the default widget options. For
example::
widget_js_attributes = {'max_values': 8}
The above code will set this HTML attribute::
data-max-values="8"
Which will override the default javascript widget maxValues option
(which is 0).
It is important to understand naming conventions which are sparse
unfortunately:
- python: lower case with underscores ie. ``max_values``,
- HTML attributes: lower case with dashes ie. ``data-max-values``,
- javascript: camel case, ie. ``maxValues``.
The python to HTML name conversion is done by the
autocomplete_light_data_attributes template filter.
The HTML to javascript name conversion is done by the jquery plugin.
.. py:attribute:: autocomplete_js_attributes
**DEPRECATED** in favor of :py:attr::`attrs`.
A dict of options like for :py:attr:`widget_js_attributes`. However,
note that HTML attributes will be prefixed by ``data-autocomplete-``
instead of just ``data-``. This allows the jQuery plugins to make the
distinction between attributes for the autocomplete instance and
attributes for the widget instance.
.. py:attribute:: extra_context
Extra context dict to pass to the template.
.. py:attribute:: widget_template
Template to use to render the widget. Default is
``autocomplete_light/widget.html``.
"""
def __init__(self, autocomplete=None, widget_js_attributes=None,
autocomplete_js_attributes=None, extra_context=None,
registry=None, widget_template=None, widget_attrs=None):
self._registry = registry
self._autocomplete = None
self.autocomplete_arg = autocomplete
self.widget_js_attributes = widget_js_attributes or {}
self.autocomplete_js_attributes = autocomplete_js_attributes or {}
self.extra_context = extra_context or {}
self.widget_template = (widget_template or
'autocomplete_light/widget.html')
self.widget_attrs = widget_attrs or {}
if autocomplete_js_attributes is not None:
raise PendingDeprecationWarning('autocomplete_js_attributes are'
'deprecated in favor of attrs')
if widget_js_attributes is not None:
raise PendingDeprecationWarning('widget_js_attributes are'
'deprecated in favor of widget_attrs')
@property
def registry(self):
if self._registry is None:
from autocomplete_light.registry import registry
self._registry = registry
return self._registry
def render(self, name, value, attrs=None):
widget_attrs = self.build_widget_attrs(name)
autocomplete = self.autocomplete(values=value)
attrs = self.build_attrs(self.attrs, attrs, autocomplete=autocomplete)
self.html_id = attrs.pop('id', name)
choices = autocomplete.choices_for_values()
values = [autocomplete.choice_value(c) for c in choices]
context = {
'name': name,
'values': values,
'choices': choices,
'widget': self,
'attrs': safestring.mark_safe(flatatt(attrs)),
'widget_attrs': safestring.mark_safe(flatatt(widget_attrs)),
'autocomplete': autocomplete,
}
context.update(self.extra_context)
template = getattr(autocomplete, 'widget_template',
self.widget_template)
return safestring.mark_safe(render_to_string(template, context))
def build_attrs(self, attrs, extra_attrs=None,
autocomplete=None, **kwargs):
attrs.copy()
attrs.update(getattr(autocomplete, 'attrs', {}))
attrs = super(WidgetBase, self).build_attrs(
attrs, extra_attrs, **kwargs)
if 'class' not in attrs.keys():
attrs['class'] = ''
attrs['class'] += ' autocomplete vTextField'
attrs.setdefault('data-autocomplete-choice-selector', '[data-value]')
attrs.setdefault('data-autocomplete-url',
self.autocomplete().get_absolute_url())
attrs.setdefault('placeholder', _(
'type some text to search in this autocomplete'))
# for backward compatibility
for key, value in self.autocomplete_js_attributes.items():
attrs['data-autocomplete-%s' % key.replace('_', '-')] = value
return attrs
def build_widget_attrs(self, name=None):
attrs = getattr(self.autocomplete, 'widget_attrs', {}).copy()
attrs.update(self.widget_attrs)
if 'class' not in attrs:
attrs['class'] = ''
attrs.setdefault('data-widget-bootstrap', 'normal')
# for backward compatibility
for key, value in self.autocomplete_js_attributes.items():
attrs['data-widget-%s' % key.replace('_', '-')] = value
attrs['class'] += ' autocomplete-light-widget '
if name:
attrs['class'] += name
if attrs.get('data-widget-maximum-values', 0) == 1:
attrs['class'] += ' single'
else:
attrs['class'] += ' multiple'
return attrs
def autocomplete():
def fget(self):
if not self._autocomplete:
self._autocomplete = self.registry.get_autocomplete_from_arg(
self.autocomplete_arg)
return self._autocomplete
def fset(self, value):
self._autocomplete = value
self.autocomplete_name = value.__class__.__name__
return {'fget': fget, 'fset': fset}
autocomplete = property(**autocomplete())
[docs]class ChoiceWidget(WidgetBase, forms.Select):
"""
Widget that provides an autocomplete for zero to one choice.
"""
def __init__(self, autocomplete=None, widget_js_attributes=None,
autocomplete_js_attributes=None, extra_context=None, registry=None,
widget_template=None, widget_attrs=None, *args,
**kwargs):
forms.Select.__init__(self, *args, **kwargs)
WidgetBase.__init__(self, autocomplete, widget_js_attributes,
autocomplete_js_attributes, extra_context, registry,
widget_template, widget_attrs)
self.widget_attrs.setdefault('data-widget-maximum-values', 1)
[docs]class MultipleChoiceWidget(WidgetBase, forms.SelectMultiple):
"""
Widget that provides an autocomplete for zero to n choices.
"""
def __init__(self, autocomplete=None, widget_js_attributes=None,
autocomplete_js_attributes=None, extra_context=None, registry=None,
widget_template=None, widget_attrs=None, *args,
**kwargs):
forms.SelectMultiple.__init__(self, *args, **kwargs)
WidgetBase.__init__(self, autocomplete,
widget_js_attributes, autocomplete_js_attributes, extra_context,
registry, widget_template, widget_attrs)
[docs]class TextWidget(WidgetBase, forms.TextInput):
"""
Widget that just adds an autocomplete to fill a text input.
Note that it only renders an ``<input>``, so attrs and widget_attrs are
merged together.
"""
def __init__(self, autocomplete=None, widget_js_attributes=None,
autocomplete_js_attributes=None, extra_context=None, registry=None,
widget_template=None, widget_attrs=None, *args,
**kwargs):
forms.TextInput.__init__(self, *args, **kwargs)
WidgetBase.__init__(self, autocomplete, widget_js_attributes,
autocomplete_js_attributes, extra_context, registry,
widget_template, widget_attrs)
[docs] def render(self, name, value, attrs=None):
""" Proxy Django's TextInput.render() """
autocomplete = self.autocomplete(values=value)
attrs = self.build_attrs(self.attrs, attrs, autocomplete=autocomplete)
return forms.TextInput.render(self, name, value, attrs)
def build_attrs(self, attrs, extra_attrs=None,
autocomplete=None, **kwargs):
attrs.copy()
attrs.update(super(TextWidget, self).build_widget_attrs())
attrs.update(getattr(autocomplete, 'attrs', {}))
attrs.update(super(TextWidget, self).build_attrs(
self.attrs, extra_attrs, **kwargs))
def update_attrs(source, prefix=''):
for key, value in source.items():
key = 'data-%s%s' % (prefix, key.replace('_', '-'))
attrs[key] = value
update_attrs(self.widget_js_attributes, 'widget-')
update_attrs(self.autocomplete_js_attributes, 'autocomplete-')
attrs['data-widget-bootstrap'] = 'text'
attrs['class'] += ' autocomplete-light-text-widget'
return attrs