# coding: utf8 EXPERIMENTAL_STUFF = True if EXPERIMENTAL_STUFF: is_mobile = request.user_agent().is_mobile if is_mobile: response.view = response.view.replace('default/','default.mobile/') response.menu = [] import re from gluon.admin import * from gluon.fileutils import abspath, read_file, write_file from gluon.utils import web2py_uuid from glob import glob import shutil import platform try: from git import * have_git = True except ImportError: have_git = False GIT_MISSING = 'requires gitpython module, but not installed or incompatible version' 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_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']: session.flash = T('disabled in demo 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 and ('w' in b or 'a' in b): class tmp: def write(self,data): pass return tmp() return open(a,b) 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 (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 your 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 request.vars.password: if verify_password(request.vars.password): 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_number = 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 version %s.%s.%s' % version_number[:3]) else: return sp_button(URL('upgrade_web2py'), T('upgrade now')) \ + XML(' %s.%s.%s' \ % version_number[:3]) 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')) 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) 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 = Repo.clone_from(form_update.vars.url,target) session.flash = T('new application "%s" imported', form_update.vars.name) except GitCommandError, 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 = urllib.urlopen(form_update.vars.url) if f.code == 404: raise Exception("404 file not found") except Exception, e: session.flash = \ DIV(T('Unable to download app because:'),PRE(str(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))) elif f and form_update.vars.overwrite: msg = 'unable to install application "%(appname)s"' session.flash = T(msg, dict(appname=form_update.vars.name)) 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 = [f for f in os.listdir(apath(r=request)) if regex.match(f)] else: apps = [f.name for f in db(db.app.owner==auth.user_id).select()] if FILTER_APPS: apps = [f for f in apps if f in FILTER_APPS] apps = sorted(apps,lambda a,b:cmp(a.upper(),b.upper())) return dict(app=None, apps=apps, myversion=myversion, 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, 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 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')}) 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) if not c: session.flash = T('application compiled') 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] if 'nodelete' in request.vars: redirect(URL(sender, anchor=request.vars.id)) elif 'delete' in request.vars: 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(filename=filename, sender=sender) 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(): 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(time.ctime()) 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) filename = '/'.join(request.args) 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 = 'js' 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, e: 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} 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(str(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, e: response.flash = DIV(T('failed to reload module because:'),PRE(str(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]) 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 viewlist: vf = os.path.split(v)[-1] vargs = "/".join([viewpath.replace(os.sep,"/"),vf]) editviewlinks.append(A(vf.split(".")[0],\ _href=URL('edit',args=[vargs]))) if len(request.args) > 2 and request.args[1] == 'controllers': controller = (request.args[2])[:-3] functions = regex_expose.findall(data) 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: editarea_preferences = {} editarea_preferences['FONT_SIZE'] = '10' editarea_preferences['FULL_SCREEN'] = 'false' editarea_preferences['ALLOW_TOGGLE'] = 'true' editarea_preferences['REPLACE_TAB_BY_SPACES'] = '4' editarea_preferences['DISPLAY'] = 'onload' for key in editarea_preferences: if globals().has_key(key): editarea_preferences[key]=globals()[key] return dict(app=request.args[0], filename=filename, filetype=filetype, data=data, edit_controller=edit_controller, file_hash=file_hash, saved_on=saved_on, controller=controller, functions=functions, view_link=view_link, editarea_preferences=editarea_preferences, editviewlinks=editviewlinks) 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 """ if item[0] == ' ': return 'normal' if item[0] == '+': return 'plus' if item[0] == '-': return 'minus' 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) 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(),lambda x,y: cmp(unicode(x,'utf-8').lower(), unicode(y,'utf-8').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(key) <= 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) rows.append(P(prefix, k, BR(), elem, TAG.BUTTON(T('delete'), _onclick='return delkey("%s")' % name), _id=name)) rows.append(INPUT(_type='submit', _value=T('update'))) 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())) rows = [] row=[T("Singular Form")] row.extend([T("Plural Form #%s", n+1) for n in xnplurals]) table=TABLE(THEAD(TR(row))) 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))) row = [B(key)] row.extend([INPUT(_type='text', _name=name+'_'+str(n), value=forms[n], _size=20) for n in xnplurals]) row.append(TD(TAG.BUTTON(T('delete'), _onclick='return delkey("%s")' % name))) rows.append(TR(row, _id=name)) if rows: table.append(TBODY(rows)) rows=[table, INPUT(_type='submit', _value=T('update'))] form = FORM(*rows) 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!=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 create application "%s"', request.vars.filename) redirect(URL(r=request)) elif isinstance(request.vars.pluginfile,str): session.flash = T('plugin not specified') redirect(URL(r=request)) # 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 = regex_expose.findall(data) functions[c] = items # 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), '[^\.#].*') statics = [x.replace('\\','/') for x in statics] statics.sort() # Get all languages languages=dict([(lang,info) for lang,info in read_possible_languages( apath(app, r=request)).iteritems() if info[2]!=0]) # info[2] is langfile_mtime: # get only existed files #Get crontab cronfolder = apath('%s/cron' % app, r=request) if not os.path.exists(cronfolder): os.mkdir(cronfolder) crontab = apath('%s/cron/crontab' % app, r=request) 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 = regex_expose.findall(data) functions[c] = items # 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), '[^\.#].*') statics = [x.replace('\\','/') for x in statics] statics.sort() # Get all languages languages = sorted([lang+'.py' for lang, info in T.get_possible_languages_info().iteritems() 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: 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: utf8 -*- # 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: utf8\n' elif path[-13:] == '/controllers/': # Handle python controllers if not filename[-3:] == '.py': filename += '.py' if len(filename) == 3: raise SyntaxError text = '# coding: utf8\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: utf8 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) 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, e: if not isinstance(e,HTTP): session.flash = T('cannot create file') redirect(request.vars.sender+anchor) 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 = 'unkown') session.flash = T('cannot upload file "%(filename)s"', d) redirect(request.vars.sender) def errors(): """ Error handler """ import operator import os import pickle import hashlib app = get_app() 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 = open(fullpath, 'r') try: error = pickle.load(fullpath_file) finally: fullpath_file.close() except IOError: continue except EOFError: continue hash = hashlib.md5(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] 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) except AttributeError: tk_db(tk_table.id == fn.id).delete() tk_db.commit() 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) 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) 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) else: for item in request.vars: if item[:7] == 'delete_': 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 = open(ticket_file).read() db_string = db_string.strip().replace('\r','').replace('\n','') 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 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 twitter(): session.forget() session._unlock(response) import gluon.tools import gluon.contrib.simplejson as sj try: if TWITTER_HASH: page = urllib.urlopen("http://search.twitter.com/search.json?q=%%40%s" % TWITTER_HASH).read() data = sj.loads(page, encoding="utf-8")['results'] d = dict() for e in data: d[e["id"]] = e r = reversed(sorted(d)) return dict(tweets = [d[k] for k in r]) else: return 'disabled' except Exception, e: return DIV(T('Unable to download because:'),BR(),str(e)) 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 """ import gluon.rewrite 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 = 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 CheckoutError, message: logging.error(message) session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.") redirect(URL('site')) except UnmergedEntriesError: 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 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')) except GitCommandError, status: logging.error(str(status)) session.flash = T("Pull failed, git exited abnormally. See logs for details.") redirect(URL('site')) except Exception,e: logging.error("Unexpected error:", sys.exc_info()[0]) session.flash = T("Pull failed, git exited abnormally. See logs for details.") redirect(URL('site')) elif 'cancel' in request.vars: 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 = 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 UnmergedEntriesError: session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.") redirect(URL('site')) except Exception, e: logging.error("Unexpected error:", sys.exc_info()[0]) session.flash = T("Push failed, git exited abnormally. See logs for details.") redirect(URL('site')) return dict(app=app,form=form)