diff --git a/edbob/db/__init__.py b/edbob/db/__init__.py index 0fd3018..3ffb237 100644 --- a/edbob/db/__init__.py +++ b/edbob/db/__init__.py @@ -44,7 +44,7 @@ inited = False engines = None engine = None Session = sessionmaker() -Base = declarative_base() +Base = declarative_base(cls=edbob.Object) # metadata = None diff --git a/edbob/pyramid/forms/__init__.py b/edbob/pyramid/forms/__init__.py index a6f3935..478b07f 100644 --- a/edbob/pyramid/forms/__init__.py +++ b/edbob/pyramid/forms/__init__.py @@ -1,29 +1,31 @@ -#!/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`` -- Forms -""" - -from edbob.pyramid.forms.formalchemy import * +#!/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`` -- Forms +""" + +from edbob.pyramid.forms.core import * +from edbob.pyramid.forms.formalchemy import * +from edbob.pyramid.forms.simpleform import * diff --git a/edbob/pyramid/forms/core.py b/edbob/pyramid/forms/core.py new file mode 100644 index 0000000..00f2ab9 --- /dev/null +++ b/edbob/pyramid/forms/core.py @@ -0,0 +1,93 @@ +#!/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.core`` -- Core Forms +""" + +from sqlalchemy.util import OrderedDict + +from webhelpers.html import literal, tags + +import edbob +from edbob.util import requires_impl + + +__all__ = ['Form'] + + +class Form(edbob.Object): + """ + Generic form class. + + This class exists primarily so that rendering calls may mimic those used by + FormAlchemy. + """ + + readonly = False + successive = False + + action_url = None + home_route = None + home_url = None + # template = None + + render_fields = OrderedDict() + errors = {} + + # def __init__(self, request=None, action_url=None, home_url=None, template=None, **kwargs): + def __init__(self, request=None, action_url=None, home_url=None, **kwargs): + super(Form, self).__init__(**kwargs) + self.request = request + if action_url: + self.action_url = action_url + if request and not self.action_url: + self.action_url = request.current_route_url() + if home_url: + self.home_url = home_url + if request and not self.home_url: + home = self.home_route if self.home_route else 'home' + self.home_url = request.route_url(home) + # if template: + # self.template = template + # if not self.template: + # self.template = '%s.mako' % self.action_url + + @property + def action_url(self): + return self.request.current_route_url() + + def standard_buttons(self, submit="Save"): + return literal(tags.submit('submit', submit) + ' ' + self.cancel_button()) + + def cancel_button(self): + return literal('') + + def render(self, **kwargs): + """ + Renders the form as HTML. All keyword arguments are passed on to the + template context. + """ + + return '' diff --git a/edbob/pyramid/forms/formalchemy.py b/edbob/pyramid/forms/formalchemy/__init__.py similarity index 90% rename from edbob/pyramid/forms/formalchemy.py rename to edbob/pyramid/forms/formalchemy/__init__.py index 220cc61..c5876d7 100644 --- a/edbob/pyramid/forms/formalchemy.py +++ b/edbob/pyramid/forms/formalchemy/__init__.py @@ -44,12 +44,14 @@ from formalchemy.validators import accepts_none import edbob from edbob.lib import pretty from edbob.util import prettify -from edbob.pyramid import Session +from edbob.pyramid import Session, helpers + +from edbob.pyramid.forms.formalchemy.fieldset import * __all__ = ['AlchemyGrid', 'ChildGridField', 'PropertyField', 'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer', - 'AutocompleteFieldRenderer', + 'AutocompleteFieldRenderer', 'FieldSet', 'make_fieldset', 'required', 'pretty_datetime'] @@ -68,33 +70,6 @@ engine = TemplateEngine() formalchemy.config.engine = engine -class FieldSet(formalchemy.FieldSet): - """ - Adds a little magic to the ``FieldSet`` class. - """ - - prettify = staticmethod(prettify) - - def __init__(self, model, class_name=None, crud_title=None, url=None, - route_name=None, action_url='', list_url=None, cancel_url=None, **kwargs): - super(FieldSet, self).__init__(model, **kwargs) - self.class_name = class_name or self._original_cls.__name__.lower() - self.crud_title = crud_title or prettify(self.class_name) - self.edit = isinstance(model, self._original_cls) - self.route_name = route_name or (self.class_name + 's') - self.action_url = action_url - self.list_url = list_url - self.cancel_url = cancel_url or self.list_url - self.allow_continue = kwargs.pop('allow_continue', False) - - def get_display_text(self): - return unicode(self.model) - - def render(self, **kwargs): - kwargs.setdefault('class_', self.class_name) - return formalchemy.FieldSet.render(self, **kwargs) - - class AlchemyGrid(formalchemy.Grid): """ This class defines the basic grid which you see in pretty much all @@ -308,11 +283,6 @@ class PropertyField(formalchemy.Field): self.set(readonly=True) -def make_fieldset(model, **kwargs): - kwargs.setdefault('session', Session()) - return FieldSet(model, **kwargs) - - @accepts_none def required(value, field=None): if value is None or value == '': @@ -371,6 +341,23 @@ class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer): 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 @@ -421,7 +408,6 @@ class _AutocompleteFieldRenderer(fields.FieldRenderer): 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_name=autocompleter_name, + autocompleter=autocompleter_name, service_url=self.service_url, width=self.width, - callback=self.callback, hidden=tags.hidden, text=tags.text, - **kwargs) + callback=self.callback, h=helpers, **kwargs) diff --git a/edbob/pyramid/forms/formalchemy/fieldset.py b/edbob/pyramid/forms/formalchemy/fieldset.py new file mode 100644 index 0000000..a880339 --- /dev/null +++ b/edbob/pyramid/forms/formalchemy/fieldset.py @@ -0,0 +1,72 @@ +#!/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.fieldset`` -- FormAlchemy FieldSet +""" + +import formalchemy + +from edbob.pyramid import Session +from edbob.util import prettify + + +__all__ = ['FieldSet', 'make_fieldset'] + + +class FieldSet(formalchemy.FieldSet): + """ + Adds a little magic to the :class:`formalchemy.FieldSet` class. + """ + + prettify = staticmethod(prettify) + + def __init__(self, model, class_name=None, crud_title=None, url=None, + route_name=None, action_url='', home_url=None, **kwargs): + super(FieldSet, self).__init__(model, **kwargs) + self.class_name = class_name or self._original_cls.__name__.lower() + self.crud_title = crud_title or prettify(self.class_name) + self.edit = isinstance(model, self._original_cls) + self.route_name = route_name or (self.class_name + 's') + self.action_url = action_url + self.home_url = home_url + self.allow_continue = kwargs.pop('allow_continue', False) + + def get_display_text(self): + return unicode(self.model) + + def render(self, **kwargs): + kwargs.setdefault('class_', self.class_name) + return super(FieldSet, self).render(**kwargs) + + +def make_fieldset(model, **kwargs): + """ + Returns a :class:`FieldSet` equipped with the current scoped + :class:`edbob.db.Session` instance (unless ``session`` is provided as a + keyword argument). + """ + + kwargs.setdefault('session', Session()) + return FieldSet(model, **kwargs) diff --git a/edbob/pyramid/forms/simpleform.py b/edbob/pyramid/forms/simpleform.py new file mode 100644 index 0000000..c1d9c51 --- /dev/null +++ b/edbob/pyramid/forms/simpleform.py @@ -0,0 +1,53 @@ +#!/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.simpleform`` -- pyramid_simpleform Forms +""" + +import pyramid_simpleform +from pyramid.renderers import render +from pyramid_simpleform.renderers import FormRenderer + +from edbob.pyramid import helpers +from edbob.pyramid.forms import Form + + +__all__ = ['SimpleForm'] + + +class SimpleForm(Form): + + template = None + + def __init__(self, request, **kwargs): + super(SimpleForm, self).__init__(request, **kwargs) + self.form = pyramid_simpleform.Form(request) + + def render(self, **kwargs): + kw = { + 'form': self, + } + kw.update(kwargs) + return render('/forms/form.mako', kw) diff --git a/edbob/pyramid/templates/edbob/base.mako b/edbob/pyramid/templates/edbob/base.mako index 83adf93..5f6a34e 100644 --- a/edbob/pyramid/templates/edbob/base.mako +++ b/edbob/pyramid/templates/edbob/base.mako @@ -1,7 +1,8 @@ <%def name="global_title()">edbob -<%def name="title()">${(fieldset.crud_title+' : '+fieldset.get_display_text() if fieldset.edit else 'New '+fieldset.crud_title) if crud else ''} +<%def name="title()"> <%def name="head_tags()"> <%def name="home_link()">

${h.link_to("Home", url('home'))}

+<%def name="menu()"> <%def name="footer()"> powered by ${h.link_to('edbob', 'http://edbob.org', target='_blank')} v${edbob.__version__} diff --git a/edbob/pyramid/templates/edbob/crud.mako b/edbob/pyramid/templates/edbob/crud.mako index 16adc2b..287c30a 100644 --- a/edbob/pyramid/templates/edbob/crud.mako +++ b/edbob/pyramid/templates/edbob/crud.mako @@ -1,5 +1,7 @@ <%inherit file="/base.mako" /> +<%def name="title()">${(fieldset.crud_title+' : '+fieldset.get_display_text() if fieldset.edit else 'New '+fieldset.crud_title) if crud else ''} +
diff --git a/edbob/pyramid/templates/edbob/form.mako b/edbob/pyramid/templates/edbob/form.mako new file mode 100644 index 0000000..ad9e14a --- /dev/null +++ b/edbob/pyramid/templates/edbob/form.mako @@ -0,0 +1,16 @@ +<%inherit file="/base.mako" /> + +<%def name="buttons()"> + +
+ +
+ ${self.menu()} +
+ +
+ <% print 'type (2) is', type(form) %> + ${form.render(buttons=self.buttons)|n} +
+ +
diff --git a/edbob/pyramid/templates/edbob/form_body.mako b/edbob/pyramid/templates/edbob/form_body.mako new file mode 100644 index 0000000..a16a25e --- /dev/null +++ b/edbob/pyramid/templates/edbob/form_body.mako @@ -0,0 +1,58 @@ +<% _focus_rendered = False %> + +
+ ${h.form(form.action_url, enctype='multipart/form-data')} + + % for error in form.errors.get(None, []): +
${error}
+ % endfor + + % for field in form.render_fields.itervalues(): + +
+ % for error in field.errors: +
${error}
+ % endfor + ${field.label_tag()|n} +
+ ${field.render()|n} +
+ % if 'instructions' in field.metadata: + ${field.metadata['instructions']} + % endif +
+ + % if (form.focus == field or form.focus is True) and not _focus_rendered: + % if not field.is_readonly(): + + <% _focus_rendered = True %> + % endif + % endif + + % endfor + + % if form.successive: +
+ ${h.checkbox('keep-going', checked=True)} + +
+ % endif + +
+ ${h.submit('submit', "Save")} + +
+ ${h.end_form()} +
+ + diff --git a/edbob/pyramid/templates/edbob/login.mako b/edbob/pyramid/templates/edbob/login.mako index cd5753d..fcc4dc1 100644 --- a/edbob/pyramid/templates/edbob/login.mako +++ b/edbob/pyramid/templates/edbob/login.mako @@ -40,21 +40,21 @@ ${h.image(logo_url, "${self.global_title()} logo", id='login-logo', **logo_kwarg $(function() { $('form').submit(function() { - if (! $('#username').val()) { - with ($('#username').get(0)) { - select(); - focus(); - } - return false; - } - if (! $('#password').val()) { - with ($('#password').get(0)) { - select(); - focus(); - } - return false; - } - return true; + if (! $('#username').val()) { + with ($('#username').get(0)) { + select(); + focus(); + } + return false; + } + if (! $('#password').val()) { + with ($('#password').get(0)) { + select(); + focus(); + } + return false; + } + return true; }); $('#username').focus(); diff --git a/edbob/pyramid/templates/form.mako b/edbob/pyramid/templates/form.mako new file mode 100644 index 0000000..cae0447 --- /dev/null +++ b/edbob/pyramid/templates/form.mako @@ -0,0 +1,2 @@ +<%inherit file="/edbob/form.mako" /> +${parent.body()} diff --git a/edbob/pyramid/templates/forms/field_autocomplete.mako b/edbob/pyramid/templates/forms/field_autocomplete.mako index d65673a..33498ce 100644 --- a/edbob/pyramid/templates/forms/field_autocomplete.mako +++ b/edbob/pyramid/templates/forms/field_autocomplete.mako @@ -1,6 +1,6 @@
- ${hidden(fieldname, id=fieldname, value=fieldvalue)} - ${text(fieldname+'-textbox', id=fieldname+'-textbox', value=display, + ${h.hidden(fieldname, id=fieldname, value=fieldvalue)} + ${h.text(fieldname+'-textbox', id=fieldname+'-textbox', value=display, class_='autocomplete-textbox', style='display: none;' if fieldvalue else '')} diff --git a/edbob/pyramid/templates/forms/fieldset.mako b/edbob/pyramid/templates/forms/fieldset.mako index a0a325a..365bb37 100644 --- a/edbob/pyramid/templates/forms/fieldset.mako +++ b/edbob/pyramid/templates/forms/fieldset.mako @@ -52,7 +52,7 @@ diff --git a/edbob/pyramid/templates/forms/form.mako b/edbob/pyramid/templates/forms/form.mako new file mode 100644 index 0000000..63b7d50 --- /dev/null +++ b/edbob/pyramid/templates/forms/form.mako @@ -0,0 +1,62 @@ +<% _focus_rendered = False %> + +
+ ${h.form(form.action_url, enctype='multipart/form-data')} + + % for error in form.errors.get(None, []): +
${error}
+ % endfor + + % for field in form.render_fields.itervalues(): + +
+ % for error in field.errors: +
${error}
+ % endfor + ${field.label_tag()|n} +
+ ${field.render()|n} +
+ % if 'instructions' in field.metadata: + ${field.metadata['instructions']} + % endif +
+ + % if (form.focus == field or form.focus is True) and not _focus_rendered: + % if not field.is_readonly(): + + <% _focus_rendered = True %> + % endif + % endif + + % endfor + + % if form.successive: +
+ ${h.checkbox('keep-going', checked=True)} + +
+ % endif + +
+ % if buttons: + ${capture(buttons)} + % else: + ${h.submit('submit', "Save")} + + % endif +
+ ${h.end_form()} +
+ + diff --git a/edbob/pyramid/views/__init__.py b/edbob/pyramid/views/__init__.py index debf617..b42d01f 100644 --- a/edbob/pyramid/views/__init__.py +++ b/edbob/pyramid/views/__init__.py @@ -27,67 +27,15 @@ """ from pyramid.httpexceptions import HTTPFound -from pyramid.renderers import render_to_response from pyramid.security import authenticated_userid from webhelpers.html import literal from webhelpers.html.tags import link_to -from edbob.pyramid import Session -from edbob.pyramid.forms.formalchemy import AutocompleteFieldRenderer -from edbob.util import requires_impl +from edbob.pyramid.views.autocomplete import * +from edbob.pyramid.views.form import * -class Autocomplete(object): - - def __init__(self, request): - self.request = request - - @property - @requires_impl(is_property=True) - def mapped_class(self): - raise NotImplementedError - - @property - @requires_impl(is_property=True) - def fieldname(self): - raise NotImplementedError - - @property - @requires_impl(is_property=True) - def route_name(self): - raise NotImplementedError - - def __call__(self): - query = self.request.params['query'] - q = Session.query(self.mapped_class) - q = q.filter(getattr(self.mapped_class, self.fieldname).ilike('%%%s%%' % query)) - objs = q.order_by(getattr(self.mapped_class, self.fieldname)).all() - data = dict( - query=query, - 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, url): - # def add_routes(cls, config, route_prefix, url_prefix, template_prefix=None, permission_prefix=None): - """ - Add 'autocomplete' route to the config object. - """ - - config.add_route(cls.route_name, url) - config.add_view(cls, route_name=cls.route_name, http_cache=0) - - @classmethod - def renderer(cls, request): - return AutocompleteFieldRenderer(request.route_url(cls.route_name), - (cls.mapped_class, cls.fieldname)) - - def forbidden(request): """ The forbidden view. This is triggered whenever access rights are denied diff --git a/edbob/pyramid/views/autocomplete.py b/edbob/pyramid/views/autocomplete.py new file mode 100644 index 0000000..8f627d1 --- /dev/null +++ b/edbob/pyramid/views/autocomplete.py @@ -0,0 +1,105 @@ +#!/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.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.util import requires_impl + + +__all__ = ['Autocomplete'] + + +class Autocomplete(object): + + route = None + url = None + + def __init__(self, request): + self.request = request + + @property + @requires_impl(is_property=True) + def mapped_class(self): + raise NotImplementedError + + @property + @requires_impl(is_property=True) + 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 + + def make_query(self, query): + q = Session.query(self.mapped_class) + q = self.filter_query(q) + q = q.filter(getattr(self.mapped_class, self.fieldname).ilike('%%%s%%' % query)) + q = q.order_by(getattr(self.mapped_class, self.fieldname)) + return q + + def query(self, query): + return self.make_query(query) + + def __call__(self): + query = self.request.params['query'] + objs = self.query(query).all() + data = dict( + query=query, + 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)) diff --git a/edbob/pyramid/views/crud.py b/edbob/pyramid/views/crud.py index 3be0b60..c2e4a3e 100644 --- a/edbob/pyramid/views/crud.py +++ b/edbob/pyramid/views/crud.py @@ -37,6 +37,7 @@ from pyramid.httpexceptions import HTTPFound, HTTPException # from rattail.db.perms import has_permission # from rattail.pyramid.forms.formalchemy import Grid +import edbob from edbob.db import Base from edbob.pyramid import forms from edbob.pyramid import Session @@ -45,28 +46,42 @@ from edbob.util import requires_impl class Crud(object): + routes = ['new', 'edit', 'delete'] + + route_prefix = None + url_prefix = None + template_prefix = None + def __init__(self, request): self.request = request @property @requires_impl(is_property=True) def mapped_class(self): - raise NotImplementedError + pass @property @requires_impl(is_property=True) - def list_route(self): - raise NotImplementedError + def home_route(self): + pass @property - def list_url(self): - return self.request.route_url(self.list_route) + def home_url(self): + return self.request.route_url(self.home_route) + + @property + def cancel_route(self): + return None + + @property + def permission_prefix(self): + return self.route_prefix + 's' def make_fieldset(self, model, **kwargs): if 'action_url' not in kwargs: kwargs['action_url'] = self.request.current_route_url() - if 'list_url' not in kwargs: - kwargs['list_url'] = self.list_url + if 'home_url' not in kwargs: + kwargs['home_url'] = self.home_url return forms.make_fieldset(model, **kwargs) def fieldset(self, obj): @@ -80,25 +95,55 @@ class Crud(object): fs = self.make_fieldset(obj) return fs - def crud(self, obj): + def post_sync(self, fs): + pass + + def crud(self, obj=None): + if obj is None: + obj = self.mapped_class + + # fs = self.fieldset(obj) + # if not fs.readonly and self.request.POST: + # fs.rebind(data=self.request.params) + # if fs.validate(): + + # with transaction.manager: + # fs.sync() + # Session.add(fs.model) + # Session.flush() + # self.request.session.flash('%s "%s" has been %s.' % ( + # fs.crud_title, fs.get_display_text(), + # 'updated' if fs.edit else 'created')) + + # if self.request.params.get('add-another') == '1': + # return HTTPFound(location=self.request.current_route_url()) + + # return HTTPFound(location=self.home_url) fs = self.fieldset(obj) if not fs.readonly and self.request.POST: fs.rebind(data=self.request.params) if fs.validate(): + result = None + with transaction.manager: fs.sync() - Session.add(fs.model) - Session.flush() - self.request.session.flash('%s "%s" has been %s.' % ( - fs.crud_title, fs.get_display_text(), - 'updated' if fs.edit else 'created')) + result = self.post_sync(fs) + if not result: + Session.add(fs.model) + Session.flush() + self.request.session.flash('%s "%s" has been %s.' % ( + fs.crud_title, fs.get_display_text(), + 'updated' if fs.edit else 'created')) + + if result: + return result if self.request.params.get('add-another') == '1': return HTTPFound(location=self.request.current_route_url()) - return HTTPFound(location=self.list_url) + return HTTPFound(location=self.home_url) # TODO: This probably needs attention. if not fs.edit: @@ -121,31 +166,40 @@ class Crud(object): assert obj with transaction.manager: Session.delete(obj) - return HTTPFound(location=self.request.route_url(self.list_route)) + return HTTPFound(location=self.home_url) @classmethod - def add_routes(cls, config, route_prefix, url_prefix, template_prefix=None, permission_prefix=None): + def add_routes(cls, config): """ - Add standard routes (i.e. 'new', 'edit' and 'delete') for the mapped - class to the config object. + Add routes to the config object. """ - if not template_prefix: - template_prefix = url_prefix - if not permission_prefix: - permission_prefix = route_prefix + 's' + routes = cls.routes + if isinstance(routes, list): + _routes = routes + routes = {} + for route in _routes: + routes[route] = {} - config.add_route('%s.new' % route_prefix, '%s/new' % url_prefix) - config.add_view(cls, attr='new', route_name='%s.new' % route_prefix, renderer='%s/crud.mako' % template_prefix, - permission='%s.create' % permission_prefix, http_cache=0) + route_prefix = cls.route_prefix or cls.mapped_class.__name__.lower() + url_prefix = cls.url_prefix or '/%ss' % route_prefix + template_prefix = cls.template_prefix or url_prefix + permission_prefix = cls.permission_prefix or '%ss' % route_prefix - config.add_route('%s.edit' % route_prefix, '%s/{uuid}/edit' % url_prefix) - config.add_view(cls, attr='edit', route_name='%s.edit' % route_prefix, renderer='%s/crud.mako' % template_prefix, - permission='%s.edit' % permission_prefix, http_cache=0) - - config.add_route('%s.delete' % route_prefix, '%s/{uuid}/delete' % url_prefix) - config.add_view(cls, attr='delete', route_name='%s.delete' % route_prefix, - permission='%s.delete' % permission_prefix, http_cache=0) + for action in routes: + kw = dict( + route='%s.%s' % (route_prefix, action), + renderer='%s/%s.mako' % (template_prefix, action), + permission='%s.%s' % (permission_prefix, dict(new='create').get(action, action)), + ) + if action == 'new': + kw['url'] = '%s/new' % url_prefix + else: + kw['url'] = '%s/{uuid}/%s' % (url_prefix, action) + kw.update(routes[action]) + config.add_route(kw['route'], kw['url']) + config.add_view(cls, attr=action, route_name=kw['route'], renderer=kw['renderer'], + permission=kw['permission'], http_cache=0) def crud(request, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None): diff --git a/edbob/pyramid/views/form.py b/edbob/pyramid/views/form.py new file mode 100644 index 0000000..3eec8a4 --- /dev/null +++ b/edbob/pyramid/views/form.py @@ -0,0 +1,98 @@ +#!/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.views.form`` -- Generic Form View +""" + +import transaction +from pyramid.httpexceptions import HTTPFound + +from edbob.pyramid import Session +from edbob.pyramid.forms import SimpleForm +from edbob.util import requires_impl + + +__all__ = ['FormView'] + + +class FormView(object): + """ + This view provides basic form processing goodies. + """ + + route = None + url = None + template = None + permission = None + + def __init__(self, request): + self.request = request + + def __call__(self): + """ + Callable for the view. This method creates the underlying form and + processes data if any was submitted. + """ + + f = self.form(self.request) + if not f.readonly and self.request.POST: + f.rebind(data=self.request.params) + if f.validate(): + + with transaction.manager: + f.save(Session) + Session.flush() + self.request.session.flash('The book "%s" has been loaned.' % f.book.value) + + if self.request.params.get('keep-going') == '1': + return HTTPFound(location=self.request.current_route_url()) + + return HTTPFound(location=f.home_url) + + return {'form': f} + + def make_form(self, request, **kwargs): + """ + Returns a :class:`edbob.pyramid.forms.Form` instance. + """ + template = kwargs.pop('template', self.template or '%s.mako' % self.url) + return SimpleForm(request, template=template, **kwargs) + + def form(self, request): + """ + Should create and return a :class:`edbob.pyramid.forms.Form` instance + for the view. + """ + return self.make_form(request) + + @classmethod + def add_route(cls, config, **kwargs): + route = kwargs.get('route', cls.route) + url = kwargs.get('url', cls.url) + permission = kwargs.get('permission', cls.permission or route) + template = kwargs.get('template', cls.template or '%s.mako' % url) + config.add_route(route, url) + config.add_view(cls, route_name=route, renderer=template, + permission=permission, http_cache=0)