From 17ee260e3cc14c62c813c6e1a648c2bb45b6fa1b Mon Sep 17 00:00:00 2001 From: jmistx Date: Fri, 30 Aug 2013 05:40:01 -0700 Subject: [PATCH 01/29] Popen objects leak fixed --- gluon/newcron.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gluon/newcron.py b/gluon/newcron.py index bc8fb438..90e6eacd 100644 --- a/gluon/newcron.py +++ b/gluon/newcron.py @@ -246,6 +246,7 @@ class cronlauncher(threading.Thread): shell=self.shell) _cron_subprocs.append(proc) (stdoutdata, stderrdata) = proc.communicate() + _cron_subprocs.remove(proc) if proc.returncode != 0: logger.warning( 'WEB2PY CRON Call returned code %s:\n%s' % From a7d4c5297e3879fd7495c311dc66f35b13dee1c1 Mon Sep 17 00:00:00 2001 From: niphlod Date: Fri, 30 Aug 2013 21:13:49 +0200 Subject: [PATCH 02/29] fixed issue 1584 (grid export with left join) --- gluon/sqlhtml.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index e3399ac4..48171c9d 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -2073,14 +2073,13 @@ class SQLFORM(FORM): order = request.vars.order or '' if sortable: if order and not order == 'None': - if order[:1] == '~': - sign, rorder = '~', order[1:] + otablename, ofieldname = order.split('~')[-1].split('.', 1) + sort_field = db[otablename][ofieldname] + exception = sort_field.type in ('date', 'datetime', 'time') + if exception: + orderby = (order[:1] == '~' and sort_field) or ~sort_field else: - sign, rorder = '', order - tablename, fieldname = rorder.split('.', 1) - orderby = db[tablename][fieldname] - if sign == '~': - orderby = ~orderby + orderby = (order[:1] == '~' and ~sort_field) or sort_field expcolumns = [str(f) for f in columns] if export_type.endswith('with_hidden_cols'): @@ -2095,7 +2094,8 @@ class SQLFORM(FORM): try: dbset = dbset(SQLFORM.build_query( fields, request.vars.get('keywords', ''))) - rows = dbset.select(cacheable=True, *expcolumns) + rows = dbset.select(left=left, orderby=orderby, + cacheable=True, *expcolumns) except Exception, e: response.flash = T('Internal Error') rows = [] From 5036e360835ec0dc28bbbbe4024bdb0673d2d658 Mon Sep 17 00:00:00 2001 From: niphlod Date: Fri, 30 Aug 2013 22:05:57 +0200 Subject: [PATCH 03/29] fix examples app --- applications/examples/controllers/cache_examples.py | 1 + applications/examples/views/cache_examples/generic.html | 3 +++ applications/examples/views/simple_examples/ajaxwiki.html | 4 ++++ 3 files changed, 8 insertions(+) create mode 100644 applications/examples/views/cache_examples/generic.html create mode 100644 applications/examples/views/simple_examples/ajaxwiki.html diff --git a/applications/examples/controllers/cache_examples.py b/applications/examples/controllers/cache_examples.py index 857833d5..33c9e447 100644 --- a/applications/examples/controllers/cache_examples.py +++ b/applications/examples/controllers/cache_examples.py @@ -1,5 +1,6 @@ import time +response.view = 'cache_examples/generic.html' def cache_in_ram(): """cache the output of the lambda function in ram""" diff --git a/applications/examples/views/cache_examples/generic.html b/applications/examples/views/cache_examples/generic.html new file mode 100644 index 00000000..22b41e70 --- /dev/null +++ b/applications/examples/views/cache_examples/generic.html @@ -0,0 +1,3 @@ +{{extend 'layout.html'}} +

Cache Examples

+{{=BEAUTIFY(response._vars)}} diff --git a/applications/examples/views/simple_examples/ajaxwiki.html b/applications/examples/views/simple_examples/ajaxwiki.html new file mode 100644 index 00000000..363fd418 --- /dev/null +++ b/applications/examples/views/simple_examples/ajaxwiki.html @@ -0,0 +1,4 @@ +{{extend 'layout.html'}} +

Ajax Wiki

+{{=form}} +{{=html}} From 761d7fd82471fcd7eaf4761a6c13f39779c6f27e Mon Sep 17 00:00:00 2001 From: niphlod Date: Fri, 30 Aug 2013 22:46:36 +0200 Subject: [PATCH 04/29] fix anyserver with logging and/or profiler --- anyserver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/anyserver.py b/anyserver.py index 4ec14b28..cfca6585 100644 --- a/anyserver.py +++ b/anyserver.py @@ -189,7 +189,7 @@ def run(servername, ip, port, softcron=True, logging=False, profiler=None): if logging: application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, logfilename='httpserver.log', - profilerfilename=profiler) + profiler_dir=profiler) else: application = gluon.main.wsgibase if softcron: @@ -319,8 +319,8 @@ def main(): parser.add_option('-P', '--profiler', default=False, - dest='profiler', - help='profiler filename') + dest='profiler_dir', + help='profiler dir') servers = ', '.join(x for x in dir(Servers) if not x[0] == '_') parser.add_option('-s', '--server', @@ -346,7 +346,7 @@ def main(): print 'starting %s on %s:%s...' % ( options.server, options.ip, options.port) run(options.server, options.ip, options.port, - logging=options.logging, profiler=options.profiler) + logging=options.logging, profiler=options.profiler_dir) if __name__ == '__main__': main() From 1f9071d5df50e61d9fe642d66041063c89451a81 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 30 Aug 2013 18:29:27 -0500 Subject: [PATCH 05/29] moved options_std.py to examples, thanks Niphlod --- VERSION | 2 +- options_std.py => examples/options_std.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename options_std.py => examples/options_std.py (100%) diff --git a/VERSION b/VERSION index 46c0f5e5..2b777e39 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.08.29.21.31.47 +Version 2.6.0-development+timestamp.2013.08.30.18.28.02 diff --git a/options_std.py b/examples/options_std.py similarity index 100% rename from options_std.py rename to examples/options_std.py From d61bbc6a5f34336532c49faf046c794d0e2d67bf Mon Sep 17 00:00:00 2001 From: niphlod Date: Sat, 31 Aug 2013 14:23:35 +0200 Subject: [PATCH 06/29] fix scripts fetching handlers from /handlers --- ...2-04-redmine-unicorn-web2py-uwsgi-nginx.sh | 3 +- scripts/setup-web2py-fedora-ami.sh | 1 + scripts/setup-web2py-fedora.sh | 1 + scripts/setup-web2py-nginx-uwsgi-centos64.sh | 86 +++++++++---------- scripts/setup-web2py-nginx-uwsgi-on-centos.sh | 1 + scripts/setup-web2py-nginx-uwsgi-opensuse.sh | 23 +++-- scripts/setup-web2py-nginx-uwsgi-ubuntu.sh | 3 +- scripts/setup-web2py-ubuntu.sh | 1 + 8 files changed, 60 insertions(+), 59 deletions(-) diff --git a/scripts/setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh b/scripts/setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh index 5c13c83d..7042029b 100644 --- a/scripts/setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh +++ b/scripts/setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh @@ -453,10 +453,9 @@ cd /home/www-data 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 /home/www-data/web2py/scripts/sessions2trash.py chown -R www-data:www-data web2py cd /home/www-data/web2py +mv handlers/wsgihandler.py wsgihandler.py sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)" /etc/init.d/redmine start start uwsgi-emperor diff --git a/scripts/setup-web2py-fedora-ami.sh b/scripts/setup-web2py-fedora-ami.sh index 48b78a9f..a36fbcc4 100755 --- a/scripts/setup-web2py-fedora-ami.sh +++ b/scripts/setup-web2py-fedora-ami.sh @@ -159,6 +159,7 @@ fi wget http://web2py.com/examples/static/web2py_src.zip unzip web2py_src.zip +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py chown -R apache:apache web2py ### diff --git a/scripts/setup-web2py-fedora.sh b/scripts/setup-web2py-fedora.sh index 41184d46..801ce8ea 100644 --- a/scripts/setup-web2py-fedora.sh +++ b/scripts/setup-web2py-fedora.sh @@ -159,6 +159,7 @@ fi wget http://web2py.com/examples/static/web2py_src.zip unzip web2py_src.zip +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py chown -R apache:apache web2py ### diff --git a/scripts/setup-web2py-nginx-uwsgi-centos64.sh b/scripts/setup-web2py-nginx-uwsgi-centos64.sh index bd794282..c6148e4b 100644 --- a/scripts/setup-web2py-nginx-uwsgi-centos64.sh +++ b/scripts/setup-web2py-nginx-uwsgi-centos64.sh @@ -3,53 +3,53 @@ echo 'setup-web2py-nginx-uwsgi-centos64.sh' echo 'Support CentOS 6.4' echo 'Installs Nginx 1.4.1 + uWSGI + Web2py' - - + + # Get Web2py Admin Password echo -e "Web2py Admin Password: \c " read PW - + echo -e "Set Server Name Ex: web2py.domain.com : \c " read SERVER_FQDN - + echo -e "Set Server IP: \c " read SERVER_IP - - + + echo "" >>/etc/hosts echo "$SERVER_IP $SERVER_FQDN" >>/etc/hosts - + yum update -y - + yum install -y http://mirror-fpt-telecom.fpt.net/fedora/epel/6/i386/epel-release-6-8.noarch.rpm yum clean all yum install -y gcc libxml2-devel python-devel python-pip PyXML unzip make sudo - + ## 64Bits System -## yum install -y http://nginx.org/packages/rhel/6/x86_64/RPMS/nginx-1.4.1-1.el6.ngx.x86_64.rpm +## yum install -y http://nginx.org/packages/rhel/6/x86_64/RPMS/nginx-1.4.1-1.el6.ngx.x86_64.rpm yum install -y http://nginx.org/packages/rhel/6/i386/RPMS/nginx-1.4.1-1.el6.ngx.i386.rpm - - + + pip-python install --upgrade pip PIPPATH=`which pip` $PIPPATH install --upgrade uwsgi - - + + # Prepare folders for uwsgi mkdir /etc/uwsgi mkdir /var/log/uwsgi mkdir -p /var/www/ - + #usermod -a -G apache nginx 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 YOUR_SERVER_IP:80; server_name YOUR_SERVER_FQDN; @@ -97,13 +97,13 @@ server { #client_max_body_size 10m; ### } - + }' >/etc/nginx/conf.d/web2py.conf - + sed -i "s/YOUR_SERVER_IP/$SERVER_IP/" /etc/nginx/conf.d/web2py.conf sed -i "s/YOUR_SERVER_FQDN/$SERVER_FQDN/" /etc/nginx/conf.d/web2py.conf - - + + # Create configuration file /etc/uwsgi/web2py.ini echo '[uwsgi] @@ -126,21 +126,21 @@ cron = 0 0 -1 -1 -1 python /var/www/web2py/web2py.py -Q -S welcome -M -R scripts no-orphans = true chmod-socket = 666 ' >/etc/uwsgi/web2py.ini - - + + cd /var/www/ curl --progress -O http://web2py.com/examples/static/web2py_src.zip unzip web2py_src.zip && rm -rf web2py_src.zip # Download latest version of sessions2trash.py -curl --output /var/www/web2py/scripts/sessions2trash.py http://web2py.googlecode.com/hg/scripts/sessions2trash.py +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py chown -R nginx:nginx web2py cd /var/www/web2py sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)" - - - + + + ## Daemons /start/stop - + echo '#!/bin/sh # Autor: Nilton OS -- www.linuxpro.com.br # @@ -154,27 +154,27 @@ echo '#!/bin/sh # Default-Start: 3 5 # Default-Stop: 0 1 2 6 ### END INIT INFO - + # Source function library. . /etc/rc.d/init.d/functions - + # Check for missing binaries (stale symlinks should not happen) UWSGI_BIN=`which uwsgi` -test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed"; +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="--enable-threads --logto /var/log/uwsgi/uwsgi.log" lockfile=/var/lock/subsys/uwsgi - + UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload" - + if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS" fi - + case "$1" in start) echo -n "Starting uWSGI " @@ -198,19 +198,19 @@ case "$1" in ;; esac exit 0 '> /etc/init.d/uwsgi - + chmod +x /etc/init.d/uwsgi - + /etc/init.d/uwsgi start /etc/init.d/nginx start - + /etc/init.d/iptables stop chkconfig --del iptables - + chkconfig --levels 235 uwsgi on chkconfig --levels 235 nginx on - + ## you can reload uwsgi with #/etc/init.d/uwsgi restart ## to reload web2py only (without restarting uwsgi) -# touch /etc/uwsgi/web2py.ini \ No newline at end of file +# touch /etc/uwsgi/web2py.ini diff --git a/scripts/setup-web2py-nginx-uwsgi-on-centos.sh b/scripts/setup-web2py-nginx-uwsgi-on-centos.sh index 74461aac..9d3c8db3 100644 --- a/scripts/setup-web2py-nginx-uwsgi-on-centos.sh +++ b/scripts/setup-web2py-nginx-uwsgi-on-centos.sh @@ -122,6 +122,7 @@ mkdir ./web-apps cd ./web-apps curl -O http://www.web2py.com/examples/static/web2py_src.zip unzip web2py_src.zip +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py echo "Set the ownership for web2py application to uwsgi" chown -R uwsgi /opt/web-apps/web2py diff --git a/scripts/setup-web2py-nginx-uwsgi-opensuse.sh b/scripts/setup-web2py-nginx-uwsgi-opensuse.sh index dd9e56dd..128ad2d0 100644 --- a/scripts/setup-web2py-nginx-uwsgi-opensuse.sh +++ b/scripts/setup-web2py-nginx-uwsgi-opensuse.sh @@ -71,7 +71,7 @@ server { uwsgi_param UWSGI_SCHEME $scheme; uwsgi_param SERVER_SOFTWARE nginx/$nginx_version; } - + }' >/etc/nginx/vhosts.d/web2py.conf @@ -102,8 +102,7 @@ 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 +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.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)" @@ -137,7 +136,7 @@ echo '#!/bin/sh # Check for missing binaries (stale symlinks should not happen) UWSGI_BIN=/usr/bin/uwsgi -test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed"; +test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed"; if [ "$1" = "stop" ]; then exit 0; else exit 5; fi; } @@ -150,13 +149,13 @@ 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 +fi + +. /etc/rc.status + +rc_reset + +case "$1" in start) echo -n "Starting uWSGI " /sbin/startproc $UWSGI_BIN $UWSGI_OPTIONS @@ -219,7 +218,7 @@ chmod +x /etc/init.d/uwsgi chkconfig --add uwsgi chkconfig --add nginx - + ## you can reload uwsgi with #/etc/init.d/uwsgi restart ## to reload web2py only (without restarting uwsgi) diff --git a/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh b/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh index 21ec33c2..32c2f61c 100644 --- a/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh +++ b/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh @@ -169,9 +169,8 @@ mkdir /home/www-data cd /home/www-data wget http://web2py.com/examples/static/web2py_src.zip unzip web2py_src.zip +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py rm web2py_src.zip -# Download latest version of sessions2trash.py -wget http://web2py.googlecode.com/hg/scripts/sessions2trash.py -O /home/www-data/web2py/scripts/sessions2trash.py chown -R www-data:www-data web2py cd /home/www-data/web2py sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)" diff --git a/scripts/setup-web2py-ubuntu.sh b/scripts/setup-web2py-ubuntu.sh index 7a8f4126..b4ef9901 100755 --- a/scripts/setup-web2py-ubuntu.sh +++ b/scripts/setup-web2py-ubuntu.sh @@ -59,6 +59,7 @@ cd www-data rm web2py_src.zip* wget http://web2py.com/examples/static/web2py_src.zip unzip web2py_src.zip +mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py chown -R www-data:www-data web2py echo "setting up apache modules" From 735357da1d60e057901bb1496b37ce9bfce29d88 Mon Sep 17 00:00:00 2001 From: niphlod Date: Sun, 1 Sep 2013 01:02:48 +0200 Subject: [PATCH 07/29] seems that --use-mirrors is ATM borked. Anyway, --use-mirrors is going to de deprecated in future releases of pip. Pypi infrastructure is more reliable than a year ago, let's hope we don't incur in any downtimes. --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ee324e3..40a1af6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,17 +6,17 @@ python: - '2.7' - 'pypy' install: - - pip install -e . --use-mirrors + - pip install -e . env: - DB=sqlite:memory - DB=mysql://root:@localhost/test_w2p - DB=postgres://postgres:@localhost/test_w2p before_script: - - if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install --use-mirrors unittest2; fi - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors coverage; fi; - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors python-coveralls; fi - - if [[ $DB == postgres* ]]; then pip install --use-mirrors psycopg2; fi; - - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install --use-mirrors pysqlite; fi + - if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install unittest2; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install coverage; fi; + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install python-coveralls; fi + - if [[ $DB == postgres* ]]; then pip install psycopg2; fi; + - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi - if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi #Temporal solution to travis issue #155 From dd7609a22acff9729ebd97bfe1380b69b3044412 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 07:44:11 -0500 Subject: [PATCH 08/29] partially fixed issue 1502 --- VERSION | 2 +- gluon/fileutils.py | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 2b777e39..356a6de8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.08.30.18.28.02 +Version 2.6.0-development+timestamp.2013.09.01.07.43.11 diff --git a/gluon/fileutils.py b/gluon/fileutils.py index 0f3953bb..7c1942da 100644 --- a/gluon/fileutils.py +++ b/gluon/fileutils.py @@ -360,12 +360,21 @@ def get_session(request, other_application='admin'): raise KeyError try: session_id = request.cookies['session_id_' + other_application].value - osession = storage.load_storage(os.path.join( - up(request.folder), other_application, 'sessions', session_id)) + session_filename = os.path.join( + up(request.folder), other_application, 'sessions', session_id) + osession = storage.load_storage(session_filename) except Exception, e: osession = storage.Storage() return osession +def set_session(request, session, other_application='admin'): + """ checks that user is authorized to access other_application""" + if request.application == other_application: + raise KeyError + session_id = request.cookies['session_id_' + other_application].value + session_filename = os.path.join( + up(request.folder), other_application, 'sessions', session_id) + storage.save_storage(session,session_filename) def check_credentials(request, other_application='admin', expiration=60 * 60, gae_login=True): @@ -381,9 +390,14 @@ def check_credentials(request, other_application='admin', else: return False else: - dt = time.time() - expiration + t0 = time.time() + dt = t0 - expiration s = get_session(request, other_application) - return (s.authorized and s.last_time and s.last_time > dt) + r = (s.authorized and s.last_time and s.last_time > dt) + if r: + s.last_time = t0 + set_session(request,s,other_application) + return r def fix_newlines(path): From 8cff939fb6b5a3fdbe17c760ad27c306c3ece901 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 07:55:17 -0500 Subject: [PATCH 09/29] fixed issue 1658, unicode in admin/languages/de.py --- VERSION | 2 +- applications/admin/languages/de.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 356a6de8..219dd5ab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.07.43.11 +Version 2.6.0-development+timestamp.2013.09.01.07.54.28 diff --git a/applications/admin/languages/de.py b/applications/admin/languages/de.py index d5750418..79ad07db 100644 --- a/applications/admin/languages/de.py +++ b/applications/admin/languages/de.py @@ -248,6 +248,7 @@ 'Next Edit Point': 'nächster Bearbeitungsschritt', 'NO': 'NEIN', 'No databases in this application': 'Keine Datenbank in dieser Anwendung', +'no package selected': 'no package selected', 'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder', 'online designer': 'online designer', 'or alternatively': 'or alternatively', From ac3e1fc84d0185eece6631e3cb789775d429be4f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 08:07:29 -0500 Subject: [PATCH 10/29] fixed issue 1657 --- VERSION | 2 +- gluon/tools.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index 219dd5ab..497bbb0c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.07.54.28 +Version 2.6.0-development+timestamp.2013.09.01.08.06.40 diff --git a/gluon/tools.py b/gluon/tools.py index 11f02c5e..53508bf1 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1304,8 +1304,9 @@ class Auth(object): else: raise HTTP(404) - def navbar(self, mode='Default', action=None, prefix='Welcome', - referrer_actions=DEFAULT, user_identifier=DEFAULT): + def navbar(self, prefix='Welcome', action=None, + separators=(' [ ', ' | ', ' ] '), user_identifier=DEFAULT, + referrer_actions=DEFAULT, mode='default'): """ Navbar with support for more templates This uses some code from the old navbar. @@ -1490,21 +1491,21 @@ class Auth(object): 'bare': bare } # Define custom modes. - try: + if mode in options and callable(options[mode]): options[mode]() - except KeyError: # KeyError if mode is not in options (do Default) + else: + s1, s2, s3 = separators if self.user_id: - self.bar = SPAN(prefix, user_identifier, '[', + self.bar = SPAN(prefix, user_identifier, s1, Anr(items[0]['name'], - _href=items[0]['href']), ']', + _href=items[0]['href']), s3, _class='auth_navbar') else: - self.bar = SPAN('[', Anr(items[0]['name'], - _href=items[0]['href']), ']', + self.bar = SPAN(s1, Anr(items[0]['name'], + _href=items[0]['href']), s3, _class='auth_navbar') - del items[0] for item in items: - self.bar.insert(-1, ']') + self.bar.insert(-1, s2) self.bar.insert(-1, Anr(item['name'], _href=item['href'])) return self.bar From 6097a00bf136413d6be26092510d345ae6741a96 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 09:35:30 -0500 Subject: [PATCH 11/29] possibly fixed issue 1637:when trying to use IS_IN_DB with a composite reference key, there is a no _id error reported --- VERSION | 2 +- gluon/dal.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 497bbb0c..fa9cee3a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.08.06.40 +Version 2.6.0-development+timestamp.2013.09.01.09.34.39 diff --git a/gluon/dal.py b/gluon/dal.py index 1edd16b8..44ee1d61 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -690,7 +690,11 @@ class BaseAdapter(ConnectionPool): return isinstance(exception, self.driver.ProgrammingError) def id_query(self, table): - return table._id != None + pkeys = getattr(table,'_primarykey',None) + if pkeys: + return table[pkeys[0]] != None + else: + return table._id != None def adapt(self, obj): return "'%s'" % obj.replace("'", "''") From c87482bdeecee473c19ffaba4cb28c6ceff43f99 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 10:24:00 -0500 Subject: [PATCH 12/29] fixed issue 1590 --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 64 +++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index fa9cee3a..22718795 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.09.34.39 +Version 2.6.0-development+timestamp.2013.09.01.10.23.10 diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index e0854ccc..1fe4f9f7 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -4,6 +4,7 @@ # recreated by Vladyslav Kozlovskyy # license MIT/BSD/GPL import re +import ast from cgi import escape from string import maketrans @@ -532,7 +533,7 @@ LINK = '\x07' DISABLED_META = '\x08' LATEX = '' regex_URL=re.compile(r'@/(?P\w*)/(?P\w*)/(?P\w*(\.\w+)?)(/(?P[\w\.\-/]+))?') -regex_env=re.compile(r'@\{(?P[\w\-\.]+?)(\:(?P.*?))?\}') +regex_env2=re.compile(r'@\{(?P[\w\-\.]+?)(\:(?P.*?))?\}') regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+'|````)') regex_dd=re.compile(r'\$\$(?P.*?)\$\$') regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P.+?)``(?::(?P[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P

[^\]]*)\])?)?)',re.S) @@ -553,6 +554,53 @@ regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])") regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])") ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05') ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05',"'`:*~\\[]{}@$+-.#\n") +regex_quote = re.compile('(?P\w+?)\s*\=\s*') + +def make_dict(b): + return '{%s}' % regex_quote.sub("'\g':",b) + +def safe_eval(node_or_string, env): + """ + Safely evaluate an expression node or a string containing a Python + expression. The string or node provided may only consist of the following + Python literal structures: strings, numbers, tuples, lists, dicts, booleans, + and None. + """ + _safe_names = {'None': None, 'True': True, 'False': False} + _safe_names.update(env) + if isinstance(node_or_string, basestring): + node_or_string = ast.parse(node_or_string, mode='eval') + if isinstance(node_or_string, ast.Expression): + node_or_string = node_or_string.body + def _convert(node): + if isinstance(node, ast.Str): + return node.s + elif isinstance(node, ast.Num): + return node.n + elif isinstance(node, ast.Tuple): + return tuple(map(_convert, node.elts)) + elif isinstance(node, ast.List): + return list(map(_convert, node.elts)) + elif isinstance(node, ast.Dict): + return dict((_convert(k), _convert(v)) for k, v + in zip(node.keys, node.values)) + elif isinstance(node, ast.Name): + if node.id in _safe_names: + return _safe_names[node.id] + elif isinstance(node, ast.BinOp) and \ + isinstance(node.op, (Add, Sub)) and \ + isinstance(node.right, Num) and \ + isinstance(node.right.n, complex) and \ + isinstance(node.left, Num) and \ + isinstance(node.left.n, (int, long, float)): + left = node.left.n + right = node.right.n + if isinstance(node.op, Add): + return left + right + else: + return left - right + raise ValueError('malformed string') + return _convert(node_or_string) def markmin_escape(text): """ insert \\ before markmin control characters: '`:*~[]{}@$ """ @@ -571,15 +619,22 @@ def replace_at_urls(text,url): return regex_URL.sub(u1,text) def replace_components(text,env): + # not perfect but acceptable def u2(match, env=env): f = env.get(match.group('a'), match.group(0)) if callable(f): + b = match.group('b') try: - f = f(match.group('b')) + b = safe_eval(make_dict(b),env) + except: + pass + try: + f = f(**b) if isinstance(b,dict) else f(b) except Exception, e: f = 'ERROR: %s' % e return str(f) - return regex_env.sub(u2, text) + text = regex_env2.sub(u2, text) + return text def autolinks_simple(url): """ @@ -844,6 +899,9 @@ def render(text, >>> render("**@{probe:1}**", environment=dict(probe=lambda t:"test %s" % t)) '

test 1

' + >>> render("**@{probe:t=a}**", environment=dict(probe=lambda t:"test %s" % t, a=1)) + '

test 1

' + >>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]') '
' From 085b4a0d15b173d84d92e2ed2b6db30923d2f643 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 11:17:16 -0500 Subject: [PATCH 13/29] fixed issue 1616:possible problem with hideerror for list:string --- VERSION | 2 +- gluon/html.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 22718795..97471f2c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.10.23.10 +Version 2.6.0-development+timestamp.2013.09.01.11.16.29 diff --git a/gluon/html.py b/gluon/html.py index da092a33..1239b448 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -841,8 +841,8 @@ class DIV(XmlComponent): c.latest = self.latest c.session = self.session c.formname = self.formname - c['hideerror'] = hideerror or \ - self.attributes.get('hideerror', False) + if not c.attributes.get('hideerror'): + c['hideerror'] = hideerror or self.attributes.get('hideerror') newstatus = c._traverse(status, hideerror) and newstatus # for input, textarea, select, option From a68ac572ab4b38c73df8ea16a8ad4905b3d29a3b Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 12:38:11 -0500 Subject: [PATCH 14/29] Wiki(..., force_render=True) --- VERSION | 2 +- gluon/tools.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index 97471f2c..b2b88b6c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.11.16.29 +Version 2.6.0-development+timestamp.2013.09.01.12.37.17 diff --git a/gluon/tools.py b/gluon/tools.py index 53508bf1..2e042138 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -3585,7 +3585,8 @@ class Auth(object): templates=None, migrate=True, controller=None, - function=None): + function=None, + force_render=False): if controller and function: resolve = False @@ -3609,7 +3610,7 @@ class Auth(object): if resolve: action = str(current.request.args(0)).startswith("_") if slug and not action: - wiki = self._wiki.read(slug) + wiki = self._wiki.read(slug,force_render) if isinstance(wiki, dict) and wiki.has_key('content'): # We don't want to return a dict object, just the wiki wiki = wiki['content'] @@ -5190,7 +5191,7 @@ class Wiki(object): controller, function, args = items[0], items[1], items[2:] return LOAD(controller, function, args=args, ajax=True).xml() - def get_render(self): + def get_renderer(self): if isinstance(self.settings.render, basestring): r = getattr(self, "%s_render" % self.settings.render) elif callable(self.settings.render): @@ -5251,7 +5252,7 @@ class Wiki(object): default=[Wiki.everybody]), Field('changelog'), Field('html', 'text', - compute=self.get_render(), + compute=self.get_renderer(), readable=False, writable=False), auth.signature], 'vars':{'format':'%(title)s', 'migrate':migrate}}), @@ -5404,7 +5405,7 @@ class Wiki(object): elif zero == '_cloud': return self.cloud() elif zero == '_preview': - return self.preview(self.get_render()) + return self.preview(self.get_renderer()) def first_paragraph(self, page): if not self.can_read(page): @@ -5418,7 +5419,7 @@ class Wiki(object): def fix_hostname(self, body): return (body or '').replace('://HOSTNAME', '://%s' % self.host) - def read(self, slug): + def read(self, slug, force_render=False): if slug in '_cloud': return self.cloud() elif slug in '_search': @@ -5433,10 +5434,12 @@ class Wiki(object): url = URL(args=('_edit', slug)) return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn")) else: + html = page.html if not force_render else self.get_renderer(page) + content = XML(self.fix_hostname(html)) return dict(title=page.title, slug=page.slug, page=page, - content=XML(self.fix_hostname(page.html)), + content=content, tags=page.tags, created_on=page.created_on, modified_on=page.modified_on) From 866d6c6db496fc2837bd1883c39e793edb4863f5 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 12:41:24 -0500 Subject: [PATCH 15/29] admin design page should not show more than 1000 files --- VERSION | 2 +- applications/admin/controllers/default.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index b2b88b6c..65e2fffd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.12.37.17 +Version 2.6.0-development+timestamp.2013.09.01.12.40.14 diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 1cdc3dd4..6aefe163 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -1016,8 +1016,9 @@ def design(): privates.sort() # Get all static files + MAXNFILES = 1000 statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') - statics = [x.replace('\\', '/') for x in statics] + statics = [x.replace('\\', '/') for x in statics[:MAXNFILES]] statics.sort() # Get all languages From b7f46c969f877d8ff6ee1d81032dcd1b5d572e45 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 12:44:02 -0500 Subject: [PATCH 16/29] multiple renderers for wiki (issue 1298), thanks Alan and sorry this took forever --- VERSION | 2 +- gluon/tools.py | 41 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 65e2fffd..c14ef9ff 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.12.40.14 +Version 2.6.0-development+timestamp.2013.09.01.12.42.52 diff --git a/gluon/tools.py b/gluon/tools.py index 2e042138..aae48d53 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -5196,8 +5196,13 @@ class Wiki(object): r = getattr(self, "%s_render" % self.settings.render) elif callable(self.settings.render): r = self.settings.render + elif isinstance(self.settings.render, dict): + return lambda page: self.settings.render.get(page.render, + getattr(self, + "%s_render" % (page.render or 'markmin')))(page) else: - raise ValueError("Invalid render type %s" % type(render)) + raise ValueError( + "Invalid render type %s" % type(self.settings.render)) return r def __init__(self, auth, env=None, render='markmin', @@ -5208,7 +5213,24 @@ class Wiki(object): settings = self.settings = auth.settings.wiki - # render: "markmin", "html", ..., + """render argument options: + - "markmin" + - "html" + - + Sets a custom render function + - dict(html=, markmin=...): + dict(...) allows multiple custom render functions + - "multiple" + Is the same as {}. It enables per-record formats + using builtins + """ + engines = set(['markmin', 'html']) + show_engine = False + if render == "multiple": + render = {} + if isinstance(render, dict): + [engines.add(key) for key in render] + show_engine = True settings.render = render perms = settings.manage_permissions = manage_permissions @@ -5254,6 +5276,11 @@ class Wiki(object): Field('html', 'text', compute=self.get_renderer(), readable=False, writable=False), + Field('render', default="markmin", + readable=show_engine, + writable=show_engine, + requires=IS_EMPTY_OR( + IS_IN_SET(engines))), auth.signature], 'vars':{'format':'%(title)s', 'migrate':migrate}}), ('wiki_tag', { @@ -5528,7 +5555,11 @@ class Wiki(object): if (prevbutton.hasClass('nopreview')) { prevbutton.addClass('preview').removeClass( 'nopreview').html('Edit Source'); - web2py_ajax_page('post', '%(url)s', {body : jQuery('#wiki_page_body').val()}, 'preview'); + try{var wiki_render = jQuery('#wiki_page_render').val()} + catch(e){var wiki_render = null;} + web2py_ajax_page('post', \ + '%(url)s', {body: jQuery('#wiki_page_body').val(), \ + render: wiki_render}, 'preview'); form.fadeOut('fast', function() {preview.fadeIn()}); } else { prevbutton.addClass( @@ -5802,6 +5833,10 @@ class Wiki(object): def preview(self, render): request = current.request + # FIXME: This is an ugly hack to ensure a default render + # engine if not specified (with multiple render engines) + if not "render" in request.post_vars: + request.post_vars.render = None return render(request.post_vars) From ca85afeb2f00a7d0de389f3a831503de824fdf53 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 13:12:30 -0500 Subject: [PATCH 17/29] fixed issue 1342:MySQL, self-reference, and null values --- VERSION | 2 +- gluon/dal.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index c14ef9ff..df3ca77e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.12.42.52 +Version 2.6.0-development+timestamp.2013.09.01.13.11.38 diff --git a/gluon/dal.py b/gluon/dal.py index 44ee1d61..4c841800 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6943,9 +6943,11 @@ def sqlhtml_validators(field): referenced._format,multiple=True) else: requires = validators.IS_IN_DB(db,referenced._id, - multiple=True) + 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 elif field_type.startswith('list:'): def repr_list(values,row=None): return', '.join(str(v) for v in (values or [])) From 9729005fffc56666d4a2edf6d5c823fc1f114a5c Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 1 Sep 2013 13:35:17 -0500 Subject: [PATCH 18/29] better import errors (issue 1368) --- VERSION | 2 +- gluon/dal.py | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index df3ca77e..3eb3cd3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.13.11.38 +Version 2.6.0-development+timestamp.2013.09.01.13.34.22 diff --git a/gluon/dal.py b/gluon/dal.py index 4c841800..8fe636e7 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -8881,24 +8881,31 @@ class Table(object): first = True unique_idx = None - for line in reader: + for lineno, line in enumerate(reader): if not line: break if not colnames: + # assume this is the first line of the input, contains colnames colnames = [x.split('.',1)[-1] for x in line][:len(line)] cols, cid = [], None for i,colname in enumerate(colnames): if is_id(colname): cid = i - else: - cols.append(i) + elif colname in self.fields: + cols.append((i,self[colname])) if colname == unique: unique_idx = i else: - items = [fix(self[colnames[i]], line[i], id_map, id_offset) \ - for i in cols if colnames[i] in self.fields] - - if not id_map and cid is not None and id_offset is not None and not unique_idx: + # every other line contains instead data + items = [] + for i, field in cols: + try: + items.append(fix(field, line[i], id_map, id_offset)) + except ValueError: + raise RuntimeError("Unable to parse line:%s field:%s value:'%s'" + % (lineno+1,field,line[i])) + + if not (id_map or cid is None or id_offset is None or unique_idx): csv_id = long(line[cid]) curr_id = self.insert(**dict(items)) if first: @@ -8906,10 +8913,8 @@ class Table(object): # First curr_id is bigger than csv_id, # then we are not restoring but # extending db table with csv db table - if curr_id>csv_id: - id_offset[self._tablename] = curr_id-csv_id - else: - id_offset[self._tablename] = 0 + id_offset[self._tablename] = (curr_id-csv_id) \ + if curr_id>csv_id else 0 # create new id until we get the same as old_id+offset while curr_id Date: Sun, 1 Sep 2013 14:14:20 -0500 Subject: [PATCH 19/29] fixed issue 813:archive table current_record field label is translated by default --- VERSION | 2 +- gluon/dal.py | 6 ++-- gluon/tools.py | 91 +++++++++++++++++++++++++++----------------------- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/VERSION b/VERSION index 3eb3cd3f..e29beaca 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.13.34.22 +Version 2.6.0-development+timestamp.2013.09.01.14.13.25 diff --git a/gluon/dal.py b/gluon/dal.py index 8fe636e7..61b3a0a8 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -8422,8 +8422,9 @@ class Table(object): def _enable_record_versioning(self, archive_db=None, archive_name = '%(tablename)s_archive', + is_active = 'is_active', current_record = 'current_record', - is_active = 'is_active'): + current_record_label = None): db = self._db archive_db = archive_db or db archive_name = archive_name % dict(tablename=self._tablename) @@ -8438,7 +8439,8 @@ class Table(object): clones.append(field.clone( unique=False, type=field.type if nfk else 'bigint')) archive_db.define_table( - archive_name, Field(current_record,field_type), *clones) + archive_name, Field(current_record,field_type, + label=current_record_label), *clones) self._before_update.append( lambda qset,fs,db=archive_db,an=archive_name,cn=current_record: archive_record(qset,fs,db[an],cn)) diff --git a/gluon/tools.py b/gluon/tools.py index aae48d53..1834b310 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1523,7 +1523,8 @@ class Auth(object): tables, archive_db=None, archive_names='%(tablename)s_archive', - current_record='current_record'): + current_record='current_record', + current_record_label=None): """ to enable full record versioning (including auth tables): @@ -1550,6 +1551,8 @@ class Auth(object): does automatically. """ + current_record_label = current_record_label or current.T( + current_record.replace('_',' ').title()) for table in tables: fieldnames = table.fields() if ('id' in fieldnames and @@ -1558,7 +1561,8 @@ class Auth(object): table._enable_record_versioning( archive_db=archive_db, archive_name=archive_names, - current_record=current_record) + current_record=current_record, + current_record_label=current_record_label) def define_signature(self): db = self.db @@ -2158,12 +2162,17 @@ class Auth(object): elif 'username' in table_user.fields: username = 'username' else: - username = 'email' + username = 'email' + settings = self.settings if 'username' in table_user.fields or \ - not self.settings.login_email_validate: + not settings.login_email_validate: tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty) + if not settings.username_case_sensitive: + tmpvalidator = [IS_LOWER(), tmpvalidator] else: tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email) + if not settings.email_case_sensitive: + tmpvalidator = [IS_LOWER(), tmpvalidator] old_requires = table_user[username].requires table_user[username].requires = tmpvalidator @@ -2171,7 +2180,7 @@ class Auth(object): response = current.response session = current.session - passfield = self.settings.password_field + passfield = settings.password_field try: table_user[passfield].requires[-1].min_length = 0 except: @@ -2187,43 +2196,43 @@ class Auth(object): if next is DEFAULT: # important for security - next = self.settings.login_next + next = settings.login_next user_next = snext if user_next: external = user_next.split('://') if external[0].lower() in ['http', 'https', 'ftp']: host_next = user_next.split('//', 1)[-1].split('/')[0] - if host_next in self.settings.cas_domains: + if host_next in settings.cas_domains: next = user_next else: next = user_next if onvalidation is DEFAULT: - onvalidation = self.settings.login_onvalidation + onvalidation = settings.login_onvalidation if onaccept is DEFAULT: - onaccept = self.settings.login_onaccept + onaccept = settings.login_onaccept if log is DEFAULT: log = self.messages['login_log'] - onfail = self.settings.login_onfail + onfail = settings.login_onfail user = None # default # do we use our own login form, or from a central source? - if self.settings.login_form == self: + if settings.login_form == self: form = SQLFORM( table_user, fields=[username, passfield], hidden=dict(_next=next), - showid=self.settings.showid, + showid=settings.showid, submit_button=self.messages.login_button, delete_label=self.messages.delete_label, - formstyle=self.settings.formstyle, - separator=self.settings.label_separator + formstyle=settings.formstyle, + separator=settings.label_separator ) - if self.settings.remember_me_form: + if settings.remember_me_form: ## adds a new input checkbox "remember me for longer" - if self.settings.formstyle != 'bootstrap': + if settings.formstyle != 'bootstrap': addrow(form, XML(" "), DIV(XML(" "), INPUT(_type='checkbox', @@ -2236,9 +2245,9 @@ class Auth(object): self.messages.label_remember_me, _for="auth_user_remember", )), "", - self.settings.formstyle, + settings.formstyle, 'auth_user_remember__row') - elif self.settings.formstyle == 'bootstrap': + elif settings.formstyle == 'bootstrap': addrow(form, "", LABEL( @@ -2248,20 +2257,20 @@ class Auth(object): self.messages.label_remember_me, _class="checkbox"), "", - self.settings.formstyle, + settings.formstyle, 'auth_user_remember__row') - captcha = self.settings.login_captcha or \ - (self.settings.login_captcha != False and self.settings.captcha) + captcha = settings.login_captcha or \ + (settings.login_captcha != False and settings.captcha) if captcha: addrow(form, captcha.label, captcha, captcha.comment, - self.settings.formstyle, 'captcha__row') + settings.formstyle, 'captcha__row') accepted_form = False if form.accepts(request, session, formname='login', dbio=False, onvalidation=onvalidation, - hideerror=self.settings.hideerror): + hideerror=settings.hideerror): accepted_form = True # check for username in db @@ -2283,36 +2292,36 @@ class Auth(object): # try alternate logins 1st as these have the # current version of the password user = None - for login_method in self.settings.login_methods: + for login_method in settings.login_methods: if login_method != self and \ login_method(request.vars[username], request.vars[passfield]): - if not self in self.settings.login_methods: + if not self in settings.login_methods: # do not store password in db form.vars[passfield] = None user = self.get_or_create_user( - form.vars, self.settings.update_fields) + form.vars, settings.update_fields) break if not user: # alternates have failed, maybe because service inaccessible - if self.settings.login_methods[0] == self: + if settings.login_methods[0] == self: # try logging in locally using cached credentials if form.vars.get(passfield, '') == temp_user[passfield]: # success user = temp_user else: # user not in db - if not self.settings.alternate_requires_registration: + if not settings.alternate_requires_registration: # we're allowed to auto-register users from external systems - for login_method in self.settings.login_methods: + for login_method in settings.login_methods: if login_method != self and \ login_method(request.vars[username], request.vars[passfield]): - if not self in self.settings.login_methods: + if not self in settings.login_methods: # do not store password in db form.vars[passfield] = None user = self.get_or_create_user( - form.vars, self.settings.update_fields) + form.vars, settings.update_fields) break if not user: self.log_event(self.messages['login_failed_log'], @@ -2322,25 +2331,25 @@ class Auth(object): callback(onfail, None) redirect( self.url(args=request.args, vars=request.get_vars), - client_side=self.settings.client_side) + client_side=settings.client_side) else: # use a central authentication server - cas = self.settings.login_form + cas = settings.login_form cas_user = cas.get_user() if cas_user: cas_user[passfield] = None user = self.get_or_create_user( table_user._filter_fields(cas_user), - self.settings.update_fields) + settings.update_fields) elif hasattr(cas, 'login_form'): return cas.login_form() else: # we need to pass through login again before going on - next = self.url(self.settings.function, args='login') + next = self.url(settings.function, args='login') redirect(cas.login_url(next), - client_side=self.settings.client_side) + client_side=settings.client_side) # process authenticated users if user: @@ -2350,20 +2359,20 @@ class Auth(object): self.login_user(user) session.auth.expiration = \ request.vars.get('remember', False) and \ - self.settings.long_expiration or \ - self.settings.expiration + settings.long_expiration or \ + settings.expiration session.auth.remember = 'remember' in request.vars self.log_event(log, user) session.flash = self.messages.logged_in # how to continue - if self.settings.login_form == self: + if settings.login_form == self: if accepted_form: callback(onaccept, form) if next == session._auth_next: session._auth_next = None next = replace_id(next, form) - redirect(next, client_side=self.settings.client_side) + redirect(next, client_side=settings.client_side) table_user[username].requires = old_requires return form @@ -2372,7 +2381,7 @@ class Auth(object): if next == session._auth_next: del session._auth_next - redirect(next, client_side=self.settings.client_side) + redirect(next, client_side=settings.client_side) def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT): """ From a51d0877979c0846b420bee31102d162030f79ca Mon Sep 17 00:00:00 2001 From: Alfonso de la Guarda Reyes Date: Mon, 2 Sep 2013 19:32:26 -0500 Subject: [PATCH 20/29] Fixing some typos inside tests and unneeded modules --- gluon/admin.py | 2 +- gluon/cache.py | 2 +- gluon/compileapp.py | 1 - gluon/custom_import.py | 1 - gluon/dal.py | 8 ++++---- gluon/debug.py | 1 - gluon/decoder.py | 2 +- gluon/fileutils.py | 1 - gluon/html.py | 10 +++++----- gluon/languages.py | 1 - gluon/main.py | 1 - gluon/settings.py | 1 - gluon/template.py | 4 ++-- gluon/utils.py | 4 +--- 14 files changed, 15 insertions(+), 24 deletions(-) diff --git a/gluon/admin.py b/gluon/admin.py index abaaf319..b4c93758 100644 --- a/gluon/admin.py +++ b/gluon/admin.py @@ -18,7 +18,7 @@ from fileutils import up, fix_newlines, abspath, recursive_unlink from fileutils import read_file, write_file, parse_version from restricted import RestrictedError from settings import global_settings -from http import HTTP + if not global_settings.web2py_runtime_gae: import site diff --git a/gluon/cache.py b/gluon/cache.py index ab7062d0..221f1989 100644 --- a/gluon/cache.py +++ b/gluon/cache.py @@ -68,7 +68,7 @@ class CacheAbstract(object): def __init__(self, request=None): """ - Paremeters + Parameters ---------- request: the global request object diff --git a/gluon/compileapp.py b/gluon/compileapp.py index fd197044..89365fe2 100644 --- a/gluon/compileapp.py +++ b/gluon/compileapp.py @@ -13,7 +13,6 @@ FOR INTERNAL USE ONLY """ import re -import sys import fnmatch import os import copy diff --git a/gluon/custom_import.py b/gluon/custom_import.py index 091aac76..133b9fda 100644 --- a/gluon/custom_import.py +++ b/gluon/custom_import.py @@ -3,7 +3,6 @@ import __builtin__ import os -import re import sys import threading import traceback diff --git a/gluon/dal.py b/gluon/dal.py index 61b3a0a8..4ba211b0 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -10703,7 +10703,7 @@ class Rows(object): def test_all(): """ - >>> if len(sys.argv)<2: db = DAL(\"sqlite://test.db\") + >>> if len(sys.argv)<2: db = DAL("sqlite://test.db") >>> if len(sys.argv)>1: db = DAL(sys.argv[1]) >>> tmp = db.define_table('users',\ Field('stringf', 'string', length=32, required=True),\ @@ -10739,8 +10739,8 @@ def test_all(): Field('name'),\ Field('birth','date'),\ migrate='test_person.table') - >>> person_id = db.person.insert(name=\"Marco\",birth='2005-06-22') - >>> person_id = db.person.insert(name=\"Massimo\",birth='1971-12-21') + >>> person_id = db.person.insert(name='Marco',birth='2005-06-22') + >>> person_id = db.person.insert(name='Massimo',birth='1971-12-21') commented len(db().select(db.person.ALL)) commented 2 @@ -10766,7 +10766,7 @@ def test_all(): Update a single record - >>> me.update_record(name=\"Max\") + >>> me.update_record(name="Max") >>> me.name 'Max' diff --git a/gluon/debug.py b/gluon/debug.py index 525604a6..253cea27 100644 --- a/gluon/debug.py +++ b/gluon/debug.py @@ -10,7 +10,6 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) """ import logging -import os import pdb import Queue import sys diff --git a/gluon/decoder.py b/gluon/decoder.py index c2b9960e..2b484df1 100644 --- a/gluon/decoder.py +++ b/gluon/decoder.py @@ -1,5 +1,5 @@ import codecs -import encodings + """Caller will hand this library a buffer and ask it to either convert it or auto-detect the type. diff --git a/gluon/fileutils.py b/gluon/fileutils.py index 7c1942da..c451cf2e 100644 --- a/gluon/fileutils.py +++ b/gluon/fileutils.py @@ -7,7 +7,6 @@ Copyrighted by Massimo Di Pierro License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) """ -import sys import storage import os import re diff --git a/gluon/html.py b/gluon/html.py index 1239b448..0d4bea60 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -1961,7 +1961,7 @@ class FORM(DIV): example:: >>> from validators import IS_NOT_EMPTY - >>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY())) + >>> form=FORM(INPUT(_name="test", requires=IS_NOT_EMPTY())) >>> form.xml() '
' @@ -2501,11 +2501,11 @@ def test(): Example: >>> from validators import * - >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml() + >>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN("World"), _class='unknown')).xml() - >>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml() + >>> print DIV(UL("doc","cat","mouse")).xml()
  • doc
  • cat
  • mouse
- >>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml() + >>> print DIV(UL("doc", LI("cat", _class='feline'), 18)).xml()
  • doc
  • cat
  • 18
>>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
abc
def
123
@@ -2531,7 +2531,7 @@ def test(): >>> print form.xml()
only alphanumeric!
>>> session={} - >>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$'))) + >>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$'))) >>> isinstance(form.as_dict(), dict) True >>> form.as_dict(flat=True).has_key("vars") diff --git a/gluon/languages.py b/gluon/languages.py index c20918b4..c8b746c6 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -15,7 +15,6 @@ import re import sys import pkgutil import logging -import marshal from cgi import escape from threading import RLock diff --git a/gluon/main.py b/gluon/main.py index 21669463..d3adf7a0 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -14,7 +14,6 @@ Contains: if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS import gc -import cStringIO import Cookie import os import re diff --git a/gluon/settings.py b/gluon/settings.py index 92962570..0e87ef9d 100644 --- a/gluon/settings.py +++ b/gluon/settings.py @@ -6,7 +6,6 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) import os import sys -import socket import platform from storage import Storage diff --git a/gluon/template.py b/gluon/template.py index bd68b157..096d5247 100644 --- a/gluon/template.py +++ b/gluon/template.py @@ -845,9 +845,9 @@ def render(content="hello world", 'hello world' >>> render(content='abc') 'abc' - >>> render(content='abc\\'') + >>> render(content='abc\'') "abc'" - >>> render(content='a"\\'bc') + >>> render(content='a"\'bc') 'a"\\'bc' >>> render(content='a\\nbc') 'a\\nbc' diff --git a/gluon/utils.py b/gluon/utils.py index 57e29a4f..289ec0ae 100644 --- a/gluon/utils.py +++ b/gluon/utils.py @@ -11,8 +11,6 @@ This file specifically includes utilities for security. import threading import struct -#import hashlib -#import hmac import uuid import random import time @@ -23,7 +21,7 @@ import logging import socket import base64 import zlib -from types import ModuleType + _struct_2_long_long = struct.Struct('=QQ') From 31904d58bdb1b5c4b30c5250288b5ab75a50bb24 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 2 Sep 2013 20:30:30 -0500 Subject: [PATCH 21/29] backported safe_eval to python 2.5 --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index e29beaca..c045fd99 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.01.14.13.25 +Version 2.6.0-development+timestamp.2013.09.02.20.29.38 diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 1fe4f9f7..5460794a 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -7,6 +7,12 @@ import re import ast from cgi import escape from string import maketrans +try: + from ast import parse as ast_parse + import ast +except ImportError: # python 2.5 + from compiler import parse + import compiler.ast as ast """ TODO: next version should use MathJax @@ -569,7 +575,7 @@ def safe_eval(node_or_string, env): _safe_names = {'None': None, 'True': True, 'False': False} _safe_names.update(env) if isinstance(node_or_string, basestring): - node_or_string = ast.parse(node_or_string, mode='eval') + node_or_string = ast_parse(node_or_string, mode='eval') if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body def _convert(node): From 74cb6c3de6175a0855db12706164806df3d652df Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 2 Sep 2013 20:33:19 -0500 Subject: [PATCH 22/29] backported safe_eval to python 2.5 --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/VERSION b/VERSION index c045fd99..3d4184d6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.02.20.29.38 +Version 2.6.0-development+timestamp.2013.09.02.20.32.13 diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 5460794a..76f4520a 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -4,7 +4,6 @@ # recreated by Vladyslav Kozlovskyy # license MIT/BSD/GPL import re -import ast from cgi import escape from string import maketrans try: From 099e02fe642cb7f08eff5d68a707b393303cb50d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 2 Sep 2013 20:43:10 -0500 Subject: [PATCH 23/29] fixed issue 1507, menu links problems with mobile user agent, thanks Paolo Valleri --- VERSION | 2 +- gluon/html.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 3d4184d6..6170e86f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.02.20.32.13 +Version 2.6.0-development+timestamp.2013.09.02.20.41.19 diff --git a/gluon/html.py b/gluon/html.py index 0d4bea60..23977a88 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -2457,15 +2457,23 @@ class MENU(DIV): def serialize_mobile(self, data, select=None, prefix=''): if not select: select = SELECT(**self.attributes) + custom_items = [] for item in data: - if len(item) <= 4 or item[4] == True: + # Custom item aren't serialized as mobile + if len(item) >= 3 and (not item[0]) or (isinstance(item[0], DIV) and not (item[2])): + # ex: ('', False,A('title',_href=URL(...),_title="title")) + # ex: (A('title',_href=URL(...),_title="title"), False, None) + custom_items.append(item) + elif len(item) <= 4 or item[4] == True: select.append(OPTION(CAT(prefix, item[0]), _value=item[2], _selected=item[1])) if len(item) > 3 and len(item[3]): self.serialize_mobile( item[3], select, prefix=CAT(prefix, item[0], '/')) select['_onchange'] = 'window.location=this.value' - return select + # avoid to wrap the select if no custom items are present + html = DIV(select, self.serialize(custom_items)) if len( custom_items) else select + return html def xml(self): if self['mobile']: From fd000fe35b87749830d8ab8581461153aaab7b30 Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 3 Sep 2013 09:53:42 -0500 Subject: [PATCH 24/29] preventing un-necessary fetch, thans Anthony and Villas --- VERSION | 2 +- gluon/dal.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 6170e86f..bc7a475e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.02.20.41.19 +Version 2.6.0-development+timestamp.2013.09.03.09.52.37 diff --git a/gluon/dal.py b/gluon/dal.py index 4ba211b0..d87cbc19 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -8212,8 +8212,12 @@ class Reference(long): def __getattr__(self, key): if key == 'id': return long(self) - self.__allocate() - return self._record.get(key, None) + if key in self._table: + self.__allocate() + if self._record: + return self._record.get(key,None) # to deal with case self.update_record() + else: + return None def get(self, key, default=None): return self.__getattr__(key, default) From e724e13564d6ae8c13aa6067d0afdb5867bbd4e4 Mon Sep 17 00:00:00 2001 From: Massimo Date: Tue, 3 Sep 2013 11:53:18 -0500 Subject: [PATCH 25/29] fixed cron deadlock on windows, thanks Helko Besemann --- VERSION | 2 +- gluon/contrib/shell.py | 14 +++++---- gluon/newcron.py | 13 ++++++-- gluon/winservice.py | 68 ++++++++++++++++++++++++++++++++---------- 4 files changed, 74 insertions(+), 23 deletions(-) diff --git a/VERSION b/VERSION index bc7a475e..619badf2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.03.09.52.37 +Version 2.6.0-development+timestamp.2013.09.03.11.52.13 diff --git a/gluon/contrib/shell.py b/gluon/contrib/shell.py index e875a57a..f04a3fc4 100755 --- a/gluon/contrib/shell.py +++ b/gluon/contrib/shell.py @@ -48,12 +48,12 @@ _DEBUG = True _HISTORY_KIND = '_Shell_History' # Types that can't be pickled. -UNPICKLABLE_TYPES = ( +UNPICKLABLE_TYPES = [ types.ModuleType, types.TypeType, types.ClassType, types.FunctionType, -) +] # Unpicklable statements to seed new historys with. INITIAL_UNPICKLABLES = [ @@ -244,8 +244,8 @@ def run(history, statement, env={}): for name, val in statement_module.__dict__.items(): if name not in old_globals or represent(val) != old_globals[name]: new_globals[name] = val - - if True in [isinstance(val, UNPICKLABLE_TYPES) + + if True in [isinstance(val, tuple(UNPICKLABLE_TYPES)) for val in new_globals.values()]: # this statement added an unpicklable global. store the statement and # the names of all of the globals it added in the unpicklables. @@ -256,7 +256,11 @@ def run(history, statement, env={}): # new globals back into the datastore. for name, val in new_globals.items(): if not name.startswith('__'): - history.set_global(name, val) + try: + history.set_global(name, val) + except TypeError, ex: + UNPICKLABLE_TYPES.append(type(val)) + history.add_unpicklable(statement, new_globals.keys()) finally: sys.modules['__main__'] = old_main diff --git a/gluon/newcron.py b/gluon/newcron.py index 90e6eacd..00d5e549 100644 --- a/gluon/newcron.py +++ b/gluon/newcron.py @@ -117,6 +117,10 @@ class Token(object): if a cron job started before 60 seconds and did not stop, a warning is issue "Stale cron.master detected" """ + if sys.platform == 'win32': + locktime = 59.5 + else: + locktime = 59.99 if portalocker.LOCK_EX is None: logger.warning('WEB2PY CRON: Disabled because no file locking') return None @@ -128,7 +132,7 @@ class Token(object): (start, stop) = cPickle.load(self.master) except: (start, stop) = (0, 1) - if startup or self.now - start > 59.99: + if startup or self.now - start > locktime: ret = self.now if not stop: # this happens if previous cron job longer than 1 minute @@ -136,6 +140,7 @@ class Token(object): logger.debug('WEB2PY CRON: Acquiring lock') self.master.seek(0) cPickle.dump((self.now, 0), self.master) + self.master.flush() finally: portalocker.unlock(self.master) if not ret: @@ -304,7 +309,11 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None): for task in tasks: if _cron_stopping: break - commands = [sys.executable] + if sys.executable.lower().endswith('pythonservice.exe'): + _python_exe = os.path.join(sys.exec_prefix, 'python.exe') + else: + _python_exe = sys.executable + commands = [_python_exe] w2p_path = fileutils.abspath('web2py.py', gluon=True) if os.path.exists(w2p_path): commands.append(w2p_path) diff --git a/gluon/winservice.py b/gluon/winservice.py index e08f3132..36cb75df 100644 --- a/gluon/winservice.py +++ b/gluon/winservice.py @@ -36,6 +36,7 @@ class Service(win32serviceutil.ServiceFramework): _svc_name_ = '_unNamed' _svc_display_name_ = '_Service Template' + _svc_description_ = '_Service Template description' def __init__(self, *args): win32serviceutil.ServiceFramework.__init__(self, *args) @@ -44,6 +45,12 @@ class Service(win32serviceutil.ServiceFramework): def log(self, msg): servicemanager.LogInfoMsg(str(msg)) + def log_error(self, msg): + """ + Log an error message to windows application event log. + """ + servicemanager.LogErrorMsg(str(msg)) + def SvcDoRun(self): self.ReportServiceStatus(win32service.SERVICE_START_PENDING) try: @@ -80,6 +87,7 @@ class Web2pyService(Service): _svc_name_ = 'web2py' _svc_display_name_ = 'web2py Service' + _svc_description_ = 'Web2py application framework service' _exe_args_ = 'options' server = None @@ -132,17 +140,16 @@ class Web2pyService(Service): server_name=options.server_name, request_queue_size=options.request_queue_size, timeout=options.timeout, + socket_timeout=options.socket_timeout, shutdown_timeout=options.shutdown_timeout, - path=options.folder + path=options.folder, + interfaces=options.interfaces ) try: from rewrite import load load() self.server.start() except: - - # self.server.stop() - self.server = None raise @@ -159,12 +166,18 @@ class Web2pyCronService(Web2pyService): _svc_name_ = 'web2py_cron' _svc_display_name_ = 'web2py Cron Service' + _svc_description_ = 'web2py Windows cron service for scheduled tasks' _exe_args_ = 'options' def start(self): import newcron - import global_settings - self.log('web2py server starting') + import logging + import logging.config + from settings import global_settings + from fileutils import abspath + from os.path import exists, join + + self.log('web2py Cron service starting') if not self.chdir(): return if len(sys.argv) == 2: @@ -172,25 +185,50 @@ class Web2pyCronService(Web2pyService): else: opt_mod = self._exe_args_ options = __import__(opt_mod, [], [], '') - global_settings.global_settings.web2py_crontype = 'external' + logpath = abspath(join(options.folder, "logging.conf")) + + if exists(logpath): + logging.config.fileConfig(logpath) + else: + logging.basicConfig() + logger = logging.getLogger("web2py.cron") + global_settings.web2py_crontype = 'external' if options.scheduler: # -K apps = [app.strip() for app in options.scheduler.split( ',') if check_existent_app(options, app.strip())] else: apps = None - self.extcron = newcron.extcron(options.folder, apps=apps) - try: - self.extcron.start() - except: - # self.server.stop() - self.extcron = None - raise + + misfire_gracetime = float(options.misfire_gracetime) if 'misfire_gracetime' in dir(options) else 0.0 + logger.info('Starting Window cron service with %0.2f secs gracetime.' % misfire_gracetime) + self._started = True + wait_full_min = lambda: 60 - time.time() % 60 + while True: + try: + if wait_full_min() >= misfire_gracetime: # an offset of max. 5 secs before full minute (e.g. time.sleep(60) == 58.99 secs) + self.extcron = newcron.extcron(options.folder, apps=apps) + self.extcron.start() + time.sleep(wait_full_min()) + else: + logger.debug('time.sleep() offset detected: %0.3f s' % wait_full_min()) + while wait_full_min() <= misfire_gracetime: + pass + if apps != None: + break + if not self._started: + break + except Exception, ex: + self.extcron = None + self.log_error('%s, restarting service.' % ex) + logger.exception('Exception! Restarting Windows cron service.' % ex) + self.start() def stop(self): - self.log('web2py cron stopping') + self.log('web2py Cron service stopping') if not self.chdir(): return if self.extcron: + self._started = False self.extcron.join() def register_service_handler(argv=None, opt_file='options', cls=Web2pyService): From cd30382c0ebefeeef1149214acb36e5fd6c93469 Mon Sep 17 00:00:00 2001 From: niphlod Date: Tue, 3 Sep 2013 20:52:25 +0200 Subject: [PATCH 26/29] attach datetime/date/time validators only on events --- applications/admin/static/js/web2py.js | 72 ++++++++++++++--------- applications/examples/static/js/web2py.js | 72 ++++++++++++++--------- applications/welcome/static/js/web2py.js | 72 ++++++++++++++--------- 3 files changed, 129 insertions(+), 87 deletions(-) diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js index de2a6e25..b38adbbf 100644 --- a/applications/admin/static/js/web2py.js +++ b/applications/admin/static/js/web2py.js @@ -74,28 +74,6 @@ * Ideally all events should be bound to the document, so we can avoid calling * this over and over... all will be bound to the document */ - var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; - var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; - $("input.date", target).each(function () { - $(this).attr('autocomplete', 'off'); - Calendar.setup({ - inputField: this, - ifFormat: date_format, - showsTime: false - }); - }); - $("input.datetime", target).each(function () { - $(this).attr('autocomplete', 'off'); - Calendar.setup({ - inputField: this, - ifFormat: datetime_format, - showsTime: true, - timeFormat: "24" - }); - }); - $("input.time", target).each(function () { - $(this).timeEntry().attr('autocomplete', 'off'); - }); /*adds btn class to buttons*/ $('button', target).addClass('btn'); $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); @@ -181,18 +159,13 @@ /* * This is called once for page * Ideally it should bound all the things that are needed + * and require no dom manipulations */ var doc = $(document); doc.on('click', '.flash', function (e) { var t = $(this); if(t.css('top') == '0px') t.slideUp('slow'); else t.fadeOut(); - /* if I want to display a clickable something - * inside flash, I should not be prevented to follow it - * - * e.preventDefault(); - */ - }); doc.on('keyup', 'input.integer', function () { this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); @@ -205,7 +178,48 @@ if(this.checked) if(!web2py.confirm(confirm_message)) this.checked = false; }); - + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + doc.on('click', "input.datetime", function () { + var tformat = $(this).data('w2p_datetime_format') + var active = $(this).data('w2p_datetime'); + var format = (typeof tformat != 'undefined') ? tformat : datetime_format; + if(active === undefined) { + Calendar.setup({ + inputField: this, + ifFormat: format, + showsTime: true, + timeFormat: "24" + }); + $(this).attr('autocomplete', 'off'); + $(this).data('w2p_datetime', 1); + $(this).trigger('click'); + } + }); + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + doc.on('click', "input.date", function () { + var tformat = $(this).data('w2p_date_format') + var active = $(this).data('w2p_date'); + var format = (typeof tformat != 'undefined') ? tformat : date_format; + if(active === undefined) { + Calendar.setup({ + inputField: this, + ifFormat: format, + showsTime: false + }); + $(this).data('w2p_date', 1); + $(this).attr('autocomplete', 'off'); + $(this).trigger('click'); + } + }); + doc.on('focus', "input.time", function () { + var active = $(this).data('w2p_time'); + if(active === undefined) { + $(this).timeEntry({ + spinnerImage: '' + }).attr('autocomplete', 'off'); + $(this).data('w2p_time', 1); + } + }); doc.ajaxSuccess(function (e, xhr) { var redirect = xhr.getResponseHeader('web2py-redirect-location'); if(redirect !== null) { diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js index de2a6e25..b38adbbf 100644 --- a/applications/examples/static/js/web2py.js +++ b/applications/examples/static/js/web2py.js @@ -74,28 +74,6 @@ * Ideally all events should be bound to the document, so we can avoid calling * this over and over... all will be bound to the document */ - var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; - var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; - $("input.date", target).each(function () { - $(this).attr('autocomplete', 'off'); - Calendar.setup({ - inputField: this, - ifFormat: date_format, - showsTime: false - }); - }); - $("input.datetime", target).each(function () { - $(this).attr('autocomplete', 'off'); - Calendar.setup({ - inputField: this, - ifFormat: datetime_format, - showsTime: true, - timeFormat: "24" - }); - }); - $("input.time", target).each(function () { - $(this).timeEntry().attr('autocomplete', 'off'); - }); /*adds btn class to buttons*/ $('button', target).addClass('btn'); $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); @@ -181,18 +159,13 @@ /* * This is called once for page * Ideally it should bound all the things that are needed + * and require no dom manipulations */ var doc = $(document); doc.on('click', '.flash', function (e) { var t = $(this); if(t.css('top') == '0px') t.slideUp('slow'); else t.fadeOut(); - /* if I want to display a clickable something - * inside flash, I should not be prevented to follow it - * - * e.preventDefault(); - */ - }); doc.on('keyup', 'input.integer', function () { this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); @@ -205,7 +178,48 @@ if(this.checked) if(!web2py.confirm(confirm_message)) this.checked = false; }); - + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + doc.on('click', "input.datetime", function () { + var tformat = $(this).data('w2p_datetime_format') + var active = $(this).data('w2p_datetime'); + var format = (typeof tformat != 'undefined') ? tformat : datetime_format; + if(active === undefined) { + Calendar.setup({ + inputField: this, + ifFormat: format, + showsTime: true, + timeFormat: "24" + }); + $(this).attr('autocomplete', 'off'); + $(this).data('w2p_datetime', 1); + $(this).trigger('click'); + } + }); + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + doc.on('click', "input.date", function () { + var tformat = $(this).data('w2p_date_format') + var active = $(this).data('w2p_date'); + var format = (typeof tformat != 'undefined') ? tformat : date_format; + if(active === undefined) { + Calendar.setup({ + inputField: this, + ifFormat: format, + showsTime: false + }); + $(this).data('w2p_date', 1); + $(this).attr('autocomplete', 'off'); + $(this).trigger('click'); + } + }); + doc.on('focus', "input.time", function () { + var active = $(this).data('w2p_time'); + if(active === undefined) { + $(this).timeEntry({ + spinnerImage: '' + }).attr('autocomplete', 'off'); + $(this).data('w2p_time', 1); + } + }); doc.ajaxSuccess(function (e, xhr) { var redirect = xhr.getResponseHeader('web2py-redirect-location'); if(redirect !== null) { diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js index de2a6e25..b38adbbf 100644 --- a/applications/welcome/static/js/web2py.js +++ b/applications/welcome/static/js/web2py.js @@ -74,28 +74,6 @@ * Ideally all events should be bound to the document, so we can avoid calling * this over and over... all will be bound to the document */ - var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; - var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; - $("input.date", target).each(function () { - $(this).attr('autocomplete', 'off'); - Calendar.setup({ - inputField: this, - ifFormat: date_format, - showsTime: false - }); - }); - $("input.datetime", target).each(function () { - $(this).attr('autocomplete', 'off'); - Calendar.setup({ - inputField: this, - ifFormat: datetime_format, - showsTime: true, - timeFormat: "24" - }); - }); - $("input.time", target).each(function () { - $(this).timeEntry().attr('autocomplete', 'off'); - }); /*adds btn class to buttons*/ $('button', target).addClass('btn'); $('form input[type="submit"], form input[type="button"]', target).addClass('btn'); @@ -181,18 +159,13 @@ /* * This is called once for page * Ideally it should bound all the things that are needed + * and require no dom manipulations */ var doc = $(document); doc.on('click', '.flash', function (e) { var t = $(this); if(t.css('top') == '0px') t.slideUp('slow'); else t.fadeOut(); - /* if I want to display a clickable something - * inside flash, I should not be prevented to follow it - * - * e.preventDefault(); - */ - }); doc.on('keyup', 'input.integer', function () { this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse(); @@ -205,7 +178,48 @@ if(this.checked) if(!web2py.confirm(confirm_message)) this.checked = false; }); - + var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; + doc.on('click', "input.datetime", function () { + var tformat = $(this).data('w2p_datetime_format') + var active = $(this).data('w2p_datetime'); + var format = (typeof tformat != 'undefined') ? tformat : datetime_format; + if(active === undefined) { + Calendar.setup({ + inputField: this, + ifFormat: format, + showsTime: true, + timeFormat: "24" + }); + $(this).attr('autocomplete', 'off'); + $(this).data('w2p_datetime', 1); + $(this).trigger('click'); + } + }); + var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; + doc.on('click', "input.date", function () { + var tformat = $(this).data('w2p_date_format') + var active = $(this).data('w2p_date'); + var format = (typeof tformat != 'undefined') ? tformat : date_format; + if(active === undefined) { + Calendar.setup({ + inputField: this, + ifFormat: format, + showsTime: false + }); + $(this).data('w2p_date', 1); + $(this).attr('autocomplete', 'off'); + $(this).trigger('click'); + } + }); + doc.on('focus', "input.time", function () { + var active = $(this).data('w2p_time'); + if(active === undefined) { + $(this).timeEntry({ + spinnerImage: '' + }).attr('autocomplete', 'off'); + $(this).data('w2p_time', 1); + } + }); doc.ajaxSuccess(function (e, xhr) { var redirect = xhr.getResponseHeader('web2py-redirect-location'); if(redirect !== null) { From 832a6039397b68126e2c5aeb41158b17794d1500 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 4 Sep 2013 16:27:42 -0500 Subject: [PATCH 27/29] fixed problem with missing .py in zip --- Makefile | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c25c4920..b8aa33e1 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ src: ### build web2py_src.zip echo '' > NEWINSTALL mv web2py_src.zip web2py_src_old.zip | echo 'no old' - cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* 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 web2py/web2py_src.zip web2py.py anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* 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: make src diff --git a/VERSION b/VERSION index 619badf2..0ef6e5d3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.03.11.52.13 +Version 2.6.0-development+timestamp.2013.09.04.16.26.40 From 5b32e3e2a9177fe15a1a5d6f4e6e4b1642461961 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 4 Sep 2013 16:30:14 -0500 Subject: [PATCH 28/29] fixed problem with missing .py in zip --- Makefile | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b8aa33e1..499388a3 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ src: ### build web2py_src.zip echo '' > NEWINSTALL mv web2py_src.zip web2py_src_old.zip | echo 'no old' - cd ..; zip -r web2py/web2py_src.zip web2py.py anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* 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 web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* 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: make src diff --git a/VERSION b/VERSION index 0ef6e5d3..36e6d484 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.04.16.26.40 +Version 2.6.0-development+timestamp.2013.09.04.16.29.23 From f171777da5644335c6056a78ccc7b010c3d4b647 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 4 Sep 2013 16:33:37 -0500 Subject: [PATCH 29/29] fixed problem with handlers in zip --- Makefile | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 499388a3..f1020b43 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ src: ### build web2py_src.zip echo '' > NEWINSTALL 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/gluon/*.py web2py/gluon/contrib/* web2py/extras/* 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 web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* 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: make src diff --git a/VERSION b/VERSION index 36e6d484..612d20bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.6.0-development+timestamp.2013.09.04.16.29.23 +Version 2.6.0-development+timestamp.2013.09.04.16.32.36

span messag in markmin ... link to id