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
|
||||
- experimental MongoDB support in DAL
|
||||
- FORM.confirm('Confirm?',{'Back':URL(...)})
|
||||
- @auth.requires_login(otherwise=URL(...))
|
||||
- request.args(0,default=0, cast=int, otherwise=URL(...)), thanks Anthony
|
||||
- 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
|
||||
### DAL Improvements
|
||||
|
||||
- MongoDB support in DAL (experimental)
|
||||
- geodal and spatialite, thanks Denes and Fran (experimental)
|
||||
- db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
|
||||
- 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)
|
||||
- IS_IN_DB distinct
|
||||
- more secure SQLFORM.grid and SQLFORM.smartgrid
|
||||
- more export options in SQLFORM.grid and SQLFORM.smartgrid
|
||||
- more secure admin against CSRF
|
||||
- improved spreadsheet, thanks Alan
|
||||
- routes in can redirect outside with routes_in=[('/path','303->http://..')]
|
||||
- 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
|
||||
- IS_IN_DB(..., distinct=True)
|
||||
- new syntax: db.mytable.insert(myuploadfield=open(....))
|
||||
- db(...).select(db.mytable.myfield.count(distinct=True))
|
||||
- db(db.a)._update(name=db(db.b.a==db.a.id).nested_select(db.b.id))
|
||||
- db.mytable.myfield.filter_in, filter_out
|
||||
- db.mytable._enable_record_versioning(db) adds verining to this table
|
||||
- teradata adapter, thanks Andrew Willimott
|
||||
- experimental Sybase Adapter
|
||||
- added db.table.field.avg()
|
||||
- db(db.a)._update(name=db(db.b.a==db.a.id).nested_select(db.b.id))
|
||||
- new layout based on bootstrap and bootswatch
|
||||
- Suport for Google App Engine projections
|
||||
|
||||
### 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
|
||||
- Romanian translation for welcome, thanks ionel
|
||||
- support for mercurial 2.6, thanks Vlad
|
||||
- GAE projections
|
||||
- SQLFORM.grid(groupby=...')
|
||||
- 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)
|
||||
|
||||
### Scheduler Inprovements
|
||||
|
||||
- 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
|
||||
@@ -125,7 +142,6 @@ This is a major revision in peparation for web2py 2.0
|
||||
- experimental PyPy support
|
||||
- experimental mongodb support, thanks Mark
|
||||
- tickets in db now accessible from admin, thanks Niphlod
|
||||
- many grid improvements and bug fixes
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -124,4 +124,8 @@ push:
|
||||
hg push
|
||||
git push
|
||||
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
|
||||
"""
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
dict.__init__(self,*args,**kwargs)
|
||||
self.__dict__ = self
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
self[key] = value
|
||||
|
||||
def __getitem__(self, 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
|
||||
:attempts (defaults to 5). Number of times to attempt connecting
|
||||
"""
|
||||
self.__dict__ = self
|
||||
if not decode_credentials:
|
||||
credential_decoder = lambda cred: cred
|
||||
else:
|
||||
@@ -6983,8 +6984,7 @@ def index():
|
||||
yield self[tablename]
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = str(key)
|
||||
return dict.__getitem__(self,key)
|
||||
return self.__getattr__(str(key))
|
||||
|
||||
def __getattr__(self, key):
|
||||
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:
|
||||
raise SyntaxError, \
|
||||
'Object %s exists and cannot be redefined' % key
|
||||
dict.__setattr__(self,key,value)
|
||||
dict.__setitem__(self,key,value)
|
||||
|
||||
def __repr__(self):
|
||||
return '<DAL ' + dict.__repr__(self) + '>'
|
||||
@@ -7201,7 +7201,6 @@ 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 \
|
||||
@@ -7463,6 +7462,9 @@ class Table(dict):
|
||||
'value must be a dictionary: %s' % value
|
||||
dict.__setitem__(self, str(key), value)
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
|
||||
def __delitem__(self, key):
|
||||
if isinstance(key, dict):
|
||||
query = self._build_query(key)
|
||||
@@ -7474,7 +7476,7 @@ class Table(dict):
|
||||
def __setattr__(self, key, value):
|
||||
if key[:1]!='_' and key in self:
|
||||
raise SyntaxError, 'Object exists and cannot be redefined: %s' % key
|
||||
dict.__setattr__(self,key,value)
|
||||
self[key] = value
|
||||
|
||||
def __iter__(self):
|
||||
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
|
||||
osession = storage.load_storage(os.path.join(
|
||||
up(request.folder), other_application, 'sessions', session_id))
|
||||
except:
|
||||
except Exception, e:
|
||||
osession = storage.Storage()
|
||||
return osession
|
||||
|
||||
|
||||
+1
-1
@@ -2044,7 +2044,7 @@ class FORM(DIV):
|
||||
|
||||
|
||||
@staticmethod
|
||||
def confim(text='OK',buttons=None,hidden=None):
|
||||
def confirm(text='OK',buttons=None,hidden=None):
|
||||
if not buttons: buttons = {}
|
||||
if not hidden: hidden={}
|
||||
inputs = [INPUT(_type='button',
|
||||
|
||||
+5
-11
@@ -476,7 +476,7 @@ def load_routers(all_apps):
|
||||
def regex_uri(e, regexes, tag, default=None):
|
||||
"filter incoming URI against a list of regexes"
|
||||
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(':')
|
||||
if i > 0:
|
||||
host = host[:i]
|
||||
@@ -823,21 +823,15 @@ class MapUrlIn(object):
|
||||
self.remote_addr = self.env.get('REMOTE_ADDR','localhost')
|
||||
self.scheme = self.env.get('wsgi.url_scheme', 'http').lower()
|
||||
self.method = self.env.get('REQUEST_METHOD', 'get').lower()
|
||||
self.host = self.env.get('HTTP_HOST')
|
||||
self.port = None
|
||||
(self.host, self.port) = (self.env.get('HTTP_HOST'), None)
|
||||
if not self.host:
|
||||
self.host = self.env.get('SERVER_NAME')
|
||||
self.port = self.env.get('SERVER_PORT')
|
||||
(self.host, self.port) = (self.env.get('SERVER_NAME'), self.env.get('SERVER_PORT'))
|
||||
if not self.host:
|
||||
self.host = 'localhost'
|
||||
self.port = '80'
|
||||
(self.host, self.port) = ('localhost', '80')
|
||||
if ':' in self.host:
|
||||
(self.host, self.port) = self.host.split(':')
|
||||
if not self.port:
|
||||
if self.scheme == 'https':
|
||||
self.port = '443'
|
||||
else:
|
||||
self.port = '80'
|
||||
self.port = '443' if self.scheme == 'https' else '80'
|
||||
|
||||
def map_prefix(self):
|
||||
"strip path prefix, if present in its entirety"
|
||||
|
||||
+57
-70
@@ -13,42 +13,11 @@ Provides:
|
||||
"""
|
||||
|
||||
import cPickle
|
||||
import portalocker
|
||||
|
||||
__all__ = ['List', 'Storage', 'Settings', 'Messages',
|
||||
'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):
|
||||
"""
|
||||
A Storage object is like a dictionary except `obj.foo` can be used
|
||||
@@ -66,31 +35,24 @@ class Storage(dict):
|
||||
2
|
||||
|
||||
>>> del o.a
|
||||
>>> print o.a
|
||||
|
||||
>>> print o.a
|
||||
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 __getattr__(self, key):
|
||||
return dict.get(self, key, None)
|
||||
def __setattr__(self, key, value):
|
||||
self[key] = value
|
||||
def __delattr__(self, key):
|
||||
del self[key]
|
||||
def __getitem__(self, key):
|
||||
return dict.get(self, key, None)
|
||||
def __repr__(self):
|
||||
return '<Storage %s>' % dict.__repr__(self)
|
||||
return '<Storage %s>' + dict.__repr__(self)
|
||||
def __getstate__(self):
|
||||
return dict(self)
|
||||
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 __setstate__(self,values):
|
||||
self.update(values)
|
||||
|
||||
def getlist(self,key):
|
||||
"""
|
||||
Return a Storage value as a list.
|
||||
@@ -111,9 +73,10 @@ class Storage(dict):
|
||||
>>> request.vars.getlist('z')
|
||||
[]
|
||||
"""
|
||||
value = getattr(self,key,[])
|
||||
value = self.get(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.
|
||||
@@ -132,8 +95,9 @@ class Storage(dict):
|
||||
'abc'
|
||||
>>> request.vars.getfirst('z')
|
||||
"""
|
||||
values = self.getlist(default)
|
||||
values = self.getlist(key)
|
||||
return values[0] if values else default
|
||||
|
||||
def getlast(self,key,default=None):
|
||||
"""
|
||||
Returns the last or only single value when
|
||||
@@ -153,8 +117,8 @@ class Storage(dict):
|
||||
'def'
|
||||
>>> request.vars.getlast('z')
|
||||
"""
|
||||
values = self.getlist(default)
|
||||
return values[0] if values else default
|
||||
values = self.getlist(key)
|
||||
return values[-1] if values else default
|
||||
|
||||
PICKABLE = (str,int,long,float,bool,list,dict,tuple,set)
|
||||
|
||||
@@ -191,29 +155,52 @@ def save_storage(storage, filename):
|
||||
|
||||
class Settings(Storage):
|
||||
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
|
||||
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
|
||||
Storage.__setattr__(self,key,value)
|
||||
self[key] = value
|
||||
|
||||
class Messages(Storage):
|
||||
class Messages(Settings):
|
||||
def __init__(self, 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):
|
||||
value = Storage.__getattr__(self,key)
|
||||
value = self[key]
|
||||
if isinstance(value, str):
|
||||
return str(self.T(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__':
|
||||
import doctest
|
||||
|
||||
+27
-25
@@ -3138,7 +3138,8 @@ class Auth(object):
|
||||
return id
|
||||
def wiki(self,slug=None,env=None,manage_permissions=False,force_prefix=''):
|
||||
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)
|
||||
else:
|
||||
self._wiki.env.update(env or {})
|
||||
@@ -4466,13 +4467,12 @@ class Wiki(object):
|
||||
items = text.split('/')
|
||||
controller, function, args = items[0], items[1], items[2:]
|
||||
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=''):
|
||||
self.env = env or {}
|
||||
self.env['component'] = Wiki.component
|
||||
if render == 'markmin': render=self.markmin_render
|
||||
self.auth = auth
|
||||
self.automenu = automenu
|
||||
if self.auth.user:
|
||||
self.force_prefix = force_prefix % self.auth.user
|
||||
else:
|
||||
@@ -4487,7 +4487,6 @@ class Wiki(object):
|
||||
readable=False,writable=False),
|
||||
Field('title',unique=True),
|
||||
Field('body','text',notnull=True),
|
||||
Field('menu'),
|
||||
Field('tags','list:string'),
|
||||
Field('can_read','list:string',writable=perms,readable=perms,
|
||||
default=[Wiki.everybody]),
|
||||
@@ -4553,9 +4552,8 @@ class Wiki(object):
|
||||
### END POLICY
|
||||
def __call__(self):
|
||||
request = current.request
|
||||
if self.automenu:
|
||||
current.response.menu = self.menu(request.controller,
|
||||
request.function)
|
||||
automenu = self.menu(request.controller,request.function)
|
||||
current.response.menu += automenu
|
||||
zero = request.args(0)
|
||||
if zero and zero.isdigit():
|
||||
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.title.default = title_guess
|
||||
db.wiki_page.slug.default = slug
|
||||
db.wiki_page.menu.default = slug
|
||||
db.wiki_page.body.default = '## %s\n\npage content' % title_guess
|
||||
if slug == 'wiki-menu':
|
||||
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
|
||||
if vars.body:
|
||||
vars.body=vars.body.replace('://%s' % self.host,'://HOSTNAME')
|
||||
@@ -4716,23 +4716,23 @@ class Wiki(object):
|
||||
def menu(self,controller='default',function='index'):
|
||||
db = self.auth.db
|
||||
request = current.request
|
||||
rows = db((db.wiki_page.menu!=None)|(db.wiki_page.menu!=''))\
|
||||
.select(db.wiki_page.menu,db.wiki_page.title,db.wiki_page.slug,
|
||||
orderby = db.wiki_page.menu)
|
||||
menu_page = db.wiki_page(slug='wiki-menu')
|
||||
menu = []
|
||||
tree = {'.':menu}
|
||||
regex = re.compile('\d\:')
|
||||
for row in rows:
|
||||
if row.menu:
|
||||
key = './'+regex.sub('',row.menu)
|
||||
base = key.rsplit('/',1)[0]
|
||||
subtree = tree[key] = []
|
||||
if base in tree:
|
||||
tree[base].append((current.T(row.title),
|
||||
request.args(0)==row.slug,
|
||||
URL(controller,function,args=row.slug),
|
||||
subtree))
|
||||
#if self.auth.user:
|
||||
if menu_page:
|
||||
tree = {'':menu}
|
||||
regex = re.compile('[\r\n\t]*(?P<base>(\s*\-\s*)+)(?P<title>\w.*?)\s+\>\s+(?P<link>\S+)')
|
||||
for match in regex.finditer(self.fix_hostname(menu_page.body)):
|
||||
base = match.group('base').replace(' ','')
|
||||
title = match.group('title')
|
||||
link = match.group('link')
|
||||
if link.startswith('@'):
|
||||
items = link[1:].split('/')
|
||||
if len(items)>3:
|
||||
link = URL(a=items[0] or None,c=items[1] or None,f=items[2] or None, args=items[3:])
|
||||
parent = tree.get(base[1:],tree[''])
|
||||
subtree = []
|
||||
tree[base] = subtree
|
||||
parent.append((current.T(title),False,link,subtree))
|
||||
if True:
|
||||
submenu = []
|
||||
menu.append((current.T('[Wiki]'),None,None,submenu))
|
||||
@@ -4763,6 +4763,8 @@ class Wiki(object):
|
||||
# if self.can_manage():
|
||||
submenu.append((current.T('Manage Pages'),None,
|
||||
URL(controller,function,args=('_pages'))))
|
||||
submenu.append((current.T('Edit Menu'),None,
|
||||
URL(controller,function,args=('_edit','wiki-menu'))))
|
||||
# if self.can_search():
|
||||
submenu.append((current.T('Search Pages'),None,
|
||||
URL(controller,function,args=('_search'))))
|
||||
|
||||
Reference in New Issue
Block a user