from __future__ import unicode_literals
import six
from django.urls import reverse, NoReverseMatch
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
__all__ = ('AutocompleteInterface', 'AutocompleteBase')
[docs]class AutocompleteInterface(object):
"""
An autocomplete proposes "choices". A choice has a "value". When the user
selects a "choice", then it is converted to a "value".
AutocompleteInterface is the minimum to implement in a custom Autocomplete
class usable by the widget and the view. It has two attributes:
.. py:attribute:: values
A list of values which
:py:meth:`~.base.AutocompleteInterface.validate_values` and
:py:meth:`~.base.AutocompleteInterface.choices_for_values` should use.
.. py:attribute:: request
A request object which
:py:meth:`~.base.AutocompleteInterface.autocomplete_html()` should use.
It is recommended that you inherit from :py:class:`~.base.AutocompleteBase`
instead when making your own classes because it has taken some design
decisions favorising a DRY implementation of
:py:class:`~.base.AutocompleteInterface`.
Instanciate an Autocomplete with a given ``request`` and ``values``
arguments. ``values`` will be casted to list if necessary and both will
be assigned to instance attributes
:py:attr:`~AutocompleteInterface.request` and
:py:attr:`~AutocompleteInterface.values` respectively.
"""
def __init__(self, request=None, values=None):
self.request = request
if values is None:
self.values = []
elif (isinstance(values, six.string_types) or
not hasattr(values, '__iter__')):
self.values = [values]
else:
self.values = values
[docs] def autocomplete_html(self):
"""
Return the HTML autocomplete that should be displayed under the text
input. :py:attr:`request` can be used, if set.
"""
raise NotImplemented()
[docs] def validate_values(self):
"""
Return True if :py:attr:`values` are all valid.
"""
raise NotImplemented()
[docs] def choices_for_values(self):
"""
Return the list of choices corresponding to :py:attr:`values`.
"""
raise NotImplemented()
[docs] def get_absolute_url(self):
"""
Return the absolute url for this autocomplete, using
autocomplete_light_autocomplete url.
"""
try:
return reverse('autocomplete_light_autocomplete',
args=(self.__class__.__name__,))
except NoReverseMatch:
# Such error will ruin form rendering. It would be automatically
# silenced because of e.silent_variable_failure=True, which is
# something we don't want. Let's give the user a hint:
raise ImproperlyConfigured("URL lookup for autocomplete '%s' "
"failed. Have you included autocomplete_light.urls in "
"your urls.py?" % (self.__class__.__name__,))
[docs]class AutocompleteBase(AutocompleteInterface):
"""
A basic implementation of AutocompleteInterface that renders HTML and
should fit most cases. It only needs overload of
:py:meth:`~.base.AutocompleteBase.choices_for_request` and
:py:meth:`~.base.AutocompleteInterface.choices_for_values` which is the
business-logic.
.. py:attribute:: choice_html_format
HTML string used to format a python choice in HTML by
:py:meth:`~.base.AutocompleteBase.choice_html`. It is formated with two
positionnal parameters: the value and the html representation,
respectively generated by
:py:meth:`~.base.AutocompleteBase.choice_value` and
:py:meth:`~.base.AutocompleteBase.choice_label`. Default is::
<span data-value="%s">%s</span>
.. py:attribute:: empty_html_format
HTML string used to format the message "no matches found" if no choices
match the current request. It takes a parameter for the translated
message. Default is::
<span class="block"><em>%s</em></span>
.. py:attribute:: autocomplete_html_format
HTML string used to format the list of HTML choices. It takes a
positionnal parameter which contains the list of HTML choices which
come from :py:meth:`~.base.AutocompleteBase.choice_html`. Default is::
%s
.. py:attribute:: add_another_url_name
Name of the url to add another choice via a javascript popup. If empty
then no "add another" link will appear.
.. py:attribute:: add_another_url_kwargs
Keyword arguments to use when reversing the add another url.
.. py:attribute:: widget_template
A special attribute used only by the widget. If it is set, the widget
will use that instead of the default
``autocomplete_light/widget.html``.
"""
choice_html_format = '<span data-value="%s">%s</span>'
empty_html_format = '<span class="block"><em>%s</em></span>'
autocomplete_html_format = '%s'
add_another_url_name = None
add_another_url_kwargs = None
[docs] def get_add_another_url(self):
"""
Return the url to use when adding another element
"""
if self.add_another_url_name:
url = reverse(self.add_another_url_name,
kwargs=self.add_another_url_kwargs)
return url + '?_popup=1'
else:
return None
[docs] def choices_for_request(self):
"""
Return the list of choices that are available. Uses :py:attr:`request`
if set, this method is used by
:py:meth:`~.base.AutocompleteBase.autocomplete_html`.
"""
raise NotImplemented()
[docs] def validate_values(self):
"""
This basic implementation returns True if all
:py:attr:`~AutocompleteInterface.values` are in
:py:meth:`~.base.AutocompleteInterface.choices_for_values`.
"""
return len(self.choices_for_values()) == len(self.values)
[docs] def autocomplete_html(self):
"""
Simple rendering of the autocomplete.
It will append the result of
:py:meth:`~.base.AutocompleteBase.choice_html` for each choice returned
by :py:meth:`~.base.AutocompleteBase.choices_for_request`, and
wrap that in :py:attr:`autocomplete_html_format`.
"""
html = ''.join(
[self.choice_html(c) for c in self.choices_for_request()])
if not html:
html = self.empty_html_format % _('No matches found')
return self.autocomplete_html_format % html
[docs] def choice_html(self, choice):
"""
Format a choice using :py:attr:`choice_html_format`.
"""
return self.choice_html_format % (
escape(self.choice_value(choice)),
escape(self.choice_label(choice)))
[docs] def choice_value(self, choice):
"""
Return the value of a choice. This simple implementation returns the
textual representation.
"""
return force_text(choice)
[docs] def choice_label(self, choice):
"""
Return the human-readable representation of a choice. This simple
implementation returns the textual representation.
"""
return force_text(choice)