From 12107da9bda4d73f67bc9ceac43cdb579a6863ec Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 09:14:58 -0500 Subject: [PATCH 01/20] allow to pass hidden to crud.update --- VERSION | 2 +- gluon/tools.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index d6b339bd..a2db4c36 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-30 11:09:19) stable +Version 2.2.1 (2012-10-31 09:14:53) stable diff --git a/gluon/tools.py b/gluon/tools.py index b6fb9ace..f89e3ecf 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -3464,10 +3464,12 @@ class Crud(object): deletable = self.settings.update_deletable if message is DEFAULT: message = self.messages.record_updated + if not 'hidden' in attributes: + attributes['hidden'] = {} + attributes['hidden']['_next'] = next form = SQLFORM( table, record, - hidden=dict(_next=next), showid=self.settings.showid, submit_button=self.messages.submit_button, delete_label=self.messages.delete_label, @@ -3475,7 +3477,7 @@ class Crud(object): upload=self.settings.download_url, formstyle=self.settings.formstyle, separator=self.settings.label_separator, - **attributes + **attributes # contains hidden ) self.accepted = False self.deleted = False From e1cd36771e5abeb3cd1a18f1f81284e68bca4aa9 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 09:50:37 -0500 Subject: [PATCH 02/20] fixed issue 1129, recapcha message error, thanks Friedrich --- VERSION | 2 +- gluon/tools.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index a2db4c36..3abf4e7c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 09:14:53) stable +Version 2.2.1 (2012-10-31 09:50:32) stable diff --git a/gluon/tools.py b/gluon/tools.py index f89e3ecf..ed14c5e5 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -769,8 +769,12 @@ class Recaptcha(DIV): del self.request_vars.recaptcha_response_field self.request_vars.captcha = '' return True - self.errors['captcha'] = self.error_message - return False + else: + # In case we get an error code, store it so we can get an error message + # from the /api/challenge URL as described in the reCAPTCHA api docs. + self.error = return_values[1] + self.errors['captcha'] = self.error_message + return False def xml(self): public_key = self.public_key From 693dce0e6c0f5da6c635c24507d177b08b2d7927 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 09:52:43 -0500 Subject: [PATCH 03/20] fixed issue 1127, SQLTABLE column with virtual fields, thanks hi21alt --- VERSION | 2 +- gluon/sqlhtml.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 3abf4e7c..81aab881 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 09:50:32) stable +Version 2.2.1 (2012-10-31 09:52:38) stable diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index a2aa12ae..a42a0aab 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -2550,7 +2550,7 @@ class SQLTABLE(TABLE): (tablename, fieldname) = colname.split('.') try: field = sqlrows.db[tablename][fieldname] - except KeyError: + except (KeyError, AttributeError): field = None if tablename in record \ and isinstance(record, Row) \ From b18a2a7aa334a99ae51d5557b5eaf83315dad42e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 10:02:08 -0500 Subject: [PATCH 04/20] fixed issue 1117, snitize of unicode, thanks Bill --- VERSION | 2 +- gluon/sanitizer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 81aab881..4ae212d9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 09:52:38) stable +Version 2.2.1 (2012-10-31 10:02:04) stable diff --git a/gluon/sanitizer.py b/gluon/sanitizer.py index e9ded858..37433361 100644 --- a/gluon/sanitizer.py +++ b/gluon/sanitizer.py @@ -220,7 +220,7 @@ def sanitize(text, permitted_tags=[ 'td': ['colspan'], }, escape=True): - if not isinstance(text, str): + if not isinstance(text, basestring): return str(text) return XssCleaner(permitted_tags=permitted_tags, allowed_attributes=allowed_attributes).strip(text, escape) From d67af48f292f7253fabd1bf3bc1487b126cbb59b Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 10:22:20 -0500 Subject: [PATCH 05/20] allows to customize the A in a MENU, thanks ilvalle --- VERSION | 2 +- gluon/html.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 4ae212d9..24059837 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 10:02:04) stable +Version 2.2.1 (2012-10-31 10:22:15) stable diff --git a/gluon/html.py b/gluon/html.py index 648fc405..9635d243 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -2320,6 +2320,8 @@ class MENU(DIV): li = LI(link) elif 'no_link_url' in self.attributes and self['no_link_url'] == link: li = LI(DIV(name)) + elif isinstance(link,dict): + li = LI(A(name, **link)) elif link: li = LI(A(name, _href=link)) elif not link and isinstance(name, A): From d72752f4538b3cb16b76d41744f0e1314f146ce3 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 10:23:56 -0500 Subject: [PATCH 06/20] scripts/extract_sqlite_models.py, thanks Michele --- VERSION | 2 +- scripts/extract_sqlite_models.py | 114 +++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 scripts/extract_sqlite_models.py diff --git a/VERSION b/VERSION index 24059837..82ba994c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 10:22:15) stable +Version 2.2.1 (2012-10-31 10:23:52) stable diff --git a/scripts/extract_sqlite_models.py b/scripts/extract_sqlite_models.py new file mode 100644 index 00000000..3f7ad2a1 --- /dev/null +++ b/scripts/extract_sqlite_models.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +''' +Create the web2py model code needed to access your sqlite legacy db. + +Usage: +python extract_sqlite_models.py + +Access your tables with: +legacy_db(legacy_db.mytable.id>0).select() + + +extract_sqlite_models.py -- Copyright (C) Michele Comitini +This code is distributed with web2py. + +The regexp code and the dictionary type map was extended from +extact_mysql_models.py that comes with web2py. extact_mysql_models.py is Copyright (C) Falko Krause. + +''' +import re +import sys +import sqlite3 + +data_type_map = dict( + varchar='string', + int='integer', + integer='integer', + tinyint='integer', + smallint='integer', + mediumint='integer', + bigint='integer', + float='double', + double='double', + char='string', + decimal='integer', + date='date', + time='time', + timestamp='datetime', + datetime='datetime', + binary='blob', + blob='blob', + tinyblob='blob', + mediumblob='blob', + longblob='blob', + text='text', + tinytext='text', + mediumtext='text', + longtext='text', + bit='boolean', + nvarchar='text', + numeric='decimal(30,15)', + real='decimal(30,15)', +) + +def get_foreign_keys(sql_lines): + fks = dict() + for line in sql_lines[1:-1]: + hit = re.search(r'FOREIGN\s+KEY\s+\("(\S+)"\)\s+REFERENCES\s+"(\S+)"\s+\("(\S+)"\)', line) + if hit: + fks[hit.group(1)] = hit.groups()[1:] + + return fks + +def sqlite(database_name): + conn = sqlite3.connect(database_name) + c = conn.cursor() + r = c.execute(r"select name,sql from sqlite_master where type='table' and not name like '\_%' and not lower(name) like 'sqlite_%'") + tables = r.fetchall() + connection_string = "legacy_db = DAL('sqlite://%s')" % database_name.split('/')[-1] + legacy_db_table_web2py_code = [] + for table_name, sql_create_stmnt in tables: + if table_name.startswith('_'): + continue + if 'CREATE' in sql_create_stmnt: # check if the table exists + #remove garbage lines from sql statement + sql_lines = sql_create_stmnt.split('\n') + sql_lines = [x for x in sql_lines if not( + x.startswith('--') or x.startswith('/*') or x == '')] + #generate the web2py code from the create statement + web2py_table_code = '' + fields = [] + fks = get_foreign_keys(sql_lines) + for line in sql_lines[1:-1]: + if re.search('KEY', line) or re.search('PRIMARY', line) or re.search('"ID"', line) or line.startswith(')'): + continue + hit = re.search(r'"(\S+)"\s+(\w+(\(\S+\))?),?( .*)?', line) + if hit is not None: + name, d_type = hit.group(1), hit.group(2) + d_type = re.sub(r'(\w+)\(.*', r'\1', d_type) + name = unicode(re.sub('`', '', name)) + if name in fks.keys(): + if fks[name][1].lower() == 'id': + field_type = 'reference %s' % (fks[name][0]) + else: + field_type = 'reference %s.%s' % (fks[name][0], fks[name][1]) + else: + field_type = data_type_map[d_type] + web2py_table_code += "\n Field('%s','%s')," % ( + name, field_type) + web2py_table_code = "legacy_db.define_table('%s',%s\n migrate=False)" % (table_name, web2py_table_code) + legacy_db_table_web2py_code.append(web2py_table_code) + #---------------------------------------- + #write the legacy db to file + legacy_db_web2py_code = connection_string + "\n\n" + legacy_db_web2py_code += "\n\n#--------\n".join( + legacy_db_table_web2py_code) + return legacy_db_web2py_code + +if len(sys.argv) < 2: + print 'USAGE:\n\n extract_mysql_models.py data_basename\n\n' +else: + print "# -*- coding: utf-8 -*-" + print sqlite(sys.argv[1]) From a61e388498935d21e20b02050fc216d2ff0d660d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Wed, 31 Oct 2012 11:43:11 -0500 Subject: [PATCH 07/20] fixed issue 1130, prevent exception for virtual fields in forms, thanks hi21alt --- VERSION | 2 +- gluon/sqlhtml.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 82ba994c..960afd2b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 10:23:52) stable +Version 2.2.1 (2012-10-31 11:43:03) stable diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index a42a0aab..77b332c8 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -2561,7 +2561,7 @@ class SQLTABLE(TABLE): else: raise SyntaxError('something wrong in Rows object') r_old = r - if not field: + if not field or isinstance(field, (Field.Virtual, Field.Lazy)): pass elif linkto and field.type == 'id': try: From 8affdbdc86aa268790528785930cc23f6016aaf1 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Nov 2012 21:01:46 -0500 Subject: [PATCH 08/20] fixed issue 1134, gae dal delete, thanks Howesc --- VERSION | 2 +- gluon/dal.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 960afd2b..ca8041e6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-31 11:43:03) stable +Version 2.2.1 (2012-11-01 21:01:40) stable diff --git a/gluon/dal.py b/gluon/dal.py index 1082aa1f..a8dae8d6 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -4608,11 +4608,14 @@ class GoogleDatastoreAdapter(NoSQLAdapter): (items, tablename, fields) = self.select_raw(query) # items can be one item or a query if not isinstance(items,list): - counter = items.count(limit=None) - leftitems = items.fetch(1000) + #use a keys_only query to ensure that this runs as a datastore + # small operations + leftitems = items.fetch(1000, keys_only=True) + counter = 0 while len(leftitems): + counter += len(leftitems) gae.delete(leftitems) - leftitems = items.fetch(1000) + leftitems = items.fetch(1000, keys_only=True) else: counter = len(items) gae.delete(items) From c9b41a43431a942366f0899623d296d18ff65c7c Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Nov 2012 21:07:47 -0500 Subject: [PATCH 09/20] fixed issue 1132, unicode in IS_IN_DB validator --- VERSION | 2 +- gluon/validators.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index ca8041e6..b4ce309c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-01 21:01:40) stable +Version 2.2.1 (2012-11-01 21:07:42) stable diff --git a/gluon/validators.py b/gluon/validators.py index 0280126a..37cbc781 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -575,7 +575,10 @@ class IS_NOT_IN_DB(Validator): self.record_id = id def __call__(self, value): - value = str(value) + if isinstance(value,unicode): + value = value.encode('utf8') + else: + value = str(value) if not value.strip(): return (value, translate(self.error_message)) if value in self.allowed_override: From b17358e761a001b0910999f43667713a20efb8a2 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Nov 2012 21:09:09 -0500 Subject: [PATCH 10/20] better gluon.tools.Expose, thanks Richard --- VERSION | 2 +- gluon/tools.py | 42 +++++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index b4ce309c..a2993b3a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-01 21:07:42) stable +Version 2.2.1 (2012-11-01 21:09:04) stable diff --git a/gluon/tools.py b/gluon/tools.py index ed14c5e5..2572c381 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4587,16 +4587,23 @@ class PluginManager(object): class Expose(object): - def __init__(self, base=None, basename='base'): + def __init__(self, base=None, basename='base', extensions=None, allow_download=True): + """ + extensions: an optional list of file extensions for filtering displayed files: + ['.py', '.jpg'] + allow_download: whether to allow downloading selected files + """ current.session.forget() base = base or os.path.join(current.request.folder, 'static') self.basename = basename - args = self.args = current.request.raw_args and \ - current.request.raw_args.split('/') or [] - filename = os.path.join(base, *args) + self.args = current.request.raw_args and \ + [arg for arg in current.request.raw_args.split('/') if arg] or [] + filename = os.path.join(base, *self.args) + if not os.path.exists(filename): + raise HTTP(404, "FILE NOT FOUND") if not os.path.normpath(filename).startswith(base): raise HTTP(401, "NOT AUTHORIZED") - if not os.path.isdir(filename): + if allow_download and not os.path.isdir(filename): current.response.headers['Content-Type'] = contenttype(filename) raise HTTP(200, open(filename, 'rb'), **current.response.headers) self.path = path = os.path.join(filename, '*') @@ -4604,23 +4611,24 @@ class Expose(object): if os.path.isdir(f) and not self.isprivate(f)] self.filenames = [f[len(path) - 1:] for f in sorted(glob.glob(path)) if not os.path.isdir(f) and not self.isprivate(f)] + if extensions: + self.filenames = [f for f in self.filenames if os.path.splitext(f)[-1] in extensions] def breadcrumbs(self, basename): path = [] span = SPAN() span.append(A(basename, _href=URL())) - span.append('/') - args = current.request.raw_args and \ - current.request.raw_args.split('/') or [] - for arg in args: + for arg in self.args: + span.append('/') path.append(arg) span.append(A(arg, _href=URL(args='/'.join(path)))) - span.append('/') return span def table_folders(self): - return TABLE(*[TR(TD(A(folder, _href=URL(args=self.args + [folder])))) - for folder in self.folders]) + if self.folders: + return SPAN(H3('Folders'), TABLE(*[TR(TD(A(folder, _href=URL(args=self.args + [folder])))) + for folder in self.folders])) + return '' @staticmethod def isprivate(f): @@ -4628,21 +4636,21 @@ class Expose(object): @staticmethod def isimage(f): - return f.rsplit('.')[-1].lower() in ('png', 'jpg', 'jpeg', 'gif', 'tiff') + return os.path.splitext(f)[-1].lower() in ('.png', '.jpg', '.jpeg', '.gif', '.tiff') def table_files(self, width=160): - return TABLE(*[TR(TD(A(f, _href=URL(args=self.args + [f]))), + if self.filenames: + return SPAN(H3('Files'), TABLE(*[TR(TD(A(f, _href=URL(args=self.args + [f]))), TD(IMG(_src=URL(args=self.args + [f]), _style='max-width:%spx' % width) if width and self.isimage(f) else '')) - for f in self.filenames]) + for f in self.filenames])) + return '' def xml(self): return DIV( H2(self.breadcrumbs(self.basename)), - H3('Folders'), self.table_folders(), - H3('Files'), self.table_files()).xml() From fa019e8960929c7f19bcf8543ab3430c5fe21b51 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Nov 2012 21:33:06 -0500 Subject: [PATCH 11/20] fixed issue 1124, create new auth.wiki page from slug model, thanks Nico --- VERSION | 2 +- gluon/tools.py | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index a2993b3a..a6b02268 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-01 21:09:04) stable +Version 2.2.1 (2012-11-01 21:33:00) stable diff --git a/gluon/tools.py b/gluon/tools.py index 2572c381..4e9dc6dd 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4823,7 +4823,7 @@ class Wiki(object): elif not zero or not zero.startswith('_'): return self.read(zero) elif zero == '_edit': - return self.edit(request.args(1) or 'index') + return self.edit(request.args(1) or 'index',request.args(2) or 0) elif zero == '_editmedia': return self.editmedia(request.args(1) or 'index') elif zero == '_create': @@ -4898,7 +4898,7 @@ class Wiki(object): raise HTTP(401, "Not Authorized") return True - def edit(self, slug): + def edit(self,slug,from_template=0): auth = self.auth db = auth.db page = db.wiki_page(slug=slug) @@ -4912,15 +4912,14 @@ class Wiki(object): % self.force_prefix redirect(URL(args=('_edit', self.force_prefix + slug))) db.wiki_page.can_read.default = [Wiki.everybody] - user_group_role = auth.user_group_role() - db.wiki_page.can_edit.default = [user_group_role] + db.wiki_page.can_edit.default = [auth.user_group_role()] db.wiki_page.title.default = title_guess db.wiki_page.slug.default = slug if slug == 'wiki-menu': db.wiki_page.body.default = \ '- Menu Item > @////index\n- - Submenu > http://web2py.com' else: - db.wiki_page.body.default = '## %s\n\npage content\n\n[[new page @////new_page]]\n' % title_guess + db.wiki_page.body.default = db(db.wiki_page.id==from_template).select(db.wiki_page.body)[0].body if int(from_template) > 0 else '## %s\n\npage content' % title_guess vars = current.request.post_vars if vars.body: vars.body = vars.body.replace('://%s' % self.host, '://HOSTNAME') @@ -5004,13 +5003,21 @@ class Wiki(object): if not self.can_edit(): return self.not_authorized() db = self.auth.db - form = FORM(INPUT(_name='slug', value=current.request.args(1), - requires=(IS_SLUG(), - IS_NOT_IN_DB(db, db.wiki_page.slug))), - INPUT(_type='submit', - _value=current.T('Create Page from Slug'))) + slugs=db(db.wiki_page.id>0).select(db.wiki_page.id,db.wiki_page.slug) + options=[OPTION(row.slug,_value=row.id) for row in slugs] + + options.insert(0, OPTION('',_value='')) + form = FORM(LABEL(INPUT(_name='slug',value=current.request.args(1), + requires=(IS_SLUG(), + IS_NOT_IN_DB(db,db.wiki_page.slug)))), + LABEL(SELECT(*options,**dict( + _name='from_template',requires=IS_EMPTY_OR(IS_IN_DB(db,db.wiki_page.id)))),current.T(" Choose Template or empty for new Page")), + INPUT(_type='submit', + _value=current.T('Create Page from Slug')), + _class="well span6") if form.process().accepted: - redirect(URL(args=('_edit', form.vars.slug))) +# form.vars.from_template = 0 if not form.vars.from_template else form.vars.from_template + redirect(URL(args=('_edit',form.vars.slug,form.vars.from_template or 0))) # added param return dict(content=form) def pages(self): From 6a81420fc0f122a9c87f180ca0783674efd00c5d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Nov 2012 21:35:53 -0500 Subject: [PATCH 12/20] many scheduler improvements, thanks Niphlod --- VERSION | 2 +- gluon/scheduler.py | 164 ++++++++++++++++++++++++++++----------------- gluon/widget.py | 2 + 3 files changed, 106 insertions(+), 62 deletions(-) diff --git a/VERSION b/VERSION index a6b02268..8a06df38 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-01 21:33:00) stable +Version 2.2.1 (2012-11-01 21:35:48) stable diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 2cc7e1c5..7b441749 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -40,8 +40,8 @@ http://127.0.0.1:8000/myapp/appadmin/select/db?query=db.scheduler_run.id>0 ## view workers http://127.0.0.1:8000/myapp/appadmin/select/db?query=db.scheduler_worker.id>0 -## To install the scheduler as a permanent daemon on Linux (w/ Upstart), put the -## following into /etc/init/web2py-scheduler.conf: +## To install the scheduler as a permanent daemon on Linux (w/ Upstart), put +## the following into /etc/init/web2py-scheduler.conf: ## (This assumes your web2py instance is installed in 's home directory, ## running as , with app , on network interface eth0.) @@ -116,7 +116,7 @@ CALLABLETYPES = (types.LambdaType, types.FunctionType, class Task(object): def __init__(self, app, function, timeout, args='[]', vars='{}', **kwargs): - logger.debug(' new task allocated: %s.%s' % (app, function)) + logger.debug(' new task allocated: %s.%s', app, function) self.app = app self.function = function self.timeout = timeout @@ -130,11 +130,11 @@ class Task(object): class TaskReport(object): def __init__(self, status, result=None, output=None, tb=None): - logger.debug(' new task report: %s' % status) + logger.debug(' new task report: %s', status) if tb: - logger.debug(' traceback: %s' % tb) + logger.debug(' traceback: %s', tb) else: - logger.debug(' result: %s' % result) + logger.debug(' result: %s', result) self.status = status self.result = result self.output = output @@ -213,7 +213,6 @@ def executor(queue, task, out): (a, c, f) = parse_path_info(task.app) _env = env(a=a, c=c, import_models=True) logging.getLogger().setLevel(level) - scheduler = current._scheduler f = task.function functions = current._scheduler.tasks if not functions: @@ -434,14 +433,14 @@ class Scheduler(MetaScheduler): self.heartbeat = heartbeat self.worker_name = worker_name or socket.gethostname( ) + '#' + str(os.getpid()) - self.worker_status = RUNNING, 1 # tuple containing status as recorded in - #the table, plus a boost parameter for - #hibernation (i.e. when someone stop the - #worker acting on the scheduler_worker table) + #list containing status as recorded in the table plus a boost parameter + #for hibernation (i.e. when someone stop the worker acting on the worker table) + self.worker_status = [RUNNING, 1] self.max_empty_runs = max_empty_runs self.discard_results = discard_results self.is_a_ticker = False self.do_assign_tasks = False + self.greedy = False self.utc_time = utc_time from gluon import current @@ -461,7 +460,7 @@ class Scheduler(MetaScheduler): def define_tables(self, db, migrate): from gluon.dal import DEFAULT - logger.debug('defining tables (migrate=%s)' % migrate) + logger.debug('defining tables (migrate=%s)', migrate) now = self.now db.define_table( 'scheduler_task', @@ -531,14 +530,16 @@ class Scheduler(MetaScheduler): self.start_heartbeats() while True and self.have_heartbeat: if self.worker_status[0] == DISABLED: - logger.debug('Someone stopped me, sleeping until better times come (%s)' % self.worker_status[1]) + logger.debug('Someone stopped me, sleeping until better times come (%s)', self.worker_status[1]) self.sleep() continue logger.debug('looping...') task = self.pop_task() if task: self.empty_runs = 0 + self.worker_status[0] = RUNNING self.report_task(task, self.async(task)) + self.worker_status[0] = ACTIVE else: self.empty_runs += 1 logger.debug('sleeping...') @@ -554,23 +555,29 @@ class Scheduler(MetaScheduler): logger.info('catched') self.die() + def wrapped_assign_tasks(self, db): + db.commit() # ?don't know if it's useful, let's be completely sure + x = 0 + while x < 10: + try: + self.assign_tasks(db) + db.commit() + break + except: + db.rollback() + logger.error('TICKER(%s): error assigning tasks', self.worker_name) + x += 1 + time.sleep(0.5) + def pop_task(self): now = self.now() db, st = self.db, self.db.scheduler_task if self.is_a_ticker and self.do_assign_tasks: #I'm a ticker, and 5 loops passed without reassigning tasks, let's do #that and loop again - db.commit() # ?don't know if it's useful, let's be completely sure - while True: - try: - self.assign_tasks() - db.commit() - break - except: - db.rollback() - logger.error('TICKER: error assigning tasks') + self.wrapped_assign_tasks(db) return None - db.commit() + #ready to process something grabbed = db(st.assigned_worker_name == self.worker_name)( st.status == ASSIGNED) @@ -579,16 +586,23 @@ class Scheduler(MetaScheduler): task.update_record(status=RUNNING, last_run_time=now) #noone will touch my task! db.commit() - logger.debug(' work to do %s' % task.id) + logger.debug(' work to do %s', task.id) else: - logger.debug('nothing to do') + if self.greedy and self.is_a_ticker: + #there are other tasks ready to be assigned + logger.info('TICKER (%s): greedy loop', self.worker_name) + self.wrapped_assign_tasks(db) + else: + logger.info('nothing to do') return None next_run_time = task.last_run_time + datetime.timedelta( seconds=task.period) times_run = task.times_run + 1 if times_run < task.repeats or task.repeats == 0: + #need to run (repeating task) run_again = True else: + #no need to run again run_again = False run_id = 0 while True and not self.discard_results: @@ -602,6 +616,7 @@ class Scheduler(MetaScheduler): db.commit() break except: + time.sleep(0.5) db.rollback() logger.info('new task %(id)s "%(task_name)s" %(application_name)s.%(function_name)s' % task) return Task( @@ -630,7 +645,7 @@ class Scheduler(MetaScheduler): #result is 'null' as a string if task completed #if it's stopped it's None as NoneType, so we record #the STOPPED "run" anyway - logger.debug(' recording task report in db (%s)' % + logger.debug(' recording task report in db (%s)', task_report.status) db(db.scheduler_run.id == task.run_id).update( status=task_report.status, @@ -641,6 +656,7 @@ class Scheduler(MetaScheduler): else: logger.debug(' deleting task report in db because of no result') db(db.scheduler_run.id == task.run_id).delete() + #if there is a stop_time and the following run would exceed it is_expired = (task.stop_time and task.next_run_time > task.stop_time and True or False) @@ -663,21 +679,26 @@ class Scheduler(MetaScheduler): and task.times_failed < task.retry_failed and QUEUED or task.retry_failed == -1 and QUEUED or st_mapping) - db(db.scheduler_task.id == task.task_id)(db.scheduler_task.status == RUNNING).update( + db( + (db.scheduler_task.id == task.task_id) & + (db.scheduler_task.status == RUNNING) + ).update( times_failed=db.scheduler_task.times_failed + 1, next_run_time=task.next_run_time, - status=status) + status=status + ) db.commit() - logger.info('task completed (%s)' % task_report.status) + logger.info('task completed (%s)', task_report.status) break except: db.rollback() + time.sleep(0.5) def adj_hibernation(self): if self.worker_status[0] == DISABLED: - hibernation = self.worker_status[1] + 1 if self.worker_status[ - 1] < MAXHIBERNATION else MAXHIBERNATION - self.worker_status = DISABLED, hibernation + wk_st = self.worker_status[1] + hibernation = wk_st + 1 if wk_st < MAXHIBERNATION else MAXHIBERNATION + self.worker_status[1] = hibernation def send_heartbeat(self, counter): if not self.db_thread: @@ -689,9 +710,6 @@ class Scheduler(MetaScheduler): db = self.db_thread sw, st = db.scheduler_worker, db.scheduler_task now = self.now() - expiration = now - datetime.timedelta(seconds=self.heartbeat * 3) - departure = now - datetime.timedelta( - seconds=self.heartbeat * 3 * MAXHIBERNATION) # record heartbeat mybackedstatus = db( sw.worker_name == self.worker_name).select().first() @@ -699,35 +717,37 @@ class Scheduler(MetaScheduler): sw.insert(status=ACTIVE, worker_name=self.worker_name, first_heartbeat=now, last_heartbeat=now, group_names=self.group_names) - self.worker_status = ACTIVE, 1 # activating the process + self.worker_status = [ACTIVE, 1] # activating the process else: if mybackedstatus.status == DISABLED: - self.worker_status = DISABLED, self.worker_status[ - 1] # keep sleeping + # keep sleeping + self.worker_status[0] = DISABLED if self.worker_status[1] == MAXHIBERNATION: logger.debug('........recording heartbeat') db(sw.worker_name == self.worker_name).update( last_heartbeat=now) - elif mybackedstatus.status == TERMINATE: - self.worker_status = TERMINATE, self.worker_status[1] + self.worker_status[0] = TERMINATE logger.debug("Waiting to terminate the current task") self.give_up() return elif mybackedstatus.status == KILL: - self.worker_status = KILL, self.worker_status[1] + self.worker_status[0] = KILL self.die() - else: - logger.debug('........recording heartbeat') + logger.debug('........recording heartbeat (%s)', self.worker_status[0]) db(sw.worker_name == self.worker_name).update( last_heartbeat=now, status=ACTIVE) - self.worker_status = ACTIVE, 1 # re-activating the process + self.worker_status[1] = 1 # re-activating the process self.do_assign_tasks = False + if counter % 5 == 0: try: # delete inactive workers + expiration = now - datetime.timedelta(seconds=self.heartbeat * 3) + departure = now - datetime.timedelta( + seconds=self.heartbeat * 3 * MAXHIBERNATION) logger.debug( ' freeing workers that have not sent heartbeat') inactive_workers = db( @@ -753,21 +773,31 @@ class Scheduler(MetaScheduler): def being_a_ticker(self): db = self.db_thread sw = db.scheduler_worker - ticker = db((sw.worker_name != self.worker_name) & ( - sw.is_ticker == True) & (sw.status == ACTIVE)).select().first() + all_active = db( + (sw.worker_name != self.worker_name) & (sw.status == ACTIVE) + ).select() + ticker = all_active.find(lambda row: row.is_ticker is True).first() + not_busy = self.worker_status[0] == ACTIVE if not ticker: - db(sw.worker_name == self.worker_name).update(is_ticker=True) - db(sw.worker_name != self.worker_name).update(is_ticker=False) - logger.info("TICKER: I'm a ticker (%s)" % self.worker_name) + if not_busy: + #only if this worker isn't busy, otherwise wait for a free one + db(sw.worker_name == self.worker_name).update(is_ticker=True) + db(sw.worker_name != self.worker_name).update(is_ticker=False) + logger.info("TICKER(%s): I'm a ticker", self.worker_name) + else: + #giving up, only if I'm not alone + if len(all_active) > 1: + db(sw.worker_name == self.worker_name).update(is_ticker=False) + else: + not_busy = True db.commit() - return True + return not_busy else: logger.info( "%s is a ticker, I'm a poor worker" % ticker.worker_name) return False - def assign_tasks(self): - db = self.db + def assign_tasks(self, db): sw, st = db.scheduler_worker, db.scheduler_task now = self.now() all_workers = db(sw.status == ACTIVE).select() @@ -786,8 +816,15 @@ class Scheduler(MetaScheduler): #the scheduler): then it wasn't expired, but now it is db(st.status.belongs( (QUEUED, ASSIGNED)))(st.stop_time < now).update(status=EXPIRED) - - all_available = db(st.status.belongs((QUEUED, ASSIGNED)))((st.times_run < st.repeats) | (st.repeats == 0))(st.start_time <= now)((st.stop_time == None) | (st.stop_time > now))(st.next_run_time <= now)(st.enabled == True) + + all_available = db( + (st.status.belongs((QUEUED, ASSIGNED))) & + ((st.times_run < st.repeats) | (st.repeats == 0)) & + (st.start_time <= now) & + ((st.stop_time == None) | (st.stop_time > now)) & + (st.next_run_time <= now) & + (st.enabled == True) + ) limit = len(all_workers) * (50 / (len(wkgroups) or 1)) #if there are a moltitude of tasks, let's figure out a maximum of tasks per worker. #this can be adjusted with some added intelligence (like esteeming how many tasks will @@ -804,8 +841,8 @@ class Scheduler(MetaScheduler): db.commit() x = 0 for group in wkgroups.keys(): - tasks = all_available(st.group_name==group).select( - limitby=(0, limit), orderby=st.next_run_time) + tasks = all_available(st.group_name == group).select( + limitby=(0, limit), orderby = st.next_run_time) #let's break up the queue evenly among workers for task in tasks: x += 1 @@ -818,8 +855,10 @@ class Scheduler(MetaScheduler): if w['c'] < counter: myw = i counter = w['c'] - d = dict(status=ASSIGNED, - assigned_worker_name=wkgroups[gname]['workers'][myw]['name']) + d = dict( + status=ASSIGNED, + assigned_worker_name=wkgroups[gname]['workers'][myw]['name'] + ) if not task.task_name: d['task_name'] = task.function_name task.update_record(**d) @@ -829,12 +868,15 @@ class Scheduler(MetaScheduler): #I didn't report tasks but I'm working nonetheless!!!! if x > 0: self.empty_runs = 0 - logger.info('TICKER: workers are %s' % len(all_workers)) - logger.info('TICKER: tasks are %s' % x) + #I'll be greedy only if tasks assigned are equal to the limit + # (meaning there could be others ready to be assigned) + self.greedy = x >= limit and True or False + logger.info('TICKER(%s): workers are %s', self.worker_name, len(all_workers)) + logger.info('TICKER(%s): tasks are %s', self.worker_name, x) def sleep(self): time.sleep(self.heartbeat * self.worker_status[1]) - # should only sleep until next available task + # should only sleep until next available task def queue_task(self, function, pargs=[], pvars={}, **kwargs): """ diff --git a/gluon/widget.py b/gluon/widget.py index d4a38d95..b754e037 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -1007,6 +1007,8 @@ def start_schedulers(options): processes.append(p) print "Currently running %s scheduler processes" % (len(processes)) p.start() + ##to avoid bashing the db at the same time + time.sleep(0.7) print "Processes started" for p in processes: try: From a511682ce1f13d3d98ffa6fad6cbfc097ea163af Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 1 Nov 2012 22:01:57 -0500 Subject: [PATCH 13/20] better google wallet, checks parameters --- VERSION | 2 +- gluon/contrib/google_wallet.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 8a06df38..a9c592de 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-01 21:35:48) stable +Version 2.2.1 (2012-11-01 22:01:51) stable diff --git a/gluon/contrib/google_wallet.py b/gluon/contrib/google_wallet.py index f24c9074..b1da6200 100644 --- a/gluon/contrib/google_wallet.py +++ b/gluon/contrib/google_wallet.py @@ -1,16 +1,15 @@ from gluon import XML - def button(merchant_id="123456789012345", products=[dict(name="shoes", quantity=1, price=23.5, currency='USD', description="running shoes black")]): - t = '' + t = '\n' list_products = '' for k, product in enumerate(products): - for key, value in product.items(): - list_products += t % dict(k=k + 1, key=key, value=value) - button = '
%s
' % (merchant_id, list_products, merchant_id) + for key in ('name','description','quantity','price','currency'): + list_products += t % dict(k=k + 1, key=key, value=product[key]) + button = """
\n%(list_products)s\n\n
""" % dict(merchant_id=merchant_id, list_products=list_products) return XML(button) From 859636e6e0b2bd0cd2845ed96bf443212774f47b Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 3 Nov 2012 11:54:46 -0500 Subject: [PATCH 14/20] fixed issue 1136, prevent redirection loop, better form, usability as embedded widgets, thanks Alan --- VERSION | 2 +- gluon/tools.py | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/VERSION b/VERSION index a9c592de..c9189afe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-01 22:01:51) stable +Version 2.2.1 (2012-11-03 11:54:40) stable diff --git a/gluon/tools.py b/gluon/tools.py index 4e9dc6dd..12f20b80 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -3298,8 +3298,16 @@ class Auth(object): self._wiki.env.update(env or {}) # if resolve is set to True, process request as wiki call # resolve=False allows initial setup without wiki redirection + wiki = None if resolve: - return self._wiki.read(slug)['content'] if slug else self._wiki() + action = str(current.request.args(0)).startswith("_") + if slug and not action: + wiki = self._wiki.read(slug)['content'] + else: + wiki = self._wiki() + if isinstance(wiki, basestring): + wiki = XML(wiki) + return wiki class Crud(object): @@ -5005,18 +5013,18 @@ class Wiki(object): db = self.auth.db slugs=db(db.wiki_page.id>0).select(db.wiki_page.id,db.wiki_page.slug) options=[OPTION(row.slug,_value=row.id) for row in slugs] - options.insert(0, OPTION('',_value='')) - form = FORM(LABEL(INPUT(_name='slug',value=current.request.args(1), - requires=(IS_SLUG(), - IS_NOT_IN_DB(db,db.wiki_page.slug)))), - LABEL(SELECT(*options,**dict( - _name='from_template',requires=IS_EMPTY_OR(IS_IN_DB(db,db.wiki_page.id)))),current.T(" Choose Template or empty for new Page")), - INPUT(_type='submit', - _value=current.T('Create Page from Slug')), - _class="well span6") + form = SQLFORM.factory(Field("slug", default=current.request.args(1), + requires=(IS_SLUG(), + IS_NOT_IN_DB(db,db.wiki_page.slug))), + Field("from_template", "reference wiki_page", + requires=IS_EMPTY_OR(IS_IN_DB(db, db.wiki_page, '%(slug)s')), + comment=current.T("Choose Template or empty for new Page")), + _class="well span6") + form.element("[type=submit]").attributes["_value"] = current.T("Create Page from Slug") + if form.process().accepted: -# form.vars.from_template = 0 if not form.vars.from_template else form.vars.from_template + # form.vars.from_template = 0 if not form.vars.from_template else form.vars.from_template redirect(URL(args=('_edit',form.vars.slug,form.vars.from_template or 0))) # added param return dict(content=form) From 7de90f18cc14c80e0df3390bbed7df8ffd84712e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 4 Nov 2012 09:14:47 -0600 Subject: [PATCH 15/20] adding missing views --- VERSION | 2 +- applications/examples/views/simple_examples/status.html | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 applications/examples/views/simple_examples/status.html diff --git a/VERSION b/VERSION index c9189afe..6e243c67 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-03 11:54:40) stable +Version 2.2.1 (2012-11-04 09:14:40) stable diff --git a/applications/examples/views/simple_examples/status.html b/applications/examples/views/simple_examples/status.html new file mode 100644 index 00000000..05184e84 --- /dev/null +++ b/applications/examples/views/simple_examples/status.html @@ -0,0 +1,3 @@ +{{extend 'layout.html'}} + +{{=toolbar}} From 8a137691ffa565f07bbed83b74018341ab0bda08 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 4 Nov 2012 18:12:41 -0600 Subject: [PATCH 16/20] fixed problem in rss2.py --- VERSION | 2 +- gluon/contrib/rss2.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 6e243c67..3de62cec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-04 09:14:40) stable +Version 2.2.1 (2012-11-04 18:12:34) stable diff --git a/gluon/contrib/rss2.py b/gluon/contrib/rss2.py index a39f15a1..95d01f45 100644 --- a/gluon/contrib/rss2.py +++ b/gluon/contrib/rss2.py @@ -520,3 +520,25 @@ class RSSItem(WriteXmlMixin): # Derived classes can hook into this to insert # output after the title and link elements pass + + +def dumps(rss, encoding='utf-8'): + s = cStringIO.StringIO() + rss.write_xml(s, encoding) + return s.getvalue() + + +def test(): + rss = RSS2(title='web2py feed', link='http://www.web2py.com', + description='About web2py', + lastBuildDate=datetime.datetime.now(), + items=[RSSItem(title='web2py and PyRSS2Gen-0.0', + link='http://www.web2py.com/examples/simple_examples/getrss', + description='web2py can now make rss feeds!', + guid=Guid('http://www.web2py.com/'), + pubDate=datetime.datetime(2007, 11, 14, 10, 30))]) + return dumps(rss) + + +if __name__ == '__main__': + print test() From 09bae08dfa8f6cda5861f433bb1d25c678fe29fa Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 4 Nov 2012 19:29:18 -0600 Subject: [PATCH 17/20] fixed rss2 again --- VERSION | 2 +- gluon/contrib/rss2.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 3de62cec..41d8cfdb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-04 18:12:34) stable +Version 2.2.1 (2012-11-04 19:29:10) stable diff --git a/gluon/contrib/rss2.py b/gluon/contrib/rss2.py index 95d01f45..155de0e7 100644 --- a/gluon/contrib/rss2.py +++ b/gluon/contrib/rss2.py @@ -45,7 +45,6 @@ __author__ = "Andrew Dalke " _generator_name = __name__ + "-" + ".".join(map(str, __version__)) import datetime - import sys if sys.version_info[0] == 3: @@ -523,7 +522,7 @@ class RSSItem(WriteXmlMixin): def dumps(rss, encoding='utf-8'): - s = cStringIO.StringIO() + s = StringIO() rss.write_xml(s, encoding) return s.getvalue() From 1f767c3497f6c8030219d8d4d990b94cd2ba2144 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 4 Nov 2012 19:55:15 -0600 Subject: [PATCH 18/20] fixed rss serializer again, thanks Charles Winebrinner --- VERSION | 2 +- gluon/contrib/rss2.py | 23 +---------------------- gluon/serializers.py | 2 +- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/VERSION b/VERSION index 41d8cfdb..6cb17dde 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-04 19:29:10) stable +Version 2.2.1 (2012-11-04 19:55:09) stable diff --git a/gluon/contrib/rss2.py b/gluon/contrib/rss2.py index 155de0e7..a39f15a1 100644 --- a/gluon/contrib/rss2.py +++ b/gluon/contrib/rss2.py @@ -45,6 +45,7 @@ __author__ = "Andrew Dalke " _generator_name = __name__ + "-" + ".".join(map(str, __version__)) import datetime + import sys if sys.version_info[0] == 3: @@ -519,25 +520,3 @@ class RSSItem(WriteXmlMixin): # Derived classes can hook into this to insert # output after the title and link elements pass - - -def dumps(rss, encoding='utf-8'): - s = StringIO() - rss.write_xml(s, encoding) - return s.getvalue() - - -def test(): - rss = RSS2(title='web2py feed', link='http://www.web2py.com', - description='About web2py', - lastBuildDate=datetime.datetime.now(), - items=[RSSItem(title='web2py and PyRSS2Gen-0.0', - link='http://www.web2py.com/examples/simple_examples/getrss', - description='web2py can now make rss feeds!', - guid=Guid('http://www.web2py.com/'), - pubDate=datetime.datetime(2007, 11, 14, 10, 30))]) - return dumps(rss) - - -if __name__ == '__main__': - print test() diff --git a/gluon/serializers.py b/gluon/serializers.py index b50ab994..ea9611d3 100644 --- a/gluon/serializers.py +++ b/gluon/serializers.py @@ -114,4 +114,4 @@ def rss(feed): description=str(entry.get('description', '')), pubDate=entry.get('created_on', now) ) for entry in feed.get('entries', [])]) - return rss2.dumps(rss) + return rss.to_xml(rss,encoding='utf-8') From 2fea9495b3932582c31166d76ae4be3f894ec22f Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 4 Nov 2012 20:45:25 -0600 Subject: [PATCH 19/20] fixed rss serializer again, thanks Charles Winebrinner --- VERSION | 2 +- gluon/serializers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 6cb17dde..410191a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-04 19:55:09) stable +Version 2.2.1 (2012-11-04 20:45:19) stable diff --git a/gluon/serializers.py b/gluon/serializers.py index ea9611d3..3cdb91fc 100644 --- a/gluon/serializers.py +++ b/gluon/serializers.py @@ -114,4 +114,4 @@ def rss(feed): description=str(entry.get('description', '')), pubDate=entry.get('created_on', now) ) for entry in feed.get('entries', [])]) - return rss.to_xml(rss,encoding='utf-8') + return rss.to_xml(encoding='utf-8') From adb0c08933f9819b3291961b455efbc30452f707 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 5 Nov 2012 12:39:17 -0600 Subject: [PATCH 20/20] improved scheduler, thanks Niphlod --- VERSION | 2 +- gluon/scheduler.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 410191a1..8e24ad95 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-11-04 20:45:19) stable +Version 2.2.1 (2012-11-05 12:39:09) stable diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 7b441749..b33a10ba 100644 --- a/gluon/scheduler.py +++ b/gluon/scheduler.py @@ -739,6 +739,8 @@ class Scheduler(MetaScheduler): db(sw.worker_name == self.worker_name).update( last_heartbeat=now, status=ACTIVE) self.worker_status[1] = 1 # re-activating the process + if self.worker_status[0] <> RUNNING: + self.worker_status[0] = ACTIVE self.do_assign_tasks = False