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)
elif method == 'dbnew':
errors_path = apath('%s/errors' % app, r=request)
tk_db, tk_table = get_ticket_storage(app)
+1 -1
View File
@@ -294,7 +294,7 @@ div.error {
margin-bottom:18px;
}
.web2py_breadcrumbs ul li {
li.w2p_grid_breadcrumb_elem {
display:inline-block;
}
+25 -17
View File
@@ -1702,6 +1702,16 @@ class BaseAdapter(ConnectionPool):
def parse_datetime(self, value, field_type):
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 = (
str(value).replace('T',' ')+' ').split(' ',1)
(y, m, d) = map(int,date_part.split('-'))
@@ -1710,6 +1720,8 @@ class BaseAdapter(ConnectionPool):
time_items = map(int,time_parts)
(h, mi, s) = time_items
value = datetime.datetime(y, m, d, h, mi, s)
if dt:
value = value + dt
return value
def parse_blob(self, value, field_type):
@@ -6269,6 +6281,10 @@ class Row(dict):
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):
key=str(key)
m = regex_table_field.match(key)
@@ -6287,12 +6303,6 @@ class Row(dict):
def __setitem__(self, 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):
### this could be made smarter
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
:attempts (defaults to 5). Number of times to attempt connecting
"""
self.__dict__ = self
if not decode_credentials:
credential_decoder = lambda cred: cred
else:
@@ -6942,7 +6953,7 @@ def index():
**args
):
if self._common_fields:
fields = fields + self._common_fields
fields = list(fields) + list(self._common_fields)
table_class = args.get('table_class',Table)
table = table_class(self, tablename, *fields, **args)
@@ -6973,6 +6984,9 @@ def index():
def __getitem__(self, 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:
tablename, fields, args = self._LAZY_TABLES.pop(key)
return self.lazy_define_table(tablename,*fields,**args)
@@ -6981,14 +6995,11 @@ def index():
def __setitem__(self, key, value):
dict.__setitem__(self, str(key), value)
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value):
if key[:1]!='_' and key in self:
raise SyntaxError, \
'Object %s exists and cannot be redefined' % key
self[key] = value
dict.__setattr__(self,key,value)
def __repr__(self):
return '<DAL ' + dict.__repr__(self) + '>'
@@ -7190,7 +7201,7 @@ class Table(dict):
:raises SyntaxError: when a supplied field is of incorrect type.
"""
self.__dict__ = self
self._actual = False # set to True by define_table()
self._tablename = tablename
self._sequence_name = args.get('sequence_name',None) or \
@@ -7261,7 +7272,7 @@ class Table(dict):
tmp = field.uploadfield = '%s_blob' % field.name
if isinstance(field.uploadfield,str) and \
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()
reserved = dir(Table) + ['fields']
@@ -7460,13 +7471,10 @@ class Table(dict):
elif not str(key).isdigit() or not self._db(self._id == key).delete():
raise SyntaxError, 'No such record: %s' % key
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value):
if key[:1]!='_' and key in self:
raise SyntaxError, 'Object exists and cannot be redefined: %s' % key
self[key] = value
dict.__setattr__(self,key,value)
def __iter__(self):
for fieldname in self.fields:
+27 -5
View File
@@ -83,6 +83,7 @@ class Request(Storage):
"""
def __init__(self):
Storage.__init__(self)
self.wsgi = Storage() # hooks to environ and start_response
self.env = Storage()
self.cookies = Cookie.SimpleCookie()
@@ -100,7 +101,7 @@ class Request(Storage):
self.is_https = False
self.is_local = False
self.global_settings = settings.global_settings
def compute_uuid(self):
self.uuid = '%s/%s.%s.%s' % (
self.application,
@@ -165,6 +166,7 @@ class Response(Storage):
"""
def __init__(self):
Storage.__init__(self)
self.status = 200
self.headers = dict()
self.headers['X-Powered-By'] = 'web2py'
@@ -274,6 +276,8 @@ class Response(Storage):
stream,
chunk_size = DEFAULT_CHUNK_SIZE,
request=None,
attachment=False,
filename=None
):
"""
if a controller function::
@@ -281,7 +285,28 @@ class Response(Storage):
return response.stream(file, 100)
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:
request = current.request
if isinstance(stream, (str, unicode)):
@@ -291,12 +316,9 @@ class Response(Storage):
headers=self.headers)
# ## the following is for backward compatibility
if hasattr(stream, 'name'):
filename = stream.name
else:
filename = None
keys = [item.lower() for item in self.headers]
if filename and not 'content-type' in keys:
self.headers['Content-Type'] = contenttype(filename)
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 \
os.path.exists(os.path.join(request.folder,'DISABLED')):
raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
request.url = Url(r=request, args=request.args,
extension=request.raw_extension)
request.url = Url(r=request,
args=request.args,
extension=request.raw_extension)
# ##################################################
# build missing folders
+1
View File
@@ -33,6 +33,7 @@ class TicketStorage(Storage):
db=None,
tablename='web2py_ticket'
):
Storage.__init__(self)
self.db = db
self.tablename = tablename
+36 -13
View File
@@ -687,18 +687,40 @@ def formstyle_ul(form, fields):
def formstyle_bootstrap(form, fields):
''' bootstrap format form layout '''
form['_class'] = 'form-horizontal'
table = FIELDSET()
parent = FIELDSET()
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'
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):
label['_class'] = 'control-label'
# styles
_help = DIV(help, _class='help-block')
# embed _help into _controls don't wrap label
_controls = DIV(controls, _help, _class='controls')
table.append(DIV(label, _controls, _class='control-group',_id=id))
return table
if _submit:
# submit button has unwrapped label and controls, different class
parent.append(DIV(label, controls, _class='form-actions'))
# unflag submit (possible side effect)
_submit = False
else:
# unwrapped label
parent.append(DIV(label, _controls, _class='control-group'))
return parent
class SQLFORM(FORM):
@@ -1086,7 +1108,8 @@ class SQLFORM(FORM):
if callable(self.formstyle):
# 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()
for id,a,b,c in xfields:
raw_b = self.field_parent[id] = b
@@ -1815,7 +1838,7 @@ class SQLFORM(FORM):
filename = '.'.join(('rows', oExp.file_ext))
response.headers['Content-Type'] = oExp.content_type
response.headers['Content-Disposition'] = \
'attachment;filename='+filename+';'
'attachment;filename='+filename+';'
raise HTTP(200, oExp.export(),**response.headers)
elif request.vars.records and not isinstance(
@@ -2159,12 +2182,12 @@ class SQLFORM(FORM):
LI(A(T(db[referee]._plural),
_class=trap_class(),
_href=url()),
SPAN(divider,_class='divider')))
SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
if kwargs.get('details',True):
breadcrumbs.append(
LI(A(name,_class=trap_class(),
_href=url(args=['view',referee,id])),
SPAN(divider,_class='divider')))
SPAN(divider,_class='divider'),_class='w2p_grid_breadcrumb_elem'))
nargs+=2
else:
break
@@ -2212,7 +2235,7 @@ class SQLFORM(FORM):
if isinstance(grid,DIV):
header = table._plural + (field and ' for '+field.name or '')
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}),
_class='web2py_breadcrumbs'))
return grid
+67 -87
View File
@@ -13,9 +13,8 @@ Provides:
"""
import cPickle
import portalocker
__all__ = ['List', 'Storage', 'Settings', 'Messages', 'PickleableStorage',
__all__ = ['List', 'Storage', 'Settings', 'Messages',
'StorageList', 'load_storage', 'save_storage']
@@ -25,7 +24,11 @@ class List(list):
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)
if 0<=i<n or -n<=i<0:
value = self[i]
@@ -36,14 +39,17 @@ class List(list):
value = cast(value)
except (ValueError, TypeError):
from http import HTTP, redirect
if url_onerror:
redirect(url_onerror)
else:
if otherwise is None:
raise HTTP(404)
elif isinstance(otherwise,str):
redirect(otherwise)
elif callable(otherwise):
return otherwise()
else:
raise RuntimeError, "invalid otherwise"
return value
class Storage(dict):
"""
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.
@@ -61,41 +67,33 @@ class Storage(dict):
>>> del o.a
>>> print o.a
None
"""
def __getattr__(self, key):
return dict.get(self, key, None)
def __setattr__(self, key, value):
if value is None:
if key in self:
del self[key]
else:
self[key] = value
def __delattr__(self, key):
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 __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self
def __getattr__(self,key):
return getattr(self,key) if key in self else None
def __getitem__(self,key):
return dict.get(self,key,None)
def copy(self):
self.__dict__ = {}
s = Storage(self)
self.__dict__ = self
return s
def __repr__(self):
return '<Storage ' + dict.__repr__(self) + '>'
return '<Storage %s>' % dict.__repr__(self)
def __getstate__(self):
return dict(self)
def __setstate__(self, value):
for (k, v) in value.items():
self[k] = v
def getlist(self, key):
"""Return a Storage value as a list.
def __setstate__(self, sdict):
dict.__init__(self, sdict)
self.__dict__ = self
def update(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self
def getlist(self,key):
"""
Return a Storage value as a list.
If the value is a list it will be returned as-is.
If object is None, an empty list will be returned.
@@ -112,17 +110,13 @@ class Storage(dict):
['abc', 'def']
>>> request.vars.getlist('z')
[]
"""
value = self.get(key, None)
if isinstance(value, (list, tuple)):
return value
elif value is None:
return []
return [value]
def getfirst(self, key):
"""Return the first or only value when given a request.vars-style key.
value = getattr(self,key,[])
return value if not value else \
value if isinstance(value,(list,tuple)) else [value]
def getfirst(self,key,default=None):
"""
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;
otherwise, the value will be returned as-is.
@@ -137,15 +131,13 @@ class Storage(dict):
>>> request.vars.getfirst('y')
'abc'
>>> request.vars.getfirst('z')
"""
value = self.getlist(key)
if len(value):
return value[0]
return None
def getlast(self, key):
"""Returns the last or only single value when given a request.vars-style key.
values = self.getlist(default)
return values[0] if values else default
def getlast(self,key,default=None):
"""
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;
otherwise, the value will be returned as-is.
@@ -160,33 +152,25 @@ class Storage(dict):
>>> request.vars.getlast('y')
'def'
>>> request.vars.getlast('z')
"""
value = self.getlist(key)
if len(value):
return value[-1]
return None
def __getinitargs__(self):
return ()
def __getnewargs__(self):
return ()
values = self.getlist(default)
return values[0] if values else default
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):
"""
like Storage but missing elements default to [] instead of None
"""
def __getitem__(self,key):
return self.__gteattr__(key)
def __getattr__(self, key):
if key in self:
return self[key]
return getattr(self,key)
else:
self[key] = []
return self[key]
r = []
setattr(self,key,r)
return r
def load_storage(filename):
fp = None
@@ -197,7 +181,6 @@ def load_storage(filename):
if fp: fp.close()
return Storage(storage)
def save_storage(storage, filename):
fp = None
try:
@@ -207,35 +190,31 @@ def save_storage(storage, filename):
if fp: fp.close()
class Settings(Storage):
def __setattr__(self, key, value):
if key != 'lock_keys' and self.get('lock_keys', None)\
and not key in self:
if key != 'lock_keys' and self.lock_keys and not key in self:
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
self[key] = value
Storage.__setattr__(self,key,value)
class Messages(Storage):
def __init__(self, T):
self['T'] = T
Storage.__init__(self,T=T)
def __setattr__(self, key, value):
if key != 'lock_keys' and self.get('lock_keys', None)\
and not key in self:
if key != 'lock_keys' and self.lock_keys and not key in self:
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
self[key] = value
Storage.__setattr__(self,key,value)
def __getattr__(self, key):
value = self[key]
value = Storage.__getattr__(self,key)
if isinstance(value, str):
return str(self['T'](value))
return str(self.T(value))
return value
if __name__ == '__main__':
import doctest
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)
db.wiki_page._after_insert.append(update_tags_insert)
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():
group = db.auth_group(role='wiki_editor')
gid = group.id if group else db.auth_group.insert(role='wiki_editor')