2004 lines
73 KiB
Python
2004 lines
73 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
EXPERIMENTAL_STUFF = True
|
|
MAXNFILES = 1000
|
|
|
|
if EXPERIMENTAL_STUFF:
|
|
if is_mobile:
|
|
response.view = response.view.replace('default/', 'default.mobile/')
|
|
response.menu = []
|
|
|
|
import re
|
|
from gluon.admin import *
|
|
from gluon.fileutils import abspath, read_file, write_file
|
|
from gluon.utils import web2py_uuid
|
|
from gluon.tools import Config
|
|
from gluon.compileapp import find_exposed_functions
|
|
from glob import glob
|
|
from gluon._compat import iteritems, PY2, pickle, xrange, urlopen, to_bytes, StringIO, to_native
|
|
import gluon.rewrite
|
|
import shutil
|
|
import platform
|
|
|
|
try:
|
|
import git
|
|
if git.__version__ < '0.3.1':
|
|
raise ImportError("Your version of git is %s. Upgrade to 0.3.1 or better." % git.__version__)
|
|
have_git = True
|
|
except ImportError as e:
|
|
have_git = False
|
|
GIT_MISSING = 'Requires gitpython module, but not installed or incompatible version: %s' % e
|
|
|
|
from gluon.languages import (read_possible_languages, read_dict, write_dict,
|
|
read_plural_dict, write_plural_dict)
|
|
|
|
|
|
if DEMO_MODE and request.function in ['change_password', 'pack',
|
|
'pack_custom', 'pack_plugin', 'upgrade_web2py', 'uninstall',
|
|
'cleanup', 'compile_app', 'remove_compiled_app', 'delete',
|
|
'delete_plugin', 'create_file', 'upload_file', 'update_languages',
|
|
'reload_routes', 'git_push', 'git_pull', 'install_plugin']:
|
|
session.flash = T('disabled in demo mode')
|
|
redirect(URL('site'))
|
|
|
|
if is_gae and request.function in ('edit', 'edit_language',
|
|
'edit_plurals', 'update_languages', 'create_file', 'install_plugin'):
|
|
session.flash = T('disabled in GAE mode')
|
|
redirect(URL('site'))
|
|
|
|
if not is_manager() and request.function in ['change_password', 'upgrade_web2py']:
|
|
session.flash = T('disabled in multi user mode')
|
|
redirect(URL('site'))
|
|
|
|
if FILTER_APPS and request.args(0) and not request.args(0) in FILTER_APPS:
|
|
session.flash = T('disabled in demo mode')
|
|
redirect(URL('site'))
|
|
|
|
|
|
if not session.token:
|
|
session.token = web2py_uuid()
|
|
|
|
|
|
def count_lines(data):
|
|
return len([line for line in data.split('\n') if line.strip() and not line.startswith('#')])
|
|
|
|
|
|
def log_progress(app, mode='EDIT', filename=None, progress=0):
|
|
progress_file = os.path.join(apath(app, r=request), 'progress.log')
|
|
now = str(request.now)[:19]
|
|
if not os.path.exists(progress_file):
|
|
safe_open(progress_file, 'w').write('[%s] START\n' % now)
|
|
if filename:
|
|
safe_open(progress_file, 'a').write(
|
|
'[%s] %s %s: %s\n' % (now, mode, filename, progress))
|
|
|
|
|
|
def safe_open(a, b):
|
|
if (DEMO_MODE or is_gae) and ('w' in b or 'a' in b):
|
|
class tmp:
|
|
|
|
def write(self, data):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
return tmp()
|
|
if PY2 or 'b' in b:
|
|
return open(a, b)
|
|
else:
|
|
return open(a, b, encoding="utf8")
|
|
|
|
|
|
def safe_read(a, b='r'):
|
|
safe_file = safe_open(a, b)
|
|
try:
|
|
return safe_file.read()
|
|
finally:
|
|
safe_file.close()
|
|
|
|
|
|
def safe_write(a, value, b='w'):
|
|
safe_file = safe_open(a, b)
|
|
try:
|
|
safe_file.write(value)
|
|
finally:
|
|
safe_file.close()
|
|
|
|
|
|
def get_app(name=None):
|
|
app = name or request.args(0)
|
|
if (app and os.path.exists(apath(app, r=request)) and
|
|
(not MULTI_USER_MODE or is_manager() or
|
|
db(db.app.name == app)(db.app.owner == auth.user.id).count())):
|
|
return app
|
|
session.flash = T('App does not exist or you are not authorized')
|
|
redirect(URL('site'))
|
|
|
|
|
|
def index():
|
|
""" Index handler """
|
|
|
|
send = request.vars.send
|
|
if DEMO_MODE:
|
|
session.authorized = True
|
|
session.last_time = t0
|
|
if not send:
|
|
send = URL('site')
|
|
if session.authorized:
|
|
redirect(send)
|
|
elif failed_login_count() >= allowed_number_of_attempts:
|
|
time.sleep(2 ** allowed_number_of_attempts)
|
|
raise HTTP(403)
|
|
elif request.vars.password:
|
|
if verify_password(request.vars.password[:1024]):
|
|
session.authorized = True
|
|
login_record(True)
|
|
|
|
if CHECK_VERSION:
|
|
session.check_version = True
|
|
else:
|
|
session.check_version = False
|
|
|
|
session.last_time = t0
|
|
if isinstance(send, list): # ## why does this happen?
|
|
send = str(send[0])
|
|
|
|
redirect(send)
|
|
else:
|
|
times_denied = login_record(False)
|
|
if times_denied >= allowed_number_of_attempts:
|
|
response.flash = \
|
|
T('admin disabled because too many invalid login attempts')
|
|
elif times_denied == allowed_number_of_attempts - 1:
|
|
response.flash = \
|
|
T('You have one more login attempt before you are locked out')
|
|
else:
|
|
response.flash = T('invalid password.')
|
|
return dict(send=send)
|
|
|
|
|
|
def check_version():
|
|
""" Checks if web2py is up to date """
|
|
|
|
session.forget()
|
|
session._unlock(response)
|
|
|
|
new_version, version = check_new_version(request.env.web2py_version,
|
|
WEB2PY_VERSION_URL)
|
|
|
|
if new_version == -1:
|
|
return A(T('Unable to check for upgrades'), _href=WEB2PY_URL)
|
|
elif new_version != True:
|
|
return A(T('web2py is up to date'), _href=WEB2PY_URL)
|
|
elif platform.system().lower() in ('windows', 'win32', 'win64') and os.path.exists("web2py.exe"):
|
|
return SPAN('You should upgrade to %s' % version.split('(')[0])
|
|
else:
|
|
return sp_button(URL('upgrade_web2py'), T('upgrade now to %s') % version.split('(')[0])
|
|
|
|
|
|
def logout():
|
|
""" Logout handler """
|
|
session.authorized = None
|
|
if MULTI_USER_MODE:
|
|
redirect(URL('user/logout'))
|
|
redirect(URL('index'))
|
|
|
|
|
|
def change_password():
|
|
|
|
if session.pam_user:
|
|
session.flash = T(
|
|
'PAM authenticated user, cannot change password here')
|
|
redirect(URL('site'))
|
|
form = SQLFORM.factory(Field('current_admin_password', 'password'),
|
|
Field('new_admin_password',
|
|
'password', requires=IS_STRONG()),
|
|
Field('new_admin_password_again', 'password'),
|
|
_class="span4 well")
|
|
if form.accepts(request.vars):
|
|
if not verify_password(request.vars.current_admin_password):
|
|
form.errors.current_admin_password = T('invalid password')
|
|
elif form.vars.new_admin_password != form.vars.new_admin_password_again:
|
|
form.errors.new_admin_password_again = T('no match')
|
|
else:
|
|
path = abspath('parameters_%s.py' % request.env.server_port)
|
|
safe_write(path, 'password="%s"' % CRYPT()(
|
|
request.vars.new_admin_password)[0])
|
|
session.flash = T('password changed')
|
|
redirect(URL('site'))
|
|
return dict(form=form)
|
|
|
|
|
|
def site():
|
|
""" Site handler """
|
|
|
|
myversion = request.env.web2py_version
|
|
|
|
# Shortcut to make the elif statements more legible
|
|
file_or_appurl = 'file' in request.vars or 'appurl' in request.vars
|
|
|
|
class IS_VALID_APPNAME(object):
|
|
|
|
def __call__(self, value):
|
|
if not re.compile('^\w+$').match(value):
|
|
return (value, T('Invalid application name'))
|
|
if not request.vars.overwrite and \
|
|
os.path.exists(os.path.join(apath(r=request), value)):
|
|
return (value, T('Application exists already'))
|
|
return (value, None)
|
|
|
|
is_appname = IS_VALID_APPNAME()
|
|
form_create = SQLFORM.factory(Field('name', requires=is_appname),
|
|
table_name='appcreate')
|
|
form_update = SQLFORM.factory(Field('name', requires=is_appname),
|
|
Field('file', 'upload', uploadfield=False),
|
|
Field('url'),
|
|
Field('overwrite', 'boolean'),
|
|
table_name='appupdate')
|
|
form_create.process()
|
|
form_update.process()
|
|
|
|
if DEMO_MODE:
|
|
pass
|
|
|
|
elif form_create.accepted:
|
|
# create a new application
|
|
appname = cleanpath(form_create.vars.name)
|
|
created, error = app_create(appname, request, info=True)
|
|
if created:
|
|
if MULTI_USER_MODE:
|
|
db.app.insert(name=appname, owner=auth.user.id)
|
|
log_progress(appname)
|
|
session.flash = T('new application "%s" created', appname)
|
|
gluon.rewrite.load()
|
|
redirect(URL('design', args=appname))
|
|
else:
|
|
session.flash = \
|
|
DIV(T('unable to create application "%s"', appname),
|
|
PRE(error))
|
|
redirect(URL(r=request))
|
|
|
|
elif form_update.accepted:
|
|
if (form_update.vars.url or '').endswith('.git'):
|
|
if not have_git:
|
|
session.flash = GIT_MISSING
|
|
redirect(URL(r=request))
|
|
target = os.path.join(apath(r=request), form_update.vars.name)
|
|
try:
|
|
new_repo = git.Repo.clone_from(form_update.vars.url, target)
|
|
session.flash = T('new application "%s" imported',
|
|
form_update.vars.name)
|
|
gluon.rewrite.load()
|
|
except git.GitCommandError as err:
|
|
session.flash = T('Invalid git repository specified.')
|
|
redirect(URL(r=request))
|
|
|
|
elif form_update.vars.url:
|
|
# fetch an application via URL or file upload
|
|
try:
|
|
f = urlopen(form_update.vars.url)
|
|
if f.code == 404:
|
|
raise Exception("404 file not found")
|
|
except Exception as e:
|
|
session.flash = \
|
|
DIV(T('Unable to download app because:'), PRE(repr(e)))
|
|
redirect(URL(r=request))
|
|
fname = form_update.vars.url
|
|
|
|
elif form_update.accepted and form_update.vars.file:
|
|
fname = request.vars.file.filename
|
|
f = request.vars.file.file
|
|
|
|
else:
|
|
session.flash = 'No file uploaded and no URL specified'
|
|
redirect(URL(r=request))
|
|
|
|
if f:
|
|
appname = cleanpath(form_update.vars.name)
|
|
installed = app_install(appname, f,
|
|
request, fname,
|
|
overwrite=form_update.vars.overwrite)
|
|
if f and installed:
|
|
msg = 'application %(appname)s installed with md5sum: %(digest)s'
|
|
if MULTI_USER_MODE:
|
|
db.app.insert(name=appname, owner=auth.user.id)
|
|
log_progress(appname)
|
|
session.flash = T(msg, dict(appname=appname,
|
|
digest=md5_hash(installed)))
|
|
gluon.rewrite.load()
|
|
else:
|
|
msg = 'unable to install application "%(appname)s"'
|
|
session.flash = T(msg, dict(appname=form_update.vars.name))
|
|
redirect(URL(r=request))
|
|
|
|
regex = re.compile('^\w+$')
|
|
|
|
if is_manager():
|
|
apps = [a for a in os.listdir(apath(r=request)) if regex.match(a) and
|
|
a != '__pycache__']
|
|
else:
|
|
apps = [a.name for a in db(db.app.owner == auth.user_id).select()]
|
|
|
|
if FILTER_APPS:
|
|
apps = [a for a in apps if a in FILTER_APPS]
|
|
|
|
apps = sorted(apps, key=lambda a: a.upper())
|
|
myplatform = platform.python_version()
|
|
return dict(app=None, apps=apps, myversion=myversion, myplatform=myplatform,
|
|
form_create=form_create, form_update=form_update)
|
|
|
|
|
|
def report_progress(app):
|
|
import datetime
|
|
progress_file = os.path.join(apath(app, r=request), 'progress.log')
|
|
regex = re.compile('\[(.*?)\][^\:]+\:\s+(\-?\d+)')
|
|
if not os.path.exists(progress_file):
|
|
return []
|
|
matches = regex.findall(open(progress_file, 'r').read())
|
|
events, counter = [], 0
|
|
for m in matches:
|
|
if not m:
|
|
continue
|
|
days = -(request.now - datetime.datetime.strptime(m[0],
|
|
'%Y-%m-%d %H:%M:%S')).days
|
|
counter += int(m[1])
|
|
events.append([days, counter])
|
|
return events
|
|
|
|
|
|
def pack():
|
|
app = get_app()
|
|
|
|
try:
|
|
if len(request.args) == 1:
|
|
fname = 'web2py.app.%s.w2p' % app
|
|
filename = app_pack(app, request, raise_ex=True)
|
|
else:
|
|
fname = 'web2py.app.%s.compiled.w2p' % app
|
|
filename = app_pack_compiled(app, request, raise_ex=True)
|
|
except Exception as e:
|
|
filename = None
|
|
|
|
if filename:
|
|
response.headers['Content-Type'] = 'application/w2p'
|
|
disposition = 'attachment; filename=%s' % fname
|
|
response.headers['Content-Disposition'] = disposition
|
|
return safe_read(filename, 'rb')
|
|
else:
|
|
session.flash = T('internal error: %s', e)
|
|
redirect(URL('site'))
|
|
|
|
|
|
def pack_plugin():
|
|
app = get_app()
|
|
if len(request.args) == 2:
|
|
fname = 'web2py.plugin.%s.w2p' % request.args[1]
|
|
filename = plugin_pack(app, request.args[1], request)
|
|
if filename:
|
|
response.headers['Content-Type'] = 'application/w2p'
|
|
disposition = 'attachment; filename=%s' % fname
|
|
response.headers['Content-Disposition'] = disposition
|
|
return safe_read(filename, 'rb')
|
|
else:
|
|
session.flash = T('internal error')
|
|
redirect(URL('plugin', args=request.args))
|
|
|
|
|
|
def pack_exe(app, base, filenames=None):
|
|
import urllib
|
|
import zipfile
|
|
# Download latest web2py_win and open it with zipfile
|
|
download_url = 'http://www.web2py.com/examples/static/web2py_win.zip'
|
|
out = StringIO()
|
|
out.write(urlopen(download_url).read())
|
|
web2py_win = zipfile.ZipFile(out, mode='a')
|
|
# Write routes.py with the application as default
|
|
routes = u'# -*- coding: utf-8 -*-\nrouters = dict(BASE=dict(default_application="%s"))' % app
|
|
web2py_win.writestr('web2py/routes.py', routes.encode('utf-8'))
|
|
# Copy the application into the zipfile
|
|
common_root = os.path.dirname(base)
|
|
for filename in filenames:
|
|
fname = os.path.join(base, filename)
|
|
arcname = os.path.join('web2py/applications', app, filename)
|
|
web2py_win.write(fname, arcname)
|
|
web2py_win.close()
|
|
response.headers['Content-Type'] = 'application/zip'
|
|
response.headers['Content-Disposition'] = 'attachment; filename=web2py.app.%s.zip' % app
|
|
out.seek(0)
|
|
return response.stream(out)
|
|
|
|
|
|
def pack_custom():
|
|
app = get_app()
|
|
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:
|
|
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 = [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:
|
|
fname = 'web2py.app.%s.w2p' % app
|
|
try:
|
|
filename = app_pack(app, request, raise_ex=True, filenames=files)
|
|
except Exception as e:
|
|
filename = None
|
|
if filename:
|
|
response.headers['Content-Type'] = 'application/w2p'
|
|
disposition = 'attachment; filename=%s' % fname
|
|
response.headers['Content-Disposition'] = disposition
|
|
return safe_read(filename, 'rb')
|
|
else:
|
|
session.flash = T('internal error: %s', e)
|
|
redirect(URL(args=request.args))
|
|
else:
|
|
return pack_exe(app, base, files)
|
|
|
|
return locals()
|
|
|
|
|
|
def upgrade_web2py():
|
|
dialog = FORM.confirm(T('Upgrade'),
|
|
{T('Cancel'): URL('site')})
|
|
if dialog.accepted:
|
|
(success, error) = upgrade(request)
|
|
if success:
|
|
session.flash = T('web2py upgraded; please restart it')
|
|
else:
|
|
session.flash = T('unable to upgrade because "%s"', error)
|
|
redirect(URL('site'))
|
|
return dict(dialog=dialog)
|
|
|
|
|
|
def uninstall():
|
|
app = get_app()
|
|
|
|
dialog = FORM.confirm(T('Uninstall'),
|
|
{T('Cancel'): URL('site')})
|
|
dialog['_id'] = 'confirm_form'
|
|
dialog['_class'] = 'well'
|
|
for component in dialog.components:
|
|
component['_class'] = 'btn'
|
|
|
|
if dialog.accepted:
|
|
if MULTI_USER_MODE:
|
|
if is_manager() and db(db.app.name == app).delete():
|
|
pass
|
|
elif db(db.app.name == app)(db.app.owner == auth.user.id).delete():
|
|
pass
|
|
else:
|
|
session.flash = T('no permission to uninstall "%s"', app)
|
|
redirect(URL('site'))
|
|
try:
|
|
filename = app_pack(app, request, raise_ex=True)
|
|
except:
|
|
session.flash = T('unable to uninstall "%s"', app)
|
|
else:
|
|
if app_uninstall(app, request):
|
|
session.flash = T('application "%s" uninstalled', app)
|
|
else:
|
|
session.flash = T('unable to uninstall "%s"', app)
|
|
redirect(URL('site'))
|
|
return dict(app=app, dialog=dialog)
|
|
|
|
|
|
def cleanup():
|
|
app = get_app()
|
|
clean = app_cleanup(app, request)
|
|
if not clean:
|
|
session.flash = T("some files could not be removed")
|
|
else:
|
|
session.flash = T('cache, errors and sessions cleaned')
|
|
|
|
redirect(URL('site'))
|
|
|
|
|
|
def compile_app():
|
|
app = get_app()
|
|
c = app_compile(app, request,
|
|
skip_failed_views=(request.args(1) == 'skip_failed_views'))
|
|
if not c:
|
|
session.flash = T('application compiled')
|
|
elif isinstance(c, list):
|
|
session.flash = DIV(*[T('application compiled'), BR(), BR(),
|
|
T('WARNING: The following views could not be compiled:'), BR()] +
|
|
[CAT(BR(), view) for view in c] +
|
|
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
|
|
else:
|
|
session.flash = DIV(T('Cannot compile: there are errors in your app:'),
|
|
CODE(c))
|
|
redirect(URL('site'))
|
|
|
|
|
|
def remove_compiled_app():
|
|
""" Remove the compiled application """
|
|
app = get_app()
|
|
remove_compiled_application(apath(app, r=request))
|
|
session.flash = T('compiled application removed')
|
|
redirect(URL('site'))
|
|
|
|
|
|
def delete():
|
|
""" Object delete handler """
|
|
app = get_app()
|
|
filename = '/'.join(request.args)
|
|
sender = request.vars.sender
|
|
|
|
if isinstance(sender, list): # ## fix a problem with Vista
|
|
sender = sender[0]
|
|
|
|
dialog = FORM.confirm(T('Delete'),
|
|
{T('Cancel'): URL(sender, anchor=request.vars.id)})
|
|
|
|
if dialog.accepted:
|
|
try:
|
|
full_path = apath(filename, r=request)
|
|
lineno = count_lines(open(full_path, 'r').read())
|
|
os.unlink(full_path)
|
|
log_progress(app, 'DELETE', filename, progress=-lineno)
|
|
session.flash = T('file "%(filename)s" deleted',
|
|
dict(filename=filename))
|
|
except Exception:
|
|
session.flash = T('unable to delete file "%(filename)s"',
|
|
dict(filename=filename))
|
|
redirect(URL(sender, anchor=request.vars.id2))
|
|
return dict(dialog=dialog, filename=filename)
|
|
|
|
def enable():
|
|
if not URL.verify(request, hmac_key=session.hmac_key): raise HTTP(401)
|
|
app = get_app()
|
|
filename = os.path.join(apath(app, r=request), 'DISABLED')
|
|
if is_gae:
|
|
return SPAN(T('Not supported'), _style='color:yellow')
|
|
elif os.path.exists(filename):
|
|
os.unlink(filename)
|
|
return SPAN(T('Disable'), _style='color:green')
|
|
else:
|
|
safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now)
|
|
return SPAN(T('Enable'), _style='color:red')
|
|
|
|
|
|
def peek():
|
|
""" Visualize object code """
|
|
app = get_app(request.vars.app)
|
|
filename = '/'.join(request.args)
|
|
if request.vars.app:
|
|
path = abspath(filename)
|
|
else:
|
|
path = apath(filename, r=request)
|
|
try:
|
|
data = safe_read(path).replace('\r', '')
|
|
except IOError:
|
|
session.flash = T('file does not exist')
|
|
redirect(URL('site'))
|
|
|
|
extension = filename[filename.rfind('.') + 1:].lower()
|
|
|
|
return dict(app=app,
|
|
filename=filename,
|
|
data=data,
|
|
extension=extension)
|
|
|
|
|
|
def test():
|
|
""" Execute controller tests """
|
|
app = get_app()
|
|
if len(request.args) > 1:
|
|
file = request.args[1]
|
|
else:
|
|
file = '.*\.py'
|
|
|
|
controllers = listdir(
|
|
apath('%s/controllers/' % app, r=request), file + '$')
|
|
|
|
return dict(app=app, controllers=controllers)
|
|
|
|
|
|
def keepalive():
|
|
return ''
|
|
|
|
|
|
def search():
|
|
keywords = request.vars.keywords or ''
|
|
app = get_app()
|
|
|
|
def match(filename, keywords):
|
|
filename = os.path.join(apath(app, r=request), filename)
|
|
if keywords in read_file(filename, 'rb'):
|
|
return True
|
|
return False
|
|
path = apath(request.args[0], r=request)
|
|
files1 = glob(os.path.join(path, '*/*.py'))
|
|
files2 = glob(os.path.join(path, '*/*.html'))
|
|
files3 = glob(os.path.join(path, '*/*/*.html'))
|
|
files = [x[len(path) + 1:].replace(
|
|
'\\', '/') for x in files1 + files2 + files3 if match(x, keywords)]
|
|
return response.json(dict(files=files, message=T.M('Searching: **%s** %%{file}', len(files))))
|
|
|
|
|
|
def edit():
|
|
""" File edit handler """
|
|
# Load json only if it is ajax edited...
|
|
app = get_app(request.vars.app)
|
|
app_path = apath(app, r=request)
|
|
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'),
|
|
section='editor', default_values={})
|
|
preferences.update(config.read())
|
|
|
|
if not(request.ajax) and not(is_mobile):
|
|
# return the scaffolding, the rest will be through ajax requests
|
|
response.title = T('Editing %s') % app
|
|
return response.render('default/edit.html', dict(app=app, editor_settings=preferences))
|
|
|
|
# show settings tab and save prefernces
|
|
if 'settings' in request.vars:
|
|
if request.post_vars: # save new preferences
|
|
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
|
|
post_vars += [(opt, 'false') for opt in preferences if opt not in request.post_vars]
|
|
if config.save(post_vars):
|
|
response.headers["web2py-component-flash"] = T('Preferences saved correctly')
|
|
else:
|
|
response.headers["web2py-component-flash"] = T('Preferences saved on session only')
|
|
response.headers["web2py-component-command"] = "update_editor(%s);$('a[href=#editor_settings] button.close').click();" % response.json(config.read())
|
|
return
|
|
else:
|
|
details = {'realfilename': 'settings', 'filename': 'settings', 'id': 'editor_settings', 'force': False}
|
|
details['plain_html'] = response.render('default/editor_settings.html', {'editor_settings': preferences})
|
|
return response.json(details)
|
|
|
|
""" File edit handler """
|
|
# Load json only if it is ajax edited...
|
|
app = get_app(request.vars.app)
|
|
filename = '/'.join(request.args)
|
|
realfilename = request.args[-1]
|
|
if request.vars.app:
|
|
path = abspath(filename)
|
|
else:
|
|
path = apath(filename, r=request)
|
|
# Try to discover the file type
|
|
if filename[-3:] == '.py':
|
|
filetype = 'python'
|
|
elif filename[-5:] == '.html':
|
|
filetype = 'html'
|
|
elif filename[-5:] == '.load':
|
|
filetype = 'html'
|
|
elif filename[-4:] == '.css':
|
|
filetype = 'css'
|
|
elif filename[-3:] == '.js':
|
|
filetype = 'javascript'
|
|
else:
|
|
filetype = 'html'
|
|
|
|
# ## check if file is not there
|
|
if ('revert' in request.vars) and os.path.exists(path + '.bak'):
|
|
try:
|
|
data = safe_read(path + '.bak')
|
|
data1 = safe_read(path)
|
|
except IOError:
|
|
session.flash = T('Invalid action')
|
|
if 'from_ajax' in request.vars:
|
|
return response.json({'error': str(T('Invalid action'))})
|
|
else:
|
|
redirect(URL('site'))
|
|
|
|
safe_write(path, data)
|
|
file_hash = md5_hash(data)
|
|
saved_on = time.ctime(os.stat(path)[stat.ST_MTIME])
|
|
safe_write(path + '.bak', data1)
|
|
response.flash = T('file "%s" of %s restored', (filename, saved_on))
|
|
else:
|
|
try:
|
|
data = safe_read(path)
|
|
except IOError:
|
|
session.flash = T('Invalid action')
|
|
if 'from_ajax' in request.vars:
|
|
return response.json({'error': str(T('Invalid action'))})
|
|
else:
|
|
redirect(URL('site'))
|
|
|
|
lineno_old = count_lines(data)
|
|
file_hash = md5_hash(data)
|
|
saved_on = time.ctime(os.stat(path)[stat.ST_MTIME])
|
|
|
|
if request.vars.file_hash and request.vars.file_hash != file_hash:
|
|
session.flash = T('file changed on disk')
|
|
data = request.vars.data.replace('\r\n', '\n').strip() + '\n'
|
|
safe_write(path + '.1', data)
|
|
if 'from_ajax' in request.vars:
|
|
return response.json({'error': str(T('file changed on disk')),
|
|
'redirect': URL('resolve',
|
|
args=request.args)})
|
|
else:
|
|
redirect(URL('resolve', args=request.args))
|
|
elif request.vars.data:
|
|
safe_write(path + '.bak', data)
|
|
data = request.vars.data.replace('\r\n', '\n').strip() + '\n'
|
|
safe_write(path, data)
|
|
lineno_new = count_lines(data)
|
|
log_progress(
|
|
app, 'EDIT', filename, progress=lineno_new - lineno_old)
|
|
file_hash = md5_hash(data)
|
|
saved_on = time.ctime(os.stat(path)[stat.ST_MTIME])
|
|
response.flash = T('file saved on %s', saved_on)
|
|
|
|
data_or_revert = (request.vars.data or request.vars.revert)
|
|
|
|
# Check compile errors
|
|
highlight = None
|
|
if filetype == 'python' and request.vars.data:
|
|
import _ast
|
|
try:
|
|
code = request.vars.data.rstrip().replace('\r\n', '\n') + '\n'
|
|
compile(code, path, "exec", _ast.PyCF_ONLY_AST)
|
|
except Exception as e:
|
|
# offset calculation is only used for textarea (start/stop)
|
|
start = sum([len(line) + 1 for l, line
|
|
in enumerate(request.vars.data.split("\n"))
|
|
if l < e.lineno - 1])
|
|
if e.text and e.offset:
|
|
offset = e.offset - (len(e.text) - len(
|
|
e.text.splitlines()[-1]))
|
|
else:
|
|
offset = 0
|
|
highlight = {'start': start, 'end': start +
|
|
offset + 1, 'lineno': e.lineno, 'offset': offset}
|
|
try:
|
|
ex_name = e.__class__.__name__
|
|
except:
|
|
ex_name = 'unknown exception!'
|
|
response.flash = DIV(T('failed to compile file because:'), BR(),
|
|
B(ex_name), ' ' + T('at line %s', e.lineno),
|
|
offset and ' ' +
|
|
T('at char %s', offset) or '',
|
|
PRE(repr(e)))
|
|
if data_or_revert and request.args[1] == 'modules':
|
|
# Lets try to reload the modules
|
|
try:
|
|
mopath = '.'.join(request.args[2:])[:-3]
|
|
exec('import applications.%s.modules.%s' % (
|
|
request.args[0], mopath))
|
|
reload(sys.modules['applications.%s.modules.%s'
|
|
% (request.args[0], mopath)])
|
|
except Exception as e:
|
|
response.flash = DIV(
|
|
T('failed to reload module because:'), PRE(repr(e)))
|
|
|
|
edit_controller = None
|
|
editviewlinks = None
|
|
view_link = None
|
|
if filetype == 'html' and len(request.args) >= 3:
|
|
cfilename = os.path.join(request.args[0], 'controllers',
|
|
request.args[2] + '.py')
|
|
if os.path.exists(apath(cfilename, r=request)):
|
|
edit_controller = URL('edit', args=[cfilename.replace(os.sep, "/")])
|
|
view = request.args[3].replace('.html', '')
|
|
view_link = URL(request.args[0], request.args[2], view)
|
|
elif filetype == 'python' and request.args[1] == 'controllers':
|
|
# it's a controller file.
|
|
# Create links to all of the associated view files.
|
|
app = get_app()
|
|
viewname = os.path.splitext(request.args[2])[0]
|
|
viewpath = os.path.join(app, 'views', viewname)
|
|
aviewpath = apath(viewpath, r=request)
|
|
viewlist = []
|
|
if os.path.exists(aviewpath):
|
|
if os.path.isdir(aviewpath):
|
|
viewlist = glob(os.path.join(aviewpath, '*.html'))
|
|
elif os.path.exists(aviewpath + '.html'):
|
|
viewlist.append(aviewpath + '.html')
|
|
if len(viewlist):
|
|
editviewlinks = []
|
|
for v in sorted(viewlist):
|
|
vf = os.path.split(v)[-1]
|
|
vargs = "/".join([viewpath.replace(os.sep, "/"), vf])
|
|
editviewlinks.append(A(vf.split(".")[0],
|
|
_class="editor_filelink",
|
|
_href=URL('edit', args=[vargs])))
|
|
|
|
if len(request.args) > 2 and request.args[1] == 'controllers':
|
|
controller = (request.args[2])[:-3]
|
|
functions = find_exposed_functions(data)
|
|
functions = functions and sorted(functions) or []
|
|
else:
|
|
(controller, functions) = (None, None)
|
|
|
|
if 'from_ajax' in request.vars:
|
|
return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight})
|
|
else:
|
|
file_details = dict(app=request.args[0],
|
|
lineno=request.vars.lineno or 1,
|
|
editor_settings=preferences,
|
|
filename=filename,
|
|
realfilename=realfilename,
|
|
filetype=filetype,
|
|
data=data,
|
|
edit_controller=edit_controller,
|
|
file_hash=file_hash,
|
|
saved_on=saved_on,
|
|
controller=controller,
|
|
functions=functions,
|
|
view_link=view_link,
|
|
editviewlinks=editviewlinks,
|
|
id=IS_SLUG()(filename)[0],
|
|
force=True if (request.vars.restore or
|
|
request.vars.revert) else False)
|
|
plain_html = response.render('default/edit_js.html', file_details)
|
|
file_details['plain_html'] = plain_html
|
|
if is_mobile:
|
|
return response.render('default.mobile/edit.html',
|
|
file_details, editor_settings=preferences)
|
|
else:
|
|
return response.json(file_details)
|
|
|
|
|
|
def todolist():
|
|
""" Returns all TODO of the requested app
|
|
"""
|
|
app = request.vars.app or ''
|
|
app_path = apath('%(app)s' % {'app': app}, r=request)
|
|
dirs = ['models', 'controllers', 'modules', 'private']
|
|
|
|
def listfiles(app, dir, regexp='.*\.py$'):
|
|
files = sorted(listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp))
|
|
files = [x.replace(os.path.sep, '/') for x in files if not x.endswith('.bak')]
|
|
return files
|
|
|
|
pattern = '#\s*(todo)+\s+(.*)'
|
|
regex = re.compile(pattern, re.IGNORECASE)
|
|
|
|
output = []
|
|
for d in dirs:
|
|
for f in listfiles(app, d):
|
|
matches = []
|
|
filename = apath(os.path.join(app, d, f), r=request)
|
|
with safe_open(filename, 'r') as f_s:
|
|
src = f_s.read()
|
|
for m in regex.finditer(src):
|
|
start = m.start()
|
|
lineno = src.count('\n', 0, start) + 1
|
|
matches.append({'text': m.group(0), 'lineno': lineno})
|
|
if len(matches) != 0:
|
|
output.append({'filename': f, 'matches': matches, 'dir': d})
|
|
|
|
return {'todo': output, 'app': app}
|
|
|
|
|
|
def editor_sessions():
|
|
config = Config(os.path.join(request.folder, 'settings.cfg'),
|
|
section='editor_sessions', default_values={})
|
|
preferences = config.read()
|
|
|
|
if request.vars.session_name and request.vars.files:
|
|
session_name = request.vars.session_name
|
|
files = request.vars.files
|
|
preferences.update({session_name: ','.join(files)})
|
|
if config.save(preferences.items()):
|
|
response.headers["web2py-component-flash"] = T('Session saved correctly')
|
|
else:
|
|
response.headers["web2py-component-flash"] = T('Session saved on session only')
|
|
|
|
return response.render('default/editor_sessions.html', {'editor_sessions': preferences})
|
|
|
|
|
|
def resolve():
|
|
"""
|
|
"""
|
|
|
|
filename = '/'.join(request.args)
|
|
# ## check if file is not there
|
|
path = apath(filename, r=request)
|
|
a = safe_read(path).split('\n')
|
|
try:
|
|
b = safe_read(path + '.1').split('\n')
|
|
except IOError:
|
|
session.flash = 'Other file, no longer there'
|
|
redirect(URL('edit', args=request.args))
|
|
|
|
d = difflib.ndiff(a, b)
|
|
|
|
def leading(line):
|
|
""" """
|
|
|
|
# TODO: we really need to comment this
|
|
z = ''
|
|
for (k, c) in enumerate(line):
|
|
if c == ' ':
|
|
z += ' '
|
|
elif c == ' \t':
|
|
z += ' '
|
|
elif k == 0 and c == '?':
|
|
pass
|
|
else:
|
|
break
|
|
|
|
return XML(z)
|
|
|
|
def getclass(item):
|
|
""" Determine item class """
|
|
operators = {' ': 'normal', '+': 'plus', '-': 'minus'}
|
|
|
|
return operators[item[0]]
|
|
|
|
if request.vars:
|
|
c = '\n'.join([item[2:].rstrip() for (i, item) in enumerate(d) if item[0]
|
|
== ' ' or 'line%i' % i in request.vars])
|
|
safe_write(path, c)
|
|
session.flash = 'files merged'
|
|
redirect(URL('edit', args=request.args))
|
|
else:
|
|
# Making the short circuit compatible with <= python2.4
|
|
gen_data = lambda index, item: not item[:1] in ['+', '-'] and "" \
|
|
or INPUT(_type='checkbox',
|
|
_name='line%i' % index,
|
|
value=item[0] == '+')
|
|
|
|
diff = TABLE(*[TR(TD(gen_data(i, item)),
|
|
TD(item[0]),
|
|
TD(leading(item[2:]),
|
|
TT(item[2:].rstrip())),
|
|
_class=getclass(item))
|
|
for (i, item) in enumerate(d) if item[0] != '?'])
|
|
|
|
return dict(diff=diff, filename=filename)
|
|
|
|
|
|
def edit_language():
|
|
""" Edit language file """
|
|
app = get_app()
|
|
filename = '/'.join(request.args)
|
|
response.title = request.args[-1]
|
|
strings = read_dict(apath(filename, r=request))
|
|
|
|
if '__corrupted__' in strings:
|
|
form = SPAN(strings['__corrupted__'], _class='error')
|
|
return dict(filename=filename, form=form)
|
|
|
|
keys = sorted(strings.keys(), key=lambda x: to_native(x).lower())
|
|
rows = []
|
|
rows.append(H2(T('Original/Translation')))
|
|
|
|
for key in keys:
|
|
name = md5_hash(key)
|
|
s = strings[key]
|
|
(prefix, sep, key) = key.partition('\x01')
|
|
if sep:
|
|
prefix = SPAN(prefix + ': ', _class='tm_ftag')
|
|
k = key
|
|
else:
|
|
(k, prefix) = (prefix, '')
|
|
|
|
_class = 'untranslated' if k == s else 'translated'
|
|
|
|
if len(s) <= 40:
|
|
elem = INPUT(_type='text', _name=name, value=s,
|
|
_size=70, _class=_class)
|
|
else:
|
|
elem = TEXTAREA(_name=name, value=s, _cols=70,
|
|
_rows=5, _class=_class)
|
|
|
|
# Making the short circuit compatible with <= python2.4
|
|
k = (s != k) and k or B(k)
|
|
|
|
new_row = DIV(LABEL(prefix, k, _style="font-weight:normal;"),
|
|
CAT(elem, '\n', TAG.BUTTON(
|
|
T('delete'),
|
|
_onclick='return delkey("%s")' % name,
|
|
_class='btn')), _id=name, _class='span6 well well-small')
|
|
|
|
rows.append(DIV(new_row, _class="row-fluid"))
|
|
rows.append(DIV(INPUT(_type='submit', _value=T('update'), _class="btn btn-primary"), _class='controls'))
|
|
form = FORM(*rows)
|
|
if form.accepts(request.vars, keepvalues=True):
|
|
strs = dict()
|
|
for key in keys:
|
|
name = md5_hash(key)
|
|
if form.vars[name] == chr(127):
|
|
continue
|
|
strs[key] = form.vars[name]
|
|
write_dict(apath(filename, r=request), strs)
|
|
session.flash = T('file saved on %(time)s', dict(time=time.ctime()))
|
|
redirect(URL(r=request, args=request.args))
|
|
return dict(app=request.args[0], filename=filename, form=form)
|
|
|
|
|
|
def edit_plurals():
|
|
""" Edit plurals file """
|
|
app = get_app()
|
|
filename = '/'.join(request.args)
|
|
plurals = read_plural_dict(
|
|
apath(filename, r=request)) # plural forms dictionary
|
|
nplurals = int(request.vars.nplurals) - 1 # plural forms quantity
|
|
xnplurals = xrange(nplurals)
|
|
|
|
if '__corrupted__' in plurals:
|
|
# show error message and exit
|
|
form = SPAN(plurals['__corrupted__'], _class='error')
|
|
return dict(filename=filename, form=form)
|
|
|
|
keys = sorted(plurals.keys(), lambda x, y: cmp(
|
|
unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower()))
|
|
tab_rows = []
|
|
for key in keys:
|
|
name = md5_hash(key)
|
|
forms = plurals[key]
|
|
|
|
if len(forms) < nplurals:
|
|
forms.extend(None for i in xrange(nplurals - len(forms)))
|
|
tab_col1 = DIV(CAT(LABEL(T("Singular Form")), B(key,
|
|
_class='fake-input')))
|
|
tab_inputs = [SPAN(LABEL(T("Plural Form #%s", n + 1)), INPUT(_type='text', _name=name + '_' + str(n), value=forms[n], _size=20), _class='span6') for n in xnplurals]
|
|
tab_col2 = DIV(CAT(*tab_inputs))
|
|
tab_col3 = DIV(CAT(LABEL(XML(' ')), TAG.BUTTON(T('delete'), _onclick='return delkey("%s")' % name, _class='btn'), _class='span6'))
|
|
tab_row = DIV(DIV(tab_col1, '\n', tab_col2, '\n', tab_col3, _class='well well-small'), _id=name, _class='row-fluid tab_row')
|
|
tab_rows.append(tab_row)
|
|
|
|
tab_rows.append(DIV(TAG['button'](T('update'), _type='submit',
|
|
_class='btn btn-primary'),
|
|
_class='controls'))
|
|
tab_container = DIV(*tab_rows, **dict(_class="row-fluid"))
|
|
|
|
form = FORM(tab_container)
|
|
if form.accepts(request.vars, keepvalues=True):
|
|
new_plurals = dict()
|
|
for key in keys:
|
|
name = md5_hash(key)
|
|
if form.vars[name + '_0'] == chr(127):
|
|
continue
|
|
new_plurals[key] = [form.vars[name + '_' + str(n)]
|
|
for n in xnplurals]
|
|
write_plural_dict(apath(filename, r=request), new_plurals)
|
|
session.flash = T('file saved on %(time)s', dict(time=time.ctime()))
|
|
redirect(URL(r=request, args=request.args, vars=dict(
|
|
nplurals=request.vars.nplurals)))
|
|
return dict(app=request.args[0], filename=filename, form=form)
|
|
|
|
|
|
def about():
|
|
""" Read about info """
|
|
app = get_app()
|
|
# ## check if file is not there
|
|
about = safe_read(apath('%s/ABOUT' % app, r=request))
|
|
license = safe_read(apath('%s/LICENSE' % app, r=request))
|
|
return dict(app=app, about=MARKMIN(about), license=MARKMIN(license), progress=report_progress(app))
|
|
|
|
|
|
def design():
|
|
""" Application design handler """
|
|
app = get_app()
|
|
|
|
if not response.flash and app == request.application:
|
|
msg = T('ATTENTION: you cannot edit the running application!')
|
|
response.flash = msg
|
|
|
|
if request.vars and not request.vars.token == session.token:
|
|
redirect(URL('logout'))
|
|
|
|
if request.vars.pluginfile is not None and not isinstance(request.vars.pluginfile, str):
|
|
filename = os.path.basename(request.vars.pluginfile.filename)
|
|
if plugin_install(app, request.vars.pluginfile.file,
|
|
request, filename):
|
|
session.flash = T('new plugin installed')
|
|
redirect(URL('design', args=app))
|
|
else:
|
|
session.flash = \
|
|
T('unable to install plugin "%s"', filename)
|
|
redirect(URL(r=request, args=app))
|
|
elif isinstance(request.vars.pluginfile, str):
|
|
session.flash = T('plugin not specified')
|
|
redirect(URL(r=request, args=app))
|
|
|
|
# If we have only pyc files it means that
|
|
# we cannot design
|
|
if os.path.exists(apath('%s/compiled' % app, r=request)):
|
|
session.flash = \
|
|
T('application is compiled and cannot be designed')
|
|
redirect(URL('site'))
|
|
|
|
# Get all models
|
|
models = listdir(apath('%s/models/' % app, r=request), '.*\.py$')
|
|
models = [x.replace('\\', '/') for x in models]
|
|
defines = {}
|
|
for m in models:
|
|
data = safe_read(apath('%s/models/%s' % (app, m), r=request))
|
|
defines[m] = regex_tables.findall(data)
|
|
defines[m].sort()
|
|
|
|
# Get all controllers
|
|
controllers = sorted(
|
|
listdir(apath('%s/controllers/' % app, r=request), '.*\.py$'))
|
|
controllers = [x.replace('\\', '/') for x in controllers]
|
|
functions = {}
|
|
for c in controllers:
|
|
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
|
items = find_exposed_functions(data)
|
|
functions[c] = items and sorted(items) or []
|
|
|
|
# Get all views
|
|
views = sorted(
|
|
listdir(apath('%s/views/' % app, r=request), '[\w/\-]+(\.\w+)+$'))
|
|
views = [x.replace('\\', '/') for x in views if not x.endswith('.bak')]
|
|
extend = {}
|
|
include = {}
|
|
for c in views:
|
|
data = safe_read(apath('%s/views/%s' % (app, c), r=request))
|
|
items = regex_extend.findall(data)
|
|
|
|
if items:
|
|
extend[c] = items[0][1]
|
|
|
|
items = regex_include.findall(data)
|
|
include[c] = [i[1] for i in items]
|
|
|
|
# Get all modules
|
|
modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$')
|
|
modules = modules = [x.replace('\\', '/') for x in modules]
|
|
modules.sort()
|
|
|
|
# Get all private files
|
|
privates = listdir(apath('%s/private/' % app, r=request), '[^\.#].*')
|
|
privates = [x.replace('\\', '/') for x in privates]
|
|
privates.sort()
|
|
|
|
# Get all static files
|
|
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*',
|
|
maxnum=MAXNFILES)
|
|
statics = [x.replace(os.path.sep, '/') for x in statics]
|
|
statics.sort()
|
|
|
|
# Get all languages
|
|
langpath = os.path.join(apath(app, r=request), 'languages')
|
|
languages = dict([(lang, info) for lang, info
|
|
in iteritems(read_possible_languages(langpath))
|
|
if info[2] != 0]) # info[2] is langfile_mtime:
|
|
# get only existed files
|
|
|
|
# Get crontab
|
|
cronfolder = apath('%s/cron' % app, r=request)
|
|
crontab = apath('%s/cron/crontab' % app, r=request)
|
|
if not is_gae:
|
|
if not os.path.exists(cronfolder):
|
|
os.mkdir(cronfolder)
|
|
if not os.path.exists(crontab):
|
|
safe_write(crontab, '#crontab')
|
|
|
|
plugins = []
|
|
|
|
def filter_plugins(items, plugins):
|
|
plugins += [item[7:].split('/')[0].split(
|
|
'.')[0] for item in items if item.startswith('plugin_')]
|
|
plugins[:] = list(set(plugins))
|
|
plugins.sort()
|
|
return [item for item in items if not item.startswith('plugin_')]
|
|
|
|
return dict(app=app,
|
|
models=filter_plugins(models, plugins),
|
|
defines=defines,
|
|
controllers=filter_plugins(controllers, plugins),
|
|
functions=functions,
|
|
views=filter_plugins(views, plugins),
|
|
modules=filter_plugins(modules, plugins),
|
|
extend=extend,
|
|
include=include,
|
|
privates=filter_plugins(privates, plugins),
|
|
statics=filter_plugins(statics, plugins),
|
|
languages=languages,
|
|
crontab=crontab,
|
|
plugins=plugins)
|
|
|
|
|
|
def delete_plugin():
|
|
""" Object delete handler """
|
|
app = request.args(0)
|
|
plugin = request.args(1)
|
|
plugin_name = 'plugin_' + plugin
|
|
|
|
dialog = FORM.confirm(
|
|
T('Delete'),
|
|
{T('Cancel'): URL('design', args=app)})
|
|
|
|
if dialog.accepted:
|
|
try:
|
|
for folder in ['models', 'views', 'controllers', 'static', 'modules', 'private']:
|
|
path = os.path.join(apath(app, r=request), folder)
|
|
for item in os.listdir(path):
|
|
if item.rsplit('.', 1)[0] == plugin_name:
|
|
filename = os.path.join(path, item)
|
|
if os.path.isdir(filename):
|
|
shutil.rmtree(filename)
|
|
else:
|
|
os.unlink(filename)
|
|
session.flash = T('plugin "%(plugin)s" deleted',
|
|
dict(plugin=plugin))
|
|
except Exception:
|
|
session.flash = T('unable to delete file plugin "%(plugin)s"',
|
|
dict(plugin=plugin))
|
|
redirect(URL('design', args=request.args(0), anchor=request.vars.id2))
|
|
return dict(dialog=dialog, plugin=plugin)
|
|
|
|
|
|
def plugin():
|
|
""" Application design handler """
|
|
app = get_app()
|
|
plugin = request.args(1)
|
|
|
|
if not response.flash and app == request.application:
|
|
msg = T('ATTENTION: you cannot edit the running application!')
|
|
response.flash = msg
|
|
|
|
# If we have only pyc files it means that
|
|
# we cannot design
|
|
if os.path.exists(apath('%s/compiled' % app, r=request)):
|
|
session.flash = \
|
|
T('application is compiled and cannot be designed')
|
|
redirect(URL('site'))
|
|
|
|
# Get all models
|
|
models = listdir(apath('%s/models/' % app, r=request), '.*\.py$')
|
|
models = [x.replace('\\', '/') for x in models]
|
|
defines = {}
|
|
for m in models:
|
|
data = safe_read(apath('%s/models/%s' % (app, m), r=request))
|
|
defines[m] = regex_tables.findall(data)
|
|
defines[m].sort()
|
|
|
|
# Get all controllers
|
|
controllers = sorted(
|
|
listdir(apath('%s/controllers/' % app, r=request), '.*\.py$'))
|
|
controllers = [x.replace('\\', '/') for x in controllers]
|
|
functions = {}
|
|
for c in controllers:
|
|
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
|
items = find_exposed_functions(data)
|
|
functions[c] = items and sorted(items) or []
|
|
|
|
# Get all views
|
|
views = sorted(
|
|
listdir(apath('%s/views/' % app, r=request), '[\w/\-]+\.\w+$'))
|
|
views = [x.replace('\\', '/') for x in views]
|
|
extend = {}
|
|
include = {}
|
|
for c in views:
|
|
data = safe_read(apath('%s/views/%s' % (app, c), r=request))
|
|
items = regex_extend.findall(data)
|
|
if items:
|
|
extend[c] = items[0][1]
|
|
|
|
items = regex_include.findall(data)
|
|
include[c] = [i[1] for i in items]
|
|
|
|
# Get all modules
|
|
modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$')
|
|
modules = modules = [x.replace('\\', '/') for x in modules]
|
|
modules.sort()
|
|
|
|
# Get all private files
|
|
privates = listdir(apath('%s/private/' % app, r=request), '[^\.#].*')
|
|
privates = [x.replace('\\', '/') for x in privates]
|
|
privates.sort()
|
|
|
|
# Get all static files
|
|
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*',
|
|
maxnum=MAXNFILES)
|
|
statics = [x.replace(os.path.sep, '/') for x in statics]
|
|
statics.sort()
|
|
|
|
# Get all languages
|
|
languages = sorted([lang + '.py' for lang, info in
|
|
iteritems(T.get_possible_languages_info())
|
|
if info[2] != 0]) # info[2] is langfile_mtime:
|
|
# get only existed files
|
|
|
|
# Get crontab
|
|
crontab = apath('%s/cron/crontab' % app, r=request)
|
|
if not os.path.exists(crontab):
|
|
safe_write(crontab, '#crontab')
|
|
|
|
def filter_plugins(items):
|
|
regex = re.compile('^plugin_' + plugin + '(/.*|\..*)?$')
|
|
return [item for item in items if item and regex.match(item)]
|
|
|
|
return dict(app=app,
|
|
models=filter_plugins(models),
|
|
defines=defines,
|
|
controllers=filter_plugins(controllers),
|
|
functions=functions,
|
|
views=filter_plugins(views),
|
|
modules=filter_plugins(modules),
|
|
extend=extend,
|
|
include=include,
|
|
privates=filter_plugins(privates),
|
|
statics=filter_plugins(statics),
|
|
languages=languages,
|
|
crontab=crontab)
|
|
|
|
|
|
def create_file():
|
|
""" Create files handler """
|
|
if request.vars and not request.vars.token == session.token:
|
|
redirect(URL('logout'))
|
|
try:
|
|
anchor = '#' + request.vars.id if request.vars.id else ''
|
|
if request.vars.app:
|
|
app = get_app(request.vars.app)
|
|
path = abspath(request.vars.location)
|
|
else:
|
|
if request.vars.dir:
|
|
request.vars.location += request.vars.dir + '/'
|
|
app = get_app(name=request.vars.location.split('/')[0])
|
|
path = apath(request.vars.location, r=request)
|
|
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
|
if path[-7:] == '/rules/':
|
|
# Handle plural rules files
|
|
if len(filename) == 0:
|
|
raise SyntaxError
|
|
if not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
lang = re.match('^plural_rules-(.*)\.py$', filename).group(1)
|
|
langinfo = read_possible_languages(apath(app, r=request))[lang]
|
|
text = dedent("""
|
|
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# Plural-Forms for %(lang)s (%(langname)s)
|
|
|
|
nplurals=2 # for example, English language has 2 forms:
|
|
# 1 singular and 1 plural
|
|
|
|
# Determine plural_id for number *n* as sequence of positive
|
|
# integers: 0,1,...
|
|
# NOTE! For singular form ALWAYS return plural_id = 0
|
|
get_plural_id = lambda n: int(n != 1)
|
|
|
|
# Construct and return plural form of *word* using
|
|
# *plural_id* (which ALWAYS>0). This function will be executed
|
|
# for words (or phrases) not found in plural_dict dictionary.
|
|
# By default this function simply returns word in singular:
|
|
construct_plural_form = lambda word, plural_id: word
|
|
""")[1:] % dict(lang=langinfo[0], langname=langinfo[1])
|
|
|
|
elif path[-11:] == '/languages/':
|
|
# Handle language files
|
|
if len(filename) == 0:
|
|
raise SyntaxError
|
|
if not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
path = os.path.join(apath(app, r=request), 'languages', filename)
|
|
if not os.path.exists(path):
|
|
safe_write(path, '')
|
|
# create language xx[-yy].py file:
|
|
findT(apath(app, r=request), filename[:-3])
|
|
session.flash = T('language file "%(filename)s" created/updated',
|
|
dict(filename=filename))
|
|
redirect(request.vars.sender + anchor)
|
|
|
|
elif path[-8:] == '/models/':
|
|
# Handle python models
|
|
if not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
if len(filename) == 3:
|
|
raise SyntaxError
|
|
|
|
text = '# -*- coding: utf-8 -*-\n'
|
|
|
|
elif path[-13:] == '/controllers/':
|
|
# Handle python controllers
|
|
if not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
if len(filename) == 3:
|
|
raise SyntaxError
|
|
|
|
text = '# -*- coding: utf-8 -*-\n# %s\ndef index(): return dict(message="hello from %s")'
|
|
text = text % (T('try something like'), filename)
|
|
|
|
elif path[-7:] == '/views/':
|
|
if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin):
|
|
filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
|
|
# Handle template (html) views
|
|
if filename.find('.') < 0:
|
|
filename += '.html'
|
|
extension = filename.split('.')[-1].lower()
|
|
|
|
if len(filename) == 5:
|
|
raise SyntaxError
|
|
|
|
msg = T(
|
|
'This is the %(filename)s template', dict(filename=filename))
|
|
if extension == 'html':
|
|
text = dedent("""
|
|
{{extend 'layout.html'}}
|
|
<h1>%s</h1>
|
|
{{=BEAUTIFY(response._vars)}}""" % msg)[1:]
|
|
else:
|
|
generic = os.path.join(path, 'generic.' + extension)
|
|
if os.path.exists(generic):
|
|
text = read_file(generic)
|
|
else:
|
|
text = ''
|
|
|
|
elif path[-9:] == '/modules/':
|
|
if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin):
|
|
filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
|
|
# Handle python module files
|
|
if not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
if len(filename) == 3:
|
|
raise SyntaxError
|
|
|
|
text = dedent("""
|
|
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
from gluon import *\n""")[1:]
|
|
|
|
elif (path[-8:] == '/static/') or (path[-9:] == '/private/'):
|
|
if (request.vars.plugin and
|
|
not filename.startswith('plugin_%s/' % request.vars.plugin)):
|
|
filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
|
|
text = ''
|
|
|
|
else:
|
|
redirect(request.vars.sender + anchor)
|
|
|
|
full_filename = os.path.join(path, filename)
|
|
dirpath = os.path.dirname(full_filename)
|
|
|
|
if not os.path.exists(dirpath):
|
|
os.makedirs(dirpath)
|
|
|
|
if os.path.exists(full_filename):
|
|
raise SyntaxError
|
|
|
|
safe_write(full_filename, text)
|
|
log_progress(app, 'CREATE', filename)
|
|
if request.vars.dir:
|
|
result = T('file "%(filename)s" created',
|
|
dict(filename=full_filename[len(path):]))
|
|
else:
|
|
session.flash = T('file "%(filename)s" created',
|
|
dict(filename=full_filename[len(path):]))
|
|
vars = {}
|
|
if request.vars.id:
|
|
vars['id'] = request.vars.id
|
|
if request.vars.app:
|
|
vars['app'] = request.vars.app
|
|
redirect(URL('edit',
|
|
args=[os.path.join(request.vars.location, filename)], vars=vars))
|
|
|
|
except Exception as e:
|
|
if not isinstance(e, HTTP):
|
|
session.flash = T('cannot create file')
|
|
|
|
if request.vars.dir:
|
|
response.flash = result
|
|
response.headers['web2py-component-content'] = 'append'
|
|
response.headers['web2py-component-command'] = "%s %s %s" % (
|
|
"$.web2py.invalidate('#files_menu');",
|
|
"load_file('%s');" % URL('edit', args=[app, request.vars.dir, filename]),
|
|
"$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));")
|
|
return ''
|
|
else:
|
|
redirect(request.vars.sender + anchor)
|
|
|
|
|
|
def listfiles(app, dir, regexp='.*\.py$'):
|
|
files = sorted(
|
|
listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp))
|
|
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
|
return files
|
|
|
|
|
|
def editfile(path, file, vars={}, app=None):
|
|
args = (path, file) if 'app' in vars else (app, path, file)
|
|
url = URL('edit', args=args, vars=vars)
|
|
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
|
|
|
|
|
|
def files_menu():
|
|
app = request.vars.app or 'welcome'
|
|
dirs = [{'name': 'models', 'reg': '.*\.py$'},
|
|
{'name': 'controllers', 'reg': '.*\.py$'},
|
|
{'name': 'views', 'reg': '[\w/\-]+(\.\w+)+$'},
|
|
{'name': 'modules', 'reg': '.*\.py$'},
|
|
{'name': 'static', 'reg': '[^\.#].*'},
|
|
{'name': 'private', 'reg': '.*\.py$'}]
|
|
result_files = []
|
|
for dir in dirs:
|
|
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
|
|
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.', '__')), app), _style="overflow:hidden", _id=dir['name'] + "__" + f.replace('.', '__'))
|
|
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
|
|
_class="nav nav-list small-font"),
|
|
_id=dir['name'] + '_files', _style="display: none;")))
|
|
return dict(result_files=result_files)
|
|
|
|
|
|
def upload_file():
|
|
""" File uploading handler """
|
|
if request.vars and not request.vars.token == session.token:
|
|
redirect(URL('logout'))
|
|
try:
|
|
filename = None
|
|
app = get_app(name=request.vars.location.split('/')[0])
|
|
path = apath(request.vars.location, r=request)
|
|
|
|
if request.vars.filename:
|
|
filename = re.sub('[^\w\./]+', '_', request.vars.filename)
|
|
else:
|
|
filename = os.path.split(request.vars.file.filename)[-1]
|
|
|
|
if path[-8:] == '/models/' and not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
if path[-9:] == '/modules/' and not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
if path[-13:] == '/controllers/' and not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
if path[-7:] == '/views/' and not filename[-5:] == '.html':
|
|
filename += '.html'
|
|
|
|
if path[-11:] == '/languages/' and not filename[-3:] == '.py':
|
|
filename += '.py'
|
|
|
|
filename = os.path.join(path, filename)
|
|
dirpath = os.path.dirname(filename)
|
|
|
|
if not os.path.exists(dirpath):
|
|
os.makedirs(dirpath)
|
|
|
|
data = request.vars.file.file.read()
|
|
lineno = count_lines(data)
|
|
safe_write(filename, data, 'wb')
|
|
log_progress(app, 'UPLOAD', filename, lineno)
|
|
session.flash = T('file "%(filename)s" uploaded',
|
|
dict(filename=filename[len(path):]))
|
|
except Exception:
|
|
if filename:
|
|
d = dict(filename=filename[len(path):])
|
|
else:
|
|
d = dict(filename='unknown')
|
|
session.flash = T('cannot upload file "%(filename)s"', d)
|
|
|
|
redirect(request.vars.sender)
|
|
|
|
|
|
def errors():
|
|
""" Error handler """
|
|
import operator
|
|
import os
|
|
import hashlib
|
|
|
|
app = get_app()
|
|
if is_gae:
|
|
method = 'dbold' if ('old' in
|
|
(request.args(1) or '')) else 'dbnew'
|
|
else:
|
|
method = request.args(1) or 'new'
|
|
db_ready = {}
|
|
db_ready['status'] = get_ticket_storage(app)
|
|
db_ready['errmessage'] = T(
|
|
"No ticket_storage.txt found under /private folder")
|
|
db_ready['errlink'] = "http://web2py.com/books/default/chapter/29/13#Collecting-tickets"
|
|
|
|
if method == 'new':
|
|
errors_path = apath('%s/errors' % app, r=request)
|
|
|
|
delete_hashes = []
|
|
for item in request.vars:
|
|
if item[:7] == 'delete_':
|
|
delete_hashes.append(item[7:])
|
|
|
|
hash2error = dict()
|
|
|
|
for fn in listdir(errors_path, '^[a-fA-F0-9.\-]+$'):
|
|
fullpath = os.path.join(errors_path, fn)
|
|
if not os.path.isfile(fullpath):
|
|
continue
|
|
try:
|
|
fullpath_file = safe_open(fullpath, 'rb')
|
|
try:
|
|
error = pickle.load(fullpath_file)
|
|
finally:
|
|
fullpath_file.close()
|
|
except IOError:
|
|
continue
|
|
except EOFError:
|
|
continue
|
|
|
|
hash = hashlib.md5(to_bytes(error['traceback'])).hexdigest()
|
|
|
|
if hash in delete_hashes:
|
|
os.unlink(fullpath)
|
|
else:
|
|
try:
|
|
hash2error[hash]['count'] += 1
|
|
except KeyError:
|
|
error_lines = error['traceback'].split("\n")
|
|
last_line = error_lines[-2] if len(error_lines) > 1 else 'unknown'
|
|
error_causer = os.path.split(error['layer'])[1]
|
|
hash2error[hash] = dict(count=1, pickel=error,
|
|
causer=error_causer,
|
|
last_line=last_line,
|
|
hash=hash, ticket=fn)
|
|
|
|
decorated = [(x['count'], x) for x in hash2error.values()]
|
|
decorated.sort(key=operator.itemgetter(0), reverse=True)
|
|
|
|
return dict(errors=[x[1] for x in decorated], app=app, method=method, db_ready=db_ready)
|
|
|
|
elif method == 'dbnew':
|
|
errors_path = apath('%s/errors' % app, r=request)
|
|
tk_db, tk_table = get_ticket_storage(app)
|
|
|
|
delete_hashes = []
|
|
for item in request.vars:
|
|
if item[:7] == 'delete_':
|
|
delete_hashes.append(item[7:])
|
|
|
|
hash2error = dict()
|
|
|
|
for fn in tk_db(tk_table.id > 0).select():
|
|
try:
|
|
error = pickle.loads(fn.ticket_data)
|
|
hash = hashlib.md5(error['traceback']).hexdigest()
|
|
|
|
if hash in delete_hashes:
|
|
tk_db(tk_table.id == fn.id).delete()
|
|
tk_db.commit()
|
|
else:
|
|
try:
|
|
hash2error[hash]['count'] += 1
|
|
except KeyError:
|
|
error_lines = error['traceback'].split("\n")
|
|
last_line = error_lines[-2]
|
|
error_causer = os.path.split(error['layer'])[1]
|
|
hash2error[hash] = dict(count=1,
|
|
pickel=error, causer=error_causer,
|
|
last_line=last_line, hash=hash,
|
|
ticket=fn.ticket_id)
|
|
except AttributeError as e:
|
|
tk_db(tk_table.id == fn.id).delete()
|
|
tk_db.commit()
|
|
|
|
decorated = [(x['count'], x) for x in hash2error.values()]
|
|
decorated.sort(key=operator.itemgetter(0), reverse=True)
|
|
return dict(errors=[x[1] for x in decorated], app=app,
|
|
method=method, db_ready=db_ready)
|
|
|
|
elif method == 'dbold':
|
|
tk_db, tk_table = get_ticket_storage(app)
|
|
for item in request.vars:
|
|
if item[:7] == 'delete_':
|
|
tk_db(tk_table.ticket_id == item[7:]).delete()
|
|
tk_db.commit()
|
|
tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id,
|
|
tk_table.created_datetime,
|
|
orderby=~tk_table.created_datetime)
|
|
tickets = [row.ticket_id for row in tickets_]
|
|
times = dict([(row.ticket_id, row.created_datetime) for
|
|
row in tickets_])
|
|
return dict(app=app, tickets=tickets, method=method,
|
|
times=times, db_ready=db_ready)
|
|
|
|
else:
|
|
for item in request.vars:
|
|
# delete_all rows doesn't contain any ticket
|
|
# Remove anything else as requested
|
|
if item[:7] == 'delete_' and (not item == "delete_all}"):
|
|
os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request))
|
|
func = lambda p: os.stat(apath('%s/errors/%s' %
|
|
(app, p), r=request)).st_mtime
|
|
tickets = sorted(
|
|
listdir(apath('%s/errors/' % app, r=request), '^\w.*'),
|
|
key=func,
|
|
reverse=True)
|
|
|
|
return dict(app=app, tickets=tickets, method=method, db_ready=db_ready)
|
|
|
|
|
|
def get_ticket_storage(app):
|
|
private_folder = apath('%s/private' % app, r=request)
|
|
ticket_file = os.path.join(private_folder, 'ticket_storage.txt')
|
|
if os.path.exists(ticket_file):
|
|
db_string = safe_open(ticket_file).read()
|
|
db_string = db_string.strip().replace('\r', '').replace('\n', '')
|
|
elif is_gae:
|
|
# use Datastore as fallback if there is no ticket_file
|
|
db_string = "google:datastore"
|
|
else:
|
|
return False
|
|
tickets_table = 'web2py_ticket'
|
|
tablename = tickets_table + '_' + app
|
|
db_path = apath('%s/databases' % app, r=request)
|
|
ticketsdb = DAL(db_string, folder=db_path, auto_import=True)
|
|
if not ticketsdb.get(tablename):
|
|
table = ticketsdb.define_table(
|
|
tablename,
|
|
Field('ticket_id', length=100),
|
|
Field('ticket_data', 'text'),
|
|
Field('created_datetime', 'datetime'),
|
|
)
|
|
return ticketsdb, ticketsdb.get(tablename)
|
|
|
|
|
|
def make_link(path):
|
|
""" Create a link from a path """
|
|
tryFile = path.replace('\\', '/')
|
|
|
|
if os.path.isabs(tryFile) and os.path.isfile(tryFile):
|
|
(folder, filename) = os.path.split(tryFile)
|
|
(base, ext) = os.path.splitext(filename)
|
|
app = get_app()
|
|
|
|
editable = {'controllers': '.py', 'models': '.py', 'views': '.html'}
|
|
for key in editable.keys():
|
|
check_extension = folder.endswith("%s/%s" % (app, key))
|
|
if ext.lower() == editable[key] and check_extension:
|
|
return to_native(A('"' + tryFile + '"',
|
|
_href=URL(r=request,
|
|
f='edit/%s/%s/%s' % (app, key, filename))).xml())
|
|
return ''
|
|
|
|
|
|
def make_links(traceback):
|
|
""" Make links using the given traceback """
|
|
|
|
lwords = traceback.split('"')
|
|
|
|
# Making the short circuit compatible with <= python2.4
|
|
result = (len(lwords) != 0) and lwords[0] or ''
|
|
|
|
i = 1
|
|
|
|
while i < len(lwords):
|
|
link = make_link(lwords[i])
|
|
|
|
if link == '':
|
|
result += '"' + lwords[i]
|
|
else:
|
|
result += link
|
|
|
|
if i + 1 < len(lwords):
|
|
result += lwords[i + 1]
|
|
i = i + 1
|
|
|
|
i = i + 1
|
|
|
|
return result
|
|
|
|
|
|
class TRACEBACK(object):
|
|
""" Generate the traceback """
|
|
|
|
def __init__(self, text):
|
|
""" TRACEBACK constructor """
|
|
|
|
self.s = make_links(CODE(text).xml())
|
|
|
|
def xml(self):
|
|
""" Returns the xml """
|
|
|
|
return self.s
|
|
|
|
|
|
def ticket():
|
|
""" Ticket handler """
|
|
|
|
if len(request.args) != 2:
|
|
session.flash = T('invalid ticket')
|
|
redirect(URL('site'))
|
|
|
|
app = get_app()
|
|
myversion = request.env.web2py_version
|
|
ticket = request.args[1]
|
|
e = RestrictedError()
|
|
e.load(request, app, ticket)
|
|
|
|
return dict(app=app,
|
|
ticket=ticket,
|
|
output=e.output,
|
|
traceback=(e.traceback and TRACEBACK(e.traceback)),
|
|
snapshot=e.snapshot,
|
|
code=e.code,
|
|
layer=e.layer,
|
|
myversion=myversion)
|
|
|
|
|
|
def ticketdb():
|
|
""" Ticket handler """
|
|
|
|
if len(request.args) != 2:
|
|
session.flash = T('invalid ticket')
|
|
redirect(URL('site'))
|
|
|
|
app = get_app()
|
|
myversion = request.env.web2py_version
|
|
ticket = request.args[1]
|
|
e = RestrictedError()
|
|
request.tickets_db = get_ticket_storage(app)[0]
|
|
e.load(request, app, ticket)
|
|
response.view = 'default/ticket.html'
|
|
return dict(app=app,
|
|
ticket=ticket,
|
|
output=e.output,
|
|
traceback=(e.traceback and TRACEBACK(e.traceback)),
|
|
snapshot=e.snapshot,
|
|
code=e.code,
|
|
layer=e.layer,
|
|
myversion=myversion)
|
|
|
|
|
|
def error():
|
|
""" Generate a ticket (for testing) """
|
|
raise RuntimeError('admin ticket generator at your service')
|
|
|
|
|
|
def update_languages():
|
|
""" Update available languages """
|
|
|
|
app = get_app()
|
|
update_all_languages(apath(app, r=request))
|
|
session.flash = T('Language files (static strings) updated')
|
|
redirect(URL('design', args=app, anchor='languages'))
|
|
|
|
|
|
def user():
|
|
if MULTI_USER_MODE:
|
|
if not db(db.auth_user).count():
|
|
auth.settings.registration_requires_approval = False
|
|
return dict(form=auth())
|
|
else:
|
|
return dict(form=T("Disabled"))
|
|
|
|
|
|
def reload_routes():
|
|
""" Reload routes.py """
|
|
gluon.rewrite.load()
|
|
redirect(URL('site'))
|
|
|
|
|
|
def manage_students():
|
|
if not (MULTI_USER_MODE and is_manager()):
|
|
session.flash = T('Not Authorized')
|
|
redirect(URL('site'))
|
|
db.auth_user.registration_key.writable = True
|
|
grid = SQLFORM.grid(db.auth_user)
|
|
return locals()
|
|
|
|
|
|
def bulk_register():
|
|
if not (MULTI_USER_MODE and is_manager()):
|
|
session.flash = T('Not Authorized')
|
|
redirect(URL('site'))
|
|
form = SQLFORM.factory(Field('emails', 'text'))
|
|
if form.process().accepted:
|
|
emails = [x.strip() for x in form.vars.emails.split('\n') if x.strip()]
|
|
n = 0
|
|
for email in emails:
|
|
if not db.auth_user(email=email):
|
|
n += db.auth_user.insert(email=email) and 1 or 0
|
|
session.flash = T('%s students registered', n)
|
|
redirect(URL('site'))
|
|
return locals()
|
|
|
|
# Begin experimental stuff need fixes:
|
|
# 1) should run in its own process - cannot os.chdir
|
|
# 2) should not prompt user at console
|
|
# 3) should give option to force commit and not reuqire manual merge
|
|
|
|
|
|
def git_pull():
|
|
""" Git Pull handler """
|
|
app = get_app()
|
|
if not have_git:
|
|
session.flash = GIT_MISSING
|
|
redirect(URL('site'))
|
|
dialog = FORM.confirm(T('Pull'),
|
|
{T('Cancel'): URL('site')})
|
|
if dialog.accepted:
|
|
try:
|
|
repo = git.Repo(os.path.join(apath(r=request), app))
|
|
origin = repo.remotes.origin
|
|
origin.fetch()
|
|
origin.pull()
|
|
session.flash = T("Application updated via git pull")
|
|
redirect(URL('site'))
|
|
|
|
except git.CheckoutError:
|
|
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
|
|
redirect(URL('site'))
|
|
except git.UnmergedEntriesError:
|
|
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
|
|
redirect(URL('site'))
|
|
except git.GitCommandError:
|
|
session.flash = T(
|
|
"Pull failed, git exited abnormally. See logs for details.")
|
|
redirect(URL('site'))
|
|
except AssertionError:
|
|
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
|
|
redirect(URL('site'))
|
|
elif 'cancel' in request.vars:
|
|
redirect(URL('site'))
|
|
return dict(app=app, dialog=dialog)
|
|
|
|
|
|
def git_push():
|
|
""" Git Push handler """
|
|
app = get_app()
|
|
if not have_git:
|
|
session.flash = GIT_MISSING
|
|
redirect(URL('site'))
|
|
form = SQLFORM.factory(Field('changelog', requires=IS_NOT_EMPTY()))
|
|
form.element('input[type=submit]')['_value'] = T('Push')
|
|
form.add_button(T('Cancel'), URL('site'))
|
|
form.process()
|
|
if form.accepted:
|
|
try:
|
|
repo = git.Repo(os.path.join(apath(r=request), app))
|
|
index = repo.index
|
|
index.add([apath(r=request) + app + '/*'])
|
|
new_commit = index.commit(form.vars.changelog)
|
|
origin = repo.remotes.origin
|
|
origin.push()
|
|
session.flash = T(
|
|
"Git repo updated with latest application changes.")
|
|
redirect(URL('site'))
|
|
except git.UnmergedEntriesError:
|
|
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
|
|
redirect(URL('site'))
|
|
return dict(app=app, form=form)
|
|
|
|
|
|
def plugins():
|
|
app = request.args(0)
|
|
from gluon.serializers import loads_json
|
|
if not session.plugins:
|
|
try:
|
|
rawlist = urlopen("http://www.web2pyslices.com/" +
|
|
"public/api.json/action/list/content/Package?package" +
|
|
"_type=plugin&search_index=false").read()
|
|
session.plugins = loads_json(rawlist)
|
|
except:
|
|
response.flash = T('Unable to download the list of plugins')
|
|
session.plugins = []
|
|
return dict(plugins=session.plugins["results"], app=request.args(0))
|
|
|
|
|
|
def install_plugin():
|
|
app = request.args(0)
|
|
source = request.vars.source
|
|
plugin = request.vars.plugin
|
|
if not (source and app):
|
|
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()
|
|
result = None
|
|
if form.process().accepted:
|
|
# get w2p plugin
|
|
if "web2py.plugin." in source:
|
|
filename = "web2py.plugin.%s.w2p" % \
|
|
source.split("web2py.plugin.")[-1].split(".w2p")[0]
|
|
else:
|
|
filename = "web2py.plugin.%s.w2p" % cleanpath(plugin)
|
|
if plugin_install(app, urlopen(source),
|
|
request, filename):
|
|
session.flash = T('New plugin installed: %s', filename)
|
|
else:
|
|
session.flash = \
|
|
T('unable to install plugin "%s"', filename)
|
|
redirect(URL(f="plugins", args=[app, ]))
|
|
return dict(form=form, app=app, plugin=plugin, source=source)
|