Compare commits

..

68 Commits

Author SHA1 Message Date
mdipierro
236fdcfafc R-2.11.2 2015-05-30 11:30:16 -05:00
mdipierro
ce0f83d00c Merge pull request #988 from gi0baro/master
Track pyDAL 15.05.29
2015-05-30 11:29:09 -05:00
gi0baro
156d771ab3 Track pyDAL 15.05.29 2015-05-29 13:45:52 -05:00
mdipierro
01474c99b0 R-2.11.1 2015-05-28 23:22:16 -05:00
mdipierro
66d15491ca Merge pull request #984 from ilvalle/minorfix
lazy request.uuid
2015-05-28 23:17:21 -05:00
mdipierro
376a27da73 Merge pull request #983 from Euphorbium/patch-1
fix cPickle not defined error
2015-05-28 23:16:33 -05:00
mdipierro
0f95c13dc7 Merge pull request #981 from gi0baro/master
Track pyDAL 15.05.26
2015-05-28 23:16:02 -05:00
mdipierro
926de90ee4 fixed bug in orderby when it is a list 2015-05-28 13:59:03 -05:00
ilvalle
538f375284 lazy request.uuid 2015-05-28 18:30:06 +02:00
Juozas Masiulis
4c61c0962d fix cPickle not defined error 2015-05-28 14:29:43 +03:00
gi0baro
9b71646fc5 Track pyDAL 15.05.26 2015-05-26 14:36:04 +02:00
mdipierro
1e66fa3a93 new version number 2015-05-24 19:14:49 -05:00
mdipierro
57a8dfe034 Merge pull request #977 from austinprog/patch-1
Changed "Import about this GIT repo" Line 16, typo
2015-05-24 18:58:22 -05:00
mdipierro
77e7631740 Merge pull request #976 from niphlod/fix/ubuntu_nginx_hardening
thanks  @wmunguiam for spotting
2015-05-24 18:57:51 -05:00
mdipierro
ba978d55cf Merge pull request #975 from gi0baro/master
Track pyDAL 15.05
2015-05-24 18:57:36 -05:00
mdipierro
12e8ee5c25 Merge pull request #974 from niphlod/fix/redis_cache_multiapp
redis multi-app. Thanks Lisandro for spotting it
2015-05-24 18:57:26 -05:00
Austin
d293e98b43 Changed "Import about this GIT repo" Line 16, typo
My proposal is to change it to "Important reminder about this GIT Repo", as I think the "Import" part in the current one is a typo.
2015-05-24 18:13:03 -05:00
niphlod
4f316d0294 thanks @wmunguiam for spotting 2015-05-24 21:25:27 +02:00
gi0baro
81e15879d4 Track pyDAL 15.05 2015-05-23 16:37:52 +02:00
niphlod
cd1d6c5af1 redis multi-app. Thanks Lisandro for spotting it
redis_cache didn't play well with multiple apps for a silly mistake.
Glad that Lisadro pointed out
2015-05-21 22:26:04 +02:00
mdipierro
c7d3758c77 Merge pull request #973 from omniavx/patch-1
Update dal.py
2015-05-20 08:27:41 -05:00
mdipierro
040e52278e Merge pull request #956 from cassiobotaro/master
More changes in List (request.args)
2015-05-20 08:27:16 -05:00
mdipierro
3daf953c66 syncing maintenance again 2015-05-20 08:26:03 -05:00
mdipierro
de3d722ac9 fixed import, thanks Auden RovelleQuartz 2015-05-20 08:24:33 -05:00
omniavx
ff10eab373 Update dal.py
I was running my application and got this error

{
<type 'exceptions.ImportError'> cannot import name Set

Version
web2py™	Version 2.10.4-stable+timestamp.2015.04.26.15.11.54
Python	Python 2.7.3: /usr/bin/python (prefix: /usr)
Traceback
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
Traceback (most recent call last):
  File "/home/www-data/web2py/gluon/restricted.py", line 227, in restricted
    exec ccode in environment
  File "/home/www-data/web2py/applications/omniavx_cxn/controllers/valuecache.py", line 6897, in <module>
  File "/home/www-data/web2py/gluon/globals.py", line 393, in <lambda>
    self._caller = lambda f: f()
  File "/home/www-data/web2py/applications/omniavx_cxn/controllers/valuecache.py", line 6584, in browse_bacct_callback
    from plugin_PowerGrid.CallBack import CallBack
  File "/home/www-data/web2py/gluon/custom_import.py", line 95, in custom_importer
    return base_importer(pname, globals, locals, fromlist, level)
  File "/home/www-data/web2py/gluon/custom_import.py", line 134, in __call__
    result = NATIVE_IMPORTER(name, globals, locals, fromlist, level)
  File "applications/omniavx_cxn/modules/plugin_PowerGrid/CallBack.py", line 41, in <module>
    from gluon.dal import Table ,Query, Set, Rows, Row
ImportError: cannot import name Set
}

same code produced no error in earlier version of web2py



line 15 of web2py/gluon/dal.py is 
{
from pydal.objects import Row, Rows, Table, Query, Expression
}

replacing that with 
{
from pydal.objects import Row, Rows, Table, Query, Set, Expression

}

solves the problem
2015-05-20 00:11:46 -05:00
mdipierro
eb4d159b37 fixed process_batch_upload 2015-05-18 23:28:17 -05:00
Cássio Botaro
5ef7a8e9a1 maintains web2py pattern 2015-05-14 12:24:58 -03:00
mdipierro
76cfba7047 Merge pull request #964 from matclab/mail-send-lazy-to-unicode
#963 : Convert subject and body to unicode before sending mail
2015-05-14 09:17:04 -05:00
mdipierro
f77f307869 Merge pull request #970 from ailnlv/patch-1
Fixing https://github.com/web2py/web2py/issues/969
2015-05-14 09:16:52 -05:00
mdipierro
5ef8648929 Merge pull request #967 from bletourmy/fix/966
fixes #966 - deadlock in cache.disk
2015-05-14 09:16:31 -05:00
mdipierro
ed042685ea Merge pull request #965 from niphlod/fix/962
fixes #962
2015-05-14 09:16:10 -05:00
mdipierro
d09ce57f12 Merge pull request #961 from niphlod/fix/628
fixes #628
2015-05-14 09:14:06 -05:00
ailnlv
169818b275 Fixing https://github.com/web2py/web2py/issues/969
Running readline.parse_and_bind("bind ^I rl_complete") makes the letter "b" stop working on the console. This patch solves the issue and correctly enables tab completition on OSX.
2015-05-13 19:23:56 -03:00
Bernard Letourmy
4b14a87463 fixes #966 - deadlock in cache.disk 2015-05-13 09:35:22 +08:00
niphlod
cadf38b4f6 fixes #962 2015-05-11 21:24:20 +02:00
Mathieu Clabaut
a6226d6391 Convert subject and body to unicode before sending mail
Not doing this was raising an exception :

Traceback (most recent call last):
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/gluon/restricted.py, line 227, in restricted
    exec ccode in environment
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/applications/foundit/controllers/default.py, line 127, in <module>
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/gluon/globals.py, line 393, in <lambda>
    self._caller = lambda f: f()
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/applications/foundit/controllers/default.py, line 34, in user
    return dict(form=auth())
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/gluon/tools.py, line 1595, in __call__
    return getattr(self, args[0])()
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/gluon/tools.py, line 3272, in request_reset_password
    if self.email_reset_password(user):
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/gluon/tools.py, line 3296, in email_reset_password
    message=self.messages.reset_password % d):
  File /base/data/home/apps/e~sacred-bonus-88417/1.384178859090314065/gluon/tools.py, line 798, in send
    subject=subject, body=text, **xcc)
  File /base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/mail.py, line 402, in send_mail
    message.send(make_sync_call=make_sync_call)
  File /base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/mail.py, line 1108, in send
    message = self.ToProto()
  File /base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/mail.py, line 1350, in ToProto
    message = super(EmailMessage, self).ToProto()
  File /base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/mail.py, line 1046, in ToProto
    message.set_subject(_to_str(self.subject))
  File cpp_message.pyx, line 124, in cpp_message.SetScalarAccessors.Setter (third_party/apphosting/python/protobuf/proto1/cpp_message.cc:2229)
TypeError: <class 'gluon.languages.lazyT'> has type <class 'gluon.languages.lazyT'>, but expected one of: str, unicode
2015-05-09 18:24:09 +02:00
niphlod
5c167907eb fixes #628
response.include_files is now cleaner and easier to maintain

You can specify a tuple of (type, url) to include external assets
without extension (such as the usecase described in #628)

Added tests for include_files, that was never included in CI tests
2015-05-08 21:51:56 +02:00
mdipierro
587ff56a94 linked 15.03-maintenance again 2015-05-07 22:26:42 -05:00
mdipierro
6f91fdd833 minor refactoring in grid(orderby) 2015-05-06 08:56:56 -05:00
mdipierro
6e2f9ad043 fixed examples support 2015-05-04 11:23:44 -05:00
Cássio Botaro
cdca2793e0 Maintain py2.6k compability 2015-05-04 12:55:44 -03:00
Cássio Botaro
a0ee649884 Update tests
- new tests to verify old behaviour 
- test otherwise without cast and defaut
- verify if behave like a list 
- more test with call function
2015-05-04 12:49:14 -03:00
Cássio Botaro
380b491724 Return old behaviours
- Better documented List
- Otherwise not binded with cast
2015-05-04 12:41:06 -03:00
mdipierro
f45bf73992 Merge branch 'master' of github.com:web2py/web2py 2015-05-03 16:42:42 -05:00
mdipierro
94461724f6 Merge pull request #954 from cassiobotaro/master
Mantain backward compatibility
2015-05-03 16:42:12 -05:00
Cássio Botaro
c36c391786 More test to prove backward compatibility 2015-05-03 14:03:26 -03:00
Cássio Botaro
f6db7c995f Its necessary because of default=None trick 2015-05-03 14:02:11 -03:00
Cássio Botaro
ccc4b96709 Added one more test
Added one more test to avoid mistake with backward compatibility
2015-05-03 13:36:24 -03:00
Cássio Botaro
71b02e3044 Maintain backward compatibility
Little change to maintain backward compatibility, related to #590
2015-05-03 13:35:05 -03:00
mdipierro
99fb1c3010 Merge branch 'master' of github.com:web2py/web2py 2015-05-03 10:31:37 -05:00
mdipierro
20067d7b93 Merge pull request #953 from niphlod/fix/691
fixes issue #691
2015-05-03 10:31:32 -05:00
mdipierro
44eb35c617 Merge pull request #952 from niphlod/fix/734
fixes #734
2015-05-03 10:31:21 -05:00
mdipierro
df03317054 Merge pull request #951 from niphlod/tests/cache_ram_and_disk
more tests, general cleanup
2015-05-03 10:31:08 -05:00
mdipierro
9d873cbd1c reverted last commit 2015-05-03 10:30:43 -05:00
mdipierro
1bb4117cbd Merge pull request #950 from cassiobotaro/master
Fix List behaviour and added new feature
2015-05-03 10:27:46 -05:00
mdipierro
e834186a86 Merge pull request #950 from cassiobotaro/master
Fix List behaviour and added new feature
2015-05-03 10:23:08 -05:00
mdipierro
1394942feb removed reference to python 2.5 2015-05-03 10:09:07 -05:00
niphlod
32b9b5c799 fixes issue #691 2015-05-03 16:06:10 +02:00
niphlod
340d7b5e6f fixes #734 2015-05-03 15:51:13 +02:00
niphlod
302f56ecc1 more tests, general cleanup 2015-05-03 15:33:19 +02:00
mdipierro
9b12459a82 Merge branch 'master' of github.com:web2py/web2py 2015-05-02 23:16:12 -05:00
mdipierro
8e3925820c allow disabling of confirmation in js delete 2015-05-02 23:16:05 -05:00
mdipierro
279d71d4cd Merge pull request #936 from niphlod/enhancement/919
added newer Recaptcha2 class to deal with v2.0. Fixes #919
2015-05-02 23:07:36 -05:00
Cássio Botaro
258e2e57ae Fix import errors 2015-05-02 20:36:35 -03:00
cassiobotaro
9357d810d8 Fix List behaviour and added new feature 2015-05-02 20:24:04 -03:00
mdipierro
54b385b321 grid(user_cursor=False) by default because it is broken 2015-04-26 17:16:19 -05:00
mdipierro
df039e734c 2.10.4 stable 2015-04-26 10:10:18 -05:00
niphlod
77f154a56b added newer Recaptcha2 class to deal with v2.0. Fixes #919
Improvements over the "old" v1.0
- behaves well also without javascript
- use_ssl is redundant, v2.0 works only in https mode
- ajax is not useful anymore as the newer API is a lot easier

Adjusted also the addrow() method that was missing newer formstyles.
2015-04-22 00:10:05 +02:00
30 changed files with 528 additions and 160 deletions

View File

@@ -1,3 +1,7 @@
## 2.11.1
- Many small but significative improvements and bug fixes
## 2.10.1-2.10.2
- welcome app defaults to Bootstrap 3

View File

@@ -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

View File

@@ -13,7 +13,7 @@ Learn more at http://web2py.com
Then edit ./app.yaml and replace "yourappname" with yourappname.
## Import about this GIT repo
## Important reminder about this GIT repo
An important part of web2py is the Database Abstraction Layer (DAL). In early 2015 this was decoupled into a separate code-base (PyDAL). In terms of git, it is a sub-module of the main repository.

View File

@@ -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

View File

@@ -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(

View File

@@ -490,12 +490,12 @@
* and prevent clicking on it */
disableElement: function(el) {
el.addClass('disabled');
var method = el.is('button') ? 'html' : 'val';
var method = el.is('input') ? 'val' : 'html';
//method = el.attr('name') ? 'html' : 'val';
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
/*store enabled state if not already disabled */
if(el.data('w2p:enable-with') === undefined) {
el.data('w2p:enable-with', el[method]());
if(el.data('w2p_enable_with') === undefined) {
el.data('w2p_enable_with', el[method]());
}
/*if you don't want to see "working..." on buttons, replace the following
* two lines with this one
@@ -515,11 +515,11 @@
/* restore element to its original state which was disabled by 'disableElement' above*/
enableElement: function(el) {
var method = el.is('button') ? 'val' : 'html';
if(el.data('w2p:enable-with') !== undefined) {
var method = el.is('input') ? 'val' : 'html';
if(el.data('w2p_enable_with') !== undefined) {
/* set to old enabled state */
el[method](el.data('w2p:enable-with'));
el.removeData('w2p:enable-with');
el[method](el.data('w2p_enable_with'));
el.removeData('w2p_enable_with');
}
el.removeClass('disabled');
el.unbind('click.w2pDisable');
@@ -586,12 +586,14 @@
if(pre_call != undefined) {
eval(pre_call);
}
if(confirm_message != undefined) {
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
if(confirm_message) {
if(confirm_message == 'default')
confirm_message = w2p_ajax_confirm_message ||
'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
}
if(target == undefined) {
if(method == 'GET') {
@@ -634,7 +636,7 @@
});
},
/* Disables form elements:
- Caches element value in 'w2p:enable-with' data store
- Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Sets disabled property to true
*/
@@ -646,8 +648,8 @@
if(disable_with == undefined) {
element.data('w2p_disable_with', element[method]())
}
if(element.data('w2p:enable-with') === undefined) {
element.data('w2p:enable-with', element[method]());
if(element.data('w2p_enable_with') === undefined) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true);
@@ -655,16 +657,16 @@
},
/* Re-enables disabled form elements:
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
- Sets disabled property to false
*/
enableFormElements: function(form) {
form.find(web2py.enableSelector).each(function() {
var element = $(this),
method = element.is('button') ? 'html' : 'val';
if(element.data('w2p:enable-with')) {
element[method](element.data('w2p:enable-with'));
element.removeData('w2p:enable-with');
if(element.data('w2p_enable_with')) {
element[method](element.data('w2p_enable_with'));
element.removeData('w2p_enable_with');
}
element.prop('disabled', false);
});
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/

View File

@@ -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]]

View File

@@ -490,12 +490,12 @@
* and prevent clicking on it */
disableElement: function(el) {
el.addClass('disabled');
var method = el.is('button') ? 'html' : 'val';
var method = el.is('input') ? 'val' : 'html';
//method = el.attr('name') ? 'html' : 'val';
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
/*store enabled state if not already disabled */
if(el.data('w2p:enable-with') === undefined) {
el.data('w2p:enable-with', el[method]());
if(el.data('w2p_enable_with') === undefined) {
el.data('w2p_enable_with', el[method]());
}
/*if you don't want to see "working..." on buttons, replace the following
* two lines with this one
@@ -515,11 +515,11 @@
/* restore element to its original state which was disabled by 'disableElement' above*/
enableElement: function(el) {
var method = el.is('button') ? 'val' : 'html';
if(el.data('w2p:enable-with') !== undefined) {
var method = el.is('input') ? 'val' : 'html';
if(el.data('w2p_enable_with') !== undefined) {
/* set to old enabled state */
el[method](el.data('w2p:enable-with'));
el.removeData('w2p:enable-with');
el[method](el.data('w2p_enable_with'));
el.removeData('w2p_enable_with');
}
el.removeClass('disabled');
el.unbind('click.w2pDisable');
@@ -586,12 +586,14 @@
if(pre_call != undefined) {
eval(pre_call);
}
if(confirm_message != undefined) {
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
if(confirm_message) {
if(confirm_message == 'default')
confirm_message = w2p_ajax_confirm_message ||
'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
}
if(target == undefined) {
if(method == 'GET') {
@@ -634,7 +636,7 @@
});
},
/* Disables form elements:
- Caches element value in 'w2p:enable-with' data store
- Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Sets disabled property to true
*/
@@ -646,8 +648,8 @@
if(disable_with == undefined) {
element.data('w2p_disable_with', element[method]())
}
if(element.data('w2p:enable-with') === undefined) {
element.data('w2p:enable-with', element[method]());
if(element.data('w2p_enable_with') === undefined) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true);
@@ -655,16 +657,16 @@
},
/* Re-enables disabled form elements:
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
- Sets disabled property to false
*/
enableFormElements: function(form) {
form.find(web2py.enableSelector).each(function() {
var element = $(this),
method = element.is('button') ? 'html' : 'val';
if(element.data('w2p:enable-with')) {
element[method](element.data('w2p:enable-with'));
element.removeData('w2p:enable-with');
if(element.data('w2p_enable_with')) {
element[method](element.data('w2p_enable_with'));
element.removeData('w2p_enable_with');
}
element.prop('disabled', false);
});
@@ -730,4 +732,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/

View File

@@ -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>

View File

@@ -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>

View File

@@ -7,11 +7,11 @@
# Language from default.py or 'en' (if the file is not found) is used as
# a default_language
#
# See <web2py-root-dir>/router.example.py for parameter's detail
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
#-------------------------------------------------------------------------------------
# To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/router.example.py to routes.py
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py
# (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interfase)

View File

@@ -586,12 +586,14 @@
if(pre_call != undefined) {
eval(pre_call);
}
if(confirm_message != undefined) {
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
if(confirm_message) {
if(confirm_message == 'default')
confirm_message = w2p_ajax_confirm_message ||
'Are you sure you want to delete this object?';
if(!web2py.confirm(confirm_message)) {
web2py.stopEverything(e);
return;
}
}
if(target == undefined) {
if(method == 'GET') {

View File

@@ -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)

View File

@@ -67,11 +67,12 @@ def RedisCache(*args, **vars):
locker.acquire()
try:
if not hasattr(RedisCache, 'redis_instance'):
RedisCache.redis_instance = RedisClient(*args, **vars)
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisCache, instance_name):
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
return getattr(RedisCache, instance_name)
finally:
locker.release()
return RedisCache.redis_instance
class RedisClient(object):

View File

@@ -126,7 +126,7 @@ class History:
def globals_dict(self):
"""Returns a dictionary view of the globals.
"""
return dict((name, cPickle.loads(val))
return dict((name, pickle.loads(val))
for name, val in zip(self.global_names, self.globals))
def add_unpicklable(self, statement, names):

View File

@@ -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

View File

@@ -26,6 +26,8 @@ import gluon.settings as settings
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
from gluon.settings import global_settings
from gluon import recfile
from gluon.cache import CacheInRam
from gluon.fileutils import copystream
import hashlib
import portalocker
try:
@@ -47,8 +49,7 @@ import cgi
import urlparse
import copy
import tempfile
from gluon.cache import CacheInRam
from gluon.fileutils import copystream
FMT = '%a, %d-%b-%Y %H:%M:%S PST'
PAST = 'Sat, 1-Jan-1971 00:00:00'
@@ -82,13 +83,22 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
css_inline = '<style type="text/css">\n%s\n</style>'
js_inline = '<script type="text/javascript">\n%s\n</script>'
template_mapping = {
'css': css_template,
'js': js_template,
'coffee': coffee_template,
'ts': typescript_template,
'less': less_template,
'css:inline': css_inline,
'js:inline': js_inline
}
# IMPORTANT:
# this is required so that pickled dict(s) and class.__dict__
# are sorted and web2py can detect without ambiguity when a session changes
class SortingPickler(Pickler):
def save_dict(self, obj):
self.write(EMPTY_DICT if self.bin else MARK+DICT)
self.write(EMPTY_DICT if self.bin else MARK + DICT)
self.memoize(obj)
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
@@ -193,6 +203,7 @@ class Request(Storage):
self.is_https = False
self.is_local = False
self.global_settings = settings.global_settings
self._uuid = None
def parse_get_vars(self):
"""Takes the QUERY_STRING and unpacks it to get_vars
@@ -275,7 +286,7 @@ class Request(Storage):
"""
self._vars = copy.copy(self.get_vars)
for key, value in self.post_vars.iteritems():
if not key in self._vars:
if key not in self._vars:
self._vars[key] = value
else:
if not isinstance(self._vars[key], list):
@@ -306,13 +317,21 @@ class Request(Storage):
self.parse_all_vars()
return self._vars
@property
def uuid(self):
"""Lazily uuid
"""
if self._uuid is None:
self.compute_uuid()
return self._uuid
def compute_uuid(self):
self.uuid = '%s/%s.%s.%s' % (
self._uuid = '%s/%s.%s.%s' % (
self.application,
self.client.replace(':', '_'),
self.now.strftime('%Y-%m-%d.%H-%M-%S'),
web2py_uuid())
return self.uuid
return self._uuid
def user_agent(self):
from gluon.contrib import user_agent_parser
@@ -436,29 +455,32 @@ class Response(Storage):
return page
def include_meta(self):
s = "\n";
s = "\n"
for meta in (self.meta or {}).iteritems():
k, v = meta
if isinstance(v,dict):
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
if isinstance(v, dict):
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
else:
s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
s += '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
self.write(s, escape=False)
def include_files(self, extensions=None):
"""
Caching method for writing out files.
Includes files (usually in the head).
Can minify and cache local files
By default, caches in ram for 5 minutes. To change,
response.cache_includes = (cache_method, time_expire).
Example: (cache.disk, 60) # caches to disk for 1 minute.
"""
from gluon import URL
files = []
ext_files = []
has_js = has_css = False
for item in self.files:
if extensions and not item.split('.')[-1] in extensions:
if isinstance(item, (list, tuple)):
ext_files.append(item)
continue
if extensions and not item.rpartition('.')[2] in extensions:
continue
if item in files:
continue
@@ -487,10 +509,13 @@ class Response(Storage):
time_expire)
else:
files = call_minify()
s = ''
files.extend(ext_files)
s = []
for item in files:
if isinstance(item, str):
f = item.lower().split('?')[0]
ext = f.rpartition('.')[2]
# if static_version we need also to check for
# static_version_urls. In that case, the _.x.x.x
# bit would have already been added by the URL()
@@ -498,24 +523,15 @@ class Response(Storage):
if self.static_version and not self.static_version_urls:
item = item.replace(
'/static/', '/static/_%s/' % self.static_version, 1)
if f.endswith('.css'):
s += css_template % item
elif f.endswith('.js'):
s += js_template % item
elif f.endswith('.coffee'):
s += coffee_template % item
elif f.endswith('.ts'):
# http://www.typescriptlang.org/
s += typescript_template % item
elif f.endswith('.less'):
s += less_template % item
tmpl = template_mapping.get(ext)
if tmpl:
s.append(tmpl % item)
elif isinstance(item, (list, tuple)):
f = item[0]
if f == 'css:inline':
s += css_inline % item[1]
elif f == 'js:inline':
s += js_inline % item[1]
self.write(s, escape=False)
tmpl = template_mapping.get(f)
if tmpl:
s.append(tmpl % item[1])
self.write(''.join(s), escape=False)
def stream(self,
stream,
@@ -663,7 +679,7 @@ class Response(Storage):
return handler(request, self, methods)
def toolbar(self):
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
from gluon.html import DIV, SCRIPT, BEAUTIFY, TAG, A
BUTTON = TAG.button
admin = URL("admin", "default", "design", extension='html',
args=current.request.application)

View File

@@ -376,7 +376,6 @@ def wsgibase(environ, responder):
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
or env.https == 'on'
)
request.compute_uuid() # requires client
request.url = environ['PATH_INFO']
# ##################################################

View File

@@ -41,9 +41,7 @@ def enable_autocomplete_and_history(adir, env):
except ImportError:
pass
else:
readline.parse_and_bind("bind ^I rl_complete"
if sys.platform == 'darwin'
else "tab: complete")
readline.parse_and_bind("tab: complete")
history_file = os.path.join(adir, '.pythonhistory')
try:
readline.read_history_file(history_file)

View 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

View File

@@ -269,31 +269,33 @@ class FastStorage(dict):
class List(list):
"""
Like a regular python list but a[i] if i is out of bounds returns None
instead of `IndexOutOfBounds`
Like a regular python list but callable.
When a(i) is called if i is out of bounds returns None
instead of `IndexError`.
"""
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
"""Allows to use a special syntax for fast-check of `request.args()`
validity
Args:
"""Allows to use a special syntax for fast-check of
`request.args()` validity.
:params:
i: index
default: use this value if arg not found
cast: type cast
otherwise: can be:
- None: results in a 404
- str: redirect to this address
- callable: calls the function (nothing is passed)
otherwise:
will be executed when:
- casts fail
- value not found, dont have default and otherwise is
especified
can be:
- None: results in a 404
- str: redirect to this address
- callable: calls the function (nothing is passed)
Example:
You can use::
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""
n = len(self)
if 0 <= i < n or -n <= i < 0:
@@ -301,23 +303,24 @@ class List(list):
elif default is DEFAULT:
value = None
else:
value, cast = default, False
if cast:
try:
value, cast, otherwise = default, False, False
try:
if cast:
value = cast(value)
except (ValueError, TypeError):
from http import HTTP, redirect
if otherwise is None:
raise HTTP(404)
elif isinstance(otherwise, str):
redirect(otherwise)
elif callable(otherwise):
return otherwise()
else:
raise RuntimeError("invalid otherwise")
if not value and otherwise:
raise ValueError('Otherwise will raised.')
except (ValueError, TypeError):
from http import HTTP, redirect
if otherwise is None:
raise HTTP(404)
elif isinstance(otherwise, str):
redirect(otherwise)
elif callable(otherwise):
return otherwise()
else:
raise RuntimeError("invalid otherwise")
return value
if __name__ == '__main__':
import doctest
doctest.testmod()

View File

@@ -4,6 +4,7 @@ from test_http import *
from test_cache import *
from test_contenttype import *
from test_fileutils import *
from test_globals import *
from test_html import *
from test_is_url import *
from test_languages import *

View File

@@ -13,6 +13,7 @@ fix_sys_path(__file__)
from storage import Storage
from cache import CacheInRam, CacheOnDisk, Cache
from gluon.dal import DAL, Field
oldcwd = None
@@ -30,6 +31,11 @@ def tearDownModule():
if oldcwd:
os.chdir(oldcwd)
oldcwd = None
try:
os.unlink('dummy.db')
except:
pass
class TestCache(unittest.TestCase):
@@ -107,7 +113,34 @@ class TestCache(unittest.TestCase):
cache.clear(regex=r'a*')
self.assertEqual(cache('a1', lambda: 2, 0), 2)
self.assertEqual(cache('a2', lambda: 3, 100), 3)
return
def testDALcache(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)
db = DAL(check_reserved=['all'])
db.define_table('t_a', Field('f_a'))
db.t_a.insert(f_a='test')
db.commit()
a = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
b = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
self.assertEqual(a.as_csv(), b.as_csv())
c = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
d = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
self.assertEqual(c.as_csv(), d.as_csv())
self.assertEqual(a.as_csv(), c.as_csv())
self.assertEqual(b.as_csv(), d.as_csv())
e = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
f = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
self.assertEqual(e.as_csv(), f.as_csv())
self.assertEqual(a.as_csv(), f.as_csv())
g = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
h = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
self.assertEqual(g.as_csv(), h.as_csv())
self.assertEqual(a.as_csv(), h.as_csv())
db.t_a.drop()
db.close()
if __name__ == '__main__':
setUpModule() # pre-python-2.7

View File

@@ -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
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.globals
"""
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.globals import Response
from gluon import URL
class testResponse(unittest.TestCase):
def test_include_files(self):
def return_includes(response, extensions=None):
response.include_files(extensions)
return response.body.getvalue()
response = Response()
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.js" type="text/javascript"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.coffee'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.coffee" type="text/coffee"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content, '<script src="/a/static/css/file.ts" type="text/typescript"></script>')
response = Response()
response.files.append(URL('a', 'static', 'css/file.less'))
content = return_includes(response)
self.assertEqual(content, '<link href="/a/static/css/file.less" rel="stylesheet/less" type="text/css" />')
response = Response()
response.files.append(('css:inline', 'background-color; white;'))
content = return_includes(response)
self.assertEqual(content, '<style type="text/css">\nbackground-color; white;\n</style>')
response = Response()
response.files.append(('js:inline', 'alert("hello")'))
content = return_includes(response)
self.assertEqual(content, '<script type="text/javascript">\nalert("hello")\n</script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js')
content = return_includes(response)
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
content = return_includes(response)
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>')
response = Response()
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append(URL('a', 'static', 'css/file.css'))
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
response.files.append(URL('a', 'static', 'css/file.css'))
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
)
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response, extensions=['css'])
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
#regr test for #628
response = Response()
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
content = return_includes(response)
self.assertEqual(content, '')
#regr test for #628
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
response = Response()
response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false'])
content = return_includes(response)
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
response = Response()
response.files.append(('js1', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
self.assertEqual(content, '')
if __name__ == '__main__':
unittest.main()

View File

@@ -120,6 +120,7 @@ class TestStorageList(unittest.TestCase):
class TestList(unittest.TestCase):
""" Tests Storage.List (fast-check for request.args()) """
def test_listcall(self):
@@ -134,6 +135,23 @@ class TestList(unittest.TestCase):
self.assertEqual(a(3, cast=int), 1234)
a.append('x')
self.assertRaises(HTTP, a, 4, cast=int)
b = List()
# default is always returned when especified
self.assertEqual(b(0, cast=int, default=None), None)
self.assertEqual(b(0, cast=int, default=None, otherwise='teste'), None)
self.assertEqual(b(0, cast=int, default='a', otherwise='teste'), 'a')
# if don't have value and otherwise is especified it will called
self.assertEqual(b(0, otherwise=lambda: 'something'), 'something')
self.assertEqual(b(0, cast=int, otherwise=lambda: 'something'),
'something')
# except if default is especified
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
def test_listgetitem(self):
'''Mantains list behaviour.'''
a = List((1, 2, 3))
self.assertEqual(a[0], 1)
self.assertEqual(a[::-1], [3, 2, 1])
if __name__ == '__main__':

View File

@@ -60,7 +60,7 @@ except ImportError:
# fallback to pure-Python module
import gluon.contrib.simplejson as json_parser
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'Wiki',
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
### mind there are two loggers here (logger and crud.settings.logger)!
@@ -767,8 +767,8 @@ class Mail(object):
if self.settings.server == 'logging':
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' %
('-' * 40, sender,
', '.join(to), subject,
text or html, '-' * 40))
', '.join(to), subject,
text or html, '-' * 40))
elif self.settings.server == 'gae':
xcc = dict()
if cc:
@@ -779,23 +779,23 @@ class Mail(object):
xcc['reply_to'] = reply_to
from google.appengine.api import mail
attachments = attachments and [mail.Attachment(
a.my_filename,
a.my_filename,
a.my_payload,
contebt_id='<attachment-%s>' % k
) for k,a in enumerate(attachments) if not raw]
if attachments:
result = mail.send_mail(
sender=sender, to=origTo,
subject=subject, body=text, html=html,
subject=unicode(subject), body=unicode(text), html=html,
attachments=attachments, **xcc)
elif html and (not raw):
result = mail.send_mail(
sender=sender, to=origTo,
subject=subject, body=text, html=html, **xcc)
subject=unicode(subject), body=unicode(text), html=html, **xcc)
else:
result = mail.send_mail(
sender=sender, to=origTo,
subject=subject, body=text, **xcc)
subject=unicode(subject), body=unicode(text), **xcc)
else:
smtp_args = self.settings.server.split(':')
kwargs = dict(timeout=self.settings.timeout)
@@ -965,7 +965,142 @@ class Recaptcha(DIV):
return XML(captcha).xml()
# this should only be used for catcha and perhaps not even for that
class Recaptcha2(DIV):
"""
Experimental:
Creates a DIV holding the newer Recaptcha from Google (v2)
Args:
request : the request. If not passed, uses current request
public_key : the public key Google gave you
private_key : the private key Google gave you
error_message : the error message to show if verification fails
label : the label to use
options (dict) : takes these parameters
- hl
- theme
- type
- tabindex
- callback
- expired-callback
see https://developers.google.com/recaptcha/docs/display for docs about those
comment : the comment
Examples:
Use as::
form = FORM(Recaptcha2(public_key='...',private_key='...'))
or::
form = SQLFORM(...)
form.append(Recaptcha2(public_key='...',private_key='...'))
to protect the login page instead, use::
from gluon.tools import Recaptcha2
auth.settings.captcha = Recaptcha2(request, public_key='...',private_key='...')
"""
API_URI = 'https://www.google.com/recaptcha/api.js'
VERIFY_SERVER = 'https://www.google.com/recaptcha/api/siteverify'
def __init__(self,
request=None,
public_key='',
private_key='',
error_message='invalid',
label='Verify:',
options=None,
comment='',
):
request = request or current.request
self.request_vars = request and request.vars or current.request.vars
self.remote_addr = request.env.remote_addr
self.public_key = public_key
self.private_key = private_key
self.errors = Storage()
self.error_message = error_message
self.components = []
self.attributes = {}
self.label = label
self.options = options or {}
self.comment = comment
def _validate(self):
recaptcha_response_field = self.request_vars.pop('g-recaptcha-response', None)
remoteip = self.remote_addr
if not recaptcha_response_field:
self.errors['captcha'] = self.error_message
return False
params = urllib.urlencode({
'secret': self.private_key,
'remoteip': remoteip,
'response': recaptcha_response_field,
})
request = urllib2.Request(
url=self.VERIFY_SERVER,
data=params,
headers={'Content-type': 'application/x-www-form-urlencoded',
'User-agent': 'reCAPTCHA Python'})
httpresp = urllib2.urlopen(request)
content = httpresp.read()
httpresp.close()
try:
response_dict = json_parser.loads(content)
except:
self.errors['captcha'] = self.error_message
return False
if response_dict.get('success', False):
self.request_vars.captcha = ''
return True
else:
self.errors['captcha'] = self.error_message
return False
def xml(self):
api_uri = self.API_URI
hl = self.options.pop('hl', None)
if hl:
api_uri = self.API_URI + '?hl=%s' % hl
public_key = self.public_key
self.options['sitekey'] = public_key
captcha = DIV(
SCRIPT(_src=api_uri, _async='', _defer=''),
DIV(_class="g-recaptcha", data=self.options),
TAG.noscript(XML("""
<div style="width: 302px; height: 352px;">
<div style="width: 302px; height: 352px; position: relative;">
<div style="width: 302px; height: 352px; position: absolute;">
<iframe src="https://www.google.com/recaptcha/api/fallback?k=%(public_key)s"
frameborder="0" scrolling="no"
style="width: 302px; height:352px; border-style: none;">
</iframe>
</div>
<div style="width: 250px; height: 80px; position: absolute; border-style: none;
bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
class="g-recaptcha-response"
style="width: 250px; height: 80px; border: 1px solid #c1c1c1;
margin: 0px; padding: 0px; resize: none;" value="">
</textarea>
</div>
</div>
</div>""" % dict(public_key=public_key))
)
)
if not self.errors.captcha:
return XML(captcha).xml()
else:
captcha.append(DIV(self.errors['captcha'], _class='error'))
return XML(captcha).xml()
# this should only be used for captcha and perhaps not even for that
def addrow(form, a, b, c, style, _id, position=-1):
if style == "divs":
form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'),
@@ -987,6 +1122,15 @@ def addrow(form, a, b, c, style, _id, position=-1):
DIV(b, SPAN(c, _class='inline-help'),
_class='controls'),
_class='control-group', _id=_id))
elif style == "bootstrap3_inline":
form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'),
DIV(b, SPAN(c, _class='help-block'),
_class='col-sm-9'),
_class='form-group', _id=_id))
elif style == "bootstrap3_stacked":
form[0].insert(position, DIV(LABEL(a, _class='control-label'),
b, SPAN(c, _class='help-block'),
_class='form-group', _id=_id))
else:
form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'),
TD(b, _class='w2p_fw'),
@@ -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()

View File

@@ -84,7 +84,7 @@ server {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
ssl_protocols SSLv3 TLSv1;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
keepalive_timeout 70;
location / {
#uwsgi_pass 127.0.0.1:9001;

View File

@@ -62,7 +62,7 @@ do_start()
start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
&& return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE \
start-stop-daemon --start --quiet -m --pidfile $PIDFILE \
${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \
--background --exec $DAEMON -- $DAEMON_ARGS \
|| return 2