Compare commits

...
35 Commits
Author SHA1 Message Date
Massimo f463de5a45 R-2.5.1 2013-06-06 10:36:33 -05:00
mdipierro 8829780def Merge pull request #101 from rpedroso/anyserver-eventlet
monkey patch eventlet
2013-06-06 08:33:36 -07:00
mdipierro a302d52698 Merge pull request #102 from mbroadst/post-vars-in-delete-requests
allow to process post vars in DELETE requests
2013-06-06 08:32:47 -07:00
Massimo 00087198e0 R-2.5.1 2013-06-06 10:24:14 -05:00
Massimo ea3aa62476 fixed show_if with LOAD, thanks Niphlod 2013-06-04 16:41:05 -05:00
Matt Broadstone 176c9f4ad9 allow to process post vars in DELETE requests 2013-06-04 15:26:32 -04:00
Massimo 0fdab02d68 fixed auth.settings.manager_group_role 2013-06-04 09:31:53 -05:00
mdipierro 0fcb5f2ccd fixed missing import in last commit, thanks Tim 2013-06-03 22:41:24 -05:00
mdipierro 27287d246a fixed Issue 1512:AutocompleteWidget, help_fields parameter, thanks Mirek 2013-06-03 22:17:11 -05:00
mdipierro 2234cd245c fixed memory leak in issue 1493 2013-06-03 22:12:05 -05:00
mdipierro c5980cd312 grid displays Virtual Fields 2013-06-03 21:48:55 -05:00
Ricardo Pedroso 4e8dbffd0e monkey patch eventlet 2013-06-04 00:28:54 +01:00
mdipierro 50dcd7a572 conditional fields 2013-06-03 17:48:13 -05:00
mdipierro 35f1cca768 Merge pull request #99 from michele-comitini/master
removed useless orderby
2013-06-02 19:44:44 -07:00
mdipierro e5bde6c0b1 Merge pull request #100 from niphlod/issue/static_version
fixed serving static files with static_version
2013-06-02 19:43:45 -07:00
mdipierro d31bba2670 No more default capture of integrity error 2013-06-01 18:24:44 -05:00
niphlod 4f806a1db9 fixed serving static files with static_version 2013-06-01 14:02:48 +02:00
mdipierro f64232df30 change in appadmin auth 2013-05-31 21:34:38 -05:00
mdipierro ecc2f0004c removed manage from auth 2013-05-31 21:31:21 -05:00
mdipierro 4f2e327d33 appadmin/auth_manage and appadmin/manage, thanks Anthony 2013-05-31 21:29:28 -05:00
Massimo 3ce3171dff user/manage, thanks Anthony 2013-05-31 11:25:56 -05:00
Massimo 0850cadfdc allow hash_vars in verify signature, thanks Anthony 2013-05-31 10:41:28 -05:00
Michele Comitini 53a5a44def Merge ssh://github.com/web2py/web2py 2013-05-31 15:56:44 +02:00
Michele Comitini ad1d414485 removed automatic orderby on db.table[id] expression and db.table(id) call 2013-05-31 15:55:52 +02:00
mdipierro ba22f9a3a5 new twitter widget in admin, deleted old 2013-05-31 08:29:19 -05:00
mdipierro 51de1740f9 new twitter widget in admin 2013-05-31 08:20:34 -05:00
Massimo 1c012b92d5 possibly fixed dropbox_auth bug? 2013-05-30 12:25:21 -05:00
Massimo 7377922211 upgraded timeEntry in calendar, now works with latest jQuery 2013-05-30 12:10:01 -05:00
mdipierro fae120f1ea new setup_app and Makefile for OSX python 2.7 2013-05-29 11:40:07 -05:00
mdipierro a915054602 reverting https://code.google.com/p/web2py/issues/detail?id=1500 as discussed https://groups.google.com/ dE-Fu9prKyI 2013-05-28 22:45:15 -05:00
mdipierro dd97b9c8dd fixed as_dict for functions, thanks Martin Hufsky and Alan 2013-05-27 11:50:31 -05:00
mdipierro d1e25796e9 fixed Issue 1505:Json fields imported from csv do not work with postgresql, thanks anonoofish 2013-05-27 10:41:57 -05:00
mdipierro e38cfc5767 fixed issue 1504, static asset management config in app.yaml, thanks Niphlod 2013-05-27 10:37:38 -05:00
mdipierro 69c888d071 Issue 1506: length argument wrong with unicode 2013-05-27 10:33:37 -05:00
mdipierro 0138b1782d jQuery 1.10.0 2013-05-25 18:36:32 -05:00
32 changed files with 582 additions and 158 deletions
+9
View File
@@ -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 ## 2.4.7
- pypy support, thanks Niphlod - pypy support, thanks Niphlod
+5 -4
View File
@@ -30,7 +30,7 @@ update:
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: src:
### Use semantic versioning ### 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 ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
@@ -64,9 +64,10 @@ app:
python2.7 -c 'import compileall; compileall.compile_dir("gluon/")' python2.7 -c 'import compileall; compileall.compile_dir("gluon/")'
#python web2py.py -S welcome -R __exit__.py #python web2py.py -S welcome -R __exit__.py
#cd ../web2py_osx/site-packages/; unzip ../site-packages.zip #cd ../web2py_osx/site-packages/; unzip ../site-packages.zip
find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \; #find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip * #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 #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 README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources
cp NEWINSTALL ../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 cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources
+1 -1
View File
@@ -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
+3
View File
@@ -180,6 +180,9 @@ def run(servername, ip, port, softcron=True, logging=False, profiler=None):
if servername == 'gevent': if servername == 'gevent':
from gevent import monkey from gevent import monkey
monkey.patch_all() monkey.patch_all()
elif servername == 'eventlet':
import eventlet
eventlet.monkey_patch()
import gluon.main import gluon.main
+7 -1
View File
@@ -25,7 +25,13 @@ handlers:
# the parametric router's language logic. # the parametric router's language logic.
# You cannot use them together. # 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 static_files: applications/\1/static/\2
upload: applications/(.+?)/static/(.+) upload: applications/(.+?)/static/(.+)
secure: optional secure: optional
+39 -1
View File
@@ -37,13 +37,19 @@ if request.env.http_x_forwarded_for or request.is_https:
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
raise HTTP(200, T('appadmin is disabled because insecure channel')) raise HTTP(200, T('appadmin is disabled because insecure channel'))
if (request.application == 'admin' and not session.authorized) or \ 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)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index', redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars)))) vars=dict(send=URL(args=request.args, vars=request.vars))))
else:
menu = True
ignore_rw = True ignore_rw = True
response.view = 'appadmin.html' response.view = 'appadmin.html'
if menu:
response.menu = [[T('design'), False, URL('admin', 'default', 'design', response.menu = [[T('design'), False, URL('admin', 'default', 'design',
args=[request.application])], [T('db'), False, args=[request.application])], [T('db'), False,
URL('index')], [T('state'), False, URL('index')], [T('state'), False,
@@ -573,3 +579,35 @@ def bg_graph_model():
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) 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)
-20
View File
@@ -1677,26 +1677,6 @@ def update_languages():
redirect(URL('design', args=app, anchor='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(): def user():
if MULTI_USER_MODE: if MULTI_USER_MODE:
if not db(db.auth_user).count(): if not db(db.auth_user).count():
File diff suppressed because one or more lines are too long
+24
View File
@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
jQuery('.hidden', target).hide(); jQuery('.hidden', target).hide();
jQuery('.error', target).hide().slideDown('slow'); jQuery('.error', target).hide().slideDown('slow');
web2py_ajax_fields(target); web2py_ajax_fields(target);
web2py_show_if(target);
}; };
function web2py_event_handlers() { 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'); 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));
};
}
+36
View File
@@ -247,3 +247,39 @@
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}} {{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}} {{pass}}
{{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}}
+2 -8
View File
@@ -145,14 +145,8 @@
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/> <p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
{{=T("(requires internet access, experimental)")}}</p> {{=T("(requires internet access, experimental)")}}</p>
</div> <!-- /APP WIZARD --> </div> <!-- /APP WIZARD -->
{{if TWITTER_HASH:}} <a class="twitter-timeline" href="https://twitter.com/web2py" data-widget-id="340456915207327745">Tweets by @web2py</a>
<!-- TWITTER --> <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 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}}
</div> </div>
</div> <!-- /sidebar --> </div> <!-- /sidebar -->
</div> <!-- /row-fluid </div> <!-- /row-fluid
+39 -1
View File
@@ -37,13 +37,19 @@ if request.env.http_x_forwarded_for or request.is_https:
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
raise HTTP(200, T('appadmin is disabled because insecure channel')) raise HTTP(200, T('appadmin is disabled because insecure channel'))
if (request.application == 'admin' and not session.authorized) or \ 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)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index', redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars)))) vars=dict(send=URL(args=request.args, vars=request.vars))))
else:
menu = True
ignore_rw = True ignore_rw = True
response.view = 'appadmin.html' response.view = 'appadmin.html'
if menu:
response.menu = [[T('design'), False, URL('admin', 'default', 'design', response.menu = [[T('design'), False, URL('admin', 'default', 'design',
args=[request.application])], [T('db'), False, args=[request.application])], [T('db'), False,
URL('index')], [T('state'), False, URL('index')], [T('state'), False,
@@ -573,3 +579,35 @@ def bg_graph_model():
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) 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)
File diff suppressed because one or more lines are too long
+24
View File
@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
jQuery('.hidden', target).hide(); jQuery('.hidden', target).hide();
jQuery('.error', target).hide().slideDown('slow'); jQuery('.error', target).hide().slideDown('slow');
web2py_ajax_fields(target); web2py_ajax_fields(target);
web2py_show_if(target);
}; };
function web2py_event_handlers() { 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'); 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));
};
}
+36
View File
@@ -247,3 +247,39 @@
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}} {{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}} {{pass}}
{{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}}
+39 -1
View File
@@ -37,13 +37,19 @@ if request.env.http_x_forwarded_for or request.is_https:
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
raise HTTP(200, T('appadmin is disabled because insecure channel')) raise HTTP(200, T('appadmin is disabled because insecure channel'))
if (request.application == 'admin' and not session.authorized) or \ 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)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index', redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars)))) vars=dict(send=URL(args=request.args, vars=request.vars))))
else:
menu = True
ignore_rw = True ignore_rw = True
response.view = 'appadmin.html' response.view = 'appadmin.html'
if menu:
response.menu = [[T('design'), False, URL('admin', 'default', 'design', response.menu = [[T('design'), False, URL('admin', 'default', 'design',
args=[request.application])], [T('db'), False, args=[request.application])], [T('db'), False,
URL('index')], [T('state'), False, URL('index')], [T('state'), False,
@@ -573,3 +579,35 @@ def bg_graph_model():
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) 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)
+1 -1
View File
@@ -31,6 +31,7 @@ def user():
http://..../[app]/default/user/profile http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
use @auth.requires_login() use @auth.requires_login()
@auth.requires_membership('group name') @auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id) @auth.requires_permission('read','table name',record_id)
@@ -38,7 +39,6 @@ def user():
""" """
return dict(form=auth()) return dict(form=auth())
@cache.action() @cache.action()
def download(): def download():
""" """
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+24
View File
@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
jQuery('.hidden', target).hide(); jQuery('.hidden', target).hide();
jQuery('.error', target).hide().slideDown('slow'); jQuery('.error', target).hide().slideDown('slow');
web2py_ajax_fields(target); web2py_ajax_fields(target);
web2py_show_if(target);
}; };
function web2py_event_handlers() { 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'); 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));
};
}
+36
View File
@@ -247,3 +247,39 @@
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}} {{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}} {{pass}}
{{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'}} {{extend 'layout.html'}}
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2> <h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
<div id="web2py_user_form"> <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); web2py_validate_entropy(jQuery('#no_table_new_password'),100);
{{pass}} {{pass}}
//--></script> //--></script>
@@ -52,12 +52,8 @@ class DropboxAccount(object):
self.sess = session.DropboxSession( self.sess = session.DropboxSession(
self.key, self.secret, self.access_type) self.key, self.secret, self.access_type)
def get_user(self): def get_token(self):
request = self.request if not current.session.dropbox_access_token:
if not current.session.dropbox_request_token:
return None
elif not current.session.dropbox_access_token:
request_token = current.session.dropbox_request_token request_token = current.session.dropbox_request_token
self.sess.set_request_token(request_token[0], request_token[1]) self.sess.set_request_token(request_token[0], request_token[1])
access_token = self.sess.obtain_access_token(self.sess.token) access_token = self.sess.obtain_access_token(self.sess.token)
@@ -67,6 +63,10 @@ class DropboxAccount(object):
access_token = current.session.dropbox_access_token access_token = current.session.dropbox_access_token
self.sess.set_token(access_token[0], access_token[1]) 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() user = Storage()
self.client = client.DropboxClient(self.sess) self.client = client.DropboxClient(self.sess)
data = self.client.account_info() data = self.client.account_info()
@@ -99,8 +99,7 @@ class DropboxAccount(object):
return next return next
def get_client(self): def get_client(self):
access_token = current.session.dropbox_access_token self.get_token()
self.sess.set_token(access_token[0], access_token[1])
self.client = client.DropboxClient(self.sess) self.client = client.DropboxClient(self.sess)
def put(self, filename, file): def put(self, filename, file):
+26 -34
View File
@@ -683,12 +683,6 @@ class BaseAdapter(ConnectionPool):
return str(obj) return str(obj)
return self.adapt(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): def file_exists(self, filename):
""" """
to be used ONLY for files that on GAE may not be on filesystem to be used ONLY for files that on GAE may not be on filesystem
@@ -738,7 +732,6 @@ class BaseAdapter(ConnectionPool):
else: else:
raise RuntimeError("no driver available %s" % str(self.drivers)) raise RuntimeError("no driver available %s" % str(self.drivers))
def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8', def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={}, credential_decoder=IDENTITY, driver_args={},
adapter_args={},do_connect=True, after_connection=None): adapter_args={},do_connect=True, after_connection=None):
@@ -1212,8 +1205,8 @@ class BaseAdapter(ConnectionPool):
self.execute(query) self.execute(query)
except Exception: except Exception:
e = sys.exc_info()[1] e = sys.exc_info()[1]
if isinstance(e,self.integrity_error_class()): if hasattr(table,'_on_insert_error'):
return None return table._on_insert_error(table,fields,e)
raise e raise e
if hasattr(table,'_primarykey'): if hasattr(table,'_primarykey'):
return dict([(k[0].name, k[1]) for k in fields \ return dict([(k[0].name, k[1]) for k in fields \
@@ -1449,7 +1442,14 @@ class BaseAdapter(ConnectionPool):
def update(self, tablename, query, fields): def update(self, tablename, query, fields):
sql = self._update(tablename, query, fields) sql = self._update(tablename, query, fields)
try:
self.execute(sql) 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: try:
return self.cursor.rowcount return self.cursor.rowcount
except: except:
@@ -1873,9 +1873,6 @@ class BaseAdapter(ConnectionPool):
def lastrowid(self, table): def lastrowid(self, table):
return None return None
def integrity_error_class(self):
return type(None)
def rowslice(self, rows, minimum=0, maximum=None): def rowslice(self, rows, minimum=0, maximum=None):
""" """
By default this function does nothing; By default this function does nothing;
@@ -2522,8 +2519,6 @@ class MySQLAdapter(BaseAdapter):
self.execute('select last_insert_id();') self.execute('select last_insert_id();')
return int(self.cursor.fetchone()[0]) return int(self.cursor.fetchone()[0])
def integrity_error_class(self):
return self.cursor.IntegrityError
class PostgreSQLAdapter(BaseAdapter): class PostgreSQLAdapter(BaseAdapter):
drivers = ('psycopg2','pg8000') drivers = ('psycopg2','pg8000')
@@ -2672,7 +2667,9 @@ class PostgreSQLAdapter(BaseAdapter):
elif self.driver_name == "zxJDBC": elif self.driver_name == "zxJDBC":
supports_json = self.connection.dbversion >= "9.2.0" supports_json = self.connection.dbversion >= "9.2.0"
else: supports_json = None 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)") else: LOGGER.debug("Your database version does not support the JSON data type (using TEXT instead)")
def LIKE(self,first,second): def LIKE(self,first,second):
@@ -3216,9 +3213,6 @@ class MSSQLAdapter(BaseAdapter):
self.execute('SELECT SCOPE_IDENTITY();') self.execute('SELECT SCOPE_IDENTITY();')
return long(self.cursor.fetchone()[0]) return long(self.cursor.fetchone()[0])
def integrity_error_class(self):
return pyodbc.IntegrityError
def rowslice(self,rows,minimum=0,maximum=None): def rowslice(self,rows,minimum=0,maximum=None):
if maximum is None: if maximum is None:
return rows[minimum:] return rows[minimum:]
@@ -3482,9 +3476,6 @@ class SybaseAdapter(MSSQLAdapter):
self.connector = connector self.connector = connector
if do_connect: self.reconnect() if do_connect: self.reconnect()
def integrity_error_class(self):
return RuntimeError # FIX THIS
class FireBirdAdapter(BaseAdapter): class FireBirdAdapter(BaseAdapter):
drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc') drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
@@ -3775,9 +3766,6 @@ class InformixAdapter(BaseAdapter):
def lastrowid(self,table): def lastrowid(self,table):
return self.cursor.sqlerrd[1] return self.cursor.sqlerrd[1]
def integrity_error_class(self):
return informixdb.IntegrityError
class InformixSEAdapter(InformixAdapter): class InformixSEAdapter(InformixAdapter):
""" work in progress """ """ work in progress """
@@ -4050,9 +4038,6 @@ class IngresAdapter(BaseAdapter):
self.execute('select current value for %s' % tmp_seqname) self.execute('select current value for %s' % tmp_seqname)
return long(self.cursor.fetchone()[0]) # don't really need int type cast here... 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): class IngresUnicodeAdapter(IngresAdapter):
@@ -4528,7 +4513,6 @@ class NoSQLAdapter(BaseAdapter):
def execute(self,*a,**b): raise SyntaxError("Not supported") def execute(self,*a,**b): raise SyntaxError("Not supported")
def represent_exceptions(self, obj, fieldtype): raise SyntaxError("Not supported") def represent_exceptions(self, obj, fieldtype): raise SyntaxError("Not supported")
def lastrowid(self,table): 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") def rowslice(self,rows,minimum=0,maximum=None): raise SyntaxError("Not supported")
@@ -8425,7 +8409,7 @@ class Table(object):
return rows[0] return rows[0]
return None return None
elif str(key).isdigit() or 'google' in DRIVERS and isinstance(key, Key): 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: elif key:
return ogetattr(self, str(key)) return ogetattr(self, str(key))
@@ -8439,19 +8423,19 @@ class Table(object):
if not key is DEFAULT: if not key is DEFAULT:
if isinstance(key, Query): if isinstance(key, Query):
record = self._db(key).select( 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(): elif not str(key).isdigit():
record = None record = None
else: else:
record = self._db(self._id == key).select( 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: if record:
for k,v in kwargs.iteritems(): for k,v in kwargs.iteritems():
if record[k]!=v: return None if record[k]!=v: return None
return record return record
elif kwargs: elif kwargs:
query = reduce(lambda a,b:a&b,[self[k]==v for k,v in kwargs.iteritems()]) 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: else:
return None return None
@@ -8709,6 +8693,12 @@ class Table(object):
value = None value = None
elif field.type=='blob': elif field.type=='blob':
value = base64.b64decode(value) 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': elif field.type=='double' or field.type=='float':
if not value.strip(): if not value.strip():
value = None value = None
@@ -9214,7 +9204,7 @@ class FieldVirtual(object):
(self.name, self.f) = (name, f) if f else ('unkown', name) (self.name, self.f) = (name, f) if f else ('unkown', name)
self.type = ftype self.type = ftype
self.label = label or self.name.capitalize().replace('_',' ') self.label = label or self.name.capitalize().replace('_',' ')
self.represent = IDENTITY self.represent = lambda v,r:v
self.formatter = IDENTITY self.formatter = IDENTITY
self.comment = None self.comment = None
self.readable = True self.readable = True
@@ -9223,6 +9213,8 @@ class FieldVirtual(object):
self.widget = None self.widget = None
self.tablename = table_name self.tablename = table_name
self.filter_out = None self.filter_out = None
def __str__(self):
return '%s.%s' % (self.tablename, self.name)
class FieldMethod(object): class FieldMethod(object):
def __init__(self, name, f=None, handler=None): def __init__(self, name, f=None, handler=None):
@@ -10362,7 +10354,7 @@ class Rows(object):
# test for multiple rows # test for multiple rows
multi = False multi = False
f = self.first() f = self.first()
if f: if f and isinstance(key, basestring):
multi = any([isinstance(v, f.__class__) for v in f.values()]) multi = any([isinstance(v, f.__class__) for v in f.values()])
if (not "." in key) and multi: if (not "." in key) and multi:
# No key provided, default to int indices # No key provided, default to int indices
+8 -5
View File
@@ -1198,6 +1198,13 @@ def TAG_pickler(data):
return (TAG_unpickler, (marshal_dump,)) 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): class __TAG__(XmlComponent):
""" """
@@ -1216,11 +1223,7 @@ class __TAG__(XmlComponent):
name = name[:-1] + '/' name = name[:-1] + '/'
if isinstance(name, unicode): if isinstance(name, unicode):
name = name.encode('utf-8') name = name.encode('utf-8')
return lambda *a,**b: __tag__(name,*a,**b)
class __tag__(DIV):
tag = name
copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
return lambda *a, **b: __tag__(*a, **b)
def __call__(self, html): def __call__(self, html):
return web2pyHTMLParser(decoder.decoder(html)).tree return web2pyHTMLParser(decoder.decoder(html)).tree
+1 -1
View File
@@ -344,7 +344,7 @@ def parse_get_post_vars(request, environ):
# parse POST variables on POST, PUT, BOTH only in post_vars # 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) dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs # The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/' is_multipart = dpost.type[:10] == 'multipart/'
+53 -22
View File
@@ -18,6 +18,7 @@ try:
except ImportError: except ImportError:
from cgi import parse_qs as psq from cgi import parse_qs as psq
import os import os
import copy
from http import HTTP from http import HTTP
from html import XmlComponent from html import XmlComponent
from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT 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 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): class FormWidget(object):
""" """
helper for SQLFORM to generate form input fields helper for SQLFORM to generate form input fields
@@ -101,12 +123,16 @@ class FormWidget(object):
:param attributes: any other supplied attributes :param attributes: any other supplied attributes
""" """
attr = dict( attr = dict(
_id='%s_%s' % (field._tablename, field.name), _id='%s_%s' % (field.tablename, field.name),
_class=cls._class or _class=cls._class or
widget_class.match(str(field.type)).group(), widget_class.match(str(field.type)).group(),
_name=field.name, _name=field.name,
requires=field.requires, 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(widget_attributes)
attr.update(attributes) attr.update(attributes)
return attr return attr
@@ -261,7 +287,7 @@ class ListWidget(StringWidget):
@classmethod @classmethod
def widget(cls, field, value, **attributes): def widget(cls, field, value, **attributes):
_id = '%s_%s' % (field._tablename, field.name) _id = '%s_%s' % (field.tablename, field.name)
_name = field.name _name = field.name
if field.type == 'list:integer': if field.type == 'list:integer':
_class = 'integer' _class = 'integer'
@@ -625,7 +651,8 @@ class AutocompleteWidget(object):
self.help_fields = help_fields or [] self.help_fields = help_fields or []
self.help_string = help_string self.help_string = help_string
if self.help_fields and not self.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.request = request
self.keyword = keyword % dict(tablename=field.tablename, self.keyword = keyword % dict(tablename=field.tablename,
@@ -651,9 +678,9 @@ class AutocompleteWidget(object):
if self.keyword in self.request.vars: if self.keyword in self.request.vars:
field = self.fields[0] field = self.fields[0]
if is_gae: 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: 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 rows:
if self.is_reference: if self.is_reference:
id_field = self.fields[1] id_field = self.fields[1]
@@ -1740,6 +1767,7 @@ class SQLFORM(FORM):
return CAT( return CAT(
DIV(_id=panel_id, _style="display:none;", *criteria), fadd) DIV(_id=panel_id, _style="display:none;", *criteria), fadd)
@staticmethod @staticmethod
def grid(query, def grid(query,
fields=None, fields=None,
@@ -1930,14 +1958,20 @@ class SQLFORM(FORM):
for join in left: for join in left:
tablenames += db._adapter.tables(join) tablenames += db._adapter.tables(join)
tables = [db[tablename] for tablename in tablenames] tables = [db[tablename] for tablename in tablenames]
if not fields: if fields:
fields = reduce(lambda a, b: a + b, columns = copy.copy(fields)
[[field for field in table] for table in tables]) 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: if not field_id:
field_id = tables[0]._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): if not any(str(f)==str(field_id) for f in fields):
fields = [f for f in fields]+[field_id] fields = [f for f in fields]+[field_id]
table = field_id.table table = field_id.table
@@ -2087,12 +2121,12 @@ class SQLFORM(FORM):
if sign == '~': if sign == '~':
orderby = ~orderby orderby = ~orderby
expcolumns = columns expcolumns = [str(f) for f in columns]
if export_type.endswith('with_hidden_cols'): if export_type.endswith('with_hidden_cols'):
expcolumns = [] expcolumns = []
for table in tables: for table in tables:
for field in table: for field in table:
if field.readable and field._tablename in tablenames: if field.readable and field.tablename in tablenames:
expcolumns.append(field) expcolumns.append(field)
if export_type in exportManager and exportManager[export_type]: if export_type in exportManager and exportManager[export_type]:
@@ -2193,9 +2227,7 @@ class SQLFORM(FORM):
headcols = [] headcols = []
if selectable: if selectable:
headcols.append(TH(_class=ui.get('default'))) headcols.append(TH(_class=ui.get('default')))
for field in fields: for field in columns:
if columns and not str(field) in columns:
continue
if not field.readable: if not field.readable:
continue continue
key = str(field) key = str(field)
@@ -2249,7 +2281,7 @@ class SQLFORM(FORM):
limitby = None limitby = None
try: 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': if dbset._db._adapter.dbengine=='google:datastore':
rows = dbset.select(left=left,orderby=orderby, rows = dbset.select(left=left,orderby=orderby,
groupby=groupby,limitby=limitby, groupby=groupby,limitby=limitby,
@@ -2337,14 +2369,12 @@ class SQLFORM(FORM):
trcols.append( trcols.append(
INPUT(_type="checkbox", _name="records", _value=id, INPUT(_type="checkbox", _name="records", _value=id,
value=request.vars.records)) value=request.vars.records))
for field in fields: for field in columns:
if not str(field) in columns:
continue
if not field.readable: if not field.readable:
continue continue
if field.type == 'blob': if field.type == 'blob':
continue continue
value = row[field] value = row[str(field)]
maxlength = maxtextlengths.get(str(field), maxtextlength) maxlength = maxtextlengths.get(str(field), maxtextlength)
if field.represent: if field.represent:
try: try:
@@ -2352,7 +2382,7 @@ class SQLFORM(FORM):
except KeyError: except KeyError:
try: try:
value = field.represent( value = field.represent(
value, row[field._tablename]) value, row[field.tablename])
except KeyError: except KeyError:
pass pass
elif field.type == 'boolean': elif field.type == 'boolean':
@@ -3102,3 +3132,4 @@ class ExporterJSON(ExportClass):
return self.rows.as_json() return self.rows.as_json()
else: else:
return 'null' return 'null'
+3 -2
View File
@@ -878,6 +878,7 @@ class Auth(object):
alternate_requires_registration=False, alternate_requires_registration=False,
create_user_groups="user_%(id)s", create_user_groups="user_%(id)s",
everybody_group_id=None, everybody_group_id=None,
manager_group_role=None,
login_captcha=None, login_captcha=None,
register_captcha=None, register_captcha=None,
retrieve_username_captcha=None, retrieve_username_captcha=None,
@@ -3054,7 +3055,7 @@ class Auth(object):
return self.has_permission(name, table_name, record_id) return self.has_permission(name, table_name, record_id)
return self.requires(has_permission, otherwise=otherwise) 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 decorator that prevents access to action if not logged in or
if user logged in is not a member of group_id. if user logged in is not a member of group_id.
@@ -3062,7 +3063,7 @@ class Auth(object):
group_id is calculated. group_id is calculated.
""" """
def verify(): 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) return self.requires(verify, otherwise)
def add_group(self, role, description=''): def add_group(self, role, description=''):
+12 -6
View File
@@ -315,15 +315,21 @@ class IS_LENGTH(Validator):
length = 0 length = 0
if self.minsize <= length <= self.maxsize: if self.minsize <= length <= self.maxsize:
return (value, None) 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: if self.minsize <= len(value) <= self.maxsize:
return (value, None) return (value, None)
elif self.minsize <= len(str(value)) <= self.maxsize: elif self.minsize <= len(str(value)) <= self.maxsize:
try: return (str(value), None)
value.decode('utf8')
return (value, None)
except:
pass
return (value, translate(self.error_message) return (value, translate(self.error_message)
% dict(min=self.minsize, max=self.maxsize)) % dict(min=self.minsize, max=self.maxsize))
+2 -2
View File
@@ -314,7 +314,7 @@ NameVirtualHost *:443
</Files> </Files>
</Directory> </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> <Directory /opt/web-apps/web2py/applications/*/static>
Options -Indexes Options -Indexes
@@ -352,7 +352,7 @@ NameVirtualHost *:443
</Files> </Files>
</Directory> </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> <Directory /opt/web-apps/web2py/applications/*/static>
Options -Indexes Options -Indexes
+3 -3
View File
@@ -106,7 +106,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
</Files> </Files>
</Directory> </Directory>
AliasMatch ^/([^/]+)/static/(.*) \ AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
/home/www-data/web2py/applications/$1/static/$2 /home/www-data/web2py/applications/$1/static/$2
<Directory /home/www-data/web2py/applications/*/static/> <Directory /home/www-data/web2py/applications/*/static/>
Options -Indexes Options -Indexes
@@ -144,7 +144,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
</Files> </Files>
</Directory> </Directory>
AliasMatch ^/([^/]+)/static/(.*) \ AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
/home/www-data/web2py/applications/$1/static/$2 /home/www-data/web2py/applications/$1/static/$2
<Directory /home/www-data/web2py/applications/*/static/> <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 # ln -s /etc/pam.d/apache2 /etc/pam.d/httpd
# usermod -a -G shadow www-data # usermod -a -G shadow www-data
echo "restarting apage" echo "restarting apache"
echo "================" echo "================"
/etc/init.d/apache2 restart /etc/init.d/apache2 restart
+109 -5
View File
@@ -8,11 +8,28 @@ Usage:
python setup.py py2app 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 setuptools import setup
from gluon.import_all import base_modules, contributed_modules from gluon.import_all import base_modules, contributed_modules
from gluon.fileutils import readlines_file
import os import os
import fnmatch 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: class reglob:
def __init__(self, directory, pattern="*"): def __init__(self, directory, pattern="*"):
@@ -39,18 +56,105 @@ class reglob:
return fullname return fullname
setup(app=['web2py.py'], setup(app=['web2py.py'],
version=web2py_version,
description="web2py web framework",
author="Massimo DiPierro",
license="LGPL v3",
data_files=[ data_files=[
'NEWINSTALL', 'NEWINSTALL',
'ABOUT', 'ABOUT',
'LICENSE', 'LICENSE',
'VERSION', 'VERSION',
] + 'splashlogo.gif',
[x for x in reglob('applications/examples')] + 'logging.example.conf',
[x for x in reglob('applications/welcome')] + 'options_std.py',
[x for x in reglob('applications/admin')], ],
options={'py2app': { options={'py2app': {
'argv_emulation': True, 'argv_emulation': True,
'includes': base_modules, 'includes': base_modules,
'packages': contributed_modules,
}}, }},
setup_requires=['py2app']) 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