Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2012-08-22 03:22:34 +02:00
10 changed files with 163 additions and 127 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.00.0 (2012-08-21 07:57:05) dev Version 2.00.0 (2012-08-21 18:20:02) dev
@@ -1379,6 +1379,7 @@ def errors():
return dict(errors = [x[1] for x in decorated], app=app, method=method, db_ready=db_ready) return dict(errors = [x[1] for x in decorated], app=app, method=method, db_ready=db_ready)
elif method == 'dbnew': elif method == 'dbnew':
errors_path = apath('%s/errors' % app, r=request) errors_path = apath('%s/errors' % app, r=request)
tk_db, tk_table = get_ticket_storage(app) tk_db, tk_table = get_ticket_storage(app)
+1 -1
View File
@@ -294,7 +294,7 @@ div.error {
margin-bottom:18px; margin-bottom:18px;
} }
.web2py_breadcrumbs ul li { li.w2p_grid_breadcrumb_elem {
display:inline-block; display:inline-block;
} }
+25 -17
View File
@@ -1702,6 +1702,16 @@ class BaseAdapter(ConnectionPool):
def parse_datetime(self, value, field_type): def parse_datetime(self, value, field_type):
if not isinstance(value, datetime.datetime): if not isinstance(value, datetime.datetime):
if '+' in value:
value,tz = value.split('+')
h,m = tz.split(':')
dt = datetime.timedelta(seconds=3600*int(h)+60*int(m))
elif '-' in value:
value,tz = value.split('-')
h,m = tz.split(':')
dt = -datetime.timedelta(seconds=3600*int(h)+60*int(m))
else:
dt = None
date_part, time_part = ( date_part, time_part = (
str(value).replace('T',' ')+' ').split(' ',1) str(value).replace('T',' ')+' ').split(' ',1)
(y, m, d) = map(int,date_part.split('-')) (y, m, d) = map(int,date_part.split('-'))
@@ -1710,6 +1720,8 @@ class BaseAdapter(ConnectionPool):
time_items = map(int,time_parts) time_items = map(int,time_parts)
(h, mi, s) = time_items (h, mi, s) = time_items
value = datetime.datetime(y, m, d, h, mi, s) value = datetime.datetime(y, m, d, h, mi, s)
if dt:
value = value + dt
return value return value
def parse_blob(self, value, field_type): def parse_blob(self, value, field_type):
@@ -6269,6 +6281,10 @@ class Row(dict):
this is only used to store a Row this is only used to store a Row
""" """
def __init__(self,*args,**kwargs):
dict.__init__(self,*args,**kwargs)
self.__dict__ = self
def __getitem__(self, key): def __getitem__(self, key):
key=str(key) key=str(key)
m = regex_table_field.match(key) m = regex_table_field.match(key)
@@ -6287,12 +6303,6 @@ class Row(dict):
def __setitem__(self, key, value): def __setitem__(self, key, value):
dict.__setitem__(self, str(key), value) dict.__setitem__(self, str(key), value)
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value):
self[key] = value
def __str__(self): def __str__(self):
### this could be made smarter ### this could be made smarter
return '<Row ' + dict.__repr__(self) + '>' return '<Row ' + dict.__repr__(self) + '>'
@@ -6562,6 +6572,7 @@ class DAL(dict):
:fake_migrate_all (defaults to False). If sets to True fake migrates ALL tables :fake_migrate_all (defaults to False). If sets to True fake migrates ALL tables
:attempts (defaults to 5). Number of times to attempt connecting :attempts (defaults to 5). Number of times to attempt connecting
""" """
self.__dict__ = self
if not decode_credentials: if not decode_credentials:
credential_decoder = lambda cred: cred credential_decoder = lambda cred: cred
else: else:
@@ -6942,7 +6953,7 @@ def index():
**args **args
): ):
if self._common_fields: if self._common_fields:
fields = fields + self._common_fields fields = list(fields) + list(self._common_fields)
table_class = args.get('table_class',Table) table_class = args.get('table_class',Table)
table = table_class(self, tablename, *fields, **args) table = table_class(self, tablename, *fields, **args)
@@ -6973,6 +6984,9 @@ def index():
def __getitem__(self, key): def __getitem__(self, key):
key = str(key) key = str(key)
return dict.__getitem__(self,key)
def __getattr__(self, key):
if not key is '_LAZY_TABLES' and key in self._LAZY_TABLES: if not key is '_LAZY_TABLES' and key in self._LAZY_TABLES:
tablename, fields, args = self._LAZY_TABLES.pop(key) tablename, fields, args = self._LAZY_TABLES.pop(key)
return self.lazy_define_table(tablename,*fields,**args) return self.lazy_define_table(tablename,*fields,**args)
@@ -6981,14 +6995,11 @@ def index():
def __setitem__(self, key, value): def __setitem__(self, key, value):
dict.__setitem__(self, str(key), value) dict.__setitem__(self, str(key), value)
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key[:1]!='_' and key in self: if key[:1]!='_' and key in self:
raise SyntaxError, \ raise SyntaxError, \
'Object %s exists and cannot be redefined' % key 'Object %s exists and cannot be redefined' % key
self[key] = value dict.__setattr__(self,key,value)
def __repr__(self): def __repr__(self):
return '<DAL ' + dict.__repr__(self) + '>' return '<DAL ' + dict.__repr__(self) + '>'
@@ -7190,7 +7201,7 @@ class Table(dict):
:raises SyntaxError: when a supplied field is of incorrect type. :raises SyntaxError: when a supplied field is of incorrect type.
""" """
self.__dict__ = self
self._actual = False # set to True by define_table() self._actual = False # set to True by define_table()
self._tablename = tablename self._tablename = tablename
self._sequence_name = args.get('sequence_name',None) or \ self._sequence_name = args.get('sequence_name',None) or \
@@ -7261,7 +7272,7 @@ class Table(dict):
tmp = field.uploadfield = '%s_blob' % field.name tmp = field.uploadfield = '%s_blob' % field.name
if isinstance(field.uploadfield,str) and \ if isinstance(field.uploadfield,str) and \
not [f for f in fields if f.name==field.uploadfield]: not [f for f in fields if f.name==field.uploadfield]:
fields.append(self._db.Field(field.uploadfield,'blob',default='')) fields.append(Field(field.uploadfield,'blob',default=''))
lower_fieldnames = set() lower_fieldnames = set()
reserved = dir(Table) + ['fields'] reserved = dir(Table) + ['fields']
@@ -7460,13 +7471,10 @@ class Table(dict):
elif not str(key).isdigit() or not self._db(self._id == key).delete(): elif not str(key).isdigit() or not self._db(self._id == key).delete():
raise SyntaxError, 'No such record: %s' % key raise SyntaxError, 'No such record: %s' % key
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key[:1]!='_' and key in self: if key[:1]!='_' and key in self:
raise SyntaxError, 'Object exists and cannot be redefined: %s' % key raise SyntaxError, 'Object exists and cannot be redefined: %s' % key
self[key] = value dict.__setattr__(self,key,value)
def __iter__(self): def __iter__(self):
for fieldname in self.fields: for fieldname in self.fields:
+26 -4
View File
@@ -83,6 +83,7 @@ class Request(Storage):
""" """
def __init__(self): def __init__(self):
Storage.__init__(self)
self.wsgi = Storage() # hooks to environ and start_response self.wsgi = Storage() # hooks to environ and start_response
self.env = Storage() self.env = Storage()
self.cookies = Cookie.SimpleCookie() self.cookies = Cookie.SimpleCookie()
@@ -165,6 +166,7 @@ class Response(Storage):
""" """
def __init__(self): def __init__(self):
Storage.__init__(self)
self.status = 200 self.status = 200
self.headers = dict() self.headers = dict()
self.headers['X-Powered-By'] = 'web2py' self.headers['X-Powered-By'] = 'web2py'
@@ -274,6 +276,8 @@ class Response(Storage):
stream, stream,
chunk_size = DEFAULT_CHUNK_SIZE, chunk_size = DEFAULT_CHUNK_SIZE,
request=None, request=None,
attachment=False,
filename=None
): ):
""" """
if a controller function:: if a controller function::
@@ -281,7 +285,28 @@ class Response(Storage):
return response.stream(file, 100) return response.stream(file, 100)
the file content will be streamed at 100 bytes at the time the file content will be streamed at 100 bytes at the time
Optional kwargs:
(for custom stream calls)
attachment=True # Send as attachment. Usually creates a
# pop-up download window on browsers
filename=None # The name for the attachment
Note: for using the stream name (filename) with attachments
the option must be explicitly set as function parameter(will
default to the last request argument otherwise)
""" """
# for attachment settings and backward compatibility
keys = [item.lower() for item in self.headers]
if attachment:
if filename is None:
attname = ""
else:
attname = filename
self.headers["Content-Disposition"] = \
"attachment;filename=%s" % attname
if not request: if not request:
request = current.request request = current.request
if isinstance(stream, (str, unicode)): if isinstance(stream, (str, unicode)):
@@ -291,12 +316,9 @@ class Response(Storage):
headers=self.headers) headers=self.headers)
# ## the following is for backward compatibility # ## the following is for backward compatibility
if hasattr(stream, 'name'): if hasattr(stream, 'name'):
filename = stream.name filename = stream.name
else:
filename = None
keys = [item.lower() for item in self.headers]
if filename and not 'content-type' in keys: if filename and not 'content-type' in keys:
self.headers['Content-Type'] = contenttype(filename) self.headers['Content-Type'] = contenttype(filename)
if filename and not 'content-length' in keys: if filename and not 'content-length' in keys:
+3 -2
View File
@@ -446,8 +446,9 @@ def wsgibase(environ, responder):
elif not request.is_local and \ elif not request.is_local and \
os.path.exists(os.path.join(request.folder,'DISABLED')): os.path.exists(os.path.join(request.folder,'DISABLED')):
raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>") raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
request.url = Url(r=request, args=request.args, request.url = Url(r=request,
extension=request.raw_extension) args=request.args,
extension=request.raw_extension)
# ################################################## # ##################################################
# build missing folders # build missing folders
+1
View File
@@ -33,6 +33,7 @@ class TicketStorage(Storage):
db=None, db=None,
tablename='web2py_ticket' tablename='web2py_ticket'
): ):
Storage.__init__(self)
self.db = db self.db = db
self.tablename = tablename self.tablename = tablename
+35 -12
View File
@@ -687,18 +687,40 @@ def formstyle_ul(form, fields):
def formstyle_bootstrap(form, fields): def formstyle_bootstrap(form, fields):
''' bootstrap format form layout ''' ''' bootstrap format form layout '''
form['_class'] = 'form-horizontal' form['_class'] = 'form-horizontal'
table = FIELDSET() parent = FIELDSET()
for id, label, controls, help in fields: for id, label, controls, help in fields:
if isinstance(controls, (INPUT, SELECT, TEXTAREA)): # wrappers
_help = SPAN(help, _class='help-inline')
# embed _help into _controls
_controls = DIV(controls, _help, _class='controls')
# submit unflag by default
_submit = False
if isinstance(controls, INPUT):
controls['_class'] = 'input-xlarge' controls['_class'] = 'input-xlarge'
if controls['_type'] == 'submit':
# flag submit button
_submit = True
controls['_class'] = 'btn btn-primary'
if isinstance(controls, SELECT):
controls['_class'] = 'input-xlarge'
if isinstance(controls, TEXTAREA):
controls['_class'] = 'input-xlarge'
if isinstance(label, LABEL): if isinstance(label, LABEL):
label['_class'] = 'control-label' label['_class'] = 'control-label'
# styles
_help = DIV(help, _class='help-block') if _submit:
# embed _help into _controls don't wrap label # submit button has unwrapped label and controls, different class
_controls = DIV(controls, _help, _class='controls') parent.append(DIV(label, controls, _class='form-actions'))
table.append(DIV(label, _controls, _class='control-group',_id=id)) # unflag submit (possible side effect)
return table _submit = False
else:
# unwrapped label
parent.append(DIV(label, _controls, _class='control-group'))
return parent
class SQLFORM(FORM): class SQLFORM(FORM):
@@ -1086,7 +1108,8 @@ class SQLFORM(FORM):
if callable(self.formstyle): if callable(self.formstyle):
# backward compatibility, 4 argument function is the old style # backward compatibility, 4 argument function is the old style
if len(inspect.getargspec(self.formstyle)[0]) == 4: args, varargs, keywords, defaults = inspect.getargspec(self.formstyle)
if defaults and len(args) - len(defaults) == 4 or len(args) == 4:
table = TABLE() table = TABLE()
for id,a,b,c in xfields: for id,a,b,c in xfields:
raw_b = self.field_parent[id] = b raw_b = self.field_parent[id] = b
@@ -2159,12 +2182,12 @@ class SQLFORM(FORM):
LI(A(T(db[referee]._plural), LI(A(T(db[referee]._plural),
_class=trap_class(), _class=trap_class(),
_href=url()), _href=url()),
SPAN(divider,_class='divider'))) SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
if kwargs.get('details',True): if kwargs.get('details',True):
breadcrumbs.append( breadcrumbs.append(
LI(A(name,_class=trap_class(), LI(A(name,_class=trap_class(),
_href=url(args=['view',referee,id])), _href=url(args=['view',referee,id])),
SPAN(divider,_class='divider'))) SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
nargs+=2 nargs+=2
else: else:
break break
@@ -2212,7 +2235,7 @@ class SQLFORM(FORM):
if isinstance(grid,DIV): if isinstance(grid,DIV):
header = table._plural + (field and ' for '+field.name or '') header = table._plural + (field and ' for '+field.name or '')
breadcrumbs.append(LI(A(T(header),_class=trap_class(), breadcrumbs.append(LI(A(T(header),_class=trap_class(),
_href=url()),_class='active')) _href=url()),_class='active w2p_grid_breadcrumb_elem'))
grid.insert(0,DIV(UL(*breadcrumbs, **{'_class':breadcrumbs_class}), grid.insert(0,DIV(UL(*breadcrumbs, **{'_class':breadcrumbs_class}),
_class='web2py_breadcrumbs')) _class='web2py_breadcrumbs'))
return grid return grid
+66 -86
View File
@@ -13,9 +13,8 @@ Provides:
""" """
import cPickle import cPickle
import portalocker
__all__ = ['List', 'Storage', 'Settings', 'Messages', 'PickleableStorage', __all__ = ['List', 'Storage', 'Settings', 'Messages',
'StorageList', 'load_storage', 'save_storage'] 'StorageList', 'load_storage', 'save_storage']
@@ -25,7 +24,11 @@ class List(list):
instead of IndexOutOfBounds instead of IndexOutOfBounds
""" """
def __call__(self, i, default=None, cast=None, url_onerror=None): def __call__(self, i, default=None, cast=None, otherwise=None):
"""
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""
n = len(self) n = len(self)
if 0<=i<n or -n<=i<0: if 0<=i<n or -n<=i<0:
value = self[i] value = self[i]
@@ -36,14 +39,17 @@ class List(list):
value = cast(value) value = cast(value)
except (ValueError, TypeError): except (ValueError, TypeError):
from http import HTTP, redirect from http import HTTP, redirect
if url_onerror: if otherwise is None:
redirect(url_onerror)
else:
raise HTTP(404) raise HTTP(404)
elif isinstance(otherwise,str):
redirect(otherwise)
elif callable(otherwise):
return otherwise()
else:
raise RuntimeError, "invalid otherwise"
return value return value
class Storage(dict): class Storage(dict):
""" """
A Storage object is like a dictionary except `obj.foo` can be used A Storage object is like a dictionary except `obj.foo` can be used
in addition to `obj['foo']`, and setting obj.foo = None deletes item foo. in addition to `obj['foo']`, and setting obj.foo = None deletes item foo.
@@ -61,41 +67,33 @@ class Storage(dict):
>>> del o.a >>> del o.a
>>> print o.a >>> print o.a
None
""" """
def __init__(self, *args, **kwargs):
def __getattr__(self, key): dict.__init__(self, *args, **kwargs)
return dict.get(self, key, None) self.__dict__ = self
def __getattr__(self,key):
def __setattr__(self, key, value): return getattr(self,key) if key in self else None
if value is None: def __getitem__(self,key):
if key in self: return dict.get(self,key,None)
del self[key] def copy(self):
else: self.__dict__ = {}
self[key] = value s = Storage(self)
self.__dict__ = self
def __delattr__(self, key): return s
if key in self:
del self[key]
else:
raise AttributeError, "missing key=%s" % key
def __getitem__(self, key):
return dict.get(self, key, None)
def __repr__(self): def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>' return '<Storage %s>' % dict.__repr__(self)
def __getstate__(self): def __getstate__(self):
return dict(self) return dict(self)
def __setstate__(self, sdict):
def __setstate__(self, value): dict.__init__(self, sdict)
for (k, v) in value.items(): self.__dict__ = self
self[k] = v def update(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
def getlist(self, key): self.__dict__ = self
"""Return a Storage value as a list. def getlist(self,key):
"""
Return a Storage value as a list.
If the value is a list it will be returned as-is. If the value is a list it will be returned as-is.
If object is None, an empty list will be returned. If object is None, an empty list will be returned.
@@ -112,17 +110,13 @@ class Storage(dict):
['abc', 'def'] ['abc', 'def']
>>> request.vars.getlist('z') >>> request.vars.getlist('z')
[] []
""" """
value = self.get(key, None) value = getattr(self,key,[])
if isinstance(value, (list, tuple)): return value if not value else \
return value value if isinstance(value,(list,tuple)) else [value]
elif value is None: def getfirst(self,key,default=None):
return [] """
return [value] Return the first or only value when given a request.vars-style key.
def getfirst(self, key):
"""Return the first or only value when given a request.vars-style key.
If the value is a list, its first item will be returned; If the value is a list, its first item will be returned;
otherwise, the value will be returned as-is. otherwise, the value will be returned as-is.
@@ -137,15 +131,13 @@ class Storage(dict):
>>> request.vars.getfirst('y') >>> request.vars.getfirst('y')
'abc' 'abc'
>>> request.vars.getfirst('z') >>> request.vars.getfirst('z')
""" """
value = self.getlist(key) values = self.getlist(default)
if len(value): return values[0] if values else default
return value[0] def getlast(self,key,default=None):
return None """
Returns the last or only single value when
def getlast(self, key): given a request.vars-style key.
"""Returns the last or only single value when given a request.vars-style key.
If the value is a list, the last item will be returned; If the value is a list, the last item will be returned;
otherwise, the value will be returned as-is. otherwise, the value will be returned as-is.
@@ -160,33 +152,25 @@ class Storage(dict):
>>> request.vars.getlast('y') >>> request.vars.getlast('y')
'def' 'def'
>>> request.vars.getlast('z') >>> request.vars.getlast('z')
""" """
value = self.getlist(key) values = self.getlist(default)
if len(value): return values[0] if values else default
return value[-1]
return None
def __getinitargs__(self):
return ()
def __getnewargs__(self):
return ()
PICKABLE = (str,int,long,float,bool,list,dict,tuple,set) PICKABLE = (str,int,long,float,bool,list,dict,tuple,set)
def PickleableStorage(data):
return Storage(dict((k,v) for (k,v) in data.items() if isinstance(v,PICKABLE)))
class StorageList(Storage): class StorageList(Storage):
""" """
like Storage but missing elements default to [] instead of None like Storage but missing elements default to [] instead of None
""" """
def __getitem__(self,key):
return self.__gteattr__(key)
def __getattr__(self, key): def __getattr__(self, key):
if key in self: if key in self:
return self[key] return getattr(self,key)
else: else:
self[key] = [] r = []
return self[key] setattr(self,key,r)
return r
def load_storage(filename): def load_storage(filename):
fp = None fp = None
@@ -197,7 +181,6 @@ def load_storage(filename):
if fp: fp.close() if fp: fp.close()
return Storage(storage) return Storage(storage)
def save_storage(storage, filename): def save_storage(storage, filename):
fp = None fp = None
try: try:
@@ -207,35 +190,31 @@ def save_storage(storage, filename):
if fp: fp.close() if fp: fp.close()
class Settings(Storage): class Settings(Storage):
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key != 'lock_keys' and self.get('lock_keys', None)\ if key != 'lock_keys' and self.lock_keys and not key in self:
and not key in self:
raise SyntaxError, 'setting key \'%s\' does not exist' % key raise SyntaxError, 'setting key \'%s\' does not exist' % key
if key != 'lock_values' and self.get('lock_values', None): if key != 'lock_values' and self.lock_values:
raise SyntaxError, 'setting value cannot be changed: %s' % key raise SyntaxError, 'setting value cannot be changed: %s' % key
self[key] = value Storage.__setattr__(self,key,value)
class Messages(Storage): class Messages(Storage):
def __init__(self, T): def __init__(self, T):
self['T'] = T Storage.__init__(self,T=T)
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key != 'lock_keys' and self.get('lock_keys', None)\ if key != 'lock_keys' and self.lock_keys and not key in self:
and not key in self:
raise SyntaxError, 'setting key \'%s\' does not exist' % key raise SyntaxError, 'setting key \'%s\' does not exist' % key
if key != 'lock_values' and self.get('lock_values', None): if key != 'lock_values' and self.lock_values:
raise SyntaxError, 'setting value cannot be changed: %s' % key raise SyntaxError, 'setting value cannot be changed: %s' % key
self[key] = value Storage.__setattr__(self,key,value)
def __getattr__(self, key): def __getattr__(self, key):
value = self[key] value = Storage.__getattr__(self,key)
if isinstance(value, str): if isinstance(value, str):
return str(self['T'](value)) return str(self.T(value))
return value return value
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()
@@ -244,3 +223,4 @@ if __name__ == '__main__':
+1 -1
View File
@@ -4519,7 +4519,7 @@ class Wiki(object):
if tag: db.wiki_tag.insert(name=tag,wiki_page=page.id) if tag: db.wiki_tag.insert(name=tag,wiki_page=page.id)
db.wiki_page._after_insert.append(update_tags_insert) db.wiki_page._after_insert.append(update_tags_insert)
db.wiki_page._after_update.append(update_tags_update) db.wiki_page._after_update.append(update_tags_update)
if check_credentials(current.request) and \ if auth.user and check_credentials(current.request) and \
not 'wiki_editor' in auth.user_groups.values(): not 'wiki_editor' in auth.user_groups.values():
group = db.auth_group(role='wiki_editor') group = db.auth_group(role='wiki_editor')
gid = group.id if group else db.auth_group.insert(role='wiki_editor') gid = group.id if group else db.auth_group.insert(role='wiki_editor')