From 95c277b596f5de02e979f2092a80beb5bbc6e586 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Aug 2012 09:31:08 -0700 Subject: [PATCH] improve autocomplete stuff --- edbob/pyramid/forms/formalchemy/__init__.py | 72 +++---------------- edbob/pyramid/forms/formalchemy/renderers.py | 36 +++++++++- edbob/pyramid/templates/autocomplete.mako | 7 +- .../templates/forms/field_autocomplete.mako | 29 +------- edbob/pyramid/views/autocomplete.py | 41 +---------- 5 files changed, 53 insertions(+), 132 deletions(-) diff --git a/edbob/pyramid/forms/formalchemy/__init__.py b/edbob/pyramid/forms/formalchemy/__init__.py index c0a35ef..6ab93d5 100644 --- a/edbob/pyramid/forms/formalchemy/__init__.py +++ b/edbob/pyramid/forms/formalchemy/__init__.py @@ -29,7 +29,6 @@ from __future__ import absolute_import import datetime -import new from pyramid.renderers import render from webhelpers.html.tags import literal @@ -38,7 +37,6 @@ import formalchemy from formalchemy.validators import accepts_none from edbob.lib import pretty -from edbob.pyramid import Session, helpers from edbob.time import localize from edbob.pyramid.forms.formalchemy.fieldset import * @@ -49,7 +47,7 @@ from edbob.pyramid.forms.formalchemy.renderers import * __all__ = ['ChildGridField', 'PropertyField', 'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer', 'AutocompleteFieldRenderer', 'FieldSet', 'make_fieldset', 'required', 'pretty_datetime', - 'AssociationProxyField'] + 'AssociationProxyField', 'YesNoFieldRenderer'] class TemplateEngine(formalchemy.templates.TemplateEngine): @@ -118,13 +116,14 @@ def pretty_datetime(value, from_='local', to='local'): pretty.date(value))) -class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer): - """ - Adds "pretty" date/time support for FormAlchemy. - """ +def PrettyDateTimeFieldRenderer(from_='local', to='local'): - def render_readonly(self, **kwargs): - return pretty_datetime(self.raw_value) + class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer): + + def render_readonly(self, **kwargs): + return pretty_datetime(self.raw_value, from_=from_, to=to) + + return PrettyDateTimeFieldRenderer class DateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer): @@ -142,58 +141,3 @@ class DateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer): return '' FieldSet.default_renderers[formalchemy.types.DateTime] = DateTimeFieldRenderer - - -def AutocompleteFieldRenderer(service_url, display, width='300px', callback=None, **kwargs): - """ - Returns a field renderer class which leverages jQuery autocomplete to - provide a more user-friendly experience. This is typically used in place - of a ``SelectFieldRenderer`` when the data set is deemed too large for that - renderer. - - ``service_url`` is required and will ultimately be passed to the - ``jQuery.autocomplete()`` function via the ``serviceUrl`` data parameter. - - ``display`` must be either a callable which accepts an object key as its - only positional argument, or else a tuple of the form ``(Class, 'attr')``. - - ``width`` is optional but is also passed to the jQuery function. - - If ``callback`` is specified, it should be the name of a JavaScript - function within the containing page's scope. This is used in place of - event handlers for autocomplete fields. - """ - - kwargs['service_url'] = service_url - kwargs['width'] = width - kwargs['callback'] = callback - Renderer = new.classobj('AutocompleteFieldRenderer', (_AutocompleteFieldRenderer,), kwargs) - if callable(display): - Renderer.display = classmethod(display) - else: - Renderer.display = display - return Renderer - - -class _AutocompleteFieldRenderer(formalchemy.fields.FieldRenderer): - """ - Implementation for :class:`AutocompleteFieldRenderer` class. - """ - - def _display(self, value): - if callable(self.display): - return self.display(value) - if not value: - return '' - obj = Session.query(self.display[0]).get(value) - if not obj: - return '' - return getattr(obj, self.display[1]) - - def render(self, **kwargs): - autocompleter_name = 'autocompleter_%s' % self.name.replace('-', '_') - return formalchemy.config.engine('field_autocomplete', fieldname=self.name, - fieldvalue=self.value, display=self._display(self.value), - autocompleter=autocompleter_name, - service_url=self.service_url, width=self.width, - callback=self.callback, h=helpers, **kwargs) diff --git a/edbob/pyramid/forms/formalchemy/renderers.py b/edbob/pyramid/forms/formalchemy/renderers.py index c2e21f3..783d323 100644 --- a/edbob/pyramid/forms/formalchemy/renderers.py +++ b/edbob/pyramid/forms/formalchemy/renderers.py @@ -28,8 +28,33 @@ import formalchemy +from pyramid.renderers import render -__all__ = ['EnumFieldRenderer'] + +__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer', + 'YesNoFieldRenderer'] + + +def AutocompleteFieldRenderer(service_url, width='300px'): + """ + Autocomplete renderer. + """ + + class AutocompleteFieldRenderer(formalchemy.fields.FieldRenderer): + + @property + def focus_name(self): + return self.name + '-textbox' + + def render(self, **kwargs): + kwargs.setdefault('field_name', self.name) + kwargs.setdefault('field_value', self.value) + kwargs.setdefault('field_display', self.raw_value) + kwargs.setdefault('service_url', service_url) + kwargs.setdefault('width', width) + return render('/forms/field_autocomplete.mako', kwargs) + + return AutocompleteFieldRenderer def EnumFieldRenderer(enum): @@ -54,3 +79,12 @@ def EnumFieldRenderer(enum): return formalchemy.fields.SelectFieldRenderer.render(self, opts, **kwargs) return Renderer + + +class YesNoFieldRenderer(formalchemy.fields.CheckBoxFieldRenderer): + + def render_readonly(self, **kwargs): + value = self.raw_value + if value is None: + return '' + return 'Yes' if value else 'No' diff --git a/edbob/pyramid/templates/autocomplete.mako b/edbob/pyramid/templates/autocomplete.mako index 7b09755..3c1dcb2 100644 --- a/edbob/pyramid/templates/autocomplete.mako +++ b/edbob/pyramid/templates/autocomplete.mako @@ -1,16 +1,16 @@ -<%def name="autocomplete(field_name, field_value, field_display, service_url, width='300px', callback=None)"> +<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', callback=None)">
${h.hidden(field_name, id=field_name, value=field_value)} ${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display, class_='autocomplete-textbox', style='display: none;' if field_value else '')}
+<%namespace file="/autocomplete.mako" import="autocomplete" /> + +${autocomplete(field_name, service_url, field_value, field_display, width=width, callback=callback)} diff --git a/edbob/pyramid/views/autocomplete.py b/edbob/pyramid/views/autocomplete.py index a967bbf..44c13a1 100644 --- a/edbob/pyramid/views/autocomplete.py +++ b/edbob/pyramid/views/autocomplete.py @@ -26,23 +26,15 @@ ``edbob.pyramid.views.autocomplete`` -- Autocomplete View """ -from pyramid.renderers import render_to_response - from edbob.pyramid import Session -from edbob.pyramid.forms.formalchemy import AutocompleteFieldRenderer +from edbob.pyramid.views.core import View from edbob.util import requires_impl __all__ = ['AutocompleteView'] -class AutocompleteView(object): - - route = None - url = None - - def __init__(self, request): - self.request = request +class AutocompleteView(View): @property @requires_impl(is_property=True) @@ -54,13 +46,6 @@ class AutocompleteView(object): def fieldname(self): raise NotImplementedError - @classmethod - def get_route(cls): - if not cls.route: - name = cls.mapped_class.__name__.lower() - cls.route = '%ss.autocomplete' % name - return cls.route - def filter_query(self, q): return q @@ -82,24 +67,4 @@ class AutocompleteView(object): suggestions=[getattr(x, self.fieldname) for x in objs], data=[x.uuid for x in objs], ) - response = render_to_response('json', data, request=self.request) - response.headers['Content-Type'] = 'application/json' - return response - - @classmethod - def add_route(cls, config): - """ - Add 'autocomplete' route to the config object. - """ - - name = cls.mapped_class.__name__.lower() - route = cls.get_route() - url = cls.url or '/%ss/autocomplete' % name - - config.add_route(route, url) - config.add_view(cls, route_name=route, http_cache=0) - - @classmethod - def renderer(cls, request): - return AutocompleteFieldRenderer(request.route_url(cls.get_route()), - (cls.mapped_class, cls.fieldname)) + return data