diff --git a/VERSION b/VERSION index 3c6269ed..e786178e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-08-21 07:57:05) dev +Version 2.00.0 (2012-08-21 18:20:02) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 16e09491..cec8bef9 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -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) diff --git a/applications/welcome/static/css/web2py.css b/applications/welcome/static/css/web2py.css index 13f24237..2ac5c115 100644 --- a/applications/welcome/static/css/web2py.css +++ b/applications/welcome/static/css/web2py.css @@ -294,7 +294,7 @@ div.error { margin-bottom:18px; } -.web2py_breadcrumbs ul li { +li.w2p_grid_breadcrumb_elem { display:inline-block; } diff --git a/gluon/dal.py b/gluon/dal.py index a1fdfa87..a8d9c296 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -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 '' @@ -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 '' @@ -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: diff --git a/gluon/globals.py b/gluon/globals.py index 80b312c2..23723731 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -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: diff --git a/gluon/main.py b/gluon/main.py index 6b4d4b20..5aa0f15a 100644 --- a/gluon/main.py +++ b/gluon/main.py @@ -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, "

Temporarily down for maintenance

") - 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 diff --git a/gluon/restricted.py b/gluon/restricted.py index fee54841..770fe48e 100644 --- a/gluon/restricted.py +++ b/gluon/restricted.py @@ -33,6 +33,7 @@ class TicketStorage(Storage): db=None, tablename='web2py_ticket' ): + Storage.__init__(self) self.db = db self.tablename = tablename diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index 91d5cfb6..a8a709c3 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -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 diff --git a/gluon/storage.py b/gluon/storage.py index b4b73c56..9ec86b37 100644 --- a/gluon/storage.py +++ b/gluon/storage.py @@ -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>> 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 '' - + return '' % 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__': + diff --git a/gluon/tools.py b/gluon/tools.py index 7ebeae8e..a3cd5a0b 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -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')