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 = '
' % (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 = """""" % 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: