Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2012-11-16 11:31:05 +01:00
17 changed files with 212 additions and 126 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.2.1 (2012-11-05 12:39:09) stable
Version 2.2.1 (2012-11-15 14:34:13) stable
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -15
View File
@@ -1,15 +1 @@
{{
###
# response._vars contains the dictionary returned by thecontroller action
###
try:
from gluon.serializers import xml
response.write(xml(response._vars), escape=False)
response.headers['Content-Type'] = 'text/xml'
except (TypeError, ValueError):
raise HTTP(405, 'XML serialization error')
except ImportError:
raise HTTP(405, 'XML not available')
except:
raise HTTP(405, 'XML error')
}}
{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
<?xml version="1.0" encoding="ISO-8859-1"?>{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
+1 -1
View File
@@ -453,7 +453,7 @@ def compile_views(folder):
"""
path = pjoin(folder, 'views')
for file in listdir(path, '^[\w/\-]+(\.\w+)+$'):
for file in listdir(path, '^[\w/\-]+(\.\w+)*$'):
try:
data = parse_template(file, path)
except Exception, e:
+2
View File
@@ -150,6 +150,8 @@ class WebClient(object):
match = self.session_regex.match(cookie)
if match:
name = match.group('name')
print repr(cookie)
print name, self.sessions.get(name,None), value
if name in self.sessions and self.sessions[name] != value:
raise RuntimeError('Broken sessions %s' % name)
self.sessions[name] = value
+1 -1
View File
@@ -16,7 +16,7 @@ INVALID_MODULES = set(('', 'gluon', 'applications', 'custom_import'))
def custom_import_install():
if __builtin__.__import__ != custom_importer:
if __builtin__.__import__ == NATIVE_IMPORTER:
INVALID_MODULES.update(sys.modules.keys())
__builtin__.__import__ = custom_importer
+4 -4
View File
@@ -1199,7 +1199,7 @@ class BaseAdapter(ConnectionPool):
def BELONGS(self, first, second):
if isinstance(second, str):
return '(%s IN (%s))' % (self.expand(first), second[:-1])
elif second==[] or second==():
elif not second:
return '(1=0)'
items = ','.join(self.expand(item, first.type) for item in second)
return '(%s IN (%s))' % (self.expand(first), items)
@@ -4588,7 +4588,7 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
processor = attributes.get('processor',self.parse)
return processor(rows,fields,colnames,False)
def count(self,query,distinct=None):
def count(self,query,distinct=None,limit=None):
if distinct:
raise RuntimeError("COUNT DISTINCT not supported")
(items, tablename, fields) = self.select_raw(query)
@@ -4596,7 +4596,7 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
try:
return len(items)
except TypeError:
return items.count(limit=None)
return items.count(limit=limit)
def delete(self,tablename, query):
"""
@@ -8987,7 +8987,7 @@ class LazySet(object):
def _count(self,distinct=None):
return self._getset()._count(distinct)
def _select(self, *fields, **attributes):
return self._getset()._select(*field,**attributes)
return self._getset()._select(*fields,**attributes)
def _delete(self):
return self._getset()._delete()
def _update(self, **update_fields):
+2 -1
View File
@@ -554,7 +554,8 @@ class Session(Storage):
else:
response.session_id = None
# do not try load the data from file is these was data in cookie
if response.session_id and not session_cookie_data:
if response.session_id and not session_cookie_data and \
os.path.exists(response.session_filename):
try:
response.session_file = \
open(response.session_filename, 'rb+')
+26 -20
View File
@@ -129,6 +129,11 @@ def xmlescape(data, quote=True):
data = cgi.escape(data, quote).replace("'", "&#x27;")
return data
def call_as_list(f,*a,**b):
if not isinstance(f, (list,tuple)):
f = [f]
for item in f:
item(*a,**b)
def truncate_string(text, length, dots='...'):
text = text.decode('utf-8')
@@ -1247,19 +1252,20 @@ class HTML(DIV):
lang = 'en'
self.attributes['_lang'] = lang
doctype = self['doctype']
if doctype:
if doctype == 'strict':
doctype = self.strict
elif doctype == 'transitional':
doctype = self.transitional
elif doctype == 'frameset':
doctype = self.frameset
elif doctype == 'html5':
doctype = self.html5
else:
doctype = '%s\n' % doctype
else:
if doctype is None:
doctype = self.transitional
elif doctype == 'strict':
doctype = self.strict
elif doctype == 'transitional':
doctype = self.transitional
elif doctype == 'frameset':
doctype = self.frameset
elif doctype == 'html5':
doctype = self.html5
elif doctype == '':
doctype = ''
else:
doctype = '%s\n' % doctype
(fa, co) = self._xml()
return '%s<%s%s>%s</%s>' % (doctype, self.tag, fa, co, self.tag)
@@ -1757,7 +1763,7 @@ class INPUT(DIV):
t = self['_type'] = 'text'
t = t.lower()
value = self['value']
if self['_value'] is None:
if self['_value'] is None or isinstance(self['_value'],cgi.FieldStorage):
_value = None
else:
_value = str(self['_value'])
@@ -1997,20 +2003,20 @@ class FORM(DIV):
onsuccess = onvalidation.get('onsuccess', None)
onfailure = onvalidation.get('onfailure', None)
onchange = onvalidation.get('onchange', None)
if [k for k in onvalidation if not k in (
'onsuccess','onfailure','onchange')]:
raise RuntimeError('Invalid key in onvalidate dict')
if onsuccess and status:
onsuccess(self)
call_as_list(onsuccess,self)
if onfailure and request_vars and not status:
onfailure(self)
call_as_list(onfailure,self)
status = len(self.errors) == 0
if changed:
if onchange and self.record_changed and \
self.detect_record_change:
onchange(self)
call_as_list(onchange,self)
elif status:
if isinstance(onvalidation, (list, tuple)):
[f(self) for f in onvalidation]
else:
onvalidation(self)
call_as_list(onvalidation, self)
if self.errors:
status = False
if not session is None:
+1 -1
View File
@@ -154,4 +154,4 @@ def redirect(location='', how=303, client_side=False):
from gluon import current
if client_side and current.request.ajax:
raise HTTP(200, **{'web2py-component-command': 'window.location.reload(true)'})
+4 -4
View File
@@ -951,10 +951,10 @@ class Scheduler(MetaScheduler):
orderby = ~st.id | ~sr.id
row = self.db(q).select(
*fields,
orderby=orderby,
left=left,
limitby=(0, 1)
).first()
**dict(orderby=orderby,
left=left,
limitby=(0, 1))
).first()
if output:
row.result = row.scheduler_run.result and \
loads(row.scheduler_run.result,
+133 -48
View File
@@ -1675,6 +1675,8 @@ class SQLFORM(FORM):
createargs={},
editargs={},
viewargs={},
buttons_placement = 'right',
links_placement = 'right'
):
# jQuery UI ThemeRoller classes (empty if ui is disabled)
@@ -1729,12 +1731,18 @@ class SQLFORM(FORM):
def url(**b):
b['args'] = args + b.get('args', [])
localvars = request.vars.copy()
localvars.update(b.get('vars', {}))
b['vars'] = localvars
b['hash_vars'] = False
b['user_signature'] = user_signature
return URL(**b)
def url2(**b):
b['args'] = request.args + b.get('args', [])
localvars = request.vars.copy()
localvars.update(b.get('vars', {}))
b['vars'] = localvars
b['hash_vars'] = False
b['user_signature'] = user_signature
return URL(**b)
@@ -1824,6 +1832,14 @@ class SQLFORM(FORM):
buttons.append(link(record))
return buttons
def linsert(lst, i, x):
"""
a = [1,2]
linsert(a, 1, [0,3])
a = [1, 0, 3, 2]
"""
lst[i:i] = x
formfooter = DIV(
_class='form_footer row_buttons %(header)s %(cornerbottom)s' % ui)
@@ -1903,6 +1919,9 @@ class SQLFORM(FORM):
(ExporterTSV, 'TSV (Excel compatible, hidden cols)'),
tsv=(ExporterTSV, 'TSV (Excel compatible)'))
if not exportclasses is None:
"""
remember: allow to set exportclasses=dict(csv=False) to disable the csv format
"""
exportManager.update(exportclasses)
export_type = request.vars._export_type
@@ -1919,23 +1938,22 @@ class SQLFORM(FORM):
if sign == '~':
orderby = ~orderby
table_fields = [f for f in fields if f._tablename in tablenames]
if export_type in ('csv_with_hidden_cols', 'tsv_with_hidden_cols'):
expcolumns = columns
if export_type.endswith('with_hidden_cols'):
expcolumns = [f for f in fields if f._tablename in tablenames]
if export_type in exportManager and exportManager[export_type]:
if request.vars.keywords:
try:
dbset = dbset(SQLFORM.build_query(
fields, request.vars.get('keywords', '')))
rows = dbset.select(cacheable=True)
rows = dbset.select(cacheable=True, *expcolumns)
except Exception, e:
response.flash = T('Internal Error')
rows = []
else:
rows = dbset.select(cacheable=True)
else:
rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *columns)
rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *expcolumns)
if export_type in exportManager:
value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value
oExp = clazz(rows)
@@ -2000,6 +2018,9 @@ class SQLFORM(FORM):
c = 'count(*)'
nrows = dbset.select(c, left=left, cacheable=True,
groupby=groupby).first()[c]
elif dbset._db._adapter.dbengine=='google:datastore':
#if we don't set a limit, this can timeout for a large table
nrows = dbset.db._adapter.count(dbset.query, limit=1000)
else:
nrows = dbset.count()
except:
@@ -2017,9 +2038,9 @@ class SQLFORM(FORM):
else:
orderby = (order[:1] == '~' and ~sort_field) or sort_field
head = TR(_class=ui.get('header'))
headcols = []
if selectable:
head.append(TH(_class=ui.get('default')))
headcols.append(TH(_class=ui.get('default')))
for field in fields:
if columns and not str(field) in columns:
continue
@@ -2037,22 +2058,88 @@ class SQLFORM(FORM):
header = A(header, marker, _href=url(vars=dict(
keywords=request.vars.keywords or '',
order=key)), _class=trap_class())
head.append(TH(header, _class=ui.get('default')))
headcols.append(TH(header, _class=ui.get('default')))
toadd = []
if links and links_in_grid:
for link in links:
if isinstance(link, dict):
head.append(TH(link['header'], _class=ui.get('default')))
toadd.append(TH(link['header'], _class=ui.get('default')))
if links_placement in ['right', 'both']:
headcols.extend(toadd)
if links_placement in ['left', 'both']:
linsert(headcols, 0, toadd)
# Include extra column for buttons if needed.
include_buttons_column = (details or editable or deletable or
(links and links_in_grid and
not all([isinstance(link, dict) for link in links])))
if include_buttons_column:
head.append(TH(_class=ui.get('default')))
if buttons_placement in ['right', 'both']:
headcols.append(TH(_class=ui.get('default','')))
if buttons_placement in ['left', 'both']:
headcols.insert(0, TH(_class=ui.get('default','')))
head = TR(*headcols, **dict(_class=ui.get('header')))
cursor = True
#figure out what page we are one to setup the limitby
if paginate and dbset._db._adapter.dbengine=='google:datastore':
cursor = request.vars.cursor or True
limitby = (0, paginate)
try: page = int(request.vars.page or 1)-1
except ValueError: page = 0
elif paginate and paginate<nrows:
try: page = int(request.vars.page or 1)-1
except ValueError: page = 0
limitby = (paginate*page,paginate*(page+1))
else:
limitby = None
try:
table_fields = [f for f in fields if f._tablename in tablenames]
if dbset._db._adapter.dbengine=='google:datastore':
rows = dbset.select(left=left,orderby=orderby,
groupby=groupby,limitby=limitby,
reusecursor=cursor,
cacheable=True,*table_fields)
next_cursor = dbset._db.get('_lastcursor', None)
else:
rows = dbset.select(left=left,orderby=orderby,
groupby=groupby,limitby=limitby,
cacheable=True,*table_fields)
except SyntaxError:
rows = None
next_cursor = None
error = T("Query Not Supported")
except Exception, e:
rows = None
next_cursor = None
error = T("Query Not Supported: %s")%e
message = error
if not message and nrows:
if dbset._db._adapter.dbengine=='google:datastore' and nrows>=1000:
message = T('at least %(nrows)s records found') % dict(nrows=nrows)
else:
message = T('%(nrows)s records found') % dict(nrows=nrows)
console.append(DIV(message,_class='web2py_counter'))
paginator = UL()
if paginate and paginate < nrows:
if paginate and dbset._db._adapter.dbengine=='google:datastore':
#this means we may have a large table with an unknown number of rows.
try:
page = int(request.vars.page or 1)-1
except ValueError:
page = 0
paginator.append(LI('page %s'%(page+1)))
if next_cursor:
d = dict(page=page+2, cursor=next_cursor)
if order: d['order']=order
if request.vars.keywords: d['keywords']=request.vars.keywords
paginator.append(LI(
A('next',_href=url(vars=d),_class=trap_class())))
elif paginate and paginate<nrows:
npages, reminder = divmod(nrows, paginate)
if reminder:
npages += 1
@@ -2060,7 +2147,6 @@ class SQLFORM(FORM):
page = int(request.vars.page or 1) - 1
except ValueError:
page = 0
limitby = (paginate * page, paginate * (page + 1))
def self_link(name, p):
d = dict(page=p + 1)
@@ -2088,38 +2174,15 @@ class SQLFORM(FORM):
else:
limitby = None
try:
table_fields = [f for f in fields if f._tablename in tablenames]
rows = dbset.select(left=left, orderby=orderby,
groupby=groupby, limitby=limitby,
*table_fields)
except SyntaxError:
rows = None
error = T("Query Not Supported")
if nrows:
message = error or T('%(nrows)s records found') % dict(nrows=nrows)
console.append(DIV(message, _class='web2py_counter'))
if rows:
htmltable = TABLE(THEAD(head))
tbody = TBODY()
numrec = 0
for row in rows:
if numrec % 2 == 0:
classtr = 'even'
else:
classtr = 'odd'
numrec += 1
trcols = []
id = row[field_id]
if id:
rid = id
if callable(rid): # can this ever be callable?
rid = rid(row)
tr = TR(_id=rid, _class='%s %s' % (classtr, 'with_id'))
else:
tr = TR(_class=classtr)
if selectable:
tr.append(
trcols.append(
INPUT(_type="checkbox", _name="records", _value=id,
value=request.vars.records))
for field in fields:
@@ -2157,15 +2220,21 @@ class SQLFORM(FORM):
value = truncate_string(value, maxlength)
elif not isinstance(value, DIV):
value = field.formatter(value)
tr.append(TD(value))
trcols.append(TD(value))
row_buttons = TD(_class='row_buttons')
if links and links_in_grid:
toadd = []
for link in links:
if isinstance(link, dict):
tr.append(TD(link['body'](row)))
toadd.append(TD(link['body'](row)))
else:
if link(row):
row_buttons.append(link(row))
if links_placement in ['right', 'both']:
trcols.extend(toadd)
if links_placement in ['left', 'both']:
linsert(trcols, 0, toadd)
if include_buttons_column:
if details and (not callable(details) or details(row)):
row_buttons.append(gridbutton(
@@ -2180,7 +2249,24 @@ class SQLFORM(FORM):
'buttondelete', 'Delete',
callback=url(args=['delete', tablename, id]),
delete='tr'))
tr.append(row_buttons)
if buttons_placement in ['right', 'both']:
trcols.append(row_buttons)
if buttons_placement in ['left', 'both']:
trcols.insert(0, row_buttons)
if numrec % 2 == 0:
classtr = 'even'
else:
classtr = 'odd'
numrec += 1
if id:
rid = id
if callable(rid): # can this ever be callable?
rid = rid(row)
tr = TR(*trcols, **dict(
_id=rid,
_class='%s %s' % (classtr, 'with_id')))
else:
tr = TR(*trcols, **dict(_class=classtr))
tbody.append(tr)
htmltable.append(tbody)
htmltable = DIV(htmltable, _style='width:100%;overflow-x:auto')
@@ -2198,6 +2284,8 @@ class SQLFORM(FORM):
if csv and nrows:
export_links = []
for k, v in sorted(exportManager.items()):
if not v:
continue
label = v[1] if hasattr(v, "__getitem__") else k
link = url2(vars=dict(
order=request.vars.order or '',
@@ -2311,7 +2399,6 @@ class SQLFORM(FORM):
name = format % record
except TypeError:
name = id
nameLink = 'view'
breadcrumbs.append(
LI(A(T(db[referee]._plural),
_class=trap_class(),
@@ -2352,9 +2439,8 @@ class SQLFORM(FORM):
check = {}
id_field_name = table._id.name
for rfield in table._referenced_by:
if rfield.readable:
check[rfield.tablename] = \
check.get(rfield.tablename, []) + [rfield.name]
check[rfield.tablename] = \
check.get(rfield.tablename, []) + [rfield.name]
if isinstance(linked_tables, dict):
linked_tables = linked_tables.get(table._tablename, [])
for tablename in sorted(check):
@@ -2740,7 +2826,6 @@ class ExporterTSV(ExportClass):
writer = csv.writer(out, delimiter='\t')
import codecs
final.write(codecs.BOM_UTF16)
colnames = [a.split('.') for a in self.rows.colnames]
writer.writerow(
[unicode(col).encode("utf8") for col in self.rows.colnames])
data = out.getvalue().decode("utf8")
+28 -23
View File
@@ -1828,14 +1828,15 @@ class Auth(object):
created_on=request.now,
renew=interactivelogin)
service = session._cas_service
query_sep = '&' if '?' in service else '?'
del session._cas_service
if 'warn' in request.vars and not interactivelogin:
response.headers[
'refresh'] = "5;URL=%s" % service + "?ticket=" + ticket
'refresh'] = "5;URL=%s" % service + query_sep + "ticket=" + ticket
return A("Continue to %s" % service,
_href=service + "?ticket=" + ticket)
_href=service + query_sep + "ticket=" + ticket)
else:
redirect(service + "?ticket=" + ticket)
redirect(service + query_sep + "ticket=" + ticket)
if self.is_logged_in() and not 'renew' in request.vars:
return allow_access()
elif not self.is_logged_in() and 'gateway' in request.vars:
@@ -2065,7 +2066,8 @@ class Auth(object):
# invalid login
session.flash = self.messages.invalid_login
redirect(
self.url(args=request.args, vars=request.get_vars))
self.url(args=request.args, vars=request.get_vars),
client_side=True)
else:
# use a central authentication server
@@ -2081,7 +2083,7 @@ class Auth(object):
else:
# we need to pass through login again before going on
next = self.url(self.settings.function, args='login')
redirect(cas.login_url(next))
redirect(cas.login_url(next), client_side=True)
# process authenticated users
if user:
@@ -2104,14 +2106,14 @@ class Auth(object):
if next == session._auth_next:
session._auth_next = None
next = replace_id(next, form)
redirect(next)
redirect(next, client_side=True)
table_user[username].requires = old_requires
return form
elif user:
callback(onaccept, None)
if next == session._auth_next:
del session._auth_next
redirect(next)
redirect(next, client_side=True)
def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT):
"""
@@ -2162,7 +2164,7 @@ class Auth(object):
response = current.response
session = current.session
if self.is_logged_in():
redirect(self.settings.logged_url)
redirect(self.settings.logged_url, client_side=True)
if next is DEFAULT:
next = self.next or self.settings.register_next
if onvalidation is DEFAULT:
@@ -2278,7 +2280,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next)
redirect(next, client_side=True)
return form
def is_logged_in(self):
@@ -2526,7 +2528,7 @@ class Auth(object):
raise Exception
except Exception:
session.flash = self.messages.invalid_reset_password
redirect(next)
redirect(next, client_side=True)
passfield = self.settings.password_field
form = SQLFORM.factory(
Field('new_password', 'password',
@@ -2551,7 +2553,7 @@ class Auth(object):
session.flash = self.messages.password_changed
if self.settings.login_after_password_change:
self.login_user(user)
redirect(next)
redirect(next, client_side=True)
return form
def request_reset_password(
@@ -2609,10 +2611,10 @@ class Auth(object):
user = table_user(email=form.vars.email)
if not user:
session.flash = self.messages.invalid_email
redirect(self.url(args=request.args))
redirect(self.url(args=request.args), client_side=True)
elif user.registration_key in ('pending', 'disabled', 'blocked'):
session.flash = self.messages.registration_pending
redirect(self.url(args=request.args))
redirect(self.url(args=request.args), client_side=True)
if self.email_reset_password(user):
session.flash = self.messages.email_sent
else:
@@ -2623,7 +2625,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next)
redirect(next, client_side=True)
# old_requires = table_user.email.requires
return form
@@ -2668,7 +2670,7 @@ class Auth(object):
"""
if not self.is_logged_in():
redirect(self.settings.login_url)
redirect(self.settings.login_url, client_side=True)
db = self.db
table_user = self.table_user()
s = db(table_user.id == self.user.id)
@@ -2718,7 +2720,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next)
redirect(next, client_side=True)
return form
def profile(
@@ -2738,7 +2740,7 @@ class Auth(object):
table_user = self.table_user()
if not self.is_logged_in():
redirect(self.settings.login_url)
redirect(self.settings.login_url, client_side=True)
passfield = self.settings.password_field
table_user[passfield].writable = False
request = current.request
@@ -2774,7 +2776,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next)
redirect(next, client_side=True)
return form
def is_impersonating(self):
@@ -3287,13 +3289,14 @@ class Auth(object):
manage_permissions=False,
force_prefix='',
restrict_search=False,
resolve=True):
resolve=True,
extra=None):
if not hasattr(self, '_wiki'):
self._wiki = Wiki(self, render=render,
manage_permissions=manage_permissions,
force_prefix=force_prefix,
restrict_search=restrict_search,
env=env)
env=env, extra=extra or {})
else:
self._wiki.env.update(env or {})
# if resolve is set to True, process request as wiki call
@@ -4242,7 +4245,7 @@ class Service(object):
if not method in methods:
return return_error(id, 100, 'method "%s" does not exist' % method)
try:
s = methods[method](*params)
s = methods[method](**params)
if hasattr(s, 'as_list'):
s = s.as_list()
return return_response(id, s)
@@ -4667,7 +4670,8 @@ class Wiki(object):
rows_page = 25
def markmin_render(self, page):
html = MARKMIN(page.body, url=True, environment=self.env,
html = MARKMIN(page.body, extra=self.extra,
url=True, environment=self.env,
autolinks=lambda link: expand_one(link, {})).xml()
html += DIV(_class='w2p_wiki_tags',
*[A(t.strip(), _href=URL(args='_search', vars=dict(q=t)))
@@ -4696,7 +4700,7 @@ class Wiki(object):
def __init__(self, auth, env=None, render='markmin',
manage_permissions=False, force_prefix='',
restrict_search=False):
restrict_search=False, extra=None):
self.env = env or {}
self.env['component'] = Wiki.component
if render == 'markmin':
@@ -4712,6 +4716,7 @@ class Wiki(object):
self.host = current.request.env.http_host
perms = self.manage_permissions = manage_permissions
self.restrict_search = restrict_search
self.extra = self.extra or {}
db = auth.db
table_definitions = [
('wiki_page', {
+1
View File
@@ -104,6 +104,7 @@ def try_start_browser(url):
try:
import webbrowser
url = url.replace('0.0.0.0','127.0.0.1')
webbrowser.open(url)
except:
print 'warning: unable to detect your browser'