Compare commits
68 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 | ||
|
|
77f154a56b |
@@ -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.4-beta+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.11.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
|
||||
@@ -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.4-stable+timestamp.2015.04.26.09.05.21
|
||||
Version 2.11.2-stable+timestamp.2015.05.30.11.29.46
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -12,7 +12,7 @@ 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: 62eb7767db...06654a1676
@@ -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__':
|
||||
|
||||
165
gluon/tools.py
165
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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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