diff --git a/VERSION b/VERSION index d6b339bd..8e24ad95 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.2.1 (2012-10-30 11:09:19) stable +Version 2.2.1 (2012-11-05 12:39:09) 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}} 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) 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) 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): 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) diff --git a/gluon/scheduler.py b/gluon/scheduler.py index 2cc7e1c5..b33a10ba 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,39 @@ 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 + if self.worker_status[0] <> RUNNING: + self.worker_status[0] = ACTIVE 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 +775,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 +818,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 +843,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 +857,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 +870,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/serializers.py b/gluon/serializers.py index b50ab994..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 rss2.dumps(rss) + return rss.to_xml(encoding='utf-8') diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index a2aa12ae..77b332c8 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) \ @@ -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: diff --git a/gluon/tools.py b/gluon/tools.py index b6fb9ace..12f20b80 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 @@ -3294,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): @@ -3464,10 +3476,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 +3489,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 @@ -4581,16 +4595,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, '*') @@ -4598,23 +4619,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): @@ -4622,21 +4644,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() @@ -4809,7 +4831,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': @@ -4884,7 +4906,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) @@ -4898,15 +4920,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') @@ -4990,13 +5011,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 = 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: - 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): 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: 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: