Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -1,10 +1,12 @@ clean: rm -f httpserver.log rm -f parameters*.py rm -f -r applications/*/compiled find ./ -name '*~' -exec rm -f {} \; + find ./ -name '*.orig' -exec rm -f {} \; + find ./ -name '*.rej' -exec rm -f {} \; find ./ -name '#*' -exec rm -f {} \; find ./ -name 'Thumbs.db' -exec rm -f {} \; find ./gluon/ -name '.*' -exec rm -f {} \; find ./gluon/ -name '*class' -exec rm -f {} \; find ./applications/admin/ -name '.*' -exec rm -f {} \; @@ -20,12 +22,15 @@ rm -f -r applications/examples/static/epydoc/ epydoc --config epydoc.conf cp applications/examples/static/title.png applications/examples/static/epydoc tests: cd gluon/tests; ./test.sh 1>tests.log 2>&1 +update: + wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py + wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py src: - echo 'Version 1.98.2 ('`date +%Y-%m-%d\ %H:%M:%S`')' > VERSION + echo 'Version 1.99.2 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION ### rm -f all junk files make clean ### clean up baisc apps rm -f routes.py rm -f applications/*/sessions/* @@ -48,11 +53,11 @@ cp LICENSE applications/admin/ cp LICENSE applications/examples/ ### build web2py_src.zip echo '' > NEWINSTALL mv web2py_src.zip web2py_src_old.zip | echo 'no old' - cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/splashlogo.gif web2py/*.py web2py/ABOUT web2py/LICENSE web2py/README web2py/NEWINSTALL web2py/VERSION web2py/Makefile web2py/epydoc.css web2py/epydoc.conf web2py/app.example.yaml web2py/logging.example.conf web2py_exe.conf web2py/queue.example.yaml MANIFEST.in mkweb2pyenv startweb2py web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py + cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/splashlogo.gif web2py/*.py web2py/ABOUT web2py/LICENSE web2py/README web2py/NEWINSTALL web2py/VERSION web2py/Makefile web2py/epydoc.css web2py/epydoc.conf web2py/app.example.yaml web2py/logging.example.conf web2py_exe.conf web2py/queue.example.yaml MANIFEST.in w2p_apps w2p_clone w2p_run startweb2py web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py mdp: make epydoc make src make app @@ -102,13 +107,17 @@ cp -r applications/welcome ../web2py_win/web2py/applications cp -r applications/examples ../web2py_win/web2py/applications cp applications/__init__.py ../web2py_win/web2py/applications cd ../web2py_win; zip -r web2py_win.zip web2py mv ../web2py_win/web2py_win.zip . +pip: + # create Web2py distribution for upload to Pypi + # after upload clean Web2py sources with rm -R ./dist + python setup.py sdist run: python2.5 web2py.py -a hello push: make src echo '' > NEWINSTALL hg push bzr push bzr+ssh://mdipierro@bazaar.launchpad.net/~mdipierro/web2py/devel --use-existing-dir Index: README ================================================================== --- README +++ README @@ -1121,11 +1121,11 @@ - better description of --shell, thanks Anthony - extra SQLTABLE columns, thanks Martin - fixed toolbar conflics, thanks Simon - GAE password shows with **** -# 1.98.1 +# 1.98.1-1.98.2 - fixed some problems with LOAD(ajax=False), thanks Anthony - jquery 1.6.2 - gevent.pywsgi adds ssl support, thanks Vasile - import/export of blobs are base64 encoded - max number of login attemts in admin, thanks Ross @@ -1140,5 +1140,39 @@ - new CAT tag for no tags - request.user_agent(), thanks Ross - fixed fawps support - SQLFORM(...,separator=': ') now customizable - many small bug fixes + +## 1.99.1 +- gluon/contrib/simplejsonrpc.py +- gluon/contrib/redis_cache.py +- support for A(name,callback=url,target='id',delete='tr') +- support for A(name,component=url,target='id',delete='tr') +- new pip installer, thanks Chris Steel +- isapiwsgihandler.py +- dal expression.coalesce(*options) +- gluon/contrib/simplejsonrpc.py, thanks Mariano +- expire_sessions.py respects expiration time, thanks iceberg +- addressed this issue: http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/ +- x509 support (thanks Michele) +- form.process() and for.validate() +- rocket upgrade (1.2.4) +- jQuery upgrade (1.6.3) +- new syntax rows[i]('tablename.fieldname') +- new query syntax field.contains(list,all=True or False) +- new SQLFORM.grid and SQLFORM.smartgrid (should replace crud.search and crud.select) +- support for natural language queries (english only) in SQLFORM.grid +- support for computed columns and additional links in SQLFORM.grid +- new style virtual fields (experimental): db.table.field=Field.Lazy(...) +- request.utcnow +- cleaner/simpler welcome/models/db.py and welcome layout.html +- response.include_meta() and response.include_files(), thanks Denes +- dal auto-reconnect on time-out connections +- COL and COLGROUP helpers +- addresed OWASP #10, thanks Anthony and Eric +- auth.settings.login_after_registration=True +- detection of mobile devices and @mobilize helper (view.mobile.html), thanks Angelo +- experimental gluon/scheduler.py +- scripts/make_min_web2py.py +- crud.search has more options, thanks Denes +- many bug fixes (thanks Jonathan, Michele, Fran and others) Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -Version 1.98.2 (2011-08-04 00:47:09) +Version 1.99.2 (2011-09-26 06:55:33) stable Index: __init__.py ================================================================== --- __init__.py +++ __init__.py @@ -1,1 +1,3 @@ + + Index: anyserver.py ================================================================== --- anyserver.py +++ anyserver.py @@ -7,11 +7,11 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) This file is based, althought a rewrite, on MIT code from the Bottle web framework. """ -import os, sys, optparse +import os, sys, optparse, urllib path = os.path.dirname(os.path.abspath(__file__)) os.chdir(path) sys.path = [path]+[p for p in sys.path if not p==path] import gluon.main from gluon.fileutils import read_file, write_file @@ -130,11 +130,21 @@ @staticmethod def eventlet(app,address, **options): from eventlet import wsgi, listen wsgi.server(listen(address), app) - + + @staticmethod + def mongrel2(app,address,**options): + import uuid + sys.path.append(os.path.abspath(os.path.dirname(__file__))) + from mongrel2 import handler + conn = handler.Connection(str(uuid.uuid4()), + "tcp://127.0.0.1:9997", + "tcp://127.0.0.1:9996") + mongrel2_handler(app,conn,debug=False) + def run(servername,ip,port,softcron=True,logging=False,profiler=None): if logging: application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, logfilename='httpserver.log', @@ -144,10 +154,105 @@ if softcron: from gluon.settings import global_settings global_settings.web2py_crontype = 'soft' getattr(Servers,servername)(application,(ip,int(port))) +def mongrel2_handler(application,conn,debug=False): + """ + Based on : + https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py + + WSGI handler based on the Python wsgiref SimpleHandler. + A WSGI application should return a iterable op StringTypes. + Any encoding must be handled by the WSGI application itself. + """ + from wsgiref.handlers import SimpleHandler + try: + import cStringIO as StringIO + except: + import StringIO + + # TODO - this wsgi handler executes the application and renders a page + # in memory completely before returning it as a response to the client. + # Thus, it does not "stream" the result back to the client. It should be + # possible though. The SimpleHandler accepts file-like stream objects. So, + # it should be just a matter of connecting 0MQ requests/response streams to + # the SimpleHandler requests and response streams. However, the Python API + # for Mongrel2 doesn't seem to support file-like stream objects for requests + # and responses. Unless I have missed something. + + while True: + if debug: print "WAITING FOR REQUEST" + + # receive a request + req = conn.recv() + if debug: print "REQUEST BODY: %r\n" % req.body + + if req.is_disconnect(): + if debug: print "DISCONNECT" + continue #effectively ignore the disconnect from the client + + # Set a couple of environment attributes a.k.a. header attributes + # that are a must according to PEP 333 + environ = req.headers + environ['SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1 + environ['REQUEST_METHOD'] = environ['METHOD'] + if ':' in environ['Host']: + environ['SERVER_NAME'] = environ['Host'].split(':')[0] + environ['SERVER_PORT'] = environ['Host'].split(':')[1] + else: + environ['SERVER_NAME'] = environ['Host'] + environ['SERVER_PORT'] = '' + environ['SCRIPT_NAME'] = '' # empty for now + environ['PATH_INFO'] = urllib.unquote(environ['PATH']) + if '?' in environ['URI']: + environ['QUERY_STRING'] = environ['URI'].split('?')[1] + else: + environ['QUERY_STRING'] = '' + if environ.has_key('Content-Length'): + environ['CONTENT_LENGTH'] = environ['Content-Length'] # necessary for POST to work with Django + environ['wsgi.input'] = req.body + + if debug: print "ENVIRON: %r\n" % environ + + # SimpleHandler needs file-like stream objects for + # requests, errors and reponses + reqIO = StringIO.StringIO(req.body) + errIO = StringIO.StringIO() + respIO = StringIO.StringIO() + + # execute the application + handler = SimpleHandler(reqIO, respIO, errIO, environ, multithread = False, multiprocess = False) + handler.run(application) + + # Get the response and filter out the response (=data) itself, + # the response headers, + # the response status code and the response status description + response = respIO.getvalue() + response = response.split("\r\n") + data = response[-1] + headers = dict([r.split(": ") for r in response[1:-2]]) + code = response[0][9:12] + status = response[0][13:] + + # strip BOM's from response data + # Especially the WSGI handler from Django seems to generate them (2 actually, huh?) + # a BOM isn't really necessary and cause HTML parsing errors in Chrome and Safari + # See also: http://www.xs4all.nl/~mechiel/projects/bomstrip/ + # Although I still find this a ugly hack, it does work. + data = data.replace('\xef\xbb\xbf', '') + + # Get the generated errors + errors = errIO.getvalue() + + # return the response + if debug: print "RESPONSE: %r\n" % response + if errors: + if debug: print "ERRORS: %r" % errors + data = "%s\r\n\r\n%s" % (data, errors) + conn.reply_http(req, data, code = code, status = status, headers = headers) + def main(): usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P" try: version = read_file('VERSION') except IOError: @@ -158,11 +263,11 @@ action='store_true', default=False, dest='logging', help='log into httpserver.log') parser.add_option('-P', - '--profiler', + '--profiler', default=False, dest='profiler', help='profiler filename') servers = ', '.join(x for x in dir(Servers) if not x[0]=='_') parser.add_option('-s', @@ -186,8 +291,10 @@ dest='workers', help='number of workers number') (options, args) = parser.parse_args() print 'starting %s on %s:%s...' % (options.server,options.ip,options.port) run(options.server,options.ip,options.port,logging=options.logging,profiler=options.profiler) + if __name__=='__main__': main() + Index: appengine_config.py ================================================================== --- appengine_config.py +++ appengine_config.py @@ -1,4 +1,5 @@ def webapp_add_wsgi_middleware(app): from google.appengine.ext.appstats import recording app = recording.appstats_wsgi_middleware(app) return app + ADDED applications/admin/controllers/debug.py Index: applications/admin/controllers/debug.py ================================================================== --- applications/admin/controllers/debug.py +++ applications/admin/controllers/debug.py @@ -0,0 +1,33 @@ +import sys +import cStringIO +import gluon.contrib.shell +import code, thread +from gluon.debug import communicate + + +if DEMO_MODE or MULTI_USER_MODE: + session.flash = T('disabled in demo mode') + redirect(URL('default','site')) + +FE=10**9 + +def index(): + app = request.args(0) or 'admin' + reset() + # read buffer + data = communicate() + return dict(app=app,data=data) + +def callback(): + app = request.args[0] + command = request.vars.statement + session['debug_commands:'+app].append(command) + output = communicate(command) + k = len(session['debug_commands:'+app]) - 1 + return '[%i] %s%s\n' % (k + 1, command, output) + +def reset(): + app = request.args(0) or 'admin' + session['debug_commands:'+app] = [] + return 'done' + Index: applications/admin/controllers/default.py ================================================================== --- applications/admin/controllers/default.py +++ applications/admin/controllers/default.py @@ -586,12 +586,12 @@ return 'plus' if item[0] == '-': return 'minus' if request.vars: - c = ''.join([item[2:] for (i, item) in enumerate(d) if item[0] \ - == ' ' or 'line%i' % i in 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 @@ -708,12 +708,12 @@ 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] + 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) @@ -1190,11 +1190,11 @@ """ 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)) + redirect(URL('design',args=app,anchor='languages')) def twitter(): session.forget() session._unlock(response) import gluon.tools @@ -1215,8 +1215,9 @@ return dict(form=auth()) else: return dict(form=T("Disabled")) def reload_routes(): - """ Reload routes.py """ - gluon.rewrite.load() - redirect(URL('site')) + """ Reload routes.py """ + import gluon.rewrite + gluon.rewrite.load() + redirect(URL('site')) Index: applications/admin/controllers/wizard.py ================================================================== --- applications/admin/controllers/wizard.py +++ applications/admin/controllers/wizard.py @@ -21,11 +21,12 @@ ('email_login',''), ('login_method','local'), ('login_config',''), ('plugins',[])], 'tables':['auth_user'], - 'table_auth_user':['username','first_name','last_name','email','password'], + 'table_auth_user':['username','first_name', + 'last_name','email','password'], 'pages':['index','error'], 'page_index':'# Welcome to my new app', 'page_error':'# Error: the document does not exist', } @@ -41,15 +42,17 @@ def index(): response.view='wizard/step.html' reset(session) apps=os.listdir(os.path.join(request.folder,'..')) - form=SQLFORM.factory(Field('name',requires=[IS_NOT_EMPTY(),IS_ALPHANUMERIC()])) + form=SQLFORM.factory(Field('name',requires=[IS_NOT_EMPTY(), + IS_ALPHANUMERIC()])) if form.accepts(request.vars): app = form.vars.name session.app['name'] = app - if MULTI_USER_MODE and db(db.app.name==app)(db.app.owner!=auth.user.id).count(): + if MULTI_USER_MODE and db(db.app.name==app)\ + (db.app.owner!=auth.user.id).count(): session.flash = 'App belongs already to other user' elif app in apps: meta = os.path.normpath(\ os.path.join(os.path.normpath(request.folder), '..',app,'wizard.metadata')) @@ -58,13 +61,13 @@ metafile = open(meta,'rb') try: session.app = pickle.load(metafile) finally: metafile.close() - session.flash = "The app exists, was created by wizard, continue to overwrite!" + session.flash = T("The app exists, was created by wizard, continue to overwrite!") except: - session.flash = "The app exists, was NOT created by wizard, continue to overwrite!" + session.flash = T("The app exists, was NOT created by wizard, continue to overwrite!") redirect(URL('step1')) return dict(step='Start',form=form) def step1(): @@ -118,24 +121,27 @@ def step2(): response.view='wizard/step.html' form=SQLFORM.factory(Field('table_names','list:string', default=session.app['tables'])) if form.accepts(request.vars): - table_names = [clean(t) for t in listify(form.vars.table_names) if t.strip()] - if [t for t in table_names if t.startswith('auth_') and not t=='auth_user']: - form.error.table_names = T('invalid table names (auth_* tables already defined)') + table_names = [clean(t) for t in listify(form.vars.table_names) \ + if t.strip()] + if [t for t in table_names if t.startswith('auth_') and \ + not t=='auth_user']: + form.error.table_names = \ + T('invalid table names (auth_* tables already defined)') else: session.app['tables']=table_names for table in session.app['tables']: if not 'table_'+table in session.app: session.app['table_'+table]=['name'] if not table=='auth_user': - for key in ['create','read','update','select','search']: - name = table+'_'+key - if not name in session.app['pages']: - session.app['pages'].append(name) - session.app['page_'+name]='## %s %s' % (key.capitalize(),table) + name = table+'_manage' + if not name in session.app['pages']: + session.app['pages'].append(name) + session.app['page_'+name] = \ + '## Manage %s\n{{=form}}' % (table) if session.app['tables']: redirect(URL('step3',args=0)) else: redirect(URL('step4')) return dict(step='2: Tables',form=form) @@ -164,11 +170,12 @@ else: if n=m: redirect(URL('step4')) page=session.app['pages'][n] markmin_url='http://web2py.com/examples/static/markmin.html' form=SQLFORM.factory(Field('content','text', default=session.app.get('page_'+page,[]), - comment=A('use markmin',_href=markmin_url,_target='_blank')), + comment=A('use markmin', + _href=markmin_url,_target='_blank')), formstyle='table2cols') if form.accepts(request.vars): session.app['page_'+page]=form.vars.content if n')}); jQuery('input.integer').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();}); jQuery('input.double,input.decimal').live('keyup', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();}); var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?"; jQuery("input[type='checkbox'].delete").live('click', function(){ if(this.checked) if(!confirm(confirm_message)) this.checked=false; }); @@ -53,14 +53,14 @@ }); function web2py_trap_form(action,target) { jQuery('#'+target+' form').each(function(i){ var form=jQuery(this); if(!form.hasClass('no_trap')) - form.submit(function(obj){ + form.submit(function(e){ jQuery('.flash').hide().html(''); web2py_ajax_page('post',action,form.serialize(),target); - return false; + e.preventDefault(); }); }); } function web2py_ajax_page(method,action,data,target) { jQuery.ajax({'type':method,'url':action,'data':data, Index: applications/admin/views/default/about.html ================================================================== --- applications/admin/views/default/about.html +++ applications/admin/views/default/about.html @@ -2,11 +2,11 @@ {{block sectionclass}}about{{end}}

{{=T("About application")}} "{{=app}}"

{{=T("About")}} {{=app}}

-

{{=button(URL('edit/%s/ABOUT' % (app)), T('edit'))}}

+

{{=button(URL('edit/%s/ABOUT' % (app)), T('Edit'))}}

{{=about}}

{{=T('License for')}} {{=app}}

-

{{=button(URL('edit/%s/LICENSE' % (app)), T('edit'))}}

+

{{=button(URL('edit/%s/LICENSE' % (app)), T('Edit'))}}

{{=license}}
Index: applications/admin/views/default/design.html ================================================================== --- applications/admin/views/default/design.html +++ applications/admin/views/default/design.html @@ -3,15 +3,15 @@ def all(items): return reduce(lambda a,b:a and b,items,True) def peekfile(path,file): return A(file.replace('\\\\','/'),_href=URL('peek', args=(app, path, file))) def editfile(path,file): - return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) def testfile(path,file): return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')), SPAN(T("Run tests in this file (to run all files, you may also use the button labelled 'test')"))), _class='icon test tooltip',_href=URL('test', args=(app, file))) def editlanguagefile(path,file): - return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) def file_upload_form(location): form=FORM(T("upload file:")," ", INPUT(_type="file",_name="file")," ",T("and rename it:")," ", INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), INPUT(_type="hidden",_name="location",_value=location), @@ -21,11 +21,11 @@ def file_create_form(location): form=FORM(T("create file with filename:")," ", INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)), - INPUT(_type="submit",_value=T("create")),_action=URL('create_file')) + INPUT(_type="submit",_value=T("Create")),_action=URL('create_file')) return form def upload_plugin_form(app): form=FORM(T("upload plugin file:")," ", INPUT(_type="file",_name="pluginfile"), INPUT(_type="submit",_value=T("upload"))) Index: applications/admin/views/default/peek.html ================================================================== --- applications/admin/views/default/peek.html +++ applications/admin/views/default/peek.html @@ -4,13 +4,13 @@

{{=T("Peeking at file")}} "{{=filename}}"

{{=button(URL('design',args=request.args[0]), T('back'))}} -{{=button(URL('edit',args=request.args), T('edit'))}} +{{=button(URL('edit',args=request.args), T('Edit'))}}

{{ if filename[-3:]=='.py': language='python' else: language='html' }} {{=CODE(data,language=language,link='/examples/global/vars/')}} Index: applications/admin/views/default/plugin.html ================================================================== --- applications/admin/views/default/plugin.html +++ applications/admin/views/default/plugin.html @@ -4,15 +4,15 @@ def all(items): return reduce(lambda a,b:a and b,items,True) def peekfile(path,file): return A(file.replace('\\\\','/'),_href=URL('peek', args=(app, path, file))) def editfile(path,file): - return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit', args=(app, path, file))) def testfile(path,file): return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')), SPAN(T("Run tests in this file"))), _class='icon test tooltip',_href=URL('test', args=(app, file))) def editlanguagefile(path,file): - return A(SPAN(T('edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) + return A(SPAN(T('Edit')),_class='button editbutton',_href=URL('edit_language', args=(app, path, file))) def file_upload_form(location): form=FORM(T("upload file:")," ", INPUT(_type="file",_name="file")," ",T("and rename it:")," ", INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY), INPUT(_type="hidden",_name="location",_value=location), Index: applications/admin/views/default/site.html ================================================================== --- applications/admin/views/default/site.html +++ applications/admin/views/default/site.html @@ -15,29 +15,29 @@ {{else:}}

{{=A(a,_href=URL(a,'default','index'))}}

{{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}}

{{if not os.path.exists('applications/%s/compiled' % a):}} - {{=sp_button(URL('design',args=a), T("edit"))}} + {{=sp_button(URL('design',args=a), T("Edit"))}} {{else:}} {{=button(URL(a,'appadmin','index'), T("appadmin"))}} {{pass}} - {{=button(URL('about',args=a), T("about"))}} + {{=button(URL('about',args=a), T("About"))}} {{pass}} - {{=button(URL('errors',args=a), T("errors"))}} - {{=button(URL('cleanup',args=a), T("clean"))}} - {{=button(URL('pack',args=a), T("pack all"))}} + {{=button(URL('errors',args=a), T("Errors"))}} + {{=button(URL('cleanup',args=a), T("Clean"))}} + {{=button(URL('pack',args=a), T("Pack all"))}} {{if not os.path.exists('applications/%s/compiled' % a):}} - {{=button(URL('compile_app',args=a), T("compile"))}} + {{=button(URL('compile_app',args=a), T("Compile"))}} {{else:}} - {{=button(URL('pack',args=(a, 'compiled')), T("pack compiled"))}} + {{=button(URL('pack',args=(a, 'compiled')), T("Pack compiled"))}} {{if glob.glob('applications/%s/controllers/*.py' % a):}} - {{=button(URL('remove_compiled_app',args=a), T("remove compiled"))}} + {{=button(URL('remove_compiled_app',args=a), T("Remove compiled"))}} {{pass}} {{pass}} {{if a!=request.application:}} - {{=button(URL('uninstall',args=a), T("uninstall"))}} + {{=button(URL('uninstall',args=a), T("Uninstall"))}} {{pass}}

{{pass}} @@ -49,52 +49,53 @@
{{if MULTI_USER_MODE:}} {{=auth.navbar()}} {{else:}} - {{=sp_button(URL('change_password'), T('change admin password'))}} + {{=sp_button(URL('change_password'), T('Change admin password'))}} {{pass}}
{{if is_manager():}}
-

{{=myversion}}

+

{{="Version %s.%s.%s (%s) %s" % myversion}}

{{if session.check_version:}}

{{=T('Checking for upgrades...')}} {{session.check_version=False}} {{else:}}

- {{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('check for upgrades'))}} + {{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}} {{pass}}

Running on {{=request.env.server_software}}
+

{{=button(URL('default','reload_routes'), T('Reload routes'))}}

{{pass}}

{{=T("New application wizard")}}

-

{{=button(URL('wizard','index'), T('start wizard'))}} +

{{=button(URL('wizard','index'), T('Start wizard'))}} {{=T("(requires internet access)")}}

{{=T("New simple application")}}

{{=LABEL(T("Application name:"), _for="scaffold_filename")}} - +
-

{{=T("Upload & install packed application")}}

+

{{=T("Upload and install packed application")}}

@@ -113,37 +114,37 @@ OR
- {{=LABEL(T("Use an url:"), _for='upload_url')}} + {{=LABEL(T("Get from URL:"), _for='upload_url')}}
- {{=LABEL(T("overwrite installed app"), _for='upload_overwrite')}} + {{=LABEL(T("Overwrite installed app"), _for='upload_overwrite')}}
- +

{{=T("Deploy on Google App Engine")}}

-

{{=button(URL('gae','deploy'), T('deploy'))}}

+

{{=button(URL('gae','deploy'), T('Deploy'))}}


{{if TWITTER_HASH:}}

{{=T("%s Recent Tweets"%TWITTER_HASH)}}

{{=T('loading...')}}
Index: applications/admin/views/layout.html ================================================================== --- applications/admin/views/layout.html +++ applications/admin/views/layout.html @@ -26,11 +26,11 @@
{{=response.flash or ''}}
{{include}}