"""
The channel.base module provides a channel class which you can extend to make
your own channel. It also serves as default channel class.
"""
from django.core import urlresolvers
from django.template import loader
from django.utils.translation import ugettext_lazy as _
__all__ = ('ChannelBase',)
[docs]class ChannelBase(object):
"""
A basic implementation of a channel, which should fit most use cases.
Attributes:
model
The model class this channel serves. If None, a new class will be
created in registry.register, and the model attribute will be set in
that subclass. So you probably don't need to worry about it, just know
that it's there for you to use.
result_template
The template to use in result_as_html method, to render a single
autocomplete suggestion. By default, it is
autocomplete_light/channelname/result.html or
autocomplete_light/result.html.
autocomplete_template
The template to use in render_autocomplete method, to render the
autocomplete box. By default, it is
autocomplete_light/channelname/autocomplete.html or
autocomplete_light/autocomplete.html.
search_field
The name of the field that the default implementation of query_filter
uses. Default is 'name'.
limit_results
The number of results that this channel should return. For example, if
query_filter returns 50 results and that limit_results is 20, then the
first 20 of 50 results will be rendered. Default is 20.
bootstrap
The name of the bootstrap kind. By default, deck.js will only
initialize decks for wrappers that have data-bootstrap="normal". If
you want to implement your own bootstrapping logic in javascript,
then you set bootstrap to anything that is not "normal". Default is
'normal'.
placeholder
The initial text in the autocomplete text input.
"""
model = None
search_field = 'name'
limit_results = 20
bootstrap = 'normal'
placeholder = _(u'type some text to search in this autocomplete')
result_template = None
autocomplete_template = None
def __init__(self):
"""
Set result_template and autocomplete_template if necessary.
"""
name = self.__class__.__name__.lower()
if not self.result_template:
self.result_template = [
'autocomplete_light/%s/result.html' % name,
'autocomplete_light/result.html',
]
if not self.autocomplete_template:
self.autocomplete_template = [
'autocomplete_light/%s/autocomplete.html' % name,
'autocomplete_light/autocomplete.html',
]
self.request = None
[docs] def get_absolute_url(self):
"""
Return the absolute url for this channel, using
autocomplete_light_channel url
"""
return urlresolvers.reverse('autocomplete_light_channel', args=(
self.__class__.__name__,))
[docs] def as_dict(self):
"""
Return a dict of variables for this channel, it is used by javascript.
"""
return {
'url': self.get_absolute_url(),
'name': self.__class__.__name__
}
[docs] def init_for_request(self, request, *args, **kwargs):
"""
Set self.request, self.args and self.kwargs, useful in query_filter.
"""
self.request = request
self.args = args
self.kwargs = kwargs
[docs] def query_filter(self, results):
"""
Filter results using the request.
By default this will expect results to be a queryset, and will filter
it with self.search_field + '__icontains'=self.request['q'].
"""
q = self.request.GET.get('q', None)
if q:
kwargs = {"%s__icontains" % self.search_field: q}
results = results.filter(**kwargs)
return results
[docs] def values_filter(self, results, values):
"""
Filter results based on a list of values.
By default this will expect values to be an iterable of model ids, and
results to be a queryset. Thus, it will return a queryset where pks are
in values.
"""
results = results.filter(pk__in=values)
return results
[docs] def get_queryset(self):
"""
Return a queryset for the channel model.
"""
return self.model.objects.all()
[docs] def get_results(self, values=None):
"""
Return an iterable of result to display in the autocomplete box.
By default, it will:
- call self.get_queryset(),
- call values_filter() if values is not None,
- call query_filter() if self.request is set,
- call order_results(),
- return a slice from offset 0 to self.limit_results.
"""
results = self.get_queryset()
if values is not None:
# used by the widget to prerender existing values
results = self.values_filter(results, values)
elif self.request:
# used by the autocomplete
results = self.query_filter(results)
return self.order_results(results)[0:self.limit_results]
[docs] def order_results(self, results):
"""
Return the result list after ordering.
By default, it expects results to be a queryset and order it by
search_field.
"""
return results.order_by(self.search_field).distinct()
[docs] def are_valid(self, values):
"""
Return True if the values are valid.
By default, expect values to be a list of object ids, return True if
all the ids are found in the queryset.
"""
return self.get_queryset().filter(pk__in=values).count() == len(values)
[docs] def result_as_html(self, result, extra_context=None):
"""
Return the html representation of a result for display in the deck
and autocomplete box.
By default, render result_template with channel and result in the
context.
"""
context = {
'channel': self,
'result': result,
'value': self.result_as_value(result),
}
context.update(extra_context or {})
return loader.render_to_string(self.result_template, context)
[docs] def result_as_value(self, result):
"""
Return the value that should be set to the widget field for a result.
By default, return result.pk.
"""
return result.pk
[docs] def render_autocomplete(self):
"""
Render the autocomplete suggestion box.
By default, render self.autocomplete_template with the channel in the
context.
"""
return loader.render_to_string(self.autocomplete_template, {
'channel': self,
})