Merge github.com:web2py/web2py
This commit is contained in:
@@ -1,67 +1,84 @@
|
|||||||
## 2.00.0
|
## 2.00.1
|
||||||
|
|
||||||
- redirect(...,type='auto') will be handled properly in ajax responses
|
### DAL Improvements
|
||||||
- experimental MongoDB support in DAL
|
|
||||||
- FORM.confirm('Confirm?',{'Back':URL(...)})
|
- MongoDB support in DAL (experimental)
|
||||||
- @auth.requires_login(otherwise=URL(...))
|
- geodal and spatialite, thanks Denes and Fran (experimental)
|
||||||
- request.args(0,default=0, cast=int, otherwise=URL(...)), thanks Anthony
|
- db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
|
||||||
- better memcache support
|
|
||||||
- generic.ics
|
|
||||||
- generic.map
|
|
||||||
- geodal and spatialite, thanks Denes and Fran
|
|
||||||
- auth.enable_record_versioning(...db=...)
|
|
||||||
- db.table._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete
|
|
||||||
- db(...).update_naive(...) same as update but ignores table._before_update and table._after_update
|
- db(...).update_naive(...) same as update but ignores table._before_update and table._after_update
|
||||||
- SQLFORM.smartdictform
|
|
||||||
- form.add_button(value,link)
|
|
||||||
- db.table.insert(uploadfield=open(....))
|
|
||||||
- DAL BIGINT support and DAL(...,bigint_id=True)
|
- DAL BIGINT support and DAL(...,bigint_id=True)
|
||||||
- IS_IN_DB distinct
|
- IS_IN_DB(..., distinct=True)
|
||||||
- more secure SQLFORM.grid and SQLFORM.smartgrid
|
- new syntax: db.mytable.insert(myuploadfield=open(....))
|
||||||
- more export options in SQLFORM.grid and SQLFORM.smartgrid
|
- db(...).select(db.mytable.myfield.count(distinct=True))
|
||||||
- more secure admin against CSRF
|
- db(db.a)._update(name=db(db.b.a==db.a.id).nested_select(db.b.id))
|
||||||
- improved spreadsheet, thanks Alan
|
- db.mytable.myfield.filter_in, filter_out
|
||||||
- routes in can redirect outside with routes_in=[('/path','303->http://..')]
|
- db.mytable._enable_record_versioning(db) adds verining to this table
|
||||||
- auth supports salt and compatible with third party data, thanks Dave Stoll
|
|
||||||
- CRYPT now defaults to pbkdf2(1000,20,sha1)
|
|
||||||
- experimental GIT integration
|
|
||||||
- experimental OpenShift deployment
|
|
||||||
- new makrmin with supports for nested lists, <i>, <em>, autolinks
|
|
||||||
- new interantionalization engine
|
|
||||||
- multi-language pluralization engine
|
|
||||||
- db.table.field.count(True)
|
|
||||||
- auth.wiki()
|
|
||||||
- new syntax BR()*5
|
|
||||||
- gluon.cache.lazy_cache decorator
|
|
||||||
- auth.settings.everybody_group_id
|
|
||||||
- ace text web editor in admin
|
|
||||||
- -E -b -L options for scheduler
|
|
||||||
- coffee and less support in response.fields, thanks Sam Sheftel
|
|
||||||
- ldap certificate support
|
|
||||||
- pg8000 postgresql driver support
|
|
||||||
- portable html.py
|
|
||||||
- new admin pages: manage_students, bulk_regsiter, and progress reports
|
|
||||||
- @cache('%(name)s%(args)s%(vars)s',5) and cache.autokey
|
|
||||||
- added tox.ini, thanks Marc
|
|
||||||
- better mongodb support (still experimental) thanks Mark
|
|
||||||
- web2py.py --run_system_tests, thanks Marc Abramowitz
|
|
||||||
- teradata adapter, thanks Andrew Willimott
|
- teradata adapter, thanks Andrew Willimott
|
||||||
- experimental Sybase Adapter
|
- experimental Sybase Adapter
|
||||||
- added db.table.field.avg()
|
- added db.table.field.avg()
|
||||||
- db(db.a)._update(name=db(db.b.a==db.a.id).nested_select(db.b.id))
|
- Suport for Google App Engine projections
|
||||||
- new layout based on bootstrap and bootswatch
|
|
||||||
|
### Auth improvements
|
||||||
|
|
||||||
|
- auth.enable_record_versioning(db) adds full versining to all tables
|
||||||
|
- @auth.requires_login(otherwise=URL(...))
|
||||||
|
- auth supports salt and compatible with third party data, thanks Dave Stoll
|
||||||
|
- CRYPT now defaults to pbkdf2(1000,20,sha1)
|
||||||
|
- Built-in wiki with menu, tags, search, media, permissions. def index: return auth.wiki()
|
||||||
|
- auth.settings.everybody_group_id
|
||||||
|
- allow storage of uploads on any PyFileSystem (including amazon)
|
||||||
|
|
||||||
|
### Form improvements
|
||||||
|
|
||||||
|
- FORM.confirm('Are you sure?',{'Back':URL(...)})
|
||||||
|
- SQLFORM.smartdictform(dict)
|
||||||
|
- form.add_button(value,link)
|
||||||
|
- SQLFORM.grid(groupby=...')
|
||||||
|
- fixed security issue with SQLFORM.grid and SQLFORM.smartgrid
|
||||||
|
- more export options in SQLFORM.grid and SQLFORM.smartgrid (html, xml, csv, ...)
|
||||||
|
|
||||||
|
### Admin improvements
|
||||||
|
|
||||||
|
- new admin pages: manage_students, bulk_regsiter, and progress reports
|
||||||
|
- increased secure admin against CSRF
|
||||||
|
- experimental Git integration
|
||||||
|
- experimental OpenShift deployment
|
||||||
|
- multi-language pluralization engine
|
||||||
|
- ace text web editor in admin
|
||||||
- Ukrainian translations, thanks Vladyslav Kozlovskyy
|
- Ukrainian translations, thanks Vladyslav Kozlovskyy
|
||||||
- Romanian translation for welcome, thanks ionel
|
- Romanian translation for welcome, thanks ionel
|
||||||
- support for mercurial 2.6, thanks Vlad
|
- support for mercurial 2.6, thanks Vlad
|
||||||
- GAE projections
|
|
||||||
- SQLFORM.grid(groupby=...')
|
### Scheduler Inprovements
|
||||||
- db.table.field.filter_in, filter_out
|
|
||||||
- allowed storage of uploads on any PyFileSystem (including amazon)
|
|
||||||
- new scheduler can now handle 10k tasks with 20 concurrent workers and no known issues (thanks to niphlod, ykessler, dhx, toomim)
|
|
||||||
- shceduler: tasks can be found in the environment (no need to define the tasks parameter)
|
|
||||||
- scheduler: max_empty_runs kills the workers automatically if no new tasks are found in queue (nice for "spikes" of processing power), discard_results to completely discard the results (if you don't need the output of the task), utc_time enables datetime calculations with UTC time, task_name is no longer required (filled automatically with function_name if found empty), uuid makes easy to coordinate scheduler_task maintenance (filled automatically if not provided), stop_time has no default (previously was today+1), retry_failed to requeue automatically failed tasks, sync_output refreshes automatically the output (nice to report percentages)
|
|
||||||
- scheduler tasks can be DISABLED (put to sleep and do nothing if not sending the heartbeat every 30 seconds), TERMINATE (complete the current task and then die), KILL (kill ASAP), EXPIRED (if stop_time passed)
|
|
||||||
- web2py.py -K myapp -X starts the myapp scheduler alongside the webserver
|
- web2py.py -K myapp -X starts the myapp scheduler alongside the webserver
|
||||||
|
- more options: web2py.py -E -b -L
|
||||||
|
- scheduler can now handle 10k tasks with 20 concurrent workers and no known issues (thanks to niphlod, ykessler, dhx, toomim)
|
||||||
|
- tasks can be found in the environment (no need to define the tasks parameter)
|
||||||
|
- max_empty_runs kills the workers automatically if no new tasks are found in queue (nice for "spikes" of processing power), discard_results to completely discard the results (if you don't need the output of the task), utc_time enables datetime calculations with UTC time, task_name is no longer required (filled automatically with function_name if found empty), uuid makes easy to coordinate scheduler_task maintenance (filled automatically if not provided), stop_time has no default (previously was today+1), retry_failed to requeue automatically failed tasks, sync_output refreshes automatically the output (nice to report percentages)
|
||||||
|
- tasks can be DISABLED (put to sleep and do nothing if not sending the heartbeat every 30 seconds), TERMINATE (complete the current task and then die), KILL (kill ASAP), EXPIRED (if stop_time passed)
|
||||||
|
|
||||||
|
### Other Improvements
|
||||||
|
|
||||||
|
- new layout based on Twitter Bootstrap
|
||||||
|
- New generic views: generic.ics (Mac Mail Calendar) and generic.map (Google Maps)
|
||||||
|
- request.args(0,default=0, cast=int, otherwise=URL(...)), thanks Anthony
|
||||||
|
- redirect(...,type='auto') will be handled properly in ajax responses
|
||||||
|
- routes in can redirect outside with routes_in=[('/path','303->http://..')]
|
||||||
|
- better memcache support
|
||||||
|
- improved spreadsheet, thanks Alan
|
||||||
|
- new interantionalization engine, thanks Vladyslav
|
||||||
|
- pluralization engine, thanks Vladyslav
|
||||||
|
- new makrmin with supports for nested lists, <i>, <em>, autolinks, thanks Vladyslav
|
||||||
|
- new syntax: {{=BR()*5}}
|
||||||
|
- gluon.cache.lazy_cache decorator allows caching functions in modules
|
||||||
|
- .coffee and .less support in response.fields, thanks Sam Sheftel
|
||||||
|
- ldap certificate support
|
||||||
|
- pg8000 postgresql driver support (experimental)
|
||||||
|
- @cache('%(name)s%(args)s%(vars)s',5) and cache.autokey
|
||||||
|
- added tox.ini, thanks Marc
|
||||||
|
- web2py.py --run_system_tests, thanks Marc Abramowitz
|
||||||
|
- html.py (and web2py helpers) can be used without web2py dependencies
|
||||||
|
|
||||||
|
|
||||||
## 1.99.5-1.99.7
|
## 1.99.5-1.99.7
|
||||||
@@ -125,7 +142,6 @@ This is a major revision in peparation for web2py 2.0
|
|||||||
- experimental PyPy support
|
- experimental PyPy support
|
||||||
- experimental mongodb support, thanks Mark
|
- experimental mongodb support, thanks Mark
|
||||||
- tickets in db now accessible from admin, thanks Niphlod
|
- tickets in db now accessible from admin, thanks Niphlod
|
||||||
- many grid improvements and bug fixes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -124,4 +124,8 @@ push:
|
|||||||
hg push
|
hg push
|
||||||
git push
|
git push
|
||||||
git push mdipierro
|
git push mdipierro
|
||||||
#bzr push bzr+ssh://mdipierro@bazaar.launchpad.net/~mdipierro/web2py/devel --use-existing-dir
|
tag:
|
||||||
|
git tag -l '$(S)'
|
||||||
|
hg tag -l '$(S)'
|
||||||
|
make commit
|
||||||
|
make push
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.00.0 (2012-08-21 18:20:02) dev
|
Version 2.00.0 (2012-08-22 19:47:08) dev
|
||||||
|
|||||||
+2
-4
File diff suppressed because one or more lines are too long
+2
-4
File diff suppressed because one or more lines are too long
+2
-4
File diff suppressed because one or more lines are too long
+11
-9
@@ -6281,9 +6281,11 @@ class Row(dict):
|
|||||||
this is only used to store a Row
|
this is only used to store a Row
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,*args,**kwargs):
|
def __getattr__(self, key):
|
||||||
dict.__init__(self,*args,**kwargs)
|
return self[key]
|
||||||
self.__dict__ = self
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
self[key] = value
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
key=str(key)
|
key=str(key)
|
||||||
@@ -6572,7 +6574,6 @@ 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:
|
||||||
@@ -6983,8 +6984,7 @@ def index():
|
|||||||
yield self[tablename]
|
yield self[tablename]
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
key = str(key)
|
return self.__getattr__(str(key))
|
||||||
return dict.__getitem__(self,key)
|
|
||||||
|
|
||||||
def __getattr__(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:
|
||||||
@@ -6999,7 +6999,7 @@ def index():
|
|||||||
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
|
||||||
dict.__setattr__(self,key,value)
|
dict.__setitem__(self,key,value)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<DAL ' + dict.__repr__(self) + '>'
|
return '<DAL ' + dict.__repr__(self) + '>'
|
||||||
@@ -7201,7 +7201,6 @@ 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 \
|
||||||
@@ -7463,6 +7462,9 @@ class Table(dict):
|
|||||||
'value must be a dictionary: %s' % value
|
'value must be a dictionary: %s' % value
|
||||||
dict.__setitem__(self, str(key), value)
|
dict.__setitem__(self, str(key), value)
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return self[key]
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
if isinstance(key, dict):
|
if isinstance(key, dict):
|
||||||
query = self._build_query(key)
|
query = self._build_query(key)
|
||||||
@@ -7474,7 +7476,7 @@ class Table(dict):
|
|||||||
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
|
||||||
dict.__setattr__(self,key,value)
|
self[key] = value
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for fieldname in self.fields:
|
for fieldname in self.fields:
|
||||||
|
|||||||
+1
-1
@@ -318,7 +318,7 @@ def get_session(request, other_application='admin'):
|
|||||||
session_id = request.cookies['session_id_' + other_application].value
|
session_id = request.cookies['session_id_' + other_application].value
|
||||||
osession = storage.load_storage(os.path.join(
|
osession = storage.load_storage(os.path.join(
|
||||||
up(request.folder), other_application, 'sessions', session_id))
|
up(request.folder), other_application, 'sessions', session_id))
|
||||||
except:
|
except Exception, e:
|
||||||
osession = storage.Storage()
|
osession = storage.Storage()
|
||||||
return osession
|
return osession
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2044,7 +2044,7 @@ class FORM(DIV):
|
|||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def confim(text='OK',buttons=None,hidden=None):
|
def confirm(text='OK',buttons=None,hidden=None):
|
||||||
if not buttons: buttons = {}
|
if not buttons: buttons = {}
|
||||||
if not hidden: hidden={}
|
if not hidden: hidden={}
|
||||||
inputs = [INPUT(_type='button',
|
inputs = [INPUT(_type='button',
|
||||||
|
|||||||
+5
-11
@@ -476,7 +476,7 @@ def load_routers(all_apps):
|
|||||||
def regex_uri(e, regexes, tag, default=None):
|
def regex_uri(e, regexes, tag, default=None):
|
||||||
"filter incoming URI against a list of regexes"
|
"filter incoming URI against a list of regexes"
|
||||||
path = e['PATH_INFO']
|
path = e['PATH_INFO']
|
||||||
host = e.get('HTTP_HOST', 'localhost').lower()
|
host = e.get('http_host', e.get('SERVER_NAME','localhost')).lower()
|
||||||
i = host.find(':')
|
i = host.find(':')
|
||||||
if i > 0:
|
if i > 0:
|
||||||
host = host[:i]
|
host = host[:i]
|
||||||
@@ -823,21 +823,15 @@ class MapUrlIn(object):
|
|||||||
self.remote_addr = self.env.get('REMOTE_ADDR','localhost')
|
self.remote_addr = self.env.get('REMOTE_ADDR','localhost')
|
||||||
self.scheme = self.env.get('wsgi.url_scheme', 'http').lower()
|
self.scheme = self.env.get('wsgi.url_scheme', 'http').lower()
|
||||||
self.method = self.env.get('REQUEST_METHOD', 'get').lower()
|
self.method = self.env.get('REQUEST_METHOD', 'get').lower()
|
||||||
self.host = self.env.get('HTTP_HOST')
|
(self.host, self.port) = (self.env.get('HTTP_HOST'), None)
|
||||||
self.port = None
|
|
||||||
if not self.host:
|
if not self.host:
|
||||||
self.host = self.env.get('SERVER_NAME')
|
(self.host, self.port) = (self.env.get('SERVER_NAME'), self.env.get('SERVER_PORT'))
|
||||||
self.port = self.env.get('SERVER_PORT')
|
|
||||||
if not self.host:
|
if not self.host:
|
||||||
self.host = 'localhost'
|
(self.host, self.port) = ('localhost', '80')
|
||||||
self.port = '80'
|
|
||||||
if ':' in self.host:
|
if ':' in self.host:
|
||||||
(self.host, self.port) = self.host.split(':')
|
(self.host, self.port) = self.host.split(':')
|
||||||
if not self.port:
|
if not self.port:
|
||||||
if self.scheme == 'https':
|
self.port = '443' if self.scheme == 'https' else '80'
|
||||||
self.port = '443'
|
|
||||||
else:
|
|
||||||
self.port = '80'
|
|
||||||
|
|
||||||
def map_prefix(self):
|
def map_prefix(self):
|
||||||
"strip path prefix, if present in its entirety"
|
"strip path prefix, if present in its entirety"
|
||||||
|
|||||||
+57
-70
@@ -13,42 +13,11 @@ Provides:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import cPickle
|
import cPickle
|
||||||
|
import portalocker
|
||||||
|
|
||||||
__all__ = ['List', 'Storage', 'Settings', 'Messages',
|
__all__ = ['List', 'Storage', 'Settings', 'Messages',
|
||||||
'StorageList', 'load_storage', 'save_storage']
|
'StorageList', 'load_storage', 'save_storage']
|
||||||
|
|
||||||
|
|
||||||
class List(list):
|
|
||||||
"""
|
|
||||||
Like a regular python list but a[i] if i is out of bounds return None
|
|
||||||
instead of IndexOutOfBounds
|
|
||||||
"""
|
|
||||||
|
|
||||||
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]
|
|
||||||
else:
|
|
||||||
value = default
|
|
||||||
if cast:
|
|
||||||
try:
|
|
||||||
value = cast(value)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
from http import HTTP, redirect
|
|
||||||
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):
|
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
|
||||||
@@ -66,31 +35,24 @@ class Storage(dict):
|
|||||||
2
|
2
|
||||||
|
|
||||||
>>> 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 __setattr__(self, key, value):
|
||||||
def __getattr__(self,key):
|
self[key] = value
|
||||||
return getattr(self,key) if key in self else None
|
def __delattr__(self, key):
|
||||||
def __getitem__(self,key):
|
del self[key]
|
||||||
return dict.get(self,key,None)
|
def __getitem__(self, key):
|
||||||
def copy(self):
|
return dict.get(self, key, None)
|
||||||
self.__dict__ = {}
|
|
||||||
s = Storage(self)
|
|
||||||
self.__dict__ = self
|
|
||||||
return s
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Storage %s>' % 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,values):
|
||||||
dict.__init__(self, sdict)
|
self.update(values)
|
||||||
self.__dict__ = self
|
|
||||||
def update(self, *args, **kwargs):
|
|
||||||
dict.__init__(self, *args, **kwargs)
|
|
||||||
self.__dict__ = self
|
|
||||||
def getlist(self,key):
|
def getlist(self,key):
|
||||||
"""
|
"""
|
||||||
Return a Storage value as a list.
|
Return a Storage value as a list.
|
||||||
@@ -111,9 +73,10 @@ class Storage(dict):
|
|||||||
>>> request.vars.getlist('z')
|
>>> request.vars.getlist('z')
|
||||||
[]
|
[]
|
||||||
"""
|
"""
|
||||||
value = getattr(self,key,[])
|
value = self.get(key,[])
|
||||||
return value if not value else \
|
return value if not value else \
|
||||||
value if isinstance(value,(list,tuple)) else [value]
|
value if isinstance(value,(list,tuple)) else [value]
|
||||||
|
|
||||||
def getfirst(self,key,default=None):
|
def getfirst(self,key,default=None):
|
||||||
"""
|
"""
|
||||||
Return the first or only value when given a request.vars-style key.
|
Return the first or only value when given a request.vars-style key.
|
||||||
@@ -132,8 +95,9 @@ class Storage(dict):
|
|||||||
'abc'
|
'abc'
|
||||||
>>> request.vars.getfirst('z')
|
>>> request.vars.getfirst('z')
|
||||||
"""
|
"""
|
||||||
values = self.getlist(default)
|
values = self.getlist(key)
|
||||||
return values[0] if values else default
|
return values[0] if values else default
|
||||||
|
|
||||||
def getlast(self,key,default=None):
|
def getlast(self,key,default=None):
|
||||||
"""
|
"""
|
||||||
Returns the last or only single value when
|
Returns the last or only single value when
|
||||||
@@ -153,8 +117,8 @@ class Storage(dict):
|
|||||||
'def'
|
'def'
|
||||||
>>> request.vars.getlast('z')
|
>>> request.vars.getlast('z')
|
||||||
"""
|
"""
|
||||||
values = self.getlist(default)
|
values = self.getlist(key)
|
||||||
return values[0] if values else default
|
return values[-1] if values else default
|
||||||
|
|
||||||
PICKABLE = (str,int,long,float,bool,list,dict,tuple,set)
|
PICKABLE = (str,int,long,float,bool,list,dict,tuple,set)
|
||||||
|
|
||||||
@@ -191,29 +155,52 @@ def save_storage(storage, filename):
|
|||||||
|
|
||||||
class Settings(Storage):
|
class Settings(Storage):
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
if key != 'lock_keys' and self.lock_keys and not key in self:
|
if key != 'lock_keys' and 'lock_keys' 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.lock_values:
|
if key != 'lock_values' and 'lock_values' in self:
|
||||||
raise SyntaxError, 'setting value cannot be changed: %s' % key
|
raise SyntaxError, 'setting value cannot be changed: %s' % key
|
||||||
Storage.__setattr__(self,key,value)
|
self[key] = value
|
||||||
|
|
||||||
class Messages(Storage):
|
class Messages(Settings):
|
||||||
def __init__(self, T):
|
def __init__(self, T):
|
||||||
Storage.__init__(self,T=T)
|
Storage.__init__(self,T=T)
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
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.lock_values:
|
|
||||||
raise SyntaxError, 'setting value cannot be changed: %s' % key
|
|
||||||
Storage.__setattr__(self,key,value)
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
value = Storage.__getattr__(self,key)
|
value = self[key]
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return str(self.T(value))
|
return str(self.T(value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
class List(list):
|
||||||
|
"""
|
||||||
|
Like a regular python list but a[i] if i is out of bounds return None
|
||||||
|
instead of IndexOutOfBounds
|
||||||
|
"""
|
||||||
|
|
||||||
|
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]
|
||||||
|
else:
|
||||||
|
value = default
|
||||||
|
if cast:
|
||||||
|
try:
|
||||||
|
value = cast(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
from http import HTTP, redirect
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
|
|||||||
+27
-25
@@ -3138,7 +3138,8 @@ class Auth(object):
|
|||||||
return id
|
return id
|
||||||
def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix=''):
|
def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix=''):
|
||||||
if not hasattr(self,'_wiki'):
|
if not hasattr(self,'_wiki'):
|
||||||
self._wiki = Wiki(self,manage_permissions=manage_permissions,
|
self._wiki = Wiki(self,
|
||||||
|
manage_permissions=manage_permissions,
|
||||||
force_prefix=force_prefix,env=env)
|
force_prefix=force_prefix,env=env)
|
||||||
else:
|
else:
|
||||||
self._wiki.env.update(env or {})
|
self._wiki.env.update(env or {})
|
||||||
@@ -4466,13 +4467,12 @@ class Wiki(object):
|
|||||||
items = text.split('/')
|
items = text.split('/')
|
||||||
controller, function, args = items[0], items[1], items[2:]
|
controller, function, args = items[0], items[1], items[2:]
|
||||||
return LOAD(controller, function, args=args, ajax=True).xml()
|
return LOAD(controller, function, args=args, ajax=True).xml()
|
||||||
def __init__(self,auth,env=None,automenu=True,render='markmin',
|
def __init__(self,auth,env=None,render='markmin',
|
||||||
manage_permissions=False,force_prefix=''):
|
manage_permissions=False,force_prefix=''):
|
||||||
self.env = env or {}
|
self.env = env or {}
|
||||||
self.env['component'] = Wiki.component
|
self.env['component'] = Wiki.component
|
||||||
if render == 'markmin': render=self.markmin_render
|
if render == 'markmin': render=self.markmin_render
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
self.automenu = automenu
|
|
||||||
if self.auth.user:
|
if self.auth.user:
|
||||||
self.force_prefix = force_prefix % self.auth.user
|
self.force_prefix = force_prefix % self.auth.user
|
||||||
else:
|
else:
|
||||||
@@ -4487,7 +4487,6 @@ class Wiki(object):
|
|||||||
readable=False,writable=False),
|
readable=False,writable=False),
|
||||||
Field('title',unique=True),
|
Field('title',unique=True),
|
||||||
Field('body','text',notnull=True),
|
Field('body','text',notnull=True),
|
||||||
Field('menu'),
|
|
||||||
Field('tags','list:string'),
|
Field('tags','list:string'),
|
||||||
Field('can_read','list:string',writable=perms,readable=perms,
|
Field('can_read','list:string',writable=perms,readable=perms,
|
||||||
default=[Wiki.everybody]),
|
default=[Wiki.everybody]),
|
||||||
@@ -4553,9 +4552,8 @@ class Wiki(object):
|
|||||||
### END POLICY
|
### END POLICY
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
request = current.request
|
request = current.request
|
||||||
if self.automenu:
|
automenu = self.menu(request.controller,request.function)
|
||||||
current.response.menu = self.menu(request.controller,
|
current.response.menu += automenu
|
||||||
request.function)
|
|
||||||
zero = request.args(0)
|
zero = request.args(0)
|
||||||
if zero and zero.isdigit():
|
if zero and zero.isdigit():
|
||||||
return self.media(int(zero))
|
return self.media(int(zero))
|
||||||
@@ -4644,8 +4642,10 @@ class Wiki(object):
|
|||||||
db.wiki_page.can_edit.default = [auth.user_group_role()]
|
db.wiki_page.can_edit.default = [auth.user_group_role()]
|
||||||
db.wiki_page.title.default = title_guess
|
db.wiki_page.title.default = title_guess
|
||||||
db.wiki_page.slug.default = slug
|
db.wiki_page.slug.default = slug
|
||||||
db.wiki_page.menu.default = slug
|
if slug == 'wiki-menu':
|
||||||
db.wiki_page.body.default = '## %s\n\npage content' % title_guess
|
db.wiki_page.body.default = '- Menu Item > @////index\n- - Submenu > http://web2py.com'
|
||||||
|
else:
|
||||||
|
db.wiki_page.body.default = '## %s\n\npage content' % title_guess
|
||||||
vars = current.request.post_vars
|
vars = current.request.post_vars
|
||||||
if vars.body:
|
if vars.body:
|
||||||
vars.body=vars.body.replace('://%s' % self.host,'://HOSTNAME')
|
vars.body=vars.body.replace('://%s' % self.host,'://HOSTNAME')
|
||||||
@@ -4716,23 +4716,23 @@ class Wiki(object):
|
|||||||
def menu(self,controller='default',function='index'):
|
def menu(self,controller='default',function='index'):
|
||||||
db = self.auth.db
|
db = self.auth.db
|
||||||
request = current.request
|
request = current.request
|
||||||
rows = db((db.wiki_page.menu!=None)|(db.wiki_page.menu!=''))\
|
menu_page = db.wiki_page(slug='wiki-menu')
|
||||||
.select(db.wiki_page.menu,db.wiki_page.title,db.wiki_page.slug,
|
|
||||||
orderby = db.wiki_page.menu)
|
|
||||||
menu = []
|
menu = []
|
||||||
tree = {'.':menu}
|
if menu_page:
|
||||||
regex = re.compile('\d\:')
|
tree = {'':menu}
|
||||||
for row in rows:
|
regex = re.compile('[\r\n\t]*(?P<base>(\s*\-\s*)+)(?P<title>\w.*?)\s+\>\s+(?P<link>\S+)')
|
||||||
if row.menu:
|
for match in regex.finditer(self.fix_hostname(menu_page.body)):
|
||||||
key = './'+regex.sub('',row.menu)
|
base = match.group('base').replace(' ','')
|
||||||
base = key.rsplit('/',1)[0]
|
title = match.group('title')
|
||||||
subtree = tree[key] = []
|
link = match.group('link')
|
||||||
if base in tree:
|
if link.startswith('@'):
|
||||||
tree[base].append((current.T(row.title),
|
items = link[1:].split('/')
|
||||||
request.args(0)==row.slug,
|
if len(items)>3:
|
||||||
URL(controller,function,args=row.slug),
|
link = URL(a=items[0] or None,c=items[1] or None,f=items[2] or None, args=items[3:])
|
||||||
subtree))
|
parent = tree.get(base[1:],tree[''])
|
||||||
#if self.auth.user:
|
subtree = []
|
||||||
|
tree[base] = subtree
|
||||||
|
parent.append((current.T(title),False,link,subtree))
|
||||||
if True:
|
if True:
|
||||||
submenu = []
|
submenu = []
|
||||||
menu.append((current.T('[Wiki]'),None,None,submenu))
|
menu.append((current.T('[Wiki]'),None,None,submenu))
|
||||||
@@ -4763,6 +4763,8 @@ class Wiki(object):
|
|||||||
# if self.can_manage():
|
# if self.can_manage():
|
||||||
submenu.append((current.T('Manage Pages'),None,
|
submenu.append((current.T('Manage Pages'),None,
|
||||||
URL(controller,function,args=('_pages'))))
|
URL(controller,function,args=('_pages'))))
|
||||||
|
submenu.append((current.T('Edit Menu'),None,
|
||||||
|
URL(controller,function,args=('_edit','wiki-menu'))))
|
||||||
# if self.can_search():
|
# if self.can_search():
|
||||||
submenu.append((current.T('Search Pages'),None,
|
submenu.append((current.T('Search Pages'),None,
|
||||||
URL(controller,function,args=('_search'))))
|
URL(controller,function,args=('_search'))))
|
||||||
|
|||||||
Reference in New Issue
Block a user