convenience commit (form refactoring)

This commit is contained in:
Lance Edgar 2012-07-11 00:49:43 -05:00
parent dd3b42d981
commit f0c8858fb0
19 changed files with 736 additions and 184 deletions

View file

@ -44,7 +44,7 @@ inited = False
engines = None engines = None
engine = None engine = None
Session = sessionmaker() Session = sessionmaker()
Base = declarative_base() Base = declarative_base(cls=edbob.Object)
# metadata = None # metadata = None

View file

@ -26,4 +26,6 @@
``edbob.pyramid.forms`` -- Forms ``edbob.pyramid.forms`` -- Forms
""" """
from edbob.pyramid.forms.core import *
from edbob.pyramid.forms.formalchemy import * from edbob.pyramid.forms.formalchemy import *
from edbob.pyramid.forms.simpleform import *

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``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('<button type="button" class="cancel">Cancel</button>')
def render(self, **kwargs):
"""
Renders the form as HTML. All keyword arguments are passed on to the
template context.
"""
return ''

View file

@ -44,12 +44,14 @@ from formalchemy.validators import accepts_none
import edbob import edbob
from edbob.lib import pretty from edbob.lib import pretty
from edbob.util import prettify 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', __all__ = ['AlchemyGrid', 'ChildGridField', 'PropertyField',
'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer', 'EnumFieldRenderer', 'PrettyDateTimeFieldRenderer',
'AutocompleteFieldRenderer', 'AutocompleteFieldRenderer', 'FieldSet',
'make_fieldset', 'required', 'pretty_datetime'] 'make_fieldset', 'required', 'pretty_datetime']
@ -68,33 +70,6 @@ engine = TemplateEngine()
formalchemy.config.engine = engine 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): class AlchemyGrid(formalchemy.Grid):
""" """
This class defines the basic grid which you see in pretty much all 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) self.set(readonly=True)
def make_fieldset(model, **kwargs):
kwargs.setdefault('session', Session())
return FieldSet(model, **kwargs)
@accepts_none @accepts_none
def required(value, field=None): def required(value, field=None):
if value is None or value == '': if value is None or value == '':
@ -371,6 +341,23 @@ class PrettyDateTimeFieldRenderer(formalchemy.fields.DateTimeFieldRenderer):
return pretty_datetime(self.raw_value) 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): def AutocompleteFieldRenderer(service_url, display, width='300px', callback=None, **kwargs):
""" """
Returns a field renderer class which leverages jQuery autocomplete to 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('-', '_') autocompleter_name = 'autocompleter_%s' % self.name.replace('-', '_')
return formalchemy.config.engine('field_autocomplete', fieldname=self.name, return formalchemy.config.engine('field_autocomplete', fieldname=self.name,
fieldvalue=self.value, display=self._display(self.value), fieldvalue=self.value, display=self._display(self.value),
autocompleter_name=autocompleter_name, autocompleter=autocompleter_name,
service_url=self.service_url, width=self.width, service_url=self.service_url, width=self.width,
callback=self.callback, hidden=tags.hidden, text=tags.text, callback=self.callback, h=helpers, **kwargs)
**kwargs)

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``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)

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``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)

View file

@ -1,7 +1,8 @@
<%def name="global_title()">edbob</%def> <%def name="global_title()">edbob</%def>
<%def name="title()">${(fieldset.crud_title+' : '+fieldset.get_display_text() if fieldset.edit else 'New '+fieldset.crud_title) if crud else ''}</%def> <%def name="title()"></%def>
<%def name="head_tags()"></%def> <%def name="head_tags()"></%def>
<%def name="home_link()"><h1 class="right">${h.link_to("Home", url('home'))}</h1></%def> <%def name="home_link()"><h1 class="right">${h.link_to("Home", url('home'))}</h1></%def>
<%def name="menu()"></%def>
<%def name="footer()"> <%def name="footer()">
powered by ${h.link_to('edbob', 'http://edbob.org', target='_blank')} v${edbob.__version__} powered by ${h.link_to('edbob', 'http://edbob.org', target='_blank')} v${edbob.__version__}
</%def> </%def>

View file

@ -1,5 +1,7 @@
<%inherit file="/base.mako" /> <%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 ''}</%def>
<div class="crud wrapper"> <div class="crud wrapper">
<div class="right"> <div class="right">

View file

@ -0,0 +1,16 @@
<%inherit file="/base.mako" />
<%def name="buttons()"></%def>
<div class="wrapper">
<div class="right">
${self.menu()}
</div>
<div class="left">
<% print 'type (2) is', type(form) %>
${form.render(buttons=self.buttons)|n}
</div>
</div>

View file

@ -0,0 +1,58 @@
<% _focus_rendered = False %>
<div class="form">
${h.form(form.action_url, enctype='multipart/form-data')}
% for error in form.errors.get(None, []):
<div class="fieldset-error">${error}</div>
% endfor
% for field in form.render_fields.itervalues():
<div class="field-couple ${field.name}">
% for error in field.errors:
<div class="field-error">${error}</div>
% endfor
${field.label_tag()|n}
<div class="field">
${field.render()|n}
</div>
% if 'instructions' in field.metadata:
<span class="instructions">${field.metadata['instructions']}</span>
% endif
</div>
% if (form.focus == field or form.focus is True) and not _focus_rendered:
% if not field.is_readonly():
<script language="javascript" type="text/javascript">
$(function() {
$('#${field.renderer.name}').focus();
});
</script>
<% _focus_rendered = True %>
% endif
% endif
% endfor
% if form.successive:
<div class="checkbox">
${h.checkbox('keep-going', checked=True)}
<label for="keep-going">Add another after this one</label>
</div>
% endif
<div class="buttons">
${h.submit('submit', "Save")}
<button type="button" class="cancel">Cancel</button>
</div>
${h.end_form()}
</div>
<script language="javascript" type="text/javascript">
$(function() {
$('button.cancel').click(function() {
location.href = '${form.home_url}';
});
});
</script>

View file

@ -0,0 +1,2 @@
<%inherit file="/edbob/form.mako" />
${parent.body()}

View file

@ -1,6 +1,6 @@
<div id="${fieldname}-container" class="autocomplete-container"> <div id="${fieldname}-container" class="autocomplete-container">
${hidden(fieldname, id=fieldname, value=fieldvalue)} ${h.hidden(fieldname, id=fieldname, value=fieldvalue)}
${text(fieldname+'-textbox', id=fieldname+'-textbox', value=display, ${h.text(fieldname+'-textbox', id=fieldname+'-textbox', value=display,
class_='autocomplete-textbox', style='display: none;' if fieldvalue else '')} class_='autocomplete-textbox', style='display: none;' if fieldvalue else '')}
<div id="${fieldname}-display" class="autocomplete-display"${'' if fieldvalue else ' style="display: none;"'|n}> <div id="${fieldname}-display" class="autocomplete-display"${'' if fieldvalue else ' style="display: none;"'|n}>
<span>${display}</span> <span>${display}</span>
@ -9,7 +9,7 @@
</div> </div>
<script language="javascript" type="text/javascript"> <script language="javascript" type="text/javascript">
$(function() { $(function() {
var ${autocompleter_name} = $('#${fieldname}-textbox').autocomplete({ var ${autocompleter} = $('#${fieldname}-textbox').autocomplete({
serviceUrl: '${service_url}', serviceUrl: '${service_url}',
width: '${width}', width: '${width}',
onSelect: function(value, data) { onSelect: function(value, data) {

View file

@ -52,7 +52,7 @@
<script language="javascript" type="text/javascript"> <script language="javascript" type="text/javascript">
$(function() { $(function() {
$('button.cancel').click(function() { $('button.cancel').click(function() {
location.href = '${fieldset.cancel_url}'; location.href = '${fieldset.home_url}';
}); });
}); });
</script> </script>

View file

@ -0,0 +1,62 @@
<% _focus_rendered = False %>
<div class="form">
${h.form(form.action_url, enctype='multipart/form-data')}
% for error in form.errors.get(None, []):
<div class="fieldset-error">${error}</div>
% endfor
% for field in form.render_fields.itervalues():
<div class="field-couple ${field.name}">
% for error in field.errors:
<div class="field-error">${error}</div>
% endfor
${field.label_tag()|n}
<div class="field">
${field.render()|n}
</div>
% if 'instructions' in field.metadata:
<span class="instructions">${field.metadata['instructions']}</span>
% endif
</div>
% if (form.focus == field or form.focus is True) and not _focus_rendered:
% if not field.is_readonly():
<script language="javascript" type="text/javascript">
$(function() {
$('#${field.renderer.name}').focus();
});
</script>
<% _focus_rendered = True %>
% endif
% endif
% endfor
% if form.successive:
<div class="checkbox">
${h.checkbox('keep-going', checked=True)}
<label for="keep-going">Add another after this one</label>
</div>
% endif
<div class="buttons">
% if buttons:
${capture(buttons)}
% else:
${h.submit('submit', "Save")}
<button type="button" class="cancel">Cancel</button>
% endif
</div>
${h.end_form()}
</div>
<script language="javascript" type="text/javascript">
$(function() {
$('button.cancel').click(function() {
location.href = '${form.home_url}';
});
});
</script>

View file

@ -27,65 +27,13 @@
""" """
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import render_to_response
from pyramid.security import authenticated_userid from pyramid.security import authenticated_userid
from webhelpers.html import literal from webhelpers.html import literal
from webhelpers.html.tags import link_to from webhelpers.html.tags import link_to
from edbob.pyramid import Session from edbob.pyramid.views.autocomplete import *
from edbob.pyramid.forms.formalchemy import AutocompleteFieldRenderer from edbob.pyramid.views.form import *
from edbob.util import requires_impl
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): def forbidden(request):

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``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))

View file

@ -37,6 +37,7 @@ from pyramid.httpexceptions import HTTPFound, HTTPException
# from rattail.db.perms import has_permission # from rattail.db.perms import has_permission
# from rattail.pyramid.forms.formalchemy import Grid # from rattail.pyramid.forms.formalchemy import Grid
import edbob
from edbob.db import Base from edbob.db import Base
from edbob.pyramid import forms from edbob.pyramid import forms
from edbob.pyramid import Session from edbob.pyramid import Session
@ -45,28 +46,42 @@ from edbob.util import requires_impl
class Crud(object): class Crud(object):
routes = ['new', 'edit', 'delete']
route_prefix = None
url_prefix = None
template_prefix = None
def __init__(self, request): def __init__(self, request):
self.request = request self.request = request
@property @property
@requires_impl(is_property=True) @requires_impl(is_property=True)
def mapped_class(self): def mapped_class(self):
raise NotImplementedError pass
@property @property
@requires_impl(is_property=True) @requires_impl(is_property=True)
def list_route(self): def home_route(self):
raise NotImplementedError pass
@property @property
def list_url(self): def home_url(self):
return self.request.route_url(self.list_route) 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): def make_fieldset(self, model, **kwargs):
if 'action_url' not in kwargs: if 'action_url' not in kwargs:
kwargs['action_url'] = self.request.current_route_url() kwargs['action_url'] = self.request.current_route_url()
if 'list_url' not in kwargs: if 'home_url' not in kwargs:
kwargs['list_url'] = self.list_url kwargs['home_url'] = self.home_url
return forms.make_fieldset(model, **kwargs) return forms.make_fieldset(model, **kwargs)
def fieldset(self, obj): def fieldset(self, obj):
@ -80,25 +95,55 @@ class Crud(object):
fs = self.make_fieldset(obj) fs = self.make_fieldset(obj)
return fs 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) fs = self.fieldset(obj)
if not fs.readonly and self.request.POST: if not fs.readonly and self.request.POST:
fs.rebind(data=self.request.params) fs.rebind(data=self.request.params)
if fs.validate(): if fs.validate():
result = None
with transaction.manager: with transaction.manager:
fs.sync() fs.sync()
result = self.post_sync(fs)
if not result:
Session.add(fs.model) Session.add(fs.model)
Session.flush() Session.flush()
self.request.session.flash('%s "%s" has been %s.' % ( self.request.session.flash('%s "%s" has been %s.' % (
fs.crud_title, fs.get_display_text(), fs.crud_title, fs.get_display_text(),
'updated' if fs.edit else 'created')) 'updated' if fs.edit else 'created'))
if result:
return result
if self.request.params.get('add-another') == '1': if self.request.params.get('add-another') == '1':
return HTTPFound(location=self.request.current_route_url()) 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. # TODO: This probably needs attention.
if not fs.edit: if not fs.edit:
@ -121,31 +166,40 @@ class Crud(object):
assert obj assert obj
with transaction.manager: with transaction.manager:
Session.delete(obj) Session.delete(obj)
return HTTPFound(location=self.request.route_url(self.list_route)) return HTTPFound(location=self.home_url)
@classmethod @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 Add routes to the config object.
class to the config object.
""" """
if not template_prefix: routes = cls.routes
template_prefix = url_prefix if isinstance(routes, list):
if not permission_prefix: _routes = routes
permission_prefix = route_prefix + 's' routes = {}
for route in _routes:
routes[route] = {}
config.add_route('%s.new' % route_prefix, '%s/new' % url_prefix) route_prefix = cls.route_prefix or cls.mapped_class.__name__.lower()
config.add_view(cls, attr='new', route_name='%s.new' % route_prefix, renderer='%s/crud.mako' % template_prefix, url_prefix = cls.url_prefix or '/%ss' % route_prefix
permission='%s.create' % permission_prefix, http_cache=0) 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) for action in routes:
config.add_view(cls, attr='edit', route_name='%s.edit' % route_prefix, renderer='%s/crud.mako' % template_prefix, kw = dict(
permission='%s.edit' % permission_prefix, http_cache=0) route='%s.%s' % (route_prefix, action),
renderer='%s/%s.mako' % (template_prefix, action),
config.add_route('%s.delete' % route_prefix, '%s/{uuid}/delete' % url_prefix) permission='%s.%s' % (permission_prefix, dict(new='create').get(action, action)),
config.add_view(cls, attr='delete', route_name='%s.delete' % route_prefix, )
permission='%s.delete' % permission_prefix, http_cache=0) 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): def crud(request, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None):

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``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)