Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2b6c645ef | |||
| 36810aaccc | |||
| 8e14d1c92e | |||
| 9b7e944dea | |||
| b6429ca5fd | |||
| 8607b09e64 | |||
| 64151a2c3e | |||
| d5749db0cd | |||
| df7d6847e6 | |||
| c6b5ad8179 | |||
| 71ec500fde | |||
| 6cf05edf63 | |||
| 39c5341dbc | |||
| e2234a8771 | |||
| cac9741cb7 | |||
| 648cb296e6 | |||
| 5a0ebb7d8c | |||
| b7b534ccf2 | |||
| fab23d210b | |||
| 6a18539f7e | |||
| 4d87a90c10 | |||
| 2c9767e500 | |||
| 2b1d9acba5 | |||
| 8a2d18b63b | |||
| b59f9f896c | |||
| 496e6663fd | |||
| a4fbf9b6b4 | |||
| b8afce746b | |||
| 4c239eaf79 | |||
| 1d03233c83 | |||
| 5358ad271e | |||
| 2fa7d72b01 | |||
| f034d83c81 | |||
| b3afa2ab57 | |||
| 36fc758690 | |||
| f764eb2653 | |||
| 63f56ddecc | |||
| 0b2bab3f3d | |||
| 66de02929b | |||
| 3c4a142f9a | |||
| bf6f090428 | |||
| 59ab818f5b | |||
| 77847daa77 | |||
| 690d2df774 | |||
| 643cc9c141 | |||
| 562cb58342 | |||
| 1c8ef29413 | |||
| 8e67c551de | |||
| 03e3324a12 | |||
| a3ba3f662a | |||
| 2b21678d48 | |||
| 09f29257d2 | |||
| 62be48decf | |||
| 952a29d5da | |||
| db9151b993 | |||
| e1bb2b4556 | |||
| 668658b7c9 | |||
| 231a3e1278 | |||
| 41d234bf48 | |||
| f16c3a394f | |||
| 570538042e | |||
| 6a97bfe517 | |||
| 7b6e5c5270 | |||
| 20deb56e33 | |||
| a23a2c52c2 | |||
| 3f6fa941cb | |||
| 28f914463b | |||
| f0c84b5230 | |||
| dc51abe54c | |||
| f501da74b7 | |||
| c1d14a35ee | |||
| 4f4a0318aa | |||
| 54a1005af3 | |||
| 7b4fac7a7f | |||
| c80a6db604 | |||
| 96247eda51 | |||
| 433a02537c | |||
| bacff3453b | |||
| b20e74c899 | |||
| efa5ceb6de | |||
| f79660dd2a | |||
| 606daabdd3 | |||
| fc1e4ad620 | |||
| 54dcf2ead9 | |||
| 69cea23cd4 | |||
| 083b598c93 | |||
| 94f11501ed | |||
| 9ac4a11db1 | |||
| a56cf59136 | |||
| 12cc81147c | |||
| f70737ddc2 | |||
| 1a22497b64 | |||
| 6da330a001 | |||
| f6652e7530 | |||
| 5148c6ec3a | |||
| 954c79abcb | |||
| 7ec3fe3bfe | |||
| 9ce1cde6ca | |||
| 672af73e96 | |||
| 67022578e3 | |||
| 65fe4c16fc | |||
| b6f9cd1863 | |||
| 4908a5e7be | |||
| 853c0b1a3a | |||
| d0d8a4827e | |||
| 4745ce42f9 | |||
| bba13becd0 | |||
| 7712e005c4 | |||
| 07c667f469 | |||
| 57ad1ec1cf | |||
| 60a043479c | |||
| 1a43e1671f | |||
| cd955d3603 | |||
| 492a0c51d1 | |||
| dd4a4c281f | |||
| 41ec6da72c | |||
| e18f38d4da | |||
| bd27df8fa5 | |||
| 2f44c2de9b | |||
| ccf154a807 | |||
| 837453dd6e | |||
| 64bc60cdf0 | |||
| af258c334e | |||
| 627fff624a | |||
| bc8127f6de | |||
| 19bf43815d | |||
| 7709074d7c | |||
| 4b73f249dd | |||
| ae9e2c2be0 | |||
| fd8edb5aa2 | |||
| 82ab59c46a | |||
| 74a63e98b5 | |||
| 9e23b3dac5 | |||
| 077ce011e2 | |||
| 1430bc824f | |||
| 722b16e620 | |||
| 30727ef9e4 | |||
| 0f322f8a69 | |||
| ec5b4dde6f | |||
| 630dcb799e | |||
| c6d1e47226 | |||
| aa8315a7c9 | |||
| 80040bf8e5 | |||
| 8f382f322e | |||
| 9ccc7dc59a | |||
| 70f6f86827 | |||
| 3055c87567 | |||
| 473955611a | |||
| 21a7ded8e0 | |||
| 23347995e2 | |||
| 4027a3cda3 | |||
| a9b6c4f1a3 | |||
| c1330662aa | |||
| 3e38f8017f | |||
| 41bc1cb1e9 | |||
| 9fb94008ea | |||
| 5f647a46b8 | |||
| 69a2e76c3c | |||
| 4d6598c645 | |||
| 56acb685e9 | |||
| f0be4416b3 | |||
| 93ce59f65b | |||
| 28b94eceb2 | |||
| e4c63769f2 | |||
| e0fc61932d | |||
| 3de3046260 | |||
| b6a96301cd | |||
| 480dea23ad | |||
| 457ef24c04 | |||
| dbf6ce6d6c | |||
| 7686a6fc93 | |||
| afde6efafa | |||
| af53f17f5a | |||
| 8f9cf4034d | |||
| 31a6c5df9b | |||
| 08d1b26738 | |||
| abd739123c | |||
| da8570b560 | |||
| 8d238890a2 | |||
| ca29f262e5 | |||
| cffefbeb2e | |||
| 52528f9311 | |||
| 8b75256281 | |||
| 2385ed22b1 | |||
| 7a0cf446ba | |||
| 586d85cf08 | |||
| f371e72e07 | |||
| 8d9bc1cb53 | |||
| 29edf94498 | |||
| d7a108ff06 | |||
| 5ed41285c0 | |||
| b16d79e654 | |||
| 90e5d06d3d | |||
| a1156cae6c | |||
| 037a200357 | |||
| e282454bfe | |||
| e499c1d5a4 | |||
| 8540b1b113 | |||
| 6b5e3f2abf | |||
| f8be550e6a | |||
| 6a7b04199f | |||
| 2847e0f4df | |||
| eec7f7c687 | |||
| a4253deda3 | |||
| 372d639601 | |||
| 06e5c6869d | |||
| 933189c216 | |||
| c942881f36 | |||
| 99e491ca0f | |||
| ea539d50e2 | |||
| 497bbf002c | |||
| 2629fac624 | |||
| 30af5901eb | |||
| 42b60d3291 | |||
| 9b53fd2302 | |||
| 972f64b6f9 | |||
| 3eb4627ccf | |||
| 2ba88b8951 | |||
| 117b04169c | |||
| c56d5bd066 | |||
| 00bfde9ce9 | |||
| 5f503d0427 | |||
| 47bcab6b26 | |||
| ecca0439f1 | |||
| e896be4037 | |||
| b0e4284130 | |||
| 3e69889cca | |||
| dfb2e62c6c |
@@ -1,4 +1,37 @@
|
||||
## 2.00.1
|
||||
## 2.1.0
|
||||
|
||||
- overall faster web2py
|
||||
- when apps are deleted, a w2p copy left in deposit folder
|
||||
- change in cron (it is now disabled by default). removed -N option and introduced -Y.
|
||||
- faster web2py_uuid() and request initialization logic, thanks Michele
|
||||
- static asset management, thanks Niphlod
|
||||
- improved mobile admin
|
||||
- request.requires_https and Auth(secure=True), thanks Yarin and Niphlod
|
||||
- better custom_import (works per app and is faster), thanks Michele
|
||||
- redis_sesssion.py, thanks Niphlod
|
||||
- allow entropy computation in IS_STRONG and web2py.js, thanks Jonathan and Niphlod
|
||||
- fixed many aith.wiki problems
|
||||
- support for auth.wiki(render='html')
|
||||
- better welcome layout, thanks Paolo
|
||||
- db.define_table(...,redefine=True)
|
||||
- DAL, Row, and Rows object can now be pickled/unpickled, thanks to zombie DAL.
|
||||
- admin uses codemirror
|
||||
- allow syntax auth = Auth(db).define_tables()
|
||||
- better auth.wiki with preview, thanks Alan
|
||||
- better auth.impersonate, thanks Alan
|
||||
- upgraded jQuery 1.8
|
||||
- upgraded Bootstrap 2.1
|
||||
- fixed problem with dropbox_account.py
|
||||
- many fixes to cache.ram, cache.disk, memcache and gae_memcache
|
||||
- cache.with_prefix(cache.ram,'prefix')
|
||||
- db.table.field.epoch() counts seconds from epoch
|
||||
- DAL support for SQL CASE, example: db().select(...query.case('true','false))
|
||||
- DAL(...,do_connect=False) allows faking connections
|
||||
- DAL(...,auto_import=True) now retieves some fiel attributes
|
||||
- mail can specify a sender: mail.send(...,sender='Mr X <%(sender)s>')
|
||||
- renamed gluon/contrib/comet_messaging.py -> gluon/contrib/websocket_messaging.py
|
||||
|
||||
## 2.0.1-11
|
||||
|
||||
### DAL Improvements
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ update:
|
||||
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
echo 'Version 2.0.9 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
|
||||
echo 'Version 2.1.1 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
@@ -107,10 +107,6 @@ win:
|
||||
cp applications/__init__.py ../web2py_win/web2py/applications
|
||||
cd ../web2py_win; zip -r web2py_win.zip web2py
|
||||
mv ../web2py_win/web2py_win.zip .
|
||||
pip:
|
||||
# create Web2py distribution for upload to Pypi
|
||||
# after upload clean Web2py sources with rm -R ./dist
|
||||
python setup.py sdist
|
||||
run:
|
||||
python2.5 web2py.py -a hello
|
||||
commit:
|
||||
@@ -129,3 +125,10 @@ tag:
|
||||
hg tag -l '$(S)'
|
||||
make commit S='$(S)'
|
||||
make push
|
||||
pip:
|
||||
# create Web2py distribution for upload to Pypi
|
||||
# after upload clean Web2py sources with rm -R ./dist
|
||||
# http://guide.python-distribute.org/creation.html
|
||||
python setup.py sdist
|
||||
python setup.py register
|
||||
python setup.py sdist upload
|
||||
|
||||
@@ -1 +1 @@
|
||||
Version 2.0.9 (2012-09-13 17:48:15) stable
|
||||
Version 2.1.1 (2012-10-15 06:40:52) stable
|
||||
|
||||
@@ -80,7 +80,6 @@ skip_files: |
|
||||
|
||||
builtins:
|
||||
- remote_api: on
|
||||
# - datastore_admin: on
|
||||
- appstats: on
|
||||
- admin_redirect: on
|
||||
- deferred: on
|
||||
|
||||
@@ -113,7 +113,6 @@ def query_by_table_type(tablename,db,request=request):
|
||||
# ## list all databases and tables
|
||||
# ###########################################################
|
||||
|
||||
|
||||
def index():
|
||||
return dict(databases=databases)
|
||||
|
||||
@@ -207,6 +206,7 @@ def select():
|
||||
if match:
|
||||
table = match.group('table')
|
||||
try:
|
||||
tb = None
|
||||
nrows = db(query).count()
|
||||
if form.vars.update_check and form.vars.update_fields:
|
||||
db(query).update(**eval_in_global_env('dict(%s)'
|
||||
@@ -221,6 +221,8 @@ def select():
|
||||
else:
|
||||
rows = db(query,ignore_common_filters=True).select(limitby=(start, stop))
|
||||
except Exception, e:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
(rows, nrows) = ([], 0)
|
||||
response.flash = DIV(T('Invalid Query'),PRE(str(e)))
|
||||
# begin handle upload csv
|
||||
@@ -250,6 +252,7 @@ def select():
|
||||
rows=rows,
|
||||
query=request.vars.query,
|
||||
formcsv = formcsv,
|
||||
tb = tb,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
EXPERIMENTAL_STUFF = True
|
||||
|
||||
if EXPERIMENTAL_STUFF:
|
||||
is_mobile = request.user_agent().is_mobile
|
||||
if is_mobile:
|
||||
response.view = response.view.replace('default/','default.mobile/')
|
||||
response.menu = []
|
||||
@@ -366,10 +365,15 @@ def uninstall():
|
||||
else:
|
||||
session.flash = T('no permission to uninstall "%s"', app)
|
||||
redirect(URL('site'))
|
||||
if app_uninstall(app, request):
|
||||
session.flash = T('application "%s" uninstalled', app)
|
||||
else:
|
||||
try:
|
||||
filename = app_pack(app, request, raise_ex=True)
|
||||
except:
|
||||
session.flash = T('unable to uninstall "%s"', app)
|
||||
else:
|
||||
if app_uninstall(app, request):
|
||||
session.flash = T('application "%s" uninstalled', app)
|
||||
else:
|
||||
session.flash = T('unable to uninstall "%s"', app)
|
||||
redirect(URL('site'))
|
||||
return dict(app=app, dialog=dialog)
|
||||
|
||||
@@ -782,7 +786,7 @@ def edit_language():
|
||||
|
||||
_class='untranslated' if k==s else 'translated'
|
||||
|
||||
if len(key) <= 40:
|
||||
if len(s) <= 40:
|
||||
elem = INPUT(_type='text', _name=name, value=s,
|
||||
_size=70,_class=_class)
|
||||
else:
|
||||
@@ -1647,7 +1651,6 @@ def git_pull():
|
||||
session.flash = T("Application updated via git pull")
|
||||
redirect(URL('site'))
|
||||
except CheckoutError, message:
|
||||
logging.error(message)
|
||||
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
|
||||
redirect(URL('site'))
|
||||
except UnmergedEntriesError:
|
||||
@@ -1657,11 +1660,9 @@ def git_pull():
|
||||
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
|
||||
redirect(URL('site'))
|
||||
except GitCommandError, status:
|
||||
logging.error(str(status))
|
||||
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
|
||||
redirect(URL('site'))
|
||||
except Exception,e:
|
||||
logging.error("Unexpected error:", sys.exc_info()[0])
|
||||
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
|
||||
redirect(URL('site'))
|
||||
elif 'cancel' in request.vars:
|
||||
@@ -1693,7 +1694,6 @@ def git_push():
|
||||
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
|
||||
redirect(URL('site'))
|
||||
except Exception, e:
|
||||
logging.error("Unexpected error:", sys.exc_info()[0])
|
||||
session.flash = T("Push failed, git exited abnormally. See logs for details.")
|
||||
redirect(URL('site'))
|
||||
return dict(app=app,form=form)
|
||||
|
||||
@@ -39,7 +39,7 @@ def deploy():
|
||||
form = SQLFORM.factory(
|
||||
Field('appcfg',default=GAE_APPCFG,label=T('Path to appcfg.py'),
|
||||
requires=EXISTS(error_message=T('file not found'))),
|
||||
Field('google_application_id',requires=IS_ALPHANUMERIC(),label=T('Google Application Id')),
|
||||
Field('google_application_id',requires=IS_MATCH('[\w\-]+'),label=T('Google Application Id')),
|
||||
Field('applications','list:string',
|
||||
requires=IS_IN_SET(apps,multiple=True),
|
||||
label=T('web2py apps to deploy')),
|
||||
|
||||
+183
-125
@@ -3,143 +3,26 @@
|
||||
'!langcode!': 'it',
|
||||
'!langname!': 'Italiano',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ',
|
||||
'%Y-%m-%d': '%d/%m/%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||
'%s %%{row} deleted': '%s righe ("record") cancellate',
|
||||
'%s %%{row} updated': '%s righe ("record") modificate',
|
||||
'%Y-%m-%d': '%d/%m/%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||
'(requires internet access)': '(requires internet access)',
|
||||
'(something like "it-it")': '(qualcosa simile a "it-it")',
|
||||
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
|
||||
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
||||
'A new version of web2py is available: %s': 'È disponibile una nuova versione di web2py: %s',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': "ATTENZIONE: L'accesso richiede una connessione sicura (HTTPS) o l'esecuzione di web2py in locale (connessione su localhost)",
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTZIONE: NON ESEGUIRE PIÙ TEST IN PARALLELO (I TEST NON SONO "THREAD SAFE")',
|
||||
'ATTENTION: you cannot edit the running application!': "ATTENZIONE: non puoi modificare l'applicazione correntemente in uso ",
|
||||
'About': 'informazioni',
|
||||
'About application': "Informazioni sull'applicazione",
|
||||
'Admin is disabled because insecure channel': 'amministrazione disabilitata: comunicazione non sicura',
|
||||
'Admin language': 'Admin language',
|
||||
'Administrator Password:': 'Password Amministratore:',
|
||||
'Application name:': 'Application name:',
|
||||
'Are you sure you want to delete file "%s"?': 'Confermi di voler cancellare il file "%s"?',
|
||||
'Are you sure you want to delete plugin "%s"?': 'Confermi di voler cancellare il plugin "%s"?',
|
||||
'Are you sure you want to uninstall application "%s"?': 'Confermi di voler disinstallare l\'applicazione "%s"?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Confermi di voler aggiornare web2py ora?',
|
||||
'Available databases and tables': 'Database e tabelle disponibili',
|
||||
'Cannot be empty': 'Non può essere vuoto',
|
||||
'Cannot compile: there are errors in your app:': "Compilazione fallita: ci sono errori nell'applicazione.",
|
||||
'Change admin password': 'change admin password',
|
||||
'Check for upgrades': 'check for upgrades',
|
||||
'Check to delete': 'Seleziona per cancellare',
|
||||
'Checking for upgrades...': 'Controllo aggiornamenti in corso...',
|
||||
'Clean': 'pulisci',
|
||||
'Compile': 'compila',
|
||||
'Controller': 'Controller',
|
||||
'Controllers': 'Controllers',
|
||||
'Copyright': 'Copyright',
|
||||
'Create': 'crea',
|
||||
'Create new simple application': 'Crea nuova applicazione',
|
||||
'Current request': 'Richiesta (request) corrente',
|
||||
'Current response': 'Risposta (response) corrente',
|
||||
'Current session': 'Sessione (session) corrente',
|
||||
'DB Model': 'Modello di DB',
|
||||
'Database': 'Database',
|
||||
'Date and Time': 'Data and Ora',
|
||||
'Delete': 'Cancella',
|
||||
'Delete:': 'Cancella:',
|
||||
'Deploy': 'deploy',
|
||||
'Deploy on Google App Engine': 'Installa su Google App Engine',
|
||||
'EDIT': 'MODIFICA',
|
||||
'Edit': 'modifica',
|
||||
'Edit This App': 'Modifica questa applicazione',
|
||||
'Edit application': 'Modifica applicazione',
|
||||
'Edit current record': 'Modifica record corrente',
|
||||
'Editing Language file': 'Modifica file linguaggio',
|
||||
'Editing file "%s"': 'Modifica del file "%s"',
|
||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||
'Error logs for "%(app)s"': 'Log degli errori per "%(app)s"',
|
||||
'Errors': 'errori',
|
||||
'Exception instance attributes': 'Exception instance attributes',
|
||||
'Functions with no doctests will result in [passed] tests.': 'I test delle funzioni senza "doctests" risulteranno sempre [passed].',
|
||||
'Hello World': 'Salve Mondo',
|
||||
'Help': 'aiuto',
|
||||
'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',
|
||||
'Index': 'Indice',
|
||||
'Install': 'installa',
|
||||
'Installed applications': 'Applicazioni installate',
|
||||
'Internal State': 'Stato interno',
|
||||
'Invalid Query': 'Richiesta (query) non valida',
|
||||
'Invalid action': 'Azione non valida',
|
||||
'Language files (static strings) updated': 'Linguaggi (documenti con stringhe statiche) aggiornati',
|
||||
'Languages': 'Linguaggi',
|
||||
'Last saved on:': 'Ultimo salvataggio:',
|
||||
'Layout': 'Layout',
|
||||
'License for': 'Licenza relativa a',
|
||||
'Login': 'Accesso',
|
||||
'Login to the Administrative Interface': "Accesso all'interfaccia amministrativa",
|
||||
'Logout': 'uscita',
|
||||
'Main Menu': 'Menu principale',
|
||||
'Menu Model': 'Menu Modelli',
|
||||
'Models': 'Modelli',
|
||||
'Modules': 'Moduli',
|
||||
'NO': 'NO',
|
||||
'New Record': 'Nuovo elemento (record)',
|
||||
'New application wizard': 'New application wizard',
|
||||
'New simple application': 'New simple application',
|
||||
'No databases in this application': 'Nessun database presente in questa applicazione',
|
||||
'Original/Translation': 'Originale/Traduzione',
|
||||
'Overwrite installed app': 'sovrascrivi applicazione installata',
|
||||
'PAM authenticated user, cannot change password here': 'utente autenticato tramite PAM, impossibile modificare password qui',
|
||||
'Pack all': 'crea pacchetto',
|
||||
'Pack compiled': 'crea pacchetto del codice compilato',
|
||||
'Peeking at file': 'Uno sguardo al file',
|
||||
'Plugin "%s" in application': 'Plugin "%s" nell\'applicazione',
|
||||
'Plugins': 'I Plugins',
|
||||
'Powered by': 'Powered by',
|
||||
'Query:': 'Richiesta (query):',
|
||||
'Remove compiled': 'rimozione codice compilato',
|
||||
'Resolve Conflict file': 'File di risoluzione conflitto',
|
||||
'Rows in table': 'Righe nella tabella',
|
||||
'Rows selected': 'Righe selezionate',
|
||||
'Saved file hash:': 'Hash del file salvato:',
|
||||
'Site': 'sito',
|
||||
'Start wizard': 'start wizard',
|
||||
'Static files': 'Files statici',
|
||||
'Stylesheet': 'Foglio di stile (stylesheet)',
|
||||
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
|
||||
'TM': 'TM',
|
||||
'Testing application': 'Test applicazione in corsg',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
|
||||
'There are no controllers': 'Non ci sono controller',
|
||||
'There are no models': 'Non ci sono modelli',
|
||||
'There are no modules': 'Non ci sono moduli',
|
||||
'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 views': 'Non ci sono viste ("view")',
|
||||
'This is the %(filename)s template': 'Questo è il template %(filename)s',
|
||||
'Ticket': 'Ticket',
|
||||
'To create a plugin, name a file/folder plugin_[name]': 'Per creare un plugin, chiamare un file o cartella plugin_[nome]',
|
||||
'Unable to check for upgrades': 'Impossibile controllare presenza di aggiornamenti',
|
||||
'Unable to download app because:': 'Impossibile scaricare applicazione perché',
|
||||
'Unable to download because': 'Impossibile scaricare perché',
|
||||
'Unable to download because:': 'Unable to download because:',
|
||||
'Uninstall': 'disinstalla',
|
||||
'Update:': 'Aggiorna:',
|
||||
'Upload & install packed application': 'Carica ed installa pacchetto con applicazione',
|
||||
'Upload a package:': 'Upload a package:',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
|
||||
'Use an url:': 'Use an url:',
|
||||
'Version': 'Versione',
|
||||
'View': 'Vista',
|
||||
'Views': 'viste',
|
||||
'Welcome %s': 'Benvenuto %s',
|
||||
'Welcome to web2py': 'Benvenuto su web2py',
|
||||
'YES': 'SI',
|
||||
'additional code for your application': 'righe di codice aggiuntive per la tua applicazione',
|
||||
'Additional code for your application': 'Additional code for your application',
|
||||
'admin disabled because no admin password': 'amministrazione disabilitata per mancanza di password amministrativa',
|
||||
'admin disabled because not supported on google app engine': 'amministrazione non supportata da Google Apps Engine',
|
||||
'admin disabled because unable to access password file': 'amministrazione disabilitata per impossibilità di leggere il file delle password',
|
||||
'Admin is disabled because insecure channel': 'amministrazione disabilitata: comunicazione non sicura',
|
||||
'Admin language': 'Admin language',
|
||||
'administrative interface': 'administrative interface',
|
||||
'Administrator Password:': 'Password Amministratore:',
|
||||
'and rename it (required):': 'e rinominala (obbligatorio):',
|
||||
'and rename it:': 'e rinominala:',
|
||||
'appadmin': 'appadmin ',
|
||||
@@ -147,44 +30,104 @@
|
||||
'application "%s" uninstalled': 'applicazione "%s" disinstallata',
|
||||
'application compiled': 'applicazione compilata',
|
||||
'application is compiled and cannot be designed': "l'applicazione è compilata e non si può modificare",
|
||||
'Application name:': 'Application name:',
|
||||
'are not used': 'are not used',
|
||||
'are not used yet': 'are not used yet',
|
||||
'Are you sure you want to delete file "%s"?': 'Confermi di voler cancellare il file "%s"?',
|
||||
'Are you sure you want to delete plugin "%s"?': 'Confermi di voler cancellare il plugin "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||
'Are you sure you want to uninstall application "%s"?': 'Confermi di voler disinstallare l\'applicazione "%s"?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Confermi di voler aggiornare web2py ora?',
|
||||
'arguments': 'arguments',
|
||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': "ATTENZIONE: L'accesso richiede una connessione sicura (HTTPS) o l'esecuzione di web2py in locale (connessione su localhost)",
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTZIONE: NON ESEGUIRE PIÙ TEST IN PARALLELO (I TEST NON SONO "THREAD SAFE")',
|
||||
'ATTENTION: you cannot edit the running application!': "ATTENZIONE: non puoi modificare l'applicazione correntemente in uso ",
|
||||
'Available databases and tables': 'Database e tabelle disponibili',
|
||||
'back': 'indietro',
|
||||
'cache': 'cache',
|
||||
'cache, errors and sessions cleaned': 'pulitura cache, errori and sessioni ',
|
||||
'can be a git repo': 'can be a git repo',
|
||||
'Cannot be empty': 'Non può essere vuoto',
|
||||
'Cannot compile: there are errors in your app:': "Compilazione fallita: ci sono errori nell'applicazione.",
|
||||
'cannot create file': 'impossibile creare il file',
|
||||
'cannot upload file "%(filename)s"': 'impossibile caricare il file "%(filename)s"',
|
||||
'Change admin password': 'change admin password',
|
||||
'change password': 'cambia password',
|
||||
'check all': 'controlla tutto',
|
||||
'Check for upgrades': 'check for upgrades',
|
||||
'Check to delete': 'Seleziona per cancellare',
|
||||
'Checking for upgrades...': 'Controllo aggiornamenti in corso...',
|
||||
'Clean': 'pulisci',
|
||||
'click here for online examples': 'clicca per vedere gli esempi',
|
||||
'click here for the administrative interface': "clicca per l'interfaccia amministrativa",
|
||||
'click to check for upgrades': 'clicca per controllare presenza di aggiornamenti',
|
||||
'code': 'code',
|
||||
'collapse/expand all': 'collapse/expand all',
|
||||
'Compile': 'compila',
|
||||
'compiled application removed': "rimosso il codice compilato dell'applicazione",
|
||||
'Controller': 'Controller',
|
||||
'Controllers': 'Controllers',
|
||||
'controllers': 'controllers',
|
||||
'Copyright': 'Copyright',
|
||||
'Create': 'crea',
|
||||
'create file with filename:': 'crea un file col nome:',
|
||||
'create new application:': 'create new application:',
|
||||
'Create new simple application': 'Crea nuova applicazione',
|
||||
'created by': 'creato da',
|
||||
'crontab': 'crontab',
|
||||
'Current request': 'Richiesta (request) corrente',
|
||||
'Current response': 'Risposta (response) corrente',
|
||||
'Current session': 'Sessione (session) corrente',
|
||||
'currently running': 'currently running',
|
||||
'currently saved or': 'attualmente salvato o',
|
||||
'customize me!': 'Personalizzami!',
|
||||
'data uploaded': 'dati caricati',
|
||||
'Database': 'Database',
|
||||
'database': 'database',
|
||||
'database %s select': 'database %s select',
|
||||
'database administration': 'amministrazione database',
|
||||
'Date and Time': 'Data and Ora',
|
||||
'db': 'db',
|
||||
'DB Model': 'Modello di DB',
|
||||
'Debug': 'Debug',
|
||||
'defines tables': 'defininisce le tabelle',
|
||||
'Delete': 'Cancella',
|
||||
'delete': 'Cancella',
|
||||
'delete all checked': 'cancella tutti i selezionati',
|
||||
'delete plugin': 'cancella plugin',
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
|
||||
'Delete:': 'Cancella:',
|
||||
'Deploy': 'deploy',
|
||||
'Deploy on Google App Engine': 'Installa su Google App Engine',
|
||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||
'design': 'progetta',
|
||||
'Detailed traceback description': 'Detailed traceback description',
|
||||
'direction: ltr': 'direction: ltr',
|
||||
'Disable': 'Disable',
|
||||
'docs': 'docs',
|
||||
'done!': 'fatto!',
|
||||
'download layouts': 'download layouts',
|
||||
'download plugins': 'download plugins',
|
||||
'EDIT': 'MODIFICA',
|
||||
'Edit': 'modifica',
|
||||
'Edit application': 'Modifica applicazione',
|
||||
'edit controller': 'modifica controller',
|
||||
'Edit current record': 'Modifica record corrente',
|
||||
'edit profile': 'modifica profilo',
|
||||
'Edit This App': 'Modifica questa applicazione',
|
||||
'edit views:': 'modifica viste (view):',
|
||||
'Editing file "%s"': 'Modifica del file "%s"',
|
||||
'Editing Language file': 'Modifica file linguaggio',
|
||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||
'Error logs for "%(app)s"': 'Log degli errori per "%(app)s"',
|
||||
'Error snapshot': 'Error snapshot',
|
||||
'Error ticket': 'Error ticket',
|
||||
'Errors': 'errori',
|
||||
'Exception instance attributes': 'Exception instance attributes',
|
||||
'Expand Abbreviation': 'Expand Abbreviation',
|
||||
'export as csv file': 'esporta come file CSV',
|
||||
'exposes': 'espone',
|
||||
'exposes:': 'exposes:',
|
||||
'extends': 'estende',
|
||||
'failed to reload module because:': 'ricaricamento modulo fallito perché:',
|
||||
'file "%(filename)s" created': 'creato il file "%(filename)s"',
|
||||
@@ -195,73 +138,188 @@
|
||||
'file does not exist': 'file inesistente',
|
||||
'file saved on %(time)s': "file salvato nell'istante %(time)s",
|
||||
'file saved on %s': 'file salvato: %s',
|
||||
'filter': 'filter',
|
||||
'Frames': 'Frames',
|
||||
'Functions with no doctests will result in [passed] tests.': 'I test delle funzioni senza "doctests" risulteranno sempre [passed].',
|
||||
'Get from URL:': 'Get from URL:',
|
||||
'Git Pull': 'Git Pull',
|
||||
'Git Push': 'Git Push',
|
||||
'Hello World': 'Salve Mondo',
|
||||
'Help': 'aiuto',
|
||||
'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.',
|
||||
'Import/Export': 'Importa/Esporta',
|
||||
'includes': 'include',
|
||||
'Index': 'Indice',
|
||||
'insert new': 'inserisci nuovo',
|
||||
'insert new %s': 'inserisci nuovo %s',
|
||||
'inspect attributes': 'inspect attributes',
|
||||
'Install': 'installa',
|
||||
'Installed applications': 'Applicazioni installate',
|
||||
'internal error': 'errore interno',
|
||||
'Internal State': 'Stato interno',
|
||||
'Invalid action': 'Azione non valida',
|
||||
'invalid password': 'password non valida',
|
||||
'Invalid Query': 'Richiesta (query) non valida',
|
||||
'invalid request': 'richiesta non valida',
|
||||
'invalid ticket': 'ticket non valido',
|
||||
'Key bindings': 'Key bindings',
|
||||
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
|
||||
'language file "%(filename)s" created/updated': 'file linguaggio "%(filename)s" creato/aggiornato',
|
||||
'Language files (static strings) updated': 'Linguaggi (documenti con stringhe statiche) aggiornati',
|
||||
'languages': 'linguaggi',
|
||||
'Languages': 'Linguaggi',
|
||||
'Last saved on:': 'Ultimo salvataggio:',
|
||||
'Layout': 'Layout',
|
||||
'License for': 'Licenza relativa a',
|
||||
'loading...': 'caricamento...',
|
||||
'locals': 'locals',
|
||||
'login': 'accesso',
|
||||
'Login': 'Accesso',
|
||||
'Login to the Administrative Interface': "Accesso all'interfaccia amministrativa",
|
||||
'Logout': 'uscita',
|
||||
'Main Menu': 'Menu principale',
|
||||
'Menu Model': 'Menu Modelli',
|
||||
'merge': 'unisci',
|
||||
'models': 'modelli',
|
||||
'Models': 'Modelli',
|
||||
'Modules': 'Moduli',
|
||||
'modules': 'moduli',
|
||||
'new application "%s" created': 'creata la nuova applicazione "%s"',
|
||||
'New application wizard': 'New application wizard',
|
||||
'new plugin installed': 'installato nuovo plugin',
|
||||
'New Record': 'Nuovo elemento (record)',
|
||||
'new record inserted': 'nuovo record inserito',
|
||||
'New simple application': 'New simple application',
|
||||
'next 100 rows': 'prossime 100 righe',
|
||||
'NO': 'NO',
|
||||
'No databases in this application': 'Nessun database presente in questa applicazione',
|
||||
'no match': 'nessuna corrispondenza',
|
||||
'or import from csv file': 'oppure importa da file CSV',
|
||||
'or provide app url:': "oppure fornisci url dell'applicazione:",
|
||||
'Original/Translation': 'Originale/Traduzione',
|
||||
'Overwrite installed app': 'sovrascrivi applicazione installata',
|
||||
'Pack all': 'crea pacchetto',
|
||||
'Pack compiled': 'crea pacchetto del codice compilato',
|
||||
'pack plugin': 'crea pacchetto del plugin',
|
||||
'PAM authenticated user, cannot change password here': 'utente autenticato tramite PAM, impossibile modificare password qui',
|
||||
'password changed': 'password modificata',
|
||||
'Peeking at file': 'Uno sguardo al file',
|
||||
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" cancellato',
|
||||
'Plugin "%s" in application': 'Plugin "%s" nell\'applicazione',
|
||||
'plugins': 'plugins',
|
||||
'Plugins': 'I Plugins',
|
||||
'Plural-Forms:': 'Plural-Forms:',
|
||||
'Powered by': 'Powered by',
|
||||
'previous 100 rows': '100 righe precedenti',
|
||||
'private files': 'private files',
|
||||
'Private files': 'Private files',
|
||||
'Query:': 'Richiesta (query):',
|
||||
'record': 'record',
|
||||
'record does not exist': 'il record non esiste',
|
||||
'record id': 'ID del record',
|
||||
'register': 'registrazione',
|
||||
'Remove compiled': 'rimozione codice compilato',
|
||||
'request': 'request',
|
||||
'Resolve Conflict file': 'File di risoluzione conflitto',
|
||||
'response': 'response',
|
||||
'restore': 'ripristino',
|
||||
'revert': 'versione precedente',
|
||||
'Rows in table': 'Righe nella tabella',
|
||||
'Rows selected': 'Righe selezionate',
|
||||
'rules are not defined': 'rules are not defined',
|
||||
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
|
||||
'Running on %s': 'Running on %s',
|
||||
'Save': 'Save',
|
||||
'Save via Ajax': 'Save via Ajax',
|
||||
'Saved file hash:': 'Hash del file salvato:',
|
||||
'selected': 'selezionato',
|
||||
'session': 'session',
|
||||
'session expired': 'sessions scaduta',
|
||||
'shell': 'shell',
|
||||
'Site': 'sito',
|
||||
'some files could not be removed': 'non è stato possibile rimuovere alcuni files',
|
||||
'Start wizard': 'start wizard',
|
||||
'state': 'stato',
|
||||
'static': 'statico',
|
||||
'Static files': 'Files statici',
|
||||
'Stylesheet': 'Foglio di stile (stylesheet)',
|
||||
'submit': 'invia',
|
||||
'Submit': 'Submit',
|
||||
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
|
||||
'table': 'tabella',
|
||||
'test': 'test',
|
||||
'Testing application': 'Test applicazione in corsg',
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
|
||||
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
|
||||
'the application logic, each URL path is mapped in one exposed function in the controller': 'logica dell\'applicazione, ogni percorso "URL" corrisponde ad una funzione esposta da un controller',
|
||||
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
|
||||
'the data representation, define database tables and sets': 'rappresentazione dei dati, definizione di tabelle di database e di "set" ',
|
||||
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
|
||||
'the presentations layer, views are also known as templates': 'Presentazione dell\'applicazione, viste (views, chiamate anche "templates")',
|
||||
'There are no controllers': 'Non ci sono controller',
|
||||
'There are no models': 'Non ci sono modelli',
|
||||
'There are no modules': 'Non ci sono moduli',
|
||||
'There are no plugins': 'There are no plugins',
|
||||
'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 views': 'Non ci sono viste ("view")',
|
||||
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
|
||||
'these files are served without processing, your images go here': 'questi files vengono serviti così come sono, le immagini vanno qui',
|
||||
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
|
||||
'This is the %(filename)s template': 'Questo è il template %(filename)s',
|
||||
'Ticket': 'Ticket',
|
||||
'Ticket ID': 'Ticket ID',
|
||||
'TM': 'TM',
|
||||
'to previous version.': 'torna a versione precedente',
|
||||
'To create a plugin, name a file/folder plugin_[name]': 'Per creare un plugin, chiamare un file o cartella plugin_[nome]',
|
||||
'toggle breakpoint': 'toggle breakpoint',
|
||||
'Toggle Fullscreen': 'Toggle Fullscreen',
|
||||
'Traceback': 'Traceback',
|
||||
'translation strings for the application': "stringhe di traduzioni per l'applicazione",
|
||||
'Translation strings for the application': 'Translation strings for the application',
|
||||
'try': 'prova',
|
||||
'try something like': 'prova qualcosa come',
|
||||
'try view': 'try view',
|
||||
'Unable to check for upgrades': 'Impossibile controllare presenza di aggiornamenti',
|
||||
'unable to create application "%s"': 'impossibile creare applicazione "%s"',
|
||||
'unable to delete file "%(filename)s"': 'impossibile rimuovere file "%(plugin)s"',
|
||||
'unable to delete file plugin "%(plugin)s"': 'impossibile rimuovere file di plugin "%(plugin)s"',
|
||||
'Unable to download app because:': 'Impossibile scaricare applicazione perché',
|
||||
'Unable to download because': 'Impossibile scaricare perché',
|
||||
'Unable to download because:': 'Unable to download because:',
|
||||
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
|
||||
'unable to uninstall "%s"': 'impossibile disinstallare "%s"',
|
||||
'unable to upgrade because "%s"': 'impossibile aggiornare perché "%s"',
|
||||
'uncheck all': 'smarca tutti',
|
||||
'Uninstall': 'disinstalla',
|
||||
'update': 'aggiorna',
|
||||
'update all languages': 'aggiorna tutti i linguaggi',
|
||||
'Update:': 'Aggiorna:',
|
||||
'upgrade web2py now': 'upgrade web2py now',
|
||||
'upload': 'upload',
|
||||
'Upload & install packed application': 'Carica ed installa pacchetto con applicazione',
|
||||
'Upload a package:': 'Upload a package:',
|
||||
'Upload and install packed application': 'Upload and install packed application',
|
||||
'upload application:': 'carica applicazione:',
|
||||
'upload file:': 'carica file:',
|
||||
'upload plugin file:': 'carica file di plugin:',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
|
||||
'Use an url:': 'Use an url:',
|
||||
'variables': 'variables',
|
||||
'Version': 'Versione',
|
||||
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
|
||||
'versioning': 'sistema di versioni',
|
||||
'Versioning': 'Versioning',
|
||||
'view': 'vista',
|
||||
'View': 'Vista',
|
||||
'Views': 'viste',
|
||||
'views': 'viste',
|
||||
'web2py Recent Tweets': 'Tweets recenti per web2py',
|
||||
'Web Framework': 'Web Framework',
|
||||
'web2py is up to date': 'web2py è aggiornato',
|
||||
'web2py Recent Tweets': 'Tweets recenti per web2py',
|
||||
'web2py upgraded; please restart it': 'web2py aggiornato; prego riavviarlo',
|
||||
'Welcome %s': 'Benvenuto %s',
|
||||
'Welcome to web2py': 'Benvenuto su web2py',
|
||||
'YES': 'SI',
|
||||
}
|
||||
|
||||
@@ -123,6 +123,21 @@ if session.authorized:
|
||||
else:
|
||||
session.last_time = t0
|
||||
|
||||
|
||||
if request.vars.is_mobile in ('true','false','auto'):
|
||||
session.is_mobile = request.vars.is_mobile or 'auto'
|
||||
if request.controller=='default' and request.function=='index':
|
||||
if not request.vars.is_mobile:
|
||||
session.is_mobile = 'auto'
|
||||
if not session.is_mobile:
|
||||
session.is_mobile = 'auto'
|
||||
if session.is_mobile == 'true':
|
||||
is_mobile = True
|
||||
elif session.is_mobile == 'false':
|
||||
is_mobile = False
|
||||
else:
|
||||
is_mobile = request.user_agent().is_mobile
|
||||
|
||||
if request.controller == "webservices":
|
||||
basic = request.env.http_authorization
|
||||
if not basic or not basic[:6].lower() == 'basic ':
|
||||
@@ -132,8 +147,8 @@ if request.controller == "webservices":
|
||||
time.sleep(10)
|
||||
raise HTTP(403,"Not authorized")
|
||||
elif not session.authorized and not \
|
||||
(request.controller == 'default' and \
|
||||
request.function in ('index','user')):
|
||||
(request.controller+'/'+request.function in
|
||||
('default/index','default/user','plugin_jqmobile/index','plugin_jqmobile/about')):
|
||||
|
||||
if request.env.query_string:
|
||||
query_string = '?' + request.env.query_string
|
||||
@@ -150,7 +165,6 @@ elif session.authorized and \
|
||||
request.function == 'index':
|
||||
redirect(URL(request.application, 'default', 'site'))
|
||||
|
||||
|
||||
if request.controller=='appadmin' and DEMO_MODE:
|
||||
session.flash = 'Appadmin disabled in demo mode'
|
||||
redirect(URL('default','sites'))
|
||||
|
||||
@@ -8,7 +8,7 @@ def A_button(*a,**b):
|
||||
return A(*a,**b)
|
||||
|
||||
def button(href, label):
|
||||
if request.user_agent().is_mobile:
|
||||
if is_mobile:
|
||||
ret = A_button(SPAN(label), _href=href)
|
||||
else:
|
||||
ret = A(SPAN(label),_class='button',_href=href)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ h3 {font-size:2.00em}
|
||||
h4 {font-size:1.50em}
|
||||
h5 {font-size:1.25em}
|
||||
h6 {font-size:1.12em}
|
||||
th,label {font-weight:bold; white-space:nowrap}
|
||||
th,label {font-weight:bold; white-space:nowrap;}
|
||||
td,th {text-align:left; padding:2px 5px 2px 5px}
|
||||
th {vertical-align:middle; border-right:1px solid white}
|
||||
td {vertical-align:top}
|
||||
@@ -69,7 +69,6 @@ fieldset {padding:16px; border-top:1px #DEDEDE solid}
|
||||
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
|
||||
|
||||
/* fix ie problem with menu */
|
||||
.ie-lte7 .topbar .container {z-index:2}
|
||||
|
||||
td.w2p_fw {padding-bottom:1px}
|
||||
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
||||
@@ -187,10 +186,7 @@ div.error {
|
||||
.web2py_paginator {}
|
||||
.web2py_grid {width:100%}
|
||||
.web2py_grid table {width:100%}
|
||||
.web2py_grid tbody td {
|
||||
padding:2px 5px 2px 5px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
||||
|
||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
||||
background-color:#EAEAEA;
|
||||
@@ -299,10 +295,16 @@ li.w2p_grid_breadcrumb_elem {
|
||||
.web2py_console input, .web2py_console select,
|
||||
.web2py_console a { margin: 2px; }
|
||||
|
||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||
|
||||
#wiki_page_body {
|
||||
width: 600px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* fix some IE problems */
|
||||
|
||||
.ie-lte7 .topbar .container {z-index:2}
|
||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -39,7 +39,7 @@ function web2py_ajax_init(target) {
|
||||
|
||||
function web2py_event_handlers() {
|
||||
var doc = jQuery(document)
|
||||
doc.on('click', '.flash', function(e){jQuery(this).fadeOut('slow'); e.preventDefault();});
|
||||
doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();});
|
||||
doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();});
|
||||
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
|
||||
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
|
||||
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
|
||||
jQuery(function() {
|
||||
var flash = jQuery('.flash');
|
||||
flash.hide();
|
||||
if(flash.html()) flash.slideDown();
|
||||
if(flash.html()) flash.append('<span style="float:right;">×</span>').slideDown();
|
||||
web2py_ajax_init(document);
|
||||
web2py_event_handlers();
|
||||
});
|
||||
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
|
||||
form.submit(function(e){
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('post',action,form.serialize(),target);
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function web2py_trap_link(target) {
|
||||
jQuery('#'+target+' a.w2p_trap').each(function(i){
|
||||
var link=jQuery(this);
|
||||
link.click(function(e) {
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('get',link.attr('href'),[],target);
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
var link=jQuery(this);
|
||||
link.click(function(e) {
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('get',link.attr('href'),[],target);
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function web2py_ajax_page(method, action, data, target) {
|
||||
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
web2py_trap_link(target);
|
||||
web2py_ajax_init('#'+target);
|
||||
if(command)
|
||||
eval(decodeURIComponent(command));
|
||||
eval(decodeURIComponent(command));
|
||||
if(flash)
|
||||
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
|
||||
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
|
||||
}
|
||||
} else {
|
||||
// run once (no timeout specified)
|
||||
element.reload_counter = Infinity;
|
||||
element.reload_counter = Infinity;
|
||||
web2py_ajax_page('get', action, null, target);
|
||||
} }); }
|
||||
|
||||
function web2py_comet(url,onmessage,onopen,onclose) {
|
||||
function web2py_websocket(url,onmessage,onopen,onclose) {
|
||||
if ("WebSocket" in window) {
|
||||
var ws = new WebSocket(url);
|
||||
ws.onopen = onopen?onopen:(function(){});
|
||||
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
|
||||
} else return false; // not supported
|
||||
}
|
||||
|
||||
|
||||
function web2py_calc_entropy(mystring) {
|
||||
//calculate a simple entropy for a given string
|
||||
var csets = new Array(
|
||||
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
|
||||
'0123456789abcdefghijklmnopqrstuvwxyz');
|
||||
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
|
||||
for (var i=0;i<mystringlist.length;i++) { // classify this character
|
||||
var c = mystringlist[i], inset=5;
|
||||
for(var j = 0; j<csets.length; j++)
|
||||
if (csets[j].indexOf(c) != -1) {inset = j; break;}
|
||||
//calculate effect of character on alphabet size
|
||||
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
|
||||
else if (!(c in other)) {score += 1;other[c] = 1;}
|
||||
if (inset != lastset) {score += 1;lastset = inset;}
|
||||
}
|
||||
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
|
||||
return Math.round(entropy*100)/100
|
||||
}
|
||||
|
||||
function web2py_validate_entropy(myfield, req_entropy) {
|
||||
var validator = function () {
|
||||
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
|
||||
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
|
||||
if(v<=0.5) {r=1.0; g=2.0*v;}
|
||||
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
|
||||
var color = '#'+rs(r)+rs(g)+rs(b);
|
||||
myfield.css('background-color',color);
|
||||
entropy_callback = myfield.data('entropy_callback');
|
||||
if(entropy_callback) entropy_callback(v);
|
||||
}
|
||||
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,173 +0,0 @@
|
||||
/*! jQuery Mobile v1.0 jquerymobile.com | jquery.org/license */
|
||||
(function(a,e){if(a.cleanData){var b=a.cleanData;a.cleanData=function(f){for(var c=0,h;(h=f[c])!=null;c++)a(h).triggerHandler("remove");b(f)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return d.call(a(this),b,c)})}}a.widget=function(b,c,h){var d=b.split(".")[0],e,b=b.split(".")[1];e=d+"-"+b;if(!h)h=c,c=a.Widget;a.expr[":"][e]=function(c){return!!a.data(c,
|
||||
b)};a[d]=a[d]||{};a[d][b]=function(a,b){arguments.length&&this._createWidget(a,b)};c=new c;c.options=a.extend(true,{},c.options);a[d][b].prototype=a.extend(true,c,{namespace:d,widgetName:b,widgetEventPrefix:a[d][b].prototype.widgetEventPrefix||b,widgetBaseClass:e},h);a.widget.bridge(b,a[d][b])};a.widget.bridge=function(b,c){a.fn[b]=function(d){var g=typeof d==="string",i=Array.prototype.slice.call(arguments,1),k=this,d=!g&&i.length?a.extend.apply(null,[true,d].concat(i)):d;if(g&&d.charAt(0)==="_")return k;
|
||||
g?this.each(function(){var c=a.data(this,b);if(!c)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+d+"'";if(!a.isFunction(c[d]))throw"no such method '"+d+"' for "+b+" widget instance";var g=c[d].apply(c,i);if(g!==c&&g!==e)return k=g,false}):this.each(function(){var e=a.data(this,b);e?e.option(d||{})._init():a.data(this,b,new c(d,this))});return k}};a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",
|
||||
options:{disabled:false},_createWidget:function(b,c){a.data(c,this.widgetName,this);this.element=a(c);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b={};a.metadata&&(b=a.metadata.get(element)[this.widgetName]);return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);
|
||||
this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,c){var d=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(c===e)return this.options[b];d={};d[b]=c}this._setOptions(d);return this},_setOptions:function(b){var c=this;a.each(b,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b;a==="disabled"&&
|
||||
this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,c,d){var e=this.options[b],c=a.Event(c);c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();d=d||{};if(c.originalEvent)for(var b=a.event.props.length,i;b;)i=a.event.props[--b],c[i]=c.originalEvent[i];this.element.trigger(c,
|
||||
d);return!(a.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
|
||||
(function(a,e){a.widget("mobile.widget",{_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments);this._trigger("init")},_getCreateOptions:function(){var b=this.element,d={};a.each(this.options,function(a){var c=b.jqmData(a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()}));c!==e&&(d[a]=c)});return d},enhanceWithin:function(b){var d=a(b).closest(":jqmData(role='page')").data("page"),d=d&&d.keepNativeSelector()||"";a(this.options.initSelector,b).not(d)[this.widgetName]()}})})(jQuery);
|
||||
(function(a){a(window);var e=a("html");a.mobile.media=function(){var b={},d=a("<div id='jquery-mediatest'>"),f=a("<body>").append(d);return function(a){if(!(a in b)){var h=document.createElement("style"),g="@media "+a+" { #jquery-mediatest { position:absolute; } }";h.type="text/css";h.styleSheet?h.styleSheet.cssText=g:h.appendChild(document.createTextNode(g));e.prepend(f).prepend(h);b[a]=d.css("position")==="absolute";f.add(h).remove()}return b[a]}}()})(jQuery);
|
||||
(function(a,e){function b(a){var b=a.charAt(0).toUpperCase()+a.substr(1),a=(a+" "+c.join(b+" ")+b).split(" "),d;for(d in a)if(f[a[d]]!==e)return true}var d=a("<body>").prependTo("html"),f=d[0].style,c=["Webkit","Moz","O"],h="palmGetResource"in window,g=window.operamini&&{}.toString.call(window.operamini)==="[object OperaMini]",i=window.blackberry;a.mobile.browser={};a.mobile.browser.ie=function(){for(var a=3,b=document.createElement("div"),c=b.all||[];b.innerHTML="<\!--[if gt IE "+ ++a+"]><br><![endif]--\>",
|
||||
c[0];);return a>4?a:!a}();a.extend(a.support,{orientation:"orientation"in window&&"onorientationchange"in window,touch:"ontouchend"in document,cssTransitions:"WebKitTransitionEvent"in window,pushState:"pushState"in history&&"replaceState"in history,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!b("content"),touchOverflow:!!b("overflowScrolling"),boxShadow:!!b("boxShadow")&&!i,scrollTop:("pageXOffset"in window||"scrollTop"in document.documentElement||"scrollTop"in d[0])&&!h&&!g,dynamicBaseTag:function(){var b=
|
||||
location.protocol+"//"+location.host+location.pathname+"ui-dir/",c=a("head base"),f=null,e="",h;c.length?e=c.attr("href"):c=f=a("<base>",{href:b}).appendTo("head");h=a("<a href='testurl' />").prependTo(d)[0].href;c[0].href=e||location.pathname;f&&f.remove();return h.indexOf(b)===0}()});d.remove();h=function(){var a=window.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")>-1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}();
|
||||
a.mobile.ajaxBlacklist=window.blackberry&&!window.WebKitPoint||g||h;h&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")});a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery);
|
||||
(function(a,e,b,d){function f(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function c(b){for(var c={},f,d;b;){f=a.data(b,n);for(d in f)if(f[d])c[d]=c.hasVirtualBinding=true;b=b.parentNode}return c}function h(){v&&(clearTimeout(v),v=0);v=setTimeout(function(){E=v=0;u.length=0;D=false;y=true},a.vmouse.resetTimerDuration)}function g(b,c,r){var e,h;if(!(h=r&&r[b])){if(r=!r)a:{for(r=c.target;r;){if((h=a.data(r,n))&&(!b||h[b]))break a;r=r.parentNode}r=null}h=r}if(h){e=c;var r=
|
||||
e.type,j,g;e=a.Event(e);e.type=b;h=e.originalEvent;j=a.event.props;if(h)for(g=j.length;g;)b=j[--g],e[b]=h[b];if(r.search(/mouse(down|up)|click/)>-1&&!e.which)e.which=1;if(r.search(/^touch/)!==-1&&(b=f(h),r=b.touches,b=b.changedTouches,r=r&&r.length?r[0]:b&&b.length?b[0]:d))for(h=0,len=z.length;h<len;h++)b=z[h],e[b]=r[b];a(c.target).trigger(e)}return e}function i(b){var c=a.data(b.target,A);if(!D&&(!E||E!==c))if(c=g("v"+b.type,b))c.isDefaultPrevented()&&b.preventDefault(),c.isPropagationStopped()&&
|
||||
b.stopPropagation(),c.isImmediatePropagationStopped()&&b.stopImmediatePropagation()}function k(b){var d=f(b).touches,e;if(d&&d.length===1&&(e=b.target,d=c(e),d.hasVirtualBinding))E=r++,a.data(e,A,E),v&&(clearTimeout(v),v=0),w=y=false,e=f(b).touches[0],x=e.pageX,t=e.pageY,g("vmouseover",b,d),g("vmousedown",b,d)}function l(a){y||(w||g("vmousecancel",a,c(a.target)),w=true,h())}function o(b){if(!y){var d=f(b).touches[0],r=w,e=a.vmouse.moveDistanceThreshold;w=w||Math.abs(d.pageX-x)>e||Math.abs(d.pageY-
|
||||
t)>e;flags=c(b.target);w&&!r&&g("vmousecancel",b,flags);g("vmousemove",b,flags);h()}}function m(a){if(!y){y=true;var b=c(a.target),d;g("vmouseup",a,b);if(!w&&(d=g("vclick",a,b))&&d.isDefaultPrevented())d=f(a).changedTouches[0],u.push({touchID:E,x:d.clientX,y:d.clientY}),D=true;g("vmouseout",a,b);w=false;h()}}function p(b){var b=a.data(b,n),c;if(b)for(c in b)if(b[c])return true;return false}function j(){}function q(b){var c=b.substr(1);return{setup:function(){p(this)||a.data(this,n,{});a.data(this,
|
||||
n)[b]=true;s[b]=(s[b]||0)+1;s[b]===1&&B.bind(c,i);a(this).bind(c,j);if(C)s.touchstart=(s.touchstart||0)+1,s.touchstart===1&&B.bind("touchstart",k).bind("touchend",m).bind("touchmove",o).bind("scroll",l)},teardown:function(){--s[b];s[b]||B.unbind(c,i);C&&(--s.touchstart,s.touchstart||B.unbind("touchstart",k).unbind("touchmove",o).unbind("touchend",m).unbind("scroll",l));var d=a(this),f=a.data(this,n);f&&(f[b]=false);d.unbind(c,j);p(this)||d.removeData(n)}}}var n="virtualMouseBindings",A="virtualTouchID",
|
||||
e="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),z="clientX clientY pageX pageY screenX screenY".split(" "),s={},v=0,x=0,t=0,w=false,u=[],D=false,y=false,C="addEventListener"in b,B=a(b),r=1,E=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10,resetTimerDuration:1500};for(var F=0;F<e.length;F++)a.event.special[e[F]]=q(e[F]);C&&b.addEventListener("click",function(b){var c=u.length,d=b.target,f,r,e,h,j;if(c){f=b.clientX;r=b.clientY;threshold=a.vmouse.clickDistanceThreshold;
|
||||
for(e=d;e;){for(h=0;h<c;h++)if(j=u[h],e===d&&Math.abs(j.x-f)<threshold&&Math.abs(j.y-r)<threshold||a.data(e,A)===j.touchID){b.preventDefault();b.stopPropagation();return}e=e.parentNode}}},true)})(jQuery,window,document);
|
||||
(function(a,e,b){function d(b,c,d){var f=d.type;d.type=c;a.event.handle.call(b,d);d.type=f}a.each("touchstart touchmove touchend orientationchange throttledresize tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(b,c){a.fn[c]=function(a){return a?this.bind(c,a):this.trigger(c)};a.attrFn[c]=true});var f=a.support.touch,c=f?"touchstart":"mousedown",h=f?"touchend":"mouseup",g=f?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function b(a,
|
||||
e){f=e;d(c,f?"scrollstart":"scrollstop",a)}var c=this,f,e;a(c).bind("touchmove scroll",function(c){a.event.special.scrollstart.enabled&&(f||b(c,true),clearTimeout(e),e=setTimeout(function(){b(c,false)},50))})}};a.event.special.tap={setup:function(){var b=this,c=a(b);c.bind("vmousedown",function(f){function e(){clearTimeout(q)}function h(){e();c.unbind("vclick",g).unbind("vmouseup",e).unbind("vmousecancel",h)}function g(a){h();j==a.target&&d(b,"tap",a)}if(f.which&&f.which!==1)return false;var j=f.target,
|
||||
q;c.bind("vmousecancel",h).bind("vmouseup",e).bind("vclick",g);q=setTimeout(function(){d(b,"taphold",a.Event("taphold"))},750)})}};a.event.special.swipe={scrollSupressionThreshold:10,durationThreshold:1E3,horizontalDistanceThreshold:30,verticalDistanceThreshold:75,setup:function(){var d=a(this);d.bind(c,function(c){function f(b){if(m){var c=b.originalEvent.touches?b.originalEvent.touches[0]:b;p={time:(new Date).getTime(),coords:[c.pageX,c.pageY]};Math.abs(m.coords[0]-p.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&
|
||||
b.preventDefault()}}var e=c.originalEvent.touches?c.originalEvent.touches[0]:c,m={time:(new Date).getTime(),coords:[e.pageX,e.pageY],origin:a(c.target)},p;d.bind(g,f).one(h,function(){d.unbind(g,f);m&&p&&p.time-m.time<a.event.special.swipe.durationThreshold&&Math.abs(m.coords[0]-p.coords[0])>a.event.special.swipe.horizontalDistanceThreshold&&Math.abs(m.coords[1]-p.coords[1])<a.event.special.swipe.verticalDistanceThreshold&&m.origin.trigger("swipe").trigger(m.coords[0]>p.coords[0]?"swipeleft":"swiperight");
|
||||
m=p=b})})}};(function(a,b){function c(){var a=f();a!==e&&(e=a,d.trigger("orientationchange"))}var d=a(b),f,e;a.event.special.orientationchange={setup:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;e=f();d.bind("throttledresize",c)},teardown:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;d.unbind("throttledresize",c)},add:function(a){var b=a.handler;a.handler=function(a){a.orientation=f();return b.apply(this,arguments)}}};a.event.special.orientationchange.orientation=
|
||||
f=function(){var c=true,c=document.documentElement;return(c=a.support.orientation?b.orientation%180==0:c&&c.clientWidth/c.clientHeight<1.1)?"portrait":"landscape"}})(jQuery,e);(function(){a.event.special.throttledresize={setup:function(){a(this).bind("resize",b)},teardown:function(){a(this).unbind("resize",b)}};var b=function(){f=(new Date).getTime();e=f-c;e>=250?(c=f,a(this).trigger("throttledresize")):(d&&clearTimeout(d),d=setTimeout(b,250-e))},c=0,d,f,e})();a.each({scrollstop:"scrollstart",taphold:"tap",
|
||||
swipeleft:"swipe",swiperight:"swipe"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)}}})})(jQuery,this);
|
||||
(function(a,e,b){function d(a){a=a||location.href;return"#"+a.replace(/^[^#]*#?(.*)$/,"$1")}var f="hashchange",c=document,h,g=a.event.special,i=c.documentMode,k="on"+f in e&&(i===b||i>7);a.fn[f]=function(a){return a?this.bind(f,a):this.trigger(f)};a.fn[f].delay=50;g[f]=a.extend(g[f],{setup:function(){if(k)return false;a(h.start)},teardown:function(){if(k)return false;a(h.stop)}});h=function(){function h(){var b=d(),c=n(p);if(b!==p)q(p=b,c),a(e).trigger(f);else if(c!==p)location.href=location.href.replace(/#.*/,
|
||||
"")+c;i=setTimeout(h,a.fn[f].delay)}var g={},i,p=d(),j=function(a){return a},q=j,n=j;g.start=function(){i||h()};g.stop=function(){i&&clearTimeout(i);i=b};a.browser.msie&&!k&&function(){var b,e;g.start=function(){if(!b)e=(e=a.fn[f].src)&&e+d(),b=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){e||q(d());h()}).attr("src",e||"javascript:0").insertAfter("body")[0].contentWindow,c.onpropertychange=function(){try{if(event.propertyName==="title")b.document.title=c.title}catch(a){}}};
|
||||
g.stop=j;n=function(){return d(b.location.href)};q=function(d,e){var h=b.document,g=a.fn[f].domain;if(d!==e)h.title=c.title,h.open(),g&&h.write('<script>document.domain="'+g+'"<\/script>'),h.close(),b.location.hash=d}}();return g}()})(jQuery,this);
|
||||
(function(a){a.widget("mobile.page",a.mobile.widget,{options:{theme:"c",domCache:false,keepNativeDefault:":jqmData(role='none'), :jqmData(role='nojs')"},_create:function(){this._trigger("beforecreate");this.element.attr("tabindex","0").addClass("ui-page ui-body-"+this.options.theme)},keepNativeSelector:function(){var e=this.options;return e.keepNative&&a.trim(e.keepNative)&&e.keepNative!==e.keepNativeDefault?[e.keepNative,e.keepNativeDefault].join(", "):e.keepNativeDefault}})})(jQuery);
|
||||
(function(a,e){var b={};a.extend(a.mobile,{ns:"",subPageUrlKey:"ui-page",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",ajaxEnabled:true,hashListeningEnabled:true,linkBindingEnabled:true,defaultPageTransition:"slide",minScrollBack:250,defaultDialogTransition:"pop",loadingMessage:"loading",pageLoadErrorMessage:"Error Loading Page",autoInitializePage:true,pushStateEnabled:true,orientationChangeEnabled:true,gradeA:function(){return a.support.mediaquery||a.mobile.browser.ie&&a.mobile.browser.ie>=
|
||||
7},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},silentScroll:function(b){if(a.type(b)!=="number")b=a.mobile.defaultHomeScroll;a.event.special.scrollstart.enabled=false;
|
||||
setTimeout(function(){e.scrollTo(0,b);a(document).trigger("silentscroll",{x:0,y:b})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},nsNormalizeDict:b,nsNormalize:function(c){return!c?void 0:b[c]||(b[c]=a.camelCase(a.mobile.ns+c))},getInheritedTheme:function(a,b){for(var d=a[0],f="",e=/ui-(bar|body)-([a-z])\b/,l,o;d;){l=d.className||"";if((o=e.exec(l))&&(f=o[2]))break;d=d.parentNode}return f||b||"a"}});a.fn.jqmData=function(b,d){var f;typeof b!="undefined"&&(f=this.data(b?
|
||||
a.mobile.nsNormalize(b):b,d));return f};a.jqmData=function(b,d,f){var e;typeof d!="undefined"&&(e=a.data(b,d?a.mobile.nsNormalize(d):d,f));return e};a.fn.jqmRemoveData=function(b){return this.removeData(a.mobile.nsNormalize(b))};a.jqmRemoveData=function(b,d){return a.removeData(b,a.mobile.nsNormalize(d))};a.fn.removeWithDependents=function(){a.removeWithDependents(this)};a.removeWithDependents=function(b){b=a(b);(b.jqmData("dependents")||a()).remove();b.remove()};a.fn.addDependents=function(b){a.addDependents(a(this),
|
||||
b)};a.addDependents=function(b,d){var f=a(b).jqmData("dependents")||a();a(b).jqmData("dependents",a.merge(f,d))};a.fn.getEncodedText=function(){return a("<div/>").text(a(this).text()).html()};var d=a.find,f=/:jqmData\(([^)]*)\)/g;a.find=function(b,e,g,i){b=b.replace(f,"[data-"+(a.mobile.ns||"")+"$1]");return d.call(this,b,e,g,i)};a.extend(a.find,d);a.find.matches=function(b,d){return a.find(b,null,null,d)};a.find.matchesSelector=function(b,d){return a.find(d,null,null,[b]).length>0}})(jQuery,this);
|
||||
(function(a,e){function b(a){var b=a.find(".ui-title:eq(0)");b.length?b.focus():a.focus()}function d(b){q&&(!q.closest(".ui-page-active").length||b)&&q.removeClass(a.mobile.activeBtnClass);q=null}function f(){z=false;A.length>0&&a.mobile.changePage.apply(null,A.pop())}function c(c,d,f,e){var g=a.mobile.urlHistory.getActive(),j=a.support.touchOverflow&&a.mobile.touchOverflowEnabled,i=g.lastScroll||(j?0:a.mobile.defaultHomeScroll),g=h();window.scrollTo(0,a.mobile.defaultHomeScroll);d&&d.data("page")._trigger("beforehide",
|
||||
null,{nextPage:c});j||c.height(g+i);c.data("page")._trigger("beforeshow",null,{prevPage:d||a("")});a.mobile.hidePageLoadingMsg();j&&i&&(c.addClass("ui-mobile-pre-transition"),b(c),c.is(".ui-native-fixed")?c.find(".ui-content").scrollTop(i):c.scrollTop(i));f=(a.mobile.transitionHandlers[f||"none"]||a.mobile.defaultTransitionHandler)(f,e,c,d);f.done(function(){j||(c.height(""),b(c));j||a.mobile.silentScroll(i);d&&(j||d.height(""),d.data("page")._trigger("hide",null,{nextPage:c}));c.data("page")._trigger("show",
|
||||
null,{prevPage:d||a("")})});return f}function h(){var b=a.event.special.orientationchange.orientation()==="portrait",c=b?screen.availHeight:screen.availWidth,b=Math.max(b?480:320,a(window).height());return Math.min(c,b)}function g(){(!a.support.touchOverflow||!a.mobile.touchOverflowEnabled)&&a("."+a.mobile.activePageClass).css("min-height",h())}function i(b,c){c&&b.attr("data-"+a.mobile.ns+"role",c);b.page()}function k(a){for(;a;){if(typeof a.nodeName==="string"&&a.nodeName.toLowerCase()=="a")break;
|
||||
a=a.parentNode}return a}function l(b){var b=a(b).closest(".ui-page").jqmData("url"),c=t.hrefNoHash;if(!b||!j.isPath(b))b=c;return j.makeUrlAbsolute(b,c)}var o=a(window),m=a("html"),p=a("head"),j={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,parseUrl:function(b){if(a.type(b)==="object")return b;b=j.urlParseRE.exec(b||"")||[];return{href:b[0]||"",hrefNoHash:b[1]||
|
||||
"",hrefNoSearch:b[2]||"",domain:b[3]||"",protocol:b[4]||"",doubleSlash:b[5]||"",authority:b[6]||"",username:b[8]||"",password:b[9]||"",host:b[10]||"",hostname:b[11]||"",port:b[12]||"",pathname:b[13]||"",directory:b[14]||"",filename:b[15]||"",search:b[16]||"",hash:b[17]||""}},makePathAbsolute:function(a,b){if(a&&a.charAt(0)==="/")return a;for(var a=a||"",c=(b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"")?b.split("/"):[],d=a.split("/"),f=0;f<d.length;f++){var e=d[f];switch(e){case ".":break;case "..":c.length&&
|
||||
c.pop();break;default:c.push(e)}}return"/"+c.join("/")},isSameDomain:function(a,b){return j.parseUrl(a).domain===j.parseUrl(b).domain},isRelativeUrl:function(a){return j.parseUrl(a).protocol===""},isAbsoluteUrl:function(a){return j.parseUrl(a).protocol!==""},makeUrlAbsolute:function(a,b){if(!j.isRelativeUrl(a))return a;var c=j.parseUrl(a),d=j.parseUrl(b),f=c.protocol||d.protocol,e=c.protocol?c.doubleSlash:c.doubleSlash||d.doubleSlash,h=c.authority||d.authority,g=c.pathname!=="",i=j.makePathAbsolute(c.pathname||
|
||||
d.filename,d.pathname);return f+e+h+i+(c.search||!g&&d.search||"")+c.hash},addSearchParams:function(b,c){var d=j.parseUrl(b),f=typeof c==="object"?a.param(c):c,e=d.search||"?";return d.hrefNoSearch+e+(e.charAt(e.length-1)!=="?"?"&":"")+f+(d.hash||"")},convertUrlToDataUrl:function(a){var b=j.parseUrl(a);if(j.isEmbeddedPage(b))return b.hash.split(s)[0].replace(/^#/,"");else if(j.isSameDomain(b,t))return b.hrefNoHash.replace(t.domain,"");return a},get:function(a){if(a===e)a=location.hash;return j.stripHash(a).replace(/[^\/]*\.[^\/*]+$/,
|
||||
"")},getFilePath:function(b){var c="&"+a.mobile.subPageUrlKey;return b&&b.split(c)[0].split(s)[0]},set:function(a){location.hash=a},isPath:function(a){return/\//.test(a)},clean:function(a){return a.replace(t.domain,"")},stripHash:function(a){return a.replace(/^#/,"")},cleanHash:function(a){return j.stripHash(a.replace(/\?.*$/,"").replace(s,""))},isExternal:function(a){a=j.parseUrl(a);return a.protocol&&a.domain!==x.domain?true:false},hasProtocol:function(a){return/^(:?\w+:)/.test(a)},isFirstPageUrl:function(b){var b=
|
||||
j.parseUrl(j.makeUrlAbsolute(b,t)),c=a.mobile.firstPage,c=c&&c[0]?c[0].id:e;return(b.hrefNoHash===x.hrefNoHash||w&&b.hrefNoHash===t.hrefNoHash)&&(!b.hash||b.hash==="#"||c&&b.hash.replace(/^#/,"")===c)},isEmbeddedPage:function(a){a=j.parseUrl(a);return a.protocol!==""?a.hash&&(a.hrefNoHash===x.hrefNoHash||w&&a.hrefNoHash===t.hrefNoHash):/^#/.test(a.href)}},q=null,n={stack:[],activeIndex:0,getActive:function(){return n.stack[n.activeIndex]},getPrev:function(){return n.stack[n.activeIndex-1]},getNext:function(){return n.stack[n.activeIndex+
|
||||
1]},addNew:function(a,b,c,d,f){n.getNext()&&n.clearForward();n.stack.push({url:a,transition:b,title:c,pageUrl:d,role:f});n.activeIndex=n.stack.length-1},clearForward:function(){n.stack=n.stack.slice(0,n.activeIndex+1)},directHashChange:function(b){var c,d,f;this.getActive();a.each(n.stack,function(a,e){b.currentUrl===e.url&&(c=a<n.activeIndex,d=!c,f=a)});this.activeIndex=f!==e?f:this.activeIndex;c?(b.either||b.isBack)(true):d&&(b.either||b.isForward)(false)},ignoreNextHashChange:false},A=[],z=false,
|
||||
s="&ui-state=dialog",v=p.children("base"),x=j.parseUrl(location.href),t=v.length?j.parseUrl(j.makeUrlAbsolute(v.attr("href"),x.href)):x,w=x.hrefNoHash!==t.hrefNoHash,u=a.support.dynamicBaseTag?{element:v.length?v:a("<base>",{href:t.hrefNoHash}).prependTo(p),set:function(a){u.element.attr("href",j.makeUrlAbsolute(a,t))},reset:function(){u.element.attr("href",t.hrefNoHash)}}:e,D=true,y,C,B;y=function(){var b=o;a.support.touchOverflow&&a.mobile.touchOverflowEnabled&&(b=a(".ui-page-active"),b=b.is(".ui-native-fixed")?
|
||||
b.find(".ui-content"):b);return b};C=function(b){if(D){var c=a.mobile.urlHistory.getActive();if(c)b=b&&b.scrollTop(),c.lastScroll=b<a.mobile.minScrollBack?a.mobile.defaultHomeScroll:b}};B=function(){setTimeout(C,100,a(this))};o.bind(a.support.pushState?"popstate":"hashchange",function(){D=false});o.one(a.support.pushState?"popstate":"hashchange",function(){D=true});o.one("pagecontainercreate",function(){a.mobile.pageContainer.bind("pagechange",function(){var a=y();D=true;a.unbind("scrollstop",B);
|
||||
a.bind("scrollstop",B)})});y().bind("scrollstop",B);a.mobile.getScreenHeight=h;a.fn.animationComplete=function(b){return a.support.cssTransitions?a(this).one("webkitAnimationEnd",b):(setTimeout(b,0),a(this))};a.mobile.path=j;a.mobile.base=u;a.mobile.urlHistory=n;a.mobile.dialogHashKey=s;a.mobile.noneTransitionHandler=function(b,c,d,f){f&&f.removeClass(a.mobile.activePageClass);d.addClass(a.mobile.activePageClass);return a.Deferred().resolve(b,c,d,f).promise()};a.mobile.defaultTransitionHandler=a.mobile.noneTransitionHandler;
|
||||
a.mobile.transitionHandlers={none:a.mobile.defaultTransitionHandler};a.mobile.allowCrossDomainPages=false;a.mobile.getDocumentUrl=function(b){return b?a.extend({},x):x.href};a.mobile.getDocumentBase=function(b){return b?a.extend({},t):t.href};a.mobile._bindPageRemove=function(){var b=a(this);!b.data("page").options.domCache&&b.is(":jqmData(external-page='true')")&&b.bind("pagehide.remove",function(){var b=a(this),c=new a.Event("pageremove");b.trigger(c);c.isDefaultPrevented()||b.removeWithDependents()})};
|
||||
a.mobile.loadPage=function(b,c){var d=a.Deferred(),f=a.extend({},a.mobile.loadPage.defaults,c),h=null,g=null,m=j.makeUrlAbsolute(b,a.mobile.activePage&&l(a.mobile.activePage)||t.hrefNoHash);if(f.data&&f.type==="get")m=j.addSearchParams(m,f.data),f.data=e;if(f.data&&f.type==="post")f.reloadPage=true;var s=j.getFilePath(m),p=j.convertUrlToDataUrl(m);f.pageContainer=f.pageContainer||a.mobile.pageContainer;h=f.pageContainer.children(":jqmData(url='"+p+"')");h.length===0&&p&&!j.isPath(p)&&(h=f.pageContainer.children("#"+
|
||||
p).attr("data-"+a.mobile.ns+"url",p));if(h.length===0)if(a.mobile.firstPage&&j.isFirstPageUrl(s))a.mobile.firstPage.parent().length&&(h=a(a.mobile.firstPage));else if(j.isEmbeddedPage(s))return d.reject(m,c),d.promise();u&&u.reset();if(h.length){if(!f.reloadPage)return i(h,f.role),d.resolve(m,c,h),d.promise();g=h}var n=f.pageContainer,k=new a.Event("pagebeforeload"),q={url:b,absUrl:m,dataUrl:p,deferred:d,options:f};n.trigger(k,q);if(k.isDefaultPrevented())return d.promise();if(f.showLoadMsg)var v=
|
||||
setTimeout(function(){a.mobile.showPageLoadingMsg()},f.loadMsgDelay);!a.mobile.allowCrossDomainPages&&!j.isSameDomain(x,m)?d.reject(m,c):a.ajax({url:s,type:f.type,data:f.data,dataType:"html",success:function(e,n,k){var o=a("<div></div>"),l=e.match(/<title[^>]*>([^<]*)/)&&RegExp.$1,t=RegExp("\\bdata-"+a.mobile.ns+"url=[\"']?([^\"'>]*)[\"']?");RegExp("(<[^>]+\\bdata-"+a.mobile.ns+"role=[\"']?page[\"']?[^>]*>)").test(e)&&RegExp.$1&&t.test(RegExp.$1)&&RegExp.$1&&(b=s=j.getFilePath(RegExp.$1));u&&u.set(s);
|
||||
o.get(0).innerHTML=e;h=o.find(":jqmData(role='page'), :jqmData(role='dialog')").first();h.length||(h=a("<div data-"+a.mobile.ns+"role='page'>"+e.split(/<\/?body[^>]*>/gmi)[1]+"</div>"));l&&!h.jqmData("title")&&(~l.indexOf("&")&&(l=a("<div>"+l+"</div>").text()),h.jqmData("title",l));if(!a.support.dynamicBaseTag){var x=j.get(s);h.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function(){var b=a(this).is("[href]")?"href":a(this).is("[src]")?"src":"action",c=a(this).attr(b),
|
||||
c=c.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(c)||a(this).attr(b,x+c)})}h.attr("data-"+a.mobile.ns+"url",j.convertUrlToDataUrl(s)).attr("data-"+a.mobile.ns+"external-page",true).appendTo(f.pageContainer);h.one("pagecreate",a.mobile._bindPageRemove);i(h,f.role);m.indexOf("&"+a.mobile.subPageUrlKey)>-1&&(h=f.pageContainer.children(":jqmData(url='"+p+"')"));f.showLoadMsg&&(clearTimeout(v),a.mobile.hidePageLoadingMsg());q.xhr=k;q.textStatus=n;q.page=h;f.pageContainer.trigger("pageload",
|
||||
q);d.resolve(m,c,h,g)},error:function(b,e,h){u&&u.set(j.get());q.xhr=b;q.textStatus=e;q.errorThrown=h;b=new a.Event("pageloadfailed");f.pageContainer.trigger(b,q);b.isDefaultPrevented()||(f.showLoadMsg&&(clearTimeout(v),a.mobile.hidePageLoadingMsg(),a("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>"+a.mobile.pageLoadErrorMessage+"</h1></div>").css({display:"block",opacity:0.96,top:o.scrollTop()+100}).appendTo(f.pageContainer).delay(800).fadeOut(400,function(){a(this).remove()})),
|
||||
d.reject(m,c))}});return d.promise()};a.mobile.loadPage.defaults={type:"get",data:e,reloadPage:false,role:e,showLoadMsg:false,pageContainer:e,loadMsgDelay:50};a.mobile.changePage=function(b,h){if(z)A.unshift(arguments);else{var g=a.extend({},a.mobile.changePage.defaults,h);g.pageContainer=g.pageContainer||a.mobile.pageContainer;g.fromPage=g.fromPage||a.mobile.activePage;var p=g.pageContainer,k=new a.Event("pagebeforechange"),q={toPage:b,options:g};p.trigger(k,q);if(!k.isDefaultPrevented())if(b=q.toPage,
|
||||
z=true,typeof b=="string")a.mobile.loadPage(b,g).done(function(b,c,d,f){z=false;c.duplicateCachedPage=f;a.mobile.changePage(d,c)}).fail(function(){z=false;d(true);f();g.pageContainer.trigger("pagechangefailed",q)});else{if(b[0]===a.mobile.firstPage[0]&&!g.dataUrl)g.dataUrl=x.hrefNoHash;var k=g.fromPage,l=g.dataUrl&&j.convertUrlToDataUrl(g.dataUrl)||b.jqmData("url"),v=l;j.getFilePath(l);var o=n.getActive(),t=n.activeIndex===0,w=0,u=document.title,y=g.role==="dialog"||b.jqmData("role")==="dialog";if(k&&
|
||||
k[0]===b[0]&&!g.allowSamePageTransition)z=false,p.trigger("pagechange",q);else{i(b,g.role);g.fromHashChange&&n.directHashChange({currentUrl:l,isBack:function(){w=-1},isForward:function(){w=1}});try{document.activeElement&&document.activeElement.nodeName.toLowerCase()!="body"?a(document.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(B){}y&&o&&(l=(o.url||"")+s);if(g.changeHash!==false&&l)n.ignoreNextHashChange=true,j.set(l);var C=!o?u:b.jqmData("title")||b.children(":jqmData(role='header')").find(".ui-title").getEncodedText();
|
||||
C&&u==document.title&&(u=C);b.jqmData("title")||b.jqmData("title",u);g.transition=g.transition||(w&&!t?o.transition:e)||(y?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition);w||n.addNew(l,g.transition,u,v,g.role);document.title=n.getActive().title;a.mobile.activePage=b;g.reverse=g.reverse||w<0;c(b,k,g.transition,g.reverse).done(function(){d();g.duplicateCachedPage&&g.duplicateCachedPage.remove();m.removeClass("ui-mobile-rendering");f();p.trigger("pagechange",q)})}}}};a.mobile.changePage.defaults=
|
||||
{transition:e,reverse:false,changeHash:true,fromHashChange:false,role:e,duplicateCachedPage:e,pageContainer:e,showLoadMsg:true,dataUrl:e,fromPage:e,allowSamePageTransition:false};a.mobile._registerInternalEvents=function(){a("form").live("submit",function(b){var c=a(this);if(a.mobile.ajaxEnabled&&!c.is(":jqmData(ajax='false')")){var d=c.attr("method"),f=c.attr("target"),e=c.attr("action");if(!e&&(e=l(c),e===t.hrefNoHash))e=x.hrefNoSearch;e=j.makeUrlAbsolute(e,l(c));!j.isExternal(e)&&!f&&(a.mobile.changePage(e,
|
||||
{type:d&&d.length&&d.toLowerCase()||"get",data:c.serialize(),transition:c.jqmData("transition"),direction:c.jqmData("direction"),reloadPage:true}),b.preventDefault())}});a(document).bind("vclick",function(b){if(!(b.which>1)&&a.mobile.linkBindingEnabled&&(b=k(b.target))&&j.parseUrl(b.getAttribute("href")||"#").hash!=="#")d(true),q=a(b).closest(".ui-btn").not(".ui-disabled"),q.addClass(a.mobile.activeBtnClass),a("."+a.mobile.activePageClass+" .ui-btn").not(b).blur()});a(document).bind("click",function(b){if(a.mobile.linkBindingEnabled){var c=
|
||||
k(b.target);if(c&&!(b.which>1)){var f=a(c),h=function(){window.setTimeout(function(){d(true)},200)};if(f.is(":jqmData(rel='back')"))return window.history.back(),false;var g=l(f),c=j.makeUrlAbsolute(f.attr("href")||"#",g);if(!a.mobile.ajaxEnabled&&!j.isEmbeddedPage(c))h();else{if(c.search("#")!=-1)if(c=c.replace(/[^#]*#/,""))c=j.isPath(c)?j.makeUrlAbsolute(c,g):j.makeUrlAbsolute("#"+c,x.hrefNoHash);else{b.preventDefault();return}var g=f.is("[rel='external']")||f.is(":jqmData(ajax='false')")||f.is("[target]"),
|
||||
i=a.mobile.allowCrossDomainPages&&x.protocol==="file:"&&c.search(/^https?:/)!=-1;g||j.isExternal(c)&&!i?h():(h=f.jqmData("transition"),g=(g=f.jqmData("direction"))&&g==="reverse"||f.jqmData("back"),f=f.attr("data-"+a.mobile.ns+"rel")||e,a.mobile.changePage(c,{transition:h,reverse:g,role:f}),b.preventDefault())}}}});a(".ui-page").live("pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=a(this),f=c.attr("href");f&&a.inArray(f,b)===-1&&(b.push(f),a.mobile.loadPage(f,
|
||||
{role:c.attr("data-"+a.mobile.ns+"rel")}))})});a.mobile._handleHashChange=function(b){var c=j.stripHash(b),f={transition:a.mobile.urlHistory.stack.length===0?"none":e,changeHash:false,fromHashChange:true};if(!a.mobile.hashListeningEnabled||n.ignoreNextHashChange)n.ignoreNextHashChange=false;else{if(n.stack.length>1&&c.indexOf(s)>-1)if(a.mobile.activePage.is(".ui-dialog"))n.directHashChange({currentUrl:c,either:function(b){var d=a.mobile.urlHistory.getActive();c=d.pageUrl;a.extend(f,{role:d.role,transition:d.transition,
|
||||
reverse:b})}});else{n.directHashChange({currentUrl:c,isBack:function(){window.history.back()},isForward:function(){window.history.forward()}});return}c?(c=typeof c==="string"&&!j.isPath(c)?j.makeUrlAbsolute("#"+c,t):c,a.mobile.changePage(c,f)):a.mobile.changePage(a.mobile.firstPage,f)}};o.bind("hashchange",function(){a.mobile._handleHashChange(location.hash)});a(document).bind("pageshow",g);a(window).bind("throttledresize",g)}})(jQuery);
|
||||
(function(a,e){var b={},d=a(e),f=a.mobile.path.parseUrl(location.href);a.extend(b,{initialFilePath:f.pathname+f.search,initialHref:f.hrefNoHash,hashchangeFired:false,state:function(){return{hash:location.hash||"#"+b.initialFilePath,title:document.title,initialHref:b.initialHref}},resetUIKeys:function(b){var f="&"+a.mobile.subPageUrlKey,d=b.indexOf(a.mobile.dialogHashKey);d>-1?b=b.slice(0,d)+"#"+b.slice(d):b.indexOf(f)>-1&&(b=b.split(f).join("#"+f));return b},nextHashChangePrevented:function(c){a.mobile.urlHistory.ignoreNextHashChange=
|
||||
c;b.onHashChangeDisabled=c},onHashChange:function(){if(!b.onHashChangeDisabled){var c,f;c=location.hash;var d=a.mobile.path.isPath(c),e=d?location.href:a.mobile.getDocumentUrl();c=d?c.replace("#",""):c;f=b.state();c=a.mobile.path.makeUrlAbsolute(c,e);d&&(c=b.resetUIKeys(c));history.replaceState(f,document.title,c)}},onPopState:function(c){var f=c.originalEvent.state;f&&(b.nextHashChangePrevented(true),setTimeout(function(){b.nextHashChangePrevented(false);a.mobile._handleHashChange(f.hash)},100))},
|
||||
init:function(){d.bind("hashchange",b.onHashChange);d.bind("popstate",b.onPopState);location.hash===""&&history.replaceState(b.state(),document.title,location.href)}});a(function(){a.mobile.pushStateEnabled&&a.support.pushState&&b.init()})})(jQuery,this);
|
||||
(function(a){function e(b,d,f,c){var e=new a.Deferred,g=d?" reverse":"",i="ui-mobile-viewport-transitioning viewport-"+b;f.animationComplete(function(){f.add(c).removeClass("out in reverse "+b);c&&c[0]!==f[0]&&c.removeClass(a.mobile.activePageClass);f.parent().removeClass(i);e.resolve(b,d,f,c)});f.parent().addClass(i);c&&c.addClass(b+" out"+g);f.addClass(a.mobile.activePageClass+" "+b+" in"+g);return e.promise()}a.mobile.css3TransitionHandler=e;if(a.mobile.defaultTransitionHandler===a.mobile.noneTransitionHandler)a.mobile.defaultTransitionHandler=
|
||||
e})(jQuery,this);
|
||||
(function(a){a.mobile.page.prototype.options.degradeInputs={color:false,date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:"text",tel:false,time:false,url:false,week:false};a(document).bind("pagecreate create",function(e){var b=a(e.target).closest(':jqmData(role="page")').data("page"),d;if(b)d=b.options,a(e.target).find("input").not(b.keepNativeSelector()).each(function(){var b=a(this),c=this.getAttribute("type"),e=d.degradeInputs[c]||"text";
|
||||
if(d.degradeInputs[c]){var g=a("<div>").html(b.clone()).html(),i=g.indexOf(" type=")>-1;b.replaceWith(g.replace(i?/\s+type=["']?\w+['"]?/:/\/?>/,' type="'+e+'" data-'+a.mobile.ns+'type="'+c+'"'+(i?"":">")))}})})})(jQuery);
|
||||
(function(a,e){a.widget("mobile.dialog",a.mobile.widget,{options:{closeBtnText:"Close",overlayTheme:"a",initSelector:":jqmData(role='dialog')"},_create:function(){var b=this,d=this.element,f=a("<a href='#' data-"+a.mobile.ns+"icon='delete' data-"+a.mobile.ns+"iconpos='notext'>"+this.options.closeBtnText+"</a>");d.addClass("ui-overlay-"+this.options.overlayTheme);d.attr("role","dialog").addClass("ui-dialog").find(":jqmData(role='header')").addClass("ui-corner-top ui-overlay-shadow").prepend(f).end().find(":jqmData(role='content'),:jqmData(role='footer')").addClass("ui-overlay-shadow").last().addClass("ui-corner-bottom");
|
||||
f.bind("vclick",function(){b.close()});d.bind("vclick submit",function(b){var b=a(b.target).closest(b.type==="vclick"?"a":"form"),f;b.length&&!b.jqmData("transition")&&(f=a.mobile.urlHistory.getActive()||{},b.attr("data-"+a.mobile.ns+"transition",f.transition||a.mobile.defaultDialogTransition).attr("data-"+a.mobile.ns+"direction","reverse"))}).bind("pagehide",function(){a(this).find("."+a.mobile.activeBtnClass).removeClass(a.mobile.activeBtnClass)})},close:function(){e.history.back()}});a(a.mobile.dialog.prototype.options.initSelector).live("pagecreate",
|
||||
function(){a(this).dialog()})})(jQuery,this);
|
||||
(function(a){a.mobile.page.prototype.options.backBtnText="Back";a.mobile.page.prototype.options.addBackBtn=false;a.mobile.page.prototype.options.backBtnTheme=null;a.mobile.page.prototype.options.headerTheme="a";a.mobile.page.prototype.options.footerTheme="a";a.mobile.page.prototype.options.contentTheme=null;a(":jqmData(role='page'), :jqmData(role='dialog')").live("pagecreate",function(){var e=a(this),b=e.data("page").options,d=e.jqmData("role"),f=b.theme;a(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')",
|
||||
this).each(function(){var c=a(this),e=c.jqmData("role"),g=c.jqmData("theme"),i=g||b.contentTheme||d==="dialog"&&f,k;c.addClass("ui-"+e);if(e==="header"||e==="footer"){var l=g||(e==="header"?b.headerTheme:b.footerTheme)||f;c.addClass("ui-bar-"+l).attr("role",e==="header"?"banner":"contentinfo");g=c.children("a");i=g.hasClass("ui-btn-left");k=g.hasClass("ui-btn-right");i=i||g.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length;k||g.eq(1).addClass("ui-btn-right");b.addBackBtn&&e==="header"&&a(".ui-page").length>
|
||||
1&&c.jqmData("url")!==a.mobile.path.stripHash(location.hash)&&!i&&a("<a href='#' class='ui-btn-left' data-"+a.mobile.ns+"rel='back' data-"+a.mobile.ns+"icon='arrow-l'>"+b.backBtnText+"</a>").attr("data-"+a.mobile.ns+"theme",b.backBtnTheme||l).prependTo(c);c.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({tabindex:"0",role:"heading","aria-level":"1"})}else e==="content"&&(i&&c.addClass("ui-body-"+i),c.attr("role","main"))})})})(jQuery);
|
||||
(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:true,heading:"h1,h2,h3,h4,h5,h6,legend",theme:null,contentTheme:null,iconTheme:"d",initSelector:":jqmData(role='collapsible')"},_create:function(){var e=this.element,b=this.options,d=e.addClass("ui-collapsible"),f=e.children(b.heading).first(),c=d.wrapInner("<div class='ui-collapsible-content'></div>").find(".ui-collapsible-content"),
|
||||
h=e.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set"),e=h.children(":jqmData(role='collapsible')");f.is("legend")&&(f=a("<div role='heading'>"+f.html()+"</div>").insertBefore(f),f.next().remove());if(h.length){if(!b.theme)b.theme=h.jqmData("theme");if(!b.contentTheme)b.contentTheme=h.jqmData("content-theme")}c.addClass(b.contentTheme?"ui-body-"+b.contentTheme:"");f.insertBefore(c).addClass("ui-collapsible-heading").append("<span class='ui-collapsible-heading-status'></span>").wrapInner("<a href='#' class='ui-collapsible-heading-toggle'></a>").find("a").first().buttonMarkup({shadow:false,
|
||||
corners:false,iconPos:"left",icon:"plus",theme:b.theme});h.length?(h.jqmData("collapsiblebound")||h.jqmData("collapsiblebound",true).bind("expand",function(b){a(b.target).closest(".ui-collapsible").siblings(".ui-collapsible").trigger("collapse")}),e.first().find("a").first().addClass("ui-corner-top").find(".ui-btn-inner").addClass("ui-corner-top"),e.last().jqmData("collapsible-last",true).find("a").first().addClass("ui-corner-bottom").find(".ui-btn-inner").addClass("ui-corner-bottom"),d.jqmData("collapsible-last")&&
|
||||
f.find("a").first().add(f.find(".ui-btn-inner")).addClass("ui-corner-bottom")):f.find("a").first().add(f.find(".ui-btn-inner")).addClass("ui-corner-top ui-corner-bottom");d.bind("expand collapse",function(e){if(!e.isDefaultPrevented()){e.preventDefault();var i=a(this),e=e.type==="collapse",k=b.contentTheme;f.toggleClass("ui-collapsible-heading-collapsed",e).find(".ui-collapsible-heading-status").text(e?b.expandCueText:b.collapseCueText).end().find(".ui-icon").toggleClass("ui-icon-minus",!e).toggleClass("ui-icon-plus",
|
||||
e);i.toggleClass("ui-collapsible-collapsed",e);c.toggleClass("ui-collapsible-content-collapsed",e).attr("aria-hidden",e);if(k&&(!h.length||d.jqmData("collapsible-last")))f.find("a").first().add(f.find(".ui-btn-inner")).toggleClass("ui-corner-bottom",e),c.toggleClass("ui-corner-bottom",!e);c.trigger("updatelayout")}}).trigger(b.collapsed?"collapse":"expand");f.bind("click",function(a){var b=f.is(".ui-collapsible-heading-collapsed")?"expand":"collapse";d.trigger(b);a.preventDefault()})}});a(document).bind("pagecreate create",
|
||||
function(e){a(a.mobile.collapsible.prototype.options.initSelector,e.target).collapsible()})})(jQuery);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br")};a(document).bind("pagecreate create",function(e){a(":jqmData(role='fieldcontain')",e.target).fieldcontain()})})(jQuery);
|
||||
(function(a){a.fn.grid=function(e){return this.each(function(){var b=a(this),d=a.extend({grid:null},e),f=b.children(),c={solo:1,a:2,b:3,c:4,d:5},d=d.grid;if(!d)if(f.length<=5)for(var h in c)c[h]===f.length&&(d=h);else d="a";c=c[d];b.addClass("ui-grid-"+d);f.filter(":nth-child("+c+"n+1)").addClass("ui-block-a");c>1&&f.filter(":nth-child("+c+"n+2)").addClass("ui-block-b");c>2&&f.filter(":nth-child(3n+3)").addClass("ui-block-c");c>3&&f.filter(":nth-child(4n+4)").addClass("ui-block-d");c>4&&f.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(jQuery);
|
||||
(function(a,e){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null,initSelector:":jqmData(role='navbar')"},_create:function(){var b=this.element,d=b.find("a"),f=d.filter(":jqmData(icon)").length?this.options.iconpos:e;b.addClass("ui-navbar").attr("role","navigation").find("ul").grid({grid:this.options.grid});f||b.addClass("ui-navbar-noicons");d.buttonMarkup({corners:false,shadow:false,iconpos:f});b.delegate("a","vclick",function(){d.not(".ui-state-persist").removeClass(a.mobile.activeBtnClass);
|
||||
a(this).addClass(a.mobile.activeBtnClass)})}});a(document).bind("pagecreate create",function(b){a(a.mobile.navbar.prototype.options.initSelector,b.target).navbar()})})(jQuery);
|
||||
(function(a){var e={};a.widget("mobile.listview",a.mobile.widget,{options:{theme:null,countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",inset:false,initSelector:":jqmData(role='listview')"},_create:function(){var a=this;a.element.addClass(function(d,f){return f+" ui-listview "+(a.options.inset?" ui-listview-inset ui-corner-all ui-shadow ":"")});a.refresh(true)},_removeCorners:function(a,d){a=a.add(a.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb"));d==="top"?a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl"):
|
||||
d==="bottom"?a.removeClass("ui-corner-bottom ui-corner-br ui-corner-bl"):a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl ui-corner-bottom ui-corner-br ui-corner-bl")},_refreshCorners:function(a){var d,f;this.options.inset&&(d=this.element.children("li"),f=a?d.not(".ui-screen-hidden"):d.filter(":visible"),this._removeCorners(d),d=f.first().addClass("ui-corner-top"),d.add(d.find(".ui-btn-inner").not(".ui-li-link-alt span:first-child")).addClass("ui-corner-top").end().find(".ui-li-link-alt, .ui-li-link-alt span:first-child").addClass("ui-corner-tr").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-tl"),
|
||||
f=f.last().addClass("ui-corner-bottom"),f.add(f.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-bl"));a||this.element.trigger("updatelayout")},_findFirstElementByTagName:function(a,d,f,c){var e={};for(e[f]=e[c]=true;a;){if(e[a.nodeName])return a;a=a[d]}return null},_getChildrenByTagName:function(b,d,f){var c=[],e={};e[d]=e[f]=true;for(b=b.firstChild;b;)e[b.nodeName]&&c.push(b),b=b.nextSibling;return a(c)},_addThumbClasses:function(b){var d,
|
||||
f,c=b.length;for(d=0;d<c;d++)f=a(this._findFirstElementByTagName(b[d].firstChild,"nextSibling","img","IMG")),f.length&&(f.addClass("ui-li-thumb"),a(this._findFirstElementByTagName(f[0].parentNode,"parentNode","li","LI")).addClass(f.is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb"))},refresh:function(b){this.parentPage=this.element.closest(".ui-page");this._createSubPages();var d=this.options,f=this.element,c=f.jqmData("dividertheme")||d.dividerTheme,e=f.jqmData("splittheme"),g=f.jqmData("spliticon"),
|
||||
i=this._getChildrenByTagName(f[0],"li","LI"),k=a.support.cssPseudoElement||!a.nodeName(f[0],"ol")?0:1,l={},o,m,p,j,q;k&&f.find(".ui-li-dec").remove();if(!d.theme)d.theme=a.mobile.getInheritedTheme(this.element,"c");for(var n=0,A=i.length;n<A;n++){o=i.eq(n);m="ui-li";if(b||!o.hasClass("ui-li"))p=o.jqmData("theme")||d.theme,j=this._getChildrenByTagName(o[0],"a","A"),j.length?(q=o.jqmData("icon"),o.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,iconpos:"right",icon:j.length>1||q===false?false:
|
||||
q||"arrow-r",theme:p}),q!=false&&j.length==1&&o.addClass("ui-li-has-arrow"),j.first().addClass("ui-link-inherit"),j.length>1&&(m+=" ui-li-has-alt",j=j.last(),q=e||j.jqmData("theme")||d.splitTheme,j.appendTo(o).attr("title",j.getEncodedText()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,corners:false,theme:p,icon:false,iconpos:false}).find(".ui-btn-inner").append(a(document.createElement("span")).buttonMarkup({shadow:true,corners:true,theme:q,iconpos:"notext",icon:g||j.jqmData("icon")||
|
||||
d.splitIcon})))):o.jqmData("role")==="list-divider"?(m+=" ui-li-divider ui-btn ui-bar-"+c,o.attr("role","heading"),k&&(k=1)):m+=" ui-li-static ui-body-"+p;k&&m.indexOf("ui-li-divider")<0&&(p=o.is(".ui-li-static:first")?o:o.find(".ui-link-inherit"),p.addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+k++ +". </span>"));l[m]||(l[m]=[]);l[m].push(o[0])}for(m in l)a(l[m]).addClass(m).children(".ui-btn-inner").addClass(m);f.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end().find("p, dl").addClass("ui-li-desc").end().find(".ui-li-aside").each(function(){var b=
|
||||
a(this);b.prependTo(b.parent())}).end().find(".ui-li-count").each(function(){a(this).closest("li").addClass("ui-li-has-count")}).addClass("ui-btn-up-"+(f.jqmData("counttheme")||this.options.countTheme)+" ui-btn-corner-all");this._addThumbClasses(i);this._addThumbClasses(f.find(".ui-link-inherit"));this._refreshCorners(b)},_idStringEscape:function(a){return a.replace(/[^a-zA-Z0-9]/g,"-")},_createSubPages:function(){var b=this.element,d=b.closest(".ui-page"),f=d.jqmData("url"),c=f||d[0][a.expando],
|
||||
h=b.attr("id"),g=this.options,i="data-"+a.mobile.ns,k=this,l=d.find(":jqmData(role='footer')").jqmData("id"),o;typeof e[c]==="undefined"&&(e[c]=-1);h=h||++e[c];a(b.find("li>ul, li>ol").toArray().reverse()).each(function(c){var d=a(this),e=d.attr("id")||h+"-"+c,c=d.parent(),k=a(d.prevAll().toArray().reverse()),k=k.length?k:a("<span>"+a.trim(c.contents()[0].nodeValue)+"</span>"),n=k.first().getEncodedText(),e=(f||"")+"&"+a.mobile.subPageUrlKey+"="+e,A=d.jqmData("theme")||g.theme,z=d.jqmData("counttheme")||
|
||||
b.jqmData("counttheme")||g.countTheme;o=true;d.detach().wrap("<div "+i+"role='page' "+i+"url='"+e+"' "+i+"theme='"+A+"' "+i+"count-theme='"+z+"'><div "+i+"role='content'></div></div>").parent().before("<div "+i+"role='header' "+i+"theme='"+g.headerTheme+"'><div class='ui-title'>"+n+"</div></div>").after(l?a("<div "+i+"role='footer' "+i+"id='"+l+"'>"):"").parent().appendTo(a.mobile.pageContainer).page();d=c.find("a:first");d.length||(d=a("<a/>").html(k||n).prependTo(c.empty()));d.attr("href","#"+e)}).listview();
|
||||
o&&d.is(":jqmData(external-page='true')")&&d.data("page").options.domCache===false&&d.unbind("pagehide.remove").bind("pagehide.remove",function(b,c){var e=c.nextPage;c.nextPage&&(e=e.jqmData("url"),e.indexOf(f+"&"+a.mobile.subPageUrlKey)!==0&&(k.childPages().remove(),d.remove()))})},childPages:function(){var b=this.parentPage.jqmData("url");return a(":jqmData(url^='"+b+"&"+a.mobile.subPageUrlKey+"')")}});a(document).bind("pagecreate create",function(b){a(a.mobile.listview.prototype.options.initSelector,
|
||||
b.target).listview()})})(jQuery);
|
||||
(function(a){a.mobile.listview.prototype.options.filter=false;a.mobile.listview.prototype.options.filterPlaceholder="Filter items...";a.mobile.listview.prototype.options.filterTheme="c";a.mobile.listview.prototype.options.filterCallback=function(a,b){return a.toLowerCase().indexOf(b)===-1};a(":jqmData(role='listview')").live("listviewcreate",function(){var e=a(this),b=e.data("listview");if(b.options.filter){var d=a("<form>",{"class":"ui-listview-filter ui-bar-"+b.options.filterTheme,role:"search"});
|
||||
a("<input>",{placeholder:b.options.filterPlaceholder}).attr("data-"+a.mobile.ns+"type","search").jqmData("lastval","").bind("keyup change",function(){var d=a(this),c=this.value.toLowerCase(),h=null,h=d.jqmData("lastval")+"",g=false,i="";d.jqmData("lastval",c);i=c.substr(0,h.length-1).replace(h,"");h=c.length<h.length||i.length!=c.length-h.length?e.children():e.children(":not(.ui-screen-hidden)");if(c){for(var k=h.length-1;k>=0;k--)d=a(h[k]),i=d.jqmData("filtertext")||d.text(),d.is("li:jqmData(role=list-divider)")?
|
||||
(d.toggleClass("ui-filter-hidequeue",!g),g=false):b.options.filterCallback(i,c)?d.toggleClass("ui-filter-hidequeue",true):g=true;h.filter(":not(.ui-filter-hidequeue)").toggleClass("ui-screen-hidden",false);h.filter(".ui-filter-hidequeue").toggleClass("ui-screen-hidden",true).toggleClass("ui-filter-hidequeue",false)}else h.toggleClass("ui-screen-hidden",false);b._refreshCorners()}).appendTo(d).textinput();a(this).jqmData("inset")&&d.addClass("ui-listview-filter-inset");d.bind("submit",function(){return false}).insertBefore(e)}})})(jQuery);
|
||||
(function(a){a(document).bind("pagecreate create",function(e){a(":jqmData(role='nojs')",e.target).addClass("ui-nojs")})})(jQuery);
|
||||
(function(a,e){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null,initSelector:"input[type='checkbox'],input[type='radio']"},_create:function(){var b=this,d=this.element,f=d.closest("form,fieldset,:jqmData(role='page')").find("label[for='"+d[0].id+"']"),c=d.attr("type"),h=c+"-on",g=c+"-off",i=d.parents(":jqmData(type='horizontal')").length?e:g;if(!(c!=="checkbox"&&c!=="radio")){a.extend(this,{label:f,inputtype:c,checkedClass:"ui-"+h+(i?"":" "+a.mobile.activeBtnClass),uncheckedClass:"ui-"+
|
||||
g,checkedicon:"ui-icon-"+h,uncheckedicon:"ui-icon-"+g});if(!this.options.theme)this.options.theme=this.element.jqmData("theme");f.buttonMarkup({theme:this.options.theme,icon:i,shadow:false});d.add(f).wrapAll("<div class='ui-"+c+"'></div>");f.bind({vmouseover:function(b){a(this).parent().is(".ui-disabled")&&b.stopPropagation()},vclick:function(a){if(d.is(":disabled"))a.preventDefault();else return b._cacheVals(),d.prop("checked",c==="radio"&&true||!d.prop("checked")),d.triggerHandler("click"),b._getInputSet().not(d).prop("checked",
|
||||
false),b._updateAll(),false}});d.bind({vmousedown:function(){b._cacheVals()},vclick:function(){var c=a(this);c.is(":checked")?(c.prop("checked",true),b._getInputSet().not(c).prop("checked",false)):c.prop("checked",false);b._updateAll()},focus:function(){f.addClass("ui-focus")},blur:function(){f.removeClass("ui-focus")}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){var b=a(this);b.jqmData("cacheVal",b.is(":checked"))})},_getInputSet:function(){return this.inputtype==
|
||||
"checkbox"?this.element:this.element.closest("form,fieldset,:jqmData(role='page')").find("input[name='"+this.element.attr("name")+"'][type='"+this.inputtype+"']")},_updateAll:function(){var b=this;this._getInputSet().each(function(){var d=a(this);(d.is(":checked")||b.inputtype==="checkbox")&&d.trigger("change")}).checkboxradio("refresh")},refresh:function(){var b=this.element,d=this.label,f=d.find(".ui-icon");a(b[0]).prop("checked")?(d.addClass(this.checkedClass).removeClass(this.uncheckedClass),
|
||||
f.addClass(this.checkedicon).removeClass(this.uncheckedicon)):(d.removeClass(this.checkedClass).addClass(this.uncheckedClass),f.removeClass(this.checkedicon).addClass(this.uncheckedicon));b.is(":disabled")?this.disable():this.enable()},disable:function(){this.element.prop("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.prop("disabled",false).parent().removeClass("ui-disabled")}});a(document).bind("pagecreate create",function(b){a.mobile.checkboxradio.prototype.enhanceWithin(b.target)})})(jQuery);
|
||||
(function(a,e){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:null,corners:true,shadow:true,iconshadow:true,initSelector:"button, [type='button'], [type='submit'], [type='reset'], [type='image']"},_create:function(){var b=this.element,d=this.options,f,c;this.button=a("<div></div>").text(b.text()||b.val()).insertBefore(b).buttonMarkup({theme:d.theme,icon:d.icon,iconpos:d.iconpos,inline:d.inline,corners:d.corners,shadow:d.shadow,iconshadow:d.iconshadow}).append(b.addClass("ui-btn-hidden"));
|
||||
d=b.attr("type");f=b.attr("name");d!=="button"&&d!=="reset"&&f&&b.bind("vclick",function(){c===e&&(c=a("<input>",{type:"hidden",name:b.attr("name"),value:b.attr("value")}).insertBefore(b),a(document).one("submit",function(){c.remove();c=e}))});this.refresh()},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",
|
||||
true);return this._setOption("disabled",true)},refresh:function(){var a=this.element;a.prop("disabled")?this.disable():this.enable();this.button.data("textWrapper").text(a.text()||a.val())}});a(document).bind("pagecreate create",function(b){a.mobile.button.prototype.enhanceWithin(b.target)})})(jQuery);
|
||||
(function(a,e){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false,initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')"},_create:function(){var b=this,d=this.element,f=a.mobile.getInheritedTheme(d,"c"),c=this.options.theme||f,h=this.options.trackTheme||f,g=d[0].nodeName.toLowerCase(),f=g=="select"?"ui-slider-switch":"",i=d.attr("id"),k=i+"-label",i=a("[for='"+i+"']").attr("id",k),l=function(){return g=="input"?parseFloat(d.val()):
|
||||
d[0].selectedIndex},o=g=="input"?parseFloat(d.attr("min")):0,m=g=="input"?parseFloat(d.attr("max")):d.find("option").length-1,p=window.parseFloat(d.attr("step")||1),j=a("<div class='ui-slider "+f+" ui-btn-down-"+h+" ui-btn-corner-all' role='application'></div>"),q=a("<a href='#' class='ui-slider-handle'></a>").appendTo(j).buttonMarkup({corners:true,theme:c,shadow:true}).attr({role:"slider","aria-valuemin":o,"aria-valuemax":m,"aria-valuenow":l(),"aria-valuetext":l(),title:l(),"aria-labelledby":k});
|
||||
a.extend(this,{slider:j,handle:q,dragging:false,beforeStart:null,userModified:false,mouseMoved:false});g=="select"&&(j.wrapInner("<div class='ui-slider-inneroffset'></div>"),q.addClass("ui-slider-handle-snapping"),d.find("option"),d.find("option").each(function(b){var c=!b?"b":"a",d=!b?"right":"left",b=!b?" ui-btn-down-"+h:" "+a.mobile.activeBtnClass;a("<div class='ui-slider-labelbg ui-slider-labelbg-"+c+b+" ui-btn-corner-"+d+"'></div>").prependTo(j);a("<span class='ui-slider-label ui-slider-label-"+
|
||||
c+b+" ui-btn-corner-"+d+"' role='img'>"+a(this).getEncodedText()+"</span>").prependTo(q)}));i.addClass("ui-slider");d.addClass(g==="input"?"ui-slider-input":"ui-slider-switch").change(function(){b.mouseMoved||b.refresh(l(),true)}).keyup(function(){b.refresh(l(),true,true)}).blur(function(){b.refresh(l(),true)});a(document).bind("vmousemove",function(a){if(b.dragging)return b.mouseMoved=true,g==="select"&&q.removeClass("ui-slider-handle-snapping"),b.refresh(a),b.userModified=b.beforeStart!==d[0].selectedIndex,
|
||||
false});j.bind("vmousedown",function(a){b.dragging=true;b.userModified=false;b.mouseMoved=false;if(g==="select")b.beforeStart=d[0].selectedIndex;b.refresh(a);return false});j.add(document).bind("vmouseup",function(){if(b.dragging)return b.dragging=false,g==="select"&&(q.addClass("ui-slider-handle-snapping"),b.mouseMoved?b.userModified?b.refresh(b.beforeStart==0?1:0):b.refresh(b.beforeStart):b.refresh(b.beforeStart==0?1:0)),b.mouseMoved=false});j.insertAfter(d);this.handle.bind("vmousedown",function(){a(this).focus()}).bind("vclick",
|
||||
false);this.handle.bind("keydown",function(c){var d=l();if(!b.options.disabled){switch(c.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:if(c.preventDefault(),!b._keySliding)b._keySliding=true,a(this).addClass("ui-state-active")}switch(c.keyCode){case a.mobile.keyCode.HOME:b.refresh(o);break;case a.mobile.keyCode.END:b.refresh(m);
|
||||
break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:b.refresh(d+p);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.refresh(d-p)}}}).keyup(function(){if(b._keySliding)b._keySliding=false,a(this).removeClass("ui-state-active")});this.refresh(e,e,true)},refresh:function(a,d,f){(this.options.disabled||this.element.attr("disabled"))&&this.disable();var c=this.element,e,g=c[0].nodeName.toLowerCase(),i=g==="input"?parseFloat(c.attr("min")):
|
||||
0,k=g==="input"?parseFloat(c.attr("max")):c.find("option").length-1;if(typeof a==="object"){if(!this.dragging||a.pageX<this.slider.offset().left-8||a.pageX>this.slider.offset().left+this.slider.width()+8)return;e=Math.round((a.pageX-this.slider.offset().left)/this.slider.width()*100)}else a==null&&(a=g==="input"?parseFloat(c.val()):c[0].selectedIndex),e=(parseFloat(a)-i)/(k-i)*100;if(!isNaN(e)&&(e<0&&(e=0),e>100&&(e=100),a=Math.round(e/100*(k-i))+i,a<i&&(a=i),a>k&&(a=k),this.handle.css("left",e+"%"),
|
||||
this.handle.attr({"aria-valuenow":g==="input"?a:c.find("option").eq(a).attr("value"),"aria-valuetext":g==="input"?a:c.find("option").eq(a).getEncodedText(),title:a}),g==="select"&&(a===0?this.slider.addClass("ui-slider-switch-a").removeClass("ui-slider-switch-b"):this.slider.addClass("ui-slider-switch-b").removeClass("ui-slider-switch-a")),!f))f=false,g==="input"?(f=c.val()!==a,c.val(a)):(f=c[0].selectedIndex!==a,c[0].selectedIndex=a),!d&&f&&c.trigger("change")},enable:function(){this.element.attr("disabled",
|
||||
false);this.slider.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}});a(document).bind("pagecreate create",function(b){a.mobile.slider.prototype.enhanceWithin(b.target)})})(jQuery);
|
||||
(function(a){a.widget("mobile.textinput",a.mobile.widget,{options:{theme:null,initSelector:"input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])"},_create:function(){var e=this.element,
|
||||
b=this.options.theme||a.mobile.getInheritedTheme(this.element,"c"),d=" ui-body-"+b,f,c;a("label[for='"+e.attr("id")+"']").addClass("ui-input-text");f=e.addClass("ui-input-text ui-body-"+b);typeof e[0].autocorrect!=="undefined"&&!a.support.touchOverflow&&(e[0].setAttribute("autocorrect","off"),e[0].setAttribute("autocomplete","off"));e.is("[type='search'],:jqmData(type='search')")?(f=e.wrap("<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield"+d+"'></div>").parent(),
|
||||
c=a("<a href='#' class='ui-input-clear' title='clear text'>clear text</a>").tap(function(a){e.val("").focus();e.trigger("change");c.addClass("ui-input-clear-hidden");a.preventDefault()}).appendTo(f).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true}),b=function(){setTimeout(function(){c.toggleClass("ui-input-clear-hidden",!e.val())},0)},b(),e.bind("paste cut keyup focus change blur",b)):e.addClass("ui-corner-all ui-shadow-inset"+d);e.focus(function(){f.addClass("ui-focus")}).blur(function(){f.removeClass("ui-focus")});
|
||||
if(e.is("textarea")){var h=function(){var a=e[0].scrollHeight;e[0].clientHeight<a&&e.height(a+15)},g;e.keyup(function(){clearTimeout(g);g=setTimeout(h,100)});a.trim(e.val())&&(a(window).load(h),a(document).one("pagechange",h))}},disable:function(){(this.element.attr("disabled",true).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is("[type='search'],:jqmData(type='search')")?this.element.parent():
|
||||
this.element).removeClass("ui-disabled")}});a(document).bind("pagecreate create",function(e){a.mobile.textinput.prototype.enhanceWithin(e.target)})})(jQuery);
|
||||
(function(a){var e=function(b){var d=b.selectID,f=b.label,c=b.select.closest(".ui-page"),e=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(c),g=b._selectOptions(),i=b.isMultiple=b.select[0].multiple,k=d+"-button",l=d+"-menu",o=a("<div data-"+a.mobile.ns+"role='dialog' data-"+a.mobile.ns+"theme='"+b.options.theme+"' data-"+a.mobile.ns+"overlay-theme='"+b.options.overlayTheme+"'><div data-"+a.mobile.ns+"role='header'><div class='ui-title'>"+f.getEncodedText()+"</div></div><div data-"+
|
||||
a.mobile.ns+"role='content'></div></div>").appendTo(a.mobile.pageContainer).page(),m=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-"+b.options.overlayTheme+" "+a.mobile.defaultDialogTransition}).insertAfter(e),p=a("<ul>",{"class":"ui-selectmenu-list",id:l,role:"listbox","aria-labelledby":k}).attr("data-"+a.mobile.ns+"theme",b.options.theme).appendTo(m),j=a("<div>",{"class":"ui-header ui-bar-"+b.options.theme}).prependTo(m),q=a("<h1>",{"class":"ui-title"}).appendTo(j),
|
||||
n=a("<a>",{text:b.options.closeText,href:"#","class":"ui-btn-left"}).attr("data-"+a.mobile.ns+"iconpos","notext").attr("data-"+a.mobile.ns+"icon","delete").appendTo(j).buttonMarkup(),A=o.find(".ui-content"),z=o.find(".ui-header a");a.extend(b,{select:b.select,selectID:d,buttonId:k,menuId:l,thisPage:c,menuPage:o,label:f,screen:e,selectOptions:g,isMultiple:i,theme:b.options.theme,listbox:m,list:p,header:j,headerTitle:q,headerClose:n,menuPageContent:A,menuPageClose:z,placeholder:"",build:function(){var b=
|
||||
this;b.refresh();b.select.attr("tabindex","-1").focus(function(){a(this).blur();b.button.focus()});b.button.bind("vclick keydown",function(c){if(c.type=="vclick"||c.keyCode&&(c.keyCode===a.mobile.keyCode.ENTER||c.keyCode===a.mobile.keyCode.SPACE))b.open(),c.preventDefault()});b.list.attr("role","listbox").delegate(".ui-li>a","focusin",function(){a(this).attr("tabindex","0")}).delegate(".ui-li>a","focusout",function(){a(this).attr("tabindex","-1")}).delegate("li:not(.ui-disabled, .ui-li-divider)",
|
||||
"click",function(c){var d=b.select[0].selectedIndex,f=b.list.find("li:not(.ui-li-divider)").index(this),e=b._selectOptions().eq(f)[0];e.selected=b.isMultiple?!e.selected:true;b.isMultiple&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",e.selected).toggleClass("ui-icon-checkbox-off",!e.selected);(b.isMultiple||d!==f)&&b.select.trigger("change");b.isMultiple||b.close();c.preventDefault()}).keydown(function(b){var c=a(b.target),d=c.closest("li");switch(b.keyCode){case 38:return b=d.prev(),
|
||||
b.length&&(c.blur().attr("tabindex","-1"),b.find("a").first().focus()),false;case 40:return b=d.next(),b.length&&(c.blur().attr("tabindex","-1"),b.find("a").first().focus()),false;case 13:case 32:return c.trigger("click"),false}});b.menuPage.bind("pagehide",function(){b.list.appendTo(b.listbox);b._focusButton();a.mobile._bindPageRemove.call(b.thisPage)});b.screen.bind("vclick",function(){b.close()});b.headerClose.click(function(){if(b.menuType=="overlay")return b.close(),false});b.thisPage.addDependents(this.menuPage)},
|
||||
_isRebuildRequired:function(){var a=this.list.find("li");return this._selectOptions().text()!==a.text()},refresh:function(b){var c=this;this._selectOptions();this.selected();var d=this.selectedIndices();(b||this._isRebuildRequired())&&c._buildList();c.setButtonText();c.setButtonCount();c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected",false).each(function(b){a.inArray(b,d)>-1&&(b=a(this),b.attr("aria-selected",true),c.isMultiple?b.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on"):
|
||||
b.addClass(a.mobile.activeBtnClass))})},close:function(){if(!this.options.disabled&&this.isOpen)this.menuType=="page"?window.history.back():(this.screen.addClass("ui-screen-hidden"),this.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in"),this.list.appendTo(this.listbox),this._focusButton()),this.isOpen=false},open:function(){if(!this.options.disabled){var b=this,c=b.list.parent().outerHeight(),d=b.list.parent().outerWidth(),f=a(".ui-page-active"),e=a.support.touchOverflow&&
|
||||
a.mobile.touchOverflowEnabled,f=f.is(".ui-native-fixed")?f.find(".ui-content"):f;scrollTop=e?f.scrollTop():a(window).scrollTop();btnOffset=b.button.offset().top;screenHeight=window.innerHeight;screenWidth=window.innerWidth;b.button.addClass(a.mobile.activeBtnClass);setTimeout(function(){b.button.removeClass(a.mobile.activeBtnClass)},300);if(c>screenHeight-80||!a.support.scrollTop){b.thisPage.unbind("pagehide.remove");if(scrollTop==0&&btnOffset>screenHeight)b.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",
|
||||
btnOffset)});b.menuPage.one("pageshow",function(){a(window).one("silentscroll",function(){b.list.find(a.mobile.activeBtnClass).focus()});b.isOpen=true});b.menuType="page";b.menuPageContent.append(b.list);b.menuPage.find("div .ui-title").text(b.label.text());a.mobile.changePage(b.menuPage,{transition:a.mobile.defaultDialogTransition})}else{b.menuType="overlay";b.screen.height(a(document).height()).removeClass("ui-screen-hidden");var f=btnOffset-scrollTop,h=scrollTop+screenHeight-btnOffset,g=c/2,e=
|
||||
parseFloat(b.list.parent().css("max-width")),c=f>c/2&&h>c/2?btnOffset+b.button.outerHeight()/2-g:f>h?scrollTop+screenHeight-c-30:scrollTop+30;d<e?e=(screenWidth-d)/2:(e=b.button.offset().left+b.button.outerWidth()/2-d/2,e<30?e=30:e+d>screenWidth&&(e=screenWidth-d-30));b.listbox.append(b.list).removeClass("ui-selectmenu-hidden").css({top:c,left:e}).addClass("in");b.list.find(a.mobile.activeBtnClass).focus();b.isOpen=true}}},_buildList:function(){var b=this,c=this.options,d=this.placeholder,f=[],e=
|
||||
[],h=b.isMultiple?"checkbox-off":"false";b.list.empty().filter(".ui-listview").listview("destroy");b.select.find("option").each(function(g){var j=a(this),i=j.parent(),m=j.getEncodedText(),p="<a href='#'>"+m+"</a>",k=[],n=[];i.is("optgroup")&&(i=i.attr("label"),a.inArray(i,f)===-1&&(e.push("<li data-"+a.mobile.ns+"role='list-divider'>"+i+"</li>"),f.push(i)));if(!this.getAttribute("value")||m.length==0||j.jqmData("placeholder"))c.hidePlaceholderMenuItems&&k.push("ui-selectmenu-placeholder"),d=b.placeholder=
|
||||
m;this.disabled&&(k.push("ui-disabled"),n.push("aria-disabled='true'"));e.push("<li data-"+a.mobile.ns+"option-index='"+g+"' data-"+a.mobile.ns+"icon='"+h+"' class='"+k.join(" ")+"' "+n.join(" ")+">"+p+"</li>")});b.list.html(e.join(" "));b.list.find("li").attr({role:"option",tabindex:"-1"}).first().attr("tabindex","0");this.isMultiple||this.headerClose.hide();!this.isMultiple&&!d.length?this.header.hide():this.headerTitle.text(this.placeholder);b.list.listview()},_button:function(){return a("<a>",
|
||||
{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})}})};a("select").live("selectmenubeforecreate",function(){var b=a(this).data("selectmenu");b.options.nativeMenu||e(b)})})(jQuery);
|
||||
(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:null,corners:true,shadow:true,iconshadow:true,menuPageTheme:"b",overlayTheme:"a",hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:true,initSelector:"select:not(:jqmData(role='slider'))"},_button:function(){return a("<div/>")},_setDisabled:function(a){this.element.attr("disabled",a);this.button.attr("aria-disabled",a);return this._setOption("disabled",a)},_focusButton:function(){var a=
|
||||
this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},_preExtension:function(){this.select=this.element.wrap("<div class='ui-select'>");this.selectID=this.select.attr("id");this.label=a("label[for='"+this.selectID+"']").addClass("ui-select");this.isMultiple=this.select[0].multiple;if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.select,"c")},_create:function(){this._preExtension();this._trigger("beforeCreate");this.button=
|
||||
this._button();var e=this,b=this.options,d=this.button.text(a(this.select[0].options.item(this.select[0].selectedIndex==-1?0:this.select[0].selectedIndex)).text()).insertBefore(this.select).buttonMarkup({theme:b.theme,icon:b.icon,iconpos:b.iconpos,inline:b.inline,corners:b.corners,shadow:b.shadow,iconshadow:b.iconshadow});b.nativeMenu&&window.opera&&window.opera.version&&this.select.addClass("ui-select-nativeonly");if(this.isMultiple)this.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(d.addClass("ui-li-has-count"));
|
||||
(b.disabled||this.element.attr("disabled"))&&this.disable();this.select.change(function(){e.refresh()});this.build()},build:function(){var e=this;this.select.appendTo(e.button).bind("vmousedown",function(){e.button.addClass(a.mobile.activeBtnClass)}).bind("focus vmouseover",function(){e.button.trigger("vmouseover")}).bind("vmousemove",function(){e.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){e.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}).bind("change blur",
|
||||
function(){e.button.removeClass("ui-btn-down-"+e.options.theme)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var e=this,b=this.selected();this.button.find(".ui-btn-text").text(function(){return!e.isMultiple?b.text():b.length?b.map(function(){return a(this).text()}).get().join(", "):e.placeholder})},setButtonCount:function(){var a=
|
||||
this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},refresh:function(){this.setButtonText();this.setButtonCount()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(true);this.button.addClass("ui-disabled")},enable:function(){this._setDisabled(false);this.button.removeClass("ui-disabled")}});a(document).bind("pagecreate create",function(e){a.mobile.selectmenu.prototype.enhanceWithin(e.target)})})(jQuery);
|
||||
(function(a,e){function b(b){for(var c;b;){if((c=typeof b.className==="string"&&b.className.split(" "))&&a.inArray("ui-btn",c)>-1&&a.inArray("ui-disabled",c)<0)break;b=b.parentNode}return b}a.fn.buttonMarkup=function(b){for(var b=b||{},c=0;c<this.length;c++){var h=this.eq(c),g=h[0],i=a.extend({},a.fn.buttonMarkup.defaults,{icon:b.icon!==e?b.icon:h.jqmData("icon"),iconpos:b.iconpos!==e?b.iconpos:h.jqmData("iconpos"),theme:b.theme!==e?b.theme:h.jqmData("theme"),inline:b.inline!==e?b.inline:h.jqmData("inline"),
|
||||
shadow:b.shadow!==e?b.shadow:h.jqmData("shadow"),corners:b.corners!==e?b.corners:h.jqmData("corners"),iconshadow:b.iconshadow!==e?b.iconshadow:h.jqmData("iconshadow")},b),k="ui-btn-inner",l,o,m=document.createElement(i.wrapperEls),p=document.createElement(i.wrapperEls),j=i.icon?document.createElement("span"):null;d&&d();if(!i.theme)i.theme=a.mobile.getInheritedTheme(h,"c");l="ui-btn ui-btn-up-"+i.theme;i.inline&&(l+=" ui-btn-inline");if(i.icon)i.icon="ui-icon-"+i.icon,i.iconpos=i.iconpos||"left",
|
||||
o="ui-icon "+i.icon,i.iconshadow&&(o+=" ui-icon-shadow");i.iconpos&&(l+=" ui-btn-icon-"+i.iconpos,i.iconpos=="notext"&&!h.attr("title")&&h.attr("title",h.getEncodedText()));i.corners&&(l+=" ui-btn-corner-all",k+=" ui-btn-corner-all");i.shadow&&(l+=" ui-shadow");g.setAttribute("data-"+a.mobile.ns+"theme",i.theme);h.addClass(l);m.className=k;m.setAttribute("aria-hidden","true");p.className="ui-btn-text";m.appendChild(p);if(j)j.className=o,m.appendChild(j);for(;g.firstChild;)p.appendChild(g.firstChild);
|
||||
g.appendChild(m);a.data(g,"textWrapper",a(p))}return this};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,inline:false,wrapperEls:"span"};var d=function(){a(document).bind({vmousedown:function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-up-"+c).addClass("ui-btn-down-"+c))},"vmousecancel vmouseup":function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-down-"+c).addClass("ui-btn-up-"+
|
||||
c))},"vmouseover focus":function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-up-"+c).addClass("ui-btn-hover-"+c))},"vmouseout blur":function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-hover-"+c+" ui-btn-down-"+c).addClass("ui-btn-up-"+c))}});d=null};a(document).bind("pagecreate create",function(b){a(":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a",
|
||||
b.target).not(".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").buttonMarkup()})})(jQuery);
|
||||
(function(a){a.fn.controlgroup=function(e){return this.each(function(){function b(a){a.removeClass("ui-btn-corner-all ui-shadow").eq(0).addClass(h[0]).end().last().addClass(h[1]).addClass("ui-controlgroup-last")}var d=a(this),f=a.extend({direction:d.jqmData("type")||"vertical",shadow:false,excludeInvisible:true},e),c=d.children("legend"),h=f.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];d.find("input").first().attr("type");c.length&&(d.wrapInner("<div class='ui-controlgroup-controls'></div>"),
|
||||
a("<div role='heading' class='ui-controlgroup-label'>"+c.html()+"</div>").insertBefore(d.children(0)),c.remove());d.addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+f.direction);b(d.find(".ui-btn"+(f.excludeInvisible?":visible":"")));b(d.find(".ui-btn-inner"));f.shadow&&d.addClass("ui-shadow")})};a(document).bind("pagecreate create",function(e){a(":jqmData(role='controlgroup')",e.target).controlgroup({excludeInvisible:false})})})(jQuery);
|
||||
(function(a){a(document).bind("pagecreate create",function(e){a(e.target).find("a").not(".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")})})(jQuery);
|
||||
(function(a,e){a.fn.fixHeaderFooter=function(){return!a.support.scrollTop||a.support.touchOverflow&&a.mobile.touchOverflowEnabled?this:this.each(function(){var b=a(this);b.jqmData("fullscreen")&&b.addClass("ui-page-fullscreen");b.find(".ui-header:jqmData(position='fixed')").addClass("ui-header-fixed ui-fixed-inline fade");b.find(".ui-footer:jqmData(position='fixed')").addClass("ui-footer-fixed ui-fixed-inline fade")})};a.mobile.fixedToolbars=function(){function b(){!i&&g==="overlay"&&(h||a.mobile.fixedToolbars.hide(true),
|
||||
a.mobile.fixedToolbars.startShowTimer())}function d(a){var b=0,c,d;if(a){d=document.body;c=a.offsetParent;for(b=a.offsetTop;a&&a!=d;){b+=a.scrollTop||0;if(a==c)b+=c.offsetTop,c=a.offsetParent;a=a.parentNode}}return b}function f(b){var c=a(window).scrollTop(),e=d(b[0]),f=b.css("top")=="auto"?0:parseFloat(b.css("top")),h=window.innerHeight,g=b.outerHeight(),i=b.parents(".ui-page:not(.ui-page-fullscreen)").length;return b.is(".ui-header-fixed")?(f=c-e+f,f<e&&(f=0),b.css("top",i?f:c)):b.css("top",i?c+
|
||||
h-g-(e-f):c+h-g)}if(a.support.scrollTop&&(!a.support.touchOverflow||!a.mobile.touchOverflowEnabled)){var c,h,g="inline",i=false,k=null,l=false,o=true;a(function(){var c=a(document),d=a(window);c.bind("vmousedown",function(){o&&(k=g)}).bind("vclick",function(b){o&&!a(b.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length&&!l&&(a.mobile.fixedToolbars.toggle(k),k=null)}).bind("silentscroll",b);(c.scrollTop()===0?d:c).bind("scrollstart",function(){l=true;k===
|
||||
null&&(k=g);var b=k=="overlay";if(i=b||!!h)a.mobile.fixedToolbars.clearShowTimer(),b&&a.mobile.fixedToolbars.hide(true)}).bind("scrollstop",function(b){a(b.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length||(l=false,i&&(a.mobile.fixedToolbars.startShowTimer(),i=false),k=null)});d.bind("resize updatelayout",b)});a(".ui-page").live("pagebeforeshow",function(b,d){var e=a(b.target).find(":jqmData(role='footer')"),h=e.data("id"),g=d.prevPage,g=g&&g.find(":jqmData(role='footer')"),
|
||||
g=g.length&&g.jqmData("id")===h;h&&g&&(c=e,f(c.removeClass("fade in out").appendTo(a.mobile.pageContainer)))}).live("pageshow",function(){var b=a(this);c&&c.length&&setTimeout(function(){f(c.appendTo(b).addClass("fade"));c=null},500);a.mobile.fixedToolbars.show(true,this)});a(".ui-collapsible-contain").live("collapse expand",b);return{show:function(b,c){a.mobile.fixedToolbars.clearShowTimer();g="overlay";return(c?a(c):a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var c=
|
||||
a(this),e=a(window).scrollTop(),h=d(c[0]),g=window.innerHeight,i=c.outerHeight(),e=c.is(".ui-header-fixed")&&e<=h+i||c.is(".ui-footer-fixed")&&h<=e+g;c.addClass("ui-fixed-overlay").removeClass("ui-fixed-inline");!e&&!b&&c.animationComplete(function(){c.removeClass("in")}).addClass("in");f(c)})},hide:function(b){g="inline";return(a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var c=a(this),
|
||||
d=c.css("top"),d=d=="auto"?0:parseFloat(d);c.addClass("ui-fixed-inline").removeClass("ui-fixed-overlay");if(d<0||c.is(".ui-header-fixed")&&d!==0)b?c.css("top",0):c.css("top")!=="auto"&&parseFloat(c.css("top"))!==0&&c.animationComplete(function(){c.removeClass("out reverse").css("top",0)}).addClass("out reverse")})},startShowTimer:function(){a.mobile.fixedToolbars.clearShowTimer();var b=[].slice.call(arguments);h=setTimeout(function(){h=e;a.mobile.fixedToolbars.show.apply(null,b)},100)},clearShowTimer:function(){h&&
|
||||
clearTimeout(h);h=e},toggle:function(b){b&&(g=b);return g==="overlay"?a.mobile.fixedToolbars.hide():a.mobile.fixedToolbars.show()},setTouchToggleEnabled:function(a){o=a}}}}();a(document).bind("pagecreate create",function(b){a(":jqmData(position='fixed')",b.target).length&&a(b.target).each(function(){if(!a.support.scrollTop||a.support.touchOverflow&&a.mobile.touchOverflowEnabled)return this;var b=a(this);b.jqmData("fullscreen")&&b.addClass("ui-page-fullscreen");b.find(".ui-header:jqmData(position='fixed')").addClass("ui-header-fixed ui-fixed-inline fade");
|
||||
b.find(".ui-footer:jqmData(position='fixed')").addClass("ui-footer-fixed ui-fixed-inline fade")})})})(jQuery);
|
||||
(function(a){a.mobile.touchOverflowEnabled=false;a.mobile.touchOverflowZoomEnabled=false;a(document).bind("pagecreate",function(e){a.support.touchOverflow&&a.mobile.touchOverflowEnabled&&(e=a(e.target),e.is(":jqmData(role='page')")&&e.each(function(){var b=a(this),d=b.find(":jqmData(role='header'), :jqmData(role='footer')").filter(":jqmData(position='fixed')"),e=b.jqmData("fullscreen"),c=d.length?b.find(".ui-content"):b;b.addClass("ui-mobile-touch-overflow");c.bind("scrollstop",function(){c.scrollTop()>
|
||||
0&&window.scrollTo(0,a.mobile.defaultHomeScroll)});d.length&&(b.addClass("ui-native-fixed"),e&&(b.addClass("ui-native-fullscreen"),d.addClass("fade in"),a(document).bind("vclick",function(){d.removeClass("ui-native-bars-hidden").toggleClass("in out").animationComplete(function(){a(this).not(".in").addClass("ui-native-bars-hidden")})})))}))})})(jQuery);
|
||||
(function(a,e){function b(){var b=a("meta[name='viewport']");b.length?b.attr("content",b.attr("content")+", user-scalable=no"):a("head").prepend("<meta>",{name:"viewport",content:"user-scalable=no"})}var d=a("html");a("head");var f=a(e);a(e.document).trigger("mobileinit");if(a.mobile.gradeA()){if(a.mobile.ajaxBlacklist)a.mobile.ajaxEnabled=false;d.addClass("ui-mobile ui-mobile-rendering");var c=a("<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1></h1></div>");
|
||||
a.extend(a.mobile,{showPageLoadingMsg:function(){if(a.mobile.loadingMessage){var b=a("."+a.mobile.activeBtnClass).first();c.find("h1").text(a.mobile.loadingMessage).end().appendTo(a.mobile.pageContainer).css({top:a.support.scrollTop&&f.scrollTop()+f.height()/2||b.length&&b.offset().top||100})}d.addClass("ui-loading")},hidePageLoadingMsg:function(){d.removeClass("ui-loading")},initializePage:function(){var b=a(":jqmData(role='page')");b.length||(b=a("body").wrapInner("<div data-"+a.mobile.ns+"role='page'></div>").children(0));
|
||||
b.add(":jqmData(role='dialog')").each(function(){var b=a(this);b.jqmData("url")||b.attr("data-"+a.mobile.ns+"url",b.attr("id")||location.pathname+location.search)});a.mobile.firstPage=b.first();a.mobile.pageContainer=b.first().parent().addClass("ui-mobile-viewport");f.trigger("pagecontainercreate");a.mobile.showPageLoadingMsg();!a.mobile.hashListeningEnabled||!a.mobile.path.stripHash(location.hash)?a.mobile.changePage(a.mobile.firstPage,{transition:"none",reverse:true,changeHash:false,fromHashChange:true}):
|
||||
f.trigger("hashchange",[true])}});a.support.touchOverflow&&a.mobile.touchOverflowEnabled&&!a.mobile.touchOverflowZoomEnabled&&b();a.mobile._registerInternalEvents();a(function(){e.scrollTo(0,1);a.mobile.defaultHomeScroll=!a.support.scrollTop||a(e).scrollTop()===1?0:1;a.mobile.autoInitializePage&&a.mobile.initializePage();f.load(a.mobile.silentScroll)})}})(jQuery,this);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -27,7 +27,7 @@
|
||||
{{pass}}
|
||||
{{else:}}
|
||||
{{qry=''}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
<tr>
|
||||
<th style="font-size: 1.75em;">
|
||||
@@ -37,13 +37,19 @@
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
|
||||
</td>
|
||||
</tr>
|
||||
{{pass}}
|
||||
</table>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
</table>
|
||||
|
||||
{{elif request.function=='select':}}
|
||||
<h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
|
||||
</h2>
|
||||
{{if tb:}}
|
||||
<h3>{{=T('Traceback')}}</h3>
|
||||
<pre>
|
||||
{{=tb}}
|
||||
</pre>
|
||||
{{pass}}
|
||||
{{if table:}}
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
|
||||
<h3>{{=T("Rows in Table")}}</h3><br/>
|
||||
@@ -149,7 +155,7 @@
|
||||
<h4>{{=T("RAM")}}</h4>
|
||||
<p>{{=T.M("Number of entries: **%s**", ram['entries'])}}</p>
|
||||
{{if ram['entries'] > 0:}}
|
||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||
dict( ratio=ram['ratio'], hits=ram['hits'], misses=ram['misses']))}}
|
||||
</p>
|
||||
<p>
|
||||
|
||||
@@ -43,8 +43,7 @@ def file_create_form(location):
|
||||
form=FORM(T("create file with filename:")," ",
|
||||
INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY),
|
||||
INPUT(_type="hidden",_name="location",_value=location),
|
||||
INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)),
|
||||
INPUT(_type="submit",_value=T("Create")),_action=URL('create_file'))
|
||||
INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)))
|
||||
return form
|
||||
def upload_plugin_form(app):
|
||||
form=FORM(T("upload plugin file:")," ",
|
||||
@@ -207,7 +206,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
||||
{{pass}}
|
||||
</ul>
|
||||
<div data-role="fieldcontain">{{=file_create_form('%s/static/' % app)}}
|
||||
{{=file_upload_form('%s/static/' % app)}}</div>
|
||||
{{#=file_upload_form('%s/static/' % app)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -225,7 +224,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
||||
</span>
|
||||
{{pass}}
|
||||
<div data-role="fieldcontain">{{=file_create_form('%s/modules/' % app)}}
|
||||
{{=file_upload_form('%s/modules/' % app)}}</div>
|
||||
{{#=file_upload_form('%s/modules/' % app)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
<div><input type="hidden" name="send" value="{{=send}}"/></div>
|
||||
<table>
|
||||
<tr><td>{{=T('Administrator Password:')}}</td><td><input type="password" name="password" id="password"/></td></tr>
|
||||
<tr><td></td><td><input type="submit" name="login" value="{{=T('Login')}}"/></td></tr>
|
||||
<tr><td></td><td><button name="login">{{=T('Login')}}</button></td></tr>
|
||||
</table>
|
||||
<input type="hidden" name="is_mobile" value="true"/>
|
||||
</form>
|
||||
</div>
|
||||
{{else:}}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
Remove this if you use the .htaccess -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
<title>web2py mobile admin</title>
|
||||
<title>Web2py Mobile Admin</title>
|
||||
|
||||
<!-- http://dev.w3.org/html5/markup/meta.name.html -->
|
||||
<meta name="application-name" content="{{=request.application}}" />
|
||||
@@ -24,16 +24,17 @@
|
||||
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
|
||||
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
|
||||
-->
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references -->
|
||||
<link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}">
|
||||
|
||||
<!-- For the less-enabled mobile browsers like Opera Mini -->
|
||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.css')}}">
|
||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.css')}}">
|
||||
|
||||
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
|
||||
|
||||
{{include 'web2py_ajax.html'}}
|
||||
|
||||
@@ -60,7 +61,7 @@ $(document).bind("mobileinit", function(){
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.js')}}"></script>
|
||||
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.js')}}"></script>
|
||||
|
||||
<style>
|
||||
.error { color: red; }
|
||||
@@ -75,7 +76,7 @@ $(document).bind("mobileinit", function(){
|
||||
<!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]-->
|
||||
|
||||
<div data-role="page">
|
||||
<div data-role="header" data-fullscreen="true" data-position="fixed">
|
||||
<div data-role="header">
|
||||
<h1>web2py mobile admin/{{block sectionclass}}{{end}}</h1>
|
||||
{{block header}}
|
||||
{{if 'auth' in globals():}}
|
||||
@@ -85,7 +86,7 @@ $(document).bind("mobileinit", function(){
|
||||
<a rel="external" href="{{=URL('default','user',args='logout')}}" data-icon="logout" class="ui-btn-left">Logout</a>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
<a rel="external" href="{{=URL('default','index')}}" data-icon="home" class="ui-btn-right">{{=T("Home")}}</a>
|
||||
<a rel="external" href="{{=URL('default','index',vars=dict(is_mobile='true'))}}" data-icon="home" class="ui-btn-right">{{=T("Home")}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div data-role="content">
|
||||
@@ -100,7 +101,7 @@ $(document).bind("mobileinit", function(){
|
||||
{{pass}}
|
||||
{{include}}
|
||||
</div>
|
||||
<div data-role="footer" data-fullscreen="true" data-position="fixed" style="padding: 5px; text-align: center">
|
||||
<div data-role="footer">
|
||||
{{block footer}}
|
||||
powered by web2py - @{{=request.now.year}}
|
||||
{{end}}
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
|
||||
<h2>{{=T("Installed applications")}}</h2>
|
||||
|
||||
<ul data-role="listview">
|
||||
<ul data-role="listview" data-inset="true">
|
||||
{{for a in apps:}}
|
||||
<li>
|
||||
{{if a==request.application:}}
|
||||
<h3>{{=a}} ({{=T('currently running')}})</h3>
|
||||
{{else:}}
|
||||
<h3>{{=a}}</h3>
|
||||
<h3>{{=T("Application")}} {{=a}}</h3>
|
||||
{{if MULTI_USER_MODE and db.app(name=a):}}<p>created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}</p>{{pass}}
|
||||
{{pass}}
|
||||
<ul>
|
||||
<ul data-role="listview" data-inset="true">
|
||||
{{if a!=request.application:}}
|
||||
{{=LI(A(T('Goto'),_rel="external",_href=URL(a,'default','index')))}}
|
||||
{{if not os.path.exists('applications/%s/compiled' % a):}}
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
<script src="{{=cm}}/mode/css/css.js"></script>
|
||||
<script src="{{=cm}}/mode/javascript/javascript.js"></script>
|
||||
<script src="{{=cm}}/mode/htmlmixed/htmlmixed.js"></script>
|
||||
<script src="{{=cm}}/lib/util/search.js"></script>
|
||||
<script src="{{=cm}}/lib/util/searchcursor.js"></script>
|
||||
<script src="{{=cm}}/lib/util/dialog.js"></script>
|
||||
<link rel="stylesheet" href="{{=cm}}/lib/util/dialog.css">
|
||||
<script src="{{=cm}}/emmet.min.js"></script>
|
||||
<script language="Javascript" type="text/javascript" src="{{=URL('static','js/ajax_editor.js')}}"></script>
|
||||
{{elif TEXT_EDITOR == 'ace':}}
|
||||
@@ -246,6 +250,11 @@ window.onload = function() {
|
||||
<ul>
|
||||
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
|
||||
{{=shortcut('Ctrl+F11', T('Toggle 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>
|
||||
{{elif TEXT_EDITOR == 'codemirror':}}
|
||||
@@ -253,6 +262,11 @@ window.onload = function() {
|
||||
<ul>
|
||||
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
|
||||
{{=shortcut('Ctrl+F11', T('Toggle 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'))}}
|
||||
</ul>
|
||||
{{else:}}
|
||||
<h3>{{=T("Key bindings")}}</h3>
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
<p>
|
||||
{{=T("Running on %s", request.env.server_software)}}
|
||||
</p>
|
||||
{{if session.is_mobile=='auto':}}<p>
|
||||
{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
|
||||
{{pass}}
|
||||
</div>
|
||||
{{pass}}
|
||||
<!-- MULTI_USER_INTERFACE -->
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="{{=URL('index')}}"></iframe>
|
||||
<iframe src="{{=URL('default','index',vars=dict(is_mobile='true'))}}"></iframe>
|
||||
<div id="about">
|
||||
<h1><a href="http://web2py.com">web2py</a> plugin</h1>
|
||||
<h2>for <a href="http://jquerymobile.com/">jQuery Mobile</a></h2>
|
||||
|
||||
@@ -24,17 +24,17 @@
|
||||
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
|
||||
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
|
||||
-->
|
||||
<!--meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;"-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references -->
|
||||
<link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}">
|
||||
|
||||
<!-- For the less-enabled mobile browsers like Opera Mini -->
|
||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.css')}}">
|
||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.css')}}">
|
||||
|
||||
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
|
||||
|
||||
{{include 'web2py_ajax.html'}}
|
||||
|
||||
@@ -61,7 +61,7 @@ $(document).bind("mobileinit", function(){
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.js')}}"></script>
|
||||
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.js')}}"></script>
|
||||
|
||||
<style>
|
||||
.error { color: red; }
|
||||
@@ -76,7 +76,7 @@ $(document).bind("mobileinit", function(){
|
||||
<!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]-->
|
||||
|
||||
<div data-role="page">
|
||||
<div data-role="header" data-fullscreen="true" data-position="fixed">
|
||||
<div data-role="header">
|
||||
<h1>{{=response.title}}</h1>
|
||||
{{block header}}
|
||||
{{if 'auth' in globals():}}
|
||||
@@ -101,7 +101,7 @@ $(document).bind("mobileinit", function(){
|
||||
{{pass}}
|
||||
{{include}}
|
||||
</div>
|
||||
<div data-role="footer" data-fullscreen="true" data-position="fixed" style="padding: 5px; text-align: center">
|
||||
<div data-role="footer">
|
||||
{{block footer}}
|
||||
powered by web2py - @{{=request.now.year}}
|
||||
{{end}}
|
||||
|
||||
@@ -113,7 +113,6 @@ def query_by_table_type(tablename,db,request=request):
|
||||
# ## list all databases and tables
|
||||
# ###########################################################
|
||||
|
||||
|
||||
def index():
|
||||
return dict(databases=databases)
|
||||
|
||||
@@ -207,6 +206,7 @@ def select():
|
||||
if match:
|
||||
table = match.group('table')
|
||||
try:
|
||||
tb = None
|
||||
nrows = db(query).count()
|
||||
if form.vars.update_check and form.vars.update_fields:
|
||||
db(query).update(**eval_in_global_env('dict(%s)'
|
||||
@@ -221,6 +221,8 @@ def select():
|
||||
else:
|
||||
rows = db(query,ignore_common_filters=True).select(limitby=(start, stop))
|
||||
except Exception, e:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
(rows, nrows) = ([], 0)
|
||||
response.flash = DIV(T('Invalid Query'),PRE(str(e)))
|
||||
# begin handle upload csv
|
||||
@@ -250,6 +252,7 @@ def select():
|
||||
rows=rows,
|
||||
query=request.vars.query,
|
||||
formcsv = formcsv,
|
||||
tb = tb,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ def hello6():
|
||||
|
||||
def status():
|
||||
""" page that shows internal status"""
|
||||
response.view = 'generic.html'
|
||||
return dict(toolbar=response.toolbar())
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ h3 {font-size:2.00em}
|
||||
h4 {font-size:1.50em}
|
||||
h5 {font-size:1.25em}
|
||||
h6 {font-size:1.12em}
|
||||
th,label {font-weight:bold; white-space:nowrap}
|
||||
th,label {font-weight:bold; white-space:nowrap;}
|
||||
td,th {text-align:left; padding:2px 5px 2px 5px}
|
||||
th {vertical-align:middle; border-right:1px solid white}
|
||||
td {vertical-align:top}
|
||||
@@ -69,7 +69,6 @@ fieldset {padding:16px; border-top:1px #DEDEDE solid}
|
||||
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
|
||||
|
||||
/* fix ie problem with menu */
|
||||
.ie-lte7 .topbar .container {z-index:2}
|
||||
|
||||
td.w2p_fw {padding-bottom:1px}
|
||||
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
||||
@@ -187,10 +186,7 @@ div.error {
|
||||
.web2py_paginator {}
|
||||
.web2py_grid {width:100%}
|
||||
.web2py_grid table {width:100%}
|
||||
.web2py_grid tbody td {
|
||||
padding:2px 5px 2px 5px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
|
||||
|
||||
.web2py_grid thead th,.web2py_grid tfoot td {
|
||||
background-color:#EAEAEA;
|
||||
@@ -299,10 +295,16 @@ li.w2p_grid_breadcrumb_elem {
|
||||
.web2py_console input, .web2py_console select,
|
||||
.web2py_console a { margin: 2px; }
|
||||
|
||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||
|
||||
#wiki_page_body {
|
||||
width: 600px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* fix some IE problems */
|
||||
|
||||
.ie-lte7 .topbar .container {z-index:2}
|
||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||
|
||||
@@ -39,7 +39,7 @@ function web2py_ajax_init(target) {
|
||||
|
||||
function web2py_event_handlers() {
|
||||
var doc = jQuery(document)
|
||||
doc.on('click', '.flash', function(e){jQuery(this).fadeOut('slow'); e.preventDefault();});
|
||||
doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();});
|
||||
doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();});
|
||||
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
|
||||
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
|
||||
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
|
||||
jQuery(function() {
|
||||
var flash = jQuery('.flash');
|
||||
flash.hide();
|
||||
if(flash.html()) flash.slideDown();
|
||||
if(flash.html()) flash.append('<span style="float:right;">×</span>').slideDown();
|
||||
web2py_ajax_init(document);
|
||||
web2py_event_handlers();
|
||||
});
|
||||
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
|
||||
form.submit(function(e){
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('post',action,form.serialize(),target);
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function web2py_trap_link(target) {
|
||||
jQuery('#'+target+' a.w2p_trap').each(function(i){
|
||||
var link=jQuery(this);
|
||||
link.click(function(e) {
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('get',link.attr('href'),[],target);
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
var link=jQuery(this);
|
||||
link.click(function(e) {
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('get',link.attr('href'),[],target);
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function web2py_ajax_page(method, action, data, target) {
|
||||
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
web2py_trap_link(target);
|
||||
web2py_ajax_init('#'+target);
|
||||
if(command)
|
||||
eval(decodeURIComponent(command));
|
||||
eval(decodeURIComponent(command));
|
||||
if(flash)
|
||||
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
|
||||
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
|
||||
}
|
||||
} else {
|
||||
// run once (no timeout specified)
|
||||
element.reload_counter = Infinity;
|
||||
element.reload_counter = Infinity;
|
||||
web2py_ajax_page('get', action, null, target);
|
||||
} }); }
|
||||
|
||||
function web2py_comet(url,onmessage,onopen,onclose) {
|
||||
function web2py_websocket(url,onmessage,onopen,onclose) {
|
||||
if ("WebSocket" in window) {
|
||||
var ws = new WebSocket(url);
|
||||
ws.onopen = onopen?onopen:(function(){});
|
||||
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
|
||||
} else return false; // not supported
|
||||
}
|
||||
|
||||
|
||||
function web2py_calc_entropy(mystring) {
|
||||
//calculate a simple entropy for a given string
|
||||
var csets = new Array(
|
||||
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
|
||||
'0123456789abcdefghijklmnopqrstuvwxyz');
|
||||
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
|
||||
for (var i=0;i<mystringlist.length;i++) { // classify this character
|
||||
var c = mystringlist[i], inset=5;
|
||||
for(var j = 0; j<csets.length; j++)
|
||||
if (csets[j].indexOf(c) != -1) {inset = j; break;}
|
||||
//calculate effect of character on alphabet size
|
||||
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
|
||||
else if (!(c in other)) {score += 1;other[c] = 1;}
|
||||
if (inset != lastset) {score += 1;lastset = inset;}
|
||||
}
|
||||
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
|
||||
return Math.round(entropy*100)/100
|
||||
}
|
||||
|
||||
function web2py_validate_entropy(myfield, req_entropy) {
|
||||
var validator = function () {
|
||||
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
|
||||
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
|
||||
if(v<=0.5) {r=1.0; g=2.0*v;}
|
||||
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
|
||||
var color = '#'+rs(r)+rs(g)+rs(b);
|
||||
myfield.css('background-color',color);
|
||||
entropy_callback = myfield.data('entropy_callback');
|
||||
if(entropy_callback) entropy_callback(v);
|
||||
}
|
||||
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{{pass}}
|
||||
{{else:}}
|
||||
{{qry=''}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
<tr>
|
||||
<th style="font-size: 1.75em;">
|
||||
@@ -37,13 +37,19 @@
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
|
||||
</td>
|
||||
</tr>
|
||||
{{pass}}
|
||||
</table>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
</table>
|
||||
|
||||
{{elif request.function=='select':}}
|
||||
<h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
|
||||
</h2>
|
||||
{{if tb:}}
|
||||
<h3>{{=T('Traceback')}}</h3>
|
||||
<pre>
|
||||
{{=tb}}
|
||||
</pre>
|
||||
{{pass}}
|
||||
{{if table:}}
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
|
||||
<h3>{{=T("Rows in Table")}}</h3><br/>
|
||||
@@ -149,7 +155,7 @@
|
||||
<h4>{{=T("RAM")}}</h4>
|
||||
<p>{{=T.M("Number of entries: **%s**", ram['entries'])}}</p>
|
||||
{{if ram['entries'] > 0:}}
|
||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||
dict( ratio=ram['ratio'], hits=ram['hits'], misses=ram['misses']))}}
|
||||
</p>
|
||||
<p>
|
||||
|
||||
@@ -80,8 +80,7 @@ Current version: {{="%s.%s.%s (%s) %s" % request.env.web2py_version}}</p>
|
||||
<br/>
|
||||
<a class="button" href="{{=URL('download')}}" style="width:90%">DOWNLOAD NOW</a><br/>
|
||||
<a class="button" href="http://web2py.com/demo_admin" style="width:90%">ONLINE DEMO</a><br/>
|
||||
<a class="button" href="http://web2py.com/poweredby" style="width:90%">SITES POWERED BY WEB2PY</a>
|
||||
<br/>
|
||||
<object width="234" height="60"><param name="movie" value="http://widget.chipin.com/widget/id/dcd384d58839aa4d"></param><param name="allowScriptAccess" value="always"></param><param name="wmode" value="transparent"></param><param name="color_scheme" value="gray"></param><embed src="http://widget.chipin.com/widget/id/dcd384d58839aa4d" flashVars="color_scheme=gray" type="application/x-shockwave-flash" allowScriptAccess="always" wmode="transparent" width="234" height="60"></embed></object>
|
||||
<a class="button" href="http://web2py.com/poweredby" style="width:90%">SITES POWERED BY WEB2PY</a><br/>
|
||||
<a class="button" href="http://www.chipin.com/contribute/id/dcd384d58839aa4d" style="width:90%">SUPPORT/DONATE</a></br>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
</li><li>Ian Reinhart Geiser (html helpers)
|
||||
</li><li>Ionel Anton (Romanian translation)
|
||||
</li><li>Jan Beilicke (markmin)
|
||||
</li><li>Jeremy Dillworth
|
||||
</li><li>Jonathan Benn (is_url validator and tests)
|
||||
</li><li>Jonathan Lundell (multiple contributions)
|
||||
</li><li>Josh Goldfoot (xaml/html sanitizer)
|
||||
@@ -156,6 +157,7 @@
|
||||
<li><a href="http://www.danga.com/memcached/">memcache</a> developed by Evan Martin</li>
|
||||
<li><a href="http://jquery.com/">jQuery</a> developed by John Resig</li>
|
||||
<li>A syntax highlighter inspired by the code of <a href="http://www.petersblog.org/node/763">Peter Wilkinson</a></li>
|
||||
<li><a href="https://github.com/jtauber/pyuca">pyUCA</a> developed by <a href="http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/">James Tauber</a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ def query_by_table_type(tablename,db,request=request):
|
||||
# ## list all databases and tables
|
||||
# ###########################################################
|
||||
|
||||
|
||||
def index():
|
||||
return dict(databases=databases)
|
||||
|
||||
@@ -207,6 +206,7 @@ def select():
|
||||
if match:
|
||||
table = match.group('table')
|
||||
try:
|
||||
tb = None
|
||||
nrows = db(query).count()
|
||||
if form.vars.update_check and form.vars.update_fields:
|
||||
db(query).update(**eval_in_global_env('dict(%s)'
|
||||
@@ -221,6 +221,8 @@ def select():
|
||||
else:
|
||||
rows = db(query,ignore_common_filters=True).select(limitby=(start, stop))
|
||||
except Exception, e:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
(rows, nrows) = ([], 0)
|
||||
response.flash = DIV(T('Invalid Query'),PRE(str(e)))
|
||||
# begin handle upload csv
|
||||
@@ -250,6 +252,7 @@ def select():
|
||||
rows=rows,
|
||||
query=request.vars.query,
|
||||
formcsv = formcsv,
|
||||
tb = tb,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
'book': ['books'],
|
||||
'is': ['are'],
|
||||
'man': ['men'],
|
||||
'miss': ['misses'],
|
||||
'person': ['people'],
|
||||
'quark': ['quarks'],
|
||||
'shop': ['shops'],
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
if not request.env.web2py_runtime_gae:
|
||||
## if NOT running on Google App Engine use SQLite or other DB
|
||||
db = DAL('sqlite://storage.sqlite')
|
||||
db = DAL('sqlite://storage.sqlite')
|
||||
else:
|
||||
## connect to Google BigTable (optional 'google:datastore://namespace')
|
||||
db = DAL('google:datastore')
|
||||
|
||||
@@ -33,9 +33,9 @@ def _():
|
||||
# shortcuts
|
||||
app = request.application
|
||||
ctr = request.controller
|
||||
# useful links to internal and external resources
|
||||
# useful links to internal and external resources
|
||||
response.menu+=[
|
||||
(SPAN('web2py',_style='color:yellow'),False, 'http://web2py.com', [
|
||||
(SPAN('web2py',_class='highlighted'),False, 'http://web2py.com', [
|
||||
(T('My Sites'),False,URL('admin','default','site')),
|
||||
(T('This App'),False,URL('admin','default','design/%s' % app), [
|
||||
(T('Controller'),False,
|
||||
|
||||
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
@@ -61,7 +61,7 @@ input[type=text],input[type=password],select{width:300px; margin-right:5px}
|
||||
border-top:1px #DEDEDE solid;
|
||||
}
|
||||
.header {
|
||||
// background:<fill here for header image>;
|
||||
/* background:<fill here for header image>; */
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ fieldset {padding:16px; border-top:1px #DEDEDE solid}
|
||||
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
|
||||
|
||||
/* fix ie problem with menu */
|
||||
.ie-lte7 .topbar .container {z-index:2}
|
||||
|
||||
td.w2p_fw {padding-bottom:1px}
|
||||
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
|
||||
@@ -183,7 +182,7 @@ div.error {
|
||||
* will look better with the declarations below
|
||||
* if needed to remove base.css consider keeping these following lines in some css file.
|
||||
*/
|
||||
// .web2py_table {border:1px solid #ccc}
|
||||
/* .web2py_table {border:1px solid #ccc} */
|
||||
.web2py_paginator {}
|
||||
.web2py_grid {width:100%}
|
||||
.web2py_grid table {width:100%}
|
||||
@@ -296,10 +295,16 @@ li.w2p_grid_breadcrumb_elem {
|
||||
.web2py_console input, .web2py_console select,
|
||||
.web2py_console a { margin: 2px; }
|
||||
|
||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||
|
||||
#wiki_page_body {
|
||||
width: 600px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* fix some IE problems */
|
||||
|
||||
.ie-lte7 .topbar .container {z-index:2}
|
||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#navbar .auth_navbar, #navbar .auth_navbar a {color:inherit;}
|
||||
/*=============================================================
|
||||
CUSTOM RULES
|
||||
==============================================================*/
|
||||
|
||||
div.flash.flash-center {
|
||||
left: 25%;
|
||||
right: 25%;
|
||||
}
|
||||
|
||||
div.flash.flash-top, div.flash.flash-top:hover {
|
||||
position: relative;
|
||||
display: block;
|
||||
body{height:auto;} /* to avoid vertical scroll bar */
|
||||
div.flash.flash-center{left:25%;right:25%;}
|
||||
div.flash.flash-top,div.flash.flash-top:hover{
|
||||
position:relative;
|
||||
display:block;
|
||||
margin:0;
|
||||
padding:1em;
|
||||
top:0;
|
||||
left:0;
|
||||
width:100%;
|
||||
text-align:center;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
color:#865100;
|
||||
background:#feea9a;
|
||||
border:1px solid;
|
||||
@@ -24,154 +23,209 @@ div.flash.flash-top, div.flash.flash-top:hover {
|
||||
border-radius:0;
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
/* bootstrap dropdown */
|
||||
|
||||
.dropdown-menu ul {
|
||||
left: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.dropdown-menu li:hover ul {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#header {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
#header{margin-top:60px;}
|
||||
.mastheader h1 {
|
||||
margin-bottom: 9px;
|
||||
font-size: 81px;
|
||||
font-weight: bold;
|
||||
letter-spacing: -1px;
|
||||
line-height: 1;
|
||||
margin-bottom:9px;
|
||||
font-size:81px;
|
||||
font-weight:bold;
|
||||
letter-spacing:-1px;
|
||||
line-height:1;
|
||||
font-size:54px;
|
||||
}
|
||||
|
||||
.mastheader h1 {
|
||||
font-size: 54px;
|
||||
}
|
||||
|
||||
.mastheader small {
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
font-size:20px;
|
||||
font-weight:300;
|
||||
}
|
||||
|
||||
.navbar .dropdown-menu ul:before {
|
||||
border-bottom: 7px solid transparent;
|
||||
border-left: none;
|
||||
border-right: 7px solid rgba(0, 0, 0, 0.2);
|
||||
border-top: 7px solid transparent;
|
||||
left: -7px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.navbar .dropdown-menu ul:after {
|
||||
border-top: 6px solid transparent;
|
||||
border-left: none;
|
||||
border-right: 6px solid #fff;
|
||||
border-bottom: 6px solid transparent;
|
||||
left: 10px;
|
||||
top: 6px;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
.dropdown-menu span{
|
||||
/* auth navbar - primitive style */
|
||||
.auth_navbar,.auth_navbar a{color:inherit;}
|
||||
.ie-lte7 .auth_navbar,.auth_navbar a{color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
|
||||
.auth_navbar a{white-space:nowrap;} /* to avoid the nav split on more lines */
|
||||
.auth_navbar a:hover{color:white;text-decoration:none;}
|
||||
ul#navbar>.auth_navbar{
|
||||
display:inline-block;
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
.chevron-right {
|
||||
border-left: 4px solid #000;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-top: 4px solid transparent;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 0;
|
||||
opacity: 0.7;
|
||||
vertical-align: top;
|
||||
width: 0;
|
||||
margin-right:-13px;
|
||||
margin-top: 7px;
|
||||
float:right;
|
||||
}
|
||||
|
||||
.open > .dropdown-menu ul { /* fix menu issue when BS2.0.4 is applied */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ie-lte7 #navbar .auth_navbar, #navbar .auth_navbar a {color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
|
||||
|
||||
#navbar .auth_navbar a:hover {color:white;text-decoration:none;} /* this overwrite bootswatch */
|
||||
ul#navbar{padding:0;} /*reset ul padding */
|
||||
ul#navbar>.auth_navbar{display:inline-block;padding:5px;} /* set padding of span.auth_navbar inside ul*/
|
||||
|
||||
body {
|
||||
height:auto; /*to avoid vertical scroll bar*/
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {font-family: inherit;}
|
||||
|
||||
li {margin-bottom: 0;} /*bootswatch*/
|
||||
|
||||
.auth_navbar, .navbar .btn-group { padding:0; }
|
||||
|
||||
[class^="icon-"],[class*=" icon-"]{background-image:url("../images/glyphicons-halflings.png")} /* right folder for bootstrap black images/icons */
|
||||
.icon-white{background-image:url("../images/glyphicons-halflings-white.png");} /* right folder for bootstrap white images/icons */
|
||||
|
||||
.navbar-inner{
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 979px) {
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
#navbar{bottom:-10px;left:4px;}
|
||||
|
||||
div.flash {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.dropdown-menu ul {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
div.error_wrapper {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
div.error {
|
||||
/* form errors message box customization */
|
||||
div.error_wrapper{margin-bottom:9px;}
|
||||
div.error_wrapper .error{
|
||||
border-radius: 4px;
|
||||
-o-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
}
|
||||
/* below rules are only for formstyle = bootstrap
|
||||
trying to make errors look like bootstrap ones */
|
||||
div.controls .error_wrapper{
|
||||
display:inline-block;
|
||||
margin-bottom:0;
|
||||
vertical-align:middle;
|
||||
}
|
||||
div.controls .error{
|
||||
min-width:5px;
|
||||
background:inherit;
|
||||
color:#B94A48;
|
||||
border:none;
|
||||
padding:0;
|
||||
margin:0;
|
||||
//display:inline; /* uncommenting this, the animation effect is lost */
|
||||
}
|
||||
div.controls .inline-help{color:#3A87AD;}
|
||||
div.controls .error_wrapper+.inline-help{margin-left:-99999px;}
|
||||
/* beautify brand */
|
||||
.navbar-inverse .brand{color:#c6cecc;}
|
||||
.navbar-inverse .brand b{display:inline-block;margin-top:-1px;}
|
||||
.navbar-inverse .brand b>span{font-size:22px;color:white}
|
||||
.navbar-inverse .brand:hover b>span{color:white}
|
||||
/* beautify web2py link in navbar */
|
||||
span.highlighted{color:#d8d800;}
|
||||
.open span.highlighted{color:#ffff00;}
|
||||
|
||||
input[type="button"], input[type="submit"] {
|
||||
// to be fixed
|
||||
/*=============================================================
|
||||
OVERRIDING WEB2PY.CSS RULES
|
||||
==============================================================*/
|
||||
|
||||
/* reset to default */
|
||||
a{white-space:normal;}
|
||||
li{margin-bottom:0;}
|
||||
textarea,button{display:block;}
|
||||
/*reset ul padding */
|
||||
ul#navbar{padding:0;}
|
||||
/* label aligned to related input */
|
||||
td.w2p_fl,td.w2p_fc {padding:0;}
|
||||
#web2py_user_form td{vertical-align:middle;}
|
||||
|
||||
/*=============================================================
|
||||
OVERRIDING BOOTSTRAP.CSS RULES
|
||||
==============================================================*/
|
||||
|
||||
/* because web2py handles this via js */
|
||||
.hidden{visibility:visible;}
|
||||
/* right folder for bootstrap black images/icons */
|
||||
[class^="icon-"],[class*=" icon-"]{
|
||||
background-image:url("../images/glyphicons-halflings.png")
|
||||
}
|
||||
/* right folder for bootstrap white images/icons */
|
||||
.icon-white,
|
||||
.nav-tabs > .active > a > [class^="icon-"],
|
||||
.nav-tabs > .active > a > [class*=" icon-"],
|
||||
.nav-pills > .active > a > [class^="icon-"],
|
||||
.nav-pills > .active > a > [class*=" icon-"],
|
||||
.nav-list > .active > a > [class^="icon-"],
|
||||
.nav-list > .active > a > [class*=" icon-"],
|
||||
.navbar-inverse .nav > .active > a > [class^="icon-"],
|
||||
.navbar-inverse .nav > .active > a > [class*=" icon-"],
|
||||
.dropdown-menu > li > a:hover > [class^="icon-"],
|
||||
.dropdown-menu > li > a:hover > [class*=" icon-"],
|
||||
.dropdown-menu > .active > a > [class^="icon-"],
|
||||
.dropdown-menu > .active > a > [class*=" icon-"] {
|
||||
background-image:url("../images/glyphicons-halflings-white.png");
|
||||
}
|
||||
/* bootstrap has a label as input's wrapper while web2py has a div */
|
||||
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
|
||||
/* bootstrap has button instead of input */
|
||||
input[type="button"], input[type="submit"]{margin-right:8px;}
|
||||
|
||||
/*=============================================================
|
||||
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
|
||||
==============================================================*/
|
||||
|
||||
/*when formstyle=table3cols*/
|
||||
tr#auth_user_remember__row>td.w2p_fw>div{padding-bottom:8px;}
|
||||
td.w2p_fw div>label{vertical-align:middle;}
|
||||
td.w2p_fc {padding-bottom:5px;}
|
||||
/*when formstyle=divs*/
|
||||
div#auth_user_remember__row{margin-top:4px;}
|
||||
div#auth_user_remember__row>.w2p_fl{display:none;}
|
||||
div#auth_user_remember__row>.w2p_fw{min-height:39px;}
|
||||
div.w2p_fw,div.w2p_fc{
|
||||
display:inline-block;
|
||||
vertical-align:middle;
|
||||
margin-bottom:0;
|
||||
}
|
||||
div.w2p_fc{
|
||||
padding-left:5px;
|
||||
margin-top:-8px;
|
||||
}
|
||||
/*when formstyle=ul*/
|
||||
form>ul{
|
||||
list-style:none;
|
||||
margin:0;
|
||||
}
|
||||
li#auth_user_remember__row{margin-top:4px;}
|
||||
li#auth_user_remember__row>.w2p_fl{display:none;}
|
||||
li#auth_user_remember__row>.w2p_fw{min-height:39px;}
|
||||
/*when formstyle=bootstrap*/
|
||||
#auth_user_remember__row label.checkbox{display:block;}
|
||||
span.inline-help{display:inline-block;}
|
||||
input[type="text"].input-xlarge,input[type="password"].input-xlarge{width:270px;}
|
||||
/*when recaptcha is used*/
|
||||
#recaptcha{min-height:30px;display:inline-block;margin-bottom:0;line-height:30px;vertical-align:middle;}
|
||||
td>#recaptcha{margin-bottom:6px;}
|
||||
div>#recaptcha{margin-bottom:9px;}
|
||||
div.control-group.error{
|
||||
width:auto;
|
||||
background:transparent;
|
||||
border:0;
|
||||
color:inherit;
|
||||
padding:0;
|
||||
background-repeat:repeat;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 479px) {
|
||||
body {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
/*=============================================================
|
||||
OTHER RULES
|
||||
==============================================================*/
|
||||
|
||||
.navbar-inner{
|
||||
position:relative; /*unnecessary ??*/
|
||||
}
|
||||
/* Massimo Di Pierro fixed alignment in forms with list:string */
|
||||
form table tr{margin-bottom:9px;}
|
||||
td.w2p_fw ul{margin-left:0px;}
|
||||
|
||||
/* web2py_console in grid and smartgrid */
|
||||
.hidden{visibility:visible;}
|
||||
.web2py_console input{
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.web2py_console input[type="submit"],
|
||||
.web2py_console input[type="button"],
|
||||
.web2py_console button{
|
||||
padding-top:4px;
|
||||
padding-bottom:4px;
|
||||
margin:3px 0 0 2px;
|
||||
}
|
||||
.web2py_console a,
|
||||
.web2py_console select,
|
||||
.web2py_console input
|
||||
{
|
||||
margin:3px 0 0 2px;
|
||||
}
|
||||
.web2py_grid form table{width:auto;}
|
||||
/* auth_user_remember checkbox extrapadding in IE fix */
|
||||
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
|
||||
|
||||
/*=============================================================
|
||||
MEDIA QUERIES
|
||||
==============================================================*/
|
||||
|
||||
@media only screen and (max-width:979px){
|
||||
body{padding-top:0px;}
|
||||
#navbar{bottom:-10px;left:4px;}
|
||||
div.flash{right:5px;}
|
||||
.dropdown-menu ul{visibility:visible;}
|
||||
}
|
||||
@media only screen and (max-width:479px){
|
||||
body{
|
||||
padding-left:10px;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
.navbar-fixed-top, .navbar-fixed-bottom {
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
.navbar-fixed-top,.navbar-fixed-bottom {
|
||||
margin-left:-10px;
|
||||
margin-right:-10px;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"], select {
|
||||
width: 95%;
|
||||
input[type="text"],input[type="password"],select{
|
||||
width:95%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,23 @@
|
||||
/*=============================================================
|
||||
BOOTSTRAP DROPDOWN MENU
|
||||
==============================================================*/
|
||||
|
||||
.dropdown-menu ul{
|
||||
left:100%;
|
||||
position:absolute;
|
||||
top:0;
|
||||
visibility:hidden;
|
||||
margin-top:-1px;
|
||||
}
|
||||
.dropdown-menu li:hover ul{visibility:visible;}
|
||||
.navbar .dropdown-menu ul:before{
|
||||
border-bottom:7px solid transparent;
|
||||
border-left:none;
|
||||
border-right:7px solid rgba(0, 0, 0, 0.2);
|
||||
border-top:7px solid transparent;
|
||||
left:-7px;
|
||||
top:5px;
|
||||
}
|
||||
.nav > li.dropdown > a:after {
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
@@ -15,8 +35,7 @@
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-top-color: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
.dropdown-menu span{display:inline-block;}
|
||||
ul.dropdown-menu li.dropdown > a:after {
|
||||
border-left: 4px solid #000;
|
||||
border-right: 4px solid transparent;
|
||||
@@ -35,4 +54,69 @@ ul.dropdown-menu li.dropdown > a:after {
|
||||
|
||||
ul.nav li.dropdown:hover ul.dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */
|
||||
|
||||
/*=============================================================
|
||||
BOOTSTRAP SUBMIT BUTTON
|
||||
==============================================================*/
|
||||
|
||||
input[type='submit']:not(.btn) {
|
||||
display: inline-block;
|
||||
padding: 4px 14px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
background-color: whiteSmoke;
|
||||
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
|
||||
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
|
||||
background-image: -o-linear-gradient(top,white,#E6E6E6);
|
||||
background-image: linear-gradient(to bottom,white,#E6E6E6);
|
||||
background-image: -moz-linear-gradient(top,white,#E6E6E6);
|
||||
background-repeat: repeat-x;
|
||||
border: 1px solid #BBB;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
border-bottom-color: #A2A2A2;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);
|
||||
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
input[type='submit']:not(.btn):hover {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
background-color: #E6E6E6;
|
||||
background-position: 0 -15px;
|
||||
-webkit-transition: background-position .1s linear;
|
||||
-moz-transition: background-position .1s linear;
|
||||
-o-transition: background-position .1s linear;
|
||||
transition: background-position .1s linear;
|
||||
}
|
||||
|
||||
input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active {
|
||||
background-color: #E6E6E6;
|
||||
background-color: #D9D9D9 9;
|
||||
background-image: none;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/*=============================================================
|
||||
OTHER
|
||||
==============================================================*/
|
||||
|
||||
.ie-lte8 .navbar-fixed-top {position:static;}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
+1
-1
File diff suppressed because one or more lines are too long
@@ -21,8 +21,8 @@ jQuery(function(){
|
||||
var title = escape(jQuery('title').text());
|
||||
var twit = 'http://twitter.com/home?status='+title+'%20'+url;
|
||||
var facebook = 'http://www.facebook.com/sharer.php?u='+url;
|
||||
var buzz = 'http://www.google.com/reader/link?url='+url+'&title='+title+'&srcURL='+host;
|
||||
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+buzz+'" id="buzz" title="Share on Buzz"><img src="'+path+'/google-buzz.png" alt="Share on Buzz" width="32" height="32" /></a></div></div>';
|
||||
var gplus = 'https://plus.google.com/share?url='+url;
|
||||
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+gplus+'" id="gplus" title="Share on Google Plus"><img src="'+path+'/gplus-32.png" alt="Share on Google Plus" width="32" height="32" /></a></div></div>';
|
||||
// Add the share tool bar.
|
||||
jQuery('body').append(tbar);
|
||||
var st = jQuery('#socialdrawer');
|
||||
|
||||
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
|
||||
jQuery(function() {
|
||||
var flash = jQuery('.flash');
|
||||
flash.hide();
|
||||
if(flash.html()) flash.append('<span style="float:right;">×<span>').slideDown();
|
||||
if(flash.html()) flash.append('<span style="float:right;">×</span>').slideDown();
|
||||
web2py_ajax_init(document);
|
||||
web2py_event_handlers();
|
||||
});
|
||||
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
|
||||
form.submit(function(e){
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('post',action,form.serialize(),target);
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function web2py_trap_link(target) {
|
||||
jQuery('#'+target+' a.w2p_trap').each(function(i){
|
||||
var link=jQuery(this);
|
||||
link.click(function(e) {
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('get',link.attr('href'),[],target);
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
var link=jQuery(this);
|
||||
link.click(function(e) {
|
||||
jQuery('.flash').hide().html('');
|
||||
web2py_ajax_page('get',link.attr('href'),[],target);
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function web2py_ajax_page(method, action, data, target) {
|
||||
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
web2py_trap_link(target);
|
||||
web2py_ajax_init('#'+target);
|
||||
if(command)
|
||||
eval(decodeURIComponent(command));
|
||||
eval(decodeURIComponent(command));
|
||||
if(flash)
|
||||
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
|
||||
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
|
||||
}
|
||||
} else {
|
||||
// run once (no timeout specified)
|
||||
element.reload_counter = Infinity;
|
||||
element.reload_counter = Infinity;
|
||||
web2py_ajax_page('get', action, null, target);
|
||||
} }); }
|
||||
|
||||
function web2py_comet(url,onmessage,onopen,onclose) {
|
||||
function web2py_websocket(url,onmessage,onopen,onclose) {
|
||||
if ("WebSocket" in window) {
|
||||
var ws = new WebSocket(url);
|
||||
ws.onopen = onopen?onopen:(function(){});
|
||||
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
|
||||
} else return false; // not supported
|
||||
}
|
||||
|
||||
|
||||
function web2py_calc_entropy(mystring) {
|
||||
//calculate a simple entropy for a given string
|
||||
var csets = new Array(
|
||||
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
|
||||
'0123456789abcdefghijklmnopqrstuvwxyz');
|
||||
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
|
||||
for (var i=0;i<mystringlist.length;i++) { // classify this character
|
||||
var c = mystringlist[i], inset=5;
|
||||
for(var j = 0; j<csets.length; j++)
|
||||
if (csets[j].indexOf(c) != -1) {inset = j; break;}
|
||||
//calculate effect of character on alphabet size
|
||||
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
|
||||
else if (!(c in other)) {score += 1;other[c] = 1;}
|
||||
if (inset != lastset) {score += 1;lastset = inset;}
|
||||
}
|
||||
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
|
||||
return Math.round(entropy*100)/100
|
||||
}
|
||||
|
||||
function web2py_validate_entropy(myfield, req_entropy) {
|
||||
var validator = function () {
|
||||
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
|
||||
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
|
||||
if(v<=0.5) {r=1.0; g=2.0*v;}
|
||||
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
|
||||
var color = '#'+rs(r)+rs(g)+rs(b);
|
||||
myfield.css('background-color',color);
|
||||
entropy_callback = myfield.data('entropy_callback');
|
||||
if(entropy_callback) entropy_callback(v);
|
||||
}
|
||||
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// this code improves bootstrap menus and adds dropdown support
|
||||
jQuery(function(){
|
||||
jQuery('.nav>li>a').each(function(){
|
||||
if(jQuery(this).parent().find('ul').length)
|
||||
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
|
||||
});
|
||||
jQuery('.nav li li').each(function(){
|
||||
if(jQuery(this).find('ul').length)
|
||||
jQuery(this).addClass('dropdown-submenu');
|
||||
});
|
||||
function hoverMenu(){
|
||||
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
|
||||
if (wid>=980){
|
||||
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
|
||||
mi = jQuery(this).addClass('open');
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
|
||||
}, function(){
|
||||
mi = jQuery(this);
|
||||
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
|
||||
});
|
||||
};
|
||||
}
|
||||
hoverMenu(); // first page load
|
||||
jQuery(window).resize(hoverMenu); // on resize event
|
||||
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
|
||||
// make all buttons bootstrap buttons
|
||||
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
|
||||
});
|
||||
@@ -27,7 +27,7 @@
|
||||
{{pass}}
|
||||
{{else:}}
|
||||
{{qry=''}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
<tr>
|
||||
<th style="font-size: 1.75em;">
|
||||
@@ -37,13 +37,19 @@
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
|
||||
</td>
|
||||
</tr>
|
||||
{{pass}}
|
||||
</table>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
</table>
|
||||
|
||||
{{elif request.function=='select':}}
|
||||
<h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
|
||||
</h2>
|
||||
{{if tb:}}
|
||||
<h3>{{=T('Traceback')}}</h3>
|
||||
<pre>
|
||||
{{=tb}}
|
||||
</pre>
|
||||
{{pass}}
|
||||
{{if table:}}
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
|
||||
<h3>{{=T("Rows in Table")}}</h3><br/>
|
||||
@@ -149,7 +155,7 @@
|
||||
<h4>{{=T("RAM")}}</h4>
|
||||
<p>{{=T.M("Number of entries: **%s**", ram['entries'])}}</p>
|
||||
{{if ram['entries'] > 0:}}
|
||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||
dict( ratio=ram['ratio'], hits=ram['hits'], misses=ram['misses']))}}
|
||||
</p>
|
||||
<p>
|
||||
|
||||
@@ -4,16 +4,20 @@
|
||||
{{
|
||||
if request.args(0)=='login':
|
||||
if not 'register' in auth.settings.actions_disabled:
|
||||
form.add_button(T('Register'),URL(args='register'))
|
||||
form.add_button(T('Register'),URL(args='register'),_class='btn')
|
||||
pass
|
||||
if not 'request_reset_password' in auth.settings.actions_disabled:
|
||||
form.add_button(T('Lost Password'),URL(args='request_reset_password'))
|
||||
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn')
|
||||
pass
|
||||
pass
|
||||
=form
|
||||
}}
|
||||
</div>
|
||||
<script language="javascript"><!--
|
||||
jQuery("#web2py_user_form input:visible:enabled:first").focus();
|
||||
//--></script>
|
||||
|
||||
jQuery("#web2py_user_form input:visible:enabled:first").focus();
|
||||
{{if request.args(0)=='register':}}
|
||||
web2py_validate_entropy(jQuery('#auth_user_password'),100);
|
||||
{{elif request.args(0)=='change_password':}}
|
||||
web2py_validate_entropy(jQuery('#no_table_new_password'),100);
|
||||
{{pass}}
|
||||
//--></script>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
|
||||
|
||||
<!-- include stylesheets -->
|
||||
{{
|
||||
{{
|
||||
response.files.append(URL('static','css/web2py.css'))
|
||||
response.files.append(URL('static','css/bootstrap.min.css'))
|
||||
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
|
||||
response.files.append(URL('static','css/web2py.css'))
|
||||
response.files.append(URL('static','css/web2py_bootstrap.css'))
|
||||
}}
|
||||
|
||||
@@ -66,17 +66,17 @@
|
||||
|
||||
<body>
|
||||
<!-- Navbar ================================================== -->
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="flash">{{=response.flash or ''}}</div>
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<!-- the next tag is necessary for bootstrap menus, do not remove -->
|
||||
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</a>
|
||||
<a class="brand" href="#">web2py™ </a>
|
||||
</button>
|
||||
<a class="brand" href="http://www.web2py.com/"><b>web<span>2</span>py</b>™ </a>
|
||||
<ul id="navbar" class="nav pull-right">{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}</ul>
|
||||
<div class="nav-collapse">
|
||||
{{is_mobile=request.user_agent().is_mobile}}
|
||||
@@ -146,30 +146,8 @@
|
||||
|
||||
<!-- The javascript =============================================
|
||||
(Placed at the end of the document so the pages load faster) -->
|
||||
<script>
|
||||
// this code improves bootstrap menus and adds dropdown support
|
||||
jQuery(function(){
|
||||
jQuery('.nav>li>a').each(function(){
|
||||
if(jQuery(this).parent().find('ul').length)
|
||||
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
|
||||
});
|
||||
jQuery('.nav li li').each(function(){
|
||||
if(jQuery(this).find('ul').length)
|
||||
jQuery(this).children('a').contents().before('<i class="chevron-right"></i>');
|
||||
});
|
||||
if(jQuery(document).width()>=980) {
|
||||
jQuery('ul.nav li.dropdown').hover(function() {
|
||||
jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeIn();
|
||||
}, function() {
|
||||
jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeOut();
|
||||
});
|
||||
}
|
||||
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
|
||||
// make all buttons bootstrap buttons
|
||||
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn').css({'margin-right':'2px','margin-bottom':'2px'});
|
||||
});
|
||||
</script>
|
||||
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
|
||||
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
|
||||
<!--[if lt IE 7 ]>
|
||||
<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
|
||||
<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>
|
||||
|
||||
@@ -83,9 +83,7 @@ def wsgiapp(env, res):
|
||||
if global_settings.web2py_runtime == 'gae:development':
|
||||
gluon.admin.create_missing_folders()
|
||||
|
||||
from gluon.custom_import import custom_import_install
|
||||
web2py_path = global_settings.applications_parent # backward compatibility
|
||||
custom_import_install(web2py_path)
|
||||
|
||||
return gluon.main.wsgibase(env, res)
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ def app_pack(app, request, raise_ex=False):
|
||||
"""
|
||||
try:
|
||||
app_cleanup(app, request)
|
||||
filename = apath('../deposit/%s.w2p' % app, request)
|
||||
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
|
||||
w2p_pack(filename, apath(app, request))
|
||||
return filename
|
||||
except Exception, e:
|
||||
|
||||
+105
-111
@@ -11,7 +11,7 @@ Basic caching classes and methods
|
||||
|
||||
- Cache - The generic caching object interfacing with the others
|
||||
- CacheInRam - providing caching in ram
|
||||
- CacheInDisk - provides caches on disk
|
||||
- CacheOnDisk - provides caches on disk
|
||||
|
||||
Memcache is also available via a different module (see gluon.contrib.memcache)
|
||||
|
||||
@@ -46,6 +46,19 @@ class CacheAbstract(object):
|
||||
Main function is now to provide referenced api documentation.
|
||||
|
||||
Use CacheInRam or CacheOnDisk instead which are derived from this class.
|
||||
|
||||
Attentions, Michele says:
|
||||
|
||||
There are signatures inside gdbm files that are used directly
|
||||
by the python gdbm adapter that often are lagging behind in the
|
||||
detection code in python part.
|
||||
On every occasion that a gdbm store is probed by the python adapter,
|
||||
the probe fails, because gdbm file version is newer.
|
||||
Using gdbm directly from C would work, because there is backward
|
||||
compatibility, but not from python!
|
||||
The .shelve file is discarded and a new one created (with new
|
||||
signature) and it works until it is probed again...
|
||||
The possible consequences are memory leaks and broken sessions.
|
||||
"""
|
||||
|
||||
cache_stats_name = 'web2py_cache_statistics'
|
||||
@@ -130,22 +143,27 @@ class CacheInRam(CacheAbstract):
|
||||
meta_storage = {}
|
||||
|
||||
def __init__(self, request=None):
|
||||
self.locker.acquire()
|
||||
self.initialized = False
|
||||
self.request = request
|
||||
|
||||
def initialize(self):
|
||||
if self.initialized: return
|
||||
else: self.initialized = True
|
||||
self.locker.acquire()
|
||||
request = self.request
|
||||
if request:
|
||||
app = request.application
|
||||
else:
|
||||
app = ''
|
||||
if not app in self.meta_storage:
|
||||
self.storage = self.meta_storage[app] = {CacheAbstract.cache_stats_name: {
|
||||
'hit_total': 0,
|
||||
'misses': 0,
|
||||
}}
|
||||
self.storage = self.meta_storage[app] = {
|
||||
CacheAbstract.cache_stats_name: {'hit_total': 0, 'misses': 0}}
|
||||
else:
|
||||
self.storage = self.meta_storage[app]
|
||||
self.locker.release()
|
||||
|
||||
def clear(self, regex=None):
|
||||
self.initialize()
|
||||
self.locker.acquire()
|
||||
storage = self.storage
|
||||
if regex is None:
|
||||
@@ -154,10 +172,7 @@ class CacheInRam(CacheAbstract):
|
||||
self._clear(storage, regex)
|
||||
|
||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
||||
storage[CacheAbstract.cache_stats_name] = {
|
||||
'hit_total': 0,
|
||||
'misses': 0,
|
||||
}
|
||||
storage[CacheAbstract.cache_stats_name] = {'hit_total': 0,'misses': 0}
|
||||
|
||||
self.locker.release()
|
||||
|
||||
@@ -172,6 +187,7 @@ class CacheInRam(CacheAbstract):
|
||||
3) would work unless we deepcopy no storage and retrival which would make things slow.
|
||||
Anyway. You can deepcopy explicitly in the function generating the value to be cached.
|
||||
"""
|
||||
self.initialize()
|
||||
|
||||
dt = time_expire
|
||||
now = time.time()
|
||||
@@ -200,6 +216,7 @@ class CacheInRam(CacheAbstract):
|
||||
return value
|
||||
|
||||
def increment(self, key, value=1):
|
||||
self.initialize()
|
||||
self.locker.acquire()
|
||||
try:
|
||||
if key in self.storage:
|
||||
@@ -226,58 +243,65 @@ class CacheOnDisk(CacheAbstract):
|
||||
Values stored in disk cache must be pickable.
|
||||
"""
|
||||
|
||||
speedup_checks = set()
|
||||
def _close_shelve_and_unlock(self):
|
||||
try:
|
||||
if self.storage:
|
||||
self.storage.close()
|
||||
finally:
|
||||
if self.locker and self.locked:
|
||||
portalocker.unlock(self.locker)
|
||||
self.locker.close()
|
||||
self.locked = False
|
||||
|
||||
def _open_shelf_with_lock(self):
|
||||
def _open_shelve_and_lock(self):
|
||||
"""Open and return a shelf object, obtaining an exclusive lock
|
||||
on self.locker first. Replaces the close method of the
|
||||
returned shelf instance with one that releases the lock upon
|
||||
closing."""
|
||||
def _close(self):
|
||||
try:
|
||||
shelve.Shelf.close(self)
|
||||
finally:
|
||||
portalocker.unlock(self.locker)
|
||||
self.locker.close()
|
||||
|
||||
storage, locker, locker_locked = None, None, False
|
||||
|
||||
storage = None
|
||||
locker = None
|
||||
locked = False
|
||||
try:
|
||||
locker = open(self.locker_name, 'a')
|
||||
locker = locker = open(self.locker_name, 'a')
|
||||
portalocker.lock(locker, portalocker.LOCK_EX)
|
||||
locker_locked = True
|
||||
storage = shelve.open(self.shelve_name)
|
||||
storage.close = _close.__get__(storage, shelve.Shelf)
|
||||
storage.locker = locker
|
||||
except Exception:
|
||||
logger.error('corrupted cache file %s, will try to delete and recreate it!' % (self.shelve_name))
|
||||
locked = True
|
||||
try:
|
||||
storage = shelve.open(self.shelve_name)
|
||||
except:
|
||||
logger.error('corrupted cache file %s, will try rebuild it' \
|
||||
% (self.shelve_name))
|
||||
storage = None
|
||||
if not storage and os.path.exists(self.shelve_name):
|
||||
os.unlink(self.shelve_name)
|
||||
storage = shelve.open(self.shelve_name)
|
||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
||||
storage[CacheAbstract.cache_stats_name] = {'hit_total':0, 'misses': 0}
|
||||
storage.sync()
|
||||
except Exception, e:
|
||||
if storage:
|
||||
storage.close()
|
||||
storage = None
|
||||
|
||||
try:
|
||||
os.unlink(self.shelve_name)
|
||||
storage = shelve.open(self.shelve_name)
|
||||
storage.close = _close.__get__(storage, shelve.Shelf)
|
||||
storage.locker = locker
|
||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
||||
storage[CacheAbstract.cache_stats_name] = {
|
||||
'hit_total': 0,
|
||||
'misses': 0,
|
||||
}
|
||||
storage.sync()
|
||||
except (IOError, OSError):
|
||||
logger.warn('unable to delete and recreate cache file %s' % self.shelve_name)
|
||||
if storage:
|
||||
storage.close()
|
||||
storage = None
|
||||
if locker_locked:
|
||||
portalocker.unlock(locker)
|
||||
if locker:
|
||||
locker.close()
|
||||
if locked:
|
||||
portalocker.unlock(locker)
|
||||
locker.close()
|
||||
locked = False
|
||||
raise RuntimeError, 'unable to create/re-create cache file %s' % self.shelve_name
|
||||
self.locker = locker
|
||||
self.locked = locked
|
||||
self.storage = storage
|
||||
return storage
|
||||
|
||||
def __init__(self, request, folder=None):
|
||||
def __init__(self, request=None, folder=None):
|
||||
self.initialized = False
|
||||
self.request = request
|
||||
self.folder = folder
|
||||
|
||||
def initialize(self):
|
||||
if self.initialized: return
|
||||
else: self.initialized = True
|
||||
folder = self.folder
|
||||
request = self.request
|
||||
|
||||
# Lets test if the cache folder exists, if not
|
||||
# we are going to create it
|
||||
@@ -291,93 +315,54 @@ class CacheOnDisk(CacheAbstract):
|
||||
self.locker_name = os.path.join(folder,'cache.lock')
|
||||
self.shelve_name = os.path.join(folder,'cache.shelve')
|
||||
|
||||
speedup_key = (folder,CacheAbstract.cache_stats_name)
|
||||
if not speedup_key in self.speedup_checks or \
|
||||
not os.path.exists(self.shelve_name):
|
||||
try:
|
||||
storage = self._open_shelf_with_lock()
|
||||
try:
|
||||
if not CacheAbstract.cache_stats_name in storage:
|
||||
storage[CacheAbstract.cache_stats_name] = {
|
||||
'hit_total': 0,
|
||||
'misses': 0,
|
||||
}
|
||||
storage.sync()
|
||||
finally:
|
||||
storage.close()
|
||||
self.speedup_checks.add(speedup_key)
|
||||
except ImportError:
|
||||
pass # no module _bsddb, ignoring exception now so it makes a ticket only if used
|
||||
|
||||
def clear(self, regex=None):
|
||||
storage = self._open_shelf_with_lock()
|
||||
self.initialize()
|
||||
storage = self._open_shelve_and_lock()
|
||||
try:
|
||||
if regex is None:
|
||||
storage.clear()
|
||||
else:
|
||||
self._clear(storage, regex)
|
||||
if not CacheAbstract.cache_stats_name in storage.keys():
|
||||
storage[CacheAbstract.cache_stats_name] = {
|
||||
'hit_total': 0,
|
||||
'misses': 0,
|
||||
}
|
||||
storage.sync()
|
||||
finally:
|
||||
storage.close()
|
||||
self._close_shelve_and_unlock()
|
||||
|
||||
def __call__(self, key, f,
|
||||
time_expire = DEFAULT_TIME_EXPIRE):
|
||||
self.initialize()
|
||||
dt = time_expire
|
||||
|
||||
storage = self._open_shelf_with_lock()
|
||||
storage = self._open_shelve_and_lock()
|
||||
try:
|
||||
item = storage.get(key, None)
|
||||
storage[CacheAbstract.cache_stats_name]['hit_total'] += 1
|
||||
if item and f is None:
|
||||
del storage[key]
|
||||
|
||||
storage[CacheAbstract.cache_stats_name] = {
|
||||
'hit_total': storage[CacheAbstract.cache_stats_name]['hit_total'] + 1,
|
||||
'misses': storage[CacheAbstract.cache_stats_name]['misses']
|
||||
}
|
||||
|
||||
storage.sync()
|
||||
storage.sync()
|
||||
now = time.time()
|
||||
if f is None:
|
||||
value = None
|
||||
elif item and (dt is None or item[0] > now - dt):
|
||||
value = item[1]
|
||||
else:
|
||||
value = f()
|
||||
storage[key] = (now, value)
|
||||
storage[CacheAbstract.cache_stats_name]['misses']+=1
|
||||
storage.sync()
|
||||
finally:
|
||||
if storage:
|
||||
storage.close()
|
||||
|
||||
now = time.time()
|
||||
if f is None:
|
||||
return None
|
||||
if item and (dt is None or item[0] > now - dt):
|
||||
return item[1]
|
||||
value = f()
|
||||
|
||||
storage = self._open_shelf_with_lock()
|
||||
try:
|
||||
storage[key] = (now, value)
|
||||
|
||||
storage[CacheAbstract.cache_stats_name] = {
|
||||
'hit_total': storage[CacheAbstract.cache_stats_name]['hit_total'],
|
||||
'misses': storage[CacheAbstract.cache_stats_name]['misses'] + 1
|
||||
}
|
||||
|
||||
storage.sync()
|
||||
finally:
|
||||
if storage:
|
||||
storage.close()
|
||||
self._close_shelve_and_unlock()
|
||||
|
||||
return value
|
||||
|
||||
def increment(self, key, value=1):
|
||||
storage = self._open_shelf_with_lock()
|
||||
self.initialize()
|
||||
storage = self._open_shelve_and_lock()
|
||||
try:
|
||||
if key in storage:
|
||||
value = storage[key][1] + value
|
||||
storage[key] = (time.time(), value)
|
||||
storage.sync()
|
||||
finally:
|
||||
if storage:
|
||||
storage.close()
|
||||
self._close_shelve_and_unlock()
|
||||
return value
|
||||
|
||||
class CacheAction(object):
|
||||
@@ -423,10 +408,9 @@ class Cache(object):
|
||||
the global request object
|
||||
"""
|
||||
# GAE will have a special caching
|
||||
|
||||
if have_settings and settings.global_settings.web2py_runtime_gae:
|
||||
from contrib.gae_memcache import MemcacheClient
|
||||
self.ram=self.disk=MemcacheClient(request)
|
||||
self.ram = self.disk = MemcacheClient(request)
|
||||
else:
|
||||
# Otherwise use ram (and try also disk)
|
||||
self.ram = CacheInRam(request)
|
||||
@@ -479,6 +463,16 @@ class Cache(object):
|
||||
return CacheAction(func,key,time_expire,self,cache_model)
|
||||
return tmp
|
||||
|
||||
@staticmethod
|
||||
def with_prefix(cache_model, prefix):
|
||||
"""
|
||||
allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
|
||||
it will add prefix to all the cache keys used.
|
||||
"""
|
||||
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix:\
|
||||
cache_model(prefix + key, f, time_expire)
|
||||
|
||||
|
||||
def lazy_cache(key=None,time_expire=None,cache_model='ram'):
|
||||
"""
|
||||
can be used to cache any function including in modules,
|
||||
|
||||
+29
-27
@@ -40,6 +40,7 @@ import imp
|
||||
import logging
|
||||
logger = logging.getLogger("web2py")
|
||||
import rewrite
|
||||
from custom_import import custom_import_install
|
||||
|
||||
try:
|
||||
import py_compile
|
||||
@@ -358,22 +359,36 @@ OLD IMPLEMENTATION:
|
||||
return module
|
||||
"""
|
||||
|
||||
_base_environment_ = dict((k,getattr(html,k)) for k in html.__all__)
|
||||
_base_environment_.update((k,getattr(validators,k)) for k in validators.__all__)
|
||||
_base_environment_['__builtins__'] = __builtins__
|
||||
_base_environment_['HTTP'] = HTTP
|
||||
_base_environment_['redirect'] = redirect
|
||||
_base_environment_['DAL'] = DAL
|
||||
_base_environment_['Field'] = Field
|
||||
_base_environment_['SQLDB'] = SQLDB # for backward compatibility
|
||||
_base_environment_['SQLField'] = SQLField # for backward compatibility
|
||||
_base_environment_['SQLFORM'] = SQLFORM
|
||||
_base_environment_['SQLTABLE'] = SQLTABLE
|
||||
_base_environment_['LOAD'] = LOAD
|
||||
|
||||
def build_environment(request, response, session, store_current=True):
|
||||
"""
|
||||
Build the environment dictionary into which web2py files are executed.
|
||||
"""
|
||||
h,v = html,validators
|
||||
environment = dict((k,getattr(h,k)) for k in h.__all__)
|
||||
environment.update((k,getattr(v, k)) for k in v.__all__)
|
||||
#h,v = html,validators
|
||||
environment = dict(_base_environment_)
|
||||
|
||||
if not request.env:
|
||||
request.env = Storage()
|
||||
# Enable standard conditional models (i.e., /*.py, /[controller]/*.py, and
|
||||
# /[controller]/[function]/*.py)
|
||||
response.models_to_run = [r'^\w+\.py$', r'^%s/\w+\.py$' % request.controller,
|
||||
r'^%s/%s/\w+\.py$' % (request.controller, request.function)]
|
||||
|
||||
|
||||
t = environment['T'] = translator(request)
|
||||
c = environment['cache'] = Cache(request)
|
||||
|
||||
if store_current:
|
||||
current.globalenv = environment
|
||||
current.request = request
|
||||
@@ -389,27 +404,17 @@ def build_environment(request, response, session, store_current=True):
|
||||
__builtins__ = mybuiltin()
|
||||
else:
|
||||
__builtins__['__import__'] = __builtin__.__import__ ### WHY?
|
||||
environment['__builtins__'] = __builtins__
|
||||
environment['HTTP'] = HTTP
|
||||
environment['redirect'] = redirect
|
||||
environment['request'] = request
|
||||
environment['response'] = response
|
||||
environment['session'] = session
|
||||
environment['DAL'] = DAL
|
||||
environment['Field'] = Field
|
||||
environment['SQLDB'] = SQLDB # for backward compatibility
|
||||
environment['SQLField'] = SQLField # for backward compatibility
|
||||
environment['SQLFORM'] = SQLFORM
|
||||
environment['SQLTABLE'] = SQLTABLE
|
||||
environment['LOAD'] = LOAD
|
||||
environment['local_import'] = \
|
||||
lambda name, reload=False, app=request.application:\
|
||||
local_import_aux(name,reload,app)
|
||||
lambda name, reload=False, app=request.application:\
|
||||
local_import_aux(name,reload,app)
|
||||
BaseAdapter.set_folder(pjoin(request.folder, 'databases'))
|
||||
response._view_environment = copy.copy(environment)
|
||||
custom_import_install()
|
||||
return environment
|
||||
|
||||
|
||||
def save_pyc(filename):
|
||||
"""
|
||||
Bytecode compiles the file `filename`
|
||||
@@ -527,7 +532,6 @@ def run_controller_in(controller, function, environment):
|
||||
"""
|
||||
|
||||
# if compiled should run compiled!
|
||||
|
||||
folder = environment['request'].folder
|
||||
path = pjoin(folder, 'compiled')
|
||||
badc = 'invalid controller (%s/%s)' % (controller, function)
|
||||
@@ -537,7 +541,7 @@ def run_controller_in(controller, function, environment):
|
||||
% (controller, function))
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404,
|
||||
rewrite.thread.routes.error_message % badf,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||
web2py_error=badf)
|
||||
restricted(read_pyc(filename), environment, layer=filename)
|
||||
elif function == '_TEST':
|
||||
@@ -552,7 +556,7 @@ def run_controller_in(controller, function, environment):
|
||||
% controller)
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404,
|
||||
rewrite.thread.routes.error_message % badc,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
||||
web2py_error=badc)
|
||||
environment['__symbols__'] = environment.keys()
|
||||
code = read_file(filename)
|
||||
@@ -563,13 +567,13 @@ def run_controller_in(controller, function, environment):
|
||||
% controller)
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404,
|
||||
rewrite.thread.routes.error_message % badc,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
||||
web2py_error=badc)
|
||||
code = read_file(filename)
|
||||
exposed = regex_expose.findall(code)
|
||||
if not function in exposed:
|
||||
raise HTTP(404,
|
||||
rewrite.thread.routes.error_message % badf,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||
web2py_error=badf)
|
||||
code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function)
|
||||
if is_gae:
|
||||
@@ -579,8 +583,7 @@ def run_controller_in(controller, function, environment):
|
||||
response = environment['response']
|
||||
vars=response._vars
|
||||
if response.postprocessing:
|
||||
for p in response.postprocessing:
|
||||
vars = p(vars)
|
||||
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
|
||||
if isinstance(vars,unicode):
|
||||
vars = vars.encode('utf8')
|
||||
elif hasattr(vars,'xml') and callable(vars.xml):
|
||||
@@ -594,7 +597,6 @@ def run_view_in(environment):
|
||||
or `view/generic.extension`
|
||||
It tries the pre-compiled views_controller_function.pyc before compiling it.
|
||||
"""
|
||||
|
||||
request = environment['request']
|
||||
response = environment['response']
|
||||
view = response.view
|
||||
@@ -630,7 +632,7 @@ def run_view_in(environment):
|
||||
restricted(code, environment, layer=filename)
|
||||
return
|
||||
raise HTTP(404,
|
||||
rewrite.thread.routes.error_message % badv,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
||||
web2py_error=badv)
|
||||
else:
|
||||
filename = pjoin(folder, 'views', view)
|
||||
@@ -639,7 +641,7 @@ def run_view_in(environment):
|
||||
filename = pjoin(folder, 'views', view)
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404,
|
||||
rewrite.thread.routes.error_message % badv,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
||||
web2py_error=badv)
|
||||
layer = filename
|
||||
if is_gae:
|
||||
|
||||
@@ -12,47 +12,56 @@ cache.ram=cache.disk=MemcacheClient(request)
|
||||
import time
|
||||
from google.appengine.api.memcache import Client
|
||||
|
||||
class MemcacheClient(object):
|
||||
|
||||
class MemcacheClient(Client):
|
||||
client = Client()
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
Client.__init__(self)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
key,
|
||||
f,
|
||||
time_expire=300,
|
||||
):
|
||||
):
|
||||
key = '%s/%s' % (self.request.application, key)
|
||||
dt = time_expire
|
||||
value = None
|
||||
obj = self.get(key)
|
||||
obj = self.client.get(key)
|
||||
if obj and (dt == None or obj[0] > time.time() - dt):
|
||||
value = obj[1]
|
||||
elif f is None:
|
||||
if obj:
|
||||
self.delete(key)
|
||||
self.client.delete(key)
|
||||
else:
|
||||
value = f()
|
||||
self.set(key, (time.time(), value))
|
||||
self.client.set(key, (time.time(), value))
|
||||
return value
|
||||
|
||||
def increment(self, key, value=1):
|
||||
key = '%s/%s' % (self.request.application, key)
|
||||
obj = self.get(key)
|
||||
obj = self.client.get(key)
|
||||
if obj:
|
||||
value = obj[1] + value
|
||||
self.set((time.time(), value))
|
||||
self.client.set(key, (time.time(), value))
|
||||
return value
|
||||
|
||||
def clear(self, key):
|
||||
key = '%s/%s' % (self.request.application, key)
|
||||
self.delete(key)
|
||||
|
||||
|
||||
def clear(self, key = None):
|
||||
if key:
|
||||
key = '%s/%s' % (self.request.application, key)
|
||||
self.client.delete(key)
|
||||
else:
|
||||
self.client.flush_all()
|
||||
|
||||
def delete(self,*a,**b):
|
||||
return self.client.delete(*a,**b)
|
||||
|
||||
def get(self,*a,**b):
|
||||
return self.client.delete(*a,**b)
|
||||
|
||||
def set(self,*a,**b):
|
||||
return self.client.delete(*a,**b)
|
||||
|
||||
def flush_all(self,*a,**b):
|
||||
return self.client.delete(*a,**b)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# coding: utf8
|
||||
|
||||
"""
|
||||
Dropbox Authentication for web2py
|
||||
Developed by Massimo Di Pierro (2012)
|
||||
Same License as Web2py License
|
||||
Dropbox Authentication for web2py
|
||||
Developed by Massimo Di Pierro (2012)
|
||||
Same License as Web2py License
|
||||
"""
|
||||
|
||||
# mind here session is dropbox session, not current.session
|
||||
@@ -27,7 +27,7 @@ class DropboxAccount(object):
|
||||
key="...",
|
||||
secret="...",
|
||||
access_type="...",
|
||||
url = "http://localhost:8000/%s/default/user/login" % request.application)
|
||||
login_url = "http://localhost:8000/%s/default/user/login" % request.application)
|
||||
when logged in
|
||||
client = auth.settings.login_form.client
|
||||
"""
|
||||
@@ -40,7 +40,7 @@ class DropboxAccount(object):
|
||||
login_url = "",
|
||||
on_login_failure=None,
|
||||
):
|
||||
|
||||
|
||||
self.request=request
|
||||
self.key=key
|
||||
self.secret=secret
|
||||
@@ -53,39 +53,48 @@ class DropboxAccount(object):
|
||||
|
||||
def get_user(self):
|
||||
request = self.request
|
||||
token = current.session.dropbox_token
|
||||
if not token:
|
||||
return None
|
||||
try:
|
||||
self.sess.set_token(token[0],token[1])
|
||||
except:
|
||||
# invalid token, should never happen
|
||||
if not current.session.dropbox_request_token:
|
||||
return None
|
||||
elif not current.session.dropbox_access_token:
|
||||
|
||||
request_token = current.session.dropbox_request_token
|
||||
self.sess.set_request_token(request_token[0],request_token[1])
|
||||
access_token = self.sess.obtain_access_token(self.sess.token)
|
||||
current.session.dropbox_access_token = \
|
||||
(access_token.key,access_token.secret)
|
||||
else:
|
||||
user = Storage()
|
||||
self.client = client.DropboxClient(self.sess)
|
||||
data = self.client.account_info()
|
||||
display_name = data.get('display_name','').split(' ',1)
|
||||
user = dict(email = data.get('email',None),
|
||||
first_name = display_name[0],
|
||||
last_name = display_name[-1],
|
||||
registration_id = data.get('uid',None))
|
||||
if not user['registration_id'] and self.on_login_failure:
|
||||
redirect(self.on_login_failure)
|
||||
return user
|
||||
access_token = current.session.dropbox_access_token
|
||||
self.sess.set_token(access_token[0],access_token[1])
|
||||
|
||||
|
||||
user = Storage()
|
||||
self.client = client.DropboxClient(self.sess)
|
||||
data = self.client.account_info()
|
||||
display_name = data.get('display_name','').split(' ',1)
|
||||
user = dict(email = data.get('email',None),
|
||||
first_name = display_name[0],
|
||||
last_name = display_name[-1],
|
||||
registration_id = data.get('uid',None))
|
||||
if not user['registration_id'] and self.on_login_failure:
|
||||
redirect(self.on_login_failure)
|
||||
return user
|
||||
|
||||
def login_form(self):
|
||||
token = self.sess.obtain_request_token()
|
||||
current.session.dropbox_token = (token.key,token.secret)
|
||||
dropbox_url = self.sess.build_authorize_url(token,self.login_url)
|
||||
|
||||
request_token = self.sess.obtain_request_token()
|
||||
current.session.dropbox_request_token = \
|
||||
(request_token.key,request_token.secret)
|
||||
dropbox_url = self.sess.build_authorize_url(request_token,
|
||||
self.login_url)
|
||||
redirect(dropbox_url)
|
||||
form = IFRAME(_src=dropbox_url,
|
||||
_scrolling="no",
|
||||
_frameborder="no",
|
||||
_style="width:400px;height:240px;")
|
||||
return form
|
||||
|
||||
def logout_url(self, next = "/"):
|
||||
current.session.dropbox_token=None
|
||||
current.session.dropbox_request_token=None
|
||||
current.session.auth=None
|
||||
redirect('https://www.dropbox.com/logout')
|
||||
return next
|
||||
|
||||
@@ -107,7 +107,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
||||
path_info = next
|
||||
else:
|
||||
path_info = r.env.path_info
|
||||
uri = '%s://%s%s' %(url_scheme, http_host, path_info)
|
||||
uri = '%s://%s%s' % (url_scheme, http_host, path_info)
|
||||
if r.get_vars and not next:
|
||||
uri += '?' + urlencode(r.get_vars)
|
||||
return uri
|
||||
|
||||
@@ -26,8 +26,10 @@ class MemcacheClientObj(Client):
|
||||
|
||||
def __init__(self, request, servers, debug=0, pickleProtocol=0,
|
||||
pickler=pickle.Pickler, unpickler=pickle.Unpickler,
|
||||
pload=None, pid=None):
|
||||
pload=None, pid=None,
|
||||
default_time_expire = DEFAULT_TIME_EXPIRE):
|
||||
self.request=request
|
||||
self.default_time_expire = default_time_expire
|
||||
if request:
|
||||
app = request.application
|
||||
else:
|
||||
@@ -43,8 +45,9 @@ class MemcacheClientObj(Client):
|
||||
else:
|
||||
self.storage = self.meta_storage[app]
|
||||
|
||||
def __call__(self, key, f,
|
||||
time_expire=DEFAULT_TIME_EXPIRE):
|
||||
def __call__(self, key, f, time_expire = 'default'):
|
||||
if time_expire == 'default':
|
||||
time_expire = self.default_time_expire
|
||||
if time_expire == None:
|
||||
time_expire = self.max_time_expire
|
||||
# this must be commented because get and set are redefined
|
||||
@@ -70,8 +73,10 @@ class MemcacheClientObj(Client):
|
||||
self.set(key, (now,value), self.max_time_expire)
|
||||
return value
|
||||
|
||||
def increment(self, key, value=1, time_expire=DEFAULT_TIME_EXPIRE):
|
||||
def increment(self, key, value=1, time_expire='default'):
|
||||
""" time_expire is ignored """
|
||||
if time_expire == 'default':
|
||||
time_expire = self.default_time_expire
|
||||
newKey = self.__keyFormat__(key)
|
||||
obj = Client.get(self, newKey)
|
||||
if obj:
|
||||
@@ -86,7 +91,9 @@ class MemcacheClientObj(Client):
|
||||
Client.set(self, newKey, value, self.max_time_expire)
|
||||
return value
|
||||
|
||||
def set(self, key, value, time_expire=DEFAULT_TIME_EXPIRE):
|
||||
def set(self, key, value, time_expire='default'):
|
||||
if time_expire == 'default':
|
||||
time_expire = self.default_time_expire
|
||||
newKey = self.__keyFormat__(key)
|
||||
return Client.set(self, newKey, value, time_expire)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import cssmin
|
||||
import jsmin
|
||||
import os
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
def read_binary_file(filename):
|
||||
f = open(filename,'rb')
|
||||
@@ -24,9 +25,10 @@ def write_binary_file(filename,data):
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
def fix_links(css,static_path):
|
||||
return css.replace('../',static_path+'/')
|
||||
|
||||
def fix_links(css,static_path):
|
||||
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
|
||||
|
||||
|
||||
def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
ignore_concat = [],
|
||||
ignore_minify = ['/jquery.js', '/anytime.js']):
|
||||
@@ -60,17 +62,30 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
processed = []
|
||||
for k,filename in enumerate(files):
|
||||
if not filename.startswith('/') or \
|
||||
any(filename.endswith(x) for x in ignore_concat):
|
||||
any(filename.endswith(x) \
|
||||
for x in ignore_concat):
|
||||
new_files.append(filename)
|
||||
continue
|
||||
|
||||
abs_filename = os.path.join(folder,'static',
|
||||
filename[len(static_path)+1:])
|
||||
abs_filename = os.path.join(
|
||||
folder,'static', filename[len(static_path)+1:])
|
||||
|
||||
if filename.lower().endswith('.css'):
|
||||
processed.append(filename)
|
||||
spath_info, sfilename = \
|
||||
path_info.split('/'), filename.split('/')
|
||||
u = 0
|
||||
for i,a in enumerate(sfilename):
|
||||
try:
|
||||
if a != spath_info[i]:
|
||||
u = i
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if concat_css:
|
||||
contents = read_binary_file(abs_filename)
|
||||
replacement = '/'.join(spath_info[:u]) + '/'
|
||||
contents = fix_links(contents, replacement)
|
||||
if minify_css:
|
||||
css.append(cssmin.cssmin(contents))
|
||||
else:
|
||||
@@ -80,9 +95,12 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
elif filename.lower().endswith('.js'):
|
||||
processed.append(filename)
|
||||
if concat_js:
|
||||
contents = read_binary_file(abs_filename)
|
||||
if minify_js and not filename.endswith('.min.js') and \
|
||||
not any(filename.endswith(x) for x in ignore_minify):
|
||||
contents = read_binary_file(abs_filename)
|
||||
|
||||
if minify_js and \
|
||||
not filename.endswith('.min.js') and \
|
||||
not any(filename.endswith(x) \
|
||||
for x in ignore_minify):
|
||||
js.append(jsmin.jsmin(contents))
|
||||
else:
|
||||
js.append(contents)
|
||||
@@ -91,16 +109,17 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
dest_key = hashlib.md5(repr(processed)).hexdigest()
|
||||
if css and concat_css:
|
||||
css = '\n\n'.join(contents for contents in css)
|
||||
if inline_css:
|
||||
css = ('css:inline',fix_links(css,static_path))
|
||||
else:
|
||||
if not inline_css:
|
||||
temppath = os.path.join(folder,'static',temp)
|
||||
if not os.path.exists(temppath): os.mkdir(temppath)
|
||||
if not os.path.exists(temppath):
|
||||
os.mkdir(temppath)
|
||||
dest = "compressed_%s.css" % dest_key
|
||||
tempfile = os.path.join(temppath, dest)
|
||||
write_binary_file(tempfile,css)
|
||||
css = path_info+'/%s' % dest
|
||||
new_files.append(css)
|
||||
new_files.append(css)
|
||||
else:
|
||||
new_files.append(('css:inline',css))
|
||||
else:
|
||||
new_files += css
|
||||
if js and concat_js:
|
||||
@@ -109,7 +128,8 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
js = ('js:inline',js)
|
||||
else:
|
||||
temppath = os.path.join(folder,'static',temp)
|
||||
if not os.path.exists(temppath): os.mkdir(temppath)
|
||||
if not os.path.exists(temppath):
|
||||
os.mkdir(temppath)
|
||||
dest = "compressed_%s.js" % dest_key
|
||||
tempfile = os.path.join(folder,'static',temp,dest)
|
||||
write_binary_file(tempfile,js)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2006-2012 James Tauber and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
@@ -0,0 +1,43 @@
|
||||
# pyuca: Python Unicode Collation Algorithm implementation
|
||||
(http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/)
|
||||
|
||||
This is my preliminary attempt at a Python implementation of the
|
||||
[Unicode Collation Algorithm (UCA)](http://unicode.org/reports/tr10/).
|
||||
I originally posted it to my blog in 2006 but it seems to get enough
|
||||
usage it really belongs here (and in PyPI).
|
||||
|
||||
What do you use it for? In short, sorting non-English strings properly.
|
||||
|
||||
The core of the algorithm involves multi-level comparison. For example,
|
||||
``café`` comes before ``caff`` because at the primary level, the accent
|
||||
is ignored and the first word is treated as if it were ``cafe``.
|
||||
The secondary level (which considers accents) only applies then to words
|
||||
that are equivalent at the primary level.
|
||||
|
||||
The Unicode Collation Algorithm and pyuca also support contraction and
|
||||
expansion. **Contraction** is where multiple letters are treated as a
|
||||
single unit. In Spanish, ``ch`` is treated as a letter coming between
|
||||
``c`` and ``d`` so that, for example, words beginning ``ch`` should
|
||||
sort after all other words beginnings with ``c``. **Expansion** is where
|
||||
a single letter is treated as though it were multiple letters. In German,
|
||||
``ä`` is sorted as if it were ``ae``, i.e. after ``ad`` but before ``af``.
|
||||
|
||||
## Here is how to use the ``pyuca`` module:
|
||||
``
|
||||
git clone https://github.com/jtauber/pyuca.git
|
||||
cd pyuca
|
||||
pip install pyuca
|
||||
``
|
||||
|
||||
**Usage example:**
|
||||
``
|
||||
from pyuca import Collator
|
||||
c = Collator("allkeys.txt")
|
||||
|
||||
sorted_words = sorted(words, key=c.sort_key)
|
||||
``
|
||||
|
||||
``allkeys.txt`` (1 MB) is available at
|
||||
|
||||
http://www.unicode.org/Public/UCA/latest/allkeys.txt
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import os
|
||||
import pyuca
|
||||
|
||||
unicode_collator = pyuca.Collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))
|
||||
unicode_collator = None
|
||||
|
||||
def set_unicode_collator(file):
|
||||
global unicode_collator
|
||||
unicode_collator = pyuca.Collator(file)
|
||||
|
||||
set_unicode_collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
# pyuca - Unicode Collation Algorithm
|
||||
# Version: 2006-02-13
|
||||
# Version: 2012-06-21
|
||||
#
|
||||
# James Tauber
|
||||
# http://jtauber.com/
|
||||
|
||||
# Copyright (c) 2006 James Tauber
|
||||
#
|
||||
# Copyright (c) 2006-2012 James Tauber and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
@@ -28,7 +28,6 @@
|
||||
"""
|
||||
Preliminary implementation of the Unicode Collation Algorithm.
|
||||
|
||||
|
||||
This only implements the simple parts of the algorithm but I have successfully
|
||||
tested it using the Default Unicode Collation Element Table (DUCET) to collate
|
||||
Ancient Greek correctly.
|
||||
@@ -48,31 +47,39 @@ but you can always subset this for just the characters you are dealing with.
|
||||
"""
|
||||
|
||||
|
||||
class Trie:
|
||||
|
||||
class Node:
|
||||
|
||||
def __init__(self):
|
||||
self.root = [None, {}]
|
||||
self.value = None
|
||||
self.children = {}
|
||||
|
||||
|
||||
class Trie:
|
||||
|
||||
def __init__(self):
|
||||
self.root = Node()
|
||||
|
||||
def add(self, key, value):
|
||||
curr_node = self.root
|
||||
for part in key:
|
||||
curr_node = curr_node[1].setdefault(part, [None, {}])
|
||||
curr_node[0] = value
|
||||
|
||||
curr_node = curr_node.children.setdefault(part, Node())
|
||||
curr_node.value = value
|
||||
|
||||
def find_prefix(self, key):
|
||||
curr_node = self.root
|
||||
remainder = key
|
||||
for part in key:
|
||||
if part not in curr_node[1]:
|
||||
if part not in curr_node.children:
|
||||
break
|
||||
curr_node = curr_node[1][part]
|
||||
curr_node = curr_node.children[part]
|
||||
remainder = remainder[1:]
|
||||
return (curr_node[0], remainder)
|
||||
return (curr_node.value, remainder)
|
||||
|
||||
|
||||
class Collator:
|
||||
|
||||
def __init__(self, filename):
|
||||
|
||||
self.table = Trie()
|
||||
self.load(filename)
|
||||
|
||||
@@ -85,7 +92,7 @@ class Collator:
|
||||
line = line[:line.find("#")] + "\n"
|
||||
line = line[:line.find("%")] + "\n"
|
||||
line = line.strip()
|
||||
|
||||
|
||||
if line.startswith("@"):
|
||||
pass
|
||||
else:
|
||||
@@ -96,32 +103,36 @@ class Collator:
|
||||
while True:
|
||||
begin = x.find("[")
|
||||
if begin == -1:
|
||||
break
|
||||
break
|
||||
end = x[begin:].find("]")
|
||||
collElement = x[begin:begin+end+1]
|
||||
x = x[begin + 1:]
|
||||
|
||||
|
||||
alt = collElement[1]
|
||||
chars = collElement[2:-1].split(".")
|
||||
|
||||
|
||||
collElements.append((alt, chars))
|
||||
integer_points = [int(ch, 16) for ch in charList]
|
||||
self.table.add(integer_points, collElements)
|
||||
|
||||
|
||||
def sort_key(self, string):
|
||||
|
||||
|
||||
collation_elements = []
|
||||
|
||||
|
||||
lookup_key = [ord(ch) for ch in string]
|
||||
while lookup_key:
|
||||
value, lookup_key = self.table.find_prefix(lookup_key)
|
||||
if not value:
|
||||
# @@@
|
||||
raise ValueError, map(hex, lookup_key)
|
||||
# Calculate implicit weighting for CJK Ideographs
|
||||
# contributed by David Schneider 2009-07-27
|
||||
# http://www.unicode.org/reports/tr10/#Implicit_Weights
|
||||
value = []
|
||||
value.append((".", ["%X" % (0xFB40 + (lookup_key[0] >> 15)), "0020", "0002", "0001"]))
|
||||
value.append((".", ["%X" % ((lookup_key[0] & 0x7FFF) | 0x8000), "0000", "0000", "0000"]))
|
||||
lookup_key = lookup_key[1:]
|
||||
collation_elements.extend(value)
|
||||
|
||||
sort_key = []
|
||||
|
||||
|
||||
for level in range(4):
|
||||
if level:
|
||||
sort_key.append(0) # level separator
|
||||
@@ -129,5 +140,5 @@ class Collator:
|
||||
ce_l = int(element[1][level], 16)
|
||||
if ce_l:
|
||||
sort_key.append(ce_l)
|
||||
|
||||
|
||||
return tuple(sort_key)
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
Developed by niphlod@gmail.com
|
||||
"""
|
||||
|
||||
import redis
|
||||
from redis.exceptions import ConnectionError
|
||||
from gluon import current
|
||||
from gluon.storage import Storage
|
||||
import cPickle as pickle
|
||||
import time
|
||||
import re
|
||||
import logging
|
||||
import thread
|
||||
|
||||
logger = logging.getLogger("web2py.session.redis")
|
||||
|
||||
locker = thread.allocate_lock()
|
||||
|
||||
def RedisSession(*args, **vars):
|
||||
"""
|
||||
Usage example: put in models
|
||||
from gluon.contrib.redis_session import RedisSession
|
||||
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
|
||||
session.connect(request, response, db = sessiondb)
|
||||
|
||||
Simple slip-in storage for session
|
||||
"""
|
||||
|
||||
locker.acquire()
|
||||
try:
|
||||
if not hasattr(RedisSession, 'redis_instance'):
|
||||
RedisSession.redis_instance = RedisClient(*args, **vars)
|
||||
finally:
|
||||
locker.release()
|
||||
return RedisSession.redis_instance
|
||||
|
||||
|
||||
class RedisClient(object):
|
||||
|
||||
meta_storage = {}
|
||||
MAX_RETRIES = 5
|
||||
RETRIES = 0
|
||||
|
||||
def __init__(self, server='localhost:6379', db=None, debug=False, session_expiry=False):
|
||||
"""session_expiry can be an integer, in seconds, to set the default expiration
|
||||
of sessions. The corresponding record will be deleted from the redis instance,
|
||||
and there's virtually no need to run sessions2trash.py
|
||||
"""
|
||||
self.server = server
|
||||
self.db = db or 0
|
||||
host,port = (self.server.split(':')+['6379'])[:2]
|
||||
port = int(port)
|
||||
self.debug = debug
|
||||
if current and current.request:
|
||||
self.app = current.request.application
|
||||
else:
|
||||
self.app = ''
|
||||
self.r_server = redis.Redis(host=host, port=port, db=self.db)
|
||||
self.tablename = None
|
||||
self.session_expiry = session_expiry
|
||||
|
||||
def get(self, what, default):
|
||||
return self.tablename
|
||||
|
||||
def Field(self, fieldname, type='string', length=None, default=None,
|
||||
required=False,requires=None):
|
||||
return None
|
||||
|
||||
def define_table(self,tablename,*fields,**args):
|
||||
if not self.tablename:
|
||||
self.tablename = MockTable(self, self.r_server, tablename, self.session_expiry)
|
||||
return self.tablename
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.tablename
|
||||
|
||||
def __call__(self, where=''):
|
||||
q = self.tablename.query
|
||||
return q
|
||||
|
||||
def commit(self):
|
||||
#this is only called by session2trash.py
|
||||
pass
|
||||
|
||||
class MockTable(object):
|
||||
|
||||
def __init__(self, db, r_server, tablename, session_expiry):
|
||||
self.db = db
|
||||
self.r_server = r_server
|
||||
self.tablename = tablename
|
||||
#set the namespace for sessions of this app
|
||||
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '')
|
||||
#fast auto-increment id (needed for session handling)
|
||||
self.serial = "%s:serial" % self.keyprefix
|
||||
#index of all the session keys of this app
|
||||
self.id_idx = "%s:id_idx" % self.keyprefix
|
||||
#remember the session_expiry setting
|
||||
self.session_expiry = session_expiry
|
||||
|
||||
def getserial(self):
|
||||
#return an auto-increment id
|
||||
return "%s" % self.r_server.incr(self.serial, 1)
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key == 'id':
|
||||
#return a fake query. We need to query it just by id for normal operations
|
||||
self.query = MockQuery(field='id', db=self.r_server, prefix=self.keyprefix, session_expiry=self.session_expiry)
|
||||
return self.query
|
||||
elif key == '_db':
|
||||
#needed because of the calls in sessions2trash.py and globals.py
|
||||
return self.db
|
||||
|
||||
def insert(self, **kwargs):
|
||||
#usually kwargs would be a Storage with several keys:
|
||||
#'locked', 'client_ip','created_datetime','modified_datetime'
|
||||
#'unique_key', 'session_data'
|
||||
#retrieve a new key
|
||||
newid = self.getserial()
|
||||
key = "%s:%s" % (self.keyprefix, newid)
|
||||
#add it to the index
|
||||
self.r_server.sadd(self.id_idx, key)
|
||||
#set a hash key with the Storage
|
||||
self.r_server.hmset(key, kwargs)
|
||||
if self.session_expiry:
|
||||
self.r_server.expire(key, self.session_expiry)
|
||||
return newid
|
||||
|
||||
class MockQuery(object):
|
||||
"""a fake Query object that supports querying by id
|
||||
and listing all keys. No other operation is supported
|
||||
"""
|
||||
def __init__(self, field=None, db=None, prefix=None, session_expiry=False):
|
||||
self.field = field
|
||||
self.value = None
|
||||
self.db = db
|
||||
self.keyprefix = prefix
|
||||
self.op = None
|
||||
self.session_expiry = session_expiry
|
||||
|
||||
def __eq__(self, value, op='eq'):
|
||||
self.value = value
|
||||
self.op = op
|
||||
|
||||
def __gt__(self, value, op='ge'):
|
||||
self.value = value
|
||||
self.op = op
|
||||
|
||||
def select(self):
|
||||
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||
#means that someone wants to retrieve the key self.value
|
||||
rtn = self.db.hgetall("%s:%s" % (self.keyprefix, self.value))
|
||||
if rtn == dict():
|
||||
#return an empty resultset for non existing key
|
||||
return []
|
||||
else:
|
||||
return [Storage(rtn)]
|
||||
elif self.op == 'ge' and self.field == 'id' and self.value == 0:
|
||||
#means that someone wants the complete list
|
||||
rtn = []
|
||||
id_idx = "%s:id_idx" % self.keyprefix
|
||||
#find all session keys of this app
|
||||
allkeys = self.db.smembers(id_idx)
|
||||
for sess in allkeys:
|
||||
val = self.db.hgetall(sess)
|
||||
if val == dict():
|
||||
if self.session_expiry:
|
||||
#clean up the idx, because the key expired
|
||||
self.db.srem(id_idx, sess)
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
val = Storage(val)
|
||||
#add a delete_record method (necessary for sessions2trash.py)
|
||||
val.delete_record = RecordDeleter(self.db, sess, self.keyprefix)
|
||||
rtn.append(val)
|
||||
return rtn
|
||||
else:
|
||||
raise Exception("Operation not supported")
|
||||
|
||||
def update(self, **kwargs):
|
||||
#means that the session has been found and needs an update
|
||||
if self.op == 'eq' and self.field == 'id' and self.value:
|
||||
rtn = self.db.hmset("%s:%s" % (self.keyprefix, self.value), kwargs)
|
||||
if self.session_expiry:
|
||||
self.db.expire(key, self.session.expiry)
|
||||
return rtn
|
||||
|
||||
class RecordDeleter(object):
|
||||
"""Dumb record deleter to support sessions2trash.py"""
|
||||
|
||||
def __init__(self, db, key, keyprefix):
|
||||
self.db, self.key, self.keyprefix = db, key, keyprefix
|
||||
|
||||
def __call__(self):
|
||||
id_idx = "%s:id_idx" % self.keyprefix
|
||||
#remove from the index
|
||||
self.db.srem(id_idx, self.key)
|
||||
#remove the key itself
|
||||
self.db.delete(self.key)
|
||||
|
||||
|
||||
@@ -12,18 +12,18 @@ Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.
|
||||
|
||||
2) start this app:
|
||||
|
||||
python gluon/contrib/comet_messaging.py -k mykey -p 8888
|
||||
python gluon/contrib/websocket_messaging.py -k mykey -p 8888
|
||||
|
||||
3) from any web2py app you can post messages with
|
||||
|
||||
from gluon.contrib.comet_messaging import comet_send
|
||||
comet_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
|
||||
from gluon.contrib.websocket_messaging import websocket_send
|
||||
websocket_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
|
||||
|
||||
4) from any template you can receive them with
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
|
||||
if(!web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
|
||||
alert("html5 websocket not supported by your browser, try Google Chrome");
|
||||
});
|
||||
</script>
|
||||
@@ -34,14 +34,14 @@ Or if you want to send json messages and store evaluated json in a var called da
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var data;
|
||||
web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
|
||||
web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
|
||||
});
|
||||
</script>
|
||||
|
||||
- All communications between web2py and comet_messaging will be digitally signed with hmac.
|
||||
- All validation is handled on the web2py side and there is no need to modify comet_messaging.py
|
||||
- Multiple web2py instances can talk with one or more comet_messaging servers.
|
||||
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the comet_messaging server.
|
||||
- All communications between web2py and websocket_messaging will be digitally signed with hmac.
|
||||
- All validation is handled on the web2py side and there is no need to modify websocket_messaging.py
|
||||
- Multiple web2py instances can talk with one or more websocket_messaging servers.
|
||||
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the websocket_messaging server.
|
||||
- Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms)
|
||||
|
||||
Here is a complete sample web2py action:
|
||||
@@ -51,7 +51,7 @@ Here is a complete sample web2py action:
|
||||
script=SCRIPT('''
|
||||
jQuery(document).ready(function(){
|
||||
var callback=function(e){alert(e.data)};
|
||||
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',callback))
|
||||
if(!web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',callback))
|
||||
alert("html5 websocket not supported by your browser, try Google Chrome");
|
||||
});
|
||||
''')
|
||||
@@ -60,8 +60,8 @@ Here is a complete sample web2py action:
|
||||
def ajax_form():
|
||||
form=SQLFORM.factory(Field('message'))
|
||||
if form.accepts(request,session):
|
||||
from gluon.contrib.comet_messaging import comet_send
|
||||
comet_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
|
||||
from gluon.contrib.websocket_messaging import websocket_send
|
||||
websocket_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
|
||||
return form
|
||||
|
||||
Acknowledgements:
|
||||
@@ -83,7 +83,7 @@ listeners = {}
|
||||
names = {}
|
||||
tokens = {}
|
||||
|
||||
def comet_send(url,message,hmac_key=None,group='default'):
|
||||
def websocket_send(url,message,hmac_key=None,group='default'):
|
||||
sig = hmac_key and hmac.new(hmac_key,message).hexdigest() or ''
|
||||
params = urllib.urlencode({'message': message, 'signature': sig, 'group':group})
|
||||
f = urllib.urlopen(url, params)
|
||||
+94
-239
@@ -6,153 +6,137 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from gluon import current
|
||||
|
||||
# Install the new import function:
|
||||
def custom_import_install(web2py_path):
|
||||
global _web2py_importer
|
||||
global _web2py_path
|
||||
if isinstance(__builtin__.__import__, _Web2pyImporter):
|
||||
return #aready installed
|
||||
_web2py_path = web2py_path
|
||||
_web2py_importer = _Web2pyImporter(web2py_path)
|
||||
__builtin__.__import__ = _web2py_importer
|
||||
NATIVE_IMPORTER = __builtin__.__import__
|
||||
INVALID_MODULES = set(('','gluon','applications','custom_import'))
|
||||
|
||||
def is_tracking_changes():
|
||||
"""
|
||||
@return: True: neo_importer is tracking changes made to Python source
|
||||
files. False: neo_import does not reload Python modules.
|
||||
"""
|
||||
|
||||
global _is_tracking_changes
|
||||
return _is_tracking_changes
|
||||
# backward compatibility API
|
||||
def custom_import_install():
|
||||
if __builtin__.__import__ != custom_importer:
|
||||
INVALID_MODULES.update(sys.modules.keys())
|
||||
__builtin__.__import__ = custom_importer
|
||||
|
||||
def track_changes(track=True):
|
||||
assert track in (True,False), "must be True or False"
|
||||
current.request._custom_import_track_changes = track
|
||||
|
||||
def is_tracking_changes():
|
||||
return current.request._custom_import_track_changes
|
||||
|
||||
class CustomImportException(ImportError):
|
||||
pass
|
||||
|
||||
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
||||
"""
|
||||
Tell neo_importer to start/stop tracking changes made to Python modules.
|
||||
@param track: True: Start tracking changes. False: Stop tracking changes.
|
||||
The web2py custom importer. Like the standard Python importer but it
|
||||
tries to transform import statements as something like
|
||||
"import applications.app_name.modules.x".
|
||||
If the import failed, fall back on naive_importer
|
||||
"""
|
||||
|
||||
global _is_tracking_changes
|
||||
global _web2py_importer
|
||||
global _web2py_date_tracker_importer
|
||||
assert track is True or track is False, "Boolean expected."
|
||||
if track == _is_tracking_changes:
|
||||
return
|
||||
if track:
|
||||
if not _web2py_date_tracker_importer:
|
||||
_web2py_date_tracker_importer = \
|
||||
_Web2pyDateTrackerImporter(_web2py_path)
|
||||
__builtin__.__import__ = _web2py_date_tracker_importer
|
||||
else:
|
||||
__builtin__.__import__ = _web2py_importer
|
||||
_is_tracking_changes = track
|
||||
globals = globals or {}
|
||||
locals = locals or {}
|
||||
fromlist = fromlist or []
|
||||
|
||||
_STANDARD_PYTHON_IMPORTER = __builtin__.__import__ # Keep standard importer
|
||||
_web2py_importer = None # The standard web2py importer
|
||||
_web2py_date_tracker_importer = None # The web2py importer with date tracking
|
||||
_web2py_path = None # Absolute path of the web2py directory
|
||||
try:
|
||||
if current.request._custom_import_track_changes:
|
||||
base_importer = TRACK_IMPORTER
|
||||
else:
|
||||
base_importer = NATIVE_IMPORTER
|
||||
except: # there is no current.request (should never happen)
|
||||
base_importer = NATIVE_IMPORTER
|
||||
|
||||
_is_tracking_changes = False # The tracking mode
|
||||
# if not relative and not from applications:
|
||||
if hasattr(current,'request') \
|
||||
and level<=0 \
|
||||
and not name.split('.')[0] in INVALID_MODULES \
|
||||
and isinstance(globals, dict):
|
||||
import_tb = None
|
||||
try:
|
||||
items = current.request.folder.split(os.path.sep)
|
||||
if not items[-1]: items = items[:-1]
|
||||
modules_prefix = '.'.join(items[-2:])+'.modules'
|
||||
if not fromlist:
|
||||
# import like "import x" or "import x.y"
|
||||
result = None
|
||||
for itemname in name.split("."):
|
||||
new_mod = base_importer(
|
||||
modules_prefix, globals,locals, [itemname], level)
|
||||
try:
|
||||
result = result or new_mod.__dict__[itemname]
|
||||
except KeyError, e:
|
||||
raise ImportError, 'Cannot import module %s' % str(e)
|
||||
modules_prefix += "." + itemname
|
||||
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:
|
||||
import_tb = sys.exc_info()[2]
|
||||
try:
|
||||
return NATIVE_IMPORTER(name,globals,locals,fromlist,level)
|
||||
except ImportError, e3:
|
||||
raise ImportError, e1, import_tb # there an import error in the module
|
||||
except Exception, e2:
|
||||
raise e2 # there is an error in the module
|
||||
finally:
|
||||
if import_tb:
|
||||
import_tb = None
|
||||
|
||||
class _BaseImporter(object):
|
||||
"""
|
||||
The base importer. Dispatch the import the call to the standard Python
|
||||
importer.
|
||||
"""
|
||||
|
||||
def begin(self):
|
||||
"""
|
||||
Many imports can be made for a single import statement. This method
|
||||
help the management of this aspect.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._STANDARD_PYTHON_IMPORTER = _STANDARD_PYTHON_IMPORTER
|
||||
def __call__(self, name, globals=None, locals=None,
|
||||
fromlist=None, level=-1):
|
||||
"""
|
||||
The import method itself.
|
||||
"""
|
||||
return self._STANDARD_PYTHON_IMPORTER(name,
|
||||
globals,
|
||||
locals,
|
||||
fromlist,
|
||||
level)
|
||||
|
||||
def end(self):
|
||||
"""
|
||||
Needed for clean up.
|
||||
"""
|
||||
return NATIVE_IMPORTER(name,globals,locals,fromlist,level)
|
||||
|
||||
|
||||
class _DateTrackerImporter(_BaseImporter):
|
||||
class TrackImporter(object):
|
||||
"""
|
||||
An importer tracking the date of the module files and reloading them when
|
||||
they have changed.
|
||||
"""
|
||||
|
||||
_PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
|
||||
THREAD_LOCAL = threading.local()
|
||||
PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
|
||||
|
||||
def __init__(self):
|
||||
super(_DateTrackerImporter, self).__init__()
|
||||
self._import_dates = {} # Import dates of the files of the modules
|
||||
# Avoid reloading cause by file modifications of reload:
|
||||
self._tl = threading.local()
|
||||
self._tl._modules_loaded = None
|
||||
|
||||
def begin(self):
|
||||
self._tl._modules_loaded = set()
|
||||
|
||||
def __call__(self, name, globals=None, locals=None,
|
||||
fromlist=None, level=-1):
|
||||
def __call__(self,name,globals=None,locals=None,fromlist=None,level=-1):
|
||||
"""
|
||||
The import method itself.
|
||||
"""
|
||||
|
||||
globals = globals or {}
|
||||
locals = locals or {}
|
||||
fromlist = fromlist or []
|
||||
|
||||
call_begin_end = self._tl._modules_loaded is None
|
||||
if call_begin_end:
|
||||
self.begin()
|
||||
if not hasattr(self.THREAD_LOCAL,'_modules_loaded'):
|
||||
self.THREAD_LOCAL._modules_loaded = set()
|
||||
try:
|
||||
self._tl.globals = globals
|
||||
self._tl.locals = locals
|
||||
self._tl.level = level
|
||||
|
||||
# Check the date and reload if needed:
|
||||
self._update_dates(name, fromlist)
|
||||
|
||||
self._update_dates(name, globals, locals, fromlist, level)
|
||||
# Try to load the module and update the dates if it works:
|
||||
result = super(_DateTrackerImporter, self) \
|
||||
.__call__(name, globals, locals, fromlist, level)
|
||||
result = NATIVE_IMPORTER(name, globals, locals, fromlist, level)
|
||||
# Module maybe loaded for the 1st time so we need to set the date
|
||||
self._update_dates(name, fromlist)
|
||||
self._update_dates(name, globals, locals, fromlist, level)
|
||||
return result
|
||||
except Exception:
|
||||
except Exception, e:
|
||||
raise # Don't hide something that went wrong
|
||||
finally:
|
||||
if call_begin_end:
|
||||
self.end()
|
||||
|
||||
def _update_dates(self, name, fromlist):
|
||||
def _update_dates(self, name, globals, locals, fromlist, level):
|
||||
"""
|
||||
Update all the dates associated to the statement import. A single
|
||||
import statement may import many modules.
|
||||
"""
|
||||
|
||||
self._reload_check(name)
|
||||
if fromlist:
|
||||
for fromlist_name in fromlist:
|
||||
self._reload_check("%s.%s" % (name, fromlist_name))
|
||||
self._reload_check(name, globals, locals, level)
|
||||
for fromlist_name in fromlist or []:
|
||||
pname = "%s.%s" % (name, fromlist_name)
|
||||
self._reload_check(pname, globals, locals, level)
|
||||
|
||||
def _reload_check(self, name):
|
||||
def _reload_check(self, name, globals, locals, level):
|
||||
"""
|
||||
Update the date associated to the module and reload the module if
|
||||
the file has changed.
|
||||
"""
|
||||
|
||||
module = sys.modules.get(name)
|
||||
file = self._get_module_file(module)
|
||||
if file:
|
||||
@@ -170,7 +154,7 @@ class _DateTrackerImporter(_BaseImporter):
|
||||
# Get path without file ext:
|
||||
file = os.path.splitext(file)[0]
|
||||
reload_mod = os.path.isdir(file) \
|
||||
and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX)
|
||||
and os.path.isfile(file+self.PACKAGE_PATH_SUFFIX)
|
||||
mod_to_pack = reload_mod
|
||||
else: # Package turning into module?
|
||||
file += ".py"
|
||||
@@ -180,156 +164,27 @@ class _DateTrackerImporter(_BaseImporter):
|
||||
if reload_mod or not date or new_date > date:
|
||||
self._import_dates[file] = new_date
|
||||
if reload_mod or (date and new_date > date):
|
||||
if module not in self._tl._modules_loaded:
|
||||
if module not in self.THREAD_LOCAL._modules_loaded:
|
||||
if mod_to_pack:
|
||||
# Module turning into a package:
|
||||
mod_name = module.__name__
|
||||
del sys.modules[mod_name] # Delete the module
|
||||
# Reload the module:
|
||||
super(_DateTrackerImporter, self).__call__ \
|
||||
(mod_name, self._tl.globals, self._tl.locals, [],
|
||||
self._tl.level)
|
||||
NATIVE_IMPORTER(mod_name, globals, locals, [], level)
|
||||
else:
|
||||
reload(module)
|
||||
self._tl._modules_loaded.add(module)
|
||||
self.THREAD_LOCAL._modules_loaded.add(module)
|
||||
|
||||
def end(self):
|
||||
self._tl._modules_loaded = None
|
||||
|
||||
@classmethod
|
||||
def _get_module_file(cls, module):
|
||||
def _get_module_file(self, module):
|
||||
"""
|
||||
Get the absolute path file associated to the module or None.
|
||||
"""
|
||||
|
||||
file = getattr(module, "__file__", None)
|
||||
if file:
|
||||
# Make path absolute if not:
|
||||
#file = os.path.join(cls.web2py_path, file)
|
||||
|
||||
file = os.path.splitext(file)[0]+".py" # Change .pyc for .py
|
||||
if file.endswith(cls._PACKAGE_PATH_SUFFIX):
|
||||
if file.endswith(self.PACKAGE_PATH_SUFFIX):
|
||||
file = os.path.dirname(file) # Track dir for packages
|
||||
return file
|
||||
|
||||
class _Web2pyImporter(_BaseImporter):
|
||||
"""
|
||||
The standard web2py importer. Like the standard Python importer but it
|
||||
tries to transform import statements as something like
|
||||
"import applications.app_name.modules.x". If the import failed, fall back
|
||||
on _BaseImporter.
|
||||
"""
|
||||
|
||||
_RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re
|
||||
|
||||
def __init__(self, web2py_path):
|
||||
"""
|
||||
@param web2py_path: The absolute path of the web2py installation.
|
||||
"""
|
||||
|
||||
global DEBUG
|
||||
self.super_class = super(_Web2pyImporter, self)
|
||||
self.super_class.__init__()
|
||||
self.web2py_path = web2py_path
|
||||
self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
|
||||
self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
|
||||
self.__RE_APP_DIR = re.compile(
|
||||
self._RE_ESCAPED_PATH_SEP.join( \
|
||||
( \
|
||||
#"^" + re.escape(web2py_path), # Not working with Python 2.5
|
||||
"^(" + "applications",
|
||||
"[^",
|
||||
"]+)",
|
||||
"",
|
||||
) ))
|
||||
|
||||
def _matchAppDir(self, file_path):
|
||||
"""
|
||||
Does the file in a directory inside the "applications" directory?
|
||||
"""
|
||||
|
||||
if file_path.startswith(self.__web2py_path_os_path_sep):
|
||||
file_path = file_path[self.__web2py_path_os_path_sep_len:]
|
||||
return self.__RE_APP_DIR.match(file_path)
|
||||
return False
|
||||
|
||||
def __call__(self, name, globals=None, locals=None,
|
||||
fromlist=None, level=-1):
|
||||
"""
|
||||
The import method itself.
|
||||
"""
|
||||
|
||||
globals = globals or {}
|
||||
locals = locals or {}
|
||||
fromlist = fromlist or []
|
||||
|
||||
self.begin()
|
||||
#try:
|
||||
# if not relative and not from applications:
|
||||
if not name.startswith(".") and level <= 0 \
|
||||
and not name.startswith("applications.") \
|
||||
and isinstance(globals, dict):
|
||||
# Get the name of the file do the import
|
||||
caller_file_name = os.path.join(self.web2py_path, \
|
||||
globals.get("__file__", ""))
|
||||
# Is the path in an application directory?
|
||||
match_app_dir = self._matchAppDir(caller_file_name)
|
||||
if match_app_dir:
|
||||
try:
|
||||
# Get the prefix to add for the import
|
||||
# (like applications.app_name.modules):
|
||||
modules_prefix = \
|
||||
".".join((match_app_dir.group(1). \
|
||||
replace(os.path.sep, "."), "modules"))
|
||||
if not fromlist:
|
||||
# import like "import x" or "import x.y"
|
||||
return self.__import__dot(modules_prefix, name,
|
||||
globals, locals, fromlist, level)
|
||||
else:
|
||||
# import like "from x import a, b, ..."
|
||||
return self.super_class \
|
||||
.__call__(modules_prefix+"."+name,
|
||||
globals, locals, fromlist, level)
|
||||
except ImportError, e:
|
||||
try:
|
||||
return self.super_class.__call__(name, globals, locals,
|
||||
fromlist, level)
|
||||
except ImportError, e1:
|
||||
raise e
|
||||
return self.super_class.__call__(name, globals, locals,
|
||||
fromlist, level)
|
||||
|
||||
def __import__dot(self, prefix, name, globals, locals, fromlist,
|
||||
level):
|
||||
"""
|
||||
Here we will import x.y.z as many imports like:
|
||||
from applications.app_name.modules import x
|
||||
from applications.app_name.modules.x import y
|
||||
from applications.app_name.modules.x.y import z.
|
||||
x will be the module returned.
|
||||
"""
|
||||
|
||||
result = None
|
||||
for name in name.split("."):
|
||||
new_mod = super(_Web2pyImporter, self).__call__(prefix, globals,
|
||||
locals, [name], level)
|
||||
try:
|
||||
result = result or new_mod.__dict__[name]
|
||||
except KeyError, e:
|
||||
raise ImportError, 'Cannot import module %s' % str(e)
|
||||
prefix += "." + name
|
||||
return result
|
||||
|
||||
class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
|
||||
"""
|
||||
Like _Web2pyImporter but using a _DateTrackerImporter.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TRACK_IMPORTER = TrackImporter()
|
||||
|
||||
+620
-567
File diff suppressed because it is too large
Load Diff
+33
-21
@@ -128,6 +128,7 @@ class Request(Storage):
|
||||
and secure the session.
|
||||
"""
|
||||
if not global_settings.cronjob and not self.is_https:
|
||||
current.session.forget()
|
||||
redirect(URL(scheme='https', args=self.args, vars=self.vars))
|
||||
|
||||
current.session.secure()
|
||||
@@ -229,7 +230,7 @@ class Response(Storage):
|
||||
for k,v in (self.meta or {}).iteritems())
|
||||
self.write(s,escape=False)
|
||||
|
||||
def include_files(self):
|
||||
def include_files(self, extensions=None):
|
||||
|
||||
"""
|
||||
Caching method for writing out files.
|
||||
@@ -240,11 +241,22 @@ class Response(Storage):
|
||||
from gluon import URL
|
||||
|
||||
files = []
|
||||
has_js = has_css = False
|
||||
for item in self.files:
|
||||
if not item in files: files.append(item)
|
||||
if have_minify and (self.optimize_css or self.optimize_js):
|
||||
if extensions and not item.split('.')[-1] in extensions:
|
||||
continue
|
||||
if item in files:
|
||||
continue
|
||||
if item.endswith('.js'):
|
||||
has_js = True
|
||||
if item.endswith('.css'):
|
||||
has_css = True
|
||||
files.append(item)
|
||||
|
||||
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
|
||||
# cache for 5 minutes by default
|
||||
key = hashlib.md5(repr(files)).hexdigest()
|
||||
|
||||
cache = self.cache_includes or (current.cache.ram, 60*5)
|
||||
def call_minify(files=files):
|
||||
return minify.minify(files,
|
||||
@@ -263,6 +275,8 @@ class Response(Storage):
|
||||
for item in files:
|
||||
if isinstance(item,str):
|
||||
f = item.lower().split('?')[0]
|
||||
if self.static_version:
|
||||
item = item.replace('/static/', '/static/_%s/' % self.static_version, 1)
|
||||
if f.endswith('.css'): s += css_template % item
|
||||
elif f.endswith('.js'): s += js_template % item
|
||||
elif f.endswith('.coffee'): s += coffee_template % item
|
||||
@@ -316,7 +330,8 @@ class Response(Storage):
|
||||
stream_file_or_304_or_206(stream,
|
||||
chunk_size=chunk_size,
|
||||
request=request,
|
||||
headers=headers)
|
||||
headers=headers,
|
||||
status=self.status)
|
||||
|
||||
# ## the following is for backward compatibility
|
||||
if hasattr(stream, 'name'):
|
||||
@@ -402,15 +417,15 @@ class Response(Storage):
|
||||
return handler(request, self, methods)
|
||||
|
||||
def toolbar(self):
|
||||
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL
|
||||
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
|
||||
BUTTON = TAG.button
|
||||
admin = URL("admin","default","design",
|
||||
args=current.request.application)
|
||||
from gluon.dal import thread
|
||||
if hasattr(thread,'instances'):
|
||||
from gluon.dal import THREAD_LOCAL
|
||||
if hasattr(THREAD_LOCAL,'instances'):
|
||||
dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \
|
||||
for row in i.db._timings]) \
|
||||
for i in thread.instances]
|
||||
for i in THREAD_LOCAL.instances]
|
||||
dbtables = dict([(regex_nopasswd.sub('******',i.uri),
|
||||
{'defined':
|
||||
sorted(list(set(i.db.tables) -
|
||||
@@ -418,24 +433,26 @@ class Response(Storage):
|
||||
'[no defined tables]',
|
||||
'lazy': sorted(i.db._LAZY_TABLES.keys()) or
|
||||
'[no lazy tables]'})
|
||||
for i in thread.instances])
|
||||
for i in THREAD_LOCAL.instances])
|
||||
else:
|
||||
dbstats = [] # if no db or on GAE
|
||||
dbtables = {}
|
||||
u = web2py_uuid()
|
||||
backtotop = A('Back to top', _href="#totop-%s" % u)
|
||||
return DIV(
|
||||
BUTTON('design',_onclick="document.location='%s'" % admin),
|
||||
BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u),
|
||||
DIV(BEAUTIFY(current.request),_class="hidden",_id="request-%s"%u),
|
||||
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u),
|
||||
DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u),
|
||||
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
|
||||
DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u),
|
||||
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u),
|
||||
BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u),
|
||||
DIV(BEAUTIFY(dbtables),_class="hidden",_id="db-tables-%s"%u),
|
||||
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
|
||||
DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u),
|
||||
DIV(BEAUTIFY(current.request), backtotop,_class="hidden",_id="request-%s"%u),
|
||||
DIV(BEAUTIFY(current.session), backtotop, _class="hidden",_id="session-%s"%u),
|
||||
DIV(BEAUTIFY(current.response), backtotop, _class="hidden",_id="response-%s"%u),
|
||||
DIV(BEAUTIFY(dbtables), backtotop, _class="hidden",_id="db-tables-%s"%u),
|
||||
DIV(BEAUTIFY(dbstats), backtotop, _class="hidden",_id="db-stats-%s"%u),
|
||||
SCRIPT("jQuery('.hidden').hide()")
|
||||
,_id="totop-%s" % u
|
||||
)
|
||||
|
||||
class Session(Storage):
|
||||
@@ -467,7 +484,7 @@ class Session(Storage):
|
||||
if not masterapp:
|
||||
masterapp = request.application
|
||||
response.session_id_name = 'session_id_%s' % masterapp.lower()
|
||||
|
||||
|
||||
# Load session data from cookie
|
||||
cookies = request.cookies
|
||||
|
||||
@@ -703,8 +720,3 @@ class Session(Storage):
|
||||
del response.session_file
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+38
-16
@@ -32,6 +32,11 @@ regex_crlf = re.compile('\r|\n')
|
||||
|
||||
join = ''.join
|
||||
|
||||
# name2codepoint is incomplete respect to xhtml (and xml): 'apos' is missing.
|
||||
entitydefs = dict(map(lambda (k,v): (k, unichr(v).encode('utf-8')), name2codepoint.iteritems()))
|
||||
entitydefs.setdefault('apos', u"'".encode('utf-8'))
|
||||
|
||||
|
||||
__all__ = [
|
||||
'A',
|
||||
'B',
|
||||
@@ -243,7 +248,7 @@ def URL(
|
||||
elif a and c and not f: (c,f,a)=(a,c,f)
|
||||
from globals import current
|
||||
if hasattr(current,'request'):
|
||||
r = current.request
|
||||
r = current.request
|
||||
|
||||
if r:
|
||||
application = r.application
|
||||
@@ -487,7 +492,7 @@ class XmlComponent(object):
|
||||
components += [other]
|
||||
return CAT(*components)
|
||||
|
||||
def add_class(self, name):
|
||||
def add_class(self, name):
|
||||
""" add a class to _class attribute """
|
||||
c = self['_class']
|
||||
classes = (set(c.split()) if c else set())|set(name.split())
|
||||
@@ -1745,7 +1750,7 @@ class INPUT(DIV):
|
||||
elif not t == 'submit':
|
||||
if value is None:
|
||||
self['value'] = _value
|
||||
else:
|
||||
elif not isinstance(value,list):
|
||||
self['_value'] = value
|
||||
|
||||
def xml(self):
|
||||
@@ -1938,6 +1943,7 @@ class FORM(DIV):
|
||||
# check formname and formkey
|
||||
|
||||
status = True
|
||||
changed = False
|
||||
request_vars = self.request_vars
|
||||
if session:
|
||||
formkey = session.get('_formkey[%s]' % formname, None)
|
||||
@@ -1950,18 +1956,23 @@ class FORM(DIV):
|
||||
# check if editing a record that has been modified by the server
|
||||
if hasattr(self,'record_hash') and self.record_hash != formkey:
|
||||
status = False
|
||||
self.record_changed = True
|
||||
self.record_changed = changed = True
|
||||
status = self._traverse(status,hideerror)
|
||||
status = self.assert_status(status, request_vars)
|
||||
if onvalidation:
|
||||
if isinstance(onvalidation, dict):
|
||||
onsuccess = onvalidation.get('onsuccess', None)
|
||||
onfailure = onvalidation.get('onfailure', None)
|
||||
onchange = onvalidation.get('onchange', None)
|
||||
if onsuccess and status:
|
||||
onsuccess(self)
|
||||
if onfailure and request_vars and not status:
|
||||
onfailure(self)
|
||||
status = len(self.errors) == 0
|
||||
if changed:
|
||||
if onchange and self.record_changed and \
|
||||
self.detect_record_change:
|
||||
onchange(self)
|
||||
elif status:
|
||||
if isinstance(onvalidation, (list, tuple)):
|
||||
[f(self) for f in onvalidation]
|
||||
@@ -2030,8 +2041,13 @@ class FORM(DIV):
|
||||
onfailure = 'flash' - will show message_onfailure in response.flash
|
||||
None - will do nothing
|
||||
can be a function (lambda form: pass)
|
||||
onchange = 'flash' - will show message_onchange in response.flash
|
||||
None - will do nothing
|
||||
can be a function (lambda form: pass)
|
||||
|
||||
message_onsuccess
|
||||
message_onfailure
|
||||
message_onchange
|
||||
next = where to redirect in case of success
|
||||
any other kwargs will be passed for form.accepts(...)
|
||||
"""
|
||||
@@ -2042,13 +2058,17 @@ class FORM(DIV):
|
||||
|
||||
onsuccess = kwargs.get('onsuccess','flash')
|
||||
onfailure = kwargs.get('onfailure','flash')
|
||||
onchange = kwargs.get('onchange', 'flash')
|
||||
message_onsuccess = kwargs.get('message_onsuccess',
|
||||
current.T("Success!"))
|
||||
message_onfailure = kwargs.get('message_onfailure',
|
||||
current.T("Errors in form, please check it out."))
|
||||
message_onchange = kwargs.get('message_onchange',
|
||||
current.T("Form consecutive submissions not allowed. " +
|
||||
"Try re-submitting or refreshing the form page."))
|
||||
next = kwargs.get('next',None)
|
||||
for key in ('message_onsuccess','message_onfailure','onsuccess',
|
||||
'onfailure','next'):
|
||||
'onfailure','next', 'message_onchange', 'onchange'):
|
||||
if key in kwargs:
|
||||
del kwargs[key]
|
||||
|
||||
@@ -2075,6 +2095,13 @@ class FORM(DIV):
|
||||
elif callable(onfailure):
|
||||
onfailure(self)
|
||||
return False
|
||||
elif hasattr(self, "record_changed"):
|
||||
if self.record_changed and self.detect_record_change:
|
||||
if onchange == 'flash':
|
||||
current.response.flash = message_onchange
|
||||
elif callable(onchange):
|
||||
onchange(self)
|
||||
return False
|
||||
|
||||
def process(self, **kwargs):
|
||||
"""
|
||||
@@ -2111,8 +2138,10 @@ class FORM(DIV):
|
||||
REDIRECT_JS = "window.location='%s';return false"
|
||||
|
||||
def add_button(self,value,url,_class=None):
|
||||
self[0][-1][1].append(INPUT(_type="button",_value=value,_class=_class,
|
||||
_onclick=self.REDIRECT_JS % url))
|
||||
submit = self.element('input[type=submit]')
|
||||
submit.parent.append(
|
||||
INPUT(_type="button",_value=value,_class=_class,
|
||||
_onclick=self.REDIRECT_JS % url))
|
||||
|
||||
|
||||
|
||||
@@ -2276,7 +2305,7 @@ class MENU(DIV):
|
||||
select = SELECT(**self.attributes)
|
||||
for item in data:
|
||||
if len(item) <= 4 or item[4] == True:
|
||||
select.append(OPTION(CAT(prefix, item[0]),
|
||||
select.append(OPTION(CAT(prefix, item[0]),
|
||||
_value=item[2], _selected=item[1]))
|
||||
if len(item)>3 and len(item[3]):
|
||||
self.serialize_mobile(
|
||||
@@ -2402,7 +2431,7 @@ class web2pyHTMLParser(HTMLParser):
|
||||
else:
|
||||
self.parent.append(unichr(int(name)).encode('utf8'))
|
||||
def handle_entityref(self,name):
|
||||
self.parent.append(unichr(name2codepoint[name]).encode('utf8'))
|
||||
self.parent.append(entitydefs[name])
|
||||
def handle_endtag(self, tagname):
|
||||
# this deals with unbalanced tags
|
||||
if tagname==self.last:
|
||||
@@ -2506,10 +2535,3 @@ class MARKMIN(XmlComponent):
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ defined_status = {
|
||||
415: 'UNSUPPORTED MEDIA TYPE',
|
||||
416: 'REQUESTED RANGE NOT SATISFIABLE',
|
||||
417: 'EXPECTATION FAILED',
|
||||
422: 'UNPROCESSABLE ENTITY',
|
||||
500: 'INTERNAL SERVER ERROR',
|
||||
501: 'NOT IMPLEMENTED',
|
||||
502: 'BAD GATEWAY',
|
||||
|
||||
+2
-2
@@ -155,9 +155,9 @@ def read_possible_plural_rules():
|
||||
create list of all possible plural rules files
|
||||
result is cached in PLURAL_RULES dictionary to increase speed
|
||||
"""
|
||||
plurals = {}
|
||||
try:
|
||||
import gluon.contrib.plural_rules as package
|
||||
plurals = {}
|
||||
import contrib.plural_rules as package
|
||||
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
|
||||
if len(modname)==2:
|
||||
module = __import__(package.__name__+'.'+modname,
|
||||
|
||||
+49
-36
@@ -35,8 +35,6 @@ from settings import global_settings
|
||||
from admin import add_path_first, create_missing_folders, create_missing_app_folders
|
||||
from globals import current
|
||||
|
||||
from custom_import import custom_import_install
|
||||
|
||||
# Remarks:
|
||||
# calling script has inserted path to script directory into sys.path
|
||||
# applications_parent (path to applications/, site-packages/ etc)
|
||||
@@ -55,8 +53,6 @@ from custom_import import custom_import_install
|
||||
|
||||
web2py_path = global_settings.applications_parent # backward compatibility
|
||||
|
||||
custom_import_install(web2py_path)
|
||||
|
||||
create_missing_folders()
|
||||
|
||||
# set up logging for subsequent imports
|
||||
@@ -88,10 +84,10 @@ from contenttype import contenttype
|
||||
from dal import BaseAdapter
|
||||
from settings import global_settings
|
||||
from validators import CRYPT
|
||||
from cache import Cache
|
||||
from cache import CacheInRam
|
||||
from html import URL, xmlescape
|
||||
from utils import is_valid_ip_address
|
||||
from rewrite import load, url_in, thread as rwthread, \
|
||||
from rewrite import load, url_in, THREAD_LOCAL as rwthread, \
|
||||
try_rewrite_on_error, fixup_missing_path_info
|
||||
import newcron
|
||||
|
||||
@@ -123,6 +119,8 @@ except:
|
||||
|
||||
load()
|
||||
|
||||
HTTPS_SCHEMES = set(('https','HTTPS'))
|
||||
|
||||
def get_client(env):
|
||||
"""
|
||||
guess the client address from the environment variables
|
||||
@@ -132,7 +130,7 @@ def get_client(env):
|
||||
"""
|
||||
g = regex_client.search(env.get('http_x_forwarded_for', ''))
|
||||
if g:
|
||||
client = g.group()
|
||||
client = (g.group() or '').split(',')[0]
|
||||
else:
|
||||
g = regex_client.search(env.get('remote_addr', ''))
|
||||
if g:
|
||||
@@ -146,29 +144,32 @@ def get_client(env):
|
||||
def copystream_progress(request, chunk_size= 10**5):
|
||||
"""
|
||||
copies request.env.wsgi_input into request.body
|
||||
and stores progress upload status in cache.ram
|
||||
and stores progress upload status in cache_ram
|
||||
X-Progress-ID:length and X-Progress-ID:uploaded
|
||||
"""
|
||||
env = request.env
|
||||
if not env.content_length:
|
||||
return cStringIO.StringIO()
|
||||
source = env.wsgi_input
|
||||
size = int(env.content_length)
|
||||
try:
|
||||
size = int(env.content_length)
|
||||
except ValueError:
|
||||
raise HTTP(400,"Invalid Content-Length header")
|
||||
dest = tempfile.TemporaryFile()
|
||||
if not 'X-Progress-ID' in request.vars:
|
||||
copystream(source, dest, size, chunk_size)
|
||||
return dest
|
||||
cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID']
|
||||
cache = Cache(request)
|
||||
cache.ram(cache_key+':length', lambda: size, 0)
|
||||
cache.ram(cache_key+':uploaded', lambda: 0, 0)
|
||||
cache_ram = CacheInRam(request) # same as cache.ram because meta_storage
|
||||
cache_ram(cache_key+':length', lambda: size, 0)
|
||||
cache_ram(cache_key+':uploaded', lambda: 0, 0)
|
||||
while size > 0:
|
||||
if size < chunk_size:
|
||||
data = source.read(size)
|
||||
cache.ram.increment(cache_key+':uploaded', size)
|
||||
cache_ram.increment(cache_key+':uploaded', size)
|
||||
else:
|
||||
data = source.read(chunk_size)
|
||||
cache.ram.increment(cache_key+':uploaded', chunk_size)
|
||||
cache_ram.increment(cache_key+':uploaded', chunk_size)
|
||||
length = len(data)
|
||||
if length > size:
|
||||
(data, length) = (data[:size], size)
|
||||
@@ -179,8 +180,8 @@ def copystream_progress(request, chunk_size= 10**5):
|
||||
if length < chunk_size:
|
||||
break
|
||||
dest.seek(0)
|
||||
cache.ram(cache_key+':length', None)
|
||||
cache.ram(cache_key+':uploaded', None)
|
||||
cache_ram(cache_key+':length', None)
|
||||
cache_ram(cache_key+':uploaded', None)
|
||||
return dest
|
||||
|
||||
|
||||
@@ -325,10 +326,12 @@ def parse_get_post_vars(request, environ):
|
||||
dpk = dpost[key]
|
||||
# if en element is not a file replace it with its value else leave it alone
|
||||
if isinstance(dpk, list):
|
||||
if not dpk[0].filename:
|
||||
value = [x.value for x in dpk]
|
||||
else:
|
||||
value = [x for x in dpk]
|
||||
value = []
|
||||
for _dpk in dpk:
|
||||
if not _dpk.filename:
|
||||
value.append(_dpk.value)
|
||||
else:
|
||||
value.append(_dpk)
|
||||
elif not dpk.filename:
|
||||
value = dpk.value
|
||||
else:
|
||||
@@ -400,7 +403,7 @@ def wsgibase(environ, responder):
|
||||
# ##################################################
|
||||
|
||||
fixup_missing_path_info(environ)
|
||||
(static_file, environ) = url_in(request, environ)
|
||||
(static_file, version, environ) = url_in(request, environ)
|
||||
response.status = env.web2py_status_code or response.status
|
||||
|
||||
if static_file:
|
||||
@@ -408,24 +411,34 @@ def wsgibase(environ, responder):
|
||||
'attachment'):
|
||||
response.headers['Content-Disposition'] \
|
||||
= 'attachment'
|
||||
if version:
|
||||
response.headers['Cache-Control'] = 'max-age=315360000'
|
||||
response.headers['Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
|
||||
response.stream(static_file, request=request)
|
||||
|
||||
# ##################################################
|
||||
# fill in request items
|
||||
# ##################################################
|
||||
app = request.application ## must go after url_in!
|
||||
|
||||
http_host = env.http_host.split(':',1)[0]
|
||||
local_hosts = [http_host,'::1','127.0.0.1',
|
||||
'::ffff:127.0.0.1']
|
||||
if not global_settings.web2py_runtime_gae:
|
||||
local_hosts.append(socket.gethostname())
|
||||
try:
|
||||
local_hosts.append(
|
||||
socket.gethostbyname(http_host))
|
||||
except socket.gaierror:
|
||||
pass
|
||||
client = get_client(env)
|
||||
|
||||
if not global_settings.local_hosts:
|
||||
local_hosts = ['127.0.0.1','::ffff:127.0.0.1']
|
||||
if not global_settings.web2py_runtime_gae:
|
||||
try:
|
||||
local_hosts.append(socket.gethostname())
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
if env.server_name:
|
||||
local_hosts += [
|
||||
env.server_name,
|
||||
socket.gethostbyname(env.server_name)]
|
||||
except (socket.gaierror,TypeError):
|
||||
pass
|
||||
global_settings.local_hosts = local_hosts
|
||||
else:
|
||||
local_hosts = global_settings.local_hosts
|
||||
client = get_client(env)
|
||||
x_req_with = str(env.http_x_requested_with).lower()
|
||||
|
||||
request.update(
|
||||
@@ -434,8 +447,9 @@ def wsgibase(environ, responder):
|
||||
ajax = x_req_with == 'xmlhttprequest',
|
||||
cid = env.http_web2py_component_element,
|
||||
is_local = env.remote_addr in local_hosts,
|
||||
is_https = env.wsgi_url_scheme \
|
||||
in ['https', 'HTTPS'] or env.https=='on')
|
||||
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES \
|
||||
or request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
||||
or env.https=='on')
|
||||
request.uuid = request.compute_uuid() # requires client
|
||||
request.url = environ['PATH_INFO']
|
||||
|
||||
@@ -781,7 +795,6 @@ class HttpServer(object):
|
||||
global_settings.applications_parent = path
|
||||
os.chdir(path)
|
||||
[add_path_first(p) for p in (path, abspath('site-packages'), "")]
|
||||
custom_import_install(web2py_path)
|
||||
if exists("logging.conf"):
|
||||
logging.config.fileConfig("logging.conf")
|
||||
|
||||
|
||||
+7
-3
@@ -314,22 +314,26 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
|
||||
(action,models,command) = (True,'-M',command[1:])
|
||||
else:
|
||||
action=False
|
||||
|
||||
if action and command.endswith('.py'):
|
||||
commands.extend(('-J', # cron job
|
||||
models, # import models?
|
||||
'-S', app, # app name
|
||||
'-a', '"<recycle>"', # password
|
||||
'-R', command)) # command
|
||||
shell = True
|
||||
elif action:
|
||||
commands.extend(('-J', # cron job
|
||||
models, # import models?
|
||||
'-S', app+'/'+command, # app name
|
||||
'-a', '"<recycle>"')) # password
|
||||
shell = True
|
||||
else:
|
||||
commands = command
|
||||
shell = False
|
||||
|
||||
# from python docs:
|
||||
# You do not need shell=True to run a batch file or
|
||||
# console-based executable.
|
||||
shell = False
|
||||
|
||||
try:
|
||||
cronlauncher(commands, shell=shell).start()
|
||||
except Exception, e:
|
||||
|
||||
@@ -140,8 +140,9 @@ class LockedFile(object):
|
||||
self.file.close()
|
||||
self.file = None
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
if not self.file is None:
|
||||
self.close()
|
||||
|
||||
def read_locked(filename):
|
||||
fp = LockedFile(filename, 'r')
|
||||
data = fp.read()
|
||||
|
||||
+129
-183
@@ -33,12 +33,29 @@ exists = os.path.exists
|
||||
pjoin = os.path.join
|
||||
|
||||
logger = logging.getLogger('web2py.rewrite')
|
||||
thread = threading.local() # thread-local storage for routing params
|
||||
THREAD_LOCAL = threading.local() # thread-local storage for routing params
|
||||
|
||||
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
|
||||
regex_anything = re.compile(r'(?<!\\)\$anything')
|
||||
regex_redirect = re.compile(r'(\d+)->(.*)')
|
||||
regex_full_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
|
||||
regex_version = re.compile(r'^(_[\d]+\.[\d]+\.[\d]+)$')
|
||||
# pattern to replace spaces with underscore in URL
|
||||
# also the html escaped variants '+' and '%20' are covered
|
||||
regex_space = re.compile('(\+|\s|%20)+')
|
||||
|
||||
# pattern to find valid paths in url /application/controller/...
|
||||
# this could be:
|
||||
# for static pages:
|
||||
# /<b:application>/static/<x:file>
|
||||
# for dynamic pages:
|
||||
# /<a:application>[/<c:controller>[/<f:function>[.<e:ext>][/<s:args>]]]
|
||||
# application, controller, function and ext may only contain [a-zA-Z0-9_]
|
||||
# file and args may also contain '-', '=', '.' and '/'
|
||||
# apps in routes_apps_raw must parse raw_args into args
|
||||
|
||||
regex_url = re.compile('^/((?P<a>\w+)(/(?P<c>\w+)(/(?P<z>(?P<f>\w+)(\.(?P<e>[\w.]+))?(?P<s>[/\w@=-]*(\.[/\w@=-]+)*)))?)?)?$')
|
||||
|
||||
|
||||
def _router_default():
|
||||
"return new copy of default base router"
|
||||
@@ -84,7 +101,7 @@ def _params_default(app=None):
|
||||
|
||||
params_apps = dict()
|
||||
params = _params_default(app=None) # regex rewrite parameters
|
||||
thread.routes = params # default to base regex rewrite parameters
|
||||
THREAD_LOCAL.routes = params # default to base regex rewrite parameters
|
||||
routers = None
|
||||
|
||||
def log_rewrite(string):
|
||||
@@ -107,16 +124,16 @@ def log_rewrite(string):
|
||||
logger.debug(string)
|
||||
|
||||
ROUTER_KEYS = set(
|
||||
('default_application', 'applications',
|
||||
('default_application', 'applications',
|
||||
'default_controller', 'controllers',
|
||||
'default_function', 'functions',
|
||||
'default_function', 'functions',
|
||||
'default_language', 'languages',
|
||||
'domain', 'domains', 'root_static', 'path_prefix',
|
||||
'exclusive_domain', 'map_hyphen', 'map_static',
|
||||
'acfe_match', 'file_match', 'args_match'))
|
||||
|
||||
ROUTER_BASE_KEYS = set(
|
||||
('applications', 'default_application',
|
||||
('applications', 'default_application',
|
||||
'domains', 'path_prefix'))
|
||||
|
||||
# The external interface to rewrite consists of:
|
||||
@@ -125,11 +142,11 @@ ROUTER_BASE_KEYS = set(
|
||||
# url_in: parse and rewrite incoming URL
|
||||
# url_out: assemble and rewrite outgoing URL
|
||||
#
|
||||
# thread.routes.default_application
|
||||
# thread.routes.error_message
|
||||
# thread.routes.error_message_ticket
|
||||
# thread.routes.try_redirect_on_error
|
||||
# thread.routes.error_handler
|
||||
# THREAD_LOCAL.routes.default_application
|
||||
# THREAD_LOCAL.routes.error_message
|
||||
# THREAD_LOCAL.routes.error_message_ticket
|
||||
# THREAD_LOCAL.routes.try_redirect_on_error
|
||||
# THREAD_LOCAL.routes.error_handler
|
||||
#
|
||||
# filter_url: helper for doctest & unittest
|
||||
# filter_err: helper for doctest & unittest
|
||||
@@ -140,8 +157,8 @@ def fixup_missing_path_info(environ):
|
||||
path_info = eget('PATH_INFO')
|
||||
request_uri = eget('REQUEST_URI')
|
||||
if not path_info and request_uri:
|
||||
# for fcgi, get path_info and
|
||||
# query_string from request_uri
|
||||
# for fcgi, get path_info and
|
||||
# query_string from request_uri
|
||||
items = request_uri.split('?')
|
||||
path_info = environ['PATH_INFO'] = items[0]
|
||||
environ['QUERY_STRING'] = items[1] if len(items) > 1 else ''
|
||||
@@ -154,45 +171,36 @@ def fixup_missing_path_info(environ):
|
||||
if not eget('HTTP_HOST'):
|
||||
environ['HTTP_HOST'] = \
|
||||
'%s:%s' % (eget('SERVER_NAME'),eget('SERVER_PORT'))
|
||||
|
||||
|
||||
|
||||
|
||||
def url_in(request, environ):
|
||||
"parse and rewrite incoming URL"
|
||||
if routers:
|
||||
return map_url_in(request, environ)
|
||||
return regex_url_in(request, environ)
|
||||
|
||||
def url_out(request, env, application, controller, function,
|
||||
def url_out(request, environ, application, controller, function,
|
||||
args, other, scheme, host, port):
|
||||
"assemble and rewrite outgoing URL"
|
||||
if routers:
|
||||
acf = map_url_out(request, env, application, controller,
|
||||
acf = map_url_out(request, environ, application, controller,
|
||||
function, args, other, scheme, host, port)
|
||||
url = '%s%s' % (acf, other)
|
||||
else:
|
||||
url = '/%s/%s/%s%s' % (application, controller, function, other)
|
||||
url = regex_filter_out(url, env)
|
||||
url = regex_filter_out(url, environ)
|
||||
#
|
||||
# fill in scheme and host if absolute URL is requested
|
||||
# scheme can be a string, eg 'http', 'https', 'ws', 'wss'
|
||||
#
|
||||
if scheme or port is not None:
|
||||
if host is None: # scheme or port implies host
|
||||
host = True
|
||||
if host is True or (host is None and (scheme or port!=None)):
|
||||
host = request.env.http_host
|
||||
if not scheme or scheme is True:
|
||||
if request and request.env:
|
||||
scheme = request.env.get('wsgi_url_scheme', 'http').lower()
|
||||
else:
|
||||
scheme = 'http' # some reasonable default in case we need it
|
||||
if host is not None:
|
||||
if host is True:
|
||||
host = request.env.http_host
|
||||
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
|
||||
if request else 'http'
|
||||
if host:
|
||||
if port is None:
|
||||
port = ''
|
||||
else:
|
||||
port = ':%s' % port
|
||||
url = '%s://%s%s%s' % (scheme, host, port, url)
|
||||
host_port = host if not port else host.split(':',1)[0]+':%s'%port
|
||||
url = '%s://%s%s' % (scheme, host_port, url)
|
||||
return url
|
||||
|
||||
def try_rewrite_on_error(http_response, request, environ, ticket=None):
|
||||
@@ -200,12 +208,12 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
|
||||
called from main.wsgibase to rewrite the http response.
|
||||
"""
|
||||
status = int(str(http_response.status).split()[0])
|
||||
if status>=399 and thread.routes.routes_onerror:
|
||||
if status>=399 and THREAD_LOCAL.routes.routes_onerror:
|
||||
keys=set(('%s/%s' % (request.application, status),
|
||||
'%s/*' % (request.application),
|
||||
'*/%s' % (status),
|
||||
'*/*'))
|
||||
for (key,uri) in thread.routes.routes_onerror:
|
||||
for (key,uri) in THREAD_LOCAL.routes.routes_onerror:
|
||||
if key in keys:
|
||||
if uri == '!':
|
||||
# do nothing!
|
||||
@@ -229,7 +237,7 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
|
||||
path_info = '/' + path_info.lstrip('/') # add leading '/' if missing
|
||||
environ['PATH_INFO'] = path_info
|
||||
error_handling_path = \
|
||||
url_in(request, environ)[1]['PATH_INFO']
|
||||
url_in(request, environ)[2]['PATH_INFO']
|
||||
# Avoid infinite loop.
|
||||
if error_handling_path != error_raising_path:
|
||||
# wsgibase will be called recursively with the routes_onerror path.
|
||||
@@ -243,12 +251,12 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
|
||||
def try_redirect_on_error(http_object, request, ticket=None):
|
||||
"called from main.wsgibase to rewrite the http response"
|
||||
status = int(str(http_object.status).split()[0])
|
||||
if status>399 and thread.routes.routes_onerror:
|
||||
if status>399 and THREAD_LOCAL.routes.routes_onerror:
|
||||
keys=set(('%s/%s' % (request.application, status),
|
||||
'%s/*' % (request.application),
|
||||
'*/%s' % (status),
|
||||
'*/*'))
|
||||
for (key,redir) in thread.routes.routes_onerror:
|
||||
for (key,redir) in THREAD_LOCAL.routes.routes_onerror:
|
||||
if key in keys:
|
||||
if redir == '!':
|
||||
break
|
||||
@@ -281,7 +289,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
|
||||
global params_apps
|
||||
params_apps = dict()
|
||||
params = _params_default(app=None) # regex rewrite parameters
|
||||
thread.routes = params # default to base regex rewrite parameters
|
||||
THREAD_LOCAL.routes = params # default to base regex rewrite parameters
|
||||
routers = None
|
||||
|
||||
if isinstance(rdict, dict):
|
||||
@@ -328,7 +336,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
|
||||
|
||||
if app is None:
|
||||
params = p # install base rewrite parameters
|
||||
thread.routes = params # install default as current routes
|
||||
THREAD_LOCAL.routes = params # install default as current routes
|
||||
#
|
||||
# create the BASE router if routers in use
|
||||
#
|
||||
@@ -537,24 +545,25 @@ def regex_select(env=None, app=None, request=None):
|
||||
select a set of regex rewrite params for the current request
|
||||
"""
|
||||
if app:
|
||||
thread.routes = params_apps.get(app, params)
|
||||
THREAD_LOCAL.routes = params_apps.get(app, params)
|
||||
elif env and params.routes_app:
|
||||
if routers:
|
||||
map_url_in(request, env, app=True)
|
||||
else:
|
||||
app = regex_uri(env, params.routes_app, "routes_app")
|
||||
thread.routes = params_apps.get(app, params)
|
||||
THREAD_LOCAL.routes = params_apps.get(app, params)
|
||||
else:
|
||||
thread.routes = params # default to base rewrite parameters
|
||||
log_rewrite("select routing parameters: %s" % thread.routes.name)
|
||||
THREAD_LOCAL.routes = params # default to base rewrite parameters
|
||||
log_rewrite("select routing parameters: %s" % THREAD_LOCAL.routes.name)
|
||||
return app # for doctest
|
||||
|
||||
def regex_filter_in(e):
|
||||
"regex rewrite incoming URL"
|
||||
routes = THREAD_LOCAL.routes
|
||||
query = e.get('QUERY_STRING', None)
|
||||
e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
|
||||
if thread.routes.routes_in:
|
||||
path = regex_uri(e, thread.routes.routes_in,
|
||||
if routes.routes_in:
|
||||
path = regex_uri(e, routes.routes_in,
|
||||
"routes_in", e['PATH_INFO'])
|
||||
rmatch = regex_redirect.match(path)
|
||||
if rmatch:
|
||||
@@ -570,58 +579,7 @@ def regex_filter_in(e):
|
||||
e['REQUEST_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
|
||||
return e
|
||||
|
||||
|
||||
# pattern to replace spaces with underscore in URL
|
||||
# also the html escaped variants '+' and '%20' are covered
|
||||
regex_space = re.compile('(\+|\s|%20)+')
|
||||
|
||||
# pattern to find valid paths in url /application/controller/...
|
||||
# this could be:
|
||||
# for static pages:
|
||||
# /<b:application>/static/<x:file>
|
||||
# for dynamic pages:
|
||||
# /<a:application>[/<c:controller>[/<f:function>[.<e:ext>][/<s:args>]]]
|
||||
# application, controller, function and ext may only contain [a-zA-Z0-9_]
|
||||
# file and args may also contain '-', '=', '.' and '/'
|
||||
# apps in routes_apps_raw must parse raw_args into args
|
||||
|
||||
regex_static = re.compile(r'''
|
||||
(^ # static pages
|
||||
/(?P<b> \w+) # b=app
|
||||
/static # /b/static
|
||||
/(?P<x> (\w[\-\=\./]?)* ) # x=file
|
||||
$)
|
||||
''', re.X)
|
||||
|
||||
regex_url = re.compile(r'''
|
||||
(^( # (/a/c/f.e/s)
|
||||
/(?P<a> [\w\s+]+ ) # /a=app
|
||||
( # (/c.f.e/s)
|
||||
/(?P<c> [\w\s+]+ ) # /a/c=controller
|
||||
( # (/f.e/s)
|
||||
/(?P<f> [\w\s+]+ ) # /a/c/f=function
|
||||
( # (.e)
|
||||
\.(?P<e> [\w\s+]+ ) # /a/c/f.e=extension
|
||||
)?
|
||||
( # (/s)
|
||||
/(?P<r> # /a/c/f.e/r=raw_args
|
||||
.*
|
||||
)
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
/?$)
|
||||
''', re.X)
|
||||
|
||||
regex_args = re.compile(r'''
|
||||
(^
|
||||
(?P<s>
|
||||
( [\w@/-][=.]? )* # s=args
|
||||
)?
|
||||
/?$) # trailing slash
|
||||
''', re.X)
|
||||
|
||||
|
||||
def sluggify(key):
|
||||
return key.lower().replace('.','_')
|
||||
|
||||
@@ -635,72 +593,61 @@ def regex_url_in(request, environ):
|
||||
# ##################################################
|
||||
|
||||
regex_select(env=environ, request=request)
|
||||
|
||||
if thread.routes.routes_in:
|
||||
routes = THREAD_LOCAL.routes
|
||||
if routes.routes_in:
|
||||
environ = regex_filter_in(environ)
|
||||
|
||||
request.env.update((sluggify(k),v) for k,v in environ.iteritems())
|
||||
|
||||
path = request.env.path_info.replace('\\', '/')
|
||||
|
||||
# ##################################################
|
||||
# serve if a static file
|
||||
# ##################################################
|
||||
|
||||
match = regex_static.match(regex_space.sub('_', path))
|
||||
if match and match.group('x'):
|
||||
static_file = pjoin(request.env.applications_parent,
|
||||
'applications', match.group('b'),
|
||||
'static', match.group('x'))
|
||||
return (static_file, environ)
|
||||
|
||||
# ##################################################
|
||||
# parse application, controller and function
|
||||
# ##################################################
|
||||
|
||||
path = re.sub('%20', ' ', path)
|
||||
path = request.env.path_info.replace('\\', '/') or '/'
|
||||
path = regex_space.sub('_',path)
|
||||
if path.endswith('/') and len(path)>1: path = path[:-1]
|
||||
match = regex_url.match(path)
|
||||
if not match or match.group('c') == 'static':
|
||||
if not match:
|
||||
raise HTTP(400,
|
||||
thread.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid path')
|
||||
|
||||
request.application = \
|
||||
regex_space.sub('_', match.group('a') or thread.routes.default_application)
|
||||
request.controller = \
|
||||
regex_space.sub('_', match.group('c') or thread.routes.default_controller)
|
||||
request.function = \
|
||||
regex_space.sub('_', match.group('f') or thread.routes.default_function)
|
||||
group_e = match.group('e')
|
||||
request.raw_extension = group_e and regex_space.sub('_', group_e) or None
|
||||
request.extension = request.raw_extension or 'html'
|
||||
request.raw_args = match.group('r')
|
||||
request.args = List([])
|
||||
if request.application in thread.routes.routes_apps_raw:
|
||||
# application is responsible for parsing args
|
||||
request.args = None
|
||||
elif request.raw_args:
|
||||
match = regex_args.match(request.raw_args.replace(' ', '_'))
|
||||
if match:
|
||||
group_s = match.group('s')
|
||||
request.args = \
|
||||
List((group_s and group_s.split('/')) or [])
|
||||
if request.args and request.args[-1] == '':
|
||||
request.args.pop() # adjust for trailing empty arg
|
||||
routes.error_message % 'invalid request',
|
||||
web2py_error='invalid path')
|
||||
elif match.group('c')=='static':
|
||||
application = match.group('a')
|
||||
version, filename = None, match.group('z')
|
||||
items = filename.split('/',1)
|
||||
if regex_version.match(items[0]):
|
||||
version,filename = items
|
||||
static_file = pjoin(request.env.applications_parent,
|
||||
'applications', application,
|
||||
'static', filename)
|
||||
return (static_file, version, environ)
|
||||
else:
|
||||
# ##################################################
|
||||
# parse application, controller and function
|
||||
# ##################################################
|
||||
request.application = match.group('a') or routes.default_application
|
||||
request.controller = match.group('c') or routes.default_controller
|
||||
request.function = match.group('f') or routes.default_function
|
||||
request.raw_extension = match.group('e')
|
||||
request.extension = request.raw_extension or 'html'
|
||||
request.raw_args = match.group('s')
|
||||
if request.application in routes.routes_apps_raw:
|
||||
# application is responsible for parsing args
|
||||
request.args = None
|
||||
elif request.raw_args:
|
||||
request.args = List(request.raw_args.split('/')[1:])
|
||||
else:
|
||||
raise HTTP(400,
|
||||
thread.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid path (args)')
|
||||
return (None, environ)
|
||||
request.args = List([])
|
||||
return (None, None, environ)
|
||||
|
||||
|
||||
def regex_filter_out(url, e=None):
|
||||
"regex rewrite outgoing URL"
|
||||
if not hasattr(thread, 'routes'):
|
||||
regex_select() # ensure thread.routes is set (for application threads)
|
||||
if not hasattr(THREAD_LOCAL, 'routes'):
|
||||
regex_select() # ensure routes is set (for application threads)
|
||||
routes = THREAD_LOCAL.routes
|
||||
if routers:
|
||||
return url # already filtered
|
||||
if thread.routes.routes_out:
|
||||
if routes.routes_out:
|
||||
items = url.split('?', 1)
|
||||
if e:
|
||||
host = e.get('http_host', 'localhost').lower()
|
||||
@@ -713,7 +660,7 @@ def regex_filter_out(url, e=None):
|
||||
e.get('request_method', 'get').lower(), items[0])
|
||||
else:
|
||||
items[0] = ':http://localhost:get %s' % items[0]
|
||||
for (regex, value, tmp) in thread.routes.routes_out:
|
||||
for (regex, value, tmp) in routes.routes_out:
|
||||
if regex.match(items[0]):
|
||||
rewritten = '?'.join([regex.sub(value, items[0])] + items[1:])
|
||||
log_rewrite('routes_out: [%s] -> %s' % (url, rewritten))
|
||||
@@ -722,9 +669,9 @@ def regex_filter_out(url, e=None):
|
||||
return url
|
||||
|
||||
|
||||
def filter_url(url, method='get', remote='0.0.0.0',
|
||||
def filter_url(url, method='get', remote='0.0.0.0',
|
||||
out=False, app=False, lang=None,
|
||||
domain=(None,None), env=False, scheme=None,
|
||||
domain=(None,None), env=False, scheme=None,
|
||||
host=None, port=None):
|
||||
"""
|
||||
doctest/unittest interface to regex_filter_in() and regex_filter_out()
|
||||
@@ -793,7 +740,7 @@ def filter_url(url, method='get', remote='0.0.0.0',
|
||||
|
||||
# rewrite inbound URL
|
||||
#
|
||||
(static, e) = url_in(request, e)
|
||||
(static, version, e) = url_in(request, e)
|
||||
if static:
|
||||
return static
|
||||
result = "/%s/%s/%s" % (request.application, request.controller, request.function)
|
||||
@@ -812,12 +759,13 @@ def filter_url(url, method='get', remote='0.0.0.0',
|
||||
|
||||
def filter_err(status, application='app', ticket='tkt'):
|
||||
"doctest/unittest interface to routes_onerror"
|
||||
if status > 399 and thread.routes.routes_onerror:
|
||||
routes = THREAD_LOCAL.routes
|
||||
if status > 399 and routes.routes_onerror:
|
||||
keys = set(('%s/%s' % (application, status),
|
||||
'%s/*' % (application),
|
||||
'*/%s' % (status),
|
||||
'*/*'))
|
||||
for (key,redir) in thread.routes.routes_onerror:
|
||||
for (key,redir) in routes.routes_onerror:
|
||||
if key in keys:
|
||||
if redir == '!':
|
||||
break
|
||||
@@ -921,12 +869,12 @@ class MapUrlIn(object):
|
||||
self.pop_arg_if(self.application == arg0)
|
||||
|
||||
if not base._acfe_match.match(self.application):
|
||||
raise HTTP(400, thread.routes.error_message % 'invalid request',
|
||||
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error="invalid application: '%s'" % self.application)
|
||||
|
||||
if self.application not in routers and \
|
||||
(self.application != thread.routes.default_application or self.application == 'welcome'):
|
||||
raise HTTP(400, thread.routes.error_message % 'invalid request',
|
||||
(self.application != THREAD_LOCAL.routes.default_application or self.application == 'welcome'):
|
||||
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error="unknown application: '%s'" % self.application)
|
||||
|
||||
# set the application router
|
||||
@@ -956,14 +904,15 @@ class MapUrlIn(object):
|
||||
a root-static file is one whose incoming URL expects it to be at the root,
|
||||
typically robots.txt & favicon.ico
|
||||
'''
|
||||
|
||||
if len(self.args) == 1 and self.arg0 in self.router.root_static:
|
||||
self.controller = self.request.controller = 'static'
|
||||
root_static_file = pjoin(self.request.env.applications_parent,
|
||||
'applications', self.application,
|
||||
self.controller, self.arg0)
|
||||
log_rewrite("route: root static=%s" % root_static_file)
|
||||
return root_static_file
|
||||
return None
|
||||
return root_static_file, None
|
||||
return None, None
|
||||
|
||||
def map_language(self):
|
||||
"handle language (no hyphen mapping)"
|
||||
@@ -989,7 +938,7 @@ class MapUrlIn(object):
|
||||
self.pop_arg_if(arg0 == self.controller)
|
||||
log_rewrite("route: controller=%s" % self.controller)
|
||||
if not self.router._acfe_match.match(self.controller):
|
||||
raise HTTP(400, thread.routes.error_message % 'invalid request',
|
||||
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid controller')
|
||||
|
||||
def map_static(self):
|
||||
@@ -998,8 +947,12 @@ class MapUrlIn(object):
|
||||
file_match but no hyphen mapping
|
||||
'''
|
||||
if self.controller != 'static':
|
||||
return None
|
||||
file = '/'.join(self.args)
|
||||
return None, None
|
||||
version = regex_version.match(self.args(0))
|
||||
if self.args and version:
|
||||
file = '/'.join(self.args[1:])
|
||||
else:
|
||||
file = '/'.join(self.args)
|
||||
if len(self.args) == 0:
|
||||
bad_static = True # require a file name
|
||||
elif '/' in self.file_match:
|
||||
@@ -1013,7 +966,7 @@ class MapUrlIn(object):
|
||||
if bad_static:
|
||||
log_rewrite('bad static path=%s' % file)
|
||||
raise HTTP(400,
|
||||
thread.routes.error_message % 'invalid request',
|
||||
THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid static file')
|
||||
#
|
||||
# support language-specific static subdirectories,
|
||||
@@ -1030,7 +983,7 @@ class MapUrlIn(object):
|
||||
'static', file)
|
||||
self.extension = None
|
||||
log_rewrite("route: static=%s" % static_file)
|
||||
return static_file
|
||||
return static_file, version
|
||||
|
||||
def map_function(self):
|
||||
"handle function.extension"
|
||||
@@ -1055,10 +1008,10 @@ class MapUrlIn(object):
|
||||
log_rewrite("route: function.ext=%s.%s" % (self.function, self.extension))
|
||||
|
||||
if not self.router._acfe_match.match(self.function):
|
||||
raise HTTP(400, thread.routes.error_message % 'invalid request',
|
||||
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid function')
|
||||
if self.extension and not self.router._acfe_match.match(self.extension):
|
||||
raise HTTP(400, thread.routes.error_message % 'invalid request',
|
||||
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid extension')
|
||||
|
||||
def validate_args(self):
|
||||
@@ -1067,14 +1020,14 @@ class MapUrlIn(object):
|
||||
'''
|
||||
for arg in self.args:
|
||||
if not self.router._args_match.match(arg):
|
||||
raise HTTP(400, thread.routes.error_message % 'invalid request',
|
||||
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
|
||||
web2py_error='invalid arg <%s>' % arg)
|
||||
|
||||
def sluggify(self):
|
||||
""
|
||||
self.request.env.update(
|
||||
(sluggify(k),v) for k,v in self.env.iteritems())
|
||||
|
||||
|
||||
def update_request(self):
|
||||
'''
|
||||
update request from self
|
||||
@@ -1098,7 +1051,7 @@ class MapUrlIn(object):
|
||||
if self.language:
|
||||
uri = '/%s%s' % (self.language, uri)
|
||||
uri = '/%s%s%s%s' % (
|
||||
app,
|
||||
app,
|
||||
uri,
|
||||
urllib.quote('/'+'/'.join(str(x) for x in self.args)) if self.args else '',
|
||||
('?' + self.query) if self.query else '')
|
||||
@@ -1292,24 +1245,24 @@ def map_url_in(request, env, app=False):
|
||||
|
||||
# initialize router-url object
|
||||
#
|
||||
thread.routes = params # default to base routes
|
||||
THREAD_LOCAL.routes = params # default to base routes
|
||||
map = MapUrlIn(request=request, env=env)
|
||||
map.sluggify()
|
||||
map.map_prefix() # strip prefix if present
|
||||
map.map_app() # determine application
|
||||
|
||||
# configure thread.routes for error rewrite
|
||||
# configure THREAD_LOCAL.routes for error rewrite
|
||||
#
|
||||
if params.routes_app:
|
||||
thread.routes = params_apps.get(app, params)
|
||||
THREAD_LOCAL.routes = params_apps.get(app, params)
|
||||
|
||||
if app:
|
||||
return map.application
|
||||
|
||||
root_static_file = map.map_root_static() # handle root-static files
|
||||
root_static_file, version = map.map_root_static() # handle root-static files
|
||||
if root_static_file:
|
||||
map.update_request()
|
||||
return (root_static_file, map.env)
|
||||
return (root_static_file, version, map.env)
|
||||
# handle mapping of lang/static to static/lang in externally-rewritten URLs
|
||||
# in case we have to handle them ourselves
|
||||
if map.languages and map.map_static is False and map.arg0 == 'static' and map.args(1) in map.languages:
|
||||
@@ -1318,14 +1271,14 @@ def map_url_in(request, env, app=False):
|
||||
else:
|
||||
map.map_language()
|
||||
map.map_controller()
|
||||
static_file = map.map_static()
|
||||
static_file, version = map.map_static()
|
||||
if static_file:
|
||||
map.update_request()
|
||||
return (static_file, map.env)
|
||||
return (static_file, version, map.env)
|
||||
map.map_function()
|
||||
map.validate_args()
|
||||
map.update_request()
|
||||
return (None, map.env)
|
||||
return (None, None, map.env)
|
||||
|
||||
def map_url_out(request, env, application, controller,
|
||||
function, args, other, scheme, host, port):
|
||||
@@ -1363,10 +1316,3 @@ def get_effective_router(appname):
|
||||
if not routers or appname not in routers:
|
||||
return None
|
||||
return Storage(routers[appname]) # return a copy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+16
-222
@@ -2,6 +2,7 @@
|
||||
|
||||
# This file is part of the Rocket Web Server
|
||||
# Copyright (c) 2011 Timothy Farrell
|
||||
# Modified by Massimo Di Pierro
|
||||
|
||||
# Import System Modules
|
||||
import sys
|
||||
@@ -12,7 +13,7 @@ import platform
|
||||
import traceback
|
||||
|
||||
# Define Constants
|
||||
VERSION = '1.2.4'
|
||||
VERSION = '1.2.5'
|
||||
SERVER_NAME = socket.gethostname()
|
||||
SERVER_SOFTWARE = 'Rocket %s' % VERSION
|
||||
HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0])
|
||||
@@ -1454,41 +1455,34 @@ class Worker(Thread):
|
||||
return req
|
||||
|
||||
|
||||
def read_headers(self, sock_file):
|
||||
def read_headers(self, sock_file, environ):
|
||||
try:
|
||||
headers = dict()
|
||||
l = sock_file.readline()
|
||||
|
||||
lname = None
|
||||
lval = None
|
||||
while True:
|
||||
l = sock_file.readline()
|
||||
if PY3K:
|
||||
try:
|
||||
l = str(l, 'ISO-8859-1')
|
||||
except UnicodeDecodeError:
|
||||
self.err_log.warning('Client sent invalid header: ' + repr(l))
|
||||
self.err_log.warning('Invalid request header: '+repr(l))
|
||||
|
||||
if l == '\r\n':
|
||||
if l.strip().replace('\0','') == '':
|
||||
break
|
||||
|
||||
if l[0] in ' \t' and lname:
|
||||
elif l[0] in ' \t' and lname:
|
||||
# Some headers take more than one line
|
||||
lval += ',' + l.strip()
|
||||
environ[lname] += ' ' + l.strip()
|
||||
else:
|
||||
# HTTP header values are latin-1 encoded
|
||||
l = l.split(':', 1)
|
||||
# HTTP header names are us-ascii encoded
|
||||
|
||||
lname = l[0].strip().upper().replace('-', '_')
|
||||
lval = l[-1].strip()
|
||||
headers[str(lname)] = str(lval)
|
||||
lname = str('HTTP_'+l[0].strip().upper().replace('-', '_'))
|
||||
lval = str(l[-1].strip())
|
||||
environ[lname] = lval
|
||||
|
||||
l = sock_file.readline()
|
||||
except socket.timeout:
|
||||
raise SocketTimeout("Socket timed out before request.")
|
||||
|
||||
return headers
|
||||
|
||||
class SocketTimeout(Exception):
|
||||
"Exception for when a socket times out between requests."
|
||||
pass
|
||||
@@ -1549,209 +1543,10 @@ class ChunkedReader(object):
|
||||
yield self.readline()
|
||||
|
||||
def get_method(method):
|
||||
|
||||
|
||||
methods = dict(wsgi=WSGIWorker,
|
||||
fs=FileSystemWorker)
|
||||
methods = dict(wsgi=WSGIWorker)
|
||||
return methods[method.lower()]
|
||||
|
||||
# Monolithic build...end of module: rocket\worker.py
|
||||
# Monolithic build...start of module: rocket\methods\__init__.py
|
||||
|
||||
# Monolithic build...end of module: rocket\methods\__init__.py
|
||||
# Monolithic build...start of module: rocket\methods\fs.py
|
||||
|
||||
# Import System Modules
|
||||
import os
|
||||
import time
|
||||
import mimetypes
|
||||
from email.utils import formatdate
|
||||
from wsgiref.headers import Headers
|
||||
from wsgiref.util import FileWrapper
|
||||
# Import Package Modules
|
||||
# package imports removed in monolithic build
|
||||
|
||||
|
||||
# Define Constants
|
||||
CHUNK_SIZE = 2**16 # 64 Kilobyte chunks
|
||||
HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s'''
|
||||
INDEX_HEADER = '''\
|
||||
<html>
|
||||
<head><title>Directory Index: %(path)s</title>
|
||||
<style> .parent { margin-bottom: 1em; }</style>
|
||||
</head>
|
||||
<body><h1>Directory Index: %(path)s</h1>
|
||||
<table>
|
||||
<tr><th>Directories</th></tr>
|
||||
'''
|
||||
INDEX_ROW = '''<tr><td><div class="%(cls)s"><a href="/%(link)s">%(name)s</a></div></td></tr>'''
|
||||
INDEX_FOOTER = '''</table></body></html>\r\n'''
|
||||
|
||||
class LimitingFileWrapper(FileWrapper):
|
||||
def __init__(self, limit=None, *args, **kwargs):
|
||||
self.limit = limit
|
||||
FileWrapper.__init__(self, *args, **kwargs)
|
||||
|
||||
def read(self, amt):
|
||||
if amt > self.limit:
|
||||
amt = self.limit
|
||||
self.limit -= amt
|
||||
return FileWrapper.read(self, amt)
|
||||
|
||||
class FileSystemWorker(Worker):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Builds some instance variables that will last the life of the
|
||||
thread."""
|
||||
|
||||
Worker.__init__(self, *args, **kwargs)
|
||||
|
||||
self.root = os.path.abspath(self.app_info['document_root'])
|
||||
self.display_index = self.app_info['display_index']
|
||||
|
||||
def serve_file(self, filepath, headers):
|
||||
filestat = os.stat(filepath)
|
||||
self.size = filestat.st_size
|
||||
modtime = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
|
||||
time.gmtime(filestat.st_mtime))
|
||||
self.headers.add_header('Last-Modified', modtime)
|
||||
if headers.get('if_modified_since') == modtime:
|
||||
# The browser cache is up-to-date, send a 304.
|
||||
self.status = "304 Not Modified"
|
||||
self.data = []
|
||||
return
|
||||
|
||||
ct = mimetypes.guess_type(filepath)[0]
|
||||
self.content_type = ct if ct else 'text/plain'
|
||||
try:
|
||||
f = open(filepath, 'rb')
|
||||
self.headers['Pragma'] = 'cache'
|
||||
self.headers['Cache-Control'] = 'private'
|
||||
self.headers['Content-Length'] = str(self.size)
|
||||
if self.etag:
|
||||
self.headers.add_header('Etag', self.etag)
|
||||
if self.expires:
|
||||
self.headers.add_header('Expires', self.expires)
|
||||
|
||||
try:
|
||||
# Implement 206 partial file support.
|
||||
start, end = headers['range'].split('-')
|
||||
start = 0 if not start.isdigit() else int(start)
|
||||
end = self.size if not end.isdigit() else int(end)
|
||||
if self.size < end or start < 0:
|
||||
self.status = "214 Unsatisfiable Range Requested"
|
||||
self.data = FileWrapper(f, CHUNK_SIZE)
|
||||
else:
|
||||
f.seek(start)
|
||||
self.data = LimitingFileWrapper(f, CHUNK_SIZE, limit=end)
|
||||
self.status = "206 Partial Content"
|
||||
except:
|
||||
self.data = FileWrapper(f, CHUNK_SIZE)
|
||||
except IOError:
|
||||
self.status = "403 Forbidden"
|
||||
|
||||
def serve_dir(self, pth, rpth):
|
||||
def rel_path(path):
|
||||
return os.path.normpath(path[len(self.root):] if path.startswith(self.root) else path)
|
||||
|
||||
if not self.display_index:
|
||||
self.status = '404 File Not Found'
|
||||
return b('')
|
||||
else:
|
||||
self.content_type = 'text/html'
|
||||
|
||||
dir_contents = [os.path.join(pth, x) for x in os.listdir(os.path.normpath(pth))]
|
||||
dir_contents.sort()
|
||||
|
||||
dirs = [rel_path(x)+'/' for x in dir_contents if os.path.isdir(x)]
|
||||
files = [rel_path(x) for x in dir_contents if os.path.isfile(x)]
|
||||
|
||||
self.data = [INDEX_HEADER % dict(path='/'+rpth)]
|
||||
if rpth:
|
||||
self.data += [INDEX_ROW % dict(name='(parent directory)', cls='dir parent', link='/'.join(rpth[:-1].split('/')[:-1]))]
|
||||
self.data += [INDEX_ROW % dict(name=os.path.basename(x[:-1]), link=os.path.join(rpth, os.path.basename(x[:-1])).replace('\\', '/'), cls='dir') for x in dirs]
|
||||
self.data += ['<tr><th>Files</th></tr>']
|
||||
self.data += [INDEX_ROW % dict(name=os.path.basename(x), link=os.path.join(rpth, os.path.basename(x)).replace('\\', '/'), cls='file') for x in files]
|
||||
self.data += [INDEX_FOOTER]
|
||||
self.headers['Content-Length'] = self.size = str(sum([len(x) for x in self.data]))
|
||||
self.status = '200 OK'
|
||||
|
||||
def run_app(self, conn):
|
||||
self.status = "200 OK"
|
||||
self.size = 0
|
||||
self.expires = None
|
||||
self.etag = None
|
||||
self.content_type = 'text/plain'
|
||||
self.content_length = None
|
||||
|
||||
if __debug__:
|
||||
self.err_log.debug('Getting sock_file')
|
||||
|
||||
# Build our file-like object
|
||||
sock_file = conn.makefile('rb',BUF_SIZE)
|
||||
request = self.read_request_line(sock_file)
|
||||
if request['method'].upper() not in ('GET', ):
|
||||
self.status = "501 Not Implemented"
|
||||
|
||||
try:
|
||||
# Get our file path
|
||||
reader = self.read_headers(sock_file)
|
||||
headers = dict((k.lower(),v) for k,v in reader.iteritems())
|
||||
rpath = request.get('path', '').lstrip('/')
|
||||
filepath = os.path.join(self.root, rpath)
|
||||
filepath = os.path.abspath(filepath)
|
||||
if __debug__:
|
||||
self.err_log.debug('Request for path: %s' % filepath)
|
||||
|
||||
self.closeConnection = headers.get('connection', 'close').lower() == 'close'
|
||||
self.headers = Headers([('Date', formatdate(usegmt=True)),
|
||||
('Server', HTTP_SERVER_SOFTWARE),
|
||||
('Connection', headers.get('connection', 'close')),
|
||||
])
|
||||
|
||||
if not filepath.lower().startswith(self.root.lower()):
|
||||
# File must be within our root directory
|
||||
self.status = "400 Bad Request"
|
||||
self.closeConnection = True
|
||||
elif not os.path.exists(filepath):
|
||||
self.status = "404 File Not Found"
|
||||
self.closeConnection = True
|
||||
elif os.path.isdir(filepath):
|
||||
self.serve_dir(filepath, rpath)
|
||||
elif os.path.isfile(filepath):
|
||||
self.serve_file(filepath, headers)
|
||||
else:
|
||||
# It exists but it's not a file or a directory????
|
||||
# What is it then?
|
||||
self.status = "501 Not Implemented"
|
||||
self.closeConnection = True
|
||||
|
||||
h = self.headers
|
||||
statcode, statstr = self.status.split(' ', 1)
|
||||
statcode = int(statcode)
|
||||
if statcode >= 400:
|
||||
h.add_header('Content-Type', self.content_type)
|
||||
self.data = [statstr]
|
||||
|
||||
# Build our output headers
|
||||
header_data = HEADER_RESPONSE % (self.status, str(h))
|
||||
|
||||
# Send the headers
|
||||
if __debug__:
|
||||
self.err_log.debug('Sending Headers: %s' % repr(header_data))
|
||||
self.conn.sendall(b(header_data))
|
||||
|
||||
for data in self.data:
|
||||
self.conn.sendall(b(data))
|
||||
|
||||
if hasattr(self.data, 'close'):
|
||||
self.data.close()
|
||||
|
||||
finally:
|
||||
if __debug__:
|
||||
self.err_log.debug('Finally closing sock_file')
|
||||
sock_file.close()
|
||||
|
||||
# Monolithic build...end of module: rocket\methods\fs.py
|
||||
# Monolithic build...start of module: rocket\methods\wsgi.py
|
||||
|
||||
# Import System Modules
|
||||
@@ -1819,16 +1614,15 @@ class WSGIWorker(Worker):
|
||||
environ = self.base_environ.copy()
|
||||
|
||||
# Grab the headers
|
||||
for k, v in self.read_headers(sock_file).items():
|
||||
environ[str('HTTP_'+k)] = v
|
||||
self.read_headers(sock_file,environ)
|
||||
|
||||
# Add CGI Variables
|
||||
environ['REQUEST_METHOD'] = request['method']
|
||||
environ['PATH_INFO'] = request['path']
|
||||
environ['SERVER_PROTOCOL'] = request['protocol']
|
||||
environ['SERVER_PORT'] = str(conn.server_port)
|
||||
environ['REMOTE_PORT'] = str(conn.client_port)
|
||||
environ['REMOTE_ADDR'] = str(conn.client_addr)
|
||||
environ['REQUEST_METHOD'] = request['method']
|
||||
environ['PATH_INFO'] = request['path']
|
||||
environ['SERVER_PROTOCOL'] = request['protocol']
|
||||
environ['QUERY_STRING'] = request['query_string']
|
||||
if 'HTTP_CONTENT_LENGTH' in environ:
|
||||
environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH']
|
||||
|
||||
+4
-3
@@ -88,7 +88,7 @@ except:
|
||||
from simplejson import loads, dumps
|
||||
|
||||
|
||||
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE
|
||||
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE, IS_DATETIME
|
||||
from gluon.utils import web2py_uuid
|
||||
|
||||
|
||||
@@ -443,7 +443,7 @@ class Scheduler(MetaScheduler):
|
||||
Field('application_name',requires=IS_NOT_EMPTY(),
|
||||
default=None,writable=False),
|
||||
Field('task_name',default=None),
|
||||
Field('group_name',default='main',writable=False),
|
||||
Field('group_name',default='main'),
|
||||
Field('status',requires=IS_IN_SET(TASK_STATUS),
|
||||
default=QUEUED,writable=False),
|
||||
Field('function_name',
|
||||
@@ -454,7 +454,8 @@ class Scheduler(MetaScheduler):
|
||||
Field('args','text',default='[]',requires=TYPE(list)),
|
||||
Field('vars','text',default='{}',requires=TYPE(dict)),
|
||||
Field('enabled','boolean',default=True),
|
||||
Field('start_time','datetime',default=now, requires=IS_NOT_EMPTY()),
|
||||
Field('start_time','datetime',default=now,
|
||||
requires = IS_DATETIME()),
|
||||
Field('next_run_time','datetime',default=now),
|
||||
Field('stop_time','datetime'),
|
||||
Field('repeats','integer',default=1,comment="0=unlimited",
|
||||
|
||||
+1
-1
@@ -6,6 +6,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import platform
|
||||
from storage import Storage
|
||||
|
||||
@@ -37,4 +38,3 @@ global_settings.is_jython = \
|
||||
str(sys.copyright).find('Jython') > 0
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-3
@@ -19,7 +19,7 @@ import optparse
|
||||
import glob
|
||||
import traceback
|
||||
import fileutils
|
||||
import settings
|
||||
from settings import global_settings
|
||||
from utils import web2py_uuid
|
||||
from compileapp import build_environment, read_pyc, run_models_in
|
||||
from restricted import RestrictedError
|
||||
@@ -28,7 +28,6 @@ from storage import Storage
|
||||
from admin import w2p_unpack
|
||||
from dal import BaseAdapter
|
||||
|
||||
|
||||
logger = logging.getLogger("web2py")
|
||||
|
||||
def exec_environment(
|
||||
@@ -112,7 +111,7 @@ def env(
|
||||
request.env.path_info = '/%s/%s/%s' % (a, c, f)
|
||||
request.env.http_host = '127.0.0.1:8000'
|
||||
request.env.remote_addr = '127.0.0.1'
|
||||
request.env.web2py_runtime_gae = settings.global_settings.web2py_runtime_gae
|
||||
request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae
|
||||
|
||||
for k,v in extra_request.items():
|
||||
request[k] = v
|
||||
|
||||
+100
-56
@@ -28,7 +28,7 @@ from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
|
||||
from storage import Storage
|
||||
from utils import md5_hash
|
||||
from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
|
||||
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE
|
||||
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG
|
||||
|
||||
import datetime
|
||||
import urllib
|
||||
@@ -174,8 +174,7 @@ class TextWidget(FormWidget):
|
||||
"""
|
||||
|
||||
default = dict(value = value)
|
||||
attr = cls._attributes(field, default,
|
||||
**attributes)
|
||||
attr = cls._attributes(field, default,**attributes)
|
||||
return TEXTAREA(**attr)
|
||||
|
||||
|
||||
@@ -239,11 +238,14 @@ class ListWidget(StringWidget):
|
||||
_name = field.name
|
||||
if field.type=='list:integer': _class = 'integer'
|
||||
else: _class = 'string'
|
||||
requires = field.requires if isinstance(field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None
|
||||
requires = field.requires if isinstance(
|
||||
field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None
|
||||
attributes['_style'] = 'list-style:none'
|
||||
items=[LI(INPUT(_id=_id, _class=_class, _name=_name,
|
||||
value=v, hideerror=True, requires=requires),
|
||||
**attributes) for v in value or ['']]
|
||||
nvalue = value or ['']
|
||||
items = [LI(INPUT(_id=_id, _class=_class, _name=_name,
|
||||
value=v, hideerror=k<len(nvalue)-1,
|
||||
requires=requires),
|
||||
**attributes) for (k,v) in enumerate(nvalue)]
|
||||
script=SCRIPT("""
|
||||
// from http://refactormycode.com/codes/694-expanding-input-list-using-jquery
|
||||
(function(){
|
||||
@@ -377,12 +379,11 @@ class CheckboxesWidget(OptionsWidget):
|
||||
requires = field.requires
|
||||
if not isinstance(requires, (list, tuple)):
|
||||
requires = [requires]
|
||||
if requires:
|
||||
if hasattr(requires[0], 'options'):
|
||||
options = requires[0].options()
|
||||
else:
|
||||
raise SyntaxError, 'widget cannot determine options of %s' \
|
||||
% field
|
||||
if requires and hasattr(requires[0], 'options'):
|
||||
options = requires[0].options()
|
||||
else:
|
||||
raise SyntaxError, 'widget cannot determine options of %s' \
|
||||
% field
|
||||
|
||||
options = [(k, v) for k, v in options if k != '']
|
||||
opts = []
|
||||
@@ -439,14 +440,23 @@ class PasswordWidget(FormWidget):
|
||||
|
||||
see also: :meth:`FormWidget.widget`
|
||||
"""
|
||||
|
||||
# detect if attached a IS_STRONG with entropy
|
||||
default=dict(
|
||||
_type='password',
|
||||
_value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '',
|
||||
)
|
||||
attr = cls._attributes(field, default, **attributes)
|
||||
output = CAT(INPUT(**attr))
|
||||
|
||||
return INPUT(**attr)
|
||||
# deal with entropy check!
|
||||
requires = field.requires
|
||||
if not isinstance(requires,(list,tuple)): requires = [requires]
|
||||
is_strong = [r for r in requires if isinstance(r, IS_STRONG)]
|
||||
if is_strong:
|
||||
output.append(SCRIPT("web2py_validate_entropy(jQuery('#%s'),%s);" \
|
||||
% (attr['_id'],is_strong[0].entropy)))
|
||||
# end entropy check
|
||||
return output
|
||||
|
||||
|
||||
class UploadWidget(FormWidget):
|
||||
@@ -679,6 +689,16 @@ def formstyle_divs(form, fields):
|
||||
table.append(DIV(_label, _controls, _help, _id=id))
|
||||
return table
|
||||
|
||||
def formstyle_inline(form, fields):
|
||||
''' divs only '''
|
||||
if len(fields)!=2:
|
||||
raise RuntimeError, "Not possible"
|
||||
id, label, controls, help = fields[0]
|
||||
submit_button = fields[1][2]
|
||||
return CAT(DIV(controls,_style='display:inline'),
|
||||
submit_button)
|
||||
|
||||
|
||||
def formstyle_ul(form, fields):
|
||||
''' unordered list '''
|
||||
table = UL()
|
||||
@@ -804,6 +824,7 @@ class SQLFORM(FORM):
|
||||
divs = formstyle_divs,
|
||||
ul = formstyle_ul,
|
||||
bootstrap = formstyle_bootstrap,
|
||||
inline = formstyle_inline,
|
||||
))
|
||||
|
||||
FIELDNAME_REQUEST_DELETE = 'delete_this_record'
|
||||
@@ -865,6 +886,8 @@ class SQLFORM(FORM):
|
||||
self.ignore_rw = ignore_rw
|
||||
self.formstyle = formstyle
|
||||
self.readonly = readonly
|
||||
# Default dbio setting
|
||||
self.detect_record_change = None
|
||||
|
||||
nbsp = XML(' ') # Firefox2 does not display fields with blanks
|
||||
FORM.__init__(self, *[], **attributes)
|
||||
@@ -998,11 +1021,11 @@ class SQLFORM(FORM):
|
||||
else:
|
||||
inp = field.formatter(default)
|
||||
elif field.type == 'upload':
|
||||
if hasattr(field, 'widget') and field.widget:
|
||||
if field.widget:
|
||||
inp = field.widget(field, default, upload)
|
||||
else:
|
||||
inp = self.widgets.upload.widget(field, default, upload)
|
||||
elif hasattr(field, 'widget') and field.widget:
|
||||
elif field.widget:
|
||||
inp = field.widget(field, default)
|
||||
elif field.type == 'boolean':
|
||||
inp = self.widgets.boolean.widget(field, default)
|
||||
@@ -1036,7 +1059,7 @@ class SQLFORM(FORM):
|
||||
|
||||
xfields.append((row_id,label,inp,comment))
|
||||
self.custom.dspval[fieldname] = dspval or nbsp
|
||||
self.custom.inpval[fieldname] = inpval or ''
|
||||
self.custom.inpval[fieldname] = inpval if not inpval is None else ''
|
||||
self.custom.widget[fieldname] = inp
|
||||
|
||||
# if a record is provided and found, as is linkto
|
||||
@@ -1118,16 +1141,16 @@ class SQLFORM(FORM):
|
||||
if defaults and len(args) - len(defaults) == 4 or len(args) == 4:
|
||||
table = TABLE()
|
||||
for id,a,b,c in xfields:
|
||||
raw_b = self.field_parent[id] = b
|
||||
newrows = formstyle(id,a,raw_b,c)
|
||||
newrows = formstyle(id,a,b,c)
|
||||
self.field_parent[id] = getattr(b,'parent',None)
|
||||
if type(newrows).__name__ != "tuple":
|
||||
newrows = [newrows]
|
||||
for newrow in newrows:
|
||||
table.append(newrow)
|
||||
else:
|
||||
for id,a,b,c in xfields:
|
||||
self.field_parent[id] = b
|
||||
table = formstyle(self, xfields)
|
||||
for id,a,b,c in xfields:
|
||||
self.field_parent[id] = getattr(b,'parent',None)
|
||||
else:
|
||||
raise RuntimeError, 'formstyle not supported'
|
||||
return table
|
||||
@@ -1142,6 +1165,7 @@ class SQLFORM(FORM):
|
||||
dbio=True,
|
||||
hideerror=False,
|
||||
detect_record_change=False,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
"""
|
||||
@@ -1166,7 +1190,8 @@ class SQLFORM(FORM):
|
||||
# implement logic to detect whether record exist but has been modified
|
||||
# server side
|
||||
self.record_changed = None
|
||||
if detect_record_change:
|
||||
self.detect_record_change = detect_record_change
|
||||
if self.detect_record_change:
|
||||
if self.record:
|
||||
self.record_changed = False
|
||||
serialized = '|'.join(str(self.record[k]) for k in self.table.fields())
|
||||
@@ -1222,6 +1247,7 @@ class SQLFORM(FORM):
|
||||
keepvalues,
|
||||
onvalidation,
|
||||
hideerror=hideerror,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self.deleted = \
|
||||
@@ -1231,31 +1257,41 @@ class SQLFORM(FORM):
|
||||
|
||||
auch = record_id and self.errors and self.deleted
|
||||
|
||||
# auch is true when user tries to delete a record
|
||||
# that does not pass validation, yet it should be deleted
|
||||
|
||||
if not ret and not auch:
|
||||
if self.record_changed and self.detect_record_change:
|
||||
message_onchange = \
|
||||
kwargs.setdefault("message_onchange",
|
||||
current.T("A record change was detected. " +
|
||||
"Consecutive update self-submissions " +
|
||||
"are not allowed. Try re-submitting or " +
|
||||
"refreshing the form page."))
|
||||
if message_onchange is not None:
|
||||
current.response.flash = message_onchange
|
||||
return ret
|
||||
elif (not ret) and (not auch):
|
||||
# auch is true when user tries to delete a record
|
||||
# that does not pass validation, yet it should be deleted
|
||||
for fieldname in self.fields:
|
||||
field = self.table[fieldname]
|
||||
### this is a workaround! widgets should always have default not None!
|
||||
if not field.widget and field.type.startswith('list:') and \
|
||||
not OptionsWidget.has_options(field):
|
||||
field.widget = self.widgets.list.widget
|
||||
if hasattr(field, 'widget') and field.widget and fieldname in request_vars:
|
||||
if field.widget and fieldname in request_vars:
|
||||
if fieldname in self.vars:
|
||||
value = self.vars[fieldname]
|
||||
elif self.record:
|
||||
value = self.record[fieldname]
|
||||
else:
|
||||
value = self.table[fieldname].default
|
||||
if field.type.startswith('list:') and \
|
||||
isinstance(value, str):
|
||||
if field.type.startswith('list:') and isinstance(value, str):
|
||||
value = [value]
|
||||
row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX)
|
||||
widget = field.widget(field, value)
|
||||
self.field_parent[row_id].components = [ widget ]
|
||||
self.field_parent[row_id]._traverse(False, hideerror)
|
||||
self.custom.widget[ fieldname ] = widget
|
||||
parent = self.field_parent[row_id]
|
||||
if parent:
|
||||
parent.components = [ widget ]
|
||||
parent._traverse(False, hideerror)
|
||||
self.custom.widget[fieldname] = widget
|
||||
self.accepted = ret
|
||||
return ret
|
||||
|
||||
@@ -1492,15 +1528,20 @@ class SQLFORM(FORM):
|
||||
'datetime':['=','!=','<','>','<=','>='],
|
||||
'integer':['=','!=','<','>','<=','>='],
|
||||
'double':['=','!=','<','>','<=','>='],
|
||||
'id':['=','!=','<','>','<=','>='],
|
||||
'reference':['=','!=','<','>','<=','>='],
|
||||
'boolean':['=','!=']}
|
||||
if fields[0]._db._adapter.dbengine=='google:datastore':
|
||||
search_options['string'] = ['=','!=','<','>','<=','>=']
|
||||
search_options['text'] = ['=','!=','<','>','<=','>=']
|
||||
search_options['list:string'] = ['contains']
|
||||
search_options['list:integer'] = ['contains']
|
||||
search_options['list:reference'] = ['contains']
|
||||
criteria = []
|
||||
selectfields = []
|
||||
for field in fields:
|
||||
name = str(field).replace('.','-')
|
||||
options = search_options.get(field.type,None)
|
||||
options = search_options.get(field.type.split(' ')[0],None)
|
||||
if options:
|
||||
label = isinstance(field.label,str) and T(field.label) or field.label
|
||||
selectfields.append(OPTION(label, _value=str(field)))
|
||||
@@ -1789,16 +1830,19 @@ class SQLFORM(FORM):
|
||||
table = db[request.args[-2]]
|
||||
record = table(request.args[-1]) or redirect(URL('error'))
|
||||
sqlformargs.update(editargs)
|
||||
update_form = SQLFORM(table, record, upload=upload, ignore_rw=ignore_rw,
|
||||
formstyle=formstyle, deletable=deletable,
|
||||
_class='web2py_form',
|
||||
submit_button=T('Submit'),
|
||||
delete_label=T('Check to delete'),
|
||||
**sqlformargs)
|
||||
update_form.process(formname=formname,
|
||||
onvalidation=onvalidation,
|
||||
onsuccess=onupdate,
|
||||
next=referrer)
|
||||
update_form = SQLFORM(
|
||||
table,
|
||||
record, upload=upload, ignore_rw=ignore_rw,
|
||||
formstyle=formstyle, deletable=deletable,
|
||||
_class='web2py_form',
|
||||
submit_button=T('Submit'),
|
||||
delete_label=T('Check to delete'),
|
||||
**sqlformargs)
|
||||
update_form.process(
|
||||
formname=formname,
|
||||
onvalidation=onvalidation,
|
||||
onsuccess=onupdate,
|
||||
next=referrer)
|
||||
res = DIV(buttons(view=details, record=record),
|
||||
update_form, formfooter, _class=_class)
|
||||
res.create_form = create_form
|
||||
@@ -1852,7 +1896,7 @@ class SQLFORM(FORM):
|
||||
rows = dbset.select(cacheable=True)
|
||||
else:
|
||||
rows = dbset.select(left=left,orderby=orderby,
|
||||
cacheable=True*columns)
|
||||
cacheable=True,*columns)
|
||||
|
||||
if export_type in exportManager:
|
||||
value = exportManager[export_type]
|
||||
@@ -1890,7 +1934,7 @@ class SQLFORM(FORM):
|
||||
search_widget = search_widget[tablename]
|
||||
if search_widget=='default':
|
||||
search_menu = SQLFORM.search_menu(sfields)
|
||||
search_widget = lambda sfield, url: CAT(add,FORM(
|
||||
search_widget = lambda sfield, url: CAT(FORM(
|
||||
INPUT(_name='keywords',_value=request.vars.keywords,
|
||||
_id='web2py_keywords',_onfocus="jQuery('#w2p_query_fields').change();jQuery('#w2p_query_panel').slideDown();"),
|
||||
INPUT(_type='submit',_value=T('Search'),_class="btn"),
|
||||
@@ -1898,6 +1942,7 @@ class SQLFORM(FORM):
|
||||
_onclick="jQuery('#web2py_keywords').val('');"),
|
||||
_method="GET",_action=url),search_menu)
|
||||
form = search_widget and search_widget(sfields,url()) or ''
|
||||
console.append(add)
|
||||
console.append(form)
|
||||
keywords = request.vars.get('keywords','')
|
||||
try:
|
||||
@@ -1942,8 +1987,7 @@ class SQLFORM(FORM):
|
||||
if columns and not str(field) in columns: continue
|
||||
if not field.readable: continue
|
||||
key = str(field)
|
||||
header = headers.get(str(field),
|
||||
hasattr(field,'label') and field.label or key)
|
||||
header = headers.get(str(field), field.label or key)
|
||||
if sortable:
|
||||
if key == order:
|
||||
key, marker = '~'+order, sorter_icons[0]
|
||||
@@ -2003,7 +2047,7 @@ class SQLFORM(FORM):
|
||||
table_fields = [f for f in fields if f._tablename in tablenames]
|
||||
rows = dbset.select(left=left,orderby=orderby,
|
||||
groupby=groupby,limitby=limitby,
|
||||
cacheable=True,*table_fields)
|
||||
*table_fields)
|
||||
except SyntaxError:
|
||||
rows = None
|
||||
error = T("Query Not Supported")
|
||||
@@ -2226,7 +2270,9 @@ class SQLFORM(FORM):
|
||||
else:
|
||||
break
|
||||
if nargs>len(args)+1:
|
||||
query = (field == id)
|
||||
query = (field == id)
|
||||
if isinstance(linked_tables,dict):
|
||||
linked_tables = linked_tables.get(table._tablename,[])
|
||||
if linked_tables is None or referee in linked_tables:
|
||||
field.represent = lambda id,r=None,referee=referee,rep=field.represent: A(callable(rep) and rep(id) or id,_class=trap_class(),_href=url(args=['view',referee,id]))
|
||||
except (KeyError,ValueError,TypeError):
|
||||
@@ -2251,6 +2297,8 @@ class SQLFORM(FORM):
|
||||
if rfield.readable:
|
||||
check[rfield.tablename] = \
|
||||
check.get(rfield.tablename,[])+[rfield.name]
|
||||
if isinstance(linked_tables,dict):
|
||||
linked_tables = linked_tables.get(table._tablename,[])
|
||||
for tablename in sorted(check):
|
||||
linked_fieldnames = check[tablename]
|
||||
tb = db[tablename]
|
||||
@@ -2366,6 +2414,7 @@ class SQLTABLE(TABLE):
|
||||
extracolumns=None,
|
||||
selectid=None,
|
||||
renderstyle=False,
|
||||
cid=None,
|
||||
**attributes
|
||||
):
|
||||
|
||||
@@ -2403,7 +2452,7 @@ class SQLTABLE(TABLE):
|
||||
row.append(TH(coldict['label'],**attrcol))
|
||||
elif orderby:
|
||||
row.append(TH(A(headers.get(c, c),
|
||||
_href=th_link+'?orderby=' + c)))
|
||||
_href=th_link+'?orderby=' + c, cid=cid)))
|
||||
else:
|
||||
row.append(TH(headers.get(c, c)))
|
||||
|
||||
@@ -2698,8 +2747,3 @@ class ExporterXML(ExportClass):
|
||||
out.write('</row>\n')
|
||||
out.write('</rows>')
|
||||
return str(out.getvalue())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+6
-19
@@ -38,30 +38,17 @@ class Storage(dict):
|
||||
>>> print o.a
|
||||
None
|
||||
"""
|
||||
__slots__=()
|
||||
|
||||
__slots__=()
|
||||
__setattr__ = dict.__setitem__
|
||||
__delattr__ = dict.__delitem__
|
||||
__getitem__ = dict.get
|
||||
__getattr__ = dict.get
|
||||
__repr__ = lambda self: '<Storage %s>' % dict.__repr__(self)
|
||||
__getstate__ = dict
|
||||
__setstate__ = dict.update
|
||||
# def __getattr__(self, key):
|
||||
# return dict.get(self, key, None)
|
||||
# def __setattr__(self, key, value):
|
||||
# self[key] = value
|
||||
# def __getitem__(self, key):
|
||||
# return dict.get(self, key, None)
|
||||
# def __delattr__(self, key):
|
||||
# del self[key]
|
||||
# def __repr__(self):
|
||||
# return '<Storage %s>' % dict.__repr__(self)
|
||||
# def __getstate__(self):
|
||||
# return dict(self)
|
||||
# def __setstate__(self,values):
|
||||
# self.update(values)
|
||||
|
||||
# http://stackoverflow.com/questions/5247250/why-does-pickle-getstate-accept-as-a-return-value-the-very-instance-it-requi
|
||||
__getstate__ = lambda self: None
|
||||
__copy__ = lambda self: Storage(self)
|
||||
|
||||
|
||||
def getlist(self,key):
|
||||
"""
|
||||
Return a Storage value as a list.
|
||||
|
||||
+39
-34
@@ -43,10 +43,11 @@ def stream_file_or_304_or_206(
|
||||
chunk_size = DEFAULT_CHUNK_SIZE,
|
||||
request = None,
|
||||
headers = {},
|
||||
status = 200,
|
||||
error_message = None,
|
||||
):
|
||||
if error_message is None:
|
||||
error_message = rewrite.thread.routes.error_message % 'invalid request'
|
||||
error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
|
||||
try:
|
||||
fp = open(static_file)
|
||||
except IOError, e:
|
||||
@@ -60,56 +61,60 @@ def stream_file_or_304_or_206(
|
||||
fp.close()
|
||||
stat_file = os.stat(static_file)
|
||||
fsize = stat_file[stat.ST_SIZE]
|
||||
mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
time.gmtime(stat_file[stat.ST_MTIME]))
|
||||
modified = stat_file[stat.ST_MTIME]
|
||||
mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT',time.gmtime(modified))
|
||||
headers.setdefault('Content-Type', contenttype(static_file))
|
||||
headers.setdefault('Last-Modified', mtime)
|
||||
headers.setdefault('Pragma', 'cache')
|
||||
headers.setdefault('Cache-Control', 'private')
|
||||
|
||||
if request and request.env.http_if_modified_since == mtime:
|
||||
raise HTTP(304, **{'Content-Type': headers['Content-Type']})
|
||||
# if this is a normal response and not a respnse to an error page
|
||||
if status == 200:
|
||||
if request and request.env.http_if_modified_since == mtime:
|
||||
raise HTTP(304, **{'Content-Type': headers['Content-Type']})
|
||||
|
||||
elif request and request.env.http_range:
|
||||
start_items = regex_start_range.findall(request.env.http_range)
|
||||
if not start_items:
|
||||
start_items = [0]
|
||||
stop_items = regex_stop_range.findall(request.env.http_range)
|
||||
if not stop_items or int(stop_items[0]) > fsize - 1:
|
||||
stop_items = [fsize - 1]
|
||||
part = (int(start_items[0]), int(stop_items[0]), fsize)
|
||||
bytes = part[1] - part[0] + 1
|
||||
try:
|
||||
stream = open(static_file, 'rb')
|
||||
except IOError, e:
|
||||
if e[0] in (errno.EISDIR, errno.EACCES):
|
||||
raise HTTP(403)
|
||||
else:
|
||||
raise HTTP(404)
|
||||
stream.seek(part[0])
|
||||
headers['Content-Range'] = 'bytes %i-%i/%i' % part
|
||||
headers['Content-Length'] = '%i' % bytes
|
||||
status = 206
|
||||
else:
|
||||
elif request and request.env.http_range:
|
||||
start_items = regex_start_range.findall(request.env.http_range)
|
||||
if not start_items:
|
||||
start_items = [0]
|
||||
stop_items = regex_stop_range.findall(request.env.http_range)
|
||||
if not stop_items or int(stop_items[0]) > fsize - 1:
|
||||
stop_items = [fsize - 1]
|
||||
part = (int(start_items[0]), int(stop_items[0]), fsize)
|
||||
bytes = part[1] - part[0] + 1
|
||||
try:
|
||||
stream = open(static_file, 'rb')
|
||||
except IOError, e:
|
||||
if e[0] in (errno.EISDIR, errno.EACCES):
|
||||
raise HTTP(403)
|
||||
else:
|
||||
raise HTTP(404)
|
||||
stream.seek(part[0])
|
||||
headers['Content-Range'] = 'bytes %i-%i/%i' % part
|
||||
headers['Content-Length'] = '%i' % bytes
|
||||
status = 206
|
||||
# in all the other cases (not 304, not 206, but 200 or error page)
|
||||
if status != 206:
|
||||
enc = request.env.http_accept_encoding
|
||||
if enc and 'gzip' in enc and not 'Content-Encoding' in headers:
|
||||
gzipped = static_file + '.gz'
|
||||
if os.path.isfile(gzipped) and os.path.getmtime(gzipped)>modified:
|
||||
static_file = gzipped
|
||||
fsize = os.path.getsize(gzipped)
|
||||
headers['Content-Encoding'] = 'gzip'
|
||||
headers['Vary'] = 'Accept-Encoding'
|
||||
try:
|
||||
stream = open(static_file, 'rb')
|
||||
except IOError, e:
|
||||
# this better does not happer when returning an error page ;-)
|
||||
if e[0] in (errno.EISDIR, errno.EACCES):
|
||||
raise HTTP(403)
|
||||
else:
|
||||
raise HTTP(404)
|
||||
headers['Content-Length'] = fsize
|
||||
bytes = None
|
||||
status = 200
|
||||
if request and request.env.web2py_use_wsgi_file_wrapper:
|
||||
wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
|
||||
else:
|
||||
wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes)
|
||||
raise HTTP(status, wrapped, **headers)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -416,7 +416,7 @@ class TemplateParser(object):
|
||||
# Allow Views to include other views dynamically
|
||||
context = self.context
|
||||
if current and not "response" in context:
|
||||
context["response"] = current.response
|
||||
context["response"] = getattr(current,'response',None)
|
||||
|
||||
# Get the filename; filename looks like ``"template.html"``.
|
||||
# We need to eval to remove the quotes and get the string type.
|
||||
|
||||
+12
-7
@@ -311,12 +311,13 @@ class TestDatetime(unittest.TestCase):
|
||||
9, 30)), 3)
|
||||
self.assertEqual(len(db(db.t.a == datetime.datetime(1971, 12,
|
||||
21, 11, 30)).select()), 1)
|
||||
self.assertEqual(len(db(db.t.a.year() == 1971).select()), 2)
|
||||
self.assertEqual(len(db(db.t.a.month() == 12).select()), 2)
|
||||
self.assertEqual(len(db(db.t.a.day() == 21).select()), 3)
|
||||
self.assertEqual(len(db(db.t.a.hour() == 11).select()), 1)
|
||||
self.assertEqual(len(db(db.t.a.minutes() == 30).select()), 3)
|
||||
self.assertEqual(len(db(db.t.a.seconds() == 0).select()), 3)
|
||||
self.assertEqual(db(db.t.a.year() == 1971).count(), 2)
|
||||
self.assertEqual(db(db.t.a.month() == 12).count(), 2)
|
||||
self.assertEqual(db(db.t.a.day() == 21).count(), 3)
|
||||
self.assertEqual(db(db.t.a.hour() == 11).count(), 1)
|
||||
self.assertEqual(db(db.t.a.minutes() == 30).count(), 3)
|
||||
self.assertEqual(db(db.t.a.seconds() == 0).count(), 3)
|
||||
self.assertEqual(db(db.t.a.epoch()<365*24*3600).count(),1)
|
||||
db.t.drop()
|
||||
|
||||
|
||||
@@ -329,7 +330,7 @@ class TestExpressions(unittest.TestCase):
|
||||
self.assertEqual(db.t.insert(a=2), 2)
|
||||
self.assertEqual(db.t.insert(a=3), 3)
|
||||
self.assertEqual(db(db.t.a == 3).update(a=db.t.a + 1), 1)
|
||||
self.assertEqual(len(db(db.t.a == 4).select()), 1)
|
||||
self.assertEqual(db(db.t.a == 4).count(), 1)
|
||||
db.t.drop()
|
||||
|
||||
|
||||
@@ -447,18 +448,22 @@ class TestMigrations(unittest.TestCase):
|
||||
db = DAL('sqlite://.storage.db')
|
||||
db.define_table('t', Field('a'), migrate='.storage.table')
|
||||
db.commit()
|
||||
db.close()
|
||||
db = DAL('sqlite://.storage.db')
|
||||
db.define_table('t', Field('a'), Field('b'),
|
||||
migrate='.storage.table')
|
||||
db.commit()
|
||||
db.close()
|
||||
db = DAL('sqlite://.storage.db')
|
||||
db.define_table('t', Field('a'), Field('b', 'text'),
|
||||
migrate='.storage.table')
|
||||
db.commit()
|
||||
db.close()
|
||||
db = DAL('sqlite://.storage.db')
|
||||
db.define_table('t', Field('a'), migrate='.storage.table')
|
||||
db.t.drop()
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists('.storage.db'):
|
||||
|
||||
@@ -163,17 +163,17 @@ default_application = 'defapp'
|
||||
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/bad!ctl')
|
||||
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/ctlr/bad!fcn')
|
||||
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/ctlr/fcn.bad!ext')
|
||||
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path \(args\)\]', filter_url, 'http://domain.com/appc/init/fcn/bad!arg')
|
||||
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/appc/init/fcn/bad!arg')
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
self.assertEqual(filter_url('http://domain.com/welcome/default/fcn_1'), "/welcome/default/fcn_1")
|
||||
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1')
|
||||
try:
|
||||
# 2.7+ only
|
||||
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/welcome/default/fcn-1')
|
||||
except AttributeError:
|
||||
pass
|
||||
#self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1')
|
||||
#try:
|
||||
# # 2.7+ only
|
||||
# self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/welcome/default/fcn-1')
|
||||
#except AttributeError:
|
||||
# pass
|
||||
|
||||
def test_routes_error(self):
|
||||
'''
|
||||
|
||||
@@ -46,6 +46,18 @@ class TestWeb(unittest.TestCase):
|
||||
client.get('site')
|
||||
client.get('design/welcome')
|
||||
|
||||
class TestStaticCacheControl(unittest.TestCase):
|
||||
def testWebClient(self):
|
||||
s=WebClient('http://127.0.0.1:8000/welcome/')
|
||||
s.get('static/js/web2py.js')
|
||||
assert('expires' not in s.headers)
|
||||
assert(not s.headers['cache-control'].startswith('max-age'))
|
||||
text = s.text
|
||||
s.get('static/_1.2.3/js/web2py.js')
|
||||
assert(text == s.text)
|
||||
assert('expires' in s.headers)
|
||||
assert(s.headers['cache-control'].startswith('max-age'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
||||
+185
-101
@@ -264,6 +264,7 @@ class Mail(object):
|
||||
cc=None,
|
||||
bcc=None,
|
||||
reply_to=None,
|
||||
sender='%(sender)s',
|
||||
encoding='utf-8',
|
||||
raw=False,
|
||||
headers={}
|
||||
@@ -606,7 +607,8 @@ class Mail(object):
|
||||
# no cryptography process as usual
|
||||
payload=payload_in
|
||||
|
||||
payload['From'] = encoded_or_raw(self.settings.sender.decode(encoding))
|
||||
sender = sender % dict(sender=self.settings.sender)
|
||||
payload['From'] = encoded_or_raw(sender.decode(encoding))
|
||||
origTo = to[:]
|
||||
if to:
|
||||
payload['To'] = encoded_or_raw(', '.join(to).decode(encoding))
|
||||
@@ -623,10 +625,10 @@ class Mail(object):
|
||||
for k,v in headers.iteritems():
|
||||
payload[k] = encoded_or_raw(v.decode(encoding))
|
||||
result = {}
|
||||
try:
|
||||
try:
|
||||
if self.settings.server == 'logging':
|
||||
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \
|
||||
('-'*40,self.settings.sender,
|
||||
('-'*40,sender,
|
||||
', '.join(to),subject,
|
||||
text or html,'-'*40))
|
||||
elif self.settings.server == 'gae':
|
||||
@@ -794,6 +796,10 @@ def addrow(form, a, b, c, style, _id, position=-1):
|
||||
DIV(b, _class='w2p_fw'),
|
||||
DIV(c, _class='w2p_fc'),
|
||||
_id = _id))
|
||||
elif style == "bootstrap":
|
||||
form[0].insert(position, DIV(LABEL(a,_class='control-label'),
|
||||
DIV(b,SPAN(c, _class='inline-help'),_class='controls'),
|
||||
_class='control-group',_id = _id))
|
||||
else:
|
||||
form[0].insert(position, TR(TD(LABEL(a),_class='w2p_fl'),
|
||||
TD(b,_class='w2p_fw'),
|
||||
@@ -1044,7 +1050,7 @@ class Auth(object):
|
||||
|
||||
def __init__(self, environment=None, db=None, mailer=True,
|
||||
hmac_key=None, controller='default', function='user',
|
||||
cas_provider=None, signature=True):
|
||||
cas_provider=None, signature=True, secure=False):
|
||||
"""
|
||||
auth=Auth(db)
|
||||
|
||||
@@ -1064,6 +1070,8 @@ class Auth(object):
|
||||
session = current.session
|
||||
auth = session.auth
|
||||
self.user_groups = auth and auth.user_groups or {}
|
||||
if secure:
|
||||
request.requires_https()
|
||||
if auth and auth.last_visit and auth.last_visit + \
|
||||
datetime.timedelta(days=0, seconds=auth.expiration) > request.now:
|
||||
self.user = auth.user
|
||||
@@ -1191,7 +1199,10 @@ class Auth(object):
|
||||
'reset_password','request_reset_password',
|
||||
'change_password','profile','groups',
|
||||
'impersonate','not_authorized'):
|
||||
return getattr(self,args[0])()
|
||||
if len(request.args) >= 2 and args[0]=='impersonate':
|
||||
return getattr(self,args[0])(request.args[1])
|
||||
else:
|
||||
return getattr(self,args[0])()
|
||||
elif args[0]=='cas' and not self.settings.cas_provider:
|
||||
if args(1) == self.settings.cas_actions['login']:
|
||||
return self.cas_login(version=2)
|
||||
@@ -1598,7 +1609,7 @@ class Auth(object):
|
||||
if settings.cas_provider: ### THIS IS NOT LAZY
|
||||
settings.actions_disabled = \
|
||||
['profile','register','change_password',
|
||||
'request_reset_password']
|
||||
'request_reset_password','retrieve_username']
|
||||
from gluon.contrib.login_methods.cas_auth import CasAuth
|
||||
maps = settings.cas_maps
|
||||
if not maps:
|
||||
@@ -1616,6 +1627,7 @@ class Auth(object):
|
||||
urlbase = settings.cas_provider,
|
||||
actions=actions,
|
||||
maps=maps)
|
||||
return self
|
||||
|
||||
def log_event(self, description, vars=None, origin='auth'):
|
||||
"""
|
||||
@@ -1700,7 +1712,8 @@ class Auth(object):
|
||||
"""
|
||||
login the user = db.auth_user(id)
|
||||
"""
|
||||
# user=Storage(self.table_user()._filter_fields(user,id=True))
|
||||
user = Storage(self.table_user()._filter_fields(user,id=True))
|
||||
if 'password' in user: del user.password
|
||||
current.session.auth = Storage(
|
||||
user = user,
|
||||
last_visit = current.request.now,
|
||||
@@ -1901,20 +1914,33 @@ class Auth(object):
|
||||
|
||||
if self.settings.remember_me_form:
|
||||
## adds a new input checkbox "remember me for longer"
|
||||
addrow(form,XML(" "),
|
||||
DIV(XML(" "),
|
||||
INPUT(_type='checkbox',
|
||||
_class='checkbox',
|
||||
_id="auth_user_remember",
|
||||
_name="remember",
|
||||
),
|
||||
XML(" "),
|
||||
if self.settings.formstyle != 'bootstrap':
|
||||
addrow(form,XML(" "),
|
||||
DIV(XML(" "),
|
||||
INPUT(_type='checkbox',
|
||||
_class='checkbox',
|
||||
_id="auth_user_remember",
|
||||
_name="remember",
|
||||
),
|
||||
XML(" "),
|
||||
LABEL(
|
||||
self.messages.label_remember_me,
|
||||
_for="auth_user_remember",
|
||||
)),"",
|
||||
self.settings.formstyle,
|
||||
'auth_user_remember__row')
|
||||
elif self.settings.formstyle == 'bootstrap':
|
||||
addrow(form,
|
||||
"",
|
||||
LABEL(
|
||||
self.messages.label_remember_me,
|
||||
_for="auth_user_remember",
|
||||
)),"",
|
||||
self.settings.formstyle,
|
||||
'auth_user_remember__row')
|
||||
INPUT(_type='checkbox',
|
||||
_id="auth_user_remember",
|
||||
_name="remember"),
|
||||
self.messages.label_remember_me,
|
||||
_class="checkbox"),
|
||||
"",
|
||||
self.settings.formstyle,
|
||||
'auth_user_remember__row')
|
||||
|
||||
captcha = self.settings.login_captcha or \
|
||||
(self.settings.login_captcha!=False and self.settings.captcha)
|
||||
@@ -2131,6 +2157,9 @@ class Auth(object):
|
||||
repr(request.vars.get(passfield, None)),
|
||||
error_message=self.messages.mismatched_password))
|
||||
|
||||
if formstyle == 'bootstrap' :
|
||||
form.custom.widget.password_two['_class'] = 'input-xlarge'
|
||||
|
||||
addrow(form, self.messages.verify_password + self.settings.label_separator,
|
||||
form.custom.widget.password_two,
|
||||
self.messages.verify_password_comment,
|
||||
@@ -2420,7 +2449,7 @@ class Auth(object):
|
||||
session = current.session
|
||||
|
||||
if next is DEFAULT:
|
||||
next = self.settings.reset_password_next
|
||||
next = self.next or self.settings.reset_password_next
|
||||
try:
|
||||
key = request.vars.key or getarg(-1)
|
||||
t0 = int(key.split('-')[0])
|
||||
@@ -2723,7 +2752,7 @@ class Auth(object):
|
||||
self.user = session.auth.user
|
||||
if requested_id is DEFAULT and not request.post_vars:
|
||||
return SQLFORM.factory(Field('user_id', 'integer'))
|
||||
return self.user
|
||||
return SQLFORM(table_user, user.id, readonly=True)
|
||||
|
||||
def update_groups(self):
|
||||
if not self.user:
|
||||
@@ -3179,7 +3208,7 @@ class Auth(object):
|
||||
def wiki(self,
|
||||
slug=None,
|
||||
env=None,
|
||||
render=None,
|
||||
render='markmin',
|
||||
manage_permissions=False,
|
||||
force_prefix='',
|
||||
restrict_search=False,
|
||||
@@ -3318,6 +3347,7 @@ class Crud(object):
|
||||
message=DEFAULT,
|
||||
deletable=DEFAULT,
|
||||
formname=DEFAULT,
|
||||
**attributes
|
||||
):
|
||||
"""
|
||||
method: Crud.update(table, record, [next=DEFAULT
|
||||
@@ -3370,7 +3400,8 @@ class Crud(object):
|
||||
deletable=deletable,
|
||||
upload=self.settings.download_url,
|
||||
formstyle=self.settings.formstyle,
|
||||
separator=self.settings.label_separator
|
||||
separator=self.settings.label_separator,
|
||||
**attributes
|
||||
)
|
||||
self.accepted = False
|
||||
self.deleted = False
|
||||
@@ -3428,6 +3459,7 @@ class Crud(object):
|
||||
log=DEFAULT,
|
||||
message=DEFAULT,
|
||||
formname=DEFAULT,
|
||||
**attributes
|
||||
):
|
||||
"""
|
||||
method: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
|
||||
@@ -3454,6 +3486,7 @@ class Crud(object):
|
||||
message=message,
|
||||
deletable=False,
|
||||
formname=formname,
|
||||
**attributes
|
||||
)
|
||||
|
||||
def read(self, table, record):
|
||||
@@ -4313,34 +4346,39 @@ def prettydate(d,T=lambda x:x):
|
||||
return ''
|
||||
else:
|
||||
return '[invalid date]'
|
||||
if dt.days < 0:
|
||||
suffix = ' from now'
|
||||
dt = -dt
|
||||
else:
|
||||
suffix = ' ago'
|
||||
if dt.days >= 2*365:
|
||||
return T('%d years ago') % int(dt.days / 365)
|
||||
return T('%d years'+suffix) % int(dt.days / 365)
|
||||
elif dt.days >= 365:
|
||||
return T('1 year ago')
|
||||
return T('1 year'+suffix)
|
||||
elif dt.days >= 60:
|
||||
return T('%d months ago') % int(dt.days / 30)
|
||||
return T('%d months'+suffix) % int(dt.days / 30)
|
||||
elif dt.days > 21:
|
||||
return T('1 month ago')
|
||||
return T('1 month'+suffix)
|
||||
elif dt.days >= 14:
|
||||
return T('%d weeks ago') % int(dt.days / 7)
|
||||
return T('%d weeks'+suffix) % int(dt.days / 7)
|
||||
elif dt.days >= 7:
|
||||
return T('1 week ago')
|
||||
return T('1 week'+suffix)
|
||||
elif dt.days > 1:
|
||||
return T('%d days ago') % dt.days
|
||||
return T('%d days'+suffix) % dt.days
|
||||
elif dt.days == 1:
|
||||
return T('1 day ago')
|
||||
return T('1 day'+suffix)
|
||||
elif dt.seconds >= 2*60*60:
|
||||
return T('%d hours ago') % int(dt.seconds / 3600)
|
||||
return T('%d hours'+suffix) % int(dt.seconds / 3600)
|
||||
elif dt.seconds >= 60*60:
|
||||
return T('1 hour ago')
|
||||
return T('1 hour'+suffix)
|
||||
elif dt.seconds >= 2*60:
|
||||
return T('%d minutes ago') % int(dt.seconds / 60)
|
||||
return T('%d minutes'+suffix) % int(dt.seconds / 60)
|
||||
elif dt.seconds >= 60:
|
||||
return T('1 minute ago')
|
||||
return T('1 minute'+suffix)
|
||||
elif dt.seconds > 1:
|
||||
return T('%d seconds ago') % dt.seconds
|
||||
return T('%d seconds'+suffix) % dt.seconds
|
||||
elif dt.seconds == 1:
|
||||
return T('1 second ago')
|
||||
return T('1 second'+suffix)
|
||||
else:
|
||||
return T('now')
|
||||
|
||||
@@ -4539,6 +4577,7 @@ class Wiki(object):
|
||||
self.env['component'] = Wiki.component
|
||||
if render == 'markmin': render=self.markmin_render
|
||||
elif render == 'html': render=self.html_render
|
||||
self.render = render
|
||||
self.auth = auth
|
||||
if self.auth.user:
|
||||
self.force_prefix = force_prefix % self.auth.user
|
||||
@@ -4548,47 +4587,56 @@ class Wiki(object):
|
||||
perms = self.manage_permissions = manage_permissions
|
||||
self.restrict_search = restrict_search
|
||||
db = auth.db
|
||||
table_definitions = {
|
||||
'wiki_page':{
|
||||
'args':[
|
||||
Field('slug',
|
||||
requires=[IS_SLUG(),
|
||||
IS_NOT_IN_DB(db,'wiki_page.slug')],
|
||||
readable=False,writable=False),
|
||||
Field('title',unique=True),
|
||||
Field('body','text',notnull=True),
|
||||
Field('tags','list:string'),
|
||||
Field('can_read','list:string',
|
||||
writable=perms,
|
||||
readable=perms,
|
||||
default=[Wiki.everybody]),
|
||||
Field('can_edit', 'list:string',
|
||||
writable=perms,readable=perms,
|
||||
default=[Wiki.everybody]),
|
||||
Field('changelog'),
|
||||
Field('html','text',compute=render,
|
||||
readable=False, writable=False),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(title)s'}},
|
||||
'wiki_tag':{
|
||||
'args':[
|
||||
Field('name'),
|
||||
Field('wiki_page','reference wiki_page'),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(name)s'}},
|
||||
'wiki_media':{
|
||||
'args':[
|
||||
Field('wiki_page','reference wiki_page'),
|
||||
Field('title',required=True),
|
||||
Field('file','upload',required=True),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(title)s'}}
|
||||
}
|
||||
table_definitions = [
|
||||
('wiki_page',{
|
||||
'args':[
|
||||
Field('slug',
|
||||
requires=[IS_SLUG(),
|
||||
IS_NOT_IN_DB(db,'wiki_page.slug')],
|
||||
readable=False,writable=False),
|
||||
Field('title',unique=True),
|
||||
Field('body','text',notnull=True),
|
||||
Field('tags','list:string'),
|
||||
Field('can_read','list:string',
|
||||
writable=perms,
|
||||
readable=perms,
|
||||
default=[Wiki.everybody]),
|
||||
Field('can_edit', 'list:string',
|
||||
writable=perms,readable=perms,
|
||||
default=[Wiki.everybody]),
|
||||
Field('changelog'),
|
||||
Field('html','text',compute=render,
|
||||
readable=False, writable=False),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(title)s'}}),
|
||||
('wiki_tag',{
|
||||
'args':[
|
||||
Field('name'),
|
||||
Field('wiki_page','reference wiki_page'),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(name)s'}}),
|
||||
('wiki_media',{
|
||||
'args':[
|
||||
Field('wiki_page','reference wiki_page'),
|
||||
Field('title',required=True),
|
||||
Field('filename','upload',required=True),
|
||||
auth.signature],
|
||||
'vars':{'format':'%(title)s'}})
|
||||
]
|
||||
|
||||
# define only non-existent tables
|
||||
for key, value in table_definitions.iteritems():
|
||||
for key, value in table_definitions:
|
||||
args = []
|
||||
if not key in db.tables():
|
||||
db.define_table(key, *value['args'], **value['vars'])
|
||||
# look for wiki_ extra fields in auth.settings
|
||||
extra_fields = auth.settings.extra_fields
|
||||
if extra_fields:
|
||||
if key in extra_fields:
|
||||
if extra_fields[key]:
|
||||
for field in extra_fields[key]:
|
||||
args.append(field)
|
||||
args += value['args']
|
||||
db.define_table(key, *args, **value['vars'])
|
||||
|
||||
def update_tags_insert(page,id,db=db):
|
||||
for tag in page.tags or []:
|
||||
@@ -4665,17 +4713,19 @@ class Wiki(object):
|
||||
)
|
||||
elif zero=='_cloud':
|
||||
return self.cloud()
|
||||
elif zero == '_preview':
|
||||
return self.preview(self.render)
|
||||
|
||||
def first_paragraph(self,page):
|
||||
if not self.can_read(page):
|
||||
mm = page.body.replace('\r','')
|
||||
mm = (page.body or '').replace('\r','')
|
||||
ps = [p for p in mm.split('\n\n') \
|
||||
if not p.startswith('#') and p.strip()]
|
||||
if ps: return ps[0]
|
||||
return ''
|
||||
|
||||
def fix_hostname(self,body):
|
||||
return body.replace('://HOSTNAME','://%s' % self.host)
|
||||
return (body or '').replace('://HOSTNAME','://%s' % self.host)
|
||||
|
||||
def read(self,slug):
|
||||
if slug in '_cloud':
|
||||
@@ -4730,7 +4780,8 @@ class Wiki(object):
|
||||
db.wiki_page.title.default = title_guess
|
||||
db.wiki_page.slug.default = slug
|
||||
if slug == 'wiki-menu':
|
||||
db.wiki_page.body.default = '- Menu Item > @////index\n- - Submenu > http://web2py.com'
|
||||
db.wiki_page.body.default = \
|
||||
'- Menu Item > @////index\n- - Submenu > http://web2py.com'
|
||||
else:
|
||||
db.wiki_page.body.default = '## %s\n\npage content' % title_guess
|
||||
vars = current.request.post_vars
|
||||
@@ -4744,17 +4795,41 @@ class Wiki(object):
|
||||
elif form.accepted:
|
||||
current.session.flash = 'page created'
|
||||
redirect(URL(args=slug))
|
||||
return dict(content=form)
|
||||
script = """
|
||||
$(function() {
|
||||
if (!$('#wiki_page_body').length) return;
|
||||
var pagecontent = $('#wiki_page_body');
|
||||
pagecontent.css('font-family', 'Monaco,Menlo,Consolas,"Courier New",monospace');
|
||||
var prevbutton = $('<button class="btn nopreview">Preview</button>');
|
||||
var preview = $('<div id="preview"></div>').hide();
|
||||
var table = $('form');
|
||||
prevbutton.insertBefore(table);
|
||||
preview.insertBefore(table);
|
||||
prevbutton.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
if (prevbutton.hasClass('nopreview')) {
|
||||
prevbutton.addClass('preview').removeClass('nopreview').html('Edit Source');
|
||||
web2py_ajax_page('post', '%(url)s', {body : $('#wiki_page_body').val()}, 'preview');
|
||||
table.fadeOut('medium', function() {preview.fadeIn()});
|
||||
} else {
|
||||
prevbutton.addClass('nopreview').removeClass('preview').html('Preview');
|
||||
preview.fadeOut('medium', function() {table.fadeIn()});
|
||||
}
|
||||
})
|
||||
})
|
||||
""" % dict(url=URL(args=('_preview')))
|
||||
return dict(content=TAG[''](form, SCRIPT(script)))
|
||||
|
||||
def editmedia(self,slug):
|
||||
auth = self.auth
|
||||
db = auth.db
|
||||
page = db.wiki_page(slug=slug)
|
||||
if not (page and self.can_edit(page)): return self.not_authorized(page)
|
||||
self.auth.db.wiki_media.id.represent = lambda id,row:\
|
||||
self.auth.db.wiki_media.id.represent = lambda id,row: \
|
||||
id if not row.filename else \
|
||||
SPAN('@////%i/%s.%s' % \
|
||||
(id,IS_SLUG.urlify(row.title.split('.')[0]),
|
||||
row.file.split('.')[-1]))
|
||||
row.filename.split('.')[-1]))
|
||||
self.auth.db.wiki_media.wiki_page.default = page.id
|
||||
self.auth.db.wiki_media.wiki_page.writable = False
|
||||
content = SQLFORM.grid(
|
||||
@@ -4799,7 +4874,7 @@ class Wiki(object):
|
||||
if self.manage_permissions:
|
||||
page = db.wiki_page(media.wiki_page)
|
||||
if not self.can_read(page): return self.not_authorized(page)
|
||||
request.args = [media.file]
|
||||
request.args = [media.filename]
|
||||
return current.response.download(request,db)
|
||||
else:
|
||||
raise HTTP(404)
|
||||
@@ -4884,12 +4959,13 @@ class Wiki(object):
|
||||
if query is None:
|
||||
query = (db.wiki_page.id==db.wiki_tag.wiki_page)&\
|
||||
(db.wiki_tag.name.belongs(tags))
|
||||
query = query|db.wiki_page.title.startswith(request.vars.q)
|
||||
query = query|db.wiki_page.title.contains(request.vars.q)
|
||||
if self.restrict_search and not self.manage():
|
||||
query = query&(db.wiki_page.created_by==self.auth.user_id)
|
||||
pages = db(query).select(
|
||||
pages = db(query).select(count,
|
||||
*fields,**dict(orderby=orderby or ~count,
|
||||
groupby=db.wiki_page.id,
|
||||
groupby=reduce(lambda a,b:a|b,fields),
|
||||
distinct=True,
|
||||
limitby=limitby))
|
||||
if request.extension in ('html','load'):
|
||||
if not pages:
|
||||
@@ -4897,18 +4973,19 @@ class Wiki(object):
|
||||
_class='w2p_wiki_form'))
|
||||
def link(t):
|
||||
return A(t,_href=URL(args='_search',vars=dict(q=t)))
|
||||
items = [DIV(H3(A(p.title,_href=URL(args=p.slug))),
|
||||
MARKMIN(self.first_paragraph(p)) \
|
||||
items = [DIV(H3(A(p.wiki_page.title,_href=URL(
|
||||
args=p.wiki_page.slug))),
|
||||
MARKMIN(self.first_paragraph(p.wiki_page)) \
|
||||
if preview else '',
|
||||
DIV(_class='w2p_wiki_tags',
|
||||
*[link(t.strip()) for t in \
|
||||
p.tags or [] if t.strip()]),
|
||||
p.wiki_page.tags or [] if t.strip()]),
|
||||
_class='w2p_wiki_search_item')
|
||||
for p in pages]
|
||||
content.append(DIV(_class='w2p_wiki_pages',*items))
|
||||
else:
|
||||
cloud=False
|
||||
content = [p.as_dict() for p in pages]
|
||||
content = [p.wiki_page.as_dict() for p in pages]
|
||||
elif cloud:
|
||||
content.append(self.cloud()['content'])
|
||||
if request.extension=='load':
|
||||
@@ -4919,19 +4996,26 @@ class Wiki(object):
|
||||
count = db.wiki_tag.wiki_page.count(distinct=True)
|
||||
ids = db(db.wiki_tag).select(
|
||||
db.wiki_tag.name,count,
|
||||
groupby=db.wiki_tag.name,
|
||||
orderby=~count,limitby=(0,20))
|
||||
distinct=True,
|
||||
groupby = db.wiki_tag.name,
|
||||
orderby = ~count, limitby=(0,20))
|
||||
if ids:
|
||||
a,b = ids[0](count), ids[-1](count)
|
||||
def scale(c):
|
||||
return '%.2f' % (3.0*(c-b)/max(a-b,1)+1)
|
||||
items = [A(item.wiki_tag.name,
|
||||
_style='padding:0.2em;font-size:%sem' \
|
||||
% scale(item(count)),
|
||||
_href=URL(args='_search',
|
||||
vars=dict(q=item.wiki_tag.name)))
|
||||
for item in ids]
|
||||
return dict(content=DIV(_class='w2p_cloud',*items))
|
||||
def style(c):
|
||||
STYLE ='padding:0 0.2em;line-height:%.2fem;font-size:%.2fem'
|
||||
size = (1.5*(c-b)/max(a-b,1)+1.3)
|
||||
return STYLE % (1.3,size)
|
||||
items = []
|
||||
for item in ids:
|
||||
items.append(A(item.wiki_tag.name,
|
||||
_style=style(item(count)),
|
||||
_href=URL(args='_search',
|
||||
vars=dict(q=item.wiki_tag.name))))
|
||||
items.append(' ')
|
||||
return dict(content = DIV(_class='w2p_cloud',*items))
|
||||
def preview(self, render):
|
||||
request = current.request
|
||||
return render(request.post_vars)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
|
||||
+46
-15
@@ -9,6 +9,9 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
This file specifically includes utilities for security.
|
||||
"""
|
||||
|
||||
import string
|
||||
import threading
|
||||
import struct
|
||||
import hashlib
|
||||
import hmac
|
||||
import uuid
|
||||
@@ -18,7 +21,11 @@ import os
|
||||
import re
|
||||
import logging
|
||||
import socket
|
||||
from contrib.pbkdf2 import pbkdf2_hex
|
||||
try:
|
||||
from contrib.pbkdf2 import pbkdf2_hex
|
||||
HAVE_PBKDF2 = True
|
||||
except ImportError:
|
||||
HAVE_PBKDF2 = False
|
||||
|
||||
logger = logging.getLogger("web2py")
|
||||
|
||||
@@ -88,7 +95,7 @@ DIGEST_ALG_BY_SIZE = {
|
||||
}
|
||||
|
||||
|
||||
### compute constant ctokens
|
||||
### compute constant CTOKENS
|
||||
def initialize_urandom():
|
||||
"""
|
||||
This function and the web2py_uuid follow from the following discussion:
|
||||
@@ -108,6 +115,7 @@ def initialize_urandom():
|
||||
random.seed(node_id + microseconds)
|
||||
try:
|
||||
os.urandom(1)
|
||||
have_urandom = True
|
||||
try:
|
||||
# try to add process-specific entropy
|
||||
frandom = open('/dev/urandom','wb')
|
||||
@@ -119,14 +127,33 @@ def initialize_urandom():
|
||||
# works anyway
|
||||
pass
|
||||
except NotImplementedError:
|
||||
have_urandom = False
|
||||
logger.warning(
|
||||
"""Cryptographically secure session management is not possible on your system because
|
||||
your system does not provide a cryptographically secure entropy source.
|
||||
This is not specific to web2py; consider deploying on a different operating system.""")
|
||||
return ctokens
|
||||
ctokens = initialize_urandom()
|
||||
unpacked_ctokens = struct.unpack('=QQ',string.join(
|
||||
(chr(x) for x in ctokens),''))
|
||||
return unpacked_ctokens, have_urandom
|
||||
UNPACKED_CTOKENS, HAVE_URANDOM = initialize_urandom()
|
||||
|
||||
def web2py_uuid():
|
||||
def fast_urandom16(urandom=[], locker = threading.RLock()):
|
||||
"""
|
||||
this is 4x faster than calling os.urandom(16) and prevents
|
||||
the "too many files open" issue with concurrent access to os.urandom()
|
||||
"""
|
||||
try:
|
||||
return urandom.pop()
|
||||
except IndexError:
|
||||
try:
|
||||
locker.acquire()
|
||||
ur = os.urandom(16*1024)
|
||||
urandom += [ur[i:i+16] for i in xrange(16,1024*16,16)]
|
||||
return ur[0:16]
|
||||
finally:
|
||||
locker.release()
|
||||
|
||||
def web2py_uuid(ctokens=UNPACKED_CTOKENS):
|
||||
"""
|
||||
This function follows from the following discussion:
|
||||
http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
|
||||
@@ -134,15 +161,17 @@ def web2py_uuid():
|
||||
It works like uuid.uuid4 except that tries to use os.urandom() if possible
|
||||
and it XORs the output with the tokens uniquely associated with this machine.
|
||||
"""
|
||||
bytes = [random.randrange(256) for i in range(16)]
|
||||
try:
|
||||
ubytes = [ord(c) for c in os.urandom(16)] # use /dev/urandom if possible
|
||||
bytes = [bytes[i] ^ ubytes[i] for i in range(16)]
|
||||
except NotImplementedError:
|
||||
pass
|
||||
## xor bytes with constant ctokens
|
||||
bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes))
|
||||
return str(uuid.UUID(bytes=bytes, version=4))
|
||||
rand_longs = (random.getrandbits(64),random.getrandbits(64))
|
||||
if HAVE_URANDOM:
|
||||
urand_longs = struct.unpack('=QQ', fast_urandom16())
|
||||
byte_s = struct.pack('=QQ',
|
||||
rand_longs[0]^urand_longs[0]^ctokens[0],
|
||||
rand_longs[1]^urand_longs[1]^ctokens[1])
|
||||
else:
|
||||
byte_s = struct.pack('=QQ',
|
||||
rand_longs[0]^ctokens[0],
|
||||
rand_longs[1]^ctokens[1])
|
||||
return str(uuid.UUID(bytes=byte_s, version=4))
|
||||
|
||||
REGEX_IPv4 = re.compile('(\d+)\.(\d+)\.(\d+)\.(\d+)')
|
||||
|
||||
@@ -161,6 +190,8 @@ def is_valid_ip_address(address):
|
||||
elif address.lower() in ('unkown',''):
|
||||
return False
|
||||
elif address.count('.')==3: # assume IPv4
|
||||
if address.startswith('::ffff:'):
|
||||
address = address[7:]
|
||||
if hasattr(socket,'inet_aton'): # try validate using the OS
|
||||
try:
|
||||
addr = socket.inet_aton(address)
|
||||
@@ -169,7 +200,7 @@ def is_valid_ip_address(address):
|
||||
return False
|
||||
else: # try validate using Regex
|
||||
match = REGEX_IPv4.match(address)
|
||||
if match and all(0<=int(math.group(i))<256 for i in (1,2,3,4)):
|
||||
if match and all(0<=int(match.group(i))<256 for i in (1,2,3,4)):
|
||||
return True
|
||||
return False
|
||||
elif hasattr(socket,'inet_pton'): # assume IPv6, try using the OS
|
||||
|
||||
+119
-20
@@ -117,6 +117,10 @@ class Validator(object):
|
||||
"""
|
||||
return value
|
||||
|
||||
def __call__(self,value):
|
||||
raise NotImplementedError
|
||||
return (value, None)
|
||||
|
||||
|
||||
class IS_MATCH(Validator):
|
||||
"""
|
||||
@@ -462,12 +466,13 @@ class IS_IN_DB(Validator):
|
||||
groupby = self.groupby
|
||||
distinct = self.distinct
|
||||
dd = dict(orderby=orderby, groupby=groupby,
|
||||
distinct=distinct, cache=self.cache)
|
||||
distinct=distinct, cache=self.cache,
|
||||
cacheable=True)
|
||||
records = self.dbset(table).select(*fields, **dd)
|
||||
else:
|
||||
orderby = self.orderby or \
|
||||
reduce(lambda a,b:a|b,(f for f in fields if not f.name=='id'))
|
||||
dd = dict(orderby=orderby, cache=self.cache)
|
||||
dd = dict(orderby=orderby, cache=self.cache, cacheable=True)
|
||||
records = self.dbset(table).select(table.ALL, **dd)
|
||||
self.theset = [str(r[self.kfield]) for r in records]
|
||||
if isinstance(self.label,str):
|
||||
@@ -488,6 +493,8 @@ class IS_IN_DB(Validator):
|
||||
table = self.dbset.db[self.ktable]
|
||||
field = table[self.kfield]
|
||||
if self.multiple:
|
||||
if self._and:
|
||||
raise NotImplementedError
|
||||
if isinstance(value,list):
|
||||
values=value
|
||||
elif value:
|
||||
@@ -497,8 +504,20 @@ class IS_IN_DB(Validator):
|
||||
if isinstance(self.multiple,(tuple,list)) and \
|
||||
not self.multiple[0]<=len(values)<self.multiple[1]:
|
||||
return (values, translate(self.error_message))
|
||||
if self.dbset(field.belongs(values)).count()==len(values):
|
||||
return (values, None)
|
||||
if self.theset:
|
||||
if not [v for v in values if not v in self.theset]:
|
||||
return (values, None)
|
||||
else:
|
||||
from dal import GoogleDatastoreAdapter
|
||||
def count(values, s=self.dbset, f=field):
|
||||
return s(f.belongs(map(int,values))).count()
|
||||
if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
|
||||
range_ids = range(0,len(values),30)
|
||||
total = sum(count(values[i:i+30]) for i in range_ids)
|
||||
if total == len(values):
|
||||
return (values, None)
|
||||
elif count(values) == len(values):
|
||||
return (values, None)
|
||||
elif self.theset:
|
||||
if str(value) in self.theset:
|
||||
if self._and:
|
||||
@@ -2329,11 +2348,12 @@ class IS_LIST_OF(Validator):
|
||||
new_value = []
|
||||
if self.other:
|
||||
for item in ivalue:
|
||||
(v, e) = self.other(item)
|
||||
if e:
|
||||
return (value, e)
|
||||
else:
|
||||
new_value.append(v)
|
||||
if item.strip():
|
||||
(v, e) = self.other(item)
|
||||
if e:
|
||||
return (ivalue, e)
|
||||
else:
|
||||
new_value.append(v)
|
||||
ivalue = new_value
|
||||
return (ivalue, None)
|
||||
|
||||
@@ -2592,7 +2612,9 @@ class LazyCrypt(object):
|
||||
key = self.crypt.key
|
||||
else:
|
||||
key = ''
|
||||
if stored_password.count('$')==2:
|
||||
if stored_password is None:
|
||||
return False
|
||||
elif stored_password.count('$')==2:
|
||||
(digest_alg, salt, hash) = stored_password.split('$')
|
||||
h = simple_hash(self.password, key, salt, digest_alg)
|
||||
temp_pass = '%s$%s$%s' % (digest_alg, salt, h)
|
||||
@@ -2707,6 +2729,43 @@ class CRYPT(object):
|
||||
return ('', translate(self.error_message))
|
||||
return (LazyCrypt(self,value),None)
|
||||
|
||||
# entropy calculator for IS_STRONG
|
||||
#
|
||||
lowerset = frozenset(unicode('abcdefghijklmnopqrstuvwxyz'))
|
||||
upperset = frozenset(unicode('ABCDEFGHIJKLMNOPQRSTUVWXYZ'))
|
||||
numberset = frozenset(unicode('0123456789'))
|
||||
sym1set = frozenset(unicode('!@#$%^&*()'))
|
||||
sym2set = frozenset(unicode('~`-_=+[]{}\\|;:\'",.<>?/'))
|
||||
otherset = frozenset(unicode('0123456789abcdefghijklmnopqrstuvwxyz')) # anything else
|
||||
|
||||
def calc_entropy(string):
|
||||
" calculate a simple entropy for a given string "
|
||||
import math
|
||||
alphabet = 0 # alphabet size
|
||||
other = set()
|
||||
seen = set()
|
||||
lastset = None
|
||||
if isinstance(string, str):
|
||||
string = unicode(string, encoding='utf8')
|
||||
for c in string:
|
||||
# classify this character
|
||||
inset = otherset
|
||||
for cset in (lowerset, upperset, numberset, sym1set, sym2set):
|
||||
if c in cset:
|
||||
inset = cset
|
||||
break
|
||||
# calculate effect of character on alphabet size
|
||||
if inset not in seen:
|
||||
seen.add(inset)
|
||||
alphabet += len(inset) # credit for a new character set
|
||||
elif c not in other:
|
||||
alphabet += 1 # credit for unique characters
|
||||
other.add(c)
|
||||
if inset is not lastset:
|
||||
alphabet += 1 # credit for set transitions
|
||||
lastset = cset
|
||||
entropy = len(string) * math.log(alphabet) / 0.6931471805599453 # math.log(2)
|
||||
return round(entropy, 2)
|
||||
|
||||
class IS_STRONG(object):
|
||||
"""
|
||||
@@ -2716,23 +2775,61 @@ class IS_STRONG(object):
|
||||
requires=IS_STRONG(min=10, special=2, upper=2))
|
||||
|
||||
enforces complexity requirements on a field
|
||||
|
||||
>>> IS_STRONG(es=True)('Abcd1234')
|
||||
('Abcd1234', 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|')
|
||||
>>> IS_STRONG(es=True)('Abcd1234!')
|
||||
('Abcd1234!', None)
|
||||
>>> IS_STRONG(es=True, entropy=1)('a')
|
||||
('a', None)
|
||||
>>> IS_STRONG(es=True, entropy=1, min=2)('a')
|
||||
('a', 'Minimum length is 2')
|
||||
>>> IS_STRONG(es=True, entropy=100)('abc123')
|
||||
('abc123', 'Entropy (32.35) less than required (100)')
|
||||
>>> IS_STRONG(es=True, entropy=100)('and')
|
||||
('and', 'Entropy (14.57) less than required (100)')
|
||||
>>> IS_STRONG(es=True, entropy=100)('aaa')
|
||||
('aaa', 'Entropy (14.42) less than required (100)')
|
||||
>>> IS_STRONG(es=True, entropy=100)('a1d')
|
||||
('a1d', 'Entropy (15.97) less than required (100)')
|
||||
>>> IS_STRONG(es=True, entropy=100)('añd')
|
||||
('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, min=8, max=20, upper=1, lower=1, number=1,
|
||||
special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
||||
invalid=' "', error_message=None):
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.upper = upper
|
||||
self.lower = lower
|
||||
self.number = number
|
||||
self.special = special
|
||||
def __init__(self, min=None, max=None, upper=None, lower=None, number=None,
|
||||
entropy=None,
|
||||
special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
||||
invalid=' "', error_message=None, es=False):
|
||||
self.entropy = entropy
|
||||
if entropy is None:
|
||||
# enforce default requirements
|
||||
self.min = 8 if min is None else min
|
||||
self.max = max # was 20, but that doesn't make sense
|
||||
self.upper = 1 if upper is None else upper
|
||||
self.lower = 1 if lower is None else lower
|
||||
self.number = 1 if number is None else number
|
||||
self.special = 1 if special is None else special
|
||||
else:
|
||||
# by default, an entropy spec is exclusive
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.upper = upper
|
||||
self.lower = lower
|
||||
self.number = number
|
||||
self.special = special
|
||||
self.specials = specials
|
||||
self.invalid = invalid
|
||||
self.error_message = error_message
|
||||
self.estring = es # return error message as string (for doctest)
|
||||
|
||||
def __call__(self, value):
|
||||
failures = []
|
||||
if self.entropy is not None:
|
||||
entropy = calc_entropy(value)
|
||||
if entropy < self.entropy:
|
||||
failures.append(translate("Entropy (%(have)s) less than required (%(need)s)") \
|
||||
% dict(have=entropy, need=self.entropy))
|
||||
if type(self.min) == int and self.min > 0:
|
||||
if not len(value) >= self.min:
|
||||
failures.append(translate("Minimum length is %s") % self.min)
|
||||
@@ -2743,7 +2840,7 @@ class IS_STRONG(object):
|
||||
all_special = [ch in value for ch in self.specials]
|
||||
if self.special > 0:
|
||||
if not all_special.count(True) >= self.special:
|
||||
failures.append(translate("Must include at least %s of the following : %s") \
|
||||
failures.append(translate("Must include at least %s of the following: %s") \
|
||||
% (self.special, self.specials))
|
||||
if self.invalid:
|
||||
all_invalid = [ch in value for ch in self.invalid]
|
||||
@@ -2783,6 +2880,8 @@ class IS_STRONG(object):
|
||||
if len(failures) == 0:
|
||||
return (value, None)
|
||||
if not self.error_message:
|
||||
if self.estring:
|
||||
return (value, '|'.join(failures))
|
||||
from html import XML
|
||||
return (value, XML('<br />'.join(failures)))
|
||||
else:
|
||||
|
||||
+32
-18
@@ -804,12 +804,12 @@ def console():
|
||||
default=False,
|
||||
help=msg)
|
||||
|
||||
parser.add_option('-N',
|
||||
'--no-cron',
|
||||
parser.add_option('-Y',
|
||||
'--run-cron',
|
||||
action='store_true',
|
||||
dest='nocron',
|
||||
dest='runcron',
|
||||
default=False,
|
||||
help='do not start cron automatically')
|
||||
help='start the background cron process')
|
||||
|
||||
parser.add_option('-J',
|
||||
'--cronjob',
|
||||
@@ -904,7 +904,7 @@ def console():
|
||||
|
||||
if options.cronjob:
|
||||
global_settings.cronjob = True # tell the world
|
||||
options.nocron = True # don't start cron jobs
|
||||
options.run = False # don't start cron jobs
|
||||
options.plain = True # cronjobs use a plain shell
|
||||
options.nobanner = True
|
||||
options.nogui = True
|
||||
@@ -955,6 +955,19 @@ def check_existent_app(options,appname):
|
||||
if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
|
||||
return True
|
||||
|
||||
def get_code_for_scheduler(app, options):
|
||||
if len(app) == 1 or app[1] == None:
|
||||
code = "from gluon import current;current._scheduler.loop()"
|
||||
else:
|
||||
code = "from gluon import current;current._scheduler.group_names = ['%s'];"
|
||||
code += "current._scheduler.loop()"
|
||||
code = code % ("','".join(app[1:]))
|
||||
app_ = app[0]
|
||||
if not check_existent_app(options, app_):
|
||||
print "Application '%s' doesn't exist, skipping" % (app_)
|
||||
return None, None
|
||||
return app_, code
|
||||
|
||||
def start_schedulers(options):
|
||||
try:
|
||||
from multiprocessing import Process
|
||||
@@ -965,20 +978,21 @@ def start_schedulers(options):
|
||||
apps = [(app.strip(), None) for app in options.scheduler.split(',')]
|
||||
if options.scheduler_groups:
|
||||
apps = options.scheduler_groups
|
||||
code = "from gluon import current;current._scheduler.loop()"
|
||||
logging.getLogger().setLevel(options.debuglevel)
|
||||
if len(apps) == 1 and not options.with_scheduler:
|
||||
app_, code = get_code_for_scheduler(apps[0], options)
|
||||
if not app_:
|
||||
return
|
||||
print 'starting single-scheduler for "%s"...' % app_
|
||||
run(app_,True,True,None,False,code)
|
||||
return
|
||||
for app in apps:
|
||||
if len(app) == 1 or app[1] == None:
|
||||
code = "from gluon import current;current._scheduler.loop()"
|
||||
else:
|
||||
code = "from gluon import current;current._scheduler.group_names = ['%s'];"
|
||||
code += "current._scheduler.loop()"
|
||||
code = code % ("','".join(app[1:]))
|
||||
app_ = app[0]
|
||||
if not check_existent_app(options, app_):
|
||||
print "Application '%s' doesn't exist, skipping" % (app_)
|
||||
app_, code = get_code_for_scheduler(app, options)
|
||||
if not app_:
|
||||
continue
|
||||
print 'starting scheduler for "%s"...' % app_
|
||||
args = (app_,True,True,None,False,code)
|
||||
logging.getLogger().setLevel(options.debuglevel)
|
||||
p = Process(target=run, args=args)
|
||||
processes.append(p)
|
||||
print "Currently running %s scheduler processes" % (len(processes))
|
||||
@@ -1069,13 +1083,13 @@ def start(cron=True):
|
||||
return
|
||||
|
||||
|
||||
# ## if -N or not cron disable cron in this *process*
|
||||
# ## if -H cron is enabled in this *process*
|
||||
# ## if --softcron use softcron
|
||||
# ## use hardcron in all other cases
|
||||
if cron and not options.nocron and options.softcron:
|
||||
if cron and options.runcron and options.softcron:
|
||||
print 'Using softcron (but this is not very efficient)'
|
||||
global_settings.web2py_crontype = 'soft'
|
||||
elif cron and not options.nocron:
|
||||
elif cron and options.runcron:
|
||||
logger.debug('Starting hardcron...')
|
||||
global_settings.web2py_crontype = 'hard'
|
||||
newcron.hardcron(options.folder).start()
|
||||
|
||||
+2
-2
@@ -94,8 +94,6 @@ class Web2pyService(Service):
|
||||
os.chdir(dir)
|
||||
from gluon.settings import global_settings
|
||||
global_settings.gluon_parent = dir
|
||||
from gluon.custom_import import custom_import_install
|
||||
custom_import_install(dir)
|
||||
return True
|
||||
except:
|
||||
self.log("Can't change to web2py working path; server is stopped")
|
||||
@@ -154,6 +152,8 @@ class Web2pyService(Service):
|
||||
def web2py_windows_service_handler(argv=None, opt_file='options'):
|
||||
path = os.path.dirname(__file__)
|
||||
web2py_path = up(path)
|
||||
if web2py_path.endswith('.zip'): # in case bianry distro 'library.zip'
|
||||
web2py_path = os.path.dirname(web2py_path)
|
||||
os.chdir(web2py_path)
|
||||
classstring = os.path.normpath(
|
||||
os.path.join(web2py_path,'gluon.winservice.Web2pyService'))
|
||||
|
||||
@@ -76,8 +76,8 @@ propagate=0
|
||||
# welcome app handler
|
||||
[logger_welcome]
|
||||
level=WARNING
|
||||
qualname=web2py.app.welcome,rotatingFileHandler
|
||||
handlers=consoleHandler
|
||||
qualname=web2py.app.welcome
|
||||
handlers=consoleHandler,rotatingFileHandler
|
||||
propagate=0
|
||||
|
||||
# loggers for legacy getLogger calls: Rocket and markdown
|
||||
|
||||
+6
-4
@@ -35,11 +35,13 @@ BASE = '' # optonal prefix for incoming URLs
|
||||
|
||||
routes_in = (
|
||||
# do not reroute admin unless you want to disable it
|
||||
(BASE+'/admin/?$anything','/admin/$anything'),
|
||||
(BASE+'/admin','/admin/default/index'),
|
||||
(BASE+'/admin/$anything','/admin/$anything'),
|
||||
# do not reroute appadmin unless you want to disable it
|
||||
(BASE+'/$app/appadmin/?$anything','/$app/appadmin/$anything'),
|
||||
(BASE+'/$app/appadmin','/$app/appadmin/index'),
|
||||
(BASE+'/$app/appadmin/$anything','/$app/appadmin/$anything'),
|
||||
# do not reroute static files
|
||||
(BASE+'/$app/static/?$anything','/$app/static/$anything'),
|
||||
(BASE+'/$app/static/$anything','/$app/static/$anything'),
|
||||
# reroute favicon and robots, use exable for lack of better choice
|
||||
('/favicon.ico', '/examples/static/favicon.ico'),
|
||||
('/robots.txt', '/examples/static/robots.txt'),
|
||||
@@ -55,7 +57,7 @@ routes_in = (
|
||||
|
||||
routes_out = (
|
||||
# do not reroute admin unless you want to disable it
|
||||
('/admin/$anything', BASE+'/admin/?$anything'),
|
||||
('/admin/$anything', BASE+'/admin/$anything'),
|
||||
# do not reroute appadmin unless you want to disable it
|
||||
('/$app/appadmin/$anything',BASE+'/$app/appadmin/$anything'),
|
||||
# do not reroute static files
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
paths = [sys.argv[1]]
|
||||
paths1 = []
|
||||
paths2 = []
|
||||
while paths:
|
||||
path = paths.pop()
|
||||
for filename in os.listdir(path):
|
||||
fullname = os.path.join(path,filename)
|
||||
if os.path.isdir(fullname):
|
||||
paths.append(fullname)
|
||||
else:
|
||||
extension = filename.split('.')[-1]
|
||||
if extension.lower() in ('png','gif','jpg','jpeg','js','css'):
|
||||
paths1.append((filename,fullname))
|
||||
if extension.lower() in ('css','js','py','html'):
|
||||
paths2.append(fullname)
|
||||
for filename,fullname in paths1:
|
||||
for otherfullname in paths2:
|
||||
if open(otherfullname).read().find(filename)>=0:
|
||||
break
|
||||
else:
|
||||
print fullname
|
||||
# os.system('hg rm '+fullname)
|
||||
# os.system('rm '+fullname)
|
||||
@@ -23,7 +23,7 @@ applications/welcome/controllers/default.py
|
||||
|
||||
# files and folders to exclude from gluon folder (comment with # if needed)
|
||||
IGNORED = """
|
||||
gluon/contrib/comet_messaging.py
|
||||
gluon/contrib/websocket_messaging.py
|
||||
gluon/contrib/feedparser.py
|
||||
gluon/contrib/generics.py
|
||||
gluon/contrib/gql.py
|
||||
|
||||
@@ -94,11 +94,11 @@ class SessionSetDb(SessionSet):
|
||||
"""Return list of SessionDb instances for existing sessions."""
|
||||
sessions = []
|
||||
tablename = 'web2py_session'
|
||||
if request.application:
|
||||
tablename = 'web2py_session_' + request.application
|
||||
if tablename in db:
|
||||
for row in db(db[tablename].id > 0).select():
|
||||
sessions.append(SessionDb(row))
|
||||
from gluon import current
|
||||
(record_id_name, table, record_id, unique_key) = \
|
||||
current.response._dbtable_and_field
|
||||
for row in table._db(table.id > 0).select():
|
||||
sessions.append(SessionDb(row))
|
||||
return sessions
|
||||
|
||||
|
||||
@@ -121,8 +121,11 @@ class SessionDb(object):
|
||||
self.row = row
|
||||
|
||||
def delete(self):
|
||||
from gluon import current
|
||||
(record_id_name, table, record_id, unique_key) = \
|
||||
current.response._dbtable_and_field
|
||||
self.row.delete_record()
|
||||
db.commit()
|
||||
table._db.commit()
|
||||
|
||||
def get(self):
|
||||
session = Storage()
|
||||
@@ -130,7 +133,13 @@ class SessionDb(object):
|
||||
return session
|
||||
|
||||
def last_visit_default(self):
|
||||
return self.row.modified_datetime
|
||||
if isinstance(self.row.modified_datetime, datetime.datetime):
|
||||
return self.row.modified_datetime
|
||||
else:
|
||||
try:
|
||||
return datetime.datetime.strptime(self.row.modified_datetime, '%Y-%m-%d %H:%M:%S.%f')
|
||||
except:
|
||||
print 'failed to retrieve last modified time (value: %s)' % self.row.modified_datetime
|
||||
|
||||
def __str__(self):
|
||||
return self.row.unique_key
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user