Compare 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
|
## 2.4.7
|
||||||
|
|
||||||
- pypy support, thanks Niphlod
|
- pypy support, thanks Niphlod
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
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':
|
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
@@ -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
|
||||||
|
|||||||
@@ -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"):
|
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'
|
||||||
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,
|
args=[request.application])], [T('db'), False,
|
||||||
URL('index')], [T('state'), False,
|
URL('index')], [T('state'), False,
|
||||||
URL('state')], [T('cache'), False,
|
URL('state')], [T('cache'), 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)
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
+6
-2
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('.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));
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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}}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"):
|
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'
|
||||||
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,
|
args=[request.application])], [T('db'), False,
|
||||||
URL('index')], [T('state'), False,
|
URL('index')], [T('state'), False,
|
||||||
URL('state')], [T('cache'), False,
|
URL('state')], [T('cache'), 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)
|
||||||
|
|||||||
+6
-2
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('.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));
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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}}
|
||||||
|
|||||||
@@ -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"):
|
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'
|
||||||
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,
|
args=[request.application])], [T('db'), False,
|
||||||
URL('index')], [T('state'), False,
|
URL('index')], [T('state'), False,
|
||||||
URL('state')], [T('cache'), False,
|
URL('state')], [T('cache'), 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)
|
||||||
|
|||||||
@@ -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
+6
-2
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('.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));
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user