diff --git a/edbob/pyramid/forms/formalchemy/__init__.py b/edbob/pyramid/forms/formalchemy/__init__.py index c5876d7..4715a96 100644 --- a/edbob/pyramid/forms/formalchemy/__init__.py +++ b/edbob/pyramid/forms/formalchemy/__init__.py @@ -32,27 +32,26 @@ import datetime import new from pyramid.renderers import render -from webhelpers import paginate -from webhelpers.html import tags -from webhelpers.html.builder import format_attrs from webhelpers.html.tags import literal import formalchemy -from formalchemy import fields from formalchemy.validators import accepts_none import edbob from edbob.lib import pretty -from edbob.util import prettify from edbob.pyramid import Session, helpers from edbob.pyramid.forms.formalchemy.fieldset import * +from edbob.pyramid.forms.formalchemy.grid import * +from edbob.pyramid.forms.formalchemy.fields import * +from edbob.pyramid.forms.formalchemy.renderers import * __all__ = ['AlchemyGrid', 'ChildGridField', 'PropertyField', 'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer', 'AutocompleteFieldRenderer', 'FieldSet', - 'make_fieldset', 'required', 'pretty_datetime'] + 'make_fieldset', 'required', 'pretty_datetime', + 'AssociationProxyField'] class TemplateEngine(formalchemy.templates.TemplateEngine): @@ -70,193 +69,6 @@ engine = TemplateEngine() formalchemy.config.engine = engine -class AlchemyGrid(formalchemy.Grid): - """ - This class defines the basic grid which you see in pretty much all - Rattail/Pyramid apps. - - .. todo:: - This needs to be documented more fully, along with the rest of - rattail.pyramid I suppose... - """ - - prettify = staticmethod(prettify) - - # uuid_key = None - - # def __init__(self, cls, instances, config, url_kwargs={}, *args, **kwargs): - # formalchemy.Grid.__init__(self, cls, instances, *args, **kwargs) - # self.pager = instances if isinstance(instances, paginate.Page) else None - # self.config = config - # self.url_kwargs = url_kwargs - # self.sortable = config.get('sortable', False) - - def __init__(self, cls, instances, config, gridurl=None, objurl=None, - delurl=None, **kwargs): - """ - Grid constructor. - - ``url`` must be the URL used to access the grid itself. This url/view - must accept a GET query string parameter of "partial=True", which will - indicate that the grid *only* is being requested, as opposed to the - full page in which the grid normally resides. - """ - - formalchemy.Grid.__init__(self, cls, instances, **kwargs) - self.config = config - self.request = config['request'] - self.gridurl = gridurl - self.objurl = objurl - self.delurl = delurl - self.sortable = config.get('sortable', False) - self.deletable = config.get('deletable', False) - self.pager = instances if isinstance(instances, paginate.Page) else None - - def field_name(self, field): - return field.name - - def iter_fields(self): - for field in self.render_fields.itervalues(): - yield field - - def render_field(self, field, readonly): - if readonly: - return field.render_readonly() - return field.render() - - def row_attrs(self, i): - attrs = dict(class_='even' if i % 2 else 'odd') - if hasattr(self.model, 'uuid'): - attrs['uuid'] = self.model.uuid - return format_attrs(**attrs) - - def url_attrs(self): - attrs = {} - url = self.request.route_url - if self.gridurl: - attrs['url'] = self.gridurl - if self.objurl: - attrs['objurl'] = url(self.objurl, uuid='{uuid}') - if self.delurl: - attrs['delurl'] = url(self.delurl, uuid='{uuid}') - return format_attrs(**attrs) - - # def render(self, class_=None, **kwargs): - # """ - # Renders the grid into HTML, and returns the result. - - # ``class_`` (if provided) is used to define the class of the ``
`` - # (wrapper) and ```` elements of the grid. - - # Any remaining ``kwargs`` are passed directly to the underlying - # ``formalchemy.Grid.render()`` method. - # """ - - # kwargs['class_'] = class_ - # # kwargs.setdefault('get_uuid', self.get_uuid) - # kwargs.setdefault('checkboxes', False) - # return formalchemy.Grid.render(self, **kwargs) - - def render(self, **kwargs): - engine = self.engine or formalchemy.config.engine - if self.readonly: - return engine('grid_readonly', grid=self, **kwargs) - kwargs.setdefault('request', self._request) - return engine('grid', grid=self, **kwargs) - - def th_sortable(self, field): - class_ = '' - label = field.label() - if self.sortable and field.key in self.config.get('sort_map', {}): - class_ = 'sortable' - if field.key == self.config['sort']: - class_ += ' sorted ' + self.config['dir'] - label = literal('') + label + literal('') - if class_: - class_ = ' class="%s"' % class_ - return literal('') + label + literal('') - - # def url(self): - # # TODO: Probably clean this up somehow... - # if self.pager is not None: - # u = self.pager._url_generator(self.pager.page, partial=True) - # else: - # u = self._url or '' - # qs = self.query_string() - # if qs: - # if '?' not in u: - # u += '?' - # u += qs - # elif '?' not in u: - # u += '?partial=True' - # return u - - # def query_string(self): - # # TODO: Probably clean this up somehow... - # qs = '' - # if self.url_kwargs: - # for k, v in self.url_kwargs.items(): - # qs += '&%s=%s' % (urllib.quote_plus(k), urllib.quote_plus(v)) - # return qs - - def get_actions(self): - """ - Returns an HTML snippet containing ``' % - (cls, text)) - return res - - # def get_uuid(self): - # """ - # .. highlight:: none - - # Returns a unique identifier for a given record, in the form of an HTML - # attribute for direct inclusion in a ```` element within a template. - # An example of what this function might return would be the string:: - - # 'uuid="420"' - - # Rattail itself will tend to use *universally-unique* IDs (true UUIDs), - # but this method may be overridden to support legacy databases with - # auto-increment IDs, etc. Really the only important thing is that the - # value returned be unique across the relevant data set. - - # If the concept is unsupported, the method should return an empty - # string. - # """ - - # def uuid(): - # if self.uuid_key and hasattr(self.model, self.uuid_key): - # return getattr(self.model, self.uuid_key) - # if hasattr(self.model, 'uuid'): - # return getattr(self.model, 'uuid') - # if hasattr(self.model, 'id'): - # return getattr(self.model, 'id') - - # uuid = uuid() - # if uuid: - # return literal('uuid="%s"' % uuid) - # return '' - - class ChildGridField(formalchemy.Field): """ Convenience class for including a child grid within a fieldset as a @@ -292,30 +104,6 @@ def required(value, field=None): raise formalchemy.ValidationError(msg) -def EnumFieldRenderer(enum): - """ - Adds support for enumeration fields. - """ - - class Renderer(formalchemy.fields.SelectFieldRenderer): - - def render_readonly(self, **kwargs): - value = self.raw_value - if value is None: - return '' - if value in enum: - return enum[value] - return value - - def render(self, **kwargs): - opts = [] - for value in sorted(enum): - opts.append((enum[value], value)) - return formalchemy.fields.SelectFieldRenderer.render(self, opts, **kwargs) - - return Renderer - - def pretty_datetime(value): """ Formats a ``datetime.datetime`` instance and returns a "pretty" @@ -389,7 +177,7 @@ def AutocompleteFieldRenderer(service_url, display, width='300px', callback=None return Renderer -class _AutocompleteFieldRenderer(fields.FieldRenderer): +class _AutocompleteFieldRenderer(formalchemy.fields.FieldRenderer): """ Implementation for :class:`AutocompleteFieldRenderer` class. """ diff --git a/edbob/pyramid/forms/formalchemy/fields.py b/edbob/pyramid/forms/formalchemy/fields.py new file mode 100644 index 0000000..44f9638 --- /dev/null +++ b/edbob/pyramid/forms/formalchemy/fields.py @@ -0,0 +1,49 @@ +#!/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 . +# +################################################################################ + +""" +``edbob.pyramid.forms.formalchemy.fields`` -- Field Classes +""" + +import formalchemy + + +__all__ = ['AssociationProxyField'] + + +def AssociationProxyField(name, **kwargs): + """ + Returns a :class:`Field` class which is aware of SQLAlchemy association + proxies. + """ + + class ProxyField(formalchemy.Field): + + def sync(self): + if not self.is_readonly(): + setattr(self.parent.model, self.name, + self.renderer.deserialize()) + + kwargs.setdefault('value', lambda x: getattr(x, name)) + return ProxyField(name, **kwargs) diff --git a/edbob/pyramid/forms/formalchemy/grid.py b/edbob/pyramid/forms/formalchemy/grid.py new file mode 100644 index 0000000..4e5fa3f --- /dev/null +++ b/edbob/pyramid/forms/formalchemy/grid.py @@ -0,0 +1,221 @@ +#!/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 . +# +################################################################################ + +""" +``edbob.pyramid.forms.formalchemy.grid`` -- FormAlchemy Grid +""" + +from webhelpers import paginate +from webhelpers.html.builder import format_attrs +from webhelpers.html.tags import literal + +import formalchemy + +from edbob.util import prettify + + +__all__ = ['AlchemyGrid'] + + +class AlchemyGrid(formalchemy.Grid): + """ + Provides an "enhanced" version of the :class:`formalchemy.Grid` class. + """ + + prettify = staticmethod(prettify) + + # uuid_key = None + + # def __init__(self, cls, instances, config, url_kwargs={}, *args, **kwargs): + # formalchemy.Grid.__init__(self, cls, instances, *args, **kwargs) + # self.pager = instances if isinstance(instances, paginate.Page) else None + # self.config = config + # self.url_kwargs = url_kwargs + # self.sortable = config.get('sortable', False) + + def __init__(self, cls, instances, config, gridurl=None, objurl=None, + delurl=None, **kwargs): + """ + Grid constructor. + + ``url`` must be the URL used to access the grid itself. This url/view + must accept a GET query string parameter of "partial=True", which will + indicate that the grid *only* is being requested, as opposed to the + full page in which the grid normally resides. + """ + + formalchemy.Grid.__init__(self, cls, instances, **kwargs) + self.config = config + self.request = config['request'] + self.gridurl = gridurl + self.objurl = objurl + self.delurl = delurl + self.sortable = config.get('sortable', False) + self.clickable = config.get('clickable', False) + self.deletable = config.get('deletable', False) + self.pager = instances if isinstance(instances, paginate.Page) else None + + def field_name(self, field): + return field.name + + def iter_fields(self): + for field in self.render_fields.itervalues(): + yield field + + def render_field(self, field, readonly): + if readonly: + return field.render_readonly() + return field.render() + + def row_attrs(self, i): + attrs = dict(class_='even' if i % 2 else 'odd') + if hasattr(self.model, 'uuid'): + attrs['uuid'] = self.model.uuid + return format_attrs(**attrs) + + def url_attrs(self): + attrs = {} + url = self.request.route_url + if self.gridurl: + attrs['url'] = self.gridurl + if self.objurl: + attrs['objurl'] = url(self.objurl, uuid='{uuid}') + if self.delurl: + attrs['delurl'] = url(self.delurl, uuid='{uuid}') + return format_attrs(**attrs) + + # def render(self, class_=None, **kwargs): + # """ + # Renders the grid into HTML, and returns the result. + + # ``class_`` (if provided) is used to define the class of the ``
`` + # (wrapper) and ``
`` elements for each "action" - defined in the grid. - """ - - def get_class(text): - return text.lower().replace(' ', '-') - - res = '' - for action in self.config['actions']: - if isinstance(action, basestring): - text = action - cls = get_class(text) - else: - text = action[0] - if len(action) == 2: - cls = action[1] - else: - cls = get_class(text) - res += literal( - '%s
`` elements of the grid. + + # Any remaining ``kwargs`` are passed directly to the underlying + # ``formalchemy.Grid.render()`` method. + # """ + + # kwargs['class_'] = class_ + # # kwargs.setdefault('get_uuid', self.get_uuid) + # kwargs.setdefault('checkboxes', False) + # return formalchemy.Grid.render(self, **kwargs) + + def render(self, **kwargs): + engine = self.engine or formalchemy.config.engine + if self.readonly: + return engine('grid_readonly', grid=self, **kwargs) + kwargs.setdefault('request', self._request) + return engine('grid', grid=self, **kwargs) + + def th_sortable(self, field): + class_ = '' + label = field.label() + if self.sortable and field.key in self.config.get('sort_map', {}): + class_ = 'sortable' + if field.key == self.config['sort']: + class_ += ' sorted ' + self.config['dir'] + label = literal('') + label + literal('') + if class_: + class_ = ' class="%s"' % class_ + return literal('') + label + literal('') + + # def url(self): + # # TODO: Probably clean this up somehow... + # if self.pager is not None: + # u = self.pager._url_generator(self.pager.page, partial=True) + # else: + # u = self._url or '' + # qs = self.query_string() + # if qs: + # if '?' not in u: + # u += '?' + # u += qs + # elif '?' not in u: + # u += '?partial=True' + # return u + + # def query_string(self): + # # TODO: Probably clean this up somehow... + # qs = '' + # if self.url_kwargs: + # for k, v in self.url_kwargs.items(): + # qs += '&%s=%s' % (urllib.quote_plus(k), urllib.quote_plus(v)) + # return qs + + def get_actions(self): + """ + Returns an HTML snippet containing ``' % + (cls, text)) + return res + + # def get_uuid(self): + # """ + # .. highlight:: none + + # Returns a unique identifier for a given record, in the form of an HTML + # attribute for direct inclusion in a ```` element within a template. + # An example of what this function might return would be the string:: + + # 'uuid="420"' + + # Rattail itself will tend to use *universally-unique* IDs (true UUIDs), + # but this method may be overridden to support legacy databases with + # auto-increment IDs, etc. Really the only important thing is that the + # value returned be unique across the relevant data set. + + # If the concept is unsupported, the method should return an empty + # string. + # """ + + # def uuid(): + # if self.uuid_key and hasattr(self.model, self.uuid_key): + # return getattr(self.model, self.uuid_key) + # if hasattr(self.model, 'uuid'): + # return getattr(self.model, 'uuid') + # if hasattr(self.model, 'id'): + # return getattr(self.model, 'id') + + # uuid = uuid() + # if uuid: + # return literal('uuid="%s"' % uuid) + # return '' diff --git a/edbob/pyramid/forms/formalchemy/renderers.py b/edbob/pyramid/forms/formalchemy/renderers.py new file mode 100644 index 0000000..c2e21f3 --- /dev/null +++ b/edbob/pyramid/forms/formalchemy/renderers.py @@ -0,0 +1,56 @@ +#!/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 . +# +################################################################################ + +""" +``edbob.pyramid.forms.formalchemy.renderers`` -- Field Renderers +""" + +import formalchemy + + +__all__ = ['EnumFieldRenderer'] + + +def EnumFieldRenderer(enum): + """ + Adds support for enumeration fields. + """ + + class Renderer(formalchemy.fields.SelectFieldRenderer): + + def render_readonly(self, **kwargs): + value = self.raw_value + if value is None: + return '' + if value in enum: + return enum[value] + return value + + def render(self, **kwargs): + opts = [] + for value in sorted(enum): + opts.append((enum[value], value)) + return formalchemy.fields.SelectFieldRenderer.render(self, opts, **kwargs) + + return Renderer
`` elements for each "action" + defined in the grid. + """ + + def get_class(text): + return text.lower().replace(' ', '-') + + res = '' + for action in self.config['actions']: + if isinstance(action, basestring): + text = action + cls = get_class(text) + else: + text = action[0] + if len(action) == 2: + cls = action[1] + else: + cls = get_class(text) + res += literal( + '%s