Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2013-02-09 02:27:16 +01:00
26 changed files with 836 additions and 193 deletions
+4 -6
View File
@@ -54,20 +54,19 @@ src:
### build web2py_src.zip ### build web2py_src.zip
echo '' > NEWINSTALL echo '' > NEWINSTALL
mv web2py_src.zip web2py_src_old.zip | echo 'no old' mv web2py_src.zip web2py_src_old.zip | echo 'no old'
cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/splashlogo.gif web2py/*.py web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/Makefile web2py/epydoc.css web2py/epydoc.conf web2py/app.example.yaml web2py/logging.example.conf web2py_exe.conf web2py/queue.example.yaml MANIFEST.in w2p_apps w2p_clone w2p_run startweb2py web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/splashlogo.gif web2py/*.py web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/Makefile web2py/epydoc.css web2py/epydoc.conf web2py/app.example.yaml web2py/logging.example.conf web2py/queue.example.yaml MANIFEST.in w2p_apps w2p_clone w2p_run web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
mdp: mdp:
make src make src
make app make app
make win make win
app: app:
echo 'did you uncomment import_all in gluon/main.py?' python2.7 -c 'import compileall; compileall.compile_dir("gluon/")'
python2.5 -c 'import compileall; compileall.compile_dir("gluon/")'
#python web2py.py -S welcome -R __exit__.py #python web2py.py -S welcome -R __exit__.py
#cd ../web2py_osx/site-packages/; unzip ../site-packages.zip #cd ../web2py_osx/site-packages/; unzip ../site-packages.zip
find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \; find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \;
cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip * cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip *
mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.5 mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7
cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources
cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources
cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources
@@ -86,7 +85,6 @@ app:
cd ../web2py_osx; zip -r web2py_osx.zip web2py cd ../web2py_osx; zip -r web2py_osx.zip web2py
mv ../web2py_osx/web2py_osx.zip . mv ../web2py_osx/web2py_osx.zip .
win: win:
echo 'did you uncomment import_all in gluon/main.py?'
python2.7 -c 'import compileall; compileall.compile_dir("gluon/")' python2.7 -c 'import compileall; compileall.compile_dir("gluon/")'
#cd ../web2py_win/library/; unzip ../library.zip #cd ../web2py_win/library/; unzip ../library.zip
find gluon -path '*.pyc' -exec cp {} ../web2py_win/library/{} \; find gluon -path '*.pyc' -exec cp {} ../web2py_win/library/{} \;
@@ -110,7 +108,7 @@ win:
cd ../web2py_win; zip -r web2py_win.zip web2py cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip . mv ../web2py_win/web2py_win.zip .
run: run:
python2.5 web2py.py -a hello python2.7 web2py.py -a hello
commit: commit:
python web2py.py --run_system_tests python web2py.py --run_system_tests
make src make src
+1 -1
View File
@@ -1 +1 @@
Version 2.4.1-alpha.2+timestamp.2013.01.23.08.48.16 Version 2.4.1-alpha.2+timestamp.2013.02.08.12.41.08
+37 -8
View File
@@ -469,10 +469,10 @@ def ccache():
def table_template(table): def table_template(table):
from gluon.html import TR, TD, TABLE, TAG from gluon.html import TR, TD, TABLE, TAG
def FONT(*args, **kwargs): def FONT(*args, **kwargs):
return TAG.font(*args, **kwargs) return TAG.font(*args, **kwargs)
def types(field): def types(field):
f_type = field.type f_type = field.type
if not isinstance(f_type,str): if not isinstance(f_type,str):
@@ -480,7 +480,7 @@ def table_template(table):
elif f_type == 'string': elif f_type == 'string':
return field.length return field.length
elif f_type == 'id': elif f_type == 'id':
return B('pk') return B('pk')
elif f_type.startswith('reference') or \ elif f_type.startswith('reference') or \
f_type.startswith('list:reference'): f_type.startswith('list:reference'):
return B('fk') return B('fk')
@@ -495,7 +495,7 @@ def table_template(table):
face = "Helvetica" face = "Helvetica"
face_bold = "Helvetica Bold" face_bold = "Helvetica Bold"
border = 0 border = 0
rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
_colspan=3, _cellpadding=cellpadding, _colspan=3, _cellpadding=cellpadding,
_align="center", _bgcolor=color))) _align="center", _bgcolor=color)))
@@ -515,10 +515,31 @@ def table_template(table):
def bg_graph_model(): def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR') graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict()
for tablename in db.tables: for tablename in db.tables:
graph.add_node(tablename, name=tablename, shape='plaintext', if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel
else:
meta_graphmodel = dict(group='Undefined', color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename)) label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n,
style='filled',
color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group'])
for tablename in db.tables: for tablename in db.tables:
for field in db[tablename]: for field in db[tablename]:
@@ -532,7 +553,15 @@ def bg_graph_model():
graph.add_edge(n1, n2, color="#4C4C4C", label='') graph.add_edge(n1, n2, color="#4C4C4C", label='')
graph.layout() graph.layout()
return graph.draw(format='png', prog='dot') #return graph.draw(format='png', prog='dot')
if not request.args:
return graph.draw(format='png', prog='dot')
else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot':
return graph.string()
else:
return graph.draw(format=request.args(0), prog='dot')
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) return dict(databases=databases, pgv=pgv)
+4 -1
View File
@@ -565,6 +565,7 @@ def edit():
# Load json only if it is ajax edited... # Load json only if it is ajax edited...
app = get_app(request.vars.app) app = get_app(request.vars.app)
filename = '/'.join(request.args) filename = '/'.join(request.args)
response.title = request.args[-1]
if request.vars.app: if request.vars.app:
path = abspath(filename) path = abspath(filename)
else: else:
@@ -645,6 +646,7 @@ def edit():
code = request.vars.data.rstrip().replace('\r\n', '\n') + '\n' code = request.vars.data.rstrip().replace('\r\n', '\n') + '\n'
compile(code, path, "exec", _ast.PyCF_ONLY_AST) compile(code, path, "exec", _ast.PyCF_ONLY_AST)
except Exception, e: except Exception, e:
# offset calculation is only used for textarea (start/stop)
start = sum([len(line) + 1 for l, line start = sum([len(line) + 1 for l, line
in enumerate(request.vars.data.split("\n")) in enumerate(request.vars.data.split("\n"))
if l < e.lineno - 1]) if l < e.lineno - 1])
@@ -654,7 +656,7 @@ def edit():
else: else:
offset = 0 offset = 0
highlight = {'start': start, 'end': start + highlight = {'start': start, 'end': start +
offset + 1, 'lineno': e.lineno} offset + 1, 'lineno': e.lineno, 'offset': offset}
try: try:
ex_name = e.__class__.__name__ ex_name = e.__class__.__name__
except: except:
@@ -811,6 +813,7 @@ def edit_language():
""" Edit language file """ """ Edit language file """
app = get_app() app = get_app()
filename = '/'.join(request.args) filename = '/'.join(request.args)
response.title = request.args[-1]
strings = read_dict(apath(filename, r=request)) strings = read_dict(apath(filename, r=request))
if '__corrupted__' in strings: if '__corrupted__' in strings:
+11
View File
@@ -23,6 +23,7 @@
'Admin language': 'Admin language', 'Admin language': 'Admin language',
'administrative interface': 'administrative interface', 'administrative interface': 'administrative interface',
'Administrator Password:': 'Password Amministratore:', 'Administrator Password:': 'Password Amministratore:',
'An error occured, please %s the page': 'An error occured, please %s the page',
'and rename it (required):': 'e rinominala (obbligatorio):', 'and rename it (required):': 'e rinominala (obbligatorio):',
'and rename it:': 'e rinominala:', 'and rename it:': 'e rinominala:',
'appadmin': 'appadmin ', 'appadmin': 'appadmin ',
@@ -144,8 +145,10 @@
'Get from URL:': 'Get from URL:', 'Get from URL:': 'Get from URL:',
'Git Pull': 'Git Pull', 'Git Pull': 'Git Pull',
'Git Push': 'Git Push', 'Git Push': 'Git Push',
'graph model': 'graph model',
'Hello World': 'Salve Mondo', 'Hello World': 'Salve Mondo',
'Help': 'aiuto', 'Help': 'aiuto',
'Hide/Show Translated strings': 'Hide/Show Translated strings',
'htmledit': 'modifica come html', 'htmledit': 'modifica come html',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.', 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'Import/Export': 'Importa/Esporta', 'Import/Export': 'Importa/Esporta',
@@ -195,6 +198,8 @@
'NO': 'NO', 'NO': 'NO',
'No databases in this application': 'Nessun database presente in questa applicazione', 'No databases in this application': 'Nessun database presente in questa applicazione',
'no match': 'nessuna corrispondenza', 'no match': 'nessuna corrispondenza',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Or Get from URL:',
'or import from csv file': 'oppure importa da file CSV', 'or import from csv file': 'oppure importa da file CSV',
'or provide app url:': "oppure fornisci url dell'applicazione:", 'or provide app url:': "oppure fornisci url dell'applicazione:",
'Original/Translation': 'Originale/Traduzione', 'Original/Translation': 'Originale/Traduzione',
@@ -219,6 +224,7 @@
'record does not exist': 'il record non esiste', 'record does not exist': 'il record non esiste',
'record id': 'ID del record', 'record id': 'ID del record',
'register': 'registrazione', 'register': 'registrazione',
'reload': 'reload',
'Remove compiled': 'rimozione codice compilato', 'Remove compiled': 'rimozione codice compilato',
'request': 'request', 'request': 'request',
'Resolve Conflict file': 'File di risoluzione conflitto', 'Resolve Conflict file': 'File di risoluzione conflitto',
@@ -242,6 +248,7 @@
'Start wizard': 'start wizard', 'Start wizard': 'start wizard',
'state': 'stato', 'state': 'stato',
'static': 'statico', 'static': 'statico',
'Static': 'Static',
'Static files': 'Files statici', 'Static files': 'Files statici',
'Stylesheet': 'Foglio di stile (stylesheet)', 'Stylesheet': 'Foglio di stile (stylesheet)',
'submit': 'invia', 'submit': 'invia',
@@ -261,6 +268,7 @@
'There are no models': 'Non ci sono modelli', 'There are no models': 'Non ci sono modelli',
'There are no modules': 'Non ci sono moduli', 'There are no modules': 'Non ci sono moduli',
'There are no plugins': 'There are no plugins', 'There are no plugins': 'There are no plugins',
'There are no private files': 'There are no private files',
'There are no static files': 'Non ci sono file statici', 'There are no static files': 'Non ci sono file statici',
'There are no translators, only default language is supported': 'Non ci sono traduzioni, viene solo supportato il linguaggio di base', 'There are no translators, only default language is supported': 'Non ci sono traduzioni, viene solo supportato il linguaggio di base',
'There are no views': 'Non ci sono viste ("view")', 'There are no views': 'Non ci sono viste ("view")',
@@ -280,6 +288,7 @@
'Translation strings for the application': 'Translation strings for the application', 'Translation strings for the application': 'Translation strings for the application',
'try': 'prova', 'try': 'prova',
'try something like': 'prova qualcosa come', 'try something like': 'prova qualcosa come',
'Try the mobile interface': 'Try the mobile interface',
'try view': 'try view', 'try view': 'try view',
'Unable to check for upgrades': 'Impossibile controllare presenza di aggiornamenti', 'Unable to check for upgrades': 'Impossibile controllare presenza di aggiornamenti',
'unable to create application "%s"': 'impossibile creare applicazione "%s"', 'unable to create application "%s"': 'impossibile creare applicazione "%s"',
@@ -298,6 +307,7 @@
'Update:': 'Aggiorna:', 'Update:': 'Aggiorna:',
'upgrade web2py now': 'upgrade web2py now', 'upgrade web2py now': 'upgrade web2py now',
'upload': 'upload', 'upload': 'upload',
'Upload': 'Upload',
'Upload & install packed application': 'Carica ed installa pacchetto con applicazione', 'Upload & install packed application': 'Carica ed installa pacchetto con applicazione',
'Upload a package:': 'Upload a package:', 'Upload a package:': 'Upload a package:',
'Upload and install packed application': 'Upload and install packed application', 'Upload and install packed application': 'Upload and install packed application',
@@ -308,6 +318,7 @@
'Use an url:': 'Use an url:', 'Use an url:': 'Use an url:',
'variables': 'variables', 'variables': 'variables',
'Version': 'Versione', 'Version': 'Versione',
'Version %s.%s.%s %s (%s)': 'Version %s.%s.%s %s (%s)',
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s', 'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
'versioning': 'sistema di versioni', 'versioning': 'sistema di versioni',
'Versioning': 'Versioning', 'Versioning': 'Versioning',
+3 -2
View File
@@ -43,8 +43,9 @@ function doHighlight(highlight) {
if (window.ace_editor) { if (window.ace_editor) {
window.ace_editor.gotoLine(highlight.lineno); window.ace_editor.gotoLine(highlight.lineno);
} else if (window.mirror) { } else if (window.mirror) {
window.mirror.setSelection({line:highlight.lineno,ch:0}, // Put the cursor at the offending line:
{line:highlight.end,ch:0}); window.mirror.setCursor({line:highlight.lineno-1,
ch:highlight.offset+1});
} else if (window.eamy) { } else if (window.eamy) {
// not implemented // not implemented
} else if (window.textarea) { } else if (window.textarea) {
+15 -1
View File
@@ -229,7 +229,21 @@
{{=T('pygraphviz library not found')}} {{=T('pygraphviz library not found')}}
{{elif not databases:}} {{elif not databases:}}
{{=T("No databases in this application")}} {{=T("No databases in this application")}}
{{else:}} {{else:}}
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-download"></i> {{=T('Save model as...')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul>
</div>
<br />
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}} {{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}} {{pass}}
{{pass}} {{pass}}
@@ -22,4 +22,5 @@ function hideShowTranslated(){
<div class="languageform"> <div class="languageform">
{{=form}} {{=form}}
</div> </div>
<!-- end "edit_language" block --> <button class="btn" style="position:fixed; right:10px; bottom: 10px;" onclick="jQuery('form input[type=submit]').click()">Save Changes</button>
<!-- end "edit_language" block -->
+37 -8
View File
@@ -469,10 +469,10 @@ def ccache():
def table_template(table): def table_template(table):
from gluon.html import TR, TD, TABLE, TAG from gluon.html import TR, TD, TABLE, TAG
def FONT(*args, **kwargs): def FONT(*args, **kwargs):
return TAG.font(*args, **kwargs) return TAG.font(*args, **kwargs)
def types(field): def types(field):
f_type = field.type f_type = field.type
if not isinstance(f_type,str): if not isinstance(f_type,str):
@@ -480,7 +480,7 @@ def table_template(table):
elif f_type == 'string': elif f_type == 'string':
return field.length return field.length
elif f_type == 'id': elif f_type == 'id':
return B('pk') return B('pk')
elif f_type.startswith('reference') or \ elif f_type.startswith('reference') or \
f_type.startswith('list:reference'): f_type.startswith('list:reference'):
return B('fk') return B('fk')
@@ -495,7 +495,7 @@ def table_template(table):
face = "Helvetica" face = "Helvetica"
face_bold = "Helvetica Bold" face_bold = "Helvetica Bold"
border = 0 border = 0
rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
_colspan=3, _cellpadding=cellpadding, _colspan=3, _cellpadding=cellpadding,
_align="center", _bgcolor=color))) _align="center", _bgcolor=color)))
@@ -515,10 +515,31 @@ def table_template(table):
def bg_graph_model(): def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR') graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict()
for tablename in db.tables: for tablename in db.tables:
graph.add_node(tablename, name=tablename, shape='plaintext', if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel
else:
meta_graphmodel = dict(group='Undefined', color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename)) label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n,
style='filled',
color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group'])
for tablename in db.tables: for tablename in db.tables:
for field in db[tablename]: for field in db[tablename]:
@@ -532,7 +553,15 @@ def bg_graph_model():
graph.add_edge(n1, n2, color="#4C4C4C", label='') graph.add_edge(n1, n2, color="#4C4C4C", label='')
graph.layout() graph.layout()
return graph.draw(format='png', prog='dot') #return graph.draw(format='png', prog='dot')
if not request.args:
return graph.draw(format='png', prog='dot')
else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot':
return graph.string()
else:
return graph.draw(format=request.args(0), prog='dot')
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) return dict(databases=databases, pgv=pgv)
+15 -1
View File
@@ -229,7 +229,21 @@
{{=T('pygraphviz library not found')}} {{=T('pygraphviz library not found')}}
{{elif not databases:}} {{elif not databases:}}
{{=T("No databases in this application")}} {{=T("No databases in this application")}}
{{else:}} {{else:}}
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-download"></i> {{=T('Save model as...')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul>
</div>
<br />
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}} {{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}} {{pass}}
{{pass}} {{pass}}
+37 -8
View File
@@ -469,10 +469,10 @@ def ccache():
def table_template(table): def table_template(table):
from gluon.html import TR, TD, TABLE, TAG from gluon.html import TR, TD, TABLE, TAG
def FONT(*args, **kwargs): def FONT(*args, **kwargs):
return TAG.font(*args, **kwargs) return TAG.font(*args, **kwargs)
def types(field): def types(field):
f_type = field.type f_type = field.type
if not isinstance(f_type,str): if not isinstance(f_type,str):
@@ -480,7 +480,7 @@ def table_template(table):
elif f_type == 'string': elif f_type == 'string':
return field.length return field.length
elif f_type == 'id': elif f_type == 'id':
return B('pk') return B('pk')
elif f_type.startswith('reference') or \ elif f_type.startswith('reference') or \
f_type.startswith('list:reference'): f_type.startswith('list:reference'):
return B('fk') return B('fk')
@@ -495,7 +495,7 @@ def table_template(table):
face = "Helvetica" face = "Helvetica"
face_bold = "Helvetica Bold" face_bold = "Helvetica Bold"
border = 0 border = 0
rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
_colspan=3, _cellpadding=cellpadding, _colspan=3, _cellpadding=cellpadding,
_align="center", _bgcolor=color))) _align="center", _bgcolor=color)))
@@ -515,10 +515,31 @@ def table_template(table):
def bg_graph_model(): def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR') graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict()
for tablename in db.tables: for tablename in db.tables:
graph.add_node(tablename, name=tablename, shape='plaintext', if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel
else:
meta_graphmodel = dict(group='Undefined', color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename)) label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n,
style='filled',
color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group'])
for tablename in db.tables: for tablename in db.tables:
for field in db[tablename]: for field in db[tablename]:
@@ -532,7 +553,15 @@ def bg_graph_model():
graph.add_edge(n1, n2, color="#4C4C4C", label='') graph.add_edge(n1, n2, color="#4C4C4C", label='')
graph.layout() graph.layout()
return graph.draw(format='png', prog='dot') #return graph.draw(format='png', prog='dot')
if not request.args:
return graph.draw(format='png', prog='dot')
else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot':
return graph.string()
else:
return graph.draw(format=request.args(0), prog='dot')
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) return dict(databases=databases, pgv=pgv)
+15 -1
View File
@@ -229,7 +229,21 @@
{{=T('pygraphviz library not found')}} {{=T('pygraphviz library not found')}}
{{elif not databases:}} {{elif not databases:}}
{{=T("No databases in this application")}} {{=T("No databases in this application")}}
{{else:}} {{else:}}
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-download"></i> {{=T('Save model as...')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul>
</div>
<br />
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}} {{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{pass}} {{pass}}
{{pass}} {{pass}}
+7 -6
View File
@@ -1,3 +1,4 @@
<!--[if HTML5]><![endif]-->
<!DOCTYPE html> <!DOCTYPE html>
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ --> <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<!--[if lt IE 7]><html class="ie ie6 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]--> <!--[if lt IE 7]><html class="ie ie6 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
@@ -6,16 +7,16 @@
<!--[if IE 9]><html class="ie9 ie-lte9 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]--> <!--[if IE 9]><html class="ie9 ie-lte9 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js" lang="{{=T.accepted_language or 'en'}}"> <!--<![endif]--> <!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js" lang="{{=T.accepted_language or 'en'}}"> <!--<![endif]-->
<head> <head>
<meta charset="utf-8" /> <title>{{=response.title or request.application}}</title>
<!--[if !HTML5]>
<meta http-equiv="X-UA-Compatible" content="IE=edge{{=not request.is_local and ',chrome=1' or ''}}">
<![endif]-->
<!-- www.phpied.com/conditional-comments-block-downloads/ --> <!-- www.phpied.com/conditional-comments-block-downloads/ -->
<!-- Always force latest IE rendering engine <!-- Always force latest IE rendering engine
(even in intranet) & Chrome Frame (even in intranet) & Chrome Frame
Remove this if you use the .htaccess --> Remove this if you use the .htaccess -->
<!--[if IE]>
<meta http-equiv="X-UA-Compatible" content="IE=edge{{=not request.is_local and ',chrome=1' or ''}}"> <meta charset="utf-8" />
<![endif]-->
<title>{{=response.title or request.application}}</title>
<!-- http://dev.w3.org/html5/markup/meta.name.html --> <!-- http://dev.w3.org/html5/markup/meta.name.html -->
<meta name="application-name" content="{{=request.application}}" /> <meta name="application-name" content="{{=request.application}}" />
+14 -13
View File
@@ -609,7 +609,7 @@ def protolinks_simple(proto, url):
#elif proto == 'embed': # NOTE: embed is a synonym to iframe now #elif proto == 'embed': # NOTE: embed is a synonym to iframe now
# return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url) # return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url)
elif proto == 'qr': elif proto == 'qr':
return '<img width="80px" src="http://qrcode.kaywa.com/img.php?s=8&amp;d=%s" alt="qr code" />'%url return '<img style="width:80px" src="http://qrcode.kaywa.com/img.php?s=8&amp;d=%s" alt="qr code" />'%url
return proto+':'+url return proto+':'+url
def render(text, def render(text,
@@ -701,7 +701,7 @@ def render(text,
'<p><img src="http://example.com" alt="this is an image" style="float:left" /></p>' '<p><img src="http://example.com" alt="this is an image" style="float:left" /></p>'
>>> render('[[this is an image http://example.com left 200px]]') >>> render('[[this is an image http://example.com left 200px]]')
'<p><img src="http://example.com" alt="this is an image" style="float:left" width="200px" /></p>' '<p><img src="http://example.com" alt="this is an image" style="float:left;width:200px" /></p>'
>>> render("[[Your browser doesn't support <video> HTML5 tag http://example.com video]]") >>> render("[[Your browser doesn't support <video> HTML5 tag http://example.com video]]")
'<p><video controls="controls"><source src="http://example.com" />Your browser doesn\\'t support &lt;video&gt; HTML5 tag</video></p>' '<p><video controls="controls"><source src="http://example.com" />Your browser doesn\\'t support &lt;video&gt; HTML5 tag</video></p>'
@@ -740,7 +740,7 @@ def render(text,
'<p>auto-image: (<img src="http://example.com/image.jpeg" controls />)</p>' '<p>auto-image: (<img src="http://example.com/image.jpeg" controls />)</p>'
>>> render("qr: (qr:http://example.com/image.jpeg)") >>> render("qr: (qr:http://example.com/image.jpeg)")
'<p>qr: (<img width="80px" src="http://qrcode.kaywa.com/img.php?s=8&amp;d=http://example.com/image.jpeg" alt="qr code" />)</p>' '<p>qr: (<img style="width:80px" src="http://qrcode.kaywa.com/img.php?s=8&amp;d=http://example.com/image.jpeg" alt="qr code" />)</p>'
>>> render("embed: (embed:http://example.com/page)") >>> render("embed: (embed:http://example.com/page)")
'<p>embed: (<iframe src="http://example.com/page" frameborder="0" allowfullscreen></iframe>)</p>' '<p>embed: (<iframe src="http://example.com/page" frameborder="0" allowfullscreen></iframe>)</p>'
@@ -776,10 +776,10 @@ def render(text,
'<p>title9: <img src="http://example.com" alt="test message" title="title" style="float:left" /></p>' '<p>title9: <img src="http://example.com" alt="test message" title="title" style="float:left" /></p>'
>>> render("title10: [[test message [title] http://example.com right 100px]]") >>> render("title10: [[test message [title] http://example.com right 100px]]")
'<p>title10: <img src="http://example.com" alt="test message" title="title" style="float:right" width="100px" /></p>' '<p>title10: <img src="http://example.com" alt="test message" title="title" style="float:right;width:100px" /></p>'
>>> render("title11: [[test message [title] http://example.com center 200px]]") >>> render("title11: [[test message [title] http://example.com center 200px]]")
'<p>title11: <p style="text-align:center"><img src="http://example.com" alt="test message" title="title" width="200px" /></p></p>' '<p>title11: <p style="text-align:center"><img src="http://example.com" alt="test message" title="title" style="width:200px" /></p></p>'
>>> render(r"\\[[probe]]") >>> render(r"\\[[probe]]")
'<p>[[probe]]</p>' '<p>[[probe]]</p>'
@@ -1236,23 +1236,24 @@ def render(text,
return m.group(0) return m.group(0)
k = escape(k) k = escape(k)
t = t or '' t = t or ''
width = ' width="%s"' % w if w else '' style = 'width:%s' % w if w else ''
title = ' title="%s"' % escape(a).replace(META, DISABLED_META) if a else '' title = ' title="%s"' % escape(a).replace(META, DISABLED_META) if a else ''
style = p_begin = p_end = '' p_begin = p_end = ''
if p == 'center': if p == 'center':
p_begin = '<p style="text-align:center">' p_begin = '<p style="text-align:center">'
p_end = '</p>'+pp p_end = '</p>'+pp
elif p in ('left','right'): elif p in ('left','right'):
style = ' style="float:%s"' % p style = ('float:%s' % p)+(';%s' % style if style else '')
if style:
style = ' style="%s"' % style
if p in ('video','audio'): if p in ('video','audio'):
t = render(t, {}, {}, 'br', URL, environment, latex, t = render(t, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print) autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(width)s><source src="%(k)s" />%(t)s</%(p)s>' \ return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, width=width, k=k, t=t) % dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else '' alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s%(width)s />%(end)s' \ return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
% dict(begin=p_begin, k=k, alt=alt, title=title, % dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
style=style, width=width, end=p_end)
def sub_link(m): def sub_link(m):
t,a,k,p = m.group('t','a','k','p') t,a,k,p = m.group('t','a','k','p')
+24 -20
View File
@@ -61,26 +61,30 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
and isinstance(globals, dict): and isinstance(globals, dict):
import_tb = None import_tb = None
try: try:
items = current.request.folder.split(os.path.sep) try:
if not items[-1]: oname = name if not name.startswith('.') else '.'+name
items = items[:-1] return NATIVE_IMPORTER(oname, globals, locals, fromlist, level)
modules_prefix = '.'.join(items[-2:]) + '.modules' except ImportError:
if not fromlist: items = current.request.folder.split(os.path.sep)
# import like "import x" or "import x.y" if not items[-1]:
result = None items = items[:-1]
for itemname in name.split("."): modules_prefix = '.'.join(items[-2:]) + '.modules'
new_mod = base_importer( if not fromlist:
modules_prefix, globals, locals, [itemname], level) # import like "import x" or "import x.y"
try: result = None
result = result or new_mod.__dict__[itemname] for itemname in name.split("."):
except KeyError, e: new_mod = base_importer(
raise ImportError, 'Cannot import module %s' % str(e) modules_prefix, globals, locals, [itemname], level)
modules_prefix += "." + itemname try:
return result result = result or new_mod.__dict__[itemname]
else: except KeyError, e:
# import like "from x import a, b, ..." raise ImportError, 'Cannot import module %s' % str(e)
pname = modules_prefix + "." + name modules_prefix += "." + itemname
return base_importer(pname, globals, locals, fromlist, level) return result
else:
# import like "from x import a, b, ..."
pname = modules_prefix + "." + name
return base_importer(pname, globals, locals, fromlist, level)
except ImportError, e1: except ImportError, e1:
import_tb = sys.exc_info()[2] import_tb = sys.exc_info()[2]
try: try:
+445 -63
View File
@@ -339,7 +339,10 @@ if not 'google' in DRIVERS:
LOGGER.debug('no Oracle driver cx_Oracle') LOGGER.debug('no Oracle driver cx_Oracle')
try: try:
#try:
import pyodbc import pyodbc
#except ImportError:
# from contrib.pypyodbc import pypyodbc as pyodbc
DRIVERS.append('MSSQL(pyodbc)') DRIVERS.append('MSSQL(pyodbc)')
DRIVERS.append('DB2(pyodbc)') DRIVERS.append('DB2(pyodbc)')
DRIVERS.append('Teradata(pyodbc)') DRIVERS.append('Teradata(pyodbc)')
@@ -876,7 +879,7 @@ class BaseAdapter(ConnectionPool):
# The reason is that we do not want to trigger # The reason is that we do not want to trigger
# a migration simply because a default value changes. # a migration simply because a default value changes.
not_null = self.NOT_NULL(field.default, field_type) not_null = self.NOT_NULL(field.default, field_type)
ftype = ftype.replace('NOT NULL', not_null) ftype = ftype.replace('NOT NULL', not_null)
sql_fields_aux[field_name] = dict(sql=ftype) sql_fields_aux[field_name] = dict(sql=ftype)
# Postgres - PostGIS: # Postgres - PostGIS:
# geometry fields are added after the table has been created, not now # geometry fields are added after the table has been created, not now
@@ -1233,12 +1236,24 @@ class BaseAdapter(ConnectionPool):
return '(%s LIKE %s)' % (self.expand(first), return '(%s LIKE %s)' % (self.expand(first),
self.expand('%'+second, 'string')) self.expand('%'+second, 'string'))
def CONTAINS(self, first, second): def CONTAINS(self, first, second, case_sensitive=False):
if first.type in ('string', 'text', 'json'): if isinstance(second,Expression):
key = '%'+str(second).replace('%','%%')+'%' field = self.expand(first)
elif first.type.startswith('list:'): expr = self.expand(second,'string')
key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%' if first.type.startswith('list:'):
return '(%s LIKE %s)' % (self.expand(first),self.expand(key,'string')) expr = 'CONCAT("|", %s, "|")' % expr
elif not first.type in ('string', 'text', 'json'):
raise RuntimeError("Expression Not Supported")
return 'INSTR(%s,%s)' % (field, expr)
else:
if first.type in ('string', 'text', 'json'):
key = '%'+str(second).replace('%','%%')+'%'
elif first.type.startswith('list:'):
key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%'
else:
raise RuntimeError("Expression Not Supported")
op = case_sensitive and self.LIKE or self.ILIKE
return op(first,key)
def EQ(self, first, second=None): def EQ(self, first, second=None):
if second is None: if second is None:
@@ -1317,10 +1332,11 @@ class BaseAdapter(ConnectionPool):
first = expression.first first = expression.first
second = expression.second second = expression.second
op = expression.op op = expression.op
optional_args = expression.optional_args or {}
if not second is None: if not second is None:
return op(first, second) return op(first, second, **optional_args)
elif not first is None: elif not first is None:
return op(first) return op(first,**optional_args)
elif isinstance(op, str): elif isinstance(op, str):
if op.endswith(';'): if op.endswith(';'):
op=op[:-1] op=op[:-1]
@@ -2569,11 +2585,25 @@ class PostgreSQLAdapter(BaseAdapter):
def after_connection(self): def after_connection(self):
self.connection.set_client_encoding('UTF8') self.connection.set_client_encoding('UTF8')
self.execute("SET standard_conforming_strings=on;") self.execute("SET standard_conforming_strings=on;")
self.try_json()
def lastrowid(self,table): def lastrowid(self,table):
self.execute("select currval('%s')" % table._sequence_name) self.execute("select currval('%s')" % table._sequence_name)
return int(self.cursor.fetchone()[0]) return int(self.cursor.fetchone()[0])
def try_json(self):
# check JSON data type support
# (to be added to after_connection)
if self.driver_name == "pg8000":
supports_json = self.connection.server_version >= "9.2.0"
elif (self.driver_name == "psycopg2") and \
(self.driver.__version__ >= "2.0.12"):
supports_json = self.connection.server_version >= 90200
elif self.driver_name == "zxJDBC":
supports_json = self.connection.dbversion >= "9.2.0"
else: supports_json = None
if supports_json: self.types["json"] = "JSON"
else: LOGGER.debug("Your database version does not support the JSON data type (using TEXT instead)")
def LIKE(self,first,second): def LIKE(self,first,second):
args = (self.expand(first), self.expand(second,'string')) args = (self.expand(first), self.expand(second,'string'))
@@ -2601,12 +2631,13 @@ class PostgreSQLAdapter(BaseAdapter):
return '(%s ILIKE %s)' % (self.expand(first), return '(%s ILIKE %s)' % (self.expand(first),
self.expand('%'+second,'string')) self.expand('%'+second,'string'))
def CONTAINS(self,first,second): def CONTAINS(self,first,second,case_sensitive=False):
if first.type in ('string','text', 'json'): if first.type in ('string','text', 'json'):
key = '%'+str(second).replace('%','%%')+'%' key = '%'+str(second).replace('%','%%')+'%'
elif first.type.startswith('list:'): elif first.type.startswith('list:'):
key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%' key = '%|'+str(second).replace('|','||').replace('%','%%')+'|%'
return '(%s ILIKE %s)' % (self.expand(first),self.expand(key,'string')) op = case_sensitive and self.LIKE or self.ILIKE
return op(first,key)
# GIS functions # GIS functions
@@ -2797,6 +2828,7 @@ class JDBCPostgreSQLAdapter(PostgreSQLAdapter):
self.connection.set_client_encoding('UTF8') self.connection.set_client_encoding('UTF8')
self.execute('BEGIN;') self.execute('BEGIN;')
self.execute("SET CLIENT_ENCODING TO 'UNICODE';") self.execute("SET CLIENT_ENCODING TO 'UNICODE';")
self.try_json()
class OracleAdapter(BaseAdapter): class OracleAdapter(BaseAdapter):
@@ -3037,6 +3069,8 @@ class MSSQLAdapter(BaseAdapter):
if limitby: if limitby:
(lmin, lmax) = limitby (lmin, lmax) = limitby
sql_s += ' TOP %i' % lmax sql_s += ' TOP %i' % lmax
if 'GROUP BY' in sql_o:
sql_o = sql_o[:sql_o.find('ORDER BY ')]
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
TRUE = 1 TRUE = 1
@@ -3179,7 +3213,18 @@ class MSSQL3Adapter(MSSQLAdapter):
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby): def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
if limitby: if limitby:
(lmin, lmax) = limitby (lmin, lmax) = limitby
return 'SELECT %s FROM (SELECT %s ROW_NUMBER() over (order by id) AS w_row, %s FROM %s%s%s) TMP WHERE w_row BETWEEN %i AND %s;' % (sql_f,sql_s,sql_f,sql_t,sql_w,sql_o,lmin,lmax) if lmin == 0:
sql_s += ' TOP %i' % lmax
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
lmin += 1
sql_o_inner = sql_o[sql_o.find('ORDER BY ')+9:]
sql_g_inner = sql_o[:sql_o.find('ORDER BY ')]
sql_f_outer = ['f_%s' % f for f in range(len(sql_f.split(',')))]
sql_f_inner = [f for f in sql_f.split(',')]
sql_f_iproxy = ['%s AS %s' % (o, n) for (o, n) in zip(sql_f_inner, sql_f_outer)]
sql_f_iproxy = ', '.join(sql_f_iproxy)
sql_f_oproxy = ', '.join(sql_f_outer)
return 'SELECT %s %s FROM (SELECT %s ROW_NUMBER() OVER (ORDER BY %s) AS w_row, %s FROM %s%s%s) TMP WHERE w_row BETWEEN %i AND %s;' % (sql_s,sql_f_oproxy,sql_s,sql_f,sql_f_iproxy,sql_t,sql_w,sql_g_inner,lmin,lmax)
return 'SELECT %s %s FROM %s%s%s;' % (sql_s,sql_f,sql_t,sql_w,sql_o) return 'SELECT %s %s FROM %s%s%s;' % (sql_s,sql_f,sql_t,sql_w,sql_o)
def rowslice(self,rows,minimum=0,maximum=None): def rowslice(self,rows,minimum=0,maximum=None):
return rows return rows
@@ -3224,6 +3269,7 @@ class MSSQL2Adapter(MSSQLAdapter):
def execute(self,a): def execute(self,a):
return self.log_execute(a.decode('utf8')) return self.log_execute(a.decode('utf8'))
class SybaseAdapter(MSSQLAdapter): class SybaseAdapter(MSSQLAdapter):
drivers = ('Sybase',) drivers = ('Sybase',)
@@ -3365,13 +3411,17 @@ class FireBirdAdapter(BaseAdapter):
def SUBSTRING(self,field,parameters): def SUBSTRING(self,field,parameters):
return 'SUBSTRING(%s from %s for %s)' % (self.expand(field), parameters[0], parameters[1]) return 'SUBSTRING(%s from %s for %s)' % (self.expand(field), parameters[0], parameters[1])
def CONTAINS(self, first, second): def CONTAINING(self,first,second):
"case in-sensitive like operator"
return '(%s CONTAINING %s)' % (self.expand(first),
self.expand(second, 'string'))
def CONTAINS(self, first, second, case_sensitive=False):
if first.type in ('string','text'): if first.type in ('string','text'):
key = str(second).replace('%','%%') key = str(second).replace('%','%%')
elif first.type.startswith('list:'): elif first.type.startswith('list:'):
key = '|'+str(second).replace('|','||').replace('%','%%')+'|' key = '|'+str(second).replace('|','||').replace('%','%%')+'|'
return '(%s CONTAINING %s)' % (self.expand(first), return self.CONTAINING(first,second)
self.expand(key,'string'))
def _drop(self,table,mode): def _drop(self,table,mode):
sequence_name = table._sequence_name sequence_name = table._sequence_name
@@ -4531,7 +4581,8 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
second = [Key.from_path(first._tablename, int(i)) for i in second] second = [Key.from_path(first._tablename, int(i)) for i in second]
return [GAEF(first.name,'in',second,lambda a,b:a in b)] return [GAEF(first.name,'in',second,lambda a,b:a in b)]
def CONTAINS(self,first,second): def CONTAINS(self,first,second,case_sensitive=False):
# silently ignoring: GAE can only do case sensitive matches!
if not first.type.startswith('list:'): if not first.type.startswith('list:'):
raise SyntaxError("Not supported") raise SyntaxError("Not supported")
return [GAEF(first.name,'=',self.expand(second,first.type[5:]),lambda a,b:b in a)] return [GAEF(first.name,'=',self.expand(second,first.type[5:]),lambda a,b:b in a)]
@@ -4989,7 +5040,7 @@ class MongoDBAdapter(NoSQLAdapter):
'list:reference': list, 'list:reference': list,
} }
error_messages = {"javascript_needed": "This must yet be replaced" + error_messages = {"javascript_needed": "This must yet be replaced" +
" with javascript in order to work."} " with javascript in order to work."}
def __init__(self,db,uri='mongodb://127.0.0.1:5984/db', def __init__(self,db,uri='mongodb://127.0.0.1:5984/db',
@@ -5068,12 +5119,12 @@ class MongoDBAdapter(NoSQLAdapter):
raise ValueError( raise ValueError(
"invalid objectid argument string: %s" % e) "invalid objectid argument string: %s" % e)
else: else:
raise ValueError("Invalid objectid argument string. " + raise ValueError("Invalid objectid argument string. " +
"Requires an integer or base 16 value") "Requires an integer or base 16 value")
elif isinstance(arg, self.ObjectId): elif isinstance(arg, self.ObjectId):
return arg return arg
if not isinstance(arg, (int, long)): if not isinstance(arg, (int, long)):
raise TypeError("object_id argument must be of type " + raise TypeError("object_id argument must be of type " +
"ObjectId or an objectid representable integer") "ObjectId or an objectid representable integer")
if arg == 0: if arg == 0:
hexvalue = "".zfill(24) hexvalue = "".zfill(24)
@@ -5105,7 +5156,7 @@ class MongoDBAdapter(NoSQLAdapter):
return value return value
return value return value
# Safe determines whether a asynchronious request is done or a # Safe determines whether a asynchronious request is done or a
# synchronious action is done # synchronious action is done
# For safety, we use by default synchronious requests # For safety, we use by default synchronious requests
def insert(self, table, fields, safe=None): def insert(self, table, fields, safe=None):
@@ -5225,7 +5276,7 @@ class MongoDBAdapter(NoSQLAdapter):
elif len(fields) != 0: elif len(fields) != 0:
tablename = fields[0].tablename tablename = fields[0].tablename
else: else:
raise SyntaxError("The table name could not be found in " + raise SyntaxError("The table name could not be found in " +
"the query nor from the select statement.") "the query nor from the select statement.")
mongoqry_dict = self.expand(query) mongoqry_dict = self.expand(query)
@@ -5251,7 +5302,7 @@ class MongoDBAdapter(NoSQLAdapter):
sort=mongosort_list, snapshot=snapshot).count()} sort=mongosort_list, snapshot=snapshot).count()}
else: else:
# pymongo cursor object # pymongo cursor object
mongo_list_dicts = ctable.find(mongoqry_dict, mongo_list_dicts = ctable.find(mongoqry_dict,
mongofields_dict, skip=limitby_skip, mongofields_dict, skip=limitby_skip,
limit=limitby_limit, sort=mongosort_list, limit=limitby_limit, sort=mongosort_list,
snapshot=snapshot) snapshot=snapshot)
@@ -5348,7 +5399,7 @@ class MongoDBAdapter(NoSQLAdapter):
if safe is None: if safe is None:
safe = self.safe safe = self.safe
amount = 0 amount = 0
amount = self.count(query, False) amount = self.count(query, False)
if not isinstance(query, Query): if not isinstance(query, Query):
raise RuntimeError("query type %s is not supported" % \ raise RuntimeError("query type %s is not supported" % \
type(query)) type(query))
@@ -5484,8 +5535,9 @@ class MongoDBAdapter(NoSQLAdapter):
return {self.expand(first): ('/%s^/' % \ return {self.expand(first): ('/%s^/' % \
self.expand(second, 'string'))} self.expand(second, 'string'))}
def CONTAINS(self, first, second): def CONTAINS(self, first, second, case_sensitive=False):
#There is a technical difference, but mongodb doesn't support # silently ignore, only case sensitive
# There is a technical difference, but mongodb doesn't support
# that, but the result will be the same # that, but the result will be the same
return {self.expand(first) : ('/%s/' % \ return {self.expand(first) : ('/%s/' % \
self.expand(second, 'string'))} self.expand(second, 'string'))}
@@ -5515,7 +5567,8 @@ class MongoDBAdapter(NoSQLAdapter):
re.escape(self.expand(second, 'string')) + '$'}} re.escape(self.expand(second, 'string')) + '$'}}
#TODO verify full compatibilty with official oracle contains operator #TODO verify full compatibilty with official oracle contains operator
def CONTAINS(self, first, second): def CONTAINS(self, first, second, case_sensitive=False):
# silently ignore, only case sensitive
#There is a technical difference, but mongodb doesn't support #There is a technical difference, but mongodb doesn't support
# that, but the result will be the same # that, but the result will be the same
#TODO contains operators need to be transformed to Regex #TODO contains operators need to be transformed to Regex
@@ -5645,7 +5698,7 @@ class IMAPAdapter(NoSQLAdapter):
# This avoids the extra server names retrieval # This avoids the extra server names retrieval
imapdb.define_tables({"inbox": "INBOX"}) imapdb.define_tables({"inbox": "INBOX"})
# Selects without content/attachments/email columns will only # Selects without content/attachments/email columns will only
# fetch header and flags # fetch header and flags
@@ -6038,7 +6091,7 @@ class IMAPAdapter(NoSQLAdapter):
# keep the requests small for header/flags # keep the requests small for header/flags
if any([(field.name in ["content", "size", if any([(field.name in ["content", "size",
"attachments", "email"]) for "attachments", "email"]) for
field in fields]): field in fields]):
imap_fields = "(RFC822 FLAGS)" imap_fields = "(RFC822 FLAGS)"
else: else:
@@ -6305,7 +6358,8 @@ class IMAPAdapter(NoSQLAdapter):
# result = "(%s %s)" % (self.expand(first), self.expand(second)) # result = "(%s %s)" % (self.expand(first), self.expand(second))
return result return result
def CONTAINS(self, first, second): def CONTAINS(self, first, second, case_sensitive=False):
# silently ignore, only case sensitive
result = None result = None
name = self.search_fields[first.name] name = self.search_fields[first.name]
@@ -6482,6 +6536,7 @@ ADAPTERS = {
'oracle': OracleAdapter, 'oracle': OracleAdapter,
'mssql': MSSQLAdapter, 'mssql': MSSQLAdapter,
'mssql2': MSSQL2Adapter, 'mssql2': MSSQL2Adapter,
'mssql3': MSSQL3Adapter,
'sybase': SybaseAdapter, 'sybase': SybaseAdapter,
'db2': DB2Adapter, 'db2': DB2Adapter,
'teradata': TeradataAdapter, 'teradata': TeradataAdapter,
@@ -6644,6 +6699,8 @@ class Row(object):
def __setitem__(self, key, value): def __setitem__(self, key, value):
setattr(self, str(key), value) setattr(self, str(key), value)
__delitem__ = delattr
__copy__ = lambda self: Row(self) __copy__ = lambda self: Row(self)
__call__ = __getitem__ __call__ = __getitem__
@@ -7081,8 +7138,12 @@ class DAL(object):
:attempts (defaults to 5). Number of times to attempt connecting :attempts (defaults to 5). Number of times to attempt connecting
""" """
dbdict = None
if uri == '<zombie>' and db_uid is not None: return if uri == '<zombie>' and db_uid is not None: return
elif isinstance(uri, dict):
dbdict = uri
uri = dbdict["uri"]
codec = dbdict["codec"] or codec
if not decode_credentials: if not decode_credentials:
credential_decoder = lambda cred: cred credential_decoder = lambda cred: cred
else: else:
@@ -7164,32 +7225,44 @@ class DAL(object):
self._fake_migrate = fake_migrate self._fake_migrate = fake_migrate
self._migrate_enabled = migrate_enabled self._migrate_enabled = migrate_enabled
self._fake_migrate_all = fake_migrate_all self._fake_migrate_all = fake_migrate_all
if auto_import: if auto_import or dbdict:
self.import_table_definitions(adapter.folder) self.import_table_definitions(adapter.folder,
items=dbdict["items"])
@property @property
def tables(self): def tables(self):
return self._tables return self._tables
def import_table_definitions(self,path,migrate=False,fake_migrate=False): def import_table_definitions(self, path, migrate=False,
fake_migrate=False, items=None):
pattern = pjoin(path,self._uri_hash+'_*.table') pattern = pjoin(path,self._uri_hash+'_*.table')
for filename in glob.glob(pattern): if items:
tfile = self._adapter.file_open(filename, 'r') for tablename, table in items.iteritems():
try: # TODO: read all field/table options
sql_fields = pickle.load(tfile) fields = []
name = filename[len(pattern)-7:-6] for fieldname, field in table["items"].iteritems():
mf = [(value['sortable'], type = field["type"]
Field(key, fields.append(Field(fieldname, type))
type=value['type'], self.define_table(tablename, *fields)
length=value.get('length',None), else:
notnull=value.get('notnull',False), for filename in glob.glob(pattern):
unique=value.get('unique',False))) \ tfile = self._adapter.file_open(filename, 'r')
for key, value in sql_fields.iteritems()] try:
mf.sort(lambda a,b: cmp(a[0],b[0])) sql_fields = pickle.load(tfile)
self.define_table(name,*[item[1] for item in mf], name = filename[len(pattern)-7:-6]
**dict(migrate=migrate,fake_migrate=fake_migrate)) mf = [(value['sortable'],
finally: Field(key,
self._adapter.file_close(tfile) type=value['type'],
length=value.get('length',None),
notnull=value.get('notnull',False),
unique=value.get('unique',False))) \
for key, value in sql_fields.iteritems()]
mf.sort(lambda a,b: cmp(a[0],b[0]))
self.define_table(name,*[item[1] for item in mf],
**dict(migrate=migrate,
fake_migrate=fake_migrate))
finally:
self._adapter.file_close(tfile)
def check_reserved_keyword(self, name): def check_reserved_keyword(self, name):
""" """
@@ -7223,14 +7296,14 @@ def index():
"/{person.name}/pets[pet.ownedby]/{pet.name}", "/{person.name}/pets[pet.ownedby]/{pet.name}",
"/{person.name}/pets[pet.ownedby]/{pet.name}/:field", "/{person.name}/pets[pet.ownedby]/{pet.name}/:field",
("/dogs[pet]", db.pet.info=='dog'), ("/dogs[pet]", db.pet.info=='dog'),
("/dogs[pet]/{pet.name.startswith}", db.pet.info=='dog'), ("/dogs[pet]/{pet.name.startswith}", db.pet.info=='dog'),
] ]
parser = db.parse_as_rest(patterns,args,vars) parser = db.parse_as_rest(patterns,args,vars)
if parser.status == 200: if parser.status == 200:
return dict(content=parser.response) return dict(content=parser.response)
else: else:
raise HTTP(parser.status,parser.error) raise HTTP(parser.status,parser.error)
def POST(table_name,**vars): def POST(table_name,**vars):
if table_name == 'person': if table_name == 'person':
return db.person.validate_and_insert(**vars) return db.person.validate_and_insert(**vars)
@@ -7321,10 +7394,12 @@ def index():
return Row({'status':200,'pattern':'list', return Row({'status':200,'pattern':'list',
'error':None,'response':patterns}) 'error':None,'response':patterns})
for pattern in patterns: for pattern in patterns:
basequery, exposedfields = None, []
if isinstance(pattern,tuple): if isinstance(pattern,tuple):
pattern, basequery = pattern if len(pattern)==2:
else: pattern, basequery = pattern
basequery = None elif len(pattern)>2:
pattern, basequery, exposedfields = pattern[0:3]
otable=table=None otable=table=None
if not isinstance(queries,dict): if not isinstance(queries,dict):
dbset=db(queries) dbset=db(queries)
@@ -7437,7 +7512,10 @@ def index():
orderby = [db[table][f] if not f.startswith('~') else ~db[table][f[1:]] for f in ofields] orderby = [db[table][f] if not f.startswith('~') else ~db[table][f[1:]] for f in ofields]
except (KeyError, AttributeError): except (KeyError, AttributeError):
return Row({'status':400,'error':'invalid orderby','response':None}) return Row({'status':400,'error':'invalid orderby','response':None})
fields = [field for field in db[table] if field.readable] if exposedfields:
fields = [field for field in db[table] if str(field).split('.')[-1] in exposedfields and field.readable]
else:
fields = [field for field in db[table] if field.readable]
count = dbset.count() count = dbset.count()
try: try:
offset = int(vars.get('offset',None) or 0) offset = int(vars.get('offset',None) or 0)
@@ -7525,6 +7603,38 @@ def index():
if on_define: on_define(table) if on_define: on_define(table)
return table return table
def as_dict(self, flat=False, sanitize=True):
dbname = codec = uid = uri = None
if not sanitize:
uri, dbname, codec, uid = (self._uri, self._dbname,
self._db_codec, self._db_uid)
db_as_dict = dict(items={}, tables=[], uri=uri, dbname=dbname,
codec=codec, uid=uid)
for table in self:
tablename = str(table)
db_as_dict["tables"].append(tablename)
db_as_dict["items"][tablename] = table.as_dict(flat=flat,
sanitize=sanitize)
return db_as_dict
def as_xml(self, sanitize=True):
if not have_serializers:
raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.xml(d)
def as_json(self, sanitize=True):
if not have_serializers:
raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.json(d)
def as_yaml(self, sanitize=True):
if not have_serializers:
raise ImportError("No YAML serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.yaml(d)
def __contains__(self, tablename): def __contains__(self, tablename):
try: try:
return tablename in self.tables return tablename in self.tables
@@ -7576,6 +7686,9 @@ def index():
query = self._adapter.id_query(query) query = self._adapter.id_query(query)
elif isinstance(query,Field): elif isinstance(query,Field):
query = query!=None query = query!=None
elif isinstance(query, dict):
icf = query.get("ignore_common_filters")
if icf: ignore_common_filters = icf
return Set(self, query, ignore_common_filters=ignore_common_filters) return Set(self, query, ignore_common_filters=ignore_common_filters)
def commit(self): def commit(self):
@@ -8411,6 +8524,34 @@ class Table(object):
if id_map and cid is not None: if id_map and cid is not None:
id_map_self[int(line[cid])] = new_id id_map_self[int(line[cid])] = new_id
def as_dict(self, flat=False, sanitize=True):
tablename = str(self)
table_as_dict = dict(name=tablename, items={}, fields=[])
for field in self:
if (field.readable or field.writable) or (not sanitize):
table_as_dict["fields"].append(field.name)
table_as_dict["items"][field.name] = \
field.as_dict(flat=flat, sanitize=sanitize)
return table_as_dict
def as_xml(self, sanitize=True):
if not have_serializers:
raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.xml(d)
def as_json(self, sanitize=True):
if not have_serializers:
raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.json(d)
def as_yaml(self, sanitize=True):
if not have_serializers:
raise ImportError("No YAML serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.yaml(d)
def with_alias(self, alias): def with_alias(self, alias):
return self._db._adapter.alias(self,alias) return self._db._adapter.alias(self,alias)
@@ -8438,6 +8579,7 @@ class Expression(object):
first=None, first=None,
second=None, second=None,
type=None, type=None,
**optional_args
): ):
self.db = db self.db = db
@@ -8450,6 +8592,7 @@ class Expression(object):
self.type = first.type self.type = first.type
else: else:
self.type = type self.type = type
self.optional_args = optional_args
def sum(self): def sum(self):
db = self.db db = self.db
@@ -8639,24 +8782,30 @@ class Expression(object):
raise SyntaxError("endswith used with incompatible field type") raise SyntaxError("endswith used with incompatible field type")
return Query(db, db._adapter.ENDSWITH, self, value) return Query(db, db._adapter.ENDSWITH, self, value)
def contains(self, value, all=False): def contains(self, value, all=False, case_sensitive=False):
"""
The case_sensitive parameters is only useful for PostgreSQL
For other RDMBs it is ignored and contains is always case in-sensitive
For MongoDB and GAE contains is always case sensitive
"""
db = self.db db = self.db
if isinstance(value,(list, tuple)): if isinstance(value,(list, tuple)):
subqueries = [self.contains(str(v).strip()) for v in value if str(v).strip()] subqueries = [self.contains(str(v).strip(),case_sensitive=case_sensitive)
for v in value if str(v).strip()]
if not subqueries: if not subqueries:
return self.contains('') return self.contains('')
else: else:
return reduce(all and AND or OR,subqueries) return reduce(all and AND or OR,subqueries)
if not self.type in ('string', 'text', 'json') and not self.type.startswith('list:'): if not self.type in ('string', 'text', 'json') and not self.type.startswith('list:'):
raise SyntaxError("contains used with incompatible field type") raise SyntaxError("contains used with incompatible field type")
return Query(db, db._adapter.CONTAINS, self, value) return Query(db, db._adapter.CONTAINS, self, value, case_sensitive=case_sensitive)
def with_alias(self, alias): def with_alias(self, alias):
db = self.db db = self.db
return Expression(db, db._adapter.AS, self, alias, self.type) return Expression(db, db._adapter.AS, self, alias, self.type)
# GIS expressions # GIS expressions
def st_asgeojson(self, precision=15, options=0, version=1): def st_asgeojson(self, precision=15, options=0, version=1):
return Expression(self.db, self.db._adapter.ST_ASGEOJSON, self, return Expression(self.db, self.db._adapter.ST_ASGEOJSON, self,
dict(precision=precision, options=options, dict(precision=precision, options=options,
@@ -8990,7 +9139,7 @@ class Field(Expression):
stream = self.uploadfs.open(name, 'rb') stream = self.uploadfs.open(name, 'rb')
else: else:
# ## if file is on regular filesystem # ## if file is on regular filesystem
stream = open(pjoin(file_properties['path'], name), 'rb') stream = pjoin(file_properties['path'], name)
return (filename, stream) return (filename, stream)
def retrieve_file_properties(self, name, path=None): def retrieve_file_properties(self, name, path=None):
@@ -9056,6 +9205,76 @@ class Field(Expression):
def count(self, distinct=None): def count(self, distinct=None):
return Expression(self.db, self.db._adapter.COUNT, self, distinct, 'integer') return Expression(self.db, self.db._adapter.COUNT, self, distinct, 'integer')
def as_dict(self, flat=False, sanitize=True):
attrs = ("readable", "writable", "label", "default", "name",
"type", "represent", "compute")
SERIALIZABLE_TYPES = (int, long, basestring, dict, list,
float, tuple, bool, type(None))
def flatten(obj):
if flat:
if isinstance(obj, flatten.__class__):
return str(type(obj))
elif isinstance(obj, type):
try:
obj = str(obj).split("'")[1]
except IndexError:
obj = str(obj)
elif not isinstance(obj, SERIALIZABLE_TYPES):
obj = str(obj)
return obj
def filter_requires(t, r):
if sanitize and any([keyword in str(t).upper() for
keyword in ("CRYPT", "IS_STRONG")]):
return None
for k, v in r.items():
if k == "other":
if not isinstance(v, dict):
other = v.__dict__
else: other = v
r[k] = {flatten(type(v)):
filter_requires(type(v), other)}
elif flat and (not isinstance(v, SERIALIZABLE_TYPES)):
r[k] = str(v)
return r
if isinstance(self.requires, (tuple, list, set)):
requires = dict([(flatten(type(r)),
filter_requires(type(r), r.__dict__)) for
r in self.requires])
else:
requires = {flatten(type(self.requires)):
filter_requires(type(self.requires),
self.requires.__dict__)}
d = dict(colname="%s.%s" % (self.tablename, self.name),
requires=requires)
d.update([(attr, flatten(getattr(self, attr))) for attr in attrs])
return d
def as_xml(self, sanitize=True):
if have_serializers:
xml = serializers.xml
else:
raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return xml(d)
def as_json(self, sanitize=True):
if have_serializers:
json = serializers.json
else:
raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return json(d)
def as_yaml(self, sanitize=True):
if have_serializers:
d = self.as_dict(flat=True, sanitize=sanitize)
return serializers.yaml(d)
else:
raise ImportError("No YAML serializers available")
def __nonzero__(self): def __nonzero__(self):
return True return True
@@ -9087,12 +9306,14 @@ class Query(object):
first=None, first=None,
second=None, second=None,
ignore_common_filters = False, ignore_common_filters = False,
**optional_args
): ):
self.db = self._db = db self.db = self._db = db
self.op = op self.op = op
self.first = first self.first = first
self.second = second self.second = second
self.ignore_common_filters = ignore_common_filters self.ignore_common_filters = ignore_common_filters
self.optional_args = optional_args
def __repr__(self): def __repr__(self):
return '<Query %s>' % BaseAdapter.expand(self.db._adapter,self) return '<Query %s>' % BaseAdapter.expand(self.db._adapter,self)
@@ -9111,10 +9332,77 @@ class Query(object):
return self.first return self.first
return Query(self.db,self.db._adapter.NOT,self) return Query(self.db,self.db._adapter.NOT,self)
def __eq__(self, other):
return repr(self) == repr(other)
def __ne__(self, other):
return not (self == other)
def case(self,t=1,f=0): def case(self,t=1,f=0):
return self.db._adapter.CASE(self,t,f) return self.db._adapter.CASE(self,t,f)
def as_dict(self, flat=False, sanitize=True):
"""Experimental stuff
This allows to return a plain dictionary with the basic
query representation. Can be used with json/xml services
for client-side db I/O
Example:
>>> q = db.auth_user.id != 0
>>> q.as_dict(flat=True)
{"op": "NE", "first":{"tablename": "auth_user",
"fieldname": "id"},
"second":0}
"""
SERIALIZABLE_TYPES = (tuple, dict, list, int, long, float,
basestring, type(None), bool)
def loop(d):
newd = dict()
for k, v in d.items():
if k in ("first", "second"):
if isinstance(v, self.__class__):
newd[k] = loop(v.__dict__)
elif isinstance(v, Field):
newd[k] = {"tablename": v._tablename,
"fieldname": v.name}
elif isinstance(v, Expression):
newd[k] = loop(v.__dict__)
elif isinstance(v, SERIALIZABLE_TYPES):
newd[k] = v
elif k == "op":
if callable(v):
newd[k] = v.__name__
elif isinstance(v, basestring):
newd[k] = v
else: pass # not callable or string
elif isinstance(v, SERIALIZABLE_TYPES):
if isinstance(v, dict):
newd[k] = loop(v)
else: newd[k] = v
return newd
if flat:
return loop(self.__dict__)
else: return self.__dict__
def as_xml(self, sanitize=True):
if have_serializers:
xml = serializers.xml
else:
raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return xml(d)
def as_json(self, sanitize=True):
if have_serializers:
json = serializers.json
else:
raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return json(d)
def xorify(orderby): def xorify(orderby):
if not orderby: if not orderby:
@@ -9148,6 +9436,12 @@ class Set(object):
def __init__(self, db, query, ignore_common_filters = None): def __init__(self, db, query, ignore_common_filters = None):
self.db = db self.db = db
self._db = db # for backward compatibility self._db = db # for backward compatibility
self.dquery = None
# if query is a dict, parse it
if isinstance(query, dict):
query = self.parse(query)
if not ignore_common_filters is None and \ if not ignore_common_filters is None and \
use_common_filters(query) == ignore_common_filters: use_common_filters(query) == ignore_common_filters:
query = copy.copy(query) query = copy.copy(query)
@@ -9166,10 +9460,10 @@ class Set(object):
query = query!=None query = query!=None
if self.query: if self.query:
return Set(self.db, self.query & query, return Set(self.db, self.query & query,
ignore_common_filters = ignore_common_filters) ignore_common_filters=ignore_common_filters)
else: else:
return Set(self.db, query, return Set(self.db, query,
ignore_common_filters = ignore_common_filters) ignore_common_filters=ignore_common_filters)
def _count(self,distinct=None): def _count(self,distinct=None):
return self.db._adapter._count(self.query,distinct) return self.db._adapter._count(self.query,distinct)
@@ -9195,6 +9489,94 @@ class Set(object):
fields = db[tablename]._listify(update_fields,update=True) fields = db[tablename]._listify(update_fields,update=True)
return db._adapter._update(tablename,self.query,fields) return db._adapter._update(tablename,self.query,fields)
def as_dict(self, flat=False, sanitize=True):
if flat:
uid = dbname = uri = None
codec = self.db._db_codec
if not sanitize:
uri, dbname, uid = (self.db._dbname, str(self.db),
self.db._db_uid)
d = {"query": self.query.as_dict(flat=flat)}
d["db"] = {"uid": uid, "codec": codec,
"name": dbname, "uri": uri}
return d
else: return self.__dict__
def as_xml(self, sanitize=True):
if have_serializers:
xml = serializers.xml
else:
raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return xml(d)
def as_json(self, sanitize=True):
if have_serializers:
json = serializers.json
else:
raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize)
return json(d)
def parse(self, dquery):
"Experimental: Turn a dictionary into a Query object"
self.dquery = dquery
return self.build(self.dquery)
def build(self, d):
"Experimental: see .parse()"
op, first, second = (d["op"], d["first"],
d.get("second", None))
left = right = built = None
if op in ("AND", "OR"):
if not (type(first), type(second)) == (dict, dict):
raise SyntaxError("Invalid AND/OR query")
if op == "AND":
built = self.build(first) & self.build(second)
else: built = self.build(first) | self.build(second)
elif op == "NOT":
if first is None:
raise SyntaxError("Invalid NOT query")
built = ~self.build(first)
else:
# normal operation (GT, EQ, LT, ...)
for k, v in {"left": first, "right": second}.items():
if isinstance(v, dict) and v.get("op"):
v = self.build(v)
if isinstance(v, dict) and ("tablename" in v):
v = self.db[v["tablename"]][v["fieldname"]]
if k == "left": left = v
else: right = v
if hasattr(self.db._adapter, op):
opm = getattr(self.db._adapter, op)
if op == "EQ": built = left == right
elif op == "NE": built = left != right
elif op == "GT": built = left > right
elif op == "GE": built = left >= right
elif op == "LT": built = left < right
elif op == "LE": built = left <= right
elif op in ("JOIN", "LEFT_JOIN", "RANDOM", "ALLOW_NULL"):
built = Expression(self.db, opm)
elif op in ("LOWER", "UPPER", "EPOCH", "PRIMARY_KEY",
"COALESCE_ZERO", "RAW", "INVERT"):
built = Expression(self.db, opm, left)
elif op in ("COUNT", "EXTRACT", "AGGREGATE", "SUBSTRING",
"REGEXP", "LIKE", "ILIKE", "STARTSWITH",
"ENDSWITH", "ADD", "SUB", "MUL", "DIV",
"MOD", "AS", "ON", "COMMA", "NOT_NULL",
"COALESCE", "CONTAINS", "BELONGS"):
built = Expression(self.db, opm, left, right)
# expression as string
elif not (left or right): built = Expression(self.db, op)
else:
raise SyntaxError("Operator not supported: %s" % op)
return built
def isempty(self): def isempty(self):
return not self.select(limitby=(0,1)) return not self.select(limitby=(0,1))
+7 -5
View File
@@ -308,8 +308,8 @@ class Response(Storage):
chunk_size=DEFAULT_CHUNK_SIZE, chunk_size=DEFAULT_CHUNK_SIZE,
request=None, request=None,
attachment=False, attachment=False,
filename=None, filename=None
): ):
""" """
if a controller function:: if a controller function::
@@ -385,6 +385,8 @@ class Response(Storage):
downloads from http://..../download/filename downloads from http://..../download/filename
""" """
current.session.forget(current.response)
if not request.args: if not request.args:
raise HTTP(404) raise HTTP(404)
name = request.args[-1] name = request.args[-1]
@@ -403,8 +405,8 @@ class Response(Storage):
raise HTTP(404) raise HTTP(404)
headers = self.headers headers = self.headers
headers['Content-Type'] = contenttype(name) headers['Content-Type'] = contenttype(name)
if download_filename == None: if download_filename == None:
download_filename = filename download_filename = filename
if attachment: if attachment:
headers['Content-Disposition'] = \ headers['Content-Disposition'] = \
'attachment; filename="%s"' % download_filename.replace('"','\"') 'attachment; filename="%s"' % download_filename.replace('"','\"')
@@ -689,7 +691,7 @@ class Session(Storage):
def forget(self, response=None): def forget(self, response=None):
self._close(response) self._close(response)
self._forget = True self._forget = True
def _try_store_in_cookie(self, request, response): def _try_store_in_cookie(self, request, response):
if response.session_storage_type != 'cookie': if response.session_storage_type != 'cookie':
+1 -1
View File
@@ -553,7 +553,7 @@ class XML(XmlComponent):
'img/', 'img/',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'tr', 'td', 'div', 'table', 'tr', 'td', 'div',
'strong', 'strong','span',
], ],
allowed_attributes={ allowed_attributes={
'a': ['href', 'title', 'target'], 'a': ['href', 'title', 'target'],
+1
View File
@@ -12,6 +12,7 @@ Contains:
""" """
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
import gc import gc
import cgi import cgi
import cStringIO import cStringIO
+1
View File
@@ -212,6 +212,7 @@ def sanitize(text, permitted_tags=[
'img/', 'img/',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'tr', 'td', 'div', 'table', 'tr', 'td', 'div',
'strong', 'span',
], ],
allowed_attributes={ allowed_attributes={
'a': ['href', 'title'], 'a': ['href', 'title'],
+22 -2
View File
@@ -19,6 +19,11 @@ except ImportError:
except: except:
import contrib.simplejson as json_parser # fallback to pure-Python module import contrib.simplejson as json_parser # fallback to pure-Python module
have_yaml = True
try:
import yaml
except ImportError:
have_yaml = False
def loads_json(o): def loads_json(o):
# deserialize a json string # deserialize a json string
@@ -68,8 +73,12 @@ def xml(value, encoding='UTF-8', key='document', quote=True):
def json(value, default=custom_json): def json(value, default=custom_json):
return json_parser.dumps(value, default=default) # replace JavaScript incompatible spacing
# http://timelessrepo.com/json-isnt-a-javascript-subset
return json_parser.dumps(value,
default=default).replace(ur'\u2028',
'\\u2028').replace(ur'\2029',
'\\u2029')
def csv(value): def csv(value):
return '' return ''
@@ -119,3 +128,14 @@ def rss(feed):
pubDate=entry.get('created_on', now) pubDate=entry.get('created_on', now)
) for entry in feed.get('entries', [])]) ) for entry in feed.get('entries', [])])
return rss.to_xml(encoding='utf-8') return rss.to_xml(encoding='utf-8')
def yaml(data):
if have_yaml:
return yaml.dump(data)
else: raise ImportError("No YAML serializer available")
def loads_yaml(data):
if have_yaml:
return yaml.load(data)
else: raise ImportError("No YAML serializer available")
+6 -3
View File
@@ -1867,7 +1867,10 @@ class SQLFORM(FORM):
dbset = db(query) dbset = db(query)
tablenames = db._adapter.tables(dbset.query) tablenames = db._adapter.tables(dbset.query)
if left is not None: if left is not None:
tablenames += db._adapter.tables(left) if not isinstance(left, (list, tuple)):
left = [left]
for join in left:
tablenames += db._adapter.tables(join)
tables = [db[tablename] for tablename in tablenames] tables = [db[tablename] for tablename in tablenames]
if not fields: if not fields:
fields = reduce(lambda a, b: a + b, fields = reduce(lambda a, b: a + b,
@@ -2511,7 +2514,7 @@ class SQLFORM(FORM):
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
redirect(URL(args=table._tablename)) redirect(URL(args=table._tablename))
if nargs == len(args) + 1: if nargs == len(args) + 1:
query = table._id > 0 query = table._id != None
# filter out data info for displayed table # filter out data info for displayed table
if table._tablename in constraints: if table._tablename in constraints:
@@ -2549,7 +2552,7 @@ class SQLFORM(FORM):
links_in_grid=links_in_grid, links_in_grid=links_in_grid,
user_signature=user_signature, **kwargs) user_signature=user_signature, **kwargs)
if isinstance(grid, DIV): if isinstance(grid, DIV):
header = table._plural + (field and ' for ' + field.name or '') header = table._plural + (field and ' for ' + field.label or '')
breadcrumbs.append(LI(A(T(header), _class=trap_class(), breadcrumbs.append(LI(A(T(header), _class=trap_class(),
_href=url()), _class='active w2p_grid_breadcrumb_elem')) _href=url()), _class='active w2p_grid_breadcrumb_elem'))
grid.insert( grid.insert(
+2 -2
View File
@@ -46,8 +46,8 @@ def stream_file_or_304_or_206(
request=None, request=None,
headers={}, headers={},
status=200, status=200,
error_message=None, error_message=None
): ):
if error_message is None: if error_message is None:
error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request' error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
try: try:
+6 -1
View File
@@ -5092,7 +5092,12 @@ class Wiki(object):
url = URL(args=('_edit', slug)) url = URL(args=('_edit', slug))
return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn")) return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn"))
else: else:
return dict(content=XML(self.fix_hostname(page.html))) return dict(title=page.title,
slug=page.slug,
content=XML(self.fix_hostname(page.html)),
tags=page.tags,
created_on=page.created_on,
modified_on=page.modified_on)
elif current.request.extension == 'load': elif current.request.extension == 'load':
return self.fix_hostname(page.html) if page else '' return self.fix_hostname(page.html) if page else ''
else: else:
+50 -18
View File
@@ -1,19 +1,49 @@
#!/bin/bash #!/bin/bash
# Script for installing Web2py with Nginx and Uwsgi on Centos 5 # -------------------------------------------------------------------
# Created By Hutchinson # Description : Installation and basic configuration of web2py,
# Modified by spametki # uWSGI, andNGINX.
# License: BSD # in CentOS 5.x GNU/Linux
# Usage : Copy the script in /home/username and run it as root,
# It was originally posted in this web2py-users group thread: # you may need to allow execution (chmod +x)
# https://groups.google.com/forum/?fromgroups#!topic/web2py/O4c4Jfr18tM #
# WARNING: This script was modified to install compiled
# There are lots of subtleties of ownership, and one has to take care # versions of Python and other packages that may be
# when installing python 2.6 not to stop the systems python2.4 from working. # available at your Centos package repository.
# This change was made in order to get the latest
# NOTE: The only thing that should need changing for # stable libraries available for avoiding compatibility
# each installation is the $BASEARCH (base architecture) of the machine. #
# This is determined by doing uname -i. This is needed for the nginx installation. # A bug was reported for the 2.7.3 version of python
# here http://bugs.python.org/issue14572
# in case you choose to change to VERSION=2.7
# (see below) mind that the automatic patch applied
# could not work for your environment. By default,
# Python 2.6 is installed.
#
# It was originally posted in this web2py-users group
# thread: https://groups.google.com/forum/?fromgroups#
# !topic/web2py/O4c4Jfr18tM
#
# There are lots of subtleties of ownership, and one
# has to take care when installing python 2.6 not to
# stop the systems python2.4 from working.
#
# NOTE: The only thing that should need changing for
# each installation is the $BASEARCH (base
# architecture)
# of the machine. This is determined by doing uname -i.
# This is needed for the nginx installation.
#
# File : setup-web2py-nginx-uwsgi-on-centos.sh
# Author : Peter Hutchinson
# Modified by : Alan Etkin
# Email : spametki@gmail.com
# Copyright : web2py
# Date : 2013-01-28
# Disclaimers : This script is provided "as is", without warranty of
# any kind.
# Licence : BSD
# -------------------------------------------------------------------
# Retrieve base architecture # Retrieve base architecture
BASEARCH=$(uname -i) BASEARCH=$(uname -i)
@@ -32,8 +62,8 @@ bzip2-devel sqlite-devel db4-devel openssl-devel tk-devel bluez-libs-devel
# to fit your deployment needs. # to fit your deployment needs.
# Python options # Python options
PREFIX=2.7 PREFIX=2.6
VERSION=2.7.3 VERSION=2.6.8
# uWSGI options # uWSGI options
version=uwsgi-1.2.4 version=uwsgi-1.2.4
@@ -47,8 +77,10 @@ wget http://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz
tar xvfz Python-$VERSION.tgz tar xvfz Python-$VERSION.tgz
cd Python-$VERSION cd Python-$VERSION
echo "Applying patch for sqlite3 bug from post http://bugs.python.org/msg161076" if [ "$VERSION" == "2.7.3" ]; then
curl -sk https://raw.github.com/gist/2727063/ | patch -p1 echo "Applying patch for sqlite3 bug from post http://bugs.python.org/msg161076"
curl -sk https://raw.github.com/gist/2727063/ | patch -p1
fi
./configure --prefix=/opt/python$PREFIX --with-threads --enable-shared ./configure --prefix=/opt/python$PREFIX --with-threads --enable-shared
make make
+69 -21
View File
@@ -19,19 +19,50 @@ apt-get -y install build-essential python-dev libxml2-dev python-pip unzip
pip install --upgrade pip pip install --upgrade pip
PIPPATH=`which pip` PIPPATH=`which pip`
$PIPPATH install --upgrade uwsgi $PIPPATH install --upgrade uwsgi
# Create common nginx sections
mkdir /etc/nginx/conf.d/web2py
echo '
gzip_static on;
gzip_http_version 1.1;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
' > /etc/nginx/conf.d/web2py/gzip_static.conf
echo '
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
' > /etc/nginx/conf.d/web2py/gzip.conf
# Create configuration file /etc/nginx/sites-available/web2py # Create configuration file /etc/nginx/sites-available/web2py
echo 'server { echo 'server {
listen 80; listen 80;
server_name $hostname; server_name $hostname;
#to enable correct use of response.static_version ###to enable correct use of response.static_version
#location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ { #location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
# alias /home/www-data/web2py/applications/$1/static/$2; # alias /home/www-data/web2py/applications/$1/static/$2;
# expires max; # expires max;
#} #}
###
###if you use something like myapp = dict(languages=['en', 'it', 'jp'], default_language='en') in your routes.py
#location ~* /(\w+)/(en|it|jp)/static/(.*)$ {
# alias /home/www-data/web2py/applications/$1/;
# try_files static/$2/$3 static/$3 =404;
#}
###
location ~* /(\w+)/static/ { location ~* /(\w+)/static/ {
root /home/www-data/web2py/applications/; root /home/www-data/web2py/applications/;
#remove next comment on production #remove next comment on production
#expires max; #expires max;
### if you want to use pre-gzipped static files (recommended)
### check scripts/zip_static_files.py and remove the comments
# include /etc/nginx/conf.d/web2py/gzip_static.conf;
###
} }
location / { location / {
#uwsgi_pass 127.0.0.1:9001; #uwsgi_pass 127.0.0.1:9001;
@@ -39,6 +70,14 @@ echo 'server {
include uwsgi_params; include uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme; uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version; uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
###remove the comments to turn on if you want gzip compression of your pages
# include /etc/nginx/conf.d/web2py/gzip.conf;
### end gzip section
### remove the comments if you use uploads (max 10 MB)
#client_max_body_size 10m;
###
} }
} }
server { server {
@@ -58,7 +97,15 @@ server {
include uwsgi_params; include uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme; uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version; uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
###remove the comments to turn on if you want gzip compression of your pages
# include /etc/nginx/conf.d/web2py/gzip.conf;
### end gzip section
### remove the comments if you want to enable uploads (max 10 MB)
#client_max_body_size 10m;
###
} }
## if you serve static files through https, copy here the section
## from the previous server instance to manage static files
}' >/etc/nginx/sites-available/web2py }' >/etc/nginx/sites-available/web2py
@@ -78,25 +125,26 @@ sudo mkdir /etc/uwsgi
sudo mkdir /var/log/uwsgi sudo mkdir /var/log/uwsgi
# Create configuration file /etc/uwsgi/web2py.xml # Create configuration file /etc/uwsgi/web2py.xml
echo '<uwsgi> echo '[uwsgi]
<socket>/tmp/web2py.socket</socket>
<pythonpath>/home/www-data/web2py/</pythonpath> socket = /tmp/web2py.socket
<mount>/=wsgihandler:application</mount> pythonpath = /home/www-data/web2py/
<master/> mount = /=wsgihandler:application
<processes>4</processes> processes = 4
<harakiri>60</harakiri> master = true
<reload-mercy>8</reload-mercy> harakiri = 60
<cpu-affinity>1</cpu-affinity> reload-mercy = 8
<stats>/tmp/stats.socket</stats> cpu-affinity = 1
<max-requests>2000</max-requests> stats = /tmp/stats.socket
<limit-as>512</limit-as> max-requests = 2000
<reload-on-as>256</reload-on-as> limit-as = 512
<reload-on-rss>192</reload-on-rss> reload-on-as = 256
<uid>www-data</uid> reload-on-rss = 192
<gid>www-data</gid> uid = www-data
<cron>0 0 -1 -1 -1 python /home/www-data/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o</cron> gid = www-data
<no-orphans/> cron = 0 0 -1 -1 -1 python /home/www-data/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o
</uwsgi>' >/etc/uwsgi/web2py.xml no-orphans = true
' >/etc/uwsgi/web2py.ini
#Create a configuration file for uwsgi in emperor-mode #Create a configuration file for uwsgi in emperor-mode
#for Upstart in /etc/init/uwsgi-emperor.conf #for Upstart in /etc/init/uwsgi-emperor.conf
@@ -135,4 +183,4 @@ start uwsgi-emperor
## and stop it with ## and stop it with
# stop uwsgi-emperor # stop uwsgi-emperor
## to reload web2py only (without restarting uwsgi) ## to reload web2py only (without restarting uwsgi)
# touch /etc/uwsgi/web2py.xml # touch /etc/uwsgi/web2py.xml