Compare commits

..
72 Commits
Author SHA1 Message Date
dlage 65ee8d378a update gluon packages. 2020-11-15 09:50:47 +00:00
dlage 98a11a717f Merge remote-tracking branch 'upstream/master' into dev-ah 2020-11-15 09:45:25 +00:00
mdipierroandGitHub a5007cc1c4 Merge pull request #2345 from bartkraan/issue/2312
issue/2312
2020-09-20 10:02:07 -07:00
mdipierroandGitHub eb4cc8ceed Merge pull request #2344 from macneiln/master
Add hash_extension functionality to URLs
2020-09-20 10:00:27 -07:00
mdipierroandGitHub 30707c169e Merge pull request #2340 from jvanbraekel/master
Fix order keyword value in Grid column sort
2020-09-20 09:59:00 -07:00
bartkraan 06209aff29 issue/2312 2020-09-07 11:47:18 +02:00
macneilnandGitHub d23a6e7b1b Remove missed period from h_args
Update as part of adding hash_extension functionality.
2020-09-07 20:55:21 +12:00
macneilnandGitHub c23e95a9e2 Add hash_extension functionality
Add hash_extension functionality so _signature can be verified regardless of the extension.
2020-09-07 19:55:59 +12:00
macneilnandGitHub d13c5349b6 Add hash_extension variable
Add hash_extension variable to requires_signature function so URL _signature can be verified ignoring the given request extension.
2020-09-07 19:48:31 +12:00
jvanbraekelandGitHub 0ad67ad5c8 Fix order keyword in Grid column sorting
Remove the order = 'None' and corresponding check.  When order is specified its a string of the form  = (~) tablename.coulumnname , where the optional ~ indicate a descending order.
2020-08-06 15:37:38 +02:00
jvanbraekelandGitHub f875bc1520 Merge pull request #1 from web2py/master
Merge Web2py  upstream
2020-08-06 15:27:26 +02:00
mdipierro d51de88096 fixed whitespaces 2020-06-24 22:10:09 -07:00
mdipierro 0e4964308f various cleanup in strings and regex, thanks Paolo 2020-06-24 21:49:43 -07:00
mdipierro fc7c1bd2de prevent loading un-necssary modules when using a handled, thanks Paolo 2020-06-22 22:39:28 -07:00
mdipierro cf76978a39 enabled admin/appadmin by default from trusted addresses, thanks Paolo 2020-06-22 22:24:49 -07:00
mdipierro 79f64cbe72 fixed handling of smtplib.SMTPException, thanks Paolo 2020-06-14 00:30:56 -07:00
mdipierroandGitHub ae2425d348 Merge pull request #2328 from manma83/remove-admin-session-query
remove last_query session in admin to prevent massive join when sorting tables in multiple browser tabs #2326
2020-06-13 23:10:55 -07:00
mdipierro d3f4c70e79 fixed some problems wth widget logging, thanks Paolo Pastori 2020-06-13 23:08:06 -07:00
mdipierro ba30988580 fixed SCRIPT(ASSIGNJS(foo=bar)), thanks Paolo Pastori 2020-06-13 23:06:38 -07:00
Mark Ma db6a06b963 remove last_query session in admin to prevent massive join when sorting tables in multiple browser tabs #2326 2020-06-03 16:16:59 -07:00
mdipierro 7427258b90 Merge branch 'master' of github.com:web2py/web2py 2020-05-31 18:22:26 -07:00
mdipierro 1ece50a988 Better handling of quit in Mail, thanks Paolo Pastori 2020-05-31 18:22:00 -07:00
mdipierro 2720f06789 better session2trash logging 2020-05-31 18:20:05 -07:00
mdipierroandGitHub e89103d796 Merge pull request #2323 from valq7711/patch-1
fix CL-args processing (-f maybe session2trash arg)
2020-05-13 20:55:01 -07:00
mdipierroandGitHub 60db14b55b Merge pull request #2319 from jpsteil/patch-1
Update appveyor.yml
2020-05-13 20:54:17 -07:00
mdipierroandGitHub 7a7c84993f Merge pull request #2316 from jpsteil/master
Proposed fix for issue 2315
2020-05-13 20:53:56 -07:00
mdipierroandGitHub 9eae04d93c Merge pull request #2313 from nicozanf/missing_app
Fix errors with unwanted new app folder
2020-05-13 20:53:35 -07:00
valq7711andGitHub 2a758d3936 fix CL-args processing (-f maybe session2trash arg) 2020-05-14 03:10:00 +03:00
Jim SteilandGitHub 44f2860d58 Update appveyor.yml
Removed Python 3.5 per Leonel's recommendation
2020-05-06 08:25:07 -05:00
Jim SteilandGitHub 64c42f7580 Update appveyor.yml
Removed Python 3.5 checks per Leonel's recommendation
2020-05-06 08:03:01 -05:00
mdipierro 777c3053e8 version 2.20.4 2020-05-02 22:05:37 -07:00
mdipierro 01270ca984 version 2.20.3 2020-05-02 21:35:30 -07:00
mdipierro 9ccbab19f4 version 2.20.2 2020-05-02 19:56:34 -07:00
mdipierro a4246c08ac version 2.20.2 2020-05-02 19:49:56 -07:00
Jim SteilandGitHub c1df6203b0 Merge pull request #1 from jpsteil/jpsteil-patch-1
Proposed fix for issue 2315
2020-05-01 14:39:39 -05:00
Jim SteilandGitHub 933e3df9d3 Proposed fix for issue 2315 2020-05-01 14:33:40 -05:00
Nico ZanferrariandGitHub 9ffce59ddb Update shell.py 2020-04-27 10:20:11 +02:00
Nico ZanferrariandGitHub 97bb26d452 Fix errors with unwanted new app folder
Replying 'no' or ENTER when asked for the creation of a missing app folder was not properly managed. It gives IOError on Mac and unwanted behaviour on Win
2020-04-26 22:14:36 +02:00
mdipierro ca8b5f6945 move binaries 2020-04-25 20:52:56 -07:00
mdipierro f70f671324 version 2.20.1 2020-04-25 20:06:03 -07:00
mdipierro 2a39a883e8 Merge branch 'master' of github.com:web2py/web2py 2020-04-25 19:57:31 -07:00
mdipierro bb402ff511 Make binaries 2020-04-25 19:57:01 -07:00
mdipierroandGitHub 0fa639ec57 Merge pull request #2311 from edersonpeka/patch-1
Converts dict_keys object to list (py3)
2020-04-20 21:39:19 -07:00
Ederson PekaandGitHub 3cbaa7eb5f Converts dict_keys object to list (py3)
In python 3, keys() method returns a dict_keys object instead of a list. Trying to access the zero index causes an error. Converting the dict_keys object to a list is enough to make it work (and still works in python 2).
2020-04-16 20:42:07 -03:00
mdipierroandGitHub e7dc64f114 Merge pull request #2309 from flavour/patch-1
Workaround Python 3.x CGI bug which causes #2262
2020-04-15 21:11:36 -07:00
mdipierroandGitHub 987263c028 Merge pull request #2306 from leonelcamara/patch-33
Fix for undefined regex_tables
2020-04-15 21:11:11 -07:00
Fran BoonandGitHub 5490480906 Workaround Python 3.x CGI bug which causes #2262
Obviously the client also needs to send a Content-Disposition header for this to work
2020-04-13 16:57:36 +01:00
Leonel CâmaraandGitHub 3f2b1fe9ca Fix for undefined regex_tables 2020-04-10 18:48:30 +01:00
mdipierroandGitHub 405b0b2e68 Merge pull request #2299 from mbelletti/master
Fix typo harattr instead hasattr for pull request #2297
2020-04-05 18:20:03 -07:00
mdipierroandGitHub b76bc6cc03 Merge pull request #2295 from nicozanf/master
fix web2py 2.19.1 compatibility with PyInstaller
2020-04-05 18:19:35 -07:00
Massimiliano Belletti 1b2d0da909 Fix typo harattr instead hasattr 2020-03-26 12:40:48 +01:00
mdipierroandGitHub 0e6cc87c8b Merge pull request #2297 from tyrbonit/patch-1
fix Py3 encoding problem
2020-03-25 21:50:37 -07:00
Igor MSKandGitHub 041ec3c63c fix Py3 encoding problem
Fix for python 3.6
>>>str(b'123')
>>>"b'123'"
>>>str(b'123', encoding='utf-8')
>>>"123"
2020-03-24 22:33:41 +03:00
Nico ZanferrariandGitHub ef9e2dca1f pyinstaller compatibility fix
pyinstaller with python 2 on Windows does not like it
2020-03-23 14:01:36 +01:00
Nico ZanferrariandGitHub b4a945b3ae hidden import needed for PyInstaller's binaries
an explicit 'import site' is needed for having a full functional interactive shell in the PyInstaller's binaries
2020-03-23 13:39:47 +01:00
Nico ZanferrariandGitHub 82cb9011ac Update for web2py 2.19.1 2020-03-23 12:45:23 +01:00
Nico ZanferrariandGitHub 1ec6411cfb Update for web2py 2.19.1 2020-03-23 12:44:47 +01:00
Nico ZanferrariandGitHub 31b1f746fe Update for web2py 2.19.1 2020-03-23 12:43:58 +01:00
mdipierro fa6e9b1caf modified fcgi, hopefully works with py3 2020-03-22 14:06:25 -07:00
mdipierro e004215ea6 fixed 0x80000000L -> 0x80000000 for py3 2020-03-21 18:25:34 -07:00
mdipierro bf0cf06343 version 2.19.1 2020-03-21 13:18:43 -07:00
mdipierro 4882890a3c reverting 88ed162dae 2020-03-21 12:50:11 -07:00
mdipierro 6c4649d992 fixed issue #2217, bs4, thanks romicasal 2020-03-21 12:40:23 -07:00
mdipierro 279858b424 fixed pysimplesoap, fixes issues #2254, thanks pgastinger 2020-03-21 12:29:22 -07:00
mdipierro a633bcde7d Expose should not clear session, was a test, fixes #2257, thanks Martin Mosbeck 2020-03-21 12:21:44 -07:00
mdipierro 88ed162dae fixes #2262, body decode on py3, thanks liuyigh 2020-03-21 12:18:15 -07:00
mdipierro 6aff6e1132 fixes #2271, sqlhtml changing dict, thanks macneiln 2020-03-21 12:11:46 -07:00
mdipierro 1ed5117200 re-raise crtl+c, fixes #2292, thanks nursix 2020-03-21 12:09:39 -07:00
mdipierro 97a0eac8ce test python 3.8 2020-03-21 11:47:28 -07:00
mdipierro f1dd7f0d89 updated pydal to 20200321.1 2020-03-21 11:45:36 -07:00
dlage 30a93c49ac Merge branch 'master' into dev-ah 2019-05-23 14:36:44 +01:00
dlage 7699766ff3 Merge branch 'migrator-pr' into dev-ah 2019-05-22 11:19:32 +01:00
44 changed files with 904 additions and 547 deletions
+1 -5
View File
@@ -12,11 +12,7 @@ python:
- '2.7' - '2.7'
- '3.6' - '3.6'
- '3.7' - '3.7'
- 'pypy3.5' - '3.8'
matrix:
allow_failures:
- python: 'pypy3.5'
install: install:
- pip install -e . - pip install -e .
+4
View File
@@ -1,3 +1,7 @@
## 2.20.1
new makefile to update binaries from Nico Zanferrari
## 2.19.0 ## 2.19.0
- new command line options (Thanks Paolo Pastori) - new command line options (Thanks Paolo Pastori)
+58 -1
View File
@@ -45,7 +45,7 @@ rmfiles:
rm -rf applications/examples/uploads/* rm -rf applications/examples/uploads/*
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.18.5-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.20.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
make clean make clean
# make rmfiles # make rmfiles
@@ -104,6 +104,63 @@ win:
mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done' mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done'
cd ../web2py_win; zip -r web2py_win.zip web2py cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip . mv ../web2py_win/web2py_win.zip .
binaries:
echo '' > NEWINSTALL
cp VERSION ../web2py_win_py27/web2py/
cp README.markdown ../web2py_win_py27/web2py/
cp NEWINSTALL ../web2py_win_py27/web2py/
cp LICENSE ../web2py_win_py27/web2py/
cp CHANGELOG ../web2py_win_py27/web2py/
rm -rf ../web2py_win_py27/web2py/gluon
cp -r gluon ../web2py_win_py27/web2py/gluon
rm -rf ../web2py_win_py27/web2py/applications/*
cp -r applications/__init__.py ../web2py_win_py27/web2py/applications/
cp -r applications/admin ../web2py_win_py27/web2py/applications/
cp -r applications/welcome ../web2py_win_py27/web2py/applications/
cp -r applications/examples ../web2py_win_py27/web2py/applications/
cd ../web2py_win_py27; zip -r ../web2py/web2py_win_py27.zip web2py
cp VERSION ../web2py_win_py37/web2py/
cp README.markdown ../web2py_win_py37/web2py/
cp NEWINSTALL ../web2py_win_py37/web2py/
cp LICENSE ../web2py_win_py37/web2py/
cp CHANGELOG ../web2py_win_py37/web2py/
rm -rf ../web2py_win_py37/web2py/gluon
cp -r gluon ../web2py_win_py37/web2py/gluon
rm -rf ../web2py_win_py37/web2py/applications/*
cp -r applications/__init__.py ../web2py_win_py37/web2py/applications/
cp -r applications/admin ../web2py_win_py37/web2py/applications/
cp -r applications/welcome ../web2py_win_py37/web2py/applications/
cp -r applications/examples ../web2py_win_py37/web2py/applications/
cd ../web2py_win_py37; zip -r ../web2py/web2py_win_py37.zip web2py
cp VERSION ../web2py_osx_py27/web2py.app/Contents/MacOS/
cp README.markdown ../web2py_osx_py27/web2py.app/Contents/MacOS/
cp NEWINSTALL ../web2py_osx_py27/web2py.app/Contents/MacOS/
cp LICENSE ../web2py_osx_py27/web2py.app/Contents/MacOS/
cp CHANGELOG ../web2py_osx_py27/web2py.app/Contents/MacOS/
rm -rf ../web2py_osx_py27/web2py.app/Contents/MacOS/gluon
cp -r gluon ../web2py_osx_py27/web2py.app/Contents/MacOS/gluon
rm -rf ../web2py_osx_py27/web2py.app/Contents/MacOS/applications/*
cp -r applications/__init__.py ../web2py_osx_py27/web2py.app/Contents/MacOS/applications/
cp -r applications/admin ../web2py_osx_py27/web2py.app/Contents/MacOS/applications/
cp -r applications/welcome ../web2py_osx_py27/web2py.app/Contents/MacOS/applications/
cp -r applications/examples ../web2py_osx_py27/web2py.app/Contents/MacOS/applications/
cd ../web2py_osx_py27; zip -r ../web2py/web2py_osx_py27.zip web2py.app
cp VERSION ../web2py_osx_py37/web2py.app/Contents/MacOS/
cp README.markdown ../web2py_osx_py37/web2py.app/Contents/MacOS/
cp NEWINSTALL ../web2py_osx_py37/web2py.app/Contents/MacOS/
cp LICENSE ../web2py_osx_py37/web2py.app/Contents/MacOS/
cp CHANGELOG ../web2py_osx_py37/web2py.app/Contents/MacOS/
rm -rf ../web2py_osx_py37/web2py.app/Contents/MacOS/gluon
cp -r gluon ../web2py_osx_py37/web2py.app/Contents/MacOS/gluon
rm -rf ../web2py_osx_py37/web2py.app/Contents/MacOS/applications/*
cp -r applications/__init__.py ../web2py_osx_py37/web2py.app/Contents/MacOS/applications/
cp -r applications/admin ../web2py_osx_py37/web2py.app/Contents/MacOS/applications/
cp -r applications/welcome ../web2py_osx_py37/web2py.app/Contents/MacOS/applications/
cp -r applications/examples ../web2py_osx_py37/web2py.app/Contents/MacOS/applications/
cd ../web2py_osx_py37; zip -r ../web2py/web2py_osx_py37.zip web2py.app
run: run:
python2.7 web2py.py -a hello python2.7 web2py.py -a hello
commit: commit:
+1 -1
View File
@@ -1 +1 @@
Version 2.18.5-stable+timestamp.2019.04.07.21.13.59 Version 2.20.4-stable+timestamp.2020.05.02.22.03.36
@@ -194,8 +194,6 @@ def select():
request.vars.query = '%s.%s.%s==%s' % (request.args[0], request.vars.query = '%s.%s.%s==%s' % (request.args[0],
match.group('table'), match.group('field'), match.group('table'), match.group('field'),
match.group('value')) match.group('value'))
else:
request.vars.query = session.last_query
query = get_query(request) query = get_query(request)
if request.vars.start: if request.vars.start:
start = int(request.vars.start) start = int(request.vars.start)
@@ -222,7 +220,6 @@ def select():
else: else:
orderby = '~' + orderby orderby = '~' + orderby
session.last_orderby = orderby session.last_orderby = orderby
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(
+1 -1
View File
@@ -1261,7 +1261,7 @@ def plugin():
defines = {} defines = {}
for m in models: for m in models:
data = safe_read(apath('%s/models/%s' % (app, m), r=request)) data = safe_read(apath('%s/models/%s' % (app, m), r=request))
defines[m] = regex_tables.findall(data) defines[m] = re.findall(REGEX_DEFINE_TABLE, data, re.MULTILINE)
defines[m].sort() defines[m].sort()
# Get all controllers # Get all controllers
+7 -1
View File
@@ -6,7 +6,10 @@ from gluon.fileutils import read_file
from gluon.utils import web2py_uuid from gluon.utils import web2py_uuid
from pydal.contrib import portalocker from pydal.contrib import portalocker
# ########################################################### # ###########################################################
# ## make sure administrator is on localhost or https # ## make sure administrator is on localhost or https,
# ## or from
# ## gluon.settings.global_settings.trusted_lan_prefix
# ## subnet
# ########################################################### # ###########################################################
@@ -22,6 +25,9 @@ else:
if request.is_https: if request.is_https:
session.secure() session.secure()
elif request.env.trusted_lan_prefix and \
request.client.startswith(request.env.trusted_lan_prefix):
request.is_local = True
elif not request.is_local and not DEMO_MODE: elif not request.is_local and not DEMO_MODE:
raise HTTP(200, T('Admin is disabled because insecure channel')) raise HTTP(200, T('Admin is disabled because insecure channel'))
+1 -1
View File
@@ -82,7 +82,7 @@
<div style="overflow:auto; width:80%;"> <div style="overflow:auto; width:80%;">
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}} {{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
{{upload=URL('download',args=request.args[0])}} {{upload=URL('download',args=request.args[0])}}
{{=SQLTABLE(rows,linkto,upload,orderby=True,_class='table table-striped table-bordered sortable')}} {{=SQLTABLE(rows,linkto,upload,orderby=True,query=query,_class='table table-striped table-bordered sortable')}}
</div> </div>
{{pass}} {{pass}}
<br/><br/> <br/><br/>
@@ -194,8 +194,6 @@ def select():
request.vars.query = '%s.%s.%s==%s' % (request.args[0], request.vars.query = '%s.%s.%s==%s' % (request.args[0],
match.group('table'), match.group('field'), match.group('table'), match.group('field'),
match.group('value')) match.group('value'))
else:
request.vars.query = session.last_query
query = get_query(request) query = get_query(request)
if request.vars.start: if request.vars.start:
start = int(request.vars.start) start = int(request.vars.start)
@@ -222,7 +220,6 @@ def select():
else: else:
orderby = '~' + orderby orderby = '~' + orderby
session.last_orderby = orderby session.last_orderby = orderby
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(
+1 -1
View File
@@ -82,7 +82,7 @@
<div style="overflow:auto; width:80%;"> <div style="overflow:auto; width:80%;">
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}} {{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
{{upload=URL('download',args=request.args[0])}} {{upload=URL('download',args=request.args[0])}}
{{=SQLTABLE(rows,linkto,upload,orderby=True,_class='table table-striped table-bordered sortable')}} {{=SQLTABLE(rows,linkto,upload,orderby=True,query=query,_class='table table-striped table-bordered sortable')}}
</div> </div>
{{pass}} {{pass}}
<br/><br/> <br/><br/>
@@ -9,8 +9,8 @@
<table class="twothirds"> <table class="twothirds">
<thead> <thead>
<tr> <tr>
<th>For Normal Users (Py3)</th> <th>For Python 3.7</th>
<th>For Legacy Users (Py2)</th> <th>For Python 2.7</th>
<th>For Testers</th> <th>For Testers</th>
<th>For Developers</th> <th>For Developers</th>
</tr> </tr>
@@ -18,13 +18,13 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_win.zip">Windows binaries</a> <a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_win_py37.zip">Windows binaries</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_win_py2.zip">Windows binaries</a> <a class="btn btn180 rounded" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_win_py27.zip">Windows binaries</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_win.zip">Windows binaries</a> <a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_win_py27.zip">Windows binaries</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a> <a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
@@ -32,13 +32,13 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_osx.zip">Mac binaries</a> <a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_osx_py37.zip">Mac binaries</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_osx_py2.zip">Mac binaries</a> <a class="btn btn180 rounded" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_osx_py27.zip">Mac binaries</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_osx.zip">Mac binaries</a> <a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_osx_py37.zip">Mac binaries</a>
</td> </td>
<td> <td>
<a class="btn btn180 rounded" href="http://mdipierro.github.io/web2py/web2py_manual_5th.pdf">Manual</a> <a class="btn btn180 rounded" href="http://mdipierro.github.io/web2py/web2py_manual_5th.pdf">Manual</a>
+3 -3
View File
@@ -30,6 +30,9 @@ except:
if request.is_https: if request.is_https:
session.secure() session.secure()
elif request.env.trusted_lan_prefix and \
remote_addr.startswith(request.env.trusted_lan_prefix):
request.is_local = True
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'))
@@ -194,8 +197,6 @@ def select():
request.vars.query = '%s.%s.%s==%s' % (request.args[0], request.vars.query = '%s.%s.%s==%s' % (request.args[0],
match.group('table'), match.group('field'), match.group('table'), match.group('field'),
match.group('value')) match.group('value'))
else:
request.vars.query = session.last_query
query = get_query(request) query = get_query(request)
if request.vars.start: if request.vars.start:
start = int(request.vars.start) start = int(request.vars.start)
@@ -222,7 +223,6 @@ def select():
else: else:
orderby = '~' + orderby orderby = '~' + orderby
session.last_orderby = orderby session.last_orderby = orderby
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(
+1 -1
View File
@@ -82,7 +82,7 @@
<div style="overflow:auto; width:80%;"> <div style="overflow:auto; width:80%;">
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}} {{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
{{upload=URL('download',args=request.args[0])}} {{upload=URL('download',args=request.args[0])}}
{{=SQLTABLE(rows,linkto,upload,orderby=True,_class='table table-striped table-bordered sortable')}} {{=SQLTABLE(rows,linkto,upload,orderby=True,query=query,_class='table table-striped table-bordered sortable')}}
</div> </div>
{{pass}} {{pass}}
<br/><br/> <br/><br/>
-4
View File
@@ -10,10 +10,6 @@ environment:
COVERAGE_PROCESS_START: gluon/tests/coverage.ini COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64" PYTHON_ARCH: "64"
- PYTHON: "C:/Python35"
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64"
- PYTHON: "C:/Python36" - PYTHON: "C:/Python36"
COVERAGE_PROCESS_START: gluon/tests/coverage.ini COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64" PYTHON_ARCH: "64"
+1 -1
View File
@@ -7,7 +7,7 @@ a = Analysis(['web2py.py'],
pathex=['.'], pathex=['.'],
binaries=[('/System/Library/Frameworks/Tk.framework/Tk', 'tk'), ('/System/Library/Frameworks/Tcl.framework/Tcl', 'tcl')], binaries=[('/System/Library/Frameworks/Tk.framework/Tk', 'tk'), ('/System/Library/Frameworks/Tcl.framework/Tcl', 'tcl')],
datas=[], datas=[],
hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures', hiddenimports=['site-packages', 'argparse', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures',
'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'cProfile', 'csv', 'ctypes.wintypes', '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', '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', 'ipaddress', 'imaplib', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats',
+1 -1
View File
@@ -7,7 +7,7 @@ a = Analysis(['web2py.py'],
pathex=['.'], pathex=['.'],
binaries=[], binaries=[],
datas=[], datas=[],
hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures', hiddenimports=['site-packages', 'argparse', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures',
'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'csv', 'ctypes.wintypes', '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', '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', 'ipaddress', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats',
@@ -7,7 +7,7 @@ a = Analysis(['web2py.py'],
pathex=['.'], pathex=['.'],
binaries=[], binaries=[],
datas=[], datas=[],
hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures', hiddenimports=['site-packages', 'argparse', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures',
'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'csv', 'ctypes.wintypes', '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', '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', 'ipaddress', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats',
+33 -27
View File
@@ -44,16 +44,18 @@ See the documentation for WSGIServer/Server for more information.
On most platforms, fcgi will fallback to regular CGI behavior if run in a On most platforms, fcgi will fallback to regular CGI behavior if run in a
non-FastCGI context. If you want to force CGI behavior, set the environment non-FastCGI context. If you want to force CGI behavior, set the environment
variable FCGI_FORCE_CGI to "Y" or "y". variable FCGI_FORCE_CGI to "Y" or "y".
Modified for web2py
""" """
__author__ = 'Allan Saddi <allan@saddi.com>' __author__ = 'Allan Saddi <allan@saddi.com>'
__version__ = '$Revision$' __version__ = '$Revision$'
import cgi
import sys import sys
import os import os
import signal import signal
import struct import struct
import cStringIO as StringIO
import select import select
import socket import socket
import errno import errno
@@ -68,6 +70,15 @@ except ImportError:
import dummy_threading as threading import dummy_threading as threading
thread_available = False thread_available = False
is_py2 = sys.version_info[0] == 2
if is_py2:
from cgi import escape
import cStringIO as StringIO
else:
from io import StringIO
from html import escape
# Apparently 2.3 doesn't define SHUT_WR? Assume it is 1 in this case. # Apparently 2.3 doesn't define SHUT_WR? Assume it is 1 in this case.
if not hasattr(socket, 'SHUT_WR'): if not hasattr(socket, 'SHUT_WR'):
socket.SHUT_WR = 1 socket.SHUT_WR = 1
@@ -234,11 +245,9 @@ class InputStream(object):
total += len(line) total += len(line)
if 0 < sizehint <= total: if 0 < sizehint <= total:
break break
line = self.readline() yield self.readline()
return lines
def __iter__(self): __iter__ = readlines
return self
def next(self): def next(self):
r = self.readline() r = self.readline()
@@ -436,13 +445,13 @@ def encode_pair(name, value):
if nameLength < 128: if nameLength < 128:
s = chr(nameLength) s = chr(nameLength)
else: else:
s = struct.pack('!L', nameLength | 0x80000000L) s = struct.pack('!L', nameLength | 0x80000000)
valueLength = len(value) valueLength = len(value)
if valueLength < 128: if valueLength < 128:
s += chr(valueLength) s += chr(valueLength)
else: else:
s += struct.pack('!L', valueLength | 0x80000000L) s += struct.pack('!L', valueLength | 0x80000000)
return s + name + value return s + name + value
@@ -592,7 +601,7 @@ class Request(object):
self._flush() self._flush()
self._end(appStatus, protocolStatus) self._end(appStatus, protocolStatus)
def _end(self, appStatus=0L, protocolStatus=FCGI_REQUEST_COMPLETE): def _end(self, appStatus=0, protocolStatus=FCGI_REQUEST_COMPLETE):
self._conn.end_request(self, appStatus, protocolStatus) self._conn.end_request(self, appStatus, protocolStatus)
def _flush(self): def _flush(self):
@@ -615,7 +624,7 @@ class CGIRequest(Request):
self.stderr = sys.stderr self.stderr = sys.stderr
self.data = StringIO.StringIO() self.data = StringIO.StringIO()
def _end(self, appStatus=0L, protocolStatus=FCGI_REQUEST_COMPLETE): def _end(self, appStatus=0, protocolStatus=FCGI_REQUEST_COMPLETE):
sys.exit(appStatus) sys.exit(appStatus)
def _flush(self): def _flush(self):
@@ -664,7 +673,7 @@ class Connection(object):
self.process_input() self.process_input()
except EOFError: except EOFError:
break break
except (select.error, socket.error), e: except (select.error, socket.error) as e:
if e.errno == errno.EBADF: # Socket was closed by Request. if e.errno == errno.EBADF: # Socket was closed by Request.
break break
raise raise
@@ -714,7 +723,7 @@ class Connection(object):
""" """
rec.write(self._sock) rec.write(self._sock)
def end_request(self, req, appStatus=0L, def end_request(self, req, appStatus=0,
protocolStatus=FCGI_REQUEST_COMPLETE, remove=True): protocolStatus=FCGI_REQUEST_COMPLETE, remove=True):
""" """
End a Request. End a Request.
@@ -763,7 +772,7 @@ class Connection(object):
if not self._multiplexed and self._requests: if not self._multiplexed and self._requests:
# Can't multiplex requests. # Can't multiplex requests.
self.end_request(req, 0L, FCGI_CANT_MPX_CONN, remove=False) self.end_request(req, 0, FCGI_CANT_MPX_CONN, remove=False)
else: else:
self._requests[inrec.requestId] = req self._requests[inrec.requestId] = req
@@ -854,7 +863,7 @@ class MultiplexedConnection(Connection):
finally: finally:
self._lock.release() self._lock.release()
def end_request(self, req, appStatus=0L, def end_request(self, req, appStatus=0,
protocolStatus=FCGI_REQUEST_COMPLETE, remove=True): protocolStatus=FCGI_REQUEST_COMPLETE, remove=True):
self._lock.acquire() self._lock.acquire()
try: try:
@@ -949,8 +958,7 @@ class Server(object):
this keyword is ignored; it's not possible to multiplex requests this keyword is ignored; it's not possible to multiplex requests
at all. at all.
""" """
if handler is not None: self.handler = handler or self.default_handler
self.handler = handler
self.maxwrite = maxwrite self.maxwrite = maxwrite
if thread_available: if thread_available:
try: try:
@@ -1077,7 +1085,7 @@ class Server(object):
while self._keepGoing: while self._keepGoing:
try: try:
r, w, e = select.select([sock], [], [], timeout) r, w, e = select.select([sock], [], [], timeout)
except select.error, e: except select.error as e:
if e.errno == errno.EINTR: if e.errno == errno.EINTR:
continue continue
raise raise
@@ -1125,13 +1133,13 @@ class Server(object):
self._keepGoing = False self._keepGoing = False
self._hupReceived = reload self._hupReceived = reload
def handler(self, req): def default_handler(self, req):
""" """
Default handler, which just raises an exception. Unless a handler Default handler, which just raises an exception. Unless a handler
is passed at initialization time, this must be implemented by is passed at initialization time, this must be implemented by
a subclass. a subclass.
""" """
raise NotImplementedError, self.__class__.__name__ + '.handler' raise NotImplementedError(self.__class__.__name__ + '.handler')
def error(self, req): def error(self, req):
""" """
@@ -1147,8 +1155,7 @@ class WSGIServer(Server):
FastCGI server that supports the Web Server Gateway Interface. See FastCGI server that supports the Web Server Gateway Interface. See
<http://www.python.org/peps/pep-0333.html>. <http://www.python.org/peps/pep-0333.html>.
""" """
def __init__(self, application, environ=None, def __init__(self, application, environ=None, multithreaded=True, **kw):
multithreaded=True, **kw):
""" """
environ, if present, must be a dictionary-like object. Its environ, if present, must be a dictionary-like object. Its
contents will be copied into application's environ. Useful contents will be copied into application's environ. Useful
@@ -1156,7 +1163,7 @@ class WSGIServer(Server):
Set multithreaded to False if your application is not MT-safe. Set multithreaded to False if your application is not MT-safe.
""" """
if kw.has_key('handler'): if 'handler'in kw:
del kw['handler'] # Doesn't make sense to let this through del kw['handler'] # Doesn't make sense to let this through
super(WSGIServer, self).__init__(**kw) super(WSGIServer, self).__init__(**kw)
@@ -1170,7 +1177,7 @@ class WSGIServer(Server):
# Used to force single-threadedness # Used to force single-threadedness
self._app_lock = thread.allocate_lock() self._app_lock = thread.allocate_lock()
def handler(self, req): def default_handler(self, req):
"""Special handler for WSGI.""" """Special handler for WSGI."""
if req.role != FCGI_RESPONDER: if req.role != FCGI_RESPONDER:
return FCGI_UNKNOWN_ROLE, 0 return FCGI_UNKNOWN_ROLE, 0
@@ -1240,7 +1247,7 @@ class WSGIServer(Server):
try: try:
if headers_sent: if headers_sent:
# Re-raise if too late # Re-raise if too late
raise exc_info[0], exc_info[1], exc_info[2] raise exc_info[0](exc_info[1], exc_info[2])
finally: finally:
exc_info = None # avoid dangling circular ref exc_info = None # avoid dangling circular ref
else: else:
@@ -1304,7 +1311,6 @@ class WSGIServer(Server):
if __name__ == '__main__': if __name__ == '__main__':
def test_app(environ, start_response): def test_app(environ, start_response):
"""Probably not the most efficient example.""" """Probably not the most efficient example."""
import cgi
start_response('200 OK', [('Content-Type', 'text/html')]) start_response('200 OK', [('Content-Type', 'text/html')])
yield '<html><head><title>Hello World!</title></head>\n' \ yield '<html><head><title>Hello World!</title></head>\n' \
'<body>\n' \ '<body>\n' \
@@ -1313,10 +1319,10 @@ if __name__ == '__main__':
names = environ.keys() names = environ.keys()
names.sort() names.sort()
for name in names: for name in names:
yield '<tr><td>%s</td><td>%s</td></tr>\n' % ( yield '<tr><td>%s</td><td>%s</td></tr>\n' % (name, escape(environ[name]))
name, cgi.escape(`environ[name]`))
form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ, form = cgi.FieldStorage(fp=environ['wsgi.input'],
environ=environ,
keep_blank_values=1) keep_blank_values=1)
if form.list: if form.list:
yield '<tr><th colspan="2">Form data</th></tr>' yield '<tr><th colspan="2">Form data</th></tr>'
+1 -1
View File
@@ -109,7 +109,7 @@ def saml2_handler(session, request, config_filename = None, entityid = None):
client = Saml2Client(config_file = config_filename) client = Saml2Client(config_file = config_filename)
if not entityid: if not entityid:
idps = client.metadata.with_descriptor("idpsso") idps = client.metadata.with_descriptor("idpsso")
entityid = idps.keys()[0] entityid = list(idps.keys())[0]
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST] bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
binding, destination = client.pick_binding( binding, destination = client.pick_binding(
"single_sign_on_service", bindings, "idpsso", entity_id=entityid) "single_sign_on_service", bindings, "idpsso", entity_id=entityid)
+13 -8
View File
@@ -15,7 +15,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import sys import sys
if sys.version > '3':
if sys.version_info[0] < 3:
is_py2 = True
else:
is_py2 = False
unicode = str unicode = str
@@ -160,7 +164,8 @@ class SoapDispatcher(object):
# Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod' # Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod'
# After that we know how the client has prefixed additional namespaces # After that we know how the client has prefixed additional namespaces
ns = NS_RX.findall(xml) decoded_xml = xml if is_py2 else xml.decode('utf8')
ns = NS_RX.findall(decoded_xml)
for k, v in ns: for k, v in ns:
if v in self.namespaces.values(): if v in self.namespaces.values():
_ns_reversed[v] = k _ns_reversed[v] = k
@@ -369,23 +374,23 @@ class SoapDispatcher(object):
e['name'] = k e['name'] = k
if array: if array:
e[:] = {'minOccurs': "0", 'maxOccurs': "unbounded"} e[:] = {'minOccurs': "0", 'maxOccurs': "unbounded"}
if v in TYPE_MAP.keys(): if v is None:
t = 'xsd:%s' % TYPE_MAP[v]
elif v is None:
t = 'xsd:anyType' t = 'xsd:anyType'
elif isinstance(v, list): elif type(v) == list:
n = "ArrayOf%s%s" % (name, k) n = "ArrayOf%s%s" % (name, k)
l = [] l = []
for d in v: for d in v:
l.extend(d.items()) l.extend(d.items())
parse_element(n, l, array=True, complex=True) parse_element(n, l, array=True, complex=True)
t = "tns:%s" % n t = "tns:%s" % n
elif isinstance(v, dict): elif type(v) == dict:
n = "%s%s" % (name, k) n = "%s%s" % (name, k)
parse_element(n, v.items(), complex=True) parse_element(n, v.items(), complex=True)
t = "tns:%s" % n t = "tns:%s" % n
elif v in TYPE_MAP:
t = 'xsd:%s' % TYPE_MAP[v]
else: else:
raise TypeError("unknonw type %s for marshalling" % str(v)) raise TypeError("unknown type %s for marshalling" % str(v))
e.add_attribute('type', t) e.add_attribute('type', t)
parse_element("%s" % method, args and args.items()) parse_element("%s" % method, args and args.items())
+9 -1
View File
@@ -242,7 +242,15 @@ class Request(Storage):
# parse POST variables on POST, PUT, BOTH only in post_vars # parse POST variables on POST, PUT, BOTH only in post_vars
if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'): if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'):
query_string = env.pop('QUERY_STRING', None) query_string = env.pop('QUERY_STRING', None)
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1) content_disposition = env.get('HTTP_CONTENT_DISPOSITION')
if content_disposition:
headers = {'content-disposition': content_disposition,
'content-type': env['CONTENT_TYPE'],
'content-length': env['CONTENT_LENGTH'],
}
else:
headers = None
dpost = cgi.FieldStorage(fp=body, environ=env, headers=headers, keep_blank_values=1)
try: try:
post_vars.update(dpost) post_vars.update(dpost)
except: except:
+20 -7
View File
@@ -190,7 +190,8 @@ def URL(a=None,
port=None, port=None,
encode_embedded_slash=False, encode_embedded_slash=False,
url_encode=True, url_encode=True,
language=None language=None,
hash_extension=True
): ):
""" """
generates a url '/a/c/f' corresponding to application a, controller c generates a url '/a/c/f' corresponding to application a, controller c
@@ -339,7 +340,8 @@ def URL(a=None,
if '.' in function: if '.' in function:
function, extension = function.rsplit('.', 1) function, extension = function.rsplit('.', 1)
function2 = '%s.%s' % (function, extension or 'html') # only include the extension as part of the variables for the hash if requested
function2 = '%s.%s' % (function, extension or 'html') if hash_extension else function
if not (application and controller and function): if not (application and controller and function):
raise SyntaxError('not enough information to build the url (%s %s %s)' % (application, controller, function)) raise SyntaxError('not enough information to build the url (%s %s %s)' % (application, controller, function))
@@ -416,7 +418,7 @@ def URL(a=None,
return url return url
def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None): def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=None, hash_extension=True):
""" """
Verifies that a request's args & vars have not been tampered with by the user Verifies that a request's args & vars have not been tampered with by the user
@@ -477,10 +479,19 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
# always include all of the args # always include all of the args
other = args and urllib_quote('/' + '/'.join([str(x) for x in args])) or '' other = args and urllib_quote('/' + '/'.join([str(x) for x in args])) or ''
h_args = '/%s/%s/%s.%s%s' % (request.application,
# decide whether the extension should be part of the hash verification
h_extension = request.extension if hash_extension else ''
# only add a period to the extension when it exists otherwise empty
# extensions will fail to validate
if h_extension:
h_extension = '.%s' % (h_extension)
h_args = '/%s/%s/%s%s%s' % (request.application,
request.controller, request.controller,
request.function, request.function,
request.extension, h_extension,
other) other)
# but only include those vars specified (allows more flexibility for use with # but only include those vars specified (allows more flexibility for use with
@@ -1450,8 +1461,10 @@ class SCRIPT(DIV):
(fa, co) = self._xml() (fa, co) = self._xml()
fa = to_bytes(fa) fa = to_bytes(fa)
# no escaping of subcomponents # no escaping of subcomponents
co = b'\n'.join([to_bytes(component) for component in co = b'\n'.join(map(to_bytes,
self.components]) # allow xml components (i.e. ASSIGNJS)
map(lambda c: c.xml() if hasattr(c, 'xml') and callable(c.xml) else c,
self.components)))
if co: if co:
# <script [attributes]><!--//--><![CDATA[//><!-- # <script [attributes]><!--//--><![CDATA[//><!--
# script body # script body
+19 -20
View File
@@ -56,19 +56,18 @@ create_missing_folders()
# set up logging for subsequent imports # set up logging for subsequent imports
import logging.config import logging.config
# This needed to prevent exception on Python 2.5: # do not need fancy graphical loggers when served by handler
# NameError: name 'gluon' is not defined # (e.g. apache + mod_wsgi), speed up execution
# See http://bugs.python.org/issue1436 if not global_settings.web2py_runtime_handler:
# attention!, the import Tkinter in messageboxhandler, changes locale ...
# attention!, the import Tkinter in messageboxhandler, changes locale ... import gluon.messageboxhandler
import gluon.messageboxhandler # This needed to prevent exception on Python 2.5:
logging.gluon = gluon # NameError: name 'gluon' is not defined
# so we must restore it! Thanks ozancag # See http://bugs.python.org/issue1436
import locale logging.gluon = gluon
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C" # so we must restore it! Thanks ozancag
import locale
exists = os.path.exists locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
pjoin = os.path.join
try: try:
logging.config.fileConfig(abspath("logging.conf")) logging.config.fileConfig(abspath("logging.conf"))
@@ -76,6 +75,9 @@ except: # fails on GAE or when logfile is missing
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger("web2py") logger = logging.getLogger("web2py")
exists = os.path.exists
pjoin = os.path.join
from gluon.restricted import RestrictedError from gluon.restricted import RestrictedError
from gluon.http import HTTP, redirect from gluon.http import HTTP, redirect
from gluon.globals import Request, Response, Session from gluon.globals import Request, Response, Session
@@ -99,17 +101,14 @@ requests = 0 # gc timer
try: try:
version_info = read_file(pjoin(global_settings.gluon_parent, 'VERSION')) version_info = read_file(pjoin(global_settings.gluon_parent, 'VERSION'))
raw_version_string = version_info.split()[-1].strip() web2py_version = global_settings.web2py_version = version_info.split()[-1].strip()
global_settings.web2py_version = raw_version_string
web2py_version = global_settings.web2py_version
except: except:
raise RuntimeError("Cannot determine web2py version") raise RuntimeError("Cannot determine web2py version")
try: # do not need rocket nor HttpServer when served by handler
# (e.g. apache + mod_wsgi), speed up execution and save memory
if not global_settings.web2py_runtime_handler:
from gluon import rocket from gluon import rocket
except:
if not global_settings.web2py_runtime_gae:
logger.warn('unable to import Rocket')
load_routes() load_routes()
+607 -394
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -23,7 +23,11 @@ HTTP_SERVER_SOFTWARE = '%s Python/%s' % (
BUF_SIZE = 16384 BUF_SIZE = 16384
SOCKET_TIMEOUT = 10 # in secs SOCKET_TIMEOUT = 10 # in secs
THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message? THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message?
IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython if hasattr(sys, 'frozen'):
# py2installer
IS_JYTHON = False
else:
IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython
IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET]) IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET])
DEFAULT_LISTEN_QUEUE_SIZE = 5 DEFAULT_LISTEN_QUEUE_SIZE = 5
DEFAULT_MIN_THREADS = 10 DEFAULT_MIN_THREADS = 10
+1 -1
View File
@@ -84,7 +84,7 @@ def custom_json(o):
elif isinstance(o, decimal.Decimal): elif isinstance(o, decimal.Decimal):
return float(o) return float(o)
elif isinstance(o, (bytes, bytearray)): elif isinstance(o, (bytes, bytearray)):
return str(o) return str(o) if hasattr(str, 'decode') else str(o, encoding='utf-8')
elif isinstance(o, lazyT): elif isinstance(o, lazyT):
return str(o) return str(o)
elif isinstance(o, XmlComponent): elif isinstance(o, XmlComponent):
+4
View File
@@ -43,3 +43,7 @@ global_settings.is_source = os.path.exists(os.path.join(
global_settings.gluon_parent, 'web2py.py')) global_settings.gluon_parent, 'web2py.py'))
global_settings.is_py2 = PY2 global_settings.is_py2 = PY2
# allow admin app for clients on trusted LAN when over plain http,
# default is to allow only from localhost or when serving https
#global_settings.trusted_lan_prefix = '192.168.0.'
+20 -1
View File
@@ -20,6 +20,7 @@ import logging
import types import types
import re import re
import glob import glob
import site
import traceback import traceback
import gluon.fileutils as fileutils import gluon.fileutils as fileutils
from gluon.settings import global_settings from gluon.settings import global_settings
@@ -240,13 +241,16 @@ def run(
if not cron_job and not scheduler_job and \ if not cron_job and not scheduler_job and \
sys.stdin and not sys.stdin.name == '/dev/null': sys.stdin and not sys.stdin.name == '/dev/null':
confirm = raw_input( confirm = raw_input(
'application %s does not exist, create (y/n)?' % a) 'application %s does not exist, create (y/N)?' % a)
else: else:
logging.warn('application does not exist and will not be created') logging.warn('application does not exist and will not be created')
return return
if confirm.lower() in ('y', 'yes'): if confirm.lower() in ('y', 'yes'):
os.mkdir(adir) os.mkdir(adir)
fileutils.create_app(adir) fileutils.create_app(adir)
else:
logging.warn('application folder does not exist and has not been created as requested')
return
if force_migrate: if force_migrate:
c = 'appadmin' # Load all models (hack already used for appadmin controller) c = 'appadmin' # Load all models (hack already used for appadmin controller)
@@ -303,6 +307,11 @@ def run(
if import_models: if import_models:
BaseAdapter.close_all_instances('commit') BaseAdapter.close_all_instances('commit')
except SystemExit:
print(traceback.format_exc())
if import_models:
BaseAdapter.close_all_instances('rollback')
raise
except: except:
print(traceback.format_exc()) print(traceback.format_exc())
if import_models: if import_models:
@@ -312,6 +321,11 @@ def run(
exec(python_code, _env) exec(python_code, _env)
if import_models: if import_models:
BaseAdapter.close_all_instances('commit') BaseAdapter.close_all_instances('commit')
except SystemExit:
print(traceback.format_exc())
if import_models:
BaseAdapter.close_all_instances('rollback')
raise
except: except:
print(traceback.format_exc()) print(traceback.format_exc())
if import_models: if import_models:
@@ -321,6 +335,11 @@ def run(
execfile("scripts/migrator.py", _env) execfile("scripts/migrator.py", _env)
if import_models: if import_models:
BaseAdapter.close_all_instances('commit') BaseAdapter.close_all_instances('commit')
except SystemExit:
print(traceback.format_exc())
if import_models:
BaseAdapter.close_all_instances('rollback')
raise
except: except:
print(traceback.format_exc()) print(traceback.format_exc())
if import_models: if import_models:
+13 -9
View File
@@ -99,7 +99,7 @@ class CacheRepresenter(object):
nvalue = field.represent(value, row[field.tablename]) nvalue = field.represent(value, row[field.tablename])
except KeyError: except KeyError:
nvalue = None nvalue = None
if isinstance(field, _repr_ref): if isinstance(field.represent, _repr_ref): # BKR ISSUE/2312 20200422
cache[field][value] = nvalue cache[field][value] = nvalue
return nvalue return nvalue
@@ -1330,7 +1330,7 @@ class SQLFORM(FORM):
# - user not trying to upload a new file # - user not trying to upload a new file
# - there is existing file and user is not trying to delete it # - there is existing file and user is not trying to delete it
# this is because removing the file may not pass validation # this is because removing the file may not pass validation
for key in self.errors.keys(): for key in list(self.errors):
if key in self.table \ if key in self.table \
and self.table[key].type == 'upload' \ and self.table[key].type == 'upload' \
and request_vars.get(key, None) in (None, '') \ and request_vars.get(key, None) in (None, '') \
@@ -1501,7 +1501,7 @@ class SQLFORM(FORM):
if readonly and not ignore_rw and not field.readable: if readonly and not ignore_rw and not field.readable:
continue continue
if record: if record and fieldname not in [x.name for x in extra_fields]:
default = record[fieldname] default = record[fieldname]
else: else:
default = field.default default = field.default
@@ -2667,7 +2667,7 @@ class SQLFORM(FORM):
if export_type: if export_type:
order = request.vars.order or '' order = request.vars.order or ''
if sortable: if sortable:
if order and not order == 'None': if order:
otablename, ofieldname = order.split('~')[-1].split('.', 1) otablename, ofieldname = order.split('~')[-1].split('.', 1)
sort_field = db[otablename][ofieldname] sort_field = db[otablename][ofieldname]
orderby = sort_field if order[:1] != '~' else ~sort_field orderby = sort_field if order[:1] != '~' else ~sort_field
@@ -2800,7 +2800,7 @@ class SQLFORM(FORM):
order = request.vars.order or '' order = request.vars.order or ''
asc_icon, desc_icon = sorter_icons asc_icon, desc_icon = sorter_icons
if sortable: if sortable:
if order and not order == 'None': if order:
otablename, ofieldname = order.split('~')[-1].split('.', 1) otablename, ofieldname = order.split('~')[-1].split('.', 1)
sort_field = db[otablename][ofieldname] sort_field = db[otablename][ofieldname]
orderby = sort_field if order[:1] != '~' else ~sort_field orderby = sort_field if order[:1] != '~' else ~sort_field
@@ -2820,14 +2820,14 @@ class SQLFORM(FORM):
if key == order.lstrip('~'): if key == order.lstrip('~'):
if inverted: if inverted:
if key == order: if key == order:
key, marker = 'None', asc_icon marker = asc_icon
else: else:
key, marker = order[1:], desc_icon key, marker = order[1:], desc_icon
else: else:
if key == order: if key == order:
key, marker = '~' + order, asc_icon key, marker = '~' + order, asc_icon
else: else:
key, marker = 'None', desc_icon marker = desc_icon
elif inverted and key == str(field): elif inverted and key == str(field):
key = '~' + key key = '~' + key
header = A(header, marker, _href=url(vars=dict( header = A(header, marker, _href=url(vars=dict(
@@ -3393,6 +3393,7 @@ class SQLTABLE(TABLE):
linkto: URL (or lambda to generate a URL) to edit individual records linkto: URL (or lambda to generate a URL) to edit individual records
upload: URL to download uploaded files upload: URL to download uploaded files
orderby: Add an orderby link to column headers. orderby: Add an orderby link to column headers.
query: Query string to support orderby headers.
headers: dictionary of headers to headers redefinions headers: dictionary of headers to headers redefinions
headers can also be a string to generate the headers from data headers can also be a string to generate the headers from data
for now only headers="fieldname:capitalize", for now only headers="fieldname:capitalize",
@@ -3428,6 +3429,7 @@ class SQLTABLE(TABLE):
linkto=None, linkto=None,
upload=None, upload=None,
orderby=None, orderby=None,
query='',
headers={}, headers={},
truncate=16, truncate=16,
columns=None, columns=None,
@@ -3499,8 +3501,10 @@ class SQLTABLE(TABLE):
attrcol.update(_class=coldict['class']) attrcol.update(_class=coldict['class'])
row.append(TH(coldict['label'], **attrcol)) row.append(TH(coldict['label'], **attrcol))
elif orderby: elif orderby:
row.append(TH(A(headers.get(c, c), link = th_link + '?orderby=' + c
_href=th_link + '?orderby=' + c, cid=cid))) if query:
link += '&query=' + query
row.append(TH(A(headers.get(c, c), _href=link, cid=cid)))
else: else:
row.append(TH(headers.get(c, re.sub(self.REGEX_ALIAS_MATCH, r'\2', c)))) row.append(TH(headers.get(c, re.sub(self.REGEX_ALIAS_MATCH, r'\2', c))))
+1 -1
View File
@@ -188,7 +188,7 @@ class TestBareHelpers(unittest.TestCase):
XML('<h1>HelloWorld</h1>').__repr__()) XML('<h1>HelloWorld</h1>').__repr__())
# bug check for the sanitizer for closing no-close tags # bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True).xml(), self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True).xml(),
XML('<p>Test</p><br /><p>Test</p><br />').xml()) XML('<p>Test</p><br/><p>Test</p><br/>').xml())
# basic flatten test # basic flatten test
self.assertEqual(XML('<p>Test</p>').flatten(), '<p>Test</p>') self.assertEqual(XML('<p>Test</p>').flatten(), '<p>Test</p>')
self.assertEqual(XML('<p>Test</p>').flatten(render=lambda text, tag, attr: text), '<p>Test</p>') self.assertEqual(XML('<p>Test</p>').flatten(render=lambda text, tag, attr: text), '<p>Test</p>')
+15 -10
View File
@@ -816,7 +816,15 @@ class Mail(object):
server.login(*self.settings.login.split(':', 1)) server.login(*self.settings.login.split(':', 1))
result = server.sendmail(sender, to, payload.as_string()) result = server.sendmail(sender, to, payload.as_string())
finally: finally:
server.quit() # do not want to hide errors raising some exception here
try:
server.quit()
except smtplib.SMTPException:
# ensure to close any socket with SMTP server
try:
server.close()
except Exception:
pass
except Exception as e: except Exception as e:
logger.warning('Mail.send failure:%s' % e) logger.warning('Mail.send failure:%s' % e)
self.result = result self.result = result
@@ -984,15 +992,15 @@ def addrow(form, a, b, c, style, _id, position=-1):
DIV(b, SPAN(c, _class='inline-help'), DIV(b, SPAN(c, _class='inline-help'),
_class='controls'), _class='controls'),
_class='control-group', _id=_id)) _class='control-group', _id=_id))
elif style == "bootstrap3_inline": elif style in ("bootstrap3_inline", "bootstrap4_inline"):
form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'), form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'),
DIV(b, SPAN(c, _class='help-block'), DIV(b, SPAN(c, _class='help-block'),
_class='col-sm-9'), _class='col-sm-9'),
_class='form-group', _id=_id)) _class='form-group row', _id=_id))
elif style == "bootstrap3_stacked": elif style in ("bootstrap3_stacked", "bootstrap4_stacked"):
form[0].insert(position, DIV(LABEL(a, _class='control-label'), form[0].insert(position, DIV(LABEL(a, _class='control-label'),
b, SPAN(c, _class='help-block'), b, SPAN(c, _class='help-block'),
_class='form-group', _id=_id)) _class='form-group row', _id=_id))
else: else:
form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'), form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'),
TD(b, _class='w2p_fw'), TD(b, _class='w2p_fw'),
@@ -3922,7 +3930,7 @@ class Auth(AuthAPI):
return self.has_permission(name, table_name, record_id) return self.has_permission(name, table_name, record_id)
return self.requires(has_permission, otherwise=otherwise) return self.requires(has_permission, otherwise=otherwise)
def requires_signature(self, otherwise=None, hash_vars=True): def requires_signature(self, otherwise=None, hash_vars=True, hash_extension=True):
""" """
Decorator that prevents access to action if not logged in or Decorator that prevents access to action if not logged in or
if user logged in is not a member of group_id. if user logged in is not a member of group_id.
@@ -3930,7 +3938,7 @@ class Auth(AuthAPI):
group_id is calculated. group_id is calculated.
""" """
def verify(): def verify():
return URL.verify(current.request, user_signature=True, hash_vars=hash_vars) return URL.verify(current.request, user_signature=True, hash_vars=hash_vars, hash_extension=True)
return self.requires(verify, otherwise) return self.requires(verify, otherwise)
def accessible_query(self, name, table, user_id=None): def accessible_query(self, name, table, user_id=None):
@@ -5605,9 +5613,6 @@ class Expose(object):
and file creation under `base`. and file creation under `base`.
""" """
# why would this not be callable? but otherwise tests do not pass
if current.session and callable(current.session.forget):
current.session.forget()
self.follow_symlink_out = follow_symlink_out self.follow_symlink_out = follow_symlink_out
self.base = self.normalize_path( self.base = self.normalize_path(
base or os.path.join(current.request.folder, 'static')) base or os.path.join(current.request.folder, 'static'))
+13 -9
View File
@@ -704,14 +704,9 @@ def start():
run_system_tests(options) run_system_tests(options)
if options.quiet: if options.quiet:
# to prevent writes on stdout set a null stream # mute existing loggers, to do that iterate
class NullFile(object): # over all loggers (root logger included) and remove
def write(self, x): # attached logging.StreamHandler instances currently
pass
sys.stdout = NullFile()
# but still has to mute existing loggers, to do that iterate
# over all existing loggers (root logger included) and remove
# all attached logging.StreamHandler instances currently
# streaming on sys.stdout or sys.stderr # streaming on sys.stdout or sys.stderr
loggers = [logging.getLogger()] loggers = [logging.getLogger()]
loggers.extend(logging.Logger.manager.loggerDict.values()) loggers.extend(logging.Logger.manager.loggerDict.values())
@@ -721,9 +716,18 @@ def start():
if isinstance(h, logging.StreamHandler) and \ if isinstance(h, logging.StreamHandler) and \
h.stream in (sys.stdout, sys.stderr): h.stream in (sys.stdout, sys.stderr):
l.removeHandler(h) l.removeHandler(h)
# this is to avoid the warning
# ``No handlers could be found for logger "..."``
# emitted by logging module when no handler is found
logging.Logger.manager.emittedNoHandlerWarning = 1
# to prevent writes on stdout set a null stream
class NullFile(object):
def write(self, x):
pass
sys.stdout = NullFile()
# NOTE: stderr.write() is still working # NOTE: stderr.write() is still working
if not options.no_banner: elif not options.no_banner:
# banner # banner
print(ProgramName) print(ProgramName)
print(ProgramAuthor) print(ProgramAuthor)
+3
View File
@@ -62,6 +62,9 @@ if not os.path.isdir('applications'):
sys.path = [path] + [p for p in sys.path if not p == path] sys.path = [path] + [p for p in sys.path if not p == path]
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
wsgiref.handlers.CGIHandler().run(gluon.main.wsgibase) wsgiref.handlers.CGIHandler().run(gluon.main.wsgibase)
+3 -1
View File
@@ -40,6 +40,9 @@ if not os.path.isdir('applications'):
sys.path = [path] + [p for p in sys.path if not p == path] sys.path = [path] + [p for p in sys.path if not p == path]
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
import gluon.contrib.gateways.fcgi as fcgi import gluon.contrib.gateways.fcgi as fcgi
@@ -51,7 +54,6 @@ else:
application = gluon.main.wsgibase application = gluon.main.wsgibase
if SOFTCRON: if SOFTCRON:
from gluon.settings import global_settings
global_settings.web2py_crontype = 'soft' global_settings.web2py_crontype = 'soft'
fcgi.WSGIServer(application, bindAddress='/tmp/fcgi.sock').run() fcgi.WSGIServer(application, bindAddress='/tmp/fcgi.sock').run()
+5 -4
View File
@@ -49,14 +49,15 @@ from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app from google.appengine.ext.webapp.util import run_wsgi_app
global_settings.web2py_runtime_handler = True
global_settings.web2py_runtime_gae = True global_settings.web2py_runtime_gae = True
global_settings.db_sessions = True global_settings.db_sessions = True
if os.environ.get('SERVER_SOFTWARE', '').startswith('Devel'): if os.environ.get('SERVER_SOFTWARE', '').startswith('Devel'):
(global_settings.web2py_runtime, DEBUG) = \ global_settings.web2py_runtime = 'gae:development'
('gae:development', True) DEBUG = True
else: else:
(global_settings.web2py_runtime, DEBUG) = \ global_settings.web2py_runtime = 'gae:production'
('gae:production', False) DEBUG = False
import gluon.main import gluon.main
+2
View File
@@ -13,6 +13,8 @@ def __ExtensionFactory__():
if not os.path.isdir('applications'): if not os.path.isdir('applications'):
raise RuntimeError('Running from the wrong folder') raise RuntimeError('Running from the wrong folder')
sys.path = [path] + [p for p in sys.path if not p == path] sys.path = [path] + [p for p in sys.path if not p == path]
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
import isapi_wsgi import isapi_wsgi
application = gluon.main.wsgibase application = gluon.main.wsgibase
+3
View File
@@ -40,6 +40,9 @@ if not os.path.isdir('applications'):
sys.path = [path] + [p for p in sys.path if not p == path] sys.path = [path] + [p for p in sys.path if not p == path]
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
+3 -1
View File
@@ -53,6 +53,9 @@ if not os.path.isdir('applications'):
sys.path = [path] + [p for p in sys.path if not p == path] sys.path = [path] + [p for p in sys.path if not p == path]
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
# uncomment one of the two imports below depending on the SCGIWSGI server installed # uncomment one of the two imports below depending on the SCGIWSGI server installed
@@ -70,7 +73,6 @@ else:
application = wsgiapp application = wsgiapp
if SOFTCRON: if SOFTCRON:
from gluon.settings import global_settings
global_settings.web2py_crontype = 'soft' global_settings.web2py_crontype = 'soft'
# uncomment one of the two rows below depending on the SCGIWSGI server installed # uncomment one of the two rows below depending on the SCGIWSGI server installed
+2
View File
@@ -21,6 +21,8 @@ from gevent import monkey
monkey.patch_all() monkey.patch_all()
def run(options): def run(options):
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
if options.password != '<recycle>': if options.password != '<recycle>':
gluon.main.save_password(options.password, int(options.port)) gluon.main.save_password(options.password, int(options.port))
+3 -1
View File
@@ -26,6 +26,9 @@ if not os.path.isdir('applications'):
sys.path = [path] + [p for p in sys.path if not p == path] sys.path = [path] + [p for p in sys.path if not p == path]
from gluon.settings import global_settings
global_settings.web2py_runtime_handler = True
import gluon.main import gluon.main
if LOGGING: if LOGGING:
@@ -36,5 +39,4 @@ else:
application = gluon.main.wsgibase application = gluon.main.wsgibase
if SOFTCRON: if SOFTCRON:
from gluon.settings import global_settings
global_settings.web2py_crontype = 'soft' global_settings.web2py_crontype = 'soft'
+12 -12
View File
@@ -41,7 +41,7 @@ from __future__ import with_statement
from gluon import current from gluon import current
from gluon.storage import Storage from gluon.storage import Storage
from gluon._compat import pickle from gluon._compat import pickle
from optparse import OptionParser
import datetime import datetime
import stat import stat
import time import time
@@ -62,13 +62,12 @@ class SessionSet(object):
def get(self): def get(self):
"""Get session files/records.""" """Get session files/records."""
raise NotImplementedError raise NotImplementedError()
def trash(self): def trash(self):
"""Trash expired sessions.""" """Trash expired sessions."""
now = datetime.datetime.now() now = datetime.datetime.now()
for item in self.get(): for item in self.get():
status = 'OK'
last_visit = item.last_visit_default() last_visit = item.last_visit_default()
try: try:
@@ -88,16 +87,18 @@ class SessionSet(object):
if age > self.expiration or not self.expiration: if age > self.expiration or not self.expiration:
item.delete() item.delete()
status = 'trashed' status = 'trashed'
else:
status = 'OK'
if self.verbose > 1: if self.verbose > 1:
print('key: %s' % str(item)) print('key: %s' % item)
print('expiration: %s seconds' % self.expiration) print('expiration: %s seconds' % self.expiration)
print('last visit: %s' % str(last_visit)) print('last visit: %s' % last_visit)
print('age: %s seconds' % age) print('age: %s seconds' % age)
print('status: %s' % status) print('status: %s' % status)
print('') print('')
elif self.verbose > 0: elif self.verbose > 0:
print('%s %s' % (str(item), status)) print('%s %s' % (item, status))
class SessionSetDb(SessionSet): class SessionSetDb(SessionSet):
@@ -179,8 +180,8 @@ class SessionFile(object):
def get(self): def get(self):
session = Storage() session = Storage()
with open(self.filename, 'rb+') as f: with open(self.filename, 'rb') as f:
session.update(cPickle.load(f)) session.update(pickle.load(f))
return session return session
def last_visit_default(self): def last_visit_default(self):
@@ -198,8 +199,7 @@ def total_seconds(delta):
Args: Args:
delta: datetime.timedelta instance. delta: datetime.timedelta instance.
""" """
return (delta.microseconds + (delta.seconds + (delta.days * 24 * 3600)) * return (delta.microseconds + (delta.seconds + (delta.days * 86400)) * 1000000) / 1e6
10 ** 6) / 10 ** 6
def single_loop(expiration=None, force=False, verbose=False): def single_loop(expiration=None, force=False, verbose=False):
if expiration is None: if expiration is None:
@@ -216,9 +216,9 @@ def single_loop(expiration=None, force=False, verbose=False):
def main(): def main():
"""Main processing.""" """Main processing."""
from optparse import OptionParser
usage = '%prog [options]' + '\nVersion: %s' % VERSION parser = OptionParser(usage="%%prog [options]\nVersion: %s" % VERSION)
parser = OptionParser(usage=usage)
parser.add_option('-f', '--force', parser.add_option('-f', '--force',
action='store_true', dest='force', default=False, action='store_true', dest='force', default=False,
+3
View File
@@ -19,6 +19,9 @@ else:
# process -f (--folder) option # process -f (--folder) option
if '-f' in sys.argv: if '-f' in sys.argv:
fi = sys.argv.index('-f') fi = sys.argv.index('-f')
# maybe session2trash arg
if '-A' in sys.argv and fi > sys.argv.index('-A'):
fi = None
elif '--folder' in sys.argv: elif '--folder' in sys.argv:
fi = sys.argv.index('--folder') fi = sys.argv.index('--folder')
else: else: