Compare commits

..
68 Commits
Author SHA1 Message Date
mdipierro c9fd0fd71e fixed test because of layout change 2017-11-14 00:55:59 -06:00
mdipierro d59e7f35ed fixed toolbar/configuration issue 2017-11-14 00:50:44 -06:00
mdipierro 999e7b7512 fixed layout in lack of configuration 2017-11-14 00:47:40 -06:00
mdipierro 7035398681 2.16.1 2017-11-13 23:50:20 -06:00
mdipierro 2c2c9d3aa2 moved to bootstrap4 2017-11-13 23:48:54 -06:00
mdipierro 65b3e6dda9 support for bootstrap4 2017-11-13 23:29:34 -06:00
mdipierroandGitHub d424e5d317 Merge pull request #1610 from geomapdev/dal-enhancements
handle reference fields for keyed tables
2017-11-13 21:24:07 -06:00
mdipierro a1417df67c Merge branch 'master' of github.com:web2py/web2py 2017-11-13 21:15:04 -06:00
mdipierroandGitHub 551c19bcaf Merge pull request #1761 from josedesoto/issue/update_role_on_update_profile
Update groups on edit profile
2017-11-13 21:14:02 -06:00
mdipierroandGitHub 8aa07760d2 Merge pull request #1803 from leonelcamara/patch-10
Fix #1715
2017-11-13 21:13:06 -06:00
mdipierroandGitHub 8bd8db5edc Merge pull request #1804 from leonelcamara/patch-11
Update supported python versions
2017-11-13 21:12:36 -06:00
mdipierro 7c1fb6643e fllowing pydal v17.11 2017-11-13 21:12:06 -06:00
Leonel CâmaraandGitHub e44a12eaf8 Update supported python versions 2017-11-13 22:40:37 +00:00
Leonel CâmaraandGitHub 12253ab757 Fix #1715 2017-11-13 15:31:05 +00:00
mdipierro 30b3d84f24 fixed options when type is list 2017-11-10 22:31:35 -06:00
mdipierro 166e268308 improved handling of regex and options field attributes 2017-11-10 18:50:52 -06:00
mdipierro 14e58276cf form handling of f.options, f.regex, f.listable, f.rearchable 2017-11-10 18:41:41 -06:00
mdipierroandGitHub eb7222aa92 Merge pull request #1802 from leonelcamara/fixsqltable
Fix a problem with Expressions in SQLTABLE
2017-11-10 18:12:50 -06:00
mdipierroandGitHub 009a0b87f2 Merge pull request #1801 from leonelcamara/i1800
Fixes #1800
2017-11-10 18:12:16 -06:00
mdipierroandGitHub aeb0244b2c Merge pull request #1799 from vinyldarkscratch/master
Add two scripts
2017-11-10 18:12:03 -06:00
mdipierroandGitHub 57522ddeb1 Merge pull request #1797 from BigManWalter/master
Set entry animation to 'fadeIn' and provide an easy switch for 'slideDown'
2017-11-10 18:10:13 -06:00
mdipierroandGitHub 934042fc04 Merge pull request #1796 from vitkarpenko/middleware_status_code
gluon.http expects status code as integer while LazyWSGI generates str
2017-11-10 18:09:37 -06:00
Leonel Câmara 4bfa9c1686 Fix a problem with Expressions in SQLTABLE possibly fixes #1735 and fixes web2py/pydal#487 2017-11-08 22:21:51 +00:00
Leonel Câmara 925f928843 Copy all CRYPT attributes thanks @abastardi 2017-11-08 11:53:29 +00:00
mdipierro 2f5eb409b6 show computed fields in readonly mode in appadmin 2017-11-08 00:40:47 -06:00
Leonel Câmara 228d3c41b6 Fixes #1800 2017-11-07 23:34:35 +00:00
Vinyl Darkscratch 3ab91b9fe9 Add check_lang_progress script 2017-11-06 01:42:25 -08:00
Vinyl Darkscratch 5db3b21437 Add update_languages script 2017-11-06 01:42:14 -08:00
Eric Waldman 2aa8fd22a2 Set entry animation to 'fadeIn' and provide an easy switch for 'slideDown' 2017-11-03 15:17:18 -04:00
Vitaly Karpenko d5eb8d8f17 gluon.http expects status code as integer 2017-11-02 13:52:25 +03:00
mdipierroandGitHub c4e08eeeb3 Merge pull request #1793 from sherdim/master
A step to PY3  closes #1792 and may be closes #1727
2017-11-01 18:59:52 -05:00
geomapdev c1a3d5d67e sync with master branch 2017-10-31 14:47:07 -07:00
geomapdev b2841de6f3 Merge remote-tracking branch 'upstream/master' 2017-10-31 14:42:19 -07:00
Дим ЩandGitHub c7d415fefd compatible unicode 2017-10-28 19:32:48 +03:00
Дим ЩandGitHub 2e572aee9a Update custom_import.py 2017-10-28 19:29:55 +03:00
Дим ЩandGitHub dd14d1c8c9 Add reload 2017-10-28 19:28:51 +03:00
Дим ЩandGitHub afa5fac074 Add reload 2017-10-28 19:26:34 +03:00
mdipierro a5b76f1dec Merge branch 'master' of github.com:web2py/web2py 2017-10-18 22:14:27 -05:00
mdipierroandGitHub 1e586b32a4 Merge pull request #1789 from timnyborg/master
Fix setting 'ac' in request.vars
2017-10-18 22:13:16 -05:00
mdipierroandGitHub dfcffe1a9d Merge pull request #1785 from tiagobar/master
Enhances/fixes validators/is_empty() for Python 3 compatibility, fix #1764
2017-10-18 22:12:09 -05:00
Tim NyborgandGitHub 2e6f529dfd Fix setting 'ac' in request.vars 2017-10-12 17:18:06 +01:00
Tim NyborgandGitHub 0ae3ee506f Merge pull request #1 from web2py/master
rebase
2017-10-12 17:16:56 +01:00
geomapdev 6b103f7e3a updated test_validators IS_IN_DB 2017-10-10 08:42:17 -07:00
geomapdev 02c32ebace update gluon unittest for dal reference fields 2017-10-09 11:22:48 -07:00
geomapdev 603cc7092a dal.py more code cleanup 2017-10-04 10:39:42 -07:00
geomapdev 8590aae2e8 dal.py code cleanup 2017-10-04 10:34:48 -07:00
tiago.bar 511245a68c Unit test for #1764 fix 2017-09-23 20:21:14 -03:00
tiago.bar 4a347a4f78 Enhances/fixes validators/is_empty() for Python 3 compatibility, fix #1764 2017-09-22 13:04:47 -03:00
Jose de SotoandGitHub 5f4c47729b Removed a tab and replaced by spaces 2017-09-21 10:17:17 +02:00
mdipierroandGitHub 5975e4767f Merge pull request #1766 from abastardi/tsv_export
Fix grid TSV export bug
2017-09-21 00:23:17 -05:00
mdipierroandGitHub 1800304ade Merge pull request #1763 from abastardi/compiled_views
Fix bug with compiled views in compiled-only apps
2017-09-21 00:22:53 -05:00
mdipierroandGitHub 9af0d8c3c9 Merge pull request #1758 from leonelcamara/patch-9
Fix #1757
2017-09-21 00:21:10 -05:00
mdipierroandGitHub 2e2d3482e4 Merge pull request #1756 from leonelcamara/i1752
Make _compat accept ducks
2017-09-21 00:20:54 -05:00
mdipierroandGitHub 48cc63741e Merge pull request #1755 from leonelcamara/i1753
Fixes #1753
2017-09-21 00:20:26 -05:00
abastardi a605e43fa6 Fix grid TSV export bug 2017-09-14 18:49:31 -04:00
abastardi 8eda21ca86 Fix bug with compiled views in compiled-only apps
Compiled views were not being executed in apps containing only compiled
files (i.e., generated via the admin "Pack compiled" functionality),
resulting in "Invalid view" errors for all pages.
2017-09-13 11:48:25 -04:00
Jose de Soto e8cf50326d When profile is updated self._update_session_user(user) set session.user_groups to None. self.update_groups() needs to be done. 2017-09-13 11:21:03 +02:00
mdipierro 27c9250efb fixed book link 2017-09-07 12:46:36 -05:00
Leonel CâmaraandGitHub 3ecdd1c11b Fix #1757 2017-09-07 10:18:41 +01:00
Leonel Câmara 912c22d593 Don't use gluon.utf8.Utf8 with py3 there's no need for it and it breaks stuff 2017-09-06 16:22:50 +01:00
Leonel Câmara 4b38186b51 simplify condition if you ain't got decode you ain't bytes 2017-09-06 15:37:51 +01:00
Leonel Câmara 83f9016528 fix in py3 too 2017-09-06 15:33:47 +01:00
Leonel Câmara a6044068cd Fixes #1751 2017-09-06 15:31:30 +01:00
Leonel Câmara 4aefb93ab4 Fixes #1752 2017-09-06 15:28:39 +01:00
Leonel Câmara 2861dc4215 Fixes #1753 2017-09-05 15:46:31 +01:00
mdipierro 087280ec17 fixed Makefile _ssd 2017-09-01 22:47:54 -05:00
geomapdevandGitHub 69e6e79e23 Update dal.py
updated to handle references without format
2017-05-01 08:39:31 -07:00
geomapdevandGitHub 3292f760ca handle reference fields for keyed tables 2017-04-20 10:21:16 -07:00
41 changed files with 691 additions and 348 deletions
+6
View File
@@ -1,3 +1,9 @@
## 2.16.1
- pydal 17.11
- bootstrap 4
- better welcome examples
- many bug fixes
## 2.15.1-4 ## 2.15.1-4
- pydal 17.08 - pydal 17.08
- dropped support for python 2.6 - dropped support for python 2.6
+9 -7
View File
@@ -30,11 +30,7 @@ update:
wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: rmfiles:
### Use semantic versioning
echo 'Version 2.15.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps ### clean up baisc apps
rm -f routes.py rm -f routes.py
rm -rf applications/*/sessions/* rm -rf applications/*/sessions/*
@@ -46,6 +42,12 @@ src:
rm -rf applications/admin/uploads/* rm -rf applications/admin/uploads/*
rm -rf applications/welcome/uploads/* rm -rf applications/welcome/uploads/*
rm -rf applications/examples/uploads/* rm -rf applications/examples/uploads/*
src:
### Use semantic versioning
echo 'Version 2.16.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
#make clean
# make rmfiles
### make welcome layout and appadmin the default ### make welcome layout and appadmin the default
cp applications/welcome/views/appadmin.html applications/admin/views cp applications/welcome/views/appadmin.html applications/admin/views
cp applications/welcome/views/appadmin.html applications/examples/views cp applications/welcome/views/appadmin.html applications/examples/views
@@ -54,7 +56,7 @@ src:
### build web2py_src.zip ### build web2py_src.zip
echo '' > NEWINSTALL echo '' > NEWINSTALL
mv web2py_src.zip web2py_src_old.zip | echo 'no old' mv web2py_src.zip web2py_src_old.zip | echo 'no old'
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py cd ..; zip -r --exclude=**.git** web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
mdp: mdp:
make src make src
@@ -98,7 +100,7 @@ win:
cp -r applications/examples ../web2py_win/web2py/applications cp -r applications/examples ../web2py_win/web2py/applications
cp applications/__init__.py ../web2py_win/web2py/applications cp applications/__init__.py ../web2py_win/web2py/applications
# per https://github.com/web2py/web2py/issues/1716 # per https://github.com/web2py/web2py/issues/1716
mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done'
cd ../web2py_win; zip -r web2py_win.zip web2py cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip . mv ../web2py_win/web2py_win.zip .
run: run:
+1 -1
View File
@@ -1 +1 @@
Version 2.15.4-stable+timestamp.2017.09.01.22.38.25 Version 2.16.1-stable+timestamp.2017.11.13.23.50.07
File diff suppressed because one or more lines are too long
+42 -16
View File
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!'); $.error('web2py.js has already been loaded!');
} }
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
var animateIn = 'fadeIn';
// var animateIn = 'slideDown';
String.prototype.reverse = function () { String.prototype.reverse = function () {
return this.split('').reverse().join(''); return this.split('').reverse().join('');
}; };
@@ -178,7 +182,7 @@
}, },
/* manage errors in forms */ /* manage errors in forms */
manage_errors: function (target) { manage_errors: function (target) {
$('div.error', target).hide().slideDown('slow'); $('div.error', target).hide()[animateIn]('slow');
}, },
after_ajax: function (xhr) { after_ajax: function (xhr) {
/* called whenever an ajax request completes */ /* called whenever an ajax request completes */
@@ -263,13 +267,17 @@
} }
}); });
/* help preventing double form submission for normal form (not LOADed) */ /* help preventing double form submission for normal form (not LOADed) */
$(doc).on('submit', 'form', function () { $(doc).on('submit', 'form', function (e) {
var submit_button = $(this).find(web2py.formInputClickSelector); var submit_buttons = $(this).find(web2py.formInputClickSelector);
web2py.disableElement(submit_button); submit_buttons.each(function() {
web2py.disableElement($(this));
})
/* safeguard in case the form doesn't trigger a refresh, /* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */ see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () { setTimeout(function () {
web2py.enableElement(submit_button); submit_buttons.each(function() {
web2py.enableElement($(this));
});
}, 5000); }, 5000);
}); });
doc.ajaxSuccess(function (e, xhr) { doc.ajaxSuccess(function (e, xhr) {
@@ -320,7 +328,15 @@
form.submit(function (e) { form.submit(function (e) {
web2py.disableElement(form.find(web2py.formInputClickSelector)); web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash(); web2py.hide_flash();
web2py.ajax_page('post', url, form.serialize(), target, form);
var formData;
if (FORMDATA_IS_SUPPORTED) {
formData = new FormData(form[0]); // Allows file uploads.
} else {
formData = form.serialize(); // Fallback for older browsers.
}
web2py.ajax_page('post', url, formData, target, form);
e.preventDefault(); e.preventDefault();
}); });
form.on('click', web2py.formInputClickSelector, function (e) { form.on('click', web2py.formInputClickSelector, function (e) {
@@ -339,11 +355,18 @@
if (web2py.isUndefined(element)) element = $(document); if (web2py.isUndefined(element)) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/ /* if target is not there, fill it with something that there isn't in the page*/
if (web2py.isUndefined(target) || target === '') target = 'w2p_none'; if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
/* processData and contentType must be set to false when passing a FormData
object to jQuery.ajax. */
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
$.ajax({ $.ajax({
'type': method, 'type': method,
'url': action, 'url': action,
'data': data, 'data': data,
'processData': !isFormData,
'contentType': contentType,
'beforeSend': function (xhr, settings) { 'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location); xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target); xhr.setRequestHeader('web2py-component-element', target);
@@ -595,7 +618,7 @@
var flash = $('.w2p_flash'); var flash = $('.w2p_flash');
web2py.hide_flash(); web2py.hide_flash();
flash.html(message).addClass(status); flash.html(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown(); if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
}, },
hide_flash: function () { hide_flash: function () {
$('.w2p_flash').fadeOut(0).html(''); $('.w2p_flash').fadeOut(0).html('');
@@ -609,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) { for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], target); var dep = $('#' + triggers[id][k], target);
var tr = $('#' + triggers[id][k] + '__row', target); var tr = $('#' + triggers[id][k] + '__row', target);
if (t.is(dep.attr('data-show-if'))) tr.slideDown(); if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
else tr.hide(); else tr.hide();
} }
}; };
@@ -699,8 +722,9 @@
}); });
}, },
/* Disables form elements: /* Disables form elements:
- Does not disable elements with 'data-w2p_disable' attribute
- Caches element value in 'w2p_enable_with' data store - Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute - Replaces element text with value of 'data-w2p_disable_with' attribute
- Sets disabled property to true - Sets disabled property to true
*/ */
disableFormElements: function (form) { disableFormElements: function (form) {
@@ -712,13 +736,15 @@
if (!web2py.isUndefined(disable)) { if (!web2py.isUndefined(disable)) {
return false; return false;
} }
if (web2py.isUndefined(disable_with)) { if (!element.is(':file')) { // Altering file input values is not allowed.
element.data('w2p_disable_with', element[method]()); if (web2py.isUndefined(disable_with)) {
element.data('w2p_disable_with', element[method]());
}
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
} }
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true); element.prop('disabled', true);
}); });
}, },
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link; web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy; web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/ */
/* compatibility code - end*/ /* compatibility code - end*/
@@ -3,9 +3,9 @@
- **Created by a community of professionals** and University professors in Computer Science and Software Engineering. - **Created by a community of professionals** and University professors in Computer Science and Software Engineering.
- **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future. - **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future.
- **Easy to run.** It requires no installation and no configuration. - **Easy to run.** It requires no installation and no configuration.
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.5/2.6/2.7/pypy, or Java with Jython. - **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.7/3.5/3.6/pypy.
- **Runs with** Apache, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware. - **Runs with** Apache, Nginx, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Oracle, IBM DB2, Informix, Ingres, and Google App Engine. - **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Sybase, Oracle, IBM DB2, Informix, Ingres, MongoDB, and Google App Engine.
- **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution. - **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution.
- **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable. - **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable.
- **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF). - **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF).
+42 -16
View File
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!'); $.error('web2py.js has already been loaded!');
} }
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
var animateIn = 'fadeIn';
// var animateIn = 'slideDown';
String.prototype.reverse = function () { String.prototype.reverse = function () {
return this.split('').reverse().join(''); return this.split('').reverse().join('');
}; };
@@ -178,7 +182,7 @@
}, },
/* manage errors in forms */ /* manage errors in forms */
manage_errors: function (target) { manage_errors: function (target) {
$('div.error', target).hide().slideDown('slow'); $('div.error', target).hide()[animateIn]('slow');
}, },
after_ajax: function (xhr) { after_ajax: function (xhr) {
/* called whenever an ajax request completes */ /* called whenever an ajax request completes */
@@ -263,13 +267,17 @@
} }
}); });
/* help preventing double form submission for normal form (not LOADed) */ /* help preventing double form submission for normal form (not LOADed) */
$(doc).on('submit', 'form', function () { $(doc).on('submit', 'form', function (e) {
var submit_button = $(this).find(web2py.formInputClickSelector); var submit_buttons = $(this).find(web2py.formInputClickSelector);
web2py.disableElement(submit_button); submit_buttons.each(function() {
web2py.disableElement($(this));
})
/* safeguard in case the form doesn't trigger a refresh, /* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */ see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () { setTimeout(function () {
web2py.enableElement(submit_button); submit_buttons.each(function() {
web2py.enableElement($(this));
});
}, 5000); }, 5000);
}); });
doc.ajaxSuccess(function (e, xhr) { doc.ajaxSuccess(function (e, xhr) {
@@ -320,7 +328,15 @@
form.submit(function (e) { form.submit(function (e) {
web2py.disableElement(form.find(web2py.formInputClickSelector)); web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash(); web2py.hide_flash();
web2py.ajax_page('post', url, form.serialize(), target, form);
var formData;
if (FORMDATA_IS_SUPPORTED) {
formData = new FormData(form[0]); // Allows file uploads.
} else {
formData = form.serialize(); // Fallback for older browsers.
}
web2py.ajax_page('post', url, formData, target, form);
e.preventDefault(); e.preventDefault();
}); });
form.on('click', web2py.formInputClickSelector, function (e) { form.on('click', web2py.formInputClickSelector, function (e) {
@@ -339,11 +355,18 @@
if (web2py.isUndefined(element)) element = $(document); if (web2py.isUndefined(element)) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/ /* if target is not there, fill it with something that there isn't in the page*/
if (web2py.isUndefined(target) || target === '') target = 'w2p_none'; if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
/* processData and contentType must be set to false when passing a FormData
object to jQuery.ajax. */
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */ if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
$.ajax({ $.ajax({
'type': method, 'type': method,
'url': action, 'url': action,
'data': data, 'data': data,
'processData': !isFormData,
'contentType': contentType,
'beforeSend': function (xhr, settings) { 'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location); xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target); xhr.setRequestHeader('web2py-component-element', target);
@@ -595,7 +618,7 @@
var flash = $('.w2p_flash'); var flash = $('.w2p_flash');
web2py.hide_flash(); web2py.hide_flash();
flash.html(message).addClass(status); flash.html(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown(); if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
}, },
hide_flash: function () { hide_flash: function () {
$('.w2p_flash').fadeOut(0).html(''); $('.w2p_flash').fadeOut(0).html('');
@@ -609,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) { for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], target); var dep = $('#' + triggers[id][k], target);
var tr = $('#' + triggers[id][k] + '__row', target); var tr = $('#' + triggers[id][k] + '__row', target);
if (t.is(dep.attr('data-show-if'))) tr.slideDown(); if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
else tr.hide(); else tr.hide();
} }
}; };
@@ -699,8 +722,9 @@
}); });
}, },
/* Disables form elements: /* Disables form elements:
- Does not disable elements with 'data-w2p_disable' attribute
- Caches element value in 'w2p_enable_with' data store - Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute - Replaces element text with value of 'data-w2p_disable_with' attribute
- Sets disabled property to true - Sets disabled property to true
*/ */
disableFormElements: function (form) { disableFormElements: function (form) {
@@ -712,13 +736,15 @@
if (!web2py.isUndefined(disable)) { if (!web2py.isUndefined(disable)) {
return false; return false;
} }
if (web2py.isUndefined(disable_with)) { if (!element.is(':file')) { // Altering file input values is not allowed.
element.data('w2p_disable_with', element[method]()); if (web2py.isUndefined(disable_with)) {
element.data('w2p_disable_with', element[method]());
}
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
} }
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true); element.prop('disabled', true);
}); });
}, },
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link; web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy; web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/ */
/* compatibility code - end*/ /* compatibility code - end*/
@@ -48,7 +48,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a> <a class="btn btn180 rounded green" href="http://mdipierro.github.io/web2py/web2py_manual_5th.pdf">Manual</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a> <a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
+23 -26
View File
@@ -1,26 +1,35 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# This is a sample controller # This is a sample controller
# - index is the default action of any application # this file is released under public domain and you can use without limitations
# - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# ---- example index page ----
def index(): def index():
"""
example action using the internationalization operator T and flash
rendered by views/default/index.html or views/generic.html
if you need a simple wiki simply replace the two lines below with:
return auth.wiki()
"""
response.flash = T("Hello World") response.flash = T("Hello World")
return dict(message=T('Welcome to web2py!')) return dict(message=T('Welcome to web2py!'))
# ---- API (example) -----
@auth.requires_login()
def api_get_user_email():
if not request.env.request_method == 'GET': raise HTTP(403)
return response.json({'status':'success', 'email':auth.user.email})
# ---- Smart Grid (example) -----
@auth.requires_membership('admin') # can only be accessed by members of admin groupd
def grid():
response.view = 'generic.html' # use a generic view
tablename = request.args(0)
if not tablename in db.tables: raise HTTP(403)
grid = SQLFORM.smartgrid(db[tablename], args=[tablename], deletable=False, editable=False)
return dict(grid=grid)
# ---- Embedded wiki (example) ----
def wiki():
auth.wikimenu() # add the wiki to the menu
return auth.wiki()
# ---- Action for login/register/etc (required for auth) -----
def user(): def user():
""" """
exposes: exposes:
@@ -39,7 +48,7 @@ def user():
""" """
return dict(form=auth()) return dict(form=auth())
# ---- action to server uploaded static content (required) ---
@cache.action() @cache.action()
def download(): def download():
""" """
@@ -47,15 +56,3 @@ def download():
http://..../[app]/default/download/[filename] http://..../[app]/default/download/[filename]
""" """
return response.download(request, db) return response.download(request, db)
def call():
"""
exposes services. for example:
http://..../[app]/default/call/jsonrpc
decorate with @services.jsonrpc the functions to expose
supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
"""
return service()
+48 -25
View File
@@ -1,12 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# -------------------------------------------------------------------------
# AppConfig configuration made easy. Look inside private/appconfig.ini
# Auth is for authenticaiton and access control
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig
from gluon.tools import Auth
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# This scaffolding model makes your app work on Google App Engine too # This scaffolding model makes your app work on Google App Engine too
# File is released under public domain and you can use without limitations # File is released under public domain and you can use without limitations
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
if request.global_settings.web2py_version < "2.14.1": if request.global_settings.web2py_version < "2.15.5":
raise HTTP(500, "Requires web2py 2.13.3 or newer") raise HTTP(500, "Requires web2py 2.15.5 or newer")
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# if SSL/HTTPS is properly configured and you want all HTTP requests to # if SSL/HTTPS is properly configured and you want all HTTP requests to
@@ -14,23 +21,18 @@ if request.global_settings.web2py_version < "2.14.1":
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# request.requires_https() # request.requires_https()
# -------------------------------------------------------------------------
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# once in production, remove reload=True to gain full speed # once in production, remove reload=True to gain full speed
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
myconf = AppConfig(reload=True) configuration = AppConfig(reload=True)
if not request.env.web2py_runtime_gae: if not request.env.web2py_runtime_gae:
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# if NOT running on Google App Engine use SQLite or other DB # if NOT running on Google App Engine use SQLite or other DB
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'), db = DAL(configuration.get('db.uri'),
pool_size=myconf.get('db.pool_size'), pool_size=configuration.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'), migrate_enabled=configuration.get('db.migrate'),
check_reserved=['all']) check_reserved=['all'])
else: else:
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
@@ -52,12 +54,15 @@ else:
# by default give a view/generic.extension to all actions from localhost # by default give a view/generic.extension to all actions from localhost
# none otherwise. a pattern can be 'controller/function.extension' # none otherwise. a pattern can be 'controller/function.extension'
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
response.generic_patterns = ['*'] if request.is_local else [] response.generic_patterns = []
if request.is_local and not configuration.get('app.production'):
response.generic_patterns.append('*')
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# choose a style for forms # choose a style for forms
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other response.formstyle = 'bootstrap4_inline'
response.form_label_separator = myconf.get('forms.separator') or '' response.form_label_separator = ''
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# (optional) optimize handling of static files # (optional) optimize handling of static files
@@ -80,27 +85,24 @@ response.form_label_separator = myconf.get('forms.separator') or ''
# (more options discussed in gluon/tools.py) # (more options discussed in gluon/tools.py)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager
# host names must be a list of allowed host names (glob syntax allowed) # host names must be a list of allowed host names (glob syntax allowed)
auth = Auth(db, host_names=myconf.get('host.names')) auth = Auth(db, host_names=configuration.get('host.names'))
service = Service()
plugins = PluginManager()
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# create all tables needed by auth if not custom tables # create all tables needed by auth, maybe add a list of extra fields
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
auth.settings.extra_fields['auth_user'] = []
auth.define_tables(username=False, signature=False) auth.define_tables(username=False, signature=False)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# configure email # configure email
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
mail = auth.settings.mailer mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server') mail.settings.server = 'logging' if request.is_local else configuration.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender') mail.settings.sender = configuration.get('smtp.sender')
mail.settings.login = myconf.get('smtp.login') mail.settings.login = configuration.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False mail.settings.tls = configuration.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False mail.settings.ssl = configuration.get('smtp.ssl') or False
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# configure auth policy # configure auth policy
@@ -109,6 +111,27 @@ auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True auth.settings.reset_password_requires_verification = True
# -------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# -------------------------------------------------------------------------
response.meta.author = configuration.get('app.author')
response.meta.description = configuration.get('app.description')
response.meta.keywords = configuration.get('app.keywords')
response.meta.generator = configuration.get('app.generator')
response.show_toolbar = configuration.get('app.toolbar')
# -------------------------------------------------------------------------
# your http://google.com/analytics id
# -------------------------------------------------------------------------
response.google_analytics_id = configuration.get('google.analytics_id')
# -------------------------------------------------------------------------
# maybe use the scheduler
# -------------------------------------------------------------------------
if configuration.get('scheduler.enabled'):
from gluon.scheduler import Scheduler
scheduler = Scheduler(db, heartbeat=configure.get('heartbeat'))
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Define your tables below (or better in another model file) for example # Define your tables below (or better in another model file) for example
# #
+14 -55
View File
@@ -1,29 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations # this file is released under public domain and you can use without limitations
# ----------------------------------------------------------------------------------------------------------------------
# Customize your APP title, subtitle and menus here
# ----------------------------------------------------------------------------------------------------------------------
response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'),
_class="navbar-brand", _href="http://www.web2py.com/",
_id="web2py-logo")
response.title = request.application.replace('_', ' ').title()
response.subtitle = ''
# ----------------------------------------------------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
response.meta.author = myconf.get('app.author')
response.meta.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
# ----------------------------------------------------------------------------------------------------------------------
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
response.google_analytics_id = None
# ---------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
# this is the main application menu add/remove items as required # this is the main application menu add/remove items as required
# ---------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
@@ -32,53 +9,42 @@ response.menu = [
(T('Home'), False, URL('default', 'index'), []) (T('Home'), False, URL('default', 'index'), [])
] ]
DEVELOPMENT_MENU = True
# ---------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
# provide shortcuts for development. remove in production # provide shortcuts for development. you can remove everything below in production
# ---------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------
def _(): if not configuration.get('app.production'):
# ------------------------------------------------------------------------------------------------------------------ _app = request.application
# shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application
ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
response.menu += [ response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')), (T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [ (T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)), (T('Design'), False, URL('admin', 'default', 'design/%s' % _app)),
LI(_class="divider"),
(T('Controller'), False, (T('Controller'), False,
URL( URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))), 'admin', 'default', 'edit/%s/controllers/%s.py' % (_app, request.controller))),
(T('View'), False, (T('View'), False,
URL( URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))), 'admin', 'default', 'edit/%s/views/%s' % (_app, response.view))),
(T('DB Model'), False, (T('DB Model'), False,
URL( URL(
'admin', 'default', 'edit/%s/models/db.py' % app)), 'admin', 'default', 'edit/%s/models/db.py' % _app)),
(T('Menu Model'), False, (T('Menu Model'), False,
URL( URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)), 'admin', 'default', 'edit/%s/models/menu.py' % _app)),
(T('Config.ini'), False, (T('Config.ini'), False,
URL( URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)), 'admin', 'default', 'edit/%s/private/appconfig.ini' % _app)),
(T('Layout'), False, (T('Layout'), False,
URL( URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)), 'admin', 'default', 'edit/%s/views/layout.html' % _app)),
(T('Stylesheet'), False, (T('Stylesheet'), False,
URL( URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)), 'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % _app)),
(T('Database'), False, URL(app, 'appadmin', 'index')), (T('Database'), False, URL(_app, 'appadmin', 'index')),
(T('Errors'), False, URL( (T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)), 'admin', 'default', 'errors/' + _app)),
(T('About'), False, URL( (T('About'), False, URL(
'admin', 'default', 'about/' + app)), 'admin', 'default', 'about/' + _app)),
]), ]),
('web2py.com', False, '#', [ ('web2py.com', False, '#', [
(T('Download'), False, (T('Download'), False,
@@ -98,7 +64,6 @@ def _():
]), ]),
(T('Documentation'), False, '#', [ (T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'), (T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"),
(T('Preface'), False, (T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'), 'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False, (T('Introduction'), False,
@@ -143,9 +108,3 @@ def _():
]), ]),
] ]
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()
+8 -5
View File
@@ -5,6 +5,8 @@ author = Your Name <you@example.com>
description = a cool new app description = a cool new app
keywords = web2py, python, framework keywords = web2py, python, framework
generator = Web2py Web Framework generator = Web2py Web Framework
production = false
toolbar = false
; Host configuration ; Host configuration
[host] [host]
@@ -14,7 +16,6 @@ names = localhost:*, 127.0.0.1:*, *:*, *
[db] [db]
uri = sqlite://storage.sqlite uri = sqlite://storage.sqlite
migrate = true migrate = true
; ignored for sqlite
pool_size = 10 pool_size = 10
; smtp address and credentials ; smtp address and credentials
@@ -25,7 +26,9 @@ login = username:password
tls = true tls = true
ssl = true ssl = true
; form styling [scheduler]
[forms] enabled = false
formstyle = bootstrap3_inline heartbeat = 1
separator =
[google]
analytics_id =
File diff suppressed because one or more lines are too long
@@ -108,7 +108,6 @@ select.autocomplete {
background: url(../images/background.jpg) no-repeat center center; background: url(../images/background.jpg) no-repeat center center;
} }
body { body {
padding-top: 60px;
margin-bottom: 60px; margin-bottom: 60px;
} }
header { header {
@@ -126,8 +125,7 @@ html {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 60px; height: 60px;
background: #333; background: #f7f7f7;
color: #aaa;
} }
header h1 { header h1 {
color: #FFF!important; color: #FFF!important;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 KiB

File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
/*! Respond.js v1.4.2: min/max-width media query polyfill
* Copyright 2014 Scott Jehl
* Licensed under MIT
* http://j.mp/respondjs */
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b<t.length;b++){var c=t[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!p[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(w(c.styleSheet.rawCssText,e,f),p[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!s||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}x()};y(),c.update=y,c.getEmValue=u,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
+6 -4
View File
@@ -13,6 +13,8 @@
} }
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined'; var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
var animateIn = 'fadeIn';
// var animateIn = 'slideDown';
String.prototype.reverse = function () { String.prototype.reverse = function () {
return this.split('').reverse().join(''); return this.split('').reverse().join('');
@@ -180,7 +182,7 @@
}, },
/* manage errors in forms */ /* manage errors in forms */
manage_errors: function (target) { manage_errors: function (target) {
$('div.error', target).hide().slideDown('slow'); $('div.error', target).hide()[animateIn]('slow');
}, },
after_ajax: function (xhr) { after_ajax: function (xhr) {
/* called whenever an ajax request completes */ /* called whenever an ajax request completes */
@@ -616,7 +618,7 @@
var flash = $('.w2p_flash'); var flash = $('.w2p_flash');
web2py.hide_flash(); web2py.hide_flash();
flash.html(message).addClass(status); flash.html(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown(); if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
}, },
hide_flash: function () { hide_flash: function () {
$('.w2p_flash').fadeOut(0).html(''); $('.w2p_flash').fadeOut(0).html('');
@@ -630,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) { for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], target); var dep = $('#' + triggers[id][k], target);
var tr = $('#' + triggers[id][k] + '__row', target); var tr = $('#' + triggers[id][k] + '__row', target);
if (t.is(dep.attr('data-show-if'))) tr.slideDown(); if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
else tr.hide(); else tr.hide();
} }
}; };
@@ -823,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link; web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy; web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/ */
/* compatibility code - end*/ /* compatibility code - end*/
+17 -26
View File
@@ -1,15 +1,9 @@
{{left_sidebar_enabled,right_sidebar_enabled=False,('message' in globals())}}
{{extend 'layout.html'}} {{extend 'layout.html'}}
{{block header}} {{block header}}
<header class="container-fluid background"> <center style="background-color: #333; color:white; padding:30px">
<div class="jumbotron text-center"> <h1>/{{=request.application}}/{{=request.controller}}/{{=request.function}}
{{if response.title:}} </center>
<h1>{{=response.title}}
<small>{{=response.subtitle or ''}}</small></h1>
{{pass}}
</div>
</header>
{{end}} {{end}}
{{if 'message' in globals():}} {{if 'message' in globals():}}
@@ -27,26 +21,23 @@
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li> _href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li>
<li>{{=T('You can modify this application and adapt it to your needs')}}</li> <li>{{=T('You can modify this application and adapt it to your needs')}}</li>
</ol> </ol>
<center style="padding:50px">
<a class="btn btn-primary" href="{{=URL('admin','default','index')}}">
<i class="glyphicon glyphicon-cog"></i>
{{=T("admin")}}
</a>
<a class="btn btn-secondary" href="{{=URL('examples','default','index')}}">{{=T("Online examples")}}</a>
<a class="btn btn-secondary" href="http://web2py.com">web2py.com</a>
<a class="btn btn-secondary" href="http://web2py.com/book">{{=T('Documentation')}}</a>
<a class="btn btn-secondary" href="{{=URL('default','api_get_user_email')}}">{{=T('API Example')}}</a>
<a class="btn btn-secondary" href="{{=URL('default','grid/auth_user')}}">{{=T('Grid Example')}}</a>
<a class="btn btn-secondary" href="{{=URL('default','wiki')}}">{{=T('Wiki Example')}}</a>
</center>
{{elif 'content' in globals():}} {{elif 'content' in globals():}}
{{=content}} {{=content}}
{{else:}} {{else:}}
{{=BEAUTIFY(response._vars)}} {{=BEAUTIFY(response._vars)}}
{{pass}} {{pass}}
{{block right_sidebar}}
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title"><a class="btn-block"
href="{{=URL('admin','default','index')}}">
<i class="glyphicon glyphicon-cog"></i>
{{=T("admin")}}
</a></h3></div>
<div class="panel-body">
{{=T("Don't know what to do?")}}
</div>
<ul class="list-group">
<li class="list-group-item">{{=A(T("Online examples"), _href=URL('examples','default','index'))}}</li>
<li class="list-group-item"><a href="http://web2py.com">web2py.com</a></li>
<li class="list-group-item"><a href="http://web2py.com/book">{{=T('Documentation')}}</a></li>
</ul>
</div>
{{end}}
-3
View File
@@ -11,6 +11,3 @@ It is used as default when a view is not provided for your controllers
{{elif len(response._vars)>1:}} {{elif len(response._vars)>1:}}
{{=BEAUTIFY(response._vars)}} {{=BEAUTIFY(response._vars)}}
{{pass}} {{pass}}
{{if request.is_local:}}
{{=response.toolbar()}}
{{pass}}
+51 -54
View File
@@ -21,84 +21,81 @@
<meta name="google-site-verification" content=""> <meta name="google-site-verification" content="">
<!-- include stylesheets --> <!-- include stylesheets -->
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/> <link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/> <link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap4.css')}}"/>
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon"> <link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}"> <link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables <!-- All JavaScript at the bottom, except for Modernizr which enables
HTML5 elements & feature detects --> HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr-2.8.3.min.js')}}"></script> <script src="{{=URL('static','js/modernizr-2.8.3.min.js')}}"></script>
<!--[if lt IE 9]>
<script src="{{=URL('static','js/respond-1.4.2.min.js')}}"></script>
<![endif]-->
<!-- Favicons --> <!-- Favicons -->
{{include 'web2py_ajax.html'}} <!-- this includes jquery.js, calendar.js/.css and web2py.js --> {{include 'web2py_ajax.html'}} <!-- this includes jquery.js, calendar.js/.css and web2py.js -->
{{block head}}{{end}} {{block head}}{{end}}
{{
# using sidebars need to know what sidebar you want to use
mc0 = 'col-md-12'
mc1 = 'col-md-9'
mc2 = 'col-md-6'
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
middle_column = {0: mc0, 1: mc1, 2: mc2}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}}
</head> </head>
<body> <body>
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div> <div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
<!-- Navbar ======================================= --> <!-- Navbar ======================================= -->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation"> <nav class="navbar navbar-toggleable-md navbar-light bg-faded">
<div class="container-fluid"> <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<div class="navbar-header"> <span class="navbar-toggler-icon"></span>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> </button>
<span class="sr-only">Toggle navigation</span> <a class="navbar-brand" href="http://web2py.com">web2py</a>
<span class="icon-bar"></span> <div class="collapse navbar-collapse">
<span class="icon-bar"></span> <ul class="navbar-nav">
<span class="icon-bar"></span> {{for _item in response.menu or []:}}
</button> {{if len(_item)<4 or not _item[3]:}}
{{=response.logo or ''}} <li class="nav-item {{if _item[1]:}}active{{pass}}">
</div> <a class="nav-link" href="{{=_item[2]}}">{{=_item[0]}}</a>
<div class="collapse navbar-collapse"> </li>
<ul class="nav navbar-nav navbar-right"> {{else:}}
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}} <li class="nav-item dropdown">
</ul> <a class="nav-link dropdown-toggle" href="{{=_item[2]}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{=_item[0]}}</a>
{{if response.menu:}} <div class="dropdown-menu">
{{=MENU(response.menu, _class='nav navbar-nav',li_class='dropdown',ul_class='dropdown-menu')}} {{for _subitem in _item[3]:}}
<a class="dropdown-item" href="{{=_subitem[2]}}">{{=_subitem[0]}}</a>
{{pass}}
</div>
</li>
{{pass}} {{pass}}
</div> {{pass}}
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="text" placeholder="Search">
</form>
{{if 'auth' in globals():}}
<ul class="navbar-nav navbar-right">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{if auth.user:}}{{=auth.user.first_name}}{{else:}}LOGIN{{pass}}
</a>
<div class="dropdown-menu">
{{if auth.user:}}
<a class="dropdown-item" href="{{=URL('default','user/profile')}}">{{=T('Profile')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/change_password')}}">{{=T('Change Password')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/logout')}}">{{=T('Logout')}}</a>
{{else:}}
<a class="dropdown-item" href="{{=URL('default','user/login')}}">{{=T('Login')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/register')}}">{{=T('Sign up')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/request_password')}}">{{=T('Lost Password')}}</a>
{{pass}}
</div>
</li>
</ul>
{{pass}}
</div> </div>
</nav> </nav>
<!-- Masthead ===================================== --> <!-- Masthead ===================================== -->
{{block header}} {{block header}}
{{end}} {{end}}
<!-- Main ========================================= --> <!-- Main ========================================= -->
<!-- Begin page content --> <!-- Begin page content -->
<div class="container-fluid main-container"> <div class="container-fluid main-container">
{{if left_sidebar_enabled:}} <div class="col-md-12">
<div class="col-md-3 left-sidebar">
{{block left_sidebar}}
<h3>Left Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
<div class="{{=middle_column}}">
{{block center}} {{block center}}
{{include}} {{include}}
{{end}} {{end}}
{{=response.toolbar() if response.show_toolbar else ''}}
</div> </div>
{{if right_sidebar_enabled:}}
<div class="col-md-3">
{{block right_sidebar}}
<h3>Right Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
</div> </div>
{{block footer}} <!-- this is default footer --> {{block footer}} <!-- this is default footer -->
@@ -114,7 +111,7 @@
{{end}} {{end}}
<!-- The javascript =============================== --> <!-- The javascript =============================== -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script> <script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script src="{{=URL('static','js/web2py-bootstrap3.js')}}"></script> <script src="{{=URL('static','js/web2py-bootstrap4.js')}}"></script>
{{block page_js}}{{end page_js}} {{block page_js}}{{end page_js}}
{{if response.google_analytics_id:}} {{if response.google_analytics_id:}}
<!-- Analytics ==================================== --> <!-- Analytics ==================================== -->
+5 -3
View File
@@ -35,6 +35,7 @@ if PY2:
from gluon.contrib import ipaddress from gluon.contrib import ipaddress
BytesIO = StringIO BytesIO = StringIO
reduce = reduce reduce = reduce
reload = reload
hashlib_md5 = hashlib.md5 hashlib_md5 = hashlib.md5
iterkeys = lambda d: d.iterkeys() iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues() itervalues = lambda d: d.itervalues()
@@ -63,7 +64,7 @@ if PY2:
return None return None
if isinstance(obj, (bytes, bytearray, buffer)): if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj) return bytes(obj)
if isinstance(obj, unicode): if hasattr(obj, 'encode'):
return obj.encode(charset, errors) return obj.encode(charset, errors)
raise TypeError('Expected bytes') raise TypeError('Expected bytes')
@@ -77,6 +78,7 @@ else:
import pickle import pickle
from io import StringIO, BytesIO from io import StringIO, BytesIO
import copyreg import copyreg
from importlib import reload
from functools import reduce from functools import reduce
from html.parser import HTMLParser from html.parser import HTMLParser
from http import cookies as Cookie from http import cookies as Cookie
@@ -122,7 +124,7 @@ else:
return None return None
if isinstance(obj, (bytes, bytearray, memoryview)): if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj) return bytes(obj)
if isinstance(obj, str): if hasattr(obj, 'encode'):
return obj.encode(charset, errors) return obj.encode(charset, errors)
raise TypeError('Expected bytes') raise TypeError('Expected bytes')
@@ -151,7 +153,7 @@ def with_metaclass(meta, *bases):
def to_unicode(obj, charset='utf-8', errors='strict'): def to_unicode(obj, charset='utf-8', errors='strict'):
if obj is None: if obj is None:
return None return None
if not isinstance(obj, bytes): if not hasattr(obj, 'decode'):
return text_type(obj) return text_type(obj)
return obj.decode(charset, errors) return obj.decode(charset, errors)
+2 -1
View File
@@ -988,7 +988,8 @@ class AuthAPI(object):
requires = [requires] requires = [requires]
requires = list(filter(lambda t: isinstance(t, CRYPT), requires)) requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
if requires: if requires:
requires[0].min_length = 0 requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
requires[0].min_length = 0 # But do not enforce minimum length for the old password
old_password = kwargs.get('old_password', '') old_password = kwargs.get('old_password', '')
new_password = kwargs.get('new_password', '') new_password = kwargs.get('new_password', '')
+12 -13
View File
@@ -18,7 +18,7 @@ import fnmatch
import os import os
import copy import copy
import random import random
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long, reload
from gluon.storage import Storage, List from gluon.storage import Storage, List
from gluon.template import parse_template from gluon.template import parse_template
from gluon.restricted import restricted, compile2 from gluon.restricted import restricted, compile2
@@ -205,7 +205,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
other_response = Response() other_response = Response()
other_request.env.path_info = '/' + \ other_request.env.path_info = '/' + \
'/'.join([request.application, c, f] + '/'.join([request.application, c, f] +
map(str, other_request.args)) [str(a) for a in other_request.args])
other_request.env.query_string = \ other_request.env.query_string = \
vars and URL(vars=vars).split('?')[1] or '' vars and URL(vars=vars).split('?')[1] or ''
other_request.env.http_web2py_component_location = \ other_request.env.http_web2py_component_location = \
@@ -288,7 +288,7 @@ class LoadFactory(object):
other_response = globals.Response() other_response = globals.Response()
other_request.env.path_info = '/' + \ other_request.env.path_info = '/' + \
'/'.join([request.application, c, f] + '/'.join([request.application, c, f] +
map(str, other_request.args)) [str(a) for a in other_request.args])
other_request.env.query_string = \ other_request.env.query_string = \
vars and html.URL(vars=vars).split('?')[1] or '' vars and html.URL(vars=vars).split('?')[1] or ''
other_request.env.http_web2py_component_location = \ other_request.env.http_web2py_component_location = \
@@ -678,7 +678,7 @@ def run_view_in(environment):
layer = None layer = None
scode = None scode = None
if patterns: if patterns:
regex = re_compile('|'.join(map(fnmatch.translate, patterns))) regex = re_compile('|'.join(fnmatch.translate(p) for p in patterns))
short_action = '%(controller)s/%(function)s.%(extension)s' % request short_action = '%(controller)s/%(function)s.%(extension)s' % request
allow_generic = regex.search(short_action) allow_generic = regex.search(short_action)
else: else:
@@ -709,23 +709,22 @@ def run_view_in(environment):
ccode = getcfs(compiled, compiled, lambda: read_pyc(compiled)) ccode = getcfs(compiled, compiled, lambda: read_pyc(compiled))
layer = compiled layer = compiled
break break
if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
# if the view is not compiled # if the view is not compiled
if not layer: if not layer:
if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
# Parse template # Parse template
scode = parse_template(view, scode = parse_template(view,
pjoin(folder, 'views'), pjoin(folder, 'views'),
context=environment) context=environment)
# Compile template # Compile template
ccode = compile2(scode, filename) ccode = compile2(scode, filename)
layer = filename layer = filename
restricted(ccode, environment, layer=layer, scode=scode) restricted(ccode, environment, layer=layer, scode=scode)
# parse_template saves everything in response body # parse_template saves everything in response body
return environment['response'].body.getvalue() return environment['response'].body.getvalue()
+2
View File
@@ -137,6 +137,8 @@ def populate_generator(table, default=True, compute=False, contents={}):
continue continue
elif field.type == 'upload': elif field.type == 'upload':
continue continue
elif field.compute is not None:
continue
elif default and not field.default in (None, ''): elif default and not field.default in (None, ''):
record[fieldname] = field.default record[fieldname] = field.default
elif compute and field.compute: elif compute and field.compute:
+1 -1
View File
@@ -8,7 +8,7 @@
Support for smart import syntax for web2py applications Support for smart import syntax for web2py applications
------------------------------------------------------- -------------------------------------------------------
""" """
from gluon._compat import builtin, unicodeT, PY2, to_native from gluon._compat import builtin, unicodeT, PY2, to_native, reload
import os import os
import sys import sys
import threading import threading
+55 -31
View File
@@ -32,7 +32,11 @@ def _default_validators(db, field):
field_type, field_length = field.type, field.length field_type, field_length = field.type, field.length
requires = [] requires = []
if field_type in (('string', 'text', 'password')): if isinstance(field.options, list) and field.requires:
requires = validators.IS_IN_SET(field.options, multiple=field_type.startswith('list:'))
elif field.regex and not field.requires:
requires.append(validators.IS_REGEX(regex))
elif field_type in (('string', 'text', 'password')):
requires.append(validators.IS_LENGTH(field_length)) requires.append(validators.IS_LENGTH(field_length))
elif field_type == 'json': elif field_type == 'json':
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON())) requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
@@ -50,42 +54,62 @@ def _default_validators(db, field):
requires.append(validators.IS_TIME()) requires.append(validators.IS_TIME())
elif field_type == 'datetime': elif field_type == 'datetime':
requires.append(validators.IS_DATETIME()) requires.append(validators.IS_DATETIME())
elif db and field_type.startswith('reference') and \ elif db and field_type.startswith('reference'):
field_type.find('.') < 0 and \ if field_type.find('.') < 0 and field_type[10:] in db.tables:
field_type[10:] in db.tables: referenced = db[field_type[10:]]
referenced = db[field_type[10:]] if hasattr(referenced, '_format') and referenced._format:
if hasattr(referenced, '_format') and referenced._format: requires = validators.IS_IN_DB(db, referenced._id,referenced._format)
requires = validators.IS_IN_DB(db, referenced._id, else:
referenced._format) requires = validators.IS_IN_DB(db, referenced._id)
if field.unique: elif field_type.find('.') > 0 and field_type[10:].split('.')[0] in db.tables:
requires._and = validators.IS_NOT_IN_DB(db, field) table_field = field_type[10:].split('.')
if field.tablename == field_type[10:]: table_name=table_field[0]
return validators.IS_EMPTY_OR(requires) field_name=table_field[1]
return requires referenced = db[table_name]
elif db and field_type.startswith('list:reference') and \ if hasattr(referenced, '_format') and referenced._format:
field_type.find('.') < 0 and \ requires = validators.IS_IN_DB(db, referenced[field_name],referenced._format)
field_type[15:] in db.tables: else:
referenced = db[field_type[15:]] requires = validators.IS_IN_DB(db, referenced[field_name])
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced._id,
referenced._format, multiple=True)
else:
requires = validators.IS_IN_DB(db, referenced._id,
multiple=True)
if field.unique: if field.unique:
requires._and = validators.IS_NOT_IN_DB(db, field) requires._and = validators.IS_NOT_IN_DB(db, field)
if not field.notnull: if not field.notnull:
requires = validators.IS_EMPTY_OR(requires) requires = validators.IS_EMPTY_OR(requires)
return requires return requires
elif db and field_type.startswith('list:reference'):
if field_type.find('.') < 0 and field_type[15:] in db.tables:
referenced = db[field_type[15:]]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced._id,
referenced._format, multiple=True)
else:
requires = validators.IS_IN_DB(db, referenced._id,
multiple=True)
elif field_type.find('.') > 0 and field_type[15:].split('.')[0] in db.tables:
table_field = field_type[15:].split('.')
table_name=table_field[0]
field_name=table_field[1]
referenced = db[table_name]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced[field_name],
referenced._format, multiple=True)
else:
requires = validators.IS_IN_DB(db, referenced[field_name],
multiple=True)
if field.unique:
requires._and = validators.IS_NOT_IN_DB(db, field)
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
return requires
# does not get here for reference and list:reference # does not get here for reference and list:reference
if field.unique: if isinstance(requires, list):
requires.insert(0, validators.IS_NOT_IN_DB(db, field)) if field.unique:
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean'] requires.insert(0, validators.IS_NOT_IN_DB(db, field))
if (field.notnull or field.unique) and field_type not in excluded_fields: excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
requires.insert(0, validators.IS_NOT_EMPTY()) if (field.notnull or field.unique) and field_type not in excluded_fields:
elif not field.notnull and not field.unique and requires: requires.insert(0, validators.IS_NOT_EMPTY())
requires[0] = \ elif not field.notnull and not field.unique and requires:
validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None) null = null='' if field.type in ('string', 'text', 'password') else None
requires[0] = validators.IS_EMPTY_OR(requires[0], null=null)
return requires return requires
DAL.serializers = {'json': custom_json, 'xml': xml} DAL.serializers = {'json': custom_json, 'xml': xml}
+7 -2
View File
@@ -220,7 +220,12 @@ class Request(Storage):
if is_json: if is_json:
try: try:
json_vars = json_parser.load(body) # In Python 3 versions prior to 3.6 load doesn't accept bytes and
# bytearray, so we read the body convert to native and use loads
# instead of load.
# This line can be simplified to json_vars = json_parser.load(body)
# if and when we drop support for python versions under 3.6
json_vars = json_parser.loads(to_native(body.read()))
except: except:
# incoherent request bodies can still be parsed "ad-hoc" # incoherent request bodies can still be parsed "ad-hoc"
json_vars = {} json_vars = {}
@@ -331,7 +336,7 @@ class Request(Storage):
user_agent = session._user_agent user_agent = session._user_agent
if user_agent: if user_agent:
return user_agent return user_agent
http_user_agent = self.env.http_user_agent http_user_agent = self.env.http_user_agent or ''
user_agent = user_agent_parser.detect(http_user_agent) user_agent = user_agent_parser.detect(http_user_agent)
for key, value in user_agent.items(): for key, value in user_agent.items():
if isinstance(value, dict): if isinstance(value, dict):
+9 -4
View File
@@ -18,10 +18,11 @@ import pkgutil
import logging import logging
from cgi import escape from cgi import escape
from threading import RLock from threading import RLock
from gluon.utf8 import Utf8
from gluon.utils import local_html_escape from gluon.utils import local_html_escape
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
from pydal.contrib.portalocker import read_locked, LockedFile from pydal.contrib.portalocker import read_locked, LockedFile
from gluon.fileutils import listdir from gluon.fileutils import listdir
@@ -49,8 +50,10 @@ DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word
if PY2: if PY2:
NUMBERS = (int, long, float) NUMBERS = (int, long, float)
from gluon.utf8 import Utf8
else: else:
NUMBERS = (int, float) NUMBERS = (int, float)
Utf8 = str
# pattern to find T(blah blah blah) expressions # pattern to find T(blah blah blah) expressions
PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\ PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
@@ -107,15 +110,17 @@ def markmin(s):
def upper_fun(s): def upper_fun(s):
return unicode(s, 'utf-8').upper().encode('utf-8') return to_unicode(s).upper()
def title_fun(s): def title_fun(s):
return unicode(s, 'utf-8').title().encode('utf-8') return to_unicode(s).title()
def cap_fun(s): def cap_fun(s):
return unicode(s, 'utf-8').capitalize().encode('utf-8') return to_unicode(s).capitalize()
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f') ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}") ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
+1 -1
View File
@@ -231,7 +231,7 @@ class LazyWSGI(object):
to call third party WSGI applications to call third party WSGI applications
""" """
self.response.status = str(status).split(' ', 1)[0] self.response.status = int(str(status).split(' ', 1)[0])
self.response.headers = dict(headers) self.response.headers = dict(headers)
return lambda *args, **kargs: \ return lambda *args, **kargs: \
self.response.write(escape=False, *args, **kargs) self.response.write(escape=False, *args, **kargs)
+115 -10
View File
@@ -17,6 +17,7 @@ Holds:
import datetime import datetime
import urllib import urllib
import re import re
import copy
import os import os
from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT, to_native, to_unicode from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT, to_native, to_unicode
@@ -682,7 +683,7 @@ class AutocompleteWidget(object):
else: else:
self.is_reference = False self.is_reference = False
if hasattr(request, 'application'): if hasattr(request, 'application'):
urlvars = request.vars urlvars = copy.copy(request.vars)
urlvars[default_var] = 1 urlvars[default_var] = 1
self.url = URL(args=request.args, vars=urlvars, self.url = URL(args=request.args, vars=urlvars,
user_signature=user_signature, hash_vars=hash_vars) user_signature=user_signature, hash_vars=hash_vars)
@@ -1087,6 +1088,106 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
return parent return parent
return _inner return _inner
# bootstrap 4
def formstyle_bootstrap4_stacked(form, fields):
""" bootstrap 3 format form layout
Note:
Experimental!
"""
parent = CAT()
for id, label, controls, help in fields:
# wrappers
_help = SPAN(help, _class='help-block')
# embed _help into _controls
_controls = CAT(controls, _help)
if isinstance(controls, INPUT):
if controls['_type'] == 'submit':
controls.add_class('btn btn-primary')
if controls['_type'] == 'button':
controls.add_class('btn btn-secondary')
elif controls['_type'] == 'file':
controls.add_class('input-file')
elif controls['_type'] in ('text', 'password'):
controls.add_class('form-control')
elif controls['_type'] == 'checkbox':
label['_for'] = None
label.insert(0, controls)
label.insert(0, ' ')
_controls = DIV(label, _help, _class="checkbox")
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
controls.add_class('form-control')
elif isinstance(controls, SPAN):
_controls = P(controls.components)
elif isinstance(controls, UL):
for e in controls.elements("input"):
e.add_class('form-control')
elif isinstance(controls, CAT) and isinstance(controls[0], INPUT):
controls[0].add_class('form-control')
if isinstance(label, LABEL):
label['_class'] = add_class(label.get('_class'), 'form-control-label')
parent.append(DIV(label, _controls, _class='form-group', _id=id))
return parent
def formstyle_bootstrap4_inline_factory(col_label_size=3):
""" bootstrap 3 horizontal form layout
Note:
Experimental!
"""
def _inner(form, fields):
form.add_class('form-horizontal')
label_col_class = "col-sm-%d" % col_label_size
col_class = "col-sm-%d" % (12 - col_label_size)
offset_class = "col-sm-offset-%d" % col_label_size
parent = CAT()
for id, label, controls, help in fields:
# wrappers
_help = SPAN(help, _class='help-block')
# embed _help into _controls
_controls = DIV(controls, _help, _class="%s" % (col_class))
if isinstance(controls, INPUT):
if controls['_type'] == 'submit':
controls.add_class('btn btn-primary')
_controls = DIV(controls, _class="%s %s" % (col_class, offset_class))
if controls['_type'] == 'button':
controls.add_class('btn btn-secondary')
elif controls['_type'] == 'file':
controls.add_class('input-file')
elif controls['_type'] in ('text', 'password'):
controls.add_class('form-control')
elif controls['_type'] == 'checkbox':
label['_for'] = None
label.insert(0, controls)
label.insert(1, ' ')
_controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class))
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
controls.add_class('form-control')
elif isinstance(controls, SPAN):
_controls = P(controls.components,
_class="form-control-static %s" % col_class)
elif isinstance(controls, UL):
for e in controls.elements("input"):
e.add_class('form-control')
elif isinstance(controls, CAT) and isinstance(controls[0], INPUT):
controls[0].add_class('form-control')
if isinstance(label, LABEL):
label['_class'] = add_class(label.get('_class'), 'form-control-label %s' % label_col_class)
parent.append(DIV(label, _controls, _class='form-group', _id=id))
return parent
return _inner
class SQLFORM(FORM): class SQLFORM(FORM):
@@ -1175,6 +1276,8 @@ class SQLFORM(FORM):
bootstrap=formstyle_bootstrap, bootstrap=formstyle_bootstrap,
bootstrap3_stacked=formstyle_bootstrap3_stacked, bootstrap3_stacked=formstyle_bootstrap3_stacked,
bootstrap3_inline=formstyle_bootstrap3_inline_factory(3), bootstrap3_inline=formstyle_bootstrap3_inline_factory(3),
bootstrap4_stacked=formstyle_bootstrap4_stacked,
bootstrap4_inline=formstyle_bootstrap4_inline_factory(3),
inline=formstyle_inline, inline=formstyle_inline,
) )
@@ -1243,8 +1346,8 @@ class SQLFORM(FORM):
# will only use writable or readable fields, unless forced to ignore # will only use writable or readable fields, unless forced to ignore
if fields is None: if fields is None:
fields = [f.name for f in table if fields = [f.name for f in table if
(ignore_rw or f.writable or f.readable) and (ignore_rw or f.writable or f.readable) and
(readonly or not f.compute)] not (f.compute and not record)]
self.fields = fields self.fields = fields
# make sure we have an id # make sure we have an id
@@ -1327,7 +1430,7 @@ class SQLFORM(FORM):
label = LABEL(label, label and sep, _for=field_id, label = LABEL(label, label and sep, _for=field_id,
_id=field_id + SQLFORM.ID_LABEL_SUFFIX) _id=field_id + SQLFORM.ID_LABEL_SUFFIX)
cond = readonly or \ cond = readonly or field.compute or \
(not ignore_rw and not field.writable and field.readable) (not ignore_rw and not field.writable and field.readable)
if cond: if cond:
@@ -2347,12 +2450,12 @@ class SQLFORM(FORM):
for k, f in iteritems(table): for k, f in iteritems(table):
if isinstance(f, Field.Virtual): if isinstance(f, Field.Virtual):
f.tablename = table._tablename f.tablename = table._tablename
columns = [f for f in fields if f.tablename in tablenames] columns = [f for f in fields if f.tablename in tablenames and f.listable]
else: else:
fields = [] fields = []
columns = [] columns = []
filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs) filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs)
filter2 = lambda f: isinstance(f, Field) and f.readable filter2 = lambda f: isinstance(f, Field) and f.readable and f.listable
for table in tables: for table in tables:
fields += filter(filter1, table) fields += filter(filter1, table)
columns += filter(filter2, table) columns += filter(filter2, table)
@@ -2570,8 +2673,8 @@ class SQLFORM(FORM):
try: try:
# the query should be constructed using searchable # the query should be constructed using searchable
# fields but not virtual fields # fields but not virtual fields
sfields = reduce(lambda a, b: a + b, is_searchable = lambda f: f.readable and not isinstance(f, Field.Virtual) and f.searchable
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables]) sfields = reduce(lambda a, b: a + b, [filter(is_searchable, t) for t in tables])
# use custom_query using searchable # use custom_query using searchable
if callable(searchable): if callable(searchable):
dbset = dbset(searchable(sfields, keywords)) dbset = dbset(searchable(sfields, keywords))
@@ -3327,7 +3430,7 @@ class SQLTABLE(TABLE):
return return
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields)) fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields))
tablemap = dict(((f.tablename, f.table) for f in fieldmap.values())) tablemap = dict(((f.tablename, f.table) if isinstance(f, Field) else (f._table._tablename, f._table) for f in fieldmap.values()))
for table in tablemap.values(): for table in tablemap.values():
pref = table._tablename + '.' pref = table._tablename + '.'
fieldmap.update(((pref+f.name, f) for f in table._virtual_fields)) fieldmap.update(((pref+f.name, f) for f in table._virtual_fields))
@@ -3570,7 +3673,9 @@ class ExportClass(object):
if not self.rows.db._adapter.REGEX_TABLE_DOT_FIELD.match(col): if not self.rows.db._adapter.REGEX_TABLE_DOT_FIELD.match(col):
row.append(record._extra[col]) row.append(record._extra[col])
else: else:
(t, f) = col.split('.') # The grid code modifies rows.colnames, adding double quotes
# around the table and field names -- so they must be removed here.
(t, f) = [name.strip('"') for name in col.split('.')]
field = self.rows.db[t][f] field = self.rows.db[t][f]
if isinstance(record.get(t, None), (Row, dict)): if isinstance(record.get(t, None), (Row, dict)):
value = record[t][f] value = record[t][f]
+3
View File
@@ -138,6 +138,9 @@ class TestAuthAPI(unittest.TestCase):
self.assertTrue('new_password2' in result['errors']) self.assertTrue('new_password2' in result['errors'])
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234') result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
self.assertTrue('old_password' in result['errors']) self.assertTrue('old_password' in result['errors'])
# Test the default 4 min_length is enforced on change password
result = self.auth.change_password(old_password='1234', new_password='123', new_password2='123')
self.assertTrue('new_password' in result['errors'])
def test_verify_key(self): def test_verify_key(self):
self.auth.settings.registration_requires_verification = True self.auth.settings.registration_requires_verification = True
+4 -4
View File
@@ -355,10 +355,10 @@ class TestSQLTABLE(unittest.TestCase):
self.db.commit() self.db.commit()
# def test_SQLTABLE(self): def test_SQLTABLE(self):
# rows = self.db(self.db.auth_user.id > 0).select(self.db.auth_user.ALL) rows = self.db(self.db.auth_user.id > 0).select(self.db.auth_user.ALL)
# sqltable = SQLTABLE(rows) sqltable = SQLTABLE(rows)
# self.assertEqual(sqltable.xml(), '<table><thead><tr><th>auth_user.id</th><th>auth_user.first_name</th><th>auth_user.last_name</th><th>auth_user.email</th><th>auth_user.username</th><th>auth_user.password</th><th>auth_user.registration_key</th><th>auth_user.reset_password_key</th><th>auth_user.registration_id</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>Bart</td><td>Simpson</td><td>user1@test.com</td><td>user1</td><td>password_123</td><td>None</td><td></td><td>None</td></tr></tbody></table>') self.assertEqual(sqltable.xml(), b'<table><thead><tr><th>auth_user.id</th><th>auth_user.first_name</th><th>auth_user.last_name</th><th>auth_user.email</th><th>auth_user.username</th><th>auth_user.password</th><th>auth_user.registration_key</th><th>auth_user.reset_password_key</th><th>auth_user.registration_id</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>Bart</td><td>Simpson</td><td>user1@test.com</td><td>user1</td><td>password_123</td><td>None</td><td></td><td>None</td></tr></tbody></table>')
# class TestExportClass(unittest.TestCase): # class TestExportClass(unittest.TestCase):
+62
View File
@@ -278,8 +278,68 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('jerry', 'oops')) self.assertEqual(rtn, ('jerry', 'oops'))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', auto_add=True)('jerry') rtn = IS_IN_DB(db, 'person.id', '%(name)s', auto_add=True)('jerry')
self.assertEqual(rtn, (3, None)) self.assertEqual(rtn, (3, None))
# Test it works with reference table
db.define_table('ref_table',
Field('name'),
Field('person_id', 'reference person')
)
ret = db.ref_table.validate_and_insert(name='test reference table')
self.assertFalse(list(ret.errors))
ret = db.ref_table.validate_and_insert(name='test reference table', person_id=george_id)
self.assertFalse(list(ret.errors))
rtn = IS_IN_DB(db, 'ref_table.person_id', '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None))
# Test it works with reference table.field and keyed table
db.define_table('person_keyed',
Field('name'),
primarykey=['name'])
db.person_keyed.insert(name='george')
db.person_keyed.insert(name='costanza')
rtn = IS_IN_DB(db, 'person_keyed.name')('george')
self.assertEqual(rtn, ('george', None))
db.define_table('ref_table_field',
Field('name'),
Field('person_name', 'reference person_keyed.name')
)
ret = db.ref_table_field.validate_and_insert(name='test reference table.field')
self.assertFalse(list(ret.errors))
ret = db.ref_table_field.validate_and_insert(name='test reference table.field', person_name='george')
self.assertFalse(list(ret.errors))
vldtr = IS_IN_DB(db, 'ref_table_field.person_name', '%(name)s')
vldtr.options()
rtn = vldtr('george')
self.assertEqual(rtn, ('george', None))
# Test it works with list:reference table
db.define_table('list_ref_table',
Field('name'),
Field('person_list', 'list:reference person'))
ret = db.list_ref_table.validate_and_insert(name='test list:reference table')
self.assertFalse(list(ret.errors))
ret = db.list_ref_table.validate_and_insert(name='test list:reference table', person_list=[george_id,costanza_id])
self.assertFalse(list(ret.errors))
vldtr = IS_IN_DB(db, 'list_ref_table.person_list')
vldtr.options()
rtn = vldtr([george_id,costanza_id])
self.assertEqual(rtn, ([george_id,costanza_id], None))
# Test it works with list:reference table.field and keyed table
#db.define_table('list_ref_table_field',
# Field('name'),
# Field('person_list', 'list:reference person_keyed.name'))
#ret = db.list_ref_table_field.validate_and_insert(name='test list:reference table.field')
#self.assertFalse(list(ret.errors))
#ret = db.list_ref_table_field.validate_and_insert(name='test list:reference table.field', person_list=['george','costanza'])
#self.assertFalse(list(ret.errors))
#vldtr = IS_IN_DB(db, 'list_ref_table_field.person_list')
#vldtr.options()
#rtn = vldtr(['george','costanza'])
#self.assertEqual(rtn, (['george','costanza'], None))
db.person.drop() db.person.drop()
db.category.drop() db.category.drop()
db.person_keyed.drop()
db.ref_table.drop()
db.ref_table_field.drop()
db.list_ref_table.drop()
#db.list_ref_table_field.drop()
def test_IS_NOT_IN_DB(self): def test_IS_NOT_IN_DB(self):
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
@@ -444,6 +504,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, (None, 'Enter a value')) self.assertEqual(rtn, (None, 'Enter a value'))
rtn = IS_NOT_EMPTY()('') rtn = IS_NOT_EMPTY()('')
self.assertEqual(rtn, ('', 'Enter a value')) self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY()(b'')
self.assertEqual(rtn, (b'', 'Enter a value'))
rtn = IS_NOT_EMPTY()(' ') rtn = IS_NOT_EMPTY()(' ')
self.assertEqual(rtn, (' ', 'Enter a value')) self.assertEqual(rtn, (' ', 'Enter a value'))
rtn = IS_NOT_EMPTY()(' \n\t') rtn = IS_NOT_EMPTY()(' \n\t')
+2 -2
View File
@@ -102,12 +102,12 @@ class TestWeb(LiveTest):
password='test', password='test',
_formname='login') _formname='login')
client.post('user/login', data=data) client.post('user/login', data=data)
self.assertTrue('Welcome Homer' in client.text) self.assertTrue('Homer' in client.text)
# check registration and login were successful # check registration and login were successful
client.get('index') client.get('index')
self.assertTrue('Welcome Homer' in client.text) self.assertTrue('Homer' in client.text)
client = WebClient('http://127.0.0.1:8000/admin/default/') client = WebClient('http://127.0.0.1:8000/admin/default/')
client.post('index', data=dict(password='testpass')) client.post('index', data=dict(password='testpass'))
+3 -2
View File
@@ -3693,7 +3693,8 @@ class Auth(AuthAPI):
requires = [requires] requires = [requires]
requires = list(filter(lambda t: isinstance(t, CRYPT), requires)) requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
if requires: if requires:
requires[0].min_length = 0 requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
requires[0].min_length = 0 # But do not enforce minimum length for the old password
form = SQLFORM.factory( form = SQLFORM.factory(
Field('old_password', 'password', requires=requires, Field('old_password', 'password', requires=requires,
label=self.messages.old_password), label=self.messages.old_password),
@@ -3777,9 +3778,9 @@ class Auth(AuthAPI):
if any(f.compute for f in extra_fields): if any(f.compute for f in extra_fields):
user = table_user[self.user.id] user = table_user[self.user.id]
self._update_session_user(user) self._update_session_user(user)
self.update_groups()
else: else:
self.user.update(table_user._filter_fields(form.vars)) self.user.update(table_user._filter_fields(form.vars))
session.flash = self.messages.profile_updated session.flash = self.messages.profile_updated
self.log_event(log, self.user) self.log_event(log, self.user)
callback(onaccept, form) callback(onaccept, form)
+1 -1
View File
@@ -996,7 +996,7 @@ def is_empty(value, empty_regex=None):
value = value.strip() value = value.strip()
if empty_regex is not None and empty_regex.match(value): if empty_regex is not None and empty_regex.match(value):
value = '' value = ''
if value is None or value == '' or value == []: if value is None or value == '' or value == b'' or value == []:
return (_value, True) return (_value, True)
return (_value, False) return (_value, False)
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Written by Vinyl Darkscratch, www.queengoob.org
import sys
import os
# TODO: add comments
# This script can be run with no arguments (which sets the language folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (language folder path and default language).
# When run, this script will compare the original language's strings to their assigned values and determine if they match, just like web2py's interface. While some phrases may be the exact same between both languages, this should provide a good idea of how far along translations are.
def check_lang_progress(cwd, default_lang):
for x in os.listdir(cwd):
if x == default_lang or x.startswith("plural-"): continue
data = eval(open(os.path.join(cwd, x)).read())
total = 0
translated = 0
for key in data:
total += 1
if key.replace('@markmin\x01', '') != data[key]: translated += 1
print "Translations for %s (%s): %d/%d Translated (%d Untranslated)" %(data['!langname!'], data['!langcode!'], translated, total, total-translated)
if __name__ == "__main__":
cwd = os.getcwd()
default_lang = 'en'
if len(sys.argv) > 2:
cwd = sys.argv[1]
default_lang = sys.argv[2]
elif len(sys.argv) > 1:
default_lang = sys.argv[1]
check_lang_progress(cwd, default_lang)
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Written by Vinyl Darkscratch, www.queengoob.org
# TODO: add comments
import os
import ast
import sys
import inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir)
from gluon.cfs import getcfs
from gluon.utf8 import Utf8
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
from gluon.languages import findT
# This script can be run with no arguments (which sets the language folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (language folder path and default language).
# When run, it will update the default language, as well as strip all of the strings found in the non-default languages but not in the default language, and add the strings found in the default language to the non-default languages it is not, making sure translators don't do additional work that will never be used.
def read_dict_aux(filename):
lang_text = open(filename, 'r').read().replace(b'\r\n', b'\n')
try:
return safe_eval(to_native(lang_text)) or {}
except Exception:
e = sys.exc_info()[1]
status = 'Syntax error in %s (%s)' % (filename, e)
return {'__corrupted__': status}
def read_dict(filename):
return getcfs('lang:' + filename, filename, lambda: read_dict_aux(filename))
def safe_eval(text):
if text.strip():
try:
return ast.literal_eval(text)
except ImportError:
return eval(text, {}, {})
return None
def sort_function(x, y):
return cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())
def write_file(file, contents):
file.write('# -*- coding: utf-8 -*-\n{\n')
for key in sorted(contents, sort_function):
file.write('%s: %s,\n' % (repr(Utf8(key)),
repr(Utf8(contents[key]))))
file.write('}\n')
file.close()
def update_languages(cwd, default_lang):
defaultfp = os.path.join(cwd, '%s.py' %default_lang)
findT(cwd, default_lang)
default = read_dict(defaultfp)
for x in os.listdir(cwd):
if x == default_lang or x.startswith("plural-"): continue
i18n = read_dict(os.path.join(cwd, x))
if i18n:
nd = default
for k_d1 in i18n:
if k_d1 in default:
nd[k_d1] = i18n[k_d1]
write_file(open(x, 'w'), nd)
print x
if __name__ == "__main__":
cwd = os.getcwd()
default_lang = 'en'
if len(sys.argv) > 2:
cwd = sys.argv[1]
default_lang = sys.argv[2]
elif len(sys.argv) > 1:
default_lang = sys.argv[1]
update_languages(cwd, default_lang)