Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236fdcfafc | ||
|
|
ce0f83d00c | ||
|
|
156d771ab3 | ||
|
|
01474c99b0 | ||
|
|
66d15491ca | ||
|
|
376a27da73 | ||
|
|
0f95c13dc7 | ||
|
|
926de90ee4 | ||
|
|
538f375284 | ||
|
|
4c61c0962d | ||
|
|
9b71646fc5 | ||
|
|
1e66fa3a93 | ||
|
|
57a8dfe034 | ||
|
|
77e7631740 | ||
|
|
ba978d55cf | ||
|
|
12e8ee5c25 | ||
|
|
d293e98b43 | ||
|
|
4f316d0294 | ||
|
|
81e15879d4 | ||
|
|
cd1d6c5af1 | ||
|
|
c7d3758c77 | ||
|
|
040e52278e | ||
|
|
3daf953c66 | ||
|
|
de3d722ac9 | ||
|
|
ff10eab373 | ||
|
|
eb4d159b37 | ||
|
|
5ef7a8e9a1 | ||
|
|
76cfba7047 | ||
|
|
f77f307869 | ||
|
|
5ef8648929 | ||
|
|
ed042685ea | ||
|
|
d09ce57f12 | ||
|
|
169818b275 | ||
|
|
4b14a87463 | ||
|
|
cadf38b4f6 | ||
|
|
a6226d6391 | ||
|
|
5c167907eb | ||
|
|
587ff56a94 | ||
|
|
6f91fdd833 | ||
|
|
6e2f9ad043 | ||
|
|
cdca2793e0 | ||
|
|
a0ee649884 | ||
|
|
380b491724 | ||
|
|
f45bf73992 | ||
|
|
94461724f6 | ||
|
|
c36c391786 | ||
|
|
f6db7c995f | ||
|
|
ccc4b96709 | ||
|
|
71b02e3044 | ||
|
|
99fb1c3010 | ||
|
|
20067d7b93 | ||
|
|
44eb35c617 | ||
|
|
df03317054 | ||
|
|
9d873cbd1c | ||
|
|
1bb4117cbd | ||
|
|
e834186a86 | ||
|
|
1394942feb | ||
|
|
32b9b5c799 | ||
|
|
340d7b5e6f | ||
|
|
302f56ecc1 | ||
|
|
9b12459a82 | ||
|
|
8e3925820c | ||
|
|
279d71d4cd | ||
|
|
258e2e57ae | ||
|
|
9357d810d8 | ||
|
|
54b385b321 | ||
|
|
df039e734c | ||
|
|
58533954dc | ||
|
|
236dc4b943 | ||
|
|
6612fd1cfe | ||
|
|
520950ba74 | ||
|
|
e943aa9c25 | ||
|
|
756aec7206 | ||
|
|
970e2ed35c | ||
|
|
1388c39636 | ||
|
|
6e84737924 | ||
|
|
0ad50630f2 | ||
|
|
f42ee15f5f | ||
|
|
77f154a56b | ||
|
|
2b0bfba649 | ||
|
|
9f1edf267d | ||
|
|
f3bda9ad02 | ||
|
|
f8afc76263 | ||
|
|
65b4aaf842 | ||
|
|
1b729cfbfc | ||
|
|
1aa5f30091 | ||
|
|
b17174c04c | ||
|
|
ac80adc9b4 | ||
|
|
888fa3dfc8 | ||
|
|
f3d815e84b | ||
|
|
9915fdf093 | ||
|
|
ef8f802df9 | ||
|
|
f7bf1020df | ||
|
|
75b8ceb022 | ||
|
|
b4f3784136 | ||
|
|
f1297bb827 | ||
|
|
435ebeaae4 | ||
|
|
537045082c | ||
|
|
4bea52a7b5 | ||
|
|
33295e516f | ||
|
|
f33ccf3366 | ||
|
|
0784680c90 | ||
|
|
e940228eaf | ||
|
|
50769a627a | ||
|
|
e68ecaa131 | ||
|
|
95e6e8577b | ||
|
|
19c83d4ad6 | ||
|
|
9c92bd1050 | ||
|
|
b3b95ccf5f | ||
|
|
cefa30841b | ||
|
|
15ff8669cb | ||
|
|
a921751e8e | ||
|
|
b02e8a6d5f | ||
|
|
a2b17967cf | ||
|
|
343ebf1714 | ||
|
|
e330791fe3 | ||
|
|
5e1e97ffc4 | ||
|
|
60f8816a10 | ||
|
|
6187c89ba6 | ||
|
|
e441e15907 | ||
|
|
e6c3410639 | ||
|
|
6b6cb5839c | ||
|
|
2a7e9c0c59 | ||
|
|
5b637c0a82 | ||
|
|
5cdd7c1215 |
@@ -1,3 +1,7 @@
|
|||||||
|
## 2.11.1
|
||||||
|
|
||||||
|
- Many small but significative improvements and bug fixes
|
||||||
|
|
||||||
## 2.10.1-2.10.2
|
## 2.10.1-2.10.2
|
||||||
|
|
||||||
- welcome app defaults to Bootstrap 3
|
- welcome app defaults to Bootstrap 3
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ update:
|
|||||||
echo "remember that pymysql was tweaked"
|
echo "remember that pymysql was tweaked"
|
||||||
src:
|
src:
|
||||||
### Use semantic versioning
|
### Use semantic versioning
|
||||||
echo 'Version 2.10.1-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
|
### rm -f all junk files
|
||||||
make clean
|
make clean
|
||||||
### clean up baisc apps
|
### clean up baisc apps
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ Learn more at http://web2py.com
|
|||||||
|
|
||||||
Then edit ./app.yaml and replace "yourappname" with yourappname.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.10.2-stable+timestamp.2015.03.31.22.25.23
|
Version 2.11.2-stable+timestamp.2015.05.30.11.29.46
|
||||||
|
|||||||
@@ -180,6 +180,11 @@ class Servers:
|
|||||||
s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address)
|
s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address)
|
||||||
s.start()
|
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):
|
def mongrel2_handler(application, conn, debug=False):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ def edit():
|
|||||||
viewlist.append(aviewpath + '.html')
|
viewlist.append(aviewpath + '.html')
|
||||||
if len(viewlist):
|
if len(viewlist):
|
||||||
editviewlinks = []
|
editviewlinks = []
|
||||||
for v in viewlist:
|
for v in sorted(viewlist):
|
||||||
vf = os.path.split(v)[-1]
|
vf = os.path.split(v)[-1]
|
||||||
vargs = "/".join([viewpath.replace(os.sep, "/"), vf])
|
vargs = "/".join([viewpath.replace(os.sep, "/"), vf])
|
||||||
editviewlinks.append(A(vf.split(".")[0],
|
editviewlinks.append(A(vf.split(".")[0],
|
||||||
@@ -754,6 +754,7 @@ def edit():
|
|||||||
if len(request.args) > 2 and request.args[1] == 'controllers':
|
if len(request.args) > 2 and request.args[1] == 'controllers':
|
||||||
controller = (request.args[2])[:-3]
|
controller = (request.args[2])[:-3]
|
||||||
functions = find_exposed_functions(data)
|
functions = find_exposed_functions(data)
|
||||||
|
functions = functions and sorted(functions) or []
|
||||||
else:
|
else:
|
||||||
(controller, functions) = (None, None)
|
(controller, functions) = (None, None)
|
||||||
|
|
||||||
@@ -1067,7 +1068,7 @@ def design():
|
|||||||
for c in controllers:
|
for c in controllers:
|
||||||
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
||||||
items = find_exposed_functions(data)
|
items = find_exposed_functions(data)
|
||||||
functions[c] = items
|
functions[c] = items and sorted(items) or []
|
||||||
|
|
||||||
# Get all views
|
# Get all views
|
||||||
views = sorted(
|
views = sorted(
|
||||||
@@ -1205,7 +1206,7 @@ def plugin():
|
|||||||
for c in controllers:
|
for c in controllers:
|
||||||
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
||||||
items = find_exposed_functions(data)
|
items = find_exposed_functions(data)
|
||||||
functions[c] = items
|
functions[c] = items and sorted(items) or []
|
||||||
|
|
||||||
# Get all views
|
# Get all views
|
||||||
views = sorted(
|
views = sorted(
|
||||||
|
|||||||
+4
-4
File diff suppressed because one or more lines are too long
@@ -490,12 +490,12 @@
|
|||||||
* and prevent clicking on it */
|
* and prevent clicking on it */
|
||||||
disableElement: function(el) {
|
disableElement: function(el) {
|
||||||
el.addClass('disabled');
|
el.addClass('disabled');
|
||||||
var method = el.is('button') ? 'html' : 'val';
|
var method = el.is('input') ? 'val' : 'html';
|
||||||
//method = el.attr('name') ? 'html' : 'val';
|
//method = el.attr('name') ? 'html' : 'val';
|
||||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||||
/*store enabled state if not already disabled */
|
/*store enabled state if not already disabled */
|
||||||
if(el.data('w2p:enable-with') === undefined) {
|
if(el.data('w2p_enable_with') === undefined) {
|
||||||
el.data('w2p:enable-with', el[method]());
|
el.data('w2p_enable_with', el[method]());
|
||||||
}
|
}
|
||||||
/*if you don't want to see "working..." on buttons, replace the following
|
/*if you don't want to see "working..." on buttons, replace the following
|
||||||
* two lines with this one
|
* two lines with this one
|
||||||
@@ -515,11 +515,11 @@
|
|||||||
|
|
||||||
/* restore element to its original state which was disabled by 'disableElement' above*/
|
/* restore element to its original state which was disabled by 'disableElement' above*/
|
||||||
enableElement: function(el) {
|
enableElement: function(el) {
|
||||||
var method = el.is('button') ? 'val' : 'html';
|
var method = el.is('input') ? 'val' : 'html';
|
||||||
if(el.data('w2p:enable-with') !== undefined) {
|
if(el.data('w2p_enable_with') !== undefined) {
|
||||||
/* set to old enabled state */
|
/* set to old enabled state */
|
||||||
el[method](el.data('w2p:enable-with'));
|
el[method](el.data('w2p_enable_with'));
|
||||||
el.removeData('w2p:enable-with');
|
el.removeData('w2p_enable_with');
|
||||||
}
|
}
|
||||||
el.removeClass('disabled');
|
el.removeClass('disabled');
|
||||||
el.unbind('click.w2pDisable');
|
el.unbind('click.w2pDisable');
|
||||||
@@ -586,12 +586,14 @@
|
|||||||
if(pre_call != undefined) {
|
if(pre_call != undefined) {
|
||||||
eval(pre_call);
|
eval(pre_call);
|
||||||
}
|
}
|
||||||
if(confirm_message != undefined) {
|
if(confirm_message) {
|
||||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
if(confirm_message == 'default')
|
||||||
if(!web2py.confirm(confirm_message)) {
|
confirm_message = w2p_ajax_confirm_message ||
|
||||||
web2py.stopEverything(e);
|
'Are you sure you want to delete this object?';
|
||||||
return;
|
if(!web2py.confirm(confirm_message)) {
|
||||||
}
|
web2py.stopEverything(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(target == undefined) {
|
if(target == undefined) {
|
||||||
if(method == 'GET') {
|
if(method == 'GET') {
|
||||||
@@ -634,7 +636,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Disables form elements:
|
/* 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
|
- Replaces element text with value of 'data-disable-with' attribute
|
||||||
- Sets disabled property to true
|
- Sets disabled property to true
|
||||||
*/
|
*/
|
||||||
@@ -646,8 +648,8 @@
|
|||||||
if(disable_with == undefined) {
|
if(disable_with == undefined) {
|
||||||
element.data('w2p_disable_with', element[method]())
|
element.data('w2p_disable_with', element[method]())
|
||||||
}
|
}
|
||||||
if(element.data('w2p:enable-with') === undefined) {
|
if(element.data('w2p_enable_with') === undefined) {
|
||||||
element.data('w2p:enable-with', element[method]());
|
element.data('w2p_enable_with', element[method]());
|
||||||
}
|
}
|
||||||
element[method](element.data('w2p_disable_with'));
|
element[method](element.data('w2p_disable_with'));
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
@@ -655,16 +657,16 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* Re-enables disabled form elements:
|
/* 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
|
- Sets disabled property to false
|
||||||
*/
|
*/
|
||||||
enableFormElements: function(form) {
|
enableFormElements: function(form) {
|
||||||
form.find(web2py.enableSelector).each(function() {
|
form.find(web2py.enableSelector).each(function() {
|
||||||
var element = $(this),
|
var element = $(this),
|
||||||
method = element.is('button') ? 'html' : 'val';
|
method = element.is('button') ? 'html' : 'val';
|
||||||
if(element.data('w2p:enable-with')) {
|
if(element.data('w2p_enable_with')) {
|
||||||
element[method](element.data('w2p:enable-with'));
|
element[method](element.data('w2p_enable_with'));
|
||||||
element.removeData('w2p:enable-with');
|
element.removeData('w2p_enable_with');
|
||||||
}
|
}
|
||||||
element.prop('disabled', false);
|
element.prop('disabled', false);
|
||||||
});
|
});
|
||||||
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
|||||||
web2py_trap_link = jQuery.web2py.trap_link;
|
web2py_trap_link = jQuery.web2py.trap_link;
|
||||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||||
*/
|
*/
|
||||||
/* compatibility code - end*/
|
/* compatibility code - end*/
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="alltables">
|
<div class="tab-pane active" id="alltables">
|
||||||
<table>
|
<table class="table">
|
||||||
{{for db in sorted(databases):}}
|
{{for db in sorted(databases):}}
|
||||||
{{for table in databases[db].tables:}}
|
{{for table in databases[db].tables:}}
|
||||||
{{qry='%s.%s.id>0'%(db,table)}}
|
{{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)))}}
|
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
</pre>
|
</pre>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{if table:}}
|
{{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/>
|
<h3>{{=T("Rows in Table")}}</h3><br/>
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<h3>{{=T("Rows selected")}}</h3><br/>
|
<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>
|
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<h4>{{=T("%s selected", nrows)}}</h4>
|
<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 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")}}{{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:}}
|
{{if rows:}}
|
||||||
<div style="overflow:auto; width:80%;">
|
<div style="overflow:auto; width:80%;">
|
||||||
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
|
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
|
<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 ''}}
|
{{=formcsv or ''}}
|
||||||
|
|
||||||
{{elif request.function=='insert':}}
|
{{elif request.function=='insert':}}
|
||||||
|
|||||||
@@ -22,5 +22,6 @@
|
|||||||
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
|
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
|
||||||
- [[Appliances http://www.web2py.com/appliances popup]]
|
- [[Appliances http://www.web2py.com/appliances popup]]
|
||||||
- [[web2py utils http://packages.python.org/web2py_utils/ 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]]
|
#### [[Sites Powered by web2py http://www.web2py.com/poweredby popup]]
|
||||||
|
|||||||
@@ -490,12 +490,12 @@
|
|||||||
* and prevent clicking on it */
|
* and prevent clicking on it */
|
||||||
disableElement: function(el) {
|
disableElement: function(el) {
|
||||||
el.addClass('disabled');
|
el.addClass('disabled');
|
||||||
var method = el.is('button') ? 'html' : 'val';
|
var method = el.is('input') ? 'val' : 'html';
|
||||||
//method = el.attr('name') ? 'html' : 'val';
|
//method = el.attr('name') ? 'html' : 'val';
|
||||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||||
/*store enabled state if not already disabled */
|
/*store enabled state if not already disabled */
|
||||||
if(el.data('w2p:enable-with') === undefined) {
|
if(el.data('w2p_enable_with') === undefined) {
|
||||||
el.data('w2p:enable-with', el[method]());
|
el.data('w2p_enable_with', el[method]());
|
||||||
}
|
}
|
||||||
/*if you don't want to see "working..." on buttons, replace the following
|
/*if you don't want to see "working..." on buttons, replace the following
|
||||||
* two lines with this one
|
* two lines with this one
|
||||||
@@ -515,11 +515,11 @@
|
|||||||
|
|
||||||
/* restore element to its original state which was disabled by 'disableElement' above*/
|
/* restore element to its original state which was disabled by 'disableElement' above*/
|
||||||
enableElement: function(el) {
|
enableElement: function(el) {
|
||||||
var method = el.is('button') ? 'val' : 'html';
|
var method = el.is('input') ? 'val' : 'html';
|
||||||
if(el.data('w2p:enable-with') !== undefined) {
|
if(el.data('w2p_enable_with') !== undefined) {
|
||||||
/* set to old enabled state */
|
/* set to old enabled state */
|
||||||
el[method](el.data('w2p:enable-with'));
|
el[method](el.data('w2p_enable_with'));
|
||||||
el.removeData('w2p:enable-with');
|
el.removeData('w2p_enable_with');
|
||||||
}
|
}
|
||||||
el.removeClass('disabled');
|
el.removeClass('disabled');
|
||||||
el.unbind('click.w2pDisable');
|
el.unbind('click.w2pDisable');
|
||||||
@@ -586,12 +586,14 @@
|
|||||||
if(pre_call != undefined) {
|
if(pre_call != undefined) {
|
||||||
eval(pre_call);
|
eval(pre_call);
|
||||||
}
|
}
|
||||||
if(confirm_message != undefined) {
|
if(confirm_message) {
|
||||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
if(confirm_message == 'default')
|
||||||
if(!web2py.confirm(confirm_message)) {
|
confirm_message = w2p_ajax_confirm_message ||
|
||||||
web2py.stopEverything(e);
|
'Are you sure you want to delete this object?';
|
||||||
return;
|
if(!web2py.confirm(confirm_message)) {
|
||||||
}
|
web2py.stopEverything(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(target == undefined) {
|
if(target == undefined) {
|
||||||
if(method == 'GET') {
|
if(method == 'GET') {
|
||||||
@@ -634,7 +636,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Disables form elements:
|
/* 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
|
- Replaces element text with value of 'data-disable-with' attribute
|
||||||
- Sets disabled property to true
|
- Sets disabled property to true
|
||||||
*/
|
*/
|
||||||
@@ -646,8 +648,8 @@
|
|||||||
if(disable_with == undefined) {
|
if(disable_with == undefined) {
|
||||||
element.data('w2p_disable_with', element[method]())
|
element.data('w2p_disable_with', element[method]())
|
||||||
}
|
}
|
||||||
if(element.data('w2p:enable-with') === undefined) {
|
if(element.data('w2p_enable_with') === undefined) {
|
||||||
element.data('w2p:enable-with', element[method]());
|
element.data('w2p_enable_with', element[method]());
|
||||||
}
|
}
|
||||||
element[method](element.data('w2p_disable_with'));
|
element[method](element.data('w2p_disable_with'));
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
@@ -655,16 +657,16 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* Re-enables disabled form elements:
|
/* 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
|
- Sets disabled property to false
|
||||||
*/
|
*/
|
||||||
enableFormElements: function(form) {
|
enableFormElements: function(form) {
|
||||||
form.find(web2py.enableSelector).each(function() {
|
form.find(web2py.enableSelector).each(function() {
|
||||||
var element = $(this),
|
var element = $(this),
|
||||||
method = element.is('button') ? 'html' : 'val';
|
method = element.is('button') ? 'html' : 'val';
|
||||||
if(element.data('w2p:enable-with')) {
|
if(element.data('w2p_enable_with')) {
|
||||||
element[method](element.data('w2p:enable-with'));
|
element[method](element.data('w2p_enable_with'));
|
||||||
element.removeData('w2p:enable-with');
|
element.removeData('w2p_enable_with');
|
||||||
}
|
}
|
||||||
element.prop('disabled', false);
|
element.prop('disabled', false);
|
||||||
});
|
});
|
||||||
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
|||||||
web2py_trap_link = jQuery.web2py.trap_link;
|
web2py_trap_link = jQuery.web2py.trap_link;
|
||||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||||
*/
|
*/
|
||||||
/* compatibility code - end*/
|
/* compatibility code - end*/
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="alltables">
|
<div class="tab-pane active" id="alltables">
|
||||||
<table>
|
<table class="table">
|
||||||
{{for db in sorted(databases):}}
|
{{for db in sorted(databases):}}
|
||||||
{{for table in databases[db].tables:}}
|
{{for table in databases[db].tables:}}
|
||||||
{{qry='%s.%s.id>0'%(db,table)}}
|
{{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)))}}
|
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
</pre>
|
</pre>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{if table:}}
|
{{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/>
|
<h3>{{=T("Rows in Table")}}</h3><br/>
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<h3>{{=T("Rows selected")}}</h3><br/>
|
<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>
|
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<h4>{{=T("%s selected", nrows)}}</h4>
|
<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 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")}}{{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:}}
|
{{if rows:}}
|
||||||
<div style="overflow:auto; width:80%;">
|
<div style="overflow:auto; width:80%;">
|
||||||
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
|
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
|
<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 ''}}
|
{{=formcsv or ''}}
|
||||||
|
|
||||||
{{elif request.function=='insert':}}
|
{{elif request.function=='insert':}}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</center>
|
</center>
|
||||||
|
|
||||||
<p style="text-align:left;">
|
<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>.
|
It runs on Windows and most Unix systems, including <b>Linux</b> and <b>BSD</b>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,13 @@
|
|||||||
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</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.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="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="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.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.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>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ auth.define_tables(username=False, signature=False)
|
|||||||
|
|
||||||
## configure email
|
## configure email
|
||||||
mail = auth.settings.mailer
|
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.sender = myconf.take('smtp.sender')
|
||||||
mail.settings.login = myconf.take('smtp.login')
|
mail.settings.login = myconf.take('smtp.login')
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
# Language from default.py or 'en' (if the file is not found) is used as
|
# Language from default.py or 'en' (if the file is not found) is used as
|
||||||
# a default_language
|
# 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:
|
# 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
|
# 2. rename this APP/routes.example.py to APP/routes.py
|
||||||
# (where APP - is your application directory)
|
# (where APP - is your application directory)
|
||||||
# 3. restart web2py (or reload routes in web2py admin interfase)
|
# 3. restart web2py (or reload routes in web2py admin interfase)
|
||||||
|
|||||||
@@ -586,12 +586,14 @@
|
|||||||
if(pre_call != undefined) {
|
if(pre_call != undefined) {
|
||||||
eval(pre_call);
|
eval(pre_call);
|
||||||
}
|
}
|
||||||
if(confirm_message != undefined) {
|
if(confirm_message) {
|
||||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
if(confirm_message == 'default')
|
||||||
if(!web2py.confirm(confirm_message)) {
|
confirm_message = w2p_ajax_confirm_message ||
|
||||||
web2py.stopEverything(e);
|
'Are you sure you want to delete this object?';
|
||||||
return;
|
if(!web2py.confirm(confirm_message)) {
|
||||||
}
|
web2py.stopEverything(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(target == undefined) {
|
if(target == undefined) {
|
||||||
if(method == 'GET') {
|
if(method == 'GET') {
|
||||||
|
|||||||
@@ -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>
|
||||||
+4
-3
@@ -114,17 +114,18 @@ def app_cleanup(app, request):
|
|||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
||||||
except IOError:
|
except (OSError, IOError):
|
||||||
r = False
|
r = False
|
||||||
|
|
||||||
# Remove cache files
|
# Remove cache files
|
||||||
CacheOnDisk(folder=apath('%s/cache/' % app, request)).clear()
|
|
||||||
path = apath('%s/cache/' % app, request)
|
path = apath('%s/cache/' % app, request)
|
||||||
|
CacheOnDisk(folder=path).clear()
|
||||||
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
||||||
except IOError:
|
except (OSError, IOError):
|
||||||
r = False
|
r = False
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|||||||
+9
-4
@@ -99,7 +99,7 @@ class CacheAbstract(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
cache_stats_name = 'web2py_cache_statistics'
|
cache_stats_name = 'web2py_cache_statistics'
|
||||||
max_ram_utilization = 90 # percent
|
max_ram_utilization = None # percent
|
||||||
|
|
||||||
def __init__(self, request=None):
|
def __init__(self, request=None):
|
||||||
"""Initializes the object
|
"""Initializes the object
|
||||||
@@ -353,7 +353,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
self.wait_portalock(val_file)
|
self.wait_portalock(val_file)
|
||||||
value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
value = pickle.load(val_file)
|
||||||
val_file.close()
|
val_file.close()
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -473,9 +473,14 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
if item and ((dt is None) or (item[0] > now - dt)):
|
if item and ((dt is None) or (item[0] > now - dt)):
|
||||||
value = item[1]
|
value = item[1]
|
||||||
else:
|
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[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})
|
default_value={'hit_total': 0, 'misses': 0})
|
||||||
|
|
||||||
self.storage.release(CacheAbstract.cache_stats_name)
|
self.storage.release(CacheAbstract.cache_stats_name)
|
||||||
|
|||||||
Executable → Regular
+50
-24
@@ -9,7 +9,7 @@ Required: Python 2.4 or later
|
|||||||
Recommended: iconv_codec <http://cjkpython.i18n.org/>
|
Recommended: iconv_codec <http://cjkpython.i18n.org/>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "5.1.2"
|
__version__ = "5.1.3"
|
||||||
__license__ = """
|
__license__ = """
|
||||||
Copyright (c) 2010-2012 Kurt McKee <contactme@kurtmckee.org>
|
Copyright (c) 2010-2012 Kurt McKee <contactme@kurtmckee.org>
|
||||||
Copyright (c) 2002-2008 Mark Pilgrim
|
Copyright (c) 2002-2008 Mark Pilgrim
|
||||||
@@ -44,7 +44,8 @@ __contributors__ = ["Jason Diamond <http://injektilo.org/>",
|
|||||||
"Sam Ruby <http://intertwingly.net/>",
|
"Sam Ruby <http://intertwingly.net/>",
|
||||||
"Ade Oshineye <http://blog.oshineye.com/>",
|
"Ade Oshineye <http://blog.oshineye.com/>",
|
||||||
"Martin Pool <http://sourcefrog.net/>",
|
"Martin Pool <http://sourcefrog.net/>",
|
||||||
"Kurt McKee <http://kurtmckee.org/>"]
|
"Kurt McKee <http://kurtmckee.org/>",
|
||||||
|
"Bernd Schlapsi <https://github.com/brot>",]
|
||||||
|
|
||||||
# HTTP "User-Agent" header to send to servers when downloading feeds.
|
# HTTP "User-Agent" header to send to servers when downloading feeds.
|
||||||
# If you are embedding feedparser in a larger application, you should
|
# If you are embedding feedparser in a larger application, you should
|
||||||
@@ -1971,6 +1972,7 @@ class _BaseHTMLProcessor(sgmllib.SGMLParser):
|
|||||||
def handle_charref(self, ref):
|
def handle_charref(self, ref):
|
||||||
# called for each character reference, e.g. for ' ', ref will be '160'
|
# called for each character reference, e.g. for ' ', ref will be '160'
|
||||||
# Reconstruct the original character reference.
|
# Reconstruct the original character reference.
|
||||||
|
ref = ref.lower()
|
||||||
if ref.startswith('x'):
|
if ref.startswith('x'):
|
||||||
value = int(ref[1:], 16)
|
value = int(ref[1:], 16)
|
||||||
else:
|
else:
|
||||||
@@ -2455,7 +2457,10 @@ class _MicroformatsParser:
|
|||||||
linktype.startswith('video/') or \
|
linktype.startswith('video/') or \
|
||||||
(linktype.startswith('application/') and not linktype.endswith('xml')):
|
(linktype.startswith('application/') and not linktype.endswith('xml')):
|
||||||
return 1
|
return 1
|
||||||
path = urlparse.urlparse(attrsD['href'])[2]
|
try:
|
||||||
|
path = urlparse.urlparse(attrsD['href'])[2]
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
if path.find('.') == -1:
|
if path.find('.') == -1:
|
||||||
return 0
|
return 0
|
||||||
fileext = path.split('.').pop().lower()
|
fileext = path.split('.').pop().lower()
|
||||||
@@ -2541,7 +2546,8 @@ class _RelativeURIResolver(_BaseHTMLProcessor):
|
|||||||
('object', 'data'),
|
('object', 'data'),
|
||||||
('object', 'usemap'),
|
('object', 'usemap'),
|
||||||
('q', 'cite'),
|
('q', 'cite'),
|
||||||
('script', 'src')])
|
('script', 'src'),
|
||||||
|
('video', 'poster')])
|
||||||
|
|
||||||
def __init__(self, baseuri, encoding, _type):
|
def __init__(self, baseuri, encoding, _type):
|
||||||
_BaseHTMLProcessor.__init__(self, encoding, _type)
|
_BaseHTMLProcessor.__init__(self, encoding, _type)
|
||||||
@@ -2618,13 +2624,13 @@ class _HTMLSanitizer(_BaseHTMLProcessor):
|
|||||||
'loop', 'loopcount', 'loopend', 'loopstart', 'low', 'lowsrc', 'max',
|
'loop', 'loopcount', 'loopend', 'loopstart', 'low', 'lowsrc', 'max',
|
||||||
'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'nohref',
|
'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'nohref',
|
||||||
'noshade', 'nowrap', 'open', 'optimum', 'pattern', 'ping', 'point-size',
|
'noshade', 'nowrap', 'open', 'optimum', 'pattern', 'ping', 'point-size',
|
||||||
'prompt', 'pqg', 'radiogroup', 'readonly', 'rel', 'repeat-max',
|
'poster', 'pqg', 'preload', 'prompt', 'radiogroup', 'readonly', 'rel',
|
||||||
'repeat-min', 'replace', 'required', 'rev', 'rightspacing', 'rows',
|
'repeat-max', 'repeat-min', 'replace', 'required', 'rev', 'rightspacing',
|
||||||
'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span', 'src',
|
'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span',
|
||||||
'start', 'step', 'summary', 'suppress', 'tabindex', 'target', 'template',
|
'src', 'start', 'step', 'summary', 'suppress', 'tabindex', 'target',
|
||||||
'title', 'toppadding', 'type', 'unselectable', 'usemap', 'urn', 'valign',
|
'template', 'title', 'toppadding', 'type', 'unselectable', 'usemap',
|
||||||
'value', 'variable', 'volume', 'vspace', 'vrml', 'width', 'wrap',
|
'urn', 'valign', 'value', 'variable', 'volume', 'vspace', 'vrml',
|
||||||
'xml:lang'])
|
'width', 'wrap', 'xml:lang'])
|
||||||
|
|
||||||
unacceptable_elements_with_end_tag = set(['script', 'applet', 'style'])
|
unacceptable_elements_with_end_tag = set(['script', 'applet', 'style'])
|
||||||
|
|
||||||
@@ -2976,9 +2982,9 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h
|
|||||||
url_file_stream_or_string = 'http:' + url_file_stream_or_string[5:]
|
url_file_stream_or_string = 'http:' + url_file_stream_or_string[5:]
|
||||||
if not agent:
|
if not agent:
|
||||||
agent = USER_AGENT
|
agent = USER_AGENT
|
||||||
# test for inline user:password for basic auth
|
# Test for inline user:password credentials for HTTP basic auth
|
||||||
auth = None
|
auth = None
|
||||||
if base64:
|
if base64 and not url_file_stream_or_string.startswith('ftp:'):
|
||||||
urltype, rest = urllib.splittype(url_file_stream_or_string)
|
urltype, rest = urllib.splittype(url_file_stream_or_string)
|
||||||
realhost, rest = urllib.splithost(rest)
|
realhost, rest = urllib.splithost(rest)
|
||||||
if realhost:
|
if realhost:
|
||||||
@@ -3471,15 +3477,7 @@ _rfc822_match = re.compile(
|
|||||||
"(?:%s, )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date, _rfc822_time)
|
"(?:%s, )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date, _rfc822_time)
|
||||||
).match
|
).match
|
||||||
|
|
||||||
def _parse_date_rfc822(dt):
|
def _parse_date_group_rfc822(m):
|
||||||
"""Parse RFC 822 dates and times, with one minor
|
|
||||||
difference: years may be 4DIGIT or 2DIGIT.
|
|
||||||
http://tools.ietf.org/html/rfc822#section-5"""
|
|
||||||
try:
|
|
||||||
m = _rfc822_match(dt.lower()).groupdict(0)
|
|
||||||
except AttributeError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Calculate a date and timestamp
|
# Calculate a date and timestamp
|
||||||
for k in ('year', 'day', 'hour', 'minute', 'second'):
|
for k in ('year', 'day', 'hour', 'minute', 'second'):
|
||||||
m[k] = int(m[k])
|
m[k] = int(m[k])
|
||||||
@@ -3487,7 +3485,7 @@ def _parse_date_rfc822(dt):
|
|||||||
# If the year is 2 digits, assume everything in the 90's is the 1990's
|
# If the year is 2 digits, assume everything in the 90's is the 1990's
|
||||||
if m['year'] < 100:
|
if m['year'] < 100:
|
||||||
m['year'] += (1900, 2000)[m['year'] < 90]
|
m['year'] += (1900, 2000)[m['year'] < 90]
|
||||||
stamp = datetime.datetime(*[m[i] for i in
|
stamp = datetime.datetime(*[m[i] for i in
|
||||||
('year', 'month', 'day', 'hour', 'minute', 'second')])
|
('year', 'month', 'day', 'hour', 'minute', 'second')])
|
||||||
|
|
||||||
# Use the timezone information to calculate the difference between
|
# Use the timezone information to calculate the difference between
|
||||||
@@ -3512,8 +3510,36 @@ def _parse_date_rfc822(dt):
|
|||||||
|
|
||||||
# Return the date and timestamp in UTC
|
# Return the date and timestamp in UTC
|
||||||
return (stamp - delta).utctimetuple()
|
return (stamp - delta).utctimetuple()
|
||||||
|
|
||||||
|
def _parse_date_rfc822(dt):
|
||||||
|
"""Parse RFC 822 dates and times, with one minor
|
||||||
|
difference: years may be 4DIGIT or 2DIGIT.
|
||||||
|
http://tools.ietf.org/html/rfc822#section-5"""
|
||||||
|
try:
|
||||||
|
m = _rfc822_match(dt.lower()).groupdict(0)
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return _parse_date_group_rfc822(m)
|
||||||
registerDateHandler(_parse_date_rfc822)
|
registerDateHandler(_parse_date_rfc822)
|
||||||
|
|
||||||
|
def _parse_date_rfc822_grubby(dt):
|
||||||
|
"""Parse date format similar to RFC 822, but
|
||||||
|
the comma after the dayname is optional and
|
||||||
|
month/day are inverted"""
|
||||||
|
_rfc822_date_grubby = "%s %s %s" % (_rfc822_month, _rfc822_day, _rfc822_year)
|
||||||
|
_rfc822_match_grubby = re.compile(
|
||||||
|
"(?:%s[,]? )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date_grubby, _rfc822_time)
|
||||||
|
).match
|
||||||
|
|
||||||
|
try:
|
||||||
|
m = _rfc822_match_grubby(dt.lower()).groupdict(0)
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return _parse_date_group_rfc822(m)
|
||||||
|
registerDateHandler(_parse_date_rfc822_grubby)
|
||||||
|
|
||||||
def _parse_date_asctime(dt):
|
def _parse_date_asctime(dt):
|
||||||
"""Parse asctime-style dates"""
|
"""Parse asctime-style dates"""
|
||||||
dayname, month, day, remainder = dt.split(None, 3)
|
dayname, month, day, remainder = dt.split(None, 3)
|
||||||
@@ -3699,7 +3725,7 @@ def convert_to_utf8(http_headers, data):
|
|||||||
u'application/xml-external-parsed-entity')
|
u'application/xml-external-parsed-entity')
|
||||||
text_content_types = (u'text/xml', u'text/xml-external-parsed-entity')
|
text_content_types = (u'text/xml', u'text/xml-external-parsed-entity')
|
||||||
if (http_content_type in application_content_types) or \
|
if (http_content_type in application_content_types) or \
|
||||||
(http_content_type.startswith(u'application/') and
|
(http_content_type.startswith(u'application/') and
|
||||||
http_content_type.endswith(u'+xml')):
|
http_content_type.endswith(u'+xml')):
|
||||||
acceptable_content_type = 1
|
acceptable_content_type = 1
|
||||||
rfc3023_encoding = http_encoding or xml_encoding or u'utf-8'
|
rfc3023_encoding = http_encoding or xml_encoding or u'utf-8'
|
||||||
|
|||||||
@@ -521,9 +521,19 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
logging.error(
|
logging.error(
|
||||||
'There is no username or email for %s!' % username)
|
'There is no username or email for %s!' % username)
|
||||||
raise
|
raise
|
||||||
db_group_search = db((db.auth_membership.user_id == db_user_id) &
|
# if old pydal version, assume this is a relational database which can do joins
|
||||||
(db.auth_user.id == db.auth_membership.user_id) &
|
db_can_join = db.can_join() if hasattr(db, 'can_join') else True
|
||||||
(db.auth_group.id == db.auth_membership.group_id))
|
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_groups_of_the_user = list()
|
||||||
db_group_id = dict()
|
db_group_id = dict()
|
||||||
|
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
from imaplib import ParseFlags
|
|
||||||
|
|
||||||
# mockimaplib: A very simple mock server module for imap client APIs
|
|
||||||
# Copyright (C) 2014 Alan Etkin <spametki@gmail.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or(at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this program. If not, see
|
|
||||||
# <http://www.gnu.org/licenses/lgpl.html>
|
|
||||||
|
|
||||||
"""
|
|
||||||
mockimaplib allows you to test applications connecting to a dummy imap
|
|
||||||
service. For more details on the api subset implemented,
|
|
||||||
refer to the imaplib docs.
|
|
||||||
|
|
||||||
The client should configure a dictionary to map imap string queries to sets
|
|
||||||
of entries stored in a message dummy storage dictionary. The module includes
|
|
||||||
a small set of default message records (SPAM and MESSAGES), two mailboxes
|
|
||||||
(Draft and INBOX) and a list of query/resultset entries (RESULTS).
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
>>> import mockimaplib
|
|
||||||
>>> connection = mockimaplib.IMAP4_SSL(<host>)
|
|
||||||
>>> connection.login(<user>, <password>)
|
|
||||||
None
|
|
||||||
>>> connection.select("INBOX")
|
|
||||||
("OK", ... <mailbox length>)
|
|
||||||
# fetch commands specifying single uid or message id
|
|
||||||
# will try to get messages recorded in SPAM
|
|
||||||
>>> connection.uid(...)
|
|
||||||
<search query or fetch result>
|
|
||||||
# returns a string list of matching message ids
|
|
||||||
>>> connection.search(<query>)
|
|
||||||
("OK", ... "1 2 ... n")
|
|
||||||
"""
|
|
||||||
|
|
||||||
MESSAGES = (
|
|
||||||
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:52:30 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:52:30 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <10101010101010010000010101010001010101001010010000001@mail.example.com>\r\nSubject: spam1\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse!\r\n\r\n\r\n',
|
|
||||||
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:52:47 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:52:47 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <101010101010100100000101010100010101010010100100000010@mail.example.com>\r\nSubject: spam2\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse, nurse!',
|
|
||||||
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:54:54 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:54:54 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <1010101010101001000001010101000101010100101001000000101@mail.example.com>\r\nSubject: spamalot1\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse!\r\n\r\n\r\n',
|
|
||||||
'MIME-Version: 1.0\r\n\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:54:54 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:54:54 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <101010101010100100000101010100010101010010100100000010101@mail.example.com>\r\nSubject: spamalot2\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse! ... Nurse! ... Nurse!\r\n\r\n\r\n')
|
|
||||||
|
|
||||||
SPAM = {
|
|
||||||
"INBOX": [
|
|
||||||
{"uid": "483209",
|
|
||||||
"headers": MESSAGES[0],
|
|
||||||
"complete": MESSAGES[0],
|
|
||||||
"flags": ""},
|
|
||||||
{"uid": "483211",
|
|
||||||
"headers": MESSAGES[1],
|
|
||||||
"complete": MESSAGES[1],
|
|
||||||
"flags": ""},
|
|
||||||
{"uid": "483225",
|
|
||||||
"headers": MESSAGES[2],
|
|
||||||
"complete": MESSAGES[2],
|
|
||||||
"flags": ""}],
|
|
||||||
"Draft":[
|
|
||||||
{"uid": "483432",
|
|
||||||
"headers": MESSAGES[3],
|
|
||||||
"complete": MESSAGES[3],
|
|
||||||
"flags": ""},]
|
|
||||||
}
|
|
||||||
|
|
||||||
RESULTS = {
|
|
||||||
# <query string>: [<str uid> | <long id>, ...]
|
|
||||||
"INBOX": {
|
|
||||||
"(ALL)": (1, 2, 3),
|
|
||||||
"(1:3)": (1, 2, 3)},
|
|
||||||
"Draft": {
|
|
||||||
"(1:1)": (1,)},
|
|
||||||
}
|
|
||||||
|
|
||||||
class Connection(object):
|
|
||||||
"""Dummy connection object for the imap client.
|
|
||||||
By default, uses the module SPAM and RESULT
|
|
||||||
sets (use Connection.setup for custom values)"""
|
|
||||||
def login(self, user, password):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._readonly = False
|
|
||||||
self._mailbox = None
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def list(self):
|
|
||||||
return ('OK', ['(\\HasNoChildren) "/" "%s"' % key for key in self.spam])
|
|
||||||
|
|
||||||
def select(self, tablename, readonly=False):
|
|
||||||
self._readonly = readonly
|
|
||||||
"""args: mailbox, boolean
|
|
||||||
result[1][0] -> int last message id / mailbox lenght
|
|
||||||
result[0] = 'OK'
|
|
||||||
"""
|
|
||||||
self._mailbox = tablename
|
|
||||||
return ('OK', (len(SPAM[self._mailbox]), None))
|
|
||||||
|
|
||||||
def uid(self, command, uid, arg):
|
|
||||||
""" args:
|
|
||||||
command: "search" | "fetch"
|
|
||||||
uid: None | uid
|
|
||||||
parts: "(ALL)" | "(RFC822 FLAGS)" | "(RFC822.HEADER FLAGS)"
|
|
||||||
|
|
||||||
"search", None, "(ALL)" -> ("OK", ("uid_1 uid_2 ... uid_<mailbox length>", None))
|
|
||||||
"search", None, "<query>" -> ("OK", ("uid_1 uid_2 ... uid_n", None))
|
|
||||||
"fetch", uid, parts -> ("OK", (("<id> ...", "<raw message as specified in parts>"), "<flags>")
|
|
||||||
[0] [1][0][0] [1][0][1] [1][1]
|
|
||||||
"""
|
|
||||||
if command == "search":
|
|
||||||
return self._search(arg)
|
|
||||||
elif command == "fetch":
|
|
||||||
return self._fetch(uid, arg)
|
|
||||||
|
|
||||||
def _search(self, query):
|
|
||||||
return ("OK", (" ".join([str(item["uid"]) for item in self._get_messages(query)]), None))
|
|
||||||
|
|
||||||
def _fetch(self, value, arg):
|
|
||||||
try:
|
|
||||||
message = self.spam[self._mailbox][value - 1]
|
|
||||||
message_id = value
|
|
||||||
except TypeError:
|
|
||||||
for x, item in enumerate(self.spam[self._mailbox]):
|
|
||||||
if item["uid"] == value:
|
|
||||||
message = item
|
|
||||||
message_id = x + 1
|
|
||||||
break
|
|
||||||
|
|
||||||
parts = "headers"
|
|
||||||
if arg in ("(ALL)", "(RFC822 FLAGS)"):
|
|
||||||
parts = "complete"
|
|
||||||
|
|
||||||
return ("OK", (("%s " % message_id, message[parts]), message["flags"]))
|
|
||||||
|
|
||||||
def _get_messages(self, query):
|
|
||||||
if query.strip().isdigit():
|
|
||||||
return [self.spam[self._mailbox][int(query.strip()) - 1],]
|
|
||||||
elif query[1:-1].strip().isdigit():
|
|
||||||
return [self.spam[self._mailbox][int(query[1:-1].strip()) -1],]
|
|
||||||
elif query[1:-1].replace("UID", "").strip().isdigit():
|
|
||||||
for item in self.spam[self._mailbox]:
|
|
||||||
if item["uid"] == query[1:-1].replace("UID", "").strip():
|
|
||||||
return [item,]
|
|
||||||
messages = []
|
|
||||||
try:
|
|
||||||
for m in self.results[self._mailbox][query]:
|
|
||||||
try:
|
|
||||||
self.spam[self._mailbox][m - 1]["id"] = m
|
|
||||||
messages.append(self.spam[self._mailbox][m - 1])
|
|
||||||
except TypeError:
|
|
||||||
for x, item in enumerate(self.spam[self._mailbox]):
|
|
||||||
if item["uid"] == m:
|
|
||||||
item["id"] = x + 1
|
|
||||||
messages.append(item)
|
|
||||||
break
|
|
||||||
except IndexError:
|
|
||||||
# message removed
|
|
||||||
pass
|
|
||||||
return messages
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError("The client issued an unexpected query: %s" % query)
|
|
||||||
|
|
||||||
def setup(self, spam={}, results={}):
|
|
||||||
"""adds custom message and query databases or sets
|
|
||||||
the values to the module defaults.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.spam = spam
|
|
||||||
self.results = results
|
|
||||||
if not spam:
|
|
||||||
for key in SPAM:
|
|
||||||
self.spam[key] = []
|
|
||||||
for d in SPAM[key]:
|
|
||||||
self.spam[key].append(d.copy())
|
|
||||||
if not results:
|
|
||||||
for key in RESULTS:
|
|
||||||
self.results[key] = RESULTS[key].copy()
|
|
||||||
|
|
||||||
|
|
||||||
def search(self, first, query):
|
|
||||||
""" args:
|
|
||||||
first: None
|
|
||||||
query: string with mailbox query (flags, date, uid, id, ...)
|
|
||||||
example: '2:15723 BEFORE 27-Jan-2014 FROM "gumby"'
|
|
||||||
result[1][0] -> "id_1 id_2 ... id_n"
|
|
||||||
"""
|
|
||||||
messages = self._get_messages(query)
|
|
||||||
ids = " ".join([str(item["id"]) for item in messages])
|
|
||||||
return ("OK", (ids, None))
|
|
||||||
|
|
||||||
def append(self, mailbox, flags, struct_time, message):
|
|
||||||
"""
|
|
||||||
result, data = self.connection.append(mailbox, flags, struct_time, message)
|
|
||||||
if result == "OK":
|
|
||||||
uid = int(re.findall("\d+", str(data))[-1])
|
|
||||||
"""
|
|
||||||
last = self.spam[mailbox][-1]
|
|
||||||
try:
|
|
||||||
uid = int(last["uid"]) +1
|
|
||||||
except ValueError:
|
|
||||||
alluids = []
|
|
||||||
for _mailbox in self.spam.keys():
|
|
||||||
for item in self.spam[_mailbox]:
|
|
||||||
try:
|
|
||||||
alluids.append(int(item["uid"]))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if len(alluids) > 0:
|
|
||||||
uid = max(alluids) + 1
|
|
||||||
else:
|
|
||||||
uid = 1
|
|
||||||
flags = "FLAGS " + flags
|
|
||||||
item = {"uid": str(uid), "headers": message, "complete": message, "flags": flags}
|
|
||||||
self.spam[mailbox].append(item)
|
|
||||||
return ("OK", "spam spam %s spam" % uid)
|
|
||||||
|
|
||||||
|
|
||||||
def store(self, *args):
|
|
||||||
"""
|
|
||||||
implements some flag commands
|
|
||||||
args: ("<id>", "<+|->FLAGS", "(\\Flag1 \\Flag2 ... \\Flagn)")
|
|
||||||
"""
|
|
||||||
message = self.spam[self._mailbox][int(args[0] - 1)]
|
|
||||||
old_flags = ParseFlags(message["flags"])
|
|
||||||
flags = ParseFlags("FLAGS" + args[2])
|
|
||||||
if args[1].strip().startswith("+"):
|
|
||||||
message["flags"] = "FLAGS (%s)" % " ".join(set(flags + old_flags))
|
|
||||||
elif args[1].strip().startswith("-"):
|
|
||||||
message["flags"] = "FLAGS (%s)" % " ".join([flag for flag in old_flags if not flag in flags])
|
|
||||||
|
|
||||||
def expunge(self):
|
|
||||||
"""implements removal of deleted flag messages"""
|
|
||||||
for x, item in enumerate(self.spam[self._mailbox]):
|
|
||||||
if "\\Deleted" in item["flags"]:
|
|
||||||
self.spam[self._mailbox].pop(x)
|
|
||||||
|
|
||||||
|
|
||||||
class IMAP4(object):
|
|
||||||
""">>> connection = IMAP4() # creates the dummy imap4 client object"""
|
|
||||||
def __new__(self, *args, **kwargs):
|
|
||||||
# args: (server, port)
|
|
||||||
return Connection()
|
|
||||||
|
|
||||||
IMAP4_SSL = IMAP4
|
|
||||||
+439
-421
File diff suppressed because it is too large
Load Diff
@@ -67,11 +67,12 @@ def RedisCache(*args, **vars):
|
|||||||
|
|
||||||
locker.acquire()
|
locker.acquire()
|
||||||
try:
|
try:
|
||||||
if not hasattr(RedisCache, 'redis_instance'):
|
instance_name = 'redis_instance_' + current.request.application
|
||||||
RedisCache.redis_instance = RedisClient(*args, **vars)
|
if not hasattr(RedisCache, instance_name):
|
||||||
|
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
|
||||||
|
return getattr(RedisCache, instance_name)
|
||||||
finally:
|
finally:
|
||||||
locker.release()
|
locker.release()
|
||||||
return RedisCache.redis_instance
|
|
||||||
|
|
||||||
|
|
||||||
class RedisClient(object):
|
class RedisClient(object):
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class History:
|
|||||||
def globals_dict(self):
|
def globals_dict(self):
|
||||||
"""Returns a dictionary view of the globals.
|
"""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))
|
for name, val in zip(self.global_names, self.globals))
|
||||||
|
|
||||||
def add_unpicklable(self, statement, names):
|
def add_unpicklable(self, statement, names):
|
||||||
|
|||||||
+35
-35
@@ -4,7 +4,24 @@ from hashlib import sha1
|
|||||||
|
|
||||||
class Stripe:
|
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>'
|
key='<api key>'
|
||||||
d = Stripe(key).charge(
|
d = Stripe(key).charge(
|
||||||
amount=100, # 1 dollar!!!!
|
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'}}
|
{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
|
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'
|
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>
|
<h3>Payment Amount: {{=currency_symbol}} {{="%.2f" % (0.01*amount)}}</h3>
|
||||||
<form action="" method="POST" id="payment-form" class="form-horizontal">
|
<form action="" method="POST" id="payment-form" class="form-horizontal">
|
||||||
|
|
||||||
<div class="form-row control-group">
|
<div class="form-row form-group">
|
||||||
<label class="control-label">Card Number</label>
|
<label class="col-sm-2 control-label">Card Number</label>
|
||||||
<div class="controls">
|
<div class="controls col-sm-10">
|
||||||
<input type="text" size="20" data-stripe="number"
|
<input type="text" size="20" data-stripe="number"
|
||||||
placeholder="4242424242424242"/>
|
placeholder="4242424242424242" class="form-control"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row control-group">
|
<div class="form-row form-group">
|
||||||
<label class="control-label">CVC</label>
|
<label class="col-sm-2 control-label">CVC</label>
|
||||||
<div class="controls">
|
<div class="controls col-sm-10">
|
||||||
<input type="text" size="4" style="width:80px" data-stripe="cvc"
|
<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>
|
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row control-group">
|
<div class="form-row form-group">
|
||||||
<label class="control-label">Expiration</label>
|
<label class="col-sm-2 control-label">Expiration</label>
|
||||||
<div class="controls">
|
<div class="controls col-sm-10">
|
||||||
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
|
<input type="text" size="2" style="width:40px; display:inline-block"
|
||||||
placeholder="MM"/>
|
data-stripe="exp-month" placeholder="MM" class="form-control"/>
|
||||||
/
|
/
|
||||||
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
|
<input type="text" size="4" style="width:80px; display:inline-block"
|
||||||
placeholder="YYYY"/>
|
data-stripe="exp-year" placeholder="YYYY" class="form-control"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row form-group">
|
||||||
<div class="control-group">
|
<div class="controls col-sm-offset-2 col-sm-10">
|
||||||
<div class="controls">
|
|
||||||
<button type="submit" class="btn btn-primary">Submit Payment</button>
|
<button type="submit" class="btn btn-primary">Submit Payment</button>
|
||||||
<div class="payment-errors error hidden"></div>
|
<div class="payment-errors error hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,12 +41,15 @@ class CustomImportException(ImportError):
|
|||||||
|
|
||||||
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
||||||
"""
|
"""
|
||||||
web2py's custom importer. It behaves like the standard Python importer but
|
web2py's custom importer. It behaves like the standard Python importer but
|
||||||
it tries to transform import statements as something like
|
it tries to transform import statements as something like
|
||||||
"import applications.app_name.modules.x".
|
"import applications.app_name.modules.x".
|
||||||
If the import fails, it falls back on naive_importer
|
If the import fails, it falls back on naive_importer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if isinstance(name, unicode):
|
||||||
|
name = name.encode('utf8')
|
||||||
|
|
||||||
globals = globals or {}
|
globals = globals or {}
|
||||||
locals = locals or {}
|
locals = locals or {}
|
||||||
fromlist = fromlist or []
|
fromlist = fromlist or []
|
||||||
@@ -77,7 +80,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
|||||||
if not fromlist:
|
if not fromlist:
|
||||||
# import like "import x" or "import x.y"
|
# import like "import x" or "import x.y"
|
||||||
result = None
|
result = None
|
||||||
for itemname in name.split("."):
|
for itemname in name.split("."):
|
||||||
new_mod = base_importer(
|
new_mod = base_importer(
|
||||||
modules_prefix, globals, locals, [itemname], level)
|
modules_prefix, globals, locals, [itemname], level)
|
||||||
try:
|
try:
|
||||||
|
|||||||
+5
-5
@@ -7,14 +7,14 @@
|
|||||||
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||||
|
|
||||||
Takes care of adapting pyDAL to web2py's needs
|
Takes care of adapting pyDAL to web2py's needs
|
||||||
--------------------------------------------
|
-----------------------------------------------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pydal import DAL as DAL
|
from pydal import DAL as DAL
|
||||||
from pydal import Field
|
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
|
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||||
import copy_reg as copyreg
|
|
||||||
|
|
||||||
def _default_validators(db, field):
|
def _default_validators(db, field):
|
||||||
"""
|
"""
|
||||||
@@ -81,12 +81,12 @@ def _default_validators(db, field):
|
|||||||
requires[0] = validators.IS_EMPTY_OR(requires[0])
|
requires[0] = validators.IS_EMPTY_OR(requires[0])
|
||||||
return requires
|
return requires
|
||||||
|
|
||||||
from gluon import serializers as w2p_serializers
|
from gluon.serializers import custom_json, xml
|
||||||
from gluon.utils import web2py_uuid
|
from gluon.utils import web2py_uuid
|
||||||
from gluon import sqlhtml
|
from gluon import sqlhtml
|
||||||
|
|
||||||
|
|
||||||
DAL.serializers = w2p_serializers
|
DAL.serializers = {'json': custom_json, 'xml': xml}
|
||||||
DAL.validators_method = _default_validators
|
DAL.validators_method = _default_validators
|
||||||
DAL.uuid = lambda x: web2py_uuid()
|
DAL.uuid = lambda x: web2py_uuid()
|
||||||
DAL.representers = {
|
DAL.representers = {
|
||||||
|
|||||||
+48
-32
@@ -26,6 +26,8 @@ import gluon.settings as settings
|
|||||||
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
from gluon import recfile
|
from gluon import recfile
|
||||||
|
from gluon.cache import CacheInRam
|
||||||
|
from gluon.fileutils import copystream
|
||||||
import hashlib
|
import hashlib
|
||||||
import portalocker
|
import portalocker
|
||||||
try:
|
try:
|
||||||
@@ -47,8 +49,7 @@ import cgi
|
|||||||
import urlparse
|
import urlparse
|
||||||
import copy
|
import copy
|
||||||
import tempfile
|
import tempfile
|
||||||
from gluon.cache import CacheInRam
|
|
||||||
from gluon.fileutils import copystream
|
|
||||||
|
|
||||||
FMT = '%a, %d-%b-%Y %H:%M:%S PST'
|
FMT = '%a, %d-%b-%Y %H:%M:%S PST'
|
||||||
PAST = 'Sat, 1-Jan-1971 00:00:00'
|
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>'
|
css_inline = '<style type="text/css">\n%s\n</style>'
|
||||||
js_inline = '<script type="text/javascript">\n%s\n</script>'
|
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:
|
# IMPORTANT:
|
||||||
# this is required so that pickled dict(s) and class.__dict__
|
# this is required so that pickled dict(s) and class.__dict__
|
||||||
# are sorted and web2py can detect without ambiguity when a session changes
|
# are sorted and web2py can detect without ambiguity when a session changes
|
||||||
class SortingPickler(Pickler):
|
class SortingPickler(Pickler):
|
||||||
def save_dict(self, obj):
|
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.memoize(obj)
|
||||||
self._batch_setitems([(key, obj[key]) for key in sorted(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_https = False
|
||||||
self.is_local = False
|
self.is_local = False
|
||||||
self.global_settings = settings.global_settings
|
self.global_settings = settings.global_settings
|
||||||
|
self._uuid = None
|
||||||
|
|
||||||
def parse_get_vars(self):
|
def parse_get_vars(self):
|
||||||
"""Takes the QUERY_STRING and unpacks it to get_vars
|
"""Takes the QUERY_STRING and unpacks it to get_vars
|
||||||
@@ -275,7 +286,7 @@ class Request(Storage):
|
|||||||
"""
|
"""
|
||||||
self._vars = copy.copy(self.get_vars)
|
self._vars = copy.copy(self.get_vars)
|
||||||
for key, value in self.post_vars.iteritems():
|
for key, value in self.post_vars.iteritems():
|
||||||
if not key in self._vars:
|
if key not in self._vars:
|
||||||
self._vars[key] = value
|
self._vars[key] = value
|
||||||
else:
|
else:
|
||||||
if not isinstance(self._vars[key], list):
|
if not isinstance(self._vars[key], list):
|
||||||
@@ -306,13 +317,21 @@ class Request(Storage):
|
|||||||
self.parse_all_vars()
|
self.parse_all_vars()
|
||||||
return self._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):
|
def compute_uuid(self):
|
||||||
self.uuid = '%s/%s.%s.%s' % (
|
self._uuid = '%s/%s.%s.%s' % (
|
||||||
self.application,
|
self.application,
|
||||||
self.client.replace(':', '_'),
|
self.client.replace(':', '_'),
|
||||||
self.now.strftime('%Y-%m-%d.%H-%M-%S'),
|
self.now.strftime('%Y-%m-%d.%H-%M-%S'),
|
||||||
web2py_uuid())
|
web2py_uuid())
|
||||||
return self.uuid
|
return self._uuid
|
||||||
|
|
||||||
def user_agent(self):
|
def user_agent(self):
|
||||||
from gluon.contrib import user_agent_parser
|
from gluon.contrib import user_agent_parser
|
||||||
@@ -436,29 +455,32 @@ class Response(Storage):
|
|||||||
return page
|
return page
|
||||||
|
|
||||||
def include_meta(self):
|
def include_meta(self):
|
||||||
s = "\n";
|
s = "\n"
|
||||||
for meta in (self.meta or {}).iteritems():
|
for meta in (self.meta or {}).iteritems():
|
||||||
k, v = meta
|
k, v = meta
|
||||||
if isinstance(v,dict):
|
if isinstance(v, dict):
|
||||||
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
|
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
|
||||||
else:
|
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)
|
self.write(s, escape=False)
|
||||||
|
|
||||||
def include_files(self, extensions=None):
|
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,
|
By default, caches in ram for 5 minutes. To change,
|
||||||
response.cache_includes = (cache_method, time_expire).
|
response.cache_includes = (cache_method, time_expire).
|
||||||
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
||||||
"""
|
"""
|
||||||
from gluon import URL
|
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
|
ext_files = []
|
||||||
has_js = has_css = False
|
has_js = has_css = False
|
||||||
for item in self.files:
|
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
|
continue
|
||||||
if item in files:
|
if item in files:
|
||||||
continue
|
continue
|
||||||
@@ -487,10 +509,13 @@ class Response(Storage):
|
|||||||
time_expire)
|
time_expire)
|
||||||
else:
|
else:
|
||||||
files = call_minify()
|
files = call_minify()
|
||||||
s = ''
|
|
||||||
|
files.extend(ext_files)
|
||||||
|
s = []
|
||||||
for item in files:
|
for item in files:
|
||||||
if isinstance(item, str):
|
if isinstance(item, str):
|
||||||
f = item.lower().split('?')[0]
|
f = item.lower().split('?')[0]
|
||||||
|
ext = f.rpartition('.')[2]
|
||||||
# if static_version we need also to check for
|
# if static_version we need also to check for
|
||||||
# static_version_urls. In that case, the _.x.x.x
|
# static_version_urls. In that case, the _.x.x.x
|
||||||
# bit would have already been added by the URL()
|
# 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:
|
if self.static_version and not self.static_version_urls:
|
||||||
item = item.replace(
|
item = item.replace(
|
||||||
'/static/', '/static/_%s/' % self.static_version, 1)
|
'/static/', '/static/_%s/' % self.static_version, 1)
|
||||||
if f.endswith('.css'):
|
tmpl = template_mapping.get(ext)
|
||||||
s += css_template % item
|
if tmpl:
|
||||||
elif f.endswith('.js'):
|
s.append(tmpl % item)
|
||||||
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
|
|
||||||
elif isinstance(item, (list, tuple)):
|
elif isinstance(item, (list, tuple)):
|
||||||
f = item[0]
|
f = item[0]
|
||||||
if f == 'css:inline':
|
tmpl = template_mapping.get(f)
|
||||||
s += css_inline % item[1]
|
if tmpl:
|
||||||
elif f == 'js:inline':
|
s.append(tmpl % item[1])
|
||||||
s += js_inline % item[1]
|
self.write(''.join(s), escape=False)
|
||||||
self.write(s, escape=False)
|
|
||||||
|
|
||||||
def stream(self,
|
def stream(self,
|
||||||
stream,
|
stream,
|
||||||
@@ -663,7 +679,7 @@ class Response(Storage):
|
|||||||
return handler(request, self, methods)
|
return handler(request, self, methods)
|
||||||
|
|
||||||
def toolbar(self):
|
def toolbar(self):
|
||||||
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
|
from gluon.html import DIV, SCRIPT, BEAUTIFY, TAG, A
|
||||||
BUTTON = TAG.button
|
BUTTON = TAG.button
|
||||||
admin = URL("admin", "default", "design", extension='html',
|
admin = URL("admin", "default", "design", extension='html',
|
||||||
args=current.request.application)
|
args=current.request.application)
|
||||||
|
|||||||
+1
-7
@@ -23,6 +23,7 @@ base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit',
|
|||||||
'collections', 'colorsys', 'compileall', 'compiler',
|
'collections', 'colorsys', 'compileall', 'compiler',
|
||||||
'compiler.ast', 'compiler.visitor', 'ConfigParser',
|
'compiler.ast', 'compiler.visitor', 'ConfigParser',
|
||||||
'contextlib', 'Cookie', 'cookielib', 'copy', 'copy_reg',
|
'contextlib', 'Cookie', 'cookielib', 'copy', 'copy_reg',
|
||||||
|
'collections',
|
||||||
'cPickle', 'cProfile', 'cStringIO', 'csv', 'ctypes',
|
'cPickle', 'cProfile', 'cStringIO', 'csv', 'ctypes',
|
||||||
'datetime', 'decimal', 'difflib', 'dircache', 'dis',
|
'datetime', 'decimal', 'difflib', 'dircache', 'dis',
|
||||||
'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_thread',
|
'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_thread',
|
||||||
@@ -67,13 +68,6 @@ base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit',
|
|||||||
'MimeWriter', 'mimify', 'multifile', 'sets']
|
'MimeWriter', 'mimify', 'multifile', 'sets']
|
||||||
|
|
||||||
contributed_modules = []
|
contributed_modules = []
|
||||||
for root, dirs, files in os.walk('gluon'):
|
|
||||||
for candidate in ['.'.join(
|
|
||||||
os.path.join(root, os.path.splitext(name)[0]).split(os.sep))
|
|
||||||
for name in files if name.endswith('.py')
|
|
||||||
and root.split(os.sep) != ['gluon', 'tests']
|
|
||||||
]:
|
|
||||||
contributed_modules.append(candidate)
|
|
||||||
|
|
||||||
# Python base version
|
# Python base version
|
||||||
python_version = sys.version[:3]
|
python_version = sys.version[:3]
|
||||||
|
|||||||
@@ -376,7 +376,6 @@ def wsgibase(environ, responder):
|
|||||||
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
||||||
or env.https == 'on'
|
or env.https == 'on'
|
||||||
)
|
)
|
||||||
request.compute_uuid() # requires client
|
|
||||||
request.url = environ['PATH_INFO']
|
request.url = environ['PATH_INFO']
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
|
|||||||
+1
-1
Submodule gluon/packages/dal updated: b08cb1f779...06654a1676
+1
-3
@@ -41,9 +41,7 @@ def enable_autocomplete_and_history(adir, env):
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
readline.parse_and_bind("bind ^I rl_complete"
|
readline.parse_and_bind("tab: complete")
|
||||||
if sys.platform == 'darwin'
|
|
||||||
else "tab: complete")
|
|
||||||
history_file = os.path.join(adir, '.pythonhistory')
|
history_file = os.path.join(adir, '.pythonhistory')
|
||||||
try:
|
try:
|
||||||
readline.read_history_file(history_file)
|
readline.read_history_file(history_file)
|
||||||
|
|||||||
+31
-20
@@ -58,9 +58,12 @@ def represent(field, value, record):
|
|||||||
f = field.represent
|
f = field.represent
|
||||||
if not callable(f):
|
if not callable(f):
|
||||||
return str(value)
|
return str(value)
|
||||||
n = f.func_code.co_argcount - len(f.func_defaults or [])
|
if hasattr(f,'func_code'):
|
||||||
if getattr(f, 'im_self', None):
|
n = f.func_code.co_argcount - len(f.func_defaults or [])
|
||||||
n -= 1
|
if getattr(f, 'im_self', None):
|
||||||
|
n -= 1
|
||||||
|
else:
|
||||||
|
n = 1
|
||||||
if n == 1:
|
if n == 1:
|
||||||
return f(value)
|
return f(value)
|
||||||
elif n == 2:
|
elif n == 2:
|
||||||
@@ -1205,6 +1208,9 @@ class SQLFORM(FORM):
|
|||||||
elif field.type == 'boolean':
|
elif field.type == 'boolean':
|
||||||
inp = self.widgets.boolean.widget(
|
inp = self.widgets.boolean.widget(
|
||||||
field, default, _disabled=True)
|
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:
|
else:
|
||||||
inp = field.formatter(default)
|
inp = field.formatter(default)
|
||||||
if getattr(field, 'show_if', None):
|
if getattr(field, 'show_if', None):
|
||||||
@@ -1246,6 +1252,9 @@ class SQLFORM(FORM):
|
|||||||
dspval = ''
|
dspval = ''
|
||||||
elif field.type == 'blob':
|
elif field.type == 'blob':
|
||||||
continue
|
continue
|
||||||
|
elif isinstance(field.type, SQLCustomType) and callable(field.type.widget):
|
||||||
|
# SQLCustomType has a widget, use it
|
||||||
|
inp = field.type.widget(field, default)
|
||||||
else:
|
else:
|
||||||
field_type = widget_class.match(str(field.type)).group()
|
field_type = widget_class.match(str(field.type)).group()
|
||||||
field_type = field_type in self.widgets and field_type or 'string'
|
field_type = field_type in self.widgets and field_type or 'string'
|
||||||
@@ -1958,7 +1967,8 @@ class SQLFORM(FORM):
|
|||||||
cache_count=None,
|
cache_count=None,
|
||||||
client_side_delete=False,
|
client_side_delete=False,
|
||||||
ignore_common_filters=None,
|
ignore_common_filters=None,
|
||||||
auto_pagination=True):
|
auto_pagination=True,
|
||||||
|
use_cursor=False):
|
||||||
|
|
||||||
formstyle = formstyle or current.response.formstyle
|
formstyle = formstyle or current.response.formstyle
|
||||||
|
|
||||||
@@ -2060,18 +2070,15 @@ class SQLFORM(FORM):
|
|||||||
# is unique and usually indexed. See issue #679
|
# is unique and usually indexed. See issue #679
|
||||||
if not orderby:
|
if not orderby:
|
||||||
orderby = field_id
|
orderby = field_id
|
||||||
else:
|
elif isinstance(orderby, list):
|
||||||
if isinstance(orderby, Expression):
|
orderby = reduce(lambda a,b: a|b, orderby)
|
||||||
if orderby.first:
|
elif isinstance(orderby, Field) and orderby is not field_id:
|
||||||
# here we're with a DESC order on a field
|
# here we're with an ASC order on a field stored as orderby
|
||||||
# stored as orderby.first
|
orderby = orderby | field_id
|
||||||
if orderby.first is not field_id:
|
elif (isinstance(orderby, Expression) and
|
||||||
orderby = orderby | field_id
|
orderby.first and orderby.first is not field_id):
|
||||||
else:
|
# here we're with a DESC order on a field stored as orderby.first
|
||||||
# here we're with an ASC order on a field
|
orderby = orderby | field_id
|
||||||
# stored as orderby
|
|
||||||
if orderby is not field_id:
|
|
||||||
orderby = orderby | field_id
|
|
||||||
return orderby
|
return orderby
|
||||||
|
|
||||||
def url(**b):
|
def url(**b):
|
||||||
@@ -2148,7 +2155,7 @@ class SQLFORM(FORM):
|
|||||||
else:
|
else:
|
||||||
fields = []
|
fields = []
|
||||||
columns = []
|
columns = []
|
||||||
filter1 = lambda f: isinstance(f, Field)
|
filter1 = lambda f: isinstance(f, Field) and f.type != 'blob'
|
||||||
filter2 = lambda f: isinstance(f, Field) and f.readable
|
filter2 = lambda f: isinstance(f, Field) and f.readable
|
||||||
for table in tables:
|
for table in tables:
|
||||||
fields += filter(filter1, table)
|
fields += filter(filter1, table)
|
||||||
@@ -2533,7 +2540,7 @@ class SQLFORM(FORM):
|
|||||||
|
|
||||||
cursor = True
|
cursor = True
|
||||||
# figure out what page we are one to setup the limitby
|
# 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
|
cursor = request.vars.cursor or True
|
||||||
limitby = (0, paginate)
|
limitby = (0, paginate)
|
||||||
try:
|
try:
|
||||||
@@ -2555,7 +2562,7 @@ class SQLFORM(FORM):
|
|||||||
table_fields = [field for field in fields
|
table_fields = [field for field in fields
|
||||||
if (field.tablename in tablenames and
|
if (field.tablename in tablenames and
|
||||||
not(isinstance(field, Field.Virtual)))]
|
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,
|
rows = dbset.select(left=left, orderby=orderby,
|
||||||
groupby=groupby, limitby=limitby,
|
groupby=groupby, limitby=limitby,
|
||||||
reusecursor=cursor,
|
reusecursor=cursor,
|
||||||
@@ -2565,6 +2572,7 @@ class SQLFORM(FORM):
|
|||||||
rows = dbset.select(left=left, orderby=orderby,
|
rows = dbset.select(left=left, orderby=orderby,
|
||||||
groupby=groupby, limitby=limitby,
|
groupby=groupby, limitby=limitby,
|
||||||
cacheable=True, *table_fields)
|
cacheable=True, *table_fields)
|
||||||
|
next_cursor = None
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
rows = None
|
rows = None
|
||||||
next_cursor = None
|
next_cursor = None
|
||||||
@@ -2583,7 +2591,7 @@ class SQLFORM(FORM):
|
|||||||
console.append(DIV(message or '', _class='web2py_counter'))
|
console.append(DIV(message or '', _class='web2py_counter'))
|
||||||
|
|
||||||
paginator = UL()
|
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.
|
# this means we may have a large table with an unknown number of rows.
|
||||||
try:
|
try:
|
||||||
page = int(request.vars.page or 1) - 1
|
page = int(request.vars.page or 1) - 1
|
||||||
@@ -2708,6 +2716,9 @@ class SQLFORM(FORM):
|
|||||||
_href='%s/%s' % (upload, value))
|
_href='%s/%s' % (upload, value))
|
||||||
else:
|
else:
|
||||||
value = ''
|
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):
|
if isinstance(value, str):
|
||||||
value = truncate_string(value, maxlength)
|
value = truncate_string(value, maxlength)
|
||||||
elif not isinstance(value, XmlComponent):
|
elif not isinstance(value, XmlComponent):
|
||||||
|
|||||||
+31
-28
@@ -269,31 +269,33 @@ class FastStorage(dict):
|
|||||||
|
|
||||||
|
|
||||||
class List(list):
|
class List(list):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Like a regular python list but a[i] if i is out of bounds returns None
|
Like a regular python list but callable.
|
||||||
instead of `IndexOutOfBounds`
|
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):
|
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
|
||||||
"""Allows to use a special syntax for fast-check of `request.args()`
|
"""Allows to use a special syntax for fast-check of
|
||||||
validity
|
`request.args()` validity.
|
||||||
|
:params:
|
||||||
Args:
|
|
||||||
i: index
|
i: index
|
||||||
default: use this value if arg not found
|
default: use this value if arg not found
|
||||||
cast: type cast
|
cast: type cast
|
||||||
otherwise: can be:
|
otherwise:
|
||||||
|
will be executed when:
|
||||||
- None: results in a 404
|
- casts fail
|
||||||
- str: redirect to this address
|
- value not found, dont have default and otherwise is
|
||||||
- callable: calls the function (nothing is passed)
|
especified
|
||||||
|
can be:
|
||||||
|
- None: results in a 404
|
||||||
|
- str: redirect to this address
|
||||||
|
- callable: calls the function (nothing is passed)
|
||||||
Example:
|
Example:
|
||||||
You can use::
|
You can use::
|
||||||
|
|
||||||
request.args(0,default=0,cast=int,otherwise='http://error_url')
|
request.args(0,default=0,cast=int,otherwise='http://error_url')
|
||||||
request.args(0,default=0,cast=int,otherwise=lambda:...)
|
request.args(0,default=0,cast=int,otherwise=lambda:...)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
n = len(self)
|
n = len(self)
|
||||||
if 0 <= i < n or -n <= i < 0:
|
if 0 <= i < n or -n <= i < 0:
|
||||||
@@ -301,23 +303,24 @@ class List(list):
|
|||||||
elif default is DEFAULT:
|
elif default is DEFAULT:
|
||||||
value = None
|
value = None
|
||||||
else:
|
else:
|
||||||
value, cast = default, False
|
value, cast, otherwise = default, False, False
|
||||||
if cast:
|
try:
|
||||||
try:
|
if cast:
|
||||||
value = cast(value)
|
value = cast(value)
|
||||||
except (ValueError, TypeError):
|
if not value and otherwise:
|
||||||
from http import HTTP, redirect
|
raise ValueError('Otherwise will raised.')
|
||||||
if otherwise is None:
|
except (ValueError, TypeError):
|
||||||
raise HTTP(404)
|
from http import HTTP, redirect
|
||||||
elif isinstance(otherwise, str):
|
if otherwise is None:
|
||||||
redirect(otherwise)
|
raise HTTP(404)
|
||||||
elif callable(otherwise):
|
elif isinstance(otherwise, str):
|
||||||
return otherwise()
|
redirect(otherwise)
|
||||||
else:
|
elif callable(otherwise):
|
||||||
raise RuntimeError("invalid otherwise")
|
return otherwise()
|
||||||
|
else:
|
||||||
|
raise RuntimeError("invalid otherwise")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from test_http import *
|
|||||||
from test_cache import *
|
from test_cache import *
|
||||||
from test_contenttype import *
|
from test_contenttype import *
|
||||||
from test_fileutils import *
|
from test_fileutils import *
|
||||||
|
from test_globals import *
|
||||||
from test_html import *
|
from test_html import *
|
||||||
from test_is_url import *
|
from test_is_url import *
|
||||||
from test_languages import *
|
from test_languages import *
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ fix_sys_path(__file__)
|
|||||||
|
|
||||||
from storage import Storage
|
from storage import Storage
|
||||||
from cache import CacheInRam, CacheOnDisk, Cache
|
from cache import CacheInRam, CacheOnDisk, Cache
|
||||||
|
from gluon.dal import DAL, Field
|
||||||
|
|
||||||
oldcwd = None
|
oldcwd = None
|
||||||
|
|
||||||
@@ -30,6 +31,11 @@ def tearDownModule():
|
|||||||
if oldcwd:
|
if oldcwd:
|
||||||
os.chdir(oldcwd)
|
os.chdir(oldcwd)
|
||||||
oldcwd = None
|
oldcwd = None
|
||||||
|
try:
|
||||||
|
os.unlink('dummy.db')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestCache(unittest.TestCase):
|
class TestCache(unittest.TestCase):
|
||||||
@@ -107,7 +113,34 @@ class TestCache(unittest.TestCase):
|
|||||||
cache.clear(regex=r'a*')
|
cache.clear(regex=r'a*')
|
||||||
self.assertEqual(cache('a1', lambda: 2, 0), 2)
|
self.assertEqual(cache('a1', lambda: 2, 0), 2)
|
||||||
self.assertEqual(cache('a2', lambda: 3, 100), 3)
|
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__':
|
if __name__ == '__main__':
|
||||||
setUpModule() # pre-python-2.7
|
setUpModule() # pre-python-2.7
|
||||||
|
|||||||
+19
-3
@@ -4,6 +4,7 @@
|
|||||||
Unit tests for gluon.dal
|
Unit tests for gluon.dal
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from fix_path import fix_sys_path
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
@@ -12,28 +13,43 @@ fix_sys_path(__file__)
|
|||||||
|
|
||||||
from gluon.dal import DAL, Field
|
from gluon.dal import DAL, Field
|
||||||
|
|
||||||
|
def tearDownModule():
|
||||||
|
try:
|
||||||
|
os.unlink('dummy.db')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestDALSubclass(unittest.TestCase):
|
class TestDALSubclass(unittest.TestCase):
|
||||||
|
|
||||||
def testRun(self):
|
def testRun(self):
|
||||||
import gluon.serializers as mserializers
|
from gluon.serializers import custom_json, xml
|
||||||
from gluon import sqlhtml
|
from gluon import sqlhtml
|
||||||
db = DAL(check_reserved=['all'])
|
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_render'], sqlhtml.represent)
|
||||||
self.assertEqual(db.representers['rows_xml'], sqlhtml.SQLTABLE)
|
self.assertEqual(db.representers['rows_xml'], sqlhtml.SQLTABLE)
|
||||||
|
db.close()
|
||||||
|
|
||||||
def testSerialization(self):
|
def testSerialization(self):
|
||||||
import pickle
|
import pickle
|
||||||
db = DAL(check_reserved=['all'])
|
db = DAL(check_reserved=['all'])
|
||||||
db.define_table('t_a', Field('f_a'))
|
db.define_table('t_a', Field('f_a'))
|
||||||
db.t_a.insert(f_a='test')
|
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)
|
s = pickle.dumps(a)
|
||||||
b = pickle.loads(s)
|
b = pickle.loads(s)
|
||||||
self.assertEqual(a.db, b.db)
|
self.assertEqual(a.db, b.db)
|
||||||
|
db.t_a.drop()
|
||||||
|
db.close()
|
||||||
|
|
||||||
""" TODO:
|
""" TODO:
|
||||||
class TestDefaultValidators(unittest.TestCase):
|
class TestDefaultValidators(unittest.TestCase):
|
||||||
def testRun(self):
|
def testRun(self):
|
||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
tearDownModule()
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -120,6 +120,7 @@ class TestStorageList(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestList(unittest.TestCase):
|
class TestList(unittest.TestCase):
|
||||||
|
|
||||||
""" Tests Storage.List (fast-check for request.args()) """
|
""" Tests Storage.List (fast-check for request.args()) """
|
||||||
|
|
||||||
def test_listcall(self):
|
def test_listcall(self):
|
||||||
@@ -134,6 +135,23 @@ class TestList(unittest.TestCase):
|
|||||||
self.assertEqual(a(3, cast=int), 1234)
|
self.assertEqual(a(3, cast=int), 1234)
|
||||||
a.append('x')
|
a.append('x')
|
||||||
self.assertRaises(HTTP, a, 4, cast=int)
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
+159
-13
@@ -42,10 +42,13 @@ from gluon import *
|
|||||||
from gluon.contrib.autolinks import expand_one
|
from gluon.contrib.autolinks import expand_one
|
||||||
from gluon.contrib.markmin.markmin2html import \
|
from gluon.contrib.markmin.markmin2html import \
|
||||||
replace_at_urls, replace_autolinks, replace_components
|
replace_at_urls, replace_autolinks, replace_components
|
||||||
from pydal.objects import Table, Row, Set, Query
|
from pydal.objects import Row, Set, Query
|
||||||
|
|
||||||
import gluon.serializers as serializers
|
import gluon.serializers as serializers
|
||||||
|
|
||||||
|
Table = DAL.Table
|
||||||
|
Field = DAL.Field
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# try stdlib (Python 2.6)
|
# try stdlib (Python 2.6)
|
||||||
import json as json_parser
|
import json as json_parser
|
||||||
@@ -57,7 +60,7 @@ except ImportError:
|
|||||||
# fallback to pure-Python module
|
# fallback to pure-Python module
|
||||||
import gluon.contrib.simplejson as json_parser
|
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']
|
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
|
||||||
|
|
||||||
### mind there are two loggers here (logger and crud.settings.logger)!
|
### mind there are two loggers here (logger and crud.settings.logger)!
|
||||||
@@ -764,8 +767,8 @@ class Mail(object):
|
|||||||
if self.settings.server == 'logging':
|
if self.settings.server == 'logging':
|
||||||
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' %
|
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' %
|
||||||
('-' * 40, sender,
|
('-' * 40, sender,
|
||||||
', '.join(to), subject,
|
', '.join(to), subject,
|
||||||
text or html, '-' * 40))
|
text or html, '-' * 40))
|
||||||
elif self.settings.server == 'gae':
|
elif self.settings.server == 'gae':
|
||||||
xcc = dict()
|
xcc = dict()
|
||||||
if cc:
|
if cc:
|
||||||
@@ -776,23 +779,23 @@ class Mail(object):
|
|||||||
xcc['reply_to'] = reply_to
|
xcc['reply_to'] = reply_to
|
||||||
from google.appengine.api import mail
|
from google.appengine.api import mail
|
||||||
attachments = attachments and [mail.Attachment(
|
attachments = attachments and [mail.Attachment(
|
||||||
a.my_filename,
|
a.my_filename,
|
||||||
a.my_payload,
|
a.my_payload,
|
||||||
contebt_id='<attachment-%s>' % k
|
contebt_id='<attachment-%s>' % k
|
||||||
) for k,a in enumerate(attachments) if not raw]
|
) for k,a in enumerate(attachments) if not raw]
|
||||||
if attachments:
|
if attachments:
|
||||||
result = mail.send_mail(
|
result = mail.send_mail(
|
||||||
sender=sender, to=origTo,
|
sender=sender, to=origTo,
|
||||||
subject=subject, body=text, html=html,
|
subject=unicode(subject), body=unicode(text), html=html,
|
||||||
attachments=attachments, **xcc)
|
attachments=attachments, **xcc)
|
||||||
elif html and (not raw):
|
elif html and (not raw):
|
||||||
result = mail.send_mail(
|
result = mail.send_mail(
|
||||||
sender=sender, to=origTo,
|
sender=sender, to=origTo,
|
||||||
subject=subject, body=text, html=html, **xcc)
|
subject=unicode(subject), body=unicode(text), html=html, **xcc)
|
||||||
else:
|
else:
|
||||||
result = mail.send_mail(
|
result = mail.send_mail(
|
||||||
sender=sender, to=origTo,
|
sender=sender, to=origTo,
|
||||||
subject=subject, body=text, **xcc)
|
subject=unicode(subject), body=unicode(text), **xcc)
|
||||||
else:
|
else:
|
||||||
smtp_args = self.settings.server.split(':')
|
smtp_args = self.settings.server.split(':')
|
||||||
kwargs = dict(timeout=self.settings.timeout)
|
kwargs = dict(timeout=self.settings.timeout)
|
||||||
@@ -962,7 +965,142 @@ class Recaptcha(DIV):
|
|||||||
return XML(captcha).xml()
|
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):
|
def addrow(form, a, b, c, style, _id, position=-1):
|
||||||
if style == "divs":
|
if style == "divs":
|
||||||
form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'),
|
form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'),
|
||||||
@@ -984,6 +1122,15 @@ def addrow(form, a, b, c, style, _id, position=-1):
|
|||||||
DIV(b, SPAN(c, _class='inline-help'),
|
DIV(b, SPAN(c, _class='inline-help'),
|
||||||
_class='controls'),
|
_class='controls'),
|
||||||
_class='control-group', _id=_id))
|
_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:
|
else:
|
||||||
form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'),
|
form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'),
|
||||||
TD(b, _class='w2p_fw'),
|
TD(b, _class='w2p_fw'),
|
||||||
@@ -1327,8 +1474,7 @@ class Auth(object):
|
|||||||
logged_url=URL(controller, function, args='profile'),
|
logged_url=URL(controller, function, args='profile'),
|
||||||
download_url=URL(controller, 'download'),
|
download_url=URL(controller, 'download'),
|
||||||
mailer=(mailer is True) and Mail() or mailer,
|
mailer=(mailer is True) and Mail() or mailer,
|
||||||
on_failed_authorization =
|
on_failed_authorization = URL(controller, function, args='not_authorized'),
|
||||||
URL(controller, function, args='not_authorized'),
|
|
||||||
login_next = url_index,
|
login_next = url_index,
|
||||||
login_onvalidation = [],
|
login_onvalidation = [],
|
||||||
login_onaccept = [],
|
login_onaccept = [],
|
||||||
@@ -3568,7 +3714,7 @@ class Auth(object):
|
|||||||
return record.id
|
return record.id
|
||||||
else:
|
else:
|
||||||
id = membership.insert(group_id=group_id, user_id=user_id)
|
id = membership.insert(group_id=group_id, user_id=user_id)
|
||||||
if role:
|
if role:
|
||||||
self.user_groups[group_id] = role
|
self.user_groups[group_id] = role
|
||||||
else:
|
else:
|
||||||
self.update_groups()
|
self.update_groups()
|
||||||
@@ -5359,7 +5505,7 @@ class Expose(object):
|
|||||||
if current.request.raw_args:
|
if current.request.raw_args:
|
||||||
self.args = [arg for arg in current.request.raw_args.split('/') if arg]
|
self.args = [arg for arg in current.request.raw_args.split('/') if arg]
|
||||||
else:
|
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)
|
filename = os.path.join(base, *self.args)
|
||||||
if not os.path.exists(filename):
|
if not os.path.exists(filename):
|
||||||
raise HTTP(404, "FILE NOT FOUND")
|
raise HTTP(404, "FILE NOT FOUND")
|
||||||
|
|||||||
+1
-1
@@ -611,7 +611,7 @@ class IS_IN_DB(Validator):
|
|||||||
|
|
||||||
def count(values, s=self.dbset, f=field):
|
def count(values, s=self.dbset, f=field):
|
||||||
return s(f.belongs(map(int, values))).count()
|
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)
|
range_ids = range(0, len(values), 30)
|
||||||
total = sum(count(values[i:i + 30]) for i in range_ids)
|
total = sum(count(values[i:i + 30]) for i in range_ids)
|
||||||
if total == len(values):
|
if total == len(values):
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ server {
|
|||||||
ssl_session_cache shared:SSL:10m;
|
ssl_session_cache shared:SSL:10m;
|
||||||
ssl_session_timeout 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_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;
|
keepalive_timeout 70;
|
||||||
location / {
|
location / {
|
||||||
#uwsgi_pass 127.0.0.1:9001;
|
#uwsgi_pass 127.0.0.1:9001;
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
""
|
||||||
Executable
+78
@@ -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)
|
||||||
@@ -62,7 +62,7 @@ do_start()
|
|||||||
start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
|
start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
|
||||||
&& return 1
|
&& 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 \
|
${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \
|
||||||
--background --exec $DAEMON -- $DAEMON_ARGS \
|
--background --exec $DAEMON -- $DAEMON_ARGS \
|
||||||
|| return 2
|
|| return 2
|
||||||
|
|||||||
Reference in New Issue
Block a user