This commit is contained in:
pallav_fdsi
2015-07-01 17:49:47 -04:00
56 changed files with 1748 additions and 771 deletions
+2 -2
View File
@@ -17,14 +17,14 @@ install:
before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache python-coveralls; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then codecov; fi
notifications:
email: true
+4
View File
@@ -1,3 +1,7 @@
## 2.11.1
- Many small but significative improvements and bug fixes
## 2.10.1-2.10.2
- welcome app defaults to Bootstrap 3
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.10.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.11.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+5 -4
View File
@@ -13,7 +13,7 @@ Learn more at http://web2py.com
Then edit ./app.yaml and replace "yourappname" with yourappname.
## Import about this GIT repo
## Important reminder about this GIT repo
An important part of web2py is the Database Abstraction Layer (DAL). In early 2015 this was decoupled into a separate code-base (PyDAL). In terms of git, it is a sub-module of the main repository.
@@ -38,9 +38,10 @@ PyDAL uses a separate stable release cycle to the rest of web2py. PyDAL releases
## Tests
[![Build Status](https://img.shields.io/travis/web2py/web2py.svg?style=flat-square)](https://travis-ci.org/web2py/web2py)
[![Build Status](https://img.shields.io/travis/web2py/web2py/master.svg?style=flat-square&label=Travis-CI)](https://travis-ci.org/web2py/web2py)
[![MS Build Status](https://img.shields.io/appveyor/ci/web2py/web2py/master.svg?style=flat-square&label=Appveyor-CI)](https://ci.appveyor.com/project/web2py/web2py)
[![Coverage Status](https://img.shields.io/codecov/c/github/web2py/web2py.svg?style=flat-square)](https://codecov.io/github/web2py/web2py)
[![Coverage Status](https://img.shields.io/coveralls/web2py/web2py.svg?style=flat-square)](https://coveralls.io/r/web2py/web2py)
## Installation Instructions
@@ -63,7 +64,7 @@ That's it!!!
packages/ > web2py submodules
dal/
contrib/ > third party libraries
tests/ > unittests
tests/ > unittests
applications/ > are the apps
admin/ > web based IDE
...
+1 -1
View File
@@ -1 +1 @@
Version 2.10.3-stable+timestamp.2015.04.02.16.28.49
Version 2.11.2-stable+timestamp.2015.05.30.11.29.46
+5
View File
@@ -180,6 +180,11 @@ class Servers:
s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address)
s.start()
@staticmethod
def waitress(app, address, **options):
from waitress import serve
serve(app, host=address[0], port=address[1], _quiet=True)
def mongrel2_handler(application, conn, debug=False):
"""
+10 -12
View File
@@ -49,7 +49,8 @@ if request.function == 'manage':
auth.table_group(),
auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None
auth.requires_membership(manager_role)(lambda: None)()
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized")
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -80,7 +81,6 @@ if False and request.tickets_db:
def get_databases(request):
dbs = {}
for (key, value) in global_env.items():
cond = False
try:
cond = isinstance(value, GQLDB)
except:
@@ -420,7 +420,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -480,12 +480,12 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
total['objects'] = ram['objects'] + disk['objects']
total['hits'] = ram['hits'] + disk['hits']
total['misses'] = ram['misses'] + disk['misses']
total['keys'] = ram['keys'] + disk['keys']
ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
total[key] = ram[key] + disk[key]
try:
total['ratio'] = total['hits'] * 100 / (total['hits'] +
total['misses'])
@@ -577,9 +577,7 @@ def bg_graph_model():
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))
+1 -2
View File
@@ -220,7 +220,7 @@ def list_breakpoints():
"Return a list of linenumbers for current breakpoints"
breakpoints = []
ok = None
ok = False
try:
filename = os.path.join(request.env['applications_parent'],
'applications', request.vars.filename)
@@ -235,5 +235,4 @@ def list_breakpoints():
ok = True
except Exception, e:
session.flash = str(e)
ok = False
return response.json({'ok': ok, 'breakpoints': breakpoints})
+51 -26
View File
@@ -292,9 +292,6 @@ def site():
log_progress(appname)
session.flash = T(msg, dict(appname=appname,
digest=md5_hash(installed)))
elif f and form_update.vars.overwrite:
msg = 'unable to install application "%(appname)s"'
session.flash = T(msg, dict(appname=form_update.vars.name))
else:
msg = 'unable to install application "%(appname)s"'
session.flash = T(msg, dict(appname=form_update.vars.name))
@@ -370,25 +367,56 @@ def pack_plugin():
session.flash = T('internal error')
redirect(URL('plugin', args=request.args))
def pack_exe(app, base, filenames=None):
import urllib
import zipfile
from cStringIO import StringIO
# Download latest web2py_win and open it with zipfile
download_url = 'http://www.web2py.com/examples/static/web2py_win.zip'
out = StringIO()
out.write(urllib.urlopen(download_url).read())
web2py_win = zipfile.ZipFile(out, mode='a')
# Write routes.py with the application as default
routes = u'# -*- coding: utf-8 -*-\nrouters = dict(BASE=dict(default_application="%s"))' % app
web2py_win.writestr('web2py/routes.py', routes.encode('utf-8'))
# Copy the application into the zipfile
common_root = os.path.dirname(base)
for filename in filenames:
fname = os.path.join(base, filename)
arcname = os.path.join('web2py/applications', app, filename)
web2py_win.write(fname, arcname)
web2py_win.close()
response.headers['Content-Type'] = 'application/zip'
response.headers['Content-Disposition'] = 'attachment; filename=web2py.app.%s.zip' % app
out.seek(0)
return response.stream(out)
def pack_custom():
app = get_app()
base = apath(app, r=request)
if request.post_vars.file:
files = request.post_vars.file
files = [files] if not isinstance(files,list) else files
fname = 'web2py.app.%s.w2p' % app
try:
filename = app_pack(app, request, raise_ex=True, filenames=files)
except Exception, e:
filename = None
if filename:
response.headers['Content-Type'] = 'application/w2p'
disposition = 'attachment; filename=%s' % fname
response.headers['Content-Disposition'] = disposition
return safe_read(filename, 'rb')
if request.post_vars.doexe is None:
fname = 'web2py.app.%s.w2p' % app
try:
filename = app_pack(app, request, raise_ex=True, filenames=files)
except Exception, e:
filename = None
if filename:
response.headers['Content-Type'] = 'application/w2p'
disposition = 'attachment; filename=%s' % fname
response.headers['Content-Disposition'] = disposition
return safe_read(filename, 'rb')
else:
session.flash = T('internal error: %s', e)
redirect(URL(args=request.args))
else:
session.flash = T('internal error: %s', e)
redirect(URL(args=request.args))
return pack_exe(app, base, files)
def ignore(fs):
return [f for f in fs if not (
f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))]
@@ -744,7 +772,7 @@ def edit():
viewlist.append(aviewpath + '.html')
if len(viewlist):
editviewlinks = []
for v in viewlist:
for v in sorted(viewlist):
vf = os.path.split(v)[-1]
vargs = "/".join([viewpath.replace(os.sep, "/"), vf])
editviewlinks.append(A(vf.split(".")[0],
@@ -754,6 +782,7 @@ def edit():
if len(request.args) > 2 and request.args[1] == 'controllers':
controller = (request.args[2])[:-3]
functions = find_exposed_functions(data)
functions = functions and sorted(functions) or []
else:
(controller, functions) = (None, None)
@@ -866,13 +895,9 @@ def resolve():
def getclass(item):
""" Determine item class """
if item[0] == ' ':
return 'normal'
if item[0] == '+':
return 'plus'
if item[0] == '-':
return 'minus'
operators = {' ':'normal', '+':'plus', '-':'minus'}
return operators[item[0]]
if request.vars:
c = '\n'.join([item[2:].rstrip() for (i, item) in enumerate(d) if item[0]
@@ -1067,7 +1092,7 @@ def design():
for c in controllers:
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
items = find_exposed_functions(data)
functions[c] = items
functions[c] = items and sorted(items) or []
# Get all views
views = sorted(
@@ -1205,7 +1230,7 @@ def plugin():
for c in controllers:
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
items = find_exposed_functions(data)
functions[c] = items
functions[c] = items and sorted(items) or []
# Get all views
views = sorted(
@@ -1509,7 +1534,7 @@ def upload_file():
if filename:
d = dict(filename=filename[len(path):])
else:
d = dict(filename='unkown')
d = dict(filename='unknown')
session.flash = T('cannot upload file "%(filename)s"', d)
redirect(request.vars.sender)
+23 -21
View File
@@ -490,12 +490,12 @@
* and prevent clicking on it */
disableElement: function(el) {
el.addClass('disabled');
var method = el.is('button') ? 'html' : 'val';
var method = el.is('input') ? 'val' : 'html';
//method = el.attr('name') ? 'html' : 'val';
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
/*store enabled state if not already disabled */
if(el.data('w2p:enable-with') === undefined) {
el.data('w2p:enable-with', el[method]());
if(el.data('w2p_enable_with') === undefined) {
el.data('w2p_enable_with', el[method]());
}
/*if you don't want to see "working..." on buttons, replace the following
* two lines with this one
@@ -515,11 +515,11 @@
/* restore element to its original state which was disabled by 'disableElement' above*/
enableElement: function(el) {
var method = el.is('button') ? 'val' : 'html';
if(el.data('w2p:enable-with') !== undefined) {
var method = el.is('input') ? 'val' : 'html';
if(el.data('w2p_enable_with') !== undefined) {
/* set to old enabled state */
el[method](el.data('w2p:enable-with'));
el.removeData('w2p:enable-with');
el[method](el.data('w2p_enable_with'));
el.removeData('w2p_enable_with');
}
el.removeClass('disabled');
el.unbind('click.w2pDisable');
@@ -586,12 +586,14 @@
if(pre_call != undefined) {
eval(pre_call);
}
if(confirm_message != undefined) {
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
if(confirm_message) {
if(confirm_message == 'default')
confirm_message = w2p_ajax_confirm_message ||
'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
}
if(target == undefined) {
if(method == 'GET') {
@@ -634,7 +636,7 @@
});
},
/* Disables form elements:
- Caches element value in 'w2p:enable-with' data store
- Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Sets disabled property to true
*/
@@ -646,8 +648,8 @@
if(disable_with == undefined) {
element.data('w2p_disable_with', element[method]())
}
if(element.data('w2p:enable-with') === undefined) {
element.data('w2p:enable-with', element[method]());
if(element.data('w2p_enable_with') === undefined) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true);
@@ -655,16 +657,16 @@
},
/* Re-enables disabled form elements:
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
- Sets disabled property to false
*/
enableFormElements: function(form) {
form.find(web2py.enableSelector).each(function() {
var element = $(this),
method = element.is('button') ? 'html' : 'val';
if(element.data('w2p:enable-with')) {
element[method](element.data('w2p:enable-with'));
element.removeData('w2p:enable-with');
if(element.data('w2p_enable_with')) {
element[method](element.data('w2p_enable_with'));
element.removeData('w2p_enable_with');
}
element.prop('disabled', false);
});
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/
+3 -1
View File
@@ -1,5 +1,7 @@
{{extend 'layout.html'}}
{{
import re
regex_space = re.compile('\s+')
def all(items):
return reduce(lambda a,b:a and b,items,True)
def peekfile(path,file,vars={},title=None):
@@ -304,7 +306,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
while path!=file_path:
if len(file_path)>=len(path) and all([v==file_path[k] for k,v in enumerate(path)]):
path.append(file_path[len(path)])
thispath='static__'+'__'.join(path)
thispath = regex_space.sub('-', 'static__'+'__'.join(path))
}}
<li class="folder"><i>&nbsp;</i>
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
@@ -25,6 +25,7 @@
<form action="{{=URL(args=request.args)}}" method="POST">
<h2>{{=T('Select Files to Package')}}</h2>
<input type="submit" value="{{=T('Download .w2p')}}" class="btn"/>
<input type="submit" name="doexe" value="{{=T('Download as .exe')}}" class="btn"/>
<div style="margin-top:20px">
{{tree(base)}}
</div>
+10 -12
View File
@@ -49,7 +49,8 @@ if request.function == 'manage':
auth.table_group(),
auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None
auth.requires_membership(manager_role)(lambda: None)()
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized")
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -80,7 +81,6 @@ if False and request.tickets_db:
def get_databases(request):
dbs = {}
for (key, value) in global_env.items():
cond = False
try:
cond = isinstance(value, GQLDB)
except:
@@ -420,7 +420,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -480,12 +480,12 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
total['objects'] = ram['objects'] + disk['objects']
total['hits'] = ram['hits'] + disk['hits']
total['misses'] = ram['misses'] + disk['misses']
total['keys'] = ram['keys'] + disk['keys']
ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
total[key] = ram[key] + disk[key]
try:
total['ratio'] = total['hits'] * 100 / (total['hits'] +
total['misses'])
@@ -577,9 +577,7 @@ def bg_graph_model():
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))
@@ -8,6 +8,7 @@
#### Learning and Demos
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
@@ -22,5 +23,6 @@
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
- [[Appliances http://www.web2py.com/appliances popup]]
- [[web2py utils http://packages.python.org/web2py_utils/ popup]]
- [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]]
#### [[Sites Powered by web2py http://www.web2py.com/poweredby popup]]
+23 -21
View File
@@ -490,12 +490,12 @@
* and prevent clicking on it */
disableElement: function(el) {
el.addClass('disabled');
var method = el.is('button') ? 'html' : 'val';
var method = el.is('input') ? 'val' : 'html';
//method = el.attr('name') ? 'html' : 'val';
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
/*store enabled state if not already disabled */
if(el.data('w2p:enable-with') === undefined) {
el.data('w2p:enable-with', el[method]());
if(el.data('w2p_enable_with') === undefined) {
el.data('w2p_enable_with', el[method]());
}
/*if you don't want to see "working..." on buttons, replace the following
* two lines with this one
@@ -515,11 +515,11 @@
/* restore element to its original state which was disabled by 'disableElement' above*/
enableElement: function(el) {
var method = el.is('button') ? 'val' : 'html';
if(el.data('w2p:enable-with') !== undefined) {
var method = el.is('input') ? 'val' : 'html';
if(el.data('w2p_enable_with') !== undefined) {
/* set to old enabled state */
el[method](el.data('w2p:enable-with'));
el.removeData('w2p:enable-with');
el[method](el.data('w2p_enable_with'));
el.removeData('w2p_enable_with');
}
el.removeClass('disabled');
el.unbind('click.w2pDisable');
@@ -586,12 +586,14 @@
if(pre_call != undefined) {
eval(pre_call);
}
if(confirm_message != undefined) {
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
if(confirm_message) {
if(confirm_message == 'default')
confirm_message = w2p_ajax_confirm_message ||
'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
}
if(target == undefined) {
if(method == 'GET') {
@@ -634,7 +636,7 @@
});
},
/* Disables form elements:
- Caches element value in 'w2p:enable-with' data store
- Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Sets disabled property to true
*/
@@ -646,8 +648,8 @@
if(disable_with == undefined) {
element.data('w2p_disable_with', element[method]())
}
if(element.data('w2p:enable-with') === undefined) {
element.data('w2p:enable-with', element[method]());
if(element.data('w2p_enable_with') === undefined) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true);
@@ -655,16 +657,16 @@
},
/* Re-enables disabled form elements:
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
- Sets disabled property to false
*/
enableFormElements: function(form) {
form.find(web2py.enableSelector).each(function() {
var element = $(this),
method = element.is('button') ? 'html' : 'val';
if(element.data('w2p:enable-with')) {
element[method](element.data('w2p:enable-with'));
element.removeData('w2p:enable-with');
if(element.data('w2p_enable_with')) {
element[method](element.data('w2p_enable_with'));
element.removeData('w2p_enable_with');
}
element.prop('disabled', false);
});
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/
+6 -6
View File
@@ -18,7 +18,7 @@
</ul>
<div class="tab-content">
<div class="tab-pane active" id="alltables">
<table>
<table class="table">
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -40,7 +40,7 @@
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</th>
<td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn btn-default")}}
</td>
</tr>
{{pass}}
@@ -61,7 +61,7 @@
</pre>
{{pass}}
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn btn-default")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}}
<h3>{{=T("Rows selected")}}</h3><br/>
@@ -72,8 +72,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
<br/><br/>
<h4>{{=T("%s selected", nrows)}}</h4>
{{if start>0:}}{{=A(T('previous %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start-step)),_class="btn")}}{{pass}}
{{if stop<nrows:}}{{=A(T('next %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start+step)),_class="btn")}}{{pass}}
{{if start>0:}}{{=A(T('previous %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start-step)),_class="btn btn-default")}}{{pass}}
{{if stop<nrows:}}{{=A(T('next %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start+step)),_class="btn btn-default")}}{{pass}}
{{if rows:}}
<div style="overflow:auto; width:80%;">
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
@@ -82,7 +82,7 @@
</div>
{{pass}}
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
<a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn">{{=T("export as csv file")}}</a>
<a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn btn-default">{{=T("export as csv file")}}</a>
{{=formcsv or ''}}
{{elif request.function=='insert':}}
@@ -36,7 +36,7 @@
</center>
<p style="text-align:left;">
The source code version works on all supported platforms, including Linux, but it requires Python 2.5, 2.6, or 2.7.
The source code version works on all supported platforms, including Linux, but it requires Python 2.6, or 2.7 (recommended).
It runs on Windows and most Unix systems, including <b>Linux</b> and <b>BSD</b>.
</p>
@@ -17,22 +17,30 @@
<ul>
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li>
<li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li>
<li><a target="_blank" href="http://www.10biosystems.com">10BioSystems</a> (USA)</li>
<li><a target="_blank" href="http://www.formatics.nl">Formatics</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li>
<li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li>
<li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li>
<li><a target="_blank" href="www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
<li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
<li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
<li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li>
<li><a target="_blank" href="http://i-am.pt">I am Consultoria</a> (Portugal)</li>
<li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li>
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
<li><a target="_blank" href="http://www.vsa-services.com/">VSA Services</a> (Singapore)</li>
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
<li><a target="_blank" href="www.corebyte.nl">Corebyte</a> (Netherland)</li>
<li><a target="_blank" href="https://loadinfo-net.appspot.com">LoadInfo</a> (Bulgaria)</li>
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://10Biosystems.com">10BioSystems</a></li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
</ul>
</div>
+10 -12
View File
@@ -49,7 +49,8 @@ if request.function == 'manage':
auth.table_group(),
auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None
auth.requires_membership(manager_role)(lambda: None)()
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized")
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -80,7 +81,6 @@ if False and request.tickets_db:
def get_databases(request):
dbs = {}
for (key, value) in global_env.items():
cond = False
try:
cond = isinstance(value, GQLDB)
except:
@@ -420,7 +420,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -480,12 +480,12 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
total['objects'] = ram['objects'] + disk['objects']
total['hits'] = ram['hits'] + disk['hits']
total['misses'] = ram['misses'] + disk['misses']
total['keys'] = ram['keys'] + disk['keys']
ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
total[key] = ram[key] + disk[key]
try:
total['ratio'] = total['hits'] * 100 / (total['hits'] +
total['misses'])
@@ -577,9 +577,7 @@ def bg_graph_model():
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))
@@ -30,6 +30,7 @@ def user():
http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
http://..../[app]/default/user/bulk_register
use @auth.requires_login()
@auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id)
+2 -2
View File
@@ -58,11 +58,11 @@ service = Service()
plugins = PluginManager()
## create all tables needed by auth if not custom tables
auth.define_tables(username=False, signature=False)
auth.define_tables(username=False, signature=False, enable_tokens=False)
## configure email
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.sender')
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server')
mail.settings.sender = myconf.take('smtp.sender')
mail.settings.login = myconf.take('smtp.login')
+2 -2
View File
@@ -7,11 +7,11 @@
# Language from default.py or 'en' (if the file is not found) is used as
# a default_language
#
# See <web2py-root-dir>/router.example.py for parameter's detail
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
#-------------------------------------------------------------------------------------
# To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/router.example.py to routes.py
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py
# (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interfase)
File diff suppressed because one or more lines are too long
@@ -24,7 +24,9 @@ div.flash.alert:hover {
.ie-lte8 div.flash:hover {
filter: alpha(opacity=25);
}
.main-container {
margin-top: 20px;
}
div.error {
width: auto;
@@ -283,6 +285,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console .form-control {
width: 20%;
display: inline;
height: 100%;
}
.web2py_console #w2p_keywords {
width: 50%;
+8 -6
View File
@@ -586,12 +586,14 @@
if(pre_call != undefined) {
eval(pre_call);
}
if(confirm_message != undefined) {
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
if(confirm_message) {
if(confirm_message == 'default')
confirm_message = w2p_ajax_confirm_message ||
'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
}
if(target == undefined) {
if(method == 'GET') {
+1 -1
View File
@@ -75,7 +75,7 @@
{{end}}
<!-- Main ========================================= -->
<!-- Begin page content -->
<div class="container-fluid">
<div class="container-fluid main-container">
{{if left_sidebar_enabled:}}
<div class="col-md-3 left-sidebar">
{{block left_sidebar}}
+25
View File
@@ -0,0 +1,25 @@
build: false
environment:
matrix:
- PYTHON: "C:/Python27"
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
clone_depth: 50
init:
- "ECHO %PYTHON%"
- set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
install:
- ps: Start-FileDownload https://bootstrap.pypa.io/get-pip.py
- python get-pip.py
- pip install codecov
- git submodule update --init --recursive
test_script:
- python web2py.py --run_system_tests --with_coverage
after_test:
- coverage combine
- codecov
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- app configuration for web2py on IIS -->
<configuration>
<appSettings>
<add key="WSGI_HANDLER" value="gluon.main.wsgibase" />
<add key="WSGI_RESTART_FILE_REGEX" value=".*((routes\.py)|(\.config))$" />
</appSettings>
<system.webServer>
<rewrite>
<rules>
<clear />
<rule name="static" enabled="true" stopProcessing="true">
<match url="^(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="Rewrite" url="applications/{R:1}/static/{R:2}" logRewrittenUrl="false" />
</rule>
<rule name="web2py_app" enabled="true" stopProcessing="true">
<match url="(.*)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="Rewrite" url="handler.web2py/{R:1}" appendQueryString="true" />
</rule>
</rules>
<outboundRules>
<rule name="static_version_cache_control" preCondition="static_version">
<match serverVariable="RESPONSE_Cache-Control" pattern=".*" />
<action type="Rewrite" value="max-age=315360000" />
<conditions>
</conditions>
</rule>
<rule name="static_version_Expires" preCondition="static_version">
<match serverVariable="RESPONSE_Expires" pattern=".*" />
<action type="Rewrite" value="Thu, 31 Dec 2037 23:59:59 GMT" />
</rule>
<preConditions>
<preCondition name="static_version">
<add input="{REQUEST_URI}" pattern="(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
<handlers>
<!-- replace SCRIPT_PROCESSOR with the configured handler for python -->
<add name="Python_via_FastCGI" path="handler.web2py" verb="*" modules="FastCgiModule" scriptProcessor="SCRIPT_PROCESSOR" resourceType="Unspecified" requireAccess="Script" />
</handlers>
</system.webServer>
</configuration>
+9 -4
View File
@@ -99,7 +99,7 @@ class CacheAbstract(object):
"""
cache_stats_name = 'web2py_cache_statistics'
max_ram_utilization = 90 # percent
max_ram_utilization = None # percent
def __init__(self, request=None):
"""Initializes the object
@@ -353,7 +353,7 @@ class CacheOnDisk(CacheAbstract):
raise KeyError
self.wait_portalock(val_file)
value = pickle.load(recfile.open(key, 'rb', path=self.folder))
value = pickle.load(val_file)
val_file.close()
return value
@@ -473,9 +473,14 @@ class CacheOnDisk(CacheAbstract):
if item and ((dt is None) or (item[0] > now - dt)):
value = item[1]
else:
value = f()
try:
value = f()
except:
self.storage.release(CacheAbstract.cache_stats_name)
self.storage.release(key)
raise
self.storage[key] = (now, value)
self.storage.safe_apply(CacheAbstract.cache_stats_name, inc_misses,
self.storage.safe_apply(CacheAbstract.cache_stats_name, inc_misses,
default_value={'hit_total': 0, 'misses': 0})
self.storage.release(CacheAbstract.cache_stats_name)
+9 -8
View File
@@ -261,7 +261,7 @@ class LoadFactory(object):
import globals
target = target or 'c' + str(random.random())[2:]
attr['_id'] = target
request = self.environment['request']
request = current.request
if '.' in f:
f, extension = f.rsplit('.', 1)
if url or ajax:
@@ -532,10 +532,11 @@ def run_models_in(environment):
It tries pre-compiled models first before compiling them.
"""
folder = environment['request'].folder
c = environment['request'].controller
request = current.request
folder = request.folder
c = request.controller
#f = environment['request'].function
response = environment['response']
response = current.response
path = pjoin(folder, 'models')
cpath = pjoin(folder, 'compiled')
@@ -577,7 +578,7 @@ def run_controller_in(controller, function, environment):
"""
# if compiled should run compiled!
folder = environment['request'].folder
folder = current.request.folder
path = pjoin(folder, 'compiled')
badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function)
@@ -631,7 +632,7 @@ def run_controller_in(controller, function, environment):
layer = filename + ':' + function
code = getcfs(layer, filename, lambda: compile2(code, layer))
restricted(code, environment, filename)
response = environment['response']
response = current.response
vars = response._vars
if response.postprocessing:
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
@@ -649,8 +650,8 @@ def run_view_in(environment):
or `view/generic.extension`
It tries the pre-compiled views_controller_function.pyc before compiling it.
"""
request = environment['request']
response = environment['response']
request = current.request
response = current.response
view = response.view
folder = request.folder
path = pjoin(folder, 'compiled')
+13 -3
View File
@@ -521,9 +521,19 @@ def ldap_auth(server='ldap', port=None,
logging.error(
'There is no username or email for %s!' % username)
raise
db_group_search = db((db.auth_membership.user_id == db_user_id) &
(db.auth_user.id == db.auth_membership.user_id) &
(db.auth_group.id == db.auth_membership.group_id))
# if old pydal version, assume this is a relational database which can do joins
db_can_join = db.can_join() if hasattr(db, 'can_join') else True
if db_can_join:
db_group_search = db(
(db.auth_membership.user_id == db_user_id) &
(db.auth_user.id == db.auth_membership.user_id) &
(db.auth_group.id == db.auth_membership.group_id))
else:
# no joins on NoSQL databases, perform two queries
db_group_search = db(db.auth_membership.user_id == db_user_id)
group_ids = [x.group_id for x in db_group_search.select(
db.auth_membership.group_id, distinct=True)]
db_group_search = db(db.auth_group.id.belongs(group_ids))
db_groups_of_the_user = list()
db_group_id = dict()
+439 -421
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -67,11 +67,12 @@ def RedisCache(*args, **vars):
locker.acquire()
try:
if not hasattr(RedisCache, 'redis_instance'):
RedisCache.redis_instance = RedisClient(*args, **vars)
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisCache, instance_name):
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
return getattr(RedisCache, instance_name)
finally:
locker.release()
return RedisCache.redis_instance
class RedisClient(object):
+1 -1
View File
@@ -126,7 +126,7 @@ class History:
def globals_dict(self):
"""Returns a dictionary view of the globals.
"""
return dict((name, cPickle.loads(val))
return dict((name, pickle.loads(val))
for name, val in zip(self.global_names, self.globals))
def add_unpicklable(self, statement, names):
+35 -35
View File
@@ -4,7 +4,24 @@ from hashlib import sha1
class Stripe:
"""
Usage:
Use in WEB2PY (guaranteed PCI compliant)
def pay():
from gluon.contrib.stripe import StripeForm
form = StripeForm(
pk=STRIPE_PUBLISHABLE_KEY,
sk=STRIPE_SECRET_KEY,
amount=150, # $1.5 (amount is in cents)
description="Nothing").process()
if form.accepted:
payment_id = form.response['id']
redirect(URL('thank_you'))
elif form.errors:
redirect(URL('pay_error'))
return dict(form=form)
Low level API:
key='<api key>'
d = Stripe(key).charge(
amount=100, # 1 dollar!!!!
@@ -22,22 +39,6 @@ class Stripe:
{u'fee': 0, u'description': u'test charge', u'created': 1321242072, u'refunded': False, u'livemode': False, u'object': u'charge', u'currency': u'usd', u'amount': 100, u'paid': True, u'id': u'ch_sdjasgfga83asf', u'card': {u'exp_month': 5, u'country': u'US', u'object': u'card', u'last4': u'4242', u'exp_year': 2012, u'type': u'Visa'}}
if paid is True than transaction was processed
Use in WEB2PY (guaranteed PCI compliant)
def pay():
from gluon.contrib.stripe import StripeForm
form = StripeForm(
pk=STRIPE_PUBLISHABLE_KEY,
sk=STRIPE_SECRET_KEY,
amount=150, # $1.5 (amount is in cents)
description="Nothing").process()
if form.accepted:
payment_id = form.response['id']
redirect(URL('thank_you'))
elif form.errors:
redirect(URL('pay_error'))
return dict(form=form)
"""
URL_CHARGE = 'https://%s:@api.stripe.com/v1/charges'
@@ -188,37 +189,36 @@ jQuery(function(){
<h3>Payment Amount: {{=currency_symbol}} {{="%.2f" % (0.01*amount)}}</h3>
<form action="" method="POST" id="payment-form" class="form-horizontal">
<div class="form-row control-group">
<label class="control-label">Card Number</label>
<div class="controls">
<div class="form-row form-group">
<label class="col-sm-2 control-label">Card Number</label>
<div class="controls col-sm-10">
<input type="text" size="20" data-stripe="number"
placeholder="4242424242424242"/>
placeholder="4242424242424242" class="form-control"/>
</div>
</div>
<div class="form-row control-group">
<label class="control-label">CVC</label>
<div class="controls">
<div class="form-row form-group">
<label class="col-sm-2 control-label">CVC</label>
<div class="controls col-sm-10">
<input type="text" size="4" style="width:80px" data-stripe="cvc"
placeholder="XXX"/>
placeholder="XXX" class="form-control"/>
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
</div>
</div>
<div class="form-row control-group">
<label class="control-label">Expiration</label>
<div class="controls">
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
placeholder="MM"/>
<div class="form-row form-group">
<label class="col-sm-2 control-label">Expiration</label>
<div class="controls col-sm-10">
<input type="text" size="2" style="width:40px; display:inline-block"
data-stripe="exp-month" placeholder="MM" class="form-control"/>
/
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
placeholder="YYYY"/>
<input type="text" size="4" style="width:80px; display:inline-block"
data-stripe="exp-year" placeholder="YYYY" class="form-control"/>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="form-row form-group">
<div class="controls col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">Submit Payment</button>
<div class="payment-errors error hidden"></div>
</div>
+5 -5
View File
@@ -7,14 +7,14 @@
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Takes care of adapting pyDAL to web2py's needs
--------------------------------------------
-----------------------------------------------
"""
from pydal import DAL as DAL
from pydal import Field
from pydal.objects import Row, Rows, Table, Query, Expression
from pydal.objects import Row, Rows, Table, Query, Set, Expression
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
import copy_reg as copyreg
def _default_validators(db, field):
"""
@@ -81,12 +81,12 @@ def _default_validators(db, field):
requires[0] = validators.IS_EMPTY_OR(requires[0])
return requires
from gluon import serializers as w2p_serializers
from gluon.serializers import custom_json, xml
from gluon.utils import web2py_uuid
from gluon import sqlhtml
DAL.serializers = w2p_serializers
DAL.serializers = {'json': custom_json, 'xml': xml}
DAL.validators_method = _default_validators
DAL.uuid = lambda x: web2py_uuid()
DAL.representers = {
+48 -32
View File
@@ -26,6 +26,8 @@ import gluon.settings as settings
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
from gluon.settings import global_settings
from gluon import recfile
from gluon.cache import CacheInRam
from gluon.fileutils import copystream
import hashlib
import portalocker
try:
@@ -47,8 +49,7 @@ import cgi
import urlparse
import copy
import tempfile
from gluon.cache import CacheInRam
from gluon.fileutils import copystream
FMT = '%a, %d-%b-%Y %H:%M:%S PST'
PAST = 'Sat, 1-Jan-1971 00:00:00'
@@ -82,13 +83,22 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
css_inline = '<style type="text/css">\n%s\n</style>'
js_inline = '<script type="text/javascript">\n%s\n</script>'
template_mapping = {
'css': css_template,
'js': js_template,
'coffee': coffee_template,
'ts': typescript_template,
'less': less_template,
'css:inline': css_inline,
'js:inline': js_inline
}
# IMPORTANT:
# this is required so that pickled dict(s) and class.__dict__
# are sorted and web2py can detect without ambiguity when a session changes
class SortingPickler(Pickler):
def save_dict(self, obj):
self.write(EMPTY_DICT if self.bin else MARK+DICT)
self.write(EMPTY_DICT if self.bin else MARK + DICT)
self.memoize(obj)
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
@@ -193,6 +203,7 @@ class Request(Storage):
self.is_https = False
self.is_local = False
self.global_settings = settings.global_settings
self._uuid = None
def parse_get_vars(self):
"""Takes the QUERY_STRING and unpacks it to get_vars
@@ -275,7 +286,7 @@ class Request(Storage):
"""
self._vars = copy.copy(self.get_vars)
for key, value in self.post_vars.iteritems():
if not key in self._vars:
if key not in self._vars:
self._vars[key] = value
else:
if not isinstance(self._vars[key], list):
@@ -306,13 +317,21 @@ class Request(Storage):
self.parse_all_vars()
return self._vars
@property
def uuid(self):
"""Lazily uuid
"""
if self._uuid is None:
self.compute_uuid()
return self._uuid
def compute_uuid(self):
self.uuid = '%s/%s.%s.%s' % (
self._uuid = '%s/%s.%s.%s' % (
self.application,
self.client.replace(':', '_'),
self.now.strftime('%Y-%m-%d.%H-%M-%S'),
web2py_uuid())
return self.uuid
return self._uuid
def user_agent(self):
from gluon.contrib import user_agent_parser
@@ -436,29 +455,32 @@ class Response(Storage):
return page
def include_meta(self):
s = "\n";
s = "\n"
for meta in (self.meta or {}).iteritems():
k, v = meta
if isinstance(v,dict):
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
if isinstance(v, dict):
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
else:
s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
s += '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
self.write(s, escape=False)
def include_files(self, extensions=None):
"""
Caching method for writing out files.
Includes files (usually in the head).
Can minify and cache local files
By default, caches in ram for 5 minutes. To change,
response.cache_includes = (cache_method, time_expire).
Example: (cache.disk, 60) # caches to disk for 1 minute.
"""
from gluon import URL
files = []
ext_files = []
has_js = has_css = False
for item in self.files:
if extensions and not item.split('.')[-1] in extensions:
if isinstance(item, (list, tuple)):
ext_files.append(item)
continue
if extensions and not item.rpartition('.')[2] in extensions:
continue
if item in files:
continue
@@ -487,10 +509,13 @@ class Response(Storage):
time_expire)
else:
files = call_minify()
s = ''
files.extend(ext_files)
s = []
for item in files:
if isinstance(item, str):
f = item.lower().split('?')[0]
ext = f.rpartition('.')[2]
# if static_version we need also to check for
# static_version_urls. In that case, the _.x.x.x
# bit would have already been added by the URL()
@@ -498,24 +523,15 @@ class Response(Storage):
if self.static_version and not self.static_version_urls:
item = item.replace(
'/static/', '/static/_%s/' % self.static_version, 1)
if f.endswith('.css'):
s += css_template % item
elif f.endswith('.js'):
s += js_template % item
elif f.endswith('.coffee'):
s += coffee_template % item
elif f.endswith('.ts'):
# http://www.typescriptlang.org/
s += typescript_template % item
elif f.endswith('.less'):
s += less_template % item
tmpl = template_mapping.get(ext)
if tmpl:
s.append(tmpl % item)
elif isinstance(item, (list, tuple)):
f = item[0]
if f == 'css:inline':
s += css_inline % item[1]
elif f == 'js:inline':
s += js_inline % item[1]
self.write(s, escape=False)
tmpl = template_mapping.get(f)
if tmpl:
s.append(tmpl % item[1])
self.write(''.join(s), escape=False)
def stream(self,
stream,
@@ -663,7 +679,7 @@ class Response(Storage):
return handler(request, self, methods)
def toolbar(self):
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
from gluon.html import DIV, SCRIPT, BEAUTIFY, TAG, A
BUTTON = TAG.button
admin = URL("admin", "default", "design", extension='html',
args=current.request.application)
-1
View File
@@ -376,7 +376,6 @@ def wsgibase(environ, responder):
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
or env.https == 'on'
)
request.compute_uuid() # requires client
request.url = environ['PATH_INFO']
# ##################################################
+1 -1
View File
@@ -119,7 +119,7 @@ class LockedFile(object):
lock(self.file, LOCK_EX)
if not 'a' in mode:
self.file.seek(0)
self.file.truncate()
self.file.truncate(0)
else:
raise RuntimeError("invalid LockedFile(...,mode)")
+19 -15
View File
@@ -66,14 +66,15 @@ class XssCleaner(HTMLParser):
#to strip or escape disallowed tags?
self.strip_disallowed = strip_disallowed
self.in_disallowed = False
# there might be data after final closing tag, that is to be ignored
self.in_disallowed = [False]
def handle_data(self, data):
if data and not self.in_disallowed:
if data and not self.in_disallowed[-1]:
self.result += xssescape(data)
def handle_charref(self, ref):
if self.in_disallowed:
if self.in_disallowed[-1]:
return
elif len(ref) < 7 and (ref.isdigit() or ref == 'x27'): # x27 is a special case for apostrophe
self.result += '&#%s;' % ref
@@ -81,7 +82,7 @@ class XssCleaner(HTMLParser):
self.result += xssescape('&#%s' % ref)
def handle_entityref(self, ref):
if self.in_disallowed:
if self.in_disallowed[-1]:
return
elif ref in entitydefs:
self.result += '&%s;' % ref
@@ -89,7 +90,7 @@ class XssCleaner(HTMLParser):
self.result += xssescape('&%s' % ref)
def handle_comment(self, comment):
if self.in_disallowed:
if self.in_disallowed[-1]:
return
elif comment:
self.result += xssescape('<!--%s-->' % comment)
@@ -100,11 +101,11 @@ class XssCleaner(HTMLParser):
attrs
):
if tag not in self.permitted_tags:
if self.strip_disallowed:
self.in_disallowed = True
else:
self.in_disallowed.append(True)
if (not self.strip_disallowed):
self.result += xssescape('<%s>' % tag)
else:
self.in_disallowed.append(False)
bt = '<' + tag
if tag in self.allowed_attributes:
attrs = dict(attrs)
@@ -119,6 +120,7 @@ class XssCleaner(HTMLParser):
else:
bt += ' %s=%s' % (xssescape(attribute),
quoteattr(attrs[attribute]))
# deal with <a> without href and <img> without src
if bt == '<a' or bt == '<img':
return
if tag in self.requires_no_close:
@@ -129,10 +131,9 @@ class XssCleaner(HTMLParser):
def handle_endtag(self, tag):
bracketed = '</%s>' % tag
self.in_disallowed.pop()
if tag not in self.permitted_tags:
if self.strip_disallowed:
self.in_disallowed = False
else:
if (not self.strip_disallowed):
self.result += xssescape(bracketed)
elif tag in self.open_tags:
self.result += bracketed
@@ -143,10 +144,13 @@ class XssCleaner(HTMLParser):
Accepts relative, absolute, and mailto urls
"""
parsed = urlparse(url)
return (parsed[0] in self.allowed_schemes and '.' in parsed[1]) \
or (parsed[0] in self.allowed_schemes and '@' in parsed[2]) \
or (parsed[0] == '' and parsed[2].startswith('/'))
if url.startswith('#'):
return True
else:
parsed = urlparse(url)
return ((parsed[0] in self.allowed_schemes and '.' in parsed[1]) or
(parsed[0] in self.allowed_schemes and '@' in parsed[2]) or
(parsed[0] == '' and parsed[2].startswith('/')))
def strip(self, rawstring, escape=True):
"""
+1 -3
View File
@@ -41,9 +41,7 @@ def enable_autocomplete_and_history(adir, env):
except ImportError:
pass
else:
readline.parse_and_bind("bind ^I rl_complete"
if sys.platform == 'darwin'
else "tab: complete")
readline.parse_and_bind("tab: complete")
history_file = os.path.join(adir, '.pythonhistory')
try:
readline.read_history_file(history_file)
+57 -40
View File
@@ -58,9 +58,12 @@ def represent(field, value, record):
f = field.represent
if not callable(f):
return str(value)
n = f.func_code.co_argcount - len(f.func_defaults or [])
if getattr(f, 'im_self', None):
n -= 1
if hasattr(f,'func_code'):
n = f.func_code.co_argcount - len(f.func_defaults or [])
if getattr(f, 'im_self', None):
n -= 1
else:
n = 1
if n == 1:
return f(value)
elif n == 2:
@@ -856,14 +859,14 @@ def formstyle_bootstrap3_stacked(form, fields):
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
controls.add_class('form-control')
elif isinstance(controls, SPAN):
_controls = P(controls.components)
elif isinstance(controls, UL):
for e in controls.elements("input"):
e.add_class('form-control')
if isinstance(label, LABEL):
label['_class'] = 'control-label'
@@ -906,9 +909,9 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
controls.add_class('form-control')
elif isinstance(controls, SPAN):
_controls = P(controls.components,
_controls = P(controls.components,
_class="form-control-static %s" % col_class)
elif isinstance(controls, UL):
for e in controls.elements("input"):
@@ -1205,6 +1208,9 @@ class SQLFORM(FORM):
elif field.type == 'boolean':
inp = self.widgets.boolean.widget(
field, default, _disabled=True)
elif isinstance(field.type, SQLCustomType) and callable(field.type.represent):
# SQLCustomType has a represent, use it
inp = field.type.represent(default, record)
else:
inp = field.formatter(default)
if getattr(field, 'show_if', None):
@@ -1246,6 +1252,9 @@ class SQLFORM(FORM):
dspval = ''
elif field.type == 'blob':
continue
elif isinstance(field.type, SQLCustomType) and callable(field.type.widget):
# SQLCustomType has a widget, use it
inp = field.type.widget(field, default)
else:
field_type = widget_class.match(str(field.type)).group()
field_type = field_type in self.widgets and field_type or 'string'
@@ -1682,6 +1691,7 @@ class SQLFORM(FORM):
AUTOTYPES = {
type(''): ('string', None),
type(u''): ('string',None),
type(True): ('boolean', None),
type(1): ('integer', IS_INT_IN_RANGE(-1e12, +1e12)),
type(1.0): ('double', IS_FLOAT_IN_RANGE()),
@@ -1746,10 +1756,16 @@ class SQLFORM(FORM):
keywords = keywords[0]
request.vars.keywords = keywords
key = keywords.strip()
if key and ' ' not in key and not '"' in key and not "'" in key:
if key and not '"' in key:
SEARCHABLE_TYPES = ('string', 'text', 'list:string')
parts = [field.contains(
key) for field in fields if field.type in SEARCHABLE_TYPES]
sfields = [field for field in fields if field.type in SEARCHABLE_TYPES]
if settings.global_settings.web2py_runtime_gae:
return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields])
else:
return reduce(lambda a,b:a&b,[
reduce(lambda a,b: a|b, [
field.contains(k) for field in sfields]
) for k in key.split()])
# from https://groups.google.com/forum/#!topic/web2py/hKe6lI25Bv4
# needs testing...
@@ -1763,10 +1779,6 @@ class SQLFORM(FORM):
# filters.append(reduce(lambda a, b: (a & b), all_words_filters))
#parts = filters
else:
parts = None
if parts:
return reduce(lambda a, b: a | b, parts)
else:
return smart_query(fields, key)
@@ -1829,15 +1841,19 @@ class SQLFORM(FORM):
operators = SELECT(*[OPTION(T(option), _value=option) for option in options], _class='form-control')
_id = "%s_%s" % (value_id, name)
if field_type in ['boolean', 'double', 'time', 'integer']:
value_input = SQLFORM.widgets[field_type].widget(field, field.default, _id=_id, _class='form-control')
widget_ = SQLFORM.widgets[field_type]
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
elif field_type == 'date':
iso_format = {'_data-w2p_date_format' : '%Y-%m-%d'}
value_input = SQLFORM.widgets.date.widget(field, field.default, _id=_id, _class='form-control', **iso_format)
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
widget_ = SQLFORM.widgets.date
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif field_type == 'datetime':
iso_format = {'_data-w2p_datetime_format' : '%Y-%m-%d %H:%M:%S'}
value_input = SQLFORM.widgets.datetime.widget(field, field.default, _id=_id, _class='form-control', **iso_format)
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif (field_type.startswith('reference ') or
field_type.startswith('list:reference ')) and \
hasattr(field.requires, 'options') or \
hasattr(field.requires, 'options'):
value_input = SELECT(
*[OPTION(v, _value=k)
@@ -1847,7 +1863,8 @@ class SQLFORM(FORM):
elif field_type.startswith('reference ') or \
field_type.startswith('list:integer') or \
field_type.startswith('list:reference '):
value_input = SQLFORM.widgets.integer.widget(field, field.default, _id=_id, _class='form-control')
widget_ = SQLFORM.widgets.integer
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
else:
value_input = INPUT(
_type='text', _id=_id,
@@ -1958,7 +1975,8 @@ class SQLFORM(FORM):
cache_count=None,
client_side_delete=False,
ignore_common_filters=None,
auto_pagination=True):
auto_pagination=True,
use_cursor=False):
formstyle = formstyle or current.response.formstyle
@@ -2060,18 +2078,15 @@ class SQLFORM(FORM):
# is unique and usually indexed. See issue #679
if not orderby:
orderby = field_id
else:
if isinstance(orderby, Expression):
if orderby.first:
# here we're with a DESC order on a field
# stored as orderby.first
if orderby.first is not field_id:
orderby = orderby | field_id
else:
# here we're with an ASC order on a field
# stored as orderby
if orderby is not field_id:
orderby = orderby | field_id
elif isinstance(orderby, list):
orderby = reduce(lambda a,b: a|b, orderby)
elif isinstance(orderby, Field) and orderby is not field_id:
# here we're with an ASC order on a field stored as orderby
orderby = orderby | field_id
elif (isinstance(orderby, Expression) and
orderby.first and orderby.first is not field_id):
# here we're with a DESC order on a field stored as orderby.first
orderby = orderby | field_id
return orderby
def url(**b):
@@ -2099,10 +2114,8 @@ class SQLFORM(FORM):
# - url has valid signature (vars are not signed, only path_info)
# = url does not contain 'create','delete','edit' (readonly)
if user_signature:
if not (
'/'.join(str(a) for a in args) == '/'.join(request.args) or
URL.verify(request, user_signature=user_signature,
hash_vars=False) or
if not ('/'.join(map(str,args)) == '/'.join(map(str,request.args)) or
URL.verify(request, user_signature=user_signature, hash_vars=False) or
(request.args(len(args)) == 'view' and not logged)):
session.flash = T('not authorized')
redirect(referrer)
@@ -2533,7 +2546,7 @@ class SQLFORM(FORM):
cursor = True
# figure out what page we are one to setup the limitby
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
cursor = request.vars.cursor or True
limitby = (0, paginate)
try:
@@ -2555,7 +2568,7 @@ class SQLFORM(FORM):
table_fields = [field for field in fields
if (field.tablename in tablenames and
not(isinstance(field, Field.Virtual)))]
if dbset._db._adapter.dbengine == 'google:datastore':
if dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
rows = dbset.select(left=left, orderby=orderby,
groupby=groupby, limitby=limitby,
reusecursor=cursor,
@@ -2565,6 +2578,7 @@ class SQLFORM(FORM):
rows = dbset.select(left=left, orderby=orderby,
groupby=groupby, limitby=limitby,
cacheable=True, *table_fields)
next_cursor = None
except SyntaxError:
rows = None
next_cursor = None
@@ -2583,7 +2597,7 @@ class SQLFORM(FORM):
console.append(DIV(message or '', _class='web2py_counter'))
paginator = UL()
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
# this means we may have a large table with an unknown number of rows.
try:
page = int(request.vars.page or 1) - 1
@@ -2708,6 +2722,9 @@ class SQLFORM(FORM):
_href='%s/%s' % (upload, value))
else:
value = ''
elif isinstance(field.type, SQLCustomType) and callable(field.type.represent):
# SQLCustomType has a represent, use it
value = field.type.represent(value, row)
if isinstance(value, str):
value = truncate_string(value, maxlength)
elif not isinstance(value, XmlComponent):
+31 -28
View File
@@ -269,31 +269,33 @@ class FastStorage(dict):
class List(list):
"""
Like a regular python list but a[i] if i is out of bounds returns None
instead of `IndexOutOfBounds`
Like a regular python list but callable.
When a(i) is called if i is out of bounds returns None
instead of `IndexError`.
"""
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
"""Allows to use a special syntax for fast-check of `request.args()`
validity
Args:
"""Allows to use a special syntax for fast-check of
`request.args()` validity.
:params:
i: index
default: use this value if arg not found
cast: type cast
otherwise: can be:
- None: results in a 404
- str: redirect to this address
- callable: calls the function (nothing is passed)
otherwise:
will be executed when:
- casts fail
- value not found, dont have default and otherwise is
especified
can be:
- None: results in a 404
- str: redirect to this address
- callable: calls the function (nothing is passed)
Example:
You can use::
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""
n = len(self)
if 0 <= i < n or -n <= i < 0:
@@ -301,23 +303,24 @@ class List(list):
elif default is DEFAULT:
value = None
else:
value, cast = default, False
if cast:
try:
value, cast, otherwise = default, False, False
try:
if cast:
value = cast(value)
except (ValueError, TypeError):
from http import HTTP, redirect
if otherwise is None:
raise HTTP(404)
elif isinstance(otherwise, str):
redirect(otherwise)
elif callable(otherwise):
return otherwise()
else:
raise RuntimeError("invalid otherwise")
if not value and otherwise:
raise ValueError('Otherwise will raised.')
except (ValueError, TypeError):
from http import HTTP, redirect
if otherwise is None:
raise HTTP(404)
elif isinstance(otherwise, str):
redirect(otherwise)
elif callable(otherwise):
return otherwise()
else:
raise RuntimeError("invalid otherwise")
return value
if __name__ == '__main__':
import doctest
doctest.testmod()
+1
View File
@@ -4,6 +4,7 @@ from test_http import *
from test_cache import *
from test_contenttype import *
from test_fileutils import *
from test_globals import *
from test_html import *
from test_is_url import *
from test_languages import *
+1
View File
@@ -26,6 +26,7 @@ exclude_lines =
ignore_errors = True
omit = gluon/contrib/*
gluon/tests/*
gluon/packages/*
[html]
directory = coverage_html_report
+34 -1
View File
@@ -13,6 +13,7 @@ fix_sys_path(__file__)
from storage import Storage
from cache import CacheInRam, CacheOnDisk, Cache
from gluon.dal import DAL, Field
oldcwd = None
@@ -30,6 +31,11 @@ def tearDownModule():
if oldcwd:
os.chdir(oldcwd)
oldcwd = None
try:
os.unlink('dummy.db')
except:
pass
class TestCache(unittest.TestCase):
@@ -107,7 +113,34 @@ class TestCache(unittest.TestCase):
cache.clear(regex=r'a*')
self.assertEqual(cache('a1', lambda: 2, 0), 2)
self.assertEqual(cache('a2', lambda: 3, 100), 3)
return
def testDALcache(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)
db = DAL(check_reserved=['all'])
db.define_table('t_a', Field('f_a'))
db.t_a.insert(f_a='test')
db.commit()
a = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
b = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
self.assertEqual(a.as_csv(), b.as_csv())
c = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
d = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
self.assertEqual(c.as_csv(), d.as_csv())
self.assertEqual(a.as_csv(), c.as_csv())
self.assertEqual(b.as_csv(), d.as_csv())
e = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
f = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
self.assertEqual(e.as_csv(), f.as_csv())
self.assertEqual(a.as_csv(), f.as_csv())
g = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
h = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
self.assertEqual(g.as_csv(), h.as_csv())
self.assertEqual(a.as_csv(), h.as_csv())
db.t_a.drop()
db.close()
if __name__ == '__main__':
setUpModule() # pre-python-2.7
+19 -3
View File
@@ -4,6 +4,7 @@
Unit tests for gluon.dal
"""
import os
import unittest
from fix_path import fix_sys_path
@@ -12,28 +13,43 @@ fix_sys_path(__file__)
from gluon.dal import DAL, Field
def tearDownModule():
try:
os.unlink('dummy.db')
except:
pass
class TestDALSubclass(unittest.TestCase):
def testRun(self):
import gluon.serializers as mserializers
from gluon.serializers import custom_json, xml
from gluon import sqlhtml
db = DAL(check_reserved=['all'])
self.assertEqual(db.serializers, mserializers)
self.assertEqual(db.serializers['json'], custom_json)
self.assertEqual(db.serializers['xml'], xml)
self.assertEqual(db.representers['rows_render'], sqlhtml.represent)
self.assertEqual(db.representers['rows_xml'], sqlhtml.SQLTABLE)
db.close()
def testSerialization(self):
import pickle
db = DAL(check_reserved=['all'])
db.define_table('t_a', Field('f_a'))
db.t_a.insert(f_a='test')
a = db(db.t_a.id>0).select(cacheable=True)
a = db(db.t_a.id > 0).select(cacheable=True)
s = pickle.dumps(a)
b = pickle.loads(s)
self.assertEqual(a.db, b.db)
db.t_a.drop()
db.close()
""" TODO:
class TestDefaultValidators(unittest.TestCase):
def testRun(self):
pass
"""
if __name__ == '__main__':
unittest.main()
tearDownModule()
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.globals
"""
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.globals import Response
from gluon import URL
class testResponse(unittest.TestCase):
def test_include_files(self):
def return_includes(response, extensions=None):
response.include_files(extensions)
return response.body.getvalue()
response = Response()
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.js" type="text/javascript"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.coffee'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.coffee" type="text/coffee"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.ts" type="text/typescript"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.less'))
content = return_includes(response)
self.assertEqual(content, '<link href="/a/static/css/file.less" rel="stylesheet/less" type="text/css" />')
response = Response()
response.files.append(('css:inline', 'background-color; white;'))
content = return_includes(response)
self.assertEqual(content, '<style type="text/css">\nbackground-color; white;\n</style>')
response = Response()
response.files.append(('js:inline', 'alert("hello")'))
content = return_includes(response)
self.assertEqual(content, '<script type="text/javascript">\nalert("hello")\n</script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js')
content = return_includes(response)
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
content = return_includes(response)
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append(URL('a', 'static', 'css/file.css'))
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append(URL('a', 'static', 'css/file.css'))
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
)
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response, extensions=['css'])
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
#regr test for #628
response = Response()
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
content = return_includes(response)
self.assertEqual(content, '')
#regr test for #628
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
response = Response()
response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false'])
content = return_includes(response)
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
response = Response()
response.files.append(('js1', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
self.assertEqual(content, '')
if __name__ == '__main__':
unittest.main()
+18
View File
@@ -120,6 +120,7 @@ class TestStorageList(unittest.TestCase):
class TestList(unittest.TestCase):
""" Tests Storage.List (fast-check for request.args()) """
def test_listcall(self):
@@ -134,6 +135,23 @@ class TestList(unittest.TestCase):
self.assertEqual(a(3, cast=int), 1234)
a.append('x')
self.assertRaises(HTTP, a, 4, cast=int)
b = List()
# default is always returned when especified
self.assertEqual(b(0, cast=int, default=None), None)
self.assertEqual(b(0, cast=int, default=None, otherwise='teste'), None)
self.assertEqual(b(0, cast=int, default='a', otherwise='teste'), 'a')
# if don't have value and otherwise is especified it will called
self.assertEqual(b(0, otherwise=lambda: 'something'), 'something')
self.assertEqual(b(0, cast=int, otherwise=lambda: 'something'),
'something')
# except if default is especified
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
def test_listgetitem(self):
'''Mantains list behaviour.'''
a = List((1, 2, 3))
self.assertEqual(a[0], 1)
self.assertEqual(a[::-1], [3, 2, 1])
if __name__ == '__main__':
+365 -25
View File
@@ -60,7 +60,7 @@ except ImportError:
# fallback to pure-Python module
import gluon.contrib.simplejson as json_parser
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'Wiki',
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
### mind there are two loggers here (logger and crud.settings.logger)!
@@ -767,8 +767,8 @@ class Mail(object):
if self.settings.server == 'logging':
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' %
('-' * 40, sender,
', '.join(to), subject,
text or html, '-' * 40))
', '.join(to), subject,
text or html, '-' * 40))
elif self.settings.server == 'gae':
xcc = dict()
if cc:
@@ -779,23 +779,23 @@ class Mail(object):
xcc['reply_to'] = reply_to
from google.appengine.api import mail
attachments = attachments and [mail.Attachment(
a.my_filename,
a.my_filename,
a.my_payload,
contebt_id='<attachment-%s>' % k
) for k,a in enumerate(attachments) if not raw]
if attachments:
result = mail.send_mail(
sender=sender, to=origTo,
subject=subject, body=text, html=html,
subject=unicode(subject), body=unicode(text), html=html,
attachments=attachments, **xcc)
elif html and (not raw):
result = mail.send_mail(
sender=sender, to=origTo,
subject=subject, body=text, html=html, **xcc)
subject=unicode(subject), body=unicode(text), html=html, **xcc)
else:
result = mail.send_mail(
sender=sender, to=origTo,
subject=subject, body=text, **xcc)
subject=unicode(subject), body=unicode(text), **xcc)
else:
smtp_args = self.settings.server.split(':')
kwargs = dict(timeout=self.settings.timeout)
@@ -965,7 +965,142 @@ class Recaptcha(DIV):
return XML(captcha).xml()
# this should only be used for catcha and perhaps not even for that
class Recaptcha2(DIV):
"""
Experimental:
Creates a DIV holding the newer Recaptcha from Google (v2)
Args:
request : the request. If not passed, uses current request
public_key : the public key Google gave you
private_key : the private key Google gave you
error_message : the error message to show if verification fails
label : the label to use
options (dict) : takes these parameters
- hl
- theme
- type
- tabindex
- callback
- expired-callback
see https://developers.google.com/recaptcha/docs/display for docs about those
comment : the comment
Examples:
Use as::
form = FORM(Recaptcha2(public_key='...',private_key='...'))
or::
form = SQLFORM(...)
form.append(Recaptcha2(public_key='...',private_key='...'))
to protect the login page instead, use::
from gluon.tools import Recaptcha2
auth.settings.captcha = Recaptcha2(request, public_key='...',private_key='...')
"""
API_URI = 'https://www.google.com/recaptcha/api.js'
VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify'
def __init__(self,
request=None,
public_key='',
private_key='',
error_message='invalid',
label='Verify:',
options=None,
comment='',
):
request = request or current.request
self.request_vars = request and request.vars or current.request.vars
self.remote_addr = request.env.remote_addr
self.public_key = public_key
self.private_key = private_key
self.errors = Storage()
self.error_message = error_message
self.components = []
self.attributes = {}
self.label = label
self.options = options or {}
self.comment = comment
def _validate(self):
recaptcha_response_field = self.request_vars.pop('g-recaptcha-response', None)
remoteip = self.remote_addr
if not recaptcha_response_field:
self.errors['captcha'] = self.error_message
return False
params = urllib.urlencode({
'secret': self.private_key,
'remoteip': remoteip,
'response': recaptcha_response_field,
})
request = urllib2.Request(
url=self.VERIFY_SERVER,
data=params,
headers={'Content-type': 'application/x-www-form-urlencoded',
'User-agent': 'reCAPTCHA Python'})
httpresp = urllib2.urlopen(request)
content = httpresp.read()
httpresp.close()
try:
response_dict = json_parser.loads(content)
except:
self.errors['captcha'] = self.error_message
return False
if response_dict.get('success', False):
self.request_vars.captcha = ''
return True
else:
self.errors['captcha'] = self.error_message
return False
def xml(self):
api_uri = self.API_URI
hl = self.options.pop('hl', None)
if hl:
api_uri = self.API_URI + '?hl=%s' % hl
public_key = self.public_key
self.options['sitekey'] = public_key
captcha = DIV(
SCRIPT(_src=api_uri, _async='', _defer=''),
DIV(_class="g-recaptcha", data=self.options),
TAG.noscript(XML("""
<div style="width: 302px; height: 352px;">
<div style="width: 302px; height: 352px; position: relative;">
<div style="width: 302px; height: 352px; position: absolute;">
<iframe src="https://www.google.com/recaptcha/api/fallback?k=%(public_key)s"
frameborder="0" scrolling="no"
style="width: 302px; height:352px; border-style: none;">
</iframe>
</div>
<div style="width: 250px; height: 80px; position: absolute; border-style: none;
bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
class="g-recaptcha-response"
style="width: 250px; height: 80px; border: 1px solid #c1c1c1;
margin: 0px; padding: 0px; resize: none;" value="">
</textarea>
</div>
</div>
</div>""" % dict(public_key=public_key))
)
)
if not self.errors.captcha:
return XML(captcha).xml()
else:
captcha.append(DIV(self.errors['captcha'], _class='error'))
return XML(captcha).xml()
# this should only be used for captcha and perhaps not even for that
def addrow(form, a, b, c, style, _id, position=-1):
if style == "divs":
form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'),
@@ -987,6 +1122,15 @@ def addrow(form, a, b, c, style, _id, position=-1):
DIV(b, SPAN(c, _class='inline-help'),
_class='controls'),
_class='control-group', _id=_id))
elif style == "bootstrap3_inline":
form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'),
DIV(b, SPAN(c, _class='help-block'),
_class='col-sm-9'),
_class='form-group', _id=_id))
elif style == "bootstrap3_stacked":
form[0].insert(position, DIV(LABEL(a, _class='control-label'),
b, SPAN(c, _class='help-block'),
_class='form-group', _id=_id))
else:
form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'),
TD(b, _class='w2p_fw'),
@@ -1002,6 +1146,7 @@ class Auth(object):
reset_password_requires_verification=False,
registration_requires_verification=False,
registration_requires_approval=False,
bulk_register_enabled=False,
login_after_registration=False,
login_after_password_change=True,
alternate_requires_registration=False,
@@ -1035,6 +1180,7 @@ class Auth(object):
table_permission_name='auth_permission',
table_event_name='auth_event',
table_cas_name='auth_cas',
table_token_name='auth_token',
table_user=None,
table_group=None,
table_membership=None,
@@ -1103,6 +1249,8 @@ class Auth(object):
retrieve_password_subject='Password retrieve',
reset_password='Click on the link %(link)s to reset your password',
reset_password_subject='Password reset',
bulk_invite_subject='Invitation to join%(site)s',
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete the process',
invalid_reset_password='Invalid reset password',
profile_updated='Profile updated',
new_password='New password',
@@ -1316,6 +1464,7 @@ class Auth(object):
settings.update(Auth.default_settings)
settings.update(
cas_domains=[request.env.http_host],
enable_tokens=False,
cas_provider=cas_provider,
cas_actions=dict(login='login',
validate='validate',
@@ -1330,8 +1479,7 @@ class Auth(object):
logged_url=URL(controller, function, args='profile'),
download_url=URL(controller, 'download'),
mailer=(mailer is True) and Mail() or mailer,
on_failed_authorization =
URL(controller, function, args='not_authorized'),
on_failed_authorization = URL(controller, function, args='not_authorized'),
login_next = url_index,
login_onvalidation = [],
login_onaccept = [],
@@ -1356,6 +1504,8 @@ class Auth(object):
change_password_onvalidation = [],
change_password_onaccept = [],
retrieve_password_onvalidation = [],
request_reset_password_onvalidation = [],
request_reset_password_onaccept = [],
reset_password_onvalidation = [],
reset_password_onaccept = [],
hmac_key = hmac_key,
@@ -1417,6 +1567,9 @@ class Auth(object):
def table_cas(self):
return self.db[self.settings.table_cas_name]
def table_token(self):
return self.db[self.settings.table_token_name]
def _HTTP(self, *a, **b):
"""
only used in lambda: self._HTTP(404)
@@ -1444,7 +1597,8 @@ class Auth(object):
'retrieve_username', 'retrieve_password',
'reset_password', 'request_reset_password',
'change_password', 'profile', 'groups',
'impersonate', 'not_authorized'):
'impersonate', 'not_authorized', 'confirm_registration',
'bulk_register','manage_tokens'):
if len(request.args) >= 2 and args[0] == 'impersonate':
return getattr(self, args[0])(request.args[1])
else:
@@ -1771,7 +1925,7 @@ class Auth(object):
writable=False, readable=False,
label=T('Modified By'), ondelete=ondelete))
def define_tables(self, username=None, signature=None,
def define_tables(self, username=None, signature=None, enable_tokens=False,
migrate=None, fake_migrate=None):
"""
To be called unless tables are defined manually
@@ -1798,6 +1952,7 @@ class Auth(object):
username = settings.use_username
else:
settings.use_username = username
settings.enable_tokens = enable_tokens
if not self.signature:
self.define_signature()
if signature == True:
@@ -1981,6 +2136,21 @@ class Auth(object):
migrate=self.__get_migrate(
settings.table_cas_name, migrate),
fake_migrate=fake_migrate))
if settings.enable_tokens:
extra_fields = settings.extra_fields.get(
settings.table_token_name, []) + signature_list
if not settings.table_token_name in db.tables:
db.define_table(
settings.table_token_name,
Field('user_id', reference_table_user, default=None,
label=self.messages.label_user_id),
Field('expires_on', 'datetime', default=datetime.datetime(2999,12,31)),
Field('token',writable=False,default=web2py_uuid(),unique=True),
*extra_fields,
**dict(
migrate=self.__get_migrate(
settings.table_token_name, migrate),
fake_migrate=fake_migrate))
if not db._lazy_tables:
settings.table_user = db[settings.table_user_name]
settings.table_group = db[settings.table_group_name]
@@ -2180,8 +2350,8 @@ class Auth(object):
# user not in database try other login methods
for login_method in self.settings.login_methods:
if login_method != self and login_method(username, password):
self.user = username
return username
self.user = user
return user
return False
def register_bare(self, **fields):
@@ -2190,14 +2360,16 @@ class Auth(object):
and a raw password.
"""
settings = self._get_login_settings()
if not fields.get(settings.passfield):
raise ValueError("register_bare: " +
"password not provided or invalid")
elif not fields.get(settings.userfield):
# users can register_bare even if no password is provided,
# in this case they will have to reset their password to login
if fields.get(settings.passfield):
fields[settings.passfield] = \
settings.table_user[settings.passfield].validate(fields[settings.passfield])[0]
if not fields.get(settings.userfield):
raise ValueError("register_bare: " +
"userfield not provided or invalid")
fields[settings.passfield] = settings.table_user[settings.passfield].validate(fields[settings.passfield])[0]
user = self.get_or_create_user(fields, login=False, get=False, update_fields=self.settings.update_fields)
user = self.get_or_create_user(fields, login=False, get=False,
update_fields=self.settings.update_fields)
if not user:
# get or create did not create a user (it ignores duplicate records)
return False
@@ -2993,6 +3165,147 @@ class Auth(object):
table_user.email.requires = old_requires
return form
def confirm_registration(
self,
next=DEFAULT,
onvalidation=DEFAULT,
onaccept=DEFAULT,
log=DEFAULT,
):
"""
Returns a form to confirm user registration
"""
table_user = self.table_user()
request = current.request
# response = current.response
session = current.session
if next is DEFAULT:
next = self.get_vars_next() or self.settings.reset_password_next
if self.settings.prevent_password_reset_attacks:
key = request.vars.key
if not key and len(request.args)>1:
key = request.args[-1]
if key:
session._reset_password_key = key
redirect(self.url(args='confirm_registration'))
else:
key = session._reset_password_key
else:
key = request.vars.key or getarg(-1)
try:
t0 = int(key.split('-')[0])
if time.time() - t0 > 60 * 60 * 24:
raise Exception
user = table_user(reset_password_key=key)
if not user:
raise Exception
except Exception as e:
session.flash = self.messages.invalid_reset_password
redirect(self.url('login', vars=dict(test=e)))
redirect(next, client_side=self.settings.client_side)
passfield = self.settings.password_field
form = SQLFORM.factory(
Field('first_name',
label='First Name',
required=True),
Field('last_name',
label='Last Name',
required=True),
Field('new_password', 'password',
label=self.messages.new_password,
requires=self.table_user()[passfield].requires),
Field('new_password2', 'password',
label=self.messages.verify_password,
requires=[IS_EXPR(
'value==%s' % repr(request.vars.new_password),
self.messages.mismatched_password)]),
submit_button='Confirm Registration',
hidden=dict(_next=next),
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
)
if form.process().accepted:
user.update_record(
**{passfield: str(form.vars.new_password),
'first_name': str(form.vars.first_name),
'last_name': str(form.vars.last_name),
'registration_key': '',
'reset_password_key': ''})
session.flash = self.messages.password_changed
if self.settings.login_after_password_change:
self.login_user(user)
redirect(next, client_side=self.settings.client_side)
return form
def email_registration(self, subject, body, user):
"""
Sends and email invitation to a user informing they have been registered with the application
"""
reset_password_key = str(int(time.time())) + '-' + web2py_uuid()
link = self.url(self.settings.function,
args=('confirm_registration',), vars={'key': reset_password_key},
scheme=True)
d = dict(user)
d.update(dict(key=reset_password_key, link=link, site=current.request.env.http_host))
if self.settings.mailer and self.settings.mailer.send(
to=user.email,
subject=subject % d,
message=body % d):
user.update_record(reset_password_key=reset_password_key)
return True
return False
def bulk_register(self, max_emails=100):
"""
Creates a form for ther user to send invites to other users to join
"""
if not self.user:
redirect(self.settings.login_url)
if not self.setting.bulk_register_enabled:
return HTTP(404)
form = SQLFORM.factory(
Field('subject','string',default=self.messages.bulk_invite_subject,requires=IS_NOT_EMPTY()),
Field('emails','text',requires=IS_NOT_EMPTY()),
Field('message','text',default=self.messages.bulk_invite_body,requires=IS_NOT_EMPTY()),
formstyle=self.settings.formstyle)
if form.process().accepted:
emails = re.compile('[^\s\'"@<>,;:]+\@[^\s\'"@<>,;:]+').findall(form.vars.emails)
# send the invitations
emails_sent = []
emails_fail = []
emails_exist = []
for email in emails[:max_emails]:
if self.table_user()(email=email):
emails_exist.append(email)
else:
user = self.register_bare(email=email)
if self.email_registration(form.vars.subject, form.vars.message, user):
emails_sent.append(email)
else:
emails_fail.append(email)
emails_fail += emails[max_emails:]
form = DIV(H4('Emails sent'),UL(*[A(x,_href='mailto:'+x) for x in emails_sent]),
H4('Emails failed'),UL(*[A(x,_href='mailto:'+x) for x in emails_fail]),
H4('Emails existing'),UL(*[A(x,_href='mailto:'+x) for x in emails_exist]))
return form
def manage_tokens(self):
if not self.user:
redirect(self.settings.login_url)
table_token =self.table_token()
table_token.user_id.writable = False
table_token.user_id.default = self.user.id
table_token.token.writable = False
if current.request.args(1) == 'new':
table_token.token.readable = False
form = SQLFORM.grid(table_token, args=['manage_tokens'])
return form
def reset_password(self,
next=DEFAULT,
onvalidation=DEFAULT,
@@ -3030,6 +3343,12 @@ class Auth(object):
except Exception:
session.flash = self.messages.invalid_reset_password
redirect(next, client_side=self.settings.client_side)
if onvalidation is DEFAULT:
onvalidation = self.settings.reset_password_onvalidation
if onaccept is DEFAULT:
onaccept = self.settings.reset_password_onaccept
passfield = self.settings.password_field
form = SQLFORM.factory(
Field('new_password', 'password',
@@ -3045,7 +3364,7 @@ class Auth(object):
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
)
if form.accepts(request, session,
if form.accepts(request, session, onvalidation=onvalidation,
hideerror=self.settings.hideerror):
user.update_record(
**{passfield: str(form.vars.new_password),
@@ -3054,6 +3373,7 @@ class Auth(object):
session.flash = self.messages.password_changed
if self.settings.login_after_password_change:
self.login_user(user)
callback(onaccept, form)
redirect(next, client_side=self.settings.client_side)
return form
@@ -3079,9 +3399,9 @@ class Auth(object):
response.flash = self.messages.function_disabled
return ''
if onvalidation is DEFAULT:
onvalidation = self.settings.reset_password_onvalidation
onvalidation = self.settings.request_reset_password_onvalidation
if onaccept is DEFAULT:
onaccept = self.settings.reset_password_onaccept
onaccept = self.settings.request_reset_password_onaccept
if log is DEFAULT:
log = self.messages['reset_password_log']
userfield = self.settings.login_userfield or 'username' \
@@ -3451,6 +3771,26 @@ class Auth(object):
"""
return self.requires(True, otherwise=otherwise)
def requires_login_or_token(self, otherwise=None):
if self.settings.enable_tokens == True:
user = None
request = current.request
token = request.env.http_web2py_user_token or request.vars._token
table_token = self.table_token()
table_user = self.table_user()
from gluon.settings import global_settings
if global_settings.web2py_runtime_gae:
row = table_token(token=token)
if row:
user = table_user(row.user_id)
else:
row = self.db(table_token.token==token)(table_user.id==table_token.user_id).select().first()
if row:
user = row[table_user._tablename]
if user:
self.login_user(user)
return self.requires(True, otherwise=otherwise)
def requires_membership(self, role=None, group_id=None, otherwise=None):
"""
Decorator that prevents access to action if not logged in or
@@ -3571,7 +3911,7 @@ class Auth(object):
return record.id
else:
id = membership.insert(group_id=group_id, user_id=user_id)
if role:
if role:
self.user_groups[group_id] = role
else:
self.update_groups()
@@ -5362,7 +5702,7 @@ class Expose(object):
if current.request.raw_args:
self.args = [arg for arg in current.request.raw_args.split('/') if arg]
else:
self.args = [arg for arg in current.request.args if args]
self.args = [arg for arg in current.request.args if arg]
filename = os.path.join(base, *self.args)
if not os.path.exists(filename):
raise HTTP(404, "FILE NOT FOUND")
+6 -3
View File
@@ -200,8 +200,11 @@ class IS_MATCH(Validator):
self.is_unicode = is_unicode
def __call__(self, value):
if self.is_unicode and not isinstance(value, unicode):
match = self.regex.search(str(value).decode('utf8'))
if self.is_unicode:
if isinstance(value,unicode):
match = self.regex.search(value)
else:
match = self.regex.search(str(value).decode('utf8'))
else:
match = self.regex.search(str(value))
if match is not None:
@@ -611,7 +614,7 @@ class IS_IN_DB(Validator):
def count(values, s=self.dbset, f=field):
return s(f.belongs(map(int, values))).count()
if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
if GoogleDatastoreAdapter is not None and isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
range_ids = range(0, len(values), 30)
total = sum(count(values[i:i + 30]) for i in range_ids)
if total == len(values):
+1 -1
View File
@@ -84,7 +84,7 @@ server {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
ssl_protocols SSLv3 TLSv1;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
keepalive_timeout 70;
location / {
#uwsgi_pass 127.0.0.1:9001;
+169
View File
@@ -0,0 +1,169 @@
"This script will work fine for a few cases 'by default':"
" - completely CLEAN WS2012R2 host"
" - python 2.7 installed in the default path"
" - wfasctgi installed on the default path"
"It'll install web2py under the default website "
" You can use it as a boilerplate to automate your deployments"
" but it still is released AS IT IS. "
"BIG FAT WARNING: It will install a bunch of dependecies
Inspect the source before executing it"
""
""
$ErrorActionPreference = 'stop'
$REALLY_SURE = Read-Host "Do you want to start with web2py deployment? [y/N]"
if (!@('y', 'Y') -contains $REALLY_SURE) {
"Ok, Exiting without doing anything"
exit 1
}
#setting root folder
$rootfolder = $pwd
### utilities - start
function ask_a_question($question) {
$response = Read-Host "$question [Y/n]"
if (@('Y', 'y', '', $null) -contains $response) {
return $true
} else {
return $false
}
}
function unzip_me {
#Load the assembly
[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
#Unzip the file
[System.IO.Compression.ZipFile]::ExtractToDirectory($pathToZip, $targetDir)
}
### utilities - end
#install 4.5 that is needed for a bunch of things anyway
Install-WindowsFeature Net-Framework-45-Core
#fetch web2py
$web2py_url = 'http://www.web2py.com/examples/static/web2py_src.zip'
$web2py_file = "$pwd\web2py_src.zip"
if (!(Test-Path $web2py_file)) {
(new-object net.webclient).DownloadFile($web2py_url, $web2py_file)
}
#Load the assembly
[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
#Unzip the file
[System.IO.Compression.ZipFile]::ExtractToDirectory($web2py_file, $pwd)
#features installation (IIS, needed modules, python, chocolatey, etc)
$installfeatures = ask_a_question('Do you want to install needed features?')
if ($installfeatures) {
Install-WindowsFeature Web-Server,Web-Default-Doc,Web-Static-Content,Web-Http-Redirect,Web-Http-Logging,Web-Request-Monitor,`
Web-Http-Tracing,Web-Stat-Compression,Web-Dyn-Compression,Web-Filtering,Web-Basic-Auth,Web-Windows-Auth,Web-AppInit,`
Web-CGI,Web-WebSockets,Web-Mgmt-Console,Web-Net-Ext45
}
$copy_web2py = ask_a_question("Copy web2py to the default website root?")
if ($copy_web2py) {
Import-Module WebAdministration
$available_websites = Get-Website
if ($available_websites[0] -eq $null) {
$default_one = $available_websites
} else {
$default_one = $available_websites[0]
}
$iis_root = [System.Environment]::ExpandEnvironmentVariables($default_one.PhysicalPath)
Copy-Item "$rootfolder\web2py\*" $iis_root -Recurse
$rootfolder = $iis_root
$acl = (Get-Item $rootfolder).GetAccessControl('Access')
$identity = "BUILTIN\IIS_IUSRS"
$fileSystemRights = "Modify"
$inheritanceFlags = "ContainerInherit, ObjectInherit"
$propagationFlags = "None"
$accessControlType = "Allow"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $inheritanceFlags, $propagationFlags, $accessControlType)
$acl.SetAccessRule($rule)
Set-Acl $rootfolder $acl
}
$create_cert = ask_a_question("Do you want to create a self-signed SSL cert?")
if ($create_cert) {
$cert = New-SelfSignedCertificate -DnsName ("localtest.me","*.localtest.me") -CertStoreLocation cert:\LocalMachine\My
$rootStore = Get-Item cert:\LocalMachine\Root
$rootStore.Open("ReadWrite")
$rootStore.Add($cert)
$rootStore.Close();
Import-Module WebAdministration
Set-Location IIS:\SslBindings
New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https
$cert | New-Item 0.0.0.0!443
Set-Location $pwd
}
"checking for chocolatey"
if (Get-Command "choco.exe" -ErrorAction SilentlyContinue)
{
"chocolatey found"
} else {
"installing chocolatey"
(new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1') | iex
}
"installing url-rewrite"
choco install UrlRewrite
$pythonexe = Read-Host 'Python.exe path [C:\Python27\python.exe]'
if (($pythonexe -eq '') -or ($pythonexe -eq $null)) {
$pythonexe = 'C:\Python27\python.exe'
}
if (!(Test-Path $pythonexe)) {
"ERROR: python executable not found"
$pythonwanted = ask_a_question("do you want to install it automatically?")
if ($pythonwanted) {
choco install webpicmd
WebpiCmd.exe /Install /Products:WFastCgi_21_279
$pythonexe = 'C:\Python27\python.exe'
}
else {
exit 1
}
}
$wfastcgipath = Read-Host 'wfastcgi.py path [C:\Python27\Scripts\wfastcgi.py]'
if (($wfastcgipath -eq '') -or ($wfastcgipath -eq $null)) {
$wfastcgipath = 'C:\Python27\Scripts\wfastcgi.py'
}
if (-not (Test-Path $wfastcgipath)) {
"ERROR: wfastcgi.py not found"
$wfastcgiwanted = ask_a_question("do you want to install it automatically?")
if ($wfastcgiwanted) {
choco install webpicmd
WebpiCmd.exe /Install /Products:WFastCgi_21_279
} else {
exit 1
}
}
$pythondir = Split-Path c:\python27\python.exe
#installing dependencies
$env:Path = $env:Path + ";$pythondir;$pythondir\Scripts"
pip install pypiwin32
$PW = Read-Host 'Web2py Admin Password'
$appcmdpath = "$env:windir\system32\inetsrv\appcmd.exe"
& $appcmdpath set config /section:system.webServer/fastCGI "/+[fullPath='$pythonexe', arguments='$wfastcgipath']"
& $appcmdpath unlock config -section:system.webServer/handlers
& cd $rootfolder
& $pythonexe -c "from gluon.main import save_password; save_password('$PW',443)"
$webconfig_template = Join-Path $rootfolder "examples\web.config"
$destination = Join-Path $rootfolder "web.config"
$scriptprocessor = 'scriptProcessor="{0}|{1}"' -f $pythonexe, $wfastcgipath
(Get-Content $webconfig_template) | Foreach-Object {$_ -replace 'scriptProcessor="SCRIPT_PROCESSOR"', $scriptprocessor} | where {$_ -ne ""} | Set-Content $destination
""
"Installation finished. Web2py is available either on http://localhost/ or at https://localtest.me/"
""
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Post error tickets to slack on a 5 minute schedule.
#
# Proper use depends on having created a web-hook through Slack, and having set
# that value in your app's model as the value of global_settings.slack_hook.
# Details on creating web-hooks can be found at https://slack.com/integrations
#
# requires the Requests module for posting to slack, other requirements are
# standard or provided by web2py
#
# Usage (on Unices), replace myapp with the name of your application and run:
# nohup python web2py.py -S myapp -M -R scripts/tickets2slack.py &
import sys
import os
import time
import pickle
import json
try:
import requests
except ImportError as e:
print "missing module 'Requests', aborting."
sys.exit(1)
from gluon import URL
from gluon.utils import md5_hash
from gluon.restricted import RestrictedError
from gluon.settings import global_settings
path = os.path.join(request.folder, 'errors')
sent_errors_file = os.path.join(path, 'slack_errors.pickle')
hashes = {}
if os.path.exists(sent_errors_file):
try:
with open(sent_errors_file, 'rb') as f:
hashes = pickle.load(f)
except Exception as _:
pass
# ## CONFIGURE HERE
SLEEP_MINUTES = 5
ALLOW_DUPLICATES = False
global_settings.slack_hook = global_settings.slack_hook or \
'https://hooks.slack.com/services/your_service'
# ## END CONFIGURATION
while 1:
for file_name in os.listdir(path):
if file_name == 'slack_errors.pickle':
continue
if not ALLOW_DUPLICATES:
key = md5_hash(file_name)
if key in hashes:
continue
hashes[key] = 1
error = RestrictedError()
try:
error.load(request, request.application, file_name)
except Exception as _:
continue # not an exception file?
url = URL(a='admin', f='ticket', args=[request.application, file],
scheme=True)
payload = json.dumps(dict(text="Error in %(app)s.\n%(url)s" %
dict(app=request.application, url=url)))
requests.post(global_settings.slack_hook, data=dict(payload=payload))
with open(sent_errors_file, 'wb') as f:
pickle.dump(hashes, f)
time.sleep(SLEEP_MINUTES * 60)
+1 -1
View File
@@ -62,7 +62,7 @@ do_start()
start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
&& return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE \
start-stop-daemon --start --quiet -m --pidfile $PIDFILE \
${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \
--background --exec $DAEMON -- $DAEMON_ARGS \
|| return 2