Merge branch 'master' of git://github.com/web2py/web2py
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Version 2.8.2-stable+timestamp.2013.11.28.07.51.37
|
Version 2.8.2-stable+timestamp.2013.12.17.16.49.17
|
||||||
|
|||||||
@@ -30,10 +30,18 @@ from gluon.languages import (read_possible_languages, read_dict, write_dict,
|
|||||||
read_plural_dict, write_plural_dict)
|
read_plural_dict, write_plural_dict)
|
||||||
|
|
||||||
|
|
||||||
if DEMO_MODE and request.function in ['change_password', 'pack', 'pack_custom','pack_plugin', 'upgrade_web2py', 'uninstall', 'cleanup', 'compile_app', 'remove_compiled_app', 'delete', 'delete_plugin', 'create_file', 'upload_file', 'update_languages', 'reload_routes', 'git_push', 'git_pull']:
|
if DEMO_MODE and request.function in ['change_password', 'pack',
|
||||||
|
'pack_custom','pack_plugin', 'upgrade_web2py', 'uninstall',
|
||||||
|
'cleanup', 'compile_app', 'remove_compiled_app', 'delete',
|
||||||
|
'delete_plugin', 'create_file', 'upload_file', 'update_languages',
|
||||||
|
'reload_routes', 'git_push', 'git_pull', 'install_plugin']:
|
||||||
session.flash = T('disabled in demo mode')
|
session.flash = T('disabled in demo mode')
|
||||||
redirect(URL('site'))
|
redirect(URL('site'))
|
||||||
|
|
||||||
|
if is_gae and request.function in ('edit', 'edit_language',
|
||||||
|
'edit_plurals', 'update_languages', 'create_file', 'install_plugin'):
|
||||||
|
session.flash = T('disabled in GAE mode')
|
||||||
|
redirect(URL('site'))
|
||||||
|
|
||||||
if not is_manager() and request.function in ['change_password', 'upgrade_web2py']:
|
if not is_manager() and request.function in ['change_password', 'upgrade_web2py']:
|
||||||
session.flash = T('disabled in multi user mode')
|
session.flash = T('disabled in multi user mode')
|
||||||
@@ -63,10 +71,12 @@ def log_progress(app, mode='EDIT', filename=None, progress=0):
|
|||||||
|
|
||||||
|
|
||||||
def safe_open(a, b):
|
def safe_open(a, b):
|
||||||
if DEMO_MODE and ('w' in b or 'a' in b):
|
if (DEMO_MODE or is_gae) and ('w' in b or 'a' in b):
|
||||||
class tmp:
|
class tmp:
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
pass
|
pass
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
return tmp()
|
return tmp()
|
||||||
return open(a, b)
|
return open(a, b)
|
||||||
|
|
||||||
@@ -750,6 +760,7 @@ def edit():
|
|||||||
return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight})
|
return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight})
|
||||||
else:
|
else:
|
||||||
file_details = dict(app=request.args[0],
|
file_details = dict(app=request.args[0],
|
||||||
|
lineno=request.vars.lineno or 1,
|
||||||
editor_settings=preferences,
|
editor_settings=preferences,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
realfilename=realfilename,
|
realfilename=realfilename,
|
||||||
@@ -773,6 +784,35 @@ def edit():
|
|||||||
else:
|
else:
|
||||||
return response.json(file_details)
|
return response.json(file_details)
|
||||||
|
|
||||||
|
def todolist():
|
||||||
|
""" Returns all TODO of the requested app
|
||||||
|
"""
|
||||||
|
app = request.vars.app or ''
|
||||||
|
app_path = apath('%(app)s' % {'app':app}, r=request)
|
||||||
|
dirs=['models', 'controllers', 'modules', 'private' ]
|
||||||
|
def listfiles(app, dir, regexp='.*\.py$'):
|
||||||
|
files = sorted( listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
||||||
|
files = [x.replace(os.path.sep, '/') for x in files if not x.endswith('.bak')]
|
||||||
|
return files
|
||||||
|
|
||||||
|
pattern = '#\s*(todo)+\s+(.*)'
|
||||||
|
regex = re.compile(pattern, re.IGNORECASE)
|
||||||
|
|
||||||
|
output = []
|
||||||
|
for d in dirs:
|
||||||
|
for f in listfiles(app, d):
|
||||||
|
matches = []
|
||||||
|
filename= apath(os.path.join(app, d, f), r=request)
|
||||||
|
with open(filename, 'r') as f_s:
|
||||||
|
src = f_s.read()
|
||||||
|
for m in regex.finditer(src):
|
||||||
|
start = m.start()
|
||||||
|
lineno = src.count('\n', 0, start) + 1
|
||||||
|
matches.append({'text':m.group(0), 'lineno':lineno})
|
||||||
|
if len(matches) != 0:
|
||||||
|
output.append({'filename':f,'matches':matches, 'dir':d})
|
||||||
|
|
||||||
|
return {'todo':output, 'app': app}
|
||||||
|
|
||||||
def resolve():
|
def resolve():
|
||||||
"""
|
"""
|
||||||
@@ -1053,11 +1093,12 @@ def design():
|
|||||||
|
|
||||||
#Get crontab
|
#Get crontab
|
||||||
cronfolder = apath('%s/cron' % app, r=request)
|
cronfolder = apath('%s/cron' % app, r=request)
|
||||||
if not os.path.exists(cronfolder):
|
|
||||||
os.mkdir(cronfolder)
|
|
||||||
crontab = apath('%s/cron/crontab' % app, r=request)
|
crontab = apath('%s/cron/crontab' % app, r=request)
|
||||||
if not os.path.exists(crontab):
|
if not is_gae:
|
||||||
safe_write(crontab, '#crontab')
|
if not os.path.exists(cronfolder):
|
||||||
|
os.mkdir(cronfolder)
|
||||||
|
if not os.path.exists(crontab):
|
||||||
|
safe_write(crontab, '#crontab')
|
||||||
|
|
||||||
plugins = []
|
plugins = []
|
||||||
|
|
||||||
@@ -1209,7 +1250,6 @@ def plugin():
|
|||||||
languages=languages,
|
languages=languages,
|
||||||
crontab=crontab)
|
crontab=crontab)
|
||||||
|
|
||||||
|
|
||||||
def create_file():
|
def create_file():
|
||||||
""" Create files handler """
|
""" Create files handler """
|
||||||
if request.vars and not request.vars.token == session.token:
|
if request.vars and not request.vars.token == session.token:
|
||||||
@@ -1220,6 +1260,8 @@ def create_file():
|
|||||||
app = get_app(request.vars.app)
|
app = get_app(request.vars.app)
|
||||||
path = abspath(request.vars.location)
|
path = abspath(request.vars.location)
|
||||||
else:
|
else:
|
||||||
|
if request.vars.dir:
|
||||||
|
request.vars.location += request.vars.dir + '/'
|
||||||
app = get_app(name=request.vars.location.split('/')[0])
|
app = get_app(name=request.vars.location.split('/')[0])
|
||||||
path = apath(request.vars.location, r=request)
|
path = apath(request.vars.location, r=request)
|
||||||
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
||||||
@@ -1347,7 +1389,11 @@ def create_file():
|
|||||||
|
|
||||||
safe_write(full_filename, text)
|
safe_write(full_filename, text)
|
||||||
log_progress(app, 'CREATE', filename)
|
log_progress(app, 'CREATE', filename)
|
||||||
session.flash = T('file "%(filename)s" created',
|
if request.vars.dir:
|
||||||
|
result = T('file "%(filename)s" created',
|
||||||
|
dict(filename=full_filename[len(path):]))
|
||||||
|
else:
|
||||||
|
session.flash = T('file "%(filename)s" created',
|
||||||
dict(filename=full_filename[len(path):]))
|
dict(filename=full_filename[len(path):]))
|
||||||
vars = {}
|
vars = {}
|
||||||
if request.vars.id:
|
if request.vars.id:
|
||||||
@@ -1356,13 +1402,51 @@ def create_file():
|
|||||||
vars['app'] = request.vars.app
|
vars['app'] = request.vars.app
|
||||||
redirect(URL('edit',
|
redirect(URL('edit',
|
||||||
args=[os.path.join(request.vars.location, filename)], vars=vars))
|
args=[os.path.join(request.vars.location, filename)], vars=vars))
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if not isinstance(e, HTTP):
|
if not isinstance(e, HTTP):
|
||||||
session.flash = T('cannot create file')
|
session.flash = T('cannot create file')
|
||||||
|
|
||||||
redirect(request.vars.sender + anchor)
|
if request.vars.dir:
|
||||||
|
response.flash = result
|
||||||
|
response.headers['web2py-component-content'] = 'append'
|
||||||
|
response.headers['web2py-component-command'] = """
|
||||||
|
$.web2py.invalidate('#files_menu');
|
||||||
|
load_file('%s');
|
||||||
|
$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));
|
||||||
|
""" % URL('edit', args=[app,request.vars.dir,filename])
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
redirect(request.vars.sender + anchor)
|
||||||
|
|
||||||
|
|
||||||
|
def listfiles(app, dir, regexp='.*\.py$'):
|
||||||
|
files = sorted(
|
||||||
|
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
||||||
|
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
||||||
|
return files
|
||||||
|
|
||||||
|
def editfile(path,file,vars={}, app = None):
|
||||||
|
args=(path,file) if 'app' in vars else (app,path,file)
|
||||||
|
url = URL('edit', args=args, vars=vars)
|
||||||
|
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
|
||||||
|
|
||||||
|
def files_menu():
|
||||||
|
app = request.vars.app or 'welcome'
|
||||||
|
dirs=[{'name':'models', 'reg':'.*\.py$'},
|
||||||
|
{'name':'controllers', 'reg':'.*\.py$'},
|
||||||
|
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
||||||
|
{'name':'modules', 'reg':'.*\.py$'},
|
||||||
|
{'name':'static', 'reg': '[^\.#].*'}]
|
||||||
|
result_files = []
|
||||||
|
for dir in dirs:
|
||||||
|
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
|
||||||
|
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
|
||||||
|
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
|
||||||
|
_class="nav nav-list small-font"),
|
||||||
|
_id=dir['name'] + '_files', _style="display: none;")))
|
||||||
|
return dict(result_files = result_files)
|
||||||
|
|
||||||
def upload_file():
|
def upload_file():
|
||||||
""" File uploading handler """
|
""" File uploading handler """
|
||||||
if request.vars and not request.vars.token == session.token:
|
if request.vars and not request.vars.token == session.token:
|
||||||
@@ -1422,8 +1506,11 @@ def errors():
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
app = get_app()
|
app = get_app()
|
||||||
|
if is_gae:
|
||||||
method = request.args(1) or 'new'
|
method = 'dbold' if ('old' in
|
||||||
|
(request.args(1) or '')) else 'dbnew'
|
||||||
|
else:
|
||||||
|
method = request.args(1) or 'new'
|
||||||
db_ready = {}
|
db_ready = {}
|
||||||
db_ready['status'] = get_ticket_storage(app)
|
db_ready['status'] = get_ticket_storage(app)
|
||||||
db_ready['errmessage'] = T(
|
db_ready['errmessage'] = T(
|
||||||
@@ -1490,32 +1577,30 @@ def errors():
|
|||||||
for fn in tk_db(tk_table.id > 0).select():
|
for fn in tk_db(tk_table.id > 0).select():
|
||||||
try:
|
try:
|
||||||
error = pickle.loads(fn.ticket_data)
|
error = pickle.loads(fn.ticket_data)
|
||||||
except AttributeError:
|
hash = hashlib.md5(error['traceback']).hexdigest()
|
||||||
|
|
||||||
|
if hash in delete_hashes:
|
||||||
|
tk_db(tk_table.id == fn.id).delete()
|
||||||
|
tk_db.commit()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
hash2error[hash]['count'] += 1
|
||||||
|
except KeyError:
|
||||||
|
error_lines = error['traceback'].split("\n")
|
||||||
|
last_line = error_lines[-2]
|
||||||
|
error_causer = os.path.split(error['layer'])[1]
|
||||||
|
hash2error[hash] = dict(count=1,
|
||||||
|
pickel=error, causer=error_causer,
|
||||||
|
last_line=last_line, hash=hash,
|
||||||
|
ticket=fn.ticket_id)
|
||||||
|
except AttributeError, e:
|
||||||
tk_db(tk_table.id == fn.id).delete()
|
tk_db(tk_table.id == fn.id).delete()
|
||||||
tk_db.commit()
|
tk_db.commit()
|
||||||
|
|
||||||
hash = hashlib.md5(error['traceback']).hexdigest()
|
|
||||||
|
|
||||||
if hash in delete_hashes:
|
|
||||||
tk_db(tk_table.id == fn.id).delete()
|
|
||||||
tk_db.commit()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
hash2error['hash']['count'] += 1
|
|
||||||
except KeyError:
|
|
||||||
error_lines = error['traceback'].split("\n")
|
|
||||||
last_line = error_lines[-2]
|
|
||||||
error_causer = os.path.split(error['layer'])[1]
|
|
||||||
hash2error[hash] = dict(count=1, pickel=error,
|
|
||||||
causer=error_causer,
|
|
||||||
last_line=last_line,
|
|
||||||
hash=hash, ticket=fn.ticket_id)
|
|
||||||
|
|
||||||
decorated = [(x['count'], x) for x in hash2error.values()]
|
decorated = [(x['count'], x) for x in hash2error.values()]
|
||||||
|
|
||||||
decorated.sort(key=operator.itemgetter(0), reverse=True)
|
decorated.sort(key=operator.itemgetter(0), reverse=True)
|
||||||
|
return dict(errors=[x[1] for x in decorated], app=app,
|
||||||
return dict(errors=[x[1] for x in decorated], app=app, method=method)
|
method=method, db_ready=db_ready)
|
||||||
|
|
||||||
elif method == 'dbold':
|
elif method == 'dbold':
|
||||||
tk_db, tk_table = get_ticket_storage(app)
|
tk_db, tk_table = get_ticket_storage(app)
|
||||||
@@ -1523,16 +1608,18 @@ def errors():
|
|||||||
if item[:7] == 'delete_':
|
if item[:7] == 'delete_':
|
||||||
tk_db(tk_table.ticket_id == item[7:]).delete()
|
tk_db(tk_table.ticket_id == item[7:]).delete()
|
||||||
tk_db.commit()
|
tk_db.commit()
|
||||||
tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id, tk_table.created_datetime, orderby=~tk_table.created_datetime)
|
tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id,
|
||||||
|
tk_table.created_datetime,
|
||||||
|
orderby=~tk_table.created_datetime)
|
||||||
tickets = [row.ticket_id for row in tickets_]
|
tickets = [row.ticket_id for row in tickets_]
|
||||||
times = dict(
|
times = dict([(row.ticket_id, row.created_datetime) for
|
||||||
[(row.ticket_id, row.created_datetime) for row in tickets_])
|
row in tickets_])
|
||||||
|
return dict(app=app, tickets=tickets, method=method,
|
||||||
return dict(app=app, tickets=tickets, method=method, times=times)
|
times=times, db_ready=db_ready)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for item in request.vars:
|
for item in request.vars:
|
||||||
# delete_all} rows doesn't contain any ticket
|
# delete_all rows doesn't contain any ticket
|
||||||
# Remove anything else as requested
|
# Remove anything else as requested
|
||||||
if item[:7] == 'delete_' and (not item == "delete_all}"):
|
if item[:7] == 'delete_' and (not item == "delete_all}"):
|
||||||
os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request))
|
os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request))
|
||||||
@@ -1552,6 +1639,9 @@ def get_ticket_storage(app):
|
|||||||
if os.path.exists(ticket_file):
|
if os.path.exists(ticket_file):
|
||||||
db_string = open(ticket_file).read()
|
db_string = open(ticket_file).read()
|
||||||
db_string = db_string.strip().replace('\r', '').replace('\n', '')
|
db_string = db_string.strip().replace('\r', '').replace('\n', '')
|
||||||
|
elif is_gae:
|
||||||
|
# use Datastore as fallback if there is no ticket_file
|
||||||
|
db_string = "google:datastore"
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
tickets_table = 'web2py_ticket'
|
tickets_table = 'web2py_ticket'
|
||||||
@@ -1804,24 +1894,7 @@ def plugins():
|
|||||||
"public/api.json/action/list/content/Package?package" +
|
"public/api.json/action/list/content/Package?package" +
|
||||||
"_type=plugin&search_index=false").read()
|
"_type=plugin&search_index=false").read()
|
||||||
session.plugins = loads_json(rawlist)
|
session.plugins = loads_json(rawlist)
|
||||||
plugins = TABLE(
|
return dict(plugins=session.plugins["results"], app=request.args(0))
|
||||||
*[TR(TD(H5(article["article"]["title"]),
|
|
||||||
A(T("Install"),
|
|
||||||
_href=URL(c="default",
|
|
||||||
f="install_plugin",
|
|
||||||
args=[app,],
|
|
||||||
vars={"source":
|
|
||||||
article["package_data"]["download"],
|
|
||||||
"plugin": article["article"]["title"]}
|
|
||||||
))),
|
|
||||||
TD(article["article"]["description"], BR(),
|
|
||||||
A(T("Plugin page"),
|
|
||||||
_href="http://www.web2pyslices.com/slice/show/%s/" % \
|
|
||||||
article["article"]["id"])),
|
|
||||||
TD(IMG(_src="http://www.web2pyslices.com/download/%s" % \
|
|
||||||
article["article"]["thumbnail"])))
|
|
||||||
for article in session.plugins["results"]])
|
|
||||||
return dict(plugins=plugins, app=request.args(0))
|
|
||||||
|
|
||||||
def install_plugin():
|
def install_plugin():
|
||||||
app = request.args(0)
|
app = request.args(0)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import cStringIO
|
|||||||
import gluon.contrib.shell
|
import gluon.contrib.shell
|
||||||
import code
|
import code
|
||||||
import thread
|
import thread
|
||||||
|
import cgi
|
||||||
from gluon.shell import env
|
from gluon.shell import env
|
||||||
|
|
||||||
if DEMO_MODE or MULTI_USER_MODE:
|
if DEMO_MODE or MULTI_USER_MODE:
|
||||||
@@ -40,7 +41,7 @@ def callback():
|
|||||||
k = len(session['commands:' + app]) - 1
|
k = len(session['commands:' + app]) - 1
|
||||||
#output = PRE(output)
|
#output = PRE(output)
|
||||||
#return TABLE(TR('In[%i]:'%k,PRE(command)),TR('Out[%i]:'%k,output))
|
#return TABLE(TR('In[%i]:'%k,PRE(command)),TR('Out[%i]:'%k,output))
|
||||||
return 'In [%i] : %s%s\n' % (k + 1, command, output)
|
return cgi.escape('In [%i] : %s%s\n' % (k + 1, command, output))
|
||||||
|
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# coding: utf8
|
# -*- coding: utf-8 -*-
|
||||||
{
|
{
|
||||||
'!langcode!': 'es',
|
'!langcode!': 'es',
|
||||||
'!langname!': 'Español',
|
'!langname!': 'Español',
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||||
'(requires internet access, experimental)': '(requiere acceso a internet, experimental)',
|
'(requires internet access, experimental)': '(requiere acceso a internet, experimental)',
|
||||||
'(something like "it-it")': '(algo como "it-it")',
|
'(something like "it-it")': '(algo como "it-it")',
|
||||||
|
'(version %s)': '(version %s)',
|
||||||
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
|
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
|
||||||
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||||
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
||||||
@@ -122,6 +123,7 @@
|
|||||||
'delete plugin': 'eliminar plugin',
|
'delete plugin': 'eliminar plugin',
|
||||||
'Delete this file (you will be asked to confirm deletion)': 'Elimine este fichero (se le pedirá confirmación)',
|
'Delete this file (you will be asked to confirm deletion)': 'Elimine este fichero (se le pedirá confirmación)',
|
||||||
'Delete:': 'Elimine:',
|
'Delete:': 'Elimine:',
|
||||||
|
'Demo': 'Demo',
|
||||||
'Deploy': 'Deploy',
|
'Deploy': 'Deploy',
|
||||||
'Deploy on Google App Engine': 'Instale en Google App Engine',
|
'Deploy on Google App Engine': 'Instale en Google App Engine',
|
||||||
'Deploy to OpenShift': 'Instale en OpenShift',
|
'Deploy to OpenShift': 'Instale en OpenShift',
|
||||||
@@ -134,6 +136,7 @@
|
|||||||
'direction: ltr': 'direction: ltr',
|
'direction: ltr': 'direction: ltr',
|
||||||
'Disable': 'Deshabilitar',
|
'Disable': 'Deshabilitar',
|
||||||
'docs': 'docs',
|
'docs': 'docs',
|
||||||
|
'Docs': 'Docs',
|
||||||
'Done!': 'Done!',
|
'Done!': 'Done!',
|
||||||
'done!': 'listo!',
|
'done!': 'listo!',
|
||||||
'Download': 'Descargar',
|
'Download': 'Descargar',
|
||||||
@@ -157,6 +160,7 @@
|
|||||||
'Editing myemail': 'Editing myemail',
|
'Editing myemail': 'Editing myemail',
|
||||||
'Editing rbare': 'Editing rbare',
|
'Editing rbare': 'Editing rbare',
|
||||||
'Editing ul': 'Editing ul',
|
'Editing ul': 'Editing ul',
|
||||||
|
'Enable': 'Enable',
|
||||||
'Enterprise Web Framework': 'Armazón Empresarial para Internet',
|
'Enterprise Web Framework': 'Armazón Empresarial para Internet',
|
||||||
'Error': 'Error',
|
'Error': 'Error',
|
||||||
'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"',
|
'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"',
|
||||||
@@ -190,6 +194,8 @@
|
|||||||
'First name': 'Nombre',
|
'First name': 'Nombre',
|
||||||
'Frames': 'Frames',
|
'Frames': 'Frames',
|
||||||
'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].',
|
'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].',
|
||||||
|
'Git Pull': 'Git Pull',
|
||||||
|
'Git Push': 'Git Push',
|
||||||
'Globals##debug': 'Globals',
|
'Globals##debug': 'Globals',
|
||||||
'graph model': 'graph model',
|
'graph model': 'graph model',
|
||||||
'Group ID': 'ID de Grupo',
|
'Group ID': 'ID de Grupo',
|
||||||
@@ -213,6 +219,7 @@
|
|||||||
'internal error': 'error interno',
|
'internal error': 'error interno',
|
||||||
'Internal State': 'Estado Interno',
|
'Internal State': 'Estado Interno',
|
||||||
'Invalid action': 'Acción inválida',
|
'Invalid action': 'Acción inválida',
|
||||||
|
'Invalid application name': 'Invalid application name',
|
||||||
'Invalid email': 'Correo inválido',
|
'Invalid email': 'Correo inválido',
|
||||||
'invalid password': 'contraseña inválida',
|
'invalid password': 'contraseña inválida',
|
||||||
'invalid password.': 'invalid password.',
|
'invalid password.': 'invalid password.',
|
||||||
@@ -230,6 +237,7 @@
|
|||||||
'Last name': 'Apellido',
|
'Last name': 'Apellido',
|
||||||
'Last saved on:': 'Guardado en:',
|
'Last saved on:': 'Guardado en:',
|
||||||
'License for': 'Licencia para',
|
'License for': 'Licencia para',
|
||||||
|
'License:': 'License:',
|
||||||
'lists by ticket': 'lists by ticket',
|
'lists by ticket': 'lists by ticket',
|
||||||
'loading...': 'cargando...',
|
'loading...': 'cargando...',
|
||||||
'locals': 'locals',
|
'locals': 'locals',
|
||||||
@@ -250,6 +258,7 @@
|
|||||||
'new application "%s" created': 'nueva aplicación "%s" creada',
|
'new application "%s" created': 'nueva aplicación "%s" creada',
|
||||||
'New application wizard': 'Asistente para nueva aplicación',
|
'New application wizard': 'Asistente para nueva aplicación',
|
||||||
'new plugin installed': 'nuevo plugin instalado',
|
'new plugin installed': 'nuevo plugin instalado',
|
||||||
|
'New plugin installed: %s': 'New plugin installed: %s',
|
||||||
'New plugin installed: web2py.plugin.attachment.w2p': 'New plugin installed: web2py.plugin.attachment.w2p',
|
'New plugin installed: web2py.plugin.attachment.w2p': 'New plugin installed: web2py.plugin.attachment.w2p',
|
||||||
'New plugin installed: web2py.plugin.dialog.w2p': 'New plugin installed: web2py.plugin.dialog.w2p',
|
'New plugin installed: web2py.plugin.dialog.w2p': 'New plugin installed: web2py.plugin.dialog.w2p',
|
||||||
'New plugin installed: web2py.plugin.math2py.w2p': 'New plugin installed: web2py.plugin.math2py.w2p',
|
'New plugin installed: web2py.plugin.math2py.w2p': 'New plugin installed: web2py.plugin.math2py.w2p',
|
||||||
@@ -310,6 +319,8 @@
|
|||||||
'Removed Breakpoint on %s at line %s': 'Eliminado punto de ruptura en %s en la línea %s',
|
'Removed Breakpoint on %s at line %s': 'Eliminado punto de ruptura en %s en la línea %s',
|
||||||
'Replace': 'Reemplazar',
|
'Replace': 'Reemplazar',
|
||||||
'Replace All': 'Reemplazar todos',
|
'Replace All': 'Reemplazar todos',
|
||||||
|
'Repository (%s)': 'Repository (%s)',
|
||||||
|
'Repository: %s': 'Repository: %s',
|
||||||
'request': 'request',
|
'request': 'request',
|
||||||
'Resolve Conflict file': 'archivo Resolución de Conflicto',
|
'Resolve Conflict file': 'archivo Resolución de Conflicto',
|
||||||
'response': 'response',
|
'response': 'response',
|
||||||
@@ -329,6 +340,8 @@
|
|||||||
'Save file: %s': 'Guardar: %s',
|
'Save file: %s': 'Guardar: %s',
|
||||||
'Save via Ajax': 'Guardar via Ajax',
|
'Save via Ajax': 'Guardar via Ajax',
|
||||||
'Saved file hash:': 'Hash del archivo guardado:',
|
'Saved file hash:': 'Hash del archivo guardado:',
|
||||||
|
'Screenshot %s': 'Screenshot %s',
|
||||||
|
'Screenshots': 'Screenshots',
|
||||||
'selected': 'seleccionado(s)',
|
'selected': 'seleccionado(s)',
|
||||||
'session': 'session',
|
'session': 'session',
|
||||||
'session expired': 'sesión expirada',
|
'session expired': 'sesión expirada',
|
||||||
@@ -336,6 +349,7 @@
|
|||||||
'shell': 'shell',
|
'shell': 'shell',
|
||||||
'Site': 'sitio',
|
'Site': 'sitio',
|
||||||
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
|
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
|
||||||
|
'source : filesystem': 'source : filesystem',
|
||||||
'Start searching': 'Iniciar búsqueda',
|
'Start searching': 'Iniciar búsqueda',
|
||||||
'Start wizard': 'Iniciar asistente',
|
'Start wizard': 'Iniciar asistente',
|
||||||
'state': 'estado',
|
'state': 'estado',
|
||||||
@@ -355,8 +369,8 @@
|
|||||||
'test': 'probar',
|
'test': 'probar',
|
||||||
'Testing application': 'Probando aplicación',
|
'Testing application': 'Probando aplicación',
|
||||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "consulta" es una condición como "db.tabla1.campo1==\'valor\'". Algo como "db.tabla1.campo1==db.tabla2.campo2" resulta en un JOIN SQL.',
|
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "consulta" es una condición como "db.tabla1.campo1==\'valor\'". Algo como "db.tabla1.campo1==db.tabla2.campo2" resulta en un JOIN SQL.',
|
||||||
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
|
|
||||||
'The application logic, each URL path is mapped in one exposed function in the controller': 'La lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
|
'The application logic, each URL path is mapped in one exposed function in the controller': 'La lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
|
||||||
|
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
|
||||||
'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
|
'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
|
||||||
'The data representation, define database tables and sets': 'La representación de datos, define tablas y conjuntos de base de datos',
|
'The data representation, define database tables and sets': 'La representación de datos, define tablas y conjuntos de base de datos',
|
||||||
'The presentations layer, views are also known as templates': 'La capa de presentación, las vistas también son llamadas plantillas',
|
'The presentations layer, views are also known as templates': 'La capa de presentación, las vistas también son llamadas plantillas',
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ if session.is_mobile == 'true':
|
|||||||
elif session.is_mobile == 'false':
|
elif session.is_mobile == 'false':
|
||||||
is_mobile = False
|
is_mobile = False
|
||||||
else:
|
else:
|
||||||
is_mobile = request.user_agent().is_mobile
|
is_mobile = request.user_agent().get('is_mobile',False)
|
||||||
|
|
||||||
if DEMO_MODE:
|
if DEMO_MODE:
|
||||||
session.authorized = True
|
session.authorized = True
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def button_enable(href, app):
|
|||||||
return A(label, _class='button btn', _id=id, callback=href, target=id)
|
return A(label, _class='button btn', _id=id, callback=href, target=id)
|
||||||
|
|
||||||
def sp_button(href, label):
|
def sp_button(href, label):
|
||||||
if request.user_agent().is_mobile:
|
if request.user_agent().get('is_mobile'):
|
||||||
ret = A_button(SPAN(label), _href=href)
|
ret = A_button(SPAN(label), _href=href)
|
||||||
else:
|
else:
|
||||||
ret = A(SPAN(label), _class='button special btn btn-inverse', _href=href)
|
ret = A(SPAN(label), _class='button special btn btn-inverse', _href=href)
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
|
/* TODO rename this file as web2py-editor.css */
|
||||||
/* Fullscreen */
|
/* Fullscreen */
|
||||||
.CodeMirror-fullscreen {
|
.CodeMirror-fullscreen {
|
||||||
z-index: 1030;
|
z-index: 1030;
|
||||||
}
|
}
|
||||||
|
.CodeMirror {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
/*border-left: 1px solid #ddd;*/
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
/* BREAKPOINTS */
|
/* BREAKPOINTS */
|
||||||
|
|
||||||
@@ -36,3 +42,15 @@
|
|||||||
/*.nav-tabs>li {
|
/*.nav-tabs>li {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
#windows_divs > div {
|
||||||
|
position: fixed;
|
||||||
|
height: 30%;
|
||||||
|
left: 0;
|
||||||
|
background: white;
|
||||||
|
right: 0;
|
||||||
|
bottom: 41px;
|
||||||
|
z-index: 1030;
|
||||||
|
overflow: inherit;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ function keepalive(url) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_file(url) {
|
function load_file(url, lineno) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
@@ -263,13 +263,13 @@ function load_file(url) {
|
|||||||
var tab_header = '<li><a title="'+ json['filename'] +'" href="#' + json['id'] + '" data-toggle="tab"><button type="button" class="close">×</button>' + json['realfilename'] + '</a></li>';
|
var tab_header = '<li><a title="'+ json['filename'] +'" href="#' + json['id'] + '" data-toggle="tab"><button type="button" class="close">×</button>' + json['realfilename'] + '</a></li>';
|
||||||
var tab_body = '<div id="' + json['id'] + '" class="tab-pane fade in " >' + json['plain_html'] + '</div>';
|
var tab_body = '<div id="' + json['id'] + '" class="tab-pane fade in " >' + json['plain_html'] + '</div>';
|
||||||
if(json['force'] === false) {
|
if(json['force'] === false) {
|
||||||
$('#filesTab').append($(tab_header));
|
$('#myTabContent').append($(tab_body)); // First load the body
|
||||||
$('#myTabContent').append($(tab_body));
|
$('#filesTab').append($(tab_header)); // Then load the header which trigger the shown event
|
||||||
} else {
|
} else {
|
||||||
$('#' + json['id']).html($(tab_body));
|
$('#' + json['id']).html($(tab_body));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("a[href='#" + json['id'] + "']").click();
|
$("a[href='#" + json['id'] + "']").trigger('click', lineno);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (x) {
|
error: function (x) {
|
||||||
|
|||||||
@@ -485,8 +485,10 @@
|
|||||||
el.addClass('disabled');
|
el.addClass('disabled');
|
||||||
var method = el.prop('type') == 'submit' ? 'val' : 'html';
|
var method = el.prop('type') == 'submit' ? 'val' : 'html';
|
||||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||||
/*store enabled state*/
|
/*store enabled state if not already disabled */
|
||||||
el.data('w2p:enable-with', el[method]());
|
if (el.data('w2p:enable-with') === undefined) {
|
||||||
|
el.data('w2p:enable-with', el[method]());
|
||||||
|
}
|
||||||
/*if you don't want to see "working..." on buttons, replace the following
|
/*if you don't want to see "working..." on buttons, replace the following
|
||||||
* two lines with this one
|
* two lines with this one
|
||||||
* el.data('w2p_disable_with', el[method]());
|
* el.data('w2p_disable_with', el[method]());
|
||||||
@@ -633,7 +635,9 @@
|
|||||||
if(disable_with == undefined) {
|
if(disable_with == undefined) {
|
||||||
element.data('w2p_disable_with', element[method]())
|
element.data('w2p_disable_with', element[method]())
|
||||||
}
|
}
|
||||||
element.data('w2p:enable-with', element[method]());
|
if (element.data('w2p:enable-with') === undefined) {
|
||||||
|
element.data('w2p:enable-with', element[method]());
|
||||||
|
}
|
||||||
element[method](element.data('w2p_disable_with'));
|
element[method](element.data('w2p_disable_with'));
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
@@ -647,7 +651,10 @@
|
|||||||
form.find(web2py.enableSelector).each(function () {
|
form.find(web2py.enableSelector).each(function () {
|
||||||
var element = $(this),
|
var element = $(this),
|
||||||
method = element.is('button') ? 'html' : 'val';
|
method = element.is('button') ? 'html' : 'val';
|
||||||
if(element.data('w2p:enable-with')) element[method](element.data('w2p:enable-with'));
|
if(element.data('w2p:enable-with')) {
|
||||||
|
element[method](element.data('w2p:enable-with'));
|
||||||
|
element.removeData('w2p:enable-with');
|
||||||
|
}
|
||||||
element.prop('disabled', false);
|
element.prop('disabled', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -659,7 +666,18 @@
|
|||||||
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
||||||
web2py.enableFormElements($(this));
|
web2py.enableFormElements($(this));
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
/* Invalidate and force reload of a web2py component
|
||||||
|
*/
|
||||||
|
invalidate: function(target) {
|
||||||
|
$('div[data-w2p_remote]', target).each(function () {
|
||||||
|
var el = $('#' + $(this).attr('id')).get(0);
|
||||||
|
if (el.timing !== undefined) { // Block triggering regular routines
|
||||||
|
clearInterval(el.timing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$.web2py.component_handler(target);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/*end of functions */
|
/*end of functions */
|
||||||
@@ -679,7 +697,7 @@
|
|||||||
/* compatibility code - start */
|
/* compatibility code - start */
|
||||||
ajax = jQuery.web2py.ajax;
|
ajax = jQuery.web2py.ajax;
|
||||||
web2py_component = jQuery.web2py.component;
|
web2py_component = jQuery.web2py.component;
|
||||||
web2py_websocket = jQuery.web2py.websocket;
|
web2py_websocket = jQuery.web2py.web2py_websocket;
|
||||||
web2py_ajax_page = jQuery.web2py.ajax_page;
|
web2py_ajax_page = jQuery.web2py.ajax_page;
|
||||||
/*needed for IS_STRONG(entropy)*/
|
/*needed for IS_STRONG(entropy)*/
|
||||||
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
||||||
|
|||||||
@@ -1,4 +1,32 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
|
{{
|
||||||
|
dirs=[{'name':'models', 'reg':'.*\.py$'},
|
||||||
|
{'name':'controllers', 'reg':'.*\.py$'},
|
||||||
|
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
||||||
|
{'name':'modules', 'reg':'.*\.py$'},
|
||||||
|
{'name':'static', 'reg': '[^\.#].*'}]
|
||||||
|
|
||||||
|
def file_create_form(location, anchor=None, helptext=""):
|
||||||
|
form=FORM(
|
||||||
|
LABEL(T("create file with filename:")),
|
||||||
|
SELECT(_name='dir', _style='width:100px;',
|
||||||
|
*[OPTION(dir['name'], _value=dir['name']) for dir in dirs]),
|
||||||
|
XML(' '),LABEL('/', _style='display:inline-block;'),XML(' '),
|
||||||
|
INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY(),_class=''),
|
||||||
|
TAG['SMALL'](helptext,_class="help-block"),
|
||||||
|
INPUT(_type='submit', name=T('filename'), _value=T('Create'), _style='display:block', _id='btn_file_create'),
|
||||||
|
INPUT(_type="hidden",_name="editor"),
|
||||||
|
INPUT(_type="hidden",_name="location",_value=location),
|
||||||
|
INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)),
|
||||||
|
INPUT(_type="hidden",_name="token",_value=session.token),
|
||||||
|
#INPUT(_type="hidden",_name="id",_value=anchor),
|
||||||
|
_action=URL('create_file'),
|
||||||
|
_id='file_create_form',
|
||||||
|
_class="generatedbyw2p well well-small")
|
||||||
|
return form
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
<!-- begin "edit" block -->
|
<!-- begin "edit" block -->
|
||||||
{{
|
{{
|
||||||
def shortcut(combo, description):
|
def shortcut(combo, description):
|
||||||
@@ -45,6 +73,7 @@
|
|||||||
<script src="{{=cm}}/addon/fold/xml-fold.js"></script>
|
<script src="{{=cm}}/addon/fold/xml-fold.js"></script>
|
||||||
<script src="{{=cm}}/addon/fold/comment-fold.js"></script>
|
<script src="{{=cm}}/addon/fold/comment-fold.js"></script>
|
||||||
<script src="{{=cm}}/addon/fold/indent-fold.js"></script>
|
<script src="{{=cm}}/addon/fold/indent-fold.js"></script>
|
||||||
|
<script src="{{=cm}}/addon/comment/comment.js"></script>
|
||||||
<link rel="stylesheet" href="{{=cm}}/addon/fold/foldgutter.css">
|
<link rel="stylesheet" href="{{=cm}}/addon/fold/foldgutter.css">
|
||||||
<script src="{{=cm}}/emmet.min.js"></script>
|
<script src="{{=cm}}/emmet.min.js"></script>
|
||||||
<script src="{{=js_url}}/ajax_editor.js"></script>
|
<script src="{{=js_url}}/ajax_editor.js"></script>
|
||||||
@@ -52,12 +81,17 @@
|
|||||||
<link rel="stylesheet" href="{{=css_url}}/web2py-codemirror.css">
|
<link rel="stylesheet" href="{{=css_url}}/web2py-codemirror.css">
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var current_font_incr = 0; // Default font-size, 0 means isn't set
|
var current_font_incr = 0; // Default font-size, 0 means isn't set
|
||||||
$(document).on('shown click', 'a[data-toggle="tab"]', function (e) {
|
$(document).on('shown click', 'a[data-toggle="tab"]', function (e, lineno) {
|
||||||
var tab_id = $(this).attr('href');
|
var tab_id = $(this).attr('href');
|
||||||
var editor = $(tab_id + " textarea").data('editor');
|
var editor = $(tab_id + " textarea").data('editor');
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.setSize('100%', $(tab_id).height());
|
editor_height = $(window).height() - $(tab_id + " .well-small").offset().top - $(tab_id + " .well-small").outerHeight(true) - $('.navbar-fixed-bottom').outerHeight() - 1;
|
||||||
|
editor.setSize('100%', editor_height);
|
||||||
editor.refresh();
|
editor.refresh();
|
||||||
|
if (lineno !== undefined) {
|
||||||
|
editor.setCursor(lineno);
|
||||||
|
editor.centerOnCursor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var n_li = $('#filesTab li').length;
|
var n_li = $('#filesTab li').length;
|
||||||
$.each($('#filesTab li'), function(index, element) {
|
$.each($('#filesTab li'), function(index, element) {
|
||||||
@@ -104,7 +138,8 @@ $(document).on('click', '#restore', function (e) {
|
|||||||
$(document).on('click', 'a.editor_filelink, a#editor_settingslink', function (e) {
|
$(document).on('click', 'a.editor_filelink, a#editor_settingslink', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var url = $(this).attr("href");
|
var url = $(this).attr("href");
|
||||||
load_file(url);
|
var lineno = $(this).data("lineno");
|
||||||
|
load_file(url, lineno);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* This method updates all editors already instantiated with the selected preferences*/
|
/* This method updates all editors already instantiated with the selected preferences*/
|
||||||
@@ -164,7 +199,7 @@ $(document).on('click', 'a.font_button', function (e) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
{{if len(request.args) > 1:}}
|
{{if len(request.args) > 1:}}
|
||||||
load_file('{{=URL(f='edit', args=request.args, vars=request.get_vars)}}');
|
load_file('{{=URL(f='edit', args=request.args, vars=request.get_vars)}}', {{=request.vars.lineno or 1}});
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -197,27 +232,22 @@ $(document).on('click', 'a.font_button', function (e) {
|
|||||||
<div class="files-menu">
|
<div class="files-menu">
|
||||||
<ul class="nav nav-list" rel="pagebookmark" id="filelist">
|
<ul class="nav nav-list" rel="pagebookmark" id="filelist">
|
||||||
<li><input type="text" placeholder="{{=T('Rapid Search')}}" class="input-block-level typeahead-tw search-query"></li>
|
<li><input type="text" placeholder="{{=T('Rapid Search')}}" class="input-block-level typeahead-tw search-query"></li>
|
||||||
{{dirs=[{'name':'models', 'reg':'.*\.py$'},
|
|
||||||
{'name':'controllers', 'reg':'.*\.py$'},
|
|
||||||
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
|
||||||
{'name':'modules', 'reg':'.*\.py$'}, ]}}
|
|
||||||
{{auto_complete_list=[]}}
|
{{auto_complete_list=[]}}
|
||||||
{{for dir in dirs:}}
|
<br />
|
||||||
<li class="nav-header component" onclick="collapse('{{="%s_files" % dir['name']}}');">{{=dir['name']}}</li>
|
<div>
|
||||||
<li id="{{="%s_files" % dir['name']}}">
|
<button onclick="jQuery('#form').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
|
||||||
<ul class="nav nav-list small-font">
|
<br />
|
||||||
{{for f in listfiles(app, dir['name'], regexp=dir['reg'] ):}}
|
<div id="form">
|
||||||
{{id="%s__" % dir['name'] + f.replace('.','__')}}
|
<div>
|
||||||
{{current_file = request.args(len(request.args) - 1)}}
|
{{=file_create_form('%s/' % app, '')}}
|
||||||
<li class="{{#='active' if current_file==f else ''}}" style="overflow:hidden">
|
</div>
|
||||||
{{a_tag=editfile(dir['name'], f, dict(id=id))}}
|
</div>
|
||||||
{{=a_tag}}
|
<script>jQuery('#form').slideToggle()</script>
|
||||||
{{auto_complete_list.append({'value':f, 'tokens':f.split('/'), 'a_tag':a_tag})}}
|
</div>
|
||||||
</li>
|
<br />
|
||||||
{{pass}}
|
<div id="files_menu">
|
||||||
</ul>
|
{{=LOAD('default', 'files_menu', vars={'app':app}, ajax=True)}}
|
||||||
</li>
|
</div>
|
||||||
{{pass}}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,13 +257,47 @@ $(document).on('click', 'a.font_button', function (e) {
|
|||||||
<div id="myTabContent" class="tab-content">
|
<div id="myTabContent" class="tab-content">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<section id="windows_divs" class="tab-content ">
|
||||||
|
<div id="window_todo" class="tab-pane container-fluid">
|
||||||
|
{{=LOAD('default', 'todolist.load', vars={'app':app}, ajax=True, timeout=60000, times="infinity")}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
{{block footer}}
|
||||||
|
<div id="" class="navbar navbar-inverse navbar-fixed-bottom">
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<ul id="windows_hooks" class="nav">
|
||||||
|
<li class="">
|
||||||
|
<a href="#window_todo">TODO</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).on('click', '#window_todo li a', function (e) {
|
||||||
|
$(this).find('i').toggleClass('icon-chevron-right');
|
||||||
|
$(this).find('i').toggleClass('icon-chevron-down');
|
||||||
|
});
|
||||||
|
$('#windows_hooks li a').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if ( $(this).parent('li').hasClass('active') ) {
|
||||||
|
$(this).parent('li').removeClass('active');
|
||||||
|
$($(this).attr('href')).removeClass('active');
|
||||||
|
} else {
|
||||||
|
$(this).tab('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var filesMenu = $('#files');
|
var filesMenu = $('#files');
|
||||||
var ow = filesMenu.outerWidth();
|
var ow = filesMenu.outerWidth();
|
||||||
filesMenu.width(ow);
|
filesMenu.width(ow);
|
||||||
$('#files').css('left', '-'+ow+'px');
|
$('#files').css('left', '-'+ow+'px');
|
||||||
|
$.web2py.trap_form('url', 'form');
|
||||||
|
$('#form form').addClass('no_trap'); // Let to reuse the same form
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<!-- Typeahead scripts here so the page load faster -->
|
<!-- Typeahead scripts here so the page load faster -->
|
||||||
|
|||||||
@@ -74,15 +74,15 @@
|
|||||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||||
URL(c='debug', f='toggle_breakpoint')))}}, sel);
|
URL(c='debug', f='toggle_breakpoint')))}}, sel);
|
||||||
});
|
});
|
||||||
function makeMarker() {
|
function makeMarker() {
|
||||||
var marker = document.createElement("div");
|
var marker = document.createElement("div");
|
||||||
marker.style.color = "#822";
|
marker.style.color = "#822";
|
||||||
marker.innerHTML = "●";
|
marker.innerHTML = "●";
|
||||||
marker.className = "breakpoint";
|
marker.className = "breakpoint";
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
{{if filetype in ('html', 'javascript', 'css'):}}
|
{{if filetype in ('html', 'javascript', 'css'):}}
|
||||||
// must be here or break emmet/zencoding
|
// must be here or break emmet/zencoding
|
||||||
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
||||||
function(instance) {
|
function(instance) {
|
||||||
@@ -94,26 +94,27 @@
|
|||||||
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
||||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||||
}
|
}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{if filetype=='python':}}
|
{{if filetype=='python':}}
|
||||||
// must be here or break emmet/zencoding for python
|
// must be here or break emmet/zencoding for python
|
||||||
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
||||||
function(instance) {
|
function(instance) {
|
||||||
doClickSave();};
|
doClickSave();};
|
||||||
CodeMirror.defaults.extraKeys["Ctrl-Space"] = "autocomplete";
|
CodeMirror.defaults.extraKeys["Ctrl-Space"] = "autocomplete";
|
||||||
CodeMirror.defaults.extraKeys["Tab"] = "indentMore";
|
CodeMirror.defaults.extraKeys["Tab"] = "indentMore";
|
||||||
CodeMirror.defaults.extraKeys["Shift-Tab"] = "indentLess";
|
CodeMirror.defaults.extraKeys["Shift-Tab"] = "indentLess";
|
||||||
CodeMirror.defaults.extraKeys["Ctrl-F11"] = function(cm) {
|
CodeMirror.defaults.extraKeys["Ctrl-F11"] = function(cm) {
|
||||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||||
},
|
},
|
||||||
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
||||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||||
}
|
}
|
||||||
//for autocomplete
|
//for autocomplete
|
||||||
CodeMirror.commands.autocomplete = function(cm) {
|
CodeMirror.commands.autocomplete = function(cm) {
|
||||||
CodeMirror.showHint(cm, CodeMirror.pythonHint);
|
CodeMirror.showHint(cm, CodeMirror.pythonHint);
|
||||||
}
|
}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
CodeMirror.defaults.extraKeys["Ctrl-/"] = "toggleComment";
|
||||||
store_changes_function = function(instance, changeObj) {
|
store_changes_function = function(instance, changeObj) {
|
||||||
jQuery(instance).data('saved', false);
|
jQuery(instance).data('saved', false);
|
||||||
instance.off("change", store_changes_function);
|
instance.off("change", store_changes_function);
|
||||||
@@ -128,6 +129,19 @@
|
|||||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||||
URL(c='debug', f='list_breakpoints')))}}, editor);
|
URL(c='debug', f='list_breakpoints')))}}, editor);
|
||||||
|
|
||||||
|
// TODO move it in a separated file
|
||||||
|
CodeMirror.defineExtension("centerOnCursor", function(limit) {
|
||||||
|
var coords = this.cursorCoords(null, "local");
|
||||||
|
if (this.getScrollerElement().clientHeight === 0 && limit !== 10) {
|
||||||
|
if (limit === undefined) limit = 1;
|
||||||
|
else limit += 1;
|
||||||
|
editor = this;
|
||||||
|
setTimeout(function() {editor.centerOnCursor()}, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clientHeight = (this.getScrollerElement().clientHeight / 2)
|
||||||
|
this.scrollTo(null, (coords.top + coords.bottom)/2 - 10);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="editor-bar-bottom" style="margin-top:9px;">
|
<div class="editor-bar-bottom" style="margin-top:9px;">
|
||||||
@@ -141,34 +155,26 @@
|
|||||||
|
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="help alert alert-block alert-info">
|
<div class="help alert alert-block alert-info">
|
||||||
{{if filetype=='html':}}
|
{{if filetype=='html':}}
|
||||||
<h3>{{=T('Key bindings for ZenCoding Plugin')}}</h3>
|
<h3>{{=T('Key bindings for ZenCoding Plugin')}}</h3>
|
||||||
<ul class="keybindings unstyled">
|
{{else:}}
|
||||||
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
|
|
||||||
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
|
|
||||||
{{=shortcut('Shift+Esc', T('Exit Fullscreen'))}}
|
|
||||||
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
|
|
||||||
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
|
|
||||||
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
|
|
||||||
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
|
|
||||||
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
|
|
||||||
{{=shortcut('Tab', T('Expand Abbreviation'))}}
|
|
||||||
</ul>
|
|
||||||
{{else:}}
|
|
||||||
<h3>{{=T("Key bindings")}}</h3>
|
<h3>{{=T("Key bindings")}}</h3>
|
||||||
|
{{pass}}
|
||||||
<ul class="keybindings unstyled">
|
<ul class="keybindings unstyled">
|
||||||
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
|
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
|
||||||
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
|
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
|
||||||
{{=shortcut('Shift+Esc', T('Exit Fullscreen'))}}
|
{{=shortcut('Shift+Esc', T('Exit Fullscreen'))}}
|
||||||
{{if filetype=='python':}}
|
|
||||||
{{=shortcut('Ctrl-Space', T('Autocomplete Python Code'))}}
|
|
||||||
{{pass}}
|
|
||||||
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
|
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
|
||||||
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
|
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
|
||||||
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
|
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
|
||||||
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
|
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
|
||||||
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
|
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
|
||||||
</ul>
|
{{=shortcut('Ctrl-/ ', T('Toggle comment'))}}
|
||||||
|
{{if filetype=='html':}}
|
||||||
|
{{=shortcut('Tab', T('Expand Abbreviation'))}}
|
||||||
|
{{elif filetype=='python':}}
|
||||||
|
{{=shortcut('Ctrl-Space', T('Autocomplete Python Code'))}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{{for files in result_files:}}
|
||||||
|
{{=files}}
|
||||||
|
{{pass}}
|
||||||
@@ -1,5 +1,21 @@
|
|||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
{{=H3("Available plugins")}}
|
{{=H3("Available plugins")}}
|
||||||
{{=P("Source: web2pyslices")}}
|
{{=P("Source: web2pyslices")}}
|
||||||
{{=plugins}}
|
{{articles = []}}
|
||||||
|
{{for article in plugins:}}
|
||||||
|
{{screenshots = [A(" ", T("Screenshot %s") % (x+1), " ", _href=item) for (x, item) in enumerate(article["package_data"]["screenshots"])]}}
|
||||||
|
{{articles.append(TR(TD(H5(article["article"]["title"]),
|
||||||
|
button(URL(c="default", f="install_plugin", args=[app,], vars={"source": article["package_data"]["download"], "plugin": article["article"]["title"]}), T("Install")),
|
||||||
|
BR(),
|
||||||
|
IMG(_src="http://www.web2pyslices.com/download/%s" % article["article"]["thumbnail"], _style="margin-top: 1em;"), _style="width: 20em;"),
|
||||||
|
TD(article["article"]["description"], BR(),
|
||||||
|
A(T("Plugin page"), _href="http://www.web2pyslices.com/slice/show/%s/" % article["article"]["id"]), " | ",
|
||||||
|
A(T("Demo"), _href=article["package_data"]["demo"]), " | ",
|
||||||
|
A(T("Docs"), _href=article["package_data"]["documentation"]), " | ",
|
||||||
|
A(T("Repository (%s)") % article["package_data"]["repository_brand"], _href=article["package_data"]["repository_page"]), " | ",
|
||||||
|
A(T("License:"), " ", (article["package_data"]["license_type"] or "").upper(), " ", T("(version %s)") % article["package_data"]["license_version"] if article["package_data"]["license_version"] else "", _href=article["package_data"]["license_url"]), " | " if screenshots else "", *screenshots, _style="width: 40em;"))
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
{{pass}}
|
||||||
|
{{=TABLE(*articles)}}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<div>
|
||||||
|
<!--div class="page-header"-->
|
||||||
|
{{n_todo=reduce(lambda x,y: x + len(y['matches']), todo, 0)}}
|
||||||
|
<h4>{{="%s TODO in %s" % (n_todo, app)}}</h4>
|
||||||
|
<!--/div-->
|
||||||
|
<ul class="nav nav-list cm-s-web2py">
|
||||||
|
{{for pos_file, file in enumerate(todo):}}
|
||||||
|
<li onclick="collapse('{{=pos_file}}_matches');"><a ><i class='icon-chevron-right'></i>{{=file['filename']}} <span class='small'>({{=len(file['matches'])}} TODO)</span></a></li>
|
||||||
|
<li id="{{=pos_file}}_matches" style="display: none;"><ul class="nav nav-list">
|
||||||
|
{{for m in file['matches']:}}
|
||||||
|
<li><a href="{{=URL('edit', args=[app,file['dir'], file['filename']], vars={'lineno':m['lineno']}, extension="")}}" data-lineno="{{=m['lineno']-1}}" class="editor_filelink">
|
||||||
|
<span class='cm-comment'>{{=m['text']}}</span>
|
||||||
|
</a></li>
|
||||||
|
{{pass}}
|
||||||
|
</ul></li>
|
||||||
|
{{pass}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
|
|
||||||
<!-- FOOTER
|
<!-- FOOTER
|
||||||
============== -->
|
============== -->
|
||||||
|
{{block footer}}
|
||||||
<footer id="footer" class="fixed">
|
<footer id="footer" class="fixed">
|
||||||
<p><span>{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}™ {{=T('created by')}} Massimo Di Pierro ©2007-{{=request.now.year}}
|
<p><span>{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}™ {{=T('created by')}} Massimo Di Pierro ©2007-{{=request.now.year}}
|
||||||
{{if hasattr(T,'get_possible_languages_info'):}}
|
{{if hasattr(T,'get_possible_languages_info'):}}
|
||||||
@@ -68,6 +69,7 @@
|
|||||||
</span>{{pass}}
|
</span>{{pass}}
|
||||||
</p>
|
</p>
|
||||||
</footer><!-- /#footer -->
|
</footer><!-- /#footer -->
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- BS JAVASCRIPT
|
<!-- BS JAVASCRIPT
|
||||||
====================== -->
|
====================== -->
|
||||||
|
|||||||
@@ -2,40 +2,41 @@
|
|||||||
{{block sectionclass}}shell{{end}}
|
{{block sectionclass}}shell{{end}}
|
||||||
<!-- begin "shell" block -->
|
<!-- begin "shell" block -->
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="output-wrapper span8">
|
<div class="output-wrapper span8">
|
||||||
<textarea id="output" readonly="readonly">web2py Shell {{=request.env.web2py_version}}</textarea>
|
<textarea id="output" readonly="readonly">web2py Shell {{=request.env.web2py_version}}
|
||||||
</div>
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<form id="form" action="{{=URL(r=request,f='callback',args=app)}}" method="get" class="span8">
|
<form id="form" action="{{=URL(r=request,f='callback',args=app)}}" method="get" class="span8">
|
||||||
<div id="shellwrapper">
|
<div id="shellwrapper">
|
||||||
<div class="prompt-wrapper">
|
<div class="prompt-wrapper">
|
||||||
<div class="prompt-container">
|
<div class="prompt-container">
|
||||||
<textarea class="prompt" name="statement" id="statement"></textarea>
|
<textarea class="prompt" name="statement" id="statement"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" rel="tooltip" data-placement="right" data-original-title="{{=T('Type some Python code in here and hit Return (Enter) to execute it.')}}">
|
<a href="#" rel="tooltip" data-placement="right" data-original-title="{{=T('Type some Python code in here and hit Return (Enter) to execute it.')}}">
|
||||||
{{=helpicon()}}
|
{{=helpicon()}}
|
||||||
<span>Type some Python code in here and hit Return (Enter) to execute it.</span>
|
<span>Type some Python code in here and hit Return (Enter) to execute it.</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<div id="caret"><span>>>></span></div>
|
|
||||||
<div id="autoscroll">autoscroll</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="caret"><span>>>></span></div>
|
||||||
|
<div id="autoscroll">autoscroll</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row-fluid clearfix">
|
<div class="row-fluid clearfix">
|
||||||
<div class="help alert alert-info span6">
|
<div class="help alert alert-info span6">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Using the shell may lock the database to other users of this app.</li>
|
<li>Using the shell may lock the database to other users of this app.</li>
|
||||||
<li>Each db statement is automatically committed.</li>
|
<li>Each db statement is automatically committed.</li>
|
||||||
<li>Creating new tables dynamically is not allowed.</li>
|
<li>Creating new tables dynamically is not allowed.</li>
|
||||||
<li>Models are automatically imported in the shell.</li>
|
<li>Models are automatically imported in the shell.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="{{=URL('static', 'js/autoscroll.js')}}"></script>
|
<script type="text/javascript" src="{{=URL('static', 'js/autoscroll.js')}}"></script>
|
||||||
|
|
||||||
|
|||||||
@@ -485,8 +485,10 @@
|
|||||||
el.addClass('disabled');
|
el.addClass('disabled');
|
||||||
var method = el.prop('type') == 'submit' ? 'val' : 'html';
|
var method = el.prop('type') == 'submit' ? 'val' : 'html';
|
||||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||||
/*store enabled state*/
|
/*store enabled state if not already disabled */
|
||||||
el.data('w2p:enable-with', el[method]());
|
if (el.data('w2p:enable-with') === undefined) {
|
||||||
|
el.data('w2p:enable-with', el[method]());
|
||||||
|
}
|
||||||
/*if you don't want to see "working..." on buttons, replace the following
|
/*if you don't want to see "working..." on buttons, replace the following
|
||||||
* two lines with this one
|
* two lines with this one
|
||||||
* el.data('w2p_disable_with', el[method]());
|
* el.data('w2p_disable_with', el[method]());
|
||||||
@@ -633,7 +635,9 @@
|
|||||||
if(disable_with == undefined) {
|
if(disable_with == undefined) {
|
||||||
element.data('w2p_disable_with', element[method]())
|
element.data('w2p_disable_with', element[method]())
|
||||||
}
|
}
|
||||||
element.data('w2p:enable-with', element[method]());
|
if (element.data('w2p:enable-with') === undefined) {
|
||||||
|
element.data('w2p:enable-with', element[method]());
|
||||||
|
}
|
||||||
element[method](element.data('w2p_disable_with'));
|
element[method](element.data('w2p_disable_with'));
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
@@ -647,7 +651,10 @@
|
|||||||
form.find(web2py.enableSelector).each(function () {
|
form.find(web2py.enableSelector).each(function () {
|
||||||
var element = $(this),
|
var element = $(this),
|
||||||
method = element.is('button') ? 'html' : 'val';
|
method = element.is('button') ? 'html' : 'val';
|
||||||
if(element.data('w2p:enable-with')) element[method](element.data('w2p:enable-with'));
|
if(element.data('w2p:enable-with')) {
|
||||||
|
element[method](element.data('w2p:enable-with'));
|
||||||
|
element.removeData('w2p:enable-with');
|
||||||
|
}
|
||||||
element.prop('disabled', false);
|
element.prop('disabled', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -659,7 +666,18 @@
|
|||||||
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
||||||
web2py.enableFormElements($(this));
|
web2py.enableFormElements($(this));
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
/* Invalidate and force reload of a web2py component
|
||||||
|
*/
|
||||||
|
invalidate: function(target) {
|
||||||
|
$('div[data-w2p_remote]', target).each(function () {
|
||||||
|
var el = $('#' + $(this).attr('id')).get(0);
|
||||||
|
if (el.timing !== undefined) { // Block triggering regular routines
|
||||||
|
clearInterval(el.timing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$.web2py.component_handler(target);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/*end of functions */
|
/*end of functions */
|
||||||
@@ -679,7 +697,7 @@
|
|||||||
/* compatibility code - start */
|
/* compatibility code - start */
|
||||||
ajax = jQuery.web2py.ajax;
|
ajax = jQuery.web2py.ajax;
|
||||||
web2py_component = jQuery.web2py.component;
|
web2py_component = jQuery.web2py.component;
|
||||||
web2py_websocket = jQuery.web2py.websocket;
|
web2py_websocket = jQuery.web2py.web2py_websocket;
|
||||||
web2py_ajax_page = jQuery.web2py.ajax_page;
|
web2py_ajax_page = jQuery.web2py.ajax_page;
|
||||||
/*needed for IS_STRONG(entropy)*/
|
/*needed for IS_STRONG(entropy)*/
|
||||||
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ random.shuffle(quotes)
|
|||||||
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/>
|
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/>
|
||||||
<a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/>
|
<a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/>
|
||||||
<a class="btn btn-danger" href="https://www.pythonanywhere.com/try-web2py" style="margin-top:10px; width:180px; color:white">Try it now online</a><br/>
|
<a class="btn btn-danger" href="https://www.pythonanywhere.com/try-web2py" style="margin-top:10px; width:180px; color:white">Try it now online</a><br/>
|
||||||
<a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powered by web2py</a>
|
<a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powered by web2py</a><br/><br/>
|
||||||
|
<a class="coinbase-button" data-code="df71ec5c2d5bc3b1c18139ab645f352b" data-button-style="donation_large" href="https://coinbase.com/checkouts/df71ec5c2d5bc3b1c18139ab645f352b">Donate Bitcoins</a><script src="https://coinbase.com/assets/button.js" type="text/javascript"></script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -485,8 +485,10 @@
|
|||||||
el.addClass('disabled');
|
el.addClass('disabled');
|
||||||
var method = el.prop('type') == 'submit' ? 'val' : 'html';
|
var method = el.prop('type') == 'submit' ? 'val' : 'html';
|
||||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||||
/*store enabled state*/
|
/*store enabled state if not already disabled */
|
||||||
el.data('w2p:enable-with', el[method]());
|
if (el.data('w2p:enable-with') === undefined) {
|
||||||
|
el.data('w2p:enable-with', el[method]());
|
||||||
|
}
|
||||||
/*if you don't want to see "working..." on buttons, replace the following
|
/*if you don't want to see "working..." on buttons, replace the following
|
||||||
* two lines with this one
|
* two lines with this one
|
||||||
* el.data('w2p_disable_with', el[method]());
|
* el.data('w2p_disable_with', el[method]());
|
||||||
@@ -633,7 +635,9 @@
|
|||||||
if(disable_with == undefined) {
|
if(disable_with == undefined) {
|
||||||
element.data('w2p_disable_with', element[method]())
|
element.data('w2p_disable_with', element[method]())
|
||||||
}
|
}
|
||||||
element.data('w2p:enable-with', element[method]());
|
if (element.data('w2p:enable-with') === undefined) {
|
||||||
|
element.data('w2p:enable-with', element[method]());
|
||||||
|
}
|
||||||
element[method](element.data('w2p_disable_with'));
|
element[method](element.data('w2p_disable_with'));
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
@@ -647,7 +651,10 @@
|
|||||||
form.find(web2py.enableSelector).each(function () {
|
form.find(web2py.enableSelector).each(function () {
|
||||||
var element = $(this),
|
var element = $(this),
|
||||||
method = element.is('button') ? 'html' : 'val';
|
method = element.is('button') ? 'html' : 'val';
|
||||||
if(element.data('w2p:enable-with')) element[method](element.data('w2p:enable-with'));
|
if(element.data('w2p:enable-with')) {
|
||||||
|
element[method](element.data('w2p:enable-with'));
|
||||||
|
element.removeData('w2p:enable-with');
|
||||||
|
}
|
||||||
element.prop('disabled', false);
|
element.prop('disabled', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -659,7 +666,18 @@
|
|||||||
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
||||||
web2py.enableFormElements($(this));
|
web2py.enableFormElements($(this));
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
/* Invalidate and force reload of a web2py component
|
||||||
|
*/
|
||||||
|
invalidate: function(target) {
|
||||||
|
$('div[data-w2p_remote]', target).each(function () {
|
||||||
|
var el = $('#' + $(this).attr('id')).get(0);
|
||||||
|
if (el.timing !== undefined) { // Block triggering regular routines
|
||||||
|
clearInterval(el.timing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$.web2py.component_handler(target);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/*end of functions */
|
/*end of functions */
|
||||||
@@ -679,7 +697,7 @@
|
|||||||
/* compatibility code - start */
|
/* compatibility code - start */
|
||||||
ajax = jQuery.web2py.ajax;
|
ajax = jQuery.web2py.ajax;
|
||||||
web2py_component = jQuery.web2py.component;
|
web2py_component = jQuery.web2py.component;
|
||||||
web2py_websocket = jQuery.web2py.websocket;
|
web2py_websocket = jQuery.web2py.web2py_websocket;
|
||||||
web2py_ajax_page = jQuery.web2py.ajax_page;
|
web2py_ajax_page = jQuery.web2py.ajax_page;
|
||||||
/*needed for IS_STRONG(entropy)*/
|
/*needed for IS_STRONG(entropy)*/
|
||||||
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ skip_files: |
|
|||||||
(.*\.py[co])|
|
(.*\.py[co])|
|
||||||
(.*/RCS/.*)|
|
(.*/RCS/.*)|
|
||||||
(\..*)|
|
(\..*)|
|
||||||
(applications/(admin|examples)/.*)|
|
(applications/examples/.*)|
|
||||||
((admin|examples|welcome)\.(w2p|tar))|
|
((examples|welcome)\.(w2p|tar))|
|
||||||
(applications/.*?/(cron|databases|errors|cache|sessions)/.*)|
|
(applications/.*?/(cron|databases|errors|cache|sessions)/.*)|
|
||||||
((logs|scripts)/.*)|
|
((logs|scripts)/.*)|
|
||||||
(anyserver\.py)|
|
(anyserver\.py)|
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ def extension(url):
|
|||||||
|
|
||||||
def expand_one(url, cdict):
|
def expand_one(url, cdict):
|
||||||
# try ombed but first check in cache
|
# try ombed but first check in cache
|
||||||
|
if '@' in url and not '://'in url:
|
||||||
|
return '<a href="mailto:%s">%s</a>' % (url, url)
|
||||||
if cdict and url in cdict:
|
if cdict and url in cdict:
|
||||||
r = cdict[url]
|
r = cdict[url]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
|
|||||||
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
|
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
|
||||||
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
|
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
|
||||||
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
|
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
|
||||||
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w)',re.M)
|
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)',re.M)
|
||||||
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
|
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
|
||||||
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
|
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
|
||||||
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
|
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
|
||||||
@@ -648,7 +648,9 @@ def autolinks_simple(url):
|
|||||||
image, video or audio tag
|
image, video or audio tag
|
||||||
"""
|
"""
|
||||||
u_url=url.lower()
|
u_url=url.lower()
|
||||||
if u_url.endswith(('.jpg','.jpeg','.gif','.png')):
|
if '@' in url and not '://' in url:
|
||||||
|
return '<a href="mailto:%s">%s</a>' % (url, url)
|
||||||
|
elif u_url.endswith(('.jpg','.jpeg','.gif','.png')):
|
||||||
return '<img src="%s" controls />' % url
|
return '<img src="%s" controls />' % url
|
||||||
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
|
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
|
||||||
return '<video src="%s" controls></video>' % url
|
return '<video src="%s" controls></video>' % url
|
||||||
@@ -673,6 +675,9 @@ def protolinks_simple(proto, url):
|
|||||||
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />'%url
|
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />'%url
|
||||||
return proto+':'+url
|
return proto+':'+url
|
||||||
|
|
||||||
|
def email_simple(email):
|
||||||
|
return '<a href="mailto:%s">%s</a>' % (email, email)
|
||||||
|
|
||||||
def render(text,
|
def render(text,
|
||||||
extra={},
|
extra={},
|
||||||
allowed={},
|
allowed={},
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ def run(history, statement, env={}):
|
|||||||
if not name.startswith('__'):
|
if not name.startswith('__'):
|
||||||
try:
|
try:
|
||||||
history.set_global(name, val)
|
history.set_global(name, val)
|
||||||
except TypeError, ex:
|
except (TypeError, cPickle.PicklingError), ex:
|
||||||
UNPICKLABLE_TYPES.append(type(val))
|
UNPICKLABLE_TYPES.append(type(val))
|
||||||
history.add_unpicklable(statement, new_globals.keys())
|
history.add_unpicklable(statement, new_globals.keys())
|
||||||
|
|
||||||
|
|||||||
@@ -858,8 +858,8 @@ class Sheet:
|
|||||||
sorted_headers = [TH(), ] + \
|
sorted_headers = [TH(), ] + \
|
||||||
[TH(header[1]) for header in sorted(unsorted_headers)]
|
[TH(header[1]) for header in sorted(unsorted_headers)]
|
||||||
table.insert(0, TR(*sorted_headers,
|
table.insert(0, TR(*sorted_headers,
|
||||||
**{_class: "%s_fieldnames" %
|
**{'_class': "%s_fieldnames" %
|
||||||
attributes["_class"]}))
|
attributes["_class"]}))
|
||||||
else:
|
else:
|
||||||
data = SCRIPT(""" // web2py Spreadsheets: no db data.""")
|
data = SCRIPT(""" // web2py Spreadsheets: no db data.""")
|
||||||
|
|
||||||
|
|||||||
+302
-162
@@ -252,7 +252,8 @@ THREAD_LOCAL = threading.local()
|
|||||||
REGEX_TYPE = re.compile('^([\w\_\:]+)')
|
REGEX_TYPE = re.compile('^([\w\_\:]+)')
|
||||||
REGEX_DBNAME = re.compile('^(\w+)(\:\w+)*')
|
REGEX_DBNAME = re.compile('^(\w+)(\:\w+)*')
|
||||||
REGEX_W = re.compile('^\w+$')
|
REGEX_W = re.compile('^\w+$')
|
||||||
REGEX_TABLE_DOT_FIELD = re.compile('^(\w+)\.(\w+)$')
|
REGEX_TABLE_DOT_FIELD = re.compile('^(\w+)\.([^.]+)$')
|
||||||
|
REGEX_NO_GREEDY_ENTITY_NAME = r'(.+?)'
|
||||||
REGEX_UPLOAD_PATTERN = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)(\.(?P<name>\w+))?\.\w+$')
|
REGEX_UPLOAD_PATTERN = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)(\.(?P<name>\w+))?\.\w+$')
|
||||||
REGEX_CLEANUP_FN = re.compile('[\'"\s;]+')
|
REGEX_CLEANUP_FN = re.compile('[\'"\s;]+')
|
||||||
REGEX_UNPACK = re.compile('(?<!\|)\|(?!\|)')
|
REGEX_UNPACK = re.compile('(?<!\|)\|(?!\|)')
|
||||||
@@ -670,12 +671,27 @@ class ConnectionPool(object):
|
|||||||
break
|
break
|
||||||
self.after_connection_hook()
|
self.after_connection_hook()
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# metaclass to prepare adapter classes static values
|
||||||
|
###################################################################################
|
||||||
|
class AdapterMeta(type):
|
||||||
|
def __new__(cls, clsname, bases, dct):
|
||||||
|
classobj = super(AdapterMeta, cls).__new__(cls, clsname, bases, dct)
|
||||||
|
classobj.REGEX_TABLE_DOT_FIELD = re.compile(r'^' + \
|
||||||
|
classobj.QUOTE_TEMPLATE % REGEX_NO_GREEDY_ENTITY_NAME + \
|
||||||
|
r'\.' + \
|
||||||
|
classobj.QUOTE_TEMPLATE % REGEX_NO_GREEDY_ENTITY_NAME + \
|
||||||
|
r'$')
|
||||||
|
return classobj
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# this is a generic adapter that does nothing; all others are derived from this one
|
# this is a generic adapter that does nothing; all others are derived from this one
|
||||||
###################################################################################
|
###################################################################################
|
||||||
|
|
||||||
class BaseAdapter(ConnectionPool):
|
class BaseAdapter(ConnectionPool):
|
||||||
|
|
||||||
|
__metaclass__ = AdapterMeta
|
||||||
|
|
||||||
native_json = False
|
native_json = False
|
||||||
driver = None
|
driver = None
|
||||||
driver_name = None
|
driver_name = None
|
||||||
@@ -687,12 +703,14 @@ class BaseAdapter(ConnectionPool):
|
|||||||
can_select_for_update = True
|
can_select_for_update = True
|
||||||
dbpath = None
|
dbpath = None
|
||||||
folder = None
|
folder = None
|
||||||
|
connector = lambda *args, **kwargs: None # __init__ should override this
|
||||||
|
|
||||||
TRUE = 'T'
|
TRUE = 'T'
|
||||||
FALSE = 'F'
|
FALSE = 'F'
|
||||||
T_SEP = ' '
|
T_SEP = ' '
|
||||||
QUOTE_TEMPLATE = '"%s"'
|
QUOTE_TEMPLATE = '"%s"'
|
||||||
|
|
||||||
|
|
||||||
types = {
|
types = {
|
||||||
'boolean': 'CHAR(1)',
|
'boolean': 'CHAR(1)',
|
||||||
'string': 'CHAR(%(length)s)',
|
'string': 'CHAR(%(length)s)',
|
||||||
@@ -717,6 +735,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
# the two below are only used when DAL(...bigint_id=True) and replace 'id','reference'
|
# the two below are only used when DAL(...bigint_id=True) and replace 'id','reference'
|
||||||
'big-id': 'BIGINT PRIMARY KEY AUTOINCREMENT',
|
'big-id': 'BIGINT PRIMARY KEY AUTOINCREMENT',
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||||
|
'reference FK': ', CONSTRAINT "FK_%(constraint_name)s" FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||||
}
|
}
|
||||||
|
|
||||||
def isOperationalError(self,exception):
|
def isOperationalError(self,exception):
|
||||||
@@ -834,8 +853,9 @@ class BaseAdapter(ConnectionPool):
|
|||||||
self.connection = Dummy()
|
self.connection = Dummy()
|
||||||
self.cursor = Dummy()
|
self.cursor = Dummy()
|
||||||
|
|
||||||
|
|
||||||
def sequence_name(self,tablename):
|
def sequence_name(self,tablename):
|
||||||
return '%s_sequence' % tablename
|
return self.QUOTE_TEMPLATE % ('%s_sequence' % tablename)
|
||||||
|
|
||||||
def trigger_name(self,tablename):
|
def trigger_name(self,tablename):
|
||||||
return '%s_sequence' % tablename
|
return '%s_sequence' % tablename
|
||||||
@@ -868,61 +888,72 @@ class BaseAdapter(ConnectionPool):
|
|||||||
if referenced == '.':
|
if referenced == '.':
|
||||||
referenced = tablename
|
referenced = tablename
|
||||||
constraint_name = self.constraint_name(tablename, field_name)
|
constraint_name = self.constraint_name(tablename, field_name)
|
||||||
if not '.' in referenced \
|
# if not '.' in referenced \
|
||||||
and referenced != tablename \
|
# and referenced != tablename \
|
||||||
and hasattr(table,'_primarykey'):
|
# and hasattr(table,'_primarykey'):
|
||||||
ftype = types['integer']
|
# ftype = types['integer']
|
||||||
else:
|
#else:
|
||||||
if hasattr(table,'_primarykey'):
|
try:
|
||||||
|
rtable = db[referenced]
|
||||||
|
rfield = rtable._id
|
||||||
|
rfieldname = rfield.name
|
||||||
|
rtablename = referenced
|
||||||
|
except (KeyError, ValueError, AttributeError), e:
|
||||||
|
LOGGER.debug('Error: %s' % e)
|
||||||
|
try:
|
||||||
rtablename,rfieldname = referenced.split('.')
|
rtablename,rfieldname = referenced.split('.')
|
||||||
rtable = db[rtablename]
|
rtable = db[rtablename]
|
||||||
rfield = rtable[rfieldname]
|
rfield = rtable[rfieldname]
|
||||||
# must be PK reference or unique
|
except Exception, e:
|
||||||
if rfieldname in rtable._primarykey or \
|
LOGGER.debug('Error: %s' %e)
|
||||||
rfield.unique:
|
raise KeyError('Cannot resolve reference %s in %s definition' % (referenced, table._tablename))
|
||||||
ftype = types[rfield.type[:9]] % \
|
|
||||||
dict(length=rfield.length)
|
|
||||||
# multicolumn primary key reference?
|
|
||||||
if not rfield.unique and len(rtable._primarykey)>1:
|
|
||||||
# then it has to be a table level FK
|
|
||||||
if rtablename not in TFK:
|
|
||||||
TFK[rtablename] = {}
|
|
||||||
TFK[rtablename][rfieldname] = field_name
|
|
||||||
else:
|
|
||||||
ftype = ftype + \
|
|
||||||
types['reference FK'] % dict(
|
|
||||||
constraint_name = constraint_name, # should be quoted
|
|
||||||
foreign_key = '%s (%s)' % (rtablename,
|
|
||||||
rfieldname),
|
|
||||||
table_name = tablename,
|
|
||||||
field_name = field._rname or field.name,
|
|
||||||
on_delete_action=field.ondelete)
|
|
||||||
else:
|
|
||||||
# make a guess here for circular references
|
|
||||||
if referenced in db:
|
|
||||||
id_fieldname = db[referenced]._id.name
|
|
||||||
elif referenced == tablename:
|
|
||||||
id_fieldname = table._id.name
|
|
||||||
else: #make a guess
|
|
||||||
id_fieldname = 'id'
|
|
||||||
#gotcha: the referenced table must be defined before
|
|
||||||
#the referencing one to be able to create the table
|
|
||||||
#Also if it's not recommended, we can still support
|
|
||||||
#references to tablenames without rname to make
|
|
||||||
#migrations and model relationship work also if tables
|
|
||||||
#are not defined in order
|
|
||||||
real_referenced = (
|
|
||||||
(db[referenced]._rname or db[referenced])
|
|
||||||
if referenced == tablename or referenced in db
|
|
||||||
else referenced)
|
|
||||||
|
|
||||||
ftype = types[field_type[:9]] % dict(
|
# must be PK reference or unique
|
||||||
index_name = field_name+'__idx',
|
if getattr(rtable, '_primarykey', None) and rfieldname in rtable._primarykey or \
|
||||||
field_name = field._rname or field.name,
|
rfield.unique:
|
||||||
constraint_name = constraint_name,
|
ftype = types[rfield.type[:9]] % \
|
||||||
foreign_key = '%s (%s)' % (real_referenced,
|
dict(length=rfield.length)
|
||||||
id_fieldname),
|
# multicolumn primary key reference?
|
||||||
on_delete_action=field.ondelete)
|
if not rfield.unique and len(rtable._primarykey)>1:
|
||||||
|
# then it has to be a table level FK
|
||||||
|
if rtablename not in TFK:
|
||||||
|
TFK[rtablename] = {}
|
||||||
|
TFK[rtablename][rfieldname] = field_name
|
||||||
|
else:
|
||||||
|
ftype = ftype + \
|
||||||
|
types['reference FK'] % dict(
|
||||||
|
constraint_name = constraint_name, # should be quoted
|
||||||
|
foreign_key = rtable.sqlsafe + ' (' + rfield.sqlsafe_name + ')',
|
||||||
|
table_name = table.sqlsafe,
|
||||||
|
field_name = field.sqlsafe_name,
|
||||||
|
on_delete_action=field.ondelete)
|
||||||
|
else:
|
||||||
|
# make a guess here for circular references
|
||||||
|
if referenced in db:
|
||||||
|
id_fieldname = db[referenced]._id.sqlsafe_name
|
||||||
|
elif referenced == tablename:
|
||||||
|
id_fieldname = table._id.sqlsafe_name
|
||||||
|
else: #make a guess
|
||||||
|
id_fieldname = self.QUOTE_TEMPLATE % 'id'
|
||||||
|
#gotcha: the referenced table must be defined before
|
||||||
|
#the referencing one to be able to create the table
|
||||||
|
#Also if it's not recommended, we can still support
|
||||||
|
#references to tablenames without rname to make
|
||||||
|
#migrations and model relationship work also if tables
|
||||||
|
#are not defined in order
|
||||||
|
if referenced == tablename:
|
||||||
|
real_referenced = db[referenced].sqlsafe
|
||||||
|
else:
|
||||||
|
real_referenced = (referenced in db
|
||||||
|
and db[referenced].sqlsafe
|
||||||
|
or referenced)
|
||||||
|
rfield = db[referenced]._id
|
||||||
|
ftype = types[field_type[:9]] % dict(
|
||||||
|
index_name = self.QUOTE_TEMPLATE % (field_name+'__idx'),
|
||||||
|
field_name = field.sqlsafe_name,
|
||||||
|
constraint_name = self.QUOTE_TEMPLATE % constraint_name,
|
||||||
|
foreign_key = '%s (%s)' % (real_referenced, rfield.sqlsafe_name),
|
||||||
|
on_delete_action=field.ondelete)
|
||||||
elif field_type.startswith('list:reference'):
|
elif field_type.startswith('list:reference'):
|
||||||
ftype = types[field_type[:14]]
|
ftype = types[field_type[:14]]
|
||||||
elif field_type.startswith('decimal'):
|
elif field_type.startswith('decimal'):
|
||||||
@@ -995,39 +1026,38 @@ class BaseAdapter(ConnectionPool):
|
|||||||
# geometry fields are added after the table has been created, not now
|
# geometry fields are added after the table has been created, not now
|
||||||
if not (self.dbengine == 'postgres' and \
|
if not (self.dbengine == 'postgres' and \
|
||||||
field_type.startswith('geom')):
|
field_type.startswith('geom')):
|
||||||
#fetch the rname if it's there
|
fields.append('%s %s' % (field.sqlsafe_name, ftype))
|
||||||
field_rname = "%s" % (field._rname or field_name)
|
|
||||||
fields.append('%s %s' % (field_rname, ftype))
|
|
||||||
other = ';'
|
other = ';'
|
||||||
|
|
||||||
# backend-specific extensions to fields
|
# backend-specific extensions to fields
|
||||||
if self.dbengine == 'mysql':
|
if self.dbengine == 'mysql':
|
||||||
if not hasattr(table, "_primarykey"):
|
if not hasattr(table, "_primarykey"):
|
||||||
fields.append('PRIMARY KEY(%s)' % (table._id.name or table._id._rname))
|
fields.append('PRIMARY KEY (%s)' % (self.QUOTE_TEMPLATE % table._id.name))
|
||||||
other = ' ENGINE=InnoDB CHARACTER SET utf8;'
|
engine = self.adapter_args.get('engine','InnoDB')
|
||||||
|
other = ' ENGINE=%s CHARACTER SET utf8;' % engine
|
||||||
|
|
||||||
fields = ',\n '.join(fields)
|
fields = ',\n '.join(fields)
|
||||||
for rtablename in TFK:
|
for rtablename in TFK:
|
||||||
rfields = TFK[rtablename]
|
rfields = TFK[rtablename]
|
||||||
pkeys = db[rtablename]._primarykey
|
pkeys = [self.QUOTE_TEMPLATE % pk for pk in db[rtablename]._primarykey]
|
||||||
fkeys = [ rfields[k] for k in pkeys ]
|
fkeys = [self.QUOTE_TEMPLATE % rfields[k].name for k in pkeys ]
|
||||||
fields = fields + ',\n ' + \
|
fields = fields + ',\n ' + \
|
||||||
types['reference TFK'] % dict(
|
types['reference TFK'] % dict(
|
||||||
table_name = tablename,
|
table_name = table.sqlsafe,
|
||||||
field_name=', '.join(fkeys),
|
field_name=', '.join(fkeys),
|
||||||
foreign_table = rtablename,
|
foreign_table = table.sqlsafe,
|
||||||
foreign_key = ', '.join(pkeys),
|
foreign_key = ', '.join(pkeys),
|
||||||
on_delete_action = field.ondelete)
|
on_delete_action = field.ondelete)
|
||||||
#if there's a _rname, let's use that instead
|
|
||||||
table_rname = table._rname or tablename
|
table_rname = table.sqlsafe
|
||||||
|
|
||||||
if getattr(table,'_primarykey',None):
|
if getattr(table,'_primarykey',None):
|
||||||
query = "CREATE TABLE %s(\n %s,\n %s) %s" % \
|
query = "CREATE TABLE %s(\n %s,\n %s) %s" % \
|
||||||
(table_rname, fields,
|
(table.sqlsafe, fields,
|
||||||
self.PRIMARY_KEY(', '.join(table._primarykey)),other)
|
self.PRIMARY_KEY(', '.join([self.QUOTE_TEMPLATE % pk for pk in table._primarykey])),other)
|
||||||
else:
|
else:
|
||||||
query = "CREATE TABLE %s(\n %s\n)%s" % \
|
query = "CREATE TABLE %s(\n %s\n)%s" % \
|
||||||
(table_rname, fields, other)
|
(table.sqlsafe, fields, other)
|
||||||
|
|
||||||
if self.uri.startswith('sqlite:///') \
|
if self.uri.startswith('sqlite:///') \
|
||||||
or self.uri.startswith('spatialite:///'):
|
or self.uri.startswith('spatialite:///'):
|
||||||
@@ -1105,6 +1135,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
k,v=item
|
k,v=item
|
||||||
if not isinstance(v,dict):
|
if not isinstance(v,dict):
|
||||||
v=dict(type='unknown',sql=v)
|
v=dict(type='unknown',sql=v)
|
||||||
|
if self.ignore_field_case is not True: return k, v
|
||||||
return k.lower(),v
|
return k.lower(),v
|
||||||
# make sure all field names are lower case to avoid
|
# make sure all field names are lower case to avoid
|
||||||
# migrations because of case cahnge
|
# migrations because of case cahnge
|
||||||
@@ -1132,7 +1163,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
query = [ sql_fields[key]['sql'] ]
|
query = [ sql_fields[key]['sql'] ]
|
||||||
else:
|
else:
|
||||||
query = ['ALTER TABLE %s ADD %s %s;' % \
|
query = ['ALTER TABLE %s ADD %s %s;' % \
|
||||||
(tablename, key,
|
(table.sqlsafe, key,
|
||||||
sql_fields_aux[key]['sql'].replace(', ', new_add))]
|
sql_fields_aux[key]['sql'].replace(', ', new_add))]
|
||||||
metadata_change = True
|
metadata_change = True
|
||||||
elif self.dbengine in ('sqlite', 'spatialite'):
|
elif self.dbengine in ('sqlite', 'spatialite'):
|
||||||
@@ -1150,10 +1181,11 @@ class BaseAdapter(ConnectionPool):
|
|||||||
"'%(table)s', '%(field)s');" %
|
"'%(table)s', '%(field)s');" %
|
||||||
dict(schema=schema, table=tablename, field=key,) ]
|
dict(schema=schema, table=tablename, field=key,) ]
|
||||||
elif self.dbengine in ('firebird',):
|
elif self.dbengine in ('firebird',):
|
||||||
query = ['ALTER TABLE %s DROP %s;' % (tablename, key)]
|
query = ['ALTER TABLE %s DROP %s;' %
|
||||||
|
(self.QUOTE_TEMPLATE % tablename, self.QUOTE_TEMPLATE % key)]
|
||||||
else:
|
else:
|
||||||
query = ['ALTER TABLE %s DROP COLUMN %s;' %
|
query = ['ALTER TABLE %s DROP COLUMN %s;' %
|
||||||
(tablename, key)]
|
(self.QUOTE_TEMPLATE % tablename, self.QUOTE_TEMPLATE % key)]
|
||||||
metadata_change = True
|
metadata_change = True
|
||||||
elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
|
elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
|
||||||
and not (key in table.fields and
|
and not (key in table.fields and
|
||||||
@@ -1169,12 +1201,15 @@ class BaseAdapter(ConnectionPool):
|
|||||||
else:
|
else:
|
||||||
drop_expr = 'ALTER TABLE %s DROP COLUMN %s;'
|
drop_expr = 'ALTER TABLE %s DROP COLUMN %s;'
|
||||||
key_tmp = key + '__tmp'
|
key_tmp = key + '__tmp'
|
||||||
query = ['ALTER TABLE %s ADD %s %s;' % (t, key_tmp, tt),
|
query = ['ALTER TABLE %s ADD %s %s;' % (self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key_tmp, tt),
|
||||||
'UPDATE %s SET %s=%s;' % (t, key_tmp, key),
|
'UPDATE %s SET %s=%s;' %
|
||||||
drop_expr % (t, key),
|
(self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key_tmp, self.QUOTE_TEMPLATE % key),
|
||||||
'ALTER TABLE %s ADD %s %s;' % (t, key, tt),
|
drop_expr % (self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key),
|
||||||
'UPDATE %s SET %s=%s;' % (t, key, key_tmp),
|
'ALTER TABLE %s ADD %s %s;' %
|
||||||
drop_expr % (t, key_tmp)]
|
(self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key, tt),
|
||||||
|
'UPDATE %s SET %s=%s;' %
|
||||||
|
(self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key, self.QUOTE_TEMPLATE % key_tmp),
|
||||||
|
drop_expr % (self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key_tmp)]
|
||||||
metadata_change = True
|
metadata_change = True
|
||||||
elif sql_fields[key]['type'] != sql_fields_old[key]['type']:
|
elif sql_fields[key]['type'] != sql_fields_old[key]['type']:
|
||||||
sql_fields_current[key] = sql_fields[key]
|
sql_fields_current[key] = sql_fields[key]
|
||||||
@@ -1269,8 +1304,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
return 'PRIMARY KEY(%s)' % key
|
return 'PRIMARY KEY(%s)' % key
|
||||||
|
|
||||||
def _drop(self, table, mode):
|
def _drop(self, table, mode):
|
||||||
table_rname = table._rname or table
|
return ['DROP TABLE %s;' % table.sqlsafe]
|
||||||
return ['DROP TABLE %s;' % table_rname]
|
|
||||||
|
|
||||||
def drop(self, table, mode=''):
|
def drop(self, table, mode=''):
|
||||||
db = table._db
|
db = table._db
|
||||||
@@ -1288,17 +1322,16 @@ class BaseAdapter(ConnectionPool):
|
|||||||
self.log('success!\n', table)
|
self.log('success!\n', table)
|
||||||
|
|
||||||
def _insert(self, table, fields):
|
def _insert(self, table, fields):
|
||||||
table_rname = table._rname or table
|
table_rname = table.sqlsafe
|
||||||
if fields:
|
if fields:
|
||||||
keys = ','.join(f._rname or f.name for f, v in fields)
|
keys = ','.join(f.sqlsafe_name for f, v in fields)
|
||||||
values = ','.join(self.expand(v, f.type) for f, v in fields)
|
values = ','.join(self.expand(v, f.type) for f, v in fields)
|
||||||
return 'INSERT INTO %s(%s) VALUES (%s);' % (table_rname, keys, values)
|
return 'INSERT INTO %s(%s) VALUES (%s);' % (table_rname, keys, values)
|
||||||
else:
|
else:
|
||||||
return self._insert_empty(table)
|
return self._insert_empty(table)
|
||||||
|
|
||||||
def _insert_empty(self, table):
|
def _insert_empty(self, table):
|
||||||
table_rname = table._rname or table
|
return 'INSERT INTO %s DEFAULT VALUES;' % (table.sqlsafe)
|
||||||
return 'INSERT INTO %s DEFAULT VALUES;' % table_rname
|
|
||||||
|
|
||||||
def insert(self, table, fields):
|
def insert(self, table, fields):
|
||||||
query = self._insert(table,fields)
|
query = self._insert(table,fields)
|
||||||
@@ -1428,7 +1461,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
return '(%s)' % ' || '.join(self.expand(x,'string') for x in items)
|
return '(%s)' % ' || '.join(self.expand(x,'string') for x in items)
|
||||||
|
|
||||||
def ADD(self, first, second):
|
def ADD(self, first, second):
|
||||||
if self.is_numerical_type(first.type):
|
if self.is_numerical_type(first.type) or isinstance(first.type, gluon.dal.Field):
|
||||||
return '(%s + %s)' % (self.expand(first),
|
return '(%s + %s)' % (self.expand(first),
|
||||||
self.expand(second, first.type))
|
self.expand(second, first.type))
|
||||||
else:
|
else:
|
||||||
@@ -1451,13 +1484,13 @@ class BaseAdapter(ConnectionPool):
|
|||||||
self.expand(second, first.type))
|
self.expand(second, first.type))
|
||||||
|
|
||||||
def AS(self, first, second):
|
def AS(self, first, second):
|
||||||
return '%s AS %s' % (self.expand(first), second)
|
return '%s AS %s' % (self.expand(first), second)
|
||||||
|
|
||||||
def ON(self, first, second):
|
def ON(self, first, second):
|
||||||
table_rname = first._ot and first or first._rname or first._tablename
|
table_rname = self.table_alias(first)
|
||||||
if use_common_filters(second):
|
if use_common_filters(second):
|
||||||
second = self.common_filter(second,[first._tablename])
|
second = self.common_filter(second,[first._tablename])
|
||||||
return '%s ON %s' % (self.expand(table_rname), self.expand(second))
|
return ('%s ON %s') % (self.expand(table_rname), self.expand(second))
|
||||||
|
|
||||||
def INVERT(self, first):
|
def INVERT(self, first):
|
||||||
return '%s DESC' % self.expand(first)
|
return '%s DESC' % self.expand(first)
|
||||||
@@ -1472,13 +1505,10 @@ class BaseAdapter(ConnectionPool):
|
|||||||
if isinstance(expression, Field):
|
if isinstance(expression, Field):
|
||||||
et = expression.table
|
et = expression.table
|
||||||
if not colnames:
|
if not colnames:
|
||||||
table_rname = et._ot and et._tablename or et._rname or et._tablename
|
table_rname = et._ot and self.QUOTE_TEMPLATE % et._tablename or et._rname or self.QUOTE_TEMPLATE % et._tablename
|
||||||
|
out = '%s.%s' % (table_rname, expression._rname or (self.QUOTE_TEMPLATE % (expression.name)))
|
||||||
else:
|
else:
|
||||||
table_rname = et._tablename
|
out = '%s.%s' % (self.QUOTE_TEMPLATE % et._tablename, self.QUOTE_TEMPLATE % expression.name)
|
||||||
if not colnames:
|
|
||||||
out = '%s.%s' % (table_rname, expression._rname or expression.name)
|
|
||||||
else:
|
|
||||||
out = '%s.%s' % (table_rname, expression.name)
|
|
||||||
if field_type == 'string' and not expression.type in (
|
if field_type == 'string' and not expression.type in (
|
||||||
'string','text','json','password'):
|
'string','text','json','password'):
|
||||||
out = self.CAST(out, self.types['text'])
|
out = self.CAST(out, self.types['text'])
|
||||||
@@ -1509,10 +1539,11 @@ class BaseAdapter(ConnectionPool):
|
|||||||
else:
|
else:
|
||||||
return str(expression)
|
return str(expression)
|
||||||
|
|
||||||
def table_alias(self,name):
|
def table_alias(self, tbl):
|
||||||
if not isinstance(name, Table):
|
if not isinstance(tbl, Table):
|
||||||
name = self.db[name]._rname or self.db[name]
|
tbl = self.db[tbl]
|
||||||
return str(name)
|
return tbl.sqlsafe_alias
|
||||||
|
|
||||||
|
|
||||||
def alias(self, table, alias):
|
def alias(self, table, alias):
|
||||||
"""
|
"""
|
||||||
@@ -1520,7 +1551,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
with alias name.
|
with alias name.
|
||||||
"""
|
"""
|
||||||
other = copy.copy(table)
|
other = copy.copy(table)
|
||||||
other['_ot'] = other._ot or other._rname or other._tablename
|
other['_ot'] = other._ot or other.sqlsafe
|
||||||
other['ALL'] = SQLALL(other)
|
other['ALL'] = SQLALL(other)
|
||||||
other['_tablename'] = alias
|
other['_tablename'] = alias
|
||||||
for fieldname in other.fields:
|
for fieldname in other.fields:
|
||||||
@@ -1532,8 +1563,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
return other
|
return other
|
||||||
|
|
||||||
def _truncate(self, table, mode=''):
|
def _truncate(self, table, mode=''):
|
||||||
tablename = table._rname or table._tablename
|
return ['TRUNCATE TABLE %s %s;' % (table.sqlsafe, mode or '')]
|
||||||
return ['TRUNCATE TABLE %s %s;' % (tablename, mode or '')]
|
|
||||||
|
|
||||||
def truncate(self, table, mode= ' '):
|
def truncate(self, table, mode= ' '):
|
||||||
# Prepare functions "write_to_logfile" and "close_logfile"
|
# Prepare functions "write_to_logfile" and "close_logfile"
|
||||||
@@ -1553,10 +1583,10 @@ class BaseAdapter(ConnectionPool):
|
|||||||
sql_w = ' WHERE ' + self.expand(query)
|
sql_w = ' WHERE ' + self.expand(query)
|
||||||
else:
|
else:
|
||||||
sql_w = ''
|
sql_w = ''
|
||||||
sql_v = ','.join(['%s=%s' % (field._rname or field.name,
|
sql_v = ','.join(['%s=%s' % (field.sqlsafe_name,
|
||||||
self.expand(value, field.type)) \
|
self.expand(value, field.type)) \
|
||||||
for (field, value) in fields])
|
for (field, value) in fields])
|
||||||
tablename = "%s" % (self.db[tablename]._rname or tablename)
|
tablename = self.db[tablename].sqlsafe
|
||||||
return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w)
|
return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w)
|
||||||
|
|
||||||
def update(self, tablename, query, fields):
|
def update(self, tablename, query, fields):
|
||||||
@@ -1581,7 +1611,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
sql_w = ' WHERE ' + self.expand(query)
|
sql_w = ' WHERE ' + self.expand(query)
|
||||||
else:
|
else:
|
||||||
sql_w = ''
|
sql_w = ''
|
||||||
tablename = '%s' % (self.db[tablename]._rname or tablename)
|
tablename = self.db[tablename].sqlsafe
|
||||||
return 'DELETE FROM %s%s;' % (tablename, sql_w)
|
return 'DELETE FROM %s%s;' % (tablename, sql_w)
|
||||||
|
|
||||||
def delete(self, tablename, query):
|
def delete(self, tablename, query):
|
||||||
@@ -1623,8 +1653,9 @@ class BaseAdapter(ConnectionPool):
|
|||||||
if isinstance(item,SQLALL):
|
if isinstance(item,SQLALL):
|
||||||
new_fields += item._table
|
new_fields += item._table
|
||||||
elif isinstance(item,str):
|
elif isinstance(item,str):
|
||||||
if REGEX_TABLE_DOT_FIELD.match(item):
|
m = self.REGEX_TABLE_DOT_FIELD.match(item)
|
||||||
tablename,fieldname = item.split('.')
|
if m:
|
||||||
|
tablename,fieldname = m.groups()
|
||||||
append(db[tablename][fieldname])
|
append(db[tablename][fieldname])
|
||||||
else:
|
else:
|
||||||
append(Expression(db,lambda item=item:item))
|
append(Expression(db,lambda item=item:item))
|
||||||
@@ -1645,10 +1676,11 @@ class BaseAdapter(ConnectionPool):
|
|||||||
tablenames = tables(query)
|
tablenames = tables(query)
|
||||||
tablenames_for_common_filters = tablenames
|
tablenames_for_common_filters = tablenames
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if isinstance(field, basestring) \
|
if isinstance(field, basestring):
|
||||||
and REGEX_TABLE_DOT_FIELD.match(field):
|
m = self.REGEX_TABLE_DOT_FIELD.match(field)
|
||||||
tn,fn = field.split('.')
|
if m:
|
||||||
field = self.db[tn][fn]
|
tn,fn = m.groups()
|
||||||
|
field = self.db[tn][fn]
|
||||||
for tablename in tables(field):
|
for tablename in tables(field):
|
||||||
if not tablename in tablenames:
|
if not tablename in tablenames:
|
||||||
tablenames.append(tablename)
|
tablenames.append(tablename)
|
||||||
@@ -1732,7 +1764,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
tables_to_merge.keys()])
|
tables_to_merge.keys()])
|
||||||
if joint:
|
if joint:
|
||||||
sql_t += ' %s %s' % (command,
|
sql_t += ' %s %s' % (command,
|
||||||
','.join([self.table_alias(t) for t in joint]))
|
','.join([t for t in joint]))
|
||||||
for t in joinon:
|
for t in joinon:
|
||||||
sql_t += ' %s %s' % (command, t)
|
sql_t += ' %s %s' % (command, t)
|
||||||
elif inner_join and left:
|
elif inner_join and left:
|
||||||
@@ -1747,7 +1779,7 @@ class BaseAdapter(ConnectionPool):
|
|||||||
sql_t += ' %s %s' % (icommand, t)
|
sql_t += ' %s %s' % (icommand, t)
|
||||||
if joint:
|
if joint:
|
||||||
sql_t += ' %s %s' % (command,
|
sql_t += ' %s %s' % (command,
|
||||||
','.join([self.table_alias(t) for t in joint]))
|
','.join([t for t in joint]))
|
||||||
for t in joinon:
|
for t in joinon:
|
||||||
sql_t += ' %s %s' % (command, t)
|
sql_t += ' %s %s' % (command, t)
|
||||||
else:
|
else:
|
||||||
@@ -1767,9 +1799,9 @@ class BaseAdapter(ConnectionPool):
|
|||||||
sql_o += ' ORDER BY %s' % self.expand(orderby)
|
sql_o += ' ORDER BY %s' % self.expand(orderby)
|
||||||
if (limitby and not groupby and tablenames and orderby_on_limitby and not orderby):
|
if (limitby and not groupby and tablenames and orderby_on_limitby and not orderby):
|
||||||
sql_o += ' ORDER BY %s' % ', '.join(
|
sql_o += ' ORDER BY %s' % ', '.join(
|
||||||
['%s.%s'%(t,x) for t in tablenames for x in (
|
[self.db[t][x].sqlsafe for t in tablenames for x in (
|
||||||
hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey
|
hasattr(self.db[t],'_primarykey') and self.db[t]._primarykey
|
||||||
or [self.db[t]._id._rname or self.db[t]._id.name]
|
or ['_id']
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -1898,7 +1930,9 @@ class BaseAdapter(ConnectionPool):
|
|||||||
def create_sequence_and_triggers(self, query, table, **args):
|
def create_sequence_and_triggers(self, query, table, **args):
|
||||||
self.execute(query)
|
self.execute(query)
|
||||||
|
|
||||||
|
|
||||||
def log_execute(self, *a, **b):
|
def log_execute(self, *a, **b):
|
||||||
|
if not self.connection: raise ValueError(a[0])
|
||||||
if not self.connection: return None
|
if not self.connection: return None
|
||||||
command = a[0]
|
command = a[0]
|
||||||
if hasattr(self,'filter_sql_command'):
|
if hasattr(self,'filter_sql_command'):
|
||||||
@@ -1955,8 +1989,17 @@ class BaseAdapter(ConnectionPool):
|
|||||||
if field_is_type('decimal'):
|
if field_is_type('decimal'):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
elif field_is_type('reference'): # reference
|
elif field_is_type('reference'): # reference
|
||||||
if fieldtype.find('.')>0:
|
# check for tablename first
|
||||||
return repr(obj)
|
referenced = fieldtype[9:].strip()
|
||||||
|
if referenced in self.db.tables:
|
||||||
|
return str(long(obj))
|
||||||
|
p = referenced.partition('.')
|
||||||
|
if p[2] != '':
|
||||||
|
try:
|
||||||
|
ftype = self.db[p[0]][p[2]].type
|
||||||
|
return self.represent(obj, ftype)
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
return repr(obj)
|
||||||
elif isinstance(obj, (Row, Reference)):
|
elif isinstance(obj, (Row, Reference)):
|
||||||
return str(obj['id'])
|
return str(obj['id'])
|
||||||
return str(long(obj))
|
return str(long(obj))
|
||||||
@@ -2162,14 +2205,15 @@ class BaseAdapter(ConnectionPool):
|
|||||||
new_rows = []
|
new_rows = []
|
||||||
tmps = []
|
tmps = []
|
||||||
for colname in colnames:
|
for colname in colnames:
|
||||||
if not REGEX_TABLE_DOT_FIELD.match(colname):
|
col_m = self.REGEX_TABLE_DOT_FIELD.match(colname)
|
||||||
|
if not col_m:
|
||||||
tmps.append(None)
|
tmps.append(None)
|
||||||
else:
|
else:
|
||||||
(tablename, _the_sep_, fieldname) = colname.partition('.')
|
tablename, fieldname = col_m.groups()
|
||||||
table = db[tablename]
|
table = db[tablename]
|
||||||
field = table[fieldname]
|
field = table[fieldname]
|
||||||
ft = field.type
|
ft = field.type
|
||||||
tmps.append((tablename,fieldname,table,field,ft))
|
tmps.append((tablename, fieldname, table, field, ft))
|
||||||
for (i,row) in enumerate(rows):
|
for (i,row) in enumerate(rows):
|
||||||
new_row = Row()
|
new_row = Row()
|
||||||
for (j,colname) in enumerate(colnames):
|
for (j,colname) in enumerate(colnames):
|
||||||
@@ -2177,9 +2221,8 @@ class BaseAdapter(ConnectionPool):
|
|||||||
tmp = tmps[j]
|
tmp = tmps[j]
|
||||||
if tmp:
|
if tmp:
|
||||||
(tablename,fieldname,table,field,ft) = tmp
|
(tablename,fieldname,table,field,ft) = tmp
|
||||||
if tablename in new_row:
|
colset = new_row.get(tablename, None)
|
||||||
colset = new_row[tablename]
|
if colset is None:
|
||||||
else:
|
|
||||||
colset = new_row[tablename] = Row()
|
colset = new_row[tablename] = Row()
|
||||||
if tablename not in virtualtables:
|
if tablename not in virtualtables:
|
||||||
virtualtables.append(tablename)
|
virtualtables.append(tablename)
|
||||||
@@ -2287,6 +2330,14 @@ class BaseAdapter(ConnectionPool):
|
|||||||
return Expression(self.db,'CASE WHEN %s THEN %s ELSE %s END' % \
|
return Expression(self.db,'CASE WHEN %s THEN %s ELSE %s END' % \
|
||||||
(self.expand(query),represent(t),represent(f)))
|
(self.expand(query),represent(t),represent(f)))
|
||||||
|
|
||||||
|
def sqlsafe_table(self, tablename, ot=None):
|
||||||
|
if ot is not None:
|
||||||
|
return ('%s AS ' + self.QUOTE_TEMPLATE) % (ot, tablename)
|
||||||
|
return self.QUOTE_TEMPLATE % tablename
|
||||||
|
|
||||||
|
def sqlsafe_field(self, fieldname):
|
||||||
|
return self.QUOTE_TEMPLATE % fieldname
|
||||||
|
|
||||||
###################################################################################
|
###################################################################################
|
||||||
# List of all the available adapters; they all extend BaseAdapter.
|
# List of all the available adapters; they all extend BaseAdapter.
|
||||||
###################################################################################
|
###################################################################################
|
||||||
@@ -2564,6 +2615,7 @@ class MySQLAdapter(BaseAdapter):
|
|||||||
'list:reference': 'LONGTEXT',
|
'list:reference': 'LONGTEXT',
|
||||||
'big-id': 'BIGINT AUTO_INCREMENT NOT NULL',
|
'big-id': 'BIGINT AUTO_INCREMENT NOT NULL',
|
||||||
'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||||
|
'reference FK': ', CONSTRAINT `FK_%(constraint_name)s` FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||||
}
|
}
|
||||||
|
|
||||||
QUOTE_TEMPLATE = "`%s`"
|
QUOTE_TEMPLATE = "`%s`"
|
||||||
@@ -2590,12 +2642,12 @@ class MySQLAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def _drop(self,table,mode):
|
def _drop(self,table,mode):
|
||||||
# breaks db integrity but without this mysql does not drop table
|
# breaks db integrity but without this mysql does not drop table
|
||||||
table_rname = table._rname or table
|
table_rname = table.sqlsafe
|
||||||
return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table_rname,
|
return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table_rname,
|
||||||
'SET FOREIGN_KEY_CHECKS=1;']
|
'SET FOREIGN_KEY_CHECKS=1;']
|
||||||
|
|
||||||
def _insert_empty(self, table):
|
def _insert_empty(self, table):
|
||||||
return 'INSERT INTO %s VALUES (DEFAULT);' % table
|
return 'INSERT INTO %s VALUES (DEFAULT);' % (table.sqlsafe)
|
||||||
|
|
||||||
def distributed_transaction_begin(self,key):
|
def distributed_transaction_begin(self,key):
|
||||||
self.execute('XA START;')
|
self.execute('XA START;')
|
||||||
@@ -2668,6 +2720,8 @@ class MySQLAdapter(BaseAdapter):
|
|||||||
class PostgreSQLAdapter(BaseAdapter):
|
class PostgreSQLAdapter(BaseAdapter):
|
||||||
drivers = ('psycopg2','pg8000')
|
drivers = ('psycopg2','pg8000')
|
||||||
|
|
||||||
|
QUOTE_TEMPLATE = '"%s"'
|
||||||
|
|
||||||
support_distributed_transaction = True
|
support_distributed_transaction = True
|
||||||
types = {
|
types = {
|
||||||
'boolean': 'CHAR(1)',
|
'boolean': 'CHAR(1)',
|
||||||
@@ -2694,12 +2748,11 @@ class PostgreSQLAdapter(BaseAdapter):
|
|||||||
'geography': 'GEOGRAPHY',
|
'geography': 'GEOGRAPHY',
|
||||||
'big-id': 'BIGSERIAL PRIMARY KEY',
|
'big-id': 'BIGSERIAL PRIMARY KEY',
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
'reference FK': ', CONSTRAINT "FK_%(constraint_name)s" FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
'reference TFK': ' CONSTRAINT "FK_%(foreign_table)s_PK" FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUOTE_TEMPLATE = '"%s"'
|
|
||||||
|
|
||||||
def varquote(self,name):
|
def varquote(self,name):
|
||||||
return varquote_aux(name,'"%s"')
|
return varquote_aux(name,'"%s"')
|
||||||
@@ -2713,7 +2766,7 @@ class PostgreSQLAdapter(BaseAdapter):
|
|||||||
return "'%s'" % str(obj).replace("'","''")
|
return "'%s'" % str(obj).replace("'","''")
|
||||||
|
|
||||||
def sequence_name(self,table):
|
def sequence_name(self,table):
|
||||||
return '%s_id_seq' % table
|
return self.QUOTE_TEMPLATE % (table + '_id_seq')
|
||||||
|
|
||||||
def RANDOM(self):
|
def RANDOM(self):
|
||||||
return 'RANDOM()'
|
return 'RANDOM()'
|
||||||
@@ -2802,8 +2855,8 @@ class PostgreSQLAdapter(BaseAdapter):
|
|||||||
self.execute("SET standard_conforming_strings=on;")
|
self.execute("SET standard_conforming_strings=on;")
|
||||||
self.try_json()
|
self.try_json()
|
||||||
|
|
||||||
def lastrowid(self,table):
|
def lastrowid(self,table = None):
|
||||||
self.execute("""select currval('"%s"')""" % table._sequence_name)
|
self.execute("select lastval()")
|
||||||
return int(self.cursor.fetchone()[0])
|
return int(self.cursor.fetchone()[0])
|
||||||
|
|
||||||
def try_json(self):
|
def try_json(self):
|
||||||
@@ -2925,6 +2978,14 @@ class PostgreSQLAdapter(BaseAdapter):
|
|||||||
"""
|
"""
|
||||||
return 'ST_Within(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
return 'ST_Within(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
||||||
|
|
||||||
|
def ST_DWITHIN(self, first, (second, third)):
|
||||||
|
"""
|
||||||
|
http://postgis.org/docs/ST_DWithin.html
|
||||||
|
"""
|
||||||
|
return 'ST_DWithin(%s,%s,%s)' %(self.expand(first),
|
||||||
|
self.expand(second, first.type),
|
||||||
|
self.expand(third, 'double'))
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
def represent(self, obj, fieldtype):
|
||||||
field_is_type = fieldtype.startswith
|
field_is_type = fieldtype.startswith
|
||||||
if field_is_type('geo'):
|
if field_is_type('geo'):
|
||||||
@@ -2942,6 +3003,11 @@ class PostgreSQLAdapter(BaseAdapter):
|
|||||||
return value
|
return value
|
||||||
return BaseAdapter.represent(self, obj, fieldtype)
|
return BaseAdapter.represent(self, obj, fieldtype)
|
||||||
|
|
||||||
|
def _drop(self, table, mode='restrict'):
|
||||||
|
if mode not in ['restrict', 'cascade', '']:
|
||||||
|
raise ValueError('Invalid mode: %s' % mode)
|
||||||
|
return ['DROP TABLE ' + table.sqlsafe + ' ' + str(mode) + ';']
|
||||||
|
|
||||||
class NewPostgreSQLAdapter(PostgreSQLAdapter):
|
class NewPostgreSQLAdapter(PostgreSQLAdapter):
|
||||||
drivers = ('psycopg2','pg8000')
|
drivers = ('psycopg2','pg8000')
|
||||||
|
|
||||||
@@ -3074,8 +3140,6 @@ class OracleAdapter(BaseAdapter):
|
|||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
||||||
}
|
}
|
||||||
|
|
||||||
def sequence_name(self,tablename):
|
|
||||||
return '%s_sequence' % tablename
|
|
||||||
|
|
||||||
def trigger_name(self,tablename):
|
def trigger_name(self,tablename):
|
||||||
return '%s_trigger' % tablename
|
return '%s_trigger' % tablename
|
||||||
@@ -3091,7 +3155,7 @@ class OracleAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def _drop(self,table,mode):
|
def _drop(self,table,mode):
|
||||||
sequence_name = table._sequence_name
|
sequence_name = table._sequence_name
|
||||||
return ['DROP TABLE %s %s;' % (table, mode), 'DROP SEQUENCE %s;' % sequence_name]
|
return ['DROP TABLE %s %s;' % (table.sqlsafe, mode), 'DROP SEQUENCE %s;' % sequence_name]
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
||||||
if limitby:
|
if limitby:
|
||||||
@@ -3218,11 +3282,18 @@ class OracleAdapter(BaseAdapter):
|
|||||||
else:
|
else:
|
||||||
return self.cursor.fetchall()
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
|
def sqlsafe_table(self, tablename, ot=None):
|
||||||
|
if ot is not None:
|
||||||
|
return (self.QUOTE_TEMPLATE + ' ' \
|
||||||
|
+ self.QUOTE_TEMPLATE) % (ot, tablename)
|
||||||
|
return self.QUOTE_TEMPLATE % tablename
|
||||||
|
|
||||||
|
|
||||||
class MSSQLAdapter(BaseAdapter):
|
class MSSQLAdapter(BaseAdapter):
|
||||||
drivers = ('pyodbc',)
|
drivers = ('pyodbc',)
|
||||||
T_SEP = 'T'
|
T_SEP = 'T'
|
||||||
|
|
||||||
QUOTE_TEMPLATE = "[%s]"
|
QUOTE_TEMPLATE = '"%s"'
|
||||||
|
|
||||||
types = {
|
types = {
|
||||||
'boolean': 'BIT',
|
'boolean': 'BIT',
|
||||||
@@ -3685,7 +3756,7 @@ class FireBirdAdapter(BaseAdapter):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def sequence_name(self,tablename):
|
def sequence_name(self,tablename):
|
||||||
return 'genid_%s' % tablename
|
return ('genid_' + self.QUOTE_TEMPLATE) % tablename
|
||||||
|
|
||||||
def trigger_name(self,tablename):
|
def trigger_name(self,tablename):
|
||||||
return 'trg_id_%s' % tablename
|
return 'trg_id_%s' % tablename
|
||||||
@@ -3714,7 +3785,7 @@ class FireBirdAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def _drop(self,table,mode):
|
def _drop(self,table,mode):
|
||||||
sequence_name = table._sequence_name
|
sequence_name = table._sequence_name
|
||||||
return ['DROP TABLE %s %s;' % (table, mode), 'DROP GENERATOR %s;' % sequence_name]
|
return ['DROP TABLE %s %s;' % (table.sqlsafe, mode), 'DROP GENERATOR %s;' % sequence_name]
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
||||||
if limitby:
|
if limitby:
|
||||||
@@ -4283,7 +4354,7 @@ class SAPDBAdapter(BaseAdapter):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def sequence_name(self,table):
|
def sequence_name(self,table):
|
||||||
return '%s_id_Seq' % table
|
return (self.QUOTE_TEMPLATE + '_id_Seq') % table
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
||||||
if limitby:
|
if limitby:
|
||||||
@@ -4548,6 +4619,7 @@ class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
|
|||||||
|
|
||||||
class NoSQLAdapter(BaseAdapter):
|
class NoSQLAdapter(BaseAdapter):
|
||||||
can_select_for_update = False
|
can_select_for_update = False
|
||||||
|
QUOTE_TEMPLATE = '%s'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_unicode(obj):
|
def to_unicode(obj):
|
||||||
@@ -4737,6 +4809,8 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
|||||||
|
|
||||||
uploads_in_blob = True
|
uploads_in_blob = True
|
||||||
types = {}
|
types = {}
|
||||||
|
# reconnect is not required for Datastore dbs
|
||||||
|
reconnect = lambda *args, **kwargs: None
|
||||||
|
|
||||||
def file_exists(self, filename): pass
|
def file_exists(self, filename): pass
|
||||||
def file_open(self, filename, mode='rb', lock=True): pass
|
def file_open(self, filename, mode='rb', lock=True): pass
|
||||||
@@ -4839,12 +4913,10 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
|||||||
elif field_type.startswith('reference'):
|
elif field_type.startswith('reference'):
|
||||||
if field.notnull:
|
if field.notnull:
|
||||||
attr = dict(required=True)
|
attr = dict(required=True)
|
||||||
referenced = field_type[10:].strip()
|
ftype = self.types[field_type[:9]](**attr)
|
||||||
ftype = self.types[field_type[:9]](referenced, **attr)
|
|
||||||
elif field_type.startswith('list:reference'):
|
elif field_type.startswith('list:reference'):
|
||||||
if field.notnull:
|
if field.notnull:
|
||||||
attr['required'] = True
|
attr['required'] = True
|
||||||
referenced = field_type[15:].strip()
|
|
||||||
ftype = self.types[field_type[:14]](**attr)
|
ftype = self.types[field_type[:14]](**attr)
|
||||||
elif field_type.startswith('list:'):
|
elif field_type.startswith('list:'):
|
||||||
ftype = self.types[field_type](**attr)
|
ftype = self.types[field_type](**attr)
|
||||||
@@ -5168,6 +5240,12 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
|||||||
processor = attributes.get('processor',self.parse)
|
processor = attributes.get('processor',self.parse)
|
||||||
return processor(rows,fields,colnames,False)
|
return processor(rows,fields,colnames,False)
|
||||||
|
|
||||||
|
def parse_list_integers(self, value, field_type):
|
||||||
|
return value[:] if self.use_ndb else value
|
||||||
|
|
||||||
|
def parse_list_strings(self, value, field_type):
|
||||||
|
return value[:] if self.use_ndb else value
|
||||||
|
|
||||||
def count(self,query,distinct=None,limit=None):
|
def count(self,query,distinct=None,limit=None):
|
||||||
if distinct:
|
if distinct:
|
||||||
raise RuntimeError("COUNT DISTINCT not supported")
|
raise RuntimeError("COUNT DISTINCT not supported")
|
||||||
@@ -5442,8 +5520,8 @@ def cleanup(text):
|
|||||||
"""
|
"""
|
||||||
validates that the given text is clean: only contains [0-9a-zA-Z_]
|
validates that the given text is clean: only contains [0-9a-zA-Z_]
|
||||||
"""
|
"""
|
||||||
if not REGEX_ALPHANUMERIC.match(text):
|
#if not REGEX_ALPHANUMERIC.match(text):
|
||||||
raise SyntaxError('invalid table or field name: %s' % text)
|
# raise SyntaxError('invalid table or field name: %s' % text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
class MongoDBAdapter(NoSQLAdapter):
|
class MongoDBAdapter(NoSQLAdapter):
|
||||||
@@ -5582,9 +5660,6 @@ class MongoDBAdapter(NoSQLAdapter):
|
|||||||
value = obj
|
value = obj
|
||||||
else:
|
else:
|
||||||
value = NoSQLAdapter.represent(self, obj, fieldtype)
|
value = NoSQLAdapter.represent(self, obj, fieldtype)
|
||||||
if isinstance(obj, (list, tuple)) and \
|
|
||||||
(not fieldtype == "json" or fieldtype.startswith('list:')):
|
|
||||||
return value
|
|
||||||
# reference types must be convert to ObjectID
|
# reference types must be convert to ObjectID
|
||||||
if fieldtype =='date':
|
if fieldtype =='date':
|
||||||
if value == None:
|
if value == None:
|
||||||
@@ -7245,12 +7320,32 @@ class Row(object):
|
|||||||
__init__ = lambda self,*args,**kwargs: self.__dict__.update(*args,**kwargs)
|
__init__ = lambda self,*args,**kwargs: self.__dict__.update(*args,**kwargs)
|
||||||
|
|
||||||
def __getitem__(self, k):
|
def __getitem__(self, k):
|
||||||
|
if isinstance(k, Table):
|
||||||
|
try:
|
||||||
|
return ogetattr(self, k._tablename)
|
||||||
|
except (KeyError,AttributeError,TypeError):
|
||||||
|
pass
|
||||||
|
elif isinstance(k, Field):
|
||||||
|
try:
|
||||||
|
return ogetattr(self, k.name)
|
||||||
|
except (KeyError,AttributeError,TypeError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return ogetattr(ogetattr(self, k.tablename), k.name)
|
||||||
|
except (KeyError,AttributeError,TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
key=str(k)
|
key=str(k)
|
||||||
_extra = self.__dict__.get('_extra', None)
|
_extra = ogetattr(self, '__dict__').get('_extra', None)
|
||||||
if _extra is not None:
|
if _extra is not None:
|
||||||
v = _extra.get(key, DEFAULT)
|
v = _extra.get(key, DEFAULT)
|
||||||
if v != DEFAULT:
|
if v != DEFAULT:
|
||||||
return v
|
return v
|
||||||
|
try:
|
||||||
|
return ogetattr(self, key)
|
||||||
|
except (KeyError,AttributeError,TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
m = REGEX_TABLE_DOT_FIELD.match(key)
|
m = REGEX_TABLE_DOT_FIELD.match(key)
|
||||||
if m:
|
if m:
|
||||||
try:
|
try:
|
||||||
@@ -7652,7 +7747,7 @@ class DAL(object):
|
|||||||
adapter_args=None, attempts=5, auto_import=False,
|
adapter_args=None, attempts=5, auto_import=False,
|
||||||
bigint_id=False, debug=False, lazy_tables=False,
|
bigint_id=False, debug=False, lazy_tables=False,
|
||||||
db_uid=None, do_connect=True,
|
db_uid=None, do_connect=True,
|
||||||
after_connection=None, tables=None):
|
after_connection=None, tables=None, ignore_field_case=True):
|
||||||
"""
|
"""
|
||||||
Creates a new Database Abstraction Layer instance.
|
Creates a new Database Abstraction Layer instance.
|
||||||
|
|
||||||
@@ -7737,6 +7832,7 @@ class DAL(object):
|
|||||||
self._decode_credentials = decode_credentials
|
self._decode_credentials = decode_credentials
|
||||||
self._attempts = attempts
|
self._attempts = attempts
|
||||||
self._do_connect = do_connect
|
self._do_connect = do_connect
|
||||||
|
self._ignore_field_case = ignore_field_case
|
||||||
|
|
||||||
if not str(attempts).isdigit() or attempts < 0:
|
if not str(attempts).isdigit() or attempts < 0:
|
||||||
attempts = 5
|
attempts = 5
|
||||||
@@ -7768,6 +7864,7 @@ class DAL(object):
|
|||||||
# copy so multiple DAL() possible
|
# copy so multiple DAL() possible
|
||||||
self._adapter.types = copy.copy(types)
|
self._adapter.types = copy.copy(types)
|
||||||
self._adapter.build_parsemap()
|
self._adapter.build_parsemap()
|
||||||
|
self._adapter.ignore_field_case = ignore_field_case
|
||||||
if bigint_id:
|
if bigint_id:
|
||||||
if 'big-id' in types and 'reference' in types:
|
if 'big-id' in types and 'reference' in types:
|
||||||
self._adapter.types['id'] = types['big-id']
|
self._adapter.types['id'] = types['big-id']
|
||||||
@@ -7810,11 +7907,11 @@ class DAL(object):
|
|||||||
|
|
||||||
def import_table_definitions(self, path, migrate=False,
|
def import_table_definitions(self, path, migrate=False,
|
||||||
fake_migrate=False, tables=None):
|
fake_migrate=False, tables=None):
|
||||||
pattern = pjoin(path,self._uri_hash+'_*.table')
|
|
||||||
if tables:
|
if tables:
|
||||||
for table in tables:
|
for table in tables:
|
||||||
self.define_table(**table)
|
self.define_table(**table)
|
||||||
else:
|
else:
|
||||||
|
pattern = pjoin(path,self._uri_hash+'_*.table')
|
||||||
for filename in glob.glob(pattern):
|
for filename in glob.glob(pattern):
|
||||||
tfile = self._adapter.file_open(filename, 'r')
|
tfile = self._adapter.file_open(filename, 'r')
|
||||||
try:
|
try:
|
||||||
@@ -8565,13 +8662,8 @@ class Table(object):
|
|||||||
self._tablename = tablename
|
self._tablename = tablename
|
||||||
self._ot = None # args.get('rname')
|
self._ot = None # args.get('rname')
|
||||||
self._rname = args.get('rname')
|
self._rname = args.get('rname')
|
||||||
if not self._rname:
|
self._sequence_name = args.get('sequence_name') or \
|
||||||
self._sequence_name = args.get('sequence_name') or \
|
db and db._adapter.sequence_name(self._rname or tablename)
|
||||||
db and db._adapter.sequence_name(tablename)
|
|
||||||
else:
|
|
||||||
tb = self._rname[1:-1]
|
|
||||||
self._sequence_name = args.get('sequence_name') or \
|
|
||||||
db and db._adapter.sequence_name(tb)
|
|
||||||
self._trigger_name = args.get('trigger_name') or \
|
self._trigger_name = args.get('trigger_name') or \
|
||||||
db and db._adapter.trigger_name(tablename)
|
db and db._adapter.trigger_name(tablename)
|
||||||
self._common_filter = args.get('common_filter')
|
self._common_filter = args.get('common_filter')
|
||||||
@@ -8652,7 +8744,7 @@ class Table(object):
|
|||||||
fields.append(Field(fn,'blob',default='',
|
fields.append(Field(fn,'blob',default='',
|
||||||
writable=False,readable=False))
|
writable=False,readable=False))
|
||||||
|
|
||||||
lower_fieldnames = set()
|
fieldnames_set = set()
|
||||||
reserved = dir(Table) + ['fields']
|
reserved = dir(Table) + ['fields']
|
||||||
if (db and db.check_reserved):
|
if (db and db.check_reserved):
|
||||||
check_reserved = db.check_reserved_keyword
|
check_reserved = db.check_reserved_keyword
|
||||||
@@ -8663,12 +8755,15 @@ class Table(object):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
field_name = field.name
|
field_name = field.name
|
||||||
check_reserved(field_name)
|
check_reserved(field_name)
|
||||||
fn_lower = field_name.lower()
|
if db and db._ignore_field_case:
|
||||||
if fn_lower in lower_fieldnames:
|
fname_item = field_name.lower()
|
||||||
|
else:
|
||||||
|
fname_item = field_name
|
||||||
|
if fname_item in fieldnames_set:
|
||||||
raise SyntaxError("duplicate field %s in table %s" \
|
raise SyntaxError("duplicate field %s in table %s" \
|
||||||
% (field_name, tablename))
|
% (field_name, tablename))
|
||||||
else:
|
else:
|
||||||
lower_fieldnames.add(fn_lower)
|
fieldnames_set.add(fname_item)
|
||||||
|
|
||||||
self.fields.append(field_name)
|
self.fields.append(field_name)
|
||||||
self[field_name] = field
|
self[field_name] = field
|
||||||
@@ -8910,8 +9005,23 @@ class Table(object):
|
|||||||
if 'Oracle' in str(type(self._db._adapter)):
|
if 'Oracle' in str(type(self._db._adapter)):
|
||||||
return '%s %s' % (ot, self._tablename)
|
return '%s %s' % (ot, self._tablename)
|
||||||
return '%s AS %s' % (ot, self._tablename)
|
return '%s AS %s' % (ot, self._tablename)
|
||||||
|
|
||||||
return self._tablename
|
return self._tablename
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sqlsafe(self):
|
||||||
|
rname = self._rname
|
||||||
|
if rname: return rname
|
||||||
|
return self._db._adapter.sqlsafe_table(self._tablename)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sqlsafe_alias(self):
|
||||||
|
rname = self._rname
|
||||||
|
ot = self._ot
|
||||||
|
if rname and not ot: return rname
|
||||||
|
return self._db._adapter.sqlsafe_table(self._tablename, self._ot)
|
||||||
|
|
||||||
|
|
||||||
def _drop(self, mode = ''):
|
def _drop(self, mode = ''):
|
||||||
return self._db._adapter._drop(self, mode)
|
return self._db._adapter._drop(self, mode)
|
||||||
|
|
||||||
@@ -9566,6 +9676,10 @@ class Expression(object):
|
|||||||
db = self.db
|
db = self.db
|
||||||
return Query(db, db._adapter.ST_WITHIN, self, value)
|
return Query(db, db._adapter.ST_WITHIN, self, value)
|
||||||
|
|
||||||
|
def st_dwithin(self, value, distance):
|
||||||
|
db = self.db
|
||||||
|
return Query(db, db._adapter.ST_DWITHIN, self, (value, distance))
|
||||||
|
|
||||||
# for use in both Query and sortby
|
# for use in both Query and sortby
|
||||||
|
|
||||||
|
|
||||||
@@ -9738,7 +9852,12 @@ class Field(Expression):
|
|||||||
if not isinstance(fieldname, str) or hasattr(Table, fieldname) or \
|
if not isinstance(fieldname, str) or hasattr(Table, fieldname) or \
|
||||||
fieldname[0] == '_' or REGEX_PYTHON_KEYWORDS.match(fieldname):
|
fieldname[0] == '_' or REGEX_PYTHON_KEYWORDS.match(fieldname):
|
||||||
raise SyntaxError('Field: invalid field name: %s' % fieldname)
|
raise SyntaxError('Field: invalid field name: %s' % fieldname)
|
||||||
self.type = type if not isinstance(type, (Table,Field)) else 'reference %s' % type
|
|
||||||
|
if not isinstance(type, (Table,Field)):
|
||||||
|
self.type = type
|
||||||
|
else:
|
||||||
|
self.type = 'reference %s' % type
|
||||||
|
|
||||||
self.length = length if not length is None else DEFAULTLENGTH.get(self.type,512)
|
self.length = length if not length is None else DEFAULTLENGTH.get(self.type,512)
|
||||||
self.default = default if default!=DEFAULT else (update or None)
|
self.default = default if default!=DEFAULT else (update or None)
|
||||||
self.required = required # is this field required
|
self.required = required # is this field required
|
||||||
@@ -10004,6 +10123,16 @@ class Field(Expression):
|
|||||||
except:
|
except:
|
||||||
return '<no table>.%s' % self.name
|
return '<no table>.%s' % self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sqlsafe(self):
|
||||||
|
if self._table:
|
||||||
|
return self._table.sqlsafe + '.' + (self._rname or self._db._adapter.sqlsafe_field(self.name))
|
||||||
|
return '<no table>.%s' % self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sqlsafe_name(self):
|
||||||
|
return self._rname or self._db._adapter.sqlsafe_field(self.name)
|
||||||
|
|
||||||
|
|
||||||
class Query(object):
|
class Query(object):
|
||||||
|
|
||||||
@@ -10356,7 +10485,7 @@ class Set(object):
|
|||||||
fields = table._listify(update_fields,update=True)
|
fields = table._listify(update_fields,update=True)
|
||||||
if not fields:
|
if not fields:
|
||||||
raise SyntaxError("No fields to update")
|
raise SyntaxError("No fields to update")
|
||||||
ret = db._adapter.update("%s" % table,self.query,fields)
|
ret = db._adapter.update("%s" % table._tablename,self.query,fields)
|
||||||
ret and [f(self,update_fields) for f in table._after_update]
|
ret and [f(self,update_fields) for f in table._after_update]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -10869,11 +10998,21 @@ class Rows(object):
|
|||||||
represent = kwargs.get('represent', False)
|
represent = kwargs.get('represent', False)
|
||||||
writer = csv.writer(ofile, delimiter=delimiter,
|
writer = csv.writer(ofile, delimiter=delimiter,
|
||||||
quotechar=quotechar, quoting=quoting)
|
quotechar=quotechar, quoting=quoting)
|
||||||
|
def unquote_colnames(colnames):
|
||||||
|
unq_colnames = []
|
||||||
|
for col in colnames:
|
||||||
|
m = self.db._adapter.REGEX_TABLE_DOT_FIELD.match(col)
|
||||||
|
if not m:
|
||||||
|
unq_colnames.append(col)
|
||||||
|
else:
|
||||||
|
unq_colnames.append('.'.join(m.groups()))
|
||||||
|
return unq_colnames
|
||||||
|
|
||||||
colnames = kwargs.get('colnames', self.colnames)
|
colnames = kwargs.get('colnames', self.colnames)
|
||||||
write_colnames = kwargs.get('write_colnames',True)
|
write_colnames = kwargs.get('write_colnames',True)
|
||||||
# a proper csv starting with the column names
|
# a proper csv starting with the column names
|
||||||
if write_colnames:
|
if write_colnames:
|
||||||
writer.writerow(colnames)
|
writer.writerow(unquote_colnames(colnames))
|
||||||
|
|
||||||
def none_exception(value):
|
def none_exception(value):
|
||||||
"""
|
"""
|
||||||
@@ -10896,10 +11035,11 @@ class Rows(object):
|
|||||||
for record in self:
|
for record in self:
|
||||||
row = []
|
row = []
|
||||||
for col in colnames:
|
for col in colnames:
|
||||||
if not REGEX_TABLE_DOT_FIELD.match(col):
|
m = self.db._adapter.REGEX_TABLE_DOT_FIELD.match(col)
|
||||||
|
if not m:
|
||||||
row.append(record._extra[col])
|
row.append(record._extra[col])
|
||||||
else:
|
else:
|
||||||
(t, f) = col.split('.')
|
(t, f) = m.groups()
|
||||||
field = self.db[t][f]
|
field = self.db[t][f]
|
||||||
if isinstance(record.get(t, None), (Row,dict)):
|
if isinstance(record.get(t, None), (Row,dict)):
|
||||||
value = record[t][f]
|
value = record[t][f]
|
||||||
|
|||||||
+4
-1
@@ -816,7 +816,10 @@ class Session(Storage):
|
|||||||
# if on GAE tickets go also in DB
|
# if on GAE tickets go also in DB
|
||||||
if settings.global_settings.web2py_runtime_gae:
|
if settings.global_settings.web2py_runtime_gae:
|
||||||
request.tickets_db = db
|
request.tickets_db = db
|
||||||
table_migrate = (masterapp == request.application)
|
if masterapp == request.application:
|
||||||
|
table_migrate = migrate
|
||||||
|
else:
|
||||||
|
table_migrate = False
|
||||||
tname = tablename + '_' + masterapp
|
tname = tablename + '_' + masterapp
|
||||||
table = db.get(tname, None)
|
table = db.get(tname, None)
|
||||||
Field = db.Field
|
Field = db.Field
|
||||||
|
|||||||
+12
-6
@@ -21,7 +21,7 @@ from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
|
|||||||
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE
|
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE
|
||||||
from gluon.html import URL, truncate_string, FIELDSET
|
from gluon.html import URL, truncate_string, FIELDSET
|
||||||
from gluon.dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
|
from gluon.dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
|
||||||
bar_encode, Reference, REGEX_TABLE_DOT_FIELD, Expression, SQLCustomType
|
bar_encode, Reference, Expression, SQLCustomType
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
from gluon.utils import md5_hash
|
from gluon.utils import md5_hash
|
||||||
from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
|
from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
|
||||||
@@ -45,6 +45,9 @@ except ImportError:
|
|||||||
table_field = re.compile('[\w_]+\.[\w_]+')
|
table_field = re.compile('[\w_]+\.[\w_]+')
|
||||||
widget_class = re.compile('^\w*')
|
widget_class = re.compile('^\w*')
|
||||||
|
|
||||||
|
def add_class(a,b):
|
||||||
|
return a+' '+b if a else b
|
||||||
|
|
||||||
def represent(field, value, record):
|
def represent(field, value, record):
|
||||||
f = field.represent
|
f = field.represent
|
||||||
if not callable(f):
|
if not callable(f):
|
||||||
@@ -334,7 +337,7 @@ class RadioWidget(OptionsWidget):
|
|||||||
|
|
||||||
|
|
||||||
attr = cls._attributes(field, {}, **attributes)
|
attr = cls._attributes(field, {}, **attributes)
|
||||||
attr['_class'] = attr.get('_class', 'web2py_radiowidget')
|
attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget')
|
||||||
|
|
||||||
requires = field.requires
|
requires = field.requires
|
||||||
if not isinstance(requires, (list, tuple)):
|
if not isinstance(requires, (list, tuple)):
|
||||||
@@ -398,7 +401,9 @@ class CheckboxesWidget(OptionsWidget):
|
|||||||
values = [str(value)]
|
values = [str(value)]
|
||||||
|
|
||||||
attr = cls._attributes(field, {}, **attributes)
|
attr = cls._attributes(field, {}, **attributes)
|
||||||
attr['_class'] = attr.get('_class', 'web2py_checkboxeswidget')
|
attr['_class'] = add_class(attr.get('_class'), 'web2py_checkboxeswidget')
|
||||||
|
|
||||||
|
label = attr.get('label',True)
|
||||||
|
|
||||||
requires = field.requires
|
requires = field.requires
|
||||||
if not isinstance(requires, (list, tuple)):
|
if not isinstance(requires, (list, tuple)):
|
||||||
@@ -439,7 +444,8 @@ class CheckboxesWidget(OptionsWidget):
|
|||||||
requires=attr.get('requires', None),
|
requires=attr.get('requires', None),
|
||||||
hideerror=True, _value=k,
|
hideerror=True, _value=k,
|
||||||
value=r_value),
|
value=r_value),
|
||||||
LABEL(v, _for='%s%s' % (field.name, k))))
|
LABEL(v, _for='%s%s' % (field.name, k))
|
||||||
|
if label else ''))
|
||||||
opts.append(child(tds))
|
opts.append(child(tds))
|
||||||
|
|
||||||
if opts:
|
if opts:
|
||||||
@@ -2893,7 +2899,7 @@ class SQLTABLE(TABLE):
|
|||||||
if not sqlrows:
|
if not sqlrows:
|
||||||
return
|
return
|
||||||
if not columns:
|
if not columns:
|
||||||
columns = sqlrows.colnames
|
columns = ['.'.join(sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD.match(c).groups()) for c in sqlrows.colnames]
|
||||||
if headers == 'fieldname:capitalize':
|
if headers == 'fieldname:capitalize':
|
||||||
headers = {}
|
headers = {}
|
||||||
for c in columns:
|
for c in columns:
|
||||||
@@ -3116,7 +3122,7 @@ class ExportClass(object):
|
|||||||
for record in self.rows:
|
for record in self.rows:
|
||||||
row = []
|
row = []
|
||||||
for col in self.rows.colnames:
|
for col in self.rows.colnames:
|
||||||
if not REGEX_TABLE_DOT_FIELD.match(col):
|
if not self.rows.db._adapter.REGEX_TABLE_DOT_FIELD.match(col):
|
||||||
row.append(record._extra[col])
|
row.append(record._extra[col])
|
||||||
else:
|
else:
|
||||||
(t, f) = col.split('.')
|
(t, f) = col.split('.')
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ def tearDownModule():
|
|||||||
class TestFields(unittest.TestCase):
|
class TestFields(unittest.TestCase):
|
||||||
|
|
||||||
def testFieldName(self):
|
def testFieldName(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Any table name is supported as long as underlying db does. The following code is ignored.
|
||||||
|
|
||||||
# Check that Fields cannot start with underscores
|
# Check that Fields cannot start with underscores
|
||||||
self.assertRaises(SyntaxError, Field, '_abc', 'string')
|
self.assertRaises(SyntaxError, Field, '_abc', 'string')
|
||||||
@@ -1394,6 +1397,137 @@ class TestRNameFields(unittest.TestCase):
|
|||||||
db.person.drop()
|
db.person.drop()
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuoting(unittest.TestCase):
|
||||||
|
# tests for complex table names
|
||||||
|
def testRun(self):
|
||||||
|
return
|
||||||
|
db = DAL(DEFAULT_URI, check_reserved=['all'])
|
||||||
|
|
||||||
|
t0 = db.define_table('A.table.with.dots and spaces',
|
||||||
|
Field('f', 'string'))
|
||||||
|
t1 = db.define_table('A.table',
|
||||||
|
Field('f_other', t0),
|
||||||
|
Field('words', 'text'))
|
||||||
|
|
||||||
|
blather = 'blah blah and so'
|
||||||
|
t0[0] = {'f': 'content'}
|
||||||
|
t1[0] = {'f_other': int(t0[1]['id']),
|
||||||
|
'words': blather}
|
||||||
|
|
||||||
|
|
||||||
|
r = db(t1['f_other']==t0.id).select()
|
||||||
|
self.assertEqual(r[0][db['A.table']].words, blather)
|
||||||
|
|
||||||
|
db.define_table('t0', Field('f0'))
|
||||||
|
db.define_table('t1', Field('f1'), Field('t0', db['t0']))
|
||||||
|
db.t0[0]=dict(f0=3)
|
||||||
|
db.t1[0]=dict(f1=3, t0=1)
|
||||||
|
|
||||||
|
rows=db(db.t0.id==db.t1.t0).select()
|
||||||
|
self.assertEqual(rows[0].t1.t0, rows[0].t0.id)
|
||||||
|
if DEFAULT_URI.startswith('mssql'):
|
||||||
|
#there's no drop cascade in mssql
|
||||||
|
t1.drop()
|
||||||
|
t0.drop()
|
||||||
|
else:
|
||||||
|
t0.drop('cascade')
|
||||||
|
t1.drop()
|
||||||
|
|
||||||
|
db.t1.drop()
|
||||||
|
db.t0.drop()
|
||||||
|
|
||||||
|
# tests for case sensitivity
|
||||||
|
def testCase(self):
|
||||||
|
return
|
||||||
|
db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False)
|
||||||
|
if DEFAULT_URI.startswith('mssql'):
|
||||||
|
#multiple cascade gotcha
|
||||||
|
for key in ['reference','reference FK']:
|
||||||
|
db._adapter.types[key]=db._adapter.types[key].replace(
|
||||||
|
'%(on_delete_action)s','NO ACTION')
|
||||||
|
|
||||||
|
# test table case
|
||||||
|
t0 = db.define_table('B',
|
||||||
|
Field('f', 'string'))
|
||||||
|
try:
|
||||||
|
t1 = db.define_table('b',
|
||||||
|
Field('B', t0),
|
||||||
|
Field('words', 'text'))
|
||||||
|
except Exception, e:
|
||||||
|
# An error is expected when database does not support case
|
||||||
|
# sensitive entity names.
|
||||||
|
if DEFAULT_URI.startswith('sqlite:'):
|
||||||
|
self.assertTrue(isinstance(e, db._adapter.driver.OperationalError))
|
||||||
|
return
|
||||||
|
raise e
|
||||||
|
|
||||||
|
blather = 'blah blah and so'
|
||||||
|
t0[0] = {'f': 'content'}
|
||||||
|
t1[0] = {'B': int(t0[1]['id']),
|
||||||
|
'words': blather}
|
||||||
|
|
||||||
|
r = db(db.B.id==db.b.B).select()
|
||||||
|
|
||||||
|
self.assertEqual(r[0].b.words, blather)
|
||||||
|
|
||||||
|
t1.drop()
|
||||||
|
t0.drop()
|
||||||
|
|
||||||
|
# test field case
|
||||||
|
try:
|
||||||
|
t0 = db.define_table('table is a test',
|
||||||
|
Field('a_a'),
|
||||||
|
Field('a_A'))
|
||||||
|
except Exception, e:
|
||||||
|
# some db does not support case sensitive field names mysql is one of them.
|
||||||
|
if DEFAULT_URI.startswith('mysql:'):
|
||||||
|
db.rollback()
|
||||||
|
return
|
||||||
|
raise e
|
||||||
|
|
||||||
|
t0[0] = dict(a_a = 'a_a', a_A='a_A')
|
||||||
|
|
||||||
|
self.assertEqual(t0[1].a_a, 'a_a')
|
||||||
|
self.assertEqual(t0[1].a_A, 'a_A')
|
||||||
|
|
||||||
|
t0.drop()
|
||||||
|
|
||||||
|
def testPKFK(self):
|
||||||
|
|
||||||
|
# test primary keys
|
||||||
|
|
||||||
|
db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False)
|
||||||
|
if DEFAULT_URI.startswith('mssql'):
|
||||||
|
#multiple cascade gotcha
|
||||||
|
for key in ['reference','reference FK']:
|
||||||
|
db._adapter.types[key]=db._adapter.types[key].replace(
|
||||||
|
'%(on_delete_action)s','NO ACTION')
|
||||||
|
# test table without surrogate key. Length must is limited to
|
||||||
|
# 100 because of MySQL limitations: it cannot handle more than
|
||||||
|
# 767 bytes in unique keys.
|
||||||
|
|
||||||
|
t0 = db.define_table('t0', Field('Code', length=100), primarykey=['Code'])
|
||||||
|
t2 = db.define_table('t2', Field('f'), Field('t0_Code', 'reference t0'))
|
||||||
|
t3 = db.define_table('t3', Field('f', length=100), Field('t0_Code', t0.Code), primarykey=['f'])
|
||||||
|
t4 = db.define_table('t4', Field('f', length=100), Field('t0', t0), primarykey=['f'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
t5 = db.define_table('t5', Field('f', length=100), Field('t0', 'reference no_table_wrong_reference'), primarykey=['f'])
|
||||||
|
except Exception, e:
|
||||||
|
self.assertTrue(isinstance(e, KeyError))
|
||||||
|
|
||||||
|
if DEFAULT_URI.startswith('mssql'):
|
||||||
|
#there's no drop cascade in mssql
|
||||||
|
t3.drop()
|
||||||
|
t4.drop()
|
||||||
|
t2.drop()
|
||||||
|
t0.drop()
|
||||||
|
else:
|
||||||
|
t0.drop('cascade')
|
||||||
|
t2.drop()
|
||||||
|
t3.drop()
|
||||||
|
t4.drop()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
tearDownModule()
|
tearDownModule()
|
||||||
+3
-3
@@ -2915,7 +2915,7 @@ class Auth(object):
|
|||||||
formname='reset_password', dbio=False,
|
formname='reset_password', dbio=False,
|
||||||
onvalidation=onvalidation,
|
onvalidation=onvalidation,
|
||||||
hideerror=self.settings.hideerror):
|
hideerror=self.settings.hideerror):
|
||||||
user = table_user(**{userfield:form.vars.email})
|
user = table_user(**{userfield:form.vars.get(userfield)})
|
||||||
if not user:
|
if not user:
|
||||||
session.flash = self.messages['invalid_%s' % userfield]
|
session.flash = self.messages['invalid_%s' % userfield]
|
||||||
redirect(self.url(args=request.args),
|
redirect(self.url(args=request.args),
|
||||||
@@ -5395,7 +5395,7 @@ class Wiki(object):
|
|||||||
if (auth.user and
|
if (auth.user and
|
||||||
check_credentials(current.request, gae_login=False) and
|
check_credentials(current.request, gae_login=False) and
|
||||||
not 'wiki_editor' in auth.user_groups.values() and
|
not 'wiki_editor' in auth.user_groups.values() and
|
||||||
self.settings.groups is None):
|
self.settings.groups == auth.user_groups.values()):
|
||||||
group = db.auth_group(role='wiki_editor')
|
group = db.auth_group(role='wiki_editor')
|
||||||
gid = group.id if group else db.auth_group.insert(
|
gid = group.id if group else db.auth_group.insert(
|
||||||
role='wiki_editor')
|
role='wiki_editor')
|
||||||
@@ -5523,7 +5523,7 @@ class Wiki(object):
|
|||||||
url = URL(args=('_edit', slug))
|
url = URL(args=('_edit', slug))
|
||||||
return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn"))
|
return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn"))
|
||||||
else:
|
else:
|
||||||
html = page.html if not force_render else self.get_renderer(page)
|
html = page.html if not force_render else self.get_renderer()(page)
|
||||||
content = XML(self.fix_hostname(html))
|
content = XML(self.fix_hostname(html))
|
||||||
return dict(title=page.title,
|
return dict(title=page.title,
|
||||||
slug=page.slug,
|
slug=page.slug,
|
||||||
|
|||||||
+4
-1
@@ -1224,7 +1224,10 @@ end tell
|
|||||||
if not options.nobanner:
|
if not options.nobanner:
|
||||||
print 'please visit:'
|
print 'please visit:'
|
||||||
print '\t', url
|
print '\t', url
|
||||||
print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid()
|
if sys.platform.startswith('win'):
|
||||||
|
print 'use "taskkill /f /pid %i" to shutdown the web2py server' % os.getpid()
|
||||||
|
else:
|
||||||
|
print 'use "kill -SIGTERM %i" to shutdown the web2py server' % os.getpid()
|
||||||
|
|
||||||
# enhance linecache.getline (used by debugger) to look at the source file
|
# enhance linecache.getline (used by debugger) to look at the source file
|
||||||
# if the line was not found (under py2exe & when file was modified)
|
# if the line was not found (under py2exe & when file was modified)
|
||||||
|
|||||||
Reference in New Issue
Block a user