From 62681e2c9a52f1426a7d8931774c45abbd2ee8c5 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 27 Jul 2012 16:53:34 -0500 Subject: [PATCH 001/155] fixed Recapcha, thanks Osman Masood --- VERSION | 2 +- gluon/tools.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 3159a75d..e4e31c82 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-27 09:32:24) dev +Version 2.00.0 (2012-07-27 16:53:30) dev diff --git a/gluon/tools.py b/gluon/tools.py index 28e7129b..86d063f5 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -662,13 +662,24 @@ class Mail(object): class Recaptcha(DIV): + """ + Usage: + + form = FORM(Recaptcha(public_key='...',private_key='...')) + + or + + form = SQLFORM(...) + form.append(Recaptcha(public_key='...',private_key='...')) + """ + API_SSL_SERVER = 'https://www.google.com/recaptcha/api' API_SERVER = 'http://www.google.com/recaptcha/api' VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify' def __init__( self, - request, + request=None, public_key='', private_key='', use_ssl=False, @@ -677,6 +688,7 @@ class Recaptcha(DIV): label = 'Verify:', options = '' ): + self.request_vars = request and request.vars or current.request.vars self.remote_addr = request.env.remote_addr self.public_key = public_key self.private_key = private_key From 15a22384805cf4c8f4f401c63637a6e9843b2a3c Mon Sep 17 00:00:00 2001 From: mdipierro Date: Fri, 27 Jul 2012 17:25:30 -0500 Subject: [PATCH 002/155] better mongodb import --- VERSION | 2 +- gluon/dal.py | 29 ++++++++++------------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/VERSION b/VERSION index e4e31c82..43e5d27d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-27 16:53:30) dev +Version 2.00.0 (2012-07-27 17:25:26) dev diff --git a/gluon/dal.py b/gluon/dal.py index 50d52717..f1691811 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -4663,7 +4663,10 @@ class MongoDBAdapter(NoSQLAdapter): # therefor call __select() connection[table].find(query).count() Since this will probably reduce the return set? def expand(self, expression, field_type=None): - import pymongo.objectid + try: + from pymongo.objectid import ObjectId + except ImportError: + from bson.objectid import ObjectId #if isinstance(expression,Field): # if expression.type=='id': # return {_id}" @@ -4675,21 +4678,21 @@ class MongoDBAdapter(NoSQLAdapter): # if second arg is 0 convert to objectid if isinstance(expression.first,Field) and expression.first.type == 'id': expression.first.name = '_id' - if expression.second != 0 and not isinstance(expression.second,pymongo.objectid.ObjectId): + if expression.second != 0 and not isinstance(expression.second,ObjectId): if isinstance(expression.second,int): try: #Because the reference field is by default an integer and therefore this must be an integer to be able to work with other databases - expression.second = pymongo.objectid.ObjectId(("%X" % expression.second)) + expression.second = ObjectId(("%X" % expression.second)) except: raise SyntaxError, 'The second argument must by an integer that can represent an objectid.' else: try: #But a direct id is also possible - expression.second = pymongo.objectid.ObjectId(expression.second) + expression.second = ObjectId(expression.second) except: - raise SyntaxError, 'second argument must be of type bson.objectid.ObjectId or an objectid representable integer' + raise SyntaxError, 'second argument must be of type ObjectId or an objectid representable integer' elif expression.second == 0: - expression.second = pymongo.objectid.ObjectId('000000000000000000000000') + expression.second = ObjectId('000000000000000000000000') return expression.op(expression.first, expression.second) if isinstance(expression, Field): if expression.type=='id': @@ -4794,7 +4797,7 @@ class MongoDBAdapter(NoSQLAdapter): column = '_id' if colname=='id' else colname if column in record: if column == '_id' and isinstance( - record[column],pymongo.objectid.ObjectId): + record[column],ObjectId): value = int(str(record[column]),16) elif column != '_id': value = record[column] @@ -4947,13 +4950,7 @@ class MongoDBAdapter(NoSQLAdapter): def GT(self,first,second): print "in GT" - #import pymongo.objectid result = {} - #if expanded_first == '_id': - #if expanded_second != 0 and not isinstance(second,pymongo.objectid.ObjectId): - #raise SyntaxError, 'second argument must be of type bson.objectid.ObjectId' - #elif expanded_second == 0: - #expanded_second = pymongo.objectid.ObjectId('000000000000000000000000') result[self.expand(first)] = {'$gt': self.expand(second)} return result @@ -5084,13 +5081,7 @@ class MongoDBAdapter(NoSQLAdapter): def GT(self,first,second): print "in GT" - #import pymongo.objectid result = {} - #if expanded_first == '_id': - #if expanded_second != 0 and not isinstance(second,pymongo.objectid.ObjectId): - #raise SyntaxError, 'second argument must be of type bson.objectid.ObjectId' - #elif expanded_second == 0: - #expanded_second = pymongo.objectid.ObjectId('000000000000000000000000') result[self.expand(first)] = {'$gt': self.expand(second)} return result From 540c9d3970316882d9ec028f3b396321fcb6cf71 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 00:56:06 -0500 Subject: [PATCH 003/155] using FORM.dialog --- VERSION | 2 +- applications/admin/controllers/default.py | 30 +++++++++++-------- applications/admin/views/default/delete.html | 4 +-- .../admin/views/default/delete_plugin.html | 5 ++-- .../admin/views/default/downgrade_web2py.html | 14 --------- .../admin/views/default/uninstall.html | 9 ++---- .../admin/views/default/upgrade_web2py.html | 3 +- 7 files changed, 26 insertions(+), 41 deletions(-) delete mode 100644 applications/admin/views/default/downgrade_web2py.html diff --git a/VERSION b/VERSION index 43e5d27d..e31700df 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-27 17:25:26) dev +Version 2.00.0 (2012-07-28 00:56:00) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 341f1ddc..8929034d 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -317,20 +317,24 @@ def pack_plugin(): redirect(URL('plugin',args=request.args)) def upgrade_web2py(): - if 'upgrade' in request.vars: + dialog = FORM.dialog(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')) - elif 'noupgrade' in request.vars: - redirect(URL('site')) - return dict() + return dict(dialog=dialog) def uninstall(): app = get_app() - if 'delete' in request.vars: + + dialog = FORM.dialog(T('Uninstall'), + {T('Cancel'):URL('site')}) + + if dialog.accepted: if MULTI_USER_MODE: if is_manager() and db(db.app.name==app).delete(): pass @@ -344,9 +348,7 @@ def uninstall(): else: session.flash = T('unable to uninstall "%s"', app) redirect(URL('site')) - elif 'nodelete' in request.vars: - redirect(URL('site')) - return dict(app=app) + return dict(app=app, dialog=dialog) def cleanup(): @@ -978,9 +980,12 @@ def delete_plugin(): app=request.args(0) plugin = request.args(1) plugin_name='plugin_'+plugin - if 'nodelete' in request.vars: - redirect(URL('design', args=app, anchor=request.vars.id)) - elif 'delete' in request.vars: + + dialog = FORM.dialog( + 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) @@ -997,7 +1002,7 @@ def delete_plugin(): 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(plugin=plugin) + return dict(dialog=dialog,plugin=plugin) def plugin(): """ Application design handler """ @@ -1586,7 +1591,6 @@ def reload_routes(): gluon.rewrite.load() redirect(URL('site')) - def manage_students(): if not (MULTI_USER_MODE and is_manager()): session.flash = T('Not Authorized') diff --git a/applications/admin/views/default/delete.html b/applications/admin/views/default/delete.html index d0f86f44..d3e406ef 100644 --- a/applications/admin/views/default/delete.html +++ b/applications/admin/views/default/delete.html @@ -2,8 +2,8 @@ {{block sectionclass}}delete{{end}} -

{{=T('Are you sure you want to delete file "%s"?', filename)}}

-

{{=dialog}}

+
+{{=dialog}}
diff --git a/applications/admin/views/default/delete_plugin.html b/applications/admin/views/default/delete_plugin.html index 1e96be2a..e48f5db9 100644 --- a/applications/admin/views/default/delete_plugin.html +++ b/applications/admin/views/default/delete_plugin.html @@ -2,9 +2,8 @@ {{block sectionclass}}delete_plugin{{end}} -

{{=T('Are you sure you want to delete plugin "%s"?', plugin)}}

-

{{=FORM(INPUT(_type='submit',_name='nodelete',_value=T('NO')))}}

-

{{=FORM(INPUT(_type='submit',_name='delete',_value=T('YES')))}}

+
+{{=dialog}}
diff --git a/applications/admin/views/default/downgrade_web2py.html b/applications/admin/views/default/downgrade_web2py.html deleted file mode 100644 index 0078e60c..00000000 --- a/applications/admin/views/default/downgrade_web2py.html +++ /dev/null @@ -1,14 +0,0 @@ -{{extend 'layout.html'}} - -{{block sectionclass}}upgrade{{end}} - -

{{=T('web2py downgrade')}}

- -

{{=T('ATTENTION:')}} {{=T('This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk')}}
-{{=T('If start the downgrade, be patient, it may take a while to rollback')}}

- -
-{{=FORM(INPUT(_type='submit',_name='nodowngrade',_value=T('Cancel')), _class='inline')}} -{{=FORM(INPUT(_type='submit',_name='downgrade',_value=T('Downgrade')), _class='inline')}} -
- diff --git a/applications/admin/views/default/uninstall.html b/applications/admin/views/default/uninstall.html index b2d9b4f0..72920aba 100644 --- a/applications/admin/views/default/uninstall.html +++ b/applications/admin/views/default/uninstall.html @@ -1,10 +1,7 @@ {{extend 'layout.html'}} -

{{=T('Are you sure you want to uninstall application "%s"?', app)}}

- - - -
{{=FORM(INPUT(_type='submit',_name='nodelete',_value=T('Abort')))}}{{=FORM(INPUT(_type='submit',_name='delete',_value=T('Uninstall')))}}
-
+
+{{=dialog}} +
diff --git a/applications/admin/views/default/upgrade_web2py.html b/applications/admin/views/default/upgrade_web2py.html index c9974570..34bf06cd 100644 --- a/applications/admin/views/default/upgrade_web2py.html +++ b/applications/admin/views/default/upgrade_web2py.html @@ -8,7 +8,6 @@ {{=T('If start the upgrade, be patient, it may take a while to download')}}

-{{=FORM(INPUT(_type='submit',_name='noupgrade',_value=T('Cancel')), _class='inline')}} -{{=FORM(INPUT(_type='submit',_name='upgrade',_value=T('Upgrade')), _class='inline')}} +{{=dialog}}
From b1e2b07cfbb05ac996b51831b95249a5f702a458 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 01:00:57 -0500 Subject: [PATCH 004/155] FORM.dialog -> FORM.confirm --- VERSION | 2 +- applications/admin/controllers/default.py | 8 ++++---- gluon/html.py | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index e31700df..e8e898f2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 00:56:00) dev +Version 2.00.0 (2012-07-28 01:00:54) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 8929034d..8468d33a 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -317,7 +317,7 @@ def pack_plugin(): redirect(URL('plugin',args=request.args)) def upgrade_web2py(): - dialog = FORM.dialog(T('Upgrade'), + dialog = FORM.confim(T('Upgrade'), {T('Cancel'):URL('site')}) if dialog.accepted: (success, error) = upgrade(request) @@ -331,7 +331,7 @@ def upgrade_web2py(): def uninstall(): app = get_app() - dialog = FORM.dialog(T('Uninstall'), + dialog = FORM.confim(T('Uninstall'), {T('Cancel'):URL('site')}) if dialog.accepted: @@ -414,7 +414,7 @@ def delete(): if isinstance(sender, list): # ## fix a problem with Vista sender = sender[0] - dialog = FORM.dialog(T('Delete'), + dialog = FORM.confim(T('Delete'), {T('Cancel'):URL(sender, anchor=request.vars.id)}) if dialog.accepted: @@ -981,7 +981,7 @@ def delete_plugin(): plugin = request.args(1) plugin_name='plugin_'+plugin - dialog = FORM.dialog( + dialog = FORM.confim( T('Delete'), {T('Cancel'):URL('design', args=app)}) diff --git a/gluon/html.py b/gluon/html.py index 3776f67b..151626b9 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -2041,8 +2041,9 @@ class FORM(DIV): _onclick=self.REDIRECT_JS % url)) + @staticmethod - def dialog(text='OK',buttons=None,hidden=None): + def confim(text='OK',buttons=None,hidden=None): if not buttons: buttons = {} if not hidden: hidden={} inputs = [INPUT(_type='button', From 165e3a72b764d71e3389bae562aa26233939d2c9 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 02:08:53 -0500 Subject: [PATCH 005/155] admin has forms --- VERSION | 2 +- applications/admin/controllers/default.py | 126 ++++++++++++--------- applications/admin/views/default/site.html | 92 ++++++--------- 3 files changed, 106 insertions(+), 114 deletions(-) diff --git a/VERSION b/VERSION index e8e898f2..b6401a53 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 01:00:54) dev +Version 2.00.0 (2012-07-28 02:08:47) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 8468d33a..13fc304b 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -167,6 +167,7 @@ def change_password(): redirect(URL('site')) return dict(form=form) + def site(): """ Site handler """ @@ -175,12 +176,32 @@ def site(): # 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 request.vars.filename and not 'file' in request.vars: + elif form_create.accepted: # create a new application - appname = cleanpath(request.vars.filename).replace('.', '_') + appname = cleanpath(form_create.vars.name) if app_create(appname, request): if MULTI_USER_MODE: db.app.insert(name=appname,owner=auth.user.id) @@ -189,65 +210,57 @@ def site(): redirect(URL('design',args=appname)) else: session.flash = \ - T('unable to create application "%s" (it may exist already)', request.vars.filename) + T('unable to create application "%s" (it may exist already)', + form_create.vars.name) redirect(URL(r=request)) - elif file_or_appurl and not request.vars.filename: - # can't do anything without an app name - msg = 'you must specify a name for the uploaded application' - response.flash = T(msg) - - elif (request.vars.appurl or '').endswith('.git') and request.vars.filename: - if not have_git: - session.flash = GIT_MISSING - elif request.vars.filename: - target = os.path.join(apath(r=request),request.vars.filename) - if os.path.exists(target): - session.flash = 'Application by that name already exists.' - else: - try: - new_repo = Repo.clone_from(request.vars.appurl,target) - session.flash = T('new application "%s" imported',request.vars.filename) - except GitCommandError, err: - session.flash = T('Invalid git repository specified.') - else: - session.flash = 'Application Name required for git import.' - redirect(URL(r=request)) - - elif file_or_appurl and request.vars.filename: - # fetch an application via URL or file upload - f = None - if request.vars.appurl: + elif form_update.accepted: + if (form_update.vars.url or '').endswith('.git'): + if not have_git: + session.flash = GIT_MISSING + target = os.path.join(apath(r=request),form_update.vars.name) try: - f = urllib.urlopen(request.vars.appurl) - except Exception, e: - session.flash = DIV(T('Unable to download app because:'),PRE(str(e))) - redirect(URL(r=request)) - fname = request.vars.appurl - elif request.vars.file: - f = request.vars.file.file - fname = request.vars.file.filename + 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)) - if f: - appname = cleanpath(request.vars.filename).replace('.', '_') - installed = app_install(appname, f, request, fname, - overwrite=request.vars.overwrite_check) - 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 request.vars.overwrite_check: - msg = 'unable to install application "%(appname)s"' - session.flash = T(msg, dict(appname=request.vars.filename)) + elif form_update.vars.url: + # fetch an application via URL or file upload + if form_update.vars.url: + try: + form_update.vars.file = \ + urllib.urlopen(form_update.vars.url) + 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 = form_update.vars.file.filename + appname = cleanpath(form_update.vars.name) + installed = app_install(appname, form_update.vars.file.file, + 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=request.vars.filename)) + else: + msg = 'unable to install application "%(appname)s"' + session.flash = T(msg, dict(appname=form_update.vars.name)) - redirect(URL(r=request)) + redirect(URL(r=request)) regex = re.compile('^\w+$') @@ -261,7 +274,8 @@ def site(): apps = sorted(apps,lambda a,b:cmp(a.upper(),b.upper())) - return dict(app=None, apps=apps, myversion=myversion) + return dict(app=None, apps=apps, myversion=myversion, + form_create=form_create, form_update=form_update) def report_progress(app): diff --git a/applications/admin/views/default/site.html b/applications/admin/views/default/site.html index fd809416..879dccc2 100644 --- a/applications/admin/views/default/site.html +++ b/applications/admin/views/default/site.html @@ -70,12 +70,12 @@ {{else:}}

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

-
+ {{pass}} +

{{=T("Running on %s", request.env.server_software)}} -

-

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

+

{{pass}} @@ -87,64 +87,42 @@

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

-
-
- {{=LABEL(T("Application name:"), _for="scaffold_filename")}} - - -
- -
+ {{=form_create.custom.begin}} +
+ {{=LABEL(T("Application name:"))}} + + {{=form_create.custom.widget.name}} + + +
+ {{=form_create.custom.end}}

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

-
-
- - - - - - - - - - - - - - -
- {{=LABEL(T("Application name:"), _form='upload_filename')}} - - -
- {{=LABEL(T("Upload a package:"), _for='upload_file')}} - - - OR -
- {{=LABEL(T("Get from URL:"), _for='upload_url')}} - -
-
+ {{=form_update.custom.begin}} + - - - - - - -
+ {{=LABEL(T("Application name:"))}} + + {{=form_update.custom.widget.name}} +
+ {{=LABEL(T("Upload a package:"))}} + + {{=form_update.custom.widget.file}} +
+ {{=LABEL('Or ',T("Get from URL:"))}} + + {{=form_update.custom.widget.url}} +
({{=T('can be a git repo')}}) - - - {{=LABEL(T("Overwrite installed app"), _for='upload_overwrite')}} -
- -
- - +
+ {{=form_update.custom.widget.overwrite}} + {{=LABEL(T("Overwrite installed app"))}} +
+ + +
+ {{=form_update.custom.end}}
From 89e4caa9df776968a33bfebed8f90eaf05a6fb4a Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 02:24:09 -0500 Subject: [PATCH 006/155] fixed IS_LENGTH --- VERSION | 2 +- gluon/validators.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index b6401a53..9fb533af 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 02:08:47) dev +Version 2.00.0 (2012-07-28 02:24:05) dev diff --git a/gluon/validators.py b/gluon/validators.py index a18099fb..0790f9b3 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -264,11 +264,13 @@ class IS_LENGTH(Validator): def __call__(self, value): if isinstance(value, cgi.FieldStorage): - if value.file: + if not value: + length = 0 + elif value.file: value.file.seek(0, os.SEEK_END) length = value.file.tell() value.file.seek(0, os.SEEK_SET) - else: + elif hasattr(value,'value'): val = value.value if val: length = len(val) From 9db2afdd0643a5dca0591e86e0de039ec38ff2fe Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 19:11:16 -0500 Subject: [PATCH 007/155] issue 912, handle lists of more than 30 references on GAE, thanks Howesc --- VERSION | 2 +- applications/admin/controllers/default.py | 8 ++++---- gluon/dal.py | 9 ++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 9fb533af..4aa86f1a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 02:24:05) dev +Version 2.00.0 (2012-07-28 19:11:10) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 13fc304b..6c03cc87 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -240,19 +240,19 @@ def site(): fname = form_update.vars.url elif form_update.accepted and form_update.vars.file: - fname = form_update.vars.file.filename + fname = request.vars.file.filename appname = cleanpath(form_update.vars.name) - installed = app_install(appname, form_update.vars.file.file, + installed = app_install(appname, request.vars.file.file, request, fname, overwrite=form_update.vars.overwrite) - if f and installed: + if 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: + elif form_update.vars.overwrite: msg = 'unable to install application "%(appname)s"' session.flash = T(msg, dict(appname=form_update.vars.name)) diff --git a/gluon/dal.py b/gluon/dal.py index f1691811..1b1dc799 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6099,7 +6099,14 @@ def sqlhtml_validators(field): def list_ref_repr(ids, row=None, r=referenced, f=ff): if not ids: return None - refs = r._db(r._id.belongs(ids)).select(r._id) + if isinstance(r._db._adapter, GoogleDatastoreAdapter): + for i in xrange(0, len(ids), 30): + if not refs: + refs = r._db(r._id.belongs(ids[i:i+30])).select(r._id) + else: + refs = refs & r._db(r._id.belongs(ids[i:i+30])).select(r._id) + else: + refs = r._db(r._id.belongs(ids)).select(r._id) return (refs and ', '.join(str(f(r,ref.id)) for ref in refs) or '') field.represent = field.represent or list_ref_repr if hasattr(referenced, '_format') and referenced._format: From e227bfc2c4b60f42656ce22b11550c9a8e77a75a Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 19:14:19 -0500 Subject: [PATCH 008/155] issue 913, handle html5 input types, thanks Howesc --- VERSION | 2 +- gluon/html.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 4aa86f1a..3e6e3981 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 19:11:10) dev +Version 2.00.0 (2012-07-28 19:14:16) dev diff --git a/gluon/html.py b/gluon/html.py index 151626b9..2c1c4ca5 100644 --- a/gluon/html.py +++ b/gluon/html.py @@ -1669,7 +1669,7 @@ class INPUT(DIV): self['_checked'] = 'checked' else: self['_checked'] = None - elif t == 'text' or t == 'hidden': + elif not t == 'submit': if value is None: self['value'] = _value else: From 58ffa90ef2123c7e1693fc39b732a762417a8b10 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 19:28:36 -0500 Subject: [PATCH 009/155] allow serialization of SQLCustomTypes, thanks Anthony --- VERSION | 2 +- gluon/dal.py | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index 3e6e3981..c259e5c0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 19:14:16) dev +Version 2.00.0 (2012-07-28 19:28:33) dev diff --git a/gluon/dal.py b/gluon/dal.py index 1b1dc799..a7f75b59 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -6199,8 +6199,12 @@ class Row(dict): def __copy__(self): return Row(dict(self)) - def as_dict(self,datetime_to_str=False): - SERIALIZABLE_TYPES = (str,unicode,int,long,float,bool,list) + def as_dict(self, datetime_to_str=False, custom_types=None): + SERIALIZABLE_TYPES = [str, unicode, int, long, float, bool, list, dict] + if isinstance(custom_types,(list,tuple,set)): + SERIALIZABLE_TYPES += custom_types + elif custom_types: + SERIALIZABLE_TYPES.append(custom_types) d = dict(self) for k in copy.copy(d.keys()): v=d[k] @@ -6215,7 +6219,7 @@ class Row(dict): elif isinstance(v, (datetime.date, datetime.datetime, datetime.time)): if datetime_to_str: d[k] = v.isoformat().replace('T',' ')[:19] - elif not isinstance(v,SERIALIZABLE_TYPES): + elif not isinstance(v,tuple(SERIALIZABLE_TYPES)): del d[k] return d @@ -8623,7 +8627,8 @@ class Rows(object): def as_list(self, compact=True, storage_to_dict=True, - datetime_to_str=True): + datetime_to_str=True, + custom_types=None): """ returns the data as a list or dictionary. :param storage_to_dict: when True returns a dict, otherwise a list(default True) @@ -8631,7 +8636,7 @@ class Rows(object): """ (oc, self.compact) = (self.compact, compact) if storage_to_dict: - items = [item.as_dict(datetime_to_str) for item in self] + items = [item.as_dict(datetime_to_str, custom_types) for item in self] else: items = [item for item in self] self.compact = compact @@ -8642,7 +8647,8 @@ class Rows(object): key='id', compact=True, storage_to_dict=True, - datetime_to_str=True): + datetime_to_str=True, + custom_types=None): """ returns the data as a dictionary of dictionaries (storage_to_dict=True) or records (False) @@ -8651,7 +8657,7 @@ class Rows(object): :param storage_to_dict: when True returns a dict, otherwise a list(default True) :param datetime_to_str: convert datetime fields as strings (default True) """ - rows = self.as_list(compact, storage_to_dict, datetime_to_str) + rows = self.as_list(compact, storage_to_dict, datetime_to_str, custom_types) if isinstance(key,str) and key.count('.')==1: (table, field) = key.split('.') return dict([(r[table][field],r) for r in rows]) From 0bc6d60fbe903d2a7052ee7f73b16701bda75a2a Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 20:19:15 -0500 Subject: [PATCH 010/155] increased security in appadmin --- Makefile | 10 +++---- VERSION | 2 +- applications/admin/controllers/appadmin.py | 27 ++++++++++++------- applications/admin/views/appadmin.html | 5 +--- applications/examples/controllers/appadmin.py | 27 ++++++++++++------- applications/examples/views/appadmin.html | 5 +--- applications/welcome/controllers/appadmin.py | 27 ++++++++++++------- applications/welcome/views/appadmin.html | 5 +--- 8 files changed, 63 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index cc599250..c433d751 100644 --- a/Makefile +++ b/Makefile @@ -43,11 +43,11 @@ src: rm -f applications/admin/uploads/* rm -f applications/welcome/uploads/* rm -f applications/examples/uploads/* - ### make admin layout and appadmin the default - cp applications/admin/views/appadmin.html applications/welcome/views - cp applications/admin/views/appadmin.html applications/examples/views - cp applications/admin/controllers/appadmin.py applications/welcome/controllers - cp applications/admin/controllers/appadmin.py applications/examples/controllers + ### make welcome layout and appadmin the default + cp applications/welcome/views/appadmin.html applications/admin/views + cp applications/welcome/views/appadmin.html applications/examples/views + cp applications/welcome/controllers/appadmin.py applications/admin/controllers + cp applications/welcome/controllers/appadmin.py applications/examples/controllers ### build web2py_src.zip echo '' > NEWINSTALL mv web2py_src.zip web2py_src_old.zip | echo 'no old' diff --git a/VERSION b/VERSION index c259e5c0..7cce570c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 19:28:33) dev +Version 2.00.0 (2012-07-28 20:19:12) dev diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py index b8eccf9e..71ff7876 100644 --- a/applications/admin/controllers/appadmin.py +++ b/applications/admin/controllers/appadmin.py @@ -199,17 +199,8 @@ def select(): _class='delete', _type='checkbox', value=False), ''), TR('', '', INPUT(_type='submit', _value=T('submit')))), _action=URL(r=request,args=request.args)) - if request.vars.csvfile != None: - try: - import_csv(db[request.vars.table], - request.vars.csvfile.file) - response.flash = T('data uploaded') - except Exception, e: - response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) if form.accepts(request.vars, formname=None): -# regex = re.compile(request.args[0] + '\.(?P\w+)\.id\>0') regex = re.compile(request.args[0] + '\.(?P
\w+)\..+') - match = regex.match(form.vars.query.strip()) if match: table = match.group('table') @@ -230,6 +221,23 @@ def select(): except Exception, e: (rows, nrows) = ([], 0) response.flash = DIV(T('Invalid Query'),PRE(str(e))) + # begin handle upload csv + if table: + formcsv = FORM(str(T('or import from csv file'))+" ", + INPUT(_type='file',_name='csvfile'), + INPUT(_type='hidden',_value=table,_name='table'), + INPUT(_type='submit',_value=T('import'))) + else: + formcsv = None + if formcsv and formcsv.process().accepted and request.vars.csvfile: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + # end handle upload csv + return dict( form=form, table=table, @@ -238,6 +246,7 @@ def select(): nrows=nrows, rows=rows, query=request.vars.query, + formcsv = formcsv, ) diff --git a/applications/admin/views/appadmin.html b/applications/admin/views/appadmin.html index 73c4ae47..02c90b0e 100644 --- a/applications/admin/views/appadmin.html +++ b/applications/admin/views/appadmin.html @@ -62,10 +62,7 @@ {{pass}}

{{=T("Import/Export")}}


[ {{=T("export as csv file")}} ] - {{if table:}} - {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value=T('import')))}} - {{pass}} - + {{=formcsv or ''}} {{elif request.function=='insert':}}

{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} diff --git a/applications/examples/controllers/appadmin.py b/applications/examples/controllers/appadmin.py index b8eccf9e..71ff7876 100644 --- a/applications/examples/controllers/appadmin.py +++ b/applications/examples/controllers/appadmin.py @@ -199,17 +199,8 @@ def select(): _class='delete', _type='checkbox', value=False), ''), TR('', '', INPUT(_type='submit', _value=T('submit')))), _action=URL(r=request,args=request.args)) - if request.vars.csvfile != None: - try: - import_csv(db[request.vars.table], - request.vars.csvfile.file) - response.flash = T('data uploaded') - except Exception, e: - response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) if form.accepts(request.vars, formname=None): -# regex = re.compile(request.args[0] + '\.(?P

\w+)\.id\>0') regex = re.compile(request.args[0] + '\.(?P
\w+)\..+') - match = regex.match(form.vars.query.strip()) if match: table = match.group('table') @@ -230,6 +221,23 @@ def select(): except Exception, e: (rows, nrows) = ([], 0) response.flash = DIV(T('Invalid Query'),PRE(str(e))) + # begin handle upload csv + if table: + formcsv = FORM(str(T('or import from csv file'))+" ", + INPUT(_type='file',_name='csvfile'), + INPUT(_type='hidden',_value=table,_name='table'), + INPUT(_type='submit',_value=T('import'))) + else: + formcsv = None + if formcsv and formcsv.process().accepted and request.vars.csvfile: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + # end handle upload csv + return dict( form=form, table=table, @@ -238,6 +246,7 @@ def select(): nrows=nrows, rows=rows, query=request.vars.query, + formcsv = formcsv, ) diff --git a/applications/examples/views/appadmin.html b/applications/examples/views/appadmin.html index 73c4ae47..02c90b0e 100644 --- a/applications/examples/views/appadmin.html +++ b/applications/examples/views/appadmin.html @@ -62,10 +62,7 @@ {{pass}}

{{=T("Import/Export")}}


[ {{=T("export as csv file")}} ] - {{if table:}} - {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value=T('import')))}} - {{pass}} - + {{=formcsv or ''}} {{elif request.function=='insert':}}

{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} diff --git a/applications/welcome/controllers/appadmin.py b/applications/welcome/controllers/appadmin.py index b8eccf9e..71ff7876 100644 --- a/applications/welcome/controllers/appadmin.py +++ b/applications/welcome/controllers/appadmin.py @@ -199,17 +199,8 @@ def select(): _class='delete', _type='checkbox', value=False), ''), TR('', '', INPUT(_type='submit', _value=T('submit')))), _action=URL(r=request,args=request.args)) - if request.vars.csvfile != None: - try: - import_csv(db[request.vars.table], - request.vars.csvfile.file) - response.flash = T('data uploaded') - except Exception, e: - response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) if form.accepts(request.vars, formname=None): -# regex = re.compile(request.args[0] + '\.(?P

\w+)\.id\>0') regex = re.compile(request.args[0] + '\.(?P
\w+)\..+') - match = regex.match(form.vars.query.strip()) if match: table = match.group('table') @@ -230,6 +221,23 @@ def select(): except Exception, e: (rows, nrows) = ([], 0) response.flash = DIV(T('Invalid Query'),PRE(str(e))) + # begin handle upload csv + if table: + formcsv = FORM(str(T('or import from csv file'))+" ", + INPUT(_type='file',_name='csvfile'), + INPUT(_type='hidden',_value=table,_name='table'), + INPUT(_type='submit',_value=T('import'))) + else: + formcsv = None + if formcsv and formcsv.process().accepted and request.vars.csvfile: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + # end handle upload csv + return dict( form=form, table=table, @@ -238,6 +246,7 @@ def select(): nrows=nrows, rows=rows, query=request.vars.query, + formcsv = formcsv, ) diff --git a/applications/welcome/views/appadmin.html b/applications/welcome/views/appadmin.html index 73c4ae47..02c90b0e 100644 --- a/applications/welcome/views/appadmin.html +++ b/applications/welcome/views/appadmin.html @@ -62,10 +62,7 @@ {{pass}}

{{=T("Import/Export")}}


[ {{=T("export as csv file")}} ] - {{if table:}} - {{=FORM(str(T('or import from csv file'))+" ",INPUT(_type='file',_name='csvfile'),INPUT(_type='hidden',_value=table,_name='table'),INPUT(_type='submit',_value=T('import')))}} - {{pass}} - + {{=formcsv or ''}} {{elif request.function=='insert':}}

{{=T("database")}} {{=A(request.args[0],_href=URL('index'))}} From edf057b6e544a8bf381743a32a2e071c8e4f4a92 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 20:51:18 -0500 Subject: [PATCH 011/155] made admin uploads more secure --- VERSION | 2 +- applications/admin/controllers/default.py | 17 +++++++++++++++-- applications/admin/views/default/design.html | 3 +++ applications/admin/views/default/git_pull.html | 8 ++------ applications/admin/views/default/plugin.html | 7 ++----- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/VERSION b/VERSION index 7cce570c..70b92f72 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 20:19:12) dev +Version 2.00.0 (2012-07-28 20:51:14) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 6c03cc87..a680efe2 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -11,6 +11,7 @@ if EXPERIMENTAL_STUFF: 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 @@ -40,6 +41,9 @@ 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('#')]) @@ -862,6 +866,9 @@ def design(): 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, @@ -1113,6 +1120,8 @@ def plugin(): 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: @@ -1260,6 +1269,8 @@ def create_file(): 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]) @@ -1639,7 +1650,9 @@ def git_pull(): if not have_git: session.flash = GIT_MISSING redirect(URL('site')) - if 'pull' in request.vars: + dialog = FORM.confim(T('Pull'), + {T('Cancel'):URL('site')}) + if dialog.accepted: try: repo = Repo(os.path.join(apath(r=request),app)) origin = repo.remotes.origin @@ -1667,7 +1680,7 @@ def git_pull(): redirect(URL('site')) elif 'cancel' in request.vars: redirect(URL('site')) - return dict(app=app) + return dict(app=app,dialog=dialog) def git_push(): diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html index e5b92ab8..9ef577e8 100644 --- a/applications/admin/views/default/design.html +++ b/applications/admin/views/default/design.html @@ -19,6 +19,7 @@ def file_upload_form(location, anchor=None): 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), + INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="sender",_value=URL('design',args=app, anchor=anchor)), INPUT(_type="submit",_value=T("upload")),_action=URL('upload_file')) return form @@ -27,6 +28,7 @@ def file_create_form(location, anchor=None): 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="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="id",_value=anchor), INPUT(_type="submit",_value=T("Create")),_action=URL('create_file')) return form @@ -34,6 +36,7 @@ def upload_plugin_form(app, anchor=None): form=FORM(T("upload plugin file:")," ", INPUT(_type="file",_name="pluginfile"), INPUT(_type="hidden",_name="id",_value=anchor), + INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="submit",_value=T("upload"))) return form def deletefile(arglist, vars={}): diff --git a/applications/admin/views/default/git_pull.html b/applications/admin/views/default/git_pull.html index 68623136..3afbf783 100644 --- a/applications/admin/views/default/git_pull.html +++ b/applications/admin/views/default/git_pull.html @@ -1,9 +1,5 @@ {{extend 'layout.html'}} -
+

{{=T('This will pull changes from the remote repo for application "%s"?', app)}}

-

- - -
{{=FORM(INPUT(_type='submit',_name='cancel',_value=T('Cancel')))}}{{=FORM(INPUT(_type='submit',_name='pull',_value=T('Pull')))}}
- +{{=dialog}} diff --git a/applications/admin/views/default/plugin.html b/applications/admin/views/default/plugin.html index aded9d40..49d328e7 100644 --- a/applications/admin/views/default/plugin.html +++ b/applications/admin/views/default/plugin.html @@ -16,6 +16,7 @@ def file_upload_form(location): 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), + INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="submit",_value=T("submit")),_action=URL('upload_file')) return form @@ -23,14 +24,10 @@ 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="token",_value=session.token), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="submit",_value=T("submit")),_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("submit"))) - return form def deletefile(arglist): return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), SPAN(T('Delete this file (you will be asked to confirm deletion)'))), _class='icon delete tooltip', _href=URL('delete',args=arglist,vars=dict(sender=request.function+'/'+app))) }} From 5f07ba20b745bb2319cebdd709831a9ad6cc3c0d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 21:33:00 -0500 Subject: [PATCH 012/155] fixed IS_LENGTH for value==None again --- VERSION | 2 +- gluon/validators.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 70b92f72..4df4cfc2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 20:51:14) dev +Version 2.00.0 (2012-07-28 21:32:56) dev diff --git a/gluon/validators.py b/gluon/validators.py index 0790f9b3..19344945 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -263,10 +263,12 @@ class IS_LENGTH(Validator): self.error_message = error_message def __call__(self, value): - if isinstance(value, cgi.FieldStorage): - if not value: - length = 0 - elif value.file: + if value is None: + length = 0 + if self.minsize <= length <= self.maxsize: + return (value, None) + elif isinstance(value, cgi.FieldStorage): + if value.file: value.file.seek(0, os.SEEK_END) length = value.file.tell() value.file.seek(0, os.SEEK_SET) From 8b4d0a6caaf6af2cdd8383dd2e502f1334a9febd Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 22:37:19 -0500 Subject: [PATCH 013/155] made git pull thread safe, thanks Andrew --- VERSION | 2 +- applications/admin/controllers/default.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 4df4cfc2..b0ebf5aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 21:32:56) dev +Version 2.00.0 (2012-07-28 22:37:15) dev diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index a680efe2..d4fbf0e8 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -1697,8 +1697,7 @@ def git_push(): try: repo = Repo(os.path.join(apath(r=request),app)) index = repo.index - os.chdir(os.path.join(apath(r=request),app)) - index.add('*') + index.add([apath(r=request)+app+'/*']) new_commit = index.commit(form.vars.changelog) origin = repo.remotes.origin origin.push() @@ -1711,6 +1710,5 @@ def git_push(): logging.error("Unexpected error:", sys.exc_info()[0]) session.flash = T("Push failed, git exited abnormally. See logs for details.") redirect(URL('site')) - os.chdir(apath(r=request)) return dict(app=app,form=form) From 606d9d7ee494d5913879304b5345152b9ab54b30 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sat, 28 Jul 2012 23:04:30 -0500 Subject: [PATCH 014/155] fixed issue qith clicking on toplevel menus --- VERSION | 2 +- applications/welcome/models/menu.py | 2 +- applications/welcome/views/layout.html | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index b0ebf5aa..0a59f10e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 22:37:15) dev +Version 2.00.0 (2012-07-28 23:04:24) dev diff --git a/applications/welcome/models/menu.py b/applications/welcome/models/menu.py index 8a9ebe4a..5c1ebc66 100644 --- a/applications/welcome/models/menu.py +++ b/applications/welcome/models/menu.py @@ -36,7 +36,7 @@ def _(): ctr = request.controller # useful links to internal and external resources response.menu+=[ - (SPAN('web2py',_style='color:yellow'),False, None, [ + (SPAN('web2py',_style='color:yellow'),False, 'http://web2py.com', [ (T('My Sites'),False,URL('admin','default','site')), (T('This App'),False,URL('admin','default','design/%s' % app), [ (T('Controller'),False, diff --git a/applications/welcome/views/layout.html b/applications/welcome/views/layout.html index 008c9815..c1b9924d 100644 --- a/applications/welcome/views/layout.html +++ b/applications/welcome/views/layout.html @@ -71,6 +71,12 @@ if(jQuery(this).find('ul').length) jQuery(this).children('a').contents().before(''); }); + jQuery('ul.nav li.dropdown').hover(function() { + jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeIn(); + }, function() { + jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeOut(); + }); + jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');}); }); From cc8c562b20a6d74bde2fa2b6ae9d12825649d246 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 07:31:01 -0500 Subject: [PATCH 015/155] fixed is_impersonating --- VERSION | 2 +- gluon/tools.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 0a59f10e..cb99d016 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-28 23:04:24) dev +Version 2.00.0 (2012-07-29 07:30:57) dev diff --git a/gluon/tools.py b/gluon/tools.py index 86d063f5..cd8fbd3a 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -2582,8 +2582,7 @@ class Auth(object): return form def is_impersonating(self): - if not current.session.auth: return None - return current.session.auth.get('impersonator',None) + return 'impersonator' in current.session.auth def impersonate(self, user_id=DEFAULT): """ From 707c1667044074876d491578d5faeec64b7062b4 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 08:24:26 -0500 Subject: [PATCH 016/155] setup-web2py-nginx-uwsgi-on-centos.sh, thanks Peter --- VERSION | 2 +- scripts/setup-web2py-nginx-uwsgi-on-centos.sh | 257 ++++++++++++++++++ 2 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 scripts/setup-web2py-nginx-uwsgi-on-centos.sh diff --git a/VERSION b/VERSION index cb99d016..ce540551 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 07:30:57) dev +Version 2.00.0 (2012-07-29 08:24:23) dev diff --git a/scripts/setup-web2py-nginx-uwsgi-on-centos.sh b/scripts/setup-web2py-nginx-uwsgi-on-centos.sh new file mode 100644 index 00000000..7c5b9f76 --- /dev/null +++ b/scripts/setup-web2py-nginx-uwsgi-on-centos.sh @@ -0,0 +1,257 @@ +# Author: Peter Hutchinson +# License: BSD +# +# Installing Web2py with Nginx and Uwsgi on Centos 5 is really tricky. +# There are lots of subtleties of ownership, and one has to take care +# when installing python 2.6 not to stop the systems python2.4 from working. +# Here is a script that does all the installation from a clean start machine. +# The only thing that should need changing for another installation is +# the $basearch (base architecture) of the machine. We assume: + +basearch=i386 + +# This can be determined by doing 'uname -i'. +# This is needed for the nginx installation. +# There is one script and three configuration files. + +# install development tools + +yum install gcc gdbm-devel readline-devel ncurses-devel zlib-devel bzip2-devel sqlite-devel db4-devel openssl-devel tk-devel bluez-libs-devel + +# Install python 2.6 without overwriting python 2.4 +# ================================================= + +VERSION=2.6.8 +mkdir ~/src +chmod 777 ~/src +cd ~/src +wget http://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz +tar xvfz Python-2.6.8.tgz +cd Python-2.6.8 +./configure --prefix=/opt/python2.6 --with-threads --enable-shared +make + +# The altinstall ensures that python2.4 is left okay +# ================================================== + +make altinstall +echo "/opt/python2.6/lib">/etc/ld.so.conf.d/opt-python2.6.conf +ldconfig + +# create alias so that python 2.6 can be run with 'python2.6' +# =========================================================== + +alias -p python2.6="/opt/python2.6/bin/python2.6" +ln -s /opt/python2.6/bin/python2.6 /usr/bin/python2.6 + +# Install uwsgi +# ========= + +version=uwsgi-1.2.3 +cd /opt/ +wget http://projects.unbit.it/downloads/$version.tar.gz +tar -zxvf $version.tar.gz +mv $version/ uwsgi/ +cd uwsgi/ + +# build using python 2.6 +# ====================== + +python2.6 setup.py build +python2.6 uwsgiconfig.py --build +useradd uwsgi + +# create and own uwsgi log +# ======================== +# Note this log will need emptying from time to time + +echo " ">/var/log/uwsgi.log +chown uwsgi /var/log/uwsgi.log + +# Install web2py +# ========== + +cd /opt +mkdir web-apps +cd web-apps +wget http://www.web2py.com/examples/static/web2py_src.zip +unzip web2py_src.zip + +# set the ownership for web2py application to uwsgi +# ================================================= + +cd web2py +chown -R uwsgi applications +chmod -R u+wx applications + +# Now install nginx +# ================= + +cd /etc/yum.repos.d +echo "[nginx]">nginx.repo + +echo "baseurl=http://nginx.org/packages/centos/5/"$basearch$"/">>nginx.repo +echo "gpgcheck=0">>nginx.repo +echo "enabled=1">>nginx.repo +yum install nginx + +# We don't want the defaults, so remove them +# ========================================== + +cd /etc/nginx/conf.d +mv default.conf default.conf.o +mv example_ssl.conf example_ssl.conf.o + +# The following configuration files are also needed +# The options for uwsgi are in the following file. +# It should be placed in /etc/uwsgi. Other options could be included. + +echo """ +[uwsgi] + +uuid=uwsgi +pythonpath = /opt/web-apps/web2py +module = wsgihandler +socket=127.0.0.1:9001 +harakiri 60 +harakiri-verbose +enable-threads +daemonize = /var/log/uwsgi.log +""" > /etc/uwsgi/uwsgi_for_nginx.conf + +# The next configuration file is for nginx, and goes in /etc/nginx/conf.d +# It serves the static diretory of applications directly. +# I have not set up ssl because I access web2py admin by using ssh +# tunneling and the web2py rocket server. +# It should be straightforward to set up the ssl server however. + +echo """ +server { + listen 80; + server_name $hostname; + location ~* /(\w+)/static/ { + root /opt/web-apps/web2py/applications/; + } + location / { + uwsgi_pass 127.0.0.1:9001; + include uwsgi_params; + } +} + +#server { +# listen 443; +# server_name $hostname; +# ssl on; +# ssl_certificate /etc/nginx/ssl/web2py.crt; +# ssl_certificate_key /etc/nginx/ssl/web2py.key; +# location uwsgi_pass 127.0.0.1:9001; +# include uwsgi_params; +# uwsgi_param UWSGI_SCHEME $scheme; +#} +""" > /etc/nginx/conf.d/web2py.conf + +# The final configuration file is only needed if you want to run +# uwsgi as a service. It should be placed in /etc/init.d + + +echo """ + +#!/bin/bash +# uwsgi - Use uwsgi to run python and wsgi web apps. +# +# chkconfig: - 85 15 +# description: Use uwsgi to run python and wsgi web apps. +# processname: uwsgi +# author: Roman Vasilyev + +# Source function library. +. /etc/rc.d/init.d/functions + +########################### +PATH=/etc/uwsgi-python:/sbin:/bin:/usr/sbin:/usr/bin +PYTHONPATH=/home/www-data/web2py +MODULE=wsgihandler +prog=/etc/uwsgi-python/uwsgi +OWNER=uwsgi +# OWNER=nginx ¿? +NAME=uwsgi +DESC=uwsgi +DAEMON_OPTS="-s 127.0.0.1:9001 -M 4 -t 30 -A 4 -p 16 -b 32768 -d /var/log/$NAME.log --pidfile /var/run/$NAME.pid --uid $OWNER --ini-paste /etc/uwsgi-python/uwsgi_for_nginx.conf" +############################## + +[ -f /etc/sysconfig/uwsgi ] && . /etc/sysconfig/uwsgi + +lockfile=/var/lock/subsys/uwsgi + +start () { + echo -n "Starting $DESC: " + daemon $prog $DAEMON_OPTS + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval +} + +stop () { + echo -n "Stopping $DESC: " + killproc $prog + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +reload () { + echo "Reloading $NAME" + killproc $prog -HUP + RETVAL=$? + echo +} + +force-reload () { + echo "Reloading $NAME" + killproc $prog -TERM + RETVAL=$? + echo +} + +restart () { + stop + start +} + +rh_status () { + status $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart|force-reload) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + status) + rh_status + ;; + *) + echo "Usage: $0 {start|stop|restart|reload|force-reload|status}" >&2 + exit 2 + ;; + esac + exit 0 + +""" > /etc/init.d/uwsgi_nginx From cc378914baacbed511355ccb385eee85809d7b13 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 10:04:30 -0500 Subject: [PATCH 017/155] better wiki _search --- VERSION | 2 +- gluon/tools.py | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/VERSION b/VERSION index ce540551..c0f888d3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 08:24:23) dev +Version 2.00.0 (2012-07-29 10:04:25) dev diff --git a/gluon/tools.py b/gluon/tools.py index cd8fbd3a..0814f7cc 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4524,7 +4524,7 @@ class Wiki(object): URL(controller,function,args=('_cloud')))) menu.append((current.T('[Wiki]'),None,None,submenu)) return menu - def search(self,tags=None,cloud=True): + def search(self,tags=None,cloud=True,preview=True,limitby=(0,100),orderby=None): content = CAT() if not tags: request = current.request @@ -4538,20 +4538,25 @@ class Wiki(object): if tags: db = self.auth.db count = db.wiki_tag.wiki_page.count() - ids = db(db.wiki_tag.name.belongs(tags))._select( - db.wiki_tag.wiki_page, - groupby=db.wiki_tag.wiki_page, - orderby=~count,limitby=(0,100)) - pages = db(db.wiki_page.id.belongs(ids)).select( - db.wiki_page.slug,db.wiki_page.title,db.wiki_page.tags) + fields = [db.wiki_page.slug,db.wiki_page.title, + db.wiki_page.tags,count] + if preview: + fields.append(db.wiki_page.body) + pages = db(db.wiki_page.id==db.wiki_tag.wiki_page) \ + (db.wiki_tag.name.belongs(tags)).select( + *fields,**dict(orderby=orderby or ~count,limitby=limitby)) if not pages: content.append(DIV(T("No results",_class='w2p_wiki_form'))) else: def link(t): return A(t,_href=URL(args='_search',vars=dict(tags=t))) - items = [DIV(H3(A(p.title,_href=URL(args=p.slug))), + items = [DIV(H3(A(p.wiki_page.title, + _href=URL(args=p.wiki_page.slug))), SPAN(*[link(t.strip()) for t in \ - p.tags.split(',') if t.strip()]), + p.wiki_page.tags.split(',') \ + if t.strip()]), + MARKMIN(p.wiki_page.body.split('\n\n')[0]) \ + if preview else '', _class='w2p_wiki_tags') for p in pages] content.append(DIV(_class='w2p_wiki_pages',*items)) From 58eb21bd87cffde68a0681179e5ff37a969cfac0 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 10:32:46 -0500 Subject: [PATCH 018/155] wiki services --- VERSION | 2 +- gluon/serializers.py | 4 +++- gluon/tools.py | 55 +++++++++++++++++++++++++++++++------------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index c0f888d3..d5d7f9cf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 10:04:25) dev +Version 2.00.0 (2012-07-29 10:32:43) dev diff --git a/gluon/serializers.py b/gluon/serializers.py index 5597cebd..a725c032 100644 --- a/gluon/serializers.py +++ b/gluon/serializers.py @@ -6,7 +6,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) import datetime import decimal from storage import Storage -from html import TAG +from html import TAG, XmlComponent from html import xmlescape from languages import lazyT import contrib.rss2 as rss2 @@ -32,6 +32,8 @@ def custom_json(o): return str(o) elif isinstance(o, lazyT): return str(o) + elif isinstance(o,XmlComponent): + return str(o) elif hasattr(o,'as_list') and callable(o.as_list): return o.as_list() elif hasattr(o,'as_dict') and callable(o.as_dict): diff --git a/gluon/tools.py b/gluon/tools.py index 0814f7cc..49368fb1 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4375,6 +4375,12 @@ class Expose(object): H3('Files'), self.table_files()).xml() +def first_paragraph(mm): + mm = mm.replace('\r','') + ps = [p for p in mm.split('\n\n') if not p.startswith('#') and p.strip()] + if ps: return ps[0] + return '' + class Wiki(object): regex_redirect = re.compile('redirect\s+(\w+\://\S+)\s*') def __init__(self,auth,env=None,automenu=True,render='markmin'): @@ -4439,13 +4445,24 @@ class Wiki(object): if slug in '_cloud': return self.cloud() page = self.auth.db.wiki_page(slug=slug) - if not page: - url = URL(args=('_edit',slug)) - return dict(content=A('Create page "%s"' % slug,_href=url,_class="btn")) + if current.request.extension == 'html': + if not page: + url = URL(args=('_edit',slug)) + return dict(content=A('Create page "%s"' % slug,_href=url,_class="btn")) + else: + match = self.regex_redirect.match(page.body) + if match: redirect(match.group(1)) + return dict(content=XML(page.html)) else: - match = self.regex_redirect.match(page.body) - if match: redirect(match.group(1)) - return dict(content=XML(page.html)) + if not page: + raise HTTP(404) + else: + return dict(title=page.title, + slug=page.slug, + content=page.body, + tags=page.tags, + created_on=page.created_on, + modified_on=page.modified_on) def check_authorization(self,role='wiki_editor',act=False): if not self.auth.user: if not act: return False @@ -4524,42 +4541,48 @@ class Wiki(object): URL(controller,function,args=('_cloud')))) menu.append((current.T('[Wiki]'),None,None,submenu)) return menu - def search(self,tags=None,cloud=True,preview=True,limitby=(0,100),orderby=None): + def search(self,tags=None,cloud=True,preview=True, + limitby=(0,100),orderby=None): content = CAT() if not tags: request = current.request form = SQLFORM.factory(Field('tags',requires=IS_NOT_EMPTY(), default=request.vars.tags, label=current.T('Search'))) + content.append(DIV(form,_class='w2p_wiki_form')) if request.vars: tags = [v.strip() for v in request.vars.tags.split(',')] tags = [v for v in tags if v] - content.append(DIV(form,_class='w2p_wiki_form')) if tags: db = self.auth.db count = db.wiki_tag.wiki_page.count() - fields = [db.wiki_page.slug,db.wiki_page.title, - db.wiki_page.tags,count] + fields = [db.wiki_page.id,db.wiki_page.slug, + db.wiki_page.title,db.wiki_page.tags,count] if preview: fields.append(db.wiki_page.body) pages = db(db.wiki_page.id==db.wiki_tag.wiki_page) \ (db.wiki_tag.name.belongs(tags)).select( - *fields,**dict(orderby=orderby or ~count,limitby=limitby)) - if not pages: - content.append(DIV(T("No results",_class='w2p_wiki_form'))) - else: + *fields,**dict(orderby=orderby or ~count, + groupby=db.wiki_page.id, + limitby=limitby)) + if request.extension=='html': + if not pages: + content.append(DIV(T("No results",_class='w2p_wiki_form'))) def link(t): return A(t,_href=URL(args='_search',vars=dict(tags=t))) items = [DIV(H3(A(p.wiki_page.title, _href=URL(args=p.wiki_page.slug))), + MARKMIN(first_paragraph(p.wiki_page.body)) \ + if preview else '', SPAN(*[link(t.strip()) for t in \ p.wiki_page.tags.split(',') \ if t.strip()]), - MARKMIN(p.wiki_page.body.split('\n\n')[0]) \ - if preview else '', _class='w2p_wiki_tags') for p in pages] content.append(DIV(_class='w2p_wiki_pages',*items)) + else: + cloud=False + content = [p.as_dict() for p in pages] elif cloud: content.append(self.cloud()['content']) return dict(content=content) From ba60717e1d507c75a1dc713f1f4ea356c31a7995 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 10:37:04 -0500 Subject: [PATCH 019/155] improved scripts/setup-web2py-nginx-uwsgi-on-centos.sh, thanks Alan --- VERSION | 2 +- scripts/setup-web2py-nginx-uwsgi-on-centos.sh | 554 ++++++++++-------- 2 files changed, 298 insertions(+), 258 deletions(-) diff --git a/VERSION b/VERSION index d5d7f9cf..ff34f716 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 10:32:43) dev +Version 2.00.0 (2012-07-29 10:37:01) dev diff --git a/scripts/setup-web2py-nginx-uwsgi-on-centos.sh b/scripts/setup-web2py-nginx-uwsgi-on-centos.sh index 7c5b9f76..617aa939 100644 --- a/scripts/setup-web2py-nginx-uwsgi-on-centos.sh +++ b/scripts/setup-web2py-nginx-uwsgi-on-centos.sh @@ -1,257 +1,297 @@ -# Author: Peter Hutchinson -# License: BSD -# -# Installing Web2py with Nginx and Uwsgi on Centos 5 is really tricky. -# There are lots of subtleties of ownership, and one has to take care -# when installing python 2.6 not to stop the systems python2.4 from working. -# Here is a script that does all the installation from a clean start machine. -# The only thing that should need changing for another installation is -# the $basearch (base architecture) of the machine. We assume: - -basearch=i386 - -# This can be determined by doing 'uname -i'. -# This is needed for the nginx installation. -# There is one script and three configuration files. - -# install development tools - -yum install gcc gdbm-devel readline-devel ncurses-devel zlib-devel bzip2-devel sqlite-devel db4-devel openssl-devel tk-devel bluez-libs-devel - -# Install python 2.6 without overwriting python 2.4 -# ================================================= - -VERSION=2.6.8 -mkdir ~/src -chmod 777 ~/src -cd ~/src -wget http://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz -tar xvfz Python-2.6.8.tgz -cd Python-2.6.8 -./configure --prefix=/opt/python2.6 --with-threads --enable-shared -make - -# The altinstall ensures that python2.4 is left okay -# ================================================== - -make altinstall -echo "/opt/python2.6/lib">/etc/ld.so.conf.d/opt-python2.6.conf -ldconfig - -# create alias so that python 2.6 can be run with 'python2.6' -# =========================================================== - -alias -p python2.6="/opt/python2.6/bin/python2.6" -ln -s /opt/python2.6/bin/python2.6 /usr/bin/python2.6 - -# Install uwsgi -# ========= - -version=uwsgi-1.2.3 -cd /opt/ -wget http://projects.unbit.it/downloads/$version.tar.gz -tar -zxvf $version.tar.gz -mv $version/ uwsgi/ -cd uwsgi/ - -# build using python 2.6 -# ====================== - -python2.6 setup.py build -python2.6 uwsgiconfig.py --build -useradd uwsgi - -# create and own uwsgi log -# ======================== -# Note this log will need emptying from time to time - -echo " ">/var/log/uwsgi.log -chown uwsgi /var/log/uwsgi.log - -# Install web2py -# ========== - -cd /opt -mkdir web-apps -cd web-apps -wget http://www.web2py.com/examples/static/web2py_src.zip -unzip web2py_src.zip - -# set the ownership for web2py application to uwsgi -# ================================================= - -cd web2py -chown -R uwsgi applications -chmod -R u+wx applications - -# Now install nginx -# ================= - -cd /etc/yum.repos.d -echo "[nginx]">nginx.repo - -echo "baseurl=http://nginx.org/packages/centos/5/"$basearch$"/">>nginx.repo -echo "gpgcheck=0">>nginx.repo -echo "enabled=1">>nginx.repo -yum install nginx - -# We don't want the defaults, so remove them -# ========================================== - -cd /etc/nginx/conf.d -mv default.conf default.conf.o -mv example_ssl.conf example_ssl.conf.o - -# The following configuration files are also needed -# The options for uwsgi are in the following file. -# It should be placed in /etc/uwsgi. Other options could be included. - -echo """ -[uwsgi] - -uuid=uwsgi -pythonpath = /opt/web-apps/web2py -module = wsgihandler -socket=127.0.0.1:9001 -harakiri 60 -harakiri-verbose -enable-threads -daemonize = /var/log/uwsgi.log -""" > /etc/uwsgi/uwsgi_for_nginx.conf - -# The next configuration file is for nginx, and goes in /etc/nginx/conf.d -# It serves the static diretory of applications directly. -# I have not set up ssl because I access web2py admin by using ssh -# tunneling and the web2py rocket server. -# It should be straightforward to set up the ssl server however. - -echo """ -server { - listen 80; - server_name $hostname; - location ~* /(\w+)/static/ { - root /opt/web-apps/web2py/applications/; - } - location / { - uwsgi_pass 127.0.0.1:9001; - include uwsgi_params; - } -} - -#server { -# listen 443; -# server_name $hostname; -# ssl on; -# ssl_certificate /etc/nginx/ssl/web2py.crt; -# ssl_certificate_key /etc/nginx/ssl/web2py.key; -# location uwsgi_pass 127.0.0.1:9001; -# include uwsgi_params; -# uwsgi_param UWSGI_SCHEME $scheme; -#} -""" > /etc/nginx/conf.d/web2py.conf - -# The final configuration file is only needed if you want to run -# uwsgi as a service. It should be placed in /etc/init.d - - -echo """ - -#!/bin/bash -# uwsgi - Use uwsgi to run python and wsgi web apps. -# -# chkconfig: - 85 15 -# description: Use uwsgi to run python and wsgi web apps. -# processname: uwsgi -# author: Roman Vasilyev - -# Source function library. -. /etc/rc.d/init.d/functions - -########################### -PATH=/etc/uwsgi-python:/sbin:/bin:/usr/sbin:/usr/bin -PYTHONPATH=/home/www-data/web2py -MODULE=wsgihandler -prog=/etc/uwsgi-python/uwsgi -OWNER=uwsgi -# OWNER=nginx ¿? -NAME=uwsgi -DESC=uwsgi -DAEMON_OPTS="-s 127.0.0.1:9001 -M 4 -t 30 -A 4 -p 16 -b 32768 -d /var/log/$NAME.log --pidfile /var/run/$NAME.pid --uid $OWNER --ini-paste /etc/uwsgi-python/uwsgi_for_nginx.conf" -############################## - -[ -f /etc/sysconfig/uwsgi ] && . /etc/sysconfig/uwsgi - -lockfile=/var/lock/subsys/uwsgi - -start () { - echo -n "Starting $DESC: " - daemon $prog $DAEMON_OPTS - retval=$? - echo - [ $retval -eq 0 ] && touch $lockfile - return $retval -} - -stop () { - echo -n "Stopping $DESC: " - killproc $prog - retval=$? - echo - [ $retval -eq 0 ] && rm -f $lockfile - return $retval -} - -reload () { - echo "Reloading $NAME" - killproc $prog -HUP - RETVAL=$? - echo -} - -force-reload () { - echo "Reloading $NAME" - killproc $prog -TERM - RETVAL=$? - echo -} - -restart () { - stop - start -} - -rh_status () { - status $prog -} - -rh_status_q() { - rh_status >/dev/null 2>&1 -} - -case "$1" in - start) - rh_status_q && exit 0 - $1 - ;; - stop) - rh_status_q || exit 0 - $1 - ;; - restart|force-reload) - $1 - ;; - reload) - rh_status_q || exit 7 - $1 - ;; - status) - rh_status - ;; - *) - echo "Usage: $0 {start|stop|restart|reload|force-reload|status}" >&2 - exit 2 - ;; - esac - exit 0 - -""" > /etc/init.d/uwsgi_nginx +#!/bin/bash + +# Script for installing Web2py with Nginx and Uwsgi on Centos 5 +# Created By Hutchinson +# Modified by spametki +# License: BSD + +# It was originally posted in this web2py-users group thread: +# https://groups.google.com/forum/?fromgroups#!topic/web2py/O4c4Jfr18tM + +# There are lots of subtleties of ownership, and one has to take care +# when installing python 2.7 not to stop the systems python2.4 from working. + +# NOTE: The only thing that should need changing for +# each installation is the $BASEARCH (base architecture) of the machine. +# This is determined by doing uname -i. This is needed for the nginx installation. + +# Retrieve base architecture +BASEARCH=$(uname -i) + +echo 'Install development tools (it should take a while)' +yum install gcc gdbm-devel readline-devel ncurses-devel zlib-devel \ +bzip2-devel sqlite-devel db4-devel openssl-devel tk-devel bluez-libs-devel + +echo 'Install python 2.7 without overwriting python 2.4 (no, really, this will take a while too)' + +VERSION=2.7.3 +cd ~ +curl -O http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz +tar -xvfz Python-2.7.3.tgz +cd Python-2.7.3 +./configure --prefix=/opt/python2.7 --with-threads --enable-shared +make + +echo 'The altinstall ensures that python2.4 is left okay' +make altinstall +echo "/opt/python2.7/lib">/etc/ld.so.conf.d/opt-python2.7.conf +ldconfig + +echo 'Create alias so that python 2.7 can be run with python2.7' +alias -p python2.7="/opt/python2.7/bin/python2.7" +ln -s /opt/python2.7/bin/python2.7 /usr/bin/python2.7 + +echo 'Install uwsgi' + +version=uwsgi-1.2.3 +cd ~ +curl -O http://projects.unbit.it/downloads/$version.tar.gz +tar -zxvf $version.tar.gz +mkdir /opt/uwsgi-python +cp -R ./$version/* /opt/uwsgi-python/* +cd /opt/uwsgi-python + +echo 'build using python 2.7' +python2.7 setup.py build +python2.7 uwsgiconfig.py --build +useradd uwsgi + +echo 'Create and own uwsgi log' +# Note this log will need emptying from time to time + +touch /var/log/uwsgi.log +chown uwsgi /var/log/uwsgi.log + +echo 'Install web2py' + +cd /opt +mkdir ./web-apps +cd ./web-apps +curl -O http://www.web2py.com/examples/static/web2py_src.zip +unzip web2py_src.zip + +echo 'Set the ownership for web2py application to uwsgi' +cd /opt/web-apps/web2py +chown -R uwsgi /opt/web-apps/web2py +chmod -R u+rwx ./applications + +echo 'Now install nginx' +cd /etc/yum.repos.d +echo "[nginx]">nginx.repo + +echo "baseurl=http://nginx.org/packages/centos/5/$BASEARCH/">>nginx.repo +echo "gpgcheck=0">>nginx.repo +echo "enabled=1">>nginx.repo +yum install nginx + +echo "We don't want the defaults, so remove them" +cd /etc/nginx/conf.d +mv default.conf default.conf.o +mv example_ssl.conf example_ssl.conf.o + +echo ' +The following configuration files are also needed +The options for uwsgi are in the following file. +Other options could be included. +' + +echo 'uwsgi_for_nginx.conf' + +echo ' +[uwsgi] +uuid=uwsgi +pythonpath = /opt/web-apps/web2py +module = wsgihandler +socket=127.0.0.1:9001 +harakiri 60 +harakiri-verbose +enable-threads +daemonize = /var/log/uwsgi.log +' > /opt/uwsgi-python/uwsgi_for_nginx.conf + +chmod 755 /opt/uwsgi-python/uwsgi_for_nginx.conf + +echo ' +The next configuration file is for nginx, and goes in /etc/nginx/conf.d +It serves the static directory of applications directly. I have not set up +ssl because I access web2py admin by using ssh tunneling and the web2py rocket server. +It should be straightforward to set up the ssl server however. +' + +echo 'web2py.conf' + +echo ' +server { + listen 80; + server_name $hostname; + location ~* /(\w+)/static/ { + root /opt/web-apps/web2py/applications/; + } + location / { + uwsgi_pass 127.0.0.1:9001; + include uwsgi_params; + } +} + +server { + listen 443; + server_name $hostname; + ssl on; + ssl_certificate /etc/nginx/ssl/web2py.cert; + ssl_certificate_key /etc/nginx/ssl/web2py.key; + location / { + uwsgi_pass 127.0.0.1:9001; + include uwsgi_params; + uwsgi_param UWSGI_SCHEME $scheme; + } +} +' > /etc/nginx/conf.d/web2py.conf + + +echo 'Auto-signed ssl certs' +mkdir /etc/nginx/ssl +echo "creating a self signed certificate" +echo "==================================" +openssl genrsa 1024 > /etc/nginx/ssl/web2py.key +chmod 400 /etc/nginx/ssl/web2py.key +openssl req -new -x509 -nodes -sha1 -days 365 -key /etc/nginx/ssl/web2py.key > /etc/nginx/ssl/web2py.cert +openssl x509 -noout -fingerprint -text < /etc/nginx/ssl/web2py.cert > /etc/nginx/ssl/web2py.info + +echo 'uwsgi as service' + +echo ' +#!/bin/bash + +# uwsgi - Use uwsgi to run python and wsgi web apps. +# +# chkconfig: - 85 15 +# description: Use uwsgi to run python and wsgi web apps. +# processname: uwsgi + +# author: Roman Vasilyev + +# Source function library. +. /etc/rc.d/init.d/functions + +########################### +PATH=/opt/uwsgi-python:/sbin:/bin:/usr/sbin:/usr/bin +PYTHONPATH=/home/www-data/web2py +MODULE=wsgihandler +PROG=/opt/uwsgi-python/uwsgi +OWNER=uwsgi +NAME=uwsgi +DESC=uwsgi +DAEMON_OPTS="-s 127.0.0.1:9001 -M 4 -t 30 -A 4 -p 16 -b 32768 -d \ +/var/log/$NAME.log --pidfile /var/run/$NAME.pid --uid $OWNER \ +--ini-paste /opt/uwsgi-python/uwsgi_for_nginx.conf" +############################## + +[ -f /etc/sysconfig/uwsgi ] && . /etc/sysconfig/uwsgi + +lockfile=/var/lock/subsys/uwsgi + +start () { + echo -n "Starting $DESC: " + daemon $PROG $DAEMON_OPTS + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval +} + +stop () { + echo -n "Stopping $DESC: " + killproc $PROG + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +reload () { + echo "Reloading $NAME" + killproc $PROG -HUP + RETVAL=$? + echo +} + +force-reload () { + echo "Reloading $NAME" + killproc $PROG -TERM + RETVAL=$? + echo +} + +restart () { + stop + start +} + +rh_status () { + status $PROG +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart|force-reload) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + status) + rh_status + ;; + *) + echo "Usage: $0 {start|stop|restart|reload|force-reload|status}" >&2 + exit 2 + ;; + esac + exit 0 +' > /etc/init.d/uwsgi + +chmod 755 /etc/init.d/uwsgi +chkconfig --add uwsgi +chkconfig uwsgi on + +echo ' +You can test it with + +service uwsgi start + +and stop it similarly. + +Nginx has automatically been set up as a service +if you want to start it run + +service nginx start + +You should find the web2py welcome app will be displayed at your web address. +As they are both services, they should automatically start on a system reboot. +If you already had a server running, such as apache, you would need to stop +that and turn its service off before running nginx. +' + +echo 'Turning off apache service' +service httpd stop +chkconfig httpd off + +echo ' +Installation complete. You might want to restart your server running + +reboot + +as superuser +' From 355d040900e2844cafcc8b946a62aa9174367341 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 10:43:35 -0500 Subject: [PATCH 020/155] fixed json and xml search in auth.wiki() --- VERSION | 2 +- gluon/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index ff34f716..4c35a529 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 10:37:01) dev +Version 2.00.0 (2012-07-29 10:43:31) dev diff --git a/gluon/tools.py b/gluon/tools.py index 49368fb1..54c60486 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4557,7 +4557,7 @@ class Wiki(object): db = self.auth.db count = db.wiki_tag.wiki_page.count() fields = [db.wiki_page.id,db.wiki_page.slug, - db.wiki_page.title,db.wiki_page.tags,count] + db.wiki_page.title,db.wiki_page.tags] if preview: fields.append(db.wiki_page.body) pages = db(db.wiki_page.id==db.wiki_tag.wiki_page) \ From 787c14436e22c70dbfd6e12f3333ecf4304fb3e6 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 13:57:01 -0500 Subject: [PATCH 021/155] router patch, thanks Jonathan --- VERSION | 2 +- gluon/rewrite.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 4c35a529..b4485c6d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 10:43:31) dev +Version 2.00.0 (2012-07-29 13:56:56) dev diff --git a/gluon/rewrite.py b/gluon/rewrite.py index abf94764..72454520 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -47,7 +47,7 @@ def _router_default(): exclusive_domain = False, map_hyphen = False, acfe_match = r'\w+$', # legal app/ctlr/fcn/ext - file_match = r'(\w+[-=./]?)+$', # legal file (path) name + file_match = r'([-+=@$%\w]+[./]?)+$', # legal static file (path) name args_match = r'([\w@ -]+[=.]?)*$', # legal arg in args ) return router @@ -900,6 +900,7 @@ class MapUrlIn(object): self.map_hyphen = self.router.map_hyphen self.exclusive_domain = self.router.exclusive_domain self._acfe_match = self.router._acfe_match + self.file_match = self.router.file_match self._file_match = self.router._file_match self._args_match = self.router._args_match @@ -954,7 +955,17 @@ class MapUrlIn(object): if self.controller != 'static': return None file = '/'.join(self.args) - if not self.router._file_match.match(file): + if len(self.args) == 0: + bad_static = True # require a file name + elif '/' in self.file_match: + # match the path + bad_static = not self.router._file_match.match(file) + else: + # match path elements + bad_static = False + for name in self.args: + bad_static = bad_static or name in ('', '.', '..') or not self.router._file_match.match(name) + if bad_static: raise HTTP(400, thread.routes.error_message % 'invalid request', web2py_error='invalid static file') # From 86986b5ae1f63f964388bbba176a52b78bc48a04 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 16:21:45 -0500 Subject: [PATCH 022/155] improved router, thanks Jonathan --- VERSION | 2 +- gluon/rewrite.py | 7 +++--- gluon/tests/test_router.py | 44 ++++++++++++++++++++++++++++++++++++++ gluon/tests/test_routes.py | 1 + router.example.py | 10 +++++---- 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index b4485c6d..9f29813a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 13:56:56) dev +Version 2.00.0 (2012-07-29 16:21:39) dev diff --git a/gluon/rewrite.py b/gluon/rewrite.py index 72454520..25b2250a 100644 --- a/gluon/rewrite.py +++ b/gluon/rewrite.py @@ -46,9 +46,9 @@ def _router_default(): domains = None, exclusive_domain = False, map_hyphen = False, - acfe_match = r'\w+$', # legal app/ctlr/fcn/ext - file_match = r'([-+=@$%\w]+[./]?)+$', # legal static file (path) name - args_match = r'([\w@ -]+[=.]?)*$', # legal arg in args + acfe_match = r'\w+$', # legal app/ctlr/fcn/ext + file_match = r'([-+=@$%\w]+[./]?)+$', # legal static subpath + args_match = r'([\w@ -]+[=.]?)*$', # legal arg in args ) return router @@ -966,6 +966,7 @@ class MapUrlIn(object): for name in self.args: bad_static = bad_static or name in ('', '.', '..') or not self.router._file_match.match(name) if bad_static: + log_rewrite('bad static path=%s' % file) raise HTTP(400, thread.routes.error_message % 'invalid request', web2py_error='invalid static file') # diff --git a/gluon/tests/test_router.py b/gluon/tests/test_router.py index 50b2e2c1..d3822919 100644 --- a/gluon/tests/test_router.py +++ b/gluon/tests/test_router.py @@ -13,6 +13,7 @@ if os.path.isdir('gluon'): sys.path.append(os.path.realpath('gluon')) # running from web2py base else: sys.path.append(os.path.realpath('../')) # running from gluon/tests/ + os.environ['web2py_path'] = os.path.realpath('../../') # for settings from rewrite import load, filter_url, filter_err, get_effective_router, map_url_out from html import URL @@ -794,6 +795,49 @@ class TestRouter(unittest.TestCase): self.assertEqual(filter_err(399), 399) self.assertEqual(filter_err(400), 400) + def test_router_static_path(self): + ''' + Test validation of static paths + Stock pattern: file_match = r'([-+=@$%\w]+[./]?)+$' + + ''' + load(rdict=dict()) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to--/static'), "%s/applications/welcome/static/path/to--/static" % root) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/==to--/static'), "%s/applications/welcome/static/path/==to--/static" % root) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/-+=@$%/static'), "%s/applications/welcome/static/path/-+=@$%%/static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/.static') + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/s..tatic') + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to//static') + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/#static') + + router_static = dict( + BASE = dict( + file_match = r'([-+=@$%#\w]+[./]?)+$', # legal static path + ), + ) + load(rdict=router_static) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/#static'), "%s/applications/welcome/static/path/to/#static" % root) + + router_static = dict( + BASE = dict( + file_match = r'[-+=@$%#.\w]+$', # legal static path element + ), + ) + load(rdict=router_static) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to--/static'), "%s/applications/welcome/static/path/to--/static" % root) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/==to--/static'), "%s/applications/welcome/static/path/==to--/static" % root) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/-+=@$%/static'), "%s/applications/welcome/static/path/-+=@$%%/static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to//static') + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/#static'), "%s/applications/welcome/static/path/to/#static" % root) + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/./static') + self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/../static') + self.assertEqual(filter_url('http://domain.com/welcome/static/path/.../static'), "%s/applications/welcome/static/path/.../static" % root) + self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/.static'), "%s/applications/welcome/static/path/to/.static" % root) + def test_router_args(self): ''' Test URL args parsing/generation diff --git a/gluon/tests/test_routes.py b/gluon/tests/test_routes.py index 7a0672db..c3b171d6 100644 --- a/gluon/tests/test_routes.py +++ b/gluon/tests/test_routes.py @@ -13,6 +13,7 @@ if os.path.isdir('gluon'): sys.path.append(os.path.realpath('gluon')) # running from web2py base else: sys.path.append(os.path.realpath('../')) # running from gluon/tests/ + os.environ['web2py_path'] = os.path.realpath('../../') # for settings from rewrite import load, filter_url, filter_err, get_effective_router, regex_filter_out, regex_select from html import URL diff --git a/router.example.py b/router.example.py index cc016a04..06710770 100644 --- a/router.example.py +++ b/router.example.py @@ -64,7 +64,9 @@ # map_static: By default, the default application is not stripped from static URLs. # Set map_static=True to override this policy. # acfe_match: regex for valid application, controller, function, extension /a/c/f.e -# file_match: regex for valid file (used for static file names) +# file_match: regex for valid subpath (used for static file paths) +# if file_match does not contain '/', it is uses to validate each element of a static file subpath, +# rather than the entire subpath. # args_match: regex for valid args # This validation provides a measure of security. # If it is changed, the application perform its own validation. @@ -84,9 +86,9 @@ # root_static = ['favicon.ico', 'robots.txt'], # domains = None, # map_hyphen = False, -# acfe_match = r'\w+$', # legal app/ctlr/fcn/ext -# file_match = r'(\w+[-=./]?)+$', # legal file (path) name -# args_match = r'([\w@ -]+[=.]?)+$', # legal arg in args +# acfe_match = r'\w+$', # legal app/ctlr/fcn/ext +# file_match = r'([-+=@$%\w]+[./]?)+$', # legal static subpath +# args_match = r'([\w@ -]+[=.]?)+$', # legal arg in args # ) # # See rewrite.map_url_in() and rewrite.map_url_out() for implementation details. From 3e10bc7f16dc17904a2fd3687a4491bcd2bb22a4 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 16:52:11 -0500 Subject: [PATCH 023/155] auth.wiki supports load --- VERSION | 2 +- gluon/tools.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 9f29813a..41a60448 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 16:21:39) dev +Version 2.00.0 (2012-07-29 16:52:05) dev diff --git a/gluon/tools.py b/gluon/tools.py index 54c60486..54cda9cb 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4453,6 +4453,8 @@ class Wiki(object): match = self.regex_redirect.match(page.body) if match: redirect(match.group(1)) return dict(content=XML(page.html)) + elif current.request.extension == 'load': + return page.html if page else '' else: if not page: raise HTTP(404) @@ -4565,17 +4567,16 @@ class Wiki(object): *fields,**dict(orderby=orderby or ~count, groupby=db.wiki_page.id, limitby=limitby)) - if request.extension=='html': + if request.extension in ('html','load'): if not pages: content.append(DIV(T("No results",_class='w2p_wiki_form'))) def link(t): return A(t,_href=URL(args='_search',vars=dict(tags=t))) - items = [DIV(H3(A(p.wiki_page.title, - _href=URL(args=p.wiki_page.slug))), - MARKMIN(first_paragraph(p.wiki_page.body)) \ + items = [DIV(H3(A(p.title,_href=URL(args=p.slug))), + MARKMIN(first_paragraph(p.body)) \ if preview else '', SPAN(*[link(t.strip()) for t in \ - p.wiki_page.tags.split(',') \ + p.tags.split(',') \ if t.strip()]), _class='w2p_wiki_tags') for p in pages] @@ -4584,7 +4585,9 @@ class Wiki(object): cloud=False content = [p.as_dict() for p in pages] elif cloud: - content.append(self.cloud()['content']) + content.append(self.cloud()['content']) + if request.extension=='load': + return content return dict(content=content) def cloud(self): db = self.auth.db From 78f97cb53d26c48345ae478f07c514893fa639c7 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 17:37:32 -0500 Subject: [PATCH 024/155] wiki force_prefix --- VERSION | 2 +- gluon/tools.py | 60 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/VERSION b/VERSION index 41a60448..f4b46525 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 16:52:05) dev +Version 2.00.0 (2012-07-29 17:37:29) dev diff --git a/gluon/tools.py b/gluon/tools.py index 54cda9cb..8fa6d4ee 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -2778,13 +2778,16 @@ class Auth(object): returns the group_id of the group uniquely associated to this user i.e. role=user:[user_id] """ + return self.id_group(self.user_group_role(user_id)) + + def user_group_role(self, user_id=None): if user_id: user = self.settings.table_user[user_id] else: user = self.user - role = self.settings.create_user_groups % user - return self.id_group(role) - + return self.settings.create_user_groups % user + + def has_membership(self, group_id=None, user_id=None, role=None): """ checks if user is member of group_id or role @@ -4382,14 +4385,18 @@ def first_paragraph(mm): return '' class Wiki(object): + everybody = 'everybody' regex_redirect = re.compile('redirect\s+(\w+\://\S+)\s*') - def __init__(self,auth,env=None,automenu=True,render='markmin'): + def __init__(self,auth,env=None,automenu=True,render='markmin', + manage_permissions=False,force_prefix=None): self.env = env or {} if render == 'markmin': render = lambda t,env=self.env: \ MARKMIN(t.body,url=True,environment=env).xml() self.auth = auth self.automenu = automenu + perms = self.manage_permissions = manage_permissions + self.force_prefix = force_prefix db = auth.db db.define_table( 'wiki_page', @@ -4398,7 +4405,12 @@ class Wiki(object): Field('title',unique=True), Field('body','text',notnull=True), Field('menu'), - Field('tags'), + Field('tags','list:string'), + Field('can_read','list:string',writable=perms,readable=perms, + default=[Wiki.everybody]), + Field('can_edit','list:string',writable=perms,readable=perms, + default=[Wiki.everybody]), + Field('changelog'), Field('html','text',readable=False,writable=False,compute=render), auth.signature,format='%(title)s') db.define_table( @@ -4413,14 +4425,13 @@ class Wiki(object): Field('file','upload',required=True), auth.signature,format='%(title)s') def update_tags_insert(page,id,db=db): - print page - for tag in page.tags.split(','): + for tag in page.tags: tag = tag.strip().lower() if tag: db.wiki_tag.insert(name=tag,wiki_page=id) def update_tags_update(dbset,page,db=db): page = dbset.select().first() db(db.wiki_tag.wiki_page==page.id).delete() - for tag in page.tags.split(','): + for tag in page.tags: tag = tag.strip().lower() if tag: db.wiki_tag.insert(name=tag,wiki_page=page.id) db.wiki_page._after_insert.append(update_tags_insert) @@ -4441,10 +4452,20 @@ class Wiki(object): return self.cloud() else: return self.read(current.request.args(0) or 'index') + def check_permission(self,page,field='can_read'): + if not self.manage_permissions: + return True + groups = set(page.get(field,None) or []) + if Wiki.everybody in groups or \ + self.auth.user and groups.intersect(self.auth.user_groups.values()): + return True + return False def read(self,slug): if slug in '_cloud': return self.cloud() page = self.auth.db.wiki_page(slug=slug) + #if page and not self.check_permission(page,'can_read'): + # raise HTTP(401) if current.request.extension == 'html': if not page: url = URL(args=('_edit',slug)) @@ -4465,7 +4486,7 @@ class Wiki(object): tags=page.tags, created_on=page.created_on, modified_on=page.modified_on) - def check_authorization(self,role='wiki_editor',act=False): + def check_editor(self,role='wiki_editor',act=False): if not self.auth.user: if not act: return False redirect(self.auth.settings.login_url) @@ -4474,22 +4495,29 @@ class Wiki(object): raise HTTP(401, "Not Authorized") return True def edit(self,slug): - self.check_authorization() + self.check_editor() auth = self.auth db = auth.db page = db.wiki_page(slug=slug) title_guess = ' '.join(c.capitalize() for c in slug.split('-')) - db.wiki_page.title.default = title_guess - db.wiki_page.slug.default = slug - db.wiki_page.menu.default = slug - db.wiki_page.body.default = '## %s\n\npage content' % title_guess + if not page: + if not slug.startswith(self.force_prefix): + raise HTTP(401) + db.wiki_page.can_read = [Wiki.everybody] + db.wiki_page.can_edit = [auth.user_group_role()] + db.wiki_page.title.default = title_guess + db.wiki_page.slug.default = slug + db.wiki_page.menu.default = slug + db.wiki_page.body.default = '## %s\n\npage content' % title_guess + elif not self.check_permission(page,'can_edit'): + raise HTTP(401) form = SQLFORM(db.wiki_page,page,deletable=True,showid=False).process() if form.accepted: current.session.flash = 'page created' redirect(URL(args=slug)) return dict(content=form) def pages(self): - self.check_authorization() + self.check_editor() self.auth.db.wiki_page.title.represent = lambda title,row: \ A(title,_href=URL(args=row.slug)) content=SQLFORM.smartgrid( @@ -4528,7 +4556,7 @@ class Wiki(object): request.args(0)==row.slug, URL(controller,function,args=row.slug), subtree)) - if self.check_authorization(act=False): + if self.check_editor(act=False): submenu = [] if URL() == URL(controller,function) and \ not str(request.args(0)).startswith('_'): From aef4f1cc9acfdab2f74164ed271d86974a548de8 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 18:09:41 -0500 Subject: [PATCH 025/155] wiki force_prefix again --- VERSION | 2 +- gluon/tools.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index f4b46525..da8ebdd6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 17:37:29) dev +Version 2.00.0 (2012-07-29 18:09:36) dev diff --git a/gluon/tools.py b/gluon/tools.py index 8fa6d4ee..95855401 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4396,7 +4396,7 @@ class Wiki(object): self.auth = auth self.automenu = automenu perms = self.manage_permissions = manage_permissions - self.force_prefix = force_prefix + self.force_prefix = force_prefix or '' db = auth.db db.define_table( 'wiki_page', @@ -4502,7 +4502,8 @@ class Wiki(object): title_guess = ' '.join(c.capitalize() for c in slug.split('-')) if not page: if not slug.startswith(self.force_prefix): - raise HTTP(401) + session.flash='slug bust have "%s" prefix' % self.force_prefix + redirect(URL(args=('_edit',force_prefix+slug))) db.wiki_page.can_read = [Wiki.everybody] db.wiki_page.can_edit = [auth.user_group_role()] db.wiki_page.title.default = title_guess From f0be6092ff5ffbe5c22fceed270e2d431734ac61 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 20:55:01 -0500 Subject: [PATCH 026/155] some blogging capabilities to auth.wiki --- VERSION | 2 +- gluon/tools.py | 62 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/VERSION b/VERSION index da8ebdd6..41bdca17 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 18:09:36) dev +Version 2.00.0 (2012-07-29 20:54:57) dev diff --git a/gluon/tools.py b/gluon/tools.py index 95855401..b3696aac 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4386,6 +4386,7 @@ def first_paragraph(mm): class Wiki(object): everybody = 'everybody' + rows_page = 25 regex_redirect = re.compile('redirect\s+(\w+\://\S+)\s*') def __init__(self,auth,env=None,automenu=True,render='markmin', manage_permissions=False,force_prefix=None): @@ -4425,33 +4426,42 @@ class Wiki(object): Field('file','upload',required=True), auth.signature,format='%(title)s') def update_tags_insert(page,id,db=db): - for tag in page.tags: + for tag in page.tags or []: tag = tag.strip().lower() if tag: db.wiki_tag.insert(name=tag,wiki_page=id) def update_tags_update(dbset,page,db=db): page = dbset.select().first() db(db.wiki_tag.wiki_page==page.id).delete() - for tag in page.tags: + for tag in page.tags or []: tag = tag.strip().lower() if tag: db.wiki_tag.insert(name=tag,wiki_page=page.id) db.wiki_page._after_insert.append(update_tags_insert) db.wiki_page._after_update.append(update_tags_update) def __call__(self): + request = current.request if self.automenu: - current.response.menu = self.menu(current.request.controller, - current.request.function) - if current.request.args(0)=='_edit': - return self.edit(current.request.args(1) or 'index') - elif current.request.args(0)=='_pages': + current.response.menu = self.menu(request.controller, + request.function) + if request.args(0)=='_edit': + return self.edit(request.args(1) or 'index') + elif request.args(0)=='_pages': return self.pages() - elif current.request.args(0)=='_media': - return self.media(current.request.args(1,cast=int)) - elif current.request.args(0)=='_search': + elif request.args(0)=='_media': + return self.media(request.args(1,cast=int)) + elif request.args(0)=='_search': return self.search() - elif current.request.args(0)=='_cloud': + elif request.args(0)=='_recent': + ipage = int(request.vars.page or 0) + query = self.auth.db.wiki_page.created_by==request.args(1,cast=int) + return self.search(query=query, + orderby=~self.auth.db.wiki_page.created_on, + limitby=(ipage*self.rows_page, + (ipage+1)*self.rows_page), + ) + elif request.args(0)=='_cloud': return self.cloud() else: - return self.read(current.request.args(0) or 'index') + return self.read(request.args(0) or 'index') def check_permission(self,page,field='can_read'): if not self.manage_permissions: return True @@ -4543,8 +4553,9 @@ class Wiki(object): def menu(self,controller='default',function='index'): db = self.auth.db request = current.request - rows = db().select(db.wiki_page.menu,db.wiki_page.title,db.wiki_page.slug, - orderby = db.wiki_page.menu) + rows = db((db.wiki_page.menu!=None)|(db.wiki_page.menu!=''))\ + .select(db.wiki_page.menu,db.wiki_page.title,db.wiki_page.slug, + orderby = db.wiki_page.menu) menu = [] tree = {'.':menu} for row in rows: @@ -4572,11 +4583,11 @@ class Wiki(object): URL(controller,function,args=('_cloud')))) menu.append((current.T('[Wiki]'),None,None,submenu)) return menu - def search(self,tags=None,cloud=True,preview=True, + def search(self,tags=None,query=None,cloud=True,preview=True, limitby=(0,100),orderby=None): + request = current.request content = CAT() - if not tags: - request = current.request + if tags is None and query is None: form = SQLFORM.factory(Field('tags',requires=IS_NOT_EMPTY(), default=request.vars.tags, label=current.T('Search'))) @@ -4584,18 +4595,20 @@ class Wiki(object): if request.vars: tags = [v.strip() for v in request.vars.tags.split(',')] tags = [v for v in tags if v] - if tags: + if tags or not query is None: db = self.auth.db count = db.wiki_tag.wiki_page.count() fields = [db.wiki_page.id,db.wiki_page.slug, db.wiki_page.title,db.wiki_page.tags] if preview: fields.append(db.wiki_page.body) - pages = db(db.wiki_page.id==db.wiki_tag.wiki_page) \ - (db.wiki_tag.name.belongs(tags)).select( - *fields,**dict(orderby=orderby or ~count, - groupby=db.wiki_page.id, - limitby=limitby)) + if query is None: + query = (db.wiki_page.id==db.wiki_tag.wiki_page)&\ + (db.wiki_tag.name.belongs(tags)) + pages = db(query).select( + *fields,**dict(orderby=orderby or ~count, + groupby=db.wiki_page.id, + limitby=limitby)) if request.extension in ('html','load'): if not pages: content.append(DIV(T("No results",_class='w2p_wiki_form'))) @@ -4605,8 +4618,7 @@ class Wiki(object): MARKMIN(first_paragraph(p.body)) \ if preview else '', SPAN(*[link(t.strip()) for t in \ - p.tags.split(',') \ - if t.strip()]), + p.tags or [] if t.strip()]), _class='w2p_wiki_tags') for p in pages] content.append(DIV(_class='w2p_wiki_pages',*items)) From cdcc97e8e32d221fb7c2999506afc6fc14cf1045 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 21:02:24 -0500 Subject: [PATCH 027/155] working auth.wiki on security --- VERSION | 2 +- gluon/tools.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 41bdca17..4146c30a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 20:54:57) dev +Version 2.00.0 (2012-07-29 21:02:21) dev diff --git a/gluon/tools.py b/gluon/tools.py index b3696aac..94501c82 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -4378,11 +4378,6 @@ class Expose(object): H3('Files'), self.table_files()).xml() -def first_paragraph(mm): - mm = mm.replace('\r','') - ps = [p for p in mm.split('\n\n') if not p.startswith('#') and p.strip()] - if ps: return ps[0] - return '' class Wiki(object): everybody = 'everybody' @@ -4437,6 +4432,7 @@ class Wiki(object): if tag: db.wiki_tag.insert(name=tag,wiki_page=page.id) db.wiki_page._after_insert.append(update_tags_insert) db.wiki_page._after_update.append(update_tags_update) + def __call__(self): request = current.request if self.automenu: @@ -4462,6 +4458,16 @@ class Wiki(object): return self.cloud() else: return self.read(request.args(0) or 'index') + + def first_paragraph(self,mm): + if self.manage_permissions: + return '' + mm = mm.replace('\r','') + ps = [p for p in mm.split('\n\n') \ + if not p.startswith('#') and p.strip()] + if ps: return ps[0] + return '' + def check_permission(self,page,field='can_read'): if not self.manage_permissions: return True @@ -4615,7 +4621,7 @@ class Wiki(object): def link(t): return A(t,_href=URL(args='_search',vars=dict(tags=t))) items = [DIV(H3(A(p.title,_href=URL(args=p.slug))), - MARKMIN(first_paragraph(p.body)) \ + MARKMIN(self.first_paragraph(p.body)) \ if preview else '', SPAN(*[link(t.strip()) for t in \ p.tags or [] if t.strip()]), From e912f1084cab3581dcf37bd8aa4ec9bc7380cb17 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Sun, 29 Jul 2012 21:55:51 -0500 Subject: [PATCH 028/155] issue 902, admin can peek private, thanks Alan --- VERSION | 2 +- applications/admin/views/default/design.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 4146c30a..2a8e5ef0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 21:02:21) dev +Version 2.00.0 (2012-07-29 21:55:47) dev diff --git a/applications/admin/views/default/design.html b/applications/admin/views/default/design.html index 9ef577e8..8597d14a 100644 --- a/applications/admin/views/default/design.html +++ b/applications/admin/views/default/design.html @@ -360,7 +360,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi {{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}} - {{=filename}} + {{=peekfile('private',file, dict(id="private"))}} {{ pass From bac7e433c67be5bd0bb7db9dacc9d65435b8fe9d Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 30 Jul 2012 16:25:35 -0500 Subject: [PATCH 029/155] possible fix for issue 188, thanks Marin --- VERSION | 2 +- gluon/sqlhtml.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 2a8e5ef0..fbbe1d31 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-29 21:55:47) dev +Version 2.00.0 (2012-07-30 16:25:32) dev diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index f77e35e5..1ecf43fd 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -1229,6 +1229,7 @@ class SQLFORM(FORM): continue # do not update if password was not changed elif field.type == 'upload': f = self.vars[fieldname] + f = f or self.table[fieldname].default or f fd = '%s__delete' % fieldname if f == '' or f is None: if self.vars.get(fd, False) or not self.record: @@ -1240,9 +1241,8 @@ class SQLFORM(FORM): elif hasattr(f, 'file'): (source_file, original_filename) = (f.file, f.filename) elif isinstance(f, (str, unicode)): - ### do not know why this happens, it should not - (source_file, original_filename) = \ - (cStringIO.StringIO(f), 'file.txt') + # warning: possible IOError exception + (source_file, original_filename) = (open(f, 'rb'), f) newfilename = field.store(source_file, original_filename, field.uploadfolder) # this line is for backward compatibility only From 041e0637bc41e77edf94282e6b64fad89384f29e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 30 Jul 2012 20:23:05 -0500 Subject: [PATCH 030/155] markmin and languages autolinks, thanks Vladyslav --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 185 +++++++++++++++++++------- gluon/languages.py | 71 ++++++---- 3 files changed, 181 insertions(+), 77 deletions(-) diff --git a/VERSION b/VERSION index fbbe1d31..fbcfe843 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-30 16:25:32) dev +Version 2.00.0 (2012-07-30 20:23:00) dev diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 2c6da1e7..893c4950 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -71,15 +71,22 @@ a list with tables in it: -----------:blockquoteclass[blockquoteid] This this a new paragraph -with a table. Table has header and footer: +with a table. Table has header, footer, sections, odd and even rows: ------------------------------- **Title 1**|**Title 2**|**Title 3** ============================== data 1 | data 2 | 2.00 -data 4 |data5(long)| 23.00 - |data 8 | 33.50 +data 3 |data4(long)| 23.00 + |data 5 | 33.50 ============================== -Total: | 3 items | 58.50 +New section|New data | 5.00 +data 1 |data2(long)|100.45 + |data 3 | 12.50 +data 4 | data 5 | .33 +data 6 |data7(long)| 8.01 + |data 8 | 514 +============================== +Total: | 9 items |698,79 ------------------------------:tableclass1[tableid2] ## Multilevel @@ -515,14 +522,8 @@ regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$") regex_list=re.compile('^(?:(#{1,6}|\.+|\++|\-+)(\.)?\s+)?(.*)$') regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$') regex_tq=re.compile('^(-{3}-*)(?::(?P[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P

[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$') -regex_qr = re.compile(r'(?/=])qr:(?P\w+://[\w\d\-+?&%/:.]+)',re.M) -regex_embed = re.compile(r'(?/=])embed:(?P\w+://[\w\d\-+_=?%&/:.]+)', re.M) -regex_iframe = re.compile(r'(?/=])iframe:(?P\w+://[\w\d\-+=?%&/:.]+)', re.M) -regex_auto_image = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=%&/:.]+\.(jpeg|JPEG|jpg|JPG|gif|GIF|png|PNG)(\?[\w\d/\-+_=%&:.]+)?)',re.M) -regex_auto_video = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=%&/:.]+\.(mp4|MP4|mpeg|MPEG|mov|MOV|ogv|OGV)(\?[\w\d/\-+_=%&:.]+)?)',re.M) -regex_auto_audio = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=%&/:.]+\.(mp3|MP3|wav|WAV|ogg|OGG)(\?[\w\d/\-+_=%&:.]+)?)',re.M) +regex_proto = re.compile(r'(?/=])(?P

\w+):(?P\w+://[\w\d\-+=?%&/:.]+)', re.M) regex_auto = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=?%&/:.]+)',re.M) - regex_link=re.compile(r'('+LINK+r')|\[\[(?P.+?)\]\]') regex_link_level2=re.compile(r'^(?P\S.*?)?(?:\s+\[(?P.+?)\])?(?:\s+(?P\S+))?(?:\s+(?P

popup))?\s*$') regex_media_level2=re.compile(r'^(?P\S.*?)?(?:\s+\[(?P.+?)\])?(?:\s+(?P\S+))?\s+(?P

img|IMG|left|right|center|video|audio)(?:\s+(?P\d+px))?\s*$') @@ -536,7 +537,48 @@ def markmin_escape(text): """ insert \\ before markmin control characters: '`:*~[]{}@$ """ return regex_markmin_escape.sub(lambda m: '\\'+m.group(0).replace('\\','\\\\'), text) -def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='google',auto=True,class_prefix='',id_prefix='markmin_'): +def autolinks(url): + """ + it automatically converts the url to link, + image, video or audio tag + """ + u_url=url.lower() + if u_url.endswith(('jpeg','gif','png')): + return '' % url + elif u_url.endswith(('mp4','mpeg','mov','ogv')): + return '' % url + elif u_url.endswith(('mp3','wav','ogg')): + return '' % url + return '%s' % (url,url) + +def protolinks(proto, url): + """ + it converts url to html-string using appropriate proto-prefix: + Uses for construction "proto:url", e.g.: + "iframe:http://www.example.com/path" will call protolinks() + with parameters: + proto="iframe" + url="http://www.example.com/path" + """ + if proto in ('iframe','embed'): #== 'iframe': + return ''%url + #elif proto == 'embed': # NOTE: embed is a synonym to iframe now + # return '%s>'%(url,class_prefix,url) + elif proto == 'qr': + return 'qr code'%url + return proto+':'+url + +def render(text, + extra={}, + allowed={}, + sep='p', + URL=None, + environment=None, + latex='google', + autolinks=autolinks, + protolinks=protolinks, + class_prefix='', + id_prefix='markmin_'): """ Arguments: - text is the text to be processed @@ -546,8 +588,18 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo allowed = dict(code=('python','cpp','java')) - sep can be 'p' to separate text in

...

or can be 'br' to separate text using
- - auto is a True/False value (default is True) - - enables auto links processing for iframe,embed,qr,url,image,video,audio + - URL - + - environment is a dictionary of environment variables (can be accessed with @{variable} + - latex - + - autolinks is a function to convert auto urls to html-code (default is autolinks(url) ) + - protolinks is a function to convert proto-urls (e.g."proto:url") to html-code + (default is protolinks(proto,url)) + - class_prefix is a prefix for ALL classes in markmin text. E.g. if class_prefix='my_' + then for ``test``:cls class will be changed to "my_cls" (default value is '') + - id_prefix is prefix for ALL ids in markmin text (default value is 'markmin_'). E.g.: + -- [[id]] will be converted to + -- [[link #id]] will be converted to link + -- ``test``:cls[id] will be converted to test >>> render('this is\\n# a section\\n\\nparagraph') '

this is

a section

paragraph

' @@ -583,7 +635,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo '
  1. this
  2. is
  3. a list

and this

  1. is
  2. another
' >>> render("----\\na | b\\nc | d\\n----\\n") - '
ab
cd
' + '
ab
cd
' >>> render("----\\nhello world\\n----\\n") '
hello world
' @@ -630,6 +682,15 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo >>> render("auto-image: (http://example.com/image.jpeg)") '

auto-image: ()

' + >>> render("qr: (qr:http://example.com/image.jpeg)") + '

qr: (qr code)

' + + >>> render("embed: (embed:http://example.com/page)") + '

embed: ()

' + + >>> render("iframe: (iframe:http://example.com/page)") + '

iframe: ()

' + >>> render("title1: [[test message [simple \[test\] title] http://example.com ]] test") '

title1: test message test

' @@ -780,14 +841,11 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo text = regex_link.sub(mark_link, text) text = escape(text) - if auto: - text = regex_iframe.sub('',text) - text = regex_embed.sub('\g',text) - text = regex_qr.sub('qr code',text) - text = regex_auto_image.sub('', text) - text = regex_auto_video.sub('', text) - text = regex_auto_audio.sub('', text) - text = regex_auto.sub('\g', text) + if protolinks: + text = regex_proto.sub(lambda m: protolinks(*m.group('p','k')), text) + + if autolinks: + text = regex_auto.sub(lambda m: autolinks(m.group('k')), text) ############################################################# # normalize spaces @@ -904,6 +962,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo tout=[] thead=[] tbody=[] + rownum=0 t_id = '' t_cls = '' @@ -914,9 +973,10 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo if s.count('=')==len(s) and len(s)>3: # header or footer if not thead: # if thead list is empty: thead = tout - else: # if tbody list is empty: + else: tbody.extend(tout) tout = [] + rownum=0 lineno+=1 continue @@ -926,12 +986,17 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo t_id = m.group('p') or '' break - tout.append(''+''.join(['%s'% \ - (' class="num"' - if regex_num.match(f) - else '', - f.strip() - ) for f in s.split('|')])+'') + if rownum % 2: + tr = '' + else: + tr = '' if rownum == 0 else '' + tout.append(tr+''.join(['%s'% \ + (' class="num"' + if regex_num.match(f) + else '', + f.strip() + ) for f in s.split('|')])+'') + rownum+=1 lineno+=1 t_cls = ' class="%s%s"'%(class_prefix, t_cls) if t_cls and t_cls != 'id' else '' @@ -990,7 +1055,8 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo URL, environment, latex, - auto, + autolinks, + protolinks, class_prefix, id_prefix) ) @@ -1107,7 +1173,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo style = ' style="float:%s"' % p if p in ('video','audio'): t = render(t, {}, {}, 'br', URL, environment, latex, - auto, class_prefix, id_prefix) + autolinks, protolinks, class_prefix, id_prefix) return '<%(p)s controls="controls"%(title)s%(width)s>%(t)s' \ % dict(p=p, title=title, width=width, k=k, t=t) alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else '' @@ -1127,14 +1193,14 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo k = escape(k) title = ' title="%s"' % a.replace(META, DISABLED_META) if a else '' target = ' target="_blank"' if p == 'popup' else '' - t = render(t, {}, {}, 'br', URL, environment, latex, auto, - class_prefix, id_prefix) if t else k + t = render(t, {}, {}, 'br', URL, environment, latex, autolinks, + protolinks, class_prefix, id_prefix) if t else k return '%(t)s' \ % dict(k=k, title=title, target=target, t=t) return '%s' % (escape(id_prefix+t), render(a, {},{},'br', URL, - environment, latex, auto, - class_prefix, id_prefix)) + environment, latex, autolinks, + protolinks, class_prefix, id_prefix)) parts = text.split(LINK) text = parts[0] @@ -1172,13 +1238,13 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo return LATEX % code.replace('"','\"').replace('\n',' ') elif b in html_colors: return '%s' \ - % (b, render(code,{},{},'br',URL,environment,latex,auto)) + % (b, render(code,{},{},'br',URL,environment,latex,autolinks, protolinks)) elif b in ('c', 'color') and p: c=p.split(':') fg='color: %s;' % c[0] if c[0] else '' bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else '' return '%s' \ - % (fg, bg, render(code,{},{},'br', URL, environment, latex, auto)) + % (fg, bg, render(code,{},{},'br', URL, environment, latex, autolinks, protolinks)) cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else '' id = ' id="%s%s"'%(id_prefix,escape(p)) if p else '' beg=(code[:1]=='\n') @@ -1190,8 +1256,8 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo text = text.translate(ttab_out) return text -def markmin2html(text, extra={}, allowed={}, sep='p', auto=True): - return render(text, extra, allowed, sep, auto=auto) +def markmin2html(text, extra={}, allowed={}, sep='p', autolinks=autolinks, protolinks=protolinks): + return render(text, extra, allowed, sep, autolinks=autolinks, protolinks=protolinks) if __name__ == '__main__': import sys @@ -1214,15 +1280,18 @@ if __name__ == '__main__': if sys.argv[1:2] == ['-h']: style=dedent(""" """)[1:] print html % dict(title="Markmin markup language", style=style, body=markmin2html(__doc__)) @@ -1236,9 +1305,29 @@ if __name__ == '__main__': elif len(sys.argv) > 1: fargv = open(sys.argv[1],'r') try: - print html % dict(title=sys.argv[1], style='', body=markmin2html(fargv.read())) + markmin_text=fargv.read() + + # embed css file from second parameter into html file + if len(sys.argv) > 2: + if sys.argv[2].startswith('@'): + markmin_style = '' + else: + fargv2 = open(sys.argv[2],'r') + try: + markmin_style = "" + finally: + fargv2.close() + else: + markmin_style = "" + + print html % dict(title=sys.argv[1], style=markmin_style, body=markmin2html(markmin_text)) finally: fargv.close() - else: - doctest.testmod() + else: + print "Usage: "+sys.argv[0]+" -h | -t | file.markmin [file.css|@path_to/css]" + print "where: -h - print __doc__" + print " -t - timeit __doc__ (for testing purpuse only)" + print " file.markmin [file.css] - process file.markmin + built in file.css (optional)" + print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)" + doctest.testmod() diff --git a/gluon/languages.py b/gluon/languages.py index ec9c9e06..542a22ec 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -28,10 +28,10 @@ from string import maketrans __all__ = ['translator', 'findT', 'update_all_languages'] -# used as a default filter i translator.M() +# used as default filter in translator.M() markmin = lambda s: render( regex_param.sub( lambda m: '{' + markmin_escape(m.group('s')) + '}', - s ), sep='br', auto=False ) + s ), sep='br', autolinks=None, id_prefix='' ) NUMBERS = (int,long,float) @@ -377,7 +377,8 @@ class lazyT(object): never to be called explicitly, returned by translator.__call__() or translator.M() """ - m = s = T = f = t = M = None + m = s = T = f = t = None + M = is_copy = False def __init__( self, @@ -388,34 +389,35 @@ class lazyT(object): ftag = None, M = False ): - self.M = M if isinstance(message, lazyT): self.m = message.m - self.s = symbols or message.s - self.T = T or message.T - self.f = filter or message.f - self.t = ftag or message.t + self.s = message.s + self.T = message.T + self.f = message.f + self.t = message.t + self.M = message.M + self.is_copy = True else: self.m = message self.s = symbols self.T = T self.f = filter self.t = ftag + self.M = M + self.is_copy = False def __repr__(self): - return "" % (repr(str(self.m)), ) + return "" % (repr(Utf8(self.m)), ) def __str__(self): return str(self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else self.T.translate(self.m, self.s)) def __eq__(self, other): - return (self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else - self.T.translate(self.m, self.s)) == other + return str(self) == str(other) def __ne__(self, other): - return (self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else - self.T.translate(self.m, self.s)) != other + return str(self) != str(other) def __add__(self, other): return '%s%s' % (self, other) @@ -423,14 +425,17 @@ class lazyT(object): def __radd__(self, other): return '%s%s' % (other, self) + def __mul__(self, other): + return str(self) * other + def __cmp__(self,other): - return cmp(str(self),str(other)) + return cmp(str(self), str(other)) def __hash__(self): return hash(str(self)) def __getattr__(self, name): - return getattr(str(self),name) + return getattr(str(self), name) def __getitem__(self, i): return str(self)[i] @@ -457,7 +462,8 @@ class lazyT(object): return str(self) def __mod__(self, symbols): - return lazyT(self.m,symbols,self.T,self.f,self.t,self.M) + if self.is_copy: return lazyT(self) + return lazyT(self.m, symbols, self.T, self.f, self.t, self.M) class translator(object): @@ -787,18 +793,21 @@ class translator(object): """ w,i = m.group('w','i') c = w[0] - word = w[c=='\\':] if c not in '!?': - return self.plural(word, symbols[int(i or 0)]) + return self.plural(w, symbols[int(i or 0)]) elif c == '?': - part2 = w[max(1,w.find('?',1)+1):] + (p1, sep, p2) = w[1:].partition("?") + part1 = p1 if sep else "" + (part2, sep, part3) = (p2 if sep else p1).partition("?") + if not sep: part3 = part2 if i is None: - # ?[word]?number or ?number - num = part2 + # ?[word]?number[?number] or ?number + if not part2: return m.group(0) + num = int(part2) else: - # ?[word]?word2[number], ?word2[number] or ?word2[number] - num = symbols[int(i or 0)] - return w[1:abs(w.find('?',1))] if int(num) == 1 else part2 + # ?[word]?word2[?word3][number] + num = int(symbols[int(i or 0)]) + return part1 if num==1 else part3 if num==0 else part2 elif w.startswith('!!!'): word = w[3:] fun = upper_fun @@ -815,16 +824,22 @@ class translator(object): def sub_dict(m): """ word(var), !word(var), !!word(var), !!!word(var) word(num), !word(num), !!word(num), !!!word(num) + ?word2(var), ?word1?word2(var), ?word1?word2?word0(var) + ?word2(num), ?word1?word2(num), ?word1?word2?word0(num) """ w,n = m.group('w','n') c = w[0] - word = w[c=='\\':] n = int(n) if n.isdigit() else symbols[n] if c not in '!?': - return self.plural(word, n) + return self.plural(w, n) elif c == '?': - # ?[word]?word2(var or num), ?word2(var or num) or ?word2(var or num) - return w[1:abs(w.find('?',1))] if int(n) == 1 else w[max(1,w.find('?',1)+1):] + # ?[word1]?word2[?word0](var or num), ?[word1]?word2(var or num) or ?word2(var or num) + (p1, sep, p2) = w[1:].partition("?") + part1 = p1 if sep else "" + (part2, sep, part3) = (p2 if sep else p1).partition("?") + if not sep: part3 = part2 + num = int(n) + return part1 if num==1 else part3 if num==0 else part2 elif w.startswith('!!!'): word = w[3:] fun = upper_fun From 9148af96a92a1c9166f75b9e96cd9e424137a5cd Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 30 Jul 2012 22:07:08 -0500 Subject: [PATCH 031/155] removed some possible tckets in auth --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 2 +- gluon/tools.py | 13 +++++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index fbcfe843..a14ce75d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-30 20:23:00) dev +Version 2.00.0 (2012-07-30 22:07:05) dev diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 893c4950..fc3e5d7b 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/bin/env python # -*- coding: utf-8 -*- # created by Massimo Di Pierro # recreated by Vladyslav Kozlovskyy diff --git a/gluon/tools.py b/gluon/tools.py index 94501c82..36ac46ad 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -49,6 +49,15 @@ logger = logging.getLogger("web2py") DEFAULT = lambda: None +def getarg(position,default=None): + args = current.request.args + if position<0 and len(args)>=-position: + return args[position] + elif position>=0 and len(args)>position: + return args[position] + else: + return default + def callback(actions,form,tablename=None): if actions: if tablename and isinstance(actions,dict): @@ -2122,7 +2131,7 @@ class Auth(object): """ - key = current.request.args[-1] + key = getarg(-1) table_user = self.settings.table_user user = self.db(table_user.registration_key == key).select().first() if not user: @@ -2332,7 +2341,7 @@ class Auth(object): if next is DEFAULT: next = self.next or self.settings.reset_password_next try: - key = request.vars.key or request.args[-1] + key = request.vars.key or getarg(-1) t0 = int(key.split('-')[0]) if time.time()-t0 > 60*60*24: raise Exception user = self.db(table_user.reset_password_key == key).select().first() From d822eb1726eb7096b4de3b06f55195084039104c Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 31 Jul 2012 10:47:23 -0500 Subject: [PATCH 032/155] fixed update_on_insert --- VERSION | 2 +- gluon/dal.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a14ce75d..7eee3624 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-30 22:07:05) dev +Version 2.00.0 (2012-07-31 10:47:18) dev diff --git a/gluon/dal.py b/gluon/dal.py index a7f75b59..6030ebc0 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -7453,6 +7453,8 @@ class Table(dict): def update_or_insert(self, _key=DEFAULT, **values): if _key is DEFAULT: record = self(**values) + elif isinstance(_key,dict): + record = self(**_key) else: record = self(_key) if record: From bedd215cfb3199f9001f35e9501eb862102bccd7 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 31 Jul 2012 11:22:39 -0500 Subject: [PATCH 033/155] autolink expand_one now does unicode --- VERSION | 2 +- gluon/contrib/autolinks.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index 7eee3624..40b2478c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-31 10:47:18) dev +Version 2.00.0 (2012-07-31 11:22:34) dev diff --git a/gluon/contrib/autolinks.py b/gluon/contrib/autolinks.py index bb3aef12..851ed881 100644 --- a/gluon/contrib/autolinks.py +++ b/gluon/contrib/autolinks.py @@ -145,18 +145,19 @@ def expand_one(url,cdict): r = oembed(url) # if oembed service if 'html' in r: - if r['html'].startswith('%s' % r['html'] + html = r['html'].encode('utf8') + if html.startswith('%s' % html else: - return r['html'] + return html elif 'url' in r: - url = r['url'] - # embed images, video, audio files - ext = extension(url) - if ext in EXTENSION_MAPS: - return EXTENSION_MAPS[ext](url) - # else regular link - return '%(u)s' % dict(u=url) + url = r['url'].encode('utf8') + # embed images, video, audio files + ext = extension(url) + if ext in EXTENSION_MAPS: + return EXTENSION_MAPS[ext](url) + # else regular link + return '%(u)s' % dict(u=url) def expand_html(html,cdict=None): soup = BeautifulSoup(html) From 2ee08445200e529831a54528c1395c34390da776 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 31 Jul 2012 12:07:11 -0500 Subject: [PATCH 034/155] some dal.py indentation work --- VERSION | 2 +- gluon/dal.py | 252 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 167 insertions(+), 87 deletions(-) diff --git a/VERSION b/VERSION index 40b2478c..73caee12 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-31 11:22:34) dev +Version 2.00.0 (2012-07-31 12:07:08) dev diff --git a/gluon/dal.py b/gluon/dal.py index 6030ebc0..d07cecea 100644 --- a/gluon/dal.py +++ b/gluon/dal.py @@ -39,7 +39,8 @@ Example of usage: >>> # from dal import DAL, Field ### create DAL connection (and create DB if it doesn't exist) ->>> db = DAL(('mysql://a:b@localhost/x', 'sqlite://storage.sqlite'), folder=None) +>>> db = DAL(('mysql://a:b@localhost/x', 'sqlite://storage.sqlite'), +... folder=None) ### define a table 'person' (create/alter as necessary) >>> person = db.define_table('person',Field('name','string')) @@ -69,7 +70,8 @@ Example of usage: 0 ### retrieve multiple records (rows) ->>> people = db(person).select(orderby=person.name, groupby=person.name, limitby=(0,100)) +>>> people = db(person).select(orderby=person.name, +... groupby=person.name, limitby=(0,100)) ### further filter them >>> james = people.find(lambda row: row.name == 'James').first() @@ -89,7 +91,8 @@ Example of usage: >>> person.drop() Supported field types: -id string text boolean integer double decimal password upload blob time date datetime +id string text boolean integer double decimal password upload +blob time date datetime Supported DAL URI strings: 'sqlite://test.db' @@ -168,7 +171,8 @@ import glob import traceback import platform -CALLABLETYPES = (types.LambdaType, types.FunctionType, types.BuiltinFunctionType, +CALLABLETYPES = (types.LambdaType, types.FunctionType, + types.BuiltinFunctionType, types.MethodType, types.BuiltinMethodType) @@ -226,7 +230,7 @@ try: from new import classobj from google.appengine.ext import db as gae from google.appengine.api import namespace_manager, rdbms - from google.appengine.api.datastore_types import Key ### needed for belongs on ID + from google.appengine.api.datastore_types import Key ### for belongs on ID from google.appengine.ext.db.polymodel import PolyModel drivers.append('google') except ImportError: @@ -411,7 +415,8 @@ if 'google' in drivers: self.round = decimal.Decimal(d) def get_value_for_datastore(self, model_instance): - value = super(GAEDecimalProperty, self).get_value_for_datastore(model_instance) + value = super(GAEDecimalProperty, self)\ + .get_value_for_datastore(model_instance) if value is None or value == '': return None else: @@ -429,7 +434,8 @@ if 'google' in drivers: return value elif isinstance(value, basestring): return decimal.Decimal(value) - raise gae.BadValueError("Property %s must be a Decimal or string." % self.name) + raise gae.BadValueError("Property %s must be a Decimal or string."\ + % self.name) ################################################################################### # class that handles connection pooling (all adapters are derived from this one) @@ -496,7 +502,8 @@ class ConnectionPool(object): def pool_connection(self, f, cursor=True): """ - this function defines: self.connection and self.cursor (iff cursor is True) + this function defines: self.connection and self.cursor + (iff cursor is True) if self.pool_size>0 it will try pull the connection from the pool if the connection is not active (closed by db server) it will loop if not self.pool_size or no active connections in pool makes a new one @@ -711,7 +718,8 @@ class BaseAdapter(ConnectionPool): schema = parms[0] ftype = "SELECT AddGeometryColumn ('%%(schema)s', '%%(tablename)s', '%%(fieldname)s', %%(srid)s, '%s', %%(dimension)s);" % self.types[geotype] ftype = ftype % dict(schema=schema, tablename=tablename, - fieldname=field.name, srid=srid, dimension=dimension) + fieldname=field.name, srid=srid, + dimension=dimension) postcreation_fields.append(ftype) elif not field.type in self.types: raise SyntaxError, 'Field: unknown field type: %s for %s' % \ @@ -719,7 +727,8 @@ class BaseAdapter(ConnectionPool): else: ftype = self.types[field.type]\ % dict(length=field.length) - if not field.type.startswith('id') and not field.type.startswith('reference'): + if not field.type.startswith('id') and \ + not field.type.startswith('reference'): if field.notnull: ftype += ' NOT NULL' else: @@ -735,11 +744,12 @@ class BaseAdapter(ConnectionPool): sql=ftype) if isinstance(field.default,(str,int,float)): - # Caveat: sql_fields and sql_fields_aux differ for default values. + # Caveat: sql_fields and sql_fields_aux + # differ for default values. # sql_fields is used to trigger migrations and sql_fields_aux # is used for create tables. - # The reason is that we do not want to trigger a migration simply - # because a default value changes. + # The reason is that we do not want to trigger + # a migration simply because a default value changes. not_null = self.NOT_NULL(field.default, field.type) ftype = ftype.replace('NOT NULL', not_null) sql_fields_aux[field.name] = dict(sql=ftype) @@ -770,20 +780,25 @@ class BaseAdapter(ConnectionPool): if hasattr(table,'_primarykey'): query = "CREATE TABLE %s(\n %s,\n %s) %s" % \ - (tablename, fields, self.PRIMARY_KEY(', '.join(table._primarykey)),other) + (tablename, fields, + self.PRIMARY_KEY(', '.join(table._primarykey)),other) else: query = "CREATE TABLE %s(\n %s\n)%s" % \ (tablename, fields, other) - if self.uri.startswith('sqlite:///') or self.uri.startswith('spatialite:///'): - path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' - dbpath = self.uri[9:self.uri.rfind('/')].decode('utf8').encode(path_encoding) + if self.uri.startswith('sqlite:///') \ + or self.uri.startswith('spatialite:///'): + path_encoding = sys.getfilesystemencoding() \ + or locale.getdefaultlocale()[1] or 'utf8' + dbpath = self.uri[9:self.uri.rfind('/')]\ + .decode('utf8').encode(path_encoding) else: dbpath = self.folder if not migrate: return query - elif self.uri.startswith('sqlite:memory') or self.uri.startswith('spatialite:memory'): + elif self.uri.startswith('sqlite:memory')\ + or self.uri.startswith('spatialite:memory'): table._dbt = None elif isinstance(migrate, str): table._dbt = os.path.join(dbpath, migrate) @@ -803,7 +818,8 @@ class BaseAdapter(ConnectionPool): if not fake_migrate: self.create_sequence_and_triggers(query,table) table._db.commit() - # Postgres geom fields are added now, after the table has been created + # Postgres geom fields are added now, + # after the table has been created for query in postcreation_fields: self.execute(query) table._db.commit() @@ -884,13 +900,14 @@ class BaseAdapter(ConnectionPool): elif not key in sql_fields: del sql_fields_current[key] ftype = sql_fields_old[key]['type'] - if self.dbengine in ('postgres',) and ftype.startswith('geometry'): + if self.dbengine in ('postgres',) \ + and ftype.startswith('geometry'): geotype, parms = ftype[:-1].split('(') schema = parms.split(',')[0] - query = [ "SELECT DropGeometryColumn ('%(schema)s', '%(table)s', '%(field)s');" % \ - dict(schema=schema, table=tablename, field=key,) ] + query = [ "SELECT DropGeometryColumn ('%(schema)s', '%(table)s', '%(field)s');" % dict(schema=schema, table=tablename, field=key,) ] elif not self.dbengine in ('firebird',): - query = ['ALTER TABLE %s DROP COLUMN %s;' % (tablename, key)] + query = ['ALTER TABLE %s DROP COLUMN %s;' + % (tablename, key)] else: query = ['ALTER TABLE %s DROP %s;' % (tablename, key)] metadata_change = True @@ -1071,13 +1088,16 @@ class BaseAdapter(ConnectionPool): def ILIKE(self, first, second): "case in-sensitive like operator" - return '(%s LIKE %s)' % (self.expand(first), self.expand(second, 'string')) + return '(%s LIKE %s)' % (self.expand(first), + self.expand(second, 'string')) def STARTSWITH(self, first, second): - return '(%s LIKE %s)' % (self.expand(first), self.expand(second+'%', 'string')) + return '(%s LIKE %s)' % (self.expand(first), + self.expand(second+'%', 'string')) def ENDSWITH(self, first, second): - return '(%s LIKE %s)' % (self.expand(first), self.expand('%'+second, 'string')) + return '(%s LIKE %s)' % (self.expand(first), + self.expand('%'+second, 'string')) def CONTAINS(self, first, second): if first.type in ('string', 'text'): @@ -1089,47 +1109,58 @@ class BaseAdapter(ConnectionPool): def EQ(self, first, second=None): if second is None: return '(%s IS NULL)' % self.expand(first) - return '(%s = %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s = %s)' % (self.expand(first), + self.expand(second, first.type)) def NE(self, first, second=None): if second is None: return '(%s IS NOT NULL)' % self.expand(first) - return '(%s <> %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s <> %s)' % (self.expand(first), + self.expand(second, first.type)) def LT(self,first,second=None): if second is None: raise RuntimeError, "Cannot compare %s < None" % first - return '(%s < %s)' % (self.expand(first),self.expand(second,first.type)) + return '(%s < %s)' % (self.expand(first), + self.expand(second,first.type)) def LE(self,first,second=None): if second is None: raise RuntimeError, "Cannot compare %s <= None" % first - return '(%s <= %s)' % (self.expand(first),self.expand(second,first.type)) + return '(%s <= %s)' % (self.expand(first), + self.expand(second,first.type)) def GT(self,first,second=None): if second is None: raise RuntimeError, "Cannot compare %s > None" % first - return '(%s > %s)' % (self.expand(first),self.expand(second,first.type)) + return '(%s > %s)' % (self.expand(first), + self.expand(second,first.type)) def GE(self,first,second=None): if second is None: raise RuntimeError, "Cannot compare %s >= None" % first - return '(%s >= %s)' % (self.expand(first),self.expand(second,first.type)) + return '(%s >= %s)' % (self.expand(first), + self.expand(second,first.type)) def ADD(self, first, second): - return '(%s + %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s + %s)' % (self.expand(first), + self.expand(second, first.type)) def SUB(self, first, second): - return '(%s - %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s - %s)' % (self.expand(first), + self.expand(second, first.type)) def MUL(self, first, second): - return '(%s * %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s * %s)' % (self.expand(first), + self.expand(second, first.type)) def DIV(self, first, second): - return '(%s / %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s / %s)' % (self.expand(first), + self.expand(second, first.type)) def MOD(self, first, second): - return '(%s %% %s)' % (self.expand(first), self.expand(second, first.type)) + return '(%s %% %s)' % (self.expand(first), + self.expand(second, first.type)) def AS(self, first, second): return '%s AS %s' % (self.expand(first), second) @@ -1160,7 +1191,8 @@ class BaseAdapter(ConnectionPool): elif field_type: return str(self.represent(expression,field_type)) elif isinstance(expression,(list,tuple)): - return ','.join(self.represent(item,field_type) for item in expression) + return ','.join(self.represent(item,field_type) \ + for item in expression) elif isinstance(expression, bool): return '1' if expression else '0' else: @@ -1216,7 +1248,8 @@ class BaseAdapter(ConnectionPool): sql_w = ' WHERE ' + self.expand(query) else: sql_w = '' - sql_v = ','.join(['%s=%s' % (field.name, self.expand(value, field.type)) \ + sql_v = ','.join(['%s=%s' % (field.name, + self.expand(value, field.type)) \ for (field, value) in fields]) return 'UPDATE %s SET %s%s;' % (tablename, sql_v, sql_w) @@ -1254,7 +1287,8 @@ class BaseAdapter(ConnectionPool): if self.dbengine in ('sqlite', 'spatialite') and counter: for tablename,fieldname in table._referenced_by: f = db[tablename][fieldname] - if f.type=='reference '+table._tablename and f.ondelete=='CASCADE': + if f.type=='reference '+table._tablename \ + and f.ondelete=='CASCADE': db(db[tablename][fieldname].belongs(deleted)).delete() ### end special code to handle CASCADE in SQLite & SpatiaLite return counter @@ -1291,7 +1325,8 @@ class BaseAdapter(ConnectionPool): tablenames = self.tables(query) for field in fields: - if isinstance(field, basestring) and regex_table_field.match(field): + if isinstance(field, basestring) \ + and regex_table_field.match(field): tn,fn = field.split('.') field = self.db[tn][fn] for tablename in self.tables(field): @@ -1329,36 +1364,45 @@ class BaseAdapter(ConnectionPool): icommand = self.JOIN() if not isinstance(inner_join, (tuple, list)): inner_join = [inner_join] - ijoint = [t._tablename for t in inner_join if not isinstance(t,Expression)] + ijoint = [t._tablename for t in inner_join + if not isinstance(t,Expression)] ijoinon = [t for t in inner_join if isinstance(t, Expression)] itables_to_merge={} #issue 490 - [itables_to_merge.update(dict.fromkeys(self.tables(t))) for t in ijoinon] # issue 490 + [itables_to_merge.update( + dict.fromkeys(self.tables(t))) for t in ijoinon] ijoinont = [t.first._tablename for t in ijoinon] - [itables_to_merge.pop(t) for t in ijoinont if t in itables_to_merge] #issue 490 - iimportant_tablenames = ijoint + ijoinont + itables_to_merge.keys() # issue 490 - iexcluded = [t for t in tablenames if not t in iimportant_tablenames] + [itables_to_merge.pop(t) for t in ijoinont + if t in itables_to_merge] #issue 490 + iimportant_tablenames = ijoint + ijoinont + itables_to_merge.keys() + iexcluded = [t for t in tablenames + if not t in iimportant_tablenames] if left: join = attributes['left'] command = self.LEFT_JOIN() if not isinstance(join, (tuple, list)): join = [join] - joint = [t._tablename for t in join if not isinstance(t, Expression)] + joint = [t._tablename for t in join + if not isinstance(t, Expression)] joinon = [t for t in join if isinstance(t, Expression)] #patch join+left patch (solves problem with ordering in left joins) tables_to_merge={} - [tables_to_merge.update(dict.fromkeys(self.tables(t))) for t in joinon] + [tables_to_merge.update( + dict.fromkeys(self.tables(t))) for t in joinon] joinont = [t.first._tablename for t in joinon] [tables_to_merge.pop(t) for t in joinont if t in tables_to_merge] important_tablenames = joint + joinont + tables_to_merge.keys() - excluded = [t for t in tablenames if not t in important_tablenames ] + excluded = [t for t in tablenames + if not t in important_tablenames ] def alias(t): return str(self.db[t]) if inner_join and not left: - sql_t = ', '.join([alias(t) for t in iexcluded + itables_to_merge.keys()]) # issue 490 + sql_t = ', '.join([alias(t) for t in iexcluded + \ + itables_to_merge.keys()]) for t in ijoinon: sql_t += ' %s %s' % (icommand, str(t)) elif not inner_join and left: - sql_t = ', '.join([alias(t) for t in excluded + tables_to_merge.keys()]) + sql_t = ', '.join([alias(t) for t in excluded + \ + tables_to_merge.keys()]) if joint: sql_t += ' %s %s' % (command, ','.join([t for t in joint])) for t in joinon: @@ -1366,10 +1410,11 @@ class BaseAdapter(ConnectionPool): elif inner_join and left: all_tables_in_query = set(important_tablenames + \ iimportant_tablenames + \ - tablenames) # issue 490 - tables_in_joinon = set(joinont + ijoinont) # issue 490 - tables_not_in_joinon = all_tables_in_query.difference(tables_in_joinon) # issue 490 - sql_t = ','.join([alias(t) for t in tables_not_in_joinon]) # issue 490 + tablenames) + tables_in_joinon = set(joinont + ijoinont) + tables_not_in_joinon = \ + all_tables_in_query.difference(tables_in_joinon) + sql_t = ','.join([alias(t) for t in tables_not_in_joinon]) for t in ijoinon: sql_t += ' %s %s' % (icommand, str(t)) if joint: @@ -1404,7 +1449,8 @@ class BaseAdapter(ConnectionPool): if limitby: (lmin, lmax) = limitby sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin) - return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o) + return 'SELECT %s %s FROM %s%s%s;' % \ + (sql_s, sql_f, sql_t, sql_w, sql_o) def select(self, query, fields, attributes): """ @@ -1442,7 +1488,8 @@ class BaseAdapter(ConnectionPool): if isinstance(distinct,(list, tuple)): distinct = xorify(distinct) sql_d = self.expand(distinct) - return 'SELECT count(DISTINCT %s) FROM %s%s;' % (sql_d, sql_t, sql_w) + return 'SELECT count(DISTINCT %s) FROM %s%s;' % \ + (sql_d, sql_t, sql_w) return 'SELECT count(*) FROM %s%s;' % (sql_t, sql_w) def count(self, query, distinct=None): @@ -1588,7 +1635,10 @@ class BaseAdapter(ConnectionPool): return type(None) def rowslice(self, rows, minimum=0, maximum=None): - """ By default this function does nothing; overload when db does not do slicing. """ + """ + By default this function does nothing; + overload when db does not do slicing. + """ return rows def parse_value(self, value, field_type, blob_decode=True): @@ -1603,7 +1653,7 @@ class BaseAdapter(ConnectionPool): value = field_type.decoder(value) if not isinstance(field_type, str) or value is None: return value - elif field_type in ('string', 'text', 'password', 'upload', 'dict'): # ??? + elif field_type in ('string', 'text', 'password', 'upload', 'dict'): return value elif field_type.startswith('geo'): return value @@ -1641,7 +1691,8 @@ class BaseAdapter(ConnectionPool): def parse_datetime(self, value, field_type): if not isinstance(value, datetime.datetime): - date_part, time_part = (str(value).replace('T',' ')+' ').split(' ',1) + date_part, time_part = ( + str(value).replace('T',' ')+' ').split(' ',1) (y, m, d) = map(int,date_part.split('-')) time_parts = time_part and time_part.split(':')[:3] or (0,0,0) while len(time_parts)<3: time_parts.append(0) @@ -1746,8 +1797,12 @@ class BaseAdapter(ConnectionPool): colset.gae_item = value else: id = value - colset.update_record = lambda _ = (colset, table, id), **a: update_record(_, a) - colset.delete_record = lambda t = table, i = id: t._db(t._id==i).delete() + colset.update_record = ( + lambda _ = (colset, table, id), **a: + update_record(_, a)) + colset.delete_record = ( + lambda t = table, i = id: + t._db(t._id==i).delete()) for (referee_table, referee_name) in table._referenced_by: s = db[referee_table][referee_name] referee_link = db._referee_name and \ @@ -1760,8 +1815,10 @@ class BaseAdapter(ConnectionPool): for tablename in virtualtables: ### new style virtual fields table = db[tablename] - fields_virtual = [(f,v) for (f,v) in table.items() if isinstance(v,FieldVirtual)] - fields_lazy = [(f,v) for (f,v) in table.items() if isinstance(v,FieldLazy)] + fields_virtual = [(f,v) for (f,v) in table.items() + if isinstance(v,FieldVirtual)] + fields_lazy = [(f,v) for (f,v) in table.items() + if isinstance(v,FieldLazy)] if fields_virtual or fields_lazy: for row in rowsobj.records: box = row[tablename] @@ -1845,13 +1902,15 @@ class SQLiteAdapter(BaseAdapter): self.folder = folder self.db_codec = db_codec self.find_or_make_work_folder() - path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + path_encoding = sys.getfilesystemencoding() \ + or locale.getdefaultlocale()[1] or 'utf8' if uri.startswith('sqlite:memory'): dbpath = ':memory:' else: dbpath = uri.split('://')[1] if dbpath[0] != '/': - dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'), dbpath) + dbpath = os.path.join( + self.folder.decode(path_encoding).encode('utf8'), dbpath) if not 'check_same_thread' in driver_args: driver_args['check_same_thread'] = False if not 'detect_types' in driver_args: @@ -1908,13 +1967,15 @@ class SpatiaLiteAdapter(SQLiteAdapter): self.db_codec = db_codec self.find_or_make_work_folder() self.srid = srid - path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + path_encoding = sys.getfilesystemencoding() \ + or locale.getdefaultlocale()[1] or 'utf8' if uri.startswith('spatialite:memory'): dbpath = ':memory:' else: dbpath = uri.split('://')[1] if dbpath[0] != '/': - dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'), dbpath) + dbpath = os.path.join( + self.folder.decode(path_encoding).encode('utf8'), dbpath) if not 'check_same_thread' in driver_args: driver_args['check_same_thread'] = False if not 'detect_types' in driver_args: @@ -1947,28 +2008,36 @@ class SpatiaLiteAdapter(SQLiteAdapter): return 'AsText(%s)' %(self.expand(first)) def ST_CONTAINS(self, first, second): - return 'Contains(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Contains(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def ST_DISTANCE(self, first, second): - return 'Distance(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Distance(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def ST_EQUALS(self, first, second): - return 'Equals(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Equals(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def ST_INTERSECTS(self, first, second): - return 'Intersects(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Intersects(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def ST_OVERLAPS(self, first, second): - return 'Overlaps(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Overlaps(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def ST_SIMPLIFY(self, first, second): - return 'Simplify(%s,%s)' %(self.expand(first), self.expand(second, 'double')) + return 'Simplify(%s,%s)' %(self.expand(first), + self.expand(second, 'double')) def ST_TOUCHES(self, first, second): - return 'Touches(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Touches(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def ST_WITHIN(self, first, second): - return 'Within(%s,%s)' %(self.expand(first), self.expand(second, first.type)) + return 'Within(%s,%s)' %(self.expand(first), + self.expand(second, first.type)) def represent(self, obj, fieldtype): if fieldtype.startswith('geo'): @@ -2003,21 +2072,26 @@ class JDBCSQLiteAdapter(SQLiteAdapter): self.folder = folder self.db_codec = db_codec self.find_or_make_work_folder() - path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8' + path_encoding = sys.getfilesystemencoding() \ + or locale.getdefaultlocale()[1] or 'utf8' if uri.startswith('sqlite:memory'): dbpath = ':memory:' else: dbpath = uri.split('://')[1] if dbpath[0] != '/': - dbpath = os.path.join(self.folder.decode(path_encoding).encode('utf8'), dbpath) + dbpath = os.path.join( + self.folder.decode(path_encoding).encode('utf8'), dbpath) def connect(dbpath=dbpath,driver_args=driver_args): - return self.driver.connect(java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath), **driver_args) + return self.driver.connect( + java.sql.DriverManager.getConnection('jdbc:sqlite:'+dbpath), + **driver_args) self.pool_connection(connect) self.after_connection() def after_connection(self): # FIXME http://www.zentus.com/sqlitejdbc/custom_functions.html for UDFs - self.connection.create_function('web2py_extract', 2, SQLiteAdapter.web2py_extract) + self.connection.create_function('web2py_extract', 2, + SQLiteAdapter.web2py_extract) def execute(self, a): return self.log_execute(a) @@ -2060,11 +2134,13 @@ class MySQLAdapter(BaseAdapter): return 'RAND()' def SUBSTRING(self,field,parameters): - return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1]) + return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), + parameters[0], parameters[1]) def _drop(self,table,mode): # breaks db integrity but without this mysql does not drop table - return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table,'SET FOREIGN_KEY_CHECKS=1;'] + return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table, + 'SET FOREIGN_KEY_CHECKS=1;'] def distributed_transaction_begin(self,key): self.execute('XA START;') @@ -5782,17 +5858,21 @@ class IMAPAdapter(NoSQLAdapter): else: unmark.append(flag) - result, data = self.connection.select(self.connection.mailbox_names[tablename]) + result, data = self.connection.select( + self.connection.mailbox_names[tablename]) string_query = "(%s)" % query result, data = self.connection.search(None, string_query) - store_list = [item.strip() for item in data[0].split() if item.strip().isdigit()] + store_list = [item.strip() for item in data[0].split() + if item.strip().isdigit()] # change marked flags for number in store_list: result = None if len(mark) > 0: - result, data = self.connection.store(number, "+FLAGS", "(%s)" % " ".join(mark)) + result, data = self.connection.store( + number, "+FLAGS", "(%s)" % " ".join(mark)) if len(unmark) > 0: - result, data = self.connection.store(number, "-FLAGS", "(%s)" % " ".join(unmark)) + result, data = self.connection.store( + number, "-FLAGS", "(%s)" % " ".join(unmark)) if result == "OK": rowcount += 1 return rowcount From 721146c2f0f2128b4dafc5b2877651cb589d71ed Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 31 Jul 2012 13:46:19 -0500 Subject: [PATCH 035/155] reverted incorrect change to autolinks --- VERSION | 2 +- gluon/contrib/autolinks.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index 73caee12..a63466b2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-31 12:07:08) dev +Version 2.00.0 (2012-07-31 13:46:15) dev diff --git a/gluon/contrib/autolinks.py b/gluon/contrib/autolinks.py index 851ed881..eea5c88f 100644 --- a/gluon/contrib/autolinks.py +++ b/gluon/contrib/autolinks.py @@ -152,12 +152,12 @@ def expand_one(url,cdict): return html elif 'url' in r: url = r['url'].encode('utf8') - # embed images, video, audio files - ext = extension(url) - if ext in EXTENSION_MAPS: - return EXTENSION_MAPS[ext](url) - # else regular link - return '%(u)s' % dict(u=url) + # embed images, video, audio files + ext = extension(url) + if ext in EXTENSION_MAPS: + return EXTENSION_MAPS[ext](url) + # else regular link + return '%(u)s' % dict(u=url) def expand_html(html,cdict=None): soup = BeautifulSoup(html) From ece313efe143f07ab941aaa1c55403b485212067 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 31 Jul 2012 15:06:26 -0500 Subject: [PATCH 036/155] come web2py.css cleanup --- VERSION | 2 +- applications/admin/static/css/web2py.css | 331 ++++++++++---------- applications/examples/static/css/web2py.css | 331 ++++++++++---------- applications/examples/views/layout.html | 2 +- applications/welcome/static/css/web2py.css | 326 ++++++++++--------- applications/welcome/views/layout.html | 55 ++-- 6 files changed, 513 insertions(+), 534 deletions(-) diff --git a/VERSION b/VERSION index a63466b2..c235bb71 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-31 13:46:15) dev +Version 2.00.0 (2012-07-31 15:06:22) dev diff --git a/applications/admin/static/css/web2py.css b/applications/admin/static/css/web2py.css index cab92321..5b645cc4 100644 --- a/applications/admin/static/css/web2py.css +++ b/applications/admin/static/css/web2py.css @@ -1,190 +1,181 @@ /** these MUST stay **/ -body { margin: 0; padding:0; border: 0; } -a { text-decoration:none; white-space: nowrap;} -a:hover {text-decoration: underline} -a.button {text-decoration: none} -h1,h2,h3,h4,h5,h6 {margin: 0.5em 0 0.25em 0; display: block; font-family: Helvetica;} - h1 { font-size: 4.00em;} - h2 { font-size: 3.00em;} - h3 { font-size: 2.00em;} - h4 { font-size: 1.50em;} - h5 { font-size: 1.25em;} - h6 { font-size: 1.12em;} -right { float:right; text-align: right; } -left { float:left; text-align: left; } -center { width:100; text-align: center; vertical-align:middle;} -th, label { font-weight: bold; white-space: nowrap; } -td, th { text-align: left; padding: 2px 5px 2px 5px; } -th { vertical-align: middle; border-right: 1px solid white;} -td { vertical-align: top; } -form table tr td label { text-align: left; } -p, table, ol, ul { padding: 0.5em 0 0.5em 0 } -p {text-align: justify } -ol, ul { padding-left: 30px } -li { margin-bottom: 0.5em; } -span, input, select, textarea, button, label, a { display: inline } -img { border: 0; } -blockquote, blockquote p, p blockquote { font-style: italic; margin: 0.5em 30px 0.5em 30px; font-size: 0.9em} -i, em { font-style: italic; } -strong { font-weight: bold; } -small { font-size: 0.8em; } -textarea { width: 100%; } -code { font-family: Courier;} -video { width:400px; } -audio { width:200px; } -input[type=text], input[type=password], select { width: 300px; margin-right: 5px } -ul { list-style-type: none; margin: 0px; padding: 0px; } +a {text-decoration:none; white-space:nowrap} +a:hover {text-decoration:underline} +a.button {text-decoration:none} +h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block; + font-family:Helvetica} +h1 {font-size:4.00em} +h2 {font-size:3.00em} +h3 {font-size:2.00em} +h4 {font-size:1.50em} +h5 {font-size:1.25em} +h6 {font-size:1.12em} +th,label {font-weight:bold; white-space:nowrap} +td,th {text-align:left; padding:2px 5px 2px 5px} +th {vertical-align:middle; border-right:1px solid white} +td {vertical-align:top} +form table tr td label {text-align:left} +p,table,ol,ul {padding:0; margin: 0.5em 0} +p {text-align:justify} +ol, ul {list-style-position:inside} +li {margin-bottom:0.5em} +span,input,select,textarea,button,label,a {display:inline} +img {border:0} +blockquote,blockquote p,p blockquote { + font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em} +i,em {font-style:italic} +strong {font-weight:bold} +small {font-size:0.8em} +code {font-family:Courier} +textarea {width:100%} +video {width:400px} +audio {width:200px} +input[type=text],input[type=password],select{width:300px; margin-right:5px} .hidden {display:none;visibility:visible} +.right {float:right; text-align:right} +.left {float:left; text-align:left} +.center {width:100; text-align:center; vertical-align:middle} /** end **/ /* Sticky footer begin */ -html, body { - height: 100%; -} + .wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -8em; /* set last value to footer height plus footer vertical padding */ + min-height:100%; + height:auto !important; + height:100%; + margin:0 auto -8em; /* set last value to footer height plus footer vertical padding */ } .main { - padding: 20px 0 50px 0; + padding:20px 0 50px 0; } -.footer, .push { - height: 6em; - padding: 1em 0; - clear: both; +.footer,.push { + height:6em; + padding:1em 0; + clear:both; } -.footer-content {position: relative; bottom: -4em; width: 100%;} +.footer-content {position:relative; bottom:-4em; width:100%} .auth_navbar { - white-space: nowrap; + white-space:nowrap; } /* Sticky footer end */ .footer { - border-top: 1px #DEDEDE solid; + border-top:1px #DEDEDE solid; } .header { - // background: ; + // background:; } -fieldset { padding: 16px; border-top: 1px #DEDEDE solid;} -fieldset legend {text-transform:uppercase; font-weight: bold; padding: 4px 16px 4px 16px; background: #f1f1f1;} +fieldset {padding:16px; border-top:1px #DEDEDE solid} +fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1} /* fix ie problem with menu */ -.ie-lte7 .topbar .container {z-index: 2; } +.ie-lte7 .topbar .container {z-index:2} -td.w2p_fw {padding-bottom: 1px;} -td.w2p_fl, td.w2p_fw, td.w2p_fc { vertical-align:top; } -td.w2p_fl { text-align:right; } -td.w2p_fl, td.w2p_fw {padding-right: 7px;} -td.w2p_fl, td.w2p_fc { padding-top: 4px; } +td.w2p_fw {padding-bottom:1px} +td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top} +td.w2p_fl {text-align:right} +td.w2p_fl, td.w2p_fw {padding-right:7px} +td.w2p_fl,td.w2p_fc {padding-top:4px} -/* tr#submit_record__row {border-top: 1px solid #E5E5E5;} */ -#submit_record__row td {padding-top: .5em;} +/* tr#submit_record__row {border-top:1px solid #E5E5E5} */ +#submit_record__row td {padding-top:.5em} /* Fix */ -#auth_user_remember__row label {display: inline;} -#web2py_user_form td { vertical-align:top; } +#auth_user_remember__row label {display:inline} +#web2py_user_form td {vertical-align:top} /*********** web2py specific ***********/ div.flash { - font-weight: bold; - display: none; - position: fixed; - padding: 10px; - top: 48px; - right: 50px; - min-width: 280px; - opacity: 0.85; - margin: 0px 0px 10px 10px; - color: #fff; - vertical-align: middle; - cursor: pointer; - background: #000; - border: 2px solid #fff; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - z-index: 2; + font-weight:bold; + display:none; + position:fixed; + padding:10px; + top:48px; + right:50px; + min-width:280px; + opacity:0.85; + margin:0px 0px 10px 10px; + color:#fff; + vertical-align:middle; + cursor:pointer; + background:#000; + border:2px solid #fff; + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + z-index:2000; } -div.flash {z-index:2000;} -div.error_wrapper { display: block; } +div.error_wrapper {display:block} div.error { - background-color: red; - color: white; - padding: 3px; - display: inline-block; + background-color:red; + color:white; + padding:3px; + display:inline-block; } .topbar { - padding: 10px 0; + padding:10px 0; width:100%; - color: #959595; - vertical-align: middle; - padding: auto; - background-image: -khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222)); - background-image: -moz-linear-gradient(top, #333333, #222222); - background-image: -ms-linear-gradient(top, #333333, #222222); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222)); - background-image: -webkit-linear-gradient(top, #333333, #222222); - background-image: -o-linear-gradient(top, #333333, #222222); - background-image: linear-gradient(top, #333333, #222222); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); + color:#959595; + vertical-align:middle; + padding:auto; + background-image:-khtml-gradient(linear,left top,left bottom,from(#333333),to(#222222)); + background-image:-moz-linear-gradient(top,#333333,#222222); + background-image:-ms-linear-gradient(top,#333333,#222222); + background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333333),color-stop(100%,#222222)); + background-image:-webkit-linear-gradient(top,#333333,#222222); + background-image:-o-linear-gradient(top,#333333,#222222); + background-image:linear-gradient(top,#333333,#222222); + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0); + -webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1); + -moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1); + box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1); } .topbar a { - color: #e1e1e1; + color:#e1e1e1; } -#navbar {float: right; padding: 5px; /* same as superfish */} - -.right { - width:100%; - text-align: right; - float: right; -} +#navbar {float:right; padding:5px; /* same as superfish */} .statusbar { - background-color: #F5F5F5; - margin-top: 1em; - margin-bottom: 1em; - padding: .5em 1em; - border: 1px solid #ddd; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; + background-color:#F5F5F5; + margin-top:1em; + margin-bottom:1em; + padding:.5em 1em; + border:1px solid #ddd; + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; } -.breadcrumbs { float: left; } +.breadcrumbs {float:left} -.copyright {float: left;} -#poweredBy {float: right;} +.copyright {float:left} +#poweredBy {float:right} /* #MEDIA QUERIES SECTION */ /* All Mobile Sizes (devices and browser) */ - @media only screen and (max-width: 767px) { + @media only screen and (max-width:767px) { /* removed because of bootswatch - .topbar {text-align: center;} - #navbar, #menu {float: none;} - #navbar {font-size: 1.2em; padding: .6em 0 1.2em;} - #menu {padding: 0 0 1.5em;} - #menu select {font-size: 1.2em; margin: 0; padding: 0;} + .topbar {text-align:center} + #navbar,#menu {float:none} + #navbar {font-size:1.2em; padding:.6em 0 1.2em} + #menu {padding:0 0 1.5em} + #menu select {font-size:1.2em; margin:0; padding:0} - div.flash {top: 110px; right: 10px;} + div.flash {top:110px; right:10px} */ - } + } /* *Grid @@ -193,43 +184,47 @@ div.error { * will look better with the declarations below * if needed to remove base.css consider keeping these following lines in some css file. */ -// .web2py_table { border: 1px solid #ccc; } -.web2py_paginator { } -.web2py_grid {width: 100% } -.web2py_grid table { width: 100% } +// .web2py_table {border:1px solid #ccc} +.web2py_paginator {} +.web2py_grid {width:100%} +.web2py_grid table {width:100%} .web2py_grid tbody td { - padding: 2px 5px 2px 5px; - vertical-align: middle; + padding:2px 5px 2px 5px; + vertical-align:middle; } -.web2py_grid thead th, .web2py_grid tfoot td { +.web2py_grid thead th,.web2py_grid tfoot td { background-color:#EAEAEA; - padding: 10px 5px 10px 5px; + padding:10px 5px 10px 5px; } -.web2py_grid tr.odd {background-color: #F9F9F9;} -.web2py_grid tr:hover {background-color: #F5F5F5; } +.web2py_grid tr.odd {background-color:#F9F9F9} +.web2py_grid tr:hover {background-color:#F5F5F5} /* .web2py_breadcrumbs a { - line-height: 20px; margin-right: 5px; display: inline-block; - padding: 3px 5px 3px 5px; - font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; - color: #3C3C3D; - text-shadow: 1px 1px 0 #FFFFFF; - white-space: nowrap; overflow: visible; cursor: pointer; + line-height:20px; margin-right:5px; display:inline-block; + padding:3px 5px 3px 5px; + font-family:'lucida grande',tahoma,verdana,arial,sans-serif; + color:#3C3C3D; + text-shadow:1px 1px 0 #FFFFFF; + white-space:nowrap; overflow:visible; cursor:pointer; background:#ECECEC; - border: 1px solid #CACACA; - -webkit-border-radius: 2px; -moz-border-radius: 2px; - -webkit-background-clip: padding-box; border-radius: 2px; - outline: none; position: relative; zoom: 1; *display: inline; + border:1px solid #CACACA; + -webkit-border-radius:2px; -moz-border-radius:2px; + -webkit-background-clip:padding-box; border-radius:2px; + outline:none; position:relative; zoom:1; *display:inline; } */ .web2py_console form { width:100%; + display:inline; } +.web2py_console form select { + margin:0; +} .web2py_search_actions{ float:left; @@ -238,14 +233,14 @@ div.error { .web2py_grid .row_buttons { min-height:25px; - vertical-align: middle; + vertical-align:middle; } .web2py_grid .row_buttons a { - margin: 3px; + margin:3px; } .web2py_search_actions { - width: 100%; + width:100%; } .web2py_grid .row_buttons a, @@ -254,13 +249,13 @@ div.error { .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { - line-height: 20px; - margin-right: 5px; display: inline-block; - padding: 3px 5px 3px 5px; + line-height:20px; + margin-right:5px; display:inline-block; + padding:3px 5px 3px 5px; } .web2py_counter { - margin-top: 5px; + margin-top:5px; margin-right:5px; width:35%; float:right; @@ -268,37 +263,37 @@ div.error { } /*Fix firefox problem*/ -.web2py_table {clear: both; display: block;} +.web2py_table {clear:both; display:block} .web2py_paginator { - padding: 5px; + padding:5px; text-align:right; - background-color: #f2f2f2; + background-color:#f2f2f2; } .web2py_paginator ul { - list-style-type: none; - margin: 0px; - padding: 0px; + list-style-type:none; + margin:0px; + padding:0px; } .web2py_paginator ul li { - display: inline; + display:inline; } .web2py_paginator .current { - font-weight: bold; + font-weight:bold; } #w2p_query_panel {} .web2py_breadcrumbs ul { - list-style: none; - margin-bottom: 18px; + list-style:none; + margin-bottom:18px; } .web2py_breadcrumbs ul li { - display: inline-block; + display:inline-block; } -.ie9 #query_panel {padding-bottom:2px;} +.ie9 #query_panel {padding-bottom:2px} diff --git a/applications/examples/static/css/web2py.css b/applications/examples/static/css/web2py.css index cab92321..5b645cc4 100644 --- a/applications/examples/static/css/web2py.css +++ b/applications/examples/static/css/web2py.css @@ -1,190 +1,181 @@ /** these MUST stay **/ -body { margin: 0; padding:0; border: 0; } -a { text-decoration:none; white-space: nowrap;} -a:hover {text-decoration: underline} -a.button {text-decoration: none} -h1,h2,h3,h4,h5,h6 {margin: 0.5em 0 0.25em 0; display: block; font-family: Helvetica;} - h1 { font-size: 4.00em;} - h2 { font-size: 3.00em;} - h3 { font-size: 2.00em;} - h4 { font-size: 1.50em;} - h5 { font-size: 1.25em;} - h6 { font-size: 1.12em;} -right { float:right; text-align: right; } -left { float:left; text-align: left; } -center { width:100; text-align: center; vertical-align:middle;} -th, label { font-weight: bold; white-space: nowrap; } -td, th { text-align: left; padding: 2px 5px 2px 5px; } -th { vertical-align: middle; border-right: 1px solid white;} -td { vertical-align: top; } -form table tr td label { text-align: left; } -p, table, ol, ul { padding: 0.5em 0 0.5em 0 } -p {text-align: justify } -ol, ul { padding-left: 30px } -li { margin-bottom: 0.5em; } -span, input, select, textarea, button, label, a { display: inline } -img { border: 0; } -blockquote, blockquote p, p blockquote { font-style: italic; margin: 0.5em 30px 0.5em 30px; font-size: 0.9em} -i, em { font-style: italic; } -strong { font-weight: bold; } -small { font-size: 0.8em; } -textarea { width: 100%; } -code { font-family: Courier;} -video { width:400px; } -audio { width:200px; } -input[type=text], input[type=password], select { width: 300px; margin-right: 5px } -ul { list-style-type: none; margin: 0px; padding: 0px; } +a {text-decoration:none; white-space:nowrap} +a:hover {text-decoration:underline} +a.button {text-decoration:none} +h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block; + font-family:Helvetica} +h1 {font-size:4.00em} +h2 {font-size:3.00em} +h3 {font-size:2.00em} +h4 {font-size:1.50em} +h5 {font-size:1.25em} +h6 {font-size:1.12em} +th,label {font-weight:bold; white-space:nowrap} +td,th {text-align:left; padding:2px 5px 2px 5px} +th {vertical-align:middle; border-right:1px solid white} +td {vertical-align:top} +form table tr td label {text-align:left} +p,table,ol,ul {padding:0; margin: 0.5em 0} +p {text-align:justify} +ol, ul {list-style-position:inside} +li {margin-bottom:0.5em} +span,input,select,textarea,button,label,a {display:inline} +img {border:0} +blockquote,blockquote p,p blockquote { + font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em} +i,em {font-style:italic} +strong {font-weight:bold} +small {font-size:0.8em} +code {font-family:Courier} +textarea {width:100%} +video {width:400px} +audio {width:200px} +input[type=text],input[type=password],select{width:300px; margin-right:5px} .hidden {display:none;visibility:visible} +.right {float:right; text-align:right} +.left {float:left; text-align:left} +.center {width:100; text-align:center; vertical-align:middle} /** end **/ /* Sticky footer begin */ -html, body { - height: 100%; -} + .wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -8em; /* set last value to footer height plus footer vertical padding */ + min-height:100%; + height:auto !important; + height:100%; + margin:0 auto -8em; /* set last value to footer height plus footer vertical padding */ } .main { - padding: 20px 0 50px 0; + padding:20px 0 50px 0; } -.footer, .push { - height: 6em; - padding: 1em 0; - clear: both; +.footer,.push { + height:6em; + padding:1em 0; + clear:both; } -.footer-content {position: relative; bottom: -4em; width: 100%;} +.footer-content {position:relative; bottom:-4em; width:100%} .auth_navbar { - white-space: nowrap; + white-space:nowrap; } /* Sticky footer end */ .footer { - border-top: 1px #DEDEDE solid; + border-top:1px #DEDEDE solid; } .header { - // background: ; + // background:; } -fieldset { padding: 16px; border-top: 1px #DEDEDE solid;} -fieldset legend {text-transform:uppercase; font-weight: bold; padding: 4px 16px 4px 16px; background: #f1f1f1;} +fieldset {padding:16px; border-top:1px #DEDEDE solid} +fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1} /* fix ie problem with menu */ -.ie-lte7 .topbar .container {z-index: 2; } +.ie-lte7 .topbar .container {z-index:2} -td.w2p_fw {padding-bottom: 1px;} -td.w2p_fl, td.w2p_fw, td.w2p_fc { vertical-align:top; } -td.w2p_fl { text-align:right; } -td.w2p_fl, td.w2p_fw {padding-right: 7px;} -td.w2p_fl, td.w2p_fc { padding-top: 4px; } +td.w2p_fw {padding-bottom:1px} +td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top} +td.w2p_fl {text-align:right} +td.w2p_fl, td.w2p_fw {padding-right:7px} +td.w2p_fl,td.w2p_fc {padding-top:4px} -/* tr#submit_record__row {border-top: 1px solid #E5E5E5;} */ -#submit_record__row td {padding-top: .5em;} +/* tr#submit_record__row {border-top:1px solid #E5E5E5} */ +#submit_record__row td {padding-top:.5em} /* Fix */ -#auth_user_remember__row label {display: inline;} -#web2py_user_form td { vertical-align:top; } +#auth_user_remember__row label {display:inline} +#web2py_user_form td {vertical-align:top} /*********** web2py specific ***********/ div.flash { - font-weight: bold; - display: none; - position: fixed; - padding: 10px; - top: 48px; - right: 50px; - min-width: 280px; - opacity: 0.85; - margin: 0px 0px 10px 10px; - color: #fff; - vertical-align: middle; - cursor: pointer; - background: #000; - border: 2px solid #fff; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - z-index: 2; + font-weight:bold; + display:none; + position:fixed; + padding:10px; + top:48px; + right:50px; + min-width:280px; + opacity:0.85; + margin:0px 0px 10px 10px; + color:#fff; + vertical-align:middle; + cursor:pointer; + background:#000; + border:2px solid #fff; + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + z-index:2000; } -div.flash {z-index:2000;} -div.error_wrapper { display: block; } +div.error_wrapper {display:block} div.error { - background-color: red; - color: white; - padding: 3px; - display: inline-block; + background-color:red; + color:white; + padding:3px; + display:inline-block; } .topbar { - padding: 10px 0; + padding:10px 0; width:100%; - color: #959595; - vertical-align: middle; - padding: auto; - background-image: -khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222)); - background-image: -moz-linear-gradient(top, #333333, #222222); - background-image: -ms-linear-gradient(top, #333333, #222222); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222)); - background-image: -webkit-linear-gradient(top, #333333, #222222); - background-image: -o-linear-gradient(top, #333333, #222222); - background-image: linear-gradient(top, #333333, #222222); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); + color:#959595; + vertical-align:middle; + padding:auto; + background-image:-khtml-gradient(linear,left top,left bottom,from(#333333),to(#222222)); + background-image:-moz-linear-gradient(top,#333333,#222222); + background-image:-ms-linear-gradient(top,#333333,#222222); + background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333333),color-stop(100%,#222222)); + background-image:-webkit-linear-gradient(top,#333333,#222222); + background-image:-o-linear-gradient(top,#333333,#222222); + background-image:linear-gradient(top,#333333,#222222); + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0); + -webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1); + -moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1); + box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1); } .topbar a { - color: #e1e1e1; + color:#e1e1e1; } -#navbar {float: right; padding: 5px; /* same as superfish */} - -.right { - width:100%; - text-align: right; - float: right; -} +#navbar {float:right; padding:5px; /* same as superfish */} .statusbar { - background-color: #F5F5F5; - margin-top: 1em; - margin-bottom: 1em; - padding: .5em 1em; - border: 1px solid #ddd; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; + background-color:#F5F5F5; + margin-top:1em; + margin-bottom:1em; + padding:.5em 1em; + border:1px solid #ddd; + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; } -.breadcrumbs { float: left; } +.breadcrumbs {float:left} -.copyright {float: left;} -#poweredBy {float: right;} +.copyright {float:left} +#poweredBy {float:right} /* #MEDIA QUERIES SECTION */ /* All Mobile Sizes (devices and browser) */ - @media only screen and (max-width: 767px) { + @media only screen and (max-width:767px) { /* removed because of bootswatch - .topbar {text-align: center;} - #navbar, #menu {float: none;} - #navbar {font-size: 1.2em; padding: .6em 0 1.2em;} - #menu {padding: 0 0 1.5em;} - #menu select {font-size: 1.2em; margin: 0; padding: 0;} + .topbar {text-align:center} + #navbar,#menu {float:none} + #navbar {font-size:1.2em; padding:.6em 0 1.2em} + #menu {padding:0 0 1.5em} + #menu select {font-size:1.2em; margin:0; padding:0} - div.flash {top: 110px; right: 10px;} + div.flash {top:110px; right:10px} */ - } + } /* *Grid @@ -193,43 +184,47 @@ div.error { * will look better with the declarations below * if needed to remove base.css consider keeping these following lines in some css file. */ -// .web2py_table { border: 1px solid #ccc; } -.web2py_paginator { } -.web2py_grid {width: 100% } -.web2py_grid table { width: 100% } +// .web2py_table {border:1px solid #ccc} +.web2py_paginator {} +.web2py_grid {width:100%} +.web2py_grid table {width:100%} .web2py_grid tbody td { - padding: 2px 5px 2px 5px; - vertical-align: middle; + padding:2px 5px 2px 5px; + vertical-align:middle; } -.web2py_grid thead th, .web2py_grid tfoot td { +.web2py_grid thead th,.web2py_grid tfoot td { background-color:#EAEAEA; - padding: 10px 5px 10px 5px; + padding:10px 5px 10px 5px; } -.web2py_grid tr.odd {background-color: #F9F9F9;} -.web2py_grid tr:hover {background-color: #F5F5F5; } +.web2py_grid tr.odd {background-color:#F9F9F9} +.web2py_grid tr:hover {background-color:#F5F5F5} /* .web2py_breadcrumbs a { - line-height: 20px; margin-right: 5px; display: inline-block; - padding: 3px 5px 3px 5px; - font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; - color: #3C3C3D; - text-shadow: 1px 1px 0 #FFFFFF; - white-space: nowrap; overflow: visible; cursor: pointer; + line-height:20px; margin-right:5px; display:inline-block; + padding:3px 5px 3px 5px; + font-family:'lucida grande',tahoma,verdana,arial,sans-serif; + color:#3C3C3D; + text-shadow:1px 1px 0 #FFFFFF; + white-space:nowrap; overflow:visible; cursor:pointer; background:#ECECEC; - border: 1px solid #CACACA; - -webkit-border-radius: 2px; -moz-border-radius: 2px; - -webkit-background-clip: padding-box; border-radius: 2px; - outline: none; position: relative; zoom: 1; *display: inline; + border:1px solid #CACACA; + -webkit-border-radius:2px; -moz-border-radius:2px; + -webkit-background-clip:padding-box; border-radius:2px; + outline:none; position:relative; zoom:1; *display:inline; } */ .web2py_console form { width:100%; + display:inline; } +.web2py_console form select { + margin:0; +} .web2py_search_actions{ float:left; @@ -238,14 +233,14 @@ div.error { .web2py_grid .row_buttons { min-height:25px; - vertical-align: middle; + vertical-align:middle; } .web2py_grid .row_buttons a { - margin: 3px; + margin:3px; } .web2py_search_actions { - width: 100%; + width:100%; } .web2py_grid .row_buttons a, @@ -254,13 +249,13 @@ div.error { .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { - line-height: 20px; - margin-right: 5px; display: inline-block; - padding: 3px 5px 3px 5px; + line-height:20px; + margin-right:5px; display:inline-block; + padding:3px 5px 3px 5px; } .web2py_counter { - margin-top: 5px; + margin-top:5px; margin-right:5px; width:35%; float:right; @@ -268,37 +263,37 @@ div.error { } /*Fix firefox problem*/ -.web2py_table {clear: both; display: block;} +.web2py_table {clear:both; display:block} .web2py_paginator { - padding: 5px; + padding:5px; text-align:right; - background-color: #f2f2f2; + background-color:#f2f2f2; } .web2py_paginator ul { - list-style-type: none; - margin: 0px; - padding: 0px; + list-style-type:none; + margin:0px; + padding:0px; } .web2py_paginator ul li { - display: inline; + display:inline; } .web2py_paginator .current { - font-weight: bold; + font-weight:bold; } #w2p_query_panel {} .web2py_breadcrumbs ul { - list-style: none; - margin-bottom: 18px; + list-style:none; + margin-bottom:18px; } .web2py_breadcrumbs ul li { - display: inline-block; + display:inline-block; } -.ie9 #query_panel {padding-bottom:2px;} +.ie9 #query_panel {padding-bottom:2px} diff --git a/applications/examples/views/layout.html b/applications/examples/views/layout.html index 0f5cc8d8..8903b09b 100644 --- a/applications/examples/views/layout.html +++ b/applications/examples/views/layout.html @@ -122,7 +122,7 @@
{{block footer}}