diff --git a/edbob/pyramid/__init__.py b/edbob/pyramid/__init__.py
index e5c4633..64defc6 100644
--- a/edbob/pyramid/__init__.py
+++ b/edbob/pyramid/__init__.py
@@ -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')
diff --git a/edbob/pyramid/grids/alchemy.py b/edbob/pyramid/grids/alchemy.py
index eb3b7d7..4402ac4 100644
--- a/edbob/pyramid/grids/alchemy.py
+++ b/edbob/pyramid/grids/alchemy.py
@@ -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('
' % (cls, field.key)) + label + literal(' | ')
+ 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}
diff --git a/edbob/pyramid/grids/core.py b/edbob/pyramid/grids/core.py
index f1de01a..312ca60 100644
--- a/edbob/pyramid/grids/core.py
+++ b/edbob/pyramid/grids/core.py
@@ -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('%s | ' % (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:
diff --git a/edbob/pyramid/progress.py b/edbob/pyramid/progress.py
new file mode 100644
index 0000000..65c79e7
--- /dev/null
+++ b/edbob/pyramid/progress.py
@@ -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 .
+#
+################################################################################
+
+"""
+``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
diff --git a/edbob/pyramid/static/__init__.py b/edbob/pyramid/static/__init__.py
index 0fc349d..64af3d3 100644
--- a/edbob/pyramid/static/__init__.py
+++ b/edbob/pyramid/static/__init__.py
@@ -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 .
-#
-################################################################################
-
-"""
-``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 .
+#
+################################################################################
+
+"""
+``edbob.pyramid.static`` -- Static Assets
+"""
+
+
+def includeme(config):
+ config.add_static_view('edbob', 'edbob.pyramid:static', cache_max_age=3600)
diff --git a/edbob/pyramid/static/css/forms.css b/edbob/pyramid/static/css/forms.css
index 086f882..3047756 100644
--- a/edbob/pyramid/static/css/forms.css
+++ b/edbob/pyramid/static/css/forms.css
@@ -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;
}
diff --git a/edbob/pyramid/static/css/grids.css b/edbob/pyramid/static/css/grids.css
index 59c84a9..1a84cbe 100644
--- a/edbob/pyramid/static/css/grids.css
+++ b/edbob/pyramid/static/css/grids.css
@@ -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, */
diff --git a/edbob/pyramid/static/img/edit.png b/edbob/pyramid/static/img/edit.png
new file mode 100644
index 0000000..c46fa7e
Binary files /dev/null and b/edbob/pyramid/static/img/edit.png differ
diff --git a/edbob/pyramid/static/js/edbob.js b/edbob/pyramid/static/js/edbob.js
index 63ed842..4713370 100644
--- a/edbob/pyramid/static/js/edbob.js
+++ b/edbob/pyramid/static/js/edbob.js
@@ -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) {
diff --git a/edbob/pyramid/subscribers.py b/edbob/pyramid/subscribers.py
index ae492fd..2f30feb 100644
--- a/edbob/pyramid/subscribers.py
+++ b/edbob/pyramid/subscribers.py
@@ -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',
diff --git a/edbob/pyramid/templates/edbob/grid.mako b/edbob/pyramid/templates/edbob/grid.mako
index 1c3bba1..9c78db0 100644
--- a/edbob/pyramid/templates/edbob/grid.mako
+++ b/edbob/pyramid/templates/edbob/grid.mako
@@ -1,19 +1,24 @@
<%inherit file="/base.mako" />
<%def name="context_menu_items()">%def>
+
+<%def name="form()">
+ % if search:
+ ${search.render()}
+ % else:
+
+ % endif
+%def>
+
<%def name="tools()">%def>