Compare commits
89 Commits
R-2.10.4.b
...
R-2.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,3 +1,7 @@
|
||||
## 2.11.1
|
||||
|
||||
- Many small but significative improvements and bug fixes
|
||||
|
||||
## 2.10.1-2.10.2
|
||||
|
||||
- welcome app defaults to Bootstrap 3
|
||||
|
||||
2
Makefile
2
Makefile
@@ -32,7 +32,7 @@ update:
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.10.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.11.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
|
||||
@@ -13,7 +13,7 @@ Learn more at http://web2py.com
|
||||
|
||||
Then edit ./app.yaml and replace "yourappname" with yourappname.
|
||||
|
||||
## Import about this GIT repo
|
||||
## Important reminder about this GIT repo
|
||||
|
||||
An important part of web2py is the Database Abstraction Layer (DAL). In early 2015 this was decoupled into a separate code-base (PyDAL). In terms of git, it is a sub-module of the main repository.
|
||||
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.10.3-stable+timestamp.2015.04.02.16.28.49
|
||||
Version 2.11.1-stable+timestamp.2015.05.28.23.17.58
|
||||
|
||||
@@ -744,7 +744,7 @@ def edit():
|
||||
viewlist.append(aviewpath + '.html')
|
||||
if len(viewlist):
|
||||
editviewlinks = []
|
||||
for v in viewlist:
|
||||
for v in sorted(viewlist):
|
||||
vf = os.path.split(v)[-1]
|
||||
vargs = "/".join([viewpath.replace(os.sep, "/"), vf])
|
||||
editviewlinks.append(A(vf.split(".")[0],
|
||||
@@ -754,6 +754,7 @@ def edit():
|
||||
if len(request.args) > 2 and request.args[1] == 'controllers':
|
||||
controller = (request.args[2])[:-3]
|
||||
functions = find_exposed_functions(data)
|
||||
functions = functions and sorted(functions) or []
|
||||
else:
|
||||
(controller, functions) = (None, None)
|
||||
|
||||
@@ -1067,7 +1068,7 @@ def design():
|
||||
for c in controllers:
|
||||
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
||||
items = find_exposed_functions(data)
|
||||
functions[c] = items
|
||||
functions[c] = items and sorted(items) or []
|
||||
|
||||
# Get all views
|
||||
views = sorted(
|
||||
@@ -1205,7 +1206,7 @@ def plugin():
|
||||
for c in controllers:
|
||||
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
||||
items = find_exposed_functions(data)
|
||||
functions[c] = items
|
||||
functions[c] = items and sorted(items) or []
|
||||
|
||||
# Get all views
|
||||
views = sorted(
|
||||
|
||||
@@ -490,12 +490,12 @@
|
||||
* and prevent clicking on it */
|
||||
disableElement: function(el) {
|
||||
el.addClass('disabled');
|
||||
var method = el.is('button') ? 'html' : 'val';
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
//method = el.attr('name') ? 'html' : 'val';
|
||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||
/*store enabled state if not already disabled */
|
||||
if(el.data('w2p:enable-with') === undefined) {
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
if(el.data('w2p_enable_with') === undefined) {
|
||||
el.data('w2p_enable_with', el[method]());
|
||||
}
|
||||
/*if you don't want to see "working..." on buttons, replace the following
|
||||
* two lines with this one
|
||||
@@ -515,11 +515,11 @@
|
||||
|
||||
/* restore element to its original state which was disabled by 'disableElement' above*/
|
||||
enableElement: function(el) {
|
||||
var method = el.is('button') ? 'val' : 'html';
|
||||
if(el.data('w2p:enable-with') !== undefined) {
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
if(el.data('w2p_enable_with') !== undefined) {
|
||||
/* set to old enabled state */
|
||||
el[method](el.data('w2p:enable-with'));
|
||||
el.removeData('w2p:enable-with');
|
||||
el[method](el.data('w2p_enable_with'));
|
||||
el.removeData('w2p_enable_with');
|
||||
}
|
||||
el.removeClass('disabled');
|
||||
el.unbind('click.w2pDisable');
|
||||
@@ -586,12 +586,14 @@
|
||||
if(pre_call != undefined) {
|
||||
eval(pre_call);
|
||||
}
|
||||
if(confirm_message != undefined) {
|
||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
if(confirm_message) {
|
||||
if(confirm_message == 'default')
|
||||
confirm_message = w2p_ajax_confirm_message ||
|
||||
'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(target == undefined) {
|
||||
if(method == 'GET') {
|
||||
@@ -634,7 +636,7 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Caches element value in 'w2p:enable-with' data store
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
@@ -646,8 +648,8 @@
|
||||
if(disable_with == undefined) {
|
||||
element.data('w2p_disable_with', element[method]())
|
||||
}
|
||||
if(element.data('w2p:enable-with') === undefined) {
|
||||
element.data('w2p:enable-with', element[method]());
|
||||
if(element.data('w2p_enable_with') === undefined) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
@@ -655,16 +657,16 @@
|
||||
},
|
||||
|
||||
/* Re-enables disabled form elements:
|
||||
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
|
||||
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
|
||||
- Sets disabled property to false
|
||||
*/
|
||||
enableFormElements: function(form) {
|
||||
form.find(web2py.enableSelector).each(function() {
|
||||
var element = $(this),
|
||||
method = element.is('button') ? 'html' : 'val';
|
||||
if(element.data('w2p:enable-with')) {
|
||||
element[method](element.data('w2p:enable-with'));
|
||||
element.removeData('w2p:enable-with');
|
||||
if(element.data('w2p_enable_with')) {
|
||||
element[method](element.data('w2p_enable_with'));
|
||||
element.removeData('w2p_enable_with');
|
||||
}
|
||||
element.prop('disabled', false);
|
||||
});
|
||||
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -22,5 +22,6 @@
|
||||
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
|
||||
- [[Appliances http://www.web2py.com/appliances popup]]
|
||||
- [[web2py utils http://packages.python.org/web2py_utils/ popup]]
|
||||
- [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]]
|
||||
|
||||
#### [[Sites Powered by web2py http://www.web2py.com/poweredby popup]]
|
||||
|
||||
@@ -490,12 +490,12 @@
|
||||
* and prevent clicking on it */
|
||||
disableElement: function(el) {
|
||||
el.addClass('disabled');
|
||||
var method = el.is('button') ? 'html' : 'val';
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
//method = el.attr('name') ? 'html' : 'val';
|
||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||
/*store enabled state if not already disabled */
|
||||
if(el.data('w2p:enable-with') === undefined) {
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
if(el.data('w2p_enable_with') === undefined) {
|
||||
el.data('w2p_enable_with', el[method]());
|
||||
}
|
||||
/*if you don't want to see "working..." on buttons, replace the following
|
||||
* two lines with this one
|
||||
@@ -515,11 +515,11 @@
|
||||
|
||||
/* restore element to its original state which was disabled by 'disableElement' above*/
|
||||
enableElement: function(el) {
|
||||
var method = el.is('button') ? 'val' : 'html';
|
||||
if(el.data('w2p:enable-with') !== undefined) {
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
if(el.data('w2p_enable_with') !== undefined) {
|
||||
/* set to old enabled state */
|
||||
el[method](el.data('w2p:enable-with'));
|
||||
el.removeData('w2p:enable-with');
|
||||
el[method](el.data('w2p_enable_with'));
|
||||
el.removeData('w2p_enable_with');
|
||||
}
|
||||
el.removeClass('disabled');
|
||||
el.unbind('click.w2pDisable');
|
||||
@@ -586,12 +586,14 @@
|
||||
if(pre_call != undefined) {
|
||||
eval(pre_call);
|
||||
}
|
||||
if(confirm_message != undefined) {
|
||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
if(confirm_message) {
|
||||
if(confirm_message == 'default')
|
||||
confirm_message = w2p_ajax_confirm_message ||
|
||||
'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(target == undefined) {
|
||||
if(method == 'GET') {
|
||||
@@ -634,7 +636,7 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Caches element value in 'w2p:enable-with' data store
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
@@ -646,8 +648,8 @@
|
||||
if(disable_with == undefined) {
|
||||
element.data('w2p_disable_with', element[method]())
|
||||
}
|
||||
if(element.data('w2p:enable-with') === undefined) {
|
||||
element.data('w2p:enable-with', element[method]());
|
||||
if(element.data('w2p_enable_with') === undefined) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
@@ -655,16 +657,16 @@
|
||||
},
|
||||
|
||||
/* Re-enables disabled form elements:
|
||||
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
|
||||
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
|
||||
- Sets disabled property to false
|
||||
*/
|
||||
enableFormElements: function(form) {
|
||||
form.find(web2py.enableSelector).each(function() {
|
||||
var element = $(this),
|
||||
method = element.is('button') ? 'html' : 'val';
|
||||
if(element.data('w2p:enable-with')) {
|
||||
element[method](element.data('w2p:enable-with'));
|
||||
element.removeData('w2p:enable-with');
|
||||
if(element.data('w2p_enable_with')) {
|
||||
element[method](element.data('w2p_enable_with'));
|
||||
element.removeData('w2p_enable_with');
|
||||
}
|
||||
element.prop('disabled', false);
|
||||
});
|
||||
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</center>
|
||||
|
||||
<p style="text-align:left;">
|
||||
The source code version works on all supported platforms, including Linux, but it requires Python 2.5, 2.6, or 2.7.
|
||||
The source code version works on all supported platforms, including Linux, but it requires Python 2.6, or 2.7 (recommended).
|
||||
It runs on Windows and most Unix systems, including <b>Linux</b> and <b>BSD</b>.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
|
||||
<li><a target="_blank" href="http://www.vsa-services.com/">VSA Services</a> (Singapore)</li>
|
||||
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
|
||||
<li><a target="_blank" href="www.corebyte.nl">Corebyte</a> (Netherland)</li>
|
||||
<li><a target="_blank" href="https://loadinfo-net.appspot.com">LoadInfo</a> (Bulgaria)</li>
|
||||
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
|
||||
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
|
||||
|
||||
@@ -62,7 +62,7 @@ auth.define_tables(username=False, signature=False)
|
||||
|
||||
## configure email
|
||||
mail = auth.settings.mailer
|
||||
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.sender')
|
||||
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server')
|
||||
mail.settings.sender = myconf.take('smtp.sender')
|
||||
mail.settings.login = myconf.take('smtp.login')
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
# Language from default.py or 'en' (if the file is not found) is used as
|
||||
# a default_language
|
||||
#
|
||||
# See <web2py-root-dir>/router.example.py for parameter's detail
|
||||
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
|
||||
#-------------------------------------------------------------------------------------
|
||||
# To enable this route file you must do the steps:
|
||||
#
|
||||
# 1. rename <web2py-root-dir>/router.example.py to routes.py
|
||||
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
|
||||
# 2. rename this APP/routes.example.py to APP/routes.py
|
||||
# (where APP - is your application directory)
|
||||
# 3. restart web2py (or reload routes in web2py admin interfase)
|
||||
|
||||
@@ -586,12 +586,14 @@
|
||||
if(pre_call != undefined) {
|
||||
eval(pre_call);
|
||||
}
|
||||
if(confirm_message != undefined) {
|
||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
if(confirm_message) {
|
||||
if(confirm_message == 'default')
|
||||
confirm_message = w2p_ajax_confirm_message ||
|
||||
'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(target == undefined) {
|
||||
if(method == 'GET') {
|
||||
|
||||
46
examples/web.config
Normal file
46
examples/web.config
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- app configuration for web2py on IIS -->
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="WSGI_HANDLER" value="gluon.main.wsgibase" />
|
||||
<add key="WSGI_RESTART_FILE_REGEX" value=".*((routes\.py)|(\.config))$" />
|
||||
</appSettings>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<clear />
|
||||
<rule name="static" enabled="true" stopProcessing="true">
|
||||
<match url="^(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$" />
|
||||
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
|
||||
<action type="Rewrite" url="applications/{R:1}/static/{R:2}" logRewrittenUrl="false" />
|
||||
</rule>
|
||||
<rule name="web2py_app" enabled="true" stopProcessing="true">
|
||||
<match url="(.*)" ignoreCase="false" />
|
||||
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
|
||||
<action type="Rewrite" url="handler.web2py/{R:1}" appendQueryString="true" />
|
||||
</rule>
|
||||
</rules>
|
||||
<outboundRules>
|
||||
<rule name="static_version_cache_control" preCondition="static_version">
|
||||
<match serverVariable="RESPONSE_Cache-Control" pattern=".*" />
|
||||
<action type="Rewrite" value="max-age=315360000" />
|
||||
<conditions>
|
||||
</conditions>
|
||||
</rule>
|
||||
<rule name="static_version_Expires" preCondition="static_version">
|
||||
<match serverVariable="RESPONSE_Expires" pattern=".*" />
|
||||
<action type="Rewrite" value="Thu, 31 Dec 2037 23:59:59 GMT" />
|
||||
</rule>
|
||||
<preConditions>
|
||||
<preCondition name="static_version">
|
||||
<add input="{REQUEST_URI}" pattern="(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$" />
|
||||
</preCondition>
|
||||
</preConditions>
|
||||
</outboundRules>
|
||||
</rewrite>
|
||||
<handlers>
|
||||
<!-- replace SCRIPT_PROCESSOR with the configured handler for python -->
|
||||
<add name="Python_via_FastCGI" path="handler.web2py" verb="*" modules="FastCgiModule" scriptProcessor="SCRIPT_PROCESSOR" resourceType="Unspecified" requireAccess="Script" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -473,9 +473,14 @@ class CacheOnDisk(CacheAbstract):
|
||||
if item and ((dt is None) or (item[0] > now - dt)):
|
||||
value = item[1]
|
||||
else:
|
||||
value = f()
|
||||
try:
|
||||
value = f()
|
||||
except:
|
||||
self.storage.release(CacheAbstract.cache_stats_name)
|
||||
self.storage.release(key)
|
||||
raise
|
||||
self.storage[key] = (now, value)
|
||||
self.storage.safe_apply(CacheAbstract.cache_stats_name, inc_misses,
|
||||
self.storage.safe_apply(CacheAbstract.cache_stats_name, inc_misses,
|
||||
default_value={'hit_total': 0, 'misses': 0})
|
||||
|
||||
self.storage.release(CacheAbstract.cache_stats_name)
|
||||
|
||||
@@ -521,9 +521,19 @@ def ldap_auth(server='ldap', port=None,
|
||||
logging.error(
|
||||
'There is no username or email for %s!' % username)
|
||||
raise
|
||||
db_group_search = db((db.auth_membership.user_id == db_user_id) &
|
||||
(db.auth_user.id == db.auth_membership.user_id) &
|
||||
(db.auth_group.id == db.auth_membership.group_id))
|
||||
# if old pydal version, assume this is a relational database which can do joins
|
||||
db_can_join = db.can_join() if hasattr(db, 'can_join') else True
|
||||
if db_can_join:
|
||||
db_group_search = db(
|
||||
(db.auth_membership.user_id == db_user_id) &
|
||||
(db.auth_user.id == db.auth_membership.user_id) &
|
||||
(db.auth_group.id == db.auth_membership.group_id))
|
||||
else:
|
||||
# no joins on NoSQL databases, perform two queries
|
||||
db_group_search = db(db.auth_membership.user_id == db_user_id)
|
||||
group_ids = [x.group_id for x in db_group_search.select(
|
||||
db.auth_membership.group_id, distinct=True)]
|
||||
db_group_search = db(db.auth_group.id.belongs(group_ids))
|
||||
db_groups_of_the_user = list()
|
||||
db_group_id = dict()
|
||||
|
||||
|
||||
@@ -67,11 +67,12 @@ def RedisCache(*args, **vars):
|
||||
|
||||
locker.acquire()
|
||||
try:
|
||||
if not hasattr(RedisCache, 'redis_instance'):
|
||||
RedisCache.redis_instance = RedisClient(*args, **vars)
|
||||
instance_name = 'redis_instance_' + current.request.application
|
||||
if not hasattr(RedisCache, instance_name):
|
||||
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
|
||||
return getattr(RedisCache, instance_name)
|
||||
finally:
|
||||
locker.release()
|
||||
return RedisCache.redis_instance
|
||||
|
||||
|
||||
class RedisClient(object):
|
||||
|
||||
@@ -126,7 +126,7 @@ class History:
|
||||
def globals_dict(self):
|
||||
"""Returns a dictionary view of the globals.
|
||||
"""
|
||||
return dict((name, cPickle.loads(val))
|
||||
return dict((name, pickle.loads(val))
|
||||
for name, val in zip(self.global_names, self.globals))
|
||||
|
||||
def add_unpicklable(self, statement, names):
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
Takes care of adapting pyDAL to web2py's needs
|
||||
--------------------------------------------
|
||||
-----------------------------------------------
|
||||
"""
|
||||
|
||||
from pydal import DAL as DAL
|
||||
from pydal import Field
|
||||
from pydal.objects import Row, Rows, Table, Query, Expression
|
||||
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
||||
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import gluon.settings as settings
|
||||
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
||||
from gluon.settings import global_settings
|
||||
from gluon import recfile
|
||||
from gluon.cache import CacheInRam
|
||||
from gluon.fileutils import copystream
|
||||
import hashlib
|
||||
import portalocker
|
||||
try:
|
||||
@@ -47,8 +49,7 @@ import cgi
|
||||
import urlparse
|
||||
import copy
|
||||
import tempfile
|
||||
from gluon.cache import CacheInRam
|
||||
from gluon.fileutils import copystream
|
||||
|
||||
|
||||
FMT = '%a, %d-%b-%Y %H:%M:%S PST'
|
||||
PAST = 'Sat, 1-Jan-1971 00:00:00'
|
||||
@@ -82,13 +83,22 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
|
||||
css_inline = '<style type="text/css">\n%s\n</style>'
|
||||
js_inline = '<script type="text/javascript">\n%s\n</script>'
|
||||
|
||||
template_mapping = {
|
||||
'css': css_template,
|
||||
'js': js_template,
|
||||
'coffee': coffee_template,
|
||||
'ts': typescript_template,
|
||||
'less': less_template,
|
||||
'css:inline': css_inline,
|
||||
'js:inline': js_inline
|
||||
}
|
||||
|
||||
# IMPORTANT:
|
||||
# this is required so that pickled dict(s) and class.__dict__
|
||||
# are sorted and web2py can detect without ambiguity when a session changes
|
||||
class SortingPickler(Pickler):
|
||||
def save_dict(self, obj):
|
||||
self.write(EMPTY_DICT if self.bin else MARK+DICT)
|
||||
self.write(EMPTY_DICT if self.bin else MARK + DICT)
|
||||
self.memoize(obj)
|
||||
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
|
||||
|
||||
@@ -193,6 +203,7 @@ class Request(Storage):
|
||||
self.is_https = False
|
||||
self.is_local = False
|
||||
self.global_settings = settings.global_settings
|
||||
self._uuid = None
|
||||
|
||||
def parse_get_vars(self):
|
||||
"""Takes the QUERY_STRING and unpacks it to get_vars
|
||||
@@ -275,7 +286,7 @@ class Request(Storage):
|
||||
"""
|
||||
self._vars = copy.copy(self.get_vars)
|
||||
for key, value in self.post_vars.iteritems():
|
||||
if not key in self._vars:
|
||||
if key not in self._vars:
|
||||
self._vars[key] = value
|
||||
else:
|
||||
if not isinstance(self._vars[key], list):
|
||||
@@ -306,13 +317,21 @@ class Request(Storage):
|
||||
self.parse_all_vars()
|
||||
return self._vars
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
"""Lazily uuid
|
||||
"""
|
||||
if self._uuid is None:
|
||||
self.compute_uuid()
|
||||
return self._uuid
|
||||
|
||||
def compute_uuid(self):
|
||||
self.uuid = '%s/%s.%s.%s' % (
|
||||
self._uuid = '%s/%s.%s.%s' % (
|
||||
self.application,
|
||||
self.client.replace(':', '_'),
|
||||
self.now.strftime('%Y-%m-%d.%H-%M-%S'),
|
||||
web2py_uuid())
|
||||
return self.uuid
|
||||
return self._uuid
|
||||
|
||||
def user_agent(self):
|
||||
from gluon.contrib import user_agent_parser
|
||||
@@ -436,29 +455,32 @@ class Response(Storage):
|
||||
return page
|
||||
|
||||
def include_meta(self):
|
||||
s = "\n";
|
||||
s = "\n"
|
||||
for meta in (self.meta or {}).iteritems():
|
||||
k, v = meta
|
||||
if isinstance(v,dict):
|
||||
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
|
||||
if isinstance(v, dict):
|
||||
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
|
||||
else:
|
||||
s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
|
||||
s += '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
|
||||
self.write(s, escape=False)
|
||||
|
||||
def include_files(self, extensions=None):
|
||||
|
||||
"""
|
||||
Caching method for writing out files.
|
||||
Includes files (usually in the head).
|
||||
Can minify and cache local files
|
||||
By default, caches in ram for 5 minutes. To change,
|
||||
response.cache_includes = (cache_method, time_expire).
|
||||
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
||||
"""
|
||||
from gluon import URL
|
||||
|
||||
files = []
|
||||
ext_files = []
|
||||
has_js = has_css = False
|
||||
for item in self.files:
|
||||
if extensions and not item.split('.')[-1] in extensions:
|
||||
if isinstance(item, (list, tuple)):
|
||||
ext_files.append(item)
|
||||
continue
|
||||
if extensions and not item.rpartition('.')[2] in extensions:
|
||||
continue
|
||||
if item in files:
|
||||
continue
|
||||
@@ -487,10 +509,13 @@ class Response(Storage):
|
||||
time_expire)
|
||||
else:
|
||||
files = call_minify()
|
||||
s = ''
|
||||
|
||||
files.extend(ext_files)
|
||||
s = []
|
||||
for item in files:
|
||||
if isinstance(item, str):
|
||||
f = item.lower().split('?')[0]
|
||||
ext = f.rpartition('.')[2]
|
||||
# if static_version we need also to check for
|
||||
# static_version_urls. In that case, the _.x.x.x
|
||||
# bit would have already been added by the URL()
|
||||
@@ -498,24 +523,15 @@ class Response(Storage):
|
||||
if self.static_version and not self.static_version_urls:
|
||||
item = item.replace(
|
||||
'/static/', '/static/_%s/' % self.static_version, 1)
|
||||
if f.endswith('.css'):
|
||||
s += css_template % item
|
||||
elif f.endswith('.js'):
|
||||
s += js_template % item
|
||||
elif f.endswith('.coffee'):
|
||||
s += coffee_template % item
|
||||
elif f.endswith('.ts'):
|
||||
# http://www.typescriptlang.org/
|
||||
s += typescript_template % item
|
||||
elif f.endswith('.less'):
|
||||
s += less_template % item
|
||||
tmpl = template_mapping.get(ext)
|
||||
if tmpl:
|
||||
s.append(tmpl % item)
|
||||
elif isinstance(item, (list, tuple)):
|
||||
f = item[0]
|
||||
if f == 'css:inline':
|
||||
s += css_inline % item[1]
|
||||
elif f == 'js:inline':
|
||||
s += js_inline % item[1]
|
||||
self.write(s, escape=False)
|
||||
tmpl = template_mapping.get(f)
|
||||
if tmpl:
|
||||
s.append(tmpl % item[1])
|
||||
self.write(''.join(s), escape=False)
|
||||
|
||||
def stream(self,
|
||||
stream,
|
||||
@@ -663,7 +679,7 @@ class Response(Storage):
|
||||
return handler(request, self, methods)
|
||||
|
||||
def toolbar(self):
|
||||
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
|
||||
from gluon.html import DIV, SCRIPT, BEAUTIFY, TAG, A
|
||||
BUTTON = TAG.button
|
||||
admin = URL("admin", "default", "design", extension='html',
|
||||
args=current.request.application)
|
||||
|
||||
@@ -376,7 +376,6 @@ def wsgibase(environ, responder):
|
||||
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
||||
or env.https == 'on'
|
||||
)
|
||||
request.compute_uuid() # requires client
|
||||
request.url = environ['PATH_INFO']
|
||||
|
||||
# ##################################################
|
||||
|
||||
Submodule gluon/packages/dal updated: 9272062bf1...4d36919c11
@@ -41,9 +41,7 @@ def enable_autocomplete_and_history(adir, env):
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
readline.parse_and_bind("bind ^I rl_complete"
|
||||
if sys.platform == 'darwin'
|
||||
else "tab: complete")
|
||||
readline.parse_and_bind("tab: complete")
|
||||
history_file = os.path.join(adir, '.pythonhistory')
|
||||
try:
|
||||
readline.read_history_file(history_file)
|
||||
|
||||
@@ -1967,7 +1967,8 @@ class SQLFORM(FORM):
|
||||
cache_count=None,
|
||||
client_side_delete=False,
|
||||
ignore_common_filters=None,
|
||||
auto_pagination=True):
|
||||
auto_pagination=True,
|
||||
use_cursor=False):
|
||||
|
||||
formstyle = formstyle or current.response.formstyle
|
||||
|
||||
@@ -2069,18 +2070,15 @@ class SQLFORM(FORM):
|
||||
# is unique and usually indexed. See issue #679
|
||||
if not orderby:
|
||||
orderby = field_id
|
||||
else:
|
||||
if isinstance(orderby, Expression):
|
||||
if orderby.first:
|
||||
# here we're with a DESC order on a field
|
||||
# stored as orderby.first
|
||||
if orderby.first is not field_id:
|
||||
orderby = orderby | field_id
|
||||
else:
|
||||
# here we're with an ASC order on a field
|
||||
# stored as orderby
|
||||
if orderby is not field_id:
|
||||
orderby = orderby | field_id
|
||||
elif isinstance(orderby, list):
|
||||
orderby = reduce(lambda a,b: a|b, orderby)
|
||||
elif isinstance(orderby, Field) and orderby is not field_id:
|
||||
# here we're with an ASC order on a field stored as orderby
|
||||
orderby = orderby | field_id
|
||||
elif (isinstance(orderby, Expression) and
|
||||
orderby.first and orderby.first is not field_id):
|
||||
# here we're with a DESC order on a field stored as orderby.first
|
||||
orderby = orderby | field_id
|
||||
return orderby
|
||||
|
||||
def url(**b):
|
||||
@@ -2542,7 +2540,7 @@ class SQLFORM(FORM):
|
||||
|
||||
cursor = True
|
||||
# figure out what page we are one to setup the limitby
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
cursor = request.vars.cursor or True
|
||||
limitby = (0, paginate)
|
||||
try:
|
||||
@@ -2564,7 +2562,7 @@ class SQLFORM(FORM):
|
||||
table_fields = [field for field in fields
|
||||
if (field.tablename in tablenames and
|
||||
not(isinstance(field, Field.Virtual)))]
|
||||
if dbset._db._adapter.dbengine == 'google:datastore':
|
||||
if dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
groupby=groupby, limitby=limitby,
|
||||
reusecursor=cursor,
|
||||
@@ -2574,6 +2572,7 @@ class SQLFORM(FORM):
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
groupby=groupby, limitby=limitby,
|
||||
cacheable=True, *table_fields)
|
||||
next_cursor = None
|
||||
except SyntaxError:
|
||||
rows = None
|
||||
next_cursor = None
|
||||
@@ -2592,7 +2591,7 @@ class SQLFORM(FORM):
|
||||
console.append(DIV(message or '', _class='web2py_counter'))
|
||||
|
||||
paginator = UL()
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
# this means we may have a large table with an unknown number of rows.
|
||||
try:
|
||||
page = int(request.vars.page or 1) - 1
|
||||
|
||||
@@ -269,31 +269,33 @@ class FastStorage(dict):
|
||||
|
||||
|
||||
class List(list):
|
||||
|
||||
"""
|
||||
Like a regular python list but a[i] if i is out of bounds returns None
|
||||
instead of `IndexOutOfBounds`
|
||||
Like a regular python list but callable.
|
||||
When a(i) is called if i is out of bounds returns None
|
||||
instead of `IndexError`.
|
||||
"""
|
||||
|
||||
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
|
||||
"""Allows to use a special syntax for fast-check of `request.args()`
|
||||
validity
|
||||
|
||||
Args:
|
||||
"""Allows to use a special syntax for fast-check of
|
||||
`request.args()` validity.
|
||||
:params:
|
||||
i: index
|
||||
default: use this value if arg not found
|
||||
cast: type cast
|
||||
otherwise: can be:
|
||||
|
||||
- None: results in a 404
|
||||
- str: redirect to this address
|
||||
- callable: calls the function (nothing is passed)
|
||||
|
||||
otherwise:
|
||||
will be executed when:
|
||||
- casts fail
|
||||
- value not found, dont have default and otherwise is
|
||||
especified
|
||||
can be:
|
||||
- None: results in a 404
|
||||
- str: redirect to this address
|
||||
- callable: calls the function (nothing is passed)
|
||||
Example:
|
||||
You can use::
|
||||
|
||||
request.args(0,default=0,cast=int,otherwise='http://error_url')
|
||||
request.args(0,default=0,cast=int,otherwise=lambda:...)
|
||||
|
||||
"""
|
||||
n = len(self)
|
||||
if 0 <= i < n or -n <= i < 0:
|
||||
@@ -301,23 +303,24 @@ class List(list):
|
||||
elif default is DEFAULT:
|
||||
value = None
|
||||
else:
|
||||
value, cast = default, False
|
||||
if cast:
|
||||
try:
|
||||
value, cast, otherwise = default, False, False
|
||||
try:
|
||||
if cast:
|
||||
value = cast(value)
|
||||
except (ValueError, TypeError):
|
||||
from http import HTTP, redirect
|
||||
if otherwise is None:
|
||||
raise HTTP(404)
|
||||
elif isinstance(otherwise, str):
|
||||
redirect(otherwise)
|
||||
elif callable(otherwise):
|
||||
return otherwise()
|
||||
else:
|
||||
raise RuntimeError("invalid otherwise")
|
||||
if not value and otherwise:
|
||||
raise ValueError('Otherwise will raised.')
|
||||
except (ValueError, TypeError):
|
||||
from http import HTTP, redirect
|
||||
if otherwise is None:
|
||||
raise HTTP(404)
|
||||
elif isinstance(otherwise, str):
|
||||
redirect(otherwise)
|
||||
elif callable(otherwise):
|
||||
return otherwise()
|
||||
else:
|
||||
raise RuntimeError("invalid otherwise")
|
||||
return value
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
@@ -4,6 +4,7 @@ from test_http import *
|
||||
from test_cache import *
|
||||
from test_contenttype import *
|
||||
from test_fileutils import *
|
||||
from test_globals import *
|
||||
from test_html import *
|
||||
from test_is_url import *
|
||||
from test_languages import *
|
||||
|
||||
@@ -13,6 +13,7 @@ fix_sys_path(__file__)
|
||||
|
||||
from storage import Storage
|
||||
from cache import CacheInRam, CacheOnDisk, Cache
|
||||
from gluon.dal import DAL, Field
|
||||
|
||||
oldcwd = None
|
||||
|
||||
@@ -30,6 +31,11 @@ def tearDownModule():
|
||||
if oldcwd:
|
||||
os.chdir(oldcwd)
|
||||
oldcwd = None
|
||||
try:
|
||||
os.unlink('dummy.db')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class TestCache(unittest.TestCase):
|
||||
@@ -107,7 +113,34 @@ class TestCache(unittest.TestCase):
|
||||
cache.clear(regex=r'a*')
|
||||
self.assertEqual(cache('a1', lambda: 2, 0), 2)
|
||||
self.assertEqual(cache('a2', lambda: 3, 100), 3)
|
||||
return
|
||||
|
||||
def testDALcache(self):
|
||||
s = Storage({'application': 'admin',
|
||||
'folder': 'applications/admin'})
|
||||
cache = Cache(s)
|
||||
db = DAL(check_reserved=['all'])
|
||||
db.define_table('t_a', Field('f_a'))
|
||||
db.t_a.insert(f_a='test')
|
||||
db.commit()
|
||||
a = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
|
||||
b = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
|
||||
self.assertEqual(a.as_csv(), b.as_csv())
|
||||
c = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
|
||||
d = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
|
||||
self.assertEqual(c.as_csv(), d.as_csv())
|
||||
self.assertEqual(a.as_csv(), c.as_csv())
|
||||
self.assertEqual(b.as_csv(), d.as_csv())
|
||||
e = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
|
||||
f = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
|
||||
self.assertEqual(e.as_csv(), f.as_csv())
|
||||
self.assertEqual(a.as_csv(), f.as_csv())
|
||||
g = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
|
||||
h = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
|
||||
self.assertEqual(g.as_csv(), h.as_csv())
|
||||
self.assertEqual(a.as_csv(), h.as_csv())
|
||||
db.t_a.drop()
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setUpModule() # pre-python-2.7
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Unit tests for gluon.dal
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from fix_path import fix_sys_path
|
||||
|
||||
@@ -12,8 +13,15 @@ fix_sys_path(__file__)
|
||||
|
||||
from gluon.dal import DAL, Field
|
||||
|
||||
def tearDownModule():
|
||||
try:
|
||||
os.unlink('dummy.db')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class TestDALSubclass(unittest.TestCase):
|
||||
|
||||
def testRun(self):
|
||||
from gluon.serializers import custom_json, xml
|
||||
from gluon import sqlhtml
|
||||
@@ -22,19 +30,26 @@ class TestDALSubclass(unittest.TestCase):
|
||||
self.assertEqual(db.serializers['xml'], xml)
|
||||
self.assertEqual(db.representers['rows_render'], sqlhtml.represent)
|
||||
self.assertEqual(db.representers['rows_xml'], sqlhtml.SQLTABLE)
|
||||
db.close()
|
||||
|
||||
def testSerialization(self):
|
||||
import pickle
|
||||
db = DAL(check_reserved=['all'])
|
||||
db.define_table('t_a', Field('f_a'))
|
||||
db.t_a.insert(f_a='test')
|
||||
a = db(db.t_a.id>0).select(cacheable=True)
|
||||
a = db(db.t_a.id > 0).select(cacheable=True)
|
||||
s = pickle.dumps(a)
|
||||
b = pickle.loads(s)
|
||||
self.assertEqual(a.db, b.db)
|
||||
db.t_a.drop()
|
||||
db.close()
|
||||
|
||||
""" TODO:
|
||||
class TestDefaultValidators(unittest.TestCase):
|
||||
def testRun(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
tearDownModule()
|
||||
|
||||
124
gluon/tests/test_globals.py
Normal file
124
gluon/tests/test_globals.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Unit tests for gluon.globals
|
||||
"""
|
||||
|
||||
|
||||
import unittest
|
||||
from fix_path import fix_sys_path
|
||||
|
||||
fix_sys_path(__file__)
|
||||
|
||||
from gluon.globals import Response
|
||||
from gluon import URL
|
||||
|
||||
|
||||
class testResponse(unittest.TestCase):
|
||||
|
||||
def test_include_files(self):
|
||||
|
||||
def return_includes(response, extensions=None):
|
||||
response.include_files(extensions)
|
||||
return response.body.getvalue()
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.js'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="/a/static/css/file.js" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.coffee'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="/a/static/css/file.coffee" type="text/coffee"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="/a/static/css/file.ts" type="text/typescript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.less'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<link href="/a/static/css/file.less" rel="stylesheet/less" type="text/css" />')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('css:inline', 'background-color; white;'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<style type="text/css">\nbackground-color; white;\n</style>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('js:inline', 'alert("hello")'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script type="text/javascript">\nalert("hello")\n</script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js')
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content,
|
||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content,
|
||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
|
||||
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
|
||||
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
|
||||
)
|
||||
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.js'))
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
content = return_includes(response, extensions=['css'])
|
||||
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||
|
||||
#regr test for #628
|
||||
response = Response()
|
||||
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '')
|
||||
|
||||
#regr test for #628
|
||||
response = Response()
|
||||
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false'])
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('js1', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -120,6 +120,7 @@ class TestStorageList(unittest.TestCase):
|
||||
|
||||
|
||||
class TestList(unittest.TestCase):
|
||||
|
||||
""" Tests Storage.List (fast-check for request.args()) """
|
||||
|
||||
def test_listcall(self):
|
||||
@@ -134,6 +135,23 @@ class TestList(unittest.TestCase):
|
||||
self.assertEqual(a(3, cast=int), 1234)
|
||||
a.append('x')
|
||||
self.assertRaises(HTTP, a, 4, cast=int)
|
||||
b = List()
|
||||
# default is always returned when especified
|
||||
self.assertEqual(b(0, cast=int, default=None), None)
|
||||
self.assertEqual(b(0, cast=int, default=None, otherwise='teste'), None)
|
||||
self.assertEqual(b(0, cast=int, default='a', otherwise='teste'), 'a')
|
||||
# if don't have value and otherwise is especified it will called
|
||||
self.assertEqual(b(0, otherwise=lambda: 'something'), 'something')
|
||||
self.assertEqual(b(0, cast=int, otherwise=lambda: 'something'),
|
||||
'something')
|
||||
# except if default is especified
|
||||
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
|
||||
|
||||
def test_listgetitem(self):
|
||||
'''Mantains list behaviour.'''
|
||||
a = List((1, 2, 3))
|
||||
self.assertEqual(a[0], 1)
|
||||
self.assertEqual(a[::-1], [3, 2, 1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
167
gluon/tools.py
167
gluon/tools.py
@@ -60,7 +60,7 @@ except ImportError:
|
||||
# fallback to pure-Python module
|
||||
import gluon.contrib.simplejson as json_parser
|
||||
|
||||
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'Wiki',
|
||||
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
|
||||
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
|
||||
|
||||
### mind there are two loggers here (logger and crud.settings.logger)!
|
||||
@@ -767,8 +767,8 @@ class Mail(object):
|
||||
if self.settings.server == 'logging':
|
||||
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' %
|
||||
('-' * 40, sender,
|
||||
', '.join(to), subject,
|
||||
text or html, '-' * 40))
|
||||
', '.join(to), subject,
|
||||
text or html, '-' * 40))
|
||||
elif self.settings.server == 'gae':
|
||||
xcc = dict()
|
||||
if cc:
|
||||
@@ -779,23 +779,23 @@ class Mail(object):
|
||||
xcc['reply_to'] = reply_to
|
||||
from google.appengine.api import mail
|
||||
attachments = attachments and [mail.Attachment(
|
||||
a.my_filename,
|
||||
a.my_filename,
|
||||
a.my_payload,
|
||||
contebt_id='<attachment-%s>' % k
|
||||
) for k,a in enumerate(attachments) if not raw]
|
||||
if attachments:
|
||||
result = mail.send_mail(
|
||||
sender=sender, to=origTo,
|
||||
subject=subject, body=text, html=html,
|
||||
subject=unicode(subject), body=unicode(text), html=html,
|
||||
attachments=attachments, **xcc)
|
||||
elif html and (not raw):
|
||||
result = mail.send_mail(
|
||||
sender=sender, to=origTo,
|
||||
subject=subject, body=text, html=html, **xcc)
|
||||
subject=unicode(subject), body=unicode(text), html=html, **xcc)
|
||||
else:
|
||||
result = mail.send_mail(
|
||||
sender=sender, to=origTo,
|
||||
subject=subject, body=text, **xcc)
|
||||
subject=unicode(subject), body=unicode(text), **xcc)
|
||||
else:
|
||||
smtp_args = self.settings.server.split(':')
|
||||
kwargs = dict(timeout=self.settings.timeout)
|
||||
@@ -965,7 +965,142 @@ class Recaptcha(DIV):
|
||||
return XML(captcha).xml()
|
||||
|
||||
|
||||
# this should only be used for catcha and perhaps not even for that
|
||||
class Recaptcha2(DIV):
|
||||
"""
|
||||
Experimental:
|
||||
Creates a DIV holding the newer Recaptcha from Google (v2)
|
||||
|
||||
Args:
|
||||
request : the request. If not passed, uses current request
|
||||
public_key : the public key Google gave you
|
||||
private_key : the private key Google gave you
|
||||
error_message : the error message to show if verification fails
|
||||
label : the label to use
|
||||
options (dict) : takes these parameters
|
||||
|
||||
- hl
|
||||
- theme
|
||||
- type
|
||||
- tabindex
|
||||
- callback
|
||||
- expired-callback
|
||||
|
||||
see https://developers.google.com/recaptcha/docs/display for docs about those
|
||||
|
||||
comment : the comment
|
||||
|
||||
Examples:
|
||||
Use as::
|
||||
|
||||
form = FORM(Recaptcha2(public_key='...',private_key='...'))
|
||||
|
||||
or::
|
||||
|
||||
form = SQLFORM(...)
|
||||
form.append(Recaptcha2(public_key='...',private_key='...'))
|
||||
|
||||
to protect the login page instead, use::
|
||||
|
||||
from gluon.tools import Recaptcha2
|
||||
auth.settings.captcha = Recaptcha2(request, public_key='...',private_key='...')
|
||||
|
||||
"""
|
||||
|
||||
API_URI = 'https://www.google.com/recaptcha/api.js'
|
||||
VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify'
|
||||
|
||||
def __init__(self,
|
||||
request=None,
|
||||
public_key='',
|
||||
private_key='',
|
||||
error_message='invalid',
|
||||
label='Verify:',
|
||||
options=None,
|
||||
comment='',
|
||||
):
|
||||
request = request or current.request
|
||||
self.request_vars = request and request.vars or current.request.vars
|
||||
self.remote_addr = request.env.remote_addr
|
||||
self.public_key = public_key
|
||||
self.private_key = private_key
|
||||
self.errors = Storage()
|
||||
self.error_message = error_message
|
||||
self.components = []
|
||||
self.attributes = {}
|
||||
self.label = label
|
||||
self.options = options or {}
|
||||
self.comment = comment
|
||||
|
||||
def _validate(self):
|
||||
recaptcha_response_field = self.request_vars.pop('g-recaptcha-response', None)
|
||||
remoteip = self.remote_addr
|
||||
if not recaptcha_response_field:
|
||||
self.errors['captcha'] = self.error_message
|
||||
return False
|
||||
params = urllib.urlencode({
|
||||
'secret': self.private_key,
|
||||
'remoteip': remoteip,
|
||||
'response': recaptcha_response_field,
|
||||
})
|
||||
request = urllib2.Request(
|
||||
url=self.VERIFY_SERVER,
|
||||
data=params,
|
||||
headers={'Content-type': 'application/x-www-form-urlencoded',
|
||||
'User-agent': 'reCAPTCHA Python'})
|
||||
httpresp = urllib2.urlopen(request)
|
||||
content = httpresp.read()
|
||||
httpresp.close()
|
||||
try:
|
||||
response_dict = json_parser.loads(content)
|
||||
except:
|
||||
self.errors['captcha'] = self.error_message
|
||||
return False
|
||||
if response_dict.get('success', False):
|
||||
self.request_vars.captcha = ''
|
||||
return True
|
||||
else:
|
||||
self.errors['captcha'] = self.error_message
|
||||
return False
|
||||
|
||||
def xml(self):
|
||||
api_uri = self.API_URI
|
||||
hl = self.options.pop('hl', None)
|
||||
if hl:
|
||||
api_uri = self.API_URI + '?hl=%s' % hl
|
||||
public_key = self.public_key
|
||||
self.options['sitekey'] = public_key
|
||||
captcha = DIV(
|
||||
SCRIPT(_src=api_uri, _async='', _defer=''),
|
||||
DIV(_class="g-recaptcha", data=self.options),
|
||||
TAG.noscript(XML("""
|
||||
<div style="width: 302px; height: 352px;">
|
||||
<div style="width: 302px; height: 352px; position: relative;">
|
||||
<div style="width: 302px; height: 352px; position: absolute;">
|
||||
<iframe src="https://www.google.com/recaptcha/api/fallback?k=%(public_key)s"
|
||||
frameborder="0" scrolling="no"
|
||||
style="width: 302px; height:352px; border-style: none;">
|
||||
</iframe>
|
||||
</div>
|
||||
<div style="width: 250px; height: 80px; position: absolute; border-style: none;
|
||||
bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
|
||||
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
|
||||
class="g-recaptcha-response"
|
||||
style="width: 250px; height: 80px; border: 1px solid #c1c1c1;
|
||||
margin: 0px; padding: 0px; resize: none;" value="">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>""" % dict(public_key=public_key))
|
||||
)
|
||||
)
|
||||
if not self.errors.captcha:
|
||||
return XML(captcha).xml()
|
||||
else:
|
||||
captcha.append(DIV(self.errors['captcha'], _class='error'))
|
||||
return XML(captcha).xml()
|
||||
|
||||
|
||||
# this should only be used for captcha and perhaps not even for that
|
||||
def addrow(form, a, b, c, style, _id, position=-1):
|
||||
if style == "divs":
|
||||
form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'),
|
||||
@@ -987,6 +1122,15 @@ def addrow(form, a, b, c, style, _id, position=-1):
|
||||
DIV(b, SPAN(c, _class='inline-help'),
|
||||
_class='controls'),
|
||||
_class='control-group', _id=_id))
|
||||
elif style == "bootstrap3_inline":
|
||||
form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'),
|
||||
DIV(b, SPAN(c, _class='help-block'),
|
||||
_class='col-sm-9'),
|
||||
_class='form-group', _id=_id))
|
||||
elif style == "bootstrap3_stacked":
|
||||
form[0].insert(position, DIV(LABEL(a, _class='control-label'),
|
||||
b, SPAN(c, _class='help-block'),
|
||||
_class='form-group', _id=_id))
|
||||
else:
|
||||
form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'),
|
||||
TD(b, _class='w2p_fw'),
|
||||
@@ -1330,8 +1474,7 @@ class Auth(object):
|
||||
logged_url=URL(controller, function, args='profile'),
|
||||
download_url=URL(controller, 'download'),
|
||||
mailer=(mailer is True) and Mail() or mailer,
|
||||
on_failed_authorization =
|
||||
URL(controller, function, args='not_authorized'),
|
||||
on_failed_authorization = URL(controller, function, args='not_authorized'),
|
||||
login_next = url_index,
|
||||
login_onvalidation = [],
|
||||
login_onaccept = [],
|
||||
@@ -3571,7 +3714,7 @@ class Auth(object):
|
||||
return record.id
|
||||
else:
|
||||
id = membership.insert(group_id=group_id, user_id=user_id)
|
||||
if role:
|
||||
if role:
|
||||
self.user_groups[group_id] = role
|
||||
else:
|
||||
self.update_groups()
|
||||
@@ -5362,7 +5505,7 @@ class Expose(object):
|
||||
if current.request.raw_args:
|
||||
self.args = [arg for arg in current.request.raw_args.split('/') if arg]
|
||||
else:
|
||||
self.args = [arg for arg in current.request.args if args]
|
||||
self.args = [arg for arg in current.request.args if arg]
|
||||
filename = os.path.join(base, *self.args)
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404, "FILE NOT FOUND")
|
||||
|
||||
@@ -84,7 +84,7 @@ server {
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
|
||||
ssl_protocols SSLv3 TLSv1;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
keepalive_timeout 70;
|
||||
location / {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
|
||||
169
scripts/setup-web2py-ws2012r2.ps1
Normal file
169
scripts/setup-web2py-ws2012r2.ps1
Normal file
@@ -0,0 +1,169 @@
|
||||
"This script will work fine for a few cases 'by default':"
|
||||
" - completely CLEAN WS2012R2 host"
|
||||
" - python 2.7 installed in the default path"
|
||||
" - wfasctgi installed on the default path"
|
||||
"It'll install web2py under the default website "
|
||||
" You can use it as a boilerplate to automate your deployments"
|
||||
" but it still is released AS IT IS. "
|
||||
"BIG FAT WARNING: It will install a bunch of dependecies
|
||||
Inspect the source before executing it"
|
||||
""
|
||||
""
|
||||
$ErrorActionPreference = 'stop'
|
||||
|
||||
$REALLY_SURE = Read-Host "Do you want to start with web2py deployment? [y/N]"
|
||||
if (!@('y', 'Y') -contains $REALLY_SURE) {
|
||||
"Ok, Exiting without doing anything"
|
||||
exit 1
|
||||
}
|
||||
#setting root folder
|
||||
$rootfolder = $pwd
|
||||
|
||||
### utilities - start
|
||||
function ask_a_question($question) {
|
||||
$response = Read-Host "$question [Y/n]"
|
||||
if (@('Y', 'y', '', $null) -contains $response) {
|
||||
return $true
|
||||
} else {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function unzip_me {
|
||||
#Load the assembly
|
||||
[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
|
||||
#Unzip the file
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($pathToZip, $targetDir)
|
||||
}
|
||||
|
||||
|
||||
### utilities - end
|
||||
|
||||
#install 4.5 that is needed for a bunch of things anyway
|
||||
Install-WindowsFeature Net-Framework-45-Core
|
||||
|
||||
#fetch web2py
|
||||
$web2py_url = 'http://www.web2py.com/examples/static/web2py_src.zip'
|
||||
$web2py_file = "$pwd\web2py_src.zip"
|
||||
if (!(Test-Path $web2py_file)) {
|
||||
(new-object net.webclient).DownloadFile($web2py_url, $web2py_file)
|
||||
}
|
||||
#Load the assembly
|
||||
[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
|
||||
#Unzip the file
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($web2py_file, $pwd)
|
||||
|
||||
#features installation (IIS, needed modules, python, chocolatey, etc)
|
||||
$installfeatures = ask_a_question('Do you want to install needed features?')
|
||||
|
||||
if ($installfeatures) {
|
||||
Install-WindowsFeature Web-Server,Web-Default-Doc,Web-Static-Content,Web-Http-Redirect,Web-Http-Logging,Web-Request-Monitor,`
|
||||
Web-Http-Tracing,Web-Stat-Compression,Web-Dyn-Compression,Web-Filtering,Web-Basic-Auth,Web-Windows-Auth,Web-AppInit,`
|
||||
Web-CGI,Web-WebSockets,Web-Mgmt-Console,Web-Net-Ext45
|
||||
}
|
||||
|
||||
$copy_web2py = ask_a_question("Copy web2py to the default website root?")
|
||||
if ($copy_web2py) {
|
||||
Import-Module WebAdministration
|
||||
$available_websites = Get-Website
|
||||
if ($available_websites[0] -eq $null) {
|
||||
$default_one = $available_websites
|
||||
} else {
|
||||
$default_one = $available_websites[0]
|
||||
}
|
||||
$iis_root = [System.Environment]::ExpandEnvironmentVariables($default_one.PhysicalPath)
|
||||
Copy-Item "$rootfolder\web2py\*" $iis_root -Recurse
|
||||
$rootfolder = $iis_root
|
||||
$acl = (Get-Item $rootfolder).GetAccessControl('Access')
|
||||
$identity = "BUILTIN\IIS_IUSRS"
|
||||
$fileSystemRights = "Modify"
|
||||
$inheritanceFlags = "ContainerInherit, ObjectInherit"
|
||||
$propagationFlags = "None"
|
||||
$accessControlType = "Allow"
|
||||
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $inheritanceFlags, $propagationFlags, $accessControlType)
|
||||
$acl.SetAccessRule($rule)
|
||||
Set-Acl $rootfolder $acl
|
||||
}
|
||||
|
||||
$create_cert = ask_a_question("Do you want to create a self-signed SSL cert?")
|
||||
if ($create_cert) {
|
||||
$cert = New-SelfSignedCertificate -DnsName ("localtest.me","*.localtest.me") -CertStoreLocation cert:\LocalMachine\My
|
||||
$rootStore = Get-Item cert:\LocalMachine\Root
|
||||
$rootStore.Open("ReadWrite")
|
||||
$rootStore.Add($cert)
|
||||
$rootStore.Close();
|
||||
Import-Module WebAdministration
|
||||
Set-Location IIS:\SslBindings
|
||||
New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https
|
||||
$cert | New-Item 0.0.0.0!443
|
||||
Set-Location $pwd
|
||||
}
|
||||
|
||||
"checking for chocolatey"
|
||||
if (Get-Command "choco.exe" -ErrorAction SilentlyContinue)
|
||||
{
|
||||
"chocolatey found"
|
||||
} else {
|
||||
"installing chocolatey"
|
||||
(new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1') | iex
|
||||
}
|
||||
"installing url-rewrite"
|
||||
choco install UrlRewrite
|
||||
$pythonexe = Read-Host 'Python.exe path [C:\Python27\python.exe]'
|
||||
if (($pythonexe -eq '') -or ($pythonexe -eq $null)) {
|
||||
$pythonexe = 'C:\Python27\python.exe'
|
||||
}
|
||||
if (!(Test-Path $pythonexe)) {
|
||||
"ERROR: python executable not found"
|
||||
$pythonwanted = ask_a_question("do you want to install it automatically?")
|
||||
|
||||
if ($pythonwanted) {
|
||||
choco install webpicmd
|
||||
WebpiCmd.exe /Install /Products:WFastCgi_21_279
|
||||
$pythonexe = 'C:\Python27\python.exe'
|
||||
}
|
||||
else {
|
||||
exit 1
|
||||
}
|
||||
|
||||
}
|
||||
$wfastcgipath = Read-Host 'wfastcgi.py path [C:\Python27\Scripts\wfastcgi.py]'
|
||||
if (($wfastcgipath -eq '') -or ($wfastcgipath -eq $null)) {
|
||||
$wfastcgipath = 'C:\Python27\Scripts\wfastcgi.py'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $wfastcgipath)) {
|
||||
"ERROR: wfastcgi.py not found"
|
||||
|
||||
$wfastcgiwanted = ask_a_question("do you want to install it automatically?")
|
||||
if ($wfastcgiwanted) {
|
||||
choco install webpicmd
|
||||
WebpiCmd.exe /Install /Products:WFastCgi_21_279
|
||||
} else {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
$pythondir = Split-Path c:\python27\python.exe
|
||||
#installing dependencies
|
||||
$env:Path = $env:Path + ";$pythondir;$pythondir\Scripts"
|
||||
|
||||
pip install pypiwin32
|
||||
|
||||
$PW = Read-Host 'Web2py Admin Password'
|
||||
|
||||
$appcmdpath = "$env:windir\system32\inetsrv\appcmd.exe"
|
||||
|
||||
& $appcmdpath set config /section:system.webServer/fastCGI "/+[fullPath='$pythonexe', arguments='$wfastcgipath']"
|
||||
& $appcmdpath unlock config -section:system.webServer/handlers
|
||||
|
||||
& cd $rootfolder
|
||||
& $pythonexe -c "from gluon.main import save_password; save_password('$PW',443)"
|
||||
|
||||
$webconfig_template = Join-Path $rootfolder "examples\web.config"
|
||||
$destination = Join-Path $rootfolder "web.config"
|
||||
$scriptprocessor = 'scriptProcessor="{0}|{1}"' -f $pythonexe, $wfastcgipath
|
||||
|
||||
(Get-Content $webconfig_template) | Foreach-Object {$_ -replace 'scriptProcessor="SCRIPT_PROCESSOR"', $scriptprocessor} | where {$_ -ne ""} | Set-Content $destination
|
||||
""
|
||||
"Installation finished. Web2py is available either on http://localhost/ or at https://localtest.me/"
|
||||
""
|
||||
78
scripts/tickets2slack.py
Executable file
78
scripts/tickets2slack.py
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Post error tickets to slack on a 5 minute schedule.
|
||||
#
|
||||
# Proper use depends on having created a web-hook through Slack, and having set
|
||||
# that value in your app's model as the value of global_settings.slack_hook.
|
||||
# Details on creating web-hooks can be found at https://slack.com/integrations
|
||||
#
|
||||
# requires the Requests module for posting to slack, other requirements are
|
||||
# standard or provided by web2py
|
||||
#
|
||||
# Usage (on Unices), replace myapp with the name of your application and run:
|
||||
# nohup python web2py.py -S myapp -M -R scripts/tickets2slack.py &
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import pickle
|
||||
import json
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError as e:
|
||||
print "missing module 'Requests', aborting."
|
||||
sys.exit(1)
|
||||
|
||||
from gluon import URL
|
||||
from gluon.utils import md5_hash
|
||||
from gluon.restricted import RestrictedError
|
||||
from gluon.settings import global_settings
|
||||
|
||||
|
||||
path = os.path.join(request.folder, 'errors')
|
||||
sent_errors_file = os.path.join(path, 'slack_errors.pickle')
|
||||
hashes = {}
|
||||
if os.path.exists(sent_errors_file):
|
||||
try:
|
||||
with open(sent_errors_file, 'rb') as f:
|
||||
hashes = pickle.load(f)
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
# ## CONFIGURE HERE
|
||||
SLEEP_MINUTES = 5
|
||||
ALLOW_DUPLICATES = False
|
||||
global_settings.slack_hook = global_settings.slack_hook or \
|
||||
'https://hooks.slack.com/services/your_service'
|
||||
# ## END CONFIGURATION
|
||||
|
||||
while 1:
|
||||
for file_name in os.listdir(path):
|
||||
if file_name == 'slack_errors.pickle':
|
||||
continue
|
||||
|
||||
if not ALLOW_DUPLICATES:
|
||||
key = md5_hash(file_name)
|
||||
if key in hashes:
|
||||
continue
|
||||
hashes[key] = 1
|
||||
|
||||
error = RestrictedError()
|
||||
|
||||
try:
|
||||
error.load(request, request.application, file_name)
|
||||
except Exception as _:
|
||||
continue # not an exception file?
|
||||
|
||||
url = URL(a='admin', f='ticket', args=[request.application, file],
|
||||
scheme=True)
|
||||
payload = json.dumps(dict(text="Error in %(app)s.\n%(url)s" %
|
||||
dict(app=request.application, url=url)))
|
||||
|
||||
requests.post(global_settings.slack_hook, data=dict(payload=payload))
|
||||
|
||||
with open(sent_errors_file, 'wb') as f:
|
||||
pickle.dump(hashes, f)
|
||||
time.sleep(SLEEP_MINUTES * 60)
|
||||
@@ -62,7 +62,7 @@ do_start()
|
||||
start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
|
||||
&& return 1
|
||||
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE \
|
||||
start-stop-daemon --start --quiet -m --pidfile $PIDFILE \
|
||||
${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \
|
||||
--background --exec $DAEMON -- $DAEMON_ARGS \
|
||||
|| return 2
|
||||
|
||||
Reference in New Issue
Block a user