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)
|
||||
|
||||
|
||||
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')
|
||||
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']:
|
||||
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):
|
||||
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:
|
||||
def write(self, data):
|
||||
pass
|
||||
def close(self):
|
||||
pass
|
||||
return tmp()
|
||||
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})
|
||||
else:
|
||||
file_details = dict(app=request.args[0],
|
||||
lineno=request.vars.lineno or 1,
|
||||
editor_settings=preferences,
|
||||
filename=filename,
|
||||
realfilename=realfilename,
|
||||
@@ -773,6 +784,35 @@ def edit():
|
||||
else:
|
||||
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():
|
||||
"""
|
||||
@@ -1053,11 +1093,12 @@ def design():
|
||||
|
||||
#Get crontab
|
||||
cronfolder = apath('%s/cron' % app, r=request)
|
||||
if not os.path.exists(cronfolder):
|
||||
os.mkdir(cronfolder)
|
||||
crontab = apath('%s/cron/crontab' % app, r=request)
|
||||
if not os.path.exists(crontab):
|
||||
safe_write(crontab, '#crontab')
|
||||
if not is_gae:
|
||||
if not os.path.exists(cronfolder):
|
||||
os.mkdir(cronfolder)
|
||||
if not os.path.exists(crontab):
|
||||
safe_write(crontab, '#crontab')
|
||||
|
||||
plugins = []
|
||||
|
||||
@@ -1209,7 +1250,6 @@ def plugin():
|
||||
languages=languages,
|
||||
crontab=crontab)
|
||||
|
||||
|
||||
def create_file():
|
||||
""" Create files handler """
|
||||
if request.vars and not request.vars.token == session.token:
|
||||
@@ -1220,6 +1260,8 @@ def create_file():
|
||||
app = get_app(request.vars.app)
|
||||
path = abspath(request.vars.location)
|
||||
else:
|
||||
if request.vars.dir:
|
||||
request.vars.location += request.vars.dir + '/'
|
||||
app = get_app(name=request.vars.location.split('/')[0])
|
||||
path = apath(request.vars.location, r=request)
|
||||
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
||||
@@ -1347,7 +1389,11 @@ def create_file():
|
||||
|
||||
safe_write(full_filename, text)
|
||||
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):]))
|
||||
vars = {}
|
||||
if request.vars.id:
|
||||
@@ -1356,13 +1402,51 @@ def create_file():
|
||||
vars['app'] = request.vars.app
|
||||
redirect(URL('edit',
|
||||
args=[os.path.join(request.vars.location, filename)], vars=vars))
|
||||
|
||||
except Exception, e:
|
||||
if not isinstance(e, HTTP):
|
||||
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():
|
||||
""" File uploading handler """
|
||||
if request.vars and not request.vars.token == session.token:
|
||||
@@ -1422,8 +1506,11 @@ def errors():
|
||||
import hashlib
|
||||
|
||||
app = get_app()
|
||||
|
||||
method = request.args(1) or 'new'
|
||||
if is_gae:
|
||||
method = 'dbold' if ('old' in
|
||||
(request.args(1) or '')) else 'dbnew'
|
||||
else:
|
||||
method = request.args(1) or 'new'
|
||||
db_ready = {}
|
||||
db_ready['status'] = get_ticket_storage(app)
|
||||
db_ready['errmessage'] = T(
|
||||
@@ -1490,32 +1577,30 @@ def errors():
|
||||
for fn in tk_db(tk_table.id > 0).select():
|
||||
try:
|
||||
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.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.sort(key=operator.itemgetter(0), reverse=True)
|
||||
|
||||
return dict(errors=[x[1] for x in decorated], app=app, method=method)
|
||||
return dict(errors=[x[1] for x in decorated], app=app,
|
||||
method=method, db_ready=db_ready)
|
||||
|
||||
elif method == 'dbold':
|
||||
tk_db, tk_table = get_ticket_storage(app)
|
||||
@@ -1523,16 +1608,18 @@ def errors():
|
||||
if item[:7] == 'delete_':
|
||||
tk_db(tk_table.ticket_id == item[7:]).delete()
|
||||
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_]
|
||||
times = dict(
|
||||
[(row.ticket_id, row.created_datetime) for row in tickets_])
|
||||
|
||||
return dict(app=app, tickets=tickets, method=method, times=times)
|
||||
times = dict([(row.ticket_id, row.created_datetime) for
|
||||
row in tickets_])
|
||||
return dict(app=app, tickets=tickets, method=method,
|
||||
times=times, db_ready=db_ready)
|
||||
|
||||
else:
|
||||
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
|
||||
if item[:7] == 'delete_' and (not item == "delete_all}"):
|
||||
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):
|
||||
db_string = open(ticket_file).read()
|
||||
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:
|
||||
return False
|
||||
tickets_table = 'web2py_ticket'
|
||||
@@ -1804,24 +1894,7 @@ def plugins():
|
||||
"public/api.json/action/list/content/Package?package" +
|
||||
"_type=plugin&search_index=false").read()
|
||||
session.plugins = loads_json(rawlist)
|
||||
plugins = TABLE(
|
||||
*[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))
|
||||
return dict(plugins=session.plugins["results"], app=request.args(0))
|
||||
|
||||
def install_plugin():
|
||||
app = request.args(0)
|
||||
|
||||
@@ -3,6 +3,7 @@ import cStringIO
|
||||
import gluon.contrib.shell
|
||||
import code
|
||||
import thread
|
||||
import cgi
|
||||
from gluon.shell import env
|
||||
|
||||
if DEMO_MODE or MULTI_USER_MODE:
|
||||
@@ -40,7 +41,7 @@ def callback():
|
||||
k = len(session['commands:' + app]) - 1
|
||||
#output = PRE(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():
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# coding: utf8
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'!langcode!': 'es',
|
||||
'!langname!': 'Español',
|
||||
@@ -9,6 +9,7 @@
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
'(requires internet access, experimental)': '(requiere acceso a internet, experimental)',
|
||||
'(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\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
||||
@@ -122,6 +123,7 @@
|
||||
'delete plugin': 'eliminar plugin',
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Elimine este fichero (se le pedirá confirmación)',
|
||||
'Delete:': 'Elimine:',
|
||||
'Demo': 'Demo',
|
||||
'Deploy': 'Deploy',
|
||||
'Deploy on Google App Engine': 'Instale en Google App Engine',
|
||||
'Deploy to OpenShift': 'Instale en OpenShift',
|
||||
@@ -134,6 +136,7 @@
|
||||
'direction: ltr': 'direction: ltr',
|
||||
'Disable': 'Deshabilitar',
|
||||
'docs': 'docs',
|
||||
'Docs': 'Docs',
|
||||
'Done!': 'Done!',
|
||||
'done!': 'listo!',
|
||||
'Download': 'Descargar',
|
||||
@@ -157,6 +160,7 @@
|
||||
'Editing myemail': 'Editing myemail',
|
||||
'Editing rbare': 'Editing rbare',
|
||||
'Editing ul': 'Editing ul',
|
||||
'Enable': 'Enable',
|
||||
'Enterprise Web Framework': 'Armazón Empresarial para Internet',
|
||||
'Error': 'Error',
|
||||
'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"',
|
||||
@@ -190,6 +194,8 @@
|
||||
'First name': 'Nombre',
|
||||
'Frames': 'Frames',
|
||||
'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',
|
||||
'graph model': 'graph model',
|
||||
'Group ID': 'ID de Grupo',
|
||||
@@ -213,6 +219,7 @@
|
||||
'internal error': 'error interno',
|
||||
'Internal State': 'Estado Interno',
|
||||
'Invalid action': 'Acción inválida',
|
||||
'Invalid application name': 'Invalid application name',
|
||||
'Invalid email': 'Correo inválido',
|
||||
'invalid password': 'contraseña inválida',
|
||||
'invalid password.': 'invalid password.',
|
||||
@@ -230,6 +237,7 @@
|
||||
'Last name': 'Apellido',
|
||||
'Last saved on:': 'Guardado en:',
|
||||
'License for': 'Licencia para',
|
||||
'License:': 'License:',
|
||||
'lists by ticket': 'lists by ticket',
|
||||
'loading...': 'cargando...',
|
||||
'locals': 'locals',
|
||||
@@ -250,6 +258,7 @@
|
||||
'new application "%s" created': 'nueva aplicación "%s" creada',
|
||||
'New application wizard': 'Asistente para nueva aplicación',
|
||||
'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.dialog.w2p': 'New plugin installed: web2py.plugin.dialog.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',
|
||||
'Replace': 'Reemplazar',
|
||||
'Replace All': 'Reemplazar todos',
|
||||
'Repository (%s)': 'Repository (%s)',
|
||||
'Repository: %s': 'Repository: %s',
|
||||
'request': 'request',
|
||||
'Resolve Conflict file': 'archivo Resolución de Conflicto',
|
||||
'response': 'response',
|
||||
@@ -329,6 +340,8 @@
|
||||
'Save file: %s': 'Guardar: %s',
|
||||
'Save via Ajax': 'Guardar via Ajax',
|
||||
'Saved file hash:': 'Hash del archivo guardado:',
|
||||
'Screenshot %s': 'Screenshot %s',
|
||||
'Screenshots': 'Screenshots',
|
||||
'selected': 'seleccionado(s)',
|
||||
'session': 'session',
|
||||
'session expired': 'sesión expirada',
|
||||
@@ -336,6 +349,7 @@
|
||||
'shell': 'shell',
|
||||
'Site': 'sitio',
|
||||
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
|
||||
'source : filesystem': 'source : filesystem',
|
||||
'Start searching': 'Iniciar búsqueda',
|
||||
'Start wizard': 'Iniciar asistente',
|
||||
'state': 'estado',
|
||||
@@ -355,8 +369,8 @@
|
||||
'test': 'probar',
|
||||
'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 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 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':
|
||||
is_mobile = False
|
||||
else:
|
||||
is_mobile = request.user_agent().is_mobile
|
||||
is_mobile = request.user_agent().get('is_mobile',False)
|
||||
|
||||
if DEMO_MODE:
|
||||
session.authorized = True
|
||||
|
||||
@@ -24,7 +24,7 @@ def button_enable(href, app):
|
||||
return A(label, _class='button btn', _id=id, callback=href, target=id)
|
||||
|
||||
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)
|
||||
else:
|
||||
ret = A(SPAN(label), _class='button special btn btn-inverse', _href=href)
|
||||
@@ -37,4 +37,4 @@ def searchbox(elementid):
|
||||
return SPAN(LABEL(IMG(_id="search_start", _src=URL('static', 'images/search.png'), _alt=T('filter')),
|
||||
_class='icon', _for=elementid), ' ',
|
||||
INPUT(_id=elementid, _type='text', _size=12, _class="input-medium"),
|
||||
_class="searchbox")
|
||||
_class="searchbox")
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
/* TODO rename this file as web2py-editor.css */
|
||||
/* Fullscreen */
|
||||
.CodeMirror-fullscreen {
|
||||
z-index: 1030;
|
||||
}
|
||||
.CodeMirror {
|
||||
border-top: 1px solid #ddd;
|
||||
/*border-left: 1px solid #ddd;*/
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* BREAKPOINTS */
|
||||
|
||||
@@ -36,3 +42,15 @@
|
||||
/*.nav-tabs>li {
|
||||
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({
|
||||
type: "GET",
|
||||
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_body = '<div id="' + json['id'] + '" class="tab-pane fade in " >' + json['plain_html'] + '</div>';
|
||||
if(json['force'] === false) {
|
||||
$('#filesTab').append($(tab_header));
|
||||
$('#myTabContent').append($(tab_body));
|
||||
$('#myTabContent').append($(tab_body)); // First load the body
|
||||
$('#filesTab').append($(tab_header)); // Then load the header which trigger the shown event
|
||||
} else {
|
||||
$('#' + json['id']).html($(tab_body));
|
||||
}
|
||||
}
|
||||
$("a[href='#" + json['id'] + "']").click();
|
||||
$("a[href='#" + json['id'] + "']").trigger('click', lineno);
|
||||
}
|
||||
},
|
||||
error: function (x) {
|
||||
|
||||
@@ -485,8 +485,10 @@
|
||||
el.addClass('disabled');
|
||||
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...";
|
||||
/*store enabled state*/
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
/*store enabled state if not already disabled */
|
||||
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
|
||||
* two lines with this one
|
||||
* el.data('w2p_disable_with', el[method]());
|
||||
@@ -633,7 +635,9 @@
|
||||
if(disable_with == undefined) {
|
||||
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.prop('disabled', true);
|
||||
});
|
||||
@@ -647,7 +651,10 @@
|
||||
form.find(web2py.enableSelector).each(function () {
|
||||
var element = $(this),
|
||||
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);
|
||||
});
|
||||
},
|
||||
@@ -659,7 +666,18 @@
|
||||
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
||||
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 */
|
||||
@@ -679,7 +697,7 @@
|
||||
/* compatibility code - start */
|
||||
ajax = jQuery.web2py.ajax;
|
||||
web2py_component = jQuery.web2py.component;
|
||||
web2py_websocket = jQuery.web2py.websocket;
|
||||
web2py_websocket = jQuery.web2py.web2py_websocket;
|
||||
web2py_ajax_page = jQuery.web2py.ajax_page;
|
||||
/*needed for IS_STRONG(entropy)*/
|
||||
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
{{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 -->
|
||||
{{
|
||||
def shortcut(combo, description):
|
||||
@@ -45,6 +73,7 @@
|
||||
<script src="{{=cm}}/addon/fold/xml-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/comment/comment.js"></script>
|
||||
<link rel="stylesheet" href="{{=cm}}/addon/fold/foldgutter.css">
|
||||
<script src="{{=cm}}/emmet.min.js"></script>
|
||||
<script src="{{=js_url}}/ajax_editor.js"></script>
|
||||
@@ -52,12 +81,17 @@
|
||||
<link rel="stylesheet" href="{{=css_url}}/web2py-codemirror.css">
|
||||
<script type="text/javascript">
|
||||
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 editor = $(tab_id + " textarea").data('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();
|
||||
if (lineno !== undefined) {
|
||||
editor.setCursor(lineno);
|
||||
editor.centerOnCursor();
|
||||
}
|
||||
}
|
||||
var n_li = $('#filesTab li').length;
|
||||
$.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) {
|
||||
e.preventDefault();
|
||||
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*/
|
||||
@@ -164,7 +199,7 @@ $(document).on('click', 'a.font_button', function (e) {
|
||||
});
|
||||
|
||||
{{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}}
|
||||
|
||||
</script>
|
||||
@@ -197,27 +232,22 @@ $(document).on('click', 'a.font_button', function (e) {
|
||||
<div class="files-menu">
|
||||
<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>
|
||||
{{dirs=[{'name':'models', 'reg':'.*\.py$'},
|
||||
{'name':'controllers', 'reg':'.*\.py$'},
|
||||
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
||||
{'name':'modules', 'reg':'.*\.py$'}, ]}}
|
||||
{{auto_complete_list=[]}}
|
||||
{{for dir in dirs:}}
|
||||
<li class="nav-header component" onclick="collapse('{{="%s_files" % dir['name']}}');">{{=dir['name']}}</li>
|
||||
<li id="{{="%s_files" % dir['name']}}">
|
||||
<ul class="nav nav-list small-font">
|
||||
{{for f in listfiles(app, dir['name'], regexp=dir['reg'] ):}}
|
||||
{{id="%s__" % dir['name'] + f.replace('.','__')}}
|
||||
{{current_file = request.args(len(request.args) - 1)}}
|
||||
<li class="{{#='active' if current_file==f else ''}}" style="overflow:hidden">
|
||||
{{a_tag=editfile(dir['name'], f, dict(id=id))}}
|
||||
{{=a_tag}}
|
||||
{{auto_complete_list.append({'value':f, 'tokens':f.split('/'), 'a_tag':a_tag})}}
|
||||
</li>
|
||||
{{pass}}
|
||||
</ul>
|
||||
</li>
|
||||
{{pass}}
|
||||
<br />
|
||||
<div>
|
||||
<button onclick="jQuery('#form').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
|
||||
<br />
|
||||
<div id="form">
|
||||
<div>
|
||||
{{=file_create_form('%s/' % app, '')}}
|
||||
</div>
|
||||
</div>
|
||||
<script>jQuery('#form').slideToggle()</script>
|
||||
</div>
|
||||
<br />
|
||||
<div id="files_menu">
|
||||
{{=LOAD('default', 'files_menu', vars={'app':app}, ajax=True)}}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -227,13 +257,47 @@ $(document).on('click', 'a.font_button', function (e) {
|
||||
<div id="myTabContent" class="tab-content">
|
||||
</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>
|
||||
{{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>
|
||||
$(document).ready(function() {
|
||||
var filesMenu = $('#files');
|
||||
var ow = filesMenu.outerWidth();
|
||||
filesMenu.width(ow);
|
||||
$('#files').css('left', '-'+ow+'px');
|
||||
$.web2py.trap_form('url', 'form');
|
||||
$('#form form').addClass('no_trap'); // Let to reuse the same form
|
||||
});
|
||||
</script>
|
||||
<!-- Typeahead scripts here so the page load faster -->
|
||||
|
||||
@@ -74,15 +74,15 @@
|
||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||
URL(c='debug', f='toggle_breakpoint')))}}, sel);
|
||||
});
|
||||
function makeMarker() {
|
||||
var marker = document.createElement("div");
|
||||
marker.style.color = "#822";
|
||||
marker.innerHTML = "●";
|
||||
marker.className = "breakpoint";
|
||||
return marker;
|
||||
}
|
||||
function makeMarker() {
|
||||
var marker = document.createElement("div");
|
||||
marker.style.color = "#822";
|
||||
marker.innerHTML = "●";
|
||||
marker.className = "breakpoint";
|
||||
return marker;
|
||||
}
|
||||
|
||||
{{if filetype in ('html', 'javascript', 'css'):}}
|
||||
{{if filetype in ('html', 'javascript', 'css'):}}
|
||||
// must be here or break emmet/zencoding
|
||||
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
||||
function(instance) {
|
||||
@@ -94,26 +94,27 @@
|
||||
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
}
|
||||
{{pass}}
|
||||
{{if filetype=='python':}}
|
||||
// must be here or break emmet/zencoding for python
|
||||
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
||||
function(instance) {
|
||||
doClickSave();};
|
||||
CodeMirror.defaults.extraKeys["Ctrl-Space"] = "autocomplete";
|
||||
CodeMirror.defaults.extraKeys["Tab"] = "indentMore";
|
||||
CodeMirror.defaults.extraKeys["Shift-Tab"] = "indentLess";
|
||||
CodeMirror.defaults.extraKeys["Ctrl-F11"] = function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
}
|
||||
//for autocomplete
|
||||
CodeMirror.commands.autocomplete = function(cm) {
|
||||
CodeMirror.showHint(cm, CodeMirror.pythonHint);
|
||||
}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{if filetype=='python':}}
|
||||
// must be here or break emmet/zencoding for python
|
||||
CodeMirror.defaults.extraKeys["Ctrl-S"] =
|
||||
function(instance) {
|
||||
doClickSave();};
|
||||
CodeMirror.defaults.extraKeys["Ctrl-Space"] = "autocomplete";
|
||||
CodeMirror.defaults.extraKeys["Tab"] = "indentMore";
|
||||
CodeMirror.defaults.extraKeys["Shift-Tab"] = "indentLess";
|
||||
CodeMirror.defaults.extraKeys["Ctrl-F11"] = function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
CodeMirror.defaults.extraKeys["Shift-Esc"] = function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
}
|
||||
//for autocomplete
|
||||
CodeMirror.commands.autocomplete = function(cm) {
|
||||
CodeMirror.showHint(cm, CodeMirror.pythonHint);
|
||||
}
|
||||
{{pass}}
|
||||
CodeMirror.defaults.extraKeys["Ctrl-/"] = "toggleComment";
|
||||
store_changes_function = function(instance, changeObj) {
|
||||
jQuery(instance).data('saved', false);
|
||||
instance.off("change", store_changes_function);
|
||||
@@ -128,6 +129,19 @@
|
||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||
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>
|
||||
|
||||
<div class="editor-bar-bottom" style="margin-top:9px;">
|
||||
@@ -141,34 +155,26 @@
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="help alert alert-block alert-info">
|
||||
{{if filetype=='html':}}
|
||||
{{if filetype=='html':}}
|
||||
<h3>{{=T('Key bindings for ZenCoding Plugin')}}</h3>
|
||||
<ul class="keybindings unstyled">
|
||||
{{=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:}}
|
||||
{{else:}}
|
||||
<h3>{{=T("Key bindings")}}</h3>
|
||||
{{pass}}
|
||||
<ul class="keybindings unstyled">
|
||||
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
|
||||
{{=shortcut('Ctrl+F11', T('Toggle 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-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'))}}
|
||||
</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}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{{for files in result_files:}}
|
||||
{{=files}}
|
||||
{{pass}}
|
||||
@@ -1,5 +1,21 @@
|
||||
{{extend 'layout.html'}}
|
||||
{{=H3("Available plugins")}}
|
||||
{{=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
|
||||
============== -->
|
||||
{{block footer}}
|
||||
<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}}
|
||||
{{if hasattr(T,'get_possible_languages_info'):}}
|
||||
@@ -68,6 +69,7 @@
|
||||
</span>{{pass}}
|
||||
</p>
|
||||
</footer><!-- /#footer -->
|
||||
{{end}}
|
||||
|
||||
<!-- BS JAVASCRIPT
|
||||
====================== -->
|
||||
|
||||
@@ -2,40 +2,41 @@
|
||||
{{block sectionclass}}shell{{end}}
|
||||
<!-- begin "shell" block -->
|
||||
<div id="wrapper">
|
||||
<div class="row-fluid">
|
||||
<div class="output-wrapper span8">
|
||||
<textarea id="output" readonly="readonly">web2py Shell {{=request.env.web2py_version}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<form id="form" action="{{=URL(r=request,f='callback',args=app)}}" method="get" class="span8">
|
||||
<div id="shellwrapper">
|
||||
<div class="prompt-wrapper">
|
||||
<div class="prompt-container">
|
||||
<textarea class="prompt" name="statement" id="statement"></textarea>
|
||||
</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.')}}">
|
||||
{{=helpicon()}}
|
||||
<span>Type some Python code in here and hit Return (Enter) to execute it.</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="caret"><span>>>></span></div>
|
||||
<div id="autoscroll">autoscroll</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid clearfix">
|
||||
<div class="help alert alert-info span6">
|
||||
<ul>
|
||||
<li>Using the shell may lock the database to other users of this app.</li>
|
||||
<li>Each db statement is automatically committed.</li>
|
||||
<li>Creating new tables dynamically is not allowed.</li>
|
||||
<li>Models are automatically imported in the shell.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="output-wrapper span8">
|
||||
<textarea id="output" readonly="readonly">web2py Shell {{=request.env.web2py_version}}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<form id="form" action="{{=URL(r=request,f='callback',args=app)}}" method="get" class="span8">
|
||||
<div id="shellwrapper">
|
||||
<div class="prompt-wrapper">
|
||||
<div class="prompt-container">
|
||||
<textarea class="prompt" name="statement" id="statement"></textarea>
|
||||
</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.')}}">
|
||||
{{=helpicon()}}
|
||||
<span>Type some Python code in here and hit Return (Enter) to execute it.</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="caret"><span>>>></span></div>
|
||||
<div id="autoscroll">autoscroll</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid clearfix">
|
||||
<div class="help alert alert-info span6">
|
||||
<ul>
|
||||
<li>Using the shell may lock the database to other users of this app.</li>
|
||||
<li>Each db statement is automatically committed.</li>
|
||||
<li>Creating new tables dynamically is not allowed.</li>
|
||||
<li>Models are automatically imported in the shell.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{{=URL('static', 'js/autoscroll.js')}}"></script>
|
||||
|
||||
@@ -113,4 +114,4 @@ jQuery(document).ready(function(){
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- end "shell" block -->
|
||||
<!-- end "shell" block -->
|
||||
|
||||
@@ -485,8 +485,10 @@
|
||||
el.addClass('disabled');
|
||||
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...";
|
||||
/*store enabled state*/
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
/*store enabled state if not already disabled */
|
||||
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
|
||||
* two lines with this one
|
||||
* el.data('w2p_disable_with', el[method]());
|
||||
@@ -633,7 +635,9 @@
|
||||
if(disable_with == undefined) {
|
||||
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.prop('disabled', true);
|
||||
});
|
||||
@@ -647,7 +651,10 @@
|
||||
form.find(web2py.enableSelector).each(function () {
|
||||
var element = $(this),
|
||||
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);
|
||||
});
|
||||
},
|
||||
@@ -659,7 +666,18 @@
|
||||
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
||||
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 */
|
||||
@@ -679,7 +697,7 @@
|
||||
/* compatibility code - start */
|
||||
ajax = jQuery.web2py.ajax;
|
||||
web2py_component = jQuery.web2py.component;
|
||||
web2py_websocket = jQuery.web2py.websocket;
|
||||
web2py_websocket = jQuery.web2py.web2py_websocket;
|
||||
web2py_ajax_page = jQuery.web2py.ajax_page;
|
||||
/*needed for IS_STRONG(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 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="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>
|
||||
|
||||
@@ -485,8 +485,10 @@
|
||||
el.addClass('disabled');
|
||||
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...";
|
||||
/*store enabled state*/
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
/*store enabled state if not already disabled */
|
||||
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
|
||||
* two lines with this one
|
||||
* el.data('w2p_disable_with', el[method]());
|
||||
@@ -633,7 +635,9 @@
|
||||
if(disable_with == undefined) {
|
||||
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.prop('disabled', true);
|
||||
});
|
||||
@@ -647,7 +651,10 @@
|
||||
form.find(web2py.enableSelector).each(function () {
|
||||
var element = $(this),
|
||||
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);
|
||||
});
|
||||
},
|
||||
@@ -659,7 +666,18 @@
|
||||
el.on('ajax:complete', 'form[data-w2p_target]', function (e) {
|
||||
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 */
|
||||
@@ -679,7 +697,7 @@
|
||||
/* compatibility code - start */
|
||||
ajax = jQuery.web2py.ajax;
|
||||
web2py_component = jQuery.web2py.component;
|
||||
web2py_websocket = jQuery.web2py.websocket;
|
||||
web2py_websocket = jQuery.web2py.web2py_websocket;
|
||||
web2py_ajax_page = jQuery.web2py.ajax_page;
|
||||
/*needed for IS_STRONG(entropy)*/
|
||||
web2py_validate_entropy = jQuery.web2py.validate_entropy;
|
||||
|
||||
@@ -65,8 +65,8 @@ skip_files: |
|
||||
(.*\.py[co])|
|
||||
(.*/RCS/.*)|
|
||||
(\..*)|
|
||||
(applications/(admin|examples)/.*)|
|
||||
((admin|examples|welcome)\.(w2p|tar))|
|
||||
(applications/examples/.*)|
|
||||
((examples|welcome)\.(w2p|tar))|
|
||||
(applications/.*?/(cron|databases|errors|cache|sessions)/.*)|
|
||||
((logs|scripts)/.*)|
|
||||
(anyserver\.py)|
|
||||
|
||||
@@ -159,6 +159,8 @@ def extension(url):
|
||||
|
||||
def expand_one(url, cdict):
|
||||
# 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:
|
||||
r = cdict[url]
|
||||
else:
|
||||
|
||||
@@ -551,7 +551,7 @@ regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
|
||||
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_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_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)
|
||||
@@ -648,7 +648,9 @@ def autolinks_simple(url):
|
||||
image, video or audio tag
|
||||
"""
|
||||
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
|
||||
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
|
||||
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 proto+':'+url
|
||||
|
||||
def email_simple(email):
|
||||
return '<a href="mailto:%s">%s</a>' % (email, email)
|
||||
|
||||
def render(text,
|
||||
extra={},
|
||||
allowed={},
|
||||
|
||||
@@ -258,7 +258,7 @@ def run(history, statement, env={}):
|
||||
if not name.startswith('__'):
|
||||
try:
|
||||
history.set_global(name, val)
|
||||
except TypeError, ex:
|
||||
except (TypeError, cPickle.PicklingError), ex:
|
||||
UNPICKLABLE_TYPES.append(type(val))
|
||||
history.add_unpicklable(statement, new_globals.keys())
|
||||
|
||||
|
||||
@@ -858,8 +858,8 @@ class Sheet:
|
||||
sorted_headers = [TH(), ] + \
|
||||
[TH(header[1]) for header in sorted(unsorted_headers)]
|
||||
table.insert(0, TR(*sorted_headers,
|
||||
**{_class: "%s_fieldnames" %
|
||||
attributes["_class"]}))
|
||||
**{'_class': "%s_fieldnames" %
|
||||
attributes["_class"]}))
|
||||
else:
|
||||
data = SCRIPT(""" // web2py Spreadsheets: no db data.""")
|
||||
|
||||
|
||||
+303
-163
@@ -252,7 +252,8 @@ THREAD_LOCAL = threading.local()
|
||||
REGEX_TYPE = re.compile('^([\w\_\:]+)')
|
||||
REGEX_DBNAME = re.compile('^(\w+)(\:\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_CLEANUP_FN = re.compile('[\'"\s;]+')
|
||||
REGEX_UNPACK = re.compile('(?<!\|)\|(?!\|)')
|
||||
@@ -670,12 +671,27 @@ class ConnectionPool(object):
|
||||
break
|
||||
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
|
||||
###################################################################################
|
||||
|
||||
class BaseAdapter(ConnectionPool):
|
||||
|
||||
__metaclass__ = AdapterMeta
|
||||
|
||||
native_json = False
|
||||
driver = None
|
||||
driver_name = None
|
||||
@@ -687,12 +703,14 @@ class BaseAdapter(ConnectionPool):
|
||||
can_select_for_update = True
|
||||
dbpath = None
|
||||
folder = None
|
||||
connector = lambda *args, **kwargs: None # __init__ should override this
|
||||
|
||||
TRUE = 'T'
|
||||
FALSE = 'F'
|
||||
T_SEP = ' '
|
||||
QUOTE_TEMPLATE = '"%s"'
|
||||
|
||||
|
||||
types = {
|
||||
'boolean': 'CHAR(1)',
|
||||
'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'
|
||||
'big-id': 'BIGINT PRIMARY KEY AUTOINCREMENT',
|
||||
'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):
|
||||
@@ -834,8 +853,9 @@ class BaseAdapter(ConnectionPool):
|
||||
self.connection = Dummy()
|
||||
self.cursor = Dummy()
|
||||
|
||||
|
||||
def sequence_name(self,tablename):
|
||||
return '%s_sequence' % tablename
|
||||
return self.QUOTE_TEMPLATE % ('%s_sequence' % tablename)
|
||||
|
||||
def trigger_name(self,tablename):
|
||||
return '%s_sequence' % tablename
|
||||
@@ -868,61 +888,72 @@ class BaseAdapter(ConnectionPool):
|
||||
if referenced == '.':
|
||||
referenced = tablename
|
||||
constraint_name = self.constraint_name(tablename, field_name)
|
||||
if not '.' in referenced \
|
||||
and referenced != tablename \
|
||||
and hasattr(table,'_primarykey'):
|
||||
ftype = types['integer']
|
||||
else:
|
||||
if hasattr(table,'_primarykey'):
|
||||
# if not '.' in referenced \
|
||||
# and referenced != tablename \
|
||||
# and hasattr(table,'_primarykey'):
|
||||
# ftype = types['integer']
|
||||
#else:
|
||||
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('.')
|
||||
rtable = db[rtablename]
|
||||
rfield = rtable[rfieldname]
|
||||
# must be PK reference or unique
|
||||
if rfieldname in rtable._primarykey or \
|
||||
rfield.unique:
|
||||
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)
|
||||
except Exception, e:
|
||||
LOGGER.debug('Error: %s' %e)
|
||||
raise KeyError('Cannot resolve reference %s in %s definition' % (referenced, table._tablename))
|
||||
|
||||
# must be PK reference or unique
|
||||
if getattr(rtable, '_primarykey', None) and rfieldname in rtable._primarykey or \
|
||||
rfield.unique:
|
||||
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:
|
||||
# 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(
|
||||
index_name = field_name+'__idx',
|
||||
field_name = field._rname or field.name,
|
||||
constraint_name = constraint_name,
|
||||
foreign_key = '%s (%s)' % (real_referenced,
|
||||
id_fieldname),
|
||||
on_delete_action=field.ondelete)
|
||||
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'):
|
||||
ftype = types[field_type[:14]]
|
||||
elif field_type.startswith('decimal'):
|
||||
@@ -995,39 +1026,38 @@ class BaseAdapter(ConnectionPool):
|
||||
# geometry fields are added after the table has been created, not now
|
||||
if not (self.dbengine == 'postgres' and \
|
||||
field_type.startswith('geom')):
|
||||
#fetch the rname if it's there
|
||||
field_rname = "%s" % (field._rname or field_name)
|
||||
fields.append('%s %s' % (field_rname, ftype))
|
||||
fields.append('%s %s' % (field.sqlsafe_name, ftype))
|
||||
other = ';'
|
||||
|
||||
# backend-specific extensions to fields
|
||||
if self.dbengine == 'mysql':
|
||||
if not hasattr(table, "_primarykey"):
|
||||
fields.append('PRIMARY KEY(%s)' % (table._id.name or table._id._rname))
|
||||
other = ' ENGINE=InnoDB CHARACTER SET utf8;'
|
||||
fields.append('PRIMARY KEY (%s)' % (self.QUOTE_TEMPLATE % table._id.name))
|
||||
engine = self.adapter_args.get('engine','InnoDB')
|
||||
other = ' ENGINE=%s CHARACTER SET utf8;' % engine
|
||||
|
||||
fields = ',\n '.join(fields)
|
||||
for rtablename in TFK:
|
||||
rfields = TFK[rtablename]
|
||||
pkeys = db[rtablename]._primarykey
|
||||
fkeys = [ rfields[k] for k in pkeys ]
|
||||
pkeys = [self.QUOTE_TEMPLATE % pk for pk in db[rtablename]._primarykey]
|
||||
fkeys = [self.QUOTE_TEMPLATE % rfields[k].name for k in pkeys ]
|
||||
fields = fields + ',\n ' + \
|
||||
types['reference TFK'] % dict(
|
||||
table_name = tablename,
|
||||
table_name = table.sqlsafe,
|
||||
field_name=', '.join(fkeys),
|
||||
foreign_table = rtablename,
|
||||
foreign_table = table.sqlsafe,
|
||||
foreign_key = ', '.join(pkeys),
|
||||
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):
|
||||
query = "CREATE TABLE %s(\n %s,\n %s) %s" % \
|
||||
(table_rname, fields,
|
||||
self.PRIMARY_KEY(', '.join(table._primarykey)),other)
|
||||
(table.sqlsafe, fields,
|
||||
self.PRIMARY_KEY(', '.join([self.QUOTE_TEMPLATE % pk for pk in table._primarykey])),other)
|
||||
else:
|
||||
query = "CREATE TABLE %s(\n %s\n)%s" % \
|
||||
(table_rname, fields, other)
|
||||
(table.sqlsafe, fields, other)
|
||||
|
||||
if self.uri.startswith('sqlite:///') \
|
||||
or self.uri.startswith('spatialite:///'):
|
||||
@@ -1105,6 +1135,7 @@ class BaseAdapter(ConnectionPool):
|
||||
k,v=item
|
||||
if not isinstance(v,dict):
|
||||
v=dict(type='unknown',sql=v)
|
||||
if self.ignore_field_case is not True: return k, v
|
||||
return k.lower(),v
|
||||
# make sure all field names are lower case to avoid
|
||||
# migrations because of case cahnge
|
||||
@@ -1132,7 +1163,7 @@ class BaseAdapter(ConnectionPool):
|
||||
query = [ sql_fields[key]['sql'] ]
|
||||
else:
|
||||
query = ['ALTER TABLE %s ADD %s %s;' % \
|
||||
(tablename, key,
|
||||
(table.sqlsafe, key,
|
||||
sql_fields_aux[key]['sql'].replace(', ', new_add))]
|
||||
metadata_change = True
|
||||
elif self.dbengine in ('sqlite', 'spatialite'):
|
||||
@@ -1150,10 +1181,11 @@ class BaseAdapter(ConnectionPool):
|
||||
"'%(table)s', '%(field)s');" %
|
||||
dict(schema=schema, table=tablename, field=key,) ]
|
||||
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:
|
||||
query = ['ALTER TABLE %s DROP COLUMN %s;' %
|
||||
(tablename, key)]
|
||||
(self.QUOTE_TEMPLATE % tablename, self.QUOTE_TEMPLATE % key)]
|
||||
metadata_change = True
|
||||
elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
|
||||
and not (key in table.fields and
|
||||
@@ -1169,12 +1201,15 @@ class BaseAdapter(ConnectionPool):
|
||||
else:
|
||||
drop_expr = 'ALTER TABLE %s DROP COLUMN %s;'
|
||||
key_tmp = key + '__tmp'
|
||||
query = ['ALTER TABLE %s ADD %s %s;' % (t, key_tmp, tt),
|
||||
'UPDATE %s SET %s=%s;' % (t, key_tmp, key),
|
||||
drop_expr % (t, key),
|
||||
'ALTER TABLE %s ADD %s %s;' % (t, key, tt),
|
||||
'UPDATE %s SET %s=%s;' % (t, key, key_tmp),
|
||||
drop_expr % (t, key_tmp)]
|
||||
query = ['ALTER TABLE %s ADD %s %s;' % (self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key_tmp, tt),
|
||||
'UPDATE %s SET %s=%s;' %
|
||||
(self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key_tmp, self.QUOTE_TEMPLATE % key),
|
||||
drop_expr % (self.QUOTE_TEMPLATE % t, self.QUOTE_TEMPLATE % key),
|
||||
'ALTER TABLE %s ADD %s %s;' %
|
||||
(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
|
||||
elif sql_fields[key]['type'] != sql_fields_old[key]['type']:
|
||||
sql_fields_current[key] = sql_fields[key]
|
||||
@@ -1269,8 +1304,7 @@ class BaseAdapter(ConnectionPool):
|
||||
return 'PRIMARY KEY(%s)' % key
|
||||
|
||||
def _drop(self, table, mode):
|
||||
table_rname = table._rname or table
|
||||
return ['DROP TABLE %s;' % table_rname]
|
||||
return ['DROP TABLE %s;' % table.sqlsafe]
|
||||
|
||||
def drop(self, table, mode=''):
|
||||
db = table._db
|
||||
@@ -1288,17 +1322,16 @@ class BaseAdapter(ConnectionPool):
|
||||
self.log('success!\n', table)
|
||||
|
||||
def _insert(self, table, fields):
|
||||
table_rname = table._rname or table
|
||||
table_rname = table.sqlsafe
|
||||
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)
|
||||
return 'INSERT INTO %s(%s) VALUES (%s);' % (table_rname, keys, values)
|
||||
else:
|
||||
return self._insert_empty(table)
|
||||
|
||||
def _insert_empty(self, table):
|
||||
table_rname = table._rname or table
|
||||
return 'INSERT INTO %s DEFAULT VALUES;' % table_rname
|
||||
return 'INSERT INTO %s DEFAULT VALUES;' % (table.sqlsafe)
|
||||
|
||||
def insert(self, 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)
|
||||
|
||||
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),
|
||||
self.expand(second, first.type))
|
||||
else:
|
||||
@@ -1451,13 +1484,13 @@ class BaseAdapter(ConnectionPool):
|
||||
self.expand(second, first.type))
|
||||
|
||||
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):
|
||||
table_rname = first._ot and first or first._rname or first._tablename
|
||||
table_rname = self.table_alias(first)
|
||||
if use_common_filters(second):
|
||||
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):
|
||||
return '%s DESC' % self.expand(first)
|
||||
@@ -1472,13 +1505,10 @@ class BaseAdapter(ConnectionPool):
|
||||
if isinstance(expression, Field):
|
||||
et = expression.table
|
||||
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:
|
||||
table_rname = et._tablename
|
||||
if not colnames:
|
||||
out = '%s.%s' % (table_rname, expression._rname or expression.name)
|
||||
else:
|
||||
out = '%s.%s' % (table_rname, expression.name)
|
||||
out = '%s.%s' % (self.QUOTE_TEMPLATE % et._tablename, self.QUOTE_TEMPLATE % expression.name)
|
||||
if field_type == 'string' and not expression.type in (
|
||||
'string','text','json','password'):
|
||||
out = self.CAST(out, self.types['text'])
|
||||
@@ -1509,10 +1539,11 @@ class BaseAdapter(ConnectionPool):
|
||||
else:
|
||||
return str(expression)
|
||||
|
||||
def table_alias(self,name):
|
||||
if not isinstance(name, Table):
|
||||
name = self.db[name]._rname or self.db[name]
|
||||
return str(name)
|
||||
def table_alias(self, tbl):
|
||||
if not isinstance(tbl, Table):
|
||||
tbl = self.db[tbl]
|
||||
return tbl.sqlsafe_alias
|
||||
|
||||
|
||||
def alias(self, table, alias):
|
||||
"""
|
||||
@@ -1520,7 +1551,7 @@ class BaseAdapter(ConnectionPool):
|
||||
with alias name.
|
||||
"""
|
||||
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['_tablename'] = alias
|
||||
for fieldname in other.fields:
|
||||
@@ -1532,8 +1563,7 @@ class BaseAdapter(ConnectionPool):
|
||||
return other
|
||||
|
||||
def _truncate(self, table, mode=''):
|
||||
tablename = table._rname or table._tablename
|
||||
return ['TRUNCATE TABLE %s %s;' % (tablename, mode or '')]
|
||||
return ['TRUNCATE TABLE %s %s;' % (table.sqlsafe, mode or '')]
|
||||
|
||||
def truncate(self, table, mode= ' '):
|
||||
# Prepare functions "write_to_logfile" and "close_logfile"
|
||||
@@ -1553,10 +1583,10 @@ class BaseAdapter(ConnectionPool):
|
||||
sql_w = ' WHERE ' + self.expand(query)
|
||||
else:
|
||||
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)) \
|
||||
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)
|
||||
|
||||
def update(self, tablename, query, fields):
|
||||
@@ -1581,7 +1611,7 @@ class BaseAdapter(ConnectionPool):
|
||||
sql_w = ' WHERE ' + self.expand(query)
|
||||
else:
|
||||
sql_w = ''
|
||||
tablename = '%s' % (self.db[tablename]._rname or tablename)
|
||||
tablename = self.db[tablename].sqlsafe
|
||||
return 'DELETE FROM %s%s;' % (tablename, sql_w)
|
||||
|
||||
def delete(self, tablename, query):
|
||||
@@ -1623,8 +1653,9 @@ class BaseAdapter(ConnectionPool):
|
||||
if isinstance(item,SQLALL):
|
||||
new_fields += item._table
|
||||
elif isinstance(item,str):
|
||||
if REGEX_TABLE_DOT_FIELD.match(item):
|
||||
tablename,fieldname = item.split('.')
|
||||
m = self.REGEX_TABLE_DOT_FIELD.match(item)
|
||||
if m:
|
||||
tablename,fieldname = m.groups()
|
||||
append(db[tablename][fieldname])
|
||||
else:
|
||||
append(Expression(db,lambda item=item:item))
|
||||
@@ -1645,10 +1676,11 @@ class BaseAdapter(ConnectionPool):
|
||||
tablenames = tables(query)
|
||||
tablenames_for_common_filters = tablenames
|
||||
for field in fields:
|
||||
if isinstance(field, basestring) \
|
||||
and REGEX_TABLE_DOT_FIELD.match(field):
|
||||
tn,fn = field.split('.')
|
||||
field = self.db[tn][fn]
|
||||
if isinstance(field, basestring):
|
||||
m = self.REGEX_TABLE_DOT_FIELD.match(field)
|
||||
if m:
|
||||
tn,fn = m.groups()
|
||||
field = self.db[tn][fn]
|
||||
for tablename in tables(field):
|
||||
if not tablename in tablenames:
|
||||
tablenames.append(tablename)
|
||||
@@ -1732,7 +1764,7 @@ class BaseAdapter(ConnectionPool):
|
||||
tables_to_merge.keys()])
|
||||
if joint:
|
||||
sql_t += ' %s %s' % (command,
|
||||
','.join([self.table_alias(t) for t in joint]))
|
||||
','.join([t for t in joint]))
|
||||
for t in joinon:
|
||||
sql_t += ' %s %s' % (command, t)
|
||||
elif inner_join and left:
|
||||
@@ -1747,7 +1779,7 @@ class BaseAdapter(ConnectionPool):
|
||||
sql_t += ' %s %s' % (icommand, t)
|
||||
if joint:
|
||||
sql_t += ' %s %s' % (command,
|
||||
','.join([self.table_alias(t) for t in joint]))
|
||||
','.join([t for t in joint]))
|
||||
for t in joinon:
|
||||
sql_t += ' %s %s' % (command, t)
|
||||
else:
|
||||
@@ -1767,9 +1799,9 @@ class BaseAdapter(ConnectionPool):
|
||||
sql_o += ' ORDER BY %s' % self.expand(orderby)
|
||||
if (limitby and not groupby and tablenames and orderby_on_limitby and not orderby):
|
||||
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
|
||||
or [self.db[t]._id._rname or self.db[t]._id.name]
|
||||
or ['_id']
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -1897,8 +1929,10 @@ class BaseAdapter(ConnectionPool):
|
||||
|
||||
def create_sequence_and_triggers(self, query, table, **args):
|
||||
self.execute(query)
|
||||
|
||||
|
||||
def log_execute(self, *a, **b):
|
||||
if not self.connection: raise ValueError(a[0])
|
||||
if not self.connection: return None
|
||||
command = a[0]
|
||||
if hasattr(self,'filter_sql_command'):
|
||||
@@ -1955,8 +1989,17 @@ class BaseAdapter(ConnectionPool):
|
||||
if field_is_type('decimal'):
|
||||
return str(obj)
|
||||
elif field_is_type('reference'): # reference
|
||||
if fieldtype.find('.')>0:
|
||||
return repr(obj)
|
||||
# check for tablename first
|
||||
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)):
|
||||
return str(obj['id'])
|
||||
return str(long(obj))
|
||||
@@ -2162,14 +2205,15 @@ class BaseAdapter(ConnectionPool):
|
||||
new_rows = []
|
||||
tmps = []
|
||||
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)
|
||||
else:
|
||||
(tablename, _the_sep_, fieldname) = colname.partition('.')
|
||||
tablename, fieldname = col_m.groups()
|
||||
table = db[tablename]
|
||||
field = table[fieldname]
|
||||
ft = field.type
|
||||
tmps.append((tablename,fieldname,table,field,ft))
|
||||
tmps.append((tablename, fieldname, table, field, ft))
|
||||
for (i,row) in enumerate(rows):
|
||||
new_row = Row()
|
||||
for (j,colname) in enumerate(colnames):
|
||||
@@ -2177,9 +2221,8 @@ class BaseAdapter(ConnectionPool):
|
||||
tmp = tmps[j]
|
||||
if tmp:
|
||||
(tablename,fieldname,table,field,ft) = tmp
|
||||
if tablename in new_row:
|
||||
colset = new_row[tablename]
|
||||
else:
|
||||
colset = new_row.get(tablename, None)
|
||||
if colset is None:
|
||||
colset = new_row[tablename] = Row()
|
||||
if tablename not in virtualtables:
|
||||
virtualtables.append(tablename)
|
||||
@@ -2287,6 +2330,14 @@ class BaseAdapter(ConnectionPool):
|
||||
return Expression(self.db,'CASE WHEN %s THEN %s ELSE %s END' % \
|
||||
(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.
|
||||
###################################################################################
|
||||
@@ -2564,6 +2615,7 @@ class MySQLAdapter(BaseAdapter):
|
||||
'list:reference': 'LONGTEXT',
|
||||
'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',
|
||||
'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`"
|
||||
@@ -2590,12 +2642,12 @@ class MySQLAdapter(BaseAdapter):
|
||||
|
||||
def _drop(self,table,mode):
|
||||
# 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,
|
||||
'SET FOREIGN_KEY_CHECKS=1;']
|
||||
|
||||
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):
|
||||
self.execute('XA START;')
|
||||
@@ -2668,6 +2720,8 @@ class MySQLAdapter(BaseAdapter):
|
||||
class PostgreSQLAdapter(BaseAdapter):
|
||||
drivers = ('psycopg2','pg8000')
|
||||
|
||||
QUOTE_TEMPLATE = '"%s"'
|
||||
|
||||
support_distributed_transaction = True
|
||||
types = {
|
||||
'boolean': 'CHAR(1)',
|
||||
@@ -2694,12 +2748,11 @@ class PostgreSQLAdapter(BaseAdapter):
|
||||
'geography': 'GEOGRAPHY',
|
||||
'big-id': 'BIGSERIAL PRIMARY KEY',
|
||||
'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 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 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',
|
||||
|
||||
}
|
||||
|
||||
QUOTE_TEMPLATE = '"%s"'
|
||||
|
||||
def varquote(self,name):
|
||||
return varquote_aux(name,'"%s"')
|
||||
@@ -2713,7 +2766,7 @@ class PostgreSQLAdapter(BaseAdapter):
|
||||
return "'%s'" % str(obj).replace("'","''")
|
||||
|
||||
def sequence_name(self,table):
|
||||
return '%s_id_seq' % table
|
||||
return self.QUOTE_TEMPLATE % (table + '_id_seq')
|
||||
|
||||
def RANDOM(self):
|
||||
return 'RANDOM()'
|
||||
@@ -2802,8 +2855,8 @@ class PostgreSQLAdapter(BaseAdapter):
|
||||
self.execute("SET standard_conforming_strings=on;")
|
||||
self.try_json()
|
||||
|
||||
def lastrowid(self,table):
|
||||
self.execute("""select currval('"%s"')""" % table._sequence_name)
|
||||
def lastrowid(self,table = None):
|
||||
self.execute("select lastval()")
|
||||
return int(self.cursor.fetchone()[0])
|
||||
|
||||
def try_json(self):
|
||||
@@ -2925,6 +2978,14 @@ class PostgreSQLAdapter(BaseAdapter):
|
||||
"""
|
||||
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):
|
||||
field_is_type = fieldtype.startswith
|
||||
if field_is_type('geo'):
|
||||
@@ -2942,6 +3003,11 @@ class PostgreSQLAdapter(BaseAdapter):
|
||||
return value
|
||||
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):
|
||||
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',
|
||||
}
|
||||
|
||||
def sequence_name(self,tablename):
|
||||
return '%s_sequence' % tablename
|
||||
|
||||
def trigger_name(self,tablename):
|
||||
return '%s_trigger' % tablename
|
||||
@@ -3091,7 +3155,7 @@ class OracleAdapter(BaseAdapter):
|
||||
|
||||
def _drop(self,table,mode):
|
||||
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):
|
||||
if limitby:
|
||||
@@ -3218,11 +3282,18 @@ class OracleAdapter(BaseAdapter):
|
||||
else:
|
||||
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):
|
||||
drivers = ('pyodbc',)
|
||||
T_SEP = 'T'
|
||||
|
||||
QUOTE_TEMPLATE = "[%s]"
|
||||
|
||||
QUOTE_TEMPLATE = '"%s"'
|
||||
|
||||
types = {
|
||||
'boolean': 'BIT',
|
||||
@@ -3685,7 +3756,7 @@ class FireBirdAdapter(BaseAdapter):
|
||||
}
|
||||
|
||||
def sequence_name(self,tablename):
|
||||
return 'genid_%s' % tablename
|
||||
return ('genid_' + self.QUOTE_TEMPLATE) % tablename
|
||||
|
||||
def trigger_name(self,tablename):
|
||||
return 'trg_id_%s' % tablename
|
||||
@@ -3714,7 +3785,7 @@ class FireBirdAdapter(BaseAdapter):
|
||||
|
||||
def _drop(self,table,mode):
|
||||
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):
|
||||
if limitby:
|
||||
@@ -4283,7 +4354,7 @@ class SAPDBAdapter(BaseAdapter):
|
||||
}
|
||||
|
||||
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):
|
||||
if limitby:
|
||||
@@ -4548,6 +4619,7 @@ class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
|
||||
|
||||
class NoSQLAdapter(BaseAdapter):
|
||||
can_select_for_update = False
|
||||
QUOTE_TEMPLATE = '%s'
|
||||
|
||||
@staticmethod
|
||||
def to_unicode(obj):
|
||||
@@ -4737,6 +4809,8 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
||||
|
||||
uploads_in_blob = True
|
||||
types = {}
|
||||
# reconnect is not required for Datastore dbs
|
||||
reconnect = lambda *args, **kwargs: None
|
||||
|
||||
def file_exists(self, filename): pass
|
||||
def file_open(self, filename, mode='rb', lock=True): pass
|
||||
@@ -4839,12 +4913,10 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
||||
elif field_type.startswith('reference'):
|
||||
if field.notnull:
|
||||
attr = dict(required=True)
|
||||
referenced = field_type[10:].strip()
|
||||
ftype = self.types[field_type[:9]](referenced, **attr)
|
||||
ftype = self.types[field_type[:9]](**attr)
|
||||
elif field_type.startswith('list:reference'):
|
||||
if field.notnull:
|
||||
attr['required'] = True
|
||||
referenced = field_type[15:].strip()
|
||||
ftype = self.types[field_type[:14]](**attr)
|
||||
elif field_type.startswith('list:'):
|
||||
ftype = self.types[field_type](**attr)
|
||||
@@ -5168,6 +5240,12 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
|
||||
processor = attributes.get('processor',self.parse)
|
||||
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):
|
||||
if distinct:
|
||||
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_]
|
||||
"""
|
||||
if not REGEX_ALPHANUMERIC.match(text):
|
||||
raise SyntaxError('invalid table or field name: %s' % text)
|
||||
#if not REGEX_ALPHANUMERIC.match(text):
|
||||
# raise SyntaxError('invalid table or field name: %s' % text)
|
||||
return text
|
||||
|
||||
class MongoDBAdapter(NoSQLAdapter):
|
||||
@@ -5582,9 +5660,6 @@ class MongoDBAdapter(NoSQLAdapter):
|
||||
value = obj
|
||||
else:
|
||||
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
|
||||
if fieldtype =='date':
|
||||
if value == None:
|
||||
@@ -7245,12 +7320,32 @@ class Row(object):
|
||||
__init__ = lambda self,*args,**kwargs: self.__dict__.update(*args,**kwargs)
|
||||
|
||||
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)
|
||||
_extra = self.__dict__.get('_extra', None)
|
||||
_extra = ogetattr(self, '__dict__').get('_extra', None)
|
||||
if _extra is not None:
|
||||
v = _extra.get(key, DEFAULT)
|
||||
if v != DEFAULT:
|
||||
return v
|
||||
try:
|
||||
return ogetattr(self, key)
|
||||
except (KeyError,AttributeError,TypeError):
|
||||
pass
|
||||
|
||||
m = REGEX_TABLE_DOT_FIELD.match(key)
|
||||
if m:
|
||||
try:
|
||||
@@ -7652,7 +7747,7 @@ class DAL(object):
|
||||
adapter_args=None, attempts=5, auto_import=False,
|
||||
bigint_id=False, debug=False, lazy_tables=False,
|
||||
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.
|
||||
|
||||
@@ -7737,6 +7832,7 @@ class DAL(object):
|
||||
self._decode_credentials = decode_credentials
|
||||
self._attempts = attempts
|
||||
self._do_connect = do_connect
|
||||
self._ignore_field_case = ignore_field_case
|
||||
|
||||
if not str(attempts).isdigit() or attempts < 0:
|
||||
attempts = 5
|
||||
@@ -7768,6 +7864,7 @@ class DAL(object):
|
||||
# copy so multiple DAL() possible
|
||||
self._adapter.types = copy.copy(types)
|
||||
self._adapter.build_parsemap()
|
||||
self._adapter.ignore_field_case = ignore_field_case
|
||||
if bigint_id:
|
||||
if 'big-id' in types and 'reference' in types:
|
||||
self._adapter.types['id'] = types['big-id']
|
||||
@@ -7810,11 +7907,11 @@ class DAL(object):
|
||||
|
||||
def import_table_definitions(self, path, migrate=False,
|
||||
fake_migrate=False, tables=None):
|
||||
pattern = pjoin(path,self._uri_hash+'_*.table')
|
||||
if tables:
|
||||
for table in tables:
|
||||
self.define_table(**table)
|
||||
else:
|
||||
pattern = pjoin(path,self._uri_hash+'_*.table')
|
||||
for filename in glob.glob(pattern):
|
||||
tfile = self._adapter.file_open(filename, 'r')
|
||||
try:
|
||||
@@ -8565,13 +8662,8 @@ class Table(object):
|
||||
self._tablename = tablename
|
||||
self._ot = None # args.get('rname')
|
||||
self._rname = args.get('rname')
|
||||
if not self._rname:
|
||||
self._sequence_name = args.get('sequence_name') or \
|
||||
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._sequence_name = args.get('sequence_name') or \
|
||||
db and db._adapter.sequence_name(self._rname or tablename)
|
||||
self._trigger_name = args.get('trigger_name') or \
|
||||
db and db._adapter.trigger_name(tablename)
|
||||
self._common_filter = args.get('common_filter')
|
||||
@@ -8652,7 +8744,7 @@ class Table(object):
|
||||
fields.append(Field(fn,'blob',default='',
|
||||
writable=False,readable=False))
|
||||
|
||||
lower_fieldnames = set()
|
||||
fieldnames_set = set()
|
||||
reserved = dir(Table) + ['fields']
|
||||
if (db and db.check_reserved):
|
||||
check_reserved = db.check_reserved_keyword
|
||||
@@ -8663,12 +8755,15 @@ class Table(object):
|
||||
for field in fields:
|
||||
field_name = field.name
|
||||
check_reserved(field_name)
|
||||
fn_lower = field_name.lower()
|
||||
if fn_lower in lower_fieldnames:
|
||||
if db and db._ignore_field_case:
|
||||
fname_item = field_name.lower()
|
||||
else:
|
||||
fname_item = field_name
|
||||
if fname_item in fieldnames_set:
|
||||
raise SyntaxError("duplicate field %s in table %s" \
|
||||
% (field_name, tablename))
|
||||
else:
|
||||
lower_fieldnames.add(fn_lower)
|
||||
fieldnames_set.add(fname_item)
|
||||
|
||||
self.fields.append(field_name)
|
||||
self[field_name] = field
|
||||
@@ -8910,8 +9005,23 @@ class Table(object):
|
||||
if 'Oracle' in str(type(self._db._adapter)):
|
||||
return '%s %s' % (ot, self._tablename)
|
||||
return '%s AS %s' % (ot, 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 = ''):
|
||||
return self._db._adapter._drop(self, mode)
|
||||
|
||||
@@ -9566,6 +9676,10 @@ class Expression(object):
|
||||
db = self.db
|
||||
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
|
||||
|
||||
|
||||
@@ -9738,7 +9852,12 @@ class Field(Expression):
|
||||
if not isinstance(fieldname, str) or hasattr(Table, fieldname) or \
|
||||
fieldname[0] == '_' or REGEX_PYTHON_KEYWORDS.match(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.default = default if default!=DEFAULT else (update or None)
|
||||
self.required = required # is this field required
|
||||
@@ -10004,6 +10123,16 @@ class Field(Expression):
|
||||
except:
|
||||
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):
|
||||
|
||||
@@ -10356,7 +10485,7 @@ class Set(object):
|
||||
fields = table._listify(update_fields,update=True)
|
||||
if not fields:
|
||||
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]
|
||||
return ret
|
||||
|
||||
@@ -10869,11 +10998,21 @@ class Rows(object):
|
||||
represent = kwargs.get('represent', False)
|
||||
writer = csv.writer(ofile, delimiter=delimiter,
|
||||
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)
|
||||
write_colnames = kwargs.get('write_colnames',True)
|
||||
# a proper csv starting with the column names
|
||||
if write_colnames:
|
||||
writer.writerow(colnames)
|
||||
writer.writerow(unquote_colnames(colnames))
|
||||
|
||||
def none_exception(value):
|
||||
"""
|
||||
@@ -10896,10 +11035,11 @@ class Rows(object):
|
||||
for record in self:
|
||||
row = []
|
||||
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])
|
||||
else:
|
||||
(t, f) = col.split('.')
|
||||
(t, f) = m.groups()
|
||||
field = self.db[t][f]
|
||||
if isinstance(record.get(t, None), (Row,dict)):
|
||||
value = record[t][f]
|
||||
|
||||
+4
-1
@@ -816,7 +816,10 @@ class Session(Storage):
|
||||
# if on GAE tickets go also in DB
|
||||
if settings.global_settings.web2py_runtime_gae:
|
||||
request.tickets_db = db
|
||||
table_migrate = (masterapp == request.application)
|
||||
if masterapp == request.application:
|
||||
table_migrate = migrate
|
||||
else:
|
||||
table_migrate = False
|
||||
tname = tablename + '_' + masterapp
|
||||
table = db.get(tname, None)
|
||||
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 URL, truncate_string, FIELDSET
|
||||
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.utils import md5_hash
|
||||
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_]+')
|
||||
widget_class = re.compile('^\w*')
|
||||
|
||||
def add_class(a,b):
|
||||
return a+' '+b if a else b
|
||||
|
||||
def represent(field, value, record):
|
||||
f = field.represent
|
||||
if not callable(f):
|
||||
@@ -334,7 +337,7 @@ class RadioWidget(OptionsWidget):
|
||||
|
||||
|
||||
attr = cls._attributes(field, {}, **attributes)
|
||||
attr['_class'] = attr.get('_class', 'web2py_radiowidget')
|
||||
attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget')
|
||||
|
||||
requires = field.requires
|
||||
if not isinstance(requires, (list, tuple)):
|
||||
@@ -398,7 +401,9 @@ class CheckboxesWidget(OptionsWidget):
|
||||
values = [str(value)]
|
||||
|
||||
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
|
||||
if not isinstance(requires, (list, tuple)):
|
||||
@@ -439,7 +444,8 @@ class CheckboxesWidget(OptionsWidget):
|
||||
requires=attr.get('requires', None),
|
||||
hideerror=True, _value=k,
|
||||
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))
|
||||
|
||||
if opts:
|
||||
@@ -2893,7 +2899,7 @@ class SQLTABLE(TABLE):
|
||||
if not sqlrows:
|
||||
return
|
||||
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':
|
||||
headers = {}
|
||||
for c in columns:
|
||||
@@ -3116,7 +3122,7 @@ class ExportClass(object):
|
||||
for record in self.rows:
|
||||
row = []
|
||||
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])
|
||||
else:
|
||||
(t, f) = col.split('.')
|
||||
|
||||
+135
-1
@@ -79,6 +79,9 @@ def tearDownModule():
|
||||
class TestFields(unittest.TestCase):
|
||||
|
||||
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
|
||||
self.assertRaises(SyntaxError, Field, '_abc', 'string')
|
||||
@@ -1394,6 +1397,137 @@ class TestRNameFields(unittest.TestCase):
|
||||
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__':
|
||||
unittest.main()
|
||||
tearDownModule()
|
||||
tearDownModule()
|
||||
|
||||
+3
-3
@@ -2915,7 +2915,7 @@ class Auth(object):
|
||||
formname='reset_password', dbio=False,
|
||||
onvalidation=onvalidation,
|
||||
hideerror=self.settings.hideerror):
|
||||
user = table_user(**{userfield:form.vars.email})
|
||||
user = table_user(**{userfield:form.vars.get(userfield)})
|
||||
if not user:
|
||||
session.flash = self.messages['invalid_%s' % userfield]
|
||||
redirect(self.url(args=request.args),
|
||||
@@ -5395,7 +5395,7 @@ class Wiki(object):
|
||||
if (auth.user and
|
||||
check_credentials(current.request, gae_login=False) 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')
|
||||
gid = group.id if group else db.auth_group.insert(
|
||||
role='wiki_editor')
|
||||
@@ -5523,7 +5523,7 @@ class Wiki(object):
|
||||
url = URL(args=('_edit', slug))
|
||||
return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn"))
|
||||
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))
|
||||
return dict(title=page.title,
|
||||
slug=page.slug,
|
||||
|
||||
+4
-1
@@ -1224,7 +1224,10 @@ end tell
|
||||
if not options.nobanner:
|
||||
print 'please visit:'
|
||||
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
|
||||
# if the line was not found (under py2exe & when file was modified)
|
||||
|
||||
Reference in New Issue
Block a user