diff --git a/CHANGELOG b/CHANGELOG index 8a8548d1..49a97ad1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,25 +1,35 @@ ## 2.1.0 -- if apps are accidentally deleted there is copy left in deposit folder -- db.table.field.epoch() counts seconds from epoch -- fixed many aith.wiki problems -- better welcome layout, thanks Paolo -- db.define_table(...,redefine=True) -- DAL, Row, and Rows object can now be pickled/unpickled, thanks to zombie DAL. -- admin uses codemirror -- support for auth.wiki(render='html') -- upgraded jQuery 1.8 -- upgraded Bootstrap 2.1 -- fixed problem with dropbox_account.py -- cache.with_prefix(cache.ram,'prefix') -- mail.send(...,sender='Mr X <%(sender)s>') -- allow entropy computation in IS_STRONG and web2py.js, thanks Jonathan and Niphlod -- renamed luon/contrib/comet_messaging.py -> gluon/contrib/websocket_messaging.py -- DAL support for SQL CASE, example: db().select(...query.case('true','false)) +- overall faster web2py +- when apps are deleted, a w2p copy left in deposit folder +- change in cron (it is now disabled by default). removed -N option and introduced -Y. - faster web2py_uuid() and request initialization logic, thanks Michele - static asset management, thanks Niphlod - improved mobile admin - request.requires_https and Auth(secure=True), thanks Yarin and Niphlod +- better custom_import (works per app and is faster), thanks Michele +- redis_sesssion.py, thanks Niphlod +- allow entropy computation in IS_STRONG and web2py.js, thanks Jonathan and Niphlod +- fixed many aith.wiki problems +- support for auth.wiki(render='html') +- better welcome layout, thanks Paolo +- db.define_table(...,redefine=True) +- DAL, Row, and Rows object can now be pickled/unpickled, thanks to zombie DAL. +- admin uses codemirror +- allow syntax auth = Auth(db).define_tables() +- better auth.wiki with preview, thanks Alan +- better auth.impersonate, thanks Alan +- upgraded jQuery 1.8 +- upgraded Bootstrap 2.1 +- fixed problem with dropbox_account.py +- many fixes to cache.ram, cache.disk, memcache and gae_memcache +- cache.with_prefix(cache.ram,'prefix') +- db.table.field.epoch() counts seconds from epoch +- DAL support for SQL CASE, example: db().select(...query.case('true','false)) +- DAL(...,do_connect=False) allows faking connections +- DAL(...,auto_import=True) now retieves some fiel attributes +- mail can specify a sender: mail.send(...,sender='Mr X <%(sender)s>') +- renamed gluon/contrib/comet_messaging.py -> gluon/contrib/websocket_messaging.py ## 2.0.1-11 diff --git a/Makefile b/Makefile index 0a7acd84..28c222ed 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ update: wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py echo "remember that pymysql was tweaked" src: - echo 'Version 2.1.0 ('`date +%Y-%m-%d\ %H:%M:%S`') dev' > VERSION + echo 'Version 2.1.1 ('`date +%Y-%m-%d\ %H:%M:%S`') dev' > VERSION ### rm -f all junk files make clean ### clean up baisc apps @@ -130,5 +130,5 @@ pip: # after upload clean Web2py sources with rm -R ./dist # http://guide.python-distribute.org/creation.html python setup.py sdist - python setup.py register - python setup.py sdist upload + sudo python setup.py register + sudo python setup.py sdist upload diff --git a/VERSION b/VERSION index 968fa96d..827cea5a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.1.0 (2012-10-14 16:40:34) dev +Version 2.1.1 (2012-10-20 15:27:09) dev diff --git a/__init__.py b/__init__.py index 584ba879..e69de29b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/anyserver.py b/anyserver.py index 53985442..473cceaa 100644 --- a/anyserver.py +++ b/anyserver.py @@ -9,47 +9,54 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) This file is based, although a rewrite, on MIT-licensed code from the Bottle web framework. """ -import os, sys, optparse, urllib +import os +import sys +import optparse +import 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] +sys.path = [path] + [p for p in sys.path if not p == path] import gluon.main from gluon.fileutils import read_file, write_file + class Servers: @staticmethod def cgi(app, address=None, **options): from wsgiref.handlers import CGIHandler - CGIHandler().run(app) # Just ignore host and port here + CGIHandler().run(app) # Just ignore host and port here @staticmethod - def flup(app,address, **options): + def flup(app, address, **options): import flup.server.fcgi flup.server.fcgi.WSGIServer(app, bindAddress=address).run() @staticmethod - def wsgiref(app,address,**options): # pragma: no cover + def wsgiref(app, address, **options): # pragma: no cover from wsgiref.simple_server import make_server, WSGIRequestHandler + class QuietHandler(WSGIRequestHandler): - def log_request(*args, **kw): pass + def log_request(*args, **kw): + pass options['handler_class'] = QuietHandler - srv = make_server(address[0],address[1],app,**options) + srv = make_server(address[0], address[1], app, **options) srv.serve_forever() @staticmethod - def cherrypy(app,address, **options): + def cherrypy(app, address, **options): from cherrypy import wsgiserver server = wsgiserver.CherryPyWSGIServer(address, app) server.start() @staticmethod - def rocket(app,address, **options): + def rocket(app, address, **options): from gluon.rocket import CherryPyWSGIServer server = CherryPyWSGIServer(address, app) server.start() @staticmethod - def rocket_with_repoze_profiler(app,address, **options): + def rocket_with_repoze_profiler(app, address, **options): from gluon.rocket import CherryPyWSGIServer from repoze.profile.profiler import AccumulatingProfileMiddleware from gluon.settings import global_settings @@ -59,44 +66,46 @@ class Servers: log_filename='wsgi.prof', discard_first_request=True, flush_at_shutdown=True, - path = '/__profile__' - ) + path='/__profile__' + ) server = CherryPyWSGIServer(address, wrapped) server.start() @staticmethod - def paste(app,address,**options): + def paste(app, address, **options): from paste import httpserver from paste.translogger import TransLogger httpserver.serve(app, host=address[0], port=address[1], **options) @staticmethod - def fapws(app,address, **options): + def fapws(app, address, **options): import fapws._evwsgi as evwsgi from fapws import base - evwsgi.start(address[0],str(address[1])) + evwsgi.start(address[0], str(address[1])) evwsgi.set_base_module(base) + def app(environ, start_response): environ['wsgi.multiprocess'] = False return app(environ, start_response) - evwsgi.wsgi_cb(('',app)) + evwsgi.wsgi_cb(('', app)) evwsgi.run() - @staticmethod - def gevent(app,address, **options): - from gevent import monkey; monkey.patch_all() + def gevent(app, address, **options): + from gevent import monkey + monkey.patch_all() from gevent import pywsgi from gevent.pool import Pool - pywsgi.WSGIServer(address, app, spawn = 'workers' in options and Pool(int(options.workers)) or 'default').serve_forever() + pywsgi.WSGIServer(address, app, spawn='workers' in options and Pool( + int(options.workers)) or 'default').serve_forever() @staticmethod - def bjoern(app,address, **options): + def bjoern(app, address, **options): import bjoern bjoern.run(app, *address) @staticmethod - def tornado(app,address, **options): + def tornado(app, address, **options): import tornado.wsgi import tornado.httpserver import tornado.ioloop @@ -106,7 +115,7 @@ class Servers: tornado.ioloop.IOLoop.instance().start() @staticmethod - def twisted(app,address, **options): + def twisted(app, address, **options): from twisted.web import server, wsgi from twisted.python.threadpool import ThreadPool from twisted.internet import reactor @@ -118,42 +127,44 @@ class Servers: reactor.run() @staticmethod - def diesel(app,address, **options): + def diesel(app, address, **options): from diesel.protocols.wsgi import WSGIApplication app = WSGIApplication(app, port=address[1]) app.run() @staticmethod - def gunicorn(app,address, **options): + def gunicorn(app, address, **options): from gunicorn.app.base import Application config = {'bind': "%s:%d" % address} config.update(options) sys.argv = ['anyserver.py'] + class GunicornApplication(Application): def init(self, parser, opts, args): return config + def load(self): return app g = GunicornApplication() g.run() @staticmethod - def eventlet(app,address, **options): + def eventlet(app, address, **options): from eventlet import wsgi, listen wsgi.server(listen(address), app) @staticmethod - def mongrel2(app,address,**options): + 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) + mongrel2_handler(app, conn, debug=False) -def run(servername,ip,port,softcron=True,logging=False,profiler=None): +def run(servername, ip, port, softcron=True, logging=False, profiler=None): if logging: application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, logfilename='httpserver.log', @@ -163,9 +174,10 @@ def run(servername,ip,port,softcron=True,logging=False,profiler=None): if softcron: from gluon.settings import global_settings global_settings.web2py_crontype = 'soft' - getattr(Servers,servername)(application,(ip,int(port))) + getattr(Servers, servername)(application, (ip, int(port))) -def mongrel2_handler(application,conn,debug=False): + +def mongrel2_handler(application, conn, debug=False): """ Based on : https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py @@ -190,20 +202,23 @@ def mongrel2_handler(application,conn,debug=False): # and responses. Unless I have missed something. while True: - if debug: print "WAITING FOR REQUEST" + if debug: + print "WAITING FOR REQUEST" # receive a request req = conn.recv() - if debug: print "REQUEST BODY: %r\n" % req.body + 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 + 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['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] @@ -211,17 +226,19 @@ def mongrel2_handler(application,conn,debug=False): else: environ['SERVER_NAME'] = environ['Host'] environ['SERVER_PORT'] = '' - environ['SCRIPT_NAME'] = '' # empty for now + 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 + if 'Content-Length' in environ: + 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 + if debug: + print "ENVIRON: %r\n" % environ # SimpleHandler needs file-like stream objects for # requests, errors and responses @@ -230,7 +247,8 @@ def mongrel2_handler(application,conn,debug=False): respIO = StringIO.StringIO() # execute the application - handler = SimpleHandler(reqIO, respIO, errIO, environ, multithread = False, multiprocess = False) + handler = SimpleHandler(reqIO, respIO, errIO, environ, + multithread=False, multiprocess=False) handler.run(application) # Get the response and filter out the response (=data) itself, @@ -254,11 +272,15 @@ def mongrel2_handler(application,conn,debug=False): errors = errIO.getvalue() # return the response - if debug: print "RESPONSE: %r\n" % response + if debug: + print "RESPONSE: %r\n" % response if errors: - if debug: print "ERRORS: %r" % 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) + 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" @@ -278,7 +300,7 @@ def main(): default=False, dest='profiler', help='profiler filename') - servers = ', '.join(x for x in dir(Servers) if not x[0]=='_') + servers = ', '.join(x for x in dir(Servers) if not x[0] == '_') parser.add_option('-s', '--server', default='rocket', @@ -300,13 +322,10 @@ def main(): 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) + 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__': +if __name__ == '__main__': main() - - - - - diff --git a/appengine_config.py b/appengine_config.py index 4bb11668..a36d1db6 100644 --- a/appengine_config.py +++ b/appengine_config.py @@ -2,8 +2,3 @@ def webapp_add_wsgi_middleware(app): from google.appengine.ext.appstats import recording app = recording.appstats_wsgi_middleware(app) return app - - - - - diff --git a/applications/__init__.py b/applications/__init__.py index e69de29b..8b137891 100644 --- a/applications/__init__.py +++ b/applications/__init__.py @@ -0,0 +1 @@ + diff --git a/applications/admin/__init__.py b/applications/admin/__init__.py index e69de29b..8b137891 100644 --- a/applications/admin/__init__.py +++ b/applications/admin/__init__.py @@ -0,0 +1 @@ + diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py index c25fed29..f62af94c 100644 --- a/applications/admin/controllers/appadmin.py +++ b/applications/admin/controllers/appadmin.py @@ -23,7 +23,7 @@ remote_addr = request.env.remote_addr try: hosts = (http_host, socket.gethostname(), socket.gethostbyname(http_host), - '::1','127.0.0.1','::ffff:127.0.0.1') + '::1', '127.0.0.1', '::ffff:127.0.0.1') except: hosts = (http_host, ) @@ -32,10 +32,10 @@ if request.env.http_x_forwarded_for or request.is_https: elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): raise HTTP(200, T('appadmin is disabled because insecure channel')) -if (request.application=='admin' and not session.authorized) or \ - (request.application!='admin' and not gluon.fileutils.check_credentials(request)): +if (request.application == 'admin' and not session.authorized) or \ + (request.application != 'admin' and not gluon.fileutils.check_credentials(request)): redirect(URL('admin', 'default', 'index', - vars=dict(send=URL(args=request.args,vars=request.vars)))) + vars=dict(send=URL(args=request.args, vars=request.vars)))) ignore_rw = True response.view = 'appadmin.html' @@ -95,24 +95,23 @@ def get_query(request): return None -def query_by_table_type(tablename,db,request=request): - keyed = hasattr(db[tablename],'_primarykey') +def query_by_table_type(tablename, db, request=request): + keyed = hasattr(db[tablename], '_primarykey') if keyed: firstkey = db[tablename][db[tablename]._primarykey[0]] cond = '>0' if firstkey.type in ['string', 'text']: cond = '!=""' - qry = '%s.%s.%s%s' % (request.args[0], request.args[1], firstkey.name, cond) + qry = '%s.%s.%s%s' % ( + request.args[0], request.args[1], firstkey.name, cond) else: qry = '%s.%s.id>0' % tuple(request.args[:2]) return qry - # ########################################################## # ## list all databases and tables # ########################################################### - def index(): return dict(databases=databases) @@ -127,7 +126,7 @@ def insert(): form = SQLFORM(db[table], ignore_rw=ignore_rw) if form.accepts(request.vars, session): response.flash = T('new record inserted') - return dict(form=form,table=db[table]) + return dict(form=form, table=db[table]) # ########################################################## @@ -138,7 +137,8 @@ def insert(): def download(): import os db = get_database(request) - return response.download(request,db) + return response.download(request, db) + def csv(): import gluon.contenttype @@ -149,26 +149,27 @@ def csv(): if not query: return None response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ - % tuple(request.vars.query.split('.')[:2]) - return str(db(query,ignore_common_filters=True).select()) + % tuple(request.vars.query.split('.')[:2]) + return str(db(query, ignore_common_filters=True).select()) def import_csv(table, file): table.import_from_csv_file(file) + def select(): import re db = get_database(request) dbname = request.args[0] regex = re.compile('(?P