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:
parent
03fe6a5c8e
commit
36634e8306
20 changed files with 481 additions and 122 deletions
|
@ -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')
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
96
edbob/pyramid/progress.py
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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, */
|
||||
|
|
BIN
edbob/pyramid/static/img/edit.png
Normal file
BIN
edbob/pyramid/static/img/edit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 533 B |
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
||||
<div class="grid-wrapper">
|
||||
|
||||
<table class="grid-header">
|
||||
<tr>
|
||||
% if search:
|
||||
<td rowspan="2" class="filters">
|
||||
${search.render()}
|
||||
</td>
|
||||
% else:
|
||||
<td rowspan="2"> </td>
|
||||
% endif
|
||||
<td rowspan="2" class="form">
|
||||
${self.form()}
|
||||
</td>
|
||||
<td class="context-menu">
|
||||
<ul>
|
||||
${self.context_menu_items()}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
% for col in grid.extra_columns:
|
||||
<th>${col.label}</td>
|
||||
% endfor
|
||||
% if grid.editable:
|
||||
<th> </th>
|
||||
% endif
|
||||
% if grid.deletable:
|
||||
<th> </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)}"> </td>
|
||||
% endif
|
||||
% if grid.deletable:
|
||||
<td class="noclick delete" url="${grid.get_delete_url(row)}"> </td>
|
||||
% endif
|
||||
|
|
138
edbob/pyramid/templates/progress.mako
Normal file
138
edbob/pyramid/templates/progress.mako
Normal 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>
|
|
@ -49,7 +49,7 @@ def forbidden(request):
|
|||
if not authenticated_userid(request):
|
||||
msg += literal(" (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')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
Loading…
Add table
Add a link
Reference in a new issue