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

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
- pypy support, thanks Niphlod

View File

@@ -30,7 +30,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.4.7-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.5.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
@@ -64,9 +64,10 @@ app:
python2.7 -c 'import compileall; compileall.compile_dir("gluon/")'
#python web2py.py -S welcome -R __exit__.py
#cd ../web2py_osx/site-packages/; unzip ../site-packages.zip
find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip *
mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7
#find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
#cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip *
#mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7
find gluon -path '*.py' -exec cp {} ../web2py_osx/web2py/web2py.app/Contents/Resources/{} \;
cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources
cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources
cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources

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

View File

@@ -180,6 +180,9 @@ def run(servername, ip, port, softcron=True, logging=False, profiler=None):
if servername == 'gevent':
from gevent import monkey
monkey.patch_all()
elif servername == 'eventlet':
import eventlet
eventlet.monkey_patch()
import gluon.main

View File

@@ -25,7 +25,13 @@ handlers:
# the parametric router's language logic.
# You cannot use them together.
- url: /(?P<a>.+?)/static/(?P<b>.+)
- url: /(.+?)/[^_]*\/?static/_\d.\d.\d\/?(.+)
static_files: applications/\1/static/\2
upload: applications/(.+?)/static/(.+)
secure: optional
expiration: "365d"
- url: /(.+?)/[^_]*\/?static/?(.+)
static_files: applications/\1/static/\2
upload: applications/(.+?)/static/(.+)
secure: optional

View File

@@ -37,14 +37,20 @@ if request.env.http_x_forwarded_for or request.is_https:
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
raise HTTP(200, T('appadmin is disabled because insecure channel'))
if (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
if request.function in ('auth_manage','manage') and 'auth' in globals():
auth.requires_membership(auth.settings.manager_group_role)(lambda: None)()
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars))))
else:
menu = True
ignore_rw = True
response.view = 'appadmin.html'
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
if menu:
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
args=[request.application])], [T('db'), False,
URL('index')], [T('state'), False,
URL('state')], [T('cache'), False,
@@ -573,3 +579,35 @@ def bg_graph_model():
def graph_model():
return dict(databases=databases, pgv=pgv)
def auth_manage():
tablename = request.args(0)
if not tablename or not tablename in auth.db.tables:
return dict()
table = auth.db[tablename]
formname = '%s_grid' % tablename
if tablename == auth.settings.table_user_name:
auth.settings.table_user._plural = T('Users')
auth.settings.table_membership._plural = T('Roles')
auth.settings.table_membership._id.readable = False
auth.settings.table_membership.user_id.label = T('User')
auth.settings.table_membership.group_id.label = T('Role')
grid = SQLFORM.smartgrid(table, args=request.args[:1], user_signature=True,
linked_tables=[auth.settings.table_membership_name],
maxtextlength=1000, formname=formname)
else:
table._id.readable = False
auth.settings.table_permission.group_id.label = T('Role')
auth.settings.table_permission.name.label = T('Permission')
orderby = 'role' if table == auth.settings.table_group_name else 'group_id'
grid = SQLFORM.grid(table, args=request.args[:1], orderby=table[orderby],
user_signature=True, maxtextlength=1000, formname=formname)
return grid if request.extension=='load' else dict(grid=grid)
def manage():
tablename = request.args(0)
if tablename in auth.db.tables:
grid = SQLFORM.smartgrid(auth.db[tablename], args=request.args[:1])
else:
return dict()
return grid if request.extension=='load' else dict(grid=grid)

View File

@@ -1677,26 +1677,6 @@ def update_languages():
redirect(URL('design', args=app, anchor='languages'))
def twitter():
session.forget()
session._unlock(response)
import gluon.tools
import gluon.contrib.simplejson as sj
try:
if TWITTER_HASH:
page = urllib.urlopen("http://search.twitter.com/search.json?q=%%40%s" % TWITTER_HASH).read()
data = sj.loads(page, encoding="utf-8")['results']
d = dict()
for e in data:
d[e["id"]] = e
r = reversed(sorted(d))
return dict(tweets=[d[k] for k in r])
else:
return 'disabled'
except Exception, e:
return DIV(T('Unable to download because:'), BR(), str(e))
def user():
if MULTI_USER_MODE:
if not db(db.auth_user).count():

File diff suppressed because one or more lines are too long

View File

@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
jQuery('.hidden', target).hide();
jQuery('.error', target).hide().slideDown('slow');
web2py_ajax_fields(target);
web2py_show_if(target);
};
function web2py_event_handlers() {
@@ -216,3 +217,26 @@ function web2py_validate_entropy(myfield, req_entropy) {
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}
function web2py_show_if(target) {
var triggers = {};
var show_if = function () {
var t = jQuery(this);
var id = t.attr('id');
t.attr('value', t.val());
for(var k = 0; k < triggers[id].length; k++) {
var dep = jQuery('#' + triggers[id][k], target);
var tr = jQuery('#' + triggers[id][k] + '__row', target);
if(t.is(dep.attr('data-show-if'))) tr.slideDown();
else tr.hide();
}
};
jQuery('[data-show-trigger]', target).each(function () {
var name = jQuery(this).attr('data-show-trigger');
if(!triggers[name]) triggers[name] = [];
triggers[name].push(jQuery(this).attr('id'));
});
for(var name in triggers) {
jQuery('#' + name, target).change(show_if).keyup(show_if);
show_if.call(jQuery('#' + name, target));
};
}

View File

@@ -247,3 +247,39 @@
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}}
{{pass}}
{{if request.function == 'auth_manage':}}
<h2>{{=T('Manage Access Control')}}</h2>
<ul class="nav nav-tabs">
<li class="active"><a href="#users" data-toggle="tab">Users</a></li>
<li><a href="#roles" data-toggle="tab">Roles</a></li>
<li><a href="#permissions" data-toggle="tab">Permissions</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="users">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_user_name,ajax=True)}}
</div>
<div class="tab-pane" id="roles">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_group_name,ajax=True)}}
</div>
<div class="tab-pane" id="permissions">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_permission_name,ajax=True)}}
</div>
</div>
{{elif request.function == 'manage':}}
<h2>{{=T('Manage Access Control')}}</h2>
<ul class="nav nav-tabs">
{{for k,tablename in enumerate(auth.db.tables):}}
<li><a href="#table-{{=tablename}}" data-toggle="tab">{{=tablename}}</a></li>
{{pass}}
</ul>
<div class="tab-content">
{{for tablename in auth.db.tables:}}
<div class="tab-pane" id="table-{{=tablename}}">
{{=LOAD(f='manage.load', args=tablename,ajax=True)}}
</div>
{{pass}}
</div>
{{pass}}

View File

@@ -145,14 +145,8 @@
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
{{=T("(requires internet access, experimental)")}}</p>
</div> <!-- /APP WIZARD -->
{{if TWITTER_HASH:}}
<!-- TWITTER -->
<div class="box">
<h4>{{=T("%s Recent Tweets"%TWITTER_HASH)}}</h4>
<div id="tweets">{{=T('loading...')}}</div>
<script>jQuery(document).ready(function(){jQuery('#tweets').load('{{=URL('twitter.load')}}');});</script>
</div> <!-- /TWITTER -->
{{pass}}
<a class="twitter-timeline" href="https://twitter.com/web2py" data-widget-id="340456915207327745">Tweets by @web2py</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</div>
</div> <!-- /sidebar -->
</div> <!-- /row-fluid

View File

@@ -37,14 +37,20 @@ if request.env.http_x_forwarded_for or request.is_https:
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
raise HTTP(200, T('appadmin is disabled because insecure channel'))
if (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
if request.function in ('auth_manage','manage') and 'auth' in globals():
auth.requires_membership(auth.settings.manager_group_role)(lambda: None)()
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars))))
else:
menu = True
ignore_rw = True
response.view = 'appadmin.html'
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
if menu:
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
args=[request.application])], [T('db'), False,
URL('index')], [T('state'), False,
URL('state')], [T('cache'), False,
@@ -573,3 +579,35 @@ def bg_graph_model():
def graph_model():
return dict(databases=databases, pgv=pgv)
def auth_manage():
tablename = request.args(0)
if not tablename or not tablename in auth.db.tables:
return dict()
table = auth.db[tablename]
formname = '%s_grid' % tablename
if tablename == auth.settings.table_user_name:
auth.settings.table_user._plural = T('Users')
auth.settings.table_membership._plural = T('Roles')
auth.settings.table_membership._id.readable = False
auth.settings.table_membership.user_id.label = T('User')
auth.settings.table_membership.group_id.label = T('Role')
grid = SQLFORM.smartgrid(table, args=request.args[:1], user_signature=True,
linked_tables=[auth.settings.table_membership_name],
maxtextlength=1000, formname=formname)
else:
table._id.readable = False
auth.settings.table_permission.group_id.label = T('Role')
auth.settings.table_permission.name.label = T('Permission')
orderby = 'role' if table == auth.settings.table_group_name else 'group_id'
grid = SQLFORM.grid(table, args=request.args[:1], orderby=table[orderby],
user_signature=True, maxtextlength=1000, formname=formname)
return grid if request.extension=='load' else dict(grid=grid)
def manage():
tablename = request.args(0)
if tablename in auth.db.tables:
grid = SQLFORM.smartgrid(auth.db[tablename], args=request.args[:1])
else:
return dict()
return grid if request.extension=='load' else dict(grid=grid)

File diff suppressed because one or more lines are too long

View File

@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
jQuery('.hidden', target).hide();
jQuery('.error', target).hide().slideDown('slow');
web2py_ajax_fields(target);
web2py_show_if(target);
};
function web2py_event_handlers() {
@@ -216,3 +217,26 @@ function web2py_validate_entropy(myfield, req_entropy) {
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}
function web2py_show_if(target) {
var triggers = {};
var show_if = function () {
var t = jQuery(this);
var id = t.attr('id');
t.attr('value', t.val());
for(var k = 0; k < triggers[id].length; k++) {
var dep = jQuery('#' + triggers[id][k], target);
var tr = jQuery('#' + triggers[id][k] + '__row', target);
if(t.is(dep.attr('data-show-if'))) tr.slideDown();
else tr.hide();
}
};
jQuery('[data-show-trigger]', target).each(function () {
var name = jQuery(this).attr('data-show-trigger');
if(!triggers[name]) triggers[name] = [];
triggers[name].push(jQuery(this).attr('id'));
});
for(var name in triggers) {
jQuery('#' + name, target).change(show_if).keyup(show_if);
show_if.call(jQuery('#' + name, target));
};
}

View File

@@ -247,3 +247,39 @@
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}}
{{pass}}
{{if request.function == 'auth_manage':}}
<h2>{{=T('Manage Access Control')}}</h2>
<ul class="nav nav-tabs">
<li class="active"><a href="#users" data-toggle="tab">Users</a></li>
<li><a href="#roles" data-toggle="tab">Roles</a></li>
<li><a href="#permissions" data-toggle="tab">Permissions</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="users">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_user_name,ajax=True)}}
</div>
<div class="tab-pane" id="roles">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_group_name,ajax=True)}}
</div>
<div class="tab-pane" id="permissions">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_permission_name,ajax=True)}}
</div>
</div>
{{elif request.function == 'manage':}}
<h2>{{=T('Manage Access Control')}}</h2>
<ul class="nav nav-tabs">
{{for k,tablename in enumerate(auth.db.tables):}}
<li><a href="#table-{{=tablename}}" data-toggle="tab">{{=tablename}}</a></li>
{{pass}}
</ul>
<div class="tab-content">
{{for tablename in auth.db.tables:}}
<div class="tab-pane" id="table-{{=tablename}}">
{{=LOAD(f='manage.load', args=tablename,ajax=True)}}
</div>
{{pass}}
</div>
{{pass}}

View File

@@ -37,14 +37,20 @@ if request.env.http_x_forwarded_for or request.is_https:
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"):
raise HTTP(200, T('appadmin is disabled because insecure channel'))
if (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
if request.function in ('auth_manage','manage') and 'auth' in globals():
auth.requires_membership(auth.settings.manager_group_role)(lambda: None)()
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars))))
else:
menu = True
ignore_rw = True
response.view = 'appadmin.html'
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
if menu:
response.menu = [[T('design'), False, URL('admin', 'default', 'design',
args=[request.application])], [T('db'), False,
URL('index')], [T('state'), False,
URL('state')], [T('cache'), False,
@@ -573,3 +579,35 @@ def bg_graph_model():
def graph_model():
return dict(databases=databases, pgv=pgv)
def auth_manage():
tablename = request.args(0)
if not tablename or not tablename in auth.db.tables:
return dict()
table = auth.db[tablename]
formname = '%s_grid' % tablename
if tablename == auth.settings.table_user_name:
auth.settings.table_user._plural = T('Users')
auth.settings.table_membership._plural = T('Roles')
auth.settings.table_membership._id.readable = False
auth.settings.table_membership.user_id.label = T('User')
auth.settings.table_membership.group_id.label = T('Role')
grid = SQLFORM.smartgrid(table, args=request.args[:1], user_signature=True,
linked_tables=[auth.settings.table_membership_name],
maxtextlength=1000, formname=formname)
else:
table._id.readable = False
auth.settings.table_permission.group_id.label = T('Role')
auth.settings.table_permission.name.label = T('Permission')
orderby = 'role' if table == auth.settings.table_group_name else 'group_id'
grid = SQLFORM.grid(table, args=request.args[:1], orderby=table[orderby],
user_signature=True, maxtextlength=1000, formname=formname)
return grid if request.extension=='load' else dict(grid=grid)
def manage():
tablename = request.args(0)
if tablename in auth.db.tables:
grid = SQLFORM.smartgrid(auth.db[tablename], args=request.args[:1])
else:
return dict()
return grid if request.extension=='load' else dict(grid=grid)

View File

@@ -31,6 +31,7 @@ def user():
http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
use @auth.requires_login()
@auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id)
@@ -38,7 +39,6 @@ def user():
"""
return dict(form=auth())
@cache.action()
def download():
"""

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -35,6 +35,7 @@ function web2py_ajax_init(target) {
jQuery('.hidden', target).hide();
jQuery('.error', target).hide().slideDown('slow');
web2py_ajax_fields(target);
web2py_show_if(target);
};
function web2py_event_handlers() {
@@ -216,3 +217,26 @@ function web2py_validate_entropy(myfield, req_entropy) {
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}
function web2py_show_if(target) {
var triggers = {};
var show_if = function () {
var t = jQuery(this);
var id = t.attr('id');
t.attr('value', t.val());
for(var k = 0; k < triggers[id].length; k++) {
var dep = jQuery('#' + triggers[id][k], target);
var tr = jQuery('#' + triggers[id][k] + '__row', target);
if(t.is(dep.attr('data-show-if'))) tr.slideDown();
else tr.hide();
}
};
jQuery('[data-show-trigger]', target).each(function () {
var name = jQuery(this).attr('data-show-trigger');
if(!triggers[name]) triggers[name] = [];
triggers[name].push(jQuery(this).attr('id'));
});
for(var name in triggers) {
jQuery('#' + name, target).change(show_if).keyup(show_if);
show_if.call(jQuery('#' + name, target));
};
}

View File

@@ -247,3 +247,39 @@
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}}
{{pass}}
{{if request.function == 'auth_manage':}}
<h2>{{=T('Manage Access Control')}}</h2>
<ul class="nav nav-tabs">
<li class="active"><a href="#users" data-toggle="tab">Users</a></li>
<li><a href="#roles" data-toggle="tab">Roles</a></li>
<li><a href="#permissions" data-toggle="tab">Permissions</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="users">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_user_name,ajax=True)}}
</div>
<div class="tab-pane" id="roles">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_group_name,ajax=True)}}
</div>
<div class="tab-pane" id="permissions">
{{=LOAD(f='auth_manage.load', args=auth.settings.table_permission_name,ajax=True)}}
</div>
</div>
{{elif request.function == 'manage':}}
<h2>{{=T('Manage Access Control')}}</h2>
<ul class="nav nav-tabs">
{{for k,tablename in enumerate(auth.db.tables):}}
<li><a href="#table-{{=tablename}}" data-toggle="tab">{{=tablename}}</a></li>
{{pass}}
</ul>
<div class="tab-content">
{{for tablename in auth.db.tables:}}
<div class="tab-pane" id="table-{{=tablename}}">
{{=LOAD(f='manage.load', args=tablename,ajax=True)}}
</div>
{{pass}}
</div>
{{pass}}

View File

@@ -1,4 +1,5 @@
{{extend 'layout.html'}}
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
<div id="web2py_user_form">
{{
@@ -21,3 +22,4 @@ jQuery("#web2py_user_form input:visible:enabled:first").focus();
web2py_validate_entropy(jQuery('#no_table_new_password'),100);
{{pass}}
//--></script>

View File

@@ -52,12 +52,8 @@ class DropboxAccount(object):
self.sess = session.DropboxSession(
self.key, self.secret, self.access_type)
def get_user(self):
request = self.request
if not current.session.dropbox_request_token:
return None
elif not current.session.dropbox_access_token:
def get_token(self):
if not current.session.dropbox_access_token:
request_token = current.session.dropbox_request_token
self.sess.set_request_token(request_token[0], request_token[1])
access_token = self.sess.obtain_access_token(self.sess.token)
@@ -67,6 +63,10 @@ class DropboxAccount(object):
access_token = current.session.dropbox_access_token
self.sess.set_token(access_token[0], access_token[1])
def get_user(self):
if not current.session.dropbox_request_token:
return None
self.get_token()
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
@@ -99,8 +99,7 @@ class DropboxAccount(object):
return next
def get_client(self):
access_token = current.session.dropbox_access_token
self.sess.set_token(access_token[0], access_token[1])
self.get_token()
self.client = client.DropboxClient(self.sess)
def put(self, filename, file):

View File

@@ -683,12 +683,6 @@ class BaseAdapter(ConnectionPool):
return str(obj)
return self.adapt(str(obj))
def integrity_error(self):
return self.driver.IntegrityError
def operational_error(self):
return self.driver.OperationalError
def file_exists(self, filename):
"""
to be used ONLY for files that on GAE may not be on filesystem
@@ -738,7 +732,6 @@ class BaseAdapter(ConnectionPool):
else:
raise RuntimeError("no driver available %s" % str(self.drivers))
def __init__(self, db,uri,pool_size=0, folder=None, db_codec='UTF-8',
credential_decoder=IDENTITY, driver_args={},
adapter_args={},do_connect=True, after_connection=None):
@@ -1212,8 +1205,8 @@ class BaseAdapter(ConnectionPool):
self.execute(query)
except Exception:
e = sys.exc_info()[1]
if isinstance(e,self.integrity_error_class()):
return None
if hasattr(table,'_on_insert_error'):
return table._on_insert_error(table,fields,e)
raise e
if hasattr(table,'_primarykey'):
return dict([(k[0].name, k[1]) for k in fields \
@@ -1449,7 +1442,14 @@ class BaseAdapter(ConnectionPool):
def update(self, tablename, query, fields):
sql = self._update(tablename, query, fields)
self.execute(sql)
try:
self.execute(sql)
except Exception:
e = sys.exc_info()[1]
table = self.db[tablename]
if hasattr(table,'_on_update_error'):
return table._on_update_error(table,query,fields,e)
raise e
try:
return self.cursor.rowcount
except:
@@ -1873,9 +1873,6 @@ class BaseAdapter(ConnectionPool):
def lastrowid(self, table):
return None
def integrity_error_class(self):
return type(None)
def rowslice(self, rows, minimum=0, maximum=None):
"""
By default this function does nothing;
@@ -2522,8 +2519,6 @@ class MySQLAdapter(BaseAdapter):
self.execute('select last_insert_id();')
return int(self.cursor.fetchone()[0])
def integrity_error_class(self):
return self.cursor.IntegrityError
class PostgreSQLAdapter(BaseAdapter):
drivers = ('psycopg2','pg8000')
@@ -2672,7 +2667,9 @@ class PostgreSQLAdapter(BaseAdapter):
elif self.driver_name == "zxJDBC":
supports_json = self.connection.dbversion >= "9.2.0"
else: supports_json = None
if supports_json: self.types["json"] = "JSON"
if supports_json:
self.types["json"] = "JSON"
self.native_json = True
else: LOGGER.debug("Your database version does not support the JSON data type (using TEXT instead)")
def LIKE(self,first,second):
@@ -3216,9 +3213,6 @@ class MSSQLAdapter(BaseAdapter):
self.execute('SELECT SCOPE_IDENTITY();')
return long(self.cursor.fetchone()[0])
def integrity_error_class(self):
return pyodbc.IntegrityError
def rowslice(self,rows,minimum=0,maximum=None):
if maximum is None:
return rows[minimum:]
@@ -3482,9 +3476,6 @@ class SybaseAdapter(MSSQLAdapter):
self.connector = connector
if do_connect: self.reconnect()
def integrity_error_class(self):
return RuntimeError # FIX THIS
class FireBirdAdapter(BaseAdapter):
drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
@@ -3775,9 +3766,6 @@ class InformixAdapter(BaseAdapter):
def lastrowid(self,table):
return self.cursor.sqlerrd[1]
def integrity_error_class(self):
return informixdb.IntegrityError
class InformixSEAdapter(InformixAdapter):
""" work in progress """
@@ -4050,9 +4038,6 @@ class IngresAdapter(BaseAdapter):
self.execute('select current value for %s' % tmp_seqname)
return long(self.cursor.fetchone()[0]) # don't really need int type cast here...
def integrity_error_class(self):
return self._driver.IntegrityError
class IngresUnicodeAdapter(IngresAdapter):
@@ -4528,7 +4513,6 @@ class NoSQLAdapter(BaseAdapter):
def execute(self,*a,**b): raise SyntaxError("Not supported")
def represent_exceptions(self, obj, fieldtype): raise SyntaxError("Not supported")
def lastrowid(self,table): raise SyntaxError("Not supported")
def integrity_error_class(self): raise SyntaxError("Not supported")
def rowslice(self,rows,minimum=0,maximum=None): raise SyntaxError("Not supported")
@@ -8425,7 +8409,7 @@ class Table(object):
return rows[0]
return None
elif str(key).isdigit() or 'google' in DRIVERS and isinstance(key, Key):
return self._db(self._id == key).select(limitby=(0,1)).first()
return self._db(self._id == key).select(limitby=(0,1), orderby_on_limitby=False).first()
elif key:
return ogetattr(self, str(key))
@@ -8439,19 +8423,19 @@ class Table(object):
if not key is DEFAULT:
if isinstance(key, Query):
record = self._db(key).select(
limitby=(0,1),for_update=for_update, orderby=orderby).first()
limitby=(0,1),for_update=for_update, orderby=orderby, orderby_on_limitby=False).first()
elif not str(key).isdigit():
record = None
else:
record = self._db(self._id == key).select(
limitby=(0,1),for_update=for_update, orderby=orderby).first()
limitby=(0,1),for_update=for_update, orderby=orderby, orderby_on_limitby=False).first()
if record:
for k,v in kwargs.iteritems():
if record[k]!=v: return None
return record
elif kwargs:
query = reduce(lambda a,b:a&b,[self[k]==v for k,v in kwargs.iteritems()])
return self._db(query).select(limitby=(0,1),for_update=for_update, orderby=orderby).first()
return self._db(query).select(limitby=(0,1),for_update=for_update, orderby=orderby, orderby_on_limitby=False).first()
else:
return None
@@ -8709,6 +8693,12 @@ class Table(object):
value = None
elif field.type=='blob':
value = base64.b64decode(value)
elif field.type=='json':
try:
json = serializers.json
value = json(value)
except TypeError:
pass
elif field.type=='double' or field.type=='float':
if not value.strip():
value = None
@@ -9214,7 +9204,7 @@ class FieldVirtual(object):
(self.name, self.f) = (name, f) if f else ('unkown', name)
self.type = ftype
self.label = label or self.name.capitalize().replace('_',' ')
self.represent = IDENTITY
self.represent = lambda v,r:v
self.formatter = IDENTITY
self.comment = None
self.readable = True
@@ -9223,6 +9213,8 @@ class FieldVirtual(object):
self.widget = None
self.tablename = table_name
self.filter_out = None
def __str__(self):
return '%s.%s' % (self.tablename, self.name)
class FieldMethod(object):
def __init__(self, name, f=None, handler=None):
@@ -10362,7 +10354,7 @@ class Rows(object):
# test for multiple rows
multi = False
f = self.first()
if f:
if f and isinstance(key, basestring):
multi = any([isinstance(v, f.__class__) for v in f.values()])
if (not "." in key) and multi:
# No key provided, default to int indices

View File

@@ -1198,6 +1198,13 @@ def TAG_pickler(data):
return (TAG_unpickler, (marshal_dump,))
class __tag__(DIV):
def __init__(self,name,*a,**b):
DIV.__init__(self,*a,**b)
self.tag = name
copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
class __TAG__(XmlComponent):
"""
@@ -1216,11 +1223,7 @@ class __TAG__(XmlComponent):
name = name[:-1] + '/'
if isinstance(name, unicode):
name = name.encode('utf-8')
class __tag__(DIV):
tag = name
copy_reg.pickle(__tag__, TAG_pickler, TAG_unpickler)
return lambda *a, **b: __tag__(*a, **b)
return lambda *a,**b: __tag__(name,*a,**b)
def __call__(self, html):
return web2pyHTMLParser(decoder.decoder(html)).tree

View File

@@ -344,7 +344,7 @@ def parse_get_post_vars(request, environ):
# parse POST variables on POST, PUT, BOTH only in post_vars
if (body and env.request_method in ('POST', 'PUT', 'BOTH')):
if (body and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/'

View File

@@ -18,6 +18,7 @@ try:
except ImportError:
from cgi import parse_qs as psq
import os
import copy
from http import HTTP
from html import XmlComponent
from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT
@@ -81,6 +82,27 @@ def safe_float(x):
return 0
def show_if(cond):
if not cond:
return None
base = "%s_%s" % (cond.first.tablename, cond.first.name)
if ((cond.op.__name__ == 'EQ' and cond.second == True) or
(cond.op.__name__ == 'NE' and cond.second == False)):
return base,":checked"
if ((cond.op.__name__ == 'EQ' and cond.second == False) or
(cond.op.__name__ == 'NE' and cond.second == True)):
return base,":not(:checked)"
if cond.op.__name__ == 'EQ':
return base,"[value='%s']" % cond.second
if cond.op.__name__ == 'NE':
return base,"[value!='%s']" % cond.second
if cond.op.__name__ == 'CONTAINS':
return base,"[value~='%s']" % cond.second
if cond.op.__name__ == 'BELONGS' and isinstance(cond.second,(list,tuple)):
return base,','.join("[value='%s']" % (v) for v in cond.second)
raise RuntimeError("Not Implemented Error")
class FormWidget(object):
"""
helper for SQLFORM to generate form input fields
@@ -101,12 +123,16 @@ class FormWidget(object):
:param attributes: any other supplied attributes
"""
attr = dict(
_id='%s_%s' % (field._tablename, field.name),
_id='%s_%s' % (field.tablename, field.name),
_class=cls._class or
widget_class.match(str(field.type)).group(),
_name=field.name,
_name=field.name,
requires=field.requires,
)
if getattr(field,'show_if',None):
trigger, cond = show_if(field.show_if)
attr['_data-show-trigger'] = trigger
attr['_data-show-if'] = cond
attr.update(widget_attributes)
attr.update(attributes)
return attr
@@ -261,7 +287,7 @@ class ListWidget(StringWidget):
@classmethod
def widget(cls, field, value, **attributes):
_id = '%s_%s' % (field._tablename, field.name)
_id = '%s_%s' % (field.tablename, field.name)
_name = field.name
if field.type == 'list:integer':
_class = 'integer'
@@ -625,7 +651,8 @@ class AutocompleteWidget(object):
self.help_fields = help_fields or []
self.help_string = help_string
if self.help_fields and not self.help_string:
self.help_string = ' '.join('%%(%s)s' for f in self.help_fields)
self.help_string = ' '.join('%%(%s)s'%f.name
for f in self.help_fields)
self.request = request
self.keyword = keyword % dict(tablename=field.tablename,
@@ -651,9 +678,9 @@ class AutocompleteWidget(object):
if self.keyword in self.request.vars:
field = self.fields[0]
if is_gae:
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *self.fields)
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_field))
else:
rows = self.db(field.like(self.request.vars[self.keyword] + '%')).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *self.fields)
rows = self.db(field.like(self.request.vars[self.keyword] + '%')).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_field))
if rows:
if self.is_reference:
id_field = self.fields[1]
@@ -1740,6 +1767,7 @@ class SQLFORM(FORM):
return CAT(
DIV(_id=panel_id, _style="display:none;", *criteria), fadd)
@staticmethod
def grid(query,
fields=None,
@@ -1930,14 +1958,20 @@ class SQLFORM(FORM):
for join in left:
tablenames += db._adapter.tables(join)
tables = [db[tablename] for tablename in tablenames]
if not fields:
fields = reduce(lambda a, b: a + b,
[[field for field in table] for table in tables])
if fields:
columns = copy.copy(fields)
else:
fields = []
columns = []
for table in tables:
fields += [f for f in table]
columns += [f for f in table]
for k,f in table.iteritems():
if isinstance(f,Field.Virtual) and f.readable:
f.tablename = table._tablename
columns.append(f)
if not field_id:
field_id = tables[0]._id
columns = [str(field) for field in fields
if field._tablename in tablenames]
if not any(str(f)==str(field_id) for f in fields):
fields = [f for f in fields]+[field_id]
table = field_id.table
@@ -2087,12 +2121,12 @@ class SQLFORM(FORM):
if sign == '~':
orderby = ~orderby
expcolumns = columns
expcolumns = [str(f) for f in columns]
if export_type.endswith('with_hidden_cols'):
expcolumns = []
for table in tables:
for field in table:
if field.readable and field._tablename in tablenames:
if field.readable and field.tablename in tablenames:
expcolumns.append(field)
if export_type in exportManager and exportManager[export_type]:
@@ -2193,9 +2227,7 @@ class SQLFORM(FORM):
headcols = []
if selectable:
headcols.append(TH(_class=ui.get('default')))
for field in fields:
if columns and not str(field) in columns:
continue
for field in columns:
if not field.readable:
continue
key = str(field)
@@ -2249,7 +2281,7 @@ class SQLFORM(FORM):
limitby = None
try:
table_fields = [f for f in fields if f._tablename in tablenames]
table_fields = [f for f in fields if f.tablename in tablenames]
if dbset._db._adapter.dbengine=='google:datastore':
rows = dbset.select(left=left,orderby=orderby,
groupby=groupby,limitby=limitby,
@@ -2337,14 +2369,12 @@ class SQLFORM(FORM):
trcols.append(
INPUT(_type="checkbox", _name="records", _value=id,
value=request.vars.records))
for field in fields:
if not str(field) in columns:
continue
for field in columns:
if not field.readable:
continue
if field.type == 'blob':
continue
value = row[field]
value = row[str(field)]
maxlength = maxtextlengths.get(str(field), maxtextlength)
if field.represent:
try:
@@ -2352,7 +2382,7 @@ class SQLFORM(FORM):
except KeyError:
try:
value = field.represent(
value, row[field._tablename])
value, row[field.tablename])
except KeyError:
pass
elif field.type == 'boolean':
@@ -3102,3 +3132,4 @@ class ExporterJSON(ExportClass):
return self.rows.as_json()
else:
return 'null'

View File

@@ -878,6 +878,7 @@ class Auth(object):
alternate_requires_registration=False,
create_user_groups="user_%(id)s",
everybody_group_id=None,
manager_group_role=None,
login_captcha=None,
register_captcha=None,
retrieve_username_captcha=None,
@@ -3054,7 +3055,7 @@ class Auth(object):
return self.has_permission(name, table_name, record_id)
return self.requires(has_permission, otherwise=otherwise)
def requires_signature(self, otherwise=None):
def requires_signature(self, otherwise=None, hash_vars=True):
"""
decorator that prevents access to action if not logged in or
if user logged in is not a member of group_id.
@@ -3062,7 +3063,7 @@ class Auth(object):
group_id is calculated.
"""
def verify():
return URL.verify(current.request, user_signature=True)
return URL.verify(current.request, user_signature=True, hash_vars=hash_vars)
return self.requires(verify, otherwise)
def add_group(self, role, description=''):

View File

@@ -315,15 +315,21 @@ class IS_LENGTH(Validator):
length = 0
if self.minsize <= length <= self.maxsize:
return (value, None)
elif isinstance(value, (str, unicode, list)):
elif isinstance(value, str):
try:
lvalue = len(value.decode('utf8'))
except:
lvalue = len(value)
if self.minsize <= lvalue <= self.maxsize:
return (value, None)
elif isinstance(value, unicode):
if self.minsize <= len(value) <= self.maxsize:
return (value.encode('utf8'), None)
elif isinstance(value, (tuple, list)):
if self.minsize <= len(value) <= self.maxsize:
return (value, None)
elif self.minsize <= len(str(value)) <= self.maxsize:
try:
value.decode('utf8')
return (value, None)
except:
pass
return (str(value), None)
return (value, translate(self.error_message)
% dict(min=self.minsize, max=self.maxsize))

View File

@@ -314,7 +314,7 @@ NameVirtualHost *:443
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
<Directory /opt/web-apps/web2py/applications/*/static>
Options -Indexes
@@ -352,7 +352,7 @@ NameVirtualHost *:443
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
<Directory /opt/web-apps/web2py/applications/*/static>
Options -Indexes

View File

@@ -106,7 +106,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(.*) \
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
/home/www-data/web2py/applications/$1/static/$2
<Directory /home/www-data/web2py/applications/*/static/>
Options -Indexes
@@ -144,7 +144,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(.*) \
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
/home/www-data/web2py/applications/$1/static/$2
<Directory /home/www-data/web2py/applications/*/static/>
@@ -167,7 +167,7 @@ WSGIDaemonProcess web2py user=www-data group=www-data
# ln -s /etc/pam.d/apache2 /etc/pam.d/httpd
# usermod -a -G shadow www-data
echo "restarting apage"
echo "restarting apache"
echo "================"
/etc/init.d/apache2 restart

View File

@@ -8,11 +8,28 @@ Usage:
python setup.py py2app
"""
copy_apps = False
copy_scripts = True
copy_site_packages = True
remove_build_files = True
make_zip = True
zip_filename = "web2py_osx"
from setuptools import setup
from gluon.import_all import base_modules, contributed_modules
from gluon.fileutils import readlines_file
import os
import fnmatch
import shutil
import sys
import re
import zipfile
#read web2py version from VERSION file
web2py_version_line = readlines_file('VERSION')[0]
#use regular expression to get just the version number
v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+')
web2py_version = v_re.search(web2py_version_line).group(0)
class reglob:
def __init__(self, directory, pattern="*"):
@@ -39,18 +56,105 @@ class reglob:
return fullname
setup(app=['web2py.py'],
version=web2py_version,
description="web2py web framework",
author="Massimo DiPierro",
license="LGPL v3",
data_files=[
'NEWINSTALL',
'ABOUT',
'LICENSE',
'VERSION',
] +
[x for x in reglob('applications/examples')] +
[x for x in reglob('applications/welcome')] +
[x for x in reglob('applications/admin')],
'splashlogo.gif',
'logging.example.conf',
'options_std.py',
],
options={'py2app': {
'argv_emulation': True,
'includes': base_modules,
'packages': contributed_modules,
}},
setup_requires=['py2app'])
def copy_folders(source, destination):
"""Copy files & folders from source to destination (within dist/)"""
print 'copying %s -> %s' % (source, destination)
base = 'dist/web2py.app/Contents/Resources/'
if os.path.exists(os.path.join(base, destination)):
shutil.rmtree(os.path.join(base, destination))
shutil.copytree(os.path.join(source), os.path.join(base, destination))
#Should we include applications?
copy_folders('gluon','gluon')
if copy_apps:
copy_folders('applications', 'applications')
print "Your application(s) have been added"
else:
#only copy web2py's default applications
copy_folders('applications/admin', 'applications/admin')
copy_folders('applications/welcome', 'applications/welcome')
copy_folders('applications/examples', 'applications/examples')
print "Only web2py's admin, examples & welcome applications have been added"
#should we copy project's site-packages into dist/site-packages
if copy_site_packages:
#copy site-packages
copy_folders('site-packages', 'site-packages')
else:
#no worries, web2py will create the (empty) folder first run
print "Skipping site-packages"
pass
#should we copy project's scripts into dist/scripts
if copy_scripts:
#copy scripts
copy_folders('scripts', 'scripts')
else:
#no worries, web2py will create the (empty) folder first run
print "Skipping scripts"
pass
#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile
def recursive_zip(zipf, directory, folder=""):
for item in os.listdir(directory):
if os.path.isfile(os.path.join(directory, item)):
zipf.write(os.path.join(directory, item), folder + os.sep + item)
elif os.path.isdir(os.path.join(directory, item)):
recursive_zip(
zipf, os.path.join(directory, item), folder + os.sep + item)
#should we create a zip file of the build?
if make_zip:
#to keep consistent with how official web2py windows zip file is setup,
#create a web2py folder & copy dist's files into it
shutil.copytree('dist', 'zip_temp/web2py')
#create zip file
#use filename specified via command line
zipf = zipfile.ZipFile(
zip_filename + ".zip", "w", compression=zipfile.ZIP_DEFLATED)
path = 'zip_temp' # just temp so the web2py directory is included in our zip file
recursive_zip(
zipf, path) # leave the first folder as None, as path is root.
zipf.close()
shutil.rmtree('zip_temp')
print "Your Windows binary version of web2py can be found in " + \
zip_filename + ".zip"
print "You may extract the archive anywhere and then run web2py/web2py.exe"
#should py2exe build files be removed?
if remove_build_files:
shutil.rmtree('build')
shutil.rmtree('deposit')
shutil.rmtree('dist')
print "py2exe build files removed"
#final info
if not make_zip and not remove_build_files:
print "Your Windows binary & associated files can also be found in /dist"
print "Finished!"
print "Enjoy web2py " + web2py_version_line