convenience commit (form refactoring)
This commit is contained in:
parent
dd3b42d981
commit
f0c8858fb0
19 changed files with 736 additions and 184 deletions
|
@ -44,7 +44,7 @@ inited = False
|
|||
engines = None
|
||||
engine = None
|
||||
Session = sessionmaker()
|
||||
Base = declarative_base()
|
||||
Base = declarative_base(cls=edbob.Object)
|
||||
# metadata = None
|
||||
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
``edbob.pyramid.forms`` -- Forms
|
||||
"""
|
||||
|
||||
from edbob.pyramid.forms.core import *
|
||||
from edbob.pyramid.forms.formalchemy import *
|
||||
from edbob.pyramid.forms.simpleform import *
|
||||
|
|
93
edbob/pyramid/forms/core.py
Normal file
93
edbob/pyramid/forms/core.py
Normal 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 ''
|
|
@ -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)
|
72
edbob/pyramid/forms/formalchemy/fieldset.py
Normal file
72
edbob/pyramid/forms/formalchemy/fieldset.py
Normal 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)
|
53
edbob/pyramid/forms/simpleform.py
Normal file
53
edbob/pyramid/forms/simpleform.py
Normal 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)
|
|
@ -1,7 +1,8 @@
|
|||
<%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="home_link()"><h1 class="right">${h.link_to("Home", url('home'))}</h1></%def>
|
||||
<%def name="menu()"></%def>
|
||||
<%def name="footer()">
|
||||
powered by ${h.link_to('edbob', 'http://edbob.org', target='_blank')} v${edbob.__version__}
|
||||
</%def>
|
||||
|
|
|
@ -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 ''}</%def>
|
||||
|
||||
<div class="crud wrapper">
|
||||
|
||||
<div class="right">
|
||||
|
|
16
edbob/pyramid/templates/edbob/form.mako
Normal file
16
edbob/pyramid/templates/edbob/form.mako
Normal 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>
|
58
edbob/pyramid/templates/edbob/form_body.mako
Normal file
58
edbob/pyramid/templates/edbob/form_body.mako
Normal 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>
|
|
@ -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();
|
||||
|
|
2
edbob/pyramid/templates/form.mako
Normal file
2
edbob/pyramid/templates/form.mako
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%inherit file="/edbob/form.mako" />
|
||||
${parent.body()}
|
|
@ -1,6 +1,6 @@
|
|||
<div id="${fieldname}-container" class="autocomplete-container">
|
||||
${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 '')}
|
||||
<div id="${fieldname}-display" class="autocomplete-display"${'' if fieldvalue else ' style="display: none;"'|n}>
|
||||
<span>${display}</span>
|
||||
|
@ -9,18 +9,18 @@
|
|||
</div>
|
||||
<script language="javascript" type="text/javascript">
|
||||
$(function() {
|
||||
var ${autocompleter_name} = $('#${fieldname}-textbox').autocomplete({
|
||||
serviceUrl: '${service_url}',
|
||||
width: '${width}',
|
||||
onSelect: function(value, data) {
|
||||
$('#${fieldname}').val(data);
|
||||
$('#${fieldname}-display span').text(value);
|
||||
$('#${fieldname}-textbox').hide();
|
||||
$('#${fieldname}-display').show();
|
||||
% if callback:
|
||||
${callback}(value, data);
|
||||
% endif
|
||||
},
|
||||
var ${autocompleter} = $('#${fieldname}-textbox').autocomplete({
|
||||
serviceUrl: '${service_url}',
|
||||
width: '${width}',
|
||||
onSelect: function(value, data) {
|
||||
$('#${fieldname}').val(data);
|
||||
$('#${fieldname}-display span').text(value);
|
||||
$('#${fieldname}-textbox').hide();
|
||||
$('#${fieldname}-display').show();
|
||||
% if callback:
|
||||
${callback}(value, data);
|
||||
% endif
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<script language="javascript" type="text/javascript">
|
||||
$(function() {
|
||||
$('button.cancel').click(function() {
|
||||
location.href = '${fieldset.cancel_url}';
|
||||
location.href = '${fieldset.home_url}';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
62
edbob/pyramid/templates/forms/form.mako
Normal file
62
edbob/pyramid/templates/forms/form.mako
Normal 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>
|
|
@ -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
|
||||
|
|
105
edbob/pyramid/views/autocomplete.py
Normal file
105
edbob/pyramid/views/autocomplete.py
Normal 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))
|
|
@ -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):
|
||||
|
|
98
edbob/pyramid/views/form.py
Normal file
98
edbob/pyramid/views/form.py
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue