Compare commits

..
3 Commits
Author SHA1 Message Date
mdipierro ba9e95a5c2 faster non forms 2015-12-28 01:50:23 -06:00
mdipierro 157146b948 form.py 2015-12-27 17:37:18 -06:00
mdipierro 82e4b7030c fixed problem with CSRF 2015-12-27 12:07:52 -06:00
132 changed files with 11930 additions and 11458 deletions
-14
View File
@@ -12,22 +12,8 @@ python:
- 'pypy' - 'pypy'
install: install:
- |
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
export PYENV_ROOT="$HOME/.pyenv"
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
pushd "$PYENV_ROOT" && git pull && popd
else
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
fi
export PYPY_VERSION="5.0.1"
"$PYENV_ROOT/bin/pyenv" install --skip-existing "pypy-$PYPY_VERSION"
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
fi
- pip install -e . - pip install -e .
before_script: before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi; - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
+2 -58
View File
@@ -1,65 +1,9 @@
## 2.14.6 ## 2.13.1-2
- Increased test coverage (thanks Richard)
- Fixed some newly discovered security issues in admin:
CSRF vulnerability in admin that allows disabling apps
Brute force password attack vulnerability in admin
(thanks Narendra and Leonel)
## 2.14.1-5
- fixed two major security issues that caused the examples app to leak information
- new Auth(…,host_names=[…]) to prevent host header injection
- improved scheduler
- pep8 enhancements
- many bug fixes
- restored GAE support that was broken in 2.13.*
- improved fabfile for deployment
- refactored examples with stupid.css
- new JWT implementation (experimental)
- new gluon.contrib.redis_scheduler
- myconf.get
- LDAP groups (experimental)
- .flash -> .w2p_flash
- Updated feedparser.py 5.2.1
- Updated jQuery 1.12.2
- welcome app now checks for version number
- Redis improvements. New syntax:
BEFORE:
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
NOW:
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_cache import RedisCache
rconn = RConn()
# or RConn(host='localhost', port=6379,
# db=0, password=None, socket_timeout=None,
# socket_connect_timeout=None, .....)
# exactly as a redis.StrictRedis instance
cache.redis = RedisCache(redis_conn=rconn, debug=True)
BEFORE:
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
session.connect(request, response, db = sessiondb)
NOW:
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_session import RedisSession
rconn = RConn()
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
session.connect(request, response, db = sessiondb)
Many thanks to Richard and Simone for their work and dedication.
## 2.13.*
- fixed a security issue in request_reset_password - fixed a security issue in request_reset_password
- added fabfile.py - added fabfile.py
- fixed oauth2 renew token, thanks dokime7 - fixed oauth2 renew token, thanks dokime7
- fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning) - fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning)
- allow passing unicode to template render - allow passing unicode to template render
- allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg - allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg
- allow HttpOnly cookies - allow HttpOnly cookies
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.13.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48 Version 2.13.3-stable+timestamp.2015.12.24.08.08.22
+109 -134
View File
@@ -32,15 +32,15 @@ from gluon.languages import (read_possible_languages, read_dict, write_dict,
if DEMO_MODE and request.function in ['change_password', 'pack', if DEMO_MODE and request.function in ['change_password', 'pack',
'pack_custom', 'pack_plugin', 'upgrade_web2py', 'uninstall', 'pack_custom','pack_plugin', 'upgrade_web2py', 'uninstall',
'cleanup', 'compile_app', 'remove_compiled_app', 'delete', 'cleanup', 'compile_app', 'remove_compiled_app', 'delete',
'delete_plugin', 'create_file', 'upload_file', 'update_languages', 'delete_plugin', 'create_file', 'upload_file', 'update_languages',
'reload_routes', 'git_push', 'git_pull', 'install_plugin']: 'reload_routes', 'git_push', 'git_pull', 'install_plugin']:
session.flash = T('disabled in demo mode') session.flash = T('disabled in demo mode')
redirect(URL('site')) redirect(URL('site'))
if is_gae and request.function in ('edit', 'edit_language', if is_gae and request.function in ('edit', 'edit_language',
'edit_plurals', 'update_languages', 'create_file', 'install_plugin'): 'edit_plurals', 'update_languages', 'create_file', 'install_plugin'):
session.flash = T('disabled in GAE mode') session.flash = T('disabled in GAE mode')
redirect(URL('site')) redirect(URL('site'))
@@ -74,10 +74,8 @@ def log_progress(app, mode='EDIT', filename=None, progress=0):
def safe_open(a, b): def safe_open(a, b):
if (DEMO_MODE or is_gae) and ('w' in b or 'a' in b): if (DEMO_MODE or is_gae) and ('w' in b or 'a' in b):
class tmp: class tmp:
def write(self, data): def write(self, data):
pass pass
def close(self): def close(self):
pass pass
return tmp() return tmp()
@@ -121,9 +119,6 @@ def index():
send = URL('site') send = URL('site')
if session.authorized: if session.authorized:
redirect(send) redirect(send)
elif failed_login_count() >= allowed_number_of_attempts:
time.sleep(2 ** allowed_number_of_attempts)
raise HTTP(403)
elif request.vars.password: elif request.vars.password:
if verify_password(request.vars.password[:1024]): if verify_password(request.vars.password[:1024]):
session.authorized = True session.authorized = True
@@ -213,7 +208,6 @@ def site():
file_or_appurl = 'file' in request.vars or 'appurl' in request.vars file_or_appurl = 'file' in request.vars or 'appurl' in request.vars
class IS_VALID_APPNAME(object): class IS_VALID_APPNAME(object):
def __call__(self, value): def __call__(self, value):
if not re.compile('^\w+$').match(value): if not re.compile('^\w+$').match(value):
return (value, T('Invalid application name')) return (value, T('Invalid application name'))
@@ -274,7 +268,7 @@ def site():
raise Exception("404 file not found") raise Exception("404 file not found")
except Exception, e: except Exception, e:
session.flash = \ session.flash = \
DIV(T('Unable to download app because:'), PRE(repr(e))) DIV(T('Unable to download app because:'), PRE(str(e)))
redirect(URL(r=request)) redirect(URL(r=request))
fname = form_update.vars.url fname = form_update.vars.url
@@ -331,7 +325,7 @@ def report_progress(app):
if not m: if not m:
continue continue
days = -(request.now - datetime.datetime.strptime(m[0], days = -(request.now - datetime.datetime.strptime(m[0],
'%Y-%m-%d %H:%M:%S')).days '%Y-%m-%d %H:%M:%S')).days
counter += int(m[1]) counter += int(m[1])
events.append([days, counter]) events.append([days, counter])
return events return events
@@ -359,7 +353,6 @@ def pack():
session.flash = T('internal error: %s', e) session.flash = T('internal error: %s', e)
redirect(URL('site')) redirect(URL('site'))
def pack_plugin(): def pack_plugin():
app = get_app() app = get_app()
if len(request.args) == 2: if len(request.args) == 2:
@@ -375,6 +368,7 @@ def pack_plugin():
redirect(URL('plugin', args=request.args)) redirect(URL('plugin', args=request.args))
def pack_exe(app, base, filenames=None): def pack_exe(app, base, filenames=None):
import urllib import urllib
import zipfile import zipfile
@@ -403,20 +397,10 @@ def pack_exe(app, base, filenames=None):
def pack_custom(): def pack_custom():
app = get_app() app = get_app()
base = apath(app, r=request) base = apath(app, r=request)
def ignore(fs):
return [f for f in fs if not (
f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))]
files = {}
for (r, d, f) in os.walk(base):
files[r] = {'folders': ignore(d), 'files': ignore(f)}
if request.post_vars.file: if request.post_vars.file:
valid_set = set(os.path.relpath(os.path.join(r, f), base) for r in files for f in files[r]['files'])
files = request.post_vars.file files = request.post_vars.file
files = [files] if not isinstance(files, list) else files files = [files] if not isinstance(files,list) else files
files = [file for file in files if file in valid_set]
if request.post_vars.doexe is None: if request.post_vars.doexe is None:
fname = 'web2py.app.%s.w2p' % app fname = 'web2py.app.%s.w2p' % app
try: try:
@@ -433,7 +417,12 @@ def pack_custom():
redirect(URL(args=request.args)) redirect(URL(args=request.args))
else: else:
return pack_exe(app, base, files) return pack_exe(app, base, files)
def ignore(fs):
return [f for f in fs if not (
f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))]
files = {}
for (r,d,f) in os.walk(base):
files[r] = {'folders':ignore(d),'files':ignore(f)}
return locals() return locals()
@@ -496,14 +485,14 @@ def cleanup():
def compile_app(): def compile_app():
app = get_app() app = get_app()
c = app_compile(app, request, c = app_compile(app, request,
skip_failed_views=(request.args(1) == 'skip_failed_views')) skip_failed_views = (request.args(1) == 'skip_failed_views'))
if not c: if not c:
session.flash = T('application compiled') session.flash = T('application compiled')
elif isinstance(c, list): elif isinstance(c, list):
session.flash = DIV(*[T('application compiled'), BR(), BR(), session.flash = DIV(*[T('application compiled'), BR(), BR(),
T('WARNING: The following views could not be compiled:'), BR()] + T('WARNING: The following views could not be compiled:'), BR()] +
[CAT(BR(), view) for view in c] + [CAT(BR(), view) for view in c] +
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')]) [BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
else: else:
session.flash = DIV(T('Cannot compile: there are errors in your app:'), session.flash = DIV(T('Cannot compile: there are errors in your app:'),
CODE(c)) CODE(c))
@@ -544,8 +533,8 @@ def delete():
redirect(URL(sender, anchor=request.vars.id2)) redirect(URL(sender, anchor=request.vars.id2))
return dict(dialog=dialog, filename=filename) return dict(dialog=dialog, filename=filename)
def enable(): def enable():
if not URL.verify(request, hmac_key=session.hmac_key): raise HTTP(401)
app = get_app() app = get_app()
filename = os.path.join(apath(app, r=request), 'DISABLED') filename = os.path.join(apath(app, r=request), 'DISABLED')
if is_gae: if is_gae:
@@ -557,7 +546,6 @@ def enable():
safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now) safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now)
return SPAN(T('Enable'), _style='color:red') return SPAN(T('Enable'), _style='color:red')
def peek(): def peek():
""" Visualize object code """ """ Visualize object code """
app = get_app(request.vars.app) app = get_app(request.vars.app)
@@ -621,7 +609,7 @@ def edit():
# Load json only if it is ajax edited... # Load json only if it is ajax edited...
app = get_app(request.vars.app) app = get_app(request.vars.app)
app_path = apath(app, r=request) app_path = apath(app, r=request)
preferences = {'theme': 'web2py', 'editor': 'default', 'closetag': 'true', 'codefolding': 'false', 'tabwidth': '4', 'indentwithtabs': 'false', 'linenumbers': 'true', 'highlightline': 'true'} preferences={'theme':'web2py', 'editor': 'default', 'closetag': 'true', 'codefolding': 'false', 'tabwidth':'4', 'indentwithtabs':'false', 'linenumbers':'true', 'highlightline':'true'}
config = Config(os.path.join(request.folder, 'settings.cfg'), config = Config(os.path.join(request.folder, 'settings.cfg'),
section='editor', default_values={}) section='editor', default_values={})
preferences.update(config.read()) preferences.update(config.read())
@@ -629,14 +617,14 @@ def edit():
if not(request.ajax) and not(is_mobile): if not(request.ajax) and not(is_mobile):
# return the scaffolding, the rest will be through ajax requests # return the scaffolding, the rest will be through ajax requests
response.title = T('Editing %s') % app response.title = T('Editing %s') % app
return response.render('default/edit.html', dict(app=app, editor_settings=preferences)) return response.render ('default/edit.html', dict(app=app, editor_settings=preferences))
# show settings tab and save prefernces # show settings tab and save prefernces
if 'settings' in request.vars: if 'settings' in request.vars:
if request.post_vars: # save new preferences if request.post_vars: #save new preferences
post_vars = request.post_vars.items() post_vars = request.post_vars.items()
# Since unchecked checkbox are not serialized, we must set them as false by hand to store the correct preference in the settings # Since unchecked checkbox are not serialized, we must set them as false by hand to store the correct preference in the settings
post_vars += [(opt, 'false') for opt in preferences if opt not in request.post_vars] post_vars+= [(opt, 'false') for opt in preferences if opt not in request.post_vars ]
if config.save(post_vars): if config.save(post_vars):
response.headers["web2py-component-flash"] = T('Preferences saved correctly') response.headers["web2py-component-flash"] = T('Preferences saved correctly')
else: else:
@@ -644,8 +632,8 @@ def edit():
response.headers["web2py-component-command"] = "update_editor(%s);$('a[href=#editor_settings] button.close').click();" % response.json(config.read()) response.headers["web2py-component-command"] = "update_editor(%s);$('a[href=#editor_settings] button.close').click();" % response.json(config.read())
return return
else: else:
details = {'realfilename': 'settings', 'filename': 'settings', 'id': 'editor_settings', 'force': False} details = {'realfilename':'settings', 'filename':'settings', 'id':'editor_settings', 'force': False}
details['plain_html'] = response.render('default/editor_settings.html', {'editor_settings': preferences}) details['plain_html'] = response.render('default/editor_settings.html', {'editor_settings':preferences})
return response.json(details) return response.json(details)
""" File edit handler """ """ File edit handler """
@@ -752,7 +740,7 @@ def edit():
B(ex_name), ' ' + T('at line %s', e.lineno), B(ex_name), ' ' + T('at line %s', e.lineno),
offset and ' ' + offset and ' ' +
T('at char %s', offset) or '', T('at char %s', offset) or '',
PRE(repr(e))) PRE(str(e)))
if data_or_revert and request.args[1] == 'modules': if data_or_revert and request.args[1] == 'modules':
# Lets try to reload the modules # Lets try to reload the modules
try: try:
@@ -763,7 +751,7 @@ def edit():
% (request.args[0], mopath)]) % (request.args[0], mopath)])
except Exception, e: except Exception, e:
response.flash = DIV( response.flash = DIV(
T('failed to reload module because:'), PRE(repr(e))) T('failed to reload module because:'), PRE(str(e)))
edit_controller = None edit_controller = None
editviewlinks = None editviewlinks = None
@@ -776,8 +764,8 @@ def edit():
view = request.args[3].replace('.html', '') view = request.args[3].replace('.html', '')
view_link = URL(request.args[0], request.args[2], view) view_link = URL(request.args[0], request.args[2], view)
elif filetype == 'python' and request.args[1] == 'controllers': elif filetype == 'python' and request.args[1] == 'controllers':
# it's a controller file. ## it's a controller file.
# Create links to all of the associated view files. ## Create links to all of the associated view files.
app = get_app() app = get_app()
viewname = os.path.splitext(request.args[2])[0] viewname = os.path.splitext(request.args[2])[0]
viewpath = os.path.join(app, 'views', viewname) viewpath = os.path.join(app, 'views', viewname)
@@ -808,22 +796,22 @@ def edit():
return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight}) return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight})
else: else:
file_details = dict(app=request.args[0], file_details = dict(app=request.args[0],
lineno=request.vars.lineno or 1, lineno=request.vars.lineno or 1,
editor_settings=preferences, editor_settings=preferences,
filename=filename, filename=filename,
realfilename=realfilename, realfilename=realfilename,
filetype=filetype, filetype=filetype,
data=data, data=data,
edit_controller=edit_controller, edit_controller=edit_controller,
file_hash=file_hash, file_hash=file_hash,
saved_on=saved_on, saved_on=saved_on,
controller=controller, controller=controller,
functions=functions, functions=functions,
view_link=view_link, view_link=view_link,
editviewlinks=editviewlinks, editviewlinks=editviewlinks,
id=IS_SLUG()(filename)[0], id=IS_SLUG()(filename)[0],
force=True if (request.vars.restore or force= True if (request.vars.restore or
request.vars.revert) else False) request.vars.revert) else False)
plain_html = response.render('default/edit_js.html', file_details) plain_html = response.render('default/edit_js.html', file_details)
file_details['plain_html'] = plain_html file_details['plain_html'] = plain_html
if is_mobile: if is_mobile:
@@ -832,16 +820,14 @@ def edit():
else: else:
return response.json(file_details) return response.json(file_details)
def todolist(): def todolist():
""" Returns all TODO of the requested app """ Returns all TODO of the requested app
""" """
app = request.vars.app or '' app = request.vars.app or ''
app_path = apath('%(app)s' % {'app': app}, r=request) app_path = apath('%(app)s' % {'app':app}, r=request)
dirs = ['models', 'controllers', 'modules', 'private'] dirs=['models', 'controllers', 'modules', 'private' ]
def listfiles(app, dir, regexp='.*\.py$'): def listfiles(app, dir, regexp='.*\.py$'):
files = sorted(listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp)) files = sorted( listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
files = [x.replace(os.path.sep, '/') for x in files if not x.endswith('.bak')] files = [x.replace(os.path.sep, '/') for x in files if not x.endswith('.bak')]
return files return files
@@ -852,18 +838,17 @@ def todolist():
for d in dirs: for d in dirs:
for f in listfiles(app, d): for f in listfiles(app, d):
matches = [] matches = []
filename = apath(os.path.join(app, d, f), r=request) filename= apath(os.path.join(app, d, f), r=request)
with open(filename, 'r') as f_s: with open(filename, 'r') as f_s:
src = f_s.read() src = f_s.read()
for m in regex.finditer(src): for m in regex.finditer(src):
start = m.start() start = m.start()
lineno = src.count('\n', 0, start) + 1 lineno = src.count('\n', 0, start) + 1
matches.append({'text': m.group(0), 'lineno': lineno}) matches.append({'text':m.group(0), 'lineno':lineno})
if len(matches) != 0: if len(matches) != 0:
output.append({'filename': f, 'matches': matches, 'dir': d}) output.append({'filename':f,'matches':matches, 'dir':d})
return {'todo': output, 'app': app}
return {'todo':output, 'app': app}
def editor_sessions(): def editor_sessions():
config = Config(os.path.join(request.folder, 'settings.cfg'), config = Config(os.path.join(request.folder, 'settings.cfg'),
@@ -873,14 +858,13 @@ def editor_sessions():
if request.vars.session_name and request.vars.files: if request.vars.session_name and request.vars.files:
session_name = request.vars.session_name session_name = request.vars.session_name
files = request.vars.files files = request.vars.files
preferences.update({session_name: ','.join(files)}) preferences.update({session_name:','.join(files)})
if config.save(preferences.items()): if config.save(preferences.items()):
response.headers["web2py-component-flash"] = T('Session saved correctly') response.headers["web2py-component-flash"] = T('Session saved correctly')
else: else:
response.headers["web2py-component-flash"] = T('Session saved on session only') response.headers["web2py-component-flash"] = T('Session saved on session only')
return response.render('default/editor_sessions.html', {'editor_sessions': preferences}) return response.render('default/editor_sessions.html', {'editor_sessions':preferences})
def resolve(): def resolve():
""" """
@@ -917,8 +901,8 @@ def resolve():
def getclass(item): def getclass(item):
""" Determine item class """ """ Determine item class """
operators = {' ': 'normal', '+': 'plus', '-': 'minus'} operators = {' ':'normal', '+':'plus', '-':'minus'}
return operators[item[0]] return operators[item[0]]
if request.vars: if request.vars:
@@ -937,7 +921,7 @@ def resolve():
diff = TABLE(*[TR(TD(gen_data(i, item)), diff = TABLE(*[TR(TD(gen_data(i, item)),
TD(item[0]), TD(item[0]),
TD(leading(item[2:]), TD(leading(item[2:]),
TT(item[2:].rstrip())), TT(item[2:].rstrip())),
_class=getclass(item)) _class=getclass(item))
for (i, item) in enumerate(d) if item[0] != '?']) for (i, item) in enumerate(d) if item[0] != '?'])
@@ -984,11 +968,11 @@ def edit_language():
new_row = DIV(LABEL(prefix, k, _style="font-weight:normal;"), new_row = DIV(LABEL(prefix, k, _style="font-weight:normal;"),
CAT(elem, '\n', TAG.BUTTON( CAT(elem, '\n', TAG.BUTTON(
T('delete'), T('delete'),
_onclick='return delkey("%s")' % name, _onclick='return delkey("%s")' % name,
_class='btn')), _id=name, _class='span6 well well-small') _class='btn')), _id=name, _class='span6 well well-small')
rows.append(DIV(new_row, _class="row-fluid")) rows.append(DIV(new_row,_class="row-fluid"))
rows.append(DIV(INPUT(_type='submit', _value=T('update'), _class="btn btn-primary"), _class='controls')) rows.append(DIV(INPUT(_type='submit', _value=T('update'), _class="btn btn-primary"), _class='controls'))
form = FORM(*rows) form = FORM(*rows)
if form.accepts(request.vars, keepvalues=True): if form.accepts(request.vars, keepvalues=True):
@@ -1144,18 +1128,18 @@ def design():
# Get all static files # Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*', statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*',
maxnum=MAXNFILES) maxnum = MAXNFILES)
statics = [x.replace(os.path.sep, '/') for x in statics] statics = [x.replace(os.path.sep, '/') for x in statics]
statics.sort() statics.sort()
# Get all languages # Get all languages
langpath = os.path.join(apath(app, r=request), 'languages') langpath = os.path.join(apath(app, r=request),'languages')
languages = dict([(lang, info) for lang, info languages = dict([(lang, info) for lang, info
in read_possible_languages(langpath).iteritems() in read_possible_languages(langpath).iteritems()
if info[2] != 0]) # info[2] is langfile_mtime: if info[2] != 0]) # info[2] is langfile_mtime:
# get only existed files # get only existed files
# Get crontab #Get crontab
cronfolder = apath('%s/cron' % app, r=request) cronfolder = apath('%s/cron' % app, r=request)
crontab = apath('%s/cron/crontab' % app, r=request) crontab = apath('%s/cron/crontab' % app, r=request)
if not is_gae: if not is_gae:
@@ -1281,7 +1265,7 @@ def plugin():
# Get all static files # Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*', statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*',
maxnum=MAXNFILES) maxnum = MAXNFILES)
statics = [x.replace(os.path.sep, '/') for x in statics] statics = [x.replace(os.path.sep, '/') for x in statics]
statics.sort() statics.sort()
@@ -1289,9 +1273,9 @@ def plugin():
languages = sorted([lang + '.py' for lang, info in languages = sorted([lang + '.py' for lang, info in
T.get_possible_languages_info().iteritems() T.get_possible_languages_info().iteritems()
if info[2] != 0]) # info[2] is langfile_mtime: if info[2] != 0]) # info[2] is langfile_mtime:
# get only existed files # get only existed files
# Get crontab #Get crontab
crontab = apath('%s/cron/crontab' % app, r=request) crontab = apath('%s/cron/crontab' % app, r=request)
if not os.path.exists(crontab): if not os.path.exists(crontab):
safe_write(crontab, '#crontab') safe_write(crontab, '#crontab')
@@ -1314,7 +1298,6 @@ def plugin():
languages=languages, languages=languages,
crontab=crontab) crontab=crontab)
def create_file(): def create_file():
""" Create files handler """ """ Create files handler """
if request.vars and not request.vars.token == session.token: if request.vars and not request.vars.token == session.token:
@@ -1326,7 +1309,7 @@ def create_file():
path = abspath(request.vars.location) path = abspath(request.vars.location)
else: else:
if request.vars.dir: if request.vars.dir:
request.vars.location += request.vars.dir + '/' request.vars.location += request.vars.dir + '/'
app = get_app(name=request.vars.location.split('/')[0]) app = get_app(name=request.vars.location.split('/')[0])
path = apath(request.vars.location, r=request) path = apath(request.vars.location, r=request)
filename = re.sub('[^\w./-]+', '_', request.vars.filename) filename = re.sub('[^\w./-]+', '_', request.vars.filename)
@@ -1436,7 +1419,7 @@ def create_file():
elif (path[-8:] == '/static/') or (path[-9:] == '/private/'): elif (path[-8:] == '/static/') or (path[-9:] == '/private/'):
if (request.vars.plugin and if (request.vars.plugin and
not filename.startswith('plugin_%s/' % request.vars.plugin)): not filename.startswith('plugin_%s/' % request.vars.plugin)):
filename = 'plugin_%s/%s' % (request.vars.plugin, filename) filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
text = '' text = ''
@@ -1456,17 +1439,17 @@ def create_file():
log_progress(app, 'CREATE', filename) log_progress(app, 'CREATE', filename)
if request.vars.dir: if request.vars.dir:
result = T('file "%(filename)s" created', result = T('file "%(filename)s" created',
dict(filename=full_filename[len(path):])) dict(filename=full_filename[len(path):]))
else: else:
session.flash = T('file "%(filename)s" created', session.flash = T('file "%(filename)s" created',
dict(filename=full_filename[len(path):])) dict(filename=full_filename[len(path):]))
vars = {} vars = {}
if request.vars.id: if request.vars.id:
vars['id'] = request.vars.id vars['id'] = request.vars.id
if request.vars.app: if request.vars.app:
vars['app'] = request.vars.app vars['app'] = request.vars.app
redirect(URL('edit', redirect(URL('edit',
args=[os.path.join(request.vars.location, filename)], vars=vars)) args=[os.path.join(request.vars.location, filename)], vars=vars))
except Exception, e: except Exception, e:
if not isinstance(e, HTTP): if not isinstance(e, HTTP):
@@ -1477,7 +1460,7 @@ def create_file():
response.headers['web2py-component-content'] = 'append' response.headers['web2py-component-content'] = 'append'
response.headers['web2py-component-command'] = "%s %s %s" % ( response.headers['web2py-component-command'] = "%s %s %s" % (
"$.web2py.invalidate('#files_menu');", "$.web2py.invalidate('#files_menu');",
"load_file('%s');" % URL('edit', args=[app, request.vars.dir, filename]), "load_file('%s');" % URL('edit', args=[app,request.vars.dir,filename]),
"$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));") "$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));")
return '' return ''
else: else:
@@ -1485,35 +1468,32 @@ def create_file():
def listfiles(app, dir, regexp='.*\.py$'): def listfiles(app, dir, regexp='.*\.py$'):
files = sorted( files = sorted(
listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp)) listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')] files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
return files return files
def editfile(path, file, vars={}, app=None):
args = (path, file) if 'app' in vars else (app, path, file)
url = URL('edit', args=args, vars=vars)
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
def editfile(path,file,vars={}, app = None):
args=(path,file) if 'app' in vars else (app,path,file)
url = URL('edit', args=args, vars=vars)
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
def files_menu(): def files_menu():
app = request.vars.app or 'welcome' app = request.vars.app or 'welcome'
dirs = [{'name': 'models', 'reg': '.*\.py$'}, dirs=[{'name':'models', 'reg':'.*\.py$'},
{'name': 'controllers', 'reg': '.*\.py$'}, {'name':'controllers', 'reg':'.*\.py$'},
{'name': 'views', 'reg': '[\w/\-]+(\.\w+)+$'}, {'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
{'name': 'modules', 'reg': '.*\.py$'}, {'name':'modules', 'reg':'.*\.py$'},
{'name': 'static', 'reg': '[^\.#].*'}, {'name':'static', 'reg': '[^\.#].*'},
{'name': 'private', 'reg': '.*\.py$'}] {'name':'private', 'reg':'.*\.py$'}]
result_files = [] result_files = []
for dir in dirs: for dir in dirs:
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"), result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.', '__')), app), _style="overflow:hidden", _id=dir['name'] + "__" + f.replace('.', '__')) LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
for f in listfiles(app, dir['name'], regexp=dir['reg'])], for f in listfiles(app, dir['name'], regexp=dir['reg'])],
_class="nav nav-list small-font"), _class="nav nav-list small-font"),
_id=dir['name'] + '_files', _style="display: none;"))) _id=dir['name'] + '_files', _style="display: none;")))
return dict(result_files=result_files) return dict(result_files = result_files)
def upload_file(): def upload_file():
""" File uploading handler """ """ File uploading handler """
@@ -1576,7 +1556,7 @@ def errors():
app = get_app() app = get_app()
if is_gae: if is_gae:
method = 'dbold' if ('old' in method = 'dbold' if ('old' in
(request.args(1) or '')) else 'dbnew' (request.args(1) or '')) else 'dbnew'
else: else:
method = request.args(1) or 'new' method = request.args(1) or 'new'
db_ready = {} db_ready = {}
@@ -1619,7 +1599,7 @@ def errors():
hash2error[hash]['count'] += 1 hash2error[hash]['count'] += 1
except KeyError: except KeyError:
error_lines = error['traceback'].split("\n") error_lines = error['traceback'].split("\n")
last_line = error_lines[-2] if len(error_lines) > 1 else 'unknown' last_line = error_lines[-2] if len(error_lines)>1 else 'unknown'
error_causer = os.path.split(error['layer'])[1] error_causer = os.path.split(error['layer'])[1]
hash2error[hash] = dict(count=1, pickel=error, hash2error[hash] = dict(count=1, pickel=error,
causer=error_causer, causer=error_causer,
@@ -1658,9 +1638,9 @@ def errors():
last_line = error_lines[-2] last_line = error_lines[-2]
error_causer = os.path.split(error['layer'])[1] error_causer = os.path.split(error['layer'])[1]
hash2error[hash] = dict(count=1, hash2error[hash] = dict(count=1,
pickel=error, causer=error_causer, pickel=error, causer=error_causer,
last_line=last_line, hash=hash, last_line=last_line, hash=hash,
ticket=fn.ticket_id) ticket=fn.ticket_id)
except AttributeError, e: except AttributeError, e:
tk_db(tk_table.id == fn.id).delete() tk_db(tk_table.id == fn.id).delete()
tk_db.commit() tk_db.commit()
@@ -1677,11 +1657,11 @@ def errors():
tk_db(tk_table.ticket_id == item[7:]).delete() tk_db(tk_table.ticket_id == item[7:]).delete()
tk_db.commit() tk_db.commit()
tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id, tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id,
tk_table.created_datetime, tk_table.created_datetime,
orderby=~tk_table.created_datetime) orderby=~tk_table.created_datetime)
tickets = [row.ticket_id for row in tickets_] tickets = [row.ticket_id for row in tickets_]
times = dict([(row.ticket_id, row.created_datetime) for times = dict([(row.ticket_id, row.created_datetime) for
row in tickets_]) row in tickets_])
return dict(app=app, tickets=tickets, method=method, return dict(app=app, tickets=tickets, method=method,
times=times, db_ready=db_ready) times=times, db_ready=db_ready)
@@ -1741,7 +1721,7 @@ def make_link(path):
if ext.lower() == editable[key] and check_extension: if ext.lower() == editable[key] and check_extension:
return A('"' + tryFile + '"', return A('"' + tryFile + '"',
_href=URL(r=request, _href=URL(r=request,
f='edit/%s/%s/%s' % (app, key, filename))).xml() f='edit/%s/%s/%s' % (app, key, filename))).xml()
return '' return ''
@@ -1887,7 +1867,7 @@ def bulk_register():
redirect(URL('site')) redirect(URL('site'))
return locals() return locals()
# Begin experimental stuff need fixes: ### Begin experimental stuff need fixes:
# 1) should run in its own process - cannot os.chdir # 1) should run in its own process - cannot os.chdir
# 2) should not prompt user at console # 2) should not prompt user at console
# 3) should give option to force commit and not reuqire manual merge # 3) should give option to force commit and not reuqire manual merge
@@ -1954,7 +1934,6 @@ def git_push():
redirect(URL('site')) redirect(URL('site'))
return dict(app=app, form=form) return dict(app=app, form=form)
def plugins(): def plugins():
app = request.args(0) app = request.args(0)
from serializers import loads_json from serializers import loads_json
@@ -1969,16 +1948,12 @@ def plugins():
session.plugins = [] session.plugins = []
return dict(plugins=session.plugins["results"], app=request.args(0)) return dict(plugins=session.plugins["results"], app=request.args(0))
def install_plugin(): def install_plugin():
app = request.args(0) app = request.args(0)
source = request.vars.source source = request.vars.source
plugin = request.vars.plugin plugin = request.vars.plugin
if not (source and app): if not (source and app):
raise HTTP(500, T("Invalid request")) raise HTTP(500, T("Invalid request"))
# make sure no XSS attacks in source
if not source.lower().split('://')[0] in ('http','https'):
raise HTTP(500, T("Invalid request"))
form = SQLFORM.factory() form = SQLFORM.factory()
result = None result = None
if form.process().accepted: if form.process().accepted:
@@ -1994,5 +1969,5 @@ def install_plugin():
else: else:
session.flash = \ session.flash = \
T('unable to install plugin "%s"', filename) T('unable to install plugin "%s"', filename)
redirect(URL(f="plugins", args=[app, ])) redirect(URL(f="plugins", args=[app,]))
return dict(form=form, app=app, plugin=plugin, source=source) return dict(form=form, app=app, plugin=plugin, source=source)
+104 -340
View File
@@ -2,131 +2,89 @@
{ {
'!langcode!': 'cs-cz', '!langcode!': 'cs-cz',
'!langname!': 'čeština', '!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". (Avšak výsledky databázového JOINu nelze mazat ani upravovat.)', '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. ': '"Uživatelská výjimka", Debug mód.', '"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'"User Exception" debug mode. An error ticket could be issued!': '"Uživatelská výjimka", Debug mód. Může být vystaven chybový tiket.',
'%%{Row} in Table': '%%{řádek} v tabulce', '%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}', '%%{Row} selected': 'označených %%{řádek}',
'%s': '%s',
'%s %%{row} deleted': '%s smazaných %%{záznam}', '%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}', '%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených', '%s selected': '%s označených',
'%s students registered': '%s studentů registrováno',
'%Y-%m-%d': '%d.%m.%Y', '%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)', '(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(vyžaduje internetové připojení, experimentální)', '(requires internet access, experimental)': '(requires internet access, experimental)',
'(something like "it-it")': '(například "cs-cs")', '(something like "it-it")': '(například "cs-cs")',
'(version %s)': '(verze %s)',
'?': '?',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)', '@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}', '@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}',
'Abort': 'Ukončit',
'About': 'O programu', 'About': 'O programu',
'About application': 'O aplikaci', 'About application': 'O aplikaci',
'Accept Terms': 'Souhlasit s podmínkami',
'Access Control': 'Řízení přístupu', 'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení', 'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci (pro příkaz import). Neběží ve specifickém režimu ani ve vláknech jako model/kontrolér/šablona, ale jako standardní python moduly. Ty tedy můžete umístit sem (pouze pro tuto aplikaci) nebo používat systémově dostupné.', 'Additional code for your application': 'Další kód pro Vaši aplikaci',
'Admin design page': 'Admin design stránka', 'Admin design page': 'Admin design page',
'admin disabled because no admin password': 'admin je zakázán, protože chybí heslo administrátora',
'admin disabled because not supported on google app engine': 'admin je zakázán kvůli chybějící podpoře na Google App Engine',
'admin disabled because too many invalid login attempts': 'Admin je zakázán po příliš mnoha nesprávných pokusech o přihlášení',
'admin disabled because unable to access password file': 'Admin je zakázán, protože nelze číst soubor s heslem',
'Admin is disabled because insecure channel': 'Admin je zakázán na nezabezpečeném připojení',
'Admin language': 'jazyk rozhraní', 'Admin language': 'jazyk rozhraní',
'Admin versioning page': 'Admin verzovací stránka',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem', 'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní', 'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu', 'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:', 'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem', 'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'Došlo k chybě, prosím %s stránku', 'An error occured, please %s the page': 'An error occured, please %s the page',
'and rename it:': 'a přejmenovat na:', 'and rename it:': 'a přejmenovat na:',
'App does not exist or you are not authorized': 'Aplikace neexistuje nebo vám chybí oprávnění',
'appadmin': 'appadmin', 'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení', 'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Aplikace', 'Application': 'Application',
'application "%s" uninstalled': 'application "%s" odinstalována', 'application "%s" uninstalled': 'application "%s" odinstalována',
'Application cannot be generated in demo mode': 'Aplikace nemůže být vytvořena v demo módu',
'application compiled': 'aplikace zkompilována', 'application compiled': 'aplikace zkompilována',
'Application exists already': 'Aplikace již existuje',
'application is compiled and cannot be designed': 'aplikace je přeložena a nelze ji editovat',
'Application name:': 'Název aplikace:', 'Application name:': 'Název aplikace:',
'Application updated via git pull': 'Aplikace byla aktualizována pomocí git pull',
'are not used': 'nepoužita', 'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita', 'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete file "%s"?': 'Skutečně chcete smazat soubor "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Skutečně chcete smazat plugin "%s"?',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?', 'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?', 'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'Are you sure?': 'Jste si jist(a)?', 'arguments': 'arguments',
'arguments': 'argumenty', 'at char %s': 'at char %s',
'at char %s': 'na pozici znaku %s', 'at line %s': 'at line %s',
'at line %s': 'na řádku %s', 'ATTENTION:': 'ATTENTION:',
'ATTENTION:': 'POZOR:', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'POZOR: Přihlášení vyžaduje zabezpečené (HTTPS) připojení nebo spouštění na localhost.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'POZOR: TESTOVÁNÍ NENÍ BEZPEČNÉ PŘI SOUBĚŽNÝCH VLÁKNECH. NESPOUŠTĚJ VÍCE TESTŮ SOUBĚŽNĚ.',
'ATTENTION: you cannot edit the running application!': 'POZOR: Nelze editovat spuštěnou aplikaci.',
'Autocomplete Python Code': 'Autocomplete Python kód',
'Available Databases and Tables': 'Dostupné databáze a tabulky', 'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět', 'back': 'zpět',
'Back to the plugins list': 'Zpět do seznamu pluginů', 'Back to wizard': 'Back to wizard',
'Back to wizard': 'Zpátky do průvodce', 'Basics': 'Basics',
'Basics': 'Základy',
'Begin': 'Začít', 'Begin': 'Začít',
'breakpoint': 'bod přerušení', 'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení', 'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení', 'breakpoints': 'body přerušení',
'Bulk Register': 'Hromadná registrace',
'Bulk Student Registration': 'Hromadná registrace studentů',
'Buy this book': 'Koupit web2py knihu', 'Buy this book': 'Koupit web2py knihu',
'Cache': 'Cache', 'Cache': 'Cache',
'cache': 'cache', 'cache': 'cache',
'Cache Cleared': 'Cache byla vymazána',
'Cache Keys': 'Klíče cache', 'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny', 'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo', 'can be a git repo': 'může to být git repo',
'Cancel': 'Storno', 'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné', 'Cannot be empty': 'Nemůže být prázdné',
'Cannot compile: there are errors in your app:': 'Nelze zkompilovat: ve vaší aplikaci jsou chyby:',
'cannot create file': 'nelze vytvořit soubor',
'cannot upload file "%(filename)s"': 'nelze nahrát soubor "%(filename)s"',
'Change Admin Password': 'Změnit heslo pro správu', 'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací', 'Change admin password': 'Změnit heslo pro správu aplikací',
'change editor settings': 'změnit nastavení editoru',
'Change password': 'Změna hesla', 'Change password': 'Změna hesla',
'Changelog': 'Žurnál změn',
'check all': 'vše označit', 'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat', 'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání', 'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:', 'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...', 'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit', 'Clean': 'Pročistit',
'Clear': 'Inicializovat',
'Clear CACHE?': 'Vymazat CACHE?', 'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK', 'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM', 'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek', 'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...', 'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta', 'Client IP': 'IP adresa klienta',
'code': 'kód', 'code': 'code',
'Code listing': 'Výpis kódu', 'Code listing': 'Code listing',
'collapse/expand all': 'vše sbalit/rozbalit', 'collapse/expand all': 'vše sbalit/rozbalit',
'Command': 'Příkaz',
'Comment:': 'Komentář:',
'Commit': 'Potvrdit',
'Commit form': 'Potvrdit formulář',
'Committed files': 'Potvrzené soubory',
'Community': 'Komunita', 'Community': 'Komunita',
'Compile': 'Zkompilovat', 'Compile': 'Zkompilovat',
'Compile (all or nothing)': 'Přeložit (vše nebo nic)',
'Compile (skip failed views)': 'Přeložit (přeskočit chybné šablony)',
'compiled application removed': 'zkompilovaná aplikace smazána', 'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly', 'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka', 'Condition': 'Podmínka',
'continue': 'pokračovat', 'continue': 'continue',
'Controller': 'Kontrolér (Controller)', 'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry', 'Controllers': 'Kontroléry',
'controllers': 'kontroléry', 'controllers': 'kontroléry',
@@ -134,12 +92,9 @@
'Count': 'Počet', 'Count': 'Počet',
'Create': 'Vytvořit', 'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:', 'create file with filename:': 'vytvořit soubor s názvem:',
'Create/Upload': 'Vytvořit/Nahrát',
'created by': 'vytvořil', 'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým', 'Created By': 'Vytvořeno - kým',
'Created by:': 'Vytvořil:',
'Created On': 'Vytvořeno - kdy', 'Created On': 'Vytvořeno - kdy',
'Created on:': 'Vytvořeno:',
'crontab': 'crontab', 'crontab': 'crontab',
'Current request': 'Aktuální požadavek', 'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď', 'Current response': 'Aktuální odpověď',
@@ -150,19 +105,18 @@
'data uploaded': 'data nahrána', 'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze', 'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr', 'Database %s select': 'databáze %s výběr',
'Database administration': 'Administrace databáze', 'Database administration': 'Database administration',
'database administration': 'správa databáze', 'database administration': 'správa databáze',
'Database Administration (appadmin)': 'Administrace databáze (appadmin)',
'Date and Time': 'Datum a čas', 'Date and Time': 'Datum a čas',
'day': 'den', 'day': 'den',
'db': 'db', 'db': 'db',
'DB Model': 'Databázový model', 'DB Model': 'Databázový model',
'Debug': 'Ladění', 'Debug': 'Ladění',
'defines tables': 'definuje tabulky', 'defines tables': 'defines tables',
'Delete': 'Smazat', 'Delete': 'Smazat',
'delete': 'smazat', 'delete': 'smazat',
'delete all checked': 'smazat vše označené', 'delete all checked': 'smazat vše označené',
'delete plugin': 'zrušit plugin', 'delete plugin': 'delete plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)', 'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:', 'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení', 'deleted after first hit': 'smazat po prvním dosažení',
@@ -170,265 +124,166 @@
'Deploy': 'Nahrát', 'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine', 'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift', 'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deploy to pythonanywhere': 'Nahrát na PythonAnywhere',
'Deploy to PythonAnywhere': 'Nahrát na PythonAnywhere',
'Deployment form': 'Forumlář pro deployment (nasazení)',
'Deployment Interface': 'Rozhraní pro deployment (nasazení)',
'Deployment Recipes': 'Postupy pro deployment', 'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis', 'Description': 'Popis',
'Description:': 'Popis:',
'design': 'návrh', 'design': 'návrh',
'Detailed traceback description': 'Podrobný výpis prostředí', 'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti', 'details': 'podrobnosti',
'direction: ltr': 'směr: ltr', 'direction: ltr': 'směr: ltr',
'directory not found': 'adresář nebyl nalezen',
'Disable': 'Zablokovat', 'Disable': 'Zablokovat',
'Disabled': 'Blokováno',
'disabled in demo mode': 'zakázáno v demo módu',
'disabled in GAE mode': 'zakázáno v GAE módu',
'disabled in multi user mode': 'zakázáno ve víceuživatelském módu',
'DISK': 'DISK', 'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache', 'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán', 'Disk Cleared': 'Disk smazán',
'Display line numbers': 'Zobrazit čísla řádků',
'DO NOT use the "Pack compiled" feature.': 'NEPOUŽÍVEJ vlastnost "Zabalit zkompilované".',
'docs': 'dokumentace', 'docs': 'dokumentace',
'Docs': 'Dokumentace',
'Documentation': 'Dokumentace', 'Documentation': 'Dokumentace',
"Don't know what to do?": 'Nevíte kudy kam?', "Don't know what to do?": 'Nevíte kudy kam?',
'done!': 'hotovo!', 'done!': 'hotovo!',
'Downgrade': 'Downgrade (vrácení verze)',
'Download': 'Stáhnout', 'Download': 'Stáhnout',
'Download .w2p': 'Stažení .w2p',
'Download as .exe': 'Stáhnout jako .exe',
'download layouts': 'stáhnout moduly rozvržení stránky', 'download layouts': 'stáhnout moduly rozvržení stránky',
'Download layouts from repository': 'Stáhnout moduly rozvržení z repozitáře',
'download plugins': 'stáhnout zásuvné moduly', 'download plugins': 'stáhnout zásuvné moduly',
'Download plugins from repository': 'Stáhnout pluginy z repozitáře',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
'Edit': 'Upravit', 'Edit': 'Upravit',
'edit all': 'editovat vše', 'edit all': 'edit all',
'Edit application': 'Správa aplikace', 'Edit application': 'Správa aplikace',
'edit controller': 'editovat controller', 'edit controller': 'edit controller',
'edit controller:': 'editovat kontrolér:',
'Edit current record': 'Upravit aktuální záznam', 'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil', 'Edit Profile': 'Upravit profil',
'edit views:': 'upravit šablonu (view):', 'edit views:': 'upravit pohled:',
'Editing %s': 'Editace %s',
'Editing file "%s"': 'Úprava souboru "%s"', 'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru', 'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editování souboru množných čísel', 'Editing Plural Forms File': 'Editing Plural Forms File',
'Editor': 'Editor',
'Email Address': 'Emailová adresa',
'Email and SMS': 'Email a SMS', 'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat', 'Enable': 'Odblokovat',
'Enable Close-Tag': 'Povolit Close-Tag',
'Enable Code Folding': 'Povolit sdružování kódu',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g', 'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g', 'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba', 'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"', 'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby', 'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Tiket chyby', 'Error ticket': 'Ticket chyby',
'Errors': 'Chyby', 'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Výjimka %(extype)s: %(exvalue)s', 'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Exception %s': 'Výjimka %s', 'Exception %s': 'Exception %s',
'Exception instance attributes': 'Prvky instance výjimky', 'Exception instance attributes': 'Prvky instance výjimky',
'Exit Fullscreen': 'Ukončit režim celé obrazovky', 'Expand Abbreviation': 'Expand Abbreviation',
'Expand Abbreviation': 'Rozvinout zkratku',
'Expand Abbreviation (html files only)': 'Rozvinout zkratku (pouze html soubory)',
'export as csv file': 'exportovat do .csv souboru', 'export as csv file': 'exportovat do .csv souboru',
'Exports:': 'Exporty:',
'exposes': 'vystavuje', 'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:', 'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje', 'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:', 'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'failed to reload module because:': 'nepodařilo se restartovat modul, protože:',
'FAQ': 'Často kladené dotazy', 'FAQ': 'Často kladené dotazy',
'File': 'Soubor', 'File': 'Soubor',
'file': 'soubor', 'file': 'soubor',
'file "%(filename)s" created': 'soubor "%(filename)s" byl vytvořen', 'file "%(filename)s" created': 'file "%(filename)s" created',
'file "%(filename)s" deleted': 'soubor "%(filename)s" byl zrušen',
'file "%(filename)s" uploaded': 'soubor "%(filename)s" byl nahrán',
'file "%s" of %s restored': 'soubor "%s" z %s byl obnoven',
'file changed on disk': 'soubor se na disku změnil',
'file does not exist': 'soubor neexistuje',
'file not found': 'soubor nebyl nalezen',
'file saved on %(time)s': 'soubor uložen %(time)s', 'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s', 'file saved on %s': 'soubor uložen %s',
'filename': 'jméno souboru',
'Filename': 'Název souboru', 'Filename': 'Název souboru',
'Files added': 'Soubory byly přidány',
'filter': 'filtr', 'filter': 'filtr',
'Find Next': 'Najít další', 'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí', 'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno', 'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?', 'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?', 'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Form has errors': 'Ve formuláři jsou chyby',
'Forms and Validators': 'Formuláře a validátory', 'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Framy', 'Frames': 'Frames',
'Free Applications': 'Aplikace zdarma', 'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Funkce bez doctestů se projeví jako [úspěšný] test.', 'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'GAE Email': 'GAE e-mail',
'GAE Output': 'GAE výstup',
'GAE Password': 'GAE heslo',
'Generate': 'Vytvořit', 'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:', 'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull', 'Git Pull': 'Git Pull',
'Git Push': 'Git Push', 'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné', 'Globals##debug': 'Globální proměnné',
'go!': 'OK!', 'go!': 'OK!',
'Google App Engine Deployment Interface': 'Google App Engine - rozhraní pro nasazení', 'Goto': 'Goto',
'Google Application Id': 'ID Google Aplikace', 'graph model': 'graph model',
'Goto': 'Přejít na',
'graph model': 'grafický model',
'Graph Model': 'Grafický model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena', 'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny', 'Group ID': 'ID skupiny',
'Groups': 'Skupiny', 'Groups': 'Skupiny',
'Hello World': 'Ahoj světe', 'Hello World': 'Ahoj světe',
'Help': 'Nápověda', 'Help': 'Nápověda',
'here': 'zde',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty', 'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Highlight current line': 'Zvýraznit aktuální řádek',
'Hits': 'Kolikrát dosaženo', 'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka', 'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně', 'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak jste se sem vlastně dostal?', 'How did you get here?': 'Jak jste se sem vlastně dostal?',
'If start the downgrade, be patient, it may take a while to rollback': 'Spustíte-li downgrade verze, vyčkejte, protože vrácení změn může trvat dlouho', 'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'If start the upgrade, be patient, it may take a while to download': 'Spustíte-li upgrade, vyčkejte, protože stahování může trvat dlouho', 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Jestliže přehled výše obsahuje číslo chybového tiketu, znamená to chybu v kontroléru, ještě před pokusem vykonat doctesty. (Často je to způsobeno chybou odsazení nebo chybou mimo kód funkce.) Zelený nadpis označuje, že žádný test nehavaroval. (V tom případě dílčí výsledky nejsou uvedeny.)',
'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.': 'Jestliže přehled výše obsahuje číslo chybového tiketu, znamená to chybu v kontroléru, ještě před pokusem vykonat doctesty. (Často je to způsobeno chybou odsazení nebo chybou mimo kód funkce.) Zelený nadpis označuje, že žádný test nehavaroval. (V tom případě dílčí výsledky nejsou uvedeny.)',
'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'Jestliže vaše aplikace používá jinou databázi než SQLite, budete muset na PythonAnywhere konfigurovat její DAL() připojení.',
'import': 'import', 'import': 'import',
'Import/Export': 'Import/Export', 'Import/Export': 'Import/Export',
'In development, use the default Rocket webserver that is currently supported by this debugger.': 'Při vývoji je doporučeno použít předvolený webserver Rocket, se kterým tento debugger spolupracuje.',
'includes': 'zahrnuje', 'includes': 'zahrnuje',
'Indent with tabs': 'Odsazení tabelátory',
'Index': 'Index', 'Index': 'Index',
'insert new': 'vložit nový záznam ', 'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s', 'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'prohlédnout atributy', 'inspect attributes': 'inspect attributes',
'Install': 'Instalovat', 'Install': 'Instalovat',
'Installation of %(plugin)s for %(app)s': 'Instalace %(plugin)s pro %(app)s',
'Installed applications': 'Nainstalované aplikace', 'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s', 'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka', 'Interactive console': 'Interaktivní příkazová řádka',
'internal error': 'vnitřní chyba',
'internal error: %s': 'vnitřní chyba: %s',
'Internal State': 'Vnitřní stav', 'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod', 'Introduction': 'Úvod',
'Invalid action': 'Chybná akce',
'Invalid application name': 'Nesprávné jméno aplikace',
'invalid circular reference': 'nepovolený kruhový odkaz',
'Invalid email': 'Neplatný email', 'Invalid email': 'Neplatný email',
'Invalid git repository specified.': 'Byl zadán nesprávný git repozitář.',
'Invalid password': 'Nesprávné heslo', 'Invalid password': 'Nesprávné heslo',
'invalid password': 'nesprávné heslo',
'invalid password.': 'neplatné heslo', 'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz', 'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek', 'invalid request': 'Neplatný požadavek',
'Invalid request': 'Nesprávný požadavek (request)',
'invalid table names (auth_* tables already defined)': 'chybná jména tabulek (auth_* tabulky už byly definovány)',
'invalid ticket': 'chybný tiket',
'Is Active': 'Je aktivní', 'Is Active': 'Je aktivní',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.', 'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč', 'Key': 'Klíč',
'Key bindings': 'Vazby kláves', 'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Vazby kláves pro ZenCoding Plugin', 'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'Keyboard shortcuts': 'Klávesové zkratky',
'kill process': 'likvidovat proces',
'language file "%(filename)s" created/updated': 'jazykový soubor "%(filename)s" byl vytvořen/aktualizován',
'Language files (static strings) updated': 'Jazykové soubory (statické řetězce) byly aktualizovány',
'languages': 'jazyky', 'languages': 'jazyky',
'Languages': 'Jazyky', 'Languages': 'Jazyky',
'Last name': 'Příjmení', 'Last name': 'Příjmení',
'Last Revision': 'Minulá verze',
'Last saved on:': 'Naposledy uloženo:', 'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)', 'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)', 'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek', 'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro', 'License for': 'Licence pro',
'License:': 'Licence:',
'Line Nr': 'Č.řádku',
'Line number': 'Číslo řádku', 'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku', 'LineNo': 'Č.řádku',
'lists by exception': 'výpis podle výjimky', 'Live Chat': 'Online pokec',
'lists by ticket': 'výpis podle tiketu',
'Live Chat': 'Online chat',
'Loading...': 'Nahrávám...',
'loading...': 'nahrávám...', 'loading...': 'nahrávám...',
'Local Apps': 'Lokální aplikace', 'locals': 'locals',
'locals': 'lokální proměnné',
'Locals##debug': 'Lokální proměnné', 'Locals##debug': 'Lokální proměnné',
'Logged in': 'Přihlášení proběhlo úspěšně', 'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně', 'Logged out': 'Odhlášení proběhlo úspěšně',
'login': 'přihlásit se',
'Login': 'Přihlásit se', 'Login': 'Přihlásit se',
'Login successful': 'Přihlášení bylo úspěšné', 'login': 'přihlásit se',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací', 'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'Login/Register': 'Přihlásit se / Registrovat',
'logout': 'odhlásit se', 'logout': 'odhlásit se',
'Logout': 'Odhlásit se', 'Logout': 'Odhlásit se',
'lost password': 'ztracené heslo',
'Lost Password': 'Zapomněl jste heslo', 'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?', 'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?', 'lost password?': 'zapomněl jste heslo?',
'Main Menu': 'Hlavní nabídka', 'Manage': 'Manage',
'Manage': 'Spravovat', 'Manage Cache': 'Manage Cache',
'Manage %(action)s': 'Spravovat %(action)s',
'Manage Access Control': 'Spravovat řízení přístupu',
'Manage Admin Users/Students': 'Spravovat Admin uživatele / Studenty',
'Manage Cache': 'Spravovat cache',
'Manage Students': 'Spravovat studenty',
'Memberships': 'Členství ve skupinách',
'Menu Model': 'Model rozbalovací nabídky', 'Menu Model': 'Model rozbalovací nabídky',
'merge': 'sloučit',
'Models': 'Modely', 'Models': 'Modely',
'models': 'modely', 'models': 'modely',
'Modified By': 'Změněno - kým', 'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy', 'Modified On': 'Změněno - kdy',
'Modules': 'Moduly', 'Modules': 'Moduly',
'modules': 'moduly', 'modules': 'moduly',
'Multi User Mode': 'Víceuživatelský mód',
'My Sites': 'Správa aplikací', 'My Sites': 'Správa aplikací',
'Name': 'Jméno', 'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena', 'new application "%s" created': 'nová aplikace "%s" vytvořena',
'new application "%s" imported': 'nová aplikace "%s" byla importována',
'New Application Wizard': 'Nový průvodce aplikací', 'New Application Wizard': 'Nový průvodce aplikací',
'New application wizard': 'Nový průvodce aplikací', 'New application wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo', 'New password': 'Nové heslo',
'new plugin installed': 'nový plugin byl instalován',
'New plugin installed: %s': 'Nový plugin byl instalován: %s',
'New Record': 'Nový záznam', 'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen', 'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit novou aplikaci', 'New simple application': 'Vytvořit primitivní aplikaci',
'next': 'další', 'next': 'next',
'next %s rows': 'dalších %s řádků',
'next 100 rows': 'dalších 100 řádků', 'next 100 rows': 'dalších 100 řádků',
'NO': 'NE',
'no changes': 'beze změn',
'No databases in this application': 'V této aplikaci nejsou žádné databáze', 'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala', 'No Interaction yet': 'Ještě žádná interakce nenastala',
'no match': 'nenalezena shoda',
'no package selected': 'nebyla vybrána žádná package',
'no permission to uninstall "%s"': 'chybí oprávnění odinstalovat "%s"',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen', 'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Node:': 'Uzel (node):',
'Not Authorized': 'Chybí autorizace',
'Not supported': 'Není podporováno',
'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.': 'Poznámka: Dostanete-li chybu s github status code = 128, ujistěte se, že systém a účet z něhož provádíte nasazení má odpovídající ssh klíč, konfigurovaný v OpenShift účtu.',
'Object or table name': 'Objekt či tabulka', 'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo', 'Old password': 'Původní heslo',
"On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": 'Pro použití tohoto debuggeru na produkci je potřeba konfigurovat webserver, aby používal jeden proces a více vláken.',
'online designer': 'online návrhář', 'online designer': 'online návrhář',
'Online examples': 'Příklady online', 'Online examples': 'Příklady online',
'Open new app in new window': 'Otevřít novou aplikaci v novém okně', 'Open new app in new window': 'Open new app in new window',
'OpenShift Deployment Interface': 'OpenShift rozhraní pro nasazení aplikace', 'or alternatively': 'or alternatively',
'OpenShift Output': 'OpenShift výstup', 'Or Get from URL:': 'Or Get from URL:',
'or alternatively': 'nebo případně',
'Or Get from URL:': 'Nebo získat z URL adresy:',
'or import from csv file': 'nebo importovat z .csv souboru', 'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ', 'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad', 'Original/Translation': 'Originál/Překlad',
@@ -438,53 +293,30 @@
'Overwrite installed app': 'Přepsat instalovanou aplikaci', 'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit', 'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované', 'Pack compiled': 'Zabalit zkompilované',
'Pack custom': 'Zabalit volitelně (custom)', 'pack plugin': 'pack plugin',
'pack plugin': 'zabalit plugin',
'Password': 'Heslo',
'password': 'heslo', 'password': 'heslo',
'password changed': 'heslo bylo změněno', 'Password': 'Heslo',
"Password fields don't match": 'Hesla se neshodují', "Password fields don't match": 'Hesla se neshodují',
'Past revisions': 'Minulá verze', 'Peeking at file': 'Peeking at file',
'Path to appcfg.py': 'Cesta ke appcfg.py',
'Path to local openshift repo root.': 'Cesta ke kořenu (rootu) lokálního OpenShift repozitáře.',
'Peeking at file': 'Sledování souboru',
'Permission': 'Oprávnění',
'Permissions': 'Oprávnění',
'Please': 'Prosím', 'Please': 'Prosím',
'Please wait, giving pythonanywhere a moment...': 'Prosím, čekejte na dokončení činnosti PythonAnywhere...', 'Plugin "%s" in application': 'Plugin "%s" in application',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" byl odstraněn',
'Plugin "%s" in application': 'Plugin "%s" v aplikaci',
'plugin not specified': 'plugin nebyl určen',
'Plugin page': 'Stránka pluginů',
'plugins': 'zásuvné moduly', 'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly', 'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Množné číslo #%s', 'Plural Form #%s': 'Plural Form #%s',
'Plural-Forms:': 'Množná čísla:', 'Plural-Forms:': 'Množná čísla:',
'Powered by': 'používá technologii', 'Powered by': 'Poháněno',
'Preface': 'Předmluva', 'Preface': 'Předmluva',
'Preferences saved correctly': 'Nastavení byla úspěšně uložena',
'Preferences saved on session only': 'Nastavení byla uložena pouze pro toto sezení',
'previous %s rows': 'předchozích %s řádků',
'previous 100 rows': 'předchozích 100 řádků', 'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory', 'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory', 'private files': 'soukromé soubory',
'profile': 'profil', 'profile': 'profil',
'Project Progress': 'Vývoj projektu', 'Project Progress': 'Vývoj projektu',
'Pull': 'Pull',
'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull selhal, některé soubory nelze zkopírovat. Pro podrobnosti zkontrolujte logy.',
'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull nelze provést, protože máte nesloučené soubory. Vyřešte tyto konflikty a pak akci opakujte.',
'Push': 'Push',
'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push selhal, protože máte nesloučené soubory. Vyřešte tyto konflikty a pak akci opakujte.',
'pygraphviz library not found': 'pygraphviz knihovna nebyla nalezena',
'Python': 'Python', 'Python': 'Python',
'PythonAnywhere Apps': 'PythonAnywhere aplikace',
'PythonAnywhere Password': 'PythonAnywhere heslo',
'Query:': 'Dotaz:', 'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady', 'Quick Examples': 'Krátké příklady',
'RAM': 'RAM', 'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache', 'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána', 'Ram Cleared': 'RAM smazána',
'Rapid Search': 'Rychlé hledání',
'Readme': 'Nápověda', 'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to', 'Recipes': 'Postupy jak na to',
'Record': 'Záznam', 'Record': 'Záznam',
@@ -503,167 +335,114 @@
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s', 'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit', 'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše', 'Replace All': 'Zaměnit vše',
'Repository (%s)': 'Repozitář (%s)', 'request': 'request',
'request': 'požadavek (request)',
'requires distutils, but not installed': 'vyžaduje distutils, jenže ty nejsou instalovány',
'requires python-git, but not installed': 'vyžaduje python-git, který ale není nainstalován',
'Reset Password key': 'Reset registračního klíče', 'Reset Password key': 'Reset registračního klíče',
'Resolve Conflict file': 'Vyřešit konflikty', 'response': 'response',
'response': 'odpověď (response)',
'restart': 'restart', 'restart': 'restart',
'restore': 'obnovit', 'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno', 'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return', 'return': 'return',
'Revert': 'Vrátit se k původnímu',
'revert': 'vrátit se k původnímu', 'revert': 'vrátit se k původnímu',
'reverted to revision %s': 'vráceno k verzi %s',
'Revision %s': 'Verze %s',
'Revision:': 'Verze:',
'Role': 'Role', 'Role': 'Role',
'Roles': 'Role',
'Rows in Table': 'Záznamy v tabulce', 'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno', 'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována', 'rules are not defined': 'pravidla nejsou definována',
'Run tests': 'Spustit testy',
'Run tests in this file': 'Spustit testy v souboru',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')", "Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s', 'Running on %s': 'Běží na %s',
'Save': 'Uložit', 'Save': 'Uložit',
'Save file:': 'Uložit soubor:', 'Save file:': 'Save file:',
'Save file: %s': 'Uložit soubor: %s',
'Save model as...': 'Uložit model jako...',
'Save via Ajax': 'Uložit pomocí Ajaxu', 'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:', 'Saved file hash:': 'hash uloženého souboru:',
'Screenshot %s': 'Screenshot %s',
'Search': 'Hledání',
'Select Files to Package': 'Vybrat soubory pro package',
'Semantic': 'Modul semantic', 'Semantic': 'Modul semantic',
'Services': 'Služby', 'Services': 'Služby',
'session': 'session (sezení)', 'session': 'session',
'session expired': 'vypršela session', 'session expired': 'session expired',
'Session saved correctly': 'Session byla úspěšně uložena',
'Session saved on session only': 'Session byla uložena jen pro toto sezení',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s', 'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka', 'shell': 'příkazová řádka',
'Showing %s to %s of %s %s found': 'Zobrazuji %s%s z %s %s nalezených', 'Singular Form': 'Singular Form',
'Singular Form': 'Jednotné číslo',
'Site': 'Správa aplikací', 'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:', 'Size of cache:': 'Velikost cache:',
'skip to generate': 'přeskočit pro vytvoření', 'skip to generate': 'skip to generate',
'some files could not be removed': 'některé soubory nelze odstranit',
'Something went wrong please wait a few minutes before retrying': 'Něco se nepodařilo. Vyčkejte několik minut a pak zkuste znova',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.', 'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'source : db': 'zdroj : db',
'source : filesystem': 'zdroj : souborový systém',
'Start a new app': 'Vytvořit novou aplikaci', 'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání', 'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce', 'Start wizard': 'Spustit průvodce',
'state': 'stav', 'state': 'stav',
'Static': 'Statické soubory', 'Static': 'Static',
'static': 'statické soubory', 'static': 'statické soubory',
'Static files': 'Statické soubory', 'Static files': 'Statické soubory',
'Statistics': 'Statistika', 'Statistics': 'Statistika',
'Step': 'Krok', 'Step': 'Step',
'step': 'krok', 'step': 'step',
'stop': 'zastavit', 'stop': 'stop',
'Stylesheet': 'CSS styly', 'Stylesheet': 'CSS styly',
'submit': 'odeslat', 'submit': 'odeslat',
'Submit': 'Odeslat', 'Submit': 'Odeslat',
'successful': 'úspěšně', 'successful': 'úspěšně',
'Support': 'Podpora', 'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?', 'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'switch to : db': 'přepnout na : db',
'switch to : filesystem': 'přepnout na : souborový systém',
'Tab width (# characters)': 'Šířka tabelátoru (# znaků)',
'Table': 'tabulka', 'Table': 'tabulka',
'Table name': 'Název tabulky', 'Table name': 'Název tabulky',
'Temporary': 'Dočasný', 'Temporary': 'Dočasný',
'test': 'test', 'test': 'test',
'Testing application': 'Zkušební aplikace', 'Testing application': 'Testing application',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je například "db.tabulka1.pole1==\'hodnota\'". Dotaz se dvěma tabulkami "db.tabulka1.pole1==db.tabulka2.pole2" vytvoří SQL JOIN.', 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'The app exists, was created by wizard, continue to overwrite!': 'Aplikace existuje, byla vytvořena průvodcem. Pokračováním ji přepíšete !',
'The app exists, was NOT created by wizard, continue to overwrite!': 'Aplikace existuje, a NEBYLA vytvořena průvodcem. Pokračováním ji přepíšete !',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.', 'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)', 'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Modely se vykonají při každém přístupu. Zde se obvykle definuje především reprezentace dat: Připojení k databázi a struktura tabulek databáze', 'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je dictionary (slovník), který se zobrazil pomocí šablony (view) %s.', 'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je slovník, který se zobrazil v pohledu %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: šablony (neboli pohledy, templaty, view). Mixuje Html, Python kód a Python data.', 'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The Views': 'Pohledy (The Views)', 'The Views': 'Pohledy (The Views)',
'Theme': 'Téma', 'There are no controllers': 'There are no controllers',
'There are no controllers': 'Nejsou vytvořeny žádné controllery', 'There are no modules': 'There are no modules',
'There are no models': 'Není vytvořen žádný model',
'There are no modules': 'Nejsou přidány žádné moduly',
'There are no plugins': 'Žádné moduly nejsou instalovány.', 'There are no plugins': 'Žádné moduly nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.', 'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'Nejsou přidány žádné statické soubory', 'There are no static files': 'There are no static files',
'There are no translators': 'Není vytvořen žádný překlad', 'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no translators, only default language is supported': 'Není vytvořen žádný překlad, je podporován jen defaultní jazyk', 'There are no views': 'There are no views',
'There are no views': 'Nejsou vytvořeny žádné šablony (views)', 'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou přístupné jen běžící aplikaci. Nejsou dostupné uživatelům, ani se nekopírují do případného vývojového repozitáře. Hesla a citlivá nastavení nedávejte nikam jinam.', 'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'These files are served without processing, your images go here': 'Tyto soubory jsou stahovány přímo, bez jakékoli přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace', 'This App': 'Tato aplikace',
"This debugger may not work properly if you don't have a threaded webserver or you're using multiple daemon processes.": 'Tento debugger nebude pracovat správně, jestliže váš webový server nepracuje pomocí vláken nebo když používáte více procesů démonů.', 'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is a copy of the scaffolding application': 'Toto je kopie vzorové aplikace.', 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk': 'Toto je experimentální vlastnost, která vyžaduje další testování. Návrat verze jen na vlastní riziko.', 'This is the %(filename)s template': 'This is the %(filename)s template',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'Toto je experimentální vlastnost, která vyžaduje další testování. Upgrade verze jen na vlastní riziko.',
'This is the %(filename)s template': 'Toto je šablona %(filename)s',
"This page can commit your changes to an openshift app repo and push them to your cloud instance. This assumes that you've already created the application instance using the web2py skeleton and have that repo somewhere on a filesystem that this web2py instance can access. This functionality requires GitPython installed and on the python path of the runtime that web2py is operating in.": 'Tato stránka umožňuje potvrdit vaše změny do OpenShift aplikačního repozitáře a odeslat je do vaší cloud instance. Předpokladem je, že jste už vytvořili aplikační instanci pomocí Web2py předlohy a že tento repozitář máte někde na disku tak, aby k němu Web2py mělo přístup. Tato funkcionalita vyžaduje, aby byl instalován GitPython a aby mohl být nalezen pomocí cesty, se kterou Web2py pracuje.',
'This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.': 'Tato stránka umožňuje zkopírovat vaši aplikaci do Google App Engine cloudu. Pamatujte, že nejprve je třeba vytvořit indexy lokálně, čehož dosáhnete instalací Google appserver a jedním lokálním spuštěním aplikace. V opačném případě bude docházet k chybám vyhledávání. Pozor: v závislosti na rychlosti sítě může nasazení trvat dlouhou dobu. Pozor: bude přepsán váš soubor app.yaml. BĚHEM SPUŠTĚNÍ NESPOUŠTĚJTE PODRUHÉ.',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.', 'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'This will pull changes from the remote repo for application "%s"?': 'Toto stáhne (pull) změny ze vzdáleného repozitáře pro aplikaci "%s"?', 'Ticket': 'Ticket',
'This will push changes to the remote repo for application "%s".': 'Toto nahraje (push) změny do vzdáleného repozitáře pro aplikaci "%s".', 'Ticket ID': 'Ticket ID',
'Ticket': 'Tiket',
'Ticket ID': 'ID tiketu',
'Ticket Missing': 'Chybový tiket chybí',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)', 'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko', 'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.', 'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete skupinu souborů nebo adresář(e) plugin_[jméno modulu]', 'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:', 'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!', 'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení', 'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle comment': 'Přepnout komentář',
'Toggle Fullscreen': 'Na celou obrazovku a zpět', 'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké', 'too short': 'Příliš krátké',
'Traceback': 'Hierarchie volání', 'Traceback': 'Traceback',
'Translation strings for the application': 'Překlad textů pro aplikaci', 'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'zkuste něco jako', 'try something like': 'try something like',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení', 'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'vyzkoušet šablonu (view)', 'try view': 'try view',
'Twitter': 'Twitter', 'Twitter': 'Twitter',
'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Zapište příkaz PDB debuggeru a stiskněte Return (Enter) pro jeho provedení.', 'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type python statement in here and hit Return (Enter) to execute it.': 'Zapište příkaz pythonu a stiskněte Return (Enter) pro jeho provedení.', 'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Zapište kód v jazyce python a stiskněte Return (Enter) pro jeho provedení.', 'Unable to check for upgrades': 'Unable to check for upgrades',
'Unable to check for upgrades': 'Nelze zjistit informaci o aktualizacích',
'unable to create application "%s"': 'nelze vytvořit aplikaci "%s"',
'unable to delete file "%(filename)s"': 'nelze zrušit soubor "%(filename)s"',
'unable to delete file plugin "%(plugin)s"': 'nelze zrušit plugin "%(plugin)s"',
'Unable to determine the line number!': 'Nelze určit číslo řádky!',
'Unable to download app because:': 'Nelze stáhnout aplikaci, protože:',
'unable to download layout': 'nelze stáhnout šablonu (layout)',
'unable to download plugin: %s': 'nelze stáhnout plugin: %s',
'Unable to download the list of plugins': 'Nelze stáhnout seznam pluginů',
'unable to install plugin "%s"': 'nelze instalovat plugin "%s"',
'unable to parse csv file': 'csv soubor nedá sa zpracovat', 'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'unable to uninstall "%s"': 'nelze instalovat "%s"',
'unable to upgrade because "%s"': 'nelze upgradovat, protože "%s"',
'uncheck all': 'vše odznačit', 'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat', 'Uninstall': 'Odinstalovat',
'Unsupported webserver working mode: %s': 'Nepodporovaný mód webového serveru: %s',
'update': 'aktualizovat', 'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky o nové texty ze zdrojových souborů', 'update all languages': 'aktualizovat všechny jazyky',
'Update:': 'Upravit:', 'Update:': 'Upravit:',
'Upgrade': 'Upgrade', 'Upgrade': 'Upgrade',
'upgrade now': 'upgradovat nyní', 'upgrade now': 'upgrade now',
'upgrade now to %s': 'upgradovat nyní na %s', 'upgrade now to %s': 'upgrade now to %s',
'upload': 'nahrát', 'upload': 'nahrát',
'Upload': 'Upload (nahrát)', 'Upload': 'Upload',
'Upload a package:': 'Nahrát balík:', 'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci', 'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:', 'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:', 'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.', 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User': 'Uživatel',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen', 'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen', 'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo', 'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
@@ -672,45 +451,30 @@
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno', 'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele', 'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno', 'Username': 'Přihlašovací jméno',
'Users': 'Uživatelé', 'variables': 'variables',
'Using the shell may lock the database to other users of this app.': 'Použití příkazového shellu může uzamknout databázi ostatním uživatelům této aplikace.',
'variables': 'proměnné',
'Verify Password': 'Zopakujte heslo', 'Verify Password': 'Zopakujte heslo',
'Version': 'Verze', 'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s', 'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování', 'Versioning': 'Verzování',
'Videos': 'Videa', 'Videos': 'Videa',
'View': 'Šablona (View)', 'View': 'Pohled (View)',
'Views': 'Šablony (Views)', 'Views': 'Pohledy',
'views': 'šablony (views)', 'views': 'pohledy',
'Warning!': 'Pozor!', 'Web Framework': 'Web Framework',
'WARNING:': 'POZOR:',
'WARNING: The following views could not be compiled:': 'POZOR: Následující šablony se nepodařilo zkompilovat:',
'Web Framework': 'Webový framework',
'web2py Admin Password': 'web2py Heslo administrátora',
'web2py apps to deploy': 'web2py aplikace k nasazení',
'web2py Debugger': 'web2py Debugger',
'web2py downgrade': 'web2py downgrade',
'web2py is up to date': 'Máte aktuální verzi web2py.', 'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program', 'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Nedávné tweety na Twitteru o web2py', 'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'aktualizace Web2py', 'web2py upgrade': 'web2py upgrade',
'web2py upgraded; please restart it': 'Web2py bylo aktualizováno; prosím restarujte jej', 'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'Welcome': 'Vítejte', 'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve Web2py aplikaci.', 'Welcome to web2py': 'Vitejte ve web2py',
'Welcome to web2py!': 'Vítejte ve Web2py aplikaci.', 'Welcome to web2py!': 'Vítejte ve web2py!',
'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.', 'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.',
'WSGI reference name': 'jméno WSGI reference',
'YES': 'ANO',
'Yes': 'Ano',
'You are successfully running web2py': 'Úspěšně jste spustili web2py.', 'You are successfully running web2py': 'Úspěšně jste spustili web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení', 'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'You can inspect variables using the console below': 'You can inspect variables using the console below',
'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.', 'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.',
'You have one more login attempt before you are locked out': 'Máte jen jeden další pokus k přihlášení před zablokováním',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na', 'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You only need these if you have already registered': 'Toto potřebujete jen tehdy, jestliže jste se už registroval(a)',
'You visited the url %s': 'Navštívili jste stránku %s,', 'You visited the url %s': 'Navštívili jste stránku %s,',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)', 'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
} }
+6 -14
View File
@@ -4,7 +4,6 @@ import time
from gluon import portalocker from gluon import portalocker
from gluon.admin import apath from gluon.admin import apath
from gluon.fileutils import read_file from gluon.fileutils import read_file
from gluon.utils import web2py_uuid
# ########################################################### # ###########################################################
# ## make sure administrator is on localhost or https # ## make sure administrator is on localhost or https
# ########################################################### # ###########################################################
@@ -50,18 +49,15 @@ except IOError:
def verify_password(password): def verify_password(password):
session.pam_user = None session.pam_user = None
if DEMO_MODE: if DEMO_MODE:
ret = True return True
elif not _config.get('password'): elif not _config.get('password'):
ret - False return False
elif _config['password'].startswith('pam_user:'): elif _config['password'].startswith('pam_user:'):
session.pam_user = _config['password'][9:].strip() session.pam_user = _config['password'][9:].strip()
import gluon.contrib.pam import gluon.contrib.pam
ret = gluon.contrib.pam.authenticate(session.pam_user, password) return gluon.contrib.pam.authenticate(session.pam_user, password)
else: else:
ret = _config['password'] == CRYPT()(password)[0] return _config['password'] == CRYPT()(password)[0]
if ret:
session.hmac_key = web2py_uuid()
return ret
# ########################################################### # ###########################################################
@@ -104,12 +100,13 @@ def write_hosts_deny(denied_hosts):
portalocker.unlock(f) portalocker.unlock(f)
f.close() f.close()
def login_record(success=True): def login_record(success=True):
denied_hosts = read_hosts_deny() denied_hosts = read_hosts_deny()
val = (0, 0) val = (0, 0)
if success and request.client in denied_hosts: if success and request.client in denied_hosts:
del denied_hosts[request.client] del denied_hosts[request.client]
elif not success: elif not success and not request.is_local:
val = denied_hosts.get(request.client, (0, 0)) val = denied_hosts.get(request.client, (0, 0))
if time.time() - val[1] < expiration_failed_logins \ if time.time() - val[1] < expiration_failed_logins \
and val[0] >= allowed_number_of_attempts: and val[0] >= allowed_number_of_attempts:
@@ -120,11 +117,6 @@ def login_record(success=True):
write_hosts_deny(denied_hosts) write_hosts_deny(denied_hosts)
return val[0] return val[0]
def failed_login_count():
denied_hosts = read_hosts_deny()
val = denied_hosts.get(request.client, (0, 0))
return val[0]
# ########################################################### # ###########################################################
# ## session expiration # ## session expiration
+579
View File
@@ -0,0 +1,579 @@
/*=============================================================
GENERAL
==============================================================*/
html,body{height:auto;background:transparent;}
/*=============================================================
CONTROLS
==============================================================*/
label,
input,
button,
select,
textarea,
button.btn
{
font-size:13px;
font-weight:normal;
line-height:18px;
}
textarea,
select
{
margin-bottom:9px;
}
select,
/*textarea,*/
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"],
.uneditable-input,
a.btn-lnk
{
height:18px;
padding:4px;
font-size:13px;
line-height:18px;
}
.design h3,
.plugin h3
{
background-position:0 2px;
}
select,
input[type="file"]
{
height:28px;
line-height:28px;
}
input[type="submit"],
input[type="button"]
{
font-size:13px;
height:28px;
line-height:18px;
padding:4px 10px;
}
input[type="radio"],
input[type="checkbox"]
{
margin-top:2px;
}
.button.btn
{
line-height:1.25em;
font-size:inherit;
border:none;
text-shadow:none;
margin-bottom:0px;
-webkit-border-radius:0px;
-moz-border-radius:0px;
border-radius:0px;
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none);
}
.button.btn:hover
{
background-color:transparent;
-webkit-transition: background-position 0s linear;
-moz-transition: background-position 0s linear;
-o-transition: background-position 0s linear;
transition: background-position 0s linear;
}
form label
{
font-weight:bold;
}
.help
{
border-color:transparent;
}
/* tree menu */
.folder
{
border:none;
}
.folder>i
{
display:none;
}
.celled
{
padding-top: 2px;
}
.celled-one
{
padding-top: 1px;
}
.test h3
{
border:0;
padding-left:18px;
}
/*=============================================================
FLASH MESSAGEBOX
==============================================================*/
.flash
{
position:fixed;
width:50%;
top:49px;
left:25%;
right:25%;
cursor:default;
text-align:center;
padding:8px 35px 8px 14px;
z-index:5620;
}
.flash>.close
{
color:inherit;
opacity:0.7;
}
.flash>.close:hover
{
opacity:0.9;
}
/*=============================================================
NAVBAR
==============================================================*/
.navbar-fixed-top .navbar-inner,
.navbar-static-top .navbar-inner
{
/* in place of shadow image */
-webkit-box-shadow:0px 10px 20px rgba(195,195,195,1.0);
-moz-box-shadow: 0px 10px 20px rgba(195,195,195,1.0);
box-shadow: 0px 10px 20px rgba(195,195,195,1.0);
//zoom:1; /* IE6-9 */
filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=0, OffY=10, Color=#000000); /* IE6-9 */
padding:0;
}
.navbar-inverse .navbar-inner
{
min-height:33px; /* required - override */
height:33px;
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
background:#292929 url(../images/header_bg.png) repeat-x;
border:none;
}
#header
{
background:transparent;
}
#header.navbar
{
overflow:visible;
}
.navbar-inverse .nav > li > a
{
padding:0;
line-height:1.25;
text-shadow:none;
}
.navbar .btn-navbar
{
padding:4px;
margin:5px 5px 0 5px;
}
#menu{margin-right:-7px;}
/*=============================================================
FOOTER
==============================================================*/
#footer
{
padding-bottom:0;
}
/*=============================================================
MAIN
==============================================================*/
#main
{
position:static;
padding-top:0;
padding-bottom:0;
}
/*=============================================================
SIDEBAR
==============================================================*/
.sidebar_inner
{
background:transparent;
padding:0;
min-width:auto;
}
.sidebar .box {
border-top:1px solid #EEE;
}
/*=============================================================
WIZARD
==============================================================*/
.step div.help li
{
line-height:inherit;
}
.ms-container .ms-selectable li.ms-elem-selectable,
.ms-container .ms-selection li.ms-elem-selected
{
font-size:13px;
}
.input-append a.btn
{
padding:4px;
height:18px;
font-size:13px;
line-height:18px;
}
/*=============================================================
ERRORS TABLE
==============================================================*/
.errors .table th
{
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
}
.tablebar span.help
{
font-weight:normal;
line-height:1.25em;
text-shadow:none;
width:auto;
}
/*=============================================================
TOOLTIP
==============================================================*/
.tooltip.in
{
opacity:1;
filter:alpha(opacity=100);
}
.tooltip-inner
{
opacity:1;
text-align:left;
background:#9fb364;
color:#eef1d9;
border:1px solid #eef1d9;
font-style:italic;
padding:0.3em;
-moz-border-radius:0.5em;
border-radius:0.5em;
font-size:13px;
text-transform:none;
}
.tooltip.right .tooltip-arrow,
.tooltip.left .tooltip-arrow
{
border-color:transparent;
}
/*=============================================================
THE GRID
==============================================================*/
.w2p_grid_bottom_bar .w2p_export_menu
{
line-height:18px;
margin-left:0;
}
.w2p_export_menu .dropdown-toggle
{
cursor:pointer;
margin:0;
padding:0;
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);
}
.w2p_export_menu ul
{
margin-top:2px;
display:none;
}
.w2p_export_menu li
{
display:list-item;
margin:0;
}
div.web2py_grid
{
font-size:13px;
line-height:18px;
}
.web2py_grid a.btn
{
font-size:13px;
line-height:18px;
padding:4px 10px;
margin-left:0;
margin-right:4px;
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
}
.web2py_grid .input-append .btn
{
padding:4px 10px;
margin-right:0;
font-family:inherit;
color:#333;
text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);
border:1px solid #c5c5c5;
}
.web2py_grid select:focus
{
border-color:rgba(232,149,60,0.8);
outline:0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(232, 149, 60, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(232,149,60,0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(232, 149, 60, 0.6);
}
.web2py_console input[type="button"],
.web2py_grid .row_buttons a.btn
{
color:#333;
line-height:18px;
padding:4px 10px;
text-shadow:rgba(255, 255, 255, 0.74902) 0px 1px 1px;
border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.web2py_console input[type="button"]:hover,
.web2py_grid .row_buttons a.btn:hover
{
color:#333;
border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
background:#E6E6E6;
background-position: 0 -15px !important;
-webkit-transition: background-position .1s linear;
-moz-transition: background-position .1s linear;
-o-transition: background-position .1s linear;
transition: background-position .1s linear;
}
.web2py_table
{
border:none;
}
.web2py_table table
{
/*table-layout:fixed;*/
margin-bottom:4px;
}
.web2py_table table td
{
/*word-wrap:break-word;*/ /*uncomment when "table-layout:fixed" is applied */
}
.web2py_grid thead th
{
background-color:transparent;
padding:4px 5px;
line-height:18px;
vertical-align:bottom;
border-right:0;
border-bottom:0;
word-wrap:break-word;
}
.web2py_grid .btn-group > .dropdown-menu
{
font-size:13px;
}
.web2py_grid .dropdown-menu li > a:hover,
.web2py_grid .dropdown-menu li > a:focus
{
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
background-image:none;
background-color:#E8953C;
}
.pagination
{
margin:0;
height:30px;
}
.pagination ul > li > a
{
line-height:28px;
}
#w2p_grid_addbtn:focus,
#w2p_search-form :focus,
.btn:focus
{
outline:none;
}
.web2py_console input[type="button"]:focus,
.web2py_grid .row_buttons a.btn:focus
{
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
}
div.web2py_counter.span6
{
min-height:20px;
}
.web2py_paginator
{
border:0;
margin:0;
padding:0;
background-color:transparent;
}
.web2py_paginator ul li a
{
margin-right:0;
padding:0 14px;
border:1px solid #DDD;
border-left-width:0;
color:#E8953C;
}
.web2py_paginator ul li a:hover
{
background: whiteSmoke;
border: 1px solid #DDD;
border-left-width:0;
color:#e2821b;
}
.web2py_paginator ul li:first-child a,
.web2py_paginator ul li:first-child a:hover
{
border-left-width:1px;
}
.web2py_paginator .current
{
font-weight:normal;
}
.web2py_paginator ul li.current a:hover
{
color:#999;
}
.editor-bar-column a[name="save"]
{
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;
padding:2px 6px;
font-size:11px;
line-height:17px;
margin:0;
}
.editor-bar-column a[name="save"]:hover
{
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;
}
.keybindings
{
padding:0 18px 10px;
}
.keybindings li
{
margin-bottom:0;
}
/*----- translate page ---*/
.languageform input
{
margin-bottom:0;
}
.languageform div
{
margin-bottom:9px;
}
.languageform input.untranslated
{
background-color:#FC0;
}
.step #wizard_nav .first-box
{
padding-top:0;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media (max-width: 979px)
{
/*-----------------------------------
Navbar
-------------------------------------*/
#header .navbar-inner
{
padding:0;
}
/*collapsed menu*/
.navbar .nav-collapse .nav
{
background:#222;
padding:8px 2px 8px 8px;
-webkit-border-bottom-right-radius:8px;
-webkit-border-bottom-left-radius:8px;
-moz-border-radius-bottomright:8px;
-moz-border-radius-bottomleft:8px;
border-bottom-right-radius:8px;
border-bottom-left-radius:8px;
}
#menu
{
margin-right:0;
}
#menu li
{
float:none;
}
#menu a.button,
#menu a.button span
{
background-image:url(../images/menu_responsive.png);
}
#menu a.button
{
padding:0 1em 0 0;
}
}
@media(max-width:632px)
{
/*-----------------------------------
footer
-------------------------------------*/
#footer
{
height:auto;
}
#footer select
{
margin-top:8px;
}
}
+1 -1
View File
@@ -476,7 +476,7 @@ h4.editableapp { background: #fff url(../images/folder.png) no-repeat; }
h4.currentapp { background: #fff url(../images/folder_locked.png) no-repeat; } h4.currentapp { background: #fff url(../images/folder_locked.png) no-repeat; }
.w2p_flash { position:fixed; width:50%; top:49px; left:25%; right:25%; cursor:default; text-align:center; z-index:5620; } .flash { position:fixed; width:50%; top:49px; left:25%; right:25%; cursor:default; text-align:center; z-index:5620; }
span#closeflash {position:absolute; top:1px; right:-1px; font-size:150%; border:1px solid black; border-color: transparent transparent #fbeed5 #fbeed5; border-radius: 0 0 0 4px; width:22px; } span#closeflash {position:absolute; top:1px; right:-1px; font-size:150%; border:1px solid black; border-color: transparent transparent #fbeed5 #fbeed5; border-radius: 0 0 0 4px; width:22px; }
span#closeflash:hover {font-weight:bold; cursor:pointer; } span#closeflash:hover {font-weight:bold; cursor:pointer; }
+322
View File
@@ -0,0 +1,322 @@
/** these MUST stay **/
a {text-decoration:none; white-space:nowrap}
a:hover {text-decoration:underline}
a.button {text-decoration:none}
h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block;
font-family:Helvetica}
h1 {font-size:4.00em}
h2 {font-size:3.00em}
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;}
td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
form table tr td label {text-align:left}
p,table,ol,ul {padding:0; margin: 0.75em 0}
p {text-align:justify}
ol, ul {list-style-position:outside; margin-left:2em}
li {margin-bottom:0.5em}
span,input,select,textarea,button,label,a {display:inline}
img {border:0}
blockquote,blockquote p,p blockquote {
font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em}
i,em {font-style:italic}
strong {font-weight:bold}
small {font-size:0.8em}
code {font-family:Courier}
textarea {width:100%}
video {width:400px}
audio {width:200px}
[type="text"], [type="password"], select {
margin-right: 5px; width: 300px;
}
.hidden {display:none;visibility:visible}
.right {float:right; text-align:right}
.left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle}
/** end **/
/* Sticky footer begin */
.main {
padding:20px 0 50px 0;
}
.footer,.push {
height:6em;
padding:1em 0;
clear:both;
}
.footer-content {position:relative; bottom:-4em; width:100%}
.auth_navbar {
white-space:nowrap;
}
/* Sticky footer end */
.footer {
border-top:1px #DEDEDE solid;
}
.header {
/* background:<fill here for header image>; */
}
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 */
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {margin:5px 0}
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em}
/* Fix */
#auth_user_remember__row label {display:inline}
#web2py_user_form td {vertical-align:top}
/*********** web2py specific ***********/
div.flash {
font-weight:bold;
display:none;
position:fixed;
padding:10px;
top:48px;
right:250px;
min-width:280px;
opacity:0.95;
margin:0px 0px 10px 10px;
vertical-align:middle;
cursor:pointer;
color:#fff;
background-color:#000;
border:2px solid #fff;
border-radius:8px;
-o-border-radius: 8px;
-moz-border-radius:8px;
-webkit-border-radius:8px;
background-image: -webkit-linear-gradient(top,#222,#000);
background-image: -o-linear-gradient(top,#222,#000);
background-image: -moz-linear-gradient(90deg, #222, #000);
background-image: linear-gradient(top,#222,#000);
background-repeat: repeat-x;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
z-index:2000;
}
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
.ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.flash:hover { opacity:0.25; }
div.error_wrapper {display:block}
div.error {
width: 298px;
background:red;
border: 2px solid #d00;
color:white;
padding:5px;
display:inline-block;
background-image: -webkit-linear-gradient(left,#f00,#fdd);
background-image: -o-linear-gradient(left,#f00,#fdd);
background-image: -moz-linear-gradient(0deg, #f00, #fdd);
background-image: linear-gradient(left,#f00,#fdd);
background-repeat: repeat-y;
}
.topbar {
padding:10px 0;
width:100%;
color:#959595;
vertical-align:middle;
padding:auto;
background-image:-khtml-gradient(linear,left top,left bottom,from(#333333),to(#222222));
background-image:-moz-linear-gradient(top,#333333,#222222);
background-image:-ms-linear-gradient(top,#333333,#222222);
background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333333),color-stop(100%,#222222));
background-image:-webkit-linear-gradient(top,#333333,#222222);
background-image:-o-linear-gradient(top,#333333,#222222);
background-image:linear-gradient(top,#333333,#222222);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);
-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
}
.topbar a {
color:#e1e1e1;
}
#navbar {float:right; padding:5px; /* same as superfish */}
.statusbar {
background-color:#F5F5F5;
margin-top:1em;
margin-bottom:1em;
padding:.5em 1em;
border:1px solid #ddd;
border-radius:5px;
-moz-border-radius:5px;
-webkit-border-radius:5px;
}
.breadcrumbs {float:left}
.copyright {float:left}
#poweredBy {float:right}
/* #MEDIA QUERIES SECTION */
/*
*Grid
*
* The default style for SQLFORM.grid even using jquery-iu or another ui framework
* 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_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
padding:10px 5px 10px 5px;
}
.web2py_grid tr.odd {background-color:#F9F9F9}
.web2py_grid tr:hover {background-color:#F5F5F5}
/*
.web2py_breadcrumbs a {
line-height:20px; margin-right:5px; display:inline-block;
padding:3px 5px 3px 5px;
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
color:#3C3C3D;
text-shadow:1px 1px 0 #FFFFFF;
white-space:nowrap; overflow:visible; cursor:pointer;
background:#ECECEC;
border:1px solid #CACACA;
-webkit-border-radius:2px; -moz-border-radius:2px;
-webkit-background-clip:padding-box; border-radius:2px;
outline:none; position:relative; zoom:1; *display:inline;
}
*/
.web2py_console form {
width: 100%;
display: inline;
vertical-align: middle;
margin: 0 0 0 5px;
}
.web2py_console form select {
margin:0;
}
.web2py_search_actions {
float:left;
text-align:left;
}
.web2py_grid .row_buttons {
min-height:25px;
vertical-align:middle;
}
.web2py_grid .row_buttons a {
margin:3px;
}
.web2py_search_actions {
width:100%;
}
.web2py_grid .row_buttons a,
.web2py_paginator ul li a,
.web2py_search_actions a,
.web2py_console input[type=submit],
.web2py_console input[type=button],
.web2py_console button {
line-height:20px;
margin-right:2px; display:inline-block;
padding:3px 5px 3px 5px;
}
.web2py_counter {
margin-top:5px;
margin-right:2px;
width:35%;
float:right;
text-align:right;
}
/*Fix firefox problem*/
.web2py_table {clear:both; display:block}
.web2py_paginator {
padding:5px;
text-align:right;
background-color:#f2f2f2;
}
.web2py_paginator ul {
list-style-type:none;
margin:0px;
padding:0px;
}
.web2py_paginator ul li {
display:inline;
}
.web2py_paginator .current {
font-weight:bold;
}
.web2py_breadcrumbs ul {
list-style:none;
margin-bottom:18px;
}
li.w2p_grid_breadcrumb_elem {
display:inline-block;
}
.web2py_console form { vertical-align: middle; }
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; }
.web2py_htmltable {
width: 100%;
overflow-x: auto;
-ms-overflow-x:scroll;
}
#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}
@@ -0,0 +1,264 @@
/*=============================================================
CUSTOM RULES
==============================================================*/
body{height:auto;} /* to avoid vertical scroll bar */
a{}
a:visited{}
a:hover{}
a:focus{}
a:active{}
h1{}
h2{}
h3{}
h4{}
h5{}
h6{}
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);
color:#865100;
background:#feea9a;
border:1px solid;
border-top:0px;
border-left:0px;
border-right:0px;
border-radius:0;
opacity:1;
}
#header{margin-top:60px;}
.mastheader h1 {
margin-bottom:9px;
font-size:81px;
font-weight:bold;
letter-spacing:-1px;
line-height:1;
font-size:54px;
}
.mastheader small {
font-size:20px;
font-weight:300;
}
/* auth navbar - primitive style */
.auth_navbar,.auth_navbar a{color:inherit;}
.navbar-inner {-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
.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;
}
/* 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 .help-inline{color:#3A87AD;}
div.controls .error_wrapper +.help-inline {margin-left:-99999px;}
div.controls select +.error_wrapper {margin-left:5px;}
.ie-lte7 div.error{color:#fff;}
/* beautify brand */
.navbar {margin-bottom:0}
.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;}
/*=============================================================
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 */
textarea { width:90%}
.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;}
/* web2py radio widget adjustment */
.generic-widget input[type='radio'] {margin:-1px 0 0 0; vertical-align: middle;}
.generic-widget input[type='radio'] + label {display:inline-block; margin:0 0 0 6px; vertical-align: middle;}
/*=============================================================
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;
}
/*=============================================================
OTHER RULES
==============================================================*/
/* 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;}
div.controls .error {
width: auto;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media only screen and (max-width:979px){
body{padding-top:0px;}
#navbar{/*top:5px;*/}
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;
}
input[type="text"],input[type="password"],select{
width:95%;
}
}
@media (max-width: 767px) {
.navbar {
margin-right: -20px;
margin-left: -20px;
}
}
@@ -0,0 +1,122 @@
/*=============================================================
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;
border-top: 4px solid #000000;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-left: 2px;
margin-top: 8px;
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;
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-left: 8px;
margin-top: 6px;
}
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;}
+4 -4
View File
@@ -77,10 +77,10 @@ function doClickSave() {
t.attr('disabled', ''); t.attr('disabled', '');
var flash = xhr.getResponseHeader('web2py-component-flash'); var flash = xhr.getResponseHeader('web2py-component-flash');
if(flash) { if(flash) {
$('.w2p_flash').html(decodeURIComponent(flash)) $('.flash').html(decodeURIComponent(flash))
.append('<a href="#" class="close">&times;</a>') .append('<a href="#" class="close">&times;</a>')
.slideDown(); .slideDown();
} else $('.w2p_flash').hide(); } else $('.flash').hide();
try { try {
if(json.error) { if(json.error) {
window.location.href = json.redirect; window.location.href = json.redirect;
@@ -158,10 +158,10 @@ function doToggleBreakpoint(filename, url, sel) {
// show flash message (if any) // show flash message (if any)
var flash = xhr.getResponseHeader('web2py-component-flash'); var flash = xhr.getResponseHeader('web2py-component-flash');
if(flash) { if(flash) {
$('.w2p_flash').html(decodeURIComponent(flash)) $('.flash').html(decodeURIComponent(flash))
.append('<a href="#" class="close">&times;</a>') .append('<a href="#" class="close">&times;</a>')
.slideDown(); .slideDown();
} else $('.w2p_flash').hide(); } else $('.flash').hide();
try { try {
if(json.error) { if(json.error) {
window.location.href = json.redirect; window.location.href = json.redirect;
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -477,11 +477,11 @@ function filter_files() {
message=data['message']; message=data['message'];
for(var i=0; i<files.length; i++) for(var i=0; i<files.length; i++)
jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown(); jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown();
jQuery('.w2p_flash').html(message).slideDown(); jQuery('.flash').html(message).slideDown();
}); });
} else { } else {
jQuery('.component_contents li, .formfield, .comptools').slideDown(); jQuery('.component_contents li, .formfield, .comptools').slideDown();
jQuery('.w2p_flash').html('').hide(); jQuery('.flash').html('').hide();
} }
} }
jQuery(document).ready(function(){ jQuery(document).ready(function(){
+1 -1
View File
@@ -56,7 +56,7 @@
{{pass}} {{pass}}
</ul> </ul>
</div> </div>
{{=button_enable(URL('enable',args=a, hmac_key=session.hmac_key), a) if a!='admin' else ''}} {{=button_enable(URL('enable',args=a), a) if a!='admin' else ''}}
</td> </td>
</tr> </tr>
{{pass}} {{pass}}
+1 -1
View File
@@ -47,7 +47,7 @@
<div id="{{=globals().get('main_id', 'main')}}" class="container-fluid"> <div id="{{=globals().get('main_id', 'main')}}" class="container-fluid">
<div id="main_inner" class="row-fluid"> <div id="main_inner" class="row-fluid">
<div class="span12"> <div class="span12">
<div class="w2p_flash alert">{{=response.flash or ''}}</div> <div class="flash alert">{{=response.flash or ''}}</div>
{{include}} {{include}}
</div><!-- /main span12 --> </div><!-- /main span12 -->
</div><!-- /main row-fluid --> </div><!-- /main row-fluid -->
@@ -3,7 +3,6 @@
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}"; var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}"; var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}"; var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
var w2p_ajax_disable_with_message = "{{=T('Working...')}}";
var ajax_error_500 = '{{=T.M('An error occured, please [[reload %s]] the page') % URL(args=request.args, vars=request.get_vars) }}' var ajax_error_500 = '{{=T.M('An error occured, please [[reload %s]] the page') % URL(args=request.args, vars=request.get_vars) }}'
//--></script> //--></script>
{{ {{
+8 -8
View File
@@ -10,7 +10,7 @@ session.forget()
cache_expire = not request.is_local and 300 or 0 cache_expire = not request.is_local and 300 or 0
# @cache.action(time_expire=300, cache_model=cache.ram, quick='P') @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def index(): def index():
return response.render() return response.render()
@@ -19,13 +19,14 @@ def index():
def what(): def what():
import urllib import urllib
try: try:
images = XML(urllib.urlopen('http://www.web2py.com/poweredby/default/images').read()) images = XML(urllib.urlopen(
'http://www.web2py.com/poweredby/default/images').read())
except: except:
images = [] images = []
return response.render(images=images) return response.render(images=images)
# @cache.action(time_expire=300, cache_model=cache.ram, quick='P') @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def download(): def download():
return response.render() return response.render()
@@ -73,15 +74,14 @@ def license():
filename = os.path.join(request.env.gluon_parent, 'LICENSE') filename = os.path.join(request.env.gluon_parent, 'LICENSE')
return response.render(dict(license=MARKMIN(read_file(filename)))) return response.render(dict(license=MARKMIN(read_file(filename))))
def version(): def version():
if request.args(0) == 'raw': if request.args(0)=='raw':
return request.env.web2py_version return request.env.web2py_version
from gluon.fileutils import parse_version from gluon.fileutils import parse_version
(a, b, c, pre_release, build) = parse_version(request.env.web2py_version) (a, b, c, pre_release, build) = parse_version(request.env.web2py_version)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % \ return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % (
(a, b, c, build.year, build.month, build.day, build.hour, build.minute, build.second, pre_release) a,b,c,build.year,build.month,build.day,
build.hour,build.minute,build.second,pre_release)
@cache.action(time_expire=300, cache_model=cache.ram, quick='P') @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def examples(): def examples():
@@ -35,6 +35,12 @@ def hello6():
response.flash = 'Hello World in a flash!' response.flash = 'Hello World in a flash!'
return dict(message=T('Hello World')) return dict(message=T('Hello World'))
def status():
""" page that shows internal status"""
return dict(toolbar=response.toolbar())
def redirectme(): def redirectme():
""" redirects to /{{=request.application}}/{{=request.controller}}/hello3 """ """ redirects to /{{=request.application}}/{{=request.controller}}/hello3 """
@@ -27,4 +27,4 @@ def xml():
def beautify(): def beautify():
return dict(message=BEAUTIFY(dict(a=1,b=[2,3,dict(hello='world')]))) return dict(message=BEAUTIFY(request))
+6 -6
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
response.menu = [ response.menu = [
(T('Home'), request.controller == 'default' and request.function == 'index', URL('default', 'index')), (T('Home'), False, URL('default', 'index')),
(T('About'), request.controller == 'default' and request.function == 'what', URL('default', 'what')), (T('About'), False, URL('default', 'what')),
(T('Download'), request.controller == 'default' and request.function == 'download', URL('default', 'download')), (T('Download'), False, URL('default', 'download')),
(T('Docs & Resources'), request.controller == 'default' and request.function == 'documentation', URL('default', 'documentation')), (T('Docs & Resources'), False, URL('default', 'documentation')),
(T('Support'), request.controller == 'default' and request.function == 'support', URL('default', 'support')), (T('Support'), False, URL('default', 'support')),
(T('Contributors'), request.controller == 'default' and request.function == 'who', URL('default', 'who'))] (T('Contributors'), False, URL('default', 'who'))]
######################################################################### #########################################################################
## Changes the menu active item ## Changes the menu active item
+1 -3
View File
@@ -1,3 +1 @@
from gluon.utils import web2py_uuid session.connect(request,response,cookie_key='yoursecret')
cookie_key = cache.ram('cookie_key',lambda: web2py_uuid(),None)
session.connect(request,response,cookie_key=cookie_key)
@@ -10,7 +10,6 @@
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]] - [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]] - [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
- [[web2py Reference Project http://www.web2pyref.com/]] - [[web2py Reference Project http://www.web2pyref.com/]]
- [[An advanced tutorial https://milesm.pythonanywhere.com/wiki]]
- [[Killer Web Development Tutorial http://killer-web-development.com/]] - [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!) - [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE) - [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
@@ -20,9 +19,9 @@
#### Code #### Code
- [[web2pyslices (recipes) http://www.web2pyslices.com popup]] - [[web2pyslices (recipes) http://www.web2pyslices.com popup]]
- [[Dashboard welcome app https://github.com/mjbeller/web2py-starter]] - [[Layouts http://www.web2py.com/layouts popup]]
- [[stupid.css theme https://github.com/mdipierro/web2py-welcome-theme-stupid]]
- [[Plugins http://www.web2py.com/plugins popup]] - [[Plugins http://www.web2py.com/plugins popup]]
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
- [[Appliances http://www.web2py.com/appliances popup]] - [[Appliances http://www.web2py.com/appliances popup]]
- [[web2py utils http://packages.python.org/web2py_utils/ popup]] - [[web2py utils http://packages.python.org/web2py_utils/ popup]]
- [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]] - [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]]
@@ -24,10 +24,6 @@ French speakers group
``web2py-fr``:groupdates ``web2py-fr``:groupdates
## Italian Group
- [[https://groups.google.com/forum/?fromgroups#!forum/web2py-it https://groups.google.com/forum/?fromgroups#!forum/web2py-it popup]]
## Japanese Group ## Japanese Group
Japanese speakers group Japanese speakers group
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6 -10
View File
@@ -1,11 +1,7 @@
.calendar {z-index:2000;position:relative;margin-top:140px;display:none;background-color:white;border:1px solid #000;color:#000;cursor:default;box-shadow:0 0 10px #666}.calendar * {text-align: center;font-size:10px!important} .calendar{z-index:99;position:relative;display:none;background:#fff;border:2px solid #000;font-size:11px;color:#000;cursor:default;font-family:Arial,Helvetica,sans-serif;
.calendar table {border-collapse:collapse} border-radius: 10px;
.calendar tbody tr:hover {background-color:#fbf6d9} -moz-border-radius: 10px;
.calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0} -webkit-border-radius: 10px;
.calendar thead tr {background-color:#f1f1f1} }.calendar table{margin:0px;font-size:11px;color:#000;cursor:default;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;color:#fff;background:#000;}.calendar .nav{background:#000;color:#fff}.calendar thead .title{font-weight:bold;padding:1px;background:#000;color:#fff;text-align:center;}.calendar thead .name{padding:2px;text-align:center;background:#bbb;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite {background-color:#666;}.calendar thead .active{padding:2px 0 0 2px;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;background:#bbb;}.calendar tbody .rowhilite td{background:#ddd;}.calendar tbody td.hilite{background:#bbb;}.calendar tbody td.active{background:#bbb;}.calendar tbody td.selected{font-weight:bold;background:#ddd;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#bbb;padding:1px;background:#000;color:#fff;text-align:center;}.calendar tfoot .hilite{background:#ddd;}.calendar tfoot .active{}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;background:#e4e0d8;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{padding:1px 0;text-align:center;background-color:#bbb;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;font-weight:bold;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}.hour,.minute{font-size:2em;}
.calendar tbody tr {border-bottom:2px solid #f1f1f1}
.calendar th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
.calendar thead th {vertical-align:bottom}
.calendar tbody th {vertical-align:top}
#CP_hourcont{z-index:2000;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:2000;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:2000} #CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99}
+20 -69
View File
@@ -1,69 +1,20 @@
/* Gray the black as suggested by Anthony */ @import url(http://fonts.googleapis.com/css?family=Economica);
h1,h2,h3,h4,h5,h6 {color: rgb(35, 35, 35); text-transform:none} @@import url(http://fonts.googleapis.com/css?family=Belleza);
.black {
color: rgb(35, 35, 35); body { font-family: Arial, Helvetica; }
background-color: rgb(35, 35, 35); a, a:visited, a:hover, h1,h2,h3,h4,h5 {color: #658883}
} a.btn-danger, a.btn-warning, a.btn-success {color:white}
h1,h2,h3,h4,h5 { font-family: "Economica", Arial, Helevtica; }
/* Spacing between thead and tbody */ body {
/* Ref: http://stackoverflow.com/questions/9258754/spacing-between-thead-and-tbody */ background: url('../images/stripes.png') repeat-x;
tbody:before { }
content: "-"; #header {
display: block; margin-top: 40px;
line-height: 1em; }
color: transparent; .btn-180 {
} width: 180px;
}
/* Improve buttons in download page */ .page-header {
th, td {padding: 0} border-bottom: 0;
}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
/* Improve CODE() display though padding has no effect as some PRE are hardcoded somewhere can't find it */
/* padding of 10px should make it... */
pre {background-color: rgb(35, 35, 35)!important; border-radius:5px; color:white; padding: 10px}
/* Improve buttons in download page */
a.btn.btn180 {padding:10px; font-size:1.2em; width:200px}
.menu .web2py-menu-active a {
color: #26a69a;
}
.spaced-vertical {
margin: 0 0.5em 0.5em 0;
}
.btn:hover,
a.noeffect img:hover {
transition: scale .5s;
transform: scale(1.05);
}
.btn,
a.noeffect img {
transition: all .2s ease-in-out;
}
/* Lower saturation of color #26a69a - 20 points lower */
/* The below change to color #26a69a should come before other color change or they override all buttons background-color */
/* The color should maybe change at stupid.css level as it herited from there also in stupid.css it would be better
to define this color at one place actually color is defined all over the place */
a {color:#47a69d}
.btn, button, [type=button], [type=submit] {background-color:#47a69d}
.progress .determinate {background-color:#47a69d}
.progress .indeterminate {background-color:#47a69d}
a:not(.btn):not(.noeffect):hover {color:#47a69d}
a:not(.btn):not(.noeffect):after {background-color:#47a69d}
.tags > span {background-color:#47a69d}
.tags.dismissible > span.off:hover {background-color:#47a69d}
.aquamarine{background-color:#47a69d}
/* Lower the saturation of 20 points */
.green {background-color: #58cc65}
.yellow {background-color: #ffe333}
.red {background-color: #cc4229}
-359
View File
@@ -1,359 +0,0 @@
/************
Created by Massimo Di Pierro
Stupid.css is what the names says, take it with a grain of salt
License: BSD
************/
/*** basic styles ***/
html {box-sizing:border-box;}
*, *:after, *:before {border:0; margin:0; padding:0; box-sizing:inherit;}
html, body {max-width: 100vw; overflow-x: hidden}
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
p, li {margin-bottom:0.5em}
p {text-align:justify}
label, strong {font-weight:bold}
ul {list-style-type:none; padding-left:20px}
a {text-decoration:none; color:#26a69a; white-space:nowrap}
a:hover {cursor:pointer}
h1,h2,h3,h4,h5,h6{font-weight:bold; text-transform:uppercase}
h1{font-size:4em; margin:1.0em 0 0.25em 0}
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
h4{font-size:1.6em; margin:0.7em 0 0.25em 0}
h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9}
thead tr {background-color:#f1f1f1}
tbody tr {border-bottom:2px solid #f1f1f1}
td, th {padding: 5px; text-align: left; vertical-align:top}
thead th {vertical-align:bottom}
header, main, footer {display:block; with:100%} /* IE fix */
@media all and (max-width:599px) {
h1{font-size:2em}
h2{font-size:1.8em}
h3{font-size:1.6em}
h4{font-size:1.4em}
h5{font-size:1.2em}
h6{font-size:1.0em}
}
/*** buttons ***/
.btn, button, [type=button], [type=submit] {padding:0.5em 1em; margin:0 0.5em 0.5em 0; display:inline-block; background-color:#26a69a; color:white}
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
.btn.small, table .btn {padding:0.25em 0.5em; font-size:0.8em}
.btn.large {padding:1em 2em; font-size:1.2em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px}
.center {text-align:center; margin-left:auto; margin-right:auto}
.center>div {text-align:left}
.right {right:0; text-align:right}
.middle div {vertical-align:middle}
.bottom div {vertical-align:bottom}
.xscroll {overflow-x:scroll}
.yscroll {overflow-y:scroll}
.nowrap {white-space:nowrap; overflow-x:hidden}
.fill {width:100%}
.lifted {box-shadow:5px 5px 10px #666}
.relative {position:relative}
.relative>div {position:absolute}
.spaced {margin-bottom:20px; margin-top:20px}
.hidden {display:none}
/*** forms ***/
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outline:none}
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd; width:100%;background-color:transparent}
input, textarea, select, button, .btn {font-size:12px}
input:not([type]):hover, input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
input:invalid, input.error {background:#cc1f00;color:white}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters {display:inline-block; padding: 0 20px 0 0; vertical-align:top}
.container>.fill{display: inline-block}
.container img, .container video {max-width:100%}
@media all and (min-width:800px) {
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
.quarter {width:25%; margin-right:-5px}
.half {width:50%; margin-right:-10px}
.third {width:33.33%; margin-right:-6.66px}
.twothirds {width:66.66%; margin-right:-13.33px}
.threequarters {width:75%; margin-right:-15px}
}
@media all and (min-width:600px) and (max-width:799px) {
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.threequarters.compressible {width:75%; margin-right:-15px}
.quarter:not(.compressible), .half:not(.compressible), .threequarters:not(.compressible) {width:100%; margin-right:-20px}
.third {width:33.33%; margin-right:-6.66px}
.twothirds {width:66.66%; margin-right:-13.33px}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
}
@media all and (max-width:599px) {
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.third.compressible {width:33.33%; margin-right:-6.66px}
.twothirds.compressible {width:66.66%; margin-right:-13.33px}
.threequarters.compressible {width:75%; margin-right:-15px}
}
/*** progress bar from http://codepen.io/holdencreative/details/pvxGxy ***/
.progress {
margin-left:-15px;
margin-right:-15px;
position:relative;
height:8px;
display:block;
width:120%;
background-color:#acece6;
border-radius:0;
background-clip:padding-box;
overflow:hidden;
}
.progress .determinate {
position:absolute;
background-color:inherit;
top:0;
bottom:0;
background-color:#26a69a;
transition:width .3s linear;
}
.progress .indeterminate {
background-color:#26a69a;
}
.progress .indeterminate:before {
content:'';
position:absolute;
background-color:inherit;
top:0;
left:0;
bottom:0;
will-change:left, right;
animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.progress .indeterminate:after {
content:'';
position:absolute;
background-color:inherit;
top:0;
left:0;
bottom:0;
will-change:left, right;
animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation-delay:1.15s;
}
@-webkit-keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@-moz-keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@-webkit-keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
@-moz-keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
@keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
/**** dropdown menu from http://codepen.io/philhoyt/pen/ujHzd ***/
.menu {list-style:none; position:relative; margin:0; padding:0}
.menu.right {float:right}
.menu a {padding:0 15px; text-decoration:none;text-align:left;font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; text-align:left}
.menu li {position:relative; float:left; margin:0; padding:0}
.menu ul {background:white; border:1px solid #e1e1e1; visibility:hidden; opacity:0; position:absolute; top:110%; padding:0; z-index:1000; transition:all 0.2s ease-out; list-style-type:none; box-shadow:5px 5px 10px #666}
.menu ul a {padding:10px 15px; color:#333; font-weight:700; font-size:12px; line-height:16px; display: block}
.menu ul li {float:none; width:200px}
.menu ul ul {top:0; left:80%; z-index:1100}
.menu li:hover > ul {visibility:visible; opacity:1}
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
.menu.dark ul {background:#111111; border:1px solid #111111}
.menu.dark ul a {color:white}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:#111111}
@media all and (max-width:599px) {
header .menu li, header .menu ul {width: 100%}
header .menu.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px}
}
@media all and (min-width:600px) {
.ham {display:none!important}
.burger.accordion * {max-height:1000px; overflow:visible}
}
/*** pulsating ring from https://jsfiddle.net/mandynicole/7xrKP/ *******/
.pulse:after {
content:"";
border:3px solid #00e6ac;
-webkit-border-radius:30px;
height:40px;
width:40px;
position:absolute;
margin-left:-20px;
margin-top:-20px;
-webkit-animation:pulsate 1s ease-out;
-webkit-animation-iteration-count:infinite;
opacity:0.0
}
@-webkit-keyframes pulsate {
0% {-webkit-transform:scale(0.1, 0.1); opacity:0.0}
50% {opacity:1.0}
100% {-webkit-transform:scale(1.2, 1.2); opacity:0.0}
}
/**** underline effect ***/
a:not(.btn):not(.noeffect) {position:relative}
a:not(.btn):not(.noeffect):hover {color:#26a69a}
a:not(.btn):not(.noeffect):hover:after {width:100%}
a:not(.btn):not(.noeffect):after {
display:block;
position:absolute;
left:0;
bottom:-1px;
width:0;
height:2px;
background-color:#26a69a;
content:"";
transition:width 0.2s;
}
/**** modal ***/
.modal {
position:fixed;
z-index:9999;
top:0;
bottom:0;
left:0;
right:0;
background-color:rgba(0,0,0,0.8);
padding-top:20vh;
transition:opacity 500ms;
visibility:hidden;
opacity:0;
}
.modal:target {visibility:visible; opacity:1}
.modal div {margin-left:auto; margin-right:auto}
.modal .close:not(.btn) {position:absolute; top:10px; right:10px; font-size:20px}
.modal .close {transition:all 200ms}
/*** tooltips from http://codepen.io/trezy/pen/Khnzy ***/
[data-tooltip] {position:relative}
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:hover:before {
border-bottom:.6em solid #111111;
border-bottom:.6em solid #111111;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
left:0;
margin-top:12px;
z-index:2000;
}
[data-tooltip]:hover:after {
z-index:2000;
background-color:rgba(0,0,0,0.8);
border:4px solid rgba(0,0,0,0.8);
border-radius:7px;
color:white;
text-transform:none;
font-size: 12px;
content:attr(data-tooltip);
left:0;
top:2px;
margin-left:-20px;
margin-top:1.5em;
padding:5px 15px;
white-space:pre-wrap;
width:100px;
}
/*** accordion ***/
.accordion>input ~ label:before {content:"▲ "; color:#ddd}
.accordion>input:checked ~ label:before {content:"▼ "; color:#ddd}
.accordion>input {display:none}
.accordion>input:checked ~ *:not(label) {
max-height: 1000px !important;
overflow:visible !important;
-webkit-transition: max-height .3s ease-in;
transition: max-height .3s ease-in;
}
.accordion>*:not(label) {
max-height: 0;
overflow: hidden;
margin: 0;
padding: 0;
-webkit-transition: max-height .3s ease-out;
transition: max-height .3s ease-out;
}
/*** cards from http://codepen.io/edeesims/pen/iGDzk ***/
.card {perspective: 500px; max-width:100%}
.card>div {
position: absolute;
width: 100%;
height: 100%;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
transition: transform 1s;
transform-style: preserve-3d;
}
.card:hover>div {
transform: rotateY( 180deg ) ;
transition: transform 0.5s;
}
.card>div>div {
position: absolute;
height: 100%;
width: 100%;
backface-visibility: hidden;
}
.card>div>div:nth-child(2) {
transform: rotateY( 180deg );
}
/**** tags ****/
.tags > span {
padding: 4px 9px;
white-space: nowrap;
color: white;
background-color: #26a69a;
border-radius: 5px;
font-size:12px;
margin: 2px 5px 2px 0;
display: inline-block;
}
.tags.dismissible > span:hover {opacity: 0.5}
.tags.dismissible > span:not(.off):after {content:" ✕"}
.tags > span.off {background-color: #ccc}
.tags.dismissible > span.off:hover {background-color:#26a69a}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f;color:white}.blue{background-color:#0074d9;color:white}.aqua{background-color:#7fdbff;color:#111111}.teal{background-color:#39cccc;color:white}.olive{background-color:#3d9970;color:white}.green{background-color:#2ecc40;color:white}.aquamarine{background-color:#26a69a;color:white}.lime{background-color:#01ff70;color:#111111}.yellow{background-color:#ffdc00;color:#111111}.orange{background-color:#ff851b;color:white}.red{background-color:#cc1f00;color:white}.fuchsia{background-color:#f012be;color:white}.pink{background-color:#ee6e73;color:white}.purple{background-color:#b10dc9;color:white}.maroon{background-color:#85144b;color:white}.white{background-color:#fff;color:#111111;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa;color:white}.silver{background-color:#f1f1f1;color:#111111}.black{background-color:#111111;color:white}.glass{background:rgba(255,255,255,0.5);color:#111111}
+145 -20
View File
@@ -1,17 +1,84 @@
header a {color: white; font-size:1.1em} /** these MUST stay **/
main {min-height: 70vh} a {text-decoration:none; white-space:nowrap}
.form-group {padding-bottom: 10px !important;} a:hover {text-decoration:underline}
.w2p_hidden {display:none;visibility:visible} a.button {text-decoration:none}
h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block;
font-family:Helvetica}
h1 {font-size:4.00em}
h2 {font-size:3.00em}
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;}
td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
form table tr td label {text-align:left}
p,table,ol,ul {padding:0; margin: 0.75em 0}
p {text-align:justify}
ol, ul {list-style-position:outside; margin-left:2em}
li {margin-bottom:0.5em}
span,input,select,textarea,button,label,a {display:inline}
img {border:0}
blockquote,blockquote p,p blockquote {
font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em}
i,em {font-style:italic}
strong {font-weight:bold}
small {font-size:0.8em}
code {font-family:Courier}
textarea {width:100%}
video {width:400px}
audio {width:200px}
[type="text"], [type="password"], select {
margin-right: 5px; width: 300px;
}
.hidden {display:none;visibility:visible}
.right {float:right; text-align:right} .right {float:right; text-align:right}
.left {float:left; text-align:left} .left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle} .center {width:100%; text-align:center; vertical-align:middle}
/** end **/
/* Sticky footer begin */
.main {
padding:20px 0 50px 0;
}
.footer,.push {
height:6em;
padding:1em 0;
clear:both;
}
.footer-content {position:relative; bottom:-4em; width:100%}
.auth_navbar {
white-space:nowrap;
}
/* Sticky footer end */
.footer {
border-top:1px #DEDEDE solid;
}
.header {
/* background:<fill here for header image>; */
}
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 */
td.w2p_fw {padding-bottom:1px} td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top} td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:left} td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px} td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px} td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {white-space: wrap; margin:5px 0} div.w2p_export_menu {margin:5px 0}
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; font-size:0.7em; color: black} div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */ /* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em} #submit_record__row td {padding-top:.5em}
@@ -21,30 +88,54 @@ div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; pa
#web2py_user_form td {vertical-align:top} #web2py_user_form td {vertical-align:top}
/*********** web2py specific ***********/ /*********** web2py specific ***********/
div.w2p_flash { div.flash {
font-weight:bold; font-weight:bold;
display:none; display:none;
padding:20px 20px 20px 50px; position:fixed;
width:100%; padding:10px;
top:48px;
right:250px;
min-width:280px;
opacity:0.95; opacity:0.95;
margin:0px 0px 10px 10px;
vertical-align:middle; vertical-align:middle;
cursor:pointer; cursor:pointer;
color:#000; color:#fff;
background-color:#ffdc00; background-color:#000;
border:2px solid #fff;
border-radius:8px;
-o-border-radius: 8px;
-moz-border-radius:8px;
-webkit-border-radius:8px;
background-image: -webkit-linear-gradient(top,#222,#000);
background-image: -o-linear-gradient(top,#222,#000);
background-image: -moz-linear-gradient(90deg, #222, #000);
background-image: linear-gradient(top,#222,#000);
background-repeat: repeat-x;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
z-index:2000; z-index:2000;
} }
div.w2p_flash:before{content:"×";float:right; margin-right:100px; color:black;} div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
.ie-lte7 div.flash #closeflash .ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;} {color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.w2p_flash:hover { opacity:0.80; } div.flash:hover { opacity:0.25; }
div.error_wrapper {display:block} div.error_wrapper {display:block}
div.error { div.error {
color:red; width: 298px;
background:red;
border: 2px solid #d00;
color:white;
padding:5px; padding:5px;
display:inline-block; display:inline-block;
background-image: -webkit-linear-gradient(left,#f00,#fdd);
background-image: -o-linear-gradient(left,#f00,#fdd);
background-image: -moz-linear-gradient(0deg, #f00, #fdd);
background-image: linear-gradient(left,#f00,#fdd);
background-repeat: repeat-y;
} }
.topbar { .topbar {
@@ -99,8 +190,34 @@ div.error {
*/ */
/* .web2py_table {border:1px solid #ccc} */ /* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {} .web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%} .web2py_grid table {width:100%}
.web2py_grid td {color: black;} .web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
padding:10px 5px 10px 5px;
}
.web2py_grid tr.odd {background-color:#F9F9F9}
.web2py_grid tr:hover {background-color:#F5F5F5}
/*
.web2py_breadcrumbs a {
line-height:20px; margin-right:5px; display:inline-block;
padding:3px 5px 3px 5px;
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
color:#3C3C3D;
text-shadow:1px 1px 0 #FFFFFF;
white-space:nowrap; overflow:visible; cursor:pointer;
background:#ECECEC;
border:1px solid #CACACA;
-webkit-border-radius:2px; -moz-border-radius:2px;
-webkit-background-clip:padding-box; border-radius:2px;
outline:none; position:relative; zoom:1; *display:inline;
}
*/
.web2py_console form { .web2py_console form {
width: 100%; width: 100%;
@@ -185,6 +302,11 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select, .web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; } .web2py_console a { margin: 2px; }
.web2py_htmltable {
width: 100%;
overflow-x: auto;
-ms-overflow-x:scroll;
}
#wiki_page_body { #wiki_page_body {
width: 600px; width: 600px;
@@ -195,10 +317,13 @@ li.w2p_grid_breadcrumb_elem {
/* fix some IE problems */ /* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2} .ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); } .ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);} .ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px} .ie9 #w2p_query_panel {padding-bottom:2px}
.control-label.readonly{
padding-top:0px !important;
padding-right:0px !important;
}
.web2py_console .form-control {width: 20%; display: inline;}
.web2py_console #w2p_keywords {width: 50%;}
.web2py_search_actions a, .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { padding: 6px 12px; }
@@ -0,0 +1,264 @@
/*=============================================================
CUSTOM RULES
==============================================================*/
body{height:auto;} /* to avoid vertical scroll bar */
a{}
a:visited{}
a:hover{}
a:focus{}
a:active{}
h1{}
h2{}
h3{}
h4{}
h5{}
h6{}
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);
color:#865100;
background:#feea9a;
border:1px solid;
border-top:0px;
border-left:0px;
border-right:0px;
border-radius:0;
opacity:1;
}
#header{margin-top:60px;}
.mastheader h1 {
margin-bottom:9px;
font-size:81px;
font-weight:bold;
letter-spacing:-1px;
line-height:1;
font-size:54px;
}
.mastheader small {
font-size:20px;
font-weight:300;
}
/* auth navbar - primitive style */
.auth_navbar,.auth_navbar a{color:inherit;}
.navbar-inner {-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
.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;
}
/* 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 .help-inline{color:#3A87AD;}
div.controls .error_wrapper +.help-inline {margin-left:-99999px;}
div.controls select +.error_wrapper {margin-left:5px;}
.ie-lte7 div.error{color:#fff;}
/* beautify brand */
.navbar {margin-bottom:0}
.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;}
/*=============================================================
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 */
textarea { width:90%}
.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;}
/* web2py radio widget adjustment */
.generic-widget input[type='radio'] {margin:-1px 0 0 0; vertical-align: middle;}
.generic-widget input[type='radio'] + label {display:inline-block; margin:0 0 0 6px; vertical-align: middle;}
/*=============================================================
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;
}
/*=============================================================
OTHER RULES
==============================================================*/
/* 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;}
div.controls .error {
width: auto;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media only screen and (max-width:979px){
body{padding-top:0px;}
#navbar{/*top:5px;*/}
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;
}
input[type="text"],input[type="password"],select{
width:95%;
}
}
@media (max-width: 767px) {
.navbar {
margin-right: -20px;
margin-left: -20px;
}
}
@@ -0,0 +1,122 @@
/*=============================================================
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;
border-top: 4px solid #000000;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-left: 2px;
margin-top: 8px;
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;
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-left: 8px;
margin-top: 6px;
}
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.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
// 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 adjust_height_of_collapsed_nav() {
var cn = jQuery('div.collapse');
if (cn.get(0)) {
var cnh = cn.get(0).style.height;
if (cnh>'0px'){
cn.css('height','auto');
}
}
}
function hoverMenu(){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
adjust_height_of_collapsed_nav();
var mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
var 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');});
});
@@ -1,13 +1,14 @@
{{extend 'layout.html'}} {{extend 'layout.html'}}
<div> <iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&amp;background=ffffff&amp;slideshow=1&amp;video_title=1&amp;video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
{{=get_content('main')}}
<center> <div class="contentleft">
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&amp;background=ffffff&amp;slideshow=1&amp;video_title=1&amp;video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe> <div >
</center> {{=get_content('main')}}
{{=get_content('official')}} </div>
{{=get_content('community')}} {{=get_content('official')}}
{{=get_content('more')}} {{=get_content('community')}}
{{=get_content('more')}}
</div> </div>
@@ -5,59 +5,33 @@
<h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2> <h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2>
<center class="spaced"> <center style="padding:20px">
<table class="twothirds"> <table class="downloads">
<thead> <tr>
<tr> <th>For Normal Users</th>
<th>For Normal Users</th> <th>For Testers</th>
<th>For Testers</th> <th>For Developers</th>
<th>For Developers</th> </tr>
</tr> <tr>
</thead> <td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a></td>
<tbody> <td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a></td>
<tr> <td><a class="btn btn-180 btn-danger" href="http://github.com/web2py/web2py/">Git Repository</a></td>
<td> </tr>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a> <tr>
</td> <td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a></td>
<td> <td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a></td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a> <td></td>
</td> </tr>
<td> <tr>
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a> <td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a></td>
</td> <td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a></td>
</tr> <td><a class="btn btn-180 btn-danger" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a></td>
<tr> </tr>
<td> <tr>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a> <td><a class="btn btn-180 btn-success" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a></td>
</td> <td><a class="btn btn-180" href="https://github.com/web2py/web2py/releases">Change Log</a></td>
<td> <td><a class="btn btn-180" href="https://github.com/web2py/web2py/issues">Report a Bug</a></td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a> </tr>
</td>
<td></td>
</tr>
<tr>
<td>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded red" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a>
</td>
</tr>
<tr>
<td>
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/issues">Report a Bug</a>
</td>
</tr>
</tbody>
</table> </table>
</center> </center>
@@ -69,9 +43,9 @@
<h3>Instructions</h3> <h3>Instructions</h3>
<p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx). <p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx).
To run from source, type:</p> To run from source, type:</p>
{{=CODE("python2.7 web2py.py", language=None, counter='>', _class='boxCode')}} {{=CODE("python2.7 web2py.py",language=None,counter='>',_class='boxCode')}}
<p>or for more info type:</p> <p>or for more info type:</p>
{{=CODE("python2.7 web2py.py -h", language=None, counter='>', _class='boxCode')}} {{=CODE("python2.7 web2py.py -h",language=None,counter='>',_class='boxCode')}}
<h3>Caveats</h3> <h3>Caveats</h3>
@@ -84,7 +58,7 @@
<p>Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.</p> <p>Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.</p>
<p>It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.</p> <p>It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.</p>
<p>web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.</p> <p>web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.</p>
<a class="btn btn-small rounded" href="{{=URL('license')}}">read more</a> <a class="btn btn-small" href="{{=URL('license')}}">read more</a>
<h3>Artwork</h3> <h3>Artwork</h3>
<center> <center>
@@ -32,7 +32,7 @@ def hello1():
return "Hello World" return "Hello World"
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>If the controller function returns a string, that is the body of the rendered page.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello1">hello1</a></p> <p>If the controller function returns a string, that is the body of the rendered page.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello1">hello1</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -40,7 +40,7 @@ def hello2():
return T("Hello World") return T("Hello World")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello2">hello2</a></p> <p>The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello2">hello2</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -51,7 +51,7 @@ def hello3():
<b>and view: simple_examples/hello3.html</b> <b>and view: simple_examples/hello3.html</b>
{{=CODE(open(os.path.join(request.folder,'views/simple_examples/hello3.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/simple_examples/hello3.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>If you return a dictionary, the variables defined in the dictionery are visible to the view (template). <p>If you return a dictionary, the variables defined in the dictionery are visible to the view (template).
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello3.html">hello3</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello3.html">hello3</a></p>
<p>Actions can also be be rendered in other formsts like JSON, <a href="/{{=request.application}}/simple_examples/hello3.json">hello3.json</a>, and XML, <a href="/{{=request.application}}/simple_examples/hello3.xml">hello3.xml</a></p> <p>Actions can also be be rendered in other formsts like JSON, <a href="/{{=request.application}}/simple_examples/hello3.json">hello3.json</a>, and XML, <a href="/{{=request.application}}/simple_examples/hello3.xml">hello3.xml</a></p>
@@ -62,7 +62,7 @@ def hello4():
return dict(message=T("Hello World")) return dict(message=T("Hello World"))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can change the view, but the default is /[controller]/[function].html. If the default is not found web2py tries to render the page using the generic.html view. <p>You can change the view, but the default is /[controller]/[function].html. If the default is not found web2py tries to render the page using the generic.html view.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello4">hello4</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello4">hello4</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -76,7 +76,7 @@ def hello5():
<li>named arguments and name starts with '_'. These are mapped blindly into tag attributes and the '_' is removed. attributes without value like "READONLY" can be created with the argument "_readonly=ON".</li> <li>named arguments and name starts with '_'. These are mapped blindly into tag attributes and the '_' is removed. attributes without value like "READONLY" can be created with the argument "_readonly=ON".</li>
<li>named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later. <li>named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later.
</ul> </ul>
<p>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello5">hello5</a></p> <p>Try it here: <a href="/{{=request.application}}/simple_examples/hello5">hello5</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -86,7 +86,7 @@ def hello6():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>response.flash allows you to flash a message to the user when the page is returned. Use session.flash instead of response.flash to display a message after redirection. With default layout, you can click on the flash to make it disappear. <p>response.flash allows you to flash a message to the user when the page is returned. Use session.flash instead of response.flash to display a message after redirection. With default layout, you can click on the flash to make it disappear.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello6">hello6</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello6">hello6</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -94,6 +94,7 @@ def status():
return dict(toobar=response.toolbar()) return dict(toobar=response.toolbar())
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here we are showing the request, session and response objects using the generic.html template. <p>Here we are showing the request, session and response objects using the generic.html template.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/status">status</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -101,7 +102,7 @@ def redirectme():
redirect(URL('hello3')) redirect(URL('hello3'))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can do redirect. <p>You can do redirect.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/redirectme">redirectme</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/redirectme">redirectme</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -109,7 +110,7 @@ def raisehttp():
raise HTTP(400,"internal error") raise HTTP(400,"internal error")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can raise HTTP exceptions to return an error page. <p>You can raise HTTP exceptions to return an error page.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/raisehttp">raisehttp</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/raisehttp">raisehttp</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -127,7 +128,7 @@ def servejs():
return 'alert("This is a Javascript document, it is not supposed to run!");' return 'alert("This is a Javascript document, it is not supposed to run!");'
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can serve other than HTML pages by changing the contenttype via the response.headers. The gluon.contenttype module can help you figure the type of the file to be served. NOTICE: this is not necessary for static files unless you want to require authorization. <p>You can serve other than HTML pages by changing the contenttype via the response.headers. The gluon.contenttype module can help you figure the type of the file to be served. NOTICE: this is not necessary for static files unless you want to require authorization.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/servejs">servejs</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/servejs">servejs</a></p>
<h3 id="example_json">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3 id="example_json">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -135,7 +136,7 @@ def servejs():
return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}]) return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}])
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>If you are into Ajax, web2py includes gluon.contrib.<a href="http://cheeseshop.python.org/pypi/simplejson">simplejson</a>, developed by Bob Ippolito. This module provides a fast and easy way to serve asynchronous content to your Ajax page. gluon.simplesjson.dumps(...) can serialize most Python types into <a href="http://www.json.org">JSON</a>. gluon.contrib.simplejson.loads(...) performs the reverse operation. <p>If you are into Ajax, web2py includes gluon.contrib.<a href="http://cheeseshop.python.org/pypi/simplejson">simplejson</a>, developed by Bob Ippolito. This module provides a fast and easy way to serve asynchronous content to your Ajax page. gluon.simplesjson.dumps(...) can serialize most Python types into <a href="http://www.json.org">JSON</a>. gluon.contrib.simplejson.loads(...) performs the reverse operation.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/makejson">makejson</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/makejson">makejson</a></p>
<p>New in web2py 1.63: Any normal action returning a dict is automatically serialized in JSON if '.json' is appended to the URL.</p> <p>New in web2py 1.63: Any normal action returning a dict is automatically serialized in JSON if '.json' is appended to the URL.</p>
@@ -151,7 +152,7 @@ def makertf():
response.headers['Content-Type']='text/rtf' response.headers['Content-Type']='text/rtf'
return q.dumps(doc) return q.dumps(doc)
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>web2py also includes gluon.contrib.<a href="http://pyrtf.sourceforge.net/">pyrtf</a>, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/makertf">makertf</a></p> <p>web2py also includes gluon.contrib.<a href="http://pyrtf.sourceforge.net/">pyrtf</a>, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/makertf">makertf</a></p>
<h3 id="example_rss">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3 id="example_rss">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -178,7 +179,7 @@ def rss_aggregator():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>web2py includes gluon.contrib.<a href="http://www.dalkescientific.com/Python/PyRSS2Gen.html">rss2</a>, developed by Dalke Scientific Software, which generates RSS2 feeds, and <p>web2py includes gluon.contrib.<a href="http://www.dalkescientific.com/Python/PyRSS2Gen.html">rss2</a>, developed by Dalke Scientific Software, which generates RSS2 feeds, and
gluon.contrib.<a href="http://www.feedparser.org/">feedparser</a>, developed by Mark Pilgrim, which collects RSS and ATOM feeds. The above controller collects a slashdot feed and makes new one. gluon.contrib.<a href="http://www.feedparser.org/">feedparser</a>, developed by Mark Pilgrim, which collects RSS and ATOM feeds. The above controller collects a slashdot feed and makes new one.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/rss_aggregator">rss_aggregator</a></p> <br/>Try it here: <a href="/{{=request.application}}/simple_examples/rss_aggregator">rss_aggregator</a></p>
<h3 id="example_wiki">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3 id="example_wiki">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
@@ -193,7 +194,7 @@ def ajaxwiki_onclick():
return MARKMIN(request.vars.text).xml() return MARKMIN(request.vars.text).xml()
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The markmin wiki markup is described <a href="{{=URL('static','markmin.html')}}">here</a>. <p>The markmin wiki markup is described <a href="{{=URL('static','markmin.html')}}">here</a>.
web2py also includes gluon.contrib.<a href="http://code.google.com/p/python-markdown2/">markdown</a>.WIKI helper (markdown2) which converts WIKI markup to HTML following <a href="http://en.wikipedia.org/wiki/Markdown">this syntax</a>. In this example we added a fancy ajax effect.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/ajaxwiki">ajaxwiki</a></p> web2py also includes gluon.contrib.<a href="http://code.google.com/p/python-markdown2/">markdown</a>.WIKI helper (markdown2) which converts WIKI markup to HTML following <a href="http://en.wikipedia.org/wiki/Markdown">this syntax</a>. In this example we added a fancy ajax effect.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/ajaxwiki">ajaxwiki</a></p>
<h2 id="session_examples">Session Examples</h2> <h2 id="session_examples">Session Examples</h2>
@@ -206,7 +207,7 @@ def counter():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: session_examples/counter.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: session_examples/counter.html</b>
{{=CODE(open(os.path.join(request.folder,'views/session_examples/counter.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/session_examples/counter.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>Click to count. The session.counter is persistent for this user and application. Every applicaiton within the system has its own separate session management. <p>Click to count. The session.counter is persistent for this user and application. Every applicaiton within the system has its own separate session management.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/session_examples/counter">counter</a></p> <br/>Try it here: <a href="/{{=request.application}}/session_examples/counter">counter</a></p>
<h2 id="template_examples">Template Examples</h2> <h2 id="template_examples">Template Examples</h2>
@@ -218,7 +219,7 @@ def variables():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/variables.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/variables.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/variables.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/variables.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>A view (also known as template) is just an HTML file with &#123;&#123;...&#125;&#125; tags. You can put ANY python code into the tags, no need to indent but you must use pass to close blocks. The view is transformed into a python code and then executed. &#123;&#123;=a&#125;&#125; prints a.xml() or escape(str(a)). <p>A view (also known as template) is just an HTML file with &#123;&#123;...&#125;&#125; tags. You can put ANY python code into the tags, no need to indent but you must use pass to close blocks. The view is transformed into a python code and then executed. &#123;&#123;=a&#125;&#125; prints a.xml() or escape(str(a)).
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/variables">variables</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/variables">variables</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -227,7 +228,7 @@ def test_for():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_for.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_for.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_for.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_for.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can do for and while loops. <p>You can do for and while loops.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_for">test_for</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/test_for">test_for</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -236,7 +237,7 @@ def test_if():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_if.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_if.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_if.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_if.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can do if, elif, else. <p>You can do if, elif, else.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_if">test_if</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/test_if">test_if</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -245,7 +246,7 @@ def test_try():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_try.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_try.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_try.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_try.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can do try, except, finally. <p>You can do try, except, finally.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_try">test_try</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/test_try">test_try</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -254,7 +255,7 @@ def test_def():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_def.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_def.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_def.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/test_def.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can write functions in HTML too. <p>You can write functions in HTML too.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_def">test_def</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/test_def">test_def</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -263,7 +264,7 @@ def escape():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/escape.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/escape.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/escape.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/escape.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>The argument of &#123;&#123;=...&#125;&#125; is always escaped unless it is an object with a .xml() method such as link, A(...), a FORM(...), a XML(...) block, etc. <p>The argument of &#123;&#123;=...&#125;&#125; is always escaped unless it is an object with a .xml() method such as link, A(...), a FORM(...), a XML(...) block, etc.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/escape">escape</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/escape">escape</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -272,16 +273,16 @@ def xml():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/xml.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/xml.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/xml.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/xml.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>If you do not want to escape the argument of &#123;&#123;=...&#125;&#125; mark it as XML. <p>If you do not want to escape the argument of &#123;&#123;=...&#125;&#125; mark it as XML.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/xml">xml</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/xml">xml</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
def beautify(): def beautify():
dict(message=BEAUTIFY(dict(a=1,b=[2,3,dict(hello='world')]))) return dict(message=BEAUTIFY(request))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can use BEAUTIFY to turn lists and dictionaries into organized HTML. <p>You can use BEAUTIFY to turn lists and dictionaries into organized HTML.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/beautify">beautify</a></p> <br/>Try it here: <a href="/{{=request.application}}/template_examples/beautify">beautify</a></p>
<h2 id="layout_examples">Layout Examples</h2> <h2 id="layout_examples">Layout Examples</h2>
@@ -297,7 +298,7 @@ def civilized():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/civilized.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/civilized.html</b>
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/civilized.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/layout_examples/civilized.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can specify the layout file at the top of your view. civilized Layout file is a view that somewhere in the body contains &#123;&#123;include&#125;&#125;. <p>You can specify the layout file at the top of your view. civilized Layout file is a view that somewhere in the body contains &#123;&#123;include&#125;&#125;.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/civilized">civilized</a></p> <br/>Try it here: <a href="/{{=request.application}}/layout_examples/civilized">civilized</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -309,7 +310,7 @@ def slick():
return dict(message="you clicked on slick") return dict(message="you clicked on slick")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/slick.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/slick.html</b>
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/slick.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/layout_examples/slick.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>Same here, but using a different template.<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/slick">slick</a></p> <p>Same here, but using a different template.<br/>Try it here: <a href="/{{=request.application}}/layout_examples/slick">slick</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -322,7 +323,7 @@ def basic():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/basic.html</b> """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/basic.html</b>
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/basic.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/layout_examples/basic.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>'layout.html' is the default template, every application has a copy of it. <p>'layout.html' is the default template, every application has a copy of it.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/basic">basic</a></p> <br/>Try it here: <a href="/{{=request.application}}/layout_examples/basic">basic</a></p>
<h2 id="form_examples">Form Examples</h2> <h2 id="form_examples">Form Examples</h2>
@@ -346,7 +347,7 @@ def form():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can use HTML helpers like FORM, INPUT, TEXTAREA, OPTION, SELECT to build forms. The "value=" attribute sets the initial value of the field (works for TEXTAREA and OPTION/SELECT too) and the requires attribute sets the validators. <p>You can use HTML helpers like FORM, INPUT, TEXTAREA, OPTION, SELECT to build forms. The "value=" attribute sets the initial value of the field (works for TEXTAREA and OPTION/SELECT too) and the requires attribute sets the validators.
FORM.accepts(..) tries to validate the form and, on success, stores vars into form.vars. On failure the error messages are stored into form.errors and shown in the form. FORM.accepts(..) tries to validate the form and, on success, stores vars into form.vars. On failure the error messages are stored into form.errors and shown in the form.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/form_examples/form">form</a></p> <br/>Try it here: <a href="/{{=request.application}}/form_examples/form">form</a></p>
<h2 id="database_examples">Database Examples</h2> <h2 id="database_examples">Database Examples</h2>
@@ -496,7 +497,7 @@ def cache_in_ram():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The output of <tt>lambda:time.ctime()</tt> is cached in ram for 5 seconds. The string 'time' is used as cache key. <p>The output of <tt>lambda:time.ctime()</tt> is cached in ram for 5 seconds. The string 'time' is used as cache key.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_in_ram">cache_in_ram</a></p> <br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_in_ram">cache_in_ram</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
@@ -507,7 +508,7 @@ def cache_on_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) for 5 seconds. <p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) for 5 seconds.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_on_disk">cache_on_disk</a></p> <br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_on_disk">cache_on_disk</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -518,7 +519,7 @@ def cache_in_ram_and_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) and then in ram for 5 seconds. web2py looks in ram first and if not there it looks on disk. If it is not on disk it calls the function. This is useful in a multiprocess type of environment. The two times do not have to be the same. <p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) and then in ram for 5 seconds. web2py looks in ram first and if not there it looks on disk. If it is not on disk it calls the function. This is useful in a multiprocess type of environment. The two times do not have to be the same.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_in_ram_and_disk">cache_in_ram_and_disk</a></p> <br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_in_ram_and_disk">cache_in_ram_and_disk</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
@@ -529,7 +530,7 @@ def cache_in_ram_and_disk():
t=time.ctime() t=time.ctime()
return dict(time=t,link=A('click to reload',_href=URL(r=request)))""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} return dict(time=t,link=A('click to reload',_href=URL(r=request)))""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here the entire controller (dictionary) is cached in ram for 5 seconds. The result of a select cannot be cached unless it is first serialized into a table <tt>lambda:SQLTABLE(db().select(db.user.ALL)).xml()</tt>. You can read below for an even better way to do it. <p>Here the entire controller (dictionary) is cached in ram for 5 seconds. The result of a select cannot be cached unless it is first serialized into a table <tt>lambda:SQLTABLE(db().select(db.user.ALL)).xml()</tt>. You can read below for an even better way to do it.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_in_ram">cache_controller_in_ram</a></p> <br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_in_ram">cache_controller_in_ram</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -540,7 +541,7 @@ def cache_controller_on_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here the entire controller (dictionary) is cached on disk for 5 seconds. This will not work if the dictionary contains unpickleable objects. <p>Here the entire controller (dictionary) is cached on disk for 5 seconds. This will not work if the dictionary contains unpickleable objects.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_on_disk">cache_controller_on_disk</a></p> <br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_on_disk">cache_controller_on_disk</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -552,7 +553,7 @@ def cache_controller_and_view():
return response.render(d) return response.render(d)
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p><tt>response.render(d)</tt> renders the dictionary inside the controller, so everything is cached now for 5 seconds. This is best and fastest way of caching! <p><tt>response.render(d)</tt> renders the dictionary inside the controller, so everything is cached now for 5 seconds. This is best and fastest way of caching!
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_and_view">cache_controller_and_view</a></p> <br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_and_view">cache_controller_and_view</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -582,7 +583,7 @@ def data():
<b>In view: ajax_examples/index.html</b> <b>In view: ajax_examples/index.html</b>
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/index.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/ajax_examples/index.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>The javascript function "ajax" is provided in "web2py_ajax.html" and included by "layout.html". It takes three arguments, a url, a list of ids and a target id. When called, it sends to the url (via a get) the values of the ids and display the response in the value (of innerHTML) of the target id. <p>The javascript function "ajax" is provided in "web2py_ajax.html" and included by "layout.html". It takes three arguments, a url, a list of ids and a target id. When called, it sends to the url (via a get) the values of the ids and display the response in the value (of innerHTML) of the target id.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/index">index</a></p> <br/>Try it here: <a href="/{{=request.application}}/ajax_examples/index">index</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -590,7 +591,7 @@ def flash():
response.flash='this text should appear!' response.flash='this text should appear!'
return dict() return dict()
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/flash">flash</a></p> <p>Try it here: <a href="/{{=request.application}}/ajax_examples/flash">flash</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -599,7 +600,7 @@ def fade():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<b>In view: ajax_examples/fade.html </b><br/> <b>In view: ajax_examples/fade.html </b><br/>
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/fade.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}} {{=CODE(open(os.path.join(request.folder,'views/ajax_examples/fade.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/fade">fade</a></p> <p>Try it here: <a href="/{{=request.application}}/ajax_examples/fade">fade</a></p>
<h3>Excel-like spreadsheet via Ajax</h3> <h3>Excel-like spreadsheet via Ajax</h3>
Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms
+52 -38
View File
@@ -1,24 +1,37 @@
{{extend 'layout.html'}} {{extend 'layout.html'}}
{{
import random
quotes = [
("web2py was the life saver today for me, my blog post: Standalone Usage of web2py's", "caglartoklu", "http://twitter.com/#!/caglartoklu/status/84292131707031553"),
("Get Things Done - Faster, Better and More Easily with web2py",
"Bruno Rocha", "http://twitter.com/#!/rochacbruno/status/73583156044890112"),
("Please use www.web2py.com when using MVC , no PHP/SQL stuff please...its 2011 not 1999", "rabblesoft", "http://twitter.com/#!/rabblesoft/status/79189028431343616"),
('web2py rules! as a sysadmin I like the no installation and no configuration approach a lot)', "kjogut", "http://twitter.com/#!/jkogut/status/61414554273447936"),
("web2py it is. Compatible with everything under the sun and great interfaces to googleappengine", "comamitc","http://twitter.com/#!/comamitc/status/51744719071477760"),
("If you are still learning python, web2py is best tool by far", "pbreit", "http://twitter.com/#!/pbreit/status/48260905775017984")
]
random.shuffle(quotes)
}}
<div class="container"> <div class="row-fluid">
<div class="twothirds"> <div class="span12">
<div class="padded"> <div class="span8">
<h3><img src="{{=URL('static/images', 'web2py_logo.png')}}"> Web Framework</h3> <h3>web2py<sup>TM</sup> Web Framework</h3>
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p> <p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
<table width="100%"> <table width="100%">
<tr> <tr>
<td> <td>
<a class="noeffect" href="http://web2py.com/book"> <a href="http://web2py.com/book">
<img src="{{=URL('static','images/book-5th.png')}}" /> <img src="{{=URL('static','images/book-5th.png')}}" />
</a> </a>
</td> </td>
<td> <td>
<a class="noeffect" href="https://vimeo.com/album/3016728"> <a href="https://vimeo.com/album/3016728">
<img src="{{=URL('static','images/videos.png')}}" /> <img src="{{=URL('static','images/videos.png')}}" />
</a> </a>
</td> </td>
<td> <td>
<a class="noeffect" href="http://link.packtpub.com/SUlnrN"> <a href="http://link.packtpub.com/SUlnrN">
<img src="{{=URL('static','images/book-recipes.png')}}" /> <img src="{{=URL('static','images/book-recipes.png')}}" />
</a> </a>
</td> </td>
@@ -26,44 +39,45 @@
</table> </table>
<p>Current version: <a href="{{=URL('download')}}">{{=request.env.web2py_version}} (<a href="http://www.gnu.org/licenses/lgpl.html">LGPLv3 License</a>)</p> <p>Current version: <a href="{{=URL('download')}}">{{=request.env.web2py_version}} (<a href="http://www.gnu.org/licenses/lgpl.html">LGPLv3 License</a>)</p>
</div> </div>
</div> <div class="span4" style="text-align:center">
<div class="third"> <a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/>
<div class="padded center"> <a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/>
<a class="noeffect" href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"> <a class="btn btn-danger" href="https://www.pythonanywhere.com/try-web2py" style="margin-top:10px; width:180px; color:white">Try it now online</a><br/>
<img class="spaced-vertical" src="{{=URL('static','images/infoworld2012.jpeg')}}"> <a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powered by web2py</a><br/><br/>
</a> <a class="coinbase-button" data-code="df71ec5c2d5bc3b1c18139ab645f352b" data-button-style="donation_large" href="https://coinbase.com/checkouts/df71ec5c2d5bc3b1c18139ab645f352b">Donate Bitcoins</a><script src="https://coinbase.com/assets/button.js" type="text/javascript"></script>
<a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now
</a>
<a class="btn rounded red fill" href="{{=URL('examples')}}">
Quick Examples
</a>
<a class="btn rounded red fill" href="https://www.pythonanywhere.com/try-web2py">
Try it now online
</a>
<a class="btn rounded red fill" href="http://web2py.com/poweredby">
Sites Powered by web2py
</a>
</div> </div>
</div> </div>
</div> </div>
<div class="container"> <div class="row-fluid">
<div class="third"> <div class="span12">
<div class="padded"> <div class="span4">
<h5><a href="{{=URL('what')}}">Batteries Included</a></h5> <h3><a href="{{=URL('what')}}">Batteries Included</a></h3>
<p>Everything you need in one package including fast multi-threaded web server, SQL database and web-based interface. No third party dependencies but works with <a href={{=URL('what')}}>third party tools</a>.</p> <p>Everything you need in one package including fast multi-threaded web server, SQL database and web-based interface. No third party dependencies but works with <a href={{=URL('what')}}>third party tools</a>.</p>
</div> </div>
</div> <div class="span4">
<div class="third"> <h3><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h3>
<div class="padded">
<h5><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h5>
<p>Create, modify, deploy and manage application from anywhere using your browser. One web2py instance can run multiple web sites using different databases. Try the <a href="http://www.web2py.com/demo_admin">interactive demo</a>.</p> <p>Create, modify, deploy and manage application from anywhere using your browser. One web2py instance can run multiple web sites using different databases. Try the <a href="http://www.web2py.com/demo_admin">interactive demo</a>.</p>
</div> </div>
</div> <div class="span4">
<div class="third"> <h3><a href="{{=URL('documentation')}}">Extensive Docs</a></h3>
<div class="padded"> <p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://dev.s-cubism.com/web2py_plugins" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
<h5><a href="{{=URL('documentation')}}">Extensive Docs</a></h5>
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://www.web2pyslices.com/home?content_type=Package" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
</div> </div>
</div> </div>
</div> </div>
<div class="row-fluid">
<div class="span12">
<img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png">
</div>
</div>
<div class="row-fluid">
<div class="span12">
{{for k,quote in enumerate(quotes[:3]):}}
<div class="span4">
<p style="text-align: left"><em>{{=quote[0]}}</em></p>
<span class="right">
<a href="{{=quote[2]}}">{{=quote[1]}}</a>
</span>
</div>
{{pass}}
</div>
</div>
@@ -17,11 +17,14 @@
<ul> <ul>
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li> <li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li>
<li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li> <li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li>
<li><a target="_blank" href="http://www.10biosystems.com">10BioSystems</a> (USA)</li>
<li><a target="_blank" href="http://www.formatics.nl">Formatics</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li> <li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li> <li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li> <li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li>
<li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li> <li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li>
<li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li> <li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li>
<li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li> <li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
<li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li> <li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
<li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li> <li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li>
@@ -29,14 +32,15 @@
<li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li> <li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li> <li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li>
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li> <li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
<li><a target="_blank" href="http://www.vsa-services.com/">VSA Services</a> (Singapore)</li>
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li> <li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
<li><a target="_blank" href="www.corebyte.nl">Corebyte</a> (Netherland)</li>
<li><a target="_blank" href="https://loadinfo-net.appspot.com">LoadInfo</a> (Bulgaria)</li>
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li> <li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li> <li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
<li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li> <li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li> <li><a target="_blank" href="http://10Biosystems.com">10BioSystems</a></li>
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li> <li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.garciac.es"> Garciac</a> (Spain)</li>
<li><a target="_blank" href="http://memoriapersistente.pt "> Memoria persistente</a> (Portugal)</li>
</ul> </ul>
</div> </div>
@@ -5,7 +5,6 @@
{{block right_sidebar}} {{block right_sidebar}}
<center> <center>
<!--
<h3 class="feature-title">SITES POWERED BY WEB2PY</h3> <h3 class="feature-title">SITES POWERED BY WEB2PY</h3>
<a href="http://web2py.com/poweredby"><img class="frame" id="img1" width="200px"/></a> <a href="http://web2py.com/poweredby"><img class="frame" id="img1" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img2" width="200px"/></a> <a href="http://web2py.com/poweredby"><img class="frame" id="img2" width="200px"/></a>
@@ -15,7 +14,7 @@
<a href="http://web2py.com/poweredby"><img class="frame" id="img6" width="200px"/></a> <a href="http://web2py.com/poweredby"><img class="frame" id="img6" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img7" width="200px"/></a> <a href="http://web2py.com/poweredby"><img class="frame" id="img7" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a> <a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a>
--> </div>
</center> </center>
<script> <script>
function showimages() { function showimages() {
@@ -82,7 +82,6 @@
</li><li>Keith Yang (openid) </li><li>Keith Yang (openid)
</li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins) </li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins)
</li><li>Kyle Smith (javascript) </li><li>Kyle Smith (javascript)
</li><li><a href="https://github.com/leonelcamara">Leonel Câmara</a>
</li><li><a href="http://blog.donews.com/limodou/">Limodou</a> (winservice) </li><li><a href="http://blog.donews.com/limodou/">Limodou</a> (winservice)
</li><li><a href="https://github.com/lucasdavila">Lucas D'Ávila</a> </li><li><a href="https://github.com/lucasdavila">Lucas D'Ávila</a>
</li><li>Marc Abramowitz (tests and travis continuous integration) </li><li>Marc Abramowitz (tests and travis continuous integration)
@@ -100,7 +99,6 @@
</li><li>Michael Willis (shell) </li><li>Michael Willis (shell)
</li><li>Michele Comitini (facebook) </li><li>Michele Comitini (facebook)
</li><li>Michael Toomim (scheduler) </li><li>Michael Toomim (scheduler)
</li><li>Narendra Bhati (security)
</li><li>Nathan Freeze (admin design, IS_STRONG, DAL features, <a href="http://web2pyslices.com">web2pyslices.com</a>) </li><li>Nathan Freeze (admin design, IS_STRONG, DAL features, <a href="http://web2pyslices.com">web2pyslices.com</a>)
</li><li>Niall Sweeny (MSSQL support) </li><li>Niall Sweeny (MSSQL support)
</li><li>Niccolo Polo (epydoc) </li><li>Niccolo Polo (epydoc)
+165 -59
View File
@@ -1,67 +1,173 @@
<!--[if HTML5]><![endif]-->
<!DOCTYPE html> <!DOCTYPE html>
<html> <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<head> <!--[if lt IE 7]><html class="ie ie6 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<meta charset="utf-8"> <!--[if IE 7]><html class="ie ie7 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <!--[if IE 8]><html class="ie ie8 ie-lte9 ie-lte8 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!--[if IE 9]><html class="ie9 ie-lte9 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<meta name="apple-mobile-web-app-capable" content="yes" /> <!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js" lang="{{=T.accepted_language or 'en'}}"> <!--<![endif]-->
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/> <head>
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/> <title>{{=response.title or request.application}}</title>
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/> <!--[if !HTML5]>
<link href="{{=URL('static','css/examples.css')}}" rel="stylesheet" type="text/css"/> <meta http-equiv="X-UA-Compatible" content="IE=edge{{=not request.is_local and ',chrome=1' or ''}}">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <![endif]-->
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon"> <!-- www.phpied.com/conditional-comments-block-downloads/ -->
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}"> <!-- Always force latest IE rendering engine
{{ (even in intranet) & Chrome Frame
left_sidebar_enabled = globals().get('left_sidebar_enabled', False) Remove this if you use the .htaccess -->
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[ <meta charset="utf-8" />
<!-- http://dev.w3.org/html5/markup/meta.name.html -->
<meta name="application-name" content="{{=request.application}}" />
<!-- Speaking of Google, don't forget to set your site up:
http://google.com/webmasters -->
<meta name="google-site-verification" content="" />
<!-- Mobile Viewport Fix
j.mp/mobileviewport & davidbcalhoun.com/2010/viewport-metatag
device-width: Occupy full width of the screen in its current orientation
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
user-scalable = yes allows the user to zoom in -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables
HTML5 elements & feature detects -->
<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_bootstrap.css'))
response.files.append(URL('static','css/examples.css'))
}}
{{include 'web2py_ajax.html'}}
{{
# using sidebars need to know what sidebar you want to use
left_sidebar_enabled = globals().get('left_sidebar_enabled',False)
right_sidebar_enabled = globals().get('right_sidebar_enabled',False)
middle_columns = {0:'span12',1:'span9',2:'span6'}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)] (left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}} }}
{{include "web2py_ajax.html"}}
</head> <!-- uncomment here to load jquery-ui
<body class="black"> <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" type="text/css" media="all" />
<header class="black padded"> <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
<div class="container middle max900"> uncomment to load jquery-ui //-->
<div class="fill middle"> <noscript><link href="{{=URL('static', 'css/web2py_bootstrap_nojs.css')}}" rel="stylesheet" type="text/css" /></noscript>
<label class="ham" for="menu"><i class="fa fa-bars padded"></i></label> {{block head}}{{end}}
<div class="burger accordion"> </head>
<input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}} <body>
</div> <!-- Navbar ================================================== -->
<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 -->
<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>
</button>
{{=response.logo or ''}}
<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}}
{{if response.menu:}}
{{=MENU(response.menu, _class='mobile-menu nav' if is_mobile else 'nav',mobile=is_mobile,li_class='dropdown',ul_class='dropdown-menu')}}
{{pass}}
</div><!--/.nav-collapse -->
</div>
</div>
</div><!--/top navbar -->
<div class="container">
<!-- Masthead ================================================== -->
<header class="mastheader" id="header">
<div class="span4">
<div class="page-header">
<img src="{{=URL('static','images/web2py_logo.png')}}" class="logo" alt="web2py logo" />
</div> </div>
</div> </div>
</header> </header>
{{if response.flash:}} </div>
<div class="w2p_flash"> <div class="container">
{{=response.flash}}
</div> <section id="main" class="main row">
{{pass}}
<main class="white">
<div class="container max900">
{{if left_sidebar_enabled:}} {{if left_sidebar_enabled:}}
<div class="quarter padded">{{block left_sidebar}}{{end}}</div> <div class="span3 left-sidebar">
{{pass}} {{block left_sidebar}}
<div class="{{=middle_column}} padded">{{include}}</div> <h3>Left Sidebar</h3>
{{if right_sidebar_enabled:}} <p></p>
<div class="quarter padded">{{block right_sidebar}}{{end}}</div> {{end}}
{{pass}}
</div>
<div class="silver center padded">
<a class="fa fa-twitter" href="https://twitter.com/web2py/"></a>
<a class="fa fa-facebook" href="https://www.facebook.com/web2py/"></a>
</div>
</main>
<footer class="black">
<div class="container padded max900">
<div class="fill">
Copyright @ 2016 - Powered by Web2py
</div> </div>
</div> {{pass}}
</footer>
</body> <div class="{{=middle_columns}}">
<script> {{block center}}
// prevent android horizontal scrolling {{include}}
window.addEventListener("scroll", function(){window.scroll(0, window.pageYOffset);}, false); {{end}}
</script> </div>
{{if right_sidebar_enabled:}}
<div class="span3">
{{block right_sidebar}}
<h3>Right Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
</section><!--/main-->
<!-- Footer ================================================== -->
<div class="row">
<footer class="footer span12" id="footer">
<div class="footer-content">
{{block footer}} <!-- this is default footer -->
<div id="poweredBy" class="pull-right">
{{=T('Copyright')}} &#169; {{=request.now.year}} -
{{=T('Powered by')}}
<a href="http://www.web2py.com/">web2py</a> -
{{=T('Hosted by')}}
<a href="http://pythonanywhere.com">PythonAnywhere</a>
</div>
{{end}}
</div>
</footer>
</div>
</div> <!-- /container -->
<!-- The javascript =============================================
(Placed at the end of the document so the pages load faster) -->
<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>
<![endif]-->
{{if response.google_analytics_id:}}
<script src="{{=URL('static','js/analytics.min.js')}}"></script>
<script type="text/javascript">
analytics.initialize({
'Google Analytics':{trackingId:'{{=response.google_analytics_id}}'}
});</script>
{{pass}}
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script>
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank">
<img src="{{=URL('static','images/questions.png')}}" />
</a>
</body>
</html> </html>
@@ -0,0 +1,3 @@
{{extend 'layout.html'}}
{{=toolbar}}
+6 -7
View File
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations # this file is released under public domain and you can use without limitations
# ------------------------------------------------------------------------- #########################################################################
# This is a sample controller ## This is a sample controller
# - index is the default action of any application ## - index is the default action of any application
# - user is required for authentication and authorization ## - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming) ## - download is for downloading files uploaded in the db (does streaming)
# ------------------------------------------------------------------------- #########################################################################
def index(): def index():
""" """
+480 -491
View File
@@ -1,491 +1,480 @@
# -*- coding: utf-8 -*- # coding: utf8
{ {
'!langcode!': 'cs-cz', '!langcode!': 'cs-cz',
'!langname!': 'čeština', '!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.', '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!', '"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'%%{Row} in Table': '%%{řádek} v tabulce', '%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}', '%%{Row} selected': 'označených %%{řádek}',
'%s %%{row} deleted': '%s smazaných %%{záznam}', '%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}', '%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených', '%s selected': '%s označených',
'%Y-%m-%d': '%d.%m.%Y', '%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)', '(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(vyžaduje internetové připojení, experimentální)', '(requires internet access, experimental)': '(requires internet access, experimental)',
'(something like "it-it")': '(například "cs-cz")', '(something like "it-it")': '(například "cs-cs")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '@markmin\x01(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)', '@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01An error occured, please [[reload %s]] the page': '@markmin\x01Došlo k chybě, prosím [[obnovte stránku %s]]', '@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}',
'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Hledání: **%s** %%{soubor}', 'About': 'O programu',
'About': 'O programu', 'About application': 'O aplikaci',
'About application': 'O aplikaci', 'Access Control': 'Řízení přístupu',
'Access Control': 'Řízení přístupu', 'Add breakpoint': 'Přidat bod přerušení',
'Add breakpoint': 'Přidat bod přerušení', 'Additional code for your application': 'Další kód pro Vaši aplikaci',
'Additional code for your application': 'Další kód pro Vaši aplikaci', 'Admin design page': 'Admin design page',
'admin': 'admin', 'Admin language': 'jazyk rozhraní',
'Admin design page': 'Admin design page', 'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Admin language': 'jazyk rozhraní', 'Administrative Interface': 'Administrátorské rozhraní',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem', 'administrative interface': 'rozhraní pro správu',
'Administrative Interface': 'Administrátorské rozhraní', 'Administrator Password:': 'Administrátorské heslo:',
'administrative interface': 'rozhraní pro správu', 'Ajax Recipes': 'Recepty s ajaxem',
'Administrator Password:': 'Administrátorské heslo:', 'An error occured, please %s the page': 'An error occured, please %s the page',
'Ajax Recipes': 'Recepty s ajaxem', 'and rename it:': 'a přejmenovat na:',
'An error occured, please %s the page': 'Došlo k chybě, prosím %s stránku', 'appadmin': 'appadmin',
'and rename it:': 'a přejmenovat na:', 'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'appadmin': 'appadmin', 'Application': 'Application',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení', 'application "%s" uninstalled': 'application "%s" odinstalována',
'Application': 'Aplikace', 'application compiled': 'aplikace zkompilována',
'application "%s" uninstalled': 'application "%s" odinstalována', 'Application name:': 'Název aplikace:',
'application compiled': 'aplikace zkompilována', 'are not used': 'nepoužita',
'Application name:': 'Název aplikace:', 'are not used yet': 'ještě nepoužita',
'are not used': 'nepoužita', 'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'are not used yet': 'ještě nepoužita', 'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?', 'arguments': 'arguments',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?', 'at char %s': 'at char %s',
'arguments': 'argumenty', 'at line %s': 'at line %s',
'at char %s': 'na pozici znaku %s', 'ATTENTION:': 'ATTENTION:',
'at line %s': 'na řádku %s', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'ATTENTION:': 'POZOR:', 'Available Databases and Tables': 'Dostupné databáze a tabulky',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.', 'back': 'zpět',
'Available Databases and Tables': 'Dostupné databáze a tabulky', 'Back to wizard': 'Back to wizard',
'back': 'zpět', 'Basics': 'Basics',
'Back to wizard': 'Zpátky do průvodce', 'Begin': 'Začít',
'Basics': 'Základy', 'breakpoint': 'bod přerušení',
'Begin': 'Začít', 'Breakpoints': 'Body přerušení',
'breakpoint': 'bod přerušení', 'breakpoints': 'body přerušení',
'Breakpoints': 'Body přerušení', 'Buy this book': 'Koupit web2py knihu',
'breakpoints': 'body přerušení', 'Cache': 'Cache',
'Buy this book': 'Koupit Web2py knihu', 'cache': 'cache',
"Buy web2py's book": 'Koupit Web2py knihu', 'Cache Keys': 'Klíče cache',
'Cache': 'Cache', 'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'cache': 'cache', 'can be a git repo': 'může to být git repo',
'Cache Keys': 'Klíče cache', 'Cancel': 'Storno',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny', 'Cannot be empty': 'Nemůže být prázdné',
'can be a git repo': 'může to být git repo', 'Change Admin Password': 'Změnit heslo pro správu',
'Cancel': 'Storno', 'Change admin password': 'Změnit heslo pro správu aplikací',
'Cannot be empty': 'Nemůže být prázdné', 'Change password': 'Změna hesla',
'Change Admin Password': 'Změnit heslo pro správu', 'check all': 'vše označit',
'Change admin password': 'Změnit heslo pro správu aplikací', 'Check for upgrades': 'Zkusit aktualizovat',
'Change password': 'Změna hesla', 'Check to delete': 'Označit ke smazání',
'check all': 'vše označit', 'Check to delete:': 'Označit ke smazání:',
'Check for upgrades': 'Zkusit aktualizovat', 'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Check to delete': 'Označit ke smazání', 'Clean': 'Pročistit',
'Check to delete:': 'Označit ke smazání:', 'Clear CACHE?': 'Vymazat CACHE?',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...', 'Clear DISK': 'Vymazat DISK',
'Clean': 'Pročistit', 'Clear RAM': 'Vymazat RAM',
'Clear CACHE?': 'Vymazat CACHE?', 'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Clear DISK': 'Vymazat DISK', 'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Clear RAM': 'Vymazat RAM', 'Client IP': 'IP adresa klienta',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek', 'code': 'code',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...', 'Code listing': 'Code listing',
'Client IP': 'IP adresa klienta', 'collapse/expand all': 'vše sbalit/rozbalit',
'code': 'kód', 'Community': 'Komunita',
'Code listing': 'Výpis kódu', 'Compile': 'Zkompilovat',
'collapse/expand all': 'vše sbalit/rozbalit', 'compiled application removed': 'zkompilovaná aplikace smazána',
'Community': 'Komunita', 'Components and Plugins': 'Komponenty a zásuvné moduly',
'Compile': 'Zkompilovat', 'Condition': 'Podmínka',
'compiled application removed': 'zkompilovaná aplikace smazána', 'continue': 'continue',
'Components and Plugins': 'Komponenty a zásuvné moduly', 'Controller': 'Kontrolér (Controller)',
'Condition': 'Podmínka', 'Controllers': 'Kontroléry',
'Config.ini': 'Config.ini', 'controllers': 'kontroléry',
'continue': 'pokračovat', 'Copyright': 'Copyright',
'Controller': 'Kontrolér (Controller)', 'Count': 'Počet',
'Controllers': 'Kontroléry', 'Create': 'Vytvořit',
'controllers': 'kontroléry', 'create file with filename:': 'vytvořit soubor s názvem:',
'Copyright': 'Copyright', 'created by': 'vytvořil',
'Count': 'Počet', 'Created By': 'Vytvořeno - kým',
'Create': 'Vytvořit', 'Created On': 'Vytvořeno - kdy',
'create file with filename:': 'vytvořit soubor s názvem:', 'crontab': 'crontab',
'created by': 'vytvořil', 'Current request': 'Aktuální požadavek',
'Created By': 'Vytvořeno - kým', 'Current response': 'Aktuální odpověď',
'Created On': 'Vytvořeno - kdy', 'Current session': 'Aktuální relace',
'crontab': 'crontab', 'currently running': 'právě běží',
'Current request': 'Aktuální požadavek', 'currently saved or': 'uloženo nebo',
'Current response': 'Aktuální odpověď', 'customize me!': 'upravte mě!',
'Current session': 'Aktuální relace', 'data uploaded': 'data nahrána',
'currently running': 'právě běží', 'Database': 'Rozhraní databáze',
'currently saved or': 'uloženo nebo', 'Database %s select': 'databáze %s výběr',
'customize me!': 'upravte mě!', 'Database administration': 'Database administration',
'data uploaded': 'data nahrána', 'database administration': 'správa databáze',
'Database': 'Rozhraní databáze', 'Date and Time': 'Datum a čas',
'Database %s select': 'databáze %s výběr', 'day': 'den',
'Database administration': 'Administrace databáze', 'db': 'db',
'database administration': 'správa databáze', 'DB Model': 'Databázový model',
'Date and Time': 'Datum a čas', 'Debug': 'Ladění',
'day': 'den', 'defines tables': 'defines tables',
'db': 'db', 'Delete': 'Smazat',
'DB Model': 'Databázový model', 'delete': 'smazat',
'Debug': 'Ladění', 'delete all checked': 'smazat vše označené',
'defines tables': 'definuje tabulky', 'delete plugin': 'delete plugin',
'Delete': 'Smazat', 'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'delete': 'smazat', 'Delete:': 'Smazat:',
'delete all checked': 'smazat vše označené', 'deleted after first hit': 'smazat po prvním dosažení',
'delete plugin': 'zrušit plugin', 'Demo': 'Demo',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)', 'Deploy': 'Nahrát',
'Delete:': 'Smazat:', 'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'deleted after first hit': 'smazat po prvním dosažení', 'Deploy to OpenShift': 'Nahrát na OpenShift',
'Demo': 'Demo', 'Deployment Recipes': 'Postupy pro deployment',
'Deploy': 'Nahrát', 'Description': 'Popis',
'Deploy on Google App Engine': 'Nahrát na Google App Engine', 'design': 'návrh',
'Deploy to OpenShift': 'Nahrát na OpenShift', 'Detailed traceback description': 'Podrobný výpis prostředí',
'Deployment Recipes': 'Postupy pro deployment', 'details': 'podrobnosti',
'Description': 'Popis', 'direction: ltr': 'směr: ltr',
'design': 'návrh', 'Disable': 'Zablokovat',
'Design': 'Design', 'DISK': 'DISK',
'Detailed traceback description': 'Podrobný výpis prostředí', 'Disk Cache Keys': 'Klíče diskové cache',
'details': 'podrobnosti', 'Disk Cleared': 'Disk smazán',
'direction: ltr': 'směr: ltr', 'docs': 'dokumentace',
'Disable': 'Zablokovat', 'Documentation': 'Dokumentace',
'DISK': 'DISK', "Don't know what to do?": 'Nevíte kudy kam?',
'Disk Cache Keys': 'Klíče diskové cache', 'done!': 'hotovo!',
'Disk Cleared': 'Disk smazán', 'Download': 'Stáhnout',
'docs': 'dokumentace', 'download layouts': 'stáhnout moduly rozvržení stránky',
'Documentation': 'Dokumentace', 'download plugins': 'stáhnout zásuvné moduly',
"Don't know what to do?": 'Kde najdu další informace ?', 'E-mail': 'E-mail',
'done!': 'hotovo!', 'Edit': 'Upravit',
'Download': 'Stáhnout', 'edit all': 'edit all',
'download layouts': 'stáhnout moduly rozvržení stránky', 'Edit application': 'Správa aplikace',
'download plugins': 'stáhnout zásuvné moduly', 'edit controller': 'edit controller',
'E-mail': 'E-mail', 'Edit current record': 'Upravit aktuální záznam',
'Edit': 'Upravit', 'Edit Profile': 'Upravit profil',
'edit all': 'editovat vše', 'edit views:': 'upravit pohled:',
'Edit application': 'Správa aplikace', 'Editing file "%s"': 'Úprava souboru "%s"',
'edit controller': 'editovat controller', 'Editing Language file': 'Úprava jazykového souboru',
'Edit current record': 'Upravit aktuální záznam', 'Editing Plural Forms File': 'Editing Plural Forms File',
'Edit Profile': 'Upravit profil', 'Email and SMS': 'Email a SMS',
'edit views:': 'upravit pohled:', 'Enable': 'Odblokovat',
'Editing file "%s"': 'Úprava souboru "%s"', 'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'Editing Language file': 'Úprava jazykového souboru', 'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Editing Plural Forms File': 'Editování souboru množných čísel', 'Error': 'Chyba',
'Email and SMS': 'Email a SMS', 'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Enable': 'Odblokovat', 'Error snapshot': 'Snapshot chyby',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g', 'Error ticket': 'Ticket chyby',
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g', 'Errors': 'Chyby',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g', 'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Error': 'Chyba', 'Exception %s': 'Exception %s',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"', 'Exception instance attributes': 'Prvky instance výjimky',
'Error snapshot': 'Snapshot chyby', 'Expand Abbreviation': 'Expand Abbreviation',
'Error ticket': 'Ticket chyby', 'export as csv file': 'exportovat do .csv souboru',
'Errors': 'Chyby', 'exposes': 'vystavuje',
'Exception %(extype)s: %(exvalue)s': 'Výjimka %(extype)s: %(exvalue)s', 'exposes:': 'vystavuje funkce:',
'Exception %s': 'Výjimka %s', 'extends': 'rozšiřuje',
'Exception instance attributes': 'Prvky instance výjimky', 'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'Expand Abbreviation': 'Expandovat zkratku', 'FAQ': 'Často kladené dotazy',
'export as csv file': 'exportovat do .csv souboru', 'File': 'Soubor',
'exposes': 'vystavuje', 'file': 'soubor',
'exposes:': 'vystavuje funkce:', 'file "%(filename)s" created': 'file "%(filename)s" created',
'extends': 'rozšiřuje', 'file saved on %(time)s': 'soubor uložen %(time)s',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:', 'file saved on %s': 'soubor uložen %s',
'FAQ': 'Často kladené dotazy', 'Filename': 'Název souboru',
'File': 'Soubor', 'filter': 'filtr',
'file': 'soubor', 'Find Next': 'Najít další',
'file "%(filename)s" created': 'soubor "%(filename)s" byl vytvořen', 'Find Previous': 'Najít předchozí',
'file saved on %(time)s': 'soubor uložen %(time)s', 'First name': 'Křestní jméno',
'file saved on %s': 'soubor uložen %s', 'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'Filename': 'Název souboru', 'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'filter': 'filtr', 'Forms and Validators': 'Formuláře a validátory',
'Find Next': 'Najít další', 'Frames': 'Frames',
'Find Previous': 'Najít předchozí', 'Free Applications': 'Aplikace zdarma',
'First name': 'Křestní jméno', 'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?', 'Generate': 'Vytvořit',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?', 'Get from URL:': 'Stáhnout z internetu:',
'Forms and Validators': 'Formuláře a validátory', 'Git Pull': 'Git Pull',
'Frames': 'Framy', 'Git Push': 'Git Push',
'Free Applications': 'Aplikace zdarma', 'Globals##debug': 'Globální proměnné',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.', 'go!': 'OK!',
'Generate': 'Vytvořit', 'Goto': 'Goto',
'Get from URL:': 'Stáhnout z internetu:', 'graph model': 'graph model',
'Git Pull': 'Git Pull', 'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Git Push': 'Git Push', 'Group ID': 'ID skupiny',
'Globals##debug': 'Globální proměnné', 'Groups': 'Skupiny',
'go!': 'OK!', 'Hello World': 'Ahoj světe',
'Goto': 'Přejít na', 'Help': 'Nápověda',
'graph model': 'grafický model', 'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena', 'Hits': 'Kolikrát dosaženo',
'Group ID': 'ID skupiny', 'Home': 'Domovská stránka',
'Groups': 'Skupiny', 'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'Hello World': 'Ahoj všichni', 'How did you get here?': 'Jak jste se sem vlastně dostal?',
'Help': 'Nápověda', 'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'Helping web2py': 'Podpořte Web2py', '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.',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty', 'import': 'import',
'Hits': 'Kolikrát dosaženo', 'Import/Export': 'Import/Export',
'Home': 'Domovská stránka', 'includes': 'zahrnuje',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně', 'Index': 'Index',
'How did you get here?': 'Jak se Ti tato stránka vlastně zobrazila?', 'insert new': 'vložit nový záznam ',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download', 'insert new %s': 'vložit nový záznam %s',
'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.\r\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.', 'inspect attributes': 'inspect attributes',
'import': 'import', 'Install': 'Instalovat',
'Import/Export': 'Import/Export', 'Installed applications': 'Nainstalované aplikace',
'includes': 'zahrnuje', 'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Index': 'Index', 'Interactive console': 'Interaktivní příkazová řádka',
'insert new': 'vložit nový záznam ', 'Internal State': 'Vnitřní stav',
'insert new %s': 'vložit nový záznam %s', 'Introduction': 'Úvod',
'inspect attributes': 'prohlédnout atributy', 'Invalid email': 'Neplatný email',
'Install': 'Instalovat', 'Invalid password': 'Nesprávné heslo',
'Installed applications': 'Nainstalované aplikace', 'invalid password.': 'neplatné heslo',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s', 'Invalid Query': 'Neplatný dotaz',
'Interactive console': 'Interaktivní příkazová řádka', 'invalid request': 'Neplatný požadavek',
'Internal State': 'Vnitřní stav', 'Is Active': 'Je aktivní',
'Introduction': 'Úvod', 'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Invalid email': 'Neplatný email', 'Key': 'Klíč',
'Invalid password': 'Nesprávné heslo', 'Key bindings': 'Vazby klíčů',
'invalid password.': 'neplatné heslo', 'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'Invalid Query': 'Neplatný dotaz', 'languages': 'jazyky',
'invalid request': 'Neplatný požadavek', 'Languages': 'Jazyky',
'Is Active': 'Je aktiv', 'Last name': 'Příjme',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.', 'Last saved on:': 'Naposledy uloženo:',
'Key': 'Klíč', 'Layout': 'Rozvržení stránky (layout)',
'Key bindings': 'Vazby klíčů', 'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Key bindings for ZenCoding Plugin': 'Key bindings pro ZenCoding Plugin', 'Layouts': 'Rozvržení stránek',
'languages': 'jazyky', 'License for': 'Licence pro',
'Languages': 'Jazyky', 'Line number': 'Číslo řádku',
'Last name': 'Příjmení', 'LineNo': 'Č.řádku',
'Last saved on:': 'Naposledy uloženo:', 'Live Chat': 'Online pokec',
'Layout': 'Rozvržení stránky (layout)', 'loading...': 'nahrávám...',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)', 'locals': 'locals',
'Layouts': 'Rozvržení stránek', 'Locals##debug': 'Lokální proměnné',
'License for': 'Licence pro', 'Logged in': 'Přihlášení proběhlo úspěšně',
'Line number': 'Číslo řádku', 'Logged out': 'Odhlášení proběhlo úspěšně',
'LineNo': 'Č.řádku', 'Login': 'Přihlásit se',
'Live Chat': 'Online chat', 'login': 'přihlásit se',
'loading...': 'nahrávám...', 'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'locals': 'locals', 'logout': 'odhlásit se',
'Locals##debug': 'Lokální proměnné', 'Logout': 'Odhlásit se',
'Log In': 'Přihlásit se', 'Lost Password': 'Zapomněl jste heslo',
'Logged in': 'Přihlášení proběhlo úspěšně', 'Lost password?': 'Zapomněl jste heslo?',
'Logged out': 'Odhlášení proběhlo úspěšně', 'lost password?': 'zapomněl jste heslo?',
'Login': 'Přihlásit se', 'Manage': 'Manage',
'login': 'přihlásit se', 'Manage Cache': 'Manage Cache',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací', 'Menu Model': 'Model rozbalovací nabídky',
'logout': 'odhlásit se', 'Models': 'Modely',
'Logout': 'Odhlásit se', 'models': 'modely',
'Lost Password': 'Zapomněl jste heslo', 'Modified By': 'Změněno - kým',
'Lost password?': 'Zapomněl jste heslo?', 'Modified On': 'Změněno - kdy',
'lost password?': 'zapomněl jste heslo?', 'Modules': 'Moduly',
'Manage': 'Spravovat', 'modules': 'moduly',
'Manage Cache': 'Spravovat cache', 'My Sites': 'Správa aplikací',
'Menu Model': 'Model rozbalovací nabídky', 'Name': 'Jméno',
'Models': 'Modely', 'new application "%s" created': 'nová aplikace "%s" vytvořena',
'models': 'modely', 'New Application Wizard': 'Nový průvodce aplikací',
'Modified By': 'Změněno - kým', 'New application wizard': 'Nový průvodce aplikací',
'Modified On': 'Změněno - kdy', 'New password': 'Nové heslo',
'Modules': 'Moduly', 'New Record': 'Nový záznam',
'modules': 'moduly', 'new record inserted': 'nový záznam byl založen',
'My Sites': 'Správa aplikací', 'New simple application': 'Vytvořit primitivní aplikaci',
'Name': 'Jméno', 'next': 'next',
'new application "%s" created': 'nová aplikace "%s" vytvořena', 'next 100 rows': 'dalších 100 řádků',
'New application wizard': 'Nový průvodce aplikací', 'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'New Application Wizard': 'Nový průvodce aplikací', 'No Interaction yet': 'Ještě žádná interakce nenastala',
'New password': 'Nové heslo', 'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'New Record': 'Nový záznam', 'Object or table name': 'Objekt či tabulka',
'new record inserted': 'nový záznam byl založen', 'Old password': 'Původní heslo',
'New simple application': 'Vytvořit novou aplikaci', 'online designer': 'online návrhář',
'next': 'další', 'Online examples': 'Příklady online',
'next 100 rows': 'dalších 100 řádků', 'Open new app in new window': 'Open new app in new window',
'No databases in this application': 'V této aplikaci nejsou žádné databáze', 'or alternatively': 'or alternatively',
'No Interaction yet': 'Ještě žádná interakce nenastala', 'Or Get from URL:': 'Or Get from URL:',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen', 'or import from csv file': 'nebo importovat z .csv souboru',
'Object or table name': 'Objekt či tabulka', 'Origin': 'Původ',
'Old password': 'Původní heslo', 'Original/Translation': 'Originál/Překlad',
'Online book': 'Online kniha', 'Other Plugins': 'Ostatní moduly',
'online designer': 'online návrhář', 'Other Recipes': 'Ostatní zásuvné moduly',
'Online examples': 'Ukázka aplikace: web2py stránky', 'Overview': 'Přehled',
'Open new app in new window': 'Otevřít novou aplikaci v novém okně', 'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'or alternatively': 'nebo případně', 'Pack all': 'Zabalit',
'Or Get from URL:': 'Nebo získat z URL adresy:', 'Pack compiled': 'Zabalit zkompilované',
'or import from csv file': 'nebo importovat z .csv souboru', 'pack plugin': 'pack plugin',
'Origin': 'Původ', 'password': 'heslo',
'Original/Translation': 'Originál/Překlad', 'Password': 'Heslo',
'Other Plugins': 'Ostatní moduly', "Password fields don't match": 'Hesla se neshodu',
'Other Recipes': 'Ostatní zásuvné moduly', 'Peeking at file': 'Peeking at file',
'Overview': 'Přehled', 'Please': 'Prosím',
'Overwrite installed app': 'Přepsat instalovanou aplikaci', 'Plugin "%s" in application': 'Plugin "%s" in application',
'Pack all': 'Zabalit', 'plugins': 'zásuvné moduly',
'Pack compiled': 'Zabalit zkompilované', 'Plugins': 'Zásuvné moduly',
'pack plugin': 'pack (zabalit) plugin', 'Plural Form #%s': 'Plural Form #%s',
'password': 'heslo', 'Plural-Forms:': 'Množná čísla:',
'Password': 'Heslo', 'Powered by': 'Poháněno',
"Password fields don't match": 'Hesla se neshodují', 'Preface': 'Předmluva',
'Peeking at file': 'Sledování souboru', 'previous 100 rows': 'předchozích 100 řádků',
'Please': 'Prosím', 'Private files': 'Soukromé soubory',
'Plugin "%s" in application': 'Plugin "%s" v aplikaci', 'private files': 'soukromé soubory',
'plugins': 'zásuvné moduly', 'profile': 'profil',
'Plugins': 'Zásuvné moduly', 'Project Progress': 'Vývoj projektu',
'Plural Form #%s': 'Množné číslo #%s', 'Python': 'Python',
'Plural-Forms:': 'Množná čísla:', 'Query:': 'Dotaz:',
'Powered by': 'Používá technologii', 'Quick Examples': 'Krátké příklady',
'Preface': 'Předmluva', 'RAM': 'RAM',
'previous 100 rows': 'předchozích 100 řádků', 'RAM Cache Keys': 'Klíče RAM Cache',
'Private files': 'Soukromé soubory', 'Ram Cleared': 'RAM smazána',
'private files': 'soukromé soubory', 'Readme': 'Nápověda',
'profile': 'profil', 'Recipes': 'Postupy jak na to',
'Project Progress': 'Vývoj projektu', 'Record': 'Záznam',
'Python': 'Python', 'record does not exist': 'záznam neexistuje',
'Query:': 'Dotaz:', 'Record ID': 'ID záznamu',
'Quick Examples': 'Krátké příklady', 'Record id': 'id záznamu',
'RAM': 'RAM', 'refresh': 'obnovte',
'RAM Cache Keys': 'Klíče RAM Cache', 'register': 'registrovat',
'Ram Cleared': 'RAM smazána', 'Register': 'Zaregistrovat se',
'Readme': 'Nápověda', 'Registration identifier': 'Registrační identifikátor',
'Recipes': 'Postupy jak na to', 'Registration key': 'Registrační klíč',
'Record': 'Záznam', 'reload': 'reload',
'record does not exist': 'záznam neexistuje', 'Reload routes': 'Znovu nahrát cesty',
'Record ID': 'ID záznamu', 'Remember me (for 30 days)': 'Zapamatovat na 30 dní',
'Record id': 'id záznamu', 'Remove compiled': 'Odstranit zkompilované',
'refresh': 'obnovte', 'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'register': 'registrovat', 'Replace': 'Zaměnit',
'Register': 'Zaregistrovat se', 'Replace All': 'Zaměnit vše',
'Registration identifier': 'Registrační identifikátor', 'request': 'request',
'Registration key': 'Registrační klíč', 'Reset Password key': 'Reset registračního klíče',
'reload': 'reload', 'response': 'response',
'Reload routes': 'Znovu nahrát cesty', 'restart': 'restart',
'Remember me (for 30 days)': 'Zapamatovat na 30 dní', 'restore': 'obnovit',
'Remove compiled': 'Odstranit zkompilované', 'Retrieve username': 'Získat přihlašovací jméno',
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s', 'return': 'return',
'Replace': 'Zaměnit', 'revert': 'vrátit se k původnímu',
'Replace All': 'Zaměnit vše', 'Role': 'Role',
'request': 'request', 'Rows in Table': 'Záznamy v tabulce',
'Reset Password key': 'Reset registračního klíče', 'Rows selected': 'Záznamů zobrazeno',
'response': 'response', 'rules are not defined': 'pravidla nejsou definována',
'restart': 'restart', "Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'restore': 'obnovit', 'Running on %s': 'Běží na %s',
'Retrieve username': 'Získat přihlašovací jméno', 'Save': 'Uložit',
'return': 'return', 'Save file:': 'Save file:',
'revert': 'vrátit se k původnímu', 'Save via Ajax': 'Uložit pomocí Ajaxu',
'Role': 'Role', 'Saved file hash:': 'hash uloženého souboru:',
'Rows in Table': 'Záznamy v tabulce', 'Semantic': 'Modul semantic',
'Rows selected': 'Záznamů zobrazeno', 'Services': 'Služby',
'rules are not defined': 'pravidla nejsou definována', 'session': 'session',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')", 'session expired': 'session expired',
'Running on %s': 'Běží na %s', 'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'Save': 'Uložit', 'shell': 'příkazová řádka',
'Save file:': 'Uložit soubor:', 'Singular Form': 'Singular Form',
'Save via Ajax': 'Uložit pomocí Ajaxu', 'Site': 'Správa aplikací',
'Saved file hash:': 'hash uloženého souboru:', 'Size of cache:': 'Velikost cache:',
'Semantic': 'Modul semantic', 'skip to generate': 'skip to generate',
'Services': 'Služby', 'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'session': 'session', 'Start a new app': 'Vytvořit novou aplikaci',
'session expired': 'vypršela session', 'Start searching': 'Začít hledání',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s', 'Start wizard': 'Spustit průvodce',
'shell': 'příkazová řádka', 'state': 'stav',
'Sign Up': 'Registrovat se', 'Static': 'Static',
'Singular Form': 'Jednotné číslo', 'static': 'statické soubory',
'Site': 'Správa aplikací', 'Static files': 'Statické soubory',
'Size of cache:': 'Velikost cache:', 'Statistics': 'Statistika',
'skip to generate': 'přeskočit pro vytvoření', 'Step': 'Step',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.', 'step': 'step',
'Start a new app': 'Vytvořit novou aplikaci', 'stop': 'stop',
'Start searching': 'Začít hledání', 'Stylesheet': 'CSS styly',
'Start wizard': 'Spustit průvodce', 'submit': 'odeslat',
'state': 'stav', 'Submit': 'Odeslat',
'Static': 'Statické soubory', 'successful': 'úspěšně',
'static': 'statické soubory', 'Support': 'Podpora',
'Static files': 'Statické soubory', 'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'Statistics': 'Statistika', 'Table': 'tabulka',
'Step': 'Krok', 'Table name': 'Název tabulky',
'step': 'krok', 'Temporary': 'Dočasný',
'stop': 'zastavit', 'test': 'test',
'Stylesheet': 'CSS styly', 'Testing application': 'Testing application',
'submit': 'odeslat', 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'Submit': 'Odeslat', 'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'successful': 'úspěšně', 'The Core': 'Jádro (The Core)',
'Support': 'Podpora', 'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?', 'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je slovník, který se zobrazil v pohledu %s.',
'Table': 'tabulka', 'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'Table name': 'Název tabulky', 'The Views': 'Pohledy (The Views)',
'Temporary': 'Dočasný', 'There are no controllers': 'There are no controllers',
'test': 'test', 'There are no modules': 'There are no modules',
'Testing application': 'Zkušební aplikace', 'There are no plugins': 'Žádné moduly nejsou instalovány.',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.', 'There are no private files': 'Žádné soukromé soubory neexistují.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.', 'There are no static files': 'There are no static files',
'The Core': 'Jádro (The Core)', 'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy', 'There are no views': 'There are no views',
'The output of the file is a dictionary that was rendered by the view %s': 'Funkce vrátila dictionary (slovník) hodnot, a ty se vypsaly pomocí šablony %s.', 'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)', 'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'The Views': 'Pohledy (The Views)', 'This App': 'Tato aplikace',
'There are no controllers': 'Nejsou vytvořeny žádné controllery', 'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'There are no modules': 'Nejsou přidány žádné moduly', 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'There are no plugins': 'Žádné pluginy nejsou instalovány.', 'This is the %(filename)s template': 'This is the %(filename)s template',
'There are no private files': 'Žádné soukromé soubory neexistují.', 'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'There are no static files': 'Nejsou přidány žádné statické soubory', 'Ticket': 'Ticket',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported', 'Ticket ID': 'Ticket ID',
'There are no views': 'Nejsou vytvořeny žádné šablony (views)', 'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.', 'Timestamp': 'Časové razítko',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.', 'to previous version.': 'k předchozí verzi.',
'This App': 'Tato aplikace', 'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.', 'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk', 'to use the debugger!': ', abyste mohli ladící program používat!',
'This is the %(filename)s template': 'Toto je šablona %(filename)s', 'toggle breakpoint': 'vyp./zap. bod přerušení',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.', 'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'Ticket': 'Tiket', 'too short': 'Příliš krátké',
'Ticket ID': 'ID tiketu', 'Traceback': 'Traceback',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)', 'Translation strings for the application': 'Překlad textů pro aplikaci',
'Timestamp': 'Časové razítko', 'try something like': 'try something like',
'to previous version.': 'k předchozí verzi.', 'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]', 'try view': 'try view',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:', 'Twitter': 'Twitter',
'to use the debugger!': ', abyste mohli ladící program používat!', 'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'toggle breakpoint': 'vyp./zap. bod přerušení', 'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Toggle Fullscreen': 'Na celou obrazovku a zpět', 'Unable to check for upgrades': 'Unable to check for upgrades',
'too short': 'Příliš krátké', 'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'Traceback': 'Hierarchie volání', 'uncheck all': 'vše odznačit',
'Translation strings for the application': 'Překlad textů pro aplikaci', 'Uninstall': 'Odinstalovat',
'try something like': 'zkuste něco jako', 'update': 'aktualizovat',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení', 'update all languages': 'aktualizovat všechny jazyky',
'try view': 'vyzkoušet šablonu (view)', 'Update:': 'Upravit:',
'Twitter': 'Twitter', 'Upgrade': 'Upgrade',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.', 'upgrade now': 'upgrade now',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.', 'upgrade now to %s': 'upgrade now to %s',
'Unable to check for upgrades': 'Nelze zjistit informaci o aktualizacích', 'upload': 'nahrát',
'unable to parse csv file': 'csv soubor nedá sa zpracovat', 'Upload': 'Upload',
'uncheck all': 'vše odznačit', 'Upload a package:': 'Nahrát balík:',
'Uninstall': 'Odinstalovat', 'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'update': 'aktualizovat', 'upload file:': 'nahrát soubor:',
'update all languages': 'aktualizovat všechny jazyky', 'upload plugin file:': 'nahrát soubor modulu:',
'Update:': 'Upravit:', 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'Upgrade': 'Upgrade', 'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'upgrade now': 'upgradovat nyní', 'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'upgrade now to %s': 'upgradovat nyní na %s', 'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
'upload': 'nahrát', 'User %(id)s Profile updated': 'Uživatel %(id)s upravil profil',
'Upload': 'Upload (nahrát)', 'User %(id)s Registered': 'Uživatel %(id)s se zaregistroval',
'Upload a package:': 'Nahrát balík:', 'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci', 'User ID': 'ID uživatele',
'upload file:': 'nahrát soubor:', 'Username': 'Přihlašovací jméno',
'upload plugin file:': 'nahrát soubor modulu:', 'variables': 'variables',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.', 'Verify Password': 'Zopakujte heslo',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen', 'Version': 'Verze',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen', 'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo', 'Versioning': 'Verzování',
'User %(id)s Profile updated': 'Uživatel %(id)s upravil profil', 'Videos': 'Videa',
'User %(id)s Registered': 'Uživatel %(id)s se zaregistroval', 'View': 'Pohled (View)',
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno', 'Views': 'Pohledy',
'User ID': 'ID uživatele', 'views': 'pohledy',
'Username': 'Přihlašovací jméno', 'Web Framework': 'Web Framework',
'variables': 'proměnné', 'web2py is up to date': 'Máte aktuální verzi web2py.',
'Verify Password': 'Zopakujte heslo', 'web2py online debugger': 'Ladící online web2py program',
'Version': 'Verze', 'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s', 'web2py upgrade': 'web2py upgrade',
'Versioning': 'Verzování', 'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'Videos': 'Videa', 'Welcome': 'Vítejte',
'View': 'Pohled (View)', 'Welcome to web2py': 'Vitejte ve web2py',
'Views': 'Pohledy', 'Welcome to web2py!': 'Vítejte ve web2py!',
'views': 'pohledy', 'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.',
'Web Framework': 'Webový framework', 'You are successfully running web2py': 'Úspěšně jste spustili web2py.',
'web2py is up to date': 'Máte aktuální verzi web2py.', 'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'web2py online debugger': 'Ladící online web2py program', 'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py', 'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'web2py upgrade': 'aktualizace Web2py', 'You visited the url %s': 'Navštívili jste stránku %s,',
'web2py upgraded; please restart it': 'Web2py bylo aktualizováno; prosím restarujte jej', 'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
'Welcome': 'Vítejte', 'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'Welcome to web2py': 'Vitejte ve Web2py aplikaci', }
'Welcome to web2py!': 'Vítejte ve Web2py aplikaci.',
'Which called the function %s located in the file %s': 'Tím byla zavolána funkce %s ze souboru (kontroléru) %s.',
'Working...': 'Pracuji...',
'You are successfully running web2py': 'Spustil(a) jsi webový server a Web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'You can modify this application and adapt it to your needs': 'V ADMIN rozhraní můžeš Vytvořit novou aplikaci jako kopii ukázkové Welcome aplikace. A začít upravovat: modely, kontroléry, šablony pro URL adresy, které požaduješ.',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You visited the url %s': 'Zadal jsi URL adresu %s.',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
}
+58 -98
View File
@@ -1,132 +1,92 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ------------------------------------------------------------------------- #########################################################################
# This scaffolding model makes your app work on Google App Engine too ## This scaffolding model makes your app work on Google App Engine too
# File is released under public domain and you can use without limitations ## File is released under public domain and you can use without limitations
# ------------------------------------------------------------------------- #########################################################################
if request.global_settings.web2py_version < "2.14.1": ## if SSL/HTTPS is properly configured and you want all HTTP requests to
raise HTTP(500, "Requires web2py 2.13.3 or newer") ## be redirected to HTTPS, uncomment the line below:
# -------------------------------------------------------------------------
# if SSL/HTTPS is properly configured and you want all HTTP requests to
# be redirected to HTTPS, uncomment the line below:
# -------------------------------------------------------------------------
# request.requires_https() # request.requires_https()
# ------------------------------------------------------------------------- ## app configuration made easy. Look inside private/appconfig.ini
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig from gluon.contrib.appconfig import AppConfig
## once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
# once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
myconf = AppConfig(reload=True) myconf = AppConfig(reload=True)
if not request.env.web2py_runtime_gae: if not request.env.web2py_runtime_gae:
# --------------------------------------------------------------------- ## if NOT running on Google App Engine use SQLite or other DB
# if NOT running on Google App Engine use SQLite or other DB db = DAL(myconf.take('db.uri'), pool_size=myconf.take('db.pool_size', cast=int), check_reserved=['all'])
# ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'),
pool_size=myconf.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'),
check_reserved=['all'])
else: else:
# --------------------------------------------------------------------- ## connect to Google BigTable (optional 'google:datastore://namespace')
# connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
db = DAL('google:datastore+ndb') db = DAL('google:datastore+ndb')
# --------------------------------------------------------------------- ## store sessions and tickets there
# store sessions and tickets there
# ---------------------------------------------------------------------
session.connect(request, response, db=db) session.connect(request, response, db=db)
# --------------------------------------------------------------------- ## or store session in Memcache, Redis, etc.
# or store session in Memcache, Redis, etc. ## from gluon.contrib.memdb import MEMDB
# from gluon.contrib.memdb import MEMDB ## from google.appengine.api.memcache import Client
# from google.appengine.api.memcache import Client ## session.connect(request, response, db = MEMDB(Client()))
# session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
# ------------------------------------------------------------------------- ## by default give a view/generic.extension to all actions from localhost
# by default give a view/generic.extension to all actions from localhost ## none otherwise. a pattern can be 'controller/function.extension'
# none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
response.generic_patterns = ['*'] if request.is_local else [] response.generic_patterns = ['*'] if request.is_local else []
# ------------------------------------------------------------------------- ## choose a style for forms
# choose a style for forms response.formstyle = myconf.take('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
# ------------------------------------------------------------------------- response.form_label_separator = myconf.take('forms.separator')
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.get('forms.separator') or ''
# -------------------------------------------------------------------------
# (optional) optimize handling of static files ## (optional) optimize handling of static files
# -------------------------------------------------------------------------
# response.optimize_css = 'concat,minify,inline' # response.optimize_css = 'concat,minify,inline'
# response.optimize_js = 'concat,minify,inline' # response.optimize_js = 'concat,minify,inline'
## (optional) static assets folder versioning
# -------------------------------------------------------------------------
# (optional) static assets folder versioning
# -------------------------------------------------------------------------
# response.static_version = '0.0.0' # response.static_version = '0.0.0'
#########################################################################
# ------------------------------------------------------------------------- ## Here is sample code if you need for
# Here is sample code if you need for ## - email capabilities
# - email capabilities ## - authentication (registration, login, logout, ... )
# - authentication (registration, login, logout, ... ) ## - authorization (role based authorization)
# - authorization (role based authorization) ## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
# - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss) ## - old style crud actions
# - old style crud actions ## (more options discussed in gluon/tools.py)
# (more options discussed in gluon/tools.py) #########################################################################
# -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager from gluon.tools import Auth, Service, PluginManager
# host names must be a list of allowed host names (glob syntax allowed) auth = Auth(db)
auth = Auth(db, host_names=myconf.get('host.names'))
service = Service() service = Service()
plugins = PluginManager() plugins = PluginManager()
# ------------------------------------------------------------------------- ## create all tables needed by auth if not custom tables
# create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
auth.define_tables(username=False, signature=False) auth.define_tables(username=False, signature=False)
# ------------------------------------------------------------------------- ## configure email
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server') mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server')
mail.settings.sender = myconf.get('smtp.sender') mail.settings.sender = myconf.take('smtp.sender')
mail.settings.login = myconf.get('smtp.login') mail.settings.login = myconf.take('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False
# ------------------------------------------------------------------------- ## configure auth policy
# configure auth policy
# -------------------------------------------------------------------------
auth.settings.registration_requires_verification = False auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True auth.settings.reset_password_requires_verification = True
# ------------------------------------------------------------------------- #########################################################################
# Define your tables below (or better in another model file) for example ## Define your tables below (or better in another model file) for example
# ##
# >>> db.define_table('mytable', Field('myfield', 'string')) ## >>> db.define_table('mytable',Field('myfield','string'))
# ##
# Fields can be 'string','text','password','integer','double','boolean' ## Fields can be 'string','text','password','integer','double','boolean'
# 'date','time','datetime','blob','upload', 'reference TABLENAME' ## 'date','time','datetime','blob','upload', 'reference TABLENAME'
# There is an implicit 'id integer autoincrement' field ## There is an implicit 'id integer autoincrement' field
# Consult manual for more options, validators, etc. ## Consult manual for more options, validators, etc.
# ##
# More API examples for controllers: ## More API examples for controllers:
# ##
# >>> db.mytable.insert(myfield='value') ## >>> db.mytable.insert(myfield='value')
# >>> rows = db(db.mytable.myfield == 'value').select(db.mytable.ALL) ## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL)
# >>> for row in rows: print row.id, row.myfield ## >>> for row in rows: print row.id, row.myfield
# ------------------------------------------------------------------------- #########################################################################
# ------------------------------------------------------------------------- ## after defining tables, uncomment below to enable auditing
# after defining tables, uncomment below to enable auditing
# -------------------------------------------------------------------------
# auth.enable_record_versioning(db) # auth.enable_record_versioning(db)
+112 -125
View File
@@ -1,32 +1,28 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations # this file is released under public domain and you can use without limitations
# ---------------------------------------------------------------------------------------------------------------------- #########################################################################
# Customize your APP title, subtitle and menus here ## Customize your APP title, subtitle and menus here
# ---------------------------------------------------------------------------------------------------------------------- #########################################################################
response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'), response.logo = A(B('web',SPAN(2),'py'),XML('&trade;&nbsp;'),
_class="navbar-brand", _href="http://www.web2py.com/", _class="navbar-brand",_href="http://www.web2py.com/",
_id="web2py-logo") _id="web2py-logo")
response.title = request.application.replace('_', ' ').title() response.title = request.application.replace('_',' ').title()
response.subtitle = '' response.subtitle = ''
# ---------------------------------------------------------------------------------------------------------------------- ## read more at http://dev.w3.org/html5/markup/meta.name.html
# read more at http://dev.w3.org/html5/markup/meta.name.html response.meta.author = 'Your Name <you@example.com>'
# ---------------------------------------------------------------------------------------------------------------------- response.meta.description = 'a cool new app'
response.meta.author = myconf.get('app.author') response.meta.keywords = 'web2py, python, framework'
response.meta.description = myconf.get('app.description') response.meta.generator = 'Web2py Web Framework'
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
# ---------------------------------------------------------------------------------------------------------------------- ## your http://google.com/analytics id
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
response.google_analytics_id = None response.google_analytics_id = None
# ---------------------------------------------------------------------------------------------------------------------- #########################################################################
# this is the main application menu add/remove items as required ## this is the main application menu add/remove items as required
# ---------------------------------------------------------------------------------------------------------------------- #########################################################################
response.menu = [ response.menu = [
(T('Home'), False, URL('default', 'index'), []) (T('Home'), False, URL('default', 'index'), [])
@@ -34,118 +30,109 @@ response.menu = [
DEVELOPMENT_MENU = True DEVELOPMENT_MENU = True
#########################################################################
# ---------------------------------------------------------------------------------------------------------------------- ## provide shortcuts for development. remove in production
# provide shortcuts for development. remove in production #########################################################################
# ----------------------------------------------------------------------------------------------------------------------
def _(): def _():
# ------------------------------------------------------------------------------------------------------------------
# shortcuts # shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application app = request.application
ctr = request.controller ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources # useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
response.menu += [ response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')), (T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [ (T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)), (T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"), LI(_class="divider"),
(T('Controller'), False, (T('Controller'), False,
URL( URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))), 'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
(T('View'), False, (T('View'), False,
URL( URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))), 'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
(T('DB Model'), False, (T('DB Model'), False,
URL( URL(
'admin', 'default', 'edit/%s/models/db.py' % app)), 'admin', 'default', 'edit/%s/models/db.py' % app)),
(T('Menu Model'), False, (T('Menu Model'), False,
URL( URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)), 'admin', 'default', 'edit/%s/models/menu.py' % app)),
(T('Config.ini'), False, (T('Config.ini'), False,
URL( URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)), 'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
(T('Layout'), False, (T('Layout'), False,
URL( URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)), 'admin', 'default', 'edit/%s/views/layout.html' % app)),
(T('Stylesheet'), False, (T('Stylesheet'), False,
URL( URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)), 'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')), (T('Database'), False, URL(app, 'appadmin', 'index')),
(T('Errors'), False, URL( (T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)), 'admin', 'default', 'errors/' + app)),
(T('About'), False, URL( (T('About'), False, URL(
'admin', 'default', 'about/' + app)), 'admin', 'default', 'about/' + app)),
]), ]),
('web2py.com', False, '#', [ ('web2py.com', False, '#', [
(T('Download'), False, (T('Download'), False,
'http://www.web2py.com/examples/default/download'), 'http://www.web2py.com/examples/default/download'),
(T('Support'), False, (T('Support'), False,
'http://www.web2py.com/examples/default/support'), 'http://www.web2py.com/examples/default/support'),
(T('Demo'), False, 'http://web2py.com/demo_admin'), (T('Demo'), False, 'http://web2py.com/demo_admin'),
(T('Quick Examples'), False, (T('Quick Examples'), False,
'http://web2py.com/examples/default/examples'), 'http://web2py.com/examples/default/examples'),
(T('FAQ'), False, 'http://web2py.com/AlterEgo'), (T('FAQ'), False, 'http://web2py.com/AlterEgo'),
(T('Videos'), False, (T('Videos'), False,
'http://www.web2py.com/examples/default/videos/'), 'http://www.web2py.com/examples/default/videos/'),
(T('Free Applications'), (T('Free Applications'),
False, 'http://web2py.com/appliances'), False, 'http://web2py.com/appliances'),
(T('Plugins'), False, 'http://web2py.com/plugins'), (T('Plugins'), False, 'http://web2py.com/plugins'),
(T('Recipes'), False, 'http://web2pyslices.com/'), (T('Recipes'), False, 'http://web2pyslices.com/'),
]), ]),
(T('Documentation'), False, '#', [ (T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'), (T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"), LI(_class="divider"),
(T('Preface'), False, (T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'), 'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False, (T('Introduction'), False,
'http://www.web2py.com/book/default/chapter/01'), 'http://www.web2py.com/book/default/chapter/01'),
(T('Python'), False, (T('Python'), False,
'http://www.web2py.com/book/default/chapter/02'), 'http://www.web2py.com/book/default/chapter/02'),
(T('Overview'), False, (T('Overview'), False,
'http://www.web2py.com/book/default/chapter/03'), 'http://www.web2py.com/book/default/chapter/03'),
(T('The Core'), False, (T('The Core'), False,
'http://www.web2py.com/book/default/chapter/04'), 'http://www.web2py.com/book/default/chapter/04'),
(T('The Views'), False, (T('The Views'), False,
'http://www.web2py.com/book/default/chapter/05'), 'http://www.web2py.com/book/default/chapter/05'),
(T('Database'), False, (T('Database'), False,
'http://www.web2py.com/book/default/chapter/06'), 'http://www.web2py.com/book/default/chapter/06'),
(T('Forms and Validators'), False, (T('Forms and Validators'), False,
'http://www.web2py.com/book/default/chapter/07'), 'http://www.web2py.com/book/default/chapter/07'),
(T('Email and SMS'), False, (T('Email and SMS'), False,
'http://www.web2py.com/book/default/chapter/08'), 'http://www.web2py.com/book/default/chapter/08'),
(T('Access Control'), False, (T('Access Control'), False,
'http://www.web2py.com/book/default/chapter/09'), 'http://www.web2py.com/book/default/chapter/09'),
(T('Services'), False, (T('Services'), False,
'http://www.web2py.com/book/default/chapter/10'), 'http://www.web2py.com/book/default/chapter/10'),
(T('Ajax Recipes'), False, (T('Ajax Recipes'), False,
'http://www.web2py.com/book/default/chapter/11'), 'http://www.web2py.com/book/default/chapter/11'),
(T('Components and Plugins'), False, (T('Components and Plugins'), False,
'http://www.web2py.com/book/default/chapter/12'), 'http://www.web2py.com/book/default/chapter/12'),
(T('Deployment Recipes'), False, (T('Deployment Recipes'), False,
'http://www.web2py.com/book/default/chapter/13'), 'http://www.web2py.com/book/default/chapter/13'),
(T('Other Recipes'), False, (T('Other Recipes'), False,
'http://www.web2py.com/book/default/chapter/14'), 'http://www.web2py.com/book/default/chapter/14'),
(T('Helping web2py'), False, (T('Helping web2py'), False,
'http://www.web2py.com/book/default/chapter/15'), 'http://www.web2py.com/book/default/chapter/15'),
(T("Buy web2py's book"), False, (T("Buy web2py's book"), False,
'http://stores.lulu.com/web2py'), 'http://stores.lulu.com/web2py'),
]), ]),
(T('Community'), False, None, [ (T('Community'), False, None, [
(T('Groups'), False, (T('Groups'), False,
'http://www.web2py.com/examples/default/usergroups'), 'http://www.web2py.com/examples/default/usergroups'),
(T('Twitter'), False, 'http://twitter.com/web2py'), (T('Twitter'), False, 'http://twitter.com/web2py'),
(T('Live Chat'), False, (T('Live Chat'), False,
'http://webchat.freenode.net/?channels=web2py'), 'http://webchat.freenode.net/?channels=web2py'),
]), ]),
] ]
if DEVELOPMENT_MENU: _()
if "auth" in locals(): auth.wikimenu()
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()
+3 -14
View File
@@ -1,28 +1,17 @@
; App configuration ; App configuration
[app]
name = Welcome
author = Your Name <you@example.com>
description = a cool new app
keywords = web2py, python, framework
generator = Web2py Web Framework
; Host configuration
[host]
names = localhost:*, 127.0.0.1:*, *:*, *
; db configuration ; db configuration
[db] [db]
uri = sqlite://storage.sqlite uri = sqlite://storage.sqlite
migrate = true migrate = 1
pool_size = 10 ; ignored for sqlite pool_size = 1
; smtp address and credentials ; smtp address and credentials
[smtp] [smtp]
server = smtp.gmail.com:587 server = smtp.gmail.com:587
sender = you@gmail.com sender = you@gmail.com
login = username:password login = username:password
tls = true
ssl = true
; form styling ; form styling
[forms] [forms]
+12 -15
View File
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------------------------------------------------
# This is an app-specific example router # This is an app-specific example router
# #
# This simple router is used for setting languages from app/languages directory # This simple router is used for setting languages from app/languages directory
@@ -9,33 +8,31 @@
# a default_language # a default_language
# #
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail # See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
# ---------------------------------------------------------------------------------------------------------------------- #-------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# To enable this route file you must do the steps: # To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py # 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py (where APP - is your application directory) # 2. rename this APP/routes.example.py to APP/routes.py
# 3. restart web2py (or reload routes in web2py admin interface) # (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interfase)
# #
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES! # YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath from fileutils import abspath
from languages import read_possible_languages from languages import read_possible_languages
possible_languages = read_possible_languages(abspath('applications', app)) possible_languages = read_possible_languages(abspath('applications', app))
# ---------------------------------------------------------------------------------------------------------------------- #NOTE! app - is an application based router's parameter with name of an
# NOTE! app - is an application based router's parameter with name of an application. E.g.'welcome' # application. E.g.'welcome'
# ----------------------------------------------------------------------------------------------------------------------
routers = { routers = {
app: dict( app: dict(
default_language=possible_languages['default'][0], default_language = possible_languages['default'][0],
languages=[lang for lang in possible_languages if lang != 'default'] languages = [lang for lang in possible_languages
if lang != 'default']
) )
} }
# ---------------------------------------------------------------------------------------------------------------------- #NOTE! To change language in your application using these rules add this line
# NOTE! To change language in your application using these rules add this line in one of your models files: #in one of your models files:
# ----------------------------------------------------------------------------------------------------------------------
# if request.uri_language: T.force(request.uri_language) # if request.uri_language: T.force(request.uri_language)
File diff suppressed because one or more lines are too long
@@ -108,7 +108,7 @@ select.autocomplete {
background: url(../images/background.jpg) no-repeat center center; background: url(../images/background.jpg) no-repeat center center;
} }
body { body {
padding-top: 60px; padding-top: 50px;
margin-bottom: 60px; margin-bottom: 60px;
} }
header { header {
@@ -233,7 +233,7 @@ div.error_wrapper {
line-height: 20px; line-height: 20px;
margin-right: 2px; margin-right: 2px;
display: inline-block; display: inline-block;
padding: 6px 12px; padding: 3px 5px;
} }
.web2py_counter { .web2py_counter {
margin-top: 5px; margin-top: 5px;
@@ -270,7 +270,6 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console select, .web2py_console select,
.web2py_console a { .web2py_console a {
margin: 2px; margin: 2px;
padding: 6px 12px;
} }
#wiki_page_body { #wiki_page_body {
width: 600px; width: 600px;
@@ -286,7 +285,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console .form-control { .web2py_console .form-control {
width: 20%; width: 20%;
display: inline; display: inline;
height: 32px; height: 100%;
} }
.web2py_console #w2p_keywords { .web2py_console #w2p_keywords {
width: 50%; width: 50%;
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -120,7 +120,7 @@ args=()
class=handlers.RotatingFileHandler class=handlers.RotatingFileHandler
level=DEBUG level=DEBUG
formatter=simpleFormatter formatter=simpleFormatter
args=("web2py.log", "a", 1000000, 5) args=("logs/web2py.log", "a", 1000000, 5)
[handler_osxSysLogHandler] [handler_osxSysLogHandler]
class=handlers.SysLogHandler class=handlers.SysLogHandler
Vendored
+10 -18
View File
@@ -5,9 +5,6 @@ import os
import datetime import datetime
import getpass import getpass
if os.path.exists('hosts'):
env.hosts = [h.strip() for h in open('hosts').readlines() if h.strip()]
env.hosts = env.hosts or raw_input('hostname (example.com):').split(',') env.hosts = env.hosts or raw_input('hostname (example.com):').split(',')
env.user = env.user or raw_input('username :') env.user = env.user or raw_input('username :')
@@ -88,7 +85,7 @@ def mkdir_or_backup(appname):
def git_deploy(appname, repo): def git_deploy(appname, repo):
"""fab -H username@host git_deploy:appname,username/remoname""" """fab -H username@host git_deploy:appname,username/remoname"""
appfolder = applications+'/'+appname appfolder = applications+'/'+appname
backup = mkdir_or_backup(appname) backup = mkdir_or_backup(appfolder)
if exists(appfolder): if exists(appfolder):
with cd(appfolder): with cd(appfolder):
@@ -98,7 +95,7 @@ def git_deploy(appname, repo):
with cd(applications): with cd(applications):
sudo('git clone git@github.com/%s %s' % (repo, name)) sudo('git clone git@github.com/%s %s' % (repo, name))
sudo('chown -R www-data:www-data %s' % name) sudo('chown -R www-data:www-data %s' % name)
def retrieve(appname=None): def retrieve(appname=None):
"""fab -H username@host retrieve:appname""" """fab -H username@host retrieve:appname"""
appname = appname or os.path.split(os.getcwd())[-1] appname = appname or os.path.split(os.getcwd())[-1]
@@ -115,26 +112,21 @@ def deploy(appname=None, all=False):
"""fab -H username@host deploy:appname,all""" """fab -H username@host deploy:appname,all"""
appname = appname or os.path.split(os.getcwd())[-1] appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname appfolder = applications+'/'+appname
zipfile = os.path.join(appfolder, '_update.zip') if os.path.exists('_update.zip'):
if os.path.exists(zipfile): os.unlink('_update.zip')
os.unlink(zipfile)
backup = mkdir_or_backup(appname) backup = mkdir_or_backup(appfolder)
if all=='all' or not backup: if all=='all' or not backup:
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2') local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
else: else:
local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*') local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*')
put('_update.zip','/tmp/_update.zip') put('_update.zip','/tmp/_update.zip')
try:
with cd(appfolder): with cd(appfolder):
sudo('unzip -o /tmp/_update.zip') sudo('unzip -o /tmp/_update.zip')
sudo('chown -R www-data:www-data *') sudo('chown -R www-data:www-data *')
sudo('echo "%s" > DATE_DEPLOYMENT' % now) sudo('echo "%s" > DATE_DEPLOYMENT' % now)
finally:
sudo('rm /tmp/_update.zip')
if backup: if backup:
print 'TO RESTORE: fab restore:%s' % backup print 'TO RESTORE: fab restore:%s' % backup
+3 -3
View File
@@ -148,7 +148,7 @@ def app_compile(app, request, skip_failed_views=False):
failed_views = compile_application(folder, skip_failed_views) failed_views = compile_application(folder, skip_failed_views)
return failed_views return failed_views
except (Exception, RestrictedError): except (Exception, RestrictedError):
tb = traceback.format_exc() tb = traceback.format_exc(sys.exc_info)
remove_compiled_application(folder) remove_compiled_application(folder)
return tb return tb
@@ -167,7 +167,7 @@ def app_create(app, request, force=False, key=None, info=False):
os.mkdir(path) os.mkdir(path)
except: except:
if info: if info:
return False, traceback.format_exc() return False, traceback.format_exc(sys.exc_info)
else: else:
return False return False
elif not force: elif not force:
@@ -197,7 +197,7 @@ def app_create(app, request, force=False, key=None, info=False):
except: except:
rmtree(path) rmtree(path)
if info: if info:
return False, traceback.format_exc() return False, traceback.format_exc(sys.exc_info)
else: else:
return False return False
+50 -37
View File
@@ -44,9 +44,9 @@ except ImportError:
have_settings = False have_settings = False
try: try:
import cPickle as pickle import cPickle as pickle
except: except:
import pickle import pickle
try: try:
import psutil import psutil
@@ -54,7 +54,6 @@ try:
except ImportError: except ImportError:
HAVE_PSUTIL = False HAVE_PSUTIL = False
def remove_oldest_entries(storage, percentage=90): def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%) # compute current memory usage (%)
old_mem = psutil.virtual_memory().percent old_mem = psutil.virtual_memory().percent
@@ -67,8 +66,7 @@ def remove_oldest_entries(storage, percentage=90):
# comute used memory again # comute used memory again
new_mem = psutil.virtual_memory().percent new_mem = psutil.virtual_memory().percent
# if the used memory did not decrease stop # if the used memory did not decrease stop
if new_mem >= old_mem: if new_mem >= old_mem: break
break
# net new measurement for memory usage and loop # net new measurement for memory usage and loop
old_mem = new_mem old_mem = new_mem
@@ -80,7 +78,6 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300 DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object): class CacheAbstract(object):
""" """
Abstract class for cache implementations. Abstract class for cache implementations.
@@ -102,7 +99,7 @@ class CacheAbstract(object):
""" """
cache_stats_name = 'web2py_cache_statistics' cache_stats_name = 'web2py_cache_statistics'
max_ram_utilization = None # percent max_ram_utilization = None # percent
def __init__(self, request=None): def __init__(self, request=None):
"""Initializes the object """Initializes the object
@@ -185,14 +182,13 @@ class CacheInRam(CacheAbstract):
self.request = request self.request = request
self.storage = OrderedDict() if HAVE_PSUTIL else {} self.storage = OrderedDict() if HAVE_PSUTIL else {}
self.app = request.application if request else '' self.app = request.application if request else ''
def initialize(self): def initialize(self):
if self.initialized: if self.initialized:
return return
else: else:
self.initialized = True self.initialized = True
self.locker.acquire() self.locker.acquire()
if self.app not in self.meta_storage: if not self.app in self.meta_storage:
self.storage = self.meta_storage[self.app] = \ self.storage = self.meta_storage[self.app] = \
OrderedDict() if HAVE_PSUTIL else {} OrderedDict() if HAVE_PSUTIL else {}
self.stats[self.app] = {'hit_total': 0, 'misses': 0} self.stats[self.app] = {'hit_total': 0, 'misses': 0}
@@ -209,7 +205,7 @@ class CacheInRam(CacheAbstract):
else: else:
self._clear(storage, regex) self._clear(storage, regex)
if self.app not in self.stats: if not self.app in self.stats:
self.stats[self.app] = {'hit_total': 0, 'misses': 0} self.stats[self.app] = {'hit_total': 0, 'misses': 0}
self.locker.release() self.locker.release()
@@ -255,8 +251,8 @@ class CacheInRam(CacheAbstract):
self.locker.acquire() self.locker.acquire()
self.storage[key] = (now, value) self.storage[key] = (now, value)
self.stats[self.app]['misses'] += 1 self.stats[self.app]['misses'] += 1
if HAVE_PSUTIL and self.max_ram_utilization is not None and random.random() < 0.10: if HAVE_PSUTIL and self.max_ram_utilization!=None and random.random()<0.10:
remove_oldest_entries(self.storage, percentage=self.max_ram_utilization) remove_oldest_entries(self.storage, percentage = self.max_ram_utilization)
self.locker.release() self.locker.release()
return value return value
@@ -296,15 +292,14 @@ class CacheOnDisk(CacheAbstract):
self.folder = folder self.folder = folder
self.key_filter_in = lambda key: key self.key_filter_in = lambda key: key
self.key_filter_out = lambda key: key self.key_filter_out = lambda key: key
self.file_lock_time_wait = file_lock_time_wait self.file_lock_time_wait = file_lock_time_wait # How long we should wait before retrying to lock a file held by another process
# How long we should wait before retrying to lock a file held by another process
# We still need a mutex for each file as portalocker only blocks other processes # We still need a mutex for each file as portalocker only blocks other processes
self.file_locks = defaultdict(thread.allocate_lock) self.file_locks = defaultdict(thread.allocate_lock)
# Make sure we use valid filenames. # Make sure we use valid filenames.
if sys.platform == "win32": if sys.platform == "win32":
import base64 import base64
def key_filter_in_windows(key): def key_filter_in_windows(key):
""" """
Windows doesn't allow \ / : * ? "< > | in filenames. Windows doesn't allow \ / : * ? "< > | in filenames.
@@ -321,6 +316,7 @@ class CacheOnDisk(CacheAbstract):
self.key_filter_in = key_filter_in_windows self.key_filter_in = key_filter_in_windows
self.key_filter_out = key_filter_out_windows self.key_filter_out = key_filter_out_windows
def wait_portalock(self, val_file): def wait_portalock(self, val_file):
""" """
Wait for the process file lock. Wait for the process file lock.
@@ -332,12 +328,15 @@ class CacheOnDisk(CacheAbstract):
except: except:
time.sleep(self.file_lock_time_wait) time.sleep(self.file_lock_time_wait)
def acquire(self, key): def acquire(self, key):
self.file_locks[key].acquire() self.file_locks[key].acquire()
def release(self, key): def release(self, key):
self.file_locks[key].release() self.file_locks[key].release()
def __setitem__(self, key, value): def __setitem__(self, key, value):
key = self.key_filter_in(key) key = self.key_filter_in(key)
val_file = recfile.open(key, mode='wb', path=self.folder) val_file = recfile.open(key, mode='wb', path=self.folder)
@@ -345,6 +344,7 @@ class CacheOnDisk(CacheAbstract):
pickle.dump(value, val_file, pickle.HIGHEST_PROTOCOL) pickle.dump(value, val_file, pickle.HIGHEST_PROTOCOL)
val_file.close() val_file.close()
def __getitem__(self, key): def __getitem__(self, key):
key = self.key_filter_in(key) key = self.key_filter_in(key)
try: try:
@@ -357,10 +357,12 @@ class CacheOnDisk(CacheAbstract):
val_file.close() val_file.close()
return value return value
def __contains__(self, key): def __contains__(self, key):
key = self.key_filter_in(key) key = self.key_filter_in(key)
return (key in self.file_locks) or recfile.exists(key, path=self.folder) return (key in self.file_locks) or recfile.exists(key, path=self.folder)
def __delitem__(self, key): def __delitem__(self, key):
key = self.key_filter_in(key) key = self.key_filter_in(key)
try: try:
@@ -368,11 +370,13 @@ class CacheOnDisk(CacheAbstract):
except IOError: except IOError:
raise KeyError raise KeyError
def __iter__(self): def __iter__(self):
for dirpath, dirnames, filenames in os.walk(self.folder): for dirpath, dirnames, filenames in os.walk(self.folder):
for filename in filenames: for filename in filenames:
yield self.key_filter_out(filename) yield self.key_filter_out(filename)
def safe_apply(self, key, function, default_value=None): def safe_apply(self, key, function, default_value=None):
""" """
Safely apply a function to the value of a key in storage and set Safely apply a function to the value of a key in storage and set
@@ -399,21 +403,25 @@ class CacheOnDisk(CacheAbstract):
val_file.close() val_file.close()
return new_value return new_value
def keys(self): def keys(self):
return list(self.__iter__()) return list(self.__iter__())
def get(self, key, default=None): def get(self, key, default=None):
try: try:
return self[key] return self[key]
except KeyError: except KeyError:
return default return default
def __init__(self, request=None, folder=None): def __init__(self, request=None, folder=None):
self.initialized = False self.initialized = False
self.request = request self.request = request
self.folder = folder self.folder = folder
self.storage = None self.storage = None
def initialize(self): def initialize(self):
if self.initialized: if self.initialized:
return return
@@ -432,6 +440,7 @@ class CacheOnDisk(CacheAbstract):
self.storage = CacheOnDisk.PersistentStorage(folder) self.storage = CacheOnDisk.PersistentStorage(folder)
def __call__(self, key, f, def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE): time_expire=DEFAULT_TIME_EXPIRE):
self.initialize() self.initialize()
@@ -478,6 +487,7 @@ class CacheOnDisk(CacheAbstract):
self.storage.release(key) self.storage.release(key)
return value return value
def clear(self, regex=None): def clear(self, regex=None):
self.initialize() self.initialize()
storage = self.storage storage = self.storage
@@ -494,6 +504,7 @@ class CacheOnDisk(CacheAbstract):
pass pass
storage.release(key) storage.release(key)
def increment(self, key, value=1): def increment(self, key, value=1):
self.initialize() self.initialize()
self.storage.acquire(key) self.storage.acquire(key)
@@ -502,6 +513,7 @@ class CacheOnDisk(CacheAbstract):
return value return value
class CacheAction(object): class CacheAction(object):
def __init__(self, func, key, time_expire, cache, cache_model): def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__ self.__name__ = func.__name__
@@ -560,9 +572,9 @@ class Cache(object):
logger.warning('no cache.disk (AttributeError)') logger.warning('no cache.disk (AttributeError)')
def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None, def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
prefix=None, session=False, vars=True, lang=True, prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None, user_agent=False, public=True, valid_statuses=None,
quick=None): quick=None):
"""Better fit for caching an action """Better fit for caching an action
Warning: Warning:
@@ -590,7 +602,6 @@ class Cache(object):
""" """
from gluon import current from gluon import current
from gluon.http import HTTP from gluon.http import HTTP
def wrap(func): def wrap(func):
def wrapped_f(): def wrapped_f():
if current.request.env.request_method != 'GET': if current.request.env.request_method != 'GET':
@@ -610,14 +621,13 @@ class Cache(object):
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire) cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
if not session_ and public_: if not session_ and public_:
cache_control += ', public' cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire) expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
).strftime('%a, %d %b %Y %H:%M:%S GMT')
else: else:
cache_control += ', private' cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT' expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model: if cache_model:
# figure out the correct cache key #figure out the correct cache key
cache_key = [current.request.env.path_info, current.response.view] cache_key = [current.request.env.path_info, current.response.view]
if session_: if session_:
cache_key.append(current.response.session_id) cache_key.append(current.response.session_id)
@@ -634,28 +644,28 @@ class Cache(object):
if prefix: if prefix:
cache_key = prefix + cache_key cache_key = prefix + cache_key
try: try:
# action returns something #action returns something
rtn = cache_model(cache_key, lambda: func(), time_expire=time_expire) rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
http, status = None, current.response.status http, status = None, current.response.status
except HTTP, e: except HTTP, e:
# action raises HTTP (can still be valid) #action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda: e.body, time_expire=time_expire) rtn = cache_model(cache_key, lambda : e.body, time_expire=time_expire)
http, status = HTTP(e.status, rtn, **e.headers), e.status http, status = HTTP(e.status, rtn, **e.headers), e.status
else: else:
# action raised a generic exception #action raised a generic exception
http = None http = None
else: else:
# no server-cache side involved #no server-cache side involved
try: try:
# action returns something #action returns something
rtn = func() rtn = func()
http, status = None, current.response.status http, status = None, current.response.status
except HTTP, e: except HTTP, e:
# action raises HTTP (can still be valid) #action raises HTTP (can still be valid)
status = e.status status = e.status
http = HTTP(e.status, e.body, **e.headers) http = HTTP(e.status, e.body, **e.headers)
else: else:
# action raised a generic exception #action raised a generic exception
http = None http = None
send_headers = False send_headers = False
if http and isinstance(valid_statuses, list): if http and isinstance(valid_statuses, list):
@@ -665,13 +675,15 @@ class Cache(object):
if str(status)[0] in '123': if str(status)[0] in '123':
send_headers = True send_headers = True
if send_headers: if send_headers:
headers = {'Pragma': None, headers = {
'Expires': expires, 'Pragma' : None,
'Cache-Control': cache_control} 'Expires' : expires,
'Cache-Control' : cache_control
}
current.response.headers.update(headers) current.response.headers.update(headers)
if cache_model and not send_headers: if cache_model and not send_headers:
# we cached already the value, but the status is not valid #we cached already the value, but the status is not valid
# so we need to delete the cached value #so we need to delete the cached value
cache_model(cache_key, None) cache_model(cache_key, None)
if http: if http:
if send_headers: if send_headers:
@@ -728,7 +740,8 @@ class Cache(object):
allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix') allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
it will add prefix to all the cache keys used. 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) 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'): def lazy_cache(key=None, time_expire=None, cache_model='ram'):
+1 -21
View File
@@ -35,6 +35,7 @@ from gluon.serializers import json_parser
locker = thread.allocate_lock() locker = thread.allocate_lock()
def AppConfig(*args, **vars): def AppConfig(*args, **vars):
locker.acquire() locker.acquire()
@@ -58,27 +59,6 @@ class AppConfigDict(dict):
dict.__init__(self, *args, **kwargs) dict.__init__(self, *args, **kwargs)
self.int_cache = {} self.int_cache = {}
def get(self, path, default=None):
try:
value = self.take(path).strip()
if value.lower() in ('none','null',''):
return None
elif value.lower() == 'true':
return True
elif value.lower() == 'false':
return False
elif value.isdigit() or (value[0]=='-' and value[1:].isdigit()):
return int(value)
elif ',' in value:
return map(lambda x:x.strip(),value.split(','))
else:
try:
return float(value)
except:
return value
except:
return default
def take(self, path, cast=None): def take(self, path, cast=None):
parts = path.split('.') parts = path.split('.')
if path in self.int_cache: if path in self.int_cache:
File diff suppressed because it is too large Load Diff
+4 -11
View File
@@ -27,10 +27,10 @@ from gluon import current
class RESIZE(object): class RESIZE(object):
def __init__(self, nx=160, ny=80, quality=100, padding = False, def __init__(self, nx=160, ny=80, quality=100,
error_message=' image resize'): error_message=' image resize'):
(self.nx, self.ny, self.quality, self.error_message, self.padding) = ( (self.nx, self.ny, self.quality, self.error_message) = (
nx, ny, quality, error_message, padding) nx, ny, quality, error_message)
def __call__(self, value): def __call__(self, value):
if isinstance(value, str) and len(value) == 0: if isinstance(value, str) and len(value) == 0:
@@ -41,14 +41,7 @@ class RESIZE(object):
img = Image.open(value.file) img = Image.open(value.file)
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS) img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
s = cStringIO.StringIO() s = cStringIO.StringIO()
if self.padding: img.save(s, 'JPEG', quality=self.quality)
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
background.paste(
img,
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
background.save(s, 'JPEG', quality=self.quality)
else:
img.save(s, 'JPEG', queality=self.quality)
s.seek(0) s.seek(0)
value.file = s value.file = s
except: except:
+3 -24
View File
@@ -36,13 +36,11 @@ def ldap_auth(server='ldap',
user_lastname_attrib='cn:2', user_lastname_attrib='cn:2',
user_mail_attrib='mail', user_mail_attrib='mail',
manage_groups=False, manage_groups=False,
manage_groups_callback=[],
db=None, db=None,
group_dn=None, group_dn=None,
group_name_attrib='cn', group_name_attrib='cn',
group_member_attrib='memberUid', group_member_attrib='memberUid',
group_filterstr='objectClass=*', group_filterstr='objectClass=*',
group_mapping={},
tls=False, tls=False,
logging_level='error'): logging_level='error'):
@@ -209,7 +207,6 @@ def ldap_auth(server='ldap',
user_mail_attrib=user_mail_attrib, user_mail_attrib=user_mail_attrib,
manage_groups=manage_groups, manage_groups=manage_groups,
allowed_groups=allowed_groups, allowed_groups=allowed_groups,
group_mapping=group_mapping,
db=db): db=db):
if password == '': # http://tools.ietf.org/html/rfc4513#section-5.1.2 if password == '': # http://tools.ietf.org/html/rfc4513#section-5.1.2
logger.warning('blank password not allowed') logger.warning('blank password not allowed')
@@ -265,7 +262,6 @@ def ldap_auth(server='ldap',
requested_attrs = ['sAMAccountName'] requested_attrs = ['sAMAccountName']
if manage_user: if manage_user:
requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib]) requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
result = con.search_ext_s( result = con.search_ext_s(
ldap_basedn, ldap.SCOPE_SUBTREE, ldap_basedn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr), "(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr),
@@ -425,8 +421,7 @@ def ldap_auth(server='ldap',
store_user_mail = None store_user_mail = None
update_or_insert_values = {'first_name': store_user_firstname, update_or_insert_values = {'first_name': store_user_firstname,
'last_name': store_user_lastname, 'last_name': store_user_lastname,
'email': store_user_mail, 'email': store_user_mail}
'username': username}
if '@' not in username: if '@' not in username:
# user as username # user as username
# ################ # ################
@@ -448,7 +443,7 @@ def ldap_auth(server='ldap',
con.unbind() con.unbind()
if manage_groups: if manage_groups:
if not do_manage_groups(username, password, group_mapping): if not do_manage_groups(username, password):
return False return False
return True return True
except ldap.INVALID_CREDENTIALS, e: except ldap.INVALID_CREDENTIALS, e:
@@ -486,7 +481,7 @@ def ldap_auth(server='ldap',
# No match # No match
return False return False
def do_manage_groups(username, password=None, group_mapping={}, db=db): def do_manage_groups(username, password=None, db=db):
""" """
Manage user groups Manage user groups
@@ -502,14 +497,6 @@ def ldap_auth(server='ldap',
ldap_groups_of_the_user = get_user_groups_from_ldap( ldap_groups_of_the_user = get_user_groups_from_ldap(
username, password) username, password)
if group_mapping != {}:
l = []
for group in ldap_groups_of_the_user:
if group in group_mapping:
l += group_mapping[group]
ldap_groups_of_the_user = l
logging.info("User groups after remapping: %s" % str(l))
# #
# Get all group name where the user is in actually in local db # Get all group name where the user is in actually in local db
# ############################################################# # #############################################################
@@ -552,7 +539,6 @@ def ldap_auth(server='ldap',
db_groups_of_the_user.append(group.role) db_groups_of_the_user.append(group.role)
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user))) logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
auth_membership_changed = False
# #
# Delete user membership from groups where user is not anymore # Delete user membership from groups where user is not anymore
# ############################################################# # #############################################################
@@ -560,7 +546,6 @@ def ldap_auth(server='ldap',
if ldap_groups_of_the_user.count(group_to_del) == 0: if ldap_groups_of_the_user.count(group_to_del) == 0:
db((db.auth_membership.user_id == db_user_id) & db((db.auth_membership.user_id == db_user_id) &
(db.auth_membership.group_id == db_group_id[group_to_del])).delete() (db.auth_membership.group_id == db_group_id[group_to_del])).delete()
auth_membership_changed = True
# #
# Create user membership in groups where user is not in already # Create user membership in groups where user is not in already
@@ -572,12 +557,6 @@ def ldap_auth(server='ldap',
else: else:
gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id
db.auth_membership.insert(user_id=db_user_id, group_id=gid) db.auth_membership.insert(user_id=db_user_id, group_id=gid)
auth_membership_changed = True
if auth_membership_changed:
for callback in manage_groups_callback:
callback()
except: except:
logger.warning("[%s] Groups are not managed successfully!" % str(username)) logger.warning("[%s] Groups are not managed successfully!" % str(username))
import traceback import traceback
@@ -51,7 +51,7 @@ class OneallAccount(object):
reg_id=profile.get('identity_token','') reg_id=profile.get('identity_token','')
username=profile.get('preferredUsername',email) username=profile.get('preferredUsername',email)
first_name=name.get('givenName', dname.split(' ')[0]) first_name=name.get('givenName', dname.split(' ')[0])
last_name=profile.get('familyName', dname.split(' ')[1] if(dname.count(' ') > 0) else None) last_name=profile.get('familyName',dname.split(' ')[1])
return dict(registration_id=reg_id,username=username,email=email, return dict(registration_id=reg_id,username=username,email=email,
first_name=first_name,last_name=last_name) first_name=first_name,last_name=last_name)
self.mappings.default = defaultmapping self.mappings.default = defaultmapping
+24 -117
View File
@@ -53,9 +53,8 @@ see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
* header-ids: Adds "id" attributes to headers. The id value is a slug of * header-ids: Adds "id" attributes to headers. The id value is a slug of
the header text. the header text.
* html-classes: Takes a dict mapping html tag names (lowercase) to a * html-classes: Takes a dict mapping html tag names (lowercase) to a
string to use for a "class" tag attribute. Currently only supports "img", string to use for a "class" tag attribute. Currently only supports
"table", "pre" and "code" tags. Add an issue if you require this for other "pre" and "code" tags. Add an issue if you require this for other tags.
tags.
* markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to * markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to
have markdown processing be done on its contents. Similar to have markdown processing be done on its contents. Similar to
<http://michelf.com/projects/php-markdown/extra/#markdown-attr> but with <http://michelf.com/projects/php-markdown/extra/#markdown-attr> but with
@@ -71,14 +70,9 @@ see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
* smarty-pants: Replaces ' and " with curly quotation marks or curly * smarty-pants: Replaces ' and " with curly quotation marks or curly
apostrophes. Replaces --, ---, ..., and . . . with en dashes, em dashes, apostrophes. Replaces --, ---, ..., and . . . with en dashes, em dashes,
and ellipses. and ellipses.
* spoiler: A special kind of blockquote commonly hidden behind a
click on SO. Syntax per <http://meta.stackexchange.com/a/72878>.
* toc: The returned HTML string gets a new "toc_html" attribute which is * toc: The returned HTML string gets a new "toc_html" attribute which is
a Table of Contents for the document. (experimental) a Table of Contents for the document. (experimental)
* xml: Passes one-liner processing instructions and namespaced XML tags. * xml: Passes one-liner processing instructions and namespaced XML tags.
* tables: Tables using the same format as GFM
<https://help.github.com/articles/github-flavored-markdown#tables> and
PHP-Markdown Extra <https://michelf.ca/projects/php-markdown/extra/#table>.
* wiki-tables: Google Code Wiki-style tables. See * wiki-tables: Google Code Wiki-style tables. See
<http://code.google.com/p/support/wiki/WikiSyntax#Tables>. <http://code.google.com/p/support/wiki/WikiSyntax#Tables>.
""" """
@@ -88,11 +82,13 @@ see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
# not yet sure if there implications with this. Compare 'pydoc sre' # not yet sure if there implications with this. Compare 'pydoc sre'
# and 'perldoc perlre'. # and 'perldoc perlre'.
__version_info__ = (2, 3, 1) __version_info__ = (2, 2, 4)
__version__ = '.'.join(map(str, __version_info__)) __version__ = '.'.join(map(str, __version_info__))
__author__ = "Trent Mick" __author__ = "Trent Mick"
import os
import sys import sys
from pprint import pprint
import re import re
import logging import logging
try: try:
@@ -106,7 +102,13 @@ import codecs
#---- Python version compat #---- Python version compat
try:
from urllib.parse import quote # python3
except ImportError:
from urllib import quote # python2
if sys.version_info[:2] < (2,4): if sys.version_info[:2] < (2,4):
from sets import Set as set
def reversed(sequence): def reversed(sequence):
for i in sequence[::-1]: for i in sequence[::-1]:
yield i yield i
@@ -802,8 +804,6 @@ class Markdown(object):
text = self._prepare_pyshell_blocks(text) text = self._prepare_pyshell_blocks(text)
if "wiki-tables" in self.extras: if "wiki-tables" in self.extras:
text = self._do_wiki_tables(text) text = self._do_wiki_tables(text)
if "tables" in self.extras:
text = self._do_tables(text)
text = self._do_code_blocks(text) text = self._do_code_blocks(text)
@@ -844,79 +844,6 @@ class Markdown(object):
return _pyshell_block_re.sub(self._pyshell_block_sub, text) return _pyshell_block_re.sub(self._pyshell_block_sub, text)
def _table_sub(self, match):
trim_space_re = '^[ \t\n]+|[ \t\n]+$'
trim_bar_re = '^\||\|$'
head, underline, body = match.groups()
# Determine aligns for columns.
cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", underline)).split('|')]
align_from_col_idx = {}
for col_idx, col in enumerate(cols):
if col[0] == ':' and col[-1] == ':':
align_from_col_idx[col_idx] = ' align="center"'
elif col[0] == ':':
align_from_col_idx[col_idx] = ' align="left"'
elif col[-1] == ':':
align_from_col_idx[col_idx] = ' align="right"'
# thead
hlines = ['<table%s>' % self._html_class_str_from_tag('table'), '<thead>', '<tr>']
cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", head)).split('|')]
for col_idx, col in enumerate(cols):
hlines.append(' <th%s>%s</th>' % (
align_from_col_idx.get(col_idx, ''),
self._run_span_gamut(col)
))
hlines.append('</tr>')
hlines.append('</thead>')
# tbody
hlines.append('<tbody>')
for line in body.strip('\n').split('\n'):
hlines.append('<tr>')
cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", line)).split('|')]
for col_idx, col in enumerate(cols):
hlines.append(' <td%s>%s</td>' % (
align_from_col_idx.get(col_idx, ''),
self._run_span_gamut(col)
))
hlines.append('</tr>')
hlines.append('</tbody>')
hlines.append('</table>')
return '\n'.join(hlines) + '\n'
def _do_tables(self, text):
"""Copying PHP-Markdown and GFM table syntax. Some regex borrowed from
https://github.com/michelf/php-markdown/blob/lib/Michelf/Markdown.php#L2538
"""
less_than_tab = self.tab_width - 1
table_re = re.compile(r'''
(?:(?<=\n\n)|\A\n?) # leading blank line
^[ ]{0,%d} # allowed whitespace
(.*[|].*) \n # $1: header row (at least one pipe)
^[ ]{0,%d} # allowed whitespace
( # $2: underline row
# underline row with leading bar
(?: \|\ *:?-+:?\ * )+ \|? \n
|
# or, underline row without leading bar
(?: \ *:?-+:?\ *\| )+ (?: \ *:?-+:?\ * )? \n
)
( # $3: data rows
(?:
^[ ]{0,%d}(?!\ ) # ensure line begins with 0 to less_than_tab spaces
.*\|.* \n
)+
)
''' % (less_than_tab, less_than_tab, less_than_tab), re.M | re.X)
return table_re.sub(self._table_sub, text)
def _wiki_table_sub(self, match): def _wiki_table_sub(self, match):
ttext = match.group(0).strip() ttext = match.group(0).strip()
#print 'wiki table: %r' % match.group(0) #print 'wiki table: %r' % match.group(0)
@@ -926,7 +853,7 @@ class Markdown(object):
row = [c.strip() for c in re.split(r'(?<!\\)\|\|', line)] row = [c.strip() for c in re.split(r'(?<!\\)\|\|', line)]
rows.append(row) rows.append(row)
#pprint(rows) #pprint(rows)
hlines = ['<table%s>' % self._html_class_str_from_tag('table'), '<tbody>'] hlines = ['<table>', '<tbody>']
for row in rows: for row in rows:
hrow = ['<tr>'] hrow = ['<tr>']
for cell in row: for cell in row:
@@ -972,9 +899,6 @@ class Markdown(object):
text = self._encode_amps_and_angles(text) text = self._encode_amps_and_angles(text)
if "strike" in self.extras:
text = self._do_strike(text)
text = self._do_italics_and_bold(text) text = self._do_italics_and_bold(text)
if "smarty-pants" in self.extras: if "smarty-pants" in self.extras:
@@ -1282,6 +1206,7 @@ class Markdown(object):
.replace('_', self._escape_table['_']) .replace('_', self._escape_table['_'])
title = self.titles.get(link_id) title = self.titles.get(link_id)
if title: if title:
before = title
title = _xml_escape_attr(title) \ title = _xml_escape_attr(title) \
.replace('*', self._escape_table['*']) \ .replace('*', self._escape_table['*']) \
.replace('_', self._escape_table['_']) .replace('_', self._escape_table['_'])
@@ -1493,6 +1418,7 @@ class Markdown(object):
def _list_item_sub(self, match): def _list_item_sub(self, match):
item = match.group(4) item = match.group(4)
leading_line = match.group(1) leading_line = match.group(1)
leading_space = match.group(2)
if leading_line or "\n\n" in item or self._last_li_endswith_two_eols: if leading_line or "\n\n" in item or self._last_li_endswith_two_eols:
item = self._run_block_gamut(self._outdent(item)) item = self._run_block_gamut(self._outdent(item))
else: else:
@@ -1728,11 +1654,6 @@ class Markdown(object):
self._escape_table[text] = hashed self._escape_table[text] = hashed
return hashed return hashed
_strike_re = re.compile(r"~~(?=\S)(.+?)(?<=\S)~~", re.S)
def _do_strike(self, text):
text = self._strike_re.sub(r"<strike>\1</strike>", text)
return text
_strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S) _strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S)
_em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S) _em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S)
_code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S) _code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S)
@@ -1793,53 +1714,38 @@ class Markdown(object):
text = text.replace(". . .", "&#8230;") text = text.replace(". . .", "&#8230;")
return text return text
_block_quote_base = r''' _block_quote_re = re.compile(r'''
( # Wrap whole match in \1 ( # Wrap whole match in \1
( (
^[ \t]*>%s[ \t]? # '>' at the start of a line ^[ \t]*>[ \t]? # '>' at the start of a line
.+\n # rest of the first line .+\n # rest of the first line
(.+\n)* # subsequent consecutive lines (.+\n)* # subsequent consecutive lines
\n* # blanks \n* # blanks
)+ )+
) )
''' ''', re.M | re.X)
_block_quote_re = re.compile(_block_quote_base % '', re.M | re.X)
_block_quote_re_spoiler = re.compile(_block_quote_base % '[ \t]*?!?', re.M | re.X)
_bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M); _bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M);
_bq_one_level_re_spoiler = re.compile('^[ \t]*>[ \t]*?![ \t]?', re.M);
_bq_all_lines_spoilers = re.compile(r'\A(?:^[ \t]*>[ \t]*?!.*[\n\r]*)+\Z', re.M)
_html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S) _html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S)
def _dedent_two_spaces_sub(self, match): def _dedent_two_spaces_sub(self, match):
return re.sub(r'(?m)^ ', '', match.group(1)) return re.sub(r'(?m)^ ', '', match.group(1))
def _block_quote_sub(self, match): def _block_quote_sub(self, match):
bq = match.group(1) bq = match.group(1)
is_spoiler = 'spoiler' in self.extras and self._bq_all_lines_spoilers.match(bq) bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting
# trim one level of quoting bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines
if is_spoiler:
bq = self._bq_one_level_re_spoiler.sub('', bq)
else:
bq = self._bq_one_level_re.sub('', bq)
# trim whitespace-only lines
bq = self._ws_only_line_re.sub('', bq)
bq = self._run_block_gamut(bq) # recurse bq = self._run_block_gamut(bq) # recurse
bq = re.sub('(?m)^', ' ', bq) bq = re.sub('(?m)^', ' ', bq)
# These leading spaces screw with <pre> content, so we need to fix that: # These leading spaces screw with <pre> content, so we need to fix that:
bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq) bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
if is_spoiler: return "<blockquote>\n%s\n</blockquote>\n\n" % bq
return '<blockquote class="spoiler">\n%s\n</blockquote>\n\n' % bq
else:
return '<blockquote>\n%s\n</blockquote>\n\n' % bq
def _do_block_quotes(self, text): def _do_block_quotes(self, text):
if '>' not in text: if '>' not in text:
return text return text
if 'spoiler' in self.extras: return self._block_quote_re.sub(self._block_quote_sub, text)
return self._block_quote_re_spoiler.sub(self._block_quote_sub, text)
else:
return self._block_quote_re.sub(self._block_quote_sub, text)
def _form_paragraphs(self, text): def _form_paragraphs(self, text):
# Strip leading and trailing lines: # Strip leading and trailing lines:
@@ -2147,6 +2053,7 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False):
if DEBUG: if DEBUG:
print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
% (tabsize, skip_first_line)) % (tabsize, skip_first_line))
indents = []
margin = None margin = None
for i, line in enumerate(lines): for i, line in enumerate(lines):
if i == 0 and skip_first_line: continue if i == 0 and skip_first_line: continue
@@ -2455,4 +2362,4 @@ def main(argv=None):
if __name__ == "__main__": if __name__ == "__main__":
sys.exit( main(sys.argv) ) sys.exit( main(sys.argv) )
+254 -286
View File
@@ -7,11 +7,10 @@ import re
import urllib import urllib
from cgi import escape from cgi import escape
from string import maketrans from string import maketrans
try: try:
from ast import parse as ast_parse from ast import parse as ast_parse
import ast import ast
except ImportError: # python 2.5 except ImportError: # python 2.5
from compiler import parse from compiler import parse
import compiler.ast as ast import compiler.ast as ast
@@ -531,47 +530,41 @@ As shown in Ref.!`!`mdipierro`!`!:cite
``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them. ``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them.
""" """
html_colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', html_colors=['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
'silver', 'teal', 'white', 'yellow'] 'silver', 'teal', 'white', 'yellow']
META = '\x06' META = '\x06'
LINK = '\x07' LINK = '\x07'
DISABLED_META = '\x08' DISABLED_META = '\x08'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />' LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
regex_URL = re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?') regex_URL=re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env2 = re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}') regex_env2=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('(' + META + '|' + DISABLED_META + '|````)') regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+'|````)')
regex_dd = re.compile(r'\$\$(?P<latex>.*?)\$\$') regex_dd=re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile( regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
'(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)', regex_strong=re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
re.S) regex_del=re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*') regex_em=re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
regex_del = re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~') regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''") regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$") regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_list = re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$') regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_bq_headline = re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq = re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M) regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)', re.M) regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)',re.M)
regex_link = re.compile(r'(' + LINK + r')|\[\[(?P<s>.+?)\]\]', re.S) regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
regex_link_level2 = re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$', re.S) regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
regex_media_level2 = re.compile( regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',
re.S)
regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])") regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])") regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05') ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05')
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n") ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05',"'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*') regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def make_dict(b): def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':", b) return '{%s}' % regex_quote.sub("'\g<name>':",b)
def safe_eval(node_or_string, env): def safe_eval(node_or_string, env):
""" """
Safely evaluate an expression node or a string containing a Python Safely evaluate an expression node or a string containing a Python
@@ -585,7 +578,6 @@ def safe_eval(node_or_string, env):
node_or_string = ast_parse(node_or_string, mode='eval') node_or_string = ast_parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression): if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body node_or_string = node_or_string.body
def _convert(node): def _convert(node):
if isinstance(node, ast.Str): if isinstance(node, ast.Str):
return node.s return node.s
@@ -602,11 +594,11 @@ def safe_eval(node_or_string, env):
if node.id in _safe_names: if node.id in _safe_names:
return _safe_names[node.id] return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \ elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \ isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \ isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \ isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \ isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)): isinstance(node.left.n, (int, long, float)):
left = node.left.n left = node.left.n
right = node.right.n right = node.right.n
if isinstance(node.op, Add): if isinstance(node.op, Add):
@@ -614,66 +606,57 @@ def safe_eval(node_or_string, env):
else: else:
return left - right return left - right
raise ValueError('malformed string') raise ValueError('malformed string')
return _convert(node_or_string) return _convert(node_or_string)
def markmin_escape(text): def markmin_escape(text):
""" insert \\ before markmin control characters: '`:*~[]{}@$ """ """ insert \\ before markmin control characters: '`:*~[]{}@$ """
return regex_markmin_escape.sub( return regex_markmin_escape.sub(
lambda m: '\\' + m.group(0).replace('\\', '\\\\'), text) lambda m: '\\'+m.group(0).replace('\\','\\\\'), text)
def replace_autolinks(text,autolinks):
def replace_autolinks(text, autolinks):
return regex_auto.sub(lambda m: autolinks(m.group('k')), text) return regex_auto.sub(lambda m: autolinks(m.group('k')), text)
def replace_at_urls(text,url):
def replace_at_urls(text, url):
# this is experimental @{function/args} # this is experimental @{function/args}
def u1(match, url=url): def u1(match,url=url):
a, c, f, args = match.group('a', 'c', 'f', 'args') a,c,f,args = match.group('a','c','f','args')
return url(a=a or None, c=c or None, f=f or None, return url(a=a or None,c=c or None,f = f or None,
args=(args or '').split('/'), scheme=True, host=True) args=(args or '').split('/'), scheme=True, host=True)
return regex_URL.sub(u1,text)
return regex_URL.sub(u1, text) def replace_components(text,env):
def replace_components(text, env):
# not perfect but acceptable # not perfect but acceptable
def u2(match, env=env): def u2(match, env=env):
f = env.get(match.group('a'), match.group(0)) f = env.get(match.group('a'), match.group(0))
if callable(f): if callable(f):
b = match.group('b') b = match.group('b')
try: try:
b = safe_eval(make_dict(b), env) b = safe_eval(make_dict(b),env)
except: except:
pass pass
try: try:
f = f(**b) if isinstance(b, dict) else f(b) f = f(**b) if isinstance(b,dict) else f(b)
except Exception, e: except Exception, e:
f = 'ERROR: %s' % e f = 'ERROR: %s' % e
return str(f) return str(f)
text = regex_env2.sub(u2, text) text = regex_env2.sub(u2, text)
return text return text
def autolinks_simple(url): def autolinks_simple(url):
""" """
it automatically converts the url to link, it automatically converts the url to link,
image, video or audio tag image, video or audio tag
""" """
u_url = url.lower() u_url=url.lower()
if '@' in url and '://' not in url: if '@' in url and not '://' in url:
return '<a href="mailto:%s">%s</a>' % (url, url) return '<a href="mailto:%s">%s</a>' % (url, url)
elif u_url.endswith(('.jpg', '.jpeg', '.gif', '.png')): elif u_url.endswith(('.jpg','.jpeg','.gif','.png')):
return '<img src="%s" controls />' % url return '<img src="%s" controls />' % url
elif u_url.endswith(('.mp4', '.mpeg', '.mov', '.ogv')): elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
return '<video src="%s" controls></video>' % url return '<video src="%s" controls></video>' % url
elif u_url.endswith(('.mp3', '.wav', '.ogg')): elif u_url.endswith(('.mp3','.wav','.ogg')):
return '<audio src="%s" controls></audio>' % url return '<audio src="%s" controls></audio>' % url
return '<a href="%s">%s</a>' % (url, url) return '<a href="%s">%s</a>' % (url,url)
def protolinks_simple(proto, url): def protolinks_simple(proto, url):
""" """
@@ -684,18 +667,16 @@ def protolinks_simple(proto, url):
proto="iframe" proto="iframe"
url="http://www.example.com/path" url="http://www.example.com/path"
""" """
if proto in ('iframe', 'embed'): # == 'iframe': if proto in ('iframe','embed'): #== 'iframe':
return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>' % url return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>'%url
# elif proto == 'embed': # NOTE: embed is a synonym to iframe now #elif proto == 'embed': # NOTE: embed is a synonym to iframe now
# return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url) # return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url)
elif proto == 'qr': elif proto == 'qr':
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />' % url return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />'%url
return proto + ':' + url return proto+':'+url
def email_simple(email): def email_simple(email):
return '<a href="mailto:%s">%s</a>' % (email, email) return '<a href="mailto:%s">%s</a>' % (email, email)
def render(text, def render(text,
extra={}, extra={},
@@ -944,19 +925,17 @@ def render(text,
>>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]") >>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
'<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>' '<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>'
""" """
if autolinks == "default": if autolinks=="default": autolinks = autolinks_simple
autolinks = autolinks_simple if protolinks=="default": protolinks = protolinks_simple
if protolinks == "default": pp='\n' if pretty_print else ''
protolinks = protolinks_simple if isinstance(text,unicode):
pp = '\n' if pretty_print else ''
if isinstance(text, unicode):
text = text.encode('utf8') text = text.encode('utf8')
text = str(text or '') text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text) text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
text = text.replace('\x05', '').replace('\r\n', '\n') # concatenate strings separeted by \\n text = text.replace('\x05','').replace('\r\n', '\n') # concatenate strings separeted by \\n
if URL is not None: if URL is not None:
text = replace_at_urls(text, URL) text = replace_at_urls(text,URL)
if latex == 'google': if latex == 'google':
text = regex_dd.sub('``\g<latex>``:latex ', text) text = regex_dd.sub('``\g<latex>``:latex ', text)
@@ -966,10 +945,9 @@ def render(text,
# store them into segments they will be treated as code # store them into segments they will be treated as code
############################################################# #############################################################
segments = [] segments = []
def mark_code(m): def mark_code(m):
g = m.group(0) g = m.group(0)
if g in (META, DISABLED_META): if g in (META, DISABLED_META ):
segments.append((None, None, None, g)) segments.append((None, None, None, g))
return m.group() return m.group()
elif g == '````': elif g == '````':
@@ -978,12 +956,10 @@ def render(text,
else: else:
c = m.group('c') or '' c = m.group('c') or ''
p = m.group('p') or '' p = m.group('p') or ''
if 'code' in allowed and c not in allowed['code']: if 'code' in allowed and not c in allowed['code']: c = ''
c = '' code = m.group('t').replace('!`!','`')
code = m.group('t').replace('!`!', '`')
segments.append((code, c, p, m.group(0))) segments.append((code, c, p, m.group(0)))
return META return META
text = regex_code.sub(mark_code, text) text = regex_code.sub(mark_code, text)
############################################################# #############################################################
@@ -991,58 +967,56 @@ def render(text,
# store them into links they will be treated as link # store them into links they will be treated as link
############################################################# #############################################################
links = [] links = []
def mark_link(m): def mark_link(m):
links.append(None if m.group() == LINK links.append( None if m.group() == LINK
else m.group('s')) else m.group('s') )
return LINK return LINK
text = regex_link.sub(mark_link, text) text = regex_link.sub(mark_link, text)
text = escape(text) text = escape(text)
if protolinks: if protolinks:
text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text) text = regex_proto.sub(lambda m: protolinks(*m.group('p','k')), text)
if autolinks: if autolinks:
text = replace_autolinks(text, autolinks) text = replace_autolinks(text,autolinks)
############################################################# #############################################################
# normalize spaces # normalize spaces
############################################################# #############################################################
strings = text.split('\n') strings=text.split('\n')
def parse_title(t, s): # out, lev, etags, tag, s): def parse_title(t, s): #out, lev, etags, tag, s):
hlevel = str(len(t)) hlevel=str(len(t))
out.extend(etags[::-1]) out.extend(etags[::-1])
out.append("<h%s>%s" % (hlevel, s)) out.append("<h%s>%s"%(hlevel,s))
etags[:] = ["</h%s>%s" % (hlevel, pp)] etags[:]=["</h%s>%s"%(hlevel,pp)]
lev = 0 lev=0
ltags[:] = [] ltags[:]=[]
tlev[:] = [] tlev[:]=[]
return (lev, 'h') return (lev, 'h')
def parse_list(t, p, s, tag, lev, mtag, lineno): def parse_list(t, p, s, tag, lev, mtag, lineno):
lent = len(t) lent=len(t)
if lent < lev: # current item level < previous item level if lent<lev: # current item level < previous item level
while ltags[-1] > lent: while ltags[-1]>lent:
ltags.pop() ltags.pop()
out.append(etags.pop()) out.append(etags.pop())
lev = lent lev=lent
tlev[lev:] = [] tlev[lev:]=[]
if lent > lev: # current item level > previous item level if lent>lev: # current item level > previous item level
if lev == 0: # previous line is not a list (paragraph or title) if lev==0: # previous line is not a list (paragraph or title)
out.extend(etags[::-1]) out.extend(etags[::-1])
ltags[:] = [] ltags[:]=[]
tlev[:] = [] tlev[:]=[]
etags[:] = [] etags[:]=[]
if pend and mtag == '.': # paragraph in a list: if pend and mtag == '.': # paragraph in a list:
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
for i in xrange(lent - lev): for i in xrange(lent-lev):
out.append('<' + tag + '>' + pp) out.append('<'+tag+'>'+pp)
etags.append('</' + tag + '>' + pp) etags.append('</'+tag+'>'+pp)
lev += 1 lev+=1
ltags.append(lev) ltags.append(lev)
tlev.append(tag) tlev.append(tag)
elif lent == lev: elif lent == lev:
@@ -1051,22 +1025,22 @@ def render(text,
for i in xrange(ltags.count(lent)): for i in xrange(ltags.count(lent)):
ltags.pop() ltags.pop()
out.append(etags.pop()) out.append(etags.pop())
tlev[-1] = tag tlev[-1]=tag
out.append('<' + tag + '>' + pp) out.append('<'+tag+'>'+pp)
etags.append('</' + tag + '>' + pp) etags.append('</'+tag+'>'+pp)
ltags.append(lev) ltags.append(lev)
else: else:
if ltags.count(lev) > 1: if ltags.count(lev)>1:
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
mtag = 'l' mtag='l'
out.append('<li>') out.append('<li>')
etags.append('</li>' + pp) etags.append('</li>'+pp)
ltags.append(lev) ltags.append(lev)
if s[:1] == '-': if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno) (s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if p and mtag == 'l': if p and mtag=='l':
(lev, mtag, lineno) = parse_point(t, s, lev, '', lineno) (lev,mtag,lineno)=parse_point(t, s, lev, '', lineno)
else: else:
out.append(s) out.append(s)
@@ -1074,28 +1048,28 @@ def render(text,
def parse_point(t, s, lev, mtag, lineno): def parse_point(t, s, lev, mtag, lineno):
""" paragraphs in lists """ """ paragraphs in lists """
lent = len(t) lent=len(t)
if lent > lev: if lent>lev:
return parse_list(t, '.', s, 'ul', lev, mtag, lineno) return parse_list(t, '.', s, 'ul', lev, mtag, lineno)
elif lent < lev: elif lent<lev:
while ltags[-1] > lent: while ltags[-1]>lent:
ltags.pop() ltags.pop()
out.append(etags.pop()) out.append(etags.pop())
lev = lent lev=lent
tlev[lev:] = [] tlev[lev:]=[]
mtag = '' mtag=''
elif lent == lev: elif lent==lev:
if pend and mtag == '.': if pend and mtag == '.':
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
if br and mtag in ('l', '.'): if br and mtag in ('l','.'):
out.append(br) out.append(br)
if s == META: if s == META:
mtag = '' mtag = ''
else: else:
mtag = '.' mtag = '.'
if s[:1] == '-': if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno) (s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if mtag == '.': if mtag == '.':
out.append(pbeg) out.append(pbeg)
if pend: if pend:
@@ -1109,19 +1083,19 @@ def render(text,
# - is empty -> this is an <hr /> tag # - is empty -> this is an <hr /> tag
# - consists '|' -> table # - consists '|' -> table
# - consists other characters -> blockquote # - consists other characters -> blockquote
if (lineno + 1 >= strings_len or if (lineno+1 >= strings_len or
not (s.count('-') == len(s) and len(s) > 3)): not(s.count('-') == len(s) and len(s)>3)):
return (s, mtag, lineno) return (s, mtag, lineno)
lineno += 1 lineno+=1
s = strings[lineno].strip() s = strings[lineno].strip()
if s: if s:
if '|' in s: if '|' in s:
# table # table
tout = [] tout=[]
thead = [] thead=[]
tbody = [] tbody=[]
rownum = 0 rownum=0
t_id = '' t_id = ''
t_cls = '' t_cls = ''
@@ -1130,14 +1104,14 @@ def render(text,
s = strings[lineno].strip() s = strings[lineno].strip()
if s[:1] == '=': if s[:1] == '=':
# header or footer # header or footer
if s.count('=') == len(s) and len(s) > 3: if s.count('=')==len(s) and len(s)>3:
if not thead: # if thead list is empty: if not thead: # if thead list is empty:
thead = tout thead = tout
else: else:
tbody.extend(tout) tbody.extend(tout)
tout = [] tout = []
rownum = 0 rownum=0
lineno += 1 lineno+=1
continue continue
m = regex_tq.match(s) m = regex_tq.match(s)
@@ -1147,36 +1121,36 @@ def render(text,
break break
if rownum % 2: if rownum % 2:
tr = '<tr class="even">' tr = '<tr class="even">'
else: else:
tr = '<tr class="first">' if rownum == 0 else '<tr>' tr = '<tr class="first">' if rownum == 0 else '<tr>'
tout.append(tr + ''.join(['<td%s>%s</td>' % ( tout.append(tr + ''.join(['<td%s>%s</td>' % (
' class="num"' ' class="num"'
if regex_num.match(f) else '', if regex_num.match(f) else '',
f.strip() f.strip()
) for f in s.split('|')]) + '</tr>' + pp) ) for f in s.split('|')])+'</tr>'+pp)
rownum += 1 rownum+=1
lineno += 1 lineno+=1
t_cls = ' class="%s%s"' % (class_prefix, t_cls) \ t_cls = ' class="%s%s"'%(class_prefix, t_cls) \
if t_cls and t_cls != 'id' else '' if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"' % (id_prefix, t_id) if t_id else '' t_id = ' id="%s%s"'%(id_prefix, t_id) if t_id else ''
s = '' s = ''
if thead: if thead:
s += '<thead>' + pp + ''.join([l for l in thead]) + '</thead>' + pp s += '<thead>'+pp+''.join([l for l in thead])+'</thead>'+pp
if not tbody: # tbody strings are in tout list if not tbody: # tbody strings are in tout list
tbody = tout tbody = tout
tout = [] tout = []
if tbody: # if tbody list is not empty: if tbody: # if tbody list is not empty:
s += '<tbody>' + pp + ''.join([l for l in tbody]) + '</tbody>' + pp s += '<tbody>'+pp+''.join([l for l in tbody])+'</tbody>'+pp
if tout: # tfoot is not empty: if tout: # tfoot is not empty:
s += '<tfoot>' + pp + ''.join([l for l in tout]) + '</tfoot>' + pp s += '<tfoot>'+pp+''.join([l for l in tout])+'</tfoot>'+pp
s = '<table%s%s>%s%s</table>%s' % (t_cls, t_id, pp, s, pp) s = '<table%s%s>%s%s</table>%s' % (t_cls, t_id, pp, s, pp)
mtag = 't' mtag='t'
else: else:
# parse blockquote: # parse blockquote:
bq_begin = lineno bq_begin=lineno
t_mode = False # embedded table t_mode = False # embedded table
t_cls = '' t_cls = ''
t_id = '' t_id = ''
@@ -1186,57 +1160,57 @@ def render(text,
if not t_mode: if not t_mode:
m = regex_tq.match(s) m = regex_tq.match(s)
if m: if m:
if (lineno + 1 == strings_len or if (lineno+1 == strings_len or
'|' not in strings[lineno + 1]): '|' not in strings[lineno+1]):
t_cls = m.group('c') or '' t_cls = m.group('c') or ''
t_id = m.group('p') or '' t_id = m.group('p') or ''
break break
if regex_bq_headline.match(s): if regex_bq_headline.match(s):
if (lineno + 1 < strings_len and if (lineno+1 < strings_len and
strings[lineno + 1].strip()): strings[lineno+1].strip()):
t_mode = True t_mode = True
lineno += 1 lineno+=1
continue continue
elif regex_tq.match(s): elif regex_tq.match(s):
t_mode = False t_mode=False
lineno += 1 lineno+=1
continue continue
lineno += 1 lineno+=1
t_cls = ' class="%s%s"' % (class_prefix, t_cls) \ t_cls = ' class="%s%s"'%(class_prefix,t_cls) \
if t_cls and t_cls != 'id' else '' if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"' % (id_prefix, t_id) \ t_id = ' id="%s%s"'%(id_prefix,t_id) \
if t_id else '' if t_id else ''
s = '<blockquote%s%s>%s</blockquote>%s' \ s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls, % (t_cls,
t_id, t_id,
'\n'.join(strings[bq_begin:lineno]), pp) '\n'.join(strings[bq_begin:lineno]),pp)
mtag = 'q' mtag='q'
else: else:
s = '<hr />' s = '<hr />'
lineno -= 1 lineno-=1
mtag = 'q' mtag='q'
return (s, 'q', lineno) return (s, 'q', lineno)
if sep == 'p': if sep == 'p':
pbeg = "<p>" pbeg = "<p>"
pend = "</p>" + pp pend = "</p>"+pp
br = '' br = ''
else: else:
pbeg = pend = '' pbeg = pend = ''
br = "<br />" + pp if sep == 'br' else '' br = "<br />"+pp if sep=='br' else ''
lev = 0 # nesting level of lists lev = 0 # nesting level of lists
c0 = '' # first character of current line c0 = '' # first character of current line
out = [] # list of processed lines out = [] # list of processed lines
etags = [] # trailing tags etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag ltags = [] # level# correspondent to trailing tag
tlev = [] # list of tags for each level ('ul' or 'ol') tlev = [] # list of tags for each level ('ul' or 'ol')
mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/> mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/>
# and to avoid <p></p> around tables and blockquotes # and to avoid <p></p> around tables and blockquotes
lineno = 0 lineno = 0
strings_len = len(strings) strings_len = len(strings)
while lineno < strings_len: while lineno < strings_len:
@@ -1248,67 +1222,65 @@ def render(text,
#### ++++ ---- .... ------- field | field | field <-body #### ++++ ---- .... ------- field | field | field <-body
##### +++++ ----- ..... ---------------------:class[id] ##### +++++ ----- ..... ---------------------:class[id]
""" """
pc0 = c0 # first character of previous line pc0=c0 # first character of previous line
c0 = s[:1] c0=s[:1]
if c0: # for non empty strings if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - . if c0 in "#+-.": # first character is one of: # + - .
(t1, t2, p, ss) = regex_list.findall(s)[0] (t1,t2,p,ss) = regex_list.findall(s)[0]
# t1 - tag ("###") # t1 - tag ("###")
# t2 - tag ("+++", "---", "...") # t2 - tag ("+++", "---", "...")
# p - paragraph point ('.')->for "++." or "--." # p - paragraph point ('.')->for "++." or "--."
# ss - other part of string # ss - other part of string
if t1 or t2: if t1 or t2:
# headers and lists: # headers and lists:
if c0 == '#': # headers if c0 == '#': # headers
(lev, mtag) = parse_title(t1, ss) (lev, mtag) = parse_title(t1, ss)
lineno += 1 lineno+=1
continue continue
elif c0 == '+': # ordered list elif c0 == '+': # ordered list
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ol', lev, mtag, lineno) (lev, mtag, lineno)= parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno += 1 lineno+=1
continue continue
elif c0 == '-': # unordered list, table or blockquote elif c0 == '-': # unordered list, table or blockquote
if p or ss: if p or ss:
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno) (lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno)
lineno += 1 lineno+=1
continue continue
else: else:
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno) (s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
elif lev > 0: # and c0 == '.' # paragraph in lists elif lev>0: # and c0 == '.' # paragraph in lists
(lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno) (lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno)
lineno += 1 lineno+=1
continue continue
if lev == 0 and (mtag == 'q' or s == META): if lev == 0 and (mtag == 'q' or s == META):
# new paragraph # new paragraph
pc0 = '' pc0=''
if pc0 == '' or (mtag != 'p' and s0 not in (' ', '\t')): if pc0 == '' or (mtag != 'p' and s0 not in (' ','\t')):
# paragraph # paragraph
out.extend(etags[::-1]) out.extend(etags[::-1])
etags = [] etags=[]
ltags = [] ltags=[]
tlev = [] tlev=[]
lev = 0 lev=0
if br and mtag == 'p': if br and mtag == 'p': out.append(br)
out.append(br)
if mtag != 'q' and s != META: if mtag != 'q' and s != META:
if pend: if pend: etags=[pend]
etags = [pend] out.append(pbeg)
out.append(pbeg) mtag = 'p'
mtag = 'p'
else: else:
mtag = '' mtag = ''
out.append(s) out.append(s)
else: else:
if lev > 0 and mtag == '.' and s == META: if lev>0 and mtag=='.' and s == META:
out.append(etags.pop()) out.append(etags.pop())
ltags.pop() ltags.pop()
out.append(s) out.append(s)
mtag = '' mtag = ''
else: else:
out.append(' ' + s) out.append(' '+s)
lineno += 1 lineno+=1
out.extend(etags[::-1]) out.extend(etags[::-1])
text = ''.join(out) text = ''.join(out)
@@ -1323,7 +1295,7 @@ def render(text,
# deal with images, videos, audios and links # deal with images, videos, audios and links
############################################################# #############################################################
def sub_media(m): def sub_media(m):
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w') t,a,k,p,w = m.group('t','a','k','p','w')
if not k: if not k:
return m.group(0) return m.group(0)
k = escape(k) k = escape(k)
@@ -1333,40 +1305,40 @@ def render(text,
p_begin = p_end = '' p_begin = p_end = ''
if p == 'center': if p == 'center':
p_begin = '<p style="text-align:center">' p_begin = '<p style="text-align:center">'
p_end = '</p>' + pp p_end = '</p>'+pp
elif p == 'blockleft': elif p == 'blockleft':
p_begin = '<p style="text-align:left">' p_begin = '<p style="text-align:left">'
p_end = '</p>' + pp p_end = '</p>'+pp
elif p == 'blockright': elif p == 'blockright':
p_begin = '<p style="text-align:right">' p_begin = '<p style="text-align:right">'
p_end = '</p>' + pp p_end = '</p>'+pp
elif p in ('left', 'right'): elif p in ('left','right'):
style = ('float:%s' % p) + (';%s' % style if style else '') style = ('float:%s' % p)+(';%s' % style if style else '')
if t and regex_auto.match(t): if t and regex_auto.match(t):
p_begin = p_begin + '<a href="%s">' % t p_begin = p_begin + '<a href="%s">' % t
p_end = '</a>' + p_end p_end = '</a>' + p_end
t = '' t = ''
if style: if style:
style = ' style="%s"' % style style = ' style="%s"' % style
if p in ('video', 'audio'): if p in ('video','audio'):
t = render(t, {}, {}, 'br', URL, environment, latex, t = render(t, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print) autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \ return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, style=style, k=k, t=t) % dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"' % escape(t).replace(META, DISABLED_META) if t else '' alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \ return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end) % dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
def sub_link(m): def sub_link(m):
t, a, k, p = m.group('t', 'a', 'k', 'p') t,a,k,p = m.group('t','a','k','p')
if not k and not t: if not k and not t:
return m.group(0) return m.group(0)
t = t or '' t = t or ''
a = escape(a) if a else '' a = escape(a) if a else ''
if k: if k:
if '#' in k and ':' not in k.split('#')[0]: if '#' in k and not ':' in k.split('#')[0]:
# wikipage, not external url # wikipage, not external url
k = k.replace('#', '#' + id_prefix) k=k.replace('#','#'+id_prefix)
k = escape(k) k = escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else '' title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else '' target = ' target="_blank"' if p == 'popup' else ''
@@ -1375,18 +1347,18 @@ def render(text,
return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \ return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \
% dict(k=k, title=title, target=target, t=t) % dict(k=k, title=title, target=target, t=t)
if t == 'NEWLINE' and not a: if t == 'NEWLINE' and not a:
return '<br />' + pp return '<br />'+pp
return '<span class="anchor" id="%s">%s</span>' % ( return '<span class="anchor" id="%s">%s</span>' % (
escape(id_prefix + t), escape(id_prefix+t),
render(a, {}, {}, 'br', URL, render(a, {},{},'br', URL,
environment, latex, autolinks, environment, latex, autolinks,
protolinks, class_prefix, protolinks, class_prefix,
id_prefix, pretty_print)) id_prefix, pretty_print))
parts = text.split(LINK) parts = text.split(LINK)
text = parts[0] text = parts[0]
for i, s in enumerate(links): for i,s in enumerate(links):
if s is None: if s == None:
html = LINK html = LINK
else: else:
html = regex_media_level2.sub(sub_media, s) html = regex_media_level2.sub(sub_media, s)
@@ -1394,53 +1366,51 @@ def render(text,
html = regex_link_level2.sub(sub_link, html) html = regex_link_level2.sub(sub_link, html)
if html == s: if html == s:
# return unprocessed string as a signal of an error # return unprocessed string as a signal of an error
html = '[[%s]]' % s html = '[[%s]]'%s
text += html + parts[i + 1] text += html + parts[i+1]
############################################################# #############################################################
# process all code text # process all code text
############################################################# #############################################################
def expand_meta(m): def expand_meta(m):
code, b, p, s = segments.pop(0) code,b,p,s = segments.pop(0)
if code is None or m.group() == DISABLED_META: if code==None or m.group() == DISABLED_META:
return escape(s) return escape(s)
if b in extra: if b in extra:
if code[:1] == '\n': if code[:1]=='\n': code=code[1:]
code = code[1:] if code[-1:]=='\n': code=code[:-1]
if code[-1:] == '\n':
code = code[:-1]
if p: if p:
return str(extra[b](code, p)) return str(extra[b](code,p))
else: else:
return str(extra[b](code)) return str(extra[b](code))
elif b == 'cite': elif b=='cite':
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' % return '['+','.join('<a href="#%s" class="%s">%s</a>' \
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']' % (id_prefix+d,b,d) \
elif b == 'latex': for d in escape(code).split(','))+']'
elif b=='latex':
return LATEX % urllib.quote(code) return LATEX % urllib.quote(code)
elif b in html_colors: elif b in html_colors:
return '<span style="color: %s">%s</span>' \ return '<span style="color: %s">%s</span>' \
% (b, render(code, {}, {}, 'br', URL, environment, latex, % (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)) autolinks, protolinks, class_prefix, id_prefix, pretty_print))
elif b in ('c', 'color') and p: elif b in ('c', 'color') and p:
c = p.split(':') c=p.split(':')
fg = 'color: %s;' % c[0] if c[0] else '' fg='color: %s;' % c[0] if c[0] else ''
bg = 'background-color: %s;' % c[1] if len(c) > 1 and c[1] else '' bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else ''
return '<span style="%s%s">%s</span>' \ return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex, % (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)) autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else '' cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else ''
id = ' id="%s%s"' % (id_prefix, escape(p)) if p else '' id = ' id="%s%s"'%(id_prefix,escape(p)) if p else ''
beg = (code[:1] == '\n') beg=(code[:1]=='\n')
end = [None, -1][code[-1:] == '\n'] end=[None,-1][code[-1:]=='\n']
if beg and end: if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp) return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end])) return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text) text = regex_expand_meta.sub(expand_meta, text)
if environment: if environment:
text = replace_components(text, environment) text = replace_components(text,environment)
return text.translate(ttab_out) return text.translate(ttab_out)
@@ -1453,18 +1423,16 @@ def markmin2html(text, extra={}, allowed={}, sep='p',
class_prefix=class_prefix, id_prefix=id_prefix, class_prefix=class_prefix, id_prefix=id_prefix,
pretty_print=pretty_print) pretty_print=pretty_print)
def run_doctests(): def run_doctests():
import doctest import doctest
doctest.testmod() doctest.testmod()
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
import doctest import doctest
from textwrap import dedent from textwrap import dedent
html = dedent(""" html=dedent("""
<!doctype html> <!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
@@ -1478,7 +1446,7 @@ if __name__ == '__main__':
</html>""")[1:] </html>""")[1:]
if sys.argv[1:2] == ['-h']: if sys.argv[1:2] == ['-h']:
style = dedent(""" style=dedent("""
<style> <style>
blockquote { background-color: #FFFAAE; padding: 7px; } blockquote { background-color: #FFFAAE; padding: 7px; }
table { border-collapse: collapse; } table { border-collapse: collapse; }
@@ -1499,23 +1467,22 @@ if __name__ == '__main__':
body=markmin2html(__doc__, pretty_print=True)) body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']: elif sys.argv[1:2] == ['-t']:
from timeit import Timer from timeit import Timer
loops=1000
loops = 1000 ts = Timer("markmin2html(__doc__)","from markmin2html import markmin2html")
ts = Timer("markmin2html(__doc__)", "from markmin2html import markmin2html")
print 'timeit "markmin2html(__doc__)":' print 'timeit "markmin2html(__doc__)":'
t = min([ts.timeit(loops) for i in range(3)]) t = min([ts.timeit(loops) for i in range(3)])
print "%s loops, best of 3: %.3f ms per loop" % (loops, t / 1000 * loops) print "%s loops, best of 3: %.3f ms per loop" % (loops, t/1000*loops)
elif len(sys.argv) > 1: elif len(sys.argv) > 1:
fargv = open(sys.argv[1], 'r') fargv = open(sys.argv[1],'r')
try: try:
markmin_text = fargv.read() markmin_text=fargv.read()
# embed css file from second parameter into html file # embed css file from second parameter into html file
if len(sys.argv) > 2: if len(sys.argv) > 2:
if sys.argv[2].startswith('@'): if sys.argv[2].startswith('@'):
markmin_style = '<link rel="stylesheet" href="' + sys.argv[2][1:] + '"/>' markmin_style = '<link rel="stylesheet" href="'+sys.argv[2][1:]+'"/>'
else: else:
fargv2 = open(sys.argv[2], 'r') fargv2 = open(sys.argv[2],'r')
try: try:
markmin_style = "<style>\n" + fargv2.read() + "</style>" markmin_style = "<style>\n" + fargv2.read() + "</style>"
finally: finally:
@@ -1529,9 +1496,10 @@ if __name__ == '__main__':
fargv.close() fargv.close()
else: else:
print "Usage: " + sys.argv[0] + " -h | -t | file.markmin [file.css|@path_to/css]" print "Usage: "+sys.argv[0]+" -h | -t | file.markmin [file.css|@path_to/css]"
print "where: -h - print __doc__" print "where: -h - print __doc__"
print " -t - timeit __doc__ (for testing purpuse only)" print " -t - timeit __doc__ (for testing purpuse only)"
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)" print " file.markmin [file.css] - process file.markmin + built in file.css (optional)"
print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)" print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
run_doctests() run_doctests()
+117 -138
View File
@@ -7,57 +7,53 @@ import sys
import doctest import doctest
from optparse import OptionParser from optparse import OptionParser
__all__ = ['render', 'markmin2latex'] __all__ = ['render','markmin2latex']
META = 'META' META = 'META'
regex_newlines = re.compile('(\n\r)|(\r\n)') regex_newlines = re.compile('(\n\r)|(\r\n)')
regex_dd = re.compile('\$\$(?P<latex>.*?)\$\$') regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('(' + META + ')|(``(?P<t>.*?)``(:(?P<c>\w+))?)', re.S) regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)', re.M) regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M)
regex_maps = [ regex_maps = [
(re.compile('[ \t\r]+\n'), '\n'), (re.compile('[ \t\r]+\n'),'\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'), '{\\\\bf \g<t>}'), (re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"), '{\\it \g<t>}'), (re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'{\\it \g<t>}'),
(re.compile('^#{5,6}\s*(?P<t>[^\n]+)', re.M), '\n\n{\\\\bf \g<t>}\n'), (re.compile('^#{5,6}\s*(?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'), (re.compile('^#{4}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsection{\g<t>}\n'), (re.compile('^#{3}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\section{\g<t>}\n'), (re.compile('^#{2}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1}\s*(?P<t>[^\n]+)', re.M), ''), (re.compile('^#{1}\s*(?P<t>[^\n]+)',re.M),''),
(re.compile('^\- +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), (re.compile('^\- +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), (re.compile('^\+ +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'), '\n'), (re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'),'\n'),
(re.compile('\n\s+\n'), '\n\n')] (re.compile('\n\s+\n'),'\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n', re.M | re.S) regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S)
regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]') regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]')
regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]') regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]')
regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]') regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]')
regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]') regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]')
# regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]') #regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
# regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]') #regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]') regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]')
regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)', re.M) regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)',re.M)
regex_commas = re.compile('[ ]+(?P<t>[,;\.])') regex_commas = re.compile('[ ]+(?P<t>[,;\.])')
regex_noindent = re.compile('\n\n(?P<t>[a-z])') regex_noindent = re.compile('\n\n(?P<t>[a-z])')
#regex_quote_left = re.compile('"(?=\w)')
#regex_quote_right = re.compile('(?=\w\.)"')
def latex_escape(text,pound=True):
# regex_quote_left = re.compile('"(?=\w)') text=text.replace('\\','{\\textbackslash}')
# regex_quote_right = re.compile('(?=\w\.)"') for c in '^_&$%{}': text=text.replace(c,'\\'+c)
text=text.replace('\\{\\textbackslash\\}','{\\textbackslash}')
def latex_escape(text, pound=True): if pound: text=text.replace('#','\\#')
text = text.replace('\\', '{\\textbackslash}')
for c in '^_&$%{}':
text = text.replace(c, '\\' + c)
text = text.replace('\\{\\textbackslash\\}', '{\\textbackslash}')
if pound: text = text.replace('#', '\\#')
return text return text
def render(text, def render(text,
extra={}, extra={},
allowed={}, allowed={},
sep='p', sep='p',
image_mapper=lambda x: x, image_mapper=lambda x:x,
chapters=False): chapters=False):
############################################################# #############################################################
# replace all blocks marked with ``...``:class with META # replace all blocks marked with ``...``:class with META
@@ -65,68 +61,62 @@ def render(text,
############################################################# #############################################################
text = str(text or '') text = str(text or '')
segments, i = [], 0 segments, i = [], 0
text = regex_dd.sub('``\g<latex>``:latex ', text) text = regex_dd.sub('``\g<latex>``:latex ',text)
text = regex_newlines.sub('\n', text) text = regex_newlines.sub('\n',text)
while True: while True:
item = regex_code.search(text, i) item = regex_code.search(text,i)
if not item: if not item: break
break if item.group()==META:
if item.group() == META: segments.append((None,None))
segments.append((None, None)) text = text[:item.start()]+META+text[item.end():]
text = text[:item.start()] + META + text[item.end():]
else: else:
c = item.group('c') or '' c = item.group('c') or ''
if 'code' in allowed and c not in allowed['code']: if 'code' in allowed and not c in allowed['code']: c = ''
c = '' code = item.group('t').replace('!`!','`')
code = item.group('t').replace('!`!', '`') segments.append((code,c))
segments.append((code, c)) text = text[:item.start()]+META+text[item.end():]
text = text[:item.start()] + META + text[item.end():] i=item.start()+3
i = item.start() + 3
############################################################# #############################################################
# do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces # do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces
############################################################# #############################################################
title = regex_title.search(text) title = regex_title.search(text)
if not title: if not title: title='Title'
title = 'Title' else: title=title.group('t')
else:
title = title.group('t')
text = latex_escape(text, pound=False) text = latex_escape(text,pound=False)
texts = text.split('## References', 1) texts = text.split('## References',1)
text = regex_anchor.sub('\\label{\g<t>}', texts[0]) text = regex_anchor.sub('\\label{\g<t>}', texts[0])
if len(texts) == 2: if len(texts)==2:
text += '\n\\begin{thebibliography}{999}\n' text += '\n\\begin{thebibliography}{999}\n'
text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1]) text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1])
text += '\n\\end{thebibliography}\n' text += '\n\\end{thebibliography}\n'
text = '\n'.join(t.strip() for t in text.split('\n')) text = '\n'.join(t.strip() for t in text.split('\n'))
for regex, sub in regex_maps: for regex, sub in regex_maps:
text = regex.sub(sub, text) text = regex.sub(sub,text)
text = text.replace('#', '\\#') text=text.replace('#','\\#')
text = text.replace('`', "'") text=text.replace('`',"'")
############################################################# #############################################################
# process tables and blockquotes # process tables and blockquotes
############################################################# #############################################################
while True: while True:
item = regex_table.search(text) item = regex_table.search(text)
if not item: if not item: break
break
c = item.group('c') or '' c = item.group('c') or ''
if 'table' in allowed and c not in allowed['table']: if 'table' in allowed and not c in allowed['table']: c = ''
c = ''
content = item.group('t') content = item.group('t')
if ' | ' in content: if ' | ' in content:
rows = content.replace('\n', '\\\\\n').replace(' | ', ' & ') rows = content.replace('\n','\\\\\n').replace(' | ',' & ')
row0, row2 = rows.split('\\\\\n', 1) row0,row2 = rows.split('\\\\\n',1)
cols = row0.count(' & ') + 1 cols=row0.count(' & ')+1
cal = '{' + ''.join('l' for j in range(cols)) + '}' cal='{'+''.join('l' for j in range(cols))+'}'
tabular = '\\begin{center}\n{\\begin{tabular}' + cal + '\\hline\n' + row0 + '\\\\ \\hline\n' + row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}' tabular = '\\begin{center}\n{\\begin{tabular}'+cal+'\\hline\n' + row0+'\\\\ \\hline\n'+row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}'
if row2.count('\n') > 20: if row2.count('\n')>20: tabular='\\newpage\n'+tabular
tabular = '\\newpage\n' + tabular
text = text[:item.start()] + tabular + text[item.end():] text = text[:item.start()] + tabular + text[item.end():]
else: else:
text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():] text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():]
@@ -136,32 +126,29 @@ def render(text,
############################################################# #############################################################
def sub(x): def sub(x):
f = image_mapper(x.group('k')) f=image_mapper(x.group('k'))
if not f: if not f: return None
return None return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f)
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % f text = regex_image_width.sub(sub,text)
text = regex_image.sub(sub,text)
text = regex_image_width.sub(sub, text)
text = regex_image.sub(sub, text)
text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text) text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text)
text = regex_commas.sub('\g<t>', text) text = regex_commas.sub('\g<t>',text)
text = regex_noindent.sub('\n\\\\noindent \g<t>', text) text = regex_noindent.sub('\n\\\\noindent \g<t>',text)
# ## fix paths in images ### fix paths in images
regex = re.compile('\\\\_\w*\.(eps|png|jpg|gif)') regex=re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
while True: while True:
match = regex.search(text) match=regex.search(text)
if not match: if not match: break
break text=text[:match.start()]+text[match.start()+1:]
text = text[:match.start()] + text[match.start() + 1:] #text = regex_quote_left.sub('``',text)
# text = regex_quote_left.sub('``',text) #text = regex_quote_right.sub("''",text)
# text = regex_quote_right.sub("''",text)
if chapters: if chapters:
text = text.replace(r'\section*{', r'\chapter*{') text=text.replace(r'\section*{',r'\chapter*{')
text = text.replace(r'\section{', r'\chapter{') text=text.replace(r'\section{',r'\chapter{')
text = text.replace(r'subsection{', r'section{') text=text.replace(r'subsection{',r'section{')
############################################################# #############################################################
# process all code text # process all code text
@@ -169,64 +156,57 @@ def render(text,
parts = text.split(META) parts = text.split(META)
text = parts[0] text = parts[0]
authors = [] authors = []
for i, (code, b) in enumerate(segments): for i,(code,b) in enumerate(segments):
if code is None: if code==None:
html = META html = META
else: else:
if b == 'hidden': if b=='hidden':
html = '' html=''
elif b == 'author': elif b=='author':
author = latex_escape(code.strip()) author = latex_escape(code.strip())
authors.append(author) authors.append(author)
html = '' html=''
elif b == 'inxx': elif b=='inxx':
html = '\inxx{%s}' % latex_escape(code) html='\inxx{%s}' % latex_escape(code)
elif b == 'cite': elif b=='cite':
html = '~\cite{%s}' % latex_escape(code.strip()) html='~\cite{%s}' % latex_escape(code.strip())
elif b == 'ref': elif b=='ref':
html = '~\ref{%s}' % latex_escape(code.strip()) html='~\ref{%s}' % latex_escape(code.strip())
elif b == 'latex': elif b=='latex':
if '\n' in code: if '\n' in code:
html = '\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip() html='\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip()
else: else:
html = '$%s$' % code.strip() html='$%s$' % code.strip()
elif b == 'latex_eqnarray': elif b=='latex_eqnarray':
code = code.strip() code=code.strip()
code = '\\\\'.join(x.replace('=', '&=&', 1) for x in code.split('\\\\')) code='\\\\'.join(x.replace('=','&=&',1) for x in code.split('\\\\'))
html = '\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code html='\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
elif b.startswith('latex_'): elif b.startswith('latex_'):
key = b[6:] key=b[6:]
html = '\\begin{%s}%s\\end{%s}' % (key, code, key) html='\\begin{%s}%s\\end{%s}' % (key,code,key)
elif b in extra: elif b in extra:
if code[:1] == '\n': if code[:1]=='\n': code=code[1:]
code = code[1:] if code[-1:]=='\n': code=code[:-1]
if code[-1:] == '\n':
code = code[:-1]
html = extra[b](code) html = extra[b](code)
elif code[:1] == '\n' or code[:-1] == '\n': elif code[:1]=='\n' or code[:-1]=='\n':
if code[:1] == '\n': if code[:1]=='\n': code=code[1:]
code = code[1:] if code[-1:]=='\n': code=code[:-1]
if code[-1:] == '\n':
code = code[:-1]
if code.startswith('<') or code.startswith('{{') or code.startswith('http'): if code.startswith('<') or code.startswith('{{') or code.startswith('http'):
html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code
else: else:
html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code
else: else:
if code[:1] == '\n': if code[:1]=='\n': code=code[1:]
code = code[1:] if code[-1:]=='\n': code=code[:-1]
if code[-1:] == '\n':
code = code[:-1]
html = '{\\ft %s}' % latex_escape(code) html = '{\\ft %s}' % latex_escape(code)
try: try:
text = text + html + parts[i + 1] text = text+html+parts[i+1]
except: except:
text = text + '... WIKI PROCESSING ERROR ...' text = text + '... WIKI PROCESSING ERROR ...'
break break
text = text.replace(' ~\\cite', '~\\cite') text = text.replace(' ~\\cite','~\\cite')
return text, title, authors return text, title, authors
WRAPPER = """ WRAPPER = """
\\documentclass[12pt]{article} \\documentclass[12pt]{article}
\\usepackage{hyperref} \\usepackage{hyperref}
@@ -259,14 +239,12 @@ WRAPPER = """
\\end{document} \\end{document}
""" """
def markmin2latex(data, image_mapper=lambda x:x, extra={},
def markmin2latex(data, image_mapper=lambda x: x, extra={},
wrapper=WRAPPER): wrapper=WRAPPER):
body, title, authors = render(data, extra=extra, image_mapper=image_mapper) body, title, authors = render(data, extra=extra, image_mapper=image_mapper)
author = '\n\\and\n'.join(a.replace('\n', '\\\\\n\\footnotesize ') for a in authors) author = '\n\\and\n'.join(a.replace('\n','\\\\\n\\footnotesize ') for a in authors)
return wrapper % dict(title=title, author=author, body=body) return wrapper % dict(title=title, author=author, body=body)
if __name__ == '__main__': if __name__ == '__main__':
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--info", dest="info", parser.add_option("-i", "--info", dest="info",
@@ -274,39 +252,40 @@ if __name__ == '__main__':
parser.add_option("-t", "--test", dest="test", action="store_true", parser.add_option("-t", "--test", dest="test", action="store_true",
default=False) default=False)
parser.add_option("-n", "--no_wrapper", dest="no_wrapper", parser.add_option("-n", "--no_wrapper", dest="no_wrapper",
action="store_true", default=False) action="store_true",default=False)
parser.add_option("-c", "--chapters", dest="chapters", action="store_true", parser.add_option("-c", "--chapters", dest="chapters",action="store_true",
default=False, help="switch section for chapter") default=False,help="switch section for chapter")
parser.add_option("-w", "--wrapper", dest="wrapper", default=False, parser.add_option("-w", "--wrapper", dest="wrapper", default=False,
help="latex file containing header and footer") help="latex file containing header and footer")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if options.info: if options.info:
import markmin2html import markmin2html
markmin2latex(markmin2html.__doc__) markmin2latex(markmin2html.__doc__)
elif options.test: elif options.test:
doctest.testmod() doctest.testmod()
else: else:
if options.wrapper: if options.wrapper:
fwrapper = open(options.wrapper, 'rb') fwrapper = open(options.wrapper,'rb')
try: try:
wrapper = fwrapper.read() wrapper = fwrapper.read()
finally: finally:
fwrapper.close() fwrapper.close()
elif options.no_wrapper: elif options.no_wrapper:
wrapper = '%(body)s' wrapper = '%(body)s'
else: else:
wrapper = WRAPPER wrapper = WRAPPER
for f in args: for f in args:
fargs = open(f, 'r') fargs = open(f,'r')
content_data = [] content_data = []
try: try:
content_data.append(fargs.read()) content_data.append(fargs.read())
finally: finally:
fargs.close() fargs.close()
content = '\n'.join(content_data) content = '\n'.join(content_data)
output = markmin2latex(content, output= markmin2latex(content,
wrapper=wrapper, wrapper=wrapper,
chapters=options.chapters) chapters=options.chapters)
print output print output
+26 -27
View File
@@ -13,22 +13,21 @@ from markmin2latex import markmin2latex
__all__ = ['markmin2pdf'] __all__ = ['markmin2pdf']
def removeall(path): def removeall(path):
ERROR_STR = """Error removing %(path)s, %(error)s """
ERROR_STR= """Error removing %(path)s, %(error)s """
def rmgeneric(path, __func__): def rmgeneric(path, __func__):
try: try:
__func__(path) __func__(path)
except OSError, (errno, strerror): except OSError, (errno, strerror):
print ERROR_STR % {'path': path, 'error': strerror} print ERROR_STR % {'path' : path, 'error': strerror }
files = [path] files=[path]
while files: while files:
file = files[0] file=files[0]
if os.path.isfile(file): if os.path.isfile(file):
f = os.remove f=os.remove
rmgeneric(file, os.remove) rmgeneric(file, os.remove)
del files[0] del files[0]
elif os.path.isdir(file): elif os.path.isdir(file):
@@ -37,7 +36,7 @@ def removeall(path):
rmgeneric(file, os.rmdir) rmgeneric(file, os.rmdir)
del files[0] del files[0]
else: else:
files = [os.path.join(file, x) for x in nested] + files files = [os.path.join(file,x) for x in nested] + files
def latex2pdf(latex, pdflatex='pdflatex', passes=3): def latex2pdf(latex, pdflatex='pdflatex', passes=3):
@@ -50,13 +49,13 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
- passes: defines how often pdflates should be run in the texfile. - passes: defines how often pdflates should be run in the texfile.
""" """
pdflatex = pdflatex pdflatex=pdflatex
passes = passes passes=passes
warnings = [] warnings=[]
# setup the envoriment # setup the envoriment
tmpdir = mkdtemp() tmpdir = mkdtemp()
texfile = open(tmpdir + '/test.tex', 'wb') texfile = open(tmpdir+'/test.tex','wb')
texfile.write(latex) texfile.write(latex)
texfile.seek(0) texfile.seek(0)
texfile.close() texfile.close()
@@ -64,8 +63,8 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
# start doing some work # start doing some work
for i in range(0, passes): for i in range(0, passes):
logfd, logname = mkstemp() logfd,logname = mkstemp()
outfile = os.fdopen(logfd) outfile=os.fdopen(logfd)
try: try:
ret = subprocess.call([pdflatex, ret = subprocess.call([pdflatex,
'-interaction=nonstopmode', '-interaction=nonstopmode',
@@ -76,18 +75,18 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
finally: finally:
outfile.close() outfile.close()
re_errors = re.compile('^\!(.*)$', re.M) re_errors=re.compile('^\!(.*)$',re.M)
re_warnings = re.compile('^LaTeX Warning\:(.*)$', re.M) re_warnings=re.compile('^LaTeX Warning\:(.*)$',re.M)
flog = open(logname) flog = open(logname)
try: try:
loglines = flog.read() loglines = flog.read()
finally: finally:
flog.close() flog.close()
errors = re_errors.findall(loglines) errors=re_errors.findall(loglines)
warnings = re_warnings.findall(loglines) warnings=re_warnings.findall(loglines)
os.unlink(logname) os.unlink(logname)
pdffile = texfile.rsplit('.', 1)[0] + '.pdf' pdffile=texfile.rsplit('.',1)[0]+'.pdf'
if os.path.isfile(pdffile): if os.path.isfile(pdffile):
fpdf = open(pdffile, 'rb') fpdf = open(pdffile, 'rb')
try: try:
@@ -101,31 +100,31 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
def markmin2pdf(text, image_mapper=lambda x: None, extra={}): def markmin2pdf(text, image_mapper=lambda x: None, extra={}):
return latex2pdf(markmin2latex(text, image_mapper=image_mapper, extra=extra)) return latex2pdf(markmin2latex(text,image_mapper=image_mapper, extra=extra))
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
import doctest import doctest
import markmin2html import markmin2html
if sys.argv[1:2]==['-h']:
if sys.argv[1:2] == ['-h']:
data, warnings, errors = markmin2pdf(markmin2html.__doc__) data, warnings, errors = markmin2pdf(markmin2html.__doc__)
if errors: if errors:
print 'ERRORS:' + '\n'.join(errors) print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:' + '\n'.join(warnings) print 'WARNGINS:'+'\n'.join(warnings)
else: else:
print data print data
elif len(sys.argv) > 1: elif len(sys.argv)>1:
fargv = open(sys.argv[1], 'rb') fargv = open(sys.argv[1],'rb')
try: try:
data, warnings, errors = markmin2pdf(fargv.read()) data, warnings, errors = markmin2pdf(fargv.read())
finally: finally:
fargv.close() fargv.close()
if errors: if errors:
print 'ERRORS:' + '\n'.join(errors) print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:' + '\n'.join(warnings) print 'WARNGINS:'+'\n'.join(warnings)
else: else:
print data print data
else: else:
doctest.testmod() doctest.testmod()
+80 -90
View File
@@ -2,58 +2,42 @@
Developed by niphlod@gmail.com Developed by niphlod@gmail.com
Released under web2py license because includes gluon/cache.py source code Released under web2py license because includes gluon/cache.py source code
""" """
import redis
from redis.exceptions import ConnectionError
from gluon import current
from gluon.cache import CacheAbstract
try: try:
import cPickle as pickle import cPickle as pickle
except: except:
import pickle import pickle
import time import time
import re import re
import logging import logging
import thread import thread
import random import random
from gluon import current
from gluon.cache import CacheAbstract
from gluon.contrib.redis_utils import acquire_lock, release_lock
from gluon.contrib.redis_utils import register_release_lock, RConnectionError
logger = logging.getLogger("web2py.cache.redis") logger = logging.getLogger("web2py.cache.redis")
locker = thread.allocate_lock() locker = thread.allocate_lock()
def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=False, db=None): def RedisCache(*args, **vars):
""" """
Usage example: put in models:: Usage example: put in models
First of all install Redis from gluon.contrib.redis_cache import RedisCache
Ubuntu : cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True, password=None)
sudo apt-get install redis-server
sudo pip install redis
Then :param db: redis db to use (0..16)
:param debug: if True adds to stats() the total_hits and misses
from gluon.contrib.redis_utils import RConn :param with_lock: sets the default locking mode for creating new keys.
rconn = RConn()
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache(redis_conn=rconn, debug=True, with_lock=True)
Args:
redis_conn: a redis-like connection object
debug: if True adds to stats() the total_hits and misses
with_lock: sets the default locking mode for creating new keys.
By default is False (usualy when you choose Redis you do it By default is False (usualy when you choose Redis you do it
for performances reason) for performances reason)
When True, only one thread/process can set a value concurrently When True, only one thread/process can set a value concurrently
fail_gracefully: if redis is unavailable, returns the value computing it
instead of raising an exception
It can be used pretty much the same as cache.ram()
When you use cache.redis directly you can use :
redis_key_and_var_name = cache.redis('redis_key_and_var_name', lambda or function,
time_expire=time.time(), with_lock=True)
When you use cache.redis directly you can use
value = cache.redis('mykey', lambda: time.time(), with_lock=True)
to enforce locking. The with_lock parameter overrides the one set in the to enforce locking. The with_lock parameter overrides the one set in the
cache.redis instance creation cache.redis instance creation
@@ -85,9 +69,7 @@ def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=Fa
try: try:
instance_name = 'redis_instance_' + current.request.application instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisCache, instance_name): if not hasattr(RedisCache, instance_name):
setattr(RedisCache, instance_name, setattr(RedisCache, instance_name, RedisClient(*args, **vars))
RedisClient(redis_conn=redis_conn, debug=debug,
with_lock=with_lock, fail_gracefully=fail_gracefully))
return getattr(RedisCache, instance_name) return getattr(RedisCache, instance_name)
finally: finally:
locker.release() locker.release()
@@ -99,19 +81,22 @@ class RedisClient(object):
MAX_RETRIES = 5 MAX_RETRIES = 5
RETRIES = 0 RETRIES = 0
def __init__(self, redis_conn=None, debug=False, def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False, password=None):
with_lock=False, fail_gracefully=False): self.server = server
self.password = password
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
self.request = current.request self.request = current.request
self.debug = debug self.debug = debug
self.with_lock = with_lock self.with_lock = with_lock
self.fail_gracefully = fail_gracefully self.prefix = "w2p:%s:" % (self.request.application)
self.prefix = "w2p:cache:%s:" % self.request.application
if self.request: if self.request:
app = self.request.application app = self.request.application
else: else:
app = '' app = ''
if app not in self.meta_storage: if not app in self.meta_storage:
self.storage = self.meta_storage[app] = { self.storage = self.meta_storage[app] = {
CacheAbstract.cache_stats_name: { CacheAbstract.cache_stats_name: {
'hit_total': 0, 'hit_total': 0,
@@ -120,10 +105,9 @@ class RedisClient(object):
else: else:
self.storage = self.meta_storage[app] self.storage = self.meta_storage[app]
self.cache_set_key = 'w2p:%s:___cache_set' % self.request.application self.cache_set_key = 'w2p:%s:___cache_set' % (self.request.application)
self.r_server = redis_conn self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
self._release_script = register_release_lock(self.r_server)
def initialize(self): def initialize(self):
pass pass
@@ -137,86 +121,90 @@ class RedisClient(object):
value = None value = None
ttl = 0 ttl = 0
try: try:
# is there a value #is there a value
obj = self.r_server.get(newKey) obj = self.r_server.get(newKey)
# what's its ttl #what's its ttl
if obj: if obj:
ttl = self.r_server.ttl(newKey) ttl = self.r_server.ttl(newKey)
if ttl > time_expire: if ttl > time_expire:
obj = None obj = None
if obj: if obj:
# was cached #was cached
if self.debug: if self.debug:
self.r_server.incr('web2py_cache_statistics:hit_total') self.r_server.incr('web2py_cache_statistics:hit_total')
value = pickle.loads(obj) value = pickle.loads(obj)
elif f is None: elif f is None:
# delete and never look back #delete and never look back
self.r_server.delete(newKey) self.r_server.delete(newKey)
else: else:
# naive distributed locking #naive distributed locking
if with_lock: if with_lock:
lock_key = '%s:__lock' % newKey lock_key = '%s:__lock' % newKey
randomvalue = time.time() try:
al = acquire_lock(self.r_server, lock_key, randomvalue) while True:
# someone may have computed it lock = self.r_server.setnx(lock_key, 1)
obj = self.r_server.get(newKey) if lock:
if obj is None: value = self.cache_it(newKey, f, time_expire)
value = self.cache_it(newKey, f, time_expire) break
else: else:
value = pickle.loads(obj) time.sleep(0.2)
release_lock(self, lock_key, al) #did someone else create it in the meanwhile ?
obj = self.r_server.get(newKey)
if obj:
value = pickle.loads(obj)
break
finally:
self.r_server.delete(lock_key)
else: else:
# without distributed locking #without distributed locking
value = self.cache_it(newKey, f, time_expire) value = self.cache_it(newKey, f, time_expire)
return value return value
except RConnectionError: except ConnectionError:
return self.retry_call(key, f, time_expire, with_lock) return self.retry_call(key, f, time_expire, with_lock)
def cache_it(self, key, f, time_expire): def cache_it(self, key, f, time_expire):
if self.debug: if self.debug:
self.r_server.incr('web2py_cache_statistics:misses') self.r_server.incr('web2py_cache_statistics:misses')
cache_set_key = self.cache_set_key cache_set_key = self.cache_set_key
expire_at = int(time.time() + time_expire) + 120 expireat = int(time.time() + time_expire) + 120
bucket_key = "%s:%s" % (cache_set_key, expire_at / 60) bucket_key = "%s:%s" % (cache_set_key, expireat / 60)
value = f() value = f()
value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
if time_expire == 0: if time_expire == 0:
time_expire = 1 time_expire = 1
self.r_server.setex(key, time_expire, value_) self.r_server.setex(key, value_, time_expire)
# print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expire_at)) #print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expireat))
# print 'that will expire on %s' % (bucket_key, time.ctime(((expire_at / 60) + 1) * 60)) #print 'that will expire on %s' % (bucket_key, time.ctime(((expireat/60) + 1)*60))
p = self.r_server.pipeline() p = self.r_server.pipeline()
# add bucket to the fixed set #add bucket to the fixed set
p.sadd(cache_set_key, bucket_key) p.sadd(cache_set_key, bucket_key)
# sets the key #sets the key
p.setex(key, time_expire, value_) p.setex(key, value_, time_expire)
# add the key to the bucket #add the key to the bucket
p.sadd(bucket_key, key) p.sadd(bucket_key, key)
# expire the bucket properly #expire the bucket properly
p.expireat(bucket_key, ((expire_at / 60) + 1) * 60) p.expireat(bucket_key, ((expireat/60) + 1)*60)
p.execute() p.execute()
return value return value
def retry_call(self, key, f, time_expire, with_lock): def retry_call(self, key, f, time_expire, with_locking):
self.RETRIES += 1 self.RETRIES += 1
if self.RETRIES <= self.MAX_RETRIES: if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping %s seconds before reconnecting" % (2 * self.RETRIES)) logger.error("sleeping %s seconds before reconnecting" %
(2 * self.RETRIES))
time.sleep(2 * self.RETRIES) time.sleep(2 * self.RETRIES)
if self.fail_gracefully: self.__init__(self.server, self.db, self.debug, self.with_lock)
self.RETRIES = 0 return self.__call__(key, f, time_expire, with_locking)
return f()
return self.__call__(key, f, time_expire, with_lock)
else: else:
self.RETRIES = 0 self.RETRIES = 0
if self.fail_gracefully: raise ConnectionError('Redis instance is unavailable at %s' % (
return f self.server))
raise RConnectionError('Redis instance is unavailable')
def increment(self, key, value=1): def increment(self, key, value=1):
try: try:
newKey = self.__keyFormat__(key) newKey = self.__keyFormat__(key)
return self.r_server.incr(newKey, value) return self.r_server.incr(newKey, value)
except RConnectionError: except ConnectionError:
return self.retry_increment(key, value) return self.retry_increment(key, value)
def retry_increment(self, key, value): def retry_increment(self, key, value):
@@ -224,10 +212,12 @@ class RedisClient(object):
if self.RETRIES <= self.MAX_RETRIES: if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping some seconds before reconnecting") logger.error("sleeping some seconds before reconnecting")
time.sleep(2 * self.RETRIES) time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug, self.with_lock)
return self.increment(key, value) return self.increment(key, value)
else: else:
self.RETRIES = 0 self.RETRIES = 0
raise RConnectionError('Redis instance is unavailable') raise ConnectionError('Redis instance is unavailable at %s' % (
self.server))
def clear(self, regex): def clear(self, regex):
""" """
@@ -235,9 +225,9 @@ class RedisClient(object):
clear cache entries clear cache entries
""" """
r = re.compile(regex) r = re.compile(regex)
# get all buckets #get all buckets
buckets = self.r_server.smembers(self.cache_set_key) buckets = self.r_server.smembers(self.cache_set_key)
# get all keys in buckets #get all keys in buckets
if buckets: if buckets:
keys = self.r_server.sunion(buckets) keys = self.r_server.sunion(buckets)
else: else:
@@ -247,8 +237,8 @@ class RedisClient(object):
for a in keys: for a in keys:
if r.match(str(a).replace(prefix, '', 1)): if r.match(str(a).replace(prefix, '', 1)):
pipe.delete(a) pipe.delete(a)
if random.randrange(0, 100) < 10: if random.randrange(0,100) < 10:
# do this just once in a while (10% chance) #do this just once in a while (10% chance)
self.clear_buckets(buckets) self.clear_buckets(buckets)
pipe.execute() pipe.execute()
@@ -264,19 +254,19 @@ class RedisClient(object):
return self.r_server.delete(newKey) return self.r_server.delete(newKey)
def stats(self): def stats(self):
stats_collector = self.r_server.info() statscollector = self.r_server.info()
if self.debug: if self.debug:
stats_collector['w2p_stats'] = dict( statscollector['w2p_stats'] = dict(
hit_total=self.r_server.get( hit_total=self.r_server.get(
'web2py_cache_statistics:hit_total'), 'web2py_cache_statistics:hit_total'),
misses=self.r_server.get('web2py_cache_statistics:misses') misses=self.r_server.get('web2py_cache_statistics:misses')
) )
stats_collector['w2p_keys'] = dict() statscollector['w2p_keys'] = dict()
for a in self.r_server.keys("w2p:%s:*" % ( for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)): self.request.application)):
stats_collector['w2p_keys']["%s_expire_in_sec" % a] = self.r_server.ttl(a) statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = self.r_server.ttl(a)
return stats_collector return statscollector
def __keyFormat__(self, key): def __keyFormat__(self, key):
return '%s%s' % (self.prefix, key.replace(' ', '_')) return '%s%s' % (self.prefix, key.replace(' ', '_'))
-799
View File
@@ -1,799 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Created by niphlod@gmail.com
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Scheduler with redis backend
---------------------------------
"""
import os
import time
import socket
import datetime
import logging
from gluon.utils import web2py_uuid
from gluon.storage import Storage
from gluon.scheduler import *
from gluon.scheduler import _decode_dict
from gluon.contrib.redis_utils import RWatchError
USAGE = """
## Example
For any existing app
Create File: app/models/scheduler.py ======
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_scheduler import RScheduler
def demo1(*args,**vars):
print 'you passed args=%s and vars=%s' % (args, vars)
return 'done!'
def demo2():
1/0
rconn = RConn()
mysched = RScheduler(db, dict(demo1=demo1,demo2=demo2), ...., redis_conn=rconn)
## run worker nodes with:
cd web2py
python web2py.py -K app
"""
path = os.getcwd()
if 'WEB2PY_PATH' not in os.environ:
os.environ['WEB2PY_PATH'] = path
try:
# try external module
from simplejson import loads, dumps
except ImportError:
try:
# try stdlib (Python >= 2.6)
from json import loads, dumps
except:
# fallback to pure-Python module
from gluon.contrib.simplejson import loads, dumps
IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
POLLING = 'POLLING'
class RScheduler(Scheduler):
def __init__(self, db, tasks=None, migrate=True,
worker_name=None, group_names=None, heartbeat=HEARTBEAT,
max_empty_runs=0, discard_results=False, utc_time=False,
redis_conn=None, mode=1):
"""
Highly-experimental coordination with redis
Takes all args from Scheduler except redis_conn which
must be something closer to a StrictRedis instance.
My only regret - and the reason why I kept this under the hood for a
while - is that it's hard to hook up in web2py to something happening
right after the commit to a table, which will enable this version of the
scheduler to process "immediate" tasks right away instead of waiting a
few seconds (see FIXME in queue_task())
mode is reserved for future usage patterns.
Right now it moves the coordination (which is the most intensive
routine in the scheduler in matters of IPC) of workers to redis.
I'd like to have incrementally redis-backed modes of operations,
such as e.g.:
- 1: IPC through redis (which is the current implementation)
- 2: Store task results in redis (which will relieve further pressure
from the db leaving the scheduler_run table empty and possibly
keep things smooth as tasks results can be set to expire
after a bit of time)
- 3: Move all the logic for storing and queueing tasks to redis
itself - which means no scheduler_task usage too - and use
the database only as an historical record-bookkeeping
(e.g. for reporting)
As usual, I'm eager to see your comments.
"""
Scheduler.__init__(self, db, tasks=tasks, migrate=migrate,
worker_name=worker_name, group_names=group_names,
heartbeat=heartbeat, max_empty_runs=max_empty_runs,
discard_results=discard_results, utc_time=utc_time)
self.r_server = redis_conn
from gluon import current
self._application = current.request.application or 'appname'
def _nkey(self, key):
"""Helper to restrict all keys to a namespace and track them."""
prefix = 'w2p:rsched:%s' % self._application
allkeys = '%s:allkeys' % prefix
newkey = "%s:%s" % (prefix, key)
self.r_server.sadd(allkeys, newkey)
return newkey
def prune_all(self):
"""Global housekeeping."""
all_keys = self._nkey('allkeys')
with self.r_server.pipeline() as pipe:
while True:
try:
pipe.watch('PRUNE_ALL')
while True:
k = pipe.spop(all_keys)
if k is None:
break
pipe.delete(k)
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
def dt2str(self, value):
return value.strftime('%Y-%m-%d %H:%M:%S')
def str2date(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
def send_heartbeat(self, counter):
"""
Workers coordination in redis.
It has evolved into something is not that easy.
Here we try to do what we need in a single transaction,
and retry that transaction if something goes wrong
"""
with self.r_server.pipeline() as pipe:
while True:
try:
pipe.watch('SEND_HEARTBEAT')
self.inner_send_heartbeat(counter, pipe)
pipe.execute()
self.adj_hibernation()
self.sleep()
break
except RWatchError:
time.sleep(0.1)
continue
def inner_send_heartbeat(self, counter, pipe):
"""
Do a few things in the "maintenance" thread.
Specifically:
- registers the workers
- accepts commands sent to workers (KILL, TERMINATE, PICK, DISABLED, etc)
- adjusts sleep
- saves stats
- elects master
- does "housecleaning" for dead workers
- triggers tasks assignment
"""
r_server = pipe
status_keyset = self._nkey('worker_statuses')
status_key = self._nkey('worker_status:%s' % (self.worker_name))
now = self.now()
mybackedstatus = r_server.hgetall(status_key)
if not mybackedstatus:
r_server.hmset(
status_key,
dict(
status=ACTIVE, worker_name=self.worker_name,
first_heartbeat=self.dt2str(now),
last_heartbeat=self.dt2str(now),
group_names=dumps(self.group_names), is_ticker=False,
worker_stats=dumps(self.w_stats))
)
r_server.sadd(status_keyset, status_key)
if not self.w_stats.status == POLLING:
self.w_stats.status = ACTIVE
self.w_stats.sleep = self.heartbeat
mybackedstatus = ACTIVE
else:
mybackedstatus = mybackedstatus['status']
if mybackedstatus == DISABLED:
# keep sleeping
self.w_stats.status = DISABLED
r_server.hmset(
status_key,
dict(last_heartbeat=self.dt2str(now),
worker_stats=dumps(self.w_stats))
)
elif mybackedstatus == TERMINATE:
self.w_stats.status = TERMINATE
logger.debug("Waiting to terminate the current task")
self.give_up()
elif mybackedstatus == KILL:
self.w_stats.status = KILL
self.die()
else:
if mybackedstatus == STOP_TASK:
logger.info('Asked to kill the current task')
self.terminate_process()
logger.info('........recording heartbeat (%s)',
self.w_stats.status)
r_server.hmset(
status_key,
dict(
last_heartbeat=self.dt2str(now), status=ACTIVE,
worker_stats=dumps(self.w_stats)
)
)
# newroutine
r_server.expire(status_key, self.heartbeat * 3 * 15)
self.w_stats.sleep = self.heartbeat # re-activating the process
if self.w_stats.status not in (RUNNING, POLLING):
self.w_stats.status = ACTIVE
self.do_assign_tasks = False
if counter % 5 == 0 or mybackedstatus == PICK:
try:
logger.info(
' freeing workers that have not sent heartbeat')
registered_workers = r_server.smembers(status_keyset)
allkeys = self._nkey('allkeys')
for worker in registered_workers:
w = r_server.hgetall(worker)
w = Storage(w)
if not w:
r_server.srem(status_keyset, worker)
logger.info('removing %s from %s', worker, allkeys)
r_server.srem(allkeys, worker)
continue
try:
self.is_a_ticker = self.being_a_ticker(pipe)
except:
pass
if self.w_stats.status in (ACTIVE, POLLING):
self.do_assign_tasks = True
if self.is_a_ticker and self.do_assign_tasks:
# I'm a ticker, and 5 loops passed without reassigning tasks,
# let's do that and loop again
if not self.db_thread:
logger.debug('thread building own DAL object')
self.db_thread = DAL(
self.db._uri, folder=self.db._adapter.folder)
self.define_tables(self.db_thread, migrate=False)
db = self.db_thread
self.wrapped_assign_tasks(db)
return None
except:
logger.error('Error assigning tasks')
def being_a_ticker(self, pipe):
"""
Elects a ticker.
This is slightly more convoluted than the original
but if far more efficient
"""
r_server = pipe
status_keyset = self._nkey('worker_statuses')
registered_workers = r_server.smembers(status_keyset)
ticker = None
all_active = []
all_workers = []
for worker in registered_workers:
w = r_server.hgetall(worker)
if w['worker_name'] != self.worker_name and w['status'] == ACTIVE:
all_active.append(w)
if w['is_ticker'] == 'True' and ticker is None:
ticker = w
all_workers.append(w)
not_busy = self.w_stats.status in (ACTIVE, POLLING)
if not ticker:
if not_busy:
# only if this worker isn't busy, otherwise wait for a free one
for worker in all_workers:
key = self._nkey('worker_status:%s' % worker['worker_name'])
if worker['worker_name'] == self.worker_name:
r_server.hset(key, 'is_ticker', True)
else:
r_server.hset(key, 'is_ticker', False)
logger.info("TICKER: I'm a ticker")
else:
# giving up, only if I'm not alone
if len(all_active) > 1:
key = self._nkey('worker_status:%s' % (self.worker_name))
r_server.hset(key, 'is_ticker', False)
else:
not_busy = True
return not_busy
else:
logger.info(
"%s is a ticker, I'm a poor worker" % ticker['worker_name'])
return False
def assign_tasks(self, db):
"""
The real beauty.
We don't need to ASSIGN tasks, we just put
them into the relevant queue
"""
st, sd = db.scheduler_task, db.scheduler_task_deps
r_server = self.r_server
now = self.now()
status_keyset = self._nkey('worker_statuses')
with r_server.pipeline() as pipe:
while 1:
try:
# making sure we're the only one doing the job
pipe.watch('ASSIGN_TASKS')
registered_workers = pipe.smembers(status_keyset)
all_workers = []
for worker in registered_workers:
w = pipe.hgetall(worker)
if w['status'] == ACTIVE:
all_workers.append(Storage(w))
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
# build workers as dict of groups
wkgroups = {}
for w in all_workers:
group_names = loads(w.group_names)
for gname in group_names:
if gname not in wkgroups:
wkgroups[gname] = dict(
workers=[{'name': w.worker_name, 'c': 0}])
else:
wkgroups[gname]['workers'].append(
{'name': w.worker_name, 'c': 0})
# set queued tasks that expired between "runs" (i.e., you turned off
# the scheduler): then it wasn't expired, but now it is
db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(st.stop_time < now)
).update(status=EXPIRED)
# calculate dependencies
deps_with_no_deps = db(
(sd.can_visit == False) &
(~sd.task_child.belongs(
db(sd.can_visit == False)._select(sd.task_parent)
)
)
)._select(sd.task_child)
no_deps = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
)
)._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) &
(sd.can_visit == False)
)
)
all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(st.next_run_time <= now) &
(st.enabled == True) &
(st.id.belongs(no_deps))
)
limit = len(all_workers) * (50 / (len(wkgroups) or 1))
# let's freeze it up
db.commit()
x = 0
r_server = self.r_server
for group in wkgroups.keys():
queued_list = self._nkey('queued:%s' % group)
queued_set = self._nkey('queued_set:%s' % group)
# if are running, let's don't assign them again
running_list = self._nkey('running:%s' % group)
while True:
# the joys for rpoplpush!
t = r_server.rpoplpush(running_list, queued_list)
if not t:
# no more
break
r_server.sadd(queued_set, t)
tasks = all_available(st.group_name == group).select(
limitby=(0, limit), orderby = st.next_run_time)
# put tasks in the processing list
for task in tasks:
x += 1
gname = task.group_name
if r_server.sismember(queued_set, task.id):
# already queued, we don't put on the list
continue
r_server.sadd(queued_set, task.id)
r_server.lpush(queued_list, task.id)
d = dict(status=QUEUED)
if not task.task_name:
d['task_name'] = task.function_name
db(
(st.id == task.id) &
(st.status.belongs((QUEUED, ASSIGNED)))
).update(**d)
db.commit()
# I didn't report tasks but I'm working nonetheless!!!!
if x > 0:
self.w_stats.empty_runs = 0
self.w_stats.queue = x
self.w_stats.distribution = wkgroups
self.w_stats.workers = len(all_workers)
# I'll be greedy only if tasks queued are equal to the limit
# (meaning there could be others ready to be queued)
self.greedy = x >= limit
logger.info('TICKER: workers are %s', len(all_workers))
logger.info('TICKER: tasks are %s', x)
def pop_task(self, db):
"""Lift a task off a queue."""
r_server = self.r_server
st = self.db.scheduler_task
task = None
# ready to process something
for group in self.group_names:
queued_set = self._nkey('queued_set:%s' % group)
queued_list = self._nkey('queued:%s' % group)
running_list = self._nkey('running:%s' % group)
running_dict = self._nkey('running_dict:%s' % group)
self.w_stats.status = POLLING
# polling for 1 minute in total. If more groups are in,
# polling is 1 minute in total
logger.debug(' polling on %s', group)
task_id = r_server.brpoplpush(queued_list, running_list,
timeout=60 / len(self.group_names))
logger.debug(' finished polling')
self.w_stats.status = ACTIVE
if task_id:
r_server.hset(running_dict, task_id, self.worker_name)
r_server.srem(queued_set, task_id)
task = db(
(st.id == task_id) &
(st.status == QUEUED)
).select().first()
if not task:
r_server.lrem(running_list, 0, task_id)
r_server.hdel(running_dict, task_id)
r_server.lrem(queued_list, 0, task_id)
logger.error("we received a task that isn't there (%s)",
task_id)
return None
break
now = self.now()
if task:
task.update_record(status=RUNNING, last_run_time=now)
# noone will touch my task!
db.commit()
logger.debug(' work to do %s', task.id)
else:
logger.info('nothing to do')
return None
times_run = task.times_run + 1
if not task.prevent_drift:
next_run_time = task.last_run_time + datetime.timedelta(
seconds=task.period
)
else:
# calc next_run_time based on available slots
# see #1191
next_run_time = task.start_time
secondspassed = self.total_seconds(now - next_run_time)
steps = secondspassed // task.period + 1
next_run_time += datetime.timedelta(seconds=task.period * steps)
if times_run < task.repeats or task.repeats == 0:
# need to run (repeating task)
run_again = True
else:
# no need to run again
run_again = False
run_id = 0
while True and not self.discard_results:
logger.debug(' new scheduler_run record')
try:
run_id = db.scheduler_run.insert(
task_id=task.id,
status=RUNNING,
start_time=now,
worker_name=self.worker_name)
db.commit()
break
except:
time.sleep(0.5)
db.rollback()
logger.info('new task %(id)s "%(task_name)s"'
' %(application_name)s.%(function_name)s' % task)
return Task(
app=task.application_name,
function=task.function_name,
timeout=task.timeout,
args=task.args, # in json
vars=task.vars, # in json
task_id=task.id,
run_id=run_id,
run_again=run_again,
next_run_time=next_run_time,
times_run=times_run,
stop_time=task.stop_time,
retry_failed=task.retry_failed,
times_failed=task.times_failed,
sync_output=task.sync_output,
uuid=task.uuid,
group_name=task.group_name)
def report_task(self, task, task_report):
"""
Override.
Needs it only because we need to pop from the
running tasks
"""
r_server = self.r_server
db = self.db
now = self.now()
st = db.scheduler_task
sr = db.scheduler_run
if not self.discard_results:
if task_report.result != 'null' or task_report.tb:
# result is 'null' as a string if task completed
# if it's stopped it's None as NoneType, so we record
# the STOPPED "run" anyway
logger.debug(' recording task report in db (%s)',
task_report.status)
db(sr.id == task.run_id).update(
status=task_report.status,
stop_time=now,
run_result=task_report.result,
run_output=task_report.output,
traceback=task_report.tb)
else:
logger.debug(' deleting task report in db because of no result')
db(sr.id == task.run_id).delete()
# if there is a stop_time and the following run would exceed it
is_expired = (task.stop_time and
task.next_run_time > task.stop_time and
True or False)
status = (task.run_again and is_expired and EXPIRED or
task.run_again and not is_expired and
QUEUED or COMPLETED)
if task_report.status == COMPLETED:
# assigned calculations
d = dict(status=status,
next_run_time=task.next_run_time,
times_run=task.times_run,
times_failed=0,
assigned_worker_name=self.worker_name
)
db(st.id == task.task_id).update(**d)
if status == COMPLETED:
self.update_dependencies(db, task.task_id)
else:
st_mapping = {'FAILED': 'FAILED',
'TIMEOUT': 'TIMEOUT',
'STOPPED': 'FAILED'}[task_report.status]
status = (task.retry_failed and
task.times_failed < task.retry_failed and
QUEUED or task.retry_failed == -1 and
QUEUED or st_mapping)
db(st.id == task.task_id).update(
times_failed=st.times_failed + 1,
next_run_time=task.next_run_time,
status=status,
assigned_worker_name=self.worker_name
)
logger.info('task completed (%s)', task_report.status)
running_list = self._nkey('running:%s' % task.group_name)
running_dict = self._nkey('running_dict:%s' % task.group_name)
r_server.lrem(running_list, 0, task.task_id)
r_server.hdel(running_dict, task.task_id)
def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions.
If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds
"""
db = self.db
db.commit() # another nifty db.commit() only for Mysql
x = 0
while x < 10:
try:
rtn = self.pop_task(db)
return rtn
break
# this is here to "interrupt" any blrpoplpush op easily
except KeyboardInterrupt:
self.give_up()
break
except:
self.w_stats.errors += 1
db.rollback()
logger.error(' error popping tasks')
x += 1
time.sleep(0.5)
def get_workers(self, only_ticker=False):
"""Return a dict holding worker_name : {**columns}
representing all "registered" workers.
only_ticker returns only the worker running as a TICKER,
if there is any
"""
r_server = self.r_server
status_keyset = self._nkey('worker_statuses')
registered_workers = r_server.smembers(status_keyset)
all_workers = {}
for worker in registered_workers:
w = r_server.hgetall(worker)
w = Storage(w)
if not w:
continue
all_workers[w.worker_name] = Storage(
status=w.status,
first_heartbeat=self.str2date(w.first_heartbeat),
last_heartbeat=self.str2date(w.last_heartbeat),
group_names=loads(w.group_names, object_hook=_decode_dict),
is_ticker=w.is_ticker == 'True' and True or False,
worker_stats=loads(w.worker_stats, object_hook=_decode_dict)
)
if only_ticker:
for k, v in all_workers.iteritems():
if v['is_ticker']:
return {k: v}
return {}
return all_workers
def set_worker_status(self, group_names=None, action=ACTIVE,
exclude=None, limit=None, worker_name=None):
"""Internal function to set worker's status"""
r_server = self.r_server
all_workers = self.get_workers()
if not group_names:
group_names = self.group_names
elif isinstance(group_names, str):
group_names = [group_names]
exclusion = exclude and exclude.append(action) or [action]
workers = []
if worker_name is not None:
if worker_name in all_workers.keys():
workers = [worker_name]
else:
for k, v in all_workers.iteritems():
if v.status not in exclusion and set(group_names) & set(v.group_names):
workers.append(k)
if limit and worker_name is None:
workers = workers[:limit]
if workers:
with r_server.pipeline() as pipe:
while True:
try:
pipe.watch('SET_WORKER_STATUS')
for w in workers:
worker_key = self._nkey('worker_status:%s' % w)
pipe.hset(worker_key, 'status', action)
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
def queue_task(self, function, pargs=[], pvars={}, **kwargs):
"""
FIXME: immediate should put item in queue. The hard part is
that currently there are no hooks happening at post-commit time
Queue tasks. This takes care of handling the validation of all
parameters
Args:
function: the function (anything callable with a __name__)
pargs: "raw" args to be passed to the function. Automatically
jsonified.
pvars: "raw" kwargs to be passed to the function. Automatically
jsonified
kwargs: all the parameters available (basically, every
`scheduler_task` column). If args and vars are here, they should
be jsonified already, and they will override pargs and pvars
Returns:
a dict just as a normal validate_and_insert(), plus a uuid key
holding the uuid of the queued task. If validation is not passed
( i.e. some parameters are invalid) both id and uuid will be None,
and you'll get an "error" dict holding the errors found.
"""
if hasattr(function, '__name__'):
function = function.__name__
targs = 'args' in kwargs and kwargs.pop('args') or dumps(pargs)
tvars = 'vars' in kwargs and kwargs.pop('vars') or dumps(pvars)
tuuid = 'uuid' in kwargs and kwargs.pop('uuid') or web2py_uuid()
tname = 'task_name' in kwargs and kwargs.pop('task_name') or function
immediate = 'immediate' in kwargs and kwargs.pop('immediate') or None
rtn = self.db.scheduler_task.validate_and_insert(
function_name=function,
task_name=tname,
args=targs,
vars=tvars,
uuid=tuuid,
**kwargs)
if not rtn.errors:
rtn.uuid = tuuid
if immediate:
r_server = self.r_server
ticker = self.get_workers(only_ticker=True)
if ticker.keys():
ticker = ticker.keys()[0]
with r_server.pipeline() as pipe:
while True:
try:
pipe.watch('SET_WORKER_STATUS')
worker_key = self._nkey('worker_status:%s' % ticker)
pipe.hset(worker_key, 'status', 'PICK')
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
else:
rtn.uuid = None
return rtn
def stop_task(self, ref):
"""Shortcut for task termination.
If the task is RUNNING it will terminate it, meaning that status
will be set as FAILED.
If the task is QUEUED, its stop_time will be set as to "now",
the enabled flag will be set to False, and the status to STOPPED
Args:
ref: can be
- an integer : lookup will be done by scheduler_task.id
- a string : lookup will be done by scheduler_task.uuid
Returns:
- 1 if task was stopped (meaning an update has been done)
- None if task was not found, or if task was not RUNNING or QUEUED
Note:
Experimental
"""
r_server = self.r_server
st = self.db.scheduler_task
if isinstance(ref, int):
q = st.id == ref
elif isinstance(ref, str):
q = st.uuid == ref
else:
raise SyntaxError(
"You can retrieve results only by id or uuid")
task = self.db(q).select(st.id, st.status, st.group_name)
task = task.first()
rtn = None
if not task:
return rtn
running_dict = self._nkey('running_dict:%s' % task.group_name)
if task.status == 'RUNNING':
worker_key = r_server.hget(running_dict, task.id)
worker_key = self._nkey('worker_status:%s' % (worker_key))
r_server.hset(worker_key, 'status', STOP_TASK)
elif task.status == 'QUEUED':
rtn = self.db(q).update(
stop_time=self.now(),
enabled=False,
status=STOPPED)
return rtn
+75 -46
View File
@@ -1,40 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
Developed by niphlod@gmail.com Developed by niphlod@gmail.com
License MIT/BSD/GPL
Redis-backed sessions
""" """
import logging import redis
import thread
from gluon import current from gluon import current
from gluon.storage import Storage from gluon.storage import Storage
from gluon.contrib.redis_utils import acquire_lock, release_lock import time
from gluon.contrib.redis_utils import register_release_lock import logging
import thread
logger = logging.getLogger("web2py.session.redis") logger = logging.getLogger("web2py.session.redis")
locker = thread.allocate_lock() locker = thread.allocate_lock()
def RedisSession(redis_conn, session_expiry=False, with_lock=False, db=None): def RedisSession(*args, **vars):
""" """
Usage example: put in models:: Usage example: put in models
from gluon.contrib.redis_session import RedisSession
from gluon.contrib.redis_utils import RConn sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False, password=None)
rconn = RConn() session.connect(request, response, db = sessiondb)
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession(redis_conn=rconn, with_lock=True, session_expiry=False)
session.connect(request, response, db = sessiondb)
Args:
redis_conn: a redis-like connection object
with_lock: prevent concurrent modifications to the same session
session_expiry: delete automatically sessions after n seconds
(still need to run sessions2trash.py every 1M sessions
or so)
Simple slip-in storage for session Simple slip-in storage for session
""" """
@@ -43,8 +28,7 @@ def RedisSession(redis_conn, session_expiry=False, with_lock=False, db=None):
try: try:
instance_name = 'redis_instance_' + current.request.application instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisSession, instance_name): if not hasattr(RedisSession, instance_name):
setattr(RedisSession, instance_name, setattr(RedisSession, instance_name, RedisClient(*args, **vars))
RedisClient(redis_conn, session_expiry=session_expiry, with_lock=with_lock))
return getattr(RedisSession, instance_name) return getattr(RedisSession, instance_name)
finally: finally:
locker.release() locker.release()
@@ -52,9 +36,30 @@ def RedisSession(redis_conn, session_expiry=False, with_lock=False, db=None):
class RedisClient(object): class RedisClient(object):
def __init__(self, redis_conn, session_expiry=False, with_lock=False): meta_storage = {}
self.r_server = redis_conn MAX_RETRIES = 5
self._release_script = register_release_lock(self.r_server) RETRIES = 0
_release_script = None
def __init__(self, server='localhost:6379', db=None, debug=False,
session_expiry=False, with_lock=False, password=None):
"""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.password = password
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, password=self.password)
if with_lock:
RedisClient._release_script = self.r_server.register_script(_LUA_RELEASE_LOCK)
self.tablename = None self.tablename = None
self.session_expiry = session_expiry self.session_expiry = session_expiry
self.with_lock = with_lock self.with_lock = with_lock
@@ -88,11 +93,12 @@ class RedisClient(object):
class MockTable(object): class MockTable(object):
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False): def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
# here self.db is the RedisClient instance
self.db = db self.db = db
self.r_server = r_server
self.tablename = tablename self.tablename = tablename
# set the namespace for sessions of this app # set the namespace for sessions of this app
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '') self.keyprefix = 'w2p:sess:%s' % tablename.replace(
'web2py_session_', '')
# fast auto-increment id (needed for session handling) # fast auto-increment id (needed for session handling)
self.serial = "%s:serial" % self.keyprefix self.serial = "%s:serial" % self.keyprefix
# index of all the session keys of this app # index of all the session keys of this app
@@ -120,7 +126,7 @@ class MockTable(object):
if key == 'id': if key == 'id':
# return a fake query. We need to query it just by id for normal operations # return a fake query. We need to query it just by id for normal operations
self.query = MockQuery( self.query = MockQuery(
field='id', db=self.db, field='id', db=self.r_server,
prefix=self.keyprefix, session_expiry=self.session_expiry, prefix=self.keyprefix, session_expiry=self.session_expiry,
with_lock=self.with_lock, unique_key=self.unique_key with_lock=self.with_lock, unique_key=self.unique_key
) )
@@ -134,12 +140,12 @@ class MockTable(object):
# 'locked', 'client_ip','created_datetime','modified_datetime' # 'locked', 'client_ip','created_datetime','modified_datetime'
# 'unique_key', 'session_data' # 'unique_key', 'session_data'
# retrieve a new key # retrieve a new key
newid = str(self.db.r_server.incr(self.serial)) newid = str(self.r_server.incr(self.serial))
key = self.keyprefix + ':' + newid key = self.keyprefix + ':' + newid
if self.with_lock: if self.with_lock:
key_lock = key + ':lock' key_lock = key + ':lock'
acquire_lock(self.db.r_server, key_lock, newid) acquire_lock(self.r_server, key_lock, newid)
with self.db.r_server.pipeline() as pipe: with self.r_server.pipeline() as pipe:
# add it to the index # add it to the index
pipe.sadd(self.id_idx, key) pipe.sadd(self.id_idx, key)
# set a hash key with the Storage # set a hash key with the Storage
@@ -148,7 +154,7 @@ class MockTable(object):
pipe.expire(key, self.session_expiry) pipe.expire(key, self.session_expiry)
pipe.execute() pipe.execute()
if self.with_lock: if self.with_lock:
release_lock(self.db, key_lock, newid) release_lock(self.r_server, key_lock, newid)
return newid return newid
@@ -180,8 +186,8 @@ class MockQuery(object):
# means that someone wants to retrieve the key self.value # means that someone wants to retrieve the key self.value
key = self.keyprefix + ':' + str(self.value) key = self.keyprefix + ':' + str(self.value)
if self.with_lock: if self.with_lock:
acquire_lock(self.db.r_server, key + ':lock', self.value, 2) acquire_lock(self.db, key + ':lock', self.value)
rtn = self.db.r_server.hgetall(key) rtn = self.db.hgetall(key)
if rtn: if rtn:
if self.unique_key: if self.unique_key:
# make sure the id and unique_key are correct # make sure the id and unique_key are correct
@@ -195,13 +201,13 @@ class MockQuery(object):
rtn = [] rtn = []
id_idx = "%s:id_idx" % self.keyprefix id_idx = "%s:id_idx" % self.keyprefix
# find all session keys of this app # find all session keys of this app
allkeys = self.db.r_server.smembers(id_idx) allkeys = self.db.smembers(id_idx)
for sess in allkeys: for sess in allkeys:
val = self.db.r_server.hgetall(sess) val = self.db.hgetall(sess)
if not val: if not val:
if self.session_expiry: if self.session_expiry:
# clean up the idx, because the key expired # clean up the idx, because the key expired
self.db.r_server.srem(id_idx, sess) self.db.srem(id_idx, sess)
continue continue
val = Storage(val) val = Storage(val)
# add a delete_record method (necessary for sessions2trash.py) # add a delete_record method (necessary for sessions2trash.py)
@@ -216,9 +222,9 @@ class MockQuery(object):
# means that the session has been found and needs an update # means that the session has been found and needs an update
if self.op == 'eq' and self.field == 'id' and self.value: if self.op == 'eq' and self.field == 'id' and self.value:
key = self.keyprefix + ':' + str(self.value) key = self.keyprefix + ':' + str(self.value)
if not self.db.r_server.exists(key): if not self.db.exists(key):
return None return None
with self.db.r_server.pipeline() as pipe: with self.db.pipeline() as pipe:
pipe.hmset(key, kwargs) pipe.hmset(key, kwargs)
if self.session_expiry: if self.session_expiry:
pipe.expire(key, self.session_expiry) pipe.expire(key, self.session_expiry)
@@ -232,7 +238,7 @@ class MockQuery(object):
if self.op == 'eq' and self.field == 'id' and self.value: if self.op == 'eq' and self.field == 'id' and self.value:
id_idx = "%s:id_idx" % self.keyprefix id_idx = "%s:id_idx" % self.keyprefix
key = self.keyprefix + ':' + str(self.value) key = self.keyprefix + ':' + str(self.value)
with self.db.r_server.pipeline() as pipe: with self.db.pipeline() as pipe:
pipe.delete(key) pipe.delete(key)
pipe.srem(id_idx, key) pipe.srem(id_idx, key)
rtn = pipe.execute() rtn = pipe.execute()
@@ -248,6 +254,29 @@ class RecordDeleter(object):
def __call__(self): def __call__(self):
id_idx = "%s:id_idx" % self.keyprefix id_idx = "%s:id_idx" % self.keyprefix
# remove from the index # remove from the index
self.db.r_server.srem(id_idx, self.key) self.db.srem(id_idx, self.key)
# remove the key itself # remove the key itself
self.db.r_server.delete(self.key) self.db.delete(self.key)
def acquire_lock(conn, lockname, identifier, ltime=10):
while True:
if conn.set(lockname, identifier, ex=ltime, nx=True):
return identifier
time.sleep(.01)
_LUA_RELEASE_LOCK = """
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
"""
def release_lock(conn, lockname, identifier):
return RedisClient._release_script(
keys=[lockname], args=[identifier],
client=conn)
-70
View File
@@ -1,70 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Developed by niphlod@gmail.com
License MIT/BSD/GPL
Serves as base to implement Redis connection object and various utils
for redis_cache, redis_session and redis_scheduler in the future
Should-could be overriden in case redis doesn't keep up (e.g. cluster support)
to ensure compatibility with another - similar - library
"""
import logging
import thread
import time
from gluon import current
logger = logging.getLogger("web2py.redis_utils")
try:
import redis
from redis.exceptions import WatchError as RWatchError
from redis.exceptions import ConnectionError as RConnectionError
except ImportError:
logger.error("Needs redis library to work")
raise RuntimeError('Needs redis library to work')
locker = thread.allocate_lock()
def RConn(*args, **vars):
"""
Istantiates a StrictRedis connection with parameters, at the first time
only
"""
locker.acquire()
try:
instance_name = 'redis_conn_' + current.request.application
if not hasattr(RConn, instance_name):
setattr(RConn, instance_name, redis.StrictRedis(*args, **vars))
return getattr(RConn, instance_name)
finally:
locker.release()
def acquire_lock(conn, lockname, identifier, ltime=10):
while True:
if conn.set(lockname, identifier, ex=ltime, nx=True):
return identifier
time.sleep(.01)
_LUA_RELEASE_LOCK = """
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
"""
def release_lock(instance, lockname, identifier):
return instance._release_script(
keys=[lockname], args=[identifier])
def register_release_lock(conn):
rtn = conn.register_script(_LUA_RELEASE_LOCK)
return rtn
+7 -8
View File
@@ -72,14 +72,13 @@ def _default_validators(db, field):
if not field.notnull: if not field.notnull:
requires = validators.IS_EMPTY_OR(requires) requires = validators.IS_EMPTY_OR(requires)
return requires return requires
# does not get here for reference and list:reference
if field.unique: if field.unique:
requires.insert(0, validators.IS_NOT_IN_DB(db, field)) requires.append(validators.IS_NOT_IN_DB(db, field))
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean'] sff = ['in', 'do', 'da', 'ti', 'de', 'bo']
if (field.notnull or field.unique) and not field_type in excluded_fields: if field.notnull and not field_type[:2] in sff:
requires.insert(0, validators.IS_NOT_EMPTY()) requires.append(validators.IS_NOT_EMPTY())
elif not field.notnull and not field.unique and requires: elif not field.notnull and field_type[:2] in sff and requires:
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field in ('string', 'text', 'password') else None) requires[0] = validators.IS_EMPTY_OR(requires[0])
return requires return requires
from gluon.serializers import custom_json, xml from gluon.serializers import custom_json, xml
@@ -93,7 +92,7 @@ DAL.uuid = lambda x: web2py_uuid()
DAL.representers = { DAL.representers = {
'rows_render': sqlhtml.represent, 'rows_render': sqlhtml.represent,
'rows_xml': sqlhtml.SQLTABLE 'rows_xml': sqlhtml.SQLTABLE
} }
DAL.Field = Field DAL.Field = Field
DAL.Table = Table DAL.Table = Table
+407
View File
@@ -0,0 +1,407 @@
import cgi
import copy_reg
from gluon import current, URL, DAL
from gluon.storage import Storage
from gluon.utils import web2py_uuid
from gluon.sanitizer import sanitize
# ################################################################
# New HTML Helpers
# ################################################################
def xmlescape(text):
return cgi.escape(text, True).replace("'", "&#x27;")
class TAG(object):
def __init__(self, name, *children, **attributes):
self.name = name
self.children = list(children)
self.attributes = attributes
for child in self.children:
if isinstance(child, TAG):
child.parent = self
def xml(self):
name = self.name
a = ' '.join('%s="%s"' %
(k[1:], k[1:] if v is True else xmlescape(unicode(v)))
for k,v in self.attributes.iteritems()
if k.startswith('_') and not v in (False,None))
if a:
a = ' '+a
if name.endswith('/'):
return '<%s%s/>' % (name, a)
else:
b = ''.join(s.xml() if isinstance(s,TAG) else xmlescape(unicode(s))
for s in self.children)
return '<%s%s>%s</%s>' %(name, a, b, name)
def __unicode__(self):
return self.xml()
def __str__(self):
return self.xml().encode('utf8')
def __getitem__(self, key):
if isinstance(key, int):
return self.children[key]
else:
return self.attributes[key]
def __setitem__(self, key, value):
if isinstance(key, int):
self.children[key] = value
else:
self.attributes[key] = value
def append(self, value):
self.children.append(value)
def __delitem__(self,key):
if isinstance(key, int):
self.children = self.children[:key]+self.children[key+1:]
else:
del self.attributes[key]
def __len__(self):
return len(self.children)
def find(self, query):
raise NotImplementedError
class METATAG(object):
def __getattr__(self, name):
return self(name)
def __call__(self, name):
return lambda *children, **attributes: TAG(name, *children, **attributes)
tag = METATAG()
DIV = tag('div')
SPAN = tag('span')
LI = tag('li')
OL = tag('ol')
UL = tag('ul')
A = tag('a')
H1 = tag('h1')
H2 = tag('h2')
H3 = tag('h3')
H4 = tag('h4')
H5 = tag('h5')
H6 = tag('h6')
EM = tag('em')
TR = tag('tr')
TD = tag('td')
TH = tag('th')
IMG = tag('img/')
FORM = tag('form')
HEAD = tag('head')
BODY = tag('body')
TABLE = tag('table')
INPUT = tag('input/')
LABEL = tag('label')
STRONG = tag('strong')
SELECT = tag('select')
OPTION = tag('option')
TEXTAREA = tag('textarea')
# ################################################################
# New XML Helpers
# ################################################################
class XML(TAG):
"""
use it to wrap a string that contains XML/HTML so that it will not be
escaped by the template
Examples:
>>> XML('<h1>Hello</h1>').xml()
'<h1>Hello</h1>'
"""
def __init__(
self,
text,
sanitize=False,
permitted_tags=[
'a','b','blockquote','br/','i','li','ol','ul','p','cite',
'code','pre','img/','h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'tr', 'td', 'div','strong', 'span'],
allowed_attributes={
'a': ['href', 'title', 'target'],
'img': ['src', 'alt'],
'blockquote': ['type'],
'td': ['colspan']},
):
"""
Args:
text: the XML text
sanitize: sanitize text using the permitted tags and allowed
attributes (default False)
permitted_tags: list of permitted tags (default: simple list of
tags)
allowed_attributes: dictionary of allowed attributed (default
for A, IMG and BlockQuote).
The key is the tag; the value is a list of allowed attributes.
"""
if sanitize:
text = sanitize(text, permitted_tags, allowed_attributes)
if isinstance(text, unicode):
text = text.encode('utf8', 'xmlcharrefreplace')
elif not isinstance(text, str):
text = str(text)
self.text = text
def xml(self):
return self.text
def __str__(self):
return self.text
def __add__(self, other):
return '%s%s' % (self, other)
def __radd__(self, other):
return '%s%s' % (other, self)
def __cmp__(self, other):
return cmp(str(self), str(other))
def __hash__(self):
return hash(str(self))
def __getitem__(self, i):
return str(self)[i]
def __getslice__(self, i, j):
return str(self)[i:j]
def __iter__(self):
for c in str(self):
yield c
def __len__(self):
return len(str(self))
def XML_unpickle(data):
return XML(marshal.loads(data))
def XML_pickle(data):
return XML_unpickle, (marshal.dumps(str(data)),)
copy_reg.pickle(XML, XML_pickle, XML_unpickle)
# ################################################################
# Simple Form Style Function (example for more complex styles)
# ################################################################
def FormStyleDefault(table, vars, errors, readonly, deletable):
form = FORM(TABLE(),_method='POST',_action='#',_enctype='multipart/form-data')
for field in table:
input_id = '%s_%s' % (field.tablename, field.name)
value = field.formatter(vars.get(field.name))
error = errors.get(field.name)
field_class = field.type.split()[0].replace(':','-')
if field.type == 'blob': # never display blobs (mistake?)
continue
elif readonly or field.type=='id':
if not field.readable:
continue
else:
control = field.represent and field.represent(value) or value or ''
elif not field.writable:
continue
elif field.widget:
control = field.widget(table, value)
elif field.type == 'text':
control = TEXTAREA(value or '', _id=input_id,_name=field.name)
elif field.type == 'boolean':
control = INPUT(_type='checkbox', _id=input_id, _name=field.name,
_value='ON', _checked = value)
elif field.type == 'upload':
control = DIV(INPUT(_type='file', _id=input_id, _name=field.name))
if value:
control.append(A('download',
_href=URL('default','download',args=value)))
control.append(INPUT(_type='checkbox',_value='ON',
_name='_delete_'+field.name))
control.append('(check to remove)')
elif hasattr(field.requires, 'options'):
multiple = field.type.startswith('list:')
value = value if isinstance(value, list) else [value]
options = [OPTION(v,_value=k,_selected=(k in value))
for k,v in field.requires.options()]
control = SELECT(*options, _id=input_id, _name=field.name,
_multiple=multiple)
else:
field_type = 'password' if field.type == 'password' else 'text'
control = INPUT(_type=field_type, _id=input_id, _name=field.name,
_value=value, _class=field_class)
form[0].append(TR(TD(LABEL(field.label,_for=input_id)),
TD(control,DIV(error,_class='error') if error else ''),
TD(field.comment or '')))
td = TD(INPUT(_type='submit',_value='Submit'))
if deletable:
td.append(INPUT(_type='checkbox',_value='ON',_name='_delete'))
td.append('(check to delete)')
form[0].append(TR(TD(),td,TD()))
return form
# ################################################################
# Form object (replaced SQLFORM)
# ################################################################
class Form(object):
"""
Usage in web2py controller:
def index():
form = Form(db.thing, record=1)
if form.accepted: ...
elif form.errors: ...
else: ...
return dict(form=form)
Arguments:
- table: a DAL table or a list of fields (equivalent to old SQLFORM.factory)
- record: a DAL record or record id
- readonly: set to True to make a readonly form
- deletable: set to False to disallow deletion of record
- formstyle: a function that renders the form using helpers (FormStyleDefault)
- dbio: set to False to prevent any DB write
- keepvalues: (NOT IMPLEMENTED)
- formname: the optional name of this form
- csrf: set to False to disable CRSF protection
"""
def __init__(self,
table,
record=None,
readonly=False,
deletable=True,
formstyle=FormStyleDefault,
dbio=True,
keepvalues=False,
formname=False,
csrf=True):
if isinstance(table, list):
dbio = False
# mimic a table from a list of fields without calling define_table
formname = formname or 'none'
for field in table: field.tablename = formname
if isinstance(record, (int, long, basestring)):
record_id = int(str(record))
self.record = table[record_id]
else:
self.record = record
self.table = table
self.readonly = readonly
self.deletable = deletable and not readonly and self.record
self.formstyle = formstyle
self.dbio = dbio
self.keepvalues = True if keepvalues or self.record else False
self.csrf = csrf
self.vars = Storage()
self.errors = Storage()
self.submitted = False
self.deleted = False
self.accepted = False
self.cached_helper = False
self.formname = formname or table._tablename
self.formkey = None
request = current.request
session = current.session
post_vars = request.post_vars
if readonly or request.env.request_method=='GET':
if self.record:
self.vars = self.record
else:
print post_vars
self.submitted = True
# check for CSRF
if csrf and self.formname in (session._formkeys or {}):
self.formkey = session._formkeys[self.formname]
# validate fields
if not csrf or post_vars._formkey == self.formkey:
if not post_vars._delete:
for field in self.table:
if field.writable:
value = post_vars.get(field.name)
(value, error) = field.validate(value)
if field.type == 'upload':
delete = post_vars.get('_delete_'+field.name)
if value is not None and hasattr(value,'file'):
value = field.store(value.file,
value.filename,
field.uploadfolder)
elif self.record and not delete:
value = self.record.get(field.name)
else:
value = None
self.vars[field.name] = value
if error:
self.errors[field.name] = error
if self.record:
self.vars.id = self.record.id
if not self.errors:
self.accepted = True
if dbio:
if self.record:
self.record.update_record(**self.vars)
else:
# warning, should we really insert if record
self.vars.id = self.table.insert(**self.vars)
elif dbio:
self.deleted = True
self.record.delete_record()
# store key for future CSRF
if csrf:
if not session._formkeys:
session._formkeys = {}
if self.formname not in session._formkeys:
session._formkeys[self.formname] = web2py_uuid()
self.formkey = session._formkeys[self.formname]
def clear():
self.vars.clear()
self.errors.clear()
for field in self.table:
self.vars[field.name] = field.default
def helper(self):
if not self.cached_helper:
cached_helper = self.formstyle(self.table,
self.vars,
self.errors,
self.readonly,
self.deletable)
if self.csrf:
cached_helper.append(INPUT(_type='hidden',_name='_formkey',
_value=self.formkey))
self.cached_helper = cached_helper
return cached_helper
def xml(self):
return self.helper().xml()
def __unicode__(self):
return self.xml()
def __str__(self):
return self.xml().encode('utf8')
if __name__=='__main__':
print(DIV(SPAN('this',STRONG('a test'),XML('1<2')),_id=1,_class="my class"))
+11 -16
View File
@@ -362,25 +362,20 @@ class Request(Storage):
redirect(URL(scheme='https', args=self.args, vars=self.vars)) redirect(URL(scheme='https', args=self.args, vars=self.vars))
def restful(self): def restful(self):
def wrapper(action, request=self): def wrapper(action, self=self):
def f(_action=action, *a, **b): def f(_action=action, _self=self, *a, **b):
request.is_restful = True self.is_restful = True
env = request.env method = _self.env.request_method
is_json = env.content_type=='application/json' if len(_self.args) and '.' in _self.args[-1]:
method = env.request_method _self.args[-1], _, self.extension = self.args[-1].rpartition('.')
if len(request.args) and '.' in request.args[-1]:
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
current.response.headers['Content-Type'] = \ current.response.headers['Content-Type'] = \
contenttype('.' + request.extension.lower()) contenttype('.' + _self.extension.lower())
rest_action = _action().get(method, None) rest_action = _action().get(method, None)
if not (rest_action and method == method.upper() if not (rest_action and method == method.upper()
and callable(rest_action)): and callable(rest_action)):
raise HTTP(405, "method not allowed") raise HTTP(405, "method not allowed")
try: try:
res = rest_action(*request.args, **request.vars) return rest_action(*_self.args, **getattr(_self, 'vars', {}))
if is_json and not isinstance(res, str):
res = json(res)
return res
except TypeError, e: except TypeError, e:
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
if len(traceback.extract_tb(exc_traceback)) == 1: if len(traceback.extract_tb(exc_traceback)) == 1:
@@ -812,7 +807,7 @@ class Session(Storage):
response.session_data_name = 'session_data_%s' % masterapp.lower() response.session_data_name = 'session_data_%s' % masterapp.lower()
response.session_cookie_expires = cookie_expires response.session_cookie_expires = cookie_expires
response.session_client = str(request.client).replace(':', '.') response.session_client = str(request.client).replace(':', '.')
current._session_cookie_key = cookie_key response.session_cookie_key = cookie_key
response.session_cookie_compression_level = compression_level response.session_cookie_compression_level = compression_level
# check if there is a session_id in cookies # check if there is a session_id in cookies
@@ -1065,7 +1060,7 @@ class Session(Storage):
# if not cookie_key, but session_data_name in cookies # if not cookie_key, but session_data_name in cookies
# expire session_data_name from cookies # expire session_data_name from cookies
if not current._session_cookie_key: if not response.session_cookie_key:
if response.session_data_name in cookies: if response.session_data_name in cookies:
rcookies[response.session_data_name] = 'expired' rcookies[response.session_data_name] = 'expired'
rcookies[response.session_data_name]['path'] = '/' rcookies[response.session_data_name]['path'] = '/'
@@ -1128,7 +1123,7 @@ class Session(Storage):
name = response.session_data_name name = response.session_data_name
compression_level = response.session_cookie_compression_level compression_level = response.session_cookie_compression_level
value = secure_dumps(dict(self), value = secure_dumps(dict(self),
current._session_cookie_key, response.session_cookie_key,
compression_level=compression_level) compression_level=compression_level)
rcookies = response.cookies rcookies = response.cookies
rcookies.pop(name, None) rcookies.pop(name, None)
+111 -119
View File
@@ -116,7 +116,6 @@ __all__ = [
DEFAULT_PASSWORD_DISPLAY = '*' * 8 DEFAULT_PASSWORD_DISPLAY = '*' * 8
def xmlescape(data, quote=True): def xmlescape(data, quote=True):
""" """
Returns an escaped string of the provided data Returns an escaped string of the provided data
@@ -140,14 +139,12 @@ def xmlescape(data, quote=True):
data = cgi.escape(data, quote).replace("'", "&#x27;") data = cgi.escape(data, quote).replace("'", "&#x27;")
return data return data
def call_as_list(f, *a, **b): def call_as_list(f, *a, **b):
if not isinstance(f, (list, tuple)): if not isinstance(f, (list, tuple)):
f = [f] f = [f]
for item in f: for item in f:
item(*a, **b) item(*a, **b)
def truncate_string(text, length, dots='...'): def truncate_string(text, length, dots='...'):
text = text.decode('utf-8') text = text.decode('utf-8')
if len(text) > length: if len(text) > length:
@@ -155,26 +152,27 @@ def truncate_string(text, length, dots='...'):
return text return text
def URL(a=None, def URL(
c=None, a=None,
f=None, c=None,
r=None, f=None,
args=None, r=None,
vars=None, args=None,
anchor='', vars=None,
extension=None, anchor='',
env=None, extension=None,
hmac_key=None, env=None,
hash_vars=True, hmac_key=None,
salt=None, hash_vars=True,
user_signature=None, salt=None,
scheme=None, user_signature=None,
host=None, scheme=None,
port=None, host=None,
encode_embedded_slash=False, port=None,
url_encode=True, encode_embedded_slash=False,
language=None url_encode=True,
): language=None,
):
""" """
generates a url '/a/c/f' corresponding to application a, controller c generates a url '/a/c/f' corresponding to application a, controller c
and function f. If r=request is passed, a, c, f are set, respectively, and function f. If r=request is passed, a, c, f are set, respectively,
@@ -258,6 +256,10 @@ def URL(a=None,
>>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True)) >>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
'/a/c/f#%25%28id%29d' '/a/c/f#%25%28id%29d'
""" """
from rewrite import url_out # done here in case used not-in web2py from rewrite import url_out # done here in case used not-in web2py
@@ -308,7 +310,7 @@ def URL(a=None,
else: else:
function = f function = f
# if the url gets a static resource, don't force extension # if the url gets a static resource, don't force extention
if controller == 'static': if controller == 'static':
extension = None extension = None
# add static version to url # add static version to url
@@ -317,7 +319,7 @@ def URL(a=None,
response = current.response response = current.response
if response.static_version and response.static_version_urls: if response.static_version and response.static_version_urls:
args = [function] + args args = [function] + args
function = '_' + str(response.static_version) function = '_'+str(response.static_version)
if '.' in function: if '.' in function:
function, extension = function.rsplit('.', 1) function, extension = function.rsplit('.', 1)
@@ -330,16 +332,18 @@ def URL(a=None,
if args: if args:
if url_encode: if url_encode:
if encode_embedded_slash: if encode_embedded_slash:
other = '/' + '/'.join([urllib.quote(str(x), '') for x in args]) other = '/' + '/'.join([urllib.quote(str(
x), '') for x in args])
else: else:
other = args and urllib.quote('/' + '/'.join([str(x) for x in args])) other = args and urllib.quote(
'/' + '/'.join([str(x) for x in args]))
else: else:
other = args and ('/' + '/'.join([str(x) for x in args])) other = args and ('/' + '/'.join([str(x) for x in args]))
else: else:
other = '' other = ''
if other.endswith('/'): if other.endswith('/'):
other += '/' # add trailing slash to make last trailing empty arg explicit other += '/' # add trailing slash to make last trailing empty arg explicit
list_vars = [] list_vars = []
for (key, vals) in sorted(vars.items()): for (key, vals) in sorted(vars.items()):
@@ -362,11 +366,11 @@ def URL(a=None,
h_args = '/%s/%s/%s%s' % (application, controller, function2, other) h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
# how many of the vars should we include in our hash? # how many of the vars should we include in our hash?
if hash_vars is True: # include them all if hash_vars is True: # include them all
h_vars = list_vars h_vars = list_vars
elif hash_vars is False: # include none of them elif hash_vars is False: # include none of them
h_vars = '' h_vars = ''
else: # include just those specified else: # include just those specified
if hash_vars and not isinstance(hash_vars, (list, tuple)): if hash_vars and not isinstance(hash_vars, (list, tuple)):
hash_vars = [hash_vars] hash_vars = [hash_vars]
h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars] h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
@@ -435,7 +439,7 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
""" """
if '_signature' not in request.get_vars: if not '_signature' in request.get_vars:
return False # no signature in the request URL return False # no signature in the request URL
# check if user_signature requires # check if user_signature requires
@@ -535,24 +539,19 @@ class XmlComponent(object):
return CAT(*components) return CAT(*components)
def add_class(self, name): def add_class(self, name):
""" """ add a class to _class attribute """
add a class to _class attribute
"""
c = self['_class'] c = self['_class']
classes = (set(c.split()) if c else set()) | set(name.split()) classes = (set(c.split()) if c else set()) | set(name.split())
self['_class'] = ' '.join(classes) if classes else None self['_class'] = ' '.join(classes) if classes else None
return self return self
def remove_class(self, name): def remove_class(self, name):
""" """ remove a class from _class attribute """
remove a class from _class attribute
"""
c = self['_class'] c = self['_class']
classes = (set(c.split()) if c else set()) - set(name.split()) classes = (set(c.split()) if c else set()) - set(name.split())
self['_class'] = ' '.join(classes) if classes else None self['_class'] = ' '.join(classes) if classes else None
return self return self
class XML(XmlComponent): class XML(XmlComponent):
""" """
use it to wrap a string that contains XML/HTML so that it will not be use it to wrap a string that contains XML/HTML so that it will not be
@@ -661,11 +660,11 @@ class XML(XmlComponent):
""" """
to be considered experimental since the behavior of this method to be considered experimental since the behavior of this method
is questionable is questionable
another option could be `TAG(self.text).elements(*args, **kwargs)` another option could be `TAG(self.text).elements(*args,**kwargs)`
""" """
return [] return []
# ## important to allow safe session.flash=T(....) ### important to allow safe session.flash=T(....)
def XML_unpickle(data): def XML_unpickle(data):
@@ -758,7 +757,7 @@ class DIV(XmlComponent):
Examples: Examples:
>>> a=DIV() >>> a=DIV()
>>> a.insert(0, SPAN('x')) >>> a.insert(0,SPAN('x'))
>>> print a >>> print a
<div><span>x</span></div> <div><span>x</span></div>
""" """
@@ -854,7 +853,7 @@ class DIV(XmlComponent):
""" """
components = [] components = []
for c in self.components: for c in self.components:
if isinstance(c, (allowed_parents, CAT)): if isinstance(c, (allowed_parents,CAT)):
pass pass
elif wrap_lambda: elif wrap_lambda:
c = wrap_lambda(c) c = wrap_lambda(c)
@@ -953,6 +952,7 @@ class DIV(XmlComponent):
# get the xml for the inner components # get the xml for the inner components
co = join([xmlescape(component) for component in co = join([xmlescape(component) for component in
self.components]) self.components])
return (fa, co) return (fa, co)
def xml(self): def xml(self):
@@ -987,7 +987,7 @@ class DIV(XmlComponent):
Examples: Examples:
>>> markdown = lambda text, tag=None, attributes={}: \ >>> markdown = lambda text,tag=None,attributes={}: \
{None: re.sub('\s+',' ',text), \ {None: re.sub('\s+',' ',text), \
'h1':'#'+text+'\\n\\n', \ 'h1':'#'+text+'\\n\\n', \
'p':text+'\\n'}.get(tag,text) 'p':text+'\\n'}.get(tag,text)
@@ -1024,7 +1024,7 @@ class DIV(XmlComponent):
Examples: Examples:
>>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y')))) >>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
>>> for c in a.elements('span', first_only=True): c[0]='z' >>> for c in a.elements('span',first_only=True): c[0]='z'
>>> print a >>> print a
<div><div><span>z</span>3<div><span>y</span></div></div></div> <div><div><span>z</span>3<div><span>y</span></div></div></div>
>>> for c in a.elements('span'): c[0]='z' >>> for c in a.elements('span'): c[0]='z'
@@ -1056,7 +1056,7 @@ class DIV(XmlComponent):
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc')))) >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements('span.abc', replace=P('x', _class='xyz')) >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
>>> print a # We should .xml() here instead of print >>> print a
<div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div> <div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
"replace" can be a callable, which will be passed the original element and "replace" can be a callable, which will be passed the original element and
@@ -1168,13 +1168,13 @@ class DIV(XmlComponent):
return i return i
else: else:
self[i] = replace(self[i]) if callable(replace) else replace self[i] = replace(self[i]) if callable(replace) else replace
return i + 1 return i+1
# loop the components # loop the components
if find_text or find_components: if find_text or find_components:
i = 0 i = 0
while i < len(self.components): while i<len(self.components):
c = self[i] c = self[i]
j = i + 1 j = i+1
if check and find_text and isinstance(c, str) and \ if check and find_text and isinstance(c, str) and \
((is_regex and find_text.search(c)) or (str(find_text) in c)): ((is_regex and find_text.search(c)) or (str(find_text) in c)):
j = replace_component(i) j = replace_component(i)
@@ -1265,7 +1265,6 @@ class __tag_div__(DIV):
copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler) copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
class __TAG__(XmlComponent): class __TAG__(XmlComponent):
""" """
@@ -1590,7 +1589,6 @@ class A(DIV):
self['_data-w2p_pre_call'] = self['pre_call'] self['_data-w2p_pre_call'] = self['pre_call']
return DIV.xml(self) return DIV.xml(self)
class BUTTON(DIV): class BUTTON(DIV):
tag = 'button' tag = 'button'
@@ -1865,11 +1863,11 @@ class INPUT(DIV):
print traceback.format_exc() print traceback.format_exc()
msg = "Validation error, field:%s %s" % (name,validator) msg = "Validation error, field:%s %s" % (name,validator)
raise Exception(msg) raise Exception(msg)
if errors is not None: if not errors is None:
self.vars[name] = value self.vars[name] = value
self.errors[name] = errors self.errors[name] = errors
break break
if name not in self.errors: if not name in self.errors:
self.vars[name] = value self.vars[name] = value
return True return True
return False return False
@@ -1884,7 +1882,7 @@ class INPUT(DIV):
_value = None _value = None
else: else:
_value = str(self['_value']) _value = str(self['_value'])
if '_checked' in self.attributes and 'value' not in self.attributes: if '_checked' in self.attributes and not 'value' in self.attributes:
pass pass
elif t == 'checkbox': elif t == 'checkbox':
if not _value: if not _value:
@@ -1914,7 +1912,8 @@ class INPUT(DIV):
if name and hasattr(self, 'errors') \ if name and hasattr(self, 'errors') \
and self.errors.get(name, None) \ and self.errors.get(name, None) \
and self['hideerror'] != True: and self['hideerror'] != True:
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput' self['_class'] = (self['_class'] and self['_class']
+ ' ' or '') + 'invalidinput'
return DIV.xml(self) + DIV( return DIV.xml(self) + DIV(
DIV( DIV(
self.errors[name], _class='error', self.errors[name], _class='error',
@@ -1942,11 +1941,11 @@ class TEXTAREA(INPUT):
tag = 'textarea' tag = 'textarea'
def _postprocessing(self): def _postprocessing(self):
if '_rows' not in self.attributes: if not '_rows' in self.attributes:
self['_rows'] = 10 self['_rows'] = 10
if '_cols' not in self.attributes: if not '_cols' in self.attributes:
self['_cols'] = 40 self['_cols'] = 40
if self['value'] is not None: if not self['value'] is None:
self.components = [self['value']] self.components = [self['value']]
elif self.components: elif self.components:
self['value'] = self.components[0] self['value'] = self.components[0]
@@ -1957,7 +1956,7 @@ class OPTION(DIV):
tag = 'option' tag = 'option'
def _fixup(self): def _fixup(self):
if '_value' not in self.attributes: if not '_value' in self.attributes:
self.attributes['_value'] = str(self.components[0]) self.attributes['_value'] = str(self.components[0])
@@ -2013,10 +2012,11 @@ class SELECT(INPUT):
options = itertools.chain(*component_list) options = itertools.chain(*component_list)
value = self['value'] value = self['value']
if value is not None: if not value is None:
if not self['_multiple']: if not self['_multiple']:
for c in options: # my patch for c in options: # my patch
if ((value is not None) and (str(c['_value']) == str(value))): if ((value is not None) and
(str(c['_value']) == str(value))):
c['_selected'] = 'selected' c['_selected'] = 'selected'
else: else:
c['_selected'] = None c['_selected'] = None
@@ -2026,7 +2026,8 @@ class SELECT(INPUT):
else: else:
values = [str(value)] values = [str(value)]
for c in options: # my patch for c in options: # my patch
if ((value is not None) and (str(c['_value']) in values)): if ((value is not None) and
(str(c['_value']) in values)):
c['_selected'] = 'selected' c['_selected'] = 'selected'
else: else:
c['_selected'] = None c['_selected'] = None
@@ -2076,15 +2077,16 @@ class FORM(DIV):
def assert_status(self, status, request_vars): def assert_status(self, status, request_vars):
return status return status
def accepts(self, def accepts(
request_vars, self,
session=None, request_vars,
formname='default', session=None,
keepvalues=False, formname='default',
onvalidation=None, keepvalues=False,
hideerror=False, onvalidation=None,
**kwargs hideerror=False,
): **kwargs
):
""" """
kwargs is not used but allows to specify the same interface for FORM and SQLFORM kwargs is not used but allows to specify the same interface for FORM and SQLFORM
""" """
@@ -2126,7 +2128,8 @@ class FORM(DIV):
onsuccess = onvalidation.get('onsuccess', None) onsuccess = onvalidation.get('onsuccess', None)
onfailure = onvalidation.get('onfailure', None) onfailure = onvalidation.get('onfailure', None)
onchange = onvalidation.get('onchange', None) onchange = onvalidation.get('onchange', None)
if [k for k in onvalidation if k not in ('onsuccess', 'onfailure', 'onchange')]: if [k for k in onvalidation if not k in (
'onsuccess', 'onfailure', 'onchange')]:
raise RuntimeError('Invalid key in onvalidate dict') raise RuntimeError('Invalid key in onvalidate dict')
if onsuccess and status: if onsuccess and status:
call_as_list(onsuccess, self) call_as_list(onsuccess, self)
@@ -2141,7 +2144,7 @@ class FORM(DIV):
call_as_list(onvalidation, self) call_as_list(onvalidation, self)
if self.errors: if self.errors:
status = False status = False
if session is not None: if not session is None:
if hasattr(self, 'record_hash'): if hasattr(self, 'record_hash'):
formkey = self.record_hash + ':' + web2py_uuid() formkey = self.record_hash + ':' + web2py_uuid()
else: else:
@@ -2155,22 +2158,25 @@ class FORM(DIV):
return status return status
def _postprocessing(self): def _postprocessing(self):
if '_action' not in self.attributes: if not '_action' in self.attributes:
self['_action'] = '#' self['_action'] = '#'
if '_method' not in self.attributes: if not '_method' in self.attributes:
self['_method'] = 'post' self['_method'] = 'post'
if '_enctype' not in self.attributes: if not '_enctype' in self.attributes:
self['_enctype'] = 'multipart/form-data' self['_enctype'] = 'multipart/form-data'
def hidden_fields(self): def hidden_fields(self):
c = [] c = []
attr = self.attributes.get('hidden', {}) attr = self.attributes.get('hidden', {})
if 'hidden' in self.attributes: if 'hidden' in self.attributes:
c = [INPUT(_type='hidden', _name=key, _value=value) for (key, value) in attr.iteritems()] c = [INPUT(_type='hidden', _name=key, _value=value)
for (key, value) in attr.iteritems()]
if hasattr(self, 'formkey') and self.formkey: if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey', _value=self.formkey)) c.append(INPUT(_type='hidden', _name='_formkey',
_value=self.formkey))
if hasattr(self, 'formname') and self.formname: if hasattr(self, 'formname') and self.formname:
c.append(INPUT(_type='hidden', _name='_formname', _value=self.formname)) c.append(INPUT(_type='hidden', _name='_formname',
_value=self.formname))
return DIV(c, _style="display:none;") return DIV(c, _style="display:none;")
def xml(self): def xml(self):
@@ -2215,7 +2221,8 @@ class FORM(DIV):
kwargs['request_vars'] = kwargs.get( kwargs['request_vars'] = kwargs.get(
'request_vars', current.request.post_vars) 'request_vars', current.request.post_vars)
kwargs['session'] = kwargs.get('session', current.session) kwargs['session'] = kwargs.get('session', current.session)
kwargs['dbio'] = kwargs.get('dbio', False) # necessary for SQLHTML forms kwargs['dbio'] = kwargs.get('dbio', False)
# necessary for SQLHTML forms
onsuccess = kwargs.get('onsuccess', 'flash') onsuccess = kwargs.get('onsuccess', 'flash')
onfailure = kwargs.get('onfailure', 'flash') onfailure = kwargs.get('onfailure', 'flash')
@@ -2294,7 +2301,8 @@ class FORM(DIV):
""" """
kwargs['dbio'] = kwargs.get('dbio', True) # necessary for SQLHTML forms kwargs['dbio'] = kwargs.get('dbio', True)
# necessary for SQLHTML forms
self.validate(**kwargs) self.validate(**kwargs)
return self return self
@@ -2340,9 +2348,10 @@ class FORM(DIV):
def sanitizer(obj): def sanitizer(obj):
if isinstance(obj, dict): if isinstance(obj, dict):
for k in obj.keys(): for k in obj.keys():
if any([unsafe in str(k).upper() for unsafe in UNSAFE]): if any([unsafe in str(k).upper() for
# erease unsafe pair unsafe in UNSAFE]):
obj.pop(k) # erease unsafe pair
obj.pop(k)
else: else:
# not implemented # not implemented
pass pass
@@ -2368,10 +2377,8 @@ class FORM(DIV):
return [flatten(item) for item in newobj] return [flatten(item) for item in newobj]
else: else:
return newobj return newobj
else: else: return str(newobj)
return str(newobj) else: return newobj
else:
return newobj
return flatten(d) return flatten(d)
def as_json(self, sanitize=True): def as_json(self, sanitize=True):
@@ -2498,19 +2505,19 @@ class MENU(DIV):
self.data = data self.data = data
self.attributes = args self.attributes = args
self.components = [] self.components = []
if '_class' not in self.attributes: if not '_class' in self.attributes:
self['_class'] = 'web2py-menu web2py-menu-vertical' self['_class'] = 'web2py-menu web2py-menu-vertical'
if 'ul_class' not in self.attributes: if not 'ul_class' in self.attributes:
self['ul_class'] = 'web2py-menu-vertical' self['ul_class'] = 'web2py-menu-vertical'
if 'li_class' not in self.attributes: if not 'li_class' in self.attributes:
self['li_class'] = 'web2py-menu-expand' self['li_class'] = 'web2py-menu-expand'
if 'li_first' not in self.attributes: if not 'li_first' in self.attributes:
self['li_first'] = 'web2py-menu-first' self['li_first'] = 'web2py-menu-first'
if 'li_last' not in self.attributes: if not 'li_last' in self.attributes:
self['li_last'] = 'web2py-menu-last' self['li_last'] = 'web2py-menu-last'
if 'li_active' not in self.attributes: if not 'li_active' in self.attributes:
self['li_active'] = 'web2py-menu-active' self['li_active'] = 'web2py-menu-active'
if 'mobile' not in self.attributes: if not 'mobile' in self.attributes:
self['mobile'] = False self['mobile'] = False
def serialize(self, data, level=0): def serialize(self, data, level=0):
@@ -2570,7 +2577,7 @@ class MENU(DIV):
item[3], select, prefix=CAT(prefix, item[0], '/')) item[3], select, prefix=CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value' select['_onchange'] = 'window.location=this.value'
# avoid to wrap the select if no custom items are present # avoid to wrap the select if no custom items are present
html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select
return html return html
def xml(self): def xml(self):
@@ -2580,11 +2587,12 @@ class MENU(DIV):
return self.serialize(self.data, 0).xml() return self.serialize(self.data, 0).xml()
def embed64(filename=None, def embed64(
file=None, filename=None,
data=None, file=None,
extension='image/gif' data=None,
): extension='image/gif',
):
""" """
helper to encode the provided (binary) data into base64. helper to encode the provided (binary) data into base64.
@@ -2602,7 +2610,6 @@ def embed64(filename=None,
return 'data:%s;base64,%s' % (extension, data) return 'data:%s;base64,%s' % (extension, data)
# TODO: Check if this test() is still relevant now that we have gluon/tests/test_html.py
def test(): def test():
""" """
Example: Example:
@@ -2802,7 +2809,7 @@ class MARKMIN(XmlComponent):
self.extra = extra or {} self.extra = extra or {}
self.allowed = allowed or {} self.allowed = allowed or {}
self.sep = sep self.sep = sep
self.url = URL if url is True else url self.url = URL if url == True else url
self.environment = environment self.environment = environment
self.latex = latex self.latex = latex
self.autolinks = autolinks self.autolinks = autolinks
@@ -2826,26 +2833,11 @@ class MARKMIN(XmlComponent):
def __str__(self): def __str__(self):
return self.xml() return self.xml()
def ASSIGNJS(**kargs): def ASSIGNJS(**kargs):
"""
Example:
ASSIGNJS(var1='1', var2='2') will return the following javascript variables assignations :
var var1 = "1";
var var2 = "2";
Args:
**kargs: Any keywords arguments and assigned values.
Returns:
Javascript vars assignations for the key/value passed.
"""
from gluon.serializers import json from gluon.serializers import json
s = "" s = ""
for key, value in kargs.items(): for key, value in kargs.items():
s += 'var %s = %s;\n' % (key, json(value)) s+='var %s = %s;\n' % (key, json(value))
return XML(s) return XML(s)
-26
View File
@@ -61,13 +61,7 @@ PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\ + r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))' + r'(?:"(?:[^"\\]|\\.)*"))'
PY_M_STRING_LITERAL_RE = r'(?<=[^\w]T\.M\()(?P<name>'\
+ r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))'
regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL) regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL)
regex_translate_m = re.compile(PY_M_STRING_LITERAL_RE, re.DOTALL)
regex_param = re.compile(r'{(?P<s>.+?)}') regex_param = re.compile(r'{(?P<s>.+?)}')
# pattern for a valid accept_language # pattern for a valid accept_language
@@ -966,7 +960,6 @@ def findT(path, language=DEFAULT_LANGUAGE):
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0): + listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
data = read_locked(filename) data = read_locked(filename)
items = regex_translate.findall(data) items = regex_translate.findall(data)
items += regex_translate_m.findall(data)
for item in items: for item in items:
try: try:
message = safe_eval(item) message = safe_eval(item)
@@ -1002,25 +995,6 @@ def update_all_languages(application_path):
findT(application_path, language[:-3]) findT(application_path, language[:-3])
def update_from_langfile(target, source, force_update=False):
"""this will update untranslated messages in target from source (where both are language files)
this can be used as first step when creating language file for new but very similar language
or if you want update your app from welcome app of newer web2py version
or in non-standard scenarios when you work on target and from any reason you have partial translation in source
Args:
force_update: if False existing translations remain unchanged, if True existing translations will update from source
"""
src = read_dict(source)
sentences = read_dict(target)
for key in sentences:
val = sentences[key]
if not val or val == key or force_update:
new_val = src.get(key)
if new_val and new_val != val:
sentences[key] = new_val
write_dict(target, sentences)
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()
+4 -3
View File
@@ -80,9 +80,10 @@ locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
exists = os.path.exists exists = os.path.exists
pjoin = os.path.join pjoin = os.path.join
try: logpath = abspath("logging.conf")
if exists(logpath):
logging.config.fileConfig(abspath("logging.conf")) logging.config.fileConfig(abspath("logging.conf"))
except: # fails on GAE or when logfile is missing else:
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger("web2py") logger = logging.getLogger("web2py")
@@ -360,7 +361,7 @@ def wsgibase(environ, responder):
local_hosts = global_settings.local_hosts local_hosts = global_settings.local_hosts
client = get_client(env) client = get_client(env)
x_req_with = str(env.http_x_requested_with).lower() x_req_with = str(env.http_x_requested_with).lower()
cmd_opts = global_settings.cmd_options cmd_opts = request.global_settings.cmd_options
request.update( request.update(
client = client, client = client,
+1 -1
View File
@@ -53,8 +53,8 @@ except:
except: except:
try: try:
import win32con import win32con
import pywintypes
import win32file import win32file
import pywintypes
os_locking = 'windows' os_locking = 'windows'
except: except:
pass pass
+18 -4
View File
@@ -9,7 +9,7 @@
Generates names for cache and session files Generates names for cache and session files
-------------------------------------------- --------------------------------------------
""" """
import os import os, uuid
def generate(filename, depth=2, base=512): def generate(filename, depth=2, base=512):
@@ -17,10 +17,10 @@ def generate(filename, depth=2, base=512):
path, filename = os.path.split(filename) path, filename = os.path.split(filename)
else: else:
path = None path = None
dummyhash = sum(ord(c) * 256 ** (i % 4) for i, c in enumerate(filename)) % base ** depth dummyhash = sum(ord(c)*256**(i % 4) for i, c in enumerate(filename)) % base**depth
folders = [] folders = []
for level in range(depth - 1, -1, -1): for level in range(depth-1, -1, -1):
code, dummyhash = divmod(dummyhash, base ** level) code, dummyhash = divmod(dummyhash, base**level)
folders.append("%03x" % code) folders.append("%03x" % code)
folders.append(filename) folders.append(filename)
if path: if path:
@@ -63,3 +63,17 @@ def open(filename, mode="r", path=None):
if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)): if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)):
os.makedirs(os.path.dirname(fullfilename)) os.makedirs(os.path.dirname(fullfilename))
return file(fullfilename, mode) return file(fullfilename, mode)
def test():
if not os.path.exists('tests'):
os.mkdir('tests')
for k in range(20):
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
open(filename, "w").write('test')
assert open(filename, "r").read() == 'test'
if exists(filename):
remove(filename)
if __name__ == '__main__':
test()
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -642,7 +642,7 @@ def regex_url_in(request, environ):
items = filename.split('/', 1) items = filename.split('/', 1)
if regex_version.match(items[0]): if regex_version.match(items[0]):
version, filename = items version, filename = items
static_folder = pjoin(global_settings.applications_parent, static_folder = pjoin(request.env.applications_parent,
'applications', application, 'static') 'applications', application, 'static')
static_file = os.path.abspath(pjoin(static_folder, filename)) static_file = os.path.abspath(pjoin(static_folder, filename))
if not static_file.startswith(static_folder): if not static_file.startswith(static_folder):
@@ -947,7 +947,7 @@ class MapUrlIn(object):
if len(self.args) == 1 and self.arg0 in self.router.root_static: if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static' self.controller = self.request.controller = 'static'
root_static_file = pjoin(global_settings.applications_parent, root_static_file = pjoin(self.request.env.applications_parent,
'applications', self.application, 'applications', self.application,
self.controller, self.arg0) self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file) log_rewrite("route: root static=%s" % root_static_file)
@@ -1016,11 +1016,11 @@ class MapUrlIn(object):
# if language-specific file doesn't exist, try same file in static # if language-specific file doesn't exist, try same file in static
# #
if self.language: if self.language:
static_file = pjoin(global_settings.applications_parent, static_file = pjoin(self.request.env.applications_parent,
'applications', self.application, 'applications', self.application,
'static', self.language, file) 'static', self.language, file)
if not self.language or not isfile(static_file): if not self.language or not isfile(static_file):
static_file = pjoin(global_settings.applications_parent, static_file = pjoin(self.request.env.applications_parent,
'applications', self.application, 'applications', self.application,
'static', file) 'static', file)
self.extension = None self.extension = None
-43
View File
@@ -1870,46 +1870,3 @@ class WSGIWorker(Worker):
sock_file.close() sock_file.close()
# Monolithic build...end of module: rocket/methods/wsgi.py # Monolithic build...end of module: rocket/methods/wsgi.py
def demo_app(environ, start_response):
global static_folder
import os
types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif',
'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'}
if static_folder:
if not static_folder.startswith('/'):
static_folder = os.path.join(os.getcwd(),static_folder)
path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html')
type = types.get(path.split('.')[-1],'text')
if os.path.exists(path):
try:
data = open(path,'rb').read()
start_response('200 OK', [('Content-Type', type)])
except IOError:
start_response('404 NOT FOUND', [])
data = '404 NOT FOUND'
else:
start_response('500 INTERNAL SERVER ERROR', [])
data = '500 INTERNAL SERVER ERROR'
else:
start_response('200 OK', [('Content-Type', 'text/html')])
data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>'
return [data]
def demo():
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1",
help="ip address of the network interface")
parser.add_option("-p", "--port", dest="port",default="8000",
help="post where to run web server")
parser.add_option("-s", "--static", dest="static",default=None,
help="folder containing static files")
(options, args) = parser.parse_args()
global static_folder
static_folder = options.static
print 'Rocket running on %s:%s' % (options.ip, options.port)
r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app})
r.start()
if __name__=='__main__':
demo()
+147 -140
View File
@@ -9,26 +9,6 @@ Background processes made simple
--------------------------------- ---------------------------------
""" """
import os
import time
import multiprocessing
import sys
import threading
import traceback
import signal
import socket
import datetime
import logging
import optparse
import tempfile
import types
import Queue
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid
from gluon.storage import Storage
USAGE = """ USAGE = """
## Example ## Example
@@ -87,6 +67,20 @@ sudo restart web2py-scheduler
sudo status web2py-scheduler sudo status web2py-scheduler
""" """
import os
import time
import multiprocessing
import sys
import threading
import traceback
import signal
import socket
import datetime
import logging
import optparse
import types
import Queue
path = os.getcwd() path = os.getcwd()
if 'WEB2PY_PATH' not in os.environ: if 'WEB2PY_PATH' not in os.environ:
@@ -107,6 +101,12 @@ IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER) logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid
from gluon.storage import Storage
QUEUED = 'QUEUED' QUEUED = 'QUEUED'
ASSIGNED = 'ASSIGNED' ASSIGNED = 'ASSIGNED'
RUNNING = 'RUNNING' RUNNING = 'RUNNING'
@@ -168,25 +168,24 @@ class TaskReport(object):
class JobGraph(object): class JobGraph(object):
"""Experimental: dependencies amongs tasks""" """Experimental: with JobGraph you can specify
dependencies amongs tasks"""
def __init__(self, db, job_name): def __init__(self, db, job_name):
self.job_name = job_name or 'job_0' self.job_name = job_name or 'job_0'
self.db = db self.db = db
def add_deps(self, task_parent, task_child): def add_deps(self, task_parent, task_child):
"""Create a dependency between task_parent and task_child.""" """Creates a dependency between task_parent and task_child"""
self.db.scheduler_task_deps.insert(task_parent=task_parent, self.db.scheduler_task_deps.insert(task_parent=task_parent,
task_child=task_child, task_child=task_child,
job_name=self.job_name) job_name=self.job_name)
def validate(self, job_name=None): def validate(self, job_name):
"""Validate if all tasks job_name can be completed. """Validates if all tasks job_name can be completed, i.e. there
are no mutual dependencies among tasks.
Checks if there are no mutual dependencies among tasks.
Commits at the end if successfull, or it rollbacks the entire Commits at the end if successfull, or it rollbacks the entire
transaction. Handle with care! transaction. Handle with care!"""
"""
db = self.db db = self.db
sd = db.scheduler_task_deps sd = db.scheduler_task_deps
if job_name: if job_name:
@@ -216,7 +215,7 @@ class JobGraph(object):
nested_dict = dict( nested_dict = dict(
(item, (dep - ordered)) for item, dep in nested_dict.items() (item, (dep - ordered)) for item, dep in nested_dict.items()
if item not in ordered if item not in ordered
) )
assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict
db.commit() db.commit()
return rtn return rtn
@@ -224,6 +223,14 @@ class JobGraph(object):
db.rollback() db.rollback()
return None return None
def demo_function(*argv, **kwargs):
""" test function """
for i in range(argv[0]):
print 'click', i
time.sleep(1)
return 'done'
# the two functions below deal with simplejson decoding as unicode, esp for the dict decode # the two functions below deal with simplejson decoding as unicode, esp for the dict decode
# and subsequent usage as function Keyword arguments unicode variable names won't work! # and subsequent usage as function Keyword arguments unicode variable names won't work!
# borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python # borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python
@@ -254,12 +261,11 @@ def _decode_dict(dct):
def executor(queue, task, out): def executor(queue, task, out):
"""The function used to execute tasks in the background process.""" """The function used to execute tasks in the background process"""
logger.debug(' task started') logger.debug(' task started')
class LogOutput(object): class LogOutput(object):
"""Facility to log output at intervals.""" """Facility to log output at intervals"""
def __init__(self, out_queue): def __init__(self, out_queue):
self.out_queue = out_queue self.out_queue = out_queue
self.stdout = sys.stdout self.stdout = sys.stdout
@@ -274,11 +280,7 @@ def executor(queue, task, out):
def write(self, data): def write(self, data):
self.out_queue.put(data) self.out_queue.put(data)
W2P_TASK = Storage({ W2P_TASK = Storage({'id': task.task_id, 'uuid': task.uuid})
'id': task.task_id,
'uuid': task.uuid,
'run_id': task.run_id
})
stdout = LogOutput(out) stdout = LogOutput(out)
try: try:
if task.app: if task.app:
@@ -295,7 +297,7 @@ def executor(queue, task, out):
f = task.function f = task.function
functions = current._scheduler.tasks functions = current._scheduler.tasks
if not functions: if not functions:
# look into env #look into env
_function = _env.get(f) _function = _env.get(f)
else: else:
_function = functions.get(f) _function = functions.get(f)
@@ -312,15 +314,10 @@ def executor(queue, task, out):
vars = loads(task.vars, object_hook=_decode_dict) vars = loads(task.vars, object_hook=_decode_dict)
result = dumps(_function(*args, **vars)) result = dumps(_function(*args, **vars))
else: else:
# for testing purpose only ### for testing purpose only
result = eval(task.function)( result = eval(task.function)(
*loads(task.args, object_hook=_decode_dict), *loads(task.args, object_hook=_decode_dict),
**loads(task.vars, object_hook=_decode_dict)) **loads(task.vars, object_hook=_decode_dict))
if len(result) >= 1024:
fd, temp_path = tempfile.mkstemp(suffix='.w2p_sched')
with os.fdopen(fd, 'w') as f:
f.write(result)
result = 'w2p_special:%s' % temp_path
queue.put(TaskReport('COMPLETED', result=result)) queue.put(TaskReport('COMPLETED', result=result))
except BaseException, e: except BaseException, e:
tb = traceback.format_exc() tb = traceback.format_exc()
@@ -338,7 +335,7 @@ class MetaScheduler(threading.Thread):
self.empty_runs = 0 self.empty_runs = 0
def async(self, task): def async(self, task):
"""Start the background process. """Starts the background process
Args: Args:
task : a `Task` object task : a `Task` object
@@ -394,6 +391,7 @@ class MetaScheduler(threading.Thread):
except: except:
p.terminate() p.terminate()
p.join() p.join()
self.have_heartbeat = False
logger.debug(' task stopped by general exception') logger.debug(' task stopped by general exception')
tr = TaskReport(STOPPED) tr = TaskReport(STOPPED)
else: else:
@@ -408,17 +406,12 @@ class MetaScheduler(threading.Thread):
except Queue.Empty: except Queue.Empty:
tr = TaskReport(TIMEOUT) tr = TaskReport(TIMEOUT)
elif queue.empty(): elif queue.empty():
self.have_heartbeat = False
logger.debug(' task stopped') logger.debug(' task stopped')
tr = TaskReport(STOPPED) tr = TaskReport(STOPPED)
else: else:
logger.debug(' task completed or failed') logger.debug(' task completed or failed')
tr = queue.get() tr = queue.get()
result = tr.result
if result and result.startswith('w2p_special'):
temp_path = result.replace('w2p_special:', '', 1)
with open(temp_path) as f:
tr.result = f.read()
os.unlink(temp_path)
tr.output = task_output tr.output = task_output
return tr return tr
@@ -453,23 +446,50 @@ class MetaScheduler(threading.Thread):
self.start() self.start()
def send_heartbeat(self, counter): def send_heartbeat(self, counter):
raise NotImplementedError print 'thum'
time.sleep(1)
def pop_task(self): def pop_task(self):
"""Fetches a task ready to be executed""" """Fetches a task ready to be executed"""
raise NotImplementedError return Task(
app=None,
function='demo_function',
timeout=7,
args='[2]',
vars='{}')
def report_task(self, task, task_report): def report_task(self, task, task_report):
"""Creates a task report""" """Creates a task report"""
raise NotImplementedError print 'reporting task'
pass
def sleep(self): def sleep(self):
raise NotImplementedError pass
def loop(self): def loop(self):
"""Main loop, fetching tasks and starting executor's background """Main loop, fetching tasks and starting executor's background
processes""" processes"""
raise NotImplementedError try:
self.start_heartbeats()
while True and self.have_heartbeat:
logger.debug('looping...')
task = self.pop_task()
if task:
self.empty_runs = 0
self.report_task(task, self.async(task))
else:
self.empty_runs += 1
logger.debug('sleeping...')
if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s',
self.empty_runs, self.max_empty_runs)
if self.empty_runs >= self.max_empty_runs:
logger.info(
'empty runs limit reached, killing myself')
self.die()
self.sleep()
except KeyboardInterrupt:
self.die()
TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED) TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED)
@@ -576,11 +596,11 @@ class Scheduler(MetaScheduler):
return True return True
def now(self): def now(self):
"""Shortcut that fetches current time based on UTC preferences.""" """Shortcut that fetches current time based on UTC preferences"""
return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now() return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now()
def set_requirements(self, scheduler_task): def set_requirements(self, scheduler_task):
"""Called to set defaults for lazy_tables connections.""" """Called to set defaults for lazy_tables connections"""
from gluon import current from gluon import current
if hasattr(current, 'request'): if hasattr(current, 'request'):
scheduler_task.application_name.default = '%s/%s' % ( scheduler_task.application_name.default = '%s/%s' % (
@@ -588,7 +608,7 @@ class Scheduler(MetaScheduler):
) )
def define_tables(self, db, migrate): def define_tables(self, db, migrate):
"""Define Scheduler tables structure.""" """Defines Scheduler tables structure"""
from pydal.base import DEFAULT from pydal.base import DEFAULT
logger.debug('defining tables (migrate=%s)', migrate) logger.debug('defining tables (migrate=%s)', migrate)
now = self.now now = self.now
@@ -645,7 +665,7 @@ class Scheduler(MetaScheduler):
Field('traceback', 'text'), Field('traceback', 'text'),
Field('worker_name', default=self.worker_name), Field('worker_name', default=self.worker_name),
migrate=self.__get_migrate('scheduler_run', migrate) migrate=self.__get_migrate('scheduler_run', migrate)
) )
db.define_table( db.define_table(
'scheduler_worker', 'scheduler_worker',
@@ -657,32 +677,25 @@ class Scheduler(MetaScheduler):
Field('group_names', 'list:string', default=self.group_names), Field('group_names', 'list:string', default=self.group_names),
Field('worker_stats', 'json'), Field('worker_stats', 'json'),
migrate=self.__get_migrate('scheduler_worker', migrate) migrate=self.__get_migrate('scheduler_worker', migrate)
) )
db.define_table( db.define_table(
'scheduler_task_deps', 'scheduler_task_deps',
Field('job_name', default='job_0'), Field('job_name', default='job_0'),
Field('task_parent', 'integer', Field('task_parent', 'integer',
requires=IS_IN_DB(db, 'scheduler_task.id', '%(task_name)s') requires=IS_IN_DB(db, 'scheduler_task.id',
), '%(task_name)s')
),
Field('task_child', 'reference scheduler_task'), Field('task_child', 'reference scheduler_task'),
Field('can_visit', 'boolean', default=False), Field('can_visit', 'boolean', default=False),
migrate=self.__get_migrate('scheduler_task_deps', migrate) migrate=self.__get_migrate('scheduler_task_deps', migrate)
) )
if migrate is not False: if migrate is not False:
db.commit() db.commit()
@staticmethod
def total_seconds(td):
"""Backport for py2.6."""
if hasattr(td, 'total_seconds'):
return td.total_seconds()
else:
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6
def loop(self, worker_name=None): def loop(self, worker_name=None):
"""Main loop. """Main loop
This works basically as a neverending loop that: This works basically as a neverending loop that:
@@ -705,7 +718,7 @@ class Scheduler(MetaScheduler):
while True and self.have_heartbeat: while True and self.have_heartbeat:
if self.w_stats.status == DISABLED: if self.w_stats.status == DISABLED:
logger.debug('Someone stopped me, sleeping until better' logger.debug('Someone stopped me, sleeping until better'
' times come (%s)', self.w_stats.sleep) ' times come (%s)', self.w_stats.sleep)
self.sleep() self.sleep()
continue continue
logger.debug('looping...') logger.debug('looping...')
@@ -722,8 +735,7 @@ class Scheduler(MetaScheduler):
logger.debug('sleeping...') logger.debug('sleeping...')
if self.max_empty_runs != 0: if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s', logger.debug('empty runs %s/%s',
self.w_stats.empty_runs, self.w_stats.empty_runs, self.max_empty_runs)
self.max_empty_runs)
if self.w_stats.empty_runs >= self.max_empty_runs: if self.w_stats.empty_runs >= self.max_empty_runs:
logger.info( logger.info(
'empty runs limit reached, killing myself') 'empty runs limit reached, killing myself')
@@ -734,8 +746,7 @@ class Scheduler(MetaScheduler):
self.die() self.die()
def wrapped_assign_tasks(self, db): def wrapped_assign_tasks(self, db):
"""Commodity function to call `assign_tasks` and trap exceptions. """Commodity function to call `assign_tasks` and trap exceptions
If an exception is raised, assume it happened because of database If an exception is raised, assume it happened because of database
contention and retries `assign_task` after 0.5 seconds contention and retries `assign_task` after 0.5 seconds
""" """
@@ -756,8 +767,7 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
def wrapped_pop_task(self): def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions. """Commodity function to call `pop_task` and trap exceptions
If an exception is raised, assume it happened because of database If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds contention and retries `pop_task` after 0.5 seconds
""" """
@@ -777,19 +787,19 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
def pop_task(self, db): def pop_task(self, db):
"""Grab a task ready to be executed from the queue.""" """Grabs a task ready to be executed from the queue"""
now = self.now() now = self.now()
st = self.db.scheduler_task st = self.db.scheduler_task
if self.is_a_ticker and self.do_assign_tasks: if self.is_a_ticker and self.do_assign_tasks:
# I'm a ticker, and 5 loops passed without reassigning tasks, #I'm a ticker, and 5 loops passed without reassigning tasks,
# let's do that and loop again #let's do that and loop again
self.wrapped_assign_tasks(db) self.wrapped_assign_tasks(db)
return None return None
# ready to process something # ready to process something
grabbed = db( grabbed = db(
(st.assigned_worker_name == self.worker_name) & (st.assigned_worker_name == self.worker_name) &
(st.status == ASSIGNED) (st.status == ASSIGNED)
) )
task = grabbed.select(limitby=(0, 1), orderby=st.next_run_time).first() task = grabbed.select(limitby=(0, 1), orderby=st.next_run_time).first()
if task: if task:
@@ -809,15 +819,11 @@ class Scheduler(MetaScheduler):
if not task.prevent_drift: if not task.prevent_drift:
next_run_time = task.last_run_time + datetime.timedelta( next_run_time = task.last_run_time + datetime.timedelta(
seconds=task.period seconds=task.period
) )
else: else:
# calc next_run_time based on available slots next_run_time = task.start_time + datetime.timedelta(
# see #1191 seconds=task.period * times_run
next_run_time = task.start_time )
secondspassed = self.total_seconds(now - next_run_time)
steps = secondspassed // task.period + 1
next_run_time += datetime.timedelta(seconds=task.period * steps)
if times_run < task.repeats or task.repeats == 0: if times_run < task.repeats or task.repeats == 0:
# need to run (repeating task) # need to run (repeating task)
run_again = True run_again = True
@@ -839,7 +845,7 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
db.rollback() db.rollback()
logger.info('new task %(id)s "%(task_name)s"' logger.info('new task %(id)s "%(task_name)s"'
' %(application_name)s.%(function_name)s' % task) ' %(application_name)s.%(function_name)s' % task)
return Task( return Task(
app=task.application_name, app=task.application_name,
function=task.function_name, function=task.function_name,
@@ -858,8 +864,7 @@ class Scheduler(MetaScheduler):
uuid=task.uuid) uuid=task.uuid)
def wrapped_report_task(self, task, task_report): def wrapped_report_task(self, task, task_report):
"""Commodity function to call `report_task` and trap exceptions. """Commodity function to call `report_task` and trap exceptions
If an exception is raised, assume it happened because of database If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds contention and retries `pop_task` after 0.5 seconds
""" """
@@ -876,10 +881,8 @@ class Scheduler(MetaScheduler):
time.sleep(0.5) time.sleep(0.5)
def report_task(self, task, task_report): def report_task(self, task, task_report):
"""Take care of storing the result according to preferences. """Takes care of storing the result according to preferences
and deals with logic for repeating tasks"""
Deals with logic for repeating tasks.
"""
db = self.db db = self.db
now = self.now() now = self.now()
st = db.scheduler_task st = db.scheduler_task
@@ -901,12 +904,12 @@ class Scheduler(MetaScheduler):
logger.debug(' deleting task report in db because of no result') logger.debug(' deleting task report in db because of no result')
db(sr.id == task.run_id).delete() db(sr.id == task.run_id).delete()
# if there is a stop_time and the following run would exceed it # if there is a stop_time and the following run would exceed it
is_expired = (task.stop_time and is_expired = (task.stop_time
task.next_run_time > task.stop_time and and task.next_run_time > task.stop_time
True or False) and True or False)
status = (task.run_again and is_expired and EXPIRED or status = (task.run_again and is_expired and EXPIRED
task.run_again and not is_expired and or task.run_again and not is_expired
QUEUED or COMPLETED) and QUEUED or COMPLETED)
if task_report.status == COMPLETED: if task_report.status == COMPLETED:
d = dict(status=status, d = dict(status=status,
next_run_time=task.next_run_time, next_run_time=task.next_run_time,
@@ -919,39 +922,40 @@ class Scheduler(MetaScheduler):
else: else:
st_mapping = {'FAILED': 'FAILED', st_mapping = {'FAILED': 'FAILED',
'TIMEOUT': 'TIMEOUT', 'TIMEOUT': 'TIMEOUT',
'STOPPED': 'FAILED'}[task_report.status] 'STOPPED': 'QUEUED'}[task_report.status]
status = (task.retry_failed status = (task.retry_failed
and task.times_failed < task.retry_failed and task.times_failed < task.retry_failed
and QUEUED or task.retry_failed == -1 and QUEUED or task.retry_failed == -1
and QUEUED or st_mapping) and QUEUED or st_mapping)
db(st.id == task.task_id).update( db(st.id == task.task_id).update(
times_failed=st.times_failed + 1, times_failed=db.scheduler_task.times_failed + 1,
next_run_time=task.next_run_time, next_run_time=task.next_run_time,
status=status status=status
) )
logger.info('task completed (%s)', task_report.status) logger.info('task completed (%s)', task_report.status)
def update_dependencies(self, db, task_id): def update_dependencies(self, db, task_id):
"""Unblock execution paths for Jobs."""
db(db.scheduler_task_deps.task_child == task_id).update(can_visit=True) db(db.scheduler_task_deps.task_child == task_id).update(can_visit=True)
def adj_hibernation(self): def adj_hibernation(self):
"""Used to increase the "sleep" interval for DISABLED workers.""" """Used to increase the "sleep" interval for DISABLED workers"""
if self.w_stats.status == DISABLED: if self.w_stats.status == DISABLED:
wk_st = self.w_stats.sleep wk_st = self.w_stats.sleep
hibernation = wk_st + HEARTBEAT if wk_st < MAXHIBERNATION else MAXHIBERNATION hibernation = wk_st + HEARTBEAT if wk_st < MAXHIBERNATION else MAXHIBERNATION
self.w_stats.sleep = hibernation self.w_stats.sleep = hibernation
def send_heartbeat(self, counter): def send_heartbeat(self, counter):
"""Coordination among available workers. """This function is vital for proper coordination among available
workers.
It: It:
- sends the heartbeat - sends the heartbeat
- elects a ticker among available workers (the only process that - elects a ticker among available workers (the only process that
effectively dispatch tasks to workers) effectively dispatch tasks to workers)
- deals with worker's statuses - deals with worker's statuses
- does "housecleaning" for dead workers - does "housecleaning" for dead workers
- triggers tasks assignment to workers - triggers tasks assignment to workers
""" """
if not self.db_thread: if not self.db_thread:
logger.debug('thread building own DAL object') logger.debug('thread building own DAL object')
@@ -978,7 +982,7 @@ class Scheduler(MetaScheduler):
# keep sleeping # keep sleeping
self.w_stats.status = DISABLED self.w_stats.status = DISABLED
logger.debug('........recording heartbeat (%s)', logger.debug('........recording heartbeat (%s)',
self.w_stats.status) self.w_stats.status)
db(sw.worker_name == self.worker_name).update( db(sw.worker_name == self.worker_name).update(
last_heartbeat=now, last_heartbeat=now,
worker_stats=self.w_stats) worker_stats=self.w_stats)
@@ -995,7 +999,7 @@ class Scheduler(MetaScheduler):
logger.info('Asked to kill the current task') logger.info('Asked to kill the current task')
self.terminate_process() self.terminate_process()
logger.debug('........recording heartbeat (%s)', logger.debug('........recording heartbeat (%s)',
self.w_stats.status) self.w_stats.status)
db(sw.worker_name == self.worker_name).update( db(sw.worker_name == self.worker_name).update(
last_heartbeat=now, status=ACTIVE, last_heartbeat=now, status=ACTIVE,
worker_stats=self.w_stats) worker_stats=self.w_stats)
@@ -1021,7 +1025,7 @@ class Scheduler(MetaScheduler):
db( db(
(st.assigned_worker_name.belongs(dead_workers_name)) & (st.assigned_worker_name.belongs(dead_workers_name)) &
(st.status == RUNNING) (st.status == RUNNING)
).update(assigned_worker_name='', status=QUEUED) ).update(assigned_worker_name='', status=QUEUED)
dead_workers.delete() dead_workers.delete()
try: try:
self.is_a_ticker = self.being_a_ticker() self.is_a_ticker = self.being_a_ticker()
@@ -1039,8 +1043,7 @@ class Scheduler(MetaScheduler):
self.sleep() self.sleep()
def being_a_ticker(self): def being_a_ticker(self):
"""Elect a TICKER process that assigns tasks to available workers. """Elects a TICKER process that assigns tasks to available workers.
Does its best to elect a worker that is not busy processing other tasks Does its best to elect a worker that is not busy processing other tasks
to allow a proper distribution of tasks among all active workers ASAP to allow a proper distribution of tasks among all active workers ASAP
""" """
@@ -1074,7 +1077,7 @@ class Scheduler(MetaScheduler):
return False return False
def assign_tasks(self, db): def assign_tasks(self, db):
"""Assign task to workers, that can then pop them from the queue. """Assigns task to workers, that can then pop them from the queue
Deals with group_name(s) logic, in order to assign linearly tasks Deals with group_name(s) logic, in order to assign linearly tasks
to available workers for those groups to available workers for those groups
@@ -1107,36 +1110,40 @@ class Scheduler(MetaScheduler):
(sd.can_visit == False) & (sd.can_visit == False) &
(~sd.task_child.belongs( (~sd.task_child.belongs(
db(sd.can_visit == False)._select(sd.task_parent) db(sd.can_visit == False)._select(sd.task_parent)
)
) )
) )._select(sd.task_child)
)._select(sd.task_child)
no_deps = db( no_deps = db(
(st.status.belongs((QUEUED, ASSIGNED))) & (st.status.belongs((QUEUED, ASSIGNED))) &
( (
(sd.id == None) | (st.id.belongs(deps_with_no_deps)) (sd.id == None) | (st.id.belongs(deps_with_no_deps))
) )
)._select(st.id, distinct=True, left=sd.on( )._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) & (st.id == sd.task_parent) &
(sd.can_visit == False) (sd.can_visit == False)
) )
) )
all_available = db( all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) & (st.status.belongs((QUEUED, ASSIGNED))) &
((st.times_run < st.repeats) | (st.repeats == 0)) &
(st.start_time <= now) &
((st.stop_time == None) | (st.stop_time > now)) &
(st.next_run_time <= now) & (st.next_run_time <= now) &
(st.enabled == True) & (st.enabled == True) &
(st.id.belongs(no_deps)) (st.id.belongs(no_deps))
) )
limit = len(all_workers) * (50 / (len(wkgroups) or 1)) limit = len(all_workers) * (50 / (len(wkgroups) or 1))
# if there are a moltitude of tasks, let's figure out a maximum of # if there are a moltitude of tasks, let's figure out a maximum of
# tasks per worker. This can be further tuned with some added # tasks per worker. This can be further tuned with some added
# intelligence (like esteeming how many tasks will a worker complete # intelligence (like esteeming how many tasks will a worker complete
# before the ticker reassign them around, but the gain is quite small # before the ticker reassign them around, but the gain is quite small
# 50 is a sweet spot also for fast tasks, with sane heartbeat values # 50 is a sweet spot also for fast tasks, with sane heartbeat values
# NB: ticker reassign tasks every 5 cycles, so if a worker completes # NB: ticker reassign tasks every 5 cycles, so if a worker completes its
# its 50 tasks in less than heartbeat*5 seconds, # 50 tasks in less than heartbeat*5 seconds,
# it won't pick new tasks until heartbeat*5 seconds pass. # it won't pick new tasks until heartbeat*5 seconds pass.
# If a worker is currently elaborating a long task, its tasks needs to # If a worker is currently elaborating a long task, its tasks needs to
@@ -1149,7 +1156,7 @@ class Scheduler(MetaScheduler):
x = 0 x = 0
for group in wkgroups.keys(): for group in wkgroups.keys():
tasks = all_available(st.group_name == group).select( tasks = all_available(st.group_name == group).select(
limitby=(0, limit), orderby=st.next_run_time) limitby=(0, limit), orderby = st.next_run_time)
# let's break up the queue evenly among workers # let's break up the queue evenly among workers
for task in tasks: for task in tasks:
x += 1 x += 1
@@ -1167,10 +1174,12 @@ class Scheduler(MetaScheduler):
status=ASSIGNED, status=ASSIGNED,
assigned_worker_name=assigned_wn assigned_worker_name=assigned_wn
) )
if not task.task_name:
d['task_name'] = task.function_name
db( db(
(st.id == task.id) & (st.id == task.id) &
(st.status.belongs((QUEUED, ASSIGNED))) (st.status.belongs((QUEUED, ASSIGNED)))
).update(**d) ).update(**d)
wkgroups[gname]['workers'][myw]['c'] += 1 wkgroups[gname]['workers'][myw]['c'] += 1
db.commit() db.commit()
# I didn't report tasks but I'm working nonetheless!!!! # I didn't report tasks but I'm working nonetheless!!!!
@@ -1186,13 +1195,14 @@ class Scheduler(MetaScheduler):
logger.info('TICKER: tasks are %s', x) logger.info('TICKER: tasks are %s', x)
def sleep(self): def sleep(self):
"""Calculate the number of seconds to sleep.""" """Calculates the number of seconds to sleep according to worker's
status and `heartbeat` parameter"""
time.sleep(self.w_stats.sleep) time.sleep(self.w_stats.sleep)
# should only sleep until next available task # should only sleep until next available task
def set_worker_status(self, group_names=None, action=ACTIVE, def set_worker_status(self, group_names=None, action=ACTIVE,
exclude=None, limit=None, worker_name=None): exclude=None, limit=None, worker_name=None):
"""Internal function to set worker's status.""" """Internal function to set worker's status"""
ws = self.db.scheduler_worker ws = self.db.scheduler_worker
if not group_names: if not group_names:
group_names = self.group_names group_names = self.group_names
@@ -1207,7 +1217,7 @@ class Scheduler(MetaScheduler):
self.db( self.db(
(ws.group_names.contains(group)) & (ws.group_names.contains(group)) &
(~ws.status.belongs(exclusion)) (~ws.status.belongs(exclusion))
).update(status=action) ).update(status=action)
else: else:
for group in group_names: for group in group_names:
workers = self.db((ws.group_names.contains(group)) & workers = self.db((ws.group_names.contains(group)) &
@@ -1216,12 +1226,10 @@ class Scheduler(MetaScheduler):
self.db(ws.id.belongs(workers)).update(status=action) self.db(ws.id.belongs(workers)).update(status=action)
def disable(self, group_names=None, limit=None, worker_name=None): def disable(self, group_names=None, limit=None, worker_name=None):
"""Set DISABLED on the workers processing `group_names` tasks. """Sets DISABLED on the workers processing `group_names` tasks.
A DISABLED worker will be kept alive but it won't be able to process A DISABLED worker will be kept alive but it won't be able to process
any waiting tasks, essentially putting it to sleep. any waiting tasks, essentially putting it to sleep.
By default, all group_names of Scheduler's instantation are selected By default, all group_names of Scheduler's instantation are selected"""
"""
self.set_worker_status( self.set_worker_status(
group_names=group_names, group_names=group_names,
action=DISABLED, action=DISABLED,
@@ -1266,9 +1274,8 @@ class Scheduler(MetaScheduler):
pvars: "raw" kwargs to be passed to the function. Automatically pvars: "raw" kwargs to be passed to the function. Automatically
jsonified jsonified
kwargs: all the parameters available (basically, every kwargs: all the parameters available (basically, every
`scheduler_task` column). If args and vars are here, they `scheduler_task` column). If args and vars are here, they should
should be jsonified already, and they will override pargs be jsonified already, and they will override pargs and pvars
and pvars
Returns: Returns:
a dict just as a normal validate_and_insert(), plus a uuid key a dict just as a normal validate_and_insert(), plus a uuid key
@@ -1295,7 +1302,7 @@ class Scheduler(MetaScheduler):
if immediate: if immediate:
self.db( self.db(
(self.db.scheduler_worker.is_ticker == True) (self.db.scheduler_worker.is_ticker == True)
).update(status=PICK) ).update(status=PICK)
else: else:
rtn.uuid = None rtn.uuid = None
return rtn return rtn
@@ -1345,7 +1352,7 @@ class Scheduler(MetaScheduler):
**dict(orderby=orderby, **dict(orderby=orderby,
left=left, left=left,
limitby=(0, 1)) limitby=(0, 1))
).first() ).first()
if row and output: if row and output:
row.result = row.scheduler_run.run_result and \ row.result = row.scheduler_run.run_result and \
loads(row.scheduler_run.run_result, loads(row.scheduler_run.run_result,
+23 -68
View File
@@ -27,7 +27,7 @@ from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from pydal.base import DEFAULT from pydal.base import DEFAULT
from pydal.objects import Table, Row, Expression, Field, Set from pydal.objects import Table, Row, Expression, Field
from pydal.adapters.base import CALLABLETYPES from pydal.adapters.base import CALLABLETYPES
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
from pydal.helpers.classes import Reference, SQLCustomType from pydal.helpers.classes import Reference, SQLCustomType
@@ -677,23 +677,7 @@ class AutocompleteWidget(object):
def callback(self): def callback(self):
if self.keyword in self.request.vars: if self.keyword in self.request.vars:
field = self.fields[0] field = self.fields[0]
if type(field) is Field.Virtual: if settings and settings.global_settings.web2py_runtime_gae:
records = []
table_rows = self.db(self.db[field.tablename]).select(orderby=self.orderby)
count = 0
for row in table_rows:
if self.at_beginning:
if row[field.name].lower().startswith(self.request.vars[self.keyword]):
count += 1
records.append(row)
else:
if self.request.vars[self.keyword] in row[field.name].lower():
count += 1
records.append(row)
if count == 10:
break
rows = Rows(self.db, records, table_rows.colnames, compact=table_rows.compact)
elif settings and settings.global_settings.web2py_runtime_gae:
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields)) rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
elif self.at_beginning: elif self.at_beginning:
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields)) rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
@@ -741,16 +725,8 @@ class AutocompleteWidget(object):
del attr['requires'] del attr['requires']
attr['_name'] = key2 attr['_name'] = key2
value = attr['value'] value = attr['value']
if type(self.fields[0]) is Field.Virtual: record = self.db(
record = None self.fields[1] == value).select(self.fields[0]).first()
table_rows = self.db(self.db[self.fields[0].tablename]).select(orderby=self.orderby)
for row in table_rows:
if row.id == value:
record = row
break
else:
record = self.db(
self.fields[1] == value).select(self.fields[0]).first()
attr['value'] = record and record[self.fields[0].name] attr['value'] = record and record[self.fields[0].name]
attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \ attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \
dict(div_id=div_id, u='F' + self.keyword) dict(div_id=div_id, u='F' + self.keyword)
@@ -901,7 +877,6 @@ def formstyle_bootstrap3_stacked(form, fields):
elif controls['_type'] == 'checkbox': elif controls['_type'] == 'checkbox':
label['_for'] = None label['_for'] = None
label.insert(0, controls) label.insert(0, controls)
label.insert(0, ' ')
_controls = DIV(label, _help, _class="checkbox") _controls = DIV(label, _help, _class="checkbox")
label = '' label = ''
elif isinstance(controls, (SELECT, TEXTAREA)): elif isinstance(controls, (SELECT, TEXTAREA)):
@@ -937,7 +912,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
# wrappers # wrappers
_help = SPAN(help, _class='help-block') _help = SPAN(help, _class='help-block')
# embed _help into _controls # embed _help into _controls
_controls = DIV(controls, _help, _class="%s" % (col_class)) _controls = DIV(controls, _help, _class=col_class)
if isinstance(controls, INPUT): if isinstance(controls, INPUT):
if controls['_type'] == 'submit': if controls['_type'] == 'submit':
controls.add_class('btn btn-primary') controls.add_class('btn btn-primary')
@@ -951,7 +926,6 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
elif controls['_type'] == 'checkbox': elif controls['_type'] == 'checkbox':
label['_for'] = None label['_for'] = None
label.insert(0, controls) label.insert(0, controls)
label.insert(1, ' ')
_controls = DIV(DIV(label, _help, _class="checkbox"), _controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class)) _class="%s %s" % (offset_class, col_class))
label = '' label = ''
@@ -964,6 +938,8 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
elif isinstance(controls, UL): elif isinstance(controls, UL):
for e in controls.elements("input"): for e in controls.elements("input"):
e.add_class('form-control') e.add_class('form-control')
elif controls is None or isinstance(controls, basestring):
_controls = P(controls, _class="form-control-static %s" % col_class)
if isinstance(label, LABEL): if isinstance(label, LABEL):
label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class) label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class)
@@ -1897,39 +1873,33 @@ class SQLFORM(FORM):
else: else:
field_type = field.type field_type = field.type
operators = SELECT(*[OPTION(T(option), _value=option) for option in options], operators = SELECT(*[OPTION(T(option), _value=option) for option in options], _class='form-control')
_class='form-control')
_id = "%s_%s" % (value_id, name) _id = "%s_%s" % (value_id, name)
if field_type in ['boolean', 'double', 'time', 'integer']: if field_type in ['boolean', 'double', 'time', 'integer']:
widget_ = SQLFORM.widgets[field_type] widget_ = SQLFORM.widgets[field_type]
value_input = widget_.widget(field, field.default, _id=_id, value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
_class=widget_._class + ' form-control')
elif field_type == 'date': elif field_type == 'date':
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'} iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
widget_ = SQLFORM.widgets.date widget_ = SQLFORM.widgets.date
value_input = widget_.widget(field, field.default, _id=_id, value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
_class=widget_._class + ' form-control',
**iso_format)
elif field_type == 'datetime': elif field_type == 'datetime':
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'} iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
_class=widget_._class + ' form-control', elif (field_type.startswith('reference ') or
**iso_format) field_type.startswith('list:reference ')) and \
elif hasattr(field.requires, 'options'): hasattr(field.requires, 'options') or \
hasattr(field.requires, 'options'):
value_input = SELECT( value_input = SELECT(
*[OPTION(v, _value=k) *[OPTION(v, _value=k)
for k, v in field.requires.options()], for k, v in field.requires.options()],
_class='form-control', _class='form-control',
**dict(_id=_id)) **dict(_id=_id))
elif (field_type.startswith('integer') or elif field_type.startswith('reference ') or \
field_type.startswith('reference ') or field_type.startswith('list:integer') or \
field_type.startswith('list:integer') or field_type.startswith('list:reference '):
field_type.startswith('list:reference ')):
widget_ = SQLFORM.widgets.integer widget_ = SQLFORM.widgets.integer
value_input = widget_.widget( value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
field, field.default, _id=_id,
_class=widget_._class + ' form-control')
else: else:
value_input = INPUT( value_input = INPUT(
_type='text', _id=_id, _type='text', _id=_id,
@@ -2044,8 +2014,6 @@ class SQLFORM(FORM):
use_cursor=False): use_cursor=False):
formstyle = formstyle or current.response.formstyle formstyle = formstyle or current.response.formstyle
if isinstance(query, Set):
query = query.query
# jQuery UI ThemeRoller classes (empty if ui is disabled) # jQuery UI ThemeRoller classes (empty if ui is disabled)
if ui == 'jquery-ui': if ui == 'jquery-ui':
@@ -2191,7 +2159,7 @@ class SQLFORM(FORM):
buttonurl=url(args=[]), callback=None, buttonurl=url(args=[]), callback=None,
delete=None, trap=True, noconfirm=None, title=None): delete=None, trap=True, noconfirm=None, title=None):
if showbuttontext: if showbuttontext:
return A(SPAN(_class=ui.get(buttonclass)), CAT(' '), return A(SPAN(_class=ui.get(buttonclass)),
SPAN(T(buttontext), _title=title or T(buttontext), SPAN(T(buttontext), _title=title or T(buttontext),
_class=ui.get('buttontext')), _class=ui.get('buttontext')),
_href=buttonurl, _href=buttonurl,
@@ -2369,7 +2337,7 @@ class SQLFORM(FORM):
if deletable(record): if deletable(record):
if ondelete: if ondelete:
ondelete(table, request.args[-1]) ondelete(table, request.args[-1])
db(table[table._id.name] == request.args[-1]).delete() record.delete_record()
if request.ajax: if request.ajax:
# this means javascript is enabled, so we don't need to do # this means javascript is enabled, so we don't need to do
# a redirect # a redirect
@@ -2749,11 +2717,7 @@ class SQLFORM(FORM):
if field.type == 'blob': if field.type == 'blob':
continue continue
if isinstance(field, Field.Virtual) and field.tablename in row: if isinstance(field, Field.Virtual) and field.tablename in row:
try: value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
# fast path, works for joins
value = row[field.tablename][field.name]
except KeyError:
value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
else: else:
value = row[str(field)] value = row[str(field)]
maxlength = maxtextlengths.get(str(field), maxtextlength) maxlength = maxtextlengths.get(str(field), maxtextlength)
@@ -3043,16 +3007,7 @@ class SQLFORM(FORM):
query = query & constraints[table._tablename] query = query & constraints[table._tablename]
if isinstance(links, dict): if isinstance(links, dict):
links = links.get(table._tablename, []) links = links.get(table._tablename, [])
for key in ('fields', 'field_id', 'left', 'headers', 'orderby', 'groupby', 'searchable', for key in 'columns,orderby,searchable,sortable,paginate,deletable,editable,details,selectable,create,fields'.split(','):
'sortable', 'paginate', 'deletable', 'editable', 'details', 'selectable',
'create', 'csv', 'links', 'links_in_grid', 'upload', 'maxtextlengths',
'maxtextlength', 'onvalidation', 'onfailure', 'oncreate', 'onupdate',
'ondelete', 'sorter_icons', 'ui', 'showbuttontext', '_class', 'formname',
'search_widget', 'advanced_search', 'ignore_rw', 'formstyle', 'exportclasses',
'formargs', 'createargs', 'editargs', 'viewargs', 'selectable_submit_button',
'buttons_placement', 'links_placement', 'noconfirm', 'cache_count', 'client_side_delete',
'ignore_common_filters', 'auto_pagination', 'use_cursor'
):
if isinstance(kwargs.get(key, None), dict): if isinstance(kwargs.get(key, None), dict):
if table._tablename in kwargs[key]: if table._tablename in kwargs[key]:
kwargs[key] = kwargs[key][table._tablename] kwargs[key] = kwargs[key][table._tablename]
+4 -4
View File
@@ -25,6 +25,7 @@ regex_stop_range = re.compile('(?<=\-)\d+')
DEFAULT_CHUNK_SIZE = 64 * 1024 DEFAULT_CHUNK_SIZE = 64 * 1024
def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None): def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None):
offset = 0 offset = 0
while bytes is None or offset < bytes: while bytes is None or offset < bytes:
@@ -50,12 +51,11 @@ def stream_file_or_304_or_206(
status=200, status=200,
error_message=None error_message=None
): ):
# FIX THIS if error_message is None:
# if error_message is None: error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
# error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
try: try:
open = file # this makes no sense but without it GAE cannot open files open = file # this makes no sense but without it GAE cannot open files
fp = open(static_file,'rb') fp = open(static_file)
except IOError, e: except IOError, e:
if e[0] == errno.EISDIR: if e[0] == errno.EISDIR:
raise HTTP(403, error_message, web2py_error='file is a directory') raise HTTP(403, error_message, web2py_error='file is a directory')
-4
View File
@@ -3,14 +3,12 @@ import sys
from test_http import * from test_http import *
from test_cache import * from test_cache import *
from test_contenttype import * from test_contenttype import *
from test_compileapp import *
from test_fileutils import * from test_fileutils import *
from test_globals import * from test_globals import *
from test_html import * from test_html import *
from test_is_url import * from test_is_url import *
from test_languages import * from test_languages import *
from test_router import * from test_router import *
from test_recfile import *
from test_routes import * from test_routes import *
from test_storage import * from test_storage import *
from test_serializers import * from test_serializers import *
@@ -21,8 +19,6 @@ from test_contribs import *
from test_web import * from test_web import *
from test_dal import * from test_dal import *
from test_tools import * from test_tools import *
from test_appadmin import *
from test_scheduler import *
if sys.version[:3] == '2.7': if sys.version[:3] == '2.7':
from test_old_doctests import * from test_old_doctests import *
-175
View File
@@ -1,175 +0,0 @@
#!/bin/python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.sqlhtml
"""
import os
import sys
if sys.version < "2.7":
import unittest2 as unittest
else:
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from compileapp import run_controller_in, run_view_in
from languages import translator
from gluon.storage import Storage, List
import gluon.fileutils
from gluon.dal import DAL, Field, Table
from gluon.http import HTTP
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
try:
import json
except ImportError:
from gluon.contrib import simplejson as json
def fake_check_credentials(foo):
return True
class TestAppAdmin(unittest.TestCase):
def setUp(self):
from gluon.globals import Request, Response, Session, current
from gluon.html import A, DIV, FORM, MENU, TABLE, TR, INPUT, URL, XML
from gluon.validators import IS_NOT_EMPTY
from compileapp import LOAD
from gluon.http import HTTP, redirect
from gluon.tools import Auth
from gluon.sql import SQLDB
from gluon.sqlhtml import SQLTABLE, SQLFORM
self.original_check_credentials = gluon.fileutils.check_credentials
gluon.fileutils.check_credentials = fake_check_credentials
request = Request(env={})
request.application = 'welcome'
request.controller = 'appadmin'
request.function = self._testMethodName.split('_')[1]
request.folder = 'applications/welcome'
request.env.http_host = '127.0.0.1:8000'
request.env.remote_addr = '127.0.0.1'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
current.request = request
current.response = response
current.session = session
current.T = T
db = DAL(DEFAULT_URI, check_reserved=['all'])
auth = Auth(db)
auth.define_tables(username=True, signature=False)
db.define_table('t0', Field('tt'), auth.signature)
# Create a user
db.auth_user.insert(first_name='Bart',
last_name='Simpson',
username='user1',
email='user1@test.com',
password='password_123',
registration_key=None,
registration_id=None)
self.env = locals()
def tearDown(self):
gluon.fileutils.check_credentials = self.original_check_credentials
def run_function(self):
return run_controller_in(self.env['request'].controller, self.env['request'].function, self.env)
def run_view(self):
return run_view_in(self.env)
def test_index(self):
result = self.run_function()
self.assertTrue('db' in result['databases'])
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_select(self):
request = self.env['request']
request.args = List(['db'])
request.env.query_string = 'query=db.auth_user.id>0'
result = self.run_function()
self.assertTrue('table' in result and 'query' in result)
self.assertTrue(result['table'] == 'auth_user')
self.assertTrue(result['query'] == 'db.auth_user.id>0')
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_insert(self):
request = self.env['request']
request.args = List(['db', 'auth_user'])
result = self.run_function()
self.assertTrue('table' in result)
self.assertTrue('form' in result)
self.assertTrue(str(result['table']) is 'auth_user')
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_insert_submit(self):
request = self.env['request']
request.args = List(['db', 'auth_user'])
form = self.run_function()['form']
hidden_fields = form.hidden_fields()
data = {}
data['_formkey'] = hidden_fields.element('input', _name='_formkey')['_value']
data['_formname'] = hidden_fields.element('input', _name='_formname')['_value']
data['first_name'] = 'Lisa'
data['last_name'] = 'Simpson'
data['username'] = 'lisasimpson'
data['password'] = 'password_123'
data['email'] = 'lisa@example.com'
request._vars = data
result = self.run_function()
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
db = self.env['db']
lisa_record = db(db.auth_user.username == 'lisasimpson').select().first()
self.assertIsNotNone(lisa_record)
del data['_formkey']
del data['_formname']
del data['password']
for key in data:
self.assertEqual(data[key], lisa_record[key])
def test_update_submit(self):
request = self.env['request']
request.args = List(['db', 'auth_user', '1'])
form = self.run_function()['form']
hidden_fields = form.hidden_fields()
data = {}
data['_formkey'] = hidden_fields.element('input', _name='_formkey')['_value']
data['_formname'] = hidden_fields.element('input', _name='_formname')['_value']
for element in form.elements('input'):
data[element['_name']] = element['_value']
data['email'] = 'user1@example.com'
data['id'] = '1'
request._vars = data
self.assertRaises(HTTP, self.run_function)
if __name__ == '__main__':
unittest.main()
+14 -20
View File
@@ -37,11 +37,10 @@ def tearDownModule():
pass pass
class TestCache(unittest.TestCase): class TestCache(unittest.TestCase):
# TODO: test_CacheAbstract(self): def testCacheInRam(self):
def test_CacheInRam(self):
# defaults to mode='http' # defaults to mode='http'
cache = CacheInRam() cache = CacheInRam()
@@ -54,21 +53,22 @@ class TestCache(unittest.TestCase):
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
# test singleton behaviour #test singleton behaviour
cache = CacheInRam() cache = CacheInRam()
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
# test key deletion #test key deletion
cache('a', None) cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5) self.assertEqual(cache('a', lambda: 5, 100), 5)
# test increment #test increment
self.assertEqual(cache.increment('a'), 6) self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6) self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b') cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1) self.assertEqual(cache('b', lambda: 'x', 100), 1)
def test_CacheOnDisk(self):
def testCacheOnDisk(self):
# defaults to mode='http' # defaults to mode='http'
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
@@ -83,36 +83,30 @@ class TestCache(unittest.TestCase):
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
# test singleton behaviour #test singleton behaviour
cache = CacheOnDisk(s) cache = CacheOnDisk(s)
cache.clear() cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3) self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4) self.assertEqual(cache('a', lambda: 4, 0), 4)
# test key deletion #test key deletion
cache('a', None) cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5) self.assertEqual(cache('a', lambda: 5, 100), 5)
# test increment #test increment
self.assertEqual(cache.increment('a'), 6) self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6) self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b') cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1) self.assertEqual(cache('b', lambda: 'x', 100), 1)
# TODO: def test_CacheAction(self): def testCacheWithPrefix(self):
# TODO: def test_Cache(self):
# TODO: def test_lazy_cache(self):
def test_CacheWithPrefix(self):
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
'folder': 'applications/admin'}) 'folder': 'applications/admin'})
cache = Cache(s) cache = Cache(s)
prefix = cache.with_prefix(cache.ram, 'prefix') prefix = cache.with_prefix(cache.ram,'prefix')
self.assertEqual(prefix('a', lambda: 1, 0), 1) self.assertEqual(prefix('a', lambda: 1, 0), 1)
self.assertEqual(prefix('a', lambda: 2, 100), 1) self.assertEqual(prefix('a', lambda: 2, 100), 1)
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1) self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
def test_Regex(self): def testRegex(self):
cache = CacheInRam() cache = CacheInRam()
self.assertEqual(cache('a1', lambda: 1, 0), 1) self.assertEqual(cache('a1', lambda: 1, 0), 1)
self.assertEqual(cache('a2', lambda: 2, 100), 2) self.assertEqual(cache('a2', lambda: 2, 100), 2)
@@ -120,7 +114,7 @@ class TestCache(unittest.TestCase):
self.assertEqual(cache('a1', lambda: 2, 0), 2) self.assertEqual(cache('a1', lambda: 2, 0), 2)
self.assertEqual(cache('a2', lambda: 3, 100), 3) self.assertEqual(cache('a2', lambda: 3, 100), 3)
def test_DALcache(self): def testDALcache(self):
s = Storage({'application': 'admin', s = Storage({'application': 'admin',
'folder': 'applications/admin'}) 'folder': 'applications/admin'})
cache = Cache(s) cache = Cache(s)
-35
View File
@@ -1,35 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Unit tests for utils.py """
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from compileapp import compile_application, remove_compiled_application
from gluon.fileutils import w2p_pack, w2p_unpack
import os
class TestPack(unittest.TestCase):
""" Tests the compileapp.py module """
def test_compile(self):
#apps = ['welcome', 'admin', 'examples']
apps = ['welcome']
for appname in apps:
appname_path = os.path.join(os.getcwd(), 'applications', appname)
compile_application(appname_path)
remove_compiled_application(appname_path)
test_path = os.path.join(os.getcwd(), "%s.w2p" % appname)
unpack_path = os.path.join(os.getcwd(), 'unpack', appname)
w2p_pack(test_path, appname_path, compiled=True, filenames=None)
w2p_pack(test_path, appname_path, compiled=False, filenames=None)
w2p_unpack(test_path, unpack_path)
return
if __name__ == '__main__':
unittest.main()
+6 -8
View File
@@ -106,18 +106,16 @@ class TestDALAdapters(unittest.TestCase):
def test_mysql(self): def test_mysql(self):
if os.environ.get('APPVEYOR'): if os.environ.get('APPVEYOR'):
return return
if os.environ.get('TRAVIS'): os.environ["DB"] = "mysql://root:@localhost/pydal"
os.environ["DB"] = "mysql://root:@localhost/pydal" result = self._run_tests()
result = self._run_tests() self.assertTrue(result)
self.assertTrue(result)
def test_pg8000(self): def test_pg8000(self):
if os.environ.get('APPVEYOR'): if os.environ.get('APPVEYOR'):
return return
if os.environ.get('TRAVIS'): os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal" result = self._run_tests()
result = self._run_tests() self.assertTrue(result)
self.assertTrue(result)
if __name__ == '__main__': if __name__ == '__main__':
+3 -9
View File
@@ -12,19 +12,13 @@ from fileutils import parse_version
class TestFileUtils(unittest.TestCase): class TestFileUtils(unittest.TestCase):
def test_parse_version(self): def testParseVersion(self):
# Legacy
rtn = parse_version('Version 1.99.0 (2011-09-19 08:23:26)')
self.assertEqual(rtn, (1, 99, 0, 'dev', datetime.datetime(2011, 9, 19, 8, 23, 26)))
# Semantic
rtn = parse_version('Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26') rtn = parse_version('Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26')
self.assertEqual(rtn, (1, 99, 0, 'rc.1', datetime.datetime(2011, 9, 19, 8, 23, 26))) self.assertEqual(rtn, (1, 99, 0, 'rc.1', datetime.datetime(2011, 9, 19, 8, 23, 26)))
# Semantic Stable
rtn = parse_version('Version 2.9.11-stable+timestamp.2014.09.15.18.31.17') rtn = parse_version('Version 2.9.11-stable+timestamp.2014.09.15.18.31.17')
self.assertEqual(rtn, (2, 9, 11, 'stable', datetime.datetime(2014, 9, 15, 18, 31, 17))) self.assertEqual(rtn, (2, 9, 11, 'stable', datetime.datetime(2014, 9, 15, 18, 31, 17)))
# Semantic Beta rtn = parse_version('Version 1.99.0 (2011-09-19 08:23:26)')
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26') self.assertEqual(rtn, (1, 99, 0, 'dev', datetime.datetime(2011, 9, 19, 8, 23, 26)))
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
if __name__ == '__main__': if __name__ == '__main__':

Some files were not shown because too many files have changed in this diff Show More