Compare commits

..

51 Commits

Author SHA1 Message Date
Dinis 6fddca4e4f Force page reload with anchors
Force page to reload even when the location contains anchors, while using "web2py-redirect-location".
Otherwise, when the location is the same and the url has an anchor it won't redirect.
2019-07-17 15:18:44 +01:00
mdipierro 0557fe9c58 sync 2019-07-15 22:02:57 -07:00
mdipierro 5945edebfd Merge pull request #2230 from bjones1/master
Allow specification of server's encoding in webclient.
2019-07-15 22:00:33 -07:00
mdipierro 558afd886c Merge pull request #2229 from leonelcamara/patch-32
__lt__ and __gt__ for lazyT
2019-07-15 22:00:13 -07:00
mdipierro 1bc8ea6838 Merge pull request #2227 from nicozanf/bookfix
fix for py 3.6+ compatibility
2019-07-15 21:59:56 -07:00
mdipierro 24d970a943 Merge pull request #2223 from gonguinguen/patch-6
Added followlinks=True when calling listdir
2019-07-15 21:58:06 -07:00
mdipierro e66dda8641 Merge pull request #2222 from gonguinguen/patch-5
Added followlinks argument to listdir
2019-07-15 21:57:36 -07:00
mdipierro 320a28d564 Merge pull request #2216 from web-py/patch-3
Update fa.py
2019-07-15 21:57:12 -07:00
Bryan A. Jones 9db1c6b0b0 Allow specification of server's encoding in webclient. 2019-07-12 10:23:53 -05:00
Leonel Câmara f307fe7d56 __lt__ and __gt__ for lazyT
Fixes #2228
2019-07-10 12:25:40 +01:00
Nico Zanferrari f845497479 fix for py 3.6+ compatibility
py 3.6+ gives errors and need more escapes - I've tested the patch on the full web2py book's latex output and it's backward compatible with 2.7. For technical details see https://bugs.python.org/issue28450 :
"Deprecated since version 3.5, will be removed in version 3.6: Unknown escapes consist of '\' and ASCII letter now raise a deprecation warning and will be forbidden in Python 3.6"
2019-07-07 00:55:39 +02:00
Lisandro 2989beae02 Added followlinks=True when calling listdir
In order to solve the issue #2221, I've added followlinks=True when calling listdir() from functions compile_views(), compile_models() and compile_controllers()
2019-06-07 19:24:59 -03:00
Lisandro 2a7b16d61f Added followlinks argument to listdir
The followlinks argument is then used by the function when it calls os.walk(path...
Notice the "followlinks" argument of os.walk() is False by default, so this change won't make any difference. Every actual call to listdir() function won't need to be changed.
This change is needed to resolve issue #2221
2019-06-07 19:21:10 -03:00
mdipierro 14c1b3e400 fixed typo 2019-06-06 22:39:38 -07:00
mdipierro 8823cde350 simpler populate 2019-06-06 21:52:20 -07:00
mdipierro 3e09add351 submit->Submit, thanks Paolo Pastori 2019-06-06 21:28:56 -07:00
mdipierro 7385904f97 --force_migrate and --cron_threads, thanks Paolo Pastori 2019-06-06 21:23:31 -07:00
web-py c82f0ba619 Update fa.py
edit plural-forms for persian agian
2019-06-02 16:07:58 +04:30
mdipierro 409a499dd0 Merge pull request #2215 from yamandu/pt_br_translation
Better Brazilian Portuguese translation
2019-06-01 11:37:17 -07:00
mdipierro 9d52f5498e Merge pull request #2214 from yamandu/issue2179
Issue2179
2019-06-01 11:36:52 -07:00
mdipierro 0fe9d0aa34 Merge pull request #2213 from web-py/patch-2
create fa.py
2019-06-01 11:36:06 -07:00
mdipierro 89a4da0594 better scheduler logic to prevent deadlocks, thanks Paolo Pastori 2019-06-01 11:34:58 -07:00
mdipierro f2e95d1bb6 no need to create folders in shell 2019-06-01 11:31:27 -07:00
Carlos Costa 273ebb9a70 Better Brazilian Portuguese translation
Including SQLFORM.grid, auth and other strings.
2019-05-31 12:04:32 -03:00
Carlos Costa 1bfffc4f12 issue #2179
fixing tab size setting that breaks the editor configuration
2019-05-31 11:20:39 -03:00
Carlos José da Costa 6d7900be82 Merge pull request #1 from web2py/master
Sync with original
2019-05-31 10:28:05 -03:00
Ankidroid-net dadf363122 create fa.py
Hello. I intend to prepare a Persian translation of the Web 2 Pie and provide a good source for Persian speakers.
2019-05-26 17:20:25 +04:30
mdipierro ce25d8dc31 description % should not be escaped, reverting 2019-05-25 01:01:32 -07:00
mdipierro 1c5b2d7fce chmod a-x, thanks Paolo 2019-05-25 00:30:34 -07:00
mdipierro 9b831a64c0 Merge pull request #2159 from nicozanf/master
Change binary scripts to use PyInstaller with PY3
2019-05-25 00:24:16 -07:00
mdipierro e6e92b43c9 Merge pull request #2212 from gonguinguen/patch-4
solves #2211
2019-05-25 00:21:44 -07:00
mdipierro c02ee6a5c0 Merge pull request #2210 from timnyborg/patch-5
prevent open redirects with no protocol specified
2019-05-25 00:21:20 -07:00
mdipierro 22f95677d9 Merge pull request #2208 from dlage/fix-missing-folders-in-script
Refactor methods from gluon/admin to gluon/fileutils
2019-05-25 00:20:52 -07:00
mdipierro 4fd44ff682 Merge pull request #2207 from dlage/migrator-pr
Migrator - add a command line option to ensure that all tables are migrated
2019-05-25 00:20:21 -07:00
mdipierro da253f7ba8 Merge pull request #2202 from bmiklautz/shell_controller
fix: controller function invocation via shell
2019-05-25 00:18:55 -07:00
mdipierro a24926ad6f Merge pull request #2199 from timnyborg/patch-4
correct SAML authorization request binding
2019-05-25 00:18:27 -07:00
dlage a65234478c Merge branch 'master' into fix-missing-folders-in-script 2019-05-23 14:32:04 +01:00
Lisandro bd892556e1 solves #2211
The change I propose solves issue #2211 
I hope this is the correct approach.
2019-05-20 11:58:42 -03:00
Tim Nyborg 99d3d1d465 prevent open redirects with no protocol specified
prevent_open_redirect doesn't currently handle a 'next' with a // but no protocol, e.g.: .../user/login?_next=//google.com
2019-05-20 11:27:21 +01:00
dlage 30a0ac6a1b Refactor methods from gluon/admin to gluon/fileutils 2019-05-16 18:51:26 +01:00
Bernhard Miklautz 7a225da44e fix: controller function invocation via shell
If only a compiled version is available and a controller function is invoked with -S app/c/f an
IOError is raised as '_' is used as delimiter instead of '.'.

web2py.py  -S test/test/test -M would lead to:

IOError: [Errno 2] No such file or directory: 'applications/test/compiled/controllers_test_test.pyc'
2019-05-11 17:33:42 +02:00
Tim Nyborg 3f15d1ceb8 correct SAML authorization request binding
AuthnRequest cannot use BINDING_HTTP_REDIRECT, according to the SAML v2 specifications.  See:
https://github.com/IdentityPython/pysaml2/issues/163
2019-05-07 09:09:56 +01:00
Nico Zanferrari 9769314f01 PY2 compatibility 2019-04-24 23:38:06 +02:00
Nico Zanferrari b79b5951f8 Update docs 2019-04-24 23:35:44 +02:00
Nico Zanferrari 7b9d2c87c2 PY2 compatibility 2019-04-24 23:28:48 +02:00
Nico Zanferrari c03c962778 Rename README to README.md 2019-03-22 15:37:44 +01:00
Nico Zanferrari ca7b676591 Update README 2019-03-22 15:37:14 +01:00
Nico Zanferrari 7a0e113a5f new PyInstaller script 2019-03-22 15:34:50 +01:00
Nico Zanferrari 7e30be377d cleanup 2019-03-22 15:33:44 +01:00
Nico Zanferrari f9db6a8306 cleanup 2019-03-22 15:33:29 +01:00
Nico Zanferrari 50878f33bd cleanup 2019-03-22 15:33:14 +01:00
106 changed files with 1253 additions and 1225 deletions
+39 -39
View File
@@ -30,7 +30,7 @@ except:
if request.is_https: if request.is_https:
session.secure() session.secure()
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1") and \ elif (remote_addr not in hosts) and (remote_addr != '127.0.0.1') and \
(request.function != 'manage'): (request.function != 'manage'):
raise HTTP(200, T('appadmin is disabled because insecure channel')) raise HTTP(200, T('appadmin is disabled because insecure channel'))
@@ -46,7 +46,7 @@ if request.function == 'manage':
auth.table_permission()]) auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None manager_role = manager_action.get('role', None) if manager_action else None
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)): if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized") raise HTTP(403, 'Not authorized')
menu = False menu = False
elif (request.application == 'admin' and not session.authorized) or \ elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -182,7 +182,7 @@ def select():
db = get_database(request) db = get_database(request)
dbname = request.args[0] dbname = request.args[0]
try: try:
is_imap = db._uri.startswith("imap://") is_imap = db._uri.startswith('imap://')
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
is_imap = False is_imap = False
regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
@@ -224,15 +224,15 @@ def select():
session.last_orderby = orderby session.last_orderby = orderby
session.last_query = request.vars.query session.last_query = request.vars.query
form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px',
_name='query', _value=request.vars.query or '', _class="form-control", _name='query', _value=request.vars.query or '', _class='form-control',
requires=IS_NOT_EMPTY( requires=IS_NOT_EMPTY(
error_message=T("Cannot be empty")))), TR(T('Update:'), error_message=T('Cannot be empty')))), TR(T('Update:'),
INPUT(_name='update_check', _type='checkbox', INPUT(_name='update_check', _type='checkbox',
value=False), INPUT(_style='width:400px', value=False), INPUT(_style='width:400px',
_name='update_fields', _value=request.vars.update_fields _name='update_fields', _value=request.vars.update_fields
or '', _class="form-control")), TR(T('Delete:'), INPUT(_name='delete_check', or '', _class='form-control')), TR(T('Delete:'), INPUT(_name='delete_check',
_class='delete', _type='checkbox', value=False), ''), _class='delete', _type='checkbox', value=False), ''),
TR('', '', INPUT(_type='submit', _value=T('submit'), _class="btn btn-primary"))), TR('', '', INPUT(_type='submit', _value=T('Submit'), _class='btn btn-primary'))),
_action=URL(r=request, args=request.args)) _action=URL(r=request, args=request.args))
tb = None tb = None
@@ -254,8 +254,8 @@ def select():
if is_imap: if is_imap:
fields = [db[table][name] for name in fields = [db[table][name] for name in
("id", "uid", "created", "to", ('id', 'uid', 'created', 'to',
"sender", "subject")] 'sender', 'subject')]
if orderby: if orderby:
rows = db(query, ignore_common_filters=True).select( rows = db(query, ignore_common_filters=True).select(
*fields, limitby=(start, stop), *fields, limitby=(start, stop),
@@ -271,10 +271,10 @@ def select():
# begin handle upload csv # begin handle upload csv
csv_table = table or request.vars.table csv_table = table or request.vars.table
if csv_table: if csv_table:
formcsv = FORM(str(T('or import from csv file')) + " ", formcsv = FORM(str(T('or import from csv file')) + ' ',
INPUT(_type='file', _name='csvfile'), INPUT(_type='file', _name='csvfile'),
INPUT(_type='hidden', _value=csv_table, _name='table'), INPUT(_type='hidden', _value=csv_table, _name='table'),
INPUT(_type='submit', _value=T('import'), _class="btn btn-primary")) INPUT(_type='submit', _value=T('import'), _class='btn btn-primary'))
else: else:
formcsv = None formcsv = None
if formcsv and formcsv.process().accepted: if formcsv and formcsv.process().accepted:
@@ -356,26 +356,26 @@ def state():
def ccache(): def ccache():
if is_gae: if is_gae:
form = FORM( form = FORM(
P(TAG.BUTTON(T("Clear CACHE?"), _type="submit", _name="yes", _value="yes"))) P(TAG.BUTTON(T('Clear CACHE?'), _type='submit', _name='yes', _value='yes')))
else: else:
cache.ram.initialize() cache.ram.initialize()
cache.disk.initialize() cache.disk.initialize()
form = FORM( form = FORM(
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")), T('Clear CACHE?'), _type='submit', _name='yes', _value='yes')),
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear RAM"), _type="submit", _name="ram", _value="ram")), T('Clear RAM'), _type='submit', _name='ram', _value='ram')),
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear DISK"), _type="submit", _name="disk", _value="disk")), T('Clear DISK'), _type='submit', _name='disk', _value='disk')),
) )
if form.accepts(request.vars, session): if form.accepts(request.vars, session):
session.flash = "" session.flash = ''
if is_gae: if is_gae:
if request.vars.yes: if request.vars.yes:
cache.ram.clear() cache.ram.clear()
session.flash += T("Cache Cleared") session.flash += T('Cache Cleared')
else: else:
clear_ram = False clear_ram = False
clear_disk = False clear_disk = False
@@ -387,10 +387,10 @@ def ccache():
clear_disk = True clear_disk = True
if clear_ram: if clear_ram:
cache.ram.clear() cache.ram.clear()
session.flash += T("Ram Cleared") session.flash += T('Ram Cleared')
if clear_disk: if clear_disk:
cache.disk.clear() cache.disk.clear()
session.flash += T("Disk Cleared") session.flash += T('Disk Cleared')
redirect(URL(r=request)) redirect(URL(r=request))
try: try:
@@ -436,7 +436,7 @@ def ccache():
gae_stats['ratio'] = ((gae_stats['hits'] * 100) / gae_stats['ratio'] = ((gae_stats['hits'] * 100) /
(gae_stats['hits'] + gae_stats['misses'])) (gae_stats['hits'] + gae_stats['misses']))
except ZeroDivisionError: except ZeroDivisionError:
gae_stats['ratio'] = T("?") gae_stats['ratio'] = T('?')
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age']) gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
total.update(gae_stats) total.update(gae_stats)
else: else:
@@ -502,7 +502,7 @@ def ccache():
TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))), TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))),
*[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys], *[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys],
**dict(_class='cache-keys', **dict(_class='cache-keys',
_style="border-collapse: separate; border-spacing: .5em;")) _style='border-collapse: separate; border-spacing: .5em;'))
if not is_gae: if not is_gae:
ram['keys'] = key_table(ram['keys']) ram['keys'] = key_table(ram['keys'])
@@ -536,26 +536,26 @@ def table_template(table):
# This is horribe HTML but the only one graphiz understands # This is horribe HTML but the only one graphiz understands
rows = [] rows = []
cellpadding = 4 cellpadding = 4
color = "#000000" color = '#000000'
bgcolor = "#FFFFFF" bgcolor = '#FFFFFF'
face = "Helvetica" face = 'Helvetica'
face_bold = "Helvetica Bold" face_bold = 'Helvetica Bold'
border = 0 border = 0
rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
_colspan=3, _cellpadding=cellpadding, _colspan=3, _cellpadding=cellpadding,
_align="center", _bgcolor=color))) _align='center', _bgcolor=color)))
for row in db[table]: for row in db[table]:
rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold), rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold),
_align="left", _cellpadding=cellpadding, _align='left', _cellpadding=cellpadding,
_border=border), _border=border),
TD(FONT(row.type, _color=color, _face=face), TD(FONT(row.type, _color=color, _face=face),
_align="left", _cellpadding=cellpadding, _align='left', _cellpadding=cellpadding,
_border=border), _border=border),
TD(FONT(types(row), _color=color, _face=face), TD(FONT(types(row), _color=color, _face=face),
_align="center", _cellpadding=cellpadding, _align='center', _cellpadding=cellpadding,
_border=border))) _border=border)))
return "< %s >" % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1, return '< %s >' % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1,
_cellborder=0, _cellspacing=0) _cellborder=0, _cellspacing=0)
).xml() ).xml()
@@ -632,15 +632,15 @@ def hooks():
if len(functions): if len(functions):
method_hooks.append({'name': op, 'functions':functions}) method_hooks.append({'name': op, 'functions':functions})
if len(method_hooks): if len(method_hooks):
tables.append({'name': "%s.%s" % (db_str, t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks}) tables.append({'name': '%s.%s' % (db_str, t), 'slug': IS_SLUG()('%s.%s' % (db_str,t))[0], 'method_hooks':method_hooks})
# Render # Render
ul_main = UL(_class='nav nav-list') ul_main = UL(_class='nav nav-list')
for t in tables: for t in tables:
ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug'])) ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug']))
ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none') ul_t = UL(_class='nav nav-list', _id='a_%s' % t['slug'], _style='display:none')
for op in t['method_hooks']: for op in t['method_hooks']:
ul_t.append(LI(op['name'])) ul_t.append(LI(op['name']))
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']])) ul_t.append(UL([LI(A(f['funcname'], _class='editor_filelink', _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
ul_main.append(ul_t) ul_main.append(ul_t)
return ul_main return ul_main
@@ -650,11 +650,11 @@ def hooks():
# ########################################################### # ###########################################################
def d3_graph_model(): def d3_graph_model():
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha ''' See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
and also the app_admin bg_graph_model function and also the app_admin bg_graph_model function
Create a list of table dicts, called "nodes" Create a list of table dicts, called 'nodes'
""" '''
nodes = [] nodes = []
links = [] links = []
@@ -670,10 +670,10 @@ def d3_graph_model():
elif f_type == 'string': elif f_type == 'string':
disp = field.length disp = field.length
elif f_type == 'id': elif f_type == 'id':
disp = "PK" disp = 'PK'
elif f_type.startswith('reference') or \ elif f_type.startswith('reference') or \
f_type.startswith('list:reference'): f_type.startswith('list:reference'):
disp = "FK" disp = 'FK'
else: else:
disp = ' ' disp = ' '
fields.append(dict(name=field.name, type=field.type, disp=disp)) fields.append(dict(name=field.name, type=field.type, disp=disp))
@@ -685,7 +685,7 @@ def d3_graph_model():
links.append(dict(source=tablename, target = referenced_table)) links.append(dict(source=tablename, target = referenced_table))
nodes.append(dict(name=tablename, type="table", fields = fields)) nodes.append(dict(name=tablename, type='table', fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below. # d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js')) response.files.append(URL('admin','static','js/d3.min.js'))
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+7 -4
View File
@@ -1,9 +1,12 @@
[DEFAULT]
[editor] [editor]
theme = web2py theme = twilight
editor = default editor = sublime
closetag = true closetag = true
tabwidth = 4
highlightline = true
linenumbers = true
codefolding = false
indentwithtabs = false
[editor_sessions] [editor_sessions]
welcome = welcome/models/db.py,welcome/controllers/default.py,welcome/views/default/index.html welcome = welcome/models/db.py,welcome/controllers/default.py,welcome/views/default/index.html
View File
View File
+1
View File
@@ -284,6 +284,7 @@
var redirect = xhr.getResponseHeader('web2py-redirect-location'); var redirect = xhr.getResponseHeader('web2py-redirect-location');
if (redirect !== null) { if (redirect !== null) {
window.location = redirect; window.location = redirect;
window.location.reload(); // Force reload even with anchors
} }
/* run this here only if this Ajax request is NOT for a web2py component. */ /* run this here only if this Ajax request is NOT for a web2py component. */
if (xhr.getResponseHeader('web2py-component-content') === null) { if (xhr.getResponseHeader('web2py-component-content') === null) {
View File

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File
View File
View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -38,19 +38,19 @@ def file_upload_form(location):
INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()),
INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="location",_value=location),
INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)),
INPUT(_type="submit",_value=T("submit")),_action=URL('upload_file')) INPUT(_type="submit",_value=T("Submit")),_action=URL('upload_file'))
return form return form
def file_create_form(location): def file_create_form(location):
form=FORM(T("create file with filename:")," ", form=FORM(T("create file with filename:")," ",
INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()), INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY()),
INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="location",_value=location),
INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)),
INPUT(_type="submit",_value=T("submit")),_action=URL('create_file')) INPUT(_type="submit",_value=T("Submit")),_action=URL('create_file'))
return form return form
def upload_plugin_form(app): def upload_plugin_form(app):
form=FORM(T("upload plugin file:")," ", form=FORM(T("upload plugin file:")," ",
INPUT(_type="file",_name="pluginfile"), INPUT(_type="file",_name="pluginfile"),
INPUT(_type="submit",_value=T("submit"))) INPUT(_type="submit",_value=T("Submit")))
return form return form
def deletefile(arglist): def deletefile(arglist):
return A_delete(SPAN(T('Delete')), _href=URL('delete',args=arglist,vars=dict(sender=request.function+'/'+app))) return A_delete(SPAN(T('Delete')), _href=URL('delete',args=arglist,vars=dict(sender=request.function+'/'+app)))
@@ -6,7 +6,7 @@
return files return files
}} }}
{{themes = [f[:-4] for f in listfiles('admin', "static/codemirror/theme", regexp='.*\.css$' )]}} {{themes = [f[:-4] for f in listfiles('admin', "static/codemirror/theme", regexp='.*\.css$' )]}}
{{editors = ['default', 'vim', 'emacs']}} {{editors = ['default', 'vim', 'emacs', 'sublime']}}
<form id="editor_settings_form" class="form-horizontal" action=""> <form id="editor_settings_form" class="form-horizontal" action="">
<div class="control-group"> <div class="control-group">
@@ -27,7 +27,7 @@
</div> </div>
<div class="control-group"> <div class="control-group">
<label class="control-label" for="tabwidth">{{=T('Tab width (# characters)')}}</label> <label class="control-label" for="tabwidth">{{=T('Tab width (# characters)')}}</label>
<div class="controls">{{=SELECT(range(1,9, 1), value=editor_settings['tabwidth'], _name="tabwidth" )}}</div> <div class="controls">{{=SELECT(list(range(1,9, 1)), value=editor_settings['tabwidth'], _name="tabwidth" )}}</div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label class="control-label" for="indentwithtabs">{{=T('Indent with tabs')}}</label> <label class="control-label" for="indentwithtabs">{{=T('Indent with tabs')}}</label>
+2 -2
View File
@@ -24,7 +24,7 @@ def file_upload_form(location):
INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="location",_value=location),
INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="token",_value=session.token),
INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)),
INPUT(_type="submit",_value=T("submit")),_action=URL('upload_file')) INPUT(_type="submit",_value=T("Submit")),_action=URL('upload_file'))
return form return form
def file_create_form(location): def file_create_form(location):
form=FORM(T("create file with filename:")," ", form=FORM(T("create file with filename:")," ",
@@ -32,7 +32,7 @@ def file_create_form(location):
INPUT(_type="hidden",_name="location",_value=location), INPUT(_type="hidden",_name="location",_value=location),
INPUT(_type="hidden",_name="token",_value=session.token), INPUT(_type="hidden",_name="token",_value=session.token),
INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)), INPUT(_type="hidden",_name="sender",_value=URL('design/'+app)),
INPUT(_type="submit",_value=T("submit")),_action=URL('create_file')) INPUT(_type="submit",_value=T("Submit")),_action=URL('create_file'))
return form return form
def deletefile(arglist): def deletefile(arglist):
return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')),
+39 -39
View File
@@ -30,7 +30,7 @@ except:
if request.is_https: if request.is_https:
session.secure() session.secure()
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1") and \ elif (remote_addr not in hosts) and (remote_addr != '127.0.0.1') and \
(request.function != 'manage'): (request.function != 'manage'):
raise HTTP(200, T('appadmin is disabled because insecure channel')) raise HTTP(200, T('appadmin is disabled because insecure channel'))
@@ -46,7 +46,7 @@ if request.function == 'manage':
auth.table_permission()]) auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None manager_role = manager_action.get('role', None) if manager_action else None
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)): if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized") raise HTTP(403, 'Not authorized')
menu = False menu = False
elif (request.application == 'admin' and not session.authorized) or \ elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -182,7 +182,7 @@ def select():
db = get_database(request) db = get_database(request)
dbname = request.args[0] dbname = request.args[0]
try: try:
is_imap = db._uri.startswith("imap://") is_imap = db._uri.startswith('imap://')
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
is_imap = False is_imap = False
regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
@@ -224,15 +224,15 @@ def select():
session.last_orderby = orderby session.last_orderby = orderby
session.last_query = request.vars.query session.last_query = request.vars.query
form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px',
_name='query', _value=request.vars.query or '', _class="form-control", _name='query', _value=request.vars.query or '', _class='form-control',
requires=IS_NOT_EMPTY( requires=IS_NOT_EMPTY(
error_message=T("Cannot be empty")))), TR(T('Update:'), error_message=T('Cannot be empty')))), TR(T('Update:'),
INPUT(_name='update_check', _type='checkbox', INPUT(_name='update_check', _type='checkbox',
value=False), INPUT(_style='width:400px', value=False), INPUT(_style='width:400px',
_name='update_fields', _value=request.vars.update_fields _name='update_fields', _value=request.vars.update_fields
or '', _class="form-control")), TR(T('Delete:'), INPUT(_name='delete_check', or '', _class='form-control')), TR(T('Delete:'), INPUT(_name='delete_check',
_class='delete', _type='checkbox', value=False), ''), _class='delete', _type='checkbox', value=False), ''),
TR('', '', INPUT(_type='submit', _value=T('submit'), _class="btn btn-primary"))), TR('', '', INPUT(_type='submit', _value=T('Submit'), _class='btn btn-primary'))),
_action=URL(r=request, args=request.args)) _action=URL(r=request, args=request.args))
tb = None tb = None
@@ -254,8 +254,8 @@ def select():
if is_imap: if is_imap:
fields = [db[table][name] for name in fields = [db[table][name] for name in
("id", "uid", "created", "to", ('id', 'uid', 'created', 'to',
"sender", "subject")] 'sender', 'subject')]
if orderby: if orderby:
rows = db(query, ignore_common_filters=True).select( rows = db(query, ignore_common_filters=True).select(
*fields, limitby=(start, stop), *fields, limitby=(start, stop),
@@ -271,10 +271,10 @@ def select():
# begin handle upload csv # begin handle upload csv
csv_table = table or request.vars.table csv_table = table or request.vars.table
if csv_table: if csv_table:
formcsv = FORM(str(T('or import from csv file')) + " ", formcsv = FORM(str(T('or import from csv file')) + ' ',
INPUT(_type='file', _name='csvfile'), INPUT(_type='file', _name='csvfile'),
INPUT(_type='hidden', _value=csv_table, _name='table'), INPUT(_type='hidden', _value=csv_table, _name='table'),
INPUT(_type='submit', _value=T('import'), _class="btn btn-primary")) INPUT(_type='submit', _value=T('import'), _class='btn btn-primary'))
else: else:
formcsv = None formcsv = None
if formcsv and formcsv.process().accepted: if formcsv and formcsv.process().accepted:
@@ -356,26 +356,26 @@ def state():
def ccache(): def ccache():
if is_gae: if is_gae:
form = FORM( form = FORM(
P(TAG.BUTTON(T("Clear CACHE?"), _type="submit", _name="yes", _value="yes"))) P(TAG.BUTTON(T('Clear CACHE?'), _type='submit', _name='yes', _value='yes')))
else: else:
cache.ram.initialize() cache.ram.initialize()
cache.disk.initialize() cache.disk.initialize()
form = FORM( form = FORM(
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")), T('Clear CACHE?'), _type='submit', _name='yes', _value='yes')),
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear RAM"), _type="submit", _name="ram", _value="ram")), T('Clear RAM'), _type='submit', _name='ram', _value='ram')),
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear DISK"), _type="submit", _name="disk", _value="disk")), T('Clear DISK'), _type='submit', _name='disk', _value='disk')),
) )
if form.accepts(request.vars, session): if form.accepts(request.vars, session):
session.flash = "" session.flash = ''
if is_gae: if is_gae:
if request.vars.yes: if request.vars.yes:
cache.ram.clear() cache.ram.clear()
session.flash += T("Cache Cleared") session.flash += T('Cache Cleared')
else: else:
clear_ram = False clear_ram = False
clear_disk = False clear_disk = False
@@ -387,10 +387,10 @@ def ccache():
clear_disk = True clear_disk = True
if clear_ram: if clear_ram:
cache.ram.clear() cache.ram.clear()
session.flash += T("Ram Cleared") session.flash += T('Ram Cleared')
if clear_disk: if clear_disk:
cache.disk.clear() cache.disk.clear()
session.flash += T("Disk Cleared") session.flash += T('Disk Cleared')
redirect(URL(r=request)) redirect(URL(r=request))
try: try:
@@ -436,7 +436,7 @@ def ccache():
gae_stats['ratio'] = ((gae_stats['hits'] * 100) / gae_stats['ratio'] = ((gae_stats['hits'] * 100) /
(gae_stats['hits'] + gae_stats['misses'])) (gae_stats['hits'] + gae_stats['misses']))
except ZeroDivisionError: except ZeroDivisionError:
gae_stats['ratio'] = T("?") gae_stats['ratio'] = T('?')
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age']) gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
total.update(gae_stats) total.update(gae_stats)
else: else:
@@ -502,7 +502,7 @@ def ccache():
TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))), TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))),
*[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys], *[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys],
**dict(_class='cache-keys', **dict(_class='cache-keys',
_style="border-collapse: separate; border-spacing: .5em;")) _style='border-collapse: separate; border-spacing: .5em;'))
if not is_gae: if not is_gae:
ram['keys'] = key_table(ram['keys']) ram['keys'] = key_table(ram['keys'])
@@ -536,26 +536,26 @@ def table_template(table):
# This is horribe HTML but the only one graphiz understands # This is horribe HTML but the only one graphiz understands
rows = [] rows = []
cellpadding = 4 cellpadding = 4
color = "#000000" color = '#000000'
bgcolor = "#FFFFFF" bgcolor = '#FFFFFF'
face = "Helvetica" face = 'Helvetica'
face_bold = "Helvetica Bold" face_bold = 'Helvetica Bold'
border = 0 border = 0
rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
_colspan=3, _cellpadding=cellpadding, _colspan=3, _cellpadding=cellpadding,
_align="center", _bgcolor=color))) _align='center', _bgcolor=color)))
for row in db[table]: for row in db[table]:
rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold), rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold),
_align="left", _cellpadding=cellpadding, _align='left', _cellpadding=cellpadding,
_border=border), _border=border),
TD(FONT(row.type, _color=color, _face=face), TD(FONT(row.type, _color=color, _face=face),
_align="left", _cellpadding=cellpadding, _align='left', _cellpadding=cellpadding,
_border=border), _border=border),
TD(FONT(types(row), _color=color, _face=face), TD(FONT(types(row), _color=color, _face=face),
_align="center", _cellpadding=cellpadding, _align='center', _cellpadding=cellpadding,
_border=border))) _border=border)))
return "< %s >" % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1, return '< %s >' % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1,
_cellborder=0, _cellspacing=0) _cellborder=0, _cellspacing=0)
).xml() ).xml()
@@ -632,15 +632,15 @@ def hooks():
if len(functions): if len(functions):
method_hooks.append({'name': op, 'functions':functions}) method_hooks.append({'name': op, 'functions':functions})
if len(method_hooks): if len(method_hooks):
tables.append({'name': "%s.%s" % (db_str, t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks}) tables.append({'name': '%s.%s' % (db_str, t), 'slug': IS_SLUG()('%s.%s' % (db_str,t))[0], 'method_hooks':method_hooks})
# Render # Render
ul_main = UL(_class='nav nav-list') ul_main = UL(_class='nav nav-list')
for t in tables: for t in tables:
ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug'])) ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug']))
ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none') ul_t = UL(_class='nav nav-list', _id='a_%s' % t['slug'], _style='display:none')
for op in t['method_hooks']: for op in t['method_hooks']:
ul_t.append(LI(op['name'])) ul_t.append(LI(op['name']))
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']])) ul_t.append(UL([LI(A(f['funcname'], _class='editor_filelink', _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
ul_main.append(ul_t) ul_main.append(ul_t)
return ul_main return ul_main
@@ -650,11 +650,11 @@ def hooks():
# ########################################################### # ###########################################################
def d3_graph_model(): def d3_graph_model():
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha ''' See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
and also the app_admin bg_graph_model function and also the app_admin bg_graph_model function
Create a list of table dicts, called "nodes" Create a list of table dicts, called 'nodes'
""" '''
nodes = [] nodes = []
links = [] links = []
@@ -670,10 +670,10 @@ def d3_graph_model():
elif f_type == 'string': elif f_type == 'string':
disp = field.length disp = field.length
elif f_type == 'id': elif f_type == 'id':
disp = "PK" disp = 'PK'
elif f_type.startswith('reference') or \ elif f_type.startswith('reference') or \
f_type.startswith('list:reference'): f_type.startswith('list:reference'):
disp = "FK" disp = 'FK'
else: else:
disp = ' ' disp = ' '
fields.append(dict(name=field.name, type=field.type, disp=disp)) fields.append(dict(name=field.name, type=field.type, disp=disp))
@@ -685,7 +685,7 @@ def d3_graph_model():
links.append(dict(source=tablename, target = referenced_table)) links.append(dict(source=tablename, target = referenced_table))
nodes.append(dict(name=tablename, type="table", fields = fields)) nodes.append(dict(name=tablename, type='table', fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below. # d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js')) response.files.append(URL('admin','static','js/d3.min.js'))
+39 -39
View File
@@ -30,7 +30,7 @@ except:
if request.is_https: if request.is_https:
session.secure() session.secure()
elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1") and \ elif (remote_addr not in hosts) and (remote_addr != '127.0.0.1') and \
(request.function != 'manage'): (request.function != 'manage'):
raise HTTP(200, T('appadmin is disabled because insecure channel')) raise HTTP(200, T('appadmin is disabled because insecure channel'))
@@ -46,7 +46,7 @@ if request.function == 'manage':
auth.table_permission()]) auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None manager_role = manager_action.get('role', None) if manager_action else None
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)): if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized") raise HTTP(403, 'Not authorized')
menu = False menu = False
elif (request.application == 'admin' and not session.authorized) or \ elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -182,7 +182,7 @@ def select():
db = get_database(request) db = get_database(request)
dbname = request.args[0] dbname = request.args[0]
try: try:
is_imap = db._uri.startswith("imap://") is_imap = db._uri.startswith('imap://')
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
is_imap = False is_imap = False
regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)')
@@ -224,15 +224,15 @@ def select():
session.last_orderby = orderby session.last_orderby = orderby
session.last_query = request.vars.query session.last_query = request.vars.query
form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px',
_name='query', _value=request.vars.query or '', _class="form-control", _name='query', _value=request.vars.query or '', _class='form-control',
requires=IS_NOT_EMPTY( requires=IS_NOT_EMPTY(
error_message=T("Cannot be empty")))), TR(T('Update:'), error_message=T('Cannot be empty')))), TR(T('Update:'),
INPUT(_name='update_check', _type='checkbox', INPUT(_name='update_check', _type='checkbox',
value=False), INPUT(_style='width:400px', value=False), INPUT(_style='width:400px',
_name='update_fields', _value=request.vars.update_fields _name='update_fields', _value=request.vars.update_fields
or '', _class="form-control")), TR(T('Delete:'), INPUT(_name='delete_check', or '', _class='form-control')), TR(T('Delete:'), INPUT(_name='delete_check',
_class='delete', _type='checkbox', value=False), ''), _class='delete', _type='checkbox', value=False), ''),
TR('', '', INPUT(_type='submit', _value=T('submit'), _class="btn btn-primary"))), TR('', '', INPUT(_type='submit', _value=T('Submit'), _class='btn btn-primary'))),
_action=URL(r=request, args=request.args)) _action=URL(r=request, args=request.args))
tb = None tb = None
@@ -254,8 +254,8 @@ def select():
if is_imap: if is_imap:
fields = [db[table][name] for name in fields = [db[table][name] for name in
("id", "uid", "created", "to", ('id', 'uid', 'created', 'to',
"sender", "subject")] 'sender', 'subject')]
if orderby: if orderby:
rows = db(query, ignore_common_filters=True).select( rows = db(query, ignore_common_filters=True).select(
*fields, limitby=(start, stop), *fields, limitby=(start, stop),
@@ -271,10 +271,10 @@ def select():
# begin handle upload csv # begin handle upload csv
csv_table = table or request.vars.table csv_table = table or request.vars.table
if csv_table: if csv_table:
formcsv = FORM(str(T('or import from csv file')) + " ", formcsv = FORM(str(T('or import from csv file')) + ' ',
INPUT(_type='file', _name='csvfile'), INPUT(_type='file', _name='csvfile'),
INPUT(_type='hidden', _value=csv_table, _name='table'), INPUT(_type='hidden', _value=csv_table, _name='table'),
INPUT(_type='submit', _value=T('import'), _class="btn btn-primary")) INPUT(_type='submit', _value=T('import'), _class='btn btn-primary'))
else: else:
formcsv = None formcsv = None
if formcsv and formcsv.process().accepted: if formcsv and formcsv.process().accepted:
@@ -356,26 +356,26 @@ def state():
def ccache(): def ccache():
if is_gae: if is_gae:
form = FORM( form = FORM(
P(TAG.BUTTON(T("Clear CACHE?"), _type="submit", _name="yes", _value="yes"))) P(TAG.BUTTON(T('Clear CACHE?'), _type='submit', _name='yes', _value='yes')))
else: else:
cache.ram.initialize() cache.ram.initialize()
cache.disk.initialize() cache.disk.initialize()
form = FORM( form = FORM(
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")), T('Clear CACHE?'), _type='submit', _name='yes', _value='yes')),
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear RAM"), _type="submit", _name="ram", _value="ram")), T('Clear RAM'), _type='submit', _name='ram', _value='ram')),
P(TAG.BUTTON( P(TAG.BUTTON(
T("Clear DISK"), _type="submit", _name="disk", _value="disk")), T('Clear DISK'), _type='submit', _name='disk', _value='disk')),
) )
if form.accepts(request.vars, session): if form.accepts(request.vars, session):
session.flash = "" session.flash = ''
if is_gae: if is_gae:
if request.vars.yes: if request.vars.yes:
cache.ram.clear() cache.ram.clear()
session.flash += T("Cache Cleared") session.flash += T('Cache Cleared')
else: else:
clear_ram = False clear_ram = False
clear_disk = False clear_disk = False
@@ -387,10 +387,10 @@ def ccache():
clear_disk = True clear_disk = True
if clear_ram: if clear_ram:
cache.ram.clear() cache.ram.clear()
session.flash += T("Ram Cleared") session.flash += T('Ram Cleared')
if clear_disk: if clear_disk:
cache.disk.clear() cache.disk.clear()
session.flash += T("Disk Cleared") session.flash += T('Disk Cleared')
redirect(URL(r=request)) redirect(URL(r=request))
try: try:
@@ -436,7 +436,7 @@ def ccache():
gae_stats['ratio'] = ((gae_stats['hits'] * 100) / gae_stats['ratio'] = ((gae_stats['hits'] * 100) /
(gae_stats['hits'] + gae_stats['misses'])) (gae_stats['hits'] + gae_stats['misses']))
except ZeroDivisionError: except ZeroDivisionError:
gae_stats['ratio'] = T("?") gae_stats['ratio'] = T('?')
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age']) gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
total.update(gae_stats) total.update(gae_stats)
else: else:
@@ -502,7 +502,7 @@ def ccache():
TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))), TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))),
*[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys], *[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys],
**dict(_class='cache-keys', **dict(_class='cache-keys',
_style="border-collapse: separate; border-spacing: .5em;")) _style='border-collapse: separate; border-spacing: .5em;'))
if not is_gae: if not is_gae:
ram['keys'] = key_table(ram['keys']) ram['keys'] = key_table(ram['keys'])
@@ -536,26 +536,26 @@ def table_template(table):
# This is horribe HTML but the only one graphiz understands # This is horribe HTML but the only one graphiz understands
rows = [] rows = []
cellpadding = 4 cellpadding = 4
color = "#000000" color = '#000000'
bgcolor = "#FFFFFF" bgcolor = '#FFFFFF'
face = "Helvetica" face = 'Helvetica'
face_bold = "Helvetica Bold" face_bold = 'Helvetica Bold'
border = 0 border = 0
rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor),
_colspan=3, _cellpadding=cellpadding, _colspan=3, _cellpadding=cellpadding,
_align="center", _bgcolor=color))) _align='center', _bgcolor=color)))
for row in db[table]: for row in db[table]:
rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold), rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold),
_align="left", _cellpadding=cellpadding, _align='left', _cellpadding=cellpadding,
_border=border), _border=border),
TD(FONT(row.type, _color=color, _face=face), TD(FONT(row.type, _color=color, _face=face),
_align="left", _cellpadding=cellpadding, _align='left', _cellpadding=cellpadding,
_border=border), _border=border),
TD(FONT(types(row), _color=color, _face=face), TD(FONT(types(row), _color=color, _face=face),
_align="center", _cellpadding=cellpadding, _align='center', _cellpadding=cellpadding,
_border=border))) _border=border)))
return "< %s >" % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1, return '< %s >' % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1,
_cellborder=0, _cellspacing=0) _cellborder=0, _cellspacing=0)
).xml() ).xml()
@@ -632,15 +632,15 @@ def hooks():
if len(functions): if len(functions):
method_hooks.append({'name': op, 'functions':functions}) method_hooks.append({'name': op, 'functions':functions})
if len(method_hooks): if len(method_hooks):
tables.append({'name': "%s.%s" % (db_str, t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks}) tables.append({'name': '%s.%s' % (db_str, t), 'slug': IS_SLUG()('%s.%s' % (db_str,t))[0], 'method_hooks':method_hooks})
# Render # Render
ul_main = UL(_class='nav nav-list') ul_main = UL(_class='nav nav-list')
for t in tables: for t in tables:
ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug'])) ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug']))
ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none') ul_t = UL(_class='nav nav-list', _id='a_%s' % t['slug'], _style='display:none')
for op in t['method_hooks']: for op in t['method_hooks']:
ul_t.append(LI(op['name'])) ul_t.append(LI(op['name']))
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']])) ul_t.append(UL([LI(A(f['funcname'], _class='editor_filelink', _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
ul_main.append(ul_t) ul_main.append(ul_t)
return ul_main return ul_main
@@ -650,11 +650,11 @@ def hooks():
# ########################################################### # ###########################################################
def d3_graph_model(): def d3_graph_model():
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha ''' See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
and also the app_admin bg_graph_model function and also the app_admin bg_graph_model function
Create a list of table dicts, called "nodes" Create a list of table dicts, called 'nodes'
""" '''
nodes = [] nodes = []
links = [] links = []
@@ -670,10 +670,10 @@ def d3_graph_model():
elif f_type == 'string': elif f_type == 'string':
disp = field.length disp = field.length
elif f_type == 'id': elif f_type == 'id':
disp = "PK" disp = 'PK'
elif f_type.startswith('reference') or \ elif f_type.startswith('reference') or \
f_type.startswith('list:reference'): f_type.startswith('list:reference'):
disp = "FK" disp = 'FK'
else: else:
disp = ' ' disp = ' '
fields.append(dict(name=field.name, type=field.type, disp=disp)) fields.append(dict(name=field.name, type=field.type, disp=disp))
@@ -685,7 +685,7 @@ def d3_graph_model():
links.append(dict(source=tablename, target = referenced_table)) links.append(dict(source=tablename, target = referenced_table))
nodes.append(dict(name=tablename, type="table", fields = fields)) nodes.append(dict(name=tablename, type='table', fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below. # d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js')) response.files.append(URL('admin','static','js/d3.min.js'))
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+125 -83
View File
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'!=': '!=',
'!langcode!': 'pt-br', '!langcode!': 'pt-br',
'!langname!': 'Português (do Brasil)', '!langname!': 'Português (do Brasil)',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novovalor\'". Você não pode atualizar ou apagar os resultados de um JOIN', '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novovalor\'". Você não pode atualizar ou apagar os resultados de um JOIN',
'%(nrows)s records found': '%(nrows)s registros encontrados',
'%s %%{row} deleted': '%s linha apagadas', '%s %%{row} deleted': '%s linha apagadas',
'%s %%{row} updated': '%s linha atualizadas', '%s %%{row} updated': '%s linha atualizadas',
'%s selected': '%s selecionado', '%s selected': '%s selecionado',
@@ -12,6 +14,13 @@
'**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}', '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}',
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}', '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)', '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'+ And': '+ E',
'+ Or': '+ Ou',
'<': '<',
'<=': '<=',
'=': '=',
'>': '>',
'>=': '>=',
'?': '?', '?': '?',
'@markmin\x01(**%.0d MB**)': '(**%.0d MB**)', '@markmin\x01(**%.0d MB**)': '(**%.0d MB**)',
'@markmin\x01**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}', '@markmin\x01**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}',
@@ -22,23 +31,28 @@
'@markmin\x01Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.', '@markmin\x01Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'@markmin\x01DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.', '@markmin\x01DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'@markmin\x01Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})', '@markmin\x01Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
'@markmin\x01Number of entries: **%s**': 'Number of entries: **%s**', '@markmin\x01Number of entries: **%s**': 'Número de entradas: **%s**',
'@markmin\x01RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.', '@markmin\x01RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)', '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'A new password was emailed to you': 'A new password was emailed to you', 'A new password was emailed to you': 'Uma nova senha foi enviada por email para você',
'About': 'Sobre', 'About': 'Sobre',
'Access Control': 'Controle de Acesso', 'Access Control': 'Controle de Acesso',
'Add Record': 'Novo Registro',
'Add record to database': 'Adicionar registro ao banco de dados',
'Add this to the search as an AND term': 'Adicionar à pesquisa como um termo E',
'Add this to the search as an OR term': 'Adicionar à pesquisa como um termo OU',
'admin': 'admin', 'admin': 'admin',
'Administrative Interface': 'Interface Administrativa', 'Administrative Interface': 'Interface Administrativa',
'Administrative interface': 'Interface administrativa', 'Administrative interface': 'Interface administrativa',
'Ajax Recipes': 'Receitas de Ajax', 'Ajax Recipes': 'Receitas de Ajax',
'An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page', 'An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'API Example': 'API Example', 'API Example': 'Exmplo de API',
'appadmin is disabled because insecure channel': 'Administração desativada porque o canal não é seguro', 'appadmin is disabled because insecure channel': 'Administração desativada porque o canal não é seguro',
'Apply changes': 'Apply changes', 'Apply changes': 'Aplicar Mudanças',
'Are you sure you want to delete this object?': 'Você tem certeza que quer apagar este objeto?', 'Are you sure you want to delete this object?': 'Você tem certeza que quer apagar este objeto?',
'Authentication code': 'Authentication code', 'Authentication code': 'Código de autenticação',
'Available Databases and Tables': 'Bancos de dados e tabelas disponíveis', 'Available Databases and Tables': 'Bancos de dados e tabelas disponíveis',
'Back': 'Voltar',
'Buy this book': 'Compre o livro', 'Buy this book': 'Compre o livro',
"Buy web2py's book": 'Compre o livro do web2py', "Buy web2py's book": 'Compre o livro do web2py',
'cache': 'cache', 'cache': 'cache',
@@ -48,19 +62,27 @@
'Cache Keys': 'Chaves de cache', 'Cache Keys': 'Chaves de cache',
'Cannot be empty': 'Não pode estar vazio', 'Cannot be empty': 'Não pode estar vazio',
'change password': 'mudar senha', 'change password': 'mudar senha',
'Change Password': 'Change Password', 'Change Password': 'Trocar Senhar',
'Change password': 'Change password', 'Change password': 'Trocar senha',
'Check to delete': 'Marque para apagar', 'Check to delete': 'Marque para apagar',
'Clear': 'Limpar',
'Clear CACHE?': 'Limpar CACHE?', 'Clear CACHE?': 'Limpar CACHE?',
'Clear DISK': 'Limpar DISCO', 'Clear DISK': 'Limpar DISCO',
'Clear RAM': 'Limpar memória RAM', 'Clear RAM': 'Limpar memória RAM',
'Click on the link %(link)s to reset your password': 'Click on the link %(link)s to reset your password', 'Click on the link %(link)s to reset your password': 'Click on the link %(link)s to reset your password',
'Client IP': 'IP do cliente', 'Client IP': 'IP do cliente',
'Close': 'Fechar',
'Comma-separated export including columns not shown; fields from other tables are exported as raw values for faster export': 'Comma-separated export including columns not shown; fields from other tables are exported as raw values for faster export',
'Comma-separated export of visible columns. Fields from other tables are exported as they appear on-screen but this may be slow for many rows': 'Comma-separated export of visible columns. Fields from other tables are exported as they appear on-screen but this may be slow for many rows',
'Community': 'Comunidade', 'Community': 'Comunidade',
'Components and Plugins': 'Componentes e Plugins', 'Components and Plugins': 'Componentes e Plugins',
'Config.ini': 'Config.ini', 'Config.ini': 'Config.ini',
'Confirm Password': 'Confirme a Senha',
'contains': 'contém',
'Controller': 'Controlador', 'Controller': 'Controlador',
'Copyright': 'Copyright', 'Copyright': 'Copyright',
'CSV': 'CSV',
'CSV (hidden cols)': 'CSV (col. ocultas)',
'Current request': 'Requisição atual', 'Current request': 'Requisição atual',
'Current response': 'Resposta atual', 'Current response': 'Resposta atual',
'Current session': 'Sessão atual', 'Current session': 'Sessão atual',
@@ -68,9 +90,10 @@
'data uploaded': 'dados enviados', 'data uploaded': 'dados enviados',
'Database': 'banco de dados', 'Database': 'banco de dados',
'Database %s select': 'Selecionar banco de dados %s', 'Database %s select': 'Selecionar banco de dados %s',
'Database Administration (appadmin)': 'Database Administration (appadmin)', 'Database Administration (appadmin)': 'Administração de Banco de Dados (appadmin)',
'db': 'bd', 'db': 'bd',
'DB Model': 'Modelo BD', 'DB Model': 'Modelo BD',
'Delete': 'Excluir',
'Delete:': 'Apagar:', 'Delete:': 'Apagar:',
'Demo': 'Demo', 'Demo': 'Demo',
'Deployment Recipes': 'Receitas de deploy', 'Deployment Recipes': 'Receitas de deploy',
@@ -91,50 +114,57 @@
'edit profile': 'editar perfil', 'edit profile': 'editar perfil',
'Edit This App': 'Editar esta aplicação', 'Edit This App': 'Editar esta aplicação',
'Email and SMS': 'Email e SMS', 'Email and SMS': 'Email e SMS',
'Email sent': 'Email sent', 'Email sent': 'Email enviado',
'Email verification': 'Email verification', 'Email verification': 'Verificação de email',
'Email verified': 'Email verified', 'Email verified': 'Email verificado',
'Enter an integer between %(min)g and %(max)g': 'Informe um valor inteiro entre %(min)g e %(max)g', 'Enter an integer between %(min)g and %(max)g': 'Informe um valor inteiro entre %(min)g e %(max)g',
'Errors': 'Erros', 'Errors': 'Erros',
'export as csv file': 'exportar como um arquivo csv', 'export as csv file': 'exportar como um arquivo csv',
'Export:': 'Exportar:',
'FAQ': 'Perguntas frequentes', 'FAQ': 'Perguntas frequentes',
'First name': 'Nome', 'First name': 'Nome',
'Forms and Validators': 'Formulários e Validadores', 'Forms and Validators': 'Formulários e Validadores',
'Free Applications': 'Aplicações gratuitas', 'Free Applications': 'Aplicações gratuitas',
'Function disabled': 'Function disabled', 'Function disabled': 'Função desabilitada',
'Graph Model': 'Graph Model', 'Graph Model': 'Modelo em Grafo',
'Grid Example': 'Exemplo de Grade', 'Grid Example': 'Exemplo de Grade',
'Group %(group_id)s created': 'Group %(group_id)s created', 'Group %(group_id)s created': 'Grupo %(group_id)s criado',
'Group %(group_id)s deleted': 'Group %(group_id)s deleted', 'Group %(group_id)s deleted': 'Grupo %(group_id)s excluído',
'Group ID': 'ID do Grupo', 'Group ID': 'ID do Grupo',
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s', 'Group uniquely assigned to user %(id)s': 'Gurpo unicamente atribuído ao usuário %(id)s',
'Groups': 'Grupos', 'Groups': 'Grupos',
'Hello World': 'Olá Mundo', 'Hello World': 'Olá Mundo',
'Helping web2py': 'Helping web2py', 'Helping web2py': 'Ajudando web2py',
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})', 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
'Home': 'Principal', 'Home': 'Principal',
'How did you get here?': 'Como você chegou aqui?', 'How did you get here?': 'Como você chegou aqui?',
'HTML': 'HTML',
'HTML export of visible columns': 'HTML exportar colunas visíveis',
'Id': 'Id',
'import': 'importar', 'import': 'importar',
'Import/Export': 'Importar/Exportar', 'Import/Export': 'Importar/Exportar',
'in': 'em',
'Incorrect code. {0} more attempt(s) remaining.': 'Incorrect code. {0} more attempt(s) remaining.', 'Incorrect code. {0} more attempt(s) remaining.': 'Incorrect code. {0} more attempt(s) remaining.',
'Index': 'Início', 'Index': 'Início',
'insert new': 'inserir novo', 'insert new': 'inserir novo',
'insert new %s': 'inserir novo %s', 'insert new %s': 'inserir novo %s',
'Insufficient privileges': 'Insufficient privileges', 'Insufficient privileges': 'Privilégios insuficientes',
'Internal State': 'Estado Interno', 'Internal State': 'Estado Interno',
'Introduction': 'Introdução', 'Introduction': 'Introdução',
'Invalid email': 'Email inválido', 'Invalid email': 'Email inválido',
'Invalid key': 'Invalid key', 'Invalid key': 'Chave inválida',
'Invalid login': 'Invalid login', 'Invalid login': 'Login Inválido',
'Invalid password': 'Invalid password', 'Invalid password': 'Senha inválida',
'Invalid Query': 'Consulta Inválida', 'Invalid Query': 'Consulta Inválida',
'invalid request': 'requisição inválida', 'invalid request': 'requisição inválida',
'Invalid reset password': 'Invalid reset password', 'Invalid reset password': 'Recriação de senha inválida',
'Invalid user': 'Invalid user', 'Invalid user': 'Usuário inválido',
'Invalid username': 'Invalid username', 'Invalid username': 'Nome de usuário inválido',
'Invitation to join %(site)s': 'Invitation to join %(site)s', 'Invitation to join %(site)s': 'Convite para entrar %(site)s',
'JSON': 'JSON',
'JSON export of visible columns': 'JSON exportar colunas visíveis',
'Key': 'Chave', 'Key': 'Chave',
'Key verified': 'Key verified', 'Key verified': 'Chave verificada',
'Last name': 'Sobrenome', 'Last name': 'Sobrenome',
'Layout': 'Layout', 'Layout': 'Layout',
'Layout Plugins': 'Plugins de Layout', 'Layout Plugins': 'Plugins de Layout',
@@ -142,13 +172,13 @@
'Live chat': 'Chat ao vivo', 'Live chat': 'Chat ao vivo',
'Live Chat': 'Chat ao vivo', 'Live Chat': 'Chat ao vivo',
'Log In': 'Entrar', 'Log In': 'Entrar',
'Logged in': 'Logged in', 'Logged in': 'Conectado',
'Logged out': 'Logged out', 'Logged out': 'Desconectado',
'login': 'Entrar', 'login': 'Entrar',
'Login': 'Entrar', 'Login': 'Entrar',
'Login disabled by administrator': 'Login disabled by administrator', 'Login disabled by administrator': 'Login desabilitado pelo administrador',
'logout': 'Sair', 'logout': 'Sair',
'Logout': 'Logout', 'Logout': 'Sair',
'Lost Password': 'Esqueceu sua senha?', 'Lost Password': 'Esqueceu sua senha?',
'lost password?': 'esqueceu sua senha?', 'lost password?': 'esqueceu sua senha?',
'Lost your password?': 'Esqueceu sua senha?', 'Lost your password?': 'Esqueceu sua senha?',
@@ -160,16 +190,19 @@
'Menu Model': 'Modelo de Menu', 'Menu Model': 'Modelo de Menu',
'My Sites': 'Meus sites', 'My Sites': 'Meus sites',
'Name': 'Nome', 'Name': 'Nome',
'New password': 'New password', 'New password': 'Nova senha',
'New Record': 'Novo Registro', 'New Record': 'Novo Registro',
'new record inserted': 'novo registro inserido', 'new record inserted': 'novo registro inserido',
'New Search': 'Nova pesquisa',
'next %s rows': 'próximas %s ´linhas', 'next %s rows': 'próximas %s ´linhas',
'next 100 rows': 'próximas 100 linhas', 'next 100 rows': 'próximas 100 linhas',
'No databases in this application': 'Não há bancos de dados nesta aplicação', 'No databases in this application': 'Não há bancos de dados nesta aplicação',
'No records found': 'Não foram encontrados registros',
'not in': 'não está em',
'Number of entries: **%s**': 'Número de entradas: **%s**', 'Number of entries: **%s**': 'Número de entradas: **%s**',
'Object or table name': 'Nome do objeto do da tabela', 'Object or table name': 'Nome do objeto do da tabela',
'Old password': 'Old password', 'Old password': 'Senha antiga',
'Online book': 'Online book', 'Online book': 'Livro online',
'Online examples': 'Exemplos online', 'Online examples': 'Exemplos online',
'or import from csv file': 'ou importar de um arquivo csv', 'or import from csv file': 'ou importar de um arquivo csv',
'Origin': 'Origem', 'Origin': 'Origem',
@@ -177,66 +210,71 @@
'Other Recipes': 'Outras Receitas', 'Other Recipes': 'Outras Receitas',
'Overview': 'Visão Geral', 'Overview': 'Visão Geral',
'Password': 'Senha', 'Password': 'Senha',
'Password changed': 'Password changed', 'Password changed': 'Senha trocada',
"Password fields don't match": "Password fields don't match", "Password fields don't match": 'Senhas não conferem',
'Password reset': 'Password reset', 'Password reset': 'Recriar senha',
'Password retrieve': 'Password retrieve', 'Password retrieve': 'Recuperar senha',
'Permission': 'Permission', 'Permission': 'Permissão',
'Permissions': 'Permissions', 'Permissions': 'Permissões',
'please input your password again': 'please input your password again', 'please input your password again': 'por favor digite a senha novamente',
'Plugins': 'Plugins', 'Plugins': 'Plugins',
'Powered by': 'Desenvolvido com', 'Powered by': 'Desenvolvido com',
'Preface': 'Prefácio', 'Preface': 'Prefácio',
'previous %s rows': '%s linhas anteriores', 'previous %s rows': '%s linhas anteriores',
'previous 100 rows': '100 linhas anteriores', 'previous 100 rows': '100 linhas anteriores',
'Profile': 'Profile', 'Profile': 'Perfil',
'Profile updated': 'Profile updated', 'Profile updated': 'Perfil atualizado',
'pygraphviz library not found': 'biblioteca pygraphviz não encontrada', 'pygraphviz library not found': 'biblioteca pygraphviz não encontrada',
'Python': 'Python', 'Python': 'Python',
'Query:': 'Consulta:', 'Query:': 'Consulta:',
'Quick Examples': 'Exemplos rápidos', 'Quick Examples': 'Exemplos rápidos',
'RAM': 'RAM', 'RAM': 'RAM',
'RAM Cache Keys': 'RAM Cache Keys', 'RAM Cache Keys': 'Chaves de Cache RAM ',
'Ram Cleared': 'Ram Limpa', 'Ram Cleared': 'Ram Limpa',
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.', 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Recipes': 'Receitas', 'Recipes': 'Receitas',
'Record': 'Registro', 'Record': 'Registro',
'Record %(id)s created': 'Record %(id)s created', 'Record %(id)s created': 'Registro %(id)s criado',
'Record %(id)s deleted': 'Record %(id)s deleted', 'Record %(id)s deleted': 'Registro %(id)s excluído',
'Record %(id)s read': 'Record %(id)s read', 'Record %(id)s read': 'Registro %(id)s lido',
'Record %(id)s updated': 'Record %(id)s updated', 'Record %(id)s updated': 'Registro %(id)s atualizado',
'Record Created': 'Record Created', 'Record Created': 'Registro Criado',
'Record Deleted': 'Record Deleted', 'Record Deleted': 'Registro Excluído',
'record does not exist': 'registro não existe', 'record does not exist': 'registro não existe',
'Record ID': 'ID do Registro', 'Record ID': 'ID do Registro',
'Record id': 'id do registro', 'Record id': 'id do registro',
'Record Updated': 'Record Updated', 'Record Updated': 'Registro Atualizado',
'register': 'Registre-se', 'register': 'Cadastre-se',
'Register': 'Registre-se', 'Register': 'Cadastre-se',
'Registration identifier': 'Idenficador de registro', 'Registration identifier': 'Idenficador de Cadastro',
'Registration is pending approval': 'Registration is pending approval', 'Registration is pending approval': 'Aprovação de cadastro pendente',
'Registration key': 'Chave de registro', 'Registration key': 'Chave de cadastro',
'Registration needs verification': 'Registration needs verification', 'Registration needs verification': 'Cadastro necessita verficação',
'Registration successful': 'Registration successful', 'Registration successful': 'Cadastro finalizado',
'Remember me (for 30 days)': 'Mantenha-me logado (por 30 dias)', 'Remember me (for 30 days)': 'Mantenha-me logado (por 30 dias)',
'Request reset password': 'Request reset password', 'Request reset password': 'Requerer recriação de senha',
'Reset Password key': 'Resetar chave de senha', 'Reset Password key': 'Resetar chave de senha',
'Resources': 'Recursos', 'Resources': 'Recursos',
'Role': 'Papel', 'Role': 'Papel',
'Roles': 'Roles', 'Roles': 'Papéis',
'Rows in Table': 'Linhas na tabela', 'Rows in Table': 'Linhas na tabela',
'Rows selected': 'Linhas selecionadas', 'Rows selected': 'Linhas selecionadas',
'Save model as...': 'Salvar modelo como...', 'Save model as...': 'Salvar modelo como...',
'Search': 'Pesquisar',
'Semantic': 'Semântico', 'Semantic': 'Semântico',
'Services': 'Serviço', 'Services': 'Serviço',
'Sign Up': 'Cadastrar', 'Sign Up': 'Cadastrar',
'Sign up': 'Cadastrar', 'Sign up': 'Cadastrar',
'Size of cache:': 'Tamanho do cache:', 'Size of cache:': 'Tamanho do cache:',
'Spreadsheet-optimised export of tab-separated content including hidden columns. May be slow': 'Spreadsheet-optimised export of tab-separated content including hidden columns. May be slow',
'Spreadsheet-optimised export of tab-separated content, visible columns only. May be slow.': 'Spreadsheet-optimised export of tab-separated content, visible columns only. May be slow.',
'Start building a new search': 'Comerçar um nova pesquisa',
'starts with': 'começa com',
'state': 'estado', 'state': 'estado',
'Statistics': 'Estatísticas', 'Statistics': 'Estatísticas',
'Stylesheet': 'Folha de estilo', 'Stylesheet': 'Folha de estilo',
'submit': 'enviar', 'submit': 'enviar',
'Submit': 'Submit', 'Submit': 'Enviar',
'Support': 'Suporte', 'Support': 'Suporte',
'Sure you want to delete this object?': 'Está certo(a) que deseja apagar este objeto?', 'Sure you want to delete this object?': 'Está certo(a) que deseja apagar este objeto?',
'Table': 'Tabela', 'Table': 'Tabela',
@@ -252,31 +290,33 @@
'Time in Cache (h:m:s)': 'Tempo em Cache (h:m:s)', 'Time in Cache (h:m:s)': 'Tempo em Cache (h:m:s)',
'Timestamp': 'Timestamp', 'Timestamp': 'Timestamp',
'Traceback': 'Traceback', 'Traceback': 'Traceback',
'TSV (Spreadsheets)': 'TSV (Planilhas)',
'TSV (Spreadsheets, hidden cols)': 'TSV (Planilhas, col. ocultas)',
'Twitter': 'Twitter', 'Twitter': 'Twitter',
'Two-step Login Authentication Code': 'Two-step Login Authentication Code', 'Two-step Login Authentication Code': 'Código de Autenticação de Login em Dois Fatores',
'unable to parse csv file': 'não foi possível analisar arquivo csv', 'unable to parse csv file': 'não foi possível analisar arquivo csv',
'Unable to send email': 'Unable to send email', 'Unable to send email': 'Não foi possível enviar email',
'Update:': 'Atualizar:', 'Update:': 'Atualizar:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir consultas mais complexas.', 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, e ~(...) para NOT para construir consultas mais complexas.',
'User': 'User', 'User': 'Usuário',
'User %(id)s is impersonating %(other_id)s': 'User %(id)s is impersonating %(other_id)s', 'User %(id)s is impersonating %(other_id)s': 'User %(id)s is impersonating %(other_id)s',
'User %(id)s Logged-in': 'User %(id)s Logged-in', 'User %(id)s Logged-in': 'Usuário %(id)s entrou',
'User %(id)s Logged-out': 'User %(id)s Logged-out', 'User %(id)s Logged-out': 'Usuário %(id)s saiu',
'User %(id)s Password changed': 'User %(id)s Password changed', 'User %(id)s Password changed': 'Usuário %(id)s trocou a senha',
'User %(id)s Password reset': 'User %(id)s Password reset', 'User %(id)s Password reset': 'Usuário %(id)s recirar a senha',
'User %(id)s Password retrieved': 'User %(id)s Password retrieved', 'User %(id)s Password retrieved': 'Usuário %(id)s Recuperou a senha',
'User %(id)s Profile updated': 'User %(id)s Profile updated', 'User %(id)s Profile updated': 'Usuário %(id)s Atualizou perfil',
'User %(id)s Registered': 'User %(id)s Registered', 'User %(id)s Registered': 'Usuário %(id)s Cadastrou-se',
'User %(id)s Username retrieved': 'User %(id)s Username retrieved', 'User %(id)s Username retrieved': 'Usuário %(id)s Recuperou nome de usuário',
'User %(id)s Verification email sent': 'User %(id)s Verification email sent', 'User %(id)s Verification email sent': 'Usuário %(id)s Email de verificação enviado',
'User %(id)s verified registration key': 'User %(id)s verified registration key', 'User %(id)s verified registration key': 'Usuário %(id)s chave de cadastro verificada',
'User ID': 'ID do Usuário', 'User ID': 'ID do Usuário',
'User Voice': 'Opinião dos usuários', 'User Voice': 'Opinião dos usuários',
'Username': 'Username', 'Username': 'Nome de Usuário',
'Username already taken': 'Username already taken', 'Username already taken': 'Nome de usuário já existe',
'Username retrieve': 'Username retrieve', 'Username retrieve': 'Recuperar nome de usuário',
'Users': 'Users', 'Users': 'Usuários',
'Verify Password': 'Verify Password', 'Verify Password': 'Verificar Senha',
'Videos': 'Vídeos', 'Videos': 'Vídeos',
'View': 'Visualização', 'View': 'Visualização',
'Web2py': 'Web2py', 'Web2py': 'Web2py',
@@ -286,15 +326,17 @@
'Welcome to web2py': 'Bem-vindo ao web2py', 'Welcome to web2py': 'Bem-vindo ao web2py',
'Welcome to web2py!': 'Bem-vindo ao web2py!', 'Welcome to web2py!': 'Bem-vindo ao web2py!',
'Which called the function %s located in the file %s': 'Que chamou a função %s localizada no arquivo %s', 'Which called the function %s located in the file %s': 'Que chamou a função %s localizada no arquivo %s',
'Wiki Example': 'Wiki Example', 'Wiki Example': 'Exmplo de Wiki',
'Working...': 'Trabalhando...', 'Working...': 'Trabalhando...',
'XML': 'XML',
'XML export of columns shown': 'XML exportar colunas visíveis',
'You are successfully running web2py': 'Você está executando o web2py com sucesso', 'You are successfully running web2py': 'Você está executando o web2py com sucesso',
'You are successfully running web2py.': 'Você está executando o web2py com sucesso.', 'You are successfully running web2py.': 'Você está executando o web2py com sucesso.',
'You can modify this application and adapt it to your needs': 'Você pode modificar esta aplicação e adaptá-la às suas necessidades', 'You can modify this application and adapt it to your needs': 'Você pode modificar esta aplicação e adaptá-la às suas necessidades',
'You have been invited to join %(site)s, click %(link)s to complete the process': 'You have been invited to join %(site)s, click %(link)s to complete the process', 'You have been invited to join %(site)s, click %(link)s to complete the process': 'You have been invited to join %(site)s, click %(link)s to complete the process',
'You visited the url %s': 'Você acessou a url %s', 'You visited the url %s': 'Você acessou a url %s',
'Your password is: %(password)s': 'Your password is: %(password)s', 'Your password is: %(password)s': 'Sua senha é: %(password)s',
'Your temporary login code is {0}': 'Your temporary login code is {0}', 'Your temporary login code is {0}': 'Seu código temporário de login é {0}',
'Your username is: %(username)s': 'Your username is: %(username)s', 'Your username is: %(username)s': 'Seu nome de usuário é: %(username)s',
'Your username was emailed to you': 'Your username was emailed to you', 'Your username was emailed to you': 'Seu nome de usuário foi enviado por email para você',
} }
View File
View File
View File
View File
View File
View File
View File
View File
View File
-2
View File
@@ -1,2 +0,0 @@
The files in this folder must be run from the main web2py folder.
They are for building windows and osx binary distribution and not meant for the end user.
+4
View File
@@ -0,0 +1,4 @@
# build-web2py
The files in this folder must be run from the main web2py folder.
They are for building windows and osx binary distribution using PyInstaller and not meant for the end user.
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# up to 2019, we have used py2applet, py2exe and bbfreeze for building web2py binaries
# The original scripts can be found on GitHub for web2py up to version 2.18.4
# See also Niphlod's work on http://www.web2pyslices.com/slice/show/1726/build-windows-binaries
# Then we switched to Pyinstaller in order to fully support Python 3
from distutils.core import setup
from gluon.import_all import base_modules, contributed_modules
from gluon.fileutils import readlines_file
from glob import glob
import os
import shutil
import sys
import re
import zipfile
import subprocess
import platform
USAGE = """
build_web2py - make web2py Windows and MacOS binaries with pyinstaller
Usage:
Install the pyinstaller program, copy this file (plus web2py.*.spec files)
to web2py root folder and run:
python build_py3.py
(tested with python 3.7.3 and 2.7.16 with PyInstaller 3.4)
"""
BUILD_DEBUG = False
"""
If BUILD_DEBUG is set to False, no gluon modules will be embedded inside the binary web2py.exe.
Thus, you can easily update the build version by changing the gluon folder inside the resulting ZIP file.
In case of problem , set BUILD_DEBUG to True. Then all the gluon modules will be analyzed and embedded, too.
You can later analyze the .exe with 'pyi-archive_viewer web2py.exe' and then 'o PYZ-00.pyz'
in order to check for missing system modules to be manually inserted in the SPEC file
"""
if len(sys.argv) != 1 or not os.path.isfile('web2py.py'):
print(USAGE)
sys.exit(1)
os_version = platform.system()
if os_version not in ('Windows', 'Darwin'):
print('Unsupported system: %s' % os_version)
sys.exit(1)
def unzip(source_filename, dest_dir):
with zipfile.ZipFile(source_filename) as zf:
zf.extractall(dest_dir)
# borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile
def recursive_zip(zipf, directory, folder=""):
for item in os.listdir(directory):
if os.path.isfile(os.path.join(directory, item)):
zipf.write(os.path.join(directory, item), folder + os.sep + item)
elif os.path.isdir(os.path.join(directory, item)):
recursive_zip(
zipf, os.path.join(directory, item), folder + os.sep + item)
# read web2py version from VERSION file
web2py_version_line = readlines_file('VERSION')[0]
# use regular expression to get just the version number
v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+')
web2py_version = v_re.search(web2py_version_line).group(0)
# Python base version
python_version = sys.version_info[:3]
if os_version == 'Windows':
print("\nBuilding binary web2py for Windows\n")
if BUILD_DEBUG: # debug only
subprocess.call('pyinstaller --clean --icon=extras/icons/web2py.ico \
--hidden-import=site-packages --hidden-import=gluon.packages.dal.pydal \
--hidden-import=gluon.packages.yatl.yatl web2py.py')
zip_filename = 'web2py_win_debug'
else: # normal run
subprocess.call('pyinstaller --clean web2py.win.spec')
subprocess.call('pyinstaller --clean web2py.win_no_console.spec')
source_no_console = 'dist/web2py_no_console/'
files = 'web2py_no_console.exe'
shutil.move(os.path.join(source_no_console, files), 'dist')
shutil.rmtree(source_no_console)
shutil.rmtree('build')
zip_filename = 'web2py_win'
source = 'dist/web2py/'
for files in os.listdir(source):
shutil.move(os.path.join(source, files), 'dist')
shutil.rmtree(source)
os.unlink('dist/web2py.exe.manifest')
bin_folders = ['dist',]
elif os_version == 'Darwin':
print("\nBuilding binary web2py for MacOS\n")
if BUILD_DEBUG: #debug only
subprocess.call("pyinstaller --clean --icon=extras/icons/web2py.icns --hidden-import=gluon.packages.dal.pydal --hidden-import=gluon.packages.yatl.yatl \
--hidden-import=site-packages --windowed web2py.py", shell=True)
zip_filename = 'web2py_osx_debug'
else: # normal run
subprocess.call("pyinstaller --clean web2py.mac.spec", shell=True)
# cleanup + move binary files to dist folder
#shutil.rmtree(os.path.join('dist', 'web2py'))
shutil.rmtree('build')
zip_filename = 'web2py_osx'
shutil.move((os.path.join('dist', 'web2py')),(os.path.join('dist', 'web2py_cmd')))
bin_folders = [(os.path.join('dist', 'web2py.app/Contents/MacOS')), (os.path.join('dist', 'web2py_cmd'))]
print("\nWeb2py binary successfully built!\n")
# add data_files
for req in ['CHANGELOG', 'LICENSE', 'VERSION']:
for bin_folder in bin_folders:
shutil.copy(req, os.path.join(bin_folder, req))
# cleanup unuseful binary cache
for dirpath, dirnames, files in os.walk('.'):
if dirpath.endswith('__pycache__'):
print('Deleting cached binary directory : %s' % dirpath)
shutil.rmtree(dirpath)
for dirpath, dirnames, files in os.walk('.'):
for file in files:
if file.endswith('.pyc'):
print('Deleting cached binary file : %s' % file)
os.unlink(os.path.join(dirpath, file))
print("\nPreparing package ...")
# misc
for folders in ['gluon', 'extras', 'site-packages', 'scripts', 'applications', 'examples', 'handlers']:
for bin_folder in bin_folders:
shutil.copytree(folders, os.path.join(bin_folder, folders))
if not os.path.exists(os.path.join(bin_folder, 'logs')):
os.mkdir(os.path.join(bin_folder, 'logs'))
# create a web2py folder & copy dist's files into it
shutil.copytree('dist', 'zip_temp/web2py')
# create zip file
zipf = zipfile.ZipFile(zip_filename + ".zip",
"w", compression=zipfile.ZIP_DEFLATED)
# just temp so the web2py directory is included in our zip file
path = 'zip_temp'
# leave the first folder as None, as path is root.
recursive_zip(zipf, path)
zipf.close()
shutil.rmtree('zip_temp')
shutil.rmtree('dist')
print("Your binary version of web2py can be found in " + \
zip_filename + ".zip")
print("You may extract the archive anywhere and then run web2py without worrying about dependency")
print("\nEnjoy binary web2py " + web2py_version_line + "\n with embedded Python " + sys.version + "\n")
-160
View File
@@ -1,160 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This is a setup.py script generated by py2applet
Usage:
python setup.py py2app
"""
copy_apps = False
copy_scripts = True
copy_site_packages = True
remove_build_files = True
make_zip = True
zip_filename = "web2py_osx"
from setuptools import setup
from gluon.import_all import base_modules, contributed_modules
from gluon.fileutils import readlines_file
import os
import fnmatch
import shutil
import sys
import re
import zipfile
#read web2py version from VERSION file
web2py_version_line = readlines_file('VERSION')[0]
#use regular expression to get just the version number
v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+')
web2py_version = v_re.search(web2py_version_line).group(0)
class reglob:
def __init__(self, directory, pattern="*"):
self.stack = [directory]
self.pattern = pattern
self.files = []
self.index = 0
def __getitem__(self, index):
while 1:
try:
file = self.files[self.index]
self.index = self.index + 1
except IndexError:
self.index = 0
self.directory = self.stack.pop()
self.files = os.listdir(self.directory)
else:
fullname = os.path.join(self.directory, file)
if os.path.isdir(fullname) and not os.path.islink(fullname):
self.stack.append(fullname)
if not (file.startswith('.') or file.startswith('#') or file.endswith('~')) \
and fnmatch.fnmatch(file, self.pattern):
return fullname
setup(app=['web2py.py'],
version=web2py_version,
description="web2py web framework",
author="Massimo DiPierro",
license="LGPL v3",
data_files=[
'NEWINSTALL',
'ABOUT',
'LICENSE',
'VERSION',
'splashlogo.gif',
'logging.example.conf',
'options_std.py',
],
options={'py2app': {
'argv_emulation': True,
'includes': base_modules,
}},
setup_requires=['py2app'])
def copy_folders(source, destination):
"""Copy files & folders from source to destination (within dist/)"""
print 'copying %s -> %s' % (source, destination)
base = 'dist/web2py.app/Contents/Resources/'
if os.path.exists(os.path.join(base, destination)):
shutil.rmtree(os.path.join(base, destination))
shutil.copytree(os.path.join(source), os.path.join(base, destination))
#Should we include applications?
copy_folders('gluon','gluon')
if copy_apps:
copy_folders('applications', 'applications')
print "Your application(s) have been added"
else:
#only copy web2py's default applications
copy_folders('applications/admin', 'applications/admin')
copy_folders('applications/welcome', 'applications/welcome')
copy_folders('applications/examples', 'applications/examples')
print "Only web2py's admin, examples & welcome applications have been added"
#should we copy project's site-packages into dist/site-packages
if copy_site_packages:
#copy site-packages
copy_folders('site-packages', 'site-packages')
else:
#no worries, web2py will create the (empty) folder first run
print "Skipping site-packages"
pass
#should we copy project's scripts into dist/scripts
if copy_scripts:
#copy scripts
copy_folders('scripts', 'scripts')
else:
#no worries, web2py will create the (empty) folder first run
print "Skipping scripts"
pass
#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile
def recursive_zip(zipf, directory, folder=""):
for item in os.listdir(directory):
if os.path.isfile(os.path.join(directory, item)):
zipf.write(os.path.join(directory, item), folder + os.sep + item)
elif os.path.isdir(os.path.join(directory, item)):
recursive_zip(
zipf, os.path.join(directory, item), folder + os.sep + item)
#should we create a zip file of the build?
if make_zip:
#to keep consistent with how official web2py windows zip file is setup,
#create a web2py folder & copy dist's files into it
shutil.copytree('dist', 'zip_temp/web2py')
#create zip file
#use filename specified via command line
zipf = zipfile.ZipFile(
zip_filename + ".zip", "w", compression=zipfile.ZIP_DEFLATED)
path = 'zip_temp' # just temp so the web2py directory is included in our zip file
recursive_zip(
zipf, path) # leave the first folder as None, as path is root.
zipf.close()
shutil.rmtree('zip_temp')
print "Your Windows binary version of web2py can be found in " + \
zip_filename + ".zip"
print "You may extract the archive anywhere and then run web2py/web2py.exe"
#should py2exe build files be removed?
if remove_build_files:
shutil.rmtree('build')
shutil.rmtree('deposit')
shutil.rmtree('dist')
print "py2exe build files removed"
#final info
if not make_zip and not remove_build_files:
print "Your Windows binary & associated files can also be found in /dist"
print "Finished!"
print "Enjoy web2py " + web2py_version_line
-27
View File
@@ -1,27 +0,0 @@
[Setup]
#py2exe often includes DLLS from windows which aren't licensed for
#open source distribution. Should they be removed?
remove_microsoft_dlls: Yes
#copy all web2py apps currently installed?
#If no, only the default admin, welcome & example apps will be included
copy_apps: No
#include the web2py\site-packages directory?
copy_site_packages: Yes
#include the web2py\scripts directory?
copy_scripts: Yes
#create a zip file of the build for easy distribution?
make_zip: Yes
#what should the zip file be named? (leave off the .zip extension)
zip_filename = web2py_win
#should the build, deposit & dist directories used by py2exe be removed?
#if you created a zip file you likely don't need these directories anymore
remove_build_files = Yes
#should the build include the gevented webserver (needs gevent)
include_gevent = Yes
-232
View File
@@ -1,232 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Adapted from http://bazaar.launchpad.net/~flavour/sahana-eden/trunk/view/head:/static/scripts/tools/standalone_exe.py
USAGE = """
Usage:
Copy this and setup_exe.conf to web2py root folder
To build with py2exe:
Install py2exe: http://sourceforge.net/projects/py2exe/files/
run python setup_exe.py py2exe
To build with bbfreeze:
Install bbfreeze: https://pypi.python.org/pypi/bbfreeze/
run python setup_exe.py bbfreeze
"""
from distutils.core import setup
from gluon.import_all import base_modules, contributed_modules
from gluon.fileutils import readlines_file
from glob import glob
import fnmatch
import os
import shutil
import sys
import re
import zipfile
if len(sys.argv) != 2 or not os.path.isfile('web2py.py'):
print USAGE
sys.exit(1)
BUILD_MODE = sys.argv[1]
if not BUILD_MODE in ('py2exe', 'bbfreeze'):
print USAGE
sys.exit(1)
def unzip(source_filename, dest_dir):
with zipfile.ZipFile(source_filename) as zf:
zf.extractall(dest_dir)
#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile
def recursive_zip(zipf, directory, folder=""):
for item in os.listdir(directory):
if os.path.isfile(os.path.join(directory, item)):
zipf.write(os.path.join(directory, item), folder + os.sep + item)
elif os.path.isdir(os.path.join(directory, item)):
recursive_zip(
zipf, os.path.join(directory, item), folder + os.sep + item)
#read web2py version from VERSION file
web2py_version_line = readlines_file('VERSION')[0]
#use regular expression to get just the version number
v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+')
web2py_version = v_re.search(web2py_version_line).group(0)
#pull in preferences from config file
import ConfigParser
Config = ConfigParser.ConfigParser()
Config.read('setup_exe.conf')
remove_msft_dlls = Config.getboolean("Setup", "remove_microsoft_dlls")
copy_apps = Config.getboolean("Setup", "copy_apps")
copy_site_packages = Config.getboolean("Setup", "copy_site_packages")
copy_scripts = Config.getboolean("Setup", "copy_scripts")
make_zip = Config.getboolean("Setup", "make_zip")
zip_filename = Config.get("Setup", "zip_filename")
remove_build_files = Config.getboolean("Setup", "remove_build_files")
include_gevent = Config.getboolean("Setup", "include_gevent")
# Python base version
python_version = sys.version_info[:3]
if BUILD_MODE == 'py2exe':
import py2exe
setup(
console=[{'script':'web2py.py',
'icon_resources': [(0, 'extras/icons/web2py.ico')]
}],
windows=[{'script':'web2py.py',
'icon_resources': [(1, 'extras/icons/web2py.ico')],
'dest_base':'web2py_no_console' # MUST NOT be just 'web2py' otherwise it overrides the standard web2py.exe
}],
name="web2py",
version=web2py_version,
description="web2py web framework",
author="Massimo DiPierro",
license="LGPL v3",
data_files=[
'ABOUT',
'LICENSE',
'VERSION'
],
options={'py2exe': {
'packages': contributed_modules,
'includes': base_modules,
}},
)
#py2exe packages lots of duplicates in the library.zip, let's save some space
library_temp_dir = os.path.join('dist', 'library_temp')
library_zip_archive = os.path.join('dist', 'library.zip')
os.makedirs(library_temp_dir)
unzip(library_zip_archive, library_temp_dir)
os.unlink(library_zip_archive)
zipl = zipfile.ZipFile(library_zip_archive, "w", compression=zipfile.ZIP_DEFLATED)
recursive_zip(zipl, library_temp_dir)
zipl.close()
shutil.rmtree(library_temp_dir)
print "web2py binary successfully built"
elif BUILD_MODE == 'bbfreeze':
modules = base_modules + contributed_modules
from bbfreeze import Freezer
f = Freezer(distdir="dist", includes=(modules))
f.addScript("web2py.py")
#to make executable without GUI we need this trick
shutil.copy("web2py.py", "web2py_no_console.py")
f.addScript("web2py_no_console.py", gui_only=True)
if include_gevent:
#fetch the gevented webserver script and copy to root
gevented_webserver = os.path.join("handlers", "web2py_on_gevent.py")
shutil.copy(gevented_webserver, "web2py_on_gevent.py")
f.addScript("web2py_on_gevent.py")
f.setIcon('extras/icons/web2py.ico')
f() # starts the freezing process
os.unlink("web2py_no_console.py")
if include_gevent:
os.unlink("web2py_on_gevent.py")
#add data_files
for req in ['ABOUT', 'LICENSE', 'VERSION']:
shutil.copy(req, os.path.join('dist', req))
print "web2py binary successfully built"
try:
os.unlink('storage.sqlite')
except:
pass
#This need to happen after bbfreeze is run because Freezer() deletes distdir before starting!
if python_version > (2,5):
# Python26 compatibility: http://www.py2exe.org/index.cgi/Tutorial#Step52
try:
shutil.copytree('C:\Bin\Microsoft.VC90.CRT', 'dist/Microsoft.VC90.CRT/')
except:
print "You MUST copy Microsoft.VC90.CRT folder into the archive"
def copy_folders(source, destination):
"""Copy files & folders from source to destination (within dist/)"""
if os.path.exists(os.path.join('dist', destination)):
shutil.rmtree(os.path.join('dist', destination))
shutil.copytree(os.path.join(source), os.path.join('dist', destination))
#should we remove Windows OS dlls user is unlikely to be able to distribute
if remove_msft_dlls:
print "Deleted Microsoft files not licensed for open source distribution"
print "You are still responsible for making sure you have the rights to distribute any other included files!"
#delete the API-MS-Win-Core DLLs
for f in glob('dist/API-MS-Win-*.dll'):
os.unlink(f)
#then delete some other files belonging to Microsoft
other_ms_files = ['KERNELBASE.dll', 'MPR.dll', 'MSWSOCK.dll',
'POWRPROF.dll']
for f in other_ms_files:
try:
os.unlink(os.path.join('dist', f))
except:
print "unable to delete dist/" + f
#Should we include applications?
if copy_apps:
copy_folders('applications', 'applications')
print "Your application(s) have been added"
else:
#only copy web2py's default applications
copy_folders('applications/admin', 'applications/admin')
copy_folders('applications/welcome', 'applications/welcome')
copy_folders('applications/examples', 'applications/examples')
print "Only web2py's admin, examples & welcome applications have been added"
copy_folders('extras', 'extras')
copy_folders('examples', 'examples')
copy_folders('handlers', 'handlers')
#should we copy project's site-packages into dist/site-packages
if copy_site_packages:
#copy site-packages
copy_folders('site-packages', 'site-packages')
else:
#no worries, web2py will create the (empty) folder first run
print "Skipping site-packages"
#should we copy project's scripts into dist/scripts
if copy_scripts:
#copy scripts
copy_folders('scripts', 'scripts')
else:
#no worries, web2py will create the (empty) folder first run
print "Skipping scripts"
#should we create a zip file of the build?
if make_zip:
#create a web2py folder & copy dist's files into it
shutil.copytree('dist', 'zip_temp/web2py')
#create zip file
zipf = zipfile.ZipFile(zip_filename + ".zip",
"w", compression=zipfile.ZIP_DEFLATED)
# just temp so the web2py directory is included in our zip file
path = 'zip_temp'
# leave the first folder as None, as path is root.
recursive_zip(zipf, path)
zipf.close()
shutil.rmtree('zip_temp')
print "Your Windows binary version of web2py can be found in " + \
zip_filename + ".zip"
print "You may extract the archive anywhere and then run web2py/web2py.exe"
#should py2exe build files be removed?
if remove_build_files:
if BUILD_MODE == 'py2exe':
shutil.rmtree('build')
shutil.rmtree('deposit')
shutil.rmtree('dist')
print "build files removed"
#final info
if not make_zip and not remove_build_files:
print "Your Windows binary & associated files can also be found in /dist"
print "Finished!"
print "Enjoy web2py " + web2py_version_line
+52
View File
@@ -0,0 +1,52 @@
# -*- mode: python -*-
block_cipher = None
a = Analysis(['web2py.py'],
pathex=['.'],
binaries=[('/System/Library/Frameworks/Tk.framework/Tk', 'tk'), ('/System/Library/Frameworks/Tcl.framework/Tcl', 'tcl')],
datas=[],
hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures',
'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'cProfile', 'csv', 'ctypes.wintypes',
'email.mime', 'email.mime.base', 'email.mime.multipart', 'email.mime.nonmultipart', 'email.mime.text', 'html.parser', 'http.cookies',
'ipaddress', 'imaplib', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats',
'psycopg2', 'psycopg2._ipaddress', 'psycopg2._json', 'psycopg2._range', 'psycopg2.extensions', 'psycopg2.extras', 'psycopg2.sql',
'psycopg2.tz', 'pyodbc', 'python-ldap', 'rlcompleter', 'sched', 'site', 'smtplib', 'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'timeit', 'tkinter',
'tkinter.commondialog', 'tkinter.constants', 'tkinter.messagebox', 'uuid', 'win32evtlogutil', 'wsgiref',
'wsgiref.handlers', 'wsgiref.headers', 'wsgiref.simple_server', 'wsgiref.util', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg',
'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xmlrpc.server'],
hookspath=[],
runtime_hooks=[],
excludes=['gluon'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='web2py',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
icon='extras/icons/web2py.icns')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='web2py')
app = BUNDLE(coll,
name='web2py.app',
icon='extras/icons/web2py.icns',
bundle_identifier=None,
info_plist={
'NSPrincipleClass': 'NSApplication',
'NSAppleScriptEnabled': False})
+44
View File
@@ -0,0 +1,44 @@
# -*- mode: python -*-
block_cipher = None
a = Analysis(['web2py.py'],
pathex=['.'],
binaries=[],
datas=[],
hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures',
'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'csv', 'ctypes.wintypes',
'email.mime', 'email.mime.base', 'email.mime.multipart', 'email.mime.nonmultipart', 'email.mime.text', 'html.parser', 'http.cookies',
'ipaddress', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats',
'psycopg2', 'psycopg2._ipaddress', 'psycopg2._json', 'psycopg2._range', 'psycopg2.extensions', 'psycopg2.extras', 'psycopg2.sql',
'psycopg2.tz', 'pyodbc', 'python-ldap', 'rlcompleter', 'sched', 'site', 'smtplib', 'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'timeit', 'tkinter',
'tkinter.commondialog', 'tkinter.constants', 'tkinter.messagebox', 'uuid', 'win32con', 'win32evtlogutil', 'winerror', 'wsgiref',
'wsgiref.handlers', 'wsgiref.headers', 'wsgiref.simple_server', 'wsgiref.util', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg',
'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xmlrpc.server'],
hookspath=[],
runtime_hooks=[],
excludes=['gluon'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='web2py',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True , icon='extras\\icons\\web2py.ico')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='web2py')
@@ -0,0 +1,44 @@
# -*- mode: python -*-
block_cipher = None
a = Analysis(['web2py.py'],
pathex=['.'],
binaries=[],
datas=[],
hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures',
'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'csv', 'ctypes.wintypes',
'email.mime', 'email.mime.base', 'email.mime.multipart', 'email.mime.nonmultipart', 'email.mime.text', 'html.parser', 'http.cookies',
'ipaddress', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats',
'psycopg2', 'psycopg2._ipaddress', 'psycopg2._json', 'psycopg2._range', 'psycopg2.extensions', 'psycopg2.extras', 'psycopg2.sql',
'psycopg2.tz', 'pyodbc', 'python-ldap', 'rlcompleter', 'sched', 'site', 'smtplib', 'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'timeit', 'tkinter',
'tkinter.commondialog', 'tkinter.constants', 'tkinter.messagebox', 'uuid', 'win32con', 'win32evtlogutil', 'winerror', 'wsgiref',
'wsgiref.handlers', 'wsgiref.headers', 'wsgiref.simple_server', 'wsgiref.util', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg',
'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xmlrpc.server'],
hookspath=[],
runtime_hooks=[],
excludes=['gluon'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='web2py_no_console',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False , icon='extras\\icons\\web2py.ico')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='web2py_no_console')
Vendored
+6 -6
View File
@@ -27,7 +27,7 @@ def create_user(username):
run('EDITOR="cp /tmp/sudoers.new" visudo') run('EDITOR="cp /tmp/sudoers.new" visudo')
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes') uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
def install_web2py(): def install_web2py():
"""fab -H username@host install_web2py""" """fab -H username@host install_web2py"""
sudo('wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/%s' % INSTALL_SCRIPT) sudo('wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/%s' % INSTALL_SCRIPT)
sudo('chmod +x %s' % INSTALL_SCRIPT) sudo('chmod +x %s' % INSTALL_SCRIPT)
@@ -119,7 +119,7 @@ def deploy(appname=None, all=False):
os.unlink(zipfile) os.unlink(zipfile)
backup = mkdir_or_backup(appname) backup = mkdir_or_backup(appname)
if all=='all' or not backup: if all=='all' or not backup:
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2') local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
else: else:
@@ -131,10 +131,10 @@ def deploy(appname=None, all=False):
sudo('unzip -o /tmp/_update.zip') sudo('unzip -o /tmp/_update.zip')
sudo('chown -R www-data:www-data *') sudo('chown -R www-data:www-data *')
sudo('echo "%s" > DATE_DEPLOYMENT' % now) sudo('echo "%s" > DATE_DEPLOYMENT' % now)
finally: finally:
sudo('rm /tmp/_update.zip') sudo('rm /tmp/_update.zip')
if backup: if backup:
print 'TO RESTORE: fab restore:%s' % backup print 'TO RESTORE: fab restore:%s' % backup
@@ -154,10 +154,10 @@ def deploynobackup(appname=None):
sudo('unzip -o /tmp/_update.zip') sudo('unzip -o /tmp/_update.zip')
sudo('chown -R www-data:www-data *') sudo('chown -R www-data:www-data *')
sudo('echo "%s" > DATE_DEPLOYMENT' % now) sudo('echo "%s" > DATE_DEPLOYMENT' % now)
finally: finally:
sudo('rm /tmp/_update.zip') sudo('rm /tmp/_update.zip')
def restore(backup): def restore(backup):
"""fab -H username@host restore:backupfilename""" """fab -H username@host restore:backupfilename"""
appname = backup.split('/')[-1].split('.')[0] appname = backup.split('/')[-1].split('.')[0]
+5 -57
View File
@@ -16,12 +16,11 @@ import os
import traceback import traceback
from shutil import rmtree, copyfileobj from shutil import rmtree, copyfileobj
import zipfile import zipfile
import sys
from gluon.fileutils import (w2p_pack, create_app, w2p_unpack, from gluon.fileutils import (w2p_pack, create_app, w2p_unpack,
w2p_pack_plugin, w2p_unpack_plugin, w2p_pack_plugin, w2p_unpack_plugin,
up, fix_newlines, abspath, recursive_unlink, up, fix_newlines, abspath, recursive_unlink,
read_file, write_file, parse_version, missing_app_folders) write_file, parse_version)
from gluon.restricted import RestrictedError from gluon.restricted import RestrictedError
from gluon.settings import global_settings from gluon.settings import global_settings
from gluon.cache import CacheOnDisk from gluon.cache import CacheOnDisk
@@ -29,7 +28,7 @@ from gluon._compat import urlopen, to_native
# TODO: move into add_path_first # TODO: move into add_path_first
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
import site pass
REGEX_DEFINE_TABLE = r"""^\w+\.define_table\(\s*['"](?P<name>\w+)['"]""" REGEX_DEFINE_TABLE = r"""^\w+\.define_table\(\s*['"](?P<name>\w+)['"]"""
@@ -37,7 +36,7 @@ REGEX_EXTEND = r"""^\s*(?P<all>\{\{\s*extend\s+['"](?P<name>[^'"]+)['"]\s*\}\})"
REGEX_INCLUDE = r"""(?P<all>\{\{\s*include\s+['"](?P<name>[^'"]+)['"]\s*\}\})""" REGEX_INCLUDE = r"""(?P<all>\{\{\s*include\s+['"](?P<name>[^'"]+)['"]\s*\}\})"""
# TODO: swap arguments, let first ('r' or whatever) be mandatory # TODO: swap arguments, let first ('r' or whatever) be mandatory
def apath(path='', r=None): def apath(path='', r=None):
"""Builds a path inside an application folder """Builds a path inside an application folder
@@ -442,54 +441,3 @@ def upgrade(request, url='http://web2py.com'):
return True, None return True, None
except Exception as e: except Exception as e:
return False, e return False, e
# TODO: move to fileutils
def add_path_first(path):
sys.path = [path] + [p for p in sys.path if (
not p == path and not p == (path + '/'))]
if not global_settings.web2py_runtime_gae:
if not path in sys.path:
site.addsitedir(path)
# TODO: move to fileutils
def try_mkdir(path):
if not os.path.exists(path):
try:
if os.path.islink(path):
# path is a broken link, try to mkdir the target of the link
# instead of the link itself.
os.mkdir(os.path.realpath(path))
else:
os.mkdir(path)
except OSError as e:
if e.errno == 17: # "File exists" (race condition).
pass
else:
raise
# TODO: move to fileutils
def create_missing_folders():
if not global_settings.web2py_runtime_gae:
for path in ('applications', 'deposit', 'site-packages', 'logs'):
try_mkdir(abspath(path, gluon=True))
"""
OLD sys.path dance
paths = (global_settings.gluon_parent, abspath(
'site-packages', gluon=True), abspath('gluon', gluon=True), '')
"""
for p in (global_settings.gluon_parent,
abspath('site-packages', gluon=True),
''):
add_path_first(p)
# TODO: move to fileutils
def create_missing_app_folders(request):
if not global_settings.web2py_runtime_gae:
if request.folder not in global_settings.app_folders:
for amf in missing_app_folders(request.folder):
try_mkdir(amf)
global_settings.app_folders.add(request.folder)
+4 -6
View File
@@ -40,12 +40,10 @@ from gluon.validators import Validator
from gluon.settings import global_settings from gluon.settings import global_settings
from pydal.base import BaseAdapter from pydal.base import BaseAdapter
from gluon.custom_import import custom_import_install from gluon.custom_import import custom_import_install
from gluon.fileutils import mktree, listdir, read_file, write_file, abspath from gluon.fileutils import mktree, listdir, read_file, write_file, abspath, add_path_first
from gluon.template import parse_template from gluon.template import parse_template
from gluon.cfs import getcfs from gluon.cfs import getcfs
from gluon.restricted import restricted, compile2 from gluon.restricted import restricted, compile2
from gluon.admin import add_path_first
CACHED_REGEXES = {} CACHED_REGEXES = {}
CACHED_REGEXES_MAX_SIZE = 1000 CACHED_REGEXES_MAX_SIZE = 1000
@@ -439,7 +437,7 @@ def compile_views(folder, skip_failed_views=False):
""" """
path = pjoin(folder, 'views') path = pjoin(folder, 'views')
failed_views = [] failed_views = []
for fname in listdir(path, REGEX_VIEW_PATH): for fname in listdir(path, REGEX_VIEW_PATH, followlinks=True):
try: try:
data = parse_template(fname, path) data = parse_template(fname, path)
except Exception as e: except Exception as e:
@@ -464,7 +462,7 @@ def compile_models(folder):
Compiles all the models in the application specified by `folder` Compiles all the models in the application specified by `folder`
""" """
path = pjoin(folder, 'models') path = pjoin(folder, 'models')
for fname in listdir(path, REGEX_MODEL_PATH): for fname in listdir(path, REGEX_MODEL_PATH, followlinks=True):
data = read_file(pjoin(path, fname)) data = read_file(pjoin(path, fname))
modelfile = 'models.'+fname.replace(os.sep, '.') modelfile = 'models.'+fname.replace(os.sep, '.')
filename = pjoin(folder, 'compiled', modelfile) filename = pjoin(folder, 'compiled', modelfile)
@@ -489,7 +487,7 @@ def compile_controllers(folder):
Compiles all the controllers in the application specified by `folder` Compiles all the controllers in the application specified by `folder`
""" """
path = pjoin(folder, 'controllers') path = pjoin(folder, 'controllers')
for fname in listdir(path, REGEX_CONTROLLER): for fname in listdir(path, REGEX_CONTROLLER, followlinks=True):
data = read_file(pjoin(path, fname)) data = read_file(pjoin(path, fname))
exposed = find_exposed_functions(data) exposed = find_exposed_functions(data)
for function in exposed: for function in exposed:
+27 -6
View File
@@ -23,9 +23,15 @@ for the benefit of code maintainers/developers:
'--run_system_tests') '--run_system_tests')
- remember to allow the '-' too as word separator (e.g. - remember to allow the '-' too as word separator (e.g.
'--run-system-tests') but do not use this form on help '--run-system-tests') but do not use this form on help
(add the minus version of the option to _omitted_opts
to hide it in usage help)
- prefer short names on help messages, instead use - prefer short names on help messages, instead use
all options names in warning/error messages (e.g. all options names in warning/error messages (e.g.
'-R/--run requires -S/--shell') '-R/--run requires -S/--shell')
Notice that options must be included into opt_map dictionary
(defined in parse_args function) to be available in
configuration file.
""" """
from __future__ import print_function from __future__ import print_function
@@ -90,11 +96,12 @@ def console(version):
# most of the options but do not show both versions on help # most of the options but do not show both versions on help
_omitted_opts = ('--add-options', '--errors-to-console', _omitted_opts = ('--add-options', '--errors-to-console',
'--no-banner', '--log-level', '--no-gui', '--import-models', '--no-banner', '--log-level', '--no-gui', '--import-models',
'--force-migrate',
'--server-name', '--server-key', '--server-cert', '--ca-cert', '--server-name', '--server-key', '--server-cert', '--ca-cert',
'--pid-filename', '--log-filename', '--min-threads', '--pid-filename', '--log-filename', '--min-threads',
'--max-threads', '--request-queue-size', '--socket-timeout', '--max-threads', '--request-queue-size', '--socket-timeout',
'--profiler-dir', '--with-scheduler', '--with-cron', '--profiler-dir', '--with-scheduler', '--with-cron',
'--soft-cron', '--cron-run', '--cron-threads', '--soft-cron', '--cron-run',
'--run-doctests', '--run-system-tests', '--with-coverage') '--run-doctests', '--run-system-tests', '--with-coverage')
_hidden_options = _omitted_opts + tuple(deprecated_opts.keys()) _hidden_options = _omitted_opts + tuple(deprecated_opts.keys())
@@ -259,12 +266,11 @@ web2py will attempt to run a GUI to ask for it when starting the web server
'(default is %(default)s), see -S above. NOTE: when the APP_ENV ' '(default is %(default)s), see -S above. NOTE: when the APP_ENV '
'argument of -S include a controller c automatic import of ' 'argument of -S include a controller c automatic import of '
'models is always enabled') 'models is always enabled')
g.add_argument('--force_migrate', g.add_argument('--force_migrate', '--force-migrate',
default=False, default=False,
action='store_true', action='store_true', help=
help= 'force DAL to migrate all tables that should be migrated when enabled; '
'force DAL to migrate all tables that should be migrated when enabled; ' 'monkeypatch in the DAL class to force _migrate_enabled=True')
'monkeypatch in the DAL class to force _migrate_enabled=True')
g.add_argument('-R', '--run', g.add_argument('-R', '--run',
type=existing_file, type=existing_file,
metavar='PYTHON_FILE', help= metavar='PYTHON_FILE', help=
@@ -452,6 +458,19 @@ web2py will attempt to run a GUI to ask for it when starting the web server
'only, the default behaviour is to read the crontab for all of the ' 'only, the default behaviour is to read the crontab for all of the '
'installed applications. NOTE: this option can be used multiple ' 'installed applications. NOTE: this option can be used multiple '
'times to build the list of crontabs to be processed by cron') 'times to build the list of crontabs to be processed by cron')
def positive_int(v, err_label='value'):
try:
iv = int(v)
if iv <= 0: raise ValueError()
return iv
except ValueError:
pass
raise argparse.ArgumentTypeError("bad %s %s" % (err_label, v))
def cron_threads(v):
return positive_int(v, err_label='cron_threads')
g.add_argument('--cron_threads', '--cron-threads',
type=cron_threads, metavar='NUM',
help='maximum number of cron threads (5)')
g.add_argument('--soft_cron', '--soft-cron', g.add_argument('--soft_cron', '--soft-cron',
'--softcron', # deprecated '--softcron', # deprecated
default=False, default=False,
@@ -664,6 +683,7 @@ def parse_args(parser, cli_args, deprecated_opts, integer_log_level,
'bpython': store_true, 'bpython': store_true,
'plain': store_true, 'plain': store_true,
'import_models': store_true, 'import_models': store_true,
'force_migrate': store_true,
'run': str_or_default, 'run': str_or_default,
'args': list_or_default, 'args': list_or_default,
# web server options # web server options
@@ -688,6 +708,7 @@ def parse_args(parser, cli_args, deprecated_opts, integer_log_level,
# cron options # cron options
'with_cron': store_true, 'with_cron': store_true,
'crontab': list_or_default, 'crontab': list_or_default,
'cron_threads': str_or_default,
'soft_cron': store_true, 'soft_cron': store_true,
'cron_run': store_true, 'cron_run': store_true,
# test options # test options
+1 -1
View File
@@ -118,7 +118,7 @@ def saml2_handler(session, request, config_filename = None, entityid = None):
elif request.env.request_method == 'POST': elif request.env.request_method == 'POST':
binding = BINDING_HTTP_POST binding = BINDING_HTTP_POST
if not request.vars.SAMLResponse: if not request.vars.SAMLResponse:
req_id, req = client.create_authn_request(destination, binding=binding) req_id, req = client.create_authn_request(destination, binding=BINDING_HTTP_POST)
relay_state = web2py_uuid().replace('-','') relay_state = web2py_uuid().replace('-','')
session.saml_outstanding_queries = {req_id: request.url} session.saml_outstanding_queries = {req_id: request.url}
session.saml_req_id = req_id session.saml_req_id = req_id
+8 -8
View File
@@ -18,14 +18,14 @@ regex_title = re.compile('^#{1} (?P<t>[^\n]+)', re.M)
regex_maps = [ regex_maps = [
(re.compile('[ \t\r]+\n'), '\n'), (re.compile('[ \t\r]+\n'), '\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'), '{\\\\bf \g<t>}'), (re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'), '{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"), '{\\it \g<t>}'), (re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"), '{\\\it \g<t>}'),
(re.compile('^#{5,6}\s*(?P<t>[^\n]+)', re.M), '\n\n{\\\\bf \g<t>}\n'), (re.compile('^#{5,6}\s*(?P<t>[^\n]+)', re.M), '\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'), (re.compile('^#{4}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\\subsubsection{\g<t>}\n'),
(re.compile('^#{3}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsection{\g<t>}\n'), (re.compile('^#{3}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\\subsection{\g<t>}\n'),
(re.compile('^#{2}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\section{\g<t>}\n'), (re.compile('^#{2}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\\section{\g<t>}\n'),
(re.compile('^#{1}\s*(?P<t>[^\n]+)', re.M), ''), (re.compile('^#{1}\s*(?P<t>[^\n]+)', re.M), ''),
(re.compile('^\- +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), (re.compile('^\- +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\\item \g<t>\n\\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'), (re.compile('^\+ +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\\item \g<t>\n\\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'), '\n'), (re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'), '\n'),
(re.compile('\n\s+\n'), '\n\n')] (re.compile('\n\s+\n'), '\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n', re.M | re.S) regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n', re.M | re.S)
@@ -97,7 +97,7 @@ def render(text,
text = latex_escape(text, pound=False) text = latex_escape(text, pound=False)
texts = text.split('## References', 1) texts = text.split('## References', 1)
text = regex_anchor.sub('\\label{\g<t>}', texts[0]) text = regex_anchor.sub('\\\label{\g<t>}', texts[0])
if len(texts) == 2: if len(texts) == 2:
text += '\n\\begin{thebibliography}{999}\n' text += '\n\\begin{thebibliography}{999}\n'
text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1]) text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1])
@@ -145,7 +145,7 @@ def render(text,
text = regex_image_width.sub(sub, text) text = regex_image_width.sub(sub, text)
text = regex_image.sub(sub, text) text = regex_image.sub(sub, text)
text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text) text = regex_link.sub('{\\\\footnotesize\\\href{\g<k>}{\g<t>}}', text)
text = regex_commas.sub('\g<t>', text) text = regex_commas.sub('\g<t>', text)
text = regex_noindent.sub('\n\\\\noindent \g<t>', text) text = regex_noindent.sub('\n\\\\noindent \g<t>', text)
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf8 -*-
# Plural-Forms for fa (Persian)
File diff suppressed because one or more lines are too long
Executable → Regular
View File
+10 -5
View File
@@ -64,7 +64,7 @@ class WebClient(object):
headers=headers, method='GET') headers=headers, method='GET')
def post(self, url, data=None, cookies=None, def post(self, url, data=None, cookies=None,
headers=None, auth=None, method='auto'): headers=None, auth=None, method='auto', charset='utf-8'):
self.url = self.app + url self.url = self.app + url
# if this POST form requires a postback do it # if this POST form requires a postback do it
@@ -147,7 +147,11 @@ class WebClient(object):
else:#python2.5 else:#python2.5
self.status = None self.status = None
self.text = to_native(self.response.read()) self.text = self.response.read()
if charset:
if charset == 'auto':
charset = self.response.headers.getparam('charset')
self.text = to_native(self.text, charset)
# In PY3 self.response.headers are case sensitive # In PY3 self.response.headers are case sensitive
self.headers = dict() self.headers = dict()
for h in self.response.headers: for h in self.response.headers:
@@ -173,9 +177,10 @@ class WebClient(object):
self.sessions[name] = value self.sessions[name] = value
# find all forms and formkeys in page # find all forms and formkeys in page
self.forms = {} if charset:
for match in FORM_REGEX.finditer(to_native(self.text)): self.forms = {}
self.forms[match.group('formname')] = match.group('formkey') for match in FORM_REGEX.finditer(self.text):
self.forms[match.group('formname')] = match.group('formkey')
# log this request # log this request
self.history.append((self.method, self.url, self.status, self.time)) self.history.append((self.method, self.url, self.status, self.time))
+54 -2
View File
@@ -11,6 +11,7 @@ File operations
from gluon import storage from gluon import storage
import os import os
import sys
import re import re
import tarfile import tarfile
import glob import glob
@@ -47,6 +48,9 @@ __all__ = (
'w2p_pack_plugin', 'w2p_pack_plugin',
'w2p_unpack_plugin', 'w2p_unpack_plugin',
'fix_newlines', 'fix_newlines',
'create_missing_folders',
'create_missing_app_folders',
'add_path_first',
) )
@@ -144,7 +148,8 @@ def listdir(path,
add_dirs=False, add_dirs=False,
sort=True, sort=True,
maxnum=None, maxnum=None,
exclude_content_from=None exclude_content_from=None,
followlinks=False
): ):
""" """
Like `os.listdir()` but you can specify a regex pattern to filter files. Like `os.listdir()` but you can specify a regex pattern to filter files.
@@ -160,7 +165,7 @@ def listdir(path,
n = 0 n = 0
regex = re.compile(expression) regex = re.compile(expression)
items = [] items = []
for (root, dirs, files) in os.walk(path, topdown=True): for (root, dirs, files) in os.walk(path, topdown=True, followlinks=followlinks):
for dir in dirs[:]: for dir in dirs[:]:
if dir.startswith('.'): if dir.startswith('.'):
dirs.remove(dir) dirs.remove(dir)
@@ -441,3 +446,50 @@ def abspath(*relpath, **kwargs):
if kwargs.get('gluon', False): if kwargs.get('gluon', False):
return os.path.join(global_settings.gluon_parent, path) return os.path.join(global_settings.gluon_parent, path)
return os.path.join(global_settings.applications_parent, path) return os.path.join(global_settings.applications_parent, path)
def try_mkdir(path):
if not os.path.exists(path):
try:
if os.path.islink(path):
# path is a broken link, try to mkdir the target of the link
# instead of the link itself.
os.mkdir(os.path.realpath(path))
else:
os.mkdir(path)
except OSError as e:
if e.errno == 17: # "File exists" (race condition).
pass
else:
raise
def create_missing_folders():
if not global_settings.web2py_runtime_gae:
for path in ('applications', 'deposit', 'site-packages', 'logs'):
try_mkdir(abspath(path, gluon=True))
"""
OLD sys.path dance
paths = (global_settings.gluon_parent, abspath(
'site-packages', gluon=True), abspath('gluon', gluon=True), '')
"""
for p in (global_settings.gluon_parent,
abspath('site-packages', gluon=True),
''):
add_path_first(p)
def create_missing_app_folders(request):
if not global_settings.web2py_runtime_gae:
if request.folder not in global_settings.app_folders:
for amf in missing_app_folders(request.folder):
try_mkdir(amf)
global_settings.app_folders.add(request.folder)
def add_path_first(path):
sys.path = [path] + [p for p in sys.path if (
not p == path and not p == (path + '/'))]
if not global_settings.web2py_runtime_gae:
if not path in sys.path:
site.addsitedir(path)
+6
View File
@@ -392,6 +392,12 @@ class lazyT(object):
def __eq__(self, other): def __eq__(self, other):
return str(self) == str(other) return str(self) == str(other)
def __lt__(self, other):
return str(self) < str(other)
def __gt__(self, other):
return str(self) > str(other)
def __ne__(self, other): def __ne__(self, other):
return str(self) != str(other) return str(self) != str(other)
+2 -2
View File
@@ -27,10 +27,10 @@ import string
from gluon._compat import Cookie, urllib_quote from gluon._compat import Cookie, urllib_quote
# from thread import allocate_lock # from thread import allocate_lock
from gluon.fileutils import abspath, read_file, write_file from gluon.fileutils import abspath, read_file, write_file, create_missing_folders, create_missing_app_folders, \
add_path_first
from gluon.settings import global_settings from gluon.settings import global_settings
from gluon.utils import web2py_uuid, unlocalised_http_header_date from gluon.utils import web2py_uuid, unlocalised_http_header_date
from gluon.admin import add_path_first, create_missing_folders, create_missing_app_folders
from gluon.globals import current from gluon.globals import current
# Remarks: # Remarks:
+7 -1
View File
@@ -334,7 +334,7 @@ class SimplePool(object):
self.running = set() self.running = set()
def grow(self, size): def grow(self, size):
if size > self.size: if size and size > self.size:
self.size = size self.size = size
def start(self, t): def start(self, t):
@@ -380,8 +380,14 @@ class SimplePool(object):
_dancer = SimplePool(5, worker_cls=SoftWorker) _dancer = SimplePool(5, worker_cls=SoftWorker)
def dancer_size(size):
_dancer.grow(size)
_launcher = SimplePool(5) _launcher = SimplePool(5)
def launcher_size(size):
_launcher.grow(size)
def crondance(applications_parent, ctype='hard', startup=False, apps=None): def crondance(applications_parent, ctype='hard', startup=False, apps=None):
""" """
Does the periodic job of cron service: read the crontab(s) and launch Does the periodic job of cron service: read the crontab(s) and launch
+419 -384
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More