edbob/edbob/pyramid/forms/formalchemy/__init__.py
2012-08-05 14:19:15 -07:00

199 lines
6.6 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# edbob is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.pyramid.forms.formalchemy`` -- FormAlchemy Interface
"""
from __future__ import absolute_import
import datetime
import new
from pyramid.renderers import render
from webhelpers.html.tags import literal
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 *
from edbob.pyramid.forms.formalchemy.fields import *
from edbob.pyramid.forms.formalchemy.renderers import *
__all__ = ['ChildGridField', 'PropertyField', 'EnumFieldRenderer',
'PrettyDateTimeFieldRenderer', 'AutocompleteFieldRenderer',
'FieldSet', 'make_fieldset', 'required', 'pretty_datetime',
'AssociationProxyField']
class TemplateEngine(formalchemy.templates.TemplateEngine):
"""
Mako template engine for FormAlchemy.
"""
def render(self, template, prefix='/forms/', suffix='.mako', **kwargs):
template = ''.join((prefix, template, suffix))
return render(template, kwargs)
# Make our TemplateEngine the default.
engine = TemplateEngine()
formalchemy.config.engine = engine
class ChildGridField(formalchemy.Field):
"""
Convenience class for including a child grid within a fieldset as a
read-only field.
"""
def __init__(self, name, value, *args, **kwargs):
super(ChildGridField, self).__init__(name, *args, **kwargs)
self.set(value=value)
self.set(readonly=True)
class PropertyField(formalchemy.Field):
"""
Convenience class for fields which simply involve a read-only property
value.
"""
def __init__(self, name, attr=None, *args, **kwargs):
super(PropertyField, self).__init__(name, *args, **kwargs)
if not attr:
attr = name
self.set(value=lambda x: getattr(x, attr))
self.set(readonly=True)
@accepts_none
def required(value, field=None):
if value is None or value == '':
msg = "Please provide a value"
if field:
msg = "You must provide a value for %s" % field.label()
raise formalchemy.ValidationError(msg)
def pretty_datetime(value, from_='local', to='local'):
"""
Formats a ``datetime.datetime`` instance and returns a "pretty"
human-readable string from it, e.g. "42 minutes ago". ``value`` is
rendered directly as a string if no date/time can be parsed from it.
"""
if not isinstance(value, datetime.datetime):
return str(value) if value else ''
if not value.tzinfo:
value = localize(value, from_=from_, to=to)
return literal('<span title="%s">%s</span>' % (
value.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
pretty.date(value)))
class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
"""
Adds "pretty" date/time support for FormAlchemy.
"""
def render_readonly(self, **kwargs):
return pretty_datetime(self.raw_value)
class DateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
"""
Leverages edbob time system to coerce timestamp to local time zone before
displaying it.
"""
def render_readonly(self, **kwargs):
value = self.raw_value
if isinstance(value, datetime.datetime):
value = edbob.local_time(value)
return value.strftime(self.format)
print type(value)
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)