diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js
index 313d00bc..7131727d 100644
--- a/applications/admin/static/js/web2py.js
+++ b/applications/admin/static/js/web2py.js
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!');
}
+ var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
+ var animateIn = 'fadeIn';
+ // var animateIn = 'slideDown';
+
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -178,7 +182,7 @@
},
/* manage errors in forms */
manage_errors: function (target) {
- $('div.error', target).hide().slideDown('slow');
+ $('div.error', target).hide()[animateIn]('slow');
},
after_ajax: function (xhr) {
/* called whenever an ajax request completes */
@@ -263,13 +267,17 @@
}
});
/* help preventing double form submission for normal form (not LOADed) */
- $(doc).on('submit', 'form', function () {
- var submit_button = $(this).find(web2py.formInputClickSelector);
- web2py.disableElement(submit_button);
+ $(doc).on('submit', 'form', function (e) {
+ var submit_buttons = $(this).find(web2py.formInputClickSelector);
+ submit_buttons.each(function() {
+ web2py.disableElement($(this));
+ })
/* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () {
- web2py.enableElement(submit_button);
+ submit_buttons.each(function() {
+ web2py.enableElement($(this));
+ });
}, 5000);
});
doc.ajaxSuccess(function (e, xhr) {
@@ -320,7 +328,15 @@
form.submit(function (e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
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();
});
form.on('click', web2py.formInputClickSelector, function (e) {
@@ -339,11 +355,18 @@
if (web2py.isUndefined(element)) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
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 */
$.ajax({
'type': method,
'url': action,
'data': data,
+ 'processData': !isFormData,
+ 'contentType': contentType,
'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target);
@@ -595,7 +618,7 @@
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
- if (flash.html()) flash.append(' × ').slideDown();
+ if (flash.html()) flash.append(' × ')[animateIn]();
},
hide_flash: function () {
$('.w2p_flash').fadeOut(0).html('');
@@ -609,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], 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();
}
};
@@ -699,8 +722,9 @@
});
},
/* Disables form elements:
+ - Does not disable elements with 'data-w2p_disable' attribute
- 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
*/
disableFormElements: function (form) {
@@ -712,13 +736,15 @@
if (!web2py.isUndefined(disable)) {
return false;
}
- if (web2py.isUndefined(disable_with)) {
- element.data('w2p_disable_with', element[method]());
+ if (!element.is(':file')) { // Altering file input values is not allowed.
+ 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);
});
},
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
-/* compatibility code - end*/
\ No newline at end of file
+/* compatibility code - end*/
diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js
index 313d00bc..7131727d 100644
--- a/applications/examples/static/js/web2py.js
+++ b/applications/examples/static/js/web2py.js
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!');
}
+ var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
+ var animateIn = 'fadeIn';
+ // var animateIn = 'slideDown';
+
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -178,7 +182,7 @@
},
/* manage errors in forms */
manage_errors: function (target) {
- $('div.error', target).hide().slideDown('slow');
+ $('div.error', target).hide()[animateIn]('slow');
},
after_ajax: function (xhr) {
/* called whenever an ajax request completes */
@@ -263,13 +267,17 @@
}
});
/* help preventing double form submission for normal form (not LOADed) */
- $(doc).on('submit', 'form', function () {
- var submit_button = $(this).find(web2py.formInputClickSelector);
- web2py.disableElement(submit_button);
+ $(doc).on('submit', 'form', function (e) {
+ var submit_buttons = $(this).find(web2py.formInputClickSelector);
+ submit_buttons.each(function() {
+ web2py.disableElement($(this));
+ })
/* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () {
- web2py.enableElement(submit_button);
+ submit_buttons.each(function() {
+ web2py.enableElement($(this));
+ });
}, 5000);
});
doc.ajaxSuccess(function (e, xhr) {
@@ -320,7 +328,15 @@
form.submit(function (e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
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();
});
form.on('click', web2py.formInputClickSelector, function (e) {
@@ -339,11 +355,18 @@
if (web2py.isUndefined(element)) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
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 */
$.ajax({
'type': method,
'url': action,
'data': data,
+ 'processData': !isFormData,
+ 'contentType': contentType,
'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target);
@@ -595,7 +618,7 @@
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
- if (flash.html()) flash.append(' × ').slideDown();
+ if (flash.html()) flash.append(' × ')[animateIn]();
},
hide_flash: function () {
$('.w2p_flash').fadeOut(0).html('');
@@ -609,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], 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();
}
};
@@ -699,8 +722,9 @@
});
},
/* Disables form elements:
+ - Does not disable elements with 'data-w2p_disable' attribute
- 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
*/
disableFormElements: function (form) {
@@ -712,13 +736,15 @@
if (!web2py.isUndefined(disable)) {
return false;
}
- if (web2py.isUndefined(disable_with)) {
- element.data('w2p_disable_with', element[method]());
+ if (!element.is(':file')) { // Altering file input values is not allowed.
+ 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);
});
},
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
-/* compatibility code - end*/
\ No newline at end of file
+/* compatibility code - end*/
diff --git a/gluon/dal.py b/gluon/dal.py
index 215fd4e1..0ebfd6a5 100644
--- a/gluon/dal.py
+++ b/gluon/dal.py
@@ -50,6 +50,10 @@ def _default_validators(db, field):
requires.append(validators.IS_TIME())
elif field_type == 'datetime':
requires.append(validators.IS_DATETIME())
+ elif field.options is not None:
+ requires = IS_IN_SET(field.options, multiple=field_type.startswith('list:'))
+ elif field.regex and not requires:
+ requires = IS_REGEX(regex)
elif db and field_type.startswith('reference') and \
field_type.find('.') < 0 and \
field_type[10:] in db.tables:
@@ -76,16 +80,17 @@ def _default_validators(db, field):
requires._and = validators.IS_NOT_IN_DB(db, field)
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
- return requires
+ return requires
# does not get here for reference and list:reference
- if field.unique:
- requires.insert(0, validators.IS_NOT_IN_DB(db, field))
- excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
- if (field.notnull or field.unique) and field_type not in excluded_fields:
- requires.insert(0, validators.IS_NOT_EMPTY())
- elif not field.notnull and not field.unique and requires:
- requires[0] = \
- validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
+ if isinstance(requires, list):
+ if field.unique:
+ requires.insert(0, validators.IS_NOT_IN_DB(db, field))
+ excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
+ if (field.notnull or field.unique) and field_type not in excluded_fields:
+ requires.insert(0, validators.IS_NOT_EMPTY())
+ elif not field.notnull and not field.unique and requires:
+ null = null='' if field.type in ('string', 'text', 'password') else None
+ requires[0] = validators.IS_EMPTY_OR(requires[0], null=null)
return requires
DAL.serializers = {'json': custom_json, 'xml': xml}
diff --git a/gluon/packages/dal b/gluon/packages/dal
index 35dd4fc6..d8631f68 160000
--- a/gluon/packages/dal
+++ b/gluon/packages/dal
@@ -1 +1 @@
-Subproject commit 35dd4fc6f8fb8187e7c08217eebf3074e0d27fbc
+Subproject commit d8631f683cd374b274f7fe5caa0abf79b7fdc54c
diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py
index 594339dd..93e431fb 100644
--- a/gluon/sqlhtml.py
+++ b/gluon/sqlhtml.py
@@ -2348,12 +2348,12 @@ class SQLFORM(FORM):
for k, f in iteritems(table):
if isinstance(f, Field.Virtual):
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:
fields = []
columns = []
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:
fields += filter(filter1, table)
columns += filter(filter2, table)
@@ -2571,8 +2571,8 @@ class SQLFORM(FORM):
try:
# the query should be constructed using searchable
# fields but not virtual fields
- sfields = reduce(lambda a, b: a + b,
- [[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
+ is_searchable = lambda f: f.readable and not isinstance(f, Field.Virtual) and f.searchable
+ sfields = reduce(lambda a, b: a + b, [filter(is_searchable, t) for t in tables])
# use custom_query using searchable
if callable(searchable):
dbset = dbset(searchable(sfields, keywords))