Merge github.com:web2py/web2py
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -294,7 +294,7 @@ div.error {
|
||||
margin-bottom:18px;
|
||||
}
|
||||
|
||||
.web2py_breadcrumbs ul li {
|
||||
li.w2p_grid_breadcrumb_elem {
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
|
||||
+25
-17
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -33,6 +33,7 @@ class TicketStorage(Storage):
|
||||
db=None,
|
||||
tablename='web2py_ticket'
|
||||
):
|
||||
Storage.__init__(self)
|
||||
self.db = db
|
||||
self.tablename = tablename
|
||||
|
||||
|
||||
+36
-13
@@ -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
@@ -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
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user