diff --git a/VERSION b/VERSION
index 4abd817f..4928b86a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.4.6-stable+timestamp.2013.04.28.23.09.04
+Version 2.4.6-stable+timestamp.2013.05.02.20.31.34
diff --git a/applications/admin/static/js/web2py_bootstrap.j b/applications/admin/static/js/web2py_bootstrap.j
new file mode 100644
index 00000000..0c94fd98
--- /dev/null
+++ b/applications/admin/static/js/web2py_bootstrap.j
@@ -0,0 +1,35 @@
+// this code improves bootstrap menus and adds dropdown support
+jQuery(function(){
+ jQuery('.nav>li>a').each(function(){
+ if(jQuery(this).parent().find('ul').length)
+ jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('');
+ });
+ jQuery('.nav li li').each(function(){
+ if(jQuery(this).find('ul').length)
+ jQuery(this).addClass('dropdown-submenu');
+ });
+ function adjust_height_of_collapsed_nav() {
+ var cn = jQuery('div.collapse');
+ if (cn.get(0)) {
+ var cnh = cn.get(0).style.height;
+ if (cnh>'0px'){
+ cn.css('height','auto');
+ }
+ }
+ }
+ function hoverMenu(){
+ jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
+ adjust_height_of_collapsed_nav();
+ mi = jQuery(this).addClass('open');
+ mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
+ }, function(){
+ mi = jQuery(this);
+ mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
+ });
+ }
+ hoverMenu(); // first page load
+ jQuery(window).resize(hoverMenu); // on resize event
+ jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
+ // make all buttons bootstrap buttons
+ jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
+});
\ No newline at end of file
diff --git a/applications/examples/static/js/web2py_bootstrap.j b/applications/examples/static/js/web2py_bootstrap.j
new file mode 100644
index 00000000..0c94fd98
--- /dev/null
+++ b/applications/examples/static/js/web2py_bootstrap.j
@@ -0,0 +1,35 @@
+// this code improves bootstrap menus and adds dropdown support
+jQuery(function(){
+ jQuery('.nav>li>a').each(function(){
+ if(jQuery(this).parent().find('ul').length)
+ jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('');
+ });
+ jQuery('.nav li li').each(function(){
+ if(jQuery(this).find('ul').length)
+ jQuery(this).addClass('dropdown-submenu');
+ });
+ function adjust_height_of_collapsed_nav() {
+ var cn = jQuery('div.collapse');
+ if (cn.get(0)) {
+ var cnh = cn.get(0).style.height;
+ if (cnh>'0px'){
+ cn.css('height','auto');
+ }
+ }
+ }
+ function hoverMenu(){
+ jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
+ adjust_height_of_collapsed_nav();
+ mi = jQuery(this).addClass('open');
+ mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
+ }, function(){
+ mi = jQuery(this);
+ mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
+ });
+ }
+ hoverMenu(); // first page load
+ jQuery(window).resize(hoverMenu); // on resize event
+ jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
+ // make all buttons bootstrap buttons
+ jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
+});
\ No newline at end of file
diff --git a/gluon/dal.py b/gluon/dal.py
index 42019a0c..82ef739b 100644
--- a/gluon/dal.py
+++ b/gluon/dal.py
@@ -4719,12 +4719,17 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
"text and blob field types not allowed in projection queries")
else:
projection.append(f.name)
+ elif args_get('filterfields') == True:
+ projection = []
+ for f in fields:
+ projection.append(f.name)
- # projection's can't include 'id'.
+ # real projection's can't include 'id'.
# it will be added to the result later
query_projection = [
p for p in projection if \
- p != db[tablename]._id.name] if projection \
+ p != db[tablename]._id.name] if projection and \
+ args_get('projection') == True\
else None
cursor = None
@@ -4802,6 +4807,11 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
what is accepted imposed by GAE: each field must be indexed,
projection queries cannot contain blob or text fields, and you
cannot use == and also select that same field. see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection
+ - optional attribute 'filterfields' when set to True web2py will only
+ parse the explicitly listed fields into the Rows object, even though
+ all fields are returned in the query. This can be used to reduce
+ memory usage in cases where true projection queries are not
+ usable.
- optional attribute 'reusecursor' allows use of cursor with queries
that have the limitby attribute. Set the attribute to True for the
first query, set it to the value of db['_lastcursor'] to continue
diff --git a/gluon/validators.py b/gluon/validators.py
index b3007de6..10372728 100644
--- a/gluon/validators.py
+++ b/gluon/validators.py
@@ -706,17 +706,20 @@ class IS_INT_IN_RANGE(Validator):
self.minimum = self.maximum = None
if minimum is None:
if maximum is None:
- self.error_message = error_message or 'enter an integer'
+ self.error_message = translate(
+ error_message or 'enter an integer')
else:
self.maximum = int(maximum)
if error_message is None:
- error_message = 'enter an integer less than or equal to %(max)g'
+ error_message = \
+ 'enter an integer less than or equal to %(max)g'
self.error_message = translate(
error_message) % dict(max=self.maximum - 1)
elif maximum is None:
self.minimum = int(minimum)
if error_message is None:
- error_message = 'enter an integer greater than or equal to %(min)g'
+ error_message = \
+ 'enter an integer greater than or equal to %(min)g'
self.error_message = translate(
error_message) % dict(min=self.minimum)
else:
diff --git a/scripts/setup-web2py-nginx-uwsgi-opensuse.sh b/scripts/setup-web2py-nginx-uwsgi-opensuse.sh
new file mode 100644
index 00000000..dd9e56dd
--- /dev/null
+++ b/scripts/setup-web2py-nginx-uwsgi-opensuse.sh
@@ -0,0 +1,226 @@
+#!/bin/bash
+echo 'setup-web2py-nginx-uwsgi-opensuse.sh'
+echo 'Requires OpenSUSE 12.3 32Bits and installs Nginx + uWSGI + Web2py'
+
+
+# Get Web2py Admin Password
+echo -e "Web2py Admin Password: \c "
+read PW
+
+zypper clean && zypper refresh && zypper up
+zypper in -y nginx python-xml python-pip unzip sudo
+zypper in -y gcc python-devel libxml2-devel python-pip unzip
+pip install --upgrade pip
+PIPPATH=`which pip`
+$PIPPATH install --upgrade uwsgi
+
+
+# Prepare folders for uwsgi
+mkdir /etc/uwsgi
+mkdir /var/log/uwsgi
+
+usermod -G www nginx
+
+mkdir -p /etc/nginx/vhosts.d/
+mkdir -p /etc/nginx/ssl/
+
+
+cd /etc/nginx/ssl
+openssl genrsa 1024 > web2py.key
+chmod 400 web2py.key
+openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
+openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
+
+
+echo 'server {
+ listen 80;
+ server_name $hostname;
+ #to enable correct use of response.static_version
+ #location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
+ # alias /srv/www/web2py/applications/$1/static/$2;
+ # expires max;
+ #}
+ location ~* /(\w+)/static/ {
+ root /srv/www/web2py/applications/;
+ #remove next comment on production
+ #expires max;
+ }
+ location / {
+ #uwsgi_pass 127.0.0.1:9001;
+ uwsgi_pass unix:///tmp/web2py.socket;
+ include /etc/nginx/uwsgi_params;
+ uwsgi_param UWSGI_SCHEME $scheme;
+ uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
+ }
+}
+server {
+ listen 443 default_server ssl;
+ server_name $hostname;
+ ssl_certificate /etc/nginx/ssl/web2py.crt;
+ ssl_certificate_key /etc/nginx/ssl/web2py.key;
+ ssl_prefer_server_ciphers on;
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 10m;
+ ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
+ ssl_protocols SSLv3 TLSv1;
+ keepalive_timeout 70;
+ location / {
+ #uwsgi_pass 127.0.0.1:9001;
+ uwsgi_pass unix:///tmp/web2py.socket;
+ include /etc/nginx/uwsgi_params;
+ uwsgi_param UWSGI_SCHEME $scheme;
+ uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
+ }
+
+}' >/etc/nginx/vhosts.d/web2py.conf
+
+
+
+# Create configuration file /etc/uwsgi/web2py.xml
+echo '
+ /tmp/web2py.socket
+ /srv/www/web2py/
+ /=wsgihandler:application
+
+ 4
+ 60
+ 8
+ 1
+ /tmp/stats.socket
+ 2000
+ 512
+ 256
+ 192
+ nginx
+ www
+ 0 0 -1 -1 -1 python /srv/www/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o
+
+' >/etc/uwsgi/web2py.xml
+
+
+cd /srv/www/
+wget http://web2py.com/examples/static/web2py_src.zip
+unzip web2py_src.zip
+rm web2py_src.zip
+# Download latest version of sessions2trash.py
+wget http://web2py.googlecode.com/hg/scripts/sessions2trash.py -O /srv/www/web2py/scripts/sessions2trash.py
+chown -R nginx:www web2py
+cd /srv/www/web2py
+sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
+
+
+
+## Daemons /start/stop
+
+echo '#!/bin/sh
+# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
+#
+# Author: James Oakley
+#
+# /etc/init.d/uwsgi
+# and its symbolic link
+# /(usr/)sbin/rcuwsgi
+#
+# LSB compatible service control script; see http://www.linuxbase.org/spec/
+#
+### BEGIN INIT INFO
+# Provides: uwsgi
+# Required-Start: $syslog $remote_fs
+# Should-Start: $time ypbind smtp
+# Required-Stop: $syslog $remote_fs
+# Should-Stop: ypbind smtp
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: Application Container Server for Networked/Clustered Web Applications
+# Description: Application Container Server for Networked/Clustered Web Applications
+### END INIT INFO
+
+# Check for missing binaries (stale symlinks should not happen)
+UWSGI_BIN=/usr/bin/uwsgi
+test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
+ if [ "$1" = "stop" ]; then exit 0;
+ else exit 5; fi; }
+
+UWSGI_EMPEROR_MODE=true
+UWSGI_VASSALS="/etc/uwsgi/"
+UWSGI_OPTIONS="--logto /var/log/uwsgi/uwsgi.log"
+
+
+UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
+
+if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
+ UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
+fi
+
+. /etc/rc.status
+
+rc_reset
+
+case "$1" in
+ start)
+ echo -n "Starting uWSGI "
+ /sbin/startproc $UWSGI_BIN $UWSGI_OPTIONS
+ rc_status -v
+ ;;
+ stop)
+ echo -n "Shutting down uWSGI "
+ /sbin/killproc $UWSGI_BIN
+ rc_status -v
+ ;;
+ try-restart|condrestart)
+ if test "$1" = "condrestart"; then
+ echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
+ fi
+ $0 status
+ if test $? = 0; then
+ $0 restart
+ else
+ rc_reset
+ fi
+ rc_status
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ rc_status
+ ;;
+ force-reload)
+ echo -n "Reload service uWSGI "
+ /sbin/killproc -HUP $UWSGI_BIN
+ rc_status -v
+ ;;
+ reload)
+ echo -n "Reload service uWSGI "
+ /sbin/killproc -HUP $UWSGI_BIN
+ rc_status -v
+ ;;
+ status)
+ echo -n "Checking for service uWSGI "
+ /sbin/checkproc $UWSGI_BIN
+ rc_status -v
+ ;;
+ probe)
+ echo -n "uWSGI does not support probe "
+ rc_failed 3
+ rc_status -v
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
+ exit 1
+ ;;
+esac
+rc_exit '> /etc/init.d/uwsgi
+
+
+chmod +x /etc/init.d/uwsgi
+/etc/init.d/uwsgi start
+/etc/init.d/nginx restart
+
+
+chkconfig --add uwsgi
+chkconfig --add nginx
+
+## you can reload uwsgi with
+#/etc/init.d/uwsgi restart
+## to reload web2py only (without restarting uwsgi)
+# touch /etc/uwsgi/web2py.xml