Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f463de5a45 | ||
|
|
8829780def | ||
|
|
a302d52698 | ||
|
|
00087198e0 | ||
|
|
ea3aa62476 | ||
|
|
176c9f4ad9 | ||
|
|
0fdab02d68 | ||
|
|
0fcb5f2ccd | ||
|
|
27287d246a | ||
|
|
2234cd245c | ||
|
|
c5980cd312 | ||
|
|
4e8dbffd0e | ||
|
|
50dcd7a572 | ||
|
|
35f1cca768 | ||
|
|
e5bde6c0b1 | ||
|
|
d31bba2670 | ||
|
|
4f806a1db9 | ||
|
|
f64232df30 | ||
|
|
ecc2f0004c | ||
|
|
4f2e327d33 | ||
|
|
3ce3171dff | ||
|
|
0850cadfdc | ||
|
|
53a5a44def | ||
|
|
ad1d414485 | ||
|
|
ba22f9a3a5 | ||
|
|
51de1740f9 | ||
|
|
1c012b92d5 | ||
|
|
7377922211 | ||
|
|
fae120f1ea | ||
|
|
a915054602 | ||
|
|
dd97b9c8dd | ||
|
|
d1e25796e9 | ||
|
|
e38cfc5767 | ||
|
|
69c888d071 | ||
|
|
0138b1782d |
@@ -1,3 +1,12 @@
|
||||
## 2.5.1
|
||||
|
||||
- New style virtual fields in grid
|
||||
- Conditional fields (experimental) ``db.table.field.show_id = db.table.otherfield==True`` or ``db.table.field.show_id = db.table.otherfiel.contains(values)``
|
||||
- auth.settings.manager_group_role="manager" enables http://.../app/appadmin/auth_manage and http://.../app/appadmin/manage for members of the "manager" group. (also experimental)
|
||||
- support for POST variables in DELETE
|
||||
- Fixed memory leak when using the TAG helper
|
||||
|
||||
|
||||
## 2.4.7
|
||||
|
||||
- pypy support, thanks Niphlod
|
||||
|
||||
9
Makefile
9
Makefile
@@ -30,7 +30,7 @@ update:
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.4.7-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.5.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
@@ -64,9 +64,10 @@ app:
|
||||
python2.7 -c 'import compileall; compileall.compile_dir("gluon/")'
|
||||
#python web2py.py -S welcome -R __exit__.py
|
||||
#cd ../web2py_osx/site-packages/; unzip ../site-packages.zip
|
||||
find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
|
||||
cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip *
|
||||
mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7
|
||||
#find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
|
||||
#cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip *
|
||||
#mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7
|
||||
find gluon -path '*.py' -exec cp {} ../web2py_osx/web2py/web2py.app/Contents/Resources/{} \;
|
||||
cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources
|
||||
cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources
|
||||
cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.4.7-stable+timestamp.2013.05.25.10.38.02
|
||||
Version 2.5.1-stable+timestamp.2013.06.06.10.35.58
|
||||
|
||||
@@ -180,6 +180,9 @@ def run(servername, ip, port, softcron=True, logging=False, profiler=None):
|
||||
if servername == 'gevent':
|
||||
from gevent import monkey
|
||||
monkey.patch_all()
|
||||
elif servername == 'eventlet':
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import gluon.main
|
||||
|
||||
|
||||
@@ -25,7 +25,13 @@ handlers:
|
||||
# the parametric router's language logic.
|
||||
# You cannot use them together.
|
||||
|
||||
- url: /(?P<a>.+?)/static/(?P<b>.+)
|
||||
- url: /(.+?)/[^_]*\/?static/_\d.\d.\d\/?(.+)
|
||||
static_files: applications/\1/static/\2
|
||||
upload: applications/(.+?)/static/(.+)
|
||||
secure: optional
|
||||
expiration: "365d"
|
||||
|
||||
- url: /(.+?)/[^_]*\/?static/?(.+)
|
||||
static_files: applications/\1/static/\2
|
||||
upload: applications/(.+?)/static/(.+)
|
||||
secure: optional
|
||||
|
||||
@@ -37,14 +37,20 @@ if request.env.http_x_forwarded_for or request.is_https:
|
||||
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
|
||||
raise HTTP(200, T('appadmin is disabled because insecure channel'))
|
||||
|
||||
if (request.application == 'admin' and not session.authorized) or \
|
||||
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
|
||||
if request.function in ('auth_manage','manage') and 'auth' in globals():
|
||||
auth.requires_membership(auth.settings.manager_group_role)(lambda: None)()
|
||||
menu = False
|
||||
elif (request.application == 'admin' and not session.authorized) or \
|
||||
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
|
||||
redirect(URL('admin', 'default', 'index',
|
||||
vars=dict(send=URL(args=request.args, vars=request.vars))))
|
||||
else:
|
||||
menu = True
|
||||
|
||||
ignore_rw = True
|
||||
response.view = 'appadmin.html'
|
||||
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
|
||||
if menu:
|
||||
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
|
||||
args=[request.application])], [T('db'), False,
|
||||
URL('index')], [T('state'), False,
|
||||
URL('state')], [T('cache'), False,
|
||||
@@ -573,3 +579,35 @@ def bg_graph_model():
|
||||
|
||||
def graph_model():
|
||||
return dict(databases=databases, pgv=pgv)
|
||||
|
||||
def auth_manage():
|
||||
tablename = request.args(0)
|
||||
if not tablename or not tablename in auth.db.tables:
|
||||
return dict()
|
||||
table = auth.db[tablename]
|
||||
formname = '%s_grid' % tablename
|
||||
if tablename == auth.settings.table_user_name:
|
||||
auth.settings.table_user._plural = T('Users')
|
||||
auth.settings.table_membership._plural = T('Roles')
|
||||
auth.settings.table_membership._id.readable = False
|
||||
auth.settings.table_membership.user_id.label = T('User')
|
||||
auth.settings.table_membership.group_id.label = T('Role')
|
||||
grid = SQLFORM.smartgrid(table, args=request.args[:1], user_signature=True,
|
||||
linked_tables=[auth.settings.table_membership_name],
|
||||
maxtextlength=1000, formname=formname)
|
||||
else:
|
||||
table._id.readable = False
|
||||
auth.settings.table_permission.group_id.label = T('Role')
|
||||
auth.settings.table_permission.name.label = T('Permission')
|
||||
orderby = 'role' if table == auth.settings.table_group_name else 'group_id'
|
||||
grid = SQLFORM.grid(table, args=request.args[:1], orderby=table[orderby],
|
||||
user_signature=True, maxtextlength=1000, formname=formname)
|
||||
return grid if request.extension=='load' else dict(grid=grid)
|
||||
|
||||
def manage():
|
||||
tablename = request.args(0)
|
||||
if tablename in auth.db.tables:
|
||||
grid = SQLFORM.smartgrid(auth.db[tablename], args=request.args[:1])
|
||||
else:
|
||||
return dict()
|
||||
return grid if request.extension=='load' else dict(grid=grid)
|
||||
|
||||
@@ -1677,26 +1677,6 @@ def update_languages():
|
||||
redirect(URL('design', args=app, anchor='languages'))
|
||||
|
||||
|
||||
def twitter():
|
||||
session.forget()
|
||||
session._unlock(response)
|
||||
import gluon.tools
|
||||
import gluon.contrib.simplejson as sj
|
||||
try:
|
||||
if TWITTER_HASH:
|
||||
page = urllib.urlopen("http://search.twitter.com/search.json?q=%%40%s" % TWITTER_HASH).read()
|
||||
data = sj.loads(page, encoding="utf-8")['results']
|
||||
d = dict()
|
||||
for e in data:
|
||||
d[e["id"]] = e
|
||||
r = reversed(sorted(d))
|
||||
return dict(tweets=[d[k] for k in r])
|
||||
else:
|
||||
return 'disabled'
|
||||
except Exception, e:
|
||||
return DIV(T('Unable to download because:'), BR(), str(e))
|
||||
|
||||
|
||||
def user():
|
||||
if MULTI_USER_MODE:
|
||||
if not db(db.auth_user).count():
|
||||
|
||||
8
applications/admin/static/js/jquery.js
vendored
8
applications/admin/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
|
||||
jQuery('.hidden', target).hide();
|
||||
jQuery('.error', target).hide().slideDown('slow');
|
||||
web2py_ajax_fields(target);
|
||||
web2py_show_if(target);
|
||||
};
|
||||
|
||||
function web2py_event_handlers() {
|
||||
@@ -216,3 +217,26 @@ function web2py_validate_entropy(myfield, req_entropy) {
|
||||
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
|
||||
}
|
||||
|
||||
function web2py_show_if(target) {
|
||||
var triggers = {};
|
||||
var show_if = function () {
|
||||
var t = jQuery(this);
|
||||
var id = t.attr('id');
|
||||
t.attr('value', t.val());
|
||||
for(var k = 0; k < triggers[id].length; k++) {
|
||||
var dep = jQuery('#' + triggers[id][k], target);
|
||||
var tr = jQuery('#' + triggers[id][k] + '__row', target);
|
||||
if(t.is(dep.attr('data-show-if'))) tr.slideDown();
|
||||
else tr.hide();
|
||||
}
|
||||
};
|
||||
jQuery('[data-show-trigger]', target).each(function () {
|
||||
var name = jQuery(this).attr('data-show-trigger');
|
||||
if(!triggers[name]) triggers[name] = [];
|
||||
triggers[name].push(jQuery(this).attr('id'));
|
||||
});
|
||||
for(var name in triggers) {
|
||||
jQuery('#' + name, target).change(show_if).keyup(show_if);
|
||||
show_if.call(jQuery('#' + name, target));
|
||||
};
|
||||
}
|
||||
@@ -247,3 +247,39 @@
|
||||
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
|
||||
{{if request.function == 'auth_manage':}}
|
||||
<h2>{{=T('Manage Access Control')}}</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#users" data-toggle="tab">Users</a></li>
|
||||
<li><a href="#roles" data-toggle="tab">Roles</a></li>
|
||||
<li><a href="#permissions" data-toggle="tab">Permissions</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="users">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_user_name,ajax=True)}}
|
||||
</div>
|
||||
<div class="tab-pane" id="roles">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_group_name,ajax=True)}}
|
||||
</div>
|
||||
<div class="tab-pane" id="permissions">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_permission_name,ajax=True)}}
|
||||
</div>
|
||||
</div>
|
||||
{{elif request.function == 'manage':}}
|
||||
<h2>{{=T('Manage Access Control')}}</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
{{for k,tablename in enumerate(auth.db.tables):}}
|
||||
<li><a href="#table-{{=tablename}}" data-toggle="tab">{{=tablename}}</a></li>
|
||||
{{pass}}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{{for tablename in auth.db.tables:}}
|
||||
<div class="tab-pane" id="table-{{=tablename}}">
|
||||
{{=LOAD(f='manage.load', args=tablename,ajax=True)}}
|
||||
</div>
|
||||
{{pass}}
|
||||
</div>
|
||||
{{pass}}
|
||||
|
||||
@@ -145,14 +145,8 @@
|
||||
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
|
||||
{{=T("(requires internet access, experimental)")}}</p>
|
||||
</div> <!-- /APP WIZARD -->
|
||||
{{if TWITTER_HASH:}}
|
||||
<!-- TWITTER -->
|
||||
<div class="box">
|
||||
<h4>{{=T("%s Recent Tweets"%TWITTER_HASH)}}</h4>
|
||||
<div id="tweets">{{=T('loading...')}}</div>
|
||||
<script>jQuery(document).ready(function(){jQuery('#tweets').load('{{=URL('twitter.load')}}');});</script>
|
||||
</div> <!-- /TWITTER -->
|
||||
{{pass}}
|
||||
<a class="twitter-timeline" href="https://twitter.com/web2py" data-widget-id="340456915207327745">Tweets by @web2py</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
||||
</div>
|
||||
</div> <!-- /sidebar -->
|
||||
</div> <!-- /row-fluid
|
||||
|
||||
@@ -37,14 +37,20 @@ if request.env.http_x_forwarded_for or request.is_https:
|
||||
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
|
||||
raise HTTP(200, T('appadmin is disabled because insecure channel'))
|
||||
|
||||
if (request.application == 'admin' and not session.authorized) or \
|
||||
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
|
||||
if request.function in ('auth_manage','manage') and 'auth' in globals():
|
||||
auth.requires_membership(auth.settings.manager_group_role)(lambda: None)()
|
||||
menu = False
|
||||
elif (request.application == 'admin' and not session.authorized) or \
|
||||
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
|
||||
redirect(URL('admin', 'default', 'index',
|
||||
vars=dict(send=URL(args=request.args, vars=request.vars))))
|
||||
else:
|
||||
menu = True
|
||||
|
||||
ignore_rw = True
|
||||
response.view = 'appadmin.html'
|
||||
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
|
||||
if menu:
|
||||
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
|
||||
args=[request.application])], [T('db'), False,
|
||||
URL('index')], [T('state'), False,
|
||||
URL('state')], [T('cache'), False,
|
||||
@@ -573,3 +579,35 @@ def bg_graph_model():
|
||||
|
||||
def graph_model():
|
||||
return dict(databases=databases, pgv=pgv)
|
||||
|
||||
def auth_manage():
|
||||
tablename = request.args(0)
|
||||
if not tablename or not tablename in auth.db.tables:
|
||||
return dict()
|
||||
table = auth.db[tablename]
|
||||
formname = '%s_grid' % tablename
|
||||
if tablename == auth.settings.table_user_name:
|
||||
auth.settings.table_user._plural = T('Users')
|
||||
auth.settings.table_membership._plural = T('Roles')
|
||||
auth.settings.table_membership._id.readable = False
|
||||
auth.settings.table_membership.user_id.label = T('User')
|
||||
auth.settings.table_membership.group_id.label = T('Role')
|
||||
grid = SQLFORM.smartgrid(table, args=request.args[:1], user_signature=True,
|
||||
linked_tables=[auth.settings.table_membership_name],
|
||||
maxtextlength=1000, formname=formname)
|
||||
else:
|
||||
table._id.readable = False
|
||||
auth.settings.table_permission.group_id.label = T('Role')
|
||||
auth.settings.table_permission.name.label = T('Permission')
|
||||
orderby = 'role' if table == auth.settings.table_group_name else 'group_id'
|
||||
grid = SQLFORM.grid(table, args=request.args[:1], orderby=table[orderby],
|
||||
user_signature=True, maxtextlength=1000, formname=formname)
|
||||
return grid if request.extension=='load' else dict(grid=grid)
|
||||
|
||||
def manage():
|
||||
tablename = request.args(0)
|
||||
if tablename in auth.db.tables:
|
||||
grid = SQLFORM.smartgrid(auth.db[tablename], args=request.args[:1])
|
||||
else:
|
||||
return dict()
|
||||
return grid if request.extension=='load' else dict(grid=grid)
|
||||
|
||||
8
applications/examples/static/js/jquery.js
vendored
8
applications/examples/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
|
||||
jQuery('.hidden', target).hide();
|
||||
jQuery('.error', target).hide().slideDown('slow');
|
||||
web2py_ajax_fields(target);
|
||||
web2py_show_if(target);
|
||||
};
|
||||
|
||||
function web2py_event_handlers() {
|
||||
@@ -216,3 +217,26 @@ function web2py_validate_entropy(myfield, req_entropy) {
|
||||
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
|
||||
}
|
||||
|
||||
function web2py_show_if(target) {
|
||||
var triggers = {};
|
||||
var show_if = function () {
|
||||
var t = jQuery(this);
|
||||
var id = t.attr('id');
|
||||
t.attr('value', t.val());
|
||||
for(var k = 0; k < triggers[id].length; k++) {
|
||||
var dep = jQuery('#' + triggers[id][k], target);
|
||||
var tr = jQuery('#' + triggers[id][k] + '__row', target);
|
||||
if(t.is(dep.attr('data-show-if'))) tr.slideDown();
|
||||
else tr.hide();
|
||||
}
|
||||
};
|
||||
jQuery('[data-show-trigger]', target).each(function () {
|
||||
var name = jQuery(this).attr('data-show-trigger');
|
||||
if(!triggers[name]) triggers[name] = [];
|
||||
triggers[name].push(jQuery(this).attr('id'));
|
||||
});
|
||||
for(var name in triggers) {
|
||||
jQuery('#' + name, target).change(show_if).keyup(show_if);
|
||||
show_if.call(jQuery('#' + name, target));
|
||||
};
|
||||
}
|
||||
@@ -247,3 +247,39 @@
|
||||
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
|
||||
{{if request.function == 'auth_manage':}}
|
||||
<h2>{{=T('Manage Access Control')}}</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#users" data-toggle="tab">Users</a></li>
|
||||
<li><a href="#roles" data-toggle="tab">Roles</a></li>
|
||||
<li><a href="#permissions" data-toggle="tab">Permissions</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="users">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_user_name,ajax=True)}}
|
||||
</div>
|
||||
<div class="tab-pane" id="roles">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_group_name,ajax=True)}}
|
||||
</div>
|
||||
<div class="tab-pane" id="permissions">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_permission_name,ajax=True)}}
|
||||
</div>
|
||||
</div>
|
||||
{{elif request.function == 'manage':}}
|
||||
<h2>{{=T('Manage Access Control')}}</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
{{for k,tablename in enumerate(auth.db.tables):}}
|
||||
<li><a href="#table-{{=tablename}}" data-toggle="tab">{{=tablename}}</a></li>
|
||||
{{pass}}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{{for tablename in auth.db.tables:}}
|
||||
<div class="tab-pane" id="table-{{=tablename}}">
|
||||
{{=LOAD(f='manage.load', args=tablename,ajax=True)}}
|
||||
</div>
|
||||
{{pass}}
|
||||
</div>
|
||||
{{pass}}
|
||||
|
||||
@@ -37,14 +37,20 @@ if request.env.http_x_forwarded_for or request.is_https:
|
||||
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
|
||||
raise HTTP(200, T('appadmin is disabled because insecure channel'))
|
||||
|
||||
if (request.application == 'admin' and not session.authorized) or \
|
||||
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
|
||||
if request.function in ('auth_manage','manage') and 'auth' in globals():
|
||||
auth.requires_membership(auth.settings.manager_group_role)(lambda: None)()
|
||||
menu = False
|
||||
elif (request.application == 'admin' and not session.authorized) or \
|
||||
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
|
||||
redirect(URL('admin', 'default', 'index',
|
||||
vars=dict(send=URL(args=request.args, vars=request.vars))))
|
||||
else:
|
||||
menu = True
|
||||
|
||||
ignore_rw = True
|
||||
response.view = 'appadmin.html'
|
||||
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
|
||||
if menu:
|
||||
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
|
||||
args=[request.application])], [T('db'), False,
|
||||
URL('index')], [T('state'), False,
|
||||
URL('state')], [T('cache'), False,
|
||||
@@ -573,3 +579,35 @@ def bg_graph_model():
|
||||
|
||||
def graph_model():
|
||||
return dict(databases=databases, pgv=pgv)
|
||||
|
||||
def auth_manage():
|
||||
tablename = request.args(0)
|
||||
if not tablename or not tablename in auth.db.tables:
|
||||
return dict()
|
||||
table = auth.db[tablename]
|
||||
formname = '%s_grid' % tablename
|
||||
if tablename == auth.settings.table_user_name:
|
||||
auth.settings.table_user._plural = T('Users')
|
||||
auth.settings.table_membership._plural = T('Roles')
|
||||
auth.settings.table_membership._id.readable = False
|
||||
auth.settings.table_membership.user_id.label = T('User')
|
||||
auth.settings.table_membership.group_id.label = T('Role')
|
||||
grid = SQLFORM.smartgrid(table, args=request.args[:1], user_signature=True,
|
||||
linked_tables=[auth.settings.table_membership_name],
|
||||
maxtextlength=1000, formname=formname)
|
||||
else:
|
||||
table._id.readable = False
|
||||
auth.settings.table_permission.group_id.label = T('Role')
|
||||
auth.settings.table_permission.name.label = T('Permission')
|
||||
orderby = 'role' if table == auth.settings.table_group_name else 'group_id'
|
||||
grid = SQLFORM.grid(table, args=request.args[:1], orderby=table[orderby],
|
||||
user_signature=True, maxtextlength=1000, formname=formname)
|
||||
return grid if request.extension=='load' else dict(grid=grid)
|
||||
|
||||
def manage():
|
||||
tablename = request.args(0)
|
||||
if tablename in auth.db.tables:
|
||||
grid = SQLFORM.smartgrid(auth.db[tablename], args=request.args[:1])
|
||||
else:
|
||||
return dict()
|
||||
return grid if request.extension=='load' else dict(grid=grid)
|
||||
|
||||
@@ -31,6 +31,7 @@ def user():
|
||||
http://..../[app]/default/user/profile
|
||||
http://..../[app]/default/user/retrieve_password
|
||||
http://..../[app]/default/user/change_password
|
||||
http://..../[app]/default/user/manage_users (requires membership in
|
||||
use @auth.requires_login()
|
||||
@auth.requires_membership('group name')
|
||||
@auth.requires_permission('read','table name',record_id)
|
||||
@@ -38,7 +39,6 @@ def user():
|
||||
"""
|
||||
return dict(form=auth())
|
||||
|
||||
|
||||
@cache.action()
|
||||
def download():
|
||||
"""
|
||||
|
||||
File diff suppressed because one or more lines are too long
8
applications/welcome/static/js/jquery.js
vendored
8
applications/welcome/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
|
||||
jQuery('.hidden', target).hide();
|
||||
jQuery('.error', target).hide().slideDown('slow');
|
||||
web2py_ajax_fields(target);
|
||||
web2py_show_if(target);
|
||||
};
|
||||
|
||||
function web2py_event_handlers() {
|
||||
@@ -216,3 +217,26 @@ function web2py_validate_entropy(myfield, req_entropy) {
|
||||
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
|
||||
}
|
||||
|
||||
function web2py_show_if(target) {
|
||||
var triggers = {};
|
||||
var show_if = function () {
|
||||
var t = jQuery(this);
|
||||
var id = t.attr('id');
|
||||
t.attr('value', t.val());
|
||||
for(var k = 0; k < triggers[id].length; k++) {
|
||||
var dep = jQuery('#' + triggers[id][k], target);
|
||||
var tr = jQuery('#' + triggers[id][k] + '__row', target);
|
||||
if(t.is(dep.attr('data-show-if'))) tr.slideDown();
|
||||
else tr.hide();
|
||||
}
|
||||
};
|
||||
jQuery('[data-show-trigger]', target).each(function () {
|
||||
var name = jQuery(this).attr('data-show-trigger');
|
||||
if(!triggers[name]) triggers[name] = [];
|
||||
triggers[name].push(jQuery(this).attr('id'));
|
||||
});
|
||||
for(var name in triggers) {
|
||||
jQuery('#' + name, target).change(show_if).keyup(show_if);
|
||||
show_if.call(jQuery('#' + name, target));
|
||||
};
|
||||
}
|
||||
@@ -247,3 +247,39 @@
|
||||
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
|
||||
{{if request.function == 'auth_manage':}}
|
||||
<h2>{{=T('Manage Access Control')}}</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#users" data-toggle="tab">Users</a></li>
|
||||
<li><a href="#roles" data-toggle="tab">Roles</a></li>
|
||||
<li><a href="#permissions" data-toggle="tab">Permissions</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="users">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_user_name,ajax=True)}}
|
||||
</div>
|
||||
<div class="tab-pane" id="roles">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_group_name,ajax=True)}}
|
||||
</div>
|
||||
<div class="tab-pane" id="permissions">
|
||||
{{=LOAD(f='auth_manage.load', args=auth.settings.table_permission_name,ajax=True)}}
|
||||
</div>
|
||||
</div>
|
||||
{{elif request.function == 'manage':}}
|
||||
<h2>{{=T('Manage Access Control')}}</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
{{for k,tablename in enumerate(auth.db.tables):}}
|
||||
<li><a href="#table-{{=tablename}}" data-toggle="tab">{{=tablename}}</a></li>
|
||||
{{pass}}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{{for tablename in auth.db.tables:}}
|
||||
<div class="tab-pane" id="table-{{=tablename}}">
|
||||
{{=LOAD(f='manage.load', args=tablename,ajax=True)}}
|
||||
</div>
|
||||
{{pass}}
|
||||
</div>
|
||||
{{pass}}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{extend 'layout.html'}}
|
||||
|
||||
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
|
||||
<div id="web2py_user_form">
|
||||
{{
|
||||
@@ -21,3 +22,4 @@ jQuery("#web2py_user_form input:visible:enabled:first").focus();
|
||||
web2py_validate_entropy(jQuery('#no_table_new_password'),100);
|
||||
{{pass}}
|
||||
//--></script>
|
||||
|
||||
|
||||
@@ -52,12 +52,8 @@ class DropboxAccount(object):
|
||||
self.sess = session.DropboxSession(
|
||||
self.key, self.secret, self.access_type)
|
||||
|
||||
def get_user(self):
|
||||
request = self.request
|
||||
if not current.session.dropbox_request_token:
|
||||
return None
|
||||
elif not current.session.dropbox_access_token:
|
||||
|
||||
def get_token(self):
|
||||
if not current.session.dropbox_access_token:
|
||||
request_token = current.session.dropbox_request_token
|
||||
self.sess.set_request_token(request_token[0], request_token[1])
|
||||
access_token = self.sess.obtain_access_token(self.sess.token)
|
||||
@@ -67,6 +63,10 @@ class DropboxAccount(object):
|
||||
access_token = current.session.dropbox_access_token
|
||||
self.sess.set_token(access_token[0], access_token[1])
|
||||
|
||||
def get_user(self):
|
||||
if not current.session.dropbox_request_token:
|
||||
return None
|
||||
self.get_token()
|
||||
user = Storage()
|
||||
self.client = client.DropboxClient(self.sess)
|
||||
data = self.client.account_info()
|
||||
@@ -99,8 +99,7 @@ class DropboxAccount(object):
|
||||
return next
|
||||
|
||||
def get_client(self):
|
||||
access_token = current.session.dropbox_access_token
|
||||
self.sess.set_token(access_token[0], access_token[1])
|
||||
self.get_token()
|
||||
self.client = client.DropboxClient(self.sess)
|
||||
|
||||
def put(self, filename, file):
|
||||
|
||||
62
gluon/dal.py
62
gluon/dal.py
@@ -683,12 +683,6 @@ class BaseAdapter(ConnectionPool):
|
||||
return str(obj)
|
||||
return self.adapt(str(obj))
|
||||
|
||||
def integrity_error(self):
|
||||
return self.driver.IntegrityError
|
||||
|
||||
def operational_error(self):
|
||||
return self.driver.OperationalError
|
||||
|
||||
def file_exists(self, filename):
|
||||
"""
|
||||
to be used ONLY for files that on GAE may not be on filesystem
|
||||
@@ -738,7 +732,6 @@ class BaseAdapter(ConnectionPool):
|
||||
else:
|
||||
raise RuntimeError("no driver available %s" % str(self.drivers))
|
||||
|
||||
|
||||
def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8',
|
||||
credential_decoder=IDENTITY, driver_args={},
|
||||
adapter_args={},do_connect=True, after_connection=None):
|
||||
@@ -1212,8 +1205,8 @@ class BaseAdapter(ConnectionPool):
|
||||
self.execute(query)
|
||||
except Exception:
|
||||
e = sys.exc_info()[1]
|
||||
if isinstance(e,self.integrity_error_class()):
|
||||
return None
|
||||
if hasattr(table,'_on_insert_error'):
|
||||
return table._on_insert_error(table,fields,e)
|
||||
raise e
|
||||
if hasattr(table,'_primarykey'):
|
||||
return dict([(k[0].name, k[1]) for k in fields \
|
||||
@@ -1449,7 +1442,14 @@ class BaseAdapter(ConnectionPool):
|
||||
|
||||
def update(self, tablename, query, fields):
|
||||
sql = self._update(tablename, query, fields)
|
||||
self.execute(sql)
|
||||
try:
|
||||
self.execute(sql)
|
||||
except Exception:
|
||||
e = sys.exc_info()[1]
|
||||
table = self.db[tablename]
|
||||
if hasattr(table,'_on_update_error'):
|
||||
return table._on_update_error(table,query,fields,e)
|
||||
raise e
|
||||
try:
|
||||
return self.cursor.rowcount
|
||||
except:
|
||||
@@ -1873,9 +1873,6 @@ class BaseAdapter(ConnectionPool):
|
||||
def lastrowid(self, table):
|
||||
return None
|
||||
|
||||
def integrity_error_class(self):
|
||||
return type(None)
|
||||
|
||||
def rowslice(self, rows, minimum=0, maximum=None):
|
||||
"""
|
||||
By default this function does nothing;
|
||||
@@ -2522,8 +2519,6 @@ class MySQLAdapter(BaseAdapter):
|
||||
self.execute('select last_insert_id();')
|
||||
return int(self.cursor.fetchone()[0])
|
||||
|
||||
def integrity_error_class(self):
|
||||
return self.cursor.IntegrityError
|
||||
|
||||
class PostgreSQLAdapter(BaseAdapter):
|
||||
drivers = ('psycopg2','pg8000')
|
||||
@@ -2672,7 +2667,9 @@ class PostgreSQLAdapter(BaseAdapter):
|
||||
elif self.driver_name == "zxJDBC":
|
||||
supports_json = self.connection.dbversion >= "9.2.0"
|
||||
else: supports_json = None
|
||||
if supports_json: self.types["json"] = "JSON"
|
||||
if supports_json:
|
||||
self.types["json"] = "JSON"
|
||||
self.native_json = True
|
||||
else: LOGGER.debug("Your database version does not support the JSON data type (using TEXT instead)")
|
||||
|
||||
def LIKE(self,first,second):
|
||||
@@ -3216,9 +3213,6 @@ class MSSQLAdapter(BaseAdapter):
|
||||
self.execute('SELECT SCOPE_IDENTITY();')
|
||||
return long(self.cursor.fetchone()[0])
|
||||
|
||||
def integrity_error_class(self):
|
||||
return pyodbc.IntegrityError
|
||||
|
||||
def rowslice(self,rows,minimum=0,maximum=None):
|
||||
if maximum is None:
|
||||
return rows[minimum:]
|
||||
@@ -3482,9 +3476,6 @@ class SybaseAdapter(MSSQLAdapter):
|
||||
self.connector = connector
|
||||
if do_connect: self.reconnect()
|
||||
|
||||
def integrity_error_class(self):
|
||||
return RuntimeError # FIX THIS
|
||||
|
||||
|
||||
class FireBirdAdapter(BaseAdapter):
|
||||
drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
|
||||
@@ -3775,9 +3766,6 @@ class InformixAdapter(BaseAdapter):
|
||||
def lastrowid(self,table):
|
||||
return self.cursor.sqlerrd[1]
|
||||
|
||||
def integrity_error_class(self):
|
||||
return informixdb.IntegrityError
|
||||
|
||||
class InformixSEAdapter(InformixAdapter):
|
||||
""" work in progress """
|
||||
|
||||
@@ -4050,9 +4038,6 @@ class IngresAdapter(BaseAdapter):
|
||||
self.execute('select current value for %s' % tmp_seqname)
|
||||
return long(self.cursor.fetchone()[0]) # don't really need int type cast here...
|
||||
|
||||
def integrity_error_class(self):
|
||||
return self._driver.IntegrityError
|
||||
|
||||
|
||||
class IngresUnicodeAdapter(IngresAdapter):
|
||||
|
||||
@@ -4528,7 +4513,6 @@ class NoSQLAdapter(BaseAdapter):
|
||||
def execute(self,*a,**b): raise SyntaxError("Not supported")
|
||||
def represent_exceptions(self, obj, fieldtype): raise SyntaxError("Not supported")
|
||||
def lastrowid(self,table): raise SyntaxError("Not supported")
|
||||
def integrity_error_class(self): raise SyntaxError("Not supported")
|
||||
def rowslice(self,rows,minimum=0,maximum=None): raise SyntaxError("Not supported")
|
||||
|
||||
|
||||
@@ -8425,7 +8409,7 @@ class Table(object):
|
||||
return rows[0]
|
||||
return None
|
||||
elif str(key).isdigit() or 'google' in DRIVERS and isinstance(key, Key):
|
||||
return self._db(self._id == key).select(limitby=(0,1)).first()
|
||||
return self._db(self._id == key).select(limitby=(0,1), orderby_on_limitby=False).first()
|
||||
elif key:
|
||||
return ogetattr(self, str(key))
|
||||
|
||||
@@ -8439,19 +8423,19 @@ class Table(object):
|
||||
if not key is DEFAULT:
|
||||
if isinstance(key, Query):
|
||||
record = self._db(key).select(
|
||||
limitby=(0,1),for_update=for_update, orderby=orderby).first()
|
||||
limitby=(0,1),for_update=for_update, orderby=orderby, orderby_on_limitby=False).first()
|
||||
elif not str(key).isdigit():
|
||||
record = None
|
||||
else:
|
||||
record = self._db(self._id == key).select(
|
||||
limitby=(0,1),for_update=for_update, orderby=orderby).first()
|
||||
limitby=(0,1),for_update=for_update, orderby=orderby, orderby_on_limitby=False).first()
|
||||
if record:
|
||||
for k,v in kwargs.iteritems():
|
||||
if record[k]!=v: return None
|
||||
return record
|
||||
elif kwargs:
|
||||
query = reduce(lambda a,b:a&b,[self[k]==v for k,v in kwargs.iteritems()])
|
||||
return self._db(query).select(limitby=(0,1),for_update=for_update, orderby=orderby).first()
|
||||
return self._db(query).select(limitby=(0,1),for_update=for_update, orderby=orderby, orderby_on_limitby=False).first()
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -8709,6 +8693,12 @@ class Table(object):
|
||||
value = None
|
||||
elif field.type=='blob':
|
||||
value = base64.b64decode(value)
|
||||
elif field.type=='json':
|
||||
try:
|
||||
json = serializers.json
|
||||
value = json(value)
|
||||
except TypeError:
|
||||
pass
|
||||
elif field.type=='double' or field.type=='float':
|
||||
if not value.strip():
|
||||
value = None
|
||||
@@ -9214,7 +9204,7 @@ class FieldVirtual(object):
|
||||
(self.name, self.f) = (name, f) if f else ('unkown', name)
|
||||
self.type = ftype
|
||||
self.label = label or self.name.capitalize().replace('_',' ')
|
||||
self.represent = IDENTITY
|
||||
self.represent = lambda v,r:v
|
||||
self.formatter = IDENTITY
|
||||
self.comment = None
|
||||
self.readable = True
|
||||
@@ -9223,6 +9213,8 @@ class FieldVirtual(object):
|
||||
self.widget = None
|
||||
self.tablename = table_name
|
||||
self.filter_out = None
|
||||
def __str__(self):
|
||||
return '%s.%s' % (self.tablename, self.name)
|
||||
|
||||
class FieldMethod(object):
|
||||
def __init__(self, name, f=None, handler=None):
|
||||
@@ -10362,7 +10354,7 @@ class Rows(object):
|
||||
# test for multiple rows
|
||||
multi = False
|
||||
f = self.first()
|
||||
if f:
|
||||
if f and isinstance(key, basestring):
|
||||
multi = any([isinstance(v, f.__class__) for v in f.values()])
|
||||
if (not "." in key) and multi:
|
||||
# No key provided, default to int indices
|
||||
|
||||
@@ -1198,6 +1198,13 @@ def TAG_pickler(data):
|
||||
return (TAG_unpickler, (marshal_dump,))
|
||||
|
||||
|
||||
class __tag__(DIV):
|
||||
def __init__(self,name,*a,**b):
|
||||
DIV.__init__(self,*a,**b)
|
||||
self.tag = name
|
||||
|
||||
copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
|
||||
|
||||
class __TAG__(XmlComponent):
|
||||
|
||||
"""
|
||||
@@ -1216,11 +1223,7 @@ class __TAG__(XmlComponent):
|
||||
name = name[:-1] + '/'
|
||||
if isinstance(name, unicode):
|
||||
name = name.encode('utf-8')
|
||||
|
||||
class __tag__(DIV):
|
||||
tag = name
|
||||
copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
|
||||
return lambda *a, **b: __tag__(*a, **b)
|
||||
return lambda *a,**b: __tag__(name,*a,**b)
|
||||
|
||||
def __call__(self, html):
|
||||
return web2pyHTMLParser(decoder.decoder(html)).tree
|
||||
|
||||
@@ -344,7 +344,7 @@ def parse_get_post_vars(request, environ):
|
||||
|
||||
|
||||
# parse POST variables on POST, PUT, BOTH only in post_vars
|
||||
if (body and env.request_method in ('POST', 'PUT', 'BOTH')):
|
||||
if (body and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
|
||||
dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
|
||||
# The same detection used by FieldStorage to detect multipart POSTs
|
||||
is_multipart = dpost.type[:10] == 'multipart/'
|
||||
|
||||
@@ -18,6 +18,7 @@ try:
|
||||
except ImportError:
|
||||
from cgi import parse_qs as psq
|
||||
import os
|
||||
import copy
|
||||
from http import HTTP
|
||||
from html import XmlComponent
|
||||
from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT
|
||||
@@ -81,6 +82,27 @@ def safe_float(x):
|
||||
return 0
|
||||
|
||||
|
||||
def show_if(cond):
|
||||
if not cond:
|
||||
return None
|
||||
base = "%s_%s" % (cond.first.tablename, cond.first.name)
|
||||
if ((cond.op.__name__ == 'EQ' and cond.second == True) or
|
||||
(cond.op.__name__ == 'NE' and cond.second == False)):
|
||||
return base,":checked"
|
||||
if ((cond.op.__name__ == 'EQ' and cond.second == False) or
|
||||
(cond.op.__name__ == 'NE' and cond.second == True)):
|
||||
return base,":not(:checked)"
|
||||
if cond.op.__name__ == 'EQ':
|
||||
return base,"[value='%s']" % cond.second
|
||||
if cond.op.__name__ == 'NE':
|
||||
return base,"[value!='%s']" % cond.second
|
||||
if cond.op.__name__ == 'CONTAINS':
|
||||
return base,"[value~='%s']" % cond.second
|
||||
if cond.op.__name__ == 'BELONGS' and isinstance(cond.second,(list,tuple)):
|
||||
return base,','.join("[value='%s']" % (v) for v in cond.second)
|
||||
raise RuntimeError("Not Implemented Error")
|
||||
|
||||
|
||||
class FormWidget(object):
|
||||
"""
|
||||
helper for SQLFORM to generate form input fields
|
||||
@@ -101,12 +123,16 @@ class FormWidget(object):
|
||||
:param attributes: any other supplied attributes
|
||||
"""
|
||||
attr = dict(
|
||||
_id='%s_%s' % (field._tablename, field.name),
|
||||
_id='%s_%s' % (field.tablename, field.name),
|
||||
_class=cls._class or
|
||||
widget_class.match(str(field.type)).group(),
|
||||
_name=field.name,
|
||||
_name=field.name,
|
||||
requires=field.requires,
|
||||
)
|
||||
if getattr(field,'show_if',None):
|
||||
trigger, cond = show_if(field.show_if)
|
||||
attr['_data-show-trigger'] = trigger
|
||||
attr['_data-show-if'] = cond
|
||||
attr.update(widget_attributes)
|
||||
attr.update(attributes)
|
||||
return attr
|
||||
@@ -261,7 +287,7 @@ class ListWidget(StringWidget):
|
||||
|
||||
@classmethod
|
||||
def widget(cls, field, value, **attributes):
|
||||
_id = '%s_%s' % (field._tablename, field.name)
|
||||
_id = '%s_%s' % (field.tablename, field.name)
|
||||
_name = field.name
|
||||
if field.type == 'list:integer':
|
||||
_class = 'integer'
|
||||
@@ -625,7 +651,8 @@ class AutocompleteWidget(object):
|
||||
self.help_fields = help_fields or []
|
||||
self.help_string = help_string
|
||||
if self.help_fields and not self.help_string:
|
||||
self.help_string = ' '.join('%%(%s)s' for f in self.help_fields)
|
||||
self.help_string = ' '.join('%%(%s)s'%f.name
|
||||
for f in self.help_fields)
|
||||
|
||||
self.request = request
|
||||
self.keyword = keyword % dict(tablename=field.tablename,
|
||||
@@ -651,9 +678,9 @@ class AutocompleteWidget(object):
|
||||
if self.keyword in self.request.vars:
|
||||
field = self.fields[0]
|
||||
if is_gae:
|
||||
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *self.fields)
|
||||
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_field))
|
||||
else:
|
||||
rows = self.db(field.like(self.request.vars[self.keyword] + '%')).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *self.fields)
|
||||
rows = self.db(field.like(self.request.vars[self.keyword] + '%')).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_field))
|
||||
if rows:
|
||||
if self.is_reference:
|
||||
id_field = self.fields[1]
|
||||
@@ -1740,6 +1767,7 @@ class SQLFORM(FORM):
|
||||
return CAT(
|
||||
DIV(_id=panel_id, _style="display:none;", *criteria), fadd)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def grid(query,
|
||||
fields=None,
|
||||
@@ -1930,14 +1958,20 @@ class SQLFORM(FORM):
|
||||
for join in left:
|
||||
tablenames += db._adapter.tables(join)
|
||||
tables = [db[tablename] for tablename in tablenames]
|
||||
if not fields:
|
||||
fields = reduce(lambda a, b: a + b,
|
||||
[[field for field in table] for table in tables])
|
||||
if fields:
|
||||
columns = copy.copy(fields)
|
||||
else:
|
||||
fields = []
|
||||
columns = []
|
||||
for table in tables:
|
||||
fields += [f for f in table]
|
||||
columns += [f for f in table]
|
||||
for k,f in table.iteritems():
|
||||
if isinstance(f,Field.Virtual) and f.readable:
|
||||
f.tablename = table._tablename
|
||||
columns.append(f)
|
||||
if not field_id:
|
||||
field_id = tables[0]._id
|
||||
columns = [str(field) for field in fields
|
||||
if field._tablename in tablenames]
|
||||
|
||||
if not any(str(f)==str(field_id) for f in fields):
|
||||
fields = [f for f in fields]+[field_id]
|
||||
table = field_id.table
|
||||
@@ -2087,12 +2121,12 @@ class SQLFORM(FORM):
|
||||
if sign == '~':
|
||||
orderby = ~orderby
|
||||
|
||||
expcolumns = columns
|
||||
expcolumns = [str(f) for f in columns]
|
||||
if export_type.endswith('with_hidden_cols'):
|
||||
expcolumns = []
|
||||
for table in tables:
|
||||
for field in table:
|
||||
if field.readable and field._tablename in tablenames:
|
||||
if field.readable and field.tablename in tablenames:
|
||||
expcolumns.append(field)
|
||||
|
||||
if export_type in exportManager and exportManager[export_type]:
|
||||
@@ -2193,9 +2227,7 @@ class SQLFORM(FORM):
|
||||
headcols = []
|
||||
if selectable:
|
||||
headcols.append(TH(_class=ui.get('default')))
|
||||
for field in fields:
|
||||
if columns and not str(field) in columns:
|
||||
continue
|
||||
for field in columns:
|
||||
if not field.readable:
|
||||
continue
|
||||
key = str(field)
|
||||
@@ -2249,7 +2281,7 @@ class SQLFORM(FORM):
|
||||
limitby = None
|
||||
|
||||
try:
|
||||
table_fields = [f for f in fields if f._tablename in tablenames]
|
||||
table_fields = [f for f in fields if f.tablename in tablenames]
|
||||
if dbset._db._adapter.dbengine=='google:datastore':
|
||||
rows = dbset.select(left=left,orderby=orderby,
|
||||
groupby=groupby,limitby=limitby,
|
||||
@@ -2337,14 +2369,12 @@ class SQLFORM(FORM):
|
||||
trcols.append(
|
||||
INPUT(_type="checkbox", _name="records", _value=id,
|
||||
value=request.vars.records))
|
||||
for field in fields:
|
||||
if not str(field) in columns:
|
||||
continue
|
||||
for field in columns:
|
||||
if not field.readable:
|
||||
continue
|
||||
if field.type == 'blob':
|
||||
continue
|
||||
value = row[field]
|
||||
value = row[str(field)]
|
||||
maxlength = maxtextlengths.get(str(field), maxtextlength)
|
||||
if field.represent:
|
||||
try:
|
||||
@@ -2352,7 +2382,7 @@ class SQLFORM(FORM):
|
||||
except KeyError:
|
||||
try:
|
||||
value = field.represent(
|
||||
value, row[field._tablename])
|
||||
value, row[field.tablename])
|
||||
except KeyError:
|
||||
pass
|
||||
elif field.type == 'boolean':
|
||||
@@ -3102,3 +3132,4 @@ class ExporterJSON(ExportClass):
|
||||
return self.rows.as_json()
|
||||
else:
|
||||
return 'null'
|
||||
|
||||
|
||||
@@ -878,6 +878,7 @@ class Auth(object):
|
||||
alternate_requires_registration=False,
|
||||
create_user_groups="user_%(id)s",
|
||||
everybody_group_id=None,
|
||||
manager_group_role=None,
|
||||
login_captcha=None,
|
||||
register_captcha=None,
|
||||
retrieve_username_captcha=None,
|
||||
@@ -3054,7 +3055,7 @@ class Auth(object):
|
||||
return self.has_permission(name, table_name, record_id)
|
||||
return self.requires(has_permission, otherwise=otherwise)
|
||||
|
||||
def requires_signature(self, otherwise=None):
|
||||
def requires_signature(self, otherwise=None, hash_vars=True):
|
||||
"""
|
||||
decorator that prevents access to action if not logged in or
|
||||
if user logged in is not a member of group_id.
|
||||
@@ -3062,7 +3063,7 @@ class Auth(object):
|
||||
group_id is calculated.
|
||||
"""
|
||||
def verify():
|
||||
return URL.verify(current.request, user_signature=True)
|
||||
return URL.verify(current.request, user_signature=True, hash_vars=hash_vars)
|
||||
return self.requires(verify, otherwise)
|
||||
|
||||
def add_group(self, role, description=''):
|
||||
|
||||
@@ -315,15 +315,21 @@ class IS_LENGTH(Validator):
|
||||
length = 0
|
||||
if self.minsize <= length <= self.maxsize:
|
||||
return (value, None)
|
||||
elif isinstance(value, (str, unicode, list)):
|
||||
elif isinstance(value, str):
|
||||
try:
|
||||
lvalue = len(value.decode('utf8'))
|
||||
except:
|
||||
lvalue = len(value)
|
||||
if self.minsize <= lvalue <= self.maxsize:
|
||||
return (value, None)
|
||||
elif isinstance(value, unicode):
|
||||
if self.minsize <= len(value) <= self.maxsize:
|
||||
return (value.encode('utf8'), None)
|
||||
elif isinstance(value, (tuple, list)):
|
||||
if self.minsize <= len(value) <= self.maxsize:
|
||||
return (value, None)
|
||||
elif self.minsize <= len(str(value)) <= self.maxsize:
|
||||
try:
|
||||
value.decode('utf8')
|
||||
return (value, None)
|
||||
except:
|
||||
pass
|
||||
return (str(value), None)
|
||||
return (value, translate(self.error_message)
|
||||
% dict(min=self.minsize, max=self.maxsize))
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ NameVirtualHost *:443
|
||||
</Files>
|
||||
</Directory>
|
||||
|
||||
AliasMatch ^/([^/]+)/static/(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
|
||||
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
|
||||
|
||||
<Directory /opt/web-apps/web2py/applications/*/static>
|
||||
Options -Indexes
|
||||
@@ -352,7 +352,7 @@ NameVirtualHost *:443
|
||||
</Files>
|
||||
</Directory>
|
||||
|
||||
AliasMatch ^/([^/]+)/static/(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
|
||||
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
|
||||
|
||||
<Directory /opt/web-apps/web2py/applications/*/static>
|
||||
Options -Indexes
|
||||
|
||||
@@ -106,7 +106,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
|
||||
</Files>
|
||||
</Directory>
|
||||
|
||||
AliasMatch ^/([^/]+)/static/(.*) \
|
||||
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
|
||||
/home/www-data/web2py/applications/$1/static/$2
|
||||
<Directory /home/www-data/web2py/applications/*/static/>
|
||||
Options -Indexes
|
||||
@@ -144,7 +144,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
|
||||
</Files>
|
||||
</Directory>
|
||||
|
||||
AliasMatch ^/([^/]+)/static/(.*) \
|
||||
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
|
||||
/home/www-data/web2py/applications/$1/static/$2
|
||||
|
||||
<Directory /home/www-data/web2py/applications/*/static/>
|
||||
@@ -167,7 +167,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
|
||||
# ln -s /etc/pam.d/apache2 /etc/pam.d/httpd
|
||||
# usermod -a -G shadow www-data
|
||||
|
||||
echo "restarting apage"
|
||||
echo "restarting apache"
|
||||
echo "================"
|
||||
|
||||
/etc/init.d/apache2 restart
|
||||
|
||||
114
setup_app.py
114
setup_app.py
@@ -8,11 +8,28 @@ Usage:
|
||||
python setup.py py2app
|
||||
"""
|
||||
|
||||
copy_apps = False
|
||||
copy_scripts = True
|
||||
copy_site_packages = True
|
||||
remove_build_files = True
|
||||
make_zip = True
|
||||
zip_filename = "web2py_osx"
|
||||
|
||||
from setuptools import setup
|
||||
from gluon.import_all import base_modules, contributed_modules
|
||||
from gluon.fileutils import readlines_file
|
||||
import os
|
||||
import fnmatch
|
||||
import shutil
|
||||
import sys
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
#read web2py version from VERSION file
|
||||
web2py_version_line = readlines_file('VERSION')[0]
|
||||
#use regular expression to get just the version number
|
||||
v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+')
|
||||
web2py_version = v_re.search(web2py_version_line).group(0)
|
||||
|
||||
class reglob:
|
||||
def __init__(self, directory, pattern="*"):
|
||||
@@ -39,18 +56,105 @@ class reglob:
|
||||
return fullname
|
||||
|
||||
setup(app=['web2py.py'],
|
||||
version=web2py_version,
|
||||
description="web2py web framework",
|
||||
author="Massimo DiPierro",
|
||||
license="LGPL v3",
|
||||
data_files=[
|
||||
'NEWINSTALL',
|
||||
'ABOUT',
|
||||
'LICENSE',
|
||||
'VERSION',
|
||||
] +
|
||||
[x for x in reglob('applications/examples')] +
|
||||
[x for x in reglob('applications/welcome')] +
|
||||
[x for x in reglob('applications/admin')],
|
||||
'splashlogo.gif',
|
||||
'logging.example.conf',
|
||||
'options_std.py',
|
||||
],
|
||||
options={'py2app': {
|
||||
'argv_emulation': True,
|
||||
'includes': base_modules,
|
||||
'packages': contributed_modules,
|
||||
}},
|
||||
setup_requires=['py2app'])
|
||||
|
||||
|
||||
def copy_folders(source, destination):
|
||||
"""Copy files & folders from source to destination (within dist/)"""
|
||||
print 'copying %s -> %s' % (source, destination)
|
||||
base = 'dist/web2py.app/Contents/Resources/'
|
||||
if os.path.exists(os.path.join(base, destination)):
|
||||
shutil.rmtree(os.path.join(base, destination))
|
||||
shutil.copytree(os.path.join(source), os.path.join(base, destination))
|
||||
|
||||
#Should we include applications?
|
||||
copy_folders('gluon','gluon')
|
||||
|
||||
if copy_apps:
|
||||
copy_folders('applications', 'applications')
|
||||
print "Your application(s) have been added"
|
||||
else:
|
||||
#only copy web2py's default applications
|
||||
copy_folders('applications/admin', 'applications/admin')
|
||||
copy_folders('applications/welcome', 'applications/welcome')
|
||||
copy_folders('applications/examples', 'applications/examples')
|
||||
print "Only web2py's admin, examples & welcome applications have been added"
|
||||
|
||||
|
||||
#should we copy project's site-packages into dist/site-packages
|
||||
if copy_site_packages:
|
||||
#copy site-packages
|
||||
copy_folders('site-packages', 'site-packages')
|
||||
else:
|
||||
#no worries, web2py will create the (empty) folder first run
|
||||
print "Skipping site-packages"
|
||||
pass
|
||||
|
||||
#should we copy project's scripts into dist/scripts
|
||||
if copy_scripts:
|
||||
#copy scripts
|
||||
copy_folders('scripts', 'scripts')
|
||||
else:
|
||||
#no worries, web2py will create the (empty) folder first run
|
||||
print "Skipping scripts"
|
||||
pass
|
||||
|
||||
|
||||
#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile
|
||||
def recursive_zip(zipf, directory, folder=""):
|
||||
for item in os.listdir(directory):
|
||||
if os.path.isfile(os.path.join(directory, item)):
|
||||
zipf.write(os.path.join(directory, item), folder + os.sep + item)
|
||||
elif os.path.isdir(os.path.join(directory, item)):
|
||||
recursive_zip(
|
||||
zipf, os.path.join(directory, item), folder + os.sep + item)
|
||||
|
||||
#should we create a zip file of the build?
|
||||
|
||||
if make_zip:
|
||||
#to keep consistent with how official web2py windows zip file is setup,
|
||||
#create a web2py folder & copy dist's files into it
|
||||
shutil.copytree('dist', 'zip_temp/web2py')
|
||||
#create zip file
|
||||
#use filename specified via command line
|
||||
zipf = zipfile.ZipFile(
|
||||
zip_filename + ".zip", "w", compression=zipfile.ZIP_DEFLATED)
|
||||
path = 'zip_temp' # just temp so the web2py directory is included in our zip file
|
||||
recursive_zip(
|
||||
zipf, path) # leave the first folder as None, as path is root.
|
||||
zipf.close()
|
||||
shutil.rmtree('zip_temp')
|
||||
print "Your Windows binary version of web2py can be found in " + \
|
||||
zip_filename + ".zip"
|
||||
print "You may extract the archive anywhere and then run web2py/web2py.exe"
|
||||
|
||||
#should py2exe build files be removed?
|
||||
if remove_build_files:
|
||||
shutil.rmtree('build')
|
||||
shutil.rmtree('deposit')
|
||||
shutil.rmtree('dist')
|
||||
print "py2exe build files removed"
|
||||
|
||||
#final info
|
||||
if not make_zip and not remove_build_files:
|
||||
print "Your Windows binary & associated files can also be found in /dist"
|
||||
|
||||
print "Finished!"
|
||||
print "Enjoy web2py " + web2py_version_line
|
||||
|
||||
Reference in New Issue
Block a user