extensive commit (see note)

The following changes are included:

- ``edbob.pyramid.Session`` uses ``sessionmaker()`` instead of ``edbob.db.Session``.

- ``edbob.pyramid.includeme()`` now configures ``pyramid_beaker`` directly.

- ``edbob.pyramid.includeme()`` now configures auth/auth policies directly.

- Pyramid progress indicator added.

- ``edbob.pyramid.Session`` added to global template render context.

- ``request.get_referrer()`` method added (removed ``edbob.pyramid.util`` module).

- ``request.get_setting()`` and ``request.save_setting()`` methods added.

- ``Grid.column_header()`` now supports ``title`` attribute.

- ``Grid.editable`` support added.

- Template / style tweaks.

- ``text`` argument to ``disable_button()`` JS function is now optional.

- Forbidden view flash message no longer duplicated when multiple redirects
  occur.

- ``CrudView`` class improved to support various workflow needs
  (e.g. post-delete procesing).

- Extra renderer keyword args support added to ``GridView`` class.

- ``SearchableAlchemyGridView`` class improved to support various workflow
  needs (e.g. obtaining an unsorted query).
This commit is contained in:
Lance Edgar 2012-08-29 11:08:43 -07:00
parent 03fe6a5c8e
commit 36634e8306
20 changed files with 481 additions and 122 deletions

View file

@ -26,15 +26,13 @@
``edbob.pyramid`` -- Pyramid Framework
"""
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker, scoped_session
from zope.sqlalchemy import ZopeTransactionExtension
import edbob.db
__all__ = ['Session']
Session = scoped_session(edbob.db.Session)
Session = scoped_session(sessionmaker())
def includeme(config):
@ -51,16 +49,23 @@ def includeme(config):
The other thing added is the ``edbob`` static view for CSS files etc.
"""
# Session is extended here instead of at module scope to prevent import
# side-effects.
# Configure Beaker session.
config.include('pyramid_beaker')
# Bring in transaction manager.
config.include('pyramid_tm')
# Configure SQLAlchemy session.
Session.configure(extension=ZopeTransactionExtension())
# Forbidden view is configured here instead of within edbob.pyramid.views
# since it's so "important."
# Configure user authentication / authorization.
from pyramid.authentication import SessionAuthenticationPolicy
config.set_authentication_policy(SessionAuthenticationPolicy())
from edbob.pyramid.auth import EdbobAuthorizationPolicy
config.set_authorization_policy(EdbobAuthorizationPolicy())
# Add forbidden view.
config.add_forbidden_view('edbob.pyramid.views.forbidden')
# Same goes with the edbob static route; we need that JS.
# Add static views.
config.include('edbob.pyramid.static')
# Include transaction manager tween.
config.include('pyramid_tm')

View file

@ -26,8 +26,8 @@
``edbob.pyramid.grids.alchemy`` -- FormAlchemy Grid Classes
"""
from webhelpers.html import literal
from webhelpers.html import tags
from webhelpers.html import HTML
import formalchemy
@ -70,16 +70,18 @@ class AlchemyGrid(Grid):
return {'uuid': row.uuid}
def column_header(self, field):
cls = ''
class_ = None
label = field.label()
if field.key in self.sort_map:
cls = 'sortable'
class_ = 'sortable'
if field.key == self.config['sort']:
cls += ' sorted ' + self.config['dir']
class_ += ' sorted ' + self.config['dir']
label = tags.link_to(label, '#')
if cls:
cls = ' class="%s"' % cls
return literal('<th%s field="%s">' % (cls, field.key)) + label + literal('</th>')
return HTML.tag('th', class_=class_, field=field.key,
title=self.column_titles.get(field.key), c=label)
def edit_route_kwargs(self, row):
return {'uuid': row.uuid}
def delete_route_kwargs(self, row):
return {'uuid': row.uuid}

View file

@ -31,7 +31,7 @@ try:
except ImportError:
from ordereddict import OrderedDict
from webhelpers.html import literal
from webhelpers.html import HTML
from webhelpers.html.builder import format_attrs
from pyramid.renderers import render
@ -48,6 +48,7 @@ class Grid(edbob.Object):
hoverable = True
clickable = False
checkboxes = False
editable = False
deletable = False
partial_only = False
@ -55,11 +56,15 @@ class Grid(edbob.Object):
click_route_name = None
click_route_kwargs = None
edit_route_name = None
edit_route_kwargs = None
delete_route_name = None
delete_route_kwargs = None
def __init__(self, request, **kwargs):
kwargs.setdefault('fields', OrderedDict())
kwargs.setdefault('column_titles', {})
kwargs.setdefault('extra_columns', [])
super(Grid, self).__init__(**kwargs)
self.request = request
@ -69,7 +74,9 @@ class Grid(edbob.Object):
edbob.Object(name=name, label=label, callback=callback))
def column_header(self, field):
return literal('<th field="%s">%s</th>' % (field.name, field.label))
return HTML.tag('th', field=field.name,
title=self.column_titles.get(field.name),
c=field.label)
def div_attrs(self):
classes = ['grid']
@ -92,6 +99,15 @@ class Grid(edbob.Object):
kwargs = self.delete_route_kwargs
return self.request.route_url(self.delete_route_name, **kwargs)
def get_edit_url(self, row):
kwargs = {}
if self.edit_route_kwargs:
if callable(self.edit_route_kwargs):
kwargs = self.edit_route_kwargs(row)
else:
kwargs = self.edit_route_kwargs
return self.request.route_url(self.edit_route_name, **kwargs)
def get_row_attrs(self, row, i):
attrs = self.row_attrs(row, i)
if self.clickable:

96
edbob/pyramid/progress.py Normal file
View file

@ -0,0 +1,96 @@
#!/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.progress`` -- Progress Indicator
"""
from beaker.session import Session
def get_progress_session(session, key):
request = session.request
id = '%s.progress.%s' % (session.id, key)
session = Session(request, id)
return session
class SessionProgress(object):
"""
Provides a session-based progress bar mechanism.
This class is only responsible for keeping the progress *data* current. It
is the responsibility of some client-side AJAX (etc.) to consume the data
for display to the user.
"""
def __init__(self, session, key):
self.session = get_progress_session(session, key)
self.cancelled = False
def __call__(self, message, maximum):
self.session['complete'] = False
self.session['message'] = message
self.session['maximum'] = maximum
self.session['cancelled'] = False
self.session['value'] = 0
self.session.save()
return self
def update(self, value):
self.session.load()
if self.session.get('cancelled'):
self.cancelled = True
else:
self.session['value'] = value
self.session.save()
return not self.cancelled
def destroy(self):
if not self.cancelled:
self.session['complete'] = True
self.session.save()
def secondary_progress(self):
return SecondarySessionProgress(self)
class SecondarySessionProgress(object):
def __init__(self, parent):
self.parent = parent
self.session = parent.session
def __call__(self, message, maximum):
self.session['message'] = message
self.session['value'] = 0
self.session['maximum'] = maximum
self.session.save()
return self
def update(self, value):
return self.parent.update(value)
def destroy(self):
pass

View file

@ -1,31 +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.static`` -- Static Assets
"""
def includeme(config):
config.add_static_view('edbob', 'edbob.pyramid:static', cache_max_age=3600)
#!/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.static`` -- Static Assets
"""
def includeme(config):
config.add_static_view('edbob', 'edbob.pyramid:static', cache_max_age=3600)

View file

@ -66,7 +66,8 @@ div.field-wrapper div.field {
div.field-wrapper div.field input[type=text],
div.field-wrapper div.field input[type=password],
div.field-wrapper div.field select {
div.field-wrapper div.field select,
div.field-wrapper div.field textarea {
width: 320px;
}

View file

@ -9,6 +9,15 @@ table.grid-header {
}
/******************************
* Form (Filters etc.)
******************************/
table.grid-header td.form {
vertical-align: bottom;
}
/******************************
* Context Menu
******************************/
@ -104,15 +113,6 @@ div.grid table tr.odd {
/* width: 15px; */
/* } */
div.grid table tbody td.delete {
background-image: url(../img/delete.png);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
min-width: 18px;
width: 18px;
}
div.grid table tbody tr.hovering {
background-color: #bbbbbb;
}
@ -129,6 +129,24 @@ div.grid table.checkable tbody tr {
div.grid.clickable table tbody tr td.noclick {
cursor: default;
text-align: center;
width: 18px;
}
div.grid table tbody tr td.noclick.edit,
div.grid table tbody tr td.noclick.delete {
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
min-width: 18px;
}
div.grid table tbody tr td.noclick.edit {
background-image: url(../img/edit.png);
}
div.grid table tbody tr td.noclick.delete {
background-image: url(../img/delete.png);
}
/* div.grid table.selectable tbody tr.selected, */

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

View file

@ -16,7 +16,9 @@ var filters_to_disable = [];
function disable_button(button, text) {
$(button).html(text + ", please wait...");
if (text) {
$(button).html(text + ", please wait...");
}
$(button).attr('disabled', 'disabled');
}
@ -287,6 +289,13 @@ $(function() {
$(this).toggleClass('selected');
});
$('div.grid table tbody td.edit').live('click', function() {
var url = $(this).attr('url');
if (url) {
location.href = url;
}
});
$('div.grid table tbody td.delete').live('click', function() {
var url = $(this).attr('url');
if (url) {

View file

@ -37,11 +37,7 @@ from edbob.pyramid import Session
def before_render(event):
"""
Adds goodies to the global template renderer context:
* ``h``
* ``url``
* ``edbob``
Adds goodies to the global template renderer context.
"""
request = event.get('request') or threadlocal.get_current_request()
@ -50,32 +46,63 @@ def before_render(event):
renderer_globals['h'] = helpers
renderer_globals['url'] = request.route_url
renderer_globals['edbob'] = edbob
renderer_globals['Session'] = Session
def context_found(event):
"""
This hook attaches the :class:`edbob.User` instance for the currently
logged-in user to the request (if there is one) as ``request.user``.
This hook attaches various attributes and methods to the ``request``
object. Specifically:
Also adds a ``has_perm()`` function to the request, which is a shortcut for
The :class:`edbob.User` instance currently logged-in (if indeed there is
one) is attached as ``request.user``.
A ``request.has_perm()`` method is attached, which is a shortcut for
:func:`edbob.db.auth.has_permission()`.
A ``request.get_referrer()`` method is attached, which contains some
convenient logic for determining the referring URL.
The ``request.get_setting()`` and ``request.save_setting()`` methods are
attached, which are shortcuts for :func:`edbob.get_setting()` and
:func:`edbob.save_setting()`, respectively.
"""
def has_perm_func(request):
def has_perm(perm):
if not request.user:
return False
return has_permission(request.user, perm)
return has_perm
request = event.request
request.has_perm = has_perm_func(request)
request.user = None
uuid = authenticated_userid(request)
if uuid:
request.user = Session.query(edbob.User).get(uuid)
def has_perm(perm):
if not request.user:
return False
return has_permission(request.user, perm)
request.has_perm = has_perm
def get_referrer(default=None):
if request.params.get('referrer'):
return request.params['referrer']
if request.session.get('referrer'):
return request.session.pop('referrer')
referrer = request.referrer
if not referrer or referrer == request.current_route_url():
if default:
referrer = default
else:
referrer = request.route_url('home')
return referrer
request.get_referrer = get_referrer
def get_setting(name):
return edbob.get_setting(name, Session())
request.get_setting = get_setting
def save_setting(name, value):
edbob.save_setting(name, value, Session())
request.save_setting = save_setting
def includeme(config):
config.add_subscriber('edbob.pyramid.subscribers:before_render',

View file

@ -1,19 +1,24 @@
<%inherit file="/base.mako" />
<%def name="context_menu_items()"></%def>
<%def name="form()">
% if search:
${search.render()}
% else:
&nbsp;
% endif
</%def>
<%def name="tools()"></%def>
<div class="grid-wrapper">
<table class="grid-header">
<tr>
% if search:
<td rowspan="2" class="filters">
${search.render()}
</td>
% else:
<td rowspan="2">&nbsp;</td>
% endif
<td rowspan="2" class="form">
${self.form()}
</td>
<td class="context-menu">
<ul>
${self.context_menu_items()}

View file

@ -3,6 +3,7 @@
<%def name="title()">Login</%def>
<%def name="head_tags()">
${parent.head_tags()}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/login.css'))}
${self.logo_styles()}
</%def>
@ -26,7 +27,7 @@
<div class="form">
${h.form('')}
## <input type="hidden" name="login" value="True" />
<input type="hidden" name="referer" value="${referer}" />
<input type="hidden" name="referrer" value="${referrer}" />
% if error:
<div class="error">${error}</div>

View file

@ -11,6 +11,9 @@
% for col in grid.extra_columns:
<th>${col.label}</td>
% endfor
% if grid.editable:
<th>&nbsp;</th>
% endif
% if grid.deletable:
<th>&nbsp;</th>
% endif
@ -28,6 +31,9 @@
% for col in grid.extra_columns:
<td class="noclick ${col.name}">${col.callback(row)}</td>
% endfor
% if grid.editable:
<td class="noclick edit" url="${grid.get_edit_url(row)}">&nbsp;</td>
% endif
% if grid.deletable:
<td class="noclick delete" url="${grid.get_delete_url(row)}">&nbsp;</td>
% endif

View file

@ -0,0 +1,138 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html style="direction: ltr;" xmlns="http://www.w3.org/1999/xhtml" lang="en-us">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>Working...</title>
${h.javascript_link(request.static_url('edbob.pyramid:static/js/jquery.js'))}
${h.javascript_link(request.static_url('edbob.pyramid:static/js/edbob.js'))}
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/edbob.css'))}
<style type="text/css">
#container {
position: relative;
}
#wrapper {
height: 60px;
left: 50%;
margin-top: -45px;
margin-left: -350px;
position: absolute;
top: 50%;
width: 700px;
}
#progress-wrapper {
border-collapse: collapse;
}
#progress {
border-collapse: collapse;
height: 25px;
width: 550px;
}
#complete {
background-color: Gray;
width: 0px;
}
#remaining {
background-color: LightGray;
width: 100%;
}
#percentage {
padding-left: 3px;
min-width: 50px;
width: 50px;
}
#cancel {
min-width: 200px;
}
</style>
<script language="javascript" type="text/javascript">
var updater = null;
function update_progress() {
$.ajax({
url: '${url('progress', key=key)}',
success: function(data) {
$('#message').html(data.message);
$('#total').html('('+data.maximum+' total)');
$('#cancel button').show();
if (data.complete) {
clearInterval(updater);
$('#cancel button').hide();
$('#total').html('done!');
$('#complete').css('width', '100%');
$('#remaining').hide();
$('#percentage').html('100%');
location.href = data.success_url;
} else {
var width = parseInt(data.value) / parseInt(data.maximum);
width = Math.round(100 * width);
if (width > 0) {
$('#complete').css('width', width+'%');
$('#remaining').css('width', 'auto');
}
$('#percentage').html(width+'%');
}
},
});
}
updater = setInterval(function() {update_progress()}, 1000);
$(function() {
$('#cancel button').click(function() {
if (confirm("Do you really wish to cancel this operation?")) {
clearInterval(updater);
disable_button(this, "Cancelling");
$.ajax({
url: '${url('progress.cancel', key=key)}',
data: {
'cancel_msg': '${cancel_msg}',
},
success: function(data) {
location.href = '${cancel_url}';
},
});
}
});
});
</script>
</head>
<body>
<div id="container">
<div id="wrapper">
<p><span id="message">Working</span> ... <span id="total"></span></p>
<table id="progress-wrapper">
<tr>
<td>
<table id="progress">
<tr>
<td id="complete"></td>
<td id="remaining"></td>
</tr>
</table><!-- #progress -->
</td>
<td id="percentage"></td>
<td id="cancel">
<button type="button" style="display: none;">Cancel</button>
</td>
</tr>
</table><!-- #progress-wrapper -->
</div><!-- #wrapper -->
</div><!-- #container -->
</body>
</html>

View file

@ -49,7 +49,7 @@ def forbidden(request):
if not authenticated_userid(request):
msg += literal("&nbsp; (Perhaps you should %s?)" %
link_to("log in", request.route_url('login')))
request.session.flash(msg)
request.session.flash(msg, allow_duplicate=False)
url = request.referer
if not url or url == request.current_route_url():
@ -60,4 +60,5 @@ def forbidden(request):
def includeme(config):
config.include('edbob.pyramid.views.auth')
config.include('edbob.pyramid.views.people')
config.include('edbob.pyramid.views.progress')
config.include('edbob.pyramid.views.users')

View file

@ -35,7 +35,6 @@ from pyramid_simpleform.renderers import FormRenderer
import edbob
from edbob.db.auth import authenticate_user
from edbob.pyramid import Session
from edbob.pyramid.util import get_referer
class UserLogin(formencode.Schema):
@ -50,11 +49,11 @@ def login(request):
The login view, responsible for displaying and handling the login form.
"""
referer = get_referer(request)
referrer = request.get_referrer()
# Redirect if already logged in.
if request.user:
return HTTPFound(location=referer)
return HTTPFound(location=referrer)
form = Form(request, schema=UserLogin)
if form.validate():
@ -66,7 +65,7 @@ def login(request):
user.display_name,
edbob.local_time().strftime('%I:%M %p')))
headers = remember(request, user.uuid)
return HTTPFound(location=referer, headers=headers)
return HTTPFound(location=referrer, headers=headers)
request.session.flash("Invalid username or password")
url = edbob.config.get('edbob.pyramid', 'login.logo_url',
@ -74,7 +73,7 @@ def login(request):
kwargs = eval(edbob.config.get('edbob.pyramid', 'login.logo_kwargs',
default="dict(width=500)"))
return {'form': FormRenderer(form), 'referer': referer,
return {'form': FormRenderer(form), 'referrer': referrer,
'logo_url': url, 'logo_kwargs': kwargs}
@ -89,8 +88,8 @@ def logout(request):
request.session.delete()
request.session.invalidate()
headers = forget(request)
referer = get_referer(request)
return HTTPFound(location=referer, headers=headers)
referrer = request.get_referrer()
return HTTPFound(location=referrer, headers=headers)
def includeme(config):

View file

@ -33,7 +33,7 @@ import formalchemy
from edbob.pyramid import Session
from edbob.pyramid.forms.formalchemy import AlchemyForm
from edbob.pyramid.views.core import View
from edbob.util import requires_impl
from edbob.util import requires_impl, prettify
__all__ = ['CrudView']
@ -41,7 +41,9 @@ __all__ = ['CrudView']
class CrudView(View):
readonly = False
allow_successive_creates = False
update_cancel_route = None
@property
@requires_impl(is_property=True)
@ -72,6 +74,7 @@ class CrudView(View):
def make_fieldset(self, model, **kwargs):
kwargs.setdefault('session', Session())
fieldset = formalchemy.FieldSet(model, **kwargs)
fieldset.prettify = prettify
return fieldset
def fieldset(self, model):
@ -84,7 +87,11 @@ class CrudView(View):
fieldset = self.fieldset(model)
kwargs.setdefault('pretty_name', self.pretty_name)
kwargs.setdefault('action_url', self.request.current_route_url())
kwargs.setdefault('cancel_url', self.cancel_url)
if self.updating and self.update_cancel_route:
kwargs.setdefault('cancel_url', self.request.route_url(
self.update_cancel_route, uuid=model.uuid))
else:
kwargs.setdefault('cancel_url', self.cancel_url)
kwargs.setdefault('creating', self.creating)
kwargs.setdefault('updating', self.updating)
form = AlchemyForm(self.request, fieldset, **kwargs)
@ -104,6 +111,9 @@ class CrudView(View):
def crud(self, model, readonly=False):
if readonly:
self.readonly = True
form = self.form(model)
if readonly:
form.readonly = True
@ -125,7 +135,7 @@ class CrudView(View):
and self.request.params.get('create_and_continue')):
return HTTPFound(location=self.request.current_route_url())
return HTTPFound(location=self.home_url)
return HTTPFound(location=self.post_save_url(form))
self.validation_failed(form)
@ -139,6 +149,9 @@ class CrudView(View):
def post_save(self, form):
pass
def post_save_url(self, form):
return self.home_url
def validation_failed(self, form):
pass
@ -160,7 +173,8 @@ class CrudView(View):
def read(self):
uuid = self.request.matchdict['uuid']
model = Session.query(self.mapped_class).get(uuid) if uuid else None
assert model
if not model:
return HTTPFound(location=self.home_url)
return self.crud(model, readonly=True)
def update(self):
@ -172,6 +186,9 @@ class CrudView(View):
def pre_delete(self, model):
pass
def post_delete(self, model):
pass
def delete(self):
uuid = self.request.matchdict['uuid']
model = Session.query(self.mapped_class).get(uuid) if uuid else None
@ -181,5 +198,6 @@ class CrudView(View):
return result
Session.delete(model)
Session.flush() # Don't set flash message if delete fails.
self.post_delete(model)
self.flash_delete(model)
return HTTPFound(location=self.home_url)

View file

@ -164,14 +164,15 @@ class SearchableAlchemyGridView(PagedAlchemyGridView):
def search_form(self):
return self.make_search_form()
def make_query(self):
def make_query(self, session=Session):
join_map = self.join_map()
query = Session.query(self.mapped_class)
query = session.query(self.mapped_class)
query = grids.search.filter_query(
query, self._filter_config, self.filter_map(), join_map)
self._sort_config['joins'] = self._filter_config['joins']
query = grids.util.sort_query(
query, self._sort_config, self.sort_map(), join_map)
if hasattr(self, '_sort_config'):
self._sort_config['joins'] = self._filter_config['joins']
query = grids.util.sort_query(
query, self._sort_config, self.sort_map(), join_map)
return query
def __call__(self):
@ -181,4 +182,5 @@ class SearchableAlchemyGridView(PagedAlchemyGridView):
self._data = self.make_pager()
grid = self.grid()
grid.pager = self._data
return grids.util.render_grid(grid, search)
kwargs = self.render_kwargs()
return grids.util.render_grid(grid, search, **kwargs)

View file

@ -61,6 +61,10 @@ class GridView(View):
def grid(self):
return self.make_grid()
def render_kwargs(self):
return {}
def __call__(self):
grid = self.grid()
return grids.util.render_grid(grid)
kwargs = self.render_kwargs()
return grids.util.render_grid(grid, **kwargs)

View file

@ -23,23 +23,34 @@
################################################################################
"""
``edbob.pyramid.util`` -- Utilities
``edbob.pyramid.views.progress`` -- Progress Views
"""
from edbob.pyramid.progress import get_progress_session
def get_referer(request, default=None):
"""
Returns a "referer" URL.
"""
if request.params.get('referer'):
return request.params['referer']
if request.session.get('referer'):
return request.session.pop('referer')
referer = request.referer
if not referer or referer == request.current_route_url():
if default:
referer = default
else:
referer = request.route_url('home')
return referer
def progress(request):
key = request.matchdict['key']
session = get_progress_session(request.session, key)
if session.get('complete') and session.get('success_msg'):
request.session.flash(session['success_msg'])
return session
def cancel(request):
key = request.matchdict['key']
session = get_progress_session(request.session, key)
session.clear()
session['cancelled'] = True
session.save()
msg = request.params.get('cancel_msg', "The operation was cancelled.")
request.session.flash(msg)
return {}
def includeme(config):
config.add_route('progress', '/progress/{key}')
config.add_view(progress, route_name='progress', renderer='json')
config.add_route('progress.cancel', '/progress/{key}/cancel')
config.add_view(cancel, route_name='progress.cancel', renderer='json')