# -*- 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'}}

%s

{{=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)