Compare commits

...
78 Commits
Author SHA1 Message Date
mdipierro 67485d16a5 R-2.14.4 2016-04-12 16:21:05 -05:00
mdipierro eba8ad4b55 fixed CHANGELOG 2016-04-12 15:45:54 -05:00
mdipierro 4ed6fb7ed9 Merge pull request #1288 from carpaIdea/patch-1
Missing <!DOCTYPE html>
2016-04-12 15:44:19 -05:00
carpaIdea 294c67194f Missing <!DOCTYPE html>
Of course if there is not any intentional reason to turn on "quirks mode" in browsers.

https://developer.mozilla.org/en-US/docs/Web/CSS/Common_CSS_Questions#Why_doesn't_my_CSS_which_is_valid_render_correctly
https://developer.mozilla.org/it/docs/Quirks_Mode_and_Standards_Mode
2016-04-12 22:24:30 +02:00
mdipierro 70f793b422 Merge pull request #1287 from leonelcamara/test_week6
Test week6
2016-04-12 11:14:14 -05:00
Leonel Câmara 9552d9d6d0 fixed sort=True test 2016-04-12 16:28:33 +01:00
Leonel Câmara 9ead66b6db test IS_IN_DB label is a Field and self.sort = True 2016-04-12 16:00:33 +01:00
Leonel Câmara 00c65ad160 Complete coverage for Mail.Attachment 2016-04-12 15:28:48 +01:00
Leonel Câmara b5c8b3ad25 closes #1286 2016-04-12 15:10:14 +01:00
mdipierro 5ca65d55d2 Merge pull request #1284 from BuhtigithuB/more-markmin-pep8
More markmin pep8
2016-04-11 13:38:54 -05:00
mdipierro 3f200fdc22 Merge pull request #1283 from leonelcamara/grid_issues
closes #1188
2016-04-11 13:38:37 -05:00
Hardirc 409c973dc4 Enhance markmin2pdf.py PEP8 2016-04-10 09:36:06 -04:00
Hardirc 59a194842d Enhance markmin2latex.py PEP8 2016-04-10 09:34:29 -04:00
Leonel Câmara ee8b11db2c closes #1188 2016-04-10 12:08:25 +01:00
mdipierro 8ef04ac425 Merge pull request #1282 from BuhtigithuB/Feature/heading-anchor
Feature/heading anchor
2016-04-09 18:39:39 -05:00
mdipierro 83cf098c07 fixed stupid.css and impersonate 2016-04-09 10:30:31 -05:00
mdipierro 70b41fa15e Merge pull request #1280 from BuhtigithuB/pep8-gluon-html-py
Enhance gluon/html.py PEP8
2016-04-08 23:37:54 -05:00
mdipierro 56af81f247 Merge pull request #1278 from BuhtigithuB/pep8-welcome
Pep8 welcome
2016-04-08 23:37:32 -05:00
mdipierro 7fd30d8360 Merge pull request #1276 from BuhtigithuB/Improve/web2py-com-display
Improve/web2py com display
2016-04-08 23:35:43 -05:00
mdipierro e1aefa2307 Merge pull request #1275 from BuhtigithuB/Improve/gluon-tools-py
PEP8 Recaptcha/2 docstring
2016-04-08 23:35:10 -05:00
mdipierro 7a2316ec9a Merge pull request #1260 from BuhtigithuB/add-test-sqlform
SQLFORM() basic check
2016-04-08 23:34:51 -05:00
Hardirc e48e47beb2 Enhance markmin2html.py contrib PEP8 2016-04-08 22:12:43 -04:00
Hardirc 864c308246 Enhance gluon/html.py PEP8 2016-04-08 21:11:25 -04:00
Hardirc 994f3e7ae4 Enhance welcome menu.py PEP8 2016-04-08 20:17:35 -04:00
Hardirc 1d2f74440e Enhance welcome routes.example.py PEP8 2016-04-08 20:13:58 -04:00
Hardirc 704ceec16f Enhance welcome controllers/default.py PEP8 2016-04-08 20:13:30 -04:00
Hardirc 038e25c259 Enhance welcome db.py PEP8 2016-04-08 20:05:21 -04:00
Richard Vézina c3aa02b3ce Improve welcome (web2py.com) site aesthetic 2016-04-08 15:16:58 -04:00
Richard Vézina 5cc4487d8c Improve PEP8 download.html 2016-04-08 14:17:56 -04:00
Richard Vézina d50e6aab6b Improve default.py PEP8 2016-04-08 11:52:09 -04:00
Richard Vézina 1d21f45e3e PEP8 Recaptcha/2 docstring 2016-04-07 10:19:57 -04:00
mdipierro 99a323c7ad Merge pull request #1272 from BuhtigithuB/new-logout-bare
New logout_bare() for shell logout and refactor test using it
2016-04-07 09:17:19 -05:00
Hardirc e0d86462c8 New logout_bare() for shell logout and refactor test using it 2016-04-06 22:46:24 -04:00
mdipierro ff0d10ac4f Merge pull request #1271 from leonelcamara/delete_unused_reserved
delete unused reserved_sql_keywords.py because the DAL uses this one …
2016-04-06 20:58:05 -05:00
mdipierro eee7be75c8 Merge pull request #1265 from BuhtigithuB/improve-tools-tests-some-more
Improve tools tests some more
2016-04-06 20:57:30 -05:00
mdipierro 3c69716672 Merge pull request #1270 from leonelcamara/test_week5b
Test Mail.Attachment sending test_tools.py itself
2016-04-06 20:57:06 -05:00
Leonel Câmara ad4b0eee54 delete unused reserved_sql_keywords.py because the DAL uses this one pydal/contrib/reserved_sql_keywords.py 2016-04-06 23:06:57 +01:00
Richard Vézina 0128ce3a93 Make test_login_bare() works, new test_impersonate() 2016-04-06 16:58:58 -04:00
Leonel Câmara 0629df71ef Test Mail.Attachment sending test_tools.py itself 2016-04-06 20:04:21 +01:00
mdipierro 4d1a4c48e6 Merge pull request #1268 from mbelletti/fix/cas_login
Fix #1267 cas_login
2016-04-06 13:49:28 -05:00
Massimiliano Belletti 2ffdb716cd Fix #1267 cas_login 2016-04-06 17:06:23 +02:00
mdipierro 40f04de9d2 fixed scripts/setup-web2py-nginx-uwsgi-ubuntu.sh 2016-04-06 09:20:13 -05:00
mdipierro 52615fbca7 Merge pull request #1263 from niphlod/tests/scheduler
initial tests for scheduler.py, plus a minor fix for JobGraph
2016-04-05 17:00:00 -05:00
niphlod 6abb78c559 initial tests for scheduler.py 2016-04-04 22:58:42 +02:00
Hardirc db701ffea8 correct unproperly generated SQLFORM methods and basic tests 2016-04-03 16:24:13 -04:00
mdipierro 0804c28331 Merge pull request #1259 from BuhtigithuB/new-test-sqlform
let start testing sqlhtml.py
2016-04-02 15:02:02 -05:00
mdipierro 24bc51447c Merge pull request #1257 from BuhtigithuB/improve-tools-tests
Improve tools tests
2016-04-02 15:00:41 -05:00
mdipierro ccbbdc2493 Merge branch 'leonelcamara-prettier_examples' 2016-04-02 14:59:23 -05:00
mdipierro d94e8415c7 reverted stupid 2016-04-02 14:57:36 -05:00
mdipierro 0a62e86156 Merge pull request #1254 from leonelcamara/test_week4b
Seriously increase template.py coverage
2016-04-02 14:53:39 -05:00
mdipierro 5744c06f59 Merge pull request #1253 from leonelcamara/test_week4
More test coverage for validators.py
2016-04-02 14:52:42 -05:00
mdipierro 947f92774b Merge pull request #1252 from BuhtigithuB/improve-test-chache
Improve test chache
2016-04-02 14:52:26 -05:00
Hardirc 4001407add let start testing sqlhtml.py 2016-04-02 15:49:18 -04:00
mdipierro 46ffaa6aea improved search form 2016-04-02 14:48:03 -05:00
mdipierro ba2be80080 updated stupic.css and suport page 2016-04-02 14:35:28 -05:00
Richard Vézina 3a9221a2b9 Reach fully some partially hit lines 2016-03-31 17:00:50 -04:00
Richard Vézina e0eb425223 Little improvement of tools.py 2016-03-31 16:25:55 -04:00
Richard Vézina f7ad31f066 Refactor TestAuth() with setUp() and add test case 2016-03-31 16:25:02 -04:00
Leonel Câmara d0f6ef4783 made stuff prettier
added myself to the contributor list :P
2016-03-31 00:29:28 +01:00
Richard Vézina 104d616cb9 Reorder test case and make inventory of what missing 2016-03-30 16:32:37 -04:00
Richard Vézina a8703270da PEP8 enhancement 2016-03-30 16:20:02 -04:00
Leonel Câmara ad57c3c613 test block extend, super, delimiters, etc. 2016-03-30 02:33:29 +01:00
Leonel Câmara 5c292640ba Complete coverage for IS_IN_SET
Removed unreachable code in IS_IN_SET
        if failures and self.theset:
            if self.multiple and (value is None or value == '')
It's impossible to have *failures* and have value be None or '' at the same time
2016-03-28 14:47:58 +01:00
Leonel Câmara 5cbf381a2c More test coverage for validators.py
Fixed a bug in IS_EMAIL throwing exceptions when asked to validate anything other than a string which was problematic for ANY_OF
Fixed a bug in ANY_OF.formatter where it was trying to format with a validator that didn't validate
2016-03-27 14:23:24 +01:00
mdipierro a0bbd7885a Merge branch 'master' of github.com:web2py/web2py 2016-03-26 17:56:23 -05:00
mdipierro c2ce90a1fe R-2.14.3 2016-03-26 17:55:52 -05:00
mdipierro 197b018534 fixed stupid.css again 2016-03-26 17:54:11 -05:00
Hardirc 9ac1e7188f tests inventory and reordering + PEP8 enhancement 2016-03-26 13:19:29 -04:00
Hardirc 58a8ba067c cache.py PEP8 enhancement 2016-03-26 13:11:27 -04:00
mdipierro 060aeff867 Merge pull request #1251 from BuhtigithuB/add-even-more-test-over-html-py
More coverage and some PEP8 enhencement to html.py
2016-03-26 00:00:43 -05:00
mdipierro d4572c5f38 Merge branch 'master' of github.com:web2py/web2py 2016-03-25 23:57:24 -05:00
mdipierro 46dc83a1bb synced to latest stupid.css 2016-03-25 23:57:13 -05:00
Hardirc f414356b67 More coverage and some PEP8 enhencement to html.py 2016-03-26 00:44:28 -04:00
mdipierro 460a017bab Merge pull request #1250 from Pierre-Thibault/strange_lines
Nonsense sys.exc_info parameter of traceback.format_exc calls.
2016-03-25 18:07:25 -05:00
mdipierro 0b966d7c0a Merge pull request #1249 from leonelcamara/patch-1
imageutils.py missing comma bug
2016-03-25 18:06:56 -05:00
Pierre Thibault d2d16d4081 I was forgetting line 200. 2016-03-25 15:54:01 -04:00
Pierre Thibault ebcf6e5671 Nonsense sys.exc_info parameter of traceback.format_exc calls. 2016-03-25 15:48:58 -04:00
Leonel Câmara 778cc3902b Update imageutils.py
missing comma
2016-03-25 12:45:33 +00:00
38 changed files with 1727 additions and 2048 deletions

No files matched your search

+2 -2
View File
@@ -1,5 +1,4 @@
## 2.14.1
## 2.14.1-4
- fixed two major security issues that caused the examples app to leak information
- new Auth(…,host_names=[…]) to prevent host header injection
@@ -45,6 +44,7 @@
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
session.connect(request, response, db = sessiondb)
Many thanks to Richard and Simone for their work and dedication.
## 2.13.*
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.14.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.14.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.14.2-stable+timestamp.2016.03.24.17.44.22
Version 2.14.4-stable+timestamp.2016.04.12.15.44.54
+5 -5
View File
@@ -19,8 +19,7 @@ def index():
def what():
import urllib
try:
images = XML(urllib.urlopen(
'http://www.web2py.com/poweredby/default/images').read())
images = XML(urllib.urlopen('http://www.web2py.com/poweredby/default/images').read())
except:
images = []
return response.render(images=images)
@@ -74,14 +73,15 @@ def license():
filename = os.path.join(request.env.gluon_parent, 'LICENSE')
return response.render(dict(license=MARKMIN(read_file(filename))))
def version():
if request.args(0) == 'raw':
return request.env.web2py_version
from gluon.fileutils import parse_version
(a, b, c, pre_release, build) = parse_version(request.env.web2py_version)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % (
a,b,c,build.year,build.month,build.day,
build.hour,build.minute,build.second,pre_release)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % \
(a, b, c, build.year, build.month, build.day, build.hour, build.minute, build.second, pre_release)
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def examples():
+6 -6
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
response.menu = [
(T('Home'), False, URL('default', 'index')),
(T('About'), False, URL('default', 'what')),
(T('Download'), False, URL('default', 'download')),
(T('Docs & Resources'), False, URL('default', 'documentation')),
(T('Support'), False, URL('default', 'support')),
(T('Contributors'), False, URL('default', 'who'))]
(T('Home'), request.controller == 'default' and request.function == 'index', URL('default', 'index')),
(T('About'), request.controller == 'default' and request.function == 'what', URL('default', 'what')),
(T('Download'), request.controller == 'default' and request.function == 'download', URL('default', 'download')),
(T('Docs & Resources'), request.controller == 'default' and request.function == 'documentation', URL('default', 'documentation')),
(T('Support'), request.controller == 'default' and request.function == 'support', URL('default', 'support')),
(T('Contributors'), request.controller == 'default' and request.function == 'who', URL('default', 'who'))]
#########################################################################
## Changes the menu active item
@@ -0,0 +1,69 @@
/* Gray the black as suggested by Anthony */
h1,h2,h3,h4,h5,h6 {color: rgb(35, 35, 35); text-transform:none}
.black {
color: rgb(35, 35, 35);
background-color: rgb(35, 35, 35);
}
/* Spacing between thead and tbody */
/* Ref: http://stackoverflow.com/questions/9258754/spacing-between-thead-and-tbody */
tbody:before {
content: "-";
display: block;
line-height: 1em;
color: transparent;
}
/* Improve buttons in download page */
th, td {padding: 0}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
/* Improve CODE() display though padding has no effect as some PRE are hardcoded somewhere can't find it */
/* padding of 10px should make it... */
pre {background-color: rgb(35, 35, 35)!important; border-radius:5px; color:white; padding: 10px}
/* Improve buttons in download page */
a.btn.btn180 {padding:10px; font-size:1.2em; width:200px}
.menu .web2py-menu-active a {
color: #26a69a;
}
.spaced-vertical {
margin: 0 0.5em 0.5em 0;
}
.btn:hover,
a.noeffect img:hover {
transition: scale .5s;
transform: scale(1.05);
}
.btn,
a.noeffect img {
transition: all .2s ease-in-out;
}
/* Lower saturation of color #26a69a - 20 points lower */
/* The below change to color #26a69a should come before other color change or they override all buttons background-color */
/* The color should maybe change at stupid.css level as it herited from there also in stupid.css it would be better
to define this color at one place actually color is defined all over the place */
a {color:#47a69d}
.btn, button, [type=button], [type=submit] {background-color:#47a69d}
.progress .determinate {background-color:#47a69d}
.progress .indeterminate {background-color:#47a69d}
a:not(.btn):not(.noeffect):hover {color:#47a69d}
a:not(.btn):not(.noeffect):after {background-color:#47a69d}
.tags > span {background-color:#47a69d}
.tags.dismissible > span.off:hover {background-color:#47a69d}
.aquamarine{background-color:#47a69d}
/* Lower the saturation of 20 points */
.green {background-color: #58cc65}
.yellow {background-color: #ffe333}
.red {background-color: #cc4229}
+31 -32
View File
@@ -6,7 +6,7 @@
/*** basic styles ***/
*, *:after, *:before {border:0; margin:0; padding:0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
html, body {max-width: 100vw !important; overflow-x: hidden !important}
html, body {max-width: 100vw; overflow-x: hidden}
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
p, li {margin-bottom:0.5em}
p {text-align:justify}
@@ -14,7 +14,7 @@ label, strong {font-weight:bold}
ul {list-style-type:none; padding-left:20px}
a {text-decoration:none; color:#26a69a; white-space:nowrap}
a:hover {cursor:pointer}
h1,h2,h3,h4,h5,h6{font-weight:strong; text-transform:uppercase}
h1,h2,h3,h4,h5,h6{font-weight:bold; text-transform:uppercase}
h1{font-size:4em; margin:1.0em 0 0.25em 0}
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
@@ -23,15 +23,13 @@ h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9}
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
thead tr {background-color:#f1f1f1}
tbody tr {border-bottom:2px solid #f1f1f1}
th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
td, th {padding: 5px; text-align: left; vertical-align:top}
thead th {vertical-align:bottom}
tbody th {vertical-align:top}
header, footer {with:100%}
@media (max-width:599px) {
@media all and (max-width:599px) {
h1{font-size:2em}
h2{font-size:1.8em}
h3{font-size:1.6em}
@@ -41,20 +39,20 @@ header, footer {with:100%}
}
/*** buttons ***/
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; line-height:2.4em; background-color:#26a69a; color:white}
.btn, button, [type=button], [type=submit] {padding:0.5em 1em; margin:0 0.5em 0.5em 0; display:inline-block; background-color:#26a69a; color:white}
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em; line-height:1.5em}
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
.btn.small, table .btn {padding:0.25em 0.5em; font-size:0.8em}
.btn.large {padding:1em 2em; font-size:1.2em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important}
.center {text-align:center !important; margin-left:auto; margin-right:auto}
.padded {padding:10px 20px}
.center {text-align:center; margin-left:auto; margin-right:auto}
.center>div {text-align:left}
.right {right:0; text-align:right}
.middle div {vertical-align:middle !important}
.bottom div {vertical-align:bottom !important}
.middle div {vertical-align:middle}
.bottom div {vertical-align:bottom}
.xscroll {overflow-x:scroll}
.yscroll {overflow-y:scroll}
.nowrap {white-space:nowrap; overflow-x:hidden}
@@ -71,14 +69,15 @@ textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outl
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd; width:100%;background-color:transparent}
input, textarea, select, button {font-size:12px}
input:not([type]):hover, input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
input:invalid, input.error {background:#cc1f00!important;color:white}
input:invalid, input.error {background:#cc1f00;color:white}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; vertical-align:top}
.container>.fill {width:100%; margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters {display:inline-block; padding: 0 20px 0 0; vertical-align:top}
.container>.fill{display: inline-block}
.container img, .container video {max-width:100%}
@media (min-width:800px) {
@media all and (min-width:800px) {
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
.quarter {width:25%; margin-right:-5px}
.half {width:50%; margin-right:-10px}
@@ -86,7 +85,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
.twothirds {width:66.66%; margin-right:-13.33px}
.threequarters {width:75%; margin-right:-15px}
}
@media (min-width:600px) and (max-width:799px) {
@media all and (min-width:600px) and (max-width:799px) {
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.threequarters.compressible {width:75%; margin-right:-15px}
@@ -95,7 +94,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
.twothirds {width:66.66%; margin-right:-13.33px}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
}
@media (max-width:599px) {
@media all and (max-width:599px) {
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
@@ -115,7 +114,7 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
display:block;
width:120%;
background-color:#acece6;
border-radius:0 !important;
border-radius:0;
background-clip:padding-box;
overflow:hidden;
}
@@ -193,17 +192,17 @@ input:invalid, input.error {background:#cc1f00!important;color:white}
.menu ul ul {top:0; left:80%; z-index:1100}
.menu li:hover > ul {visibility:visible; opacity:1}
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
.menu.dark ul {background:black; border:1px solid black}
.menu.dark ul {background:#111111; border:1px solid #111111}
.menu.dark ul a {color:white}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:#111111}
@media (max-width:599px) {
@media all and (max-width:599px) {
header .menu li, header .menu ul {width: 100%}
header .menu.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px}
}
@media (min-width:600px) {
@media all and (min-width:600px) {
.ham {display:none!important}
.burger.accordion * {max-height:1000px; overflow:visible}
}
@@ -268,8 +267,8 @@ a:not(.btn):not(.noeffect):after {
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:hover:before {
border-bottom:.6em solid black;
border-bottom:.6em solid black;
border-bottom:.6em solid #111111;
border-bottom:.6em solid #111111;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
@@ -288,7 +287,7 @@ a:not(.btn):not(.noeffect):after {
content:attr(data-tooltip);
left:0;
top:2px;
margin-left:-20;
margin-left:-20px;
margin-top:1.5em;
padding:5px 15px;
white-space:pre-wrap;
@@ -339,9 +338,6 @@ a:not(.btn):not(.noeffect):after {
transform: rotateY( 180deg );
}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
/**** tags ****/
.tags > span {
padding: 4px 9px;
@@ -350,10 +346,13 @@ a:not(.btn):not(.noeffect):after {
background-color: #26a69a;
border-radius: 5px;
font-size:12px;
margin: 5px 5px 5px 0 !important;
line-height: 32px;
margin: 2px 5px 2px 0;
display: inline-block;
}
.tags.dismissible > span:hover {opacity: 0.5}
.tags.dismissible > span:not(.off):after {content:" ✕"}
.tags > span.off {background-color: #ccc}
.tags.dismissible > span.off:hover {background-color:#26a69a}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f;color:white}.blue{background-color:#0074d9;color:white}.aqua{background-color:#7fdbff;color:#111111}.teal{background-color:#39cccc;color:white}.olive{background-color:#3d9970;color:white}.green{background-color:#2ecc40;color:white}.aquamarine{background-color:#26a69a;color:white}.lime{background-color:#01ff70;color:#111111}.yellow{background-color:#ffdc00;color:#111111}.orange{background-color:#ff851b;color:white}.red{background-color:#cc1f00;color:white}.fuchsia{background-color:#f012be;color:white}.pink{background-color:#ee6e73;color:white}.purple{background-color:#b10dc9;color:white}.maroon{background-color:#85144b;color:white}.white{background-color:#fff;color:#111111;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa;color:white}.silver{background-color:#f1f1f1;color:#111111}.black{background-color:#111111;color:white}.glass{background:rgba(255,255,255,0.5);color:#111111}
@@ -17,7 +17,7 @@
<tbody>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
@@ -28,7 +28,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
@@ -37,7 +37,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
@@ -48,7 +48,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
@@ -84,7 +84,7 @@
<p>Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.</p>
<p>It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.</p>
<p>web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.</p>
<a class="btn btn-small" href="{{=URL('license')}}">read more</a>
<a class="btn btn-small rounded" href="{{=URL('license')}}">read more</a>
<h3>Artwork</h3>
<center>
@@ -3,22 +3,22 @@
<div class="container">
<div class="twothirds">
<div class="padded">
<h3>web2py<sup>TM</sup> Web Framework</h3>
<h3><img src="{{=URL('static/images', 'web2py_logo.png')}}"> Web Framework</h3>
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
<table width="100%">
<tr>
<td>
<a href="http://web2py.com/book">
<a class="noeffect" href="http://web2py.com/book">
<img src="{{=URL('static','images/book-5th.png')}}" />
</a>
</td>
<td>
<a href="https://vimeo.com/album/3016728">
<a class="noeffect" href="https://vimeo.com/album/3016728">
<img src="{{=URL('static','images/videos.png')}}" />
</a>
</td>
<td>
<a href="http://link.packtpub.com/SUlnrN">
<a class="noeffect" href="http://link.packtpub.com/SUlnrN">
<img src="{{=URL('static','images/book-recipes.png')}}" />
</a>
</td>
@@ -29,8 +29,8 @@
</div>
<div class="third">
<div class="padded center">
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img src="{{=URL('static','images/infoworld2012.jpeg')}}">
<a class="noeffect" href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img class="spaced-vertical" src="{{=URL('static','images/infoworld2012.jpeg')}}">
</a>
<a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now
@@ -32,7 +32,6 @@
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li>
<li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li>
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li>
@@ -82,6 +82,7 @@
</li><li>Keith Yang (openid)
</li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins)
</li><li>Kyle Smith (javascript)
</li><li><a href="https://github.com/leonelcamara">Leonel Câmara</a>
</li><li><a href="http://blog.donews.com/limodou/">Limodou</a> (winservice)
</li><li><a href="https://github.com/lucasdavila">Lucas D'Ávila</a>
</li><li>Marc Abramowitz (tests and travis continuous integration)
+6 -11
View File
@@ -1,22 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/examples.css')}}" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
th, td {color: black!important}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
</style>
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
{{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
@@ -29,7 +24,7 @@
<header class="black padded">
<div class="container middle max900">
<div class="fill middle">
<label class="ham padded fa fa-bars" for="menu"></label>
<label class="ham" for="menu"><i class="fa fa-bars padded"></i></label>
<div class="burger accordion">
<input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}}
+7 -6
View File
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
#########################################################################
## This is a sample controller
## - index is the default action of any application
## - user is required for authentication and authorization
## - download is for downloading files uploaded in the db (does streaming)
#########################################################################
# -------------------------------------------------------------------------
# This is a sample controller
# - index is the default action of any application
# - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming)
# -------------------------------------------------------------------------
def index():
"""
+82 -50
View File
@@ -1,60 +1,84 @@
# -*- coding: utf-8 -*-
#########################################################################
## This scaffolding model makes your app work on Google App Engine too
## File is released under public domain and you can use without limitations
#########################################################################
# -------------------------------------------------------------------------
# This scaffolding model makes your app work on Google App Engine too
# File is released under public domain and you can use without limitations
# -------------------------------------------------------------------------
if request.global_settings.web2py_version < "2.14.1":
raise HTTP(500, "Requires web2py 2.13.3 or newer")
## if SSL/HTTPS is properly configured and you want all HTTP requests to
## be redirected to HTTPS, uncomment the line below:
# -------------------------------------------------------------------------
# if SSL/HTTPS is properly configured and you want all HTTP requests to
# be redirected to HTTPS, uncomment the line below:
# -------------------------------------------------------------------------
# request.requires_https()
## app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig
## once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
# once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
myconf = AppConfig(reload=True)
if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB
# ---------------------------------------------------------------------
# if NOT running on Google App Engine use SQLite or other DB
# ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'),
pool_size=myconf.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'),
check_reserved=['all'])
else:
## connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
# connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
db = DAL('google:datastore+ndb')
## store sessions and tickets there
# ---------------------------------------------------------------------
# store sessions and tickets there
# ---------------------------------------------------------------------
session.connect(request, response, db=db)
## or store session in Memcache, Redis, etc.
## from gluon.contrib.memdb import MEMDB
## from google.appengine.api.memcache import Client
## session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
# or store session in Memcache, Redis, etc.
# from gluon.contrib.memdb import MEMDB
# from google.appengine.api.memcache import Client
# session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
## by default give a view/generic.extension to all actions from localhost
## none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
# by default give a view/generic.extension to all actions from localhost
# none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
response.generic_patterns = ['*'] if request.is_local else []
## choose a style for forms
# -------------------------------------------------------------------------
# choose a style for forms
# -------------------------------------------------------------------------
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.get('forms.separator') or ''
## (optional) optimize handling of static files
# -------------------------------------------------------------------------
# (optional) optimize handling of static files
# -------------------------------------------------------------------------
# response.optimize_css = 'concat,minify,inline'
# response.optimize_js = 'concat,minify,inline'
## (optional) static assets folder versioning
# -------------------------------------------------------------------------
# (optional) static assets folder versioning
# -------------------------------------------------------------------------
# response.static_version = '0.0.0'
#########################################################################
## Here is sample code if you need for
## - email capabilities
## - authentication (registration, login, logout, ... )
## - authorization (role based authorization)
## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
## - old style crud actions
## (more options discussed in gluon/tools.py)
#########################################################################
# -------------------------------------------------------------------------
# Here is sample code if you need for
# - email capabilities
# - authentication (registration, login, logout, ... )
# - authorization (role based authorization)
# - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
# - old style crud actions
# (more options discussed in gluon/tools.py)
# -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager
@@ -63,10 +87,14 @@ auth = Auth(db, host_names=myconf.get('host.names'))
service = Service()
plugins = PluginManager()
## create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
# create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
auth.define_tables(username=False, signature=False)
## configure email
# -------------------------------------------------------------------------
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender')
@@ -74,27 +102,31 @@ mail.settings.login = myconf.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False
## configure auth policy
# -------------------------------------------------------------------------
# configure auth policy
# -------------------------------------------------------------------------
auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
#########################################################################
## Define your tables below (or better in another model file) for example
##
## >>> db.define_table('mytable',Field('myfield','string'))
##
## Fields can be 'string','text','password','integer','double','boolean'
## 'date','time','datetime','blob','upload', 'reference TABLENAME'
## There is an implicit 'id integer autoincrement' field
## Consult manual for more options, validators, etc.
##
## More API examples for controllers:
##
## >>> db.mytable.insert(myfield='value')
## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL)
## >>> for row in rows: print row.id, row.myfield
#########################################################################
# -------------------------------------------------------------------------
# Define your tables below (or better in another model file) for example
#
# >>> db.define_table('mytable', Field('myfield', 'string'))
#
# Fields can be 'string','text','password','integer','double','boolean'
# 'date','time','datetime','blob','upload', 'reference TABLENAME'
# There is an implicit 'id integer autoincrement' field
# Consult manual for more options, validators, etc.
#
# More API examples for controllers:
#
# >>> db.mytable.insert(myfield='value')
# >>> rows = db(db.mytable.myfield == 'value').select(db.mytable.ALL)
# >>> for row in rows: print row.id, row.myfield
# -------------------------------------------------------------------------
## after defining tables, uncomment below to enable auditing
# -------------------------------------------------------------------------
# after defining tables, uncomment below to enable auditing
# -------------------------------------------------------------------------
# auth.enable_record_versioning(db)
+26 -13
View File
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
#########################################################################
## Customize your APP title, subtitle and menus here
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# Customize your APP title, subtitle and menus here
# ----------------------------------------------------------------------------------------------------------------------
response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'),
_class="navbar-brand", _href="http://www.web2py.com/",
@@ -11,18 +11,22 @@ response.logo = A(B('web',SPAN(2),'py'),XML('&trade;&nbsp;'),
response.title = request.application.replace('_', ' ').title()
response.subtitle = ''
## read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
response.meta.author = myconf.get('app.author')
response.meta.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
## your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
response.google_analytics_id = None
#########################################################################
## this is the main application menu add/remove items as required
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# this is the main application menu add/remove items as required
# ----------------------------------------------------------------------------------------------------------------------
response.menu = [
(T('Home'), False, URL('default', 'index'), [])
@@ -30,15 +34,20 @@ response.menu = [
DEVELOPMENT_MENU = True
#########################################################################
## provide shortcuts for development. remove in production
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# provide shortcuts for development. remove in production
# ----------------------------------------------------------------------------------------------------------------------
def _():
# ------------------------------------------------------------------------------------------------------------------
# shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application
ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [
@@ -133,6 +142,10 @@ def _():
'http://webchat.freenode.net/?channels=web2py'),
]),
]
if DEVELOPMENT_MENU: _()
if "auth" in locals(): auth.wikimenu()
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()
+14 -11
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------------------------------------------------
# This is an app-specific example router
#
# This simple router is used for setting languages from app/languages directory
@@ -8,31 +9,33 @@
# a default_language
#
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
#-------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py
# (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interfase)
# 2. rename this APP/routes.example.py to APP/routes.py (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interface)
#
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath
from languages import read_possible_languages
possible_languages = read_possible_languages(abspath('applications', app))
#NOTE! app - is an application based router's parameter with name of an
# application. E.g.'welcome'
# ----------------------------------------------------------------------------------------------------------------------
# NOTE! app - is an application based router's parameter with name of an application. E.g.'welcome'
# ----------------------------------------------------------------------------------------------------------------------
routers = {
app: dict(
default_language=possible_languages['default'][0],
languages = [lang for lang in possible_languages
if lang != 'default']
languages=[lang for lang in possible_languages if lang != 'default']
)
}
#NOTE! To change language in your application using these rules add this line
#in one of your models files:
# ----------------------------------------------------------------------------------------------------------------------
# NOTE! To change language in your application using these rules add this line in one of your models files:
# ----------------------------------------------------------------------------------------------------------------------
# if request.uri_language: T.force(request.uri_language)
+3 -3
View File
@@ -148,7 +148,7 @@ def app_compile(app, request, skip_failed_views=False):
failed_views = compile_application(folder, skip_failed_views)
return failed_views
except (Exception, RestrictedError):
tb = traceback.format_exc(sys.exc_info)
tb = traceback.format_exc()
remove_compiled_application(folder)
return tb
@@ -167,7 +167,7 @@ def app_create(app, request, force=False, key=None, info=False):
os.mkdir(path)
except:
if info:
return False, traceback.format_exc(sys.exc_info)
return False, traceback.format_exc()
else:
return False
elif not force:
@@ -197,7 +197,7 @@ def app_create(app, request, force=False, key=None, info=False):
except:
rmtree(path)
if info:
return False, traceback.format_exc(sys.exc_info)
return False, traceback.format_exc()
else:
return False
+17 -30
View File
@@ -54,6 +54,7 @@ try:
except ImportError:
HAVE_PSUTIL = False
def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%)
old_mem = psutil.virtual_memory().percent
@@ -66,7 +67,8 @@ def remove_oldest_entries(storage, percentage=90):
# comute used memory again
new_mem = psutil.virtual_memory().percent
# if the used memory did not decrease stop
if new_mem >= old_mem: break
if new_mem >= old_mem:
break
# net new measurement for memory usage and loop
old_mem = new_mem
@@ -78,6 +80,7 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object):
"""
Abstract class for cache implementations.
@@ -182,13 +185,14 @@ class CacheInRam(CacheAbstract):
self.request = request
self.storage = OrderedDict() if HAVE_PSUTIL else {}
self.app = request.application if request else ''
def initialize(self):
if self.initialized:
return
else:
self.initialized = True
self.locker.acquire()
if not self.app in self.meta_storage:
if self.app not in self.meta_storage:
self.storage = self.meta_storage[self.app] = \
OrderedDict() if HAVE_PSUTIL else {}
self.stats[self.app] = {'hit_total': 0, 'misses': 0}
@@ -205,7 +209,7 @@ class CacheInRam(CacheAbstract):
else:
self._clear(storage, regex)
if not self.app in self.stats:
if self.app not in self.stats:
self.stats[self.app] = {'hit_total': 0, 'misses': 0}
self.locker.release()
@@ -251,7 +255,7 @@ class CacheInRam(CacheAbstract):
self.locker.acquire()
self.storage[key] = (now, value)
self.stats[self.app]['misses'] += 1
if HAVE_PSUTIL and self.max_ram_utilization!=None and random.random()<0.10:
if HAVE_PSUTIL and self.max_ram_utilization is not None and random.random() < 0.10:
remove_oldest_entries(self.storage, percentage=self.max_ram_utilization)
self.locker.release()
return value
@@ -292,14 +296,15 @@ class CacheOnDisk(CacheAbstract):
self.folder = folder
self.key_filter_in = lambda key: key
self.key_filter_out = lambda key: key
self.file_lock_time_wait = file_lock_time_wait # How long we should wait before retrying to lock a file held by another process
self.file_lock_time_wait = file_lock_time_wait
# How long we should wait before retrying to lock a file held by another process
# We still need a mutex for each file as portalocker only blocks other processes
self.file_locks = defaultdict(thread.allocate_lock)
# Make sure we use valid filenames.
if sys.platform == "win32":
import base64
def key_filter_in_windows(key):
"""
Windows doesn't allow \ / : * ? "< > | in filenames.
@@ -316,7 +321,6 @@ class CacheOnDisk(CacheAbstract):
self.key_filter_in = key_filter_in_windows
self.key_filter_out = key_filter_out_windows
def wait_portalock(self, val_file):
"""
Wait for the process file lock.
@@ -328,15 +332,12 @@ class CacheOnDisk(CacheAbstract):
except:
time.sleep(self.file_lock_time_wait)
def acquire(self, key):
self.file_locks[key].acquire()
def release(self, key):
self.file_locks[key].release()
def __setitem__(self, key, value):
key = self.key_filter_in(key)
val_file = recfile.open(key, mode='wb', path=self.folder)
@@ -344,7 +345,6 @@ class CacheOnDisk(CacheAbstract):
pickle.dump(value, val_file, pickle.HIGHEST_PROTOCOL)
val_file.close()
def __getitem__(self, key):
key = self.key_filter_in(key)
try:
@@ -357,12 +357,10 @@ class CacheOnDisk(CacheAbstract):
val_file.close()
return value
def __contains__(self, key):
key = self.key_filter_in(key)
return (key in self.file_locks) or recfile.exists(key, path=self.folder)
def __delitem__(self, key):
key = self.key_filter_in(key)
try:
@@ -370,13 +368,11 @@ class CacheOnDisk(CacheAbstract):
except IOError:
raise KeyError
def __iter__(self):
for dirpath, dirnames, filenames in os.walk(self.folder):
for filename in filenames:
yield self.key_filter_out(filename)
def safe_apply(self, key, function, default_value=None):
"""
Safely apply a function to the value of a key in storage and set
@@ -403,25 +399,21 @@ class CacheOnDisk(CacheAbstract):
val_file.close()
return new_value
def keys(self):
return list(self.__iter__())
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __init__(self, request=None, folder=None):
self.initialized = False
self.request = request
self.folder = folder
self.storage = None
def initialize(self):
if self.initialized:
return
@@ -440,7 +432,6 @@ class CacheOnDisk(CacheAbstract):
self.storage = CacheOnDisk.PersistentStorage(folder)
def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE):
self.initialize()
@@ -487,7 +478,6 @@ class CacheOnDisk(CacheAbstract):
self.storage.release(key)
return value
def clear(self, regex=None):
self.initialize()
storage = self.storage
@@ -504,7 +494,6 @@ class CacheOnDisk(CacheAbstract):
pass
storage.release(key)
def increment(self, key, value=1):
self.initialize()
self.storage.acquire(key)
@@ -513,7 +502,6 @@ class CacheOnDisk(CacheAbstract):
return value
class CacheAction(object):
def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__
@@ -602,6 +590,7 @@ class Cache(object):
"""
from gluon import current
from gluon.http import HTTP
def wrap(func):
def wrapped_f():
if current.request.env.request_method != 'GET':
@@ -621,7 +610,8 @@ class Cache(object):
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
if not session_ and public_:
cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)
).strftime('%a, %d %b %Y %H:%M:%S GMT')
else:
cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
@@ -675,11 +665,9 @@ class Cache(object):
if str(status)[0] in '123':
send_headers = True
if send_headers:
headers = {
'Pragma' : None,
headers = {'Pragma': None,
'Expires': expires,
'Cache-Control' : cache_control
}
'Cache-Control': cache_control}
current.response.headers.update(headers)
if cache_model and not send_headers:
# we cached already the value, but the status is not valid
@@ -740,8 +728,7 @@ class Cache(object):
allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
it will add prefix to all the cache keys used.
"""
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix:\
cache_model(prefix + key, f, time_expire)
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix: cache_model(prefix + key, f, time_expire)
def lazy_cache(key=None, time_expire=None, cache_model='ram'):
+1 -1
View File
@@ -27,7 +27,7 @@ from gluon import current
class RESIZE(object):
def __init__(self, nx=160, ny=80, quality=100, padding = False
def __init__(self, nx=160, ny=80, quality=100, padding = False,
error_message=' image resize'):
(self.nx, self.ny, self.quality, self.error_message, self.padding) = (
nx, ny, quality, error_message, padding)
+49 -17
View File
@@ -7,6 +7,7 @@ import re
import urllib
from cgi import escape
from string import maketrans
try:
from ast import parse as ast_parse
import ast
@@ -542,7 +543,9 @@ regex_URL=re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\
regex_env2 = re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('(' + META + '|' + DISABLED_META + '|````)')
regex_dd = re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
regex_code = re.compile(
'(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',
re.S)
regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_del = re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
@@ -554,7 +557,9 @@ regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)', re.M)
regex_link = re.compile(r'(' + LINK + r')|\[\[(?P<s>.+?)\]\]', re.S)
regex_link_level2 = re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$', re.S)
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
regex_media_level2 = re.compile(
r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',
re.S)
regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
@@ -562,9 +567,11 @@ ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x1
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':", b)
def safe_eval(node_or_string, env):
"""
Safely evaluate an expression node or a string containing a Python
@@ -578,6 +585,7 @@ def safe_eval(node_or_string, env):
node_or_string = ast_parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
def _convert(node):
if isinstance(node, ast.Str):
return node.s
@@ -606,24 +614,30 @@ def safe_eval(node_or_string, env):
else:
return left - right
raise ValueError('malformed string')
return _convert(node_or_string)
def markmin_escape(text):
""" insert \\ before markmin control characters: '`:*~[]{}@$ """
return regex_markmin_escape.sub(
lambda m: '\\' + m.group(0).replace('\\', '\\\\'), text)
def replace_autolinks(text, autolinks):
return regex_auto.sub(lambda m: autolinks(m.group('k')), text)
def replace_at_urls(text, url):
# this is experimental @{function/args}
def u1(match, url=url):
a, c, f, args = match.group('a', 'c', 'f', 'args')
return url(a=a or None, c=c or None, f=f or None,
args=(args or '').split('/'), scheme=True, host=True)
return regex_URL.sub(u1, text)
def replace_components(text, env):
# not perfect but acceptable
def u2(match, env=env):
@@ -639,16 +653,18 @@ def replace_components(text,env):
except Exception, e:
f = 'ERROR: %s' % e
return str(f)
text = regex_env2.sub(u2, text)
return text
def autolinks_simple(url):
"""
it automatically converts the url to link,
image, video or audio tag
"""
u_url = url.lower()
if '@' in url and not '://' in url:
if '@' in url and '://' not in url:
return '<a href="mailto:%s">%s</a>' % (url, url)
elif u_url.endswith(('.jpg', '.jpeg', '.gif', '.png')):
return '<img src="%s" controls />' % url
@@ -658,6 +674,7 @@ def autolinks_simple(url):
return '<audio src="%s" controls></audio>' % url
return '<a href="%s">%s</a>' % (url, url)
def protolinks_simple(proto, url):
"""
it converts url to html-string using appropriate proto-prefix:
@@ -675,9 +692,11 @@ def protolinks_simple(proto, url):
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />' % url
return proto + ':' + url
def email_simple(email):
return '<a href="mailto:%s">%s</a>' % (email, email)
def render(text,
extra={},
allowed={},
@@ -925,8 +944,10 @@ def render(text,
>>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
'<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>'
"""
if autolinks=="default": autolinks = autolinks_simple
if protolinks=="default": protolinks = protolinks_simple
if autolinks == "default":
autolinks = autolinks_simple
if protolinks == "default":
protolinks = protolinks_simple
pp = '\n' if pretty_print else ''
if isinstance(text, unicode):
text = text.encode('utf8')
@@ -945,6 +966,7 @@ def render(text,
# store them into segments they will be treated as code
#############################################################
segments = []
def mark_code(m):
g = m.group(0)
if g in (META, DISABLED_META):
@@ -956,10 +978,12 @@ def render(text,
else:
c = m.group('c') or ''
p = m.group('p') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
if 'code' in allowed and c not in allowed['code']:
c = ''
code = m.group('t').replace('!`!', '`')
segments.append((code, c, p, m.group(0)))
return META
text = regex_code.sub(mark_code, text)
#############################################################
@@ -967,10 +991,12 @@ def render(text,
# store them into links they will be treated as link
#############################################################
links = []
def mark_link(m):
links.append(None if m.group() == LINK
else m.group('s'))
return LINK
text = regex_link.sub(mark_link, text)
text = escape(text)
@@ -1264,9 +1290,11 @@ def render(text,
ltags = []
tlev = []
lev = 0
if br and mtag == 'p': out.append(br)
if br and mtag == 'p':
out.append(br)
if mtag != 'q' and s != META:
if pend: etags=[pend]
if pend:
etags = [pend]
out.append(pbeg)
mtag = 'p'
else:
@@ -1336,7 +1364,7 @@ def render(text,
t = t or ''
a = escape(a) if a else ''
if k:
if '#' in k and not ':' in k.split('#')[0]:
if '#' in k and ':' not in k.split('#')[0]:
# wikipage, not external url
k = k.replace('#', '#' + id_prefix)
k = escape(k)
@@ -1358,7 +1386,7 @@ def render(text,
parts = text.split(LINK)
text = parts[0]
for i, s in enumerate(links):
if s == None:
if s is None:
html = LINK
else:
html = regex_media_level2.sub(sub_media, s)
@@ -1374,19 +1402,20 @@ def render(text,
#############################################################
def expand_meta(m):
code, b, p, s = segments.pop(0)
if code==None or m.group() == DISABLED_META:
if code is None or m.group() == DISABLED_META:
return escape(s)
if b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if p:
return str(extra[b](code, p))
else:
return str(extra[b](code))
elif b == 'cite':
return '['+','.join('<a href="#%s" class="%s">%s</a>' \
% (id_prefix+d,b,d) \
for d in escape(code).split(','))+']'
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
elif b == 'latex':
return LATEX % urllib.quote(code)
elif b in html_colors:
@@ -1407,6 +1436,7 @@ def render(text,
if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text)
if environment:
@@ -1423,10 +1453,12 @@ def markmin2html(text, extra={}, allowed={}, sep='p',
class_prefix=class_prefix, id_prefix=id_prefix,
pretty_print=pretty_print)
def run_doctests():
import doctest
doctest.testmod()
if __name__ == '__main__':
import sys
import doctest
@@ -1467,6 +1499,7 @@ if __name__ == '__main__':
body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']:
from timeit import Timer
loops = 1000
ts = Timer("markmin2html(__doc__)", "from markmin2html import markmin2html")
print 'timeit "markmin2html(__doc__)":'
@@ -1502,4 +1535,3 @@ if __name__ == '__main__':
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)"
print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
run_doctests()
+42 -21
View File
@@ -39,16 +39,20 @@ regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]')
regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)', re.M)
regex_commas = re.compile('[ ]+(?P<t>[,;\.])')
regex_noindent = re.compile('\n\n(?P<t>[a-z])')
# regex_quote_left = re.compile('"(?=\w)')
# regex_quote_right = re.compile('(?=\w\.)"')
def latex_escape(text, pound=True):
text = text.replace('\\', '{\\textbackslash}')
for c in '^_&$%{}': text=text.replace(c,'\\'+c)
for c in '^_&$%{}':
text = text.replace(c, '\\' + c)
text = text.replace('\\{\\textbackslash\\}', '{\\textbackslash}')
if pound: text = text.replace('#', '\\#')
return text
def render(text,
extra={},
allowed={},
@@ -65,26 +69,29 @@ def render(text,
text = regex_newlines.sub('\n', text)
while True:
item = regex_code.search(text, i)
if not item: break
if not item:
break
if item.group() == META:
segments.append((None, None))
text = text[:item.start()] + META + text[item.end():]
else:
c = item.group('c') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
if 'code' in allowed and c not in allowed['code']:
c = ''
code = item.group('t').replace('!`!', '`')
segments.append((code, c))
text = text[:item.start()] + META + text[item.end():]
i = item.start() + 3
#############################################################
# do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces
#############################################################
title = regex_title.search(text)
if not title: title='Title'
else: title=title.group('t')
if not title:
title = 'Title'
else:
title = title.group('t')
text = latex_escape(text, pound=False)
@@ -106,9 +113,11 @@ def render(text,
#############################################################
while True:
item = regex_table.search(text)
if not item: break
if not item:
break
c = item.group('c') or ''
if 'table' in allowed and not c in allowed['table']: c = ''
if 'table' in allowed and c not in allowed['table']:
c = ''
content = item.group('t')
if ' | ' in content:
rows = content.replace('\n', '\\\\\n').replace(' | ', ' & ')
@@ -116,7 +125,8 @@ def render(text,
cols = row0.count(' & ') + 1
cal = '{' + ''.join('l' for j in range(cols)) + '}'
tabular = '\\begin{center}\n{\\begin{tabular}' + cal + '\\hline\n' + row0 + '\\\\ \\hline\n' + row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}'
if row2.count('\n')>20: tabular='\\newpage\n'+tabular
if row2.count('\n') > 20:
tabular = '\\newpage\n' + tabular
text = text[:item.start()] + tabular + text[item.end():]
else:
text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():]
@@ -127,8 +137,10 @@ def render(text,
def sub(x):
f = image_mapper(x.group('k'))
if not f: return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f)
if not f:
return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % f
text = regex_image_width.sub(sub, text)
text = regex_image.sub(sub, text)
@@ -140,7 +152,8 @@ def render(text,
regex = re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
while True:
match = regex.search(text)
if not match: break
if not match:
break
text = text[:match.start()] + text[match.start() + 1:]
# text = regex_quote_left.sub('``',text)
# text = regex_quote_right.sub("''",text)
@@ -157,7 +170,7 @@ def render(text,
text = parts[0]
authors = []
for i, (code, b) in enumerate(segments):
if code==None:
if code is None:
html = META
else:
if b == 'hidden':
@@ -185,19 +198,25 @@ def render(text,
key = b[6:]
html = '\\begin{%s}%s\\end{%s}' % (key, code, key)
elif b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = extra[b](code)
elif code[:1] == '\n' or code[:-1] == '\n':
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if code.startswith('<') or code.startswith('{{') or code.startswith('http'):
html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code
else:
html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code
else:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = '{\\ft %s}' % latex_escape(code)
try:
text = text + html + parts[i + 1]
@@ -207,6 +226,7 @@ def render(text,
text = text.replace(' ~\\cite', '~\\cite')
return text, title, authors
WRAPPER = """
\\documentclass[12pt]{article}
\\usepackage{hyperref}
@@ -239,12 +259,14 @@ WRAPPER = """
\\end{document}
"""
def markmin2latex(data, image_mapper=lambda x: x, extra={},
wrapper=WRAPPER):
body, title, authors = render(data, extra=extra, image_mapper=image_mapper)
author = '\n\\and\n'.join(a.replace('\n', '\\\\\n\\footnotesize ') for a in authors)
return wrapper % dict(title=title, author=author, body=body)
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("-i", "--info", dest="info",
@@ -261,6 +283,7 @@ if __name__ == '__main__':
(options, args) = parser.parse_args()
if options.info:
import markmin2html
markmin2latex(markmin2html.__doc__)
elif options.test:
doctest.testmod()
@@ -287,5 +310,3 @@ if __name__ == '__main__':
wrapper=wrapper,
chapters=options.chapters)
print output
+3 -2
View File
@@ -13,9 +13,10 @@ from markmin2latex import markmin2latex
__all__ = ['markmin2pdf']
def removeall(path):
def removeall(path):
ERROR_STR = """Error removing %(path)s, %(error)s """
def rmgeneric(path, __func__):
try:
__func__(path)
@@ -107,6 +108,7 @@ if __name__ == '__main__':
import sys
import doctest
import markmin2html
if sys.argv[1:2] == ['-h']:
data, warnings, errors = markmin2pdf(markmin2html.__doc__)
if errors:
@@ -127,4 +129,3 @@ if __name__ == '__main__':
print data
else:
doctest.testmod()
+3 -4
View File
@@ -308,7 +308,7 @@ def URL(a=None,
else:
function = f
# if the url gets a static resource, don't force extention
# if the url gets a static resource, don't force extension
if controller == 'static':
extension = None
# add static version to url
@@ -955,7 +955,6 @@ class DIV(XmlComponent):
# get the xml for the inner components
co = join([xmlescape(component) for component in
self.components])
return (fa, co)
def xml(self):
@@ -1059,7 +1058,7 @@ class DIV(XmlComponent):
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
>>> print a
>>> print a # We should .xml() here instead of print
<div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
"replace" can be a callable, which will be passed the original element and
@@ -2805,7 +2804,7 @@ class MARKMIN(XmlComponent):
self.extra = extra or {}
self.allowed = allowed or {}
self.sep = sep
self.url = URL if url == True else url
self.url = URL if url is True else url
self.environment = environment
self.latex = latex
self.autolinks = autolinks
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -181,7 +181,7 @@ class JobGraph(object):
task_child=task_child,
job_name=self.job_name)
def validate(self, job_name):
def validate(self, job_name=None):
"""Validates if all tasks job_name can be completed, i.e. there
are no mutual dependencies among tasks.
Commits at the end if successfull, or it rollbacks the entire
+28 -13
View File
@@ -1897,33 +1897,39 @@ class SQLFORM(FORM):
else:
field_type = field.type
operators = SELECT(*[OPTION(T(option), _value=option) for option in options], _class='form-control')
operators = SELECT(*[OPTION(T(option), _value=option) for option in options],
_class='form-control')
_id = "%s_%s" % (value_id, name)
if field_type in ['boolean', 'double', 'time', 'integer']:
widget_ = SQLFORM.widgets[field_type]
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control')
elif field_type == 'date':
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
widget_ = SQLFORM.widgets.date
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control',
**iso_format)
elif field_type == 'datetime':
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif (field_type.startswith('reference ') or
field_type.startswith('list:reference ')) and \
hasattr(field.requires, 'options') or \
hasattr(field.requires, 'options'):
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control',
**iso_format)
elif hasattr(field.requires, 'options'):
value_input = SELECT(
*[OPTION(v, _value=k)
for k, v in field.requires.options()],
_class='form-control',
**dict(_id=_id))
elif field_type.startswith('reference ') or \
field_type.startswith('list:integer') or \
field_type.startswith('list:reference '):
elif (field_type.startswith('integer') or
field_type.startswith('reference ') or
field_type.startswith('list:integer') or
field_type.startswith('list:reference ')):
widget_ = SQLFORM.widgets.integer
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
value_input = widget_.widget(
field, field.default, _id=_id,
_class=widget_._class + ' form-control')
else:
value_input = INPUT(
_type='text', _id=_id,
@@ -3037,7 +3043,16 @@ class SQLFORM(FORM):
query = query & constraints[table._tablename]
if isinstance(links, dict):
links = links.get(table._tablename, [])
for key in 'columns,orderby,searchable,sortable,paginate,deletable,editable,details,selectable,create,fields'.split(','):
for key in ('fields', 'field_id', 'left', 'headers', 'orderby', 'groupby', 'searchable',
'sortable', 'paginate', 'deletable', 'editable', 'details', 'selectable',
'create', 'csv', 'links', 'links_in_grid', 'upload', 'maxtextlengths',
'maxtextlength', 'onvalidation', 'onfailure', 'oncreate', 'onupdate',
'ondelete', 'sorter_icons', 'ui', 'showbuttontext', '_class', 'formname',
'search_widget', 'advanced_search', 'ignore_rw', 'formstyle', 'exportclasses',
'formargs', 'createargs', 'editargs', 'viewargs', 'selectable_submit_button',
'buttons_placement', 'links_placement', 'noconfirm', 'cache_count', 'client_side_delete',
'ignore_common_filters', 'auto_pagination', 'use_cursor'
):
if isinstance(kwargs.get(key, None), dict):
if table._tablename in kwargs[key]:
kwargs[key] = kwargs[key][table._tablename]
+1
View File
@@ -21,6 +21,7 @@ from test_contribs import *
from test_web import *
from test_dal import *
from test_tools import *
from test_scheduler import *
if sys.version[:3] == '2.7':
from test_old_doctests import *
+13 -7
View File
@@ -37,10 +37,11 @@ def tearDownModule():
pass
class TestCache(unittest.TestCase):
def testCacheInRam(self):
# TODO: test_CacheAbstract(self):
def test_CacheInRam(self):
# defaults to mode='http'
cache = CacheInRam()
@@ -67,8 +68,7 @@ class TestCache(unittest.TestCase):
cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1)
def testCacheOnDisk(self):
def test_CacheOnDisk(self):
# defaults to mode='http'
s = Storage({'application': 'admin',
@@ -97,7 +97,13 @@ class TestCache(unittest.TestCase):
cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1)
def testCacheWithPrefix(self):
# TODO: def test_CacheAction(self):
# TODO: def test_Cache(self):
# TODO: def test_lazy_cache(self):
def test_CacheWithPrefix(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)
@@ -106,7 +112,7 @@ class TestCache(unittest.TestCase):
self.assertEqual(prefix('a', lambda: 2, 100), 1)
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
def testRegex(self):
def test_Regex(self):
cache = CacheInRam()
self.assertEqual(cache('a1', lambda: 1, 0), 1)
self.assertEqual(cache('a2', lambda: 2, 100), 2)
@@ -114,7 +120,7 @@ class TestCache(unittest.TestCase):
self.assertEqual(cache('a1', lambda: 2, 0), 2)
self.assertEqual(cache('a2', lambda: 3, 100), 3)
def testDALcache(self):
def test_DALcache(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)
+2
View File
@@ -106,6 +106,7 @@ class TestDALAdapters(unittest.TestCase):
def test_mysql(self):
if os.environ.get('APPVEYOR'):
return
if os.environ.get('TRAVIS'):
os.environ["DB"] = "mysql://root:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
@@ -113,6 +114,7 @@ class TestDALAdapters(unittest.TestCase):
def test_pg8000(self):
if os.environ.get('APPVEYOR'):
return
if os.environ.get('TRAVIS'):
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
+81 -9
View File
@@ -14,11 +14,14 @@ from html import *
from html import verifyURL
from html import truncate_string
from storage import Storage
from html import XML_pickle, XML_unpickle
from html import TAG_pickler, TAG_unpickler
class TestBareHelpers(unittest.TestCase):
# TODO: def test_xmlescape(self):
# xmlescape() = covered by other tests
# TODO: def test_call_as_list(self):
def test_truncate_string(self):
@@ -26,6 +29,8 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(truncate_string('Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
length=30), 'Lorem ipsum dolor sit amet,...')
self.assertEqual(truncate_string('Short text shorter than the length parameter.', length=100),
'Short text shorter than the length parameter.')
# French text
self.assertEqual(truncate_string('Un texte en français avec des accents et des caractères bizarre.', length=30),
'Un texte en français avec d...')
@@ -169,9 +174,9 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
# TODO: def test_XML_unpickle(self):
# TODO: def test_XML_pickle(self):
def test_XML_pickle_unpickle(self):
# weird test
self.assertEqual(XML_unpickle(XML_pickle('data to be pickle')[1][0]), 'data to be pickle')
def test_DIV(self):
# Empty DIV()
@@ -199,7 +204,31 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(a.xml(), '<div class="abc"><span>a</span><div>b</div></div>')
self.assertEqual([el.xml() for el in s.siblings()], ['<div>b</div>'])
self.assertEqual(s.sibling().xml(), '<div>b</div>')
# siblings with wrong args
self.assertEqual(s.siblings('a'), [])
# siblings with good args
self.assertEqual(s.siblings('div')[0].xml(), '<div>b</div>')
# Check for siblings with wrong kargs and value
self.assertEqual(s.siblings(a='d'), [])
# Check for siblings with good kargs and value
# Can't figure this one out what is a right value here??
# Commented for now...
# self.assertEqual(s.siblings(div='<div>b</div>'), ???)
# No other sibling should return None
self.assertEqual(DIV(P('First element')).element('p').sibling(), None)
# --------------------------------------------------------------------------------------------------------------
# This use unicode to hit xmlescape() line :
# """
# elif isinstance(data, unicode):
# data = data.encode('utf8', 'xmlcharrefreplace')
# """
self.assertEqual(DIV(u'Texte en français avec des caractères accentués...').xml(),
'<div>Texte en fran\xc3\xa7ais avec des caract\xc3\xa8res accentu\xc3\xa9s...</div>')
# --------------------------------------------------------------------------------------------------------------
self.assertEqual(DIV('Test with an ID', _id='id-of-the-element').xml(),
'<div id="id-of-the-element">Test with an ID</div>')
self.assertEqual(DIV().element('p'), None)
# Corner case for raise coverage of one line
# I think such assert fail cause of python 2.6
# Work under python 2.7
@@ -215,9 +244,10 @@ class TestBareHelpers(unittest.TestCase):
# CAT(' ')
self.assertEqual(CAT(' ').xml(), ' ')
# TODO: def test_TAG_unpickler(self):
# TODO: def test_TAG_pickler(self):
def test_TAG_pickler_unpickler(self):
# weird test
self.assertEqual(TAG_unpickler(TAG_pickler(TAG.div('data to be pickle'))[1][0]).xml(),
'<div>data to be pickle</div>')
def test_TAG(self):
self.assertEqual(TAG.first(TAG.second('test'), _key=3).xml(),
@@ -225,6 +255,9 @@ class TestBareHelpers(unittest.TestCase):
# ending in underscore "triggers" <input /> style
self.assertEqual(TAG.first_(TAG.second('test'), _key=3).xml(),
'<first key="3" />')
# unicode test for TAG
self.assertEqual(TAG.div(u'Texte en français avec des caractères accentués...').xml(),
'<div>Texte en fran\xc3\xa7ais avec des caract\xc3\xa8res accentu\xc3\xa9s...</div>')
def test_HTML(self):
self.assertEqual(HTML('<>', _a='1', _b='2').xml(),
@@ -259,6 +292,8 @@ class TestBareHelpers(unittest.TestCase):
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='xmlns').xml(),
'xmlns\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', _xmlns='xmlns').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
def test_HEAD(self):
self.assertEqual(HEAD('<>', _a='1', _b='2').xml(),
@@ -290,6 +325,8 @@ class TestBareHelpers(unittest.TestCase):
def test_STYLE(self):
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
# Try to hit : return DIV.xml(self)
self.assertEqual(STYLE().xml(), '<style></style>')
def test_IMG(self):
self.assertEqual(IMG(_a='1', _b='2').xml(),
@@ -515,8 +552,9 @@ class TestBareHelpers(unittest.TestCase):
'<iframe a="1" b="2">&lt;&gt;</iframe>')
def test_INPUT(self):
self.assertEqual(INPUT(_a='1', _b='2').xml(),
'<input a="1" b="2" type="text" />')
self.assertEqual(INPUT(_a='1', _b='2').xml(), '<input a="1" b="2" type="text" />')
# list value
self.assertEqual(INPUT(_value=[1, 2, 3]).xml(), '<input type="text" value="[1, 2, 3]" />')
def test_TEXTAREA(self):
self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(),
@@ -560,6 +598,12 @@ class TestBareHelpers(unittest.TestCase):
OPTION('option 2', _value='2'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option selected="selected" value="1">option 1</option><option value="2">option 2</option></select>')
# More then one select with mutilple
self.assertEqual(SELECT(OPTION('option 1', _value='1', _selected='selected'),
OPTION('option 2', _value='2', _selected='selected'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option selected="selected" value="1">option 1</option><option selected="selected" value="2">option 2</option></select>'
)
# OPTGROUP
self.assertEqual(SELECT(OPTGROUP(OPTION('option 1', _value='1'),
OPTION('option 2', _value='2'),
@@ -574,6 +618,11 @@ class TestBareHelpers(unittest.TestCase):
# String value
self.assertEqual(SELECT('Option 1', 'Option 2').xml(),
'<select><option value="Option 1">Option 1</option><option value="Option 2">Option 2</option></select>')
# list as a value
self.assertEqual(SELECT(OPTION('option 1', _value=[1, 2, 3]),
OPTION('option 2', _value=[4, 5, 6], _selected='selected'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option value="[1, 2, 3]">option 1</option><option selected="selected" value="[4, 5, 6]">option 2</option></select>')
def test_FIELDSET(self):
self.assertEqual(FIELDSET('<>', _a='1', _b='2').xml(),
@@ -586,17 +635,40 @@ class TestBareHelpers(unittest.TestCase):
def test_FORM(self):
self.assertEqual(FORM('<>', _a='1', _b='2').xml(),
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post">&lt;&gt;</form>')
# These 2 crash AppVeyor and Travis with: "ImportError: No YAML serializer available"
# self.assertEqual(FORM('<>', _a='1', _b='2').as_yaml(),
# "accepted: null\nattributes: {_a: '1', _action: '#', _b: '2', _enctype: multipart/form-data, _method: post}\ncomponents: [<>]\nerrors: {}\nlatest: {}\nparent: null\nvars: {}\n")
# self.assertEqual(FORM('<>', _a='1', _b='2').as_xml(),
# '<?xml version="1.0" encoding="UTF-8"?><document><errors></errors><vars></vars><parent>None</parent><attributes><_enctype>multipart/form-data</_enctype><_action>#</_action><_b>2</_b><_a>1</_a><_method>post</_method></attributes><components><item>&amp;lt;&amp;gt;</item></components><accepted>None</accepted><latest></latest></document>')
def test_BEAUTIFY(self):
self.assertEqual(BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml(),
'<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>')
# unicode
self.assertEqual(BEAUTIFY([P(u'àéèûôç'), 'a', 'b', {'hello': 'world'}]).xml(),
'<div><table><tr><td><div><p>\xc3\xa0\xc3\xa9\xc3\xa8\xc3\xbb\xc3\xb4\xc3\xa7</p></div></td></tr><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>')
def test_MENU(self):
self.assertEqual(MENU([('Home', False, '/welcome/default/index', [])]).xml(),
'<ul class="web2py-menu web2py-menu-vertical"><li class="web2py-menu-first"><a href="/welcome/default/index">Home</a></li></ul>')
# Multiples entries menu
self.assertEqual(MENU([('Home', False, '/welcome/default/index', []),
('Item 1', False, '/welcome/default/func_one', []),
('Item 2', False, '/welcome/default/func_two', []),
('Item 3', False, '/welcome/default/func_three', []),
('Item 4', False, '/welcome/default/func_four', [])]).xml(),
'<ul class="web2py-menu web2py-menu-vertical"><li class="web2py-menu-first"><a href="/welcome/default/index">Home</a></li><li><a href="/welcome/default/func_one">Item 1</a></li><li><a href="/welcome/default/func_two">Item 2</a></li><li><a href="/welcome/default/func_three">Item 3</a></li><li class="web2py-menu-last"><a href="/welcome/default/func_four">Item 4</a></li></ul>'
)
# mobile=True
self.assertEqual(MENU([('Home', False, '/welcome/default/index', [])], mobile=True).xml(),
'<select class="web2py-menu web2py-menu-vertical" onchange="window.location=this.value"><option value="/welcome/default/index">Home</option></select>')
# Multiples entries menu for mobile
self.assertEqual(MENU([('Home', False, '/welcome/default/index', []),
('Item 1', False, '/welcome/default/func_one', []),
('Item 2', False, '/welcome/default/func_two', []),
('Item 3', False, '/welcome/default/func_three', []),
('Item 4', False, '/welcome/default/func_four', [])], mobile=True).xml(),
'<select class="web2py-menu web2py-menu-vertical" onchange="window.location=this.value"><option value="/welcome/default/index">Home</option><option value="/welcome/default/func_one">Item 1</option><option value="/welcome/default/func_two">Item 2</option><option value="/welcome/default/func_three">Item 3</option><option value="/welcome/default/func_four">Item 4</option></select>')
# TODO: def test_embed64(self):
+354
View File
@@ -0,0 +1,354 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.scheduler
"""
import os
import unittest
import glob
import datetime
import sys
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.storage import Storage
from gluon.languages import translator
from gluon.scheduler import JobGraph, Scheduler
from gluon.dal import DAL
class BaseTestScheduler(unittest.TestCase):
def setUp(self):
self.db = None
self.cleanfolder()
from gluon import current
s = Storage({'application': 'welcome',
'folder': 'applications/welcome',
'controller': 'default'})
current.request = s
T = translator('', 'en')
current.T = T
self.db = DAL('sqlite://dummy2.db', check_reserved=['all'])
def cleanfolder(self):
if self.db:
self.db.close()
try:
os.unlink('dummy2.db')
except:
pass
tfiles = glob.glob('*_scheduler*.table')
for a in tfiles:
os.unlink(a)
def tearDown(self):
self.cleanfolder()
try:
self.inner_teardown()
except:
pass
class TestsForJobGraph(BaseTestScheduler):
def testJobGraph(self):
s = Scheduler(self.db)
myjob = JobGraph(self.db, 'job_1')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob.add_deps(shoes.id, socks.id)
## results in the following topological sort
# 9,3,6 --> 4,5 --> 8,7 --> 2
# socks, shirt, undershorts
# tie, pants
# shoes, belt
# jacket
known_toposort = [
set([socks.id, shirt.id, undershorts.id]),
set([tie.id, pants.id]),
set([shoes.id, belt.id]),
set([jacket.id])
]
toposort = myjob.validate('job_1')
self.assertEqual(toposort, known_toposort)
# add a cyclic dependency, jacket to undershorts
myjob.add_deps(undershorts.id, jacket.id)
# no exceptions raised, but result None
self.assertEqual(myjob.validate('job_1'), None)
def testJobGraphFailing(self):
s = Scheduler(self.db)
myjob = JobGraph(self.db, 'job_1')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob.add_deps(shoes.id, socks.id)
# add a cyclic dependency, jacket to undershorts
myjob.add_deps(undershorts.id, jacket.id)
# no exceptions raised, but result None
self.assertEqual(myjob.validate('job_1'), None)
# and no deps added
deps_inserted = self.db(self.db.scheduler_task_deps.id>0).count()
self.assertEqual(deps_inserted, 0)
def testJobGraphDifferentJobs(self):
s = Scheduler(self.db)
myjob1 = JobGraph(self.db, 'job_1')
myjob2 = JobGraph(self.db, 'job_2')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob1.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob1.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob1.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob1.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob2.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob2.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob2.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob2.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob2.add_deps(shoes.id, socks.id)
# every job by itself can be completed
self.assertNotEqual(myjob1.validate('job_1'), None)
self.assertNotEqual(myjob1.validate('job_2'), None)
# and, implicitly, every queued task can be too
self.assertNotEqual(myjob1.validate(), None)
# add a cyclic dependency, jacket to undershorts
myjob2.add_deps(undershorts.id, jacket.id)
# every job can still be completed by itself
self.assertNotEqual(myjob1.validate('job_1'), None)
self.assertNotEqual(myjob1.validate('job_2'), None)
# but trying to see if every task will ever be completed fails
self.assertEqual(myjob2.validate(), None)
class TestsForSchedulerAPIs(BaseTestScheduler):
def testQueue_Task(self):
def isnotqueued(result):
self.assertEqual(result.id, None)
self.assertEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()) > 0, True)
def isqueued(result):
self.assertNotEqual(result.id, None)
self.assertNotEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()), 0)
s = Scheduler(self.db)
fname = 'foo'
watch = s.queue_task(fname, task_name='watch')
# queuing a task returns id, errors, uuid
self.assertEqual(set(watch.keys()), set(['id', 'uuid', 'errors']))
# queueing nothing isn't allowed
self.assertRaises(TypeError, s.queue_task, *[])
# passing pargs and pvars wrongly
# # pargs as dict
isnotqueued(s.queue_task(fname, dict(a=1), dict(b=1)))
# # pvars as list
isnotqueued(s.queue_task(fname, ['foo', 'bar'], ['foo', 'bar']))
# two tasks with the same uuid won't be there
isqueued(s.queue_task(fname, uuid='a'))
isnotqueued(s.queue_task(fname, uuid='a'))
# # #FIXME add here every parameter
def testTask_Status(self):
s = Scheduler(self.db)
fname = 'foo'
watch = s.queue_task(fname, task_name='watch')
# fetch status by id
by_id = s.task_status(watch.id)
# fetch status by uuid
by_uuid = s.task_status(watch.uuid)
# fetch status by query
by_query = s.task_status(self.db.scheduler_task.function_name == 'foo')
self.assertEqual(by_id, by_uuid)
self.assertEqual(by_id, by_query)
# fetch status by anything else throws
self.assertRaises(SyntaxError, s.task_status, *[[1, 2]])
# adding output returns the joined set, plus "result"
rtn = s.task_status(watch.id, output=True)
self.assertEqual(set(rtn.keys()), set(['scheduler_run', 'scheduler_task', 'result']))
class testForSchedulerRunnerBase(BaseTestScheduler):
def inner_teardown(self):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
os.unlink(fdest)
def writefunction(self, content, initlines=None):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
if initlines is None:
initlines = """
import os
import time
from gluon.scheduler import Scheduler
db_dal = os.path.abspath(os.path.join(request.folder, '..', '..', 'dummy2.db'))
sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal))
sched = Scheduler(sched_dal, max_empty_runs=20, migrate=False, heartbeat=1)
"""
with open(fdest, 'w') as q:
q.write(initlines)
q.write(content)
def exec_sched(self):
import subprocess
call_args = [sys.executable, 'web2py.py', '-K', 'welcome']
ret = subprocess.call(call_args, env=dict(os.environ))
return ret
class TestsForSchedulerRunner(testForSchedulerRunnerBase):
def testBasic(self):
s = Scheduler(self.db)
foo = s.queue_task('foo')
self.db.commit()
self.writefunction(r"""
def foo():
return 'a'
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
info = s.task_status(foo.id, output=True)
self.assertEqual(info.result, 'a')
def testRetryFailed(self):
s = Scheduler(self.db)
failed = s.queue_task('demo2', retry_failed=1, period=5)
failed_consecutive = s.queue_task('demo8', retry_failed=2, repeats=2, period=5)
self.db.commit()
self.writefunction(r"""
def demo2():
1/0
def demo8():
placeholder = os.path.join(request.folder, 'private', 'demo8.pholder')
with open(placeholder, 'a') as g:
g.write('\nplaceholder for demo8 created')
num_of_lines = 0
with open(placeholder) as f:
num_of_lines = len([a for a in f.read().split('\n') if a])
print 'number of lines', num_of_lines
if num_of_lines <= 2:
1/0
else:
os.unlink(placeholder)
return 1
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
# failed - checks
info = s.task_status(failed.id)
task_runs = self.db(self.db.scheduler_run.task_id == info.id).select()
res = [
("task status failed", info.status == 'FAILED'),
("task times_run is 0", info.times_run == 0),
("task times_failed is 2", info.times_failed == 2),
("task ran 2 times only", len(task_runs) == 2),
("scheduler_run records are FAILED", (task_runs[0].status == task_runs[1].status == 'FAILED')),
("period is respected", (task_runs[1].start_time > task_runs[0].start_time + datetime.timedelta(seconds=info.period)))
]
for a in res:
self.assertEqual(a[1], True, msg=a[0])
# failed consecutive - checks
info = s.task_status(failed_consecutive.id)
task_runs = self.db(self.db.scheduler_run.task_id == info.id).select()
res = [
("task status completed", info.status == 'COMPLETED'),
("task times_run is 2", info.times_run == 2),
("task times_failed is 0", info.times_failed == 0),
("task ran 6 times", len(task_runs) == 6),
("scheduler_run records for COMPLETED is 2", len([run.status for run in task_runs if run.status == 'COMPLETED']) == 2),
("scheduler_run records for FAILED is 4", len([run.status for run in task_runs if run.status == 'FAILED']) == 4),
]
for a in res:
self.assertEqual(a[1], True, msg=a[0])
if __name__ == '__main__':
unittest.main()
+439
View File
@@ -0,0 +1,439 @@
#!/bin/python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.sqlhtml
"""
import os
import sys
if sys.version < "2.7":
import unittest2 as unittest
else:
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from sqlhtml import safe_int, SQLFORM, SQLTABLE
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field
from pydal.objects import Table
from tools import Auth, Mail
from gluon.globals import Request, Response, Session
from storage import Storage
from languages import translator
from gluon.http import HTTP
from validators import *
# TODO: Create these test...
# class Test_add_class(unittest.TestCase):
# def test_add_class(self):
# pass
# class Test_represent(unittest.TestCase):
# def test_represent(self):
# pass
# class TestCacheRepresenter(unittest.TestCase):
# def test___call__(self):
# pass
# def test___init__(self):
# pass
class Test_safe_int(unittest.TestCase):
def test_safe_int(self):
# safe int
self.assertEqual(safe_int(1), 1)
# not safe int
self.assertEqual(safe_int('1x'), 0)
# class Test_safe_float(unittest.TestCase):
# def test_safe_float(self):
# pass
# class Test_show_if(unittest.TestCase):
# def test_show_if(self):
# pass
# class TestFormWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestStringWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestIntegerWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestDoubleWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestDecimalWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestDateWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestDatetimeWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestTextWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestJSONWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestBooleanWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestListWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestMultipleOptionsWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestRadioWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestCheckboxesWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestPasswordWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_widget(self):
# pass
# class TestUploadWidget(unittest.TestCase):
# def test__attributes(self):
# pass
# def test_represent(self):
# pass
# def test_widget(self):
# pass
# class TestAutocompleteWidget(unittest.TestCase):
# def test___call__(self):
# pass
# def test___init__(self):
# pass
# def test_callback(self):
# pass
# class Test_formstyle_table3cols(unittest.TestCase):
# def test_formstyle_table3cols(self):
# pass
# class Test_formstyle_table2cols(unittest.TestCase):
# def test_formstyle_table2cols(self):
# pass
# class Test_formstyle_divs(unittest.TestCase):
# def test_formstyle_divs(self):
# pass
# class Test_formstyle_inline(unittest.TestCase):
# def test_formstyle_inline(self):
# pass
# class Test_formstyle_ul(unittest.TestCase):
# def test_formstyle_ul(self):
# pass
# class Test_formstyle_bootstrap(unittest.TestCase):
# def test_formstyle_bootstrap(self):
# pass
# class Test_formstyle_bootstrap3_stacked(unittest.TestCase):
# def test_formstyle_bootstrap3_stacked(self):
# pass
# class Test_formstyle_bootstrap3_inline_factory(unittest.TestCase):
# def test_formstyle_bootstrap3_inline_factory(self):
# pass
class TestSQLFORM(unittest.TestCase):
def setUp(self):
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
current.T = T
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
self.auth = Auth(self.db)
self.auth.define_tables(username=True, signature=False)
self.db.define_table('t0', Field('tt'), self.auth.signature)
self.auth.enable_record_versioning(self.db)
# Create a user
self.db.auth_user.insert(first_name='Bart',
last_name='Simpson',
username='user1',
email='user1@test.com',
password='password_123',
registration_key=None,
registration_id=None)
self.db.commit()
def test_SQLFORM(self):
form = SQLFORM(self.db.auth_user)
self.assertEqual(form.xml(), '<form action="#" enctype="multipart/form-data" method="post"><table><tr id="auth_user_first_name__row"><td class="w2p_fl"><label class="" for="auth_user_first_name" id="auth_user_first_name__label">First name: </label></td><td class="w2p_fw"><input class="string" id="auth_user_first_name" name="first_name" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="auth_user_last_name__row"><td class="w2p_fl"><label class="" for="auth_user_last_name" id="auth_user_last_name__label">Last name: </label></td><td class="w2p_fw"><input class="string" id="auth_user_last_name" name="last_name" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="auth_user_email__row"><td class="w2p_fl"><label class="" for="auth_user_email" id="auth_user_email__label">E-mail: </label></td><td class="w2p_fw"><input class="string" id="auth_user_email" name="email" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="auth_user_username__row"><td class="w2p_fl"><label class="" for="auth_user_username" id="auth_user_username__label">Username: </label></td><td class="w2p_fw"><input class="string" id="auth_user_username" name="username" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="auth_user_password__row"><td class="w2p_fl"><label class="" for="auth_user_password" id="auth_user_password__label">Password: </label></td><td class="w2p_fw"><input class="password" id="auth_user_password" name="password" type="password" value="" /></td><td class="w2p_fc"></td></tr><tr id="submit_record__row"><td class="w2p_fl"></td><td class="w2p_fw"><input type="submit" value="Submit" /></td><td class="w2p_fc"></td></tr></table></form>')
# def test_assert_status(self):
# pass
# def test_createform(self):
# pass
# def test_accepts(self):
# pass
# def test_dictform(self):
# pass
# def test_smartdictform(self):
# pass
def test_factory(self):
factory_form = SQLFORM.factory(Field('field_one', 'string', IS_NOT_EMPTY()),
Field('field_two', 'string'))
self.assertEqual(factory_form.xml(), '<form action="#" enctype="multipart/form-data" method="post"><table><tr id="no_table_field_one__row"><td class="w2p_fl"><label class="" for="no_table_field_one" id="no_table_field_one__label">Field One: </label></td><td class="w2p_fw"><input class="string" id="no_table_field_one" name="field_one" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="no_table_field_two__row"><td class="w2p_fl"><label class="" for="no_table_field_two" id="no_table_field_two__label">Field Two: </label></td><td class="w2p_fw"><input class="string" id="no_table_field_two" name="field_two" type="text" value="" /></td><td class="w2p_fc"></td></tr><tr id="submit_record__row"><td class="w2p_fl"></td><td class="w2p_fw"><input type="submit" value="Submit" /></td><td class="w2p_fc"></td></tr></table></form>')
# def test_build_query(self):
# pass
# def test_search_menu(self):
# pass
def test_grid(self):
grid_form = SQLFORM.grid(self.db.auth_user)
self.assertEqual(grid_form.xml(), '<div class="web2py_grid "><div class="web2py_console "><form action="/a/c/f" enctype="multipart/form-data" method="GET"><input class="form-control" id="w2p_keywords" name="keywords" onfocus="jQuery(&#x27;#w2p_query_fields&#x27;).change();jQuery(&#x27;#w2p_query_panel&#x27;).slideDown();" type="text" value="" /><input class="btn btn-default" type="submit" value="Search" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_keywords&#x27;).val(&#x27;&#x27;);" type="submit" value="Clear" /></form><div id="w2p_query_panel" style="display:none;"><select class="form-control" id="w2p_query_fields" onchange="jQuery(&#x27;.w2p_query_row&#x27;).hide();jQuery(&#x27;#w2p_field_&#x27;+jQuery(&#x27;#w2p_query_fields&#x27;).val().replace(&#x27;.&#x27;,&#x27;-&#x27;)).show();" style="float:left"><option value="auth_user.id">Id</option><option value="auth_user.first_name">First name</option><option value="auth_user.last_name">Last name</option><option value="auth_user.email">E-mail</option><option value="auth_user.username">Username</option></select><div class="w2p_query_row" id="w2p_field_auth_user-id" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="in">in</option><option value="not in">not in</option></select><input class="id form-control" id="w2p_value_auth_user-id" type="text" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;new&#x27;,&#x27;auth_user.id&#x27;)" title="Start building a new search" type="button" value="New Search" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;and&#x27;,&#x27;auth_user.id&#x27;)" title="Add this to the search as an AND term" type="button" value="+ And" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;or&#x27;,&#x27;auth_user.id&#x27;)" title="Add this to the search as an OR term" type="button" value="+ Or" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_query_panel&#x27;).slideUp()" type="button" value="Close" /></div><div class="w2p_query_row" id="w2p_field_auth_user-first_name" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="starts with">starts with</option><option value="contains">contains</option><option value="in">in</option><option value="not in">not in</option></select><input class="string form-control" id="w2p_value_auth_user-first_name" type="text" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;new&#x27;,&#x27;auth_user.first_name&#x27;)" title="Start building a new search" type="button" value="New Search" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;and&#x27;,&#x27;auth_user.first_name&#x27;)" title="Add this to the search as an AND term" type="button" value="+ And" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;or&#x27;,&#x27;auth_user.first_name&#x27;)" title="Add this to the search as an OR term" type="button" value="+ Or" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_query_panel&#x27;).slideUp()" type="button" value="Close" /></div><div class="w2p_query_row" id="w2p_field_auth_user-last_name" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="starts with">starts with</option><option value="contains">contains</option><option value="in">in</option><option value="not in">not in</option></select><input class="string form-control" id="w2p_value_auth_user-last_name" type="text" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;new&#x27;,&#x27;auth_user.last_name&#x27;)" title="Start building a new search" type="button" value="New Search" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;and&#x27;,&#x27;auth_user.last_name&#x27;)" title="Add this to the search as an AND term" type="button" value="+ And" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;or&#x27;,&#x27;auth_user.last_name&#x27;)" title="Add this to the search as an OR term" type="button" value="+ Or" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_query_panel&#x27;).slideUp()" type="button" value="Close" /></div><div class="w2p_query_row" id="w2p_field_auth_user-email" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="starts with">starts with</option><Line truncated
def test_smartgrid(self):
smartgrid_form = SQLFORM.smartgrid(self.db.auth_user)
self.assertEqual(smartgrid_form.xml(), '<div class="web2py_grid "><div class="web2py_breadcrumbs"><ul class=""><li class="active w2p_grid_breadcrumb_elem"><a href="/a/c/f/auth_user">Auth users</a></li></ul></div><div class="web2py_console "><form action="/a/c/f/auth_user" enctype="multipart/form-data" method="GET"><input class="form-control" id="w2p_keywords" name="keywords" onfocus="jQuery(&#x27;#w2p_query_fields&#x27;).change();jQuery(&#x27;#w2p_query_panel&#x27;).slideDown();" type="text" value="" /><input class="btn btn-default" type="submit" value="Search" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_keywords&#x27;).val(&#x27;&#x27;);" type="submit" value="Clear" /></form><div id="w2p_query_panel" style="display:none;"><select class="form-control" id="w2p_query_fields" onchange="jQuery(&#x27;.w2p_query_row&#x27;).hide();jQuery(&#x27;#w2p_field_&#x27;+jQuery(&#x27;#w2p_query_fields&#x27;).val().replace(&#x27;.&#x27;,&#x27;-&#x27;)).show();" style="float:left"><option value="auth_user.id">Id</option><option value="auth_user.first_name">First name</option><option value="auth_user.last_name">Last name</option><option value="auth_user.email">E-mail</option><option value="auth_user.username">Username</option></select><div class="w2p_query_row" id="w2p_field_auth_user-id" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="in">in</option><option value="not in">not in</option></select><input class="id form-control" id="w2p_value_auth_user-id" type="text" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;new&#x27;,&#x27;auth_user.id&#x27;)" title="Start building a new search" type="button" value="New Search" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;and&#x27;,&#x27;auth_user.id&#x27;)" title="Add this to the search as an AND term" type="button" value="+ And" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;or&#x27;,&#x27;auth_user.id&#x27;)" title="Add this to the search as an OR term" type="button" value="+ Or" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_query_panel&#x27;).slideUp()" type="button" value="Close" /></div><div class="w2p_query_row" id="w2p_field_auth_user-first_name" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="starts with">starts with</option><option value="contains">contains</option><option value="in">in</option><option value="not in">not in</option></select><input class="string form-control" id="w2p_value_auth_user-first_name" type="text" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;new&#x27;,&#x27;auth_user.first_name&#x27;)" title="Start building a new search" type="button" value="New Search" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;and&#x27;,&#x27;auth_user.first_name&#x27;)" title="Add this to the search as an AND term" type="button" value="+ And" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;or&#x27;,&#x27;auth_user.first_name&#x27;)" title="Add this to the search as an OR term" type="button" value="+ Or" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_query_panel&#x27;).slideUp()" type="button" value="Close" /></div><div class="w2p_query_row" id="w2p_field_auth_user-last_name" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</option><option value="&gt;">&gt;</option><option value="&lt;=">&lt;=</option><option value="&gt;=">&gt;=</option><option value="starts with">starts with</option><option value="contains">contains</option><option value="in">in</option><option value="not in">not in</option></select><input class="string form-control" id="w2p_value_auth_user-last_name" type="text" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;new&#x27;,&#x27;auth_user.last_name&#x27;)" title="Start building a new search" type="button" value="New Search" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;and&#x27;,&#x27;auth_user.last_name&#x27;)" title="Add this to the search as an AND term" type="button" value="+ And" /><input class="btn btn-default" onclick="w2p_build_query(&#x27;or&#x27;,&#x27;auth_user.last_name&#x27;)" title="Add this to the search as an OR term" type="button" value="+ Or" /><input class="btn btn-default" onclick="jQuery(&#x27;#w2p_query_panel&#x27;).slideUp()" type="button" value="Close" /></div><div class="w2p_query_row" id="w2p_field_auth_user-email" style="display:none"><select class="form-control"><option value="=">=</option><option value="!=">!=</option><option value="&lt;">&lt;</oLine truncated
class TestSQLTABLE(unittest.TestCase):
def setUp(self):
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
current.T = T
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
self.auth = Auth(self.db)
self.auth.define_tables(username=True, signature=False)
self.db.define_table('t0', Field('tt'), self.auth.signature)
self.auth.enable_record_versioning(self.db)
# Create a user
self.db.auth_user.insert(first_name='Bart',
last_name='Simpson',
username='user1',
email='user1@test.com',
password='password_123',
registration_key=None,
registration_id=None)
self.db.commit()
def test_SQLTABLE(self):
rows = self.db(self.db.auth_user.id > 0).select(self.db.auth_user.ALL)
sqltable = SQLTABLE(rows)
self.assertEqual(sqltable.xml(), '<table><thead><tr><th>auth_user.id</th><th>auth_user.first_name</th><th>auth_user.last_name</th><th>auth_user.email</th><th>auth_user.username</th><th>auth_user.password</th><th>auth_user.registration_key</th><th>auth_user.reset_password_key</th><th>auth_user.registration_id</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>Bart</td><td>Simpson</td><td>user1@test.com</td><td>user1</td><td>password_123</td><td>None</td><td></td><td>None</td></tr></tbody></table>')
# class TestExportClass(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
# class TestExporterTSV(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
# class TestExporterCSV(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
# class TestExporterCSV_hidden(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
# class TestExporterHTML(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
# class TestExporterXML(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
# class TestExporterJSON(unittest.TestCase):
# def test___init__(self):
# pass
# def test_export(self):
# pass
# def test_represented(self):
# pass
if __name__ == '__main__':
unittest.main()
+76 -1
View File
@@ -9,10 +9,11 @@ from fix_path import fix_sys_path
fix_sys_path(__file__)
import template
from template import render
class TestVirtualFields(unittest.TestCase):
class TestTemplate(unittest.TestCase):
def testRun(self):
self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}',
@@ -61,6 +62,80 @@ class TestVirtualFields(unittest.TestCase):
self.assertRaises(
SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}')
def testWithDummyFileSystem(self):
from os.path import join as pjoin
import contextlib
from StringIO import StringIO
from gluon.restricted import RestrictedError
@contextlib.contextmanager
def monkey_patch(module, fn_name, patch):
try:
unpatch = getattr(module, fn_name)
except AttributeError:
unpatch = None
setattr(module, fn_name, patch)
try:
yield
finally:
if unpatch is None:
delattr(module, fn_name)
else:
setattr(module, fn_name, unpatch)
def dummy_open(path, mode):
if path == pjoin('views', 'layout.html'):
return StringIO("{{block left_sidebar}}left{{end}}"
"{{include}}"
"{{block right_sidebar}}right{{end}}")
elif path == pjoin('views', 'layoutbrackets.html'):
return StringIO("[[block left_sidebar]]left[[end]]"
"[[include]]"
"[[block right_sidebar]]right[[end]]")
elif path == pjoin('views', 'default', 'index.html'):
return StringIO("{{extend 'layout.html'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'indexbrackets.html'):
return StringIO("[[extend 'layoutbrackets.html']]"
"[[block left_sidebar]][[super]] [[end]]"
"to"
"[[block right_sidebar]] [[super]][[end]]")
elif path == pjoin('views', 'default', 'missing.html'):
return StringIO("{{extend 'wut'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'noescape.html'):
return StringIO("""{{=NOESCAPE('<script></script>')}}""")
raise IOError
with monkey_patch(template, 'open', dummy_open):
self.assertEqual(
render(filename=pjoin('views', 'default', 'index.html'),
path='views'),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', delimiters=('[[', ']]')),
'left to right')
self.assertRaises(
RestrictedError,
render,
filename=pjoin('views', 'default', 'missing.html'),
path='views')
response = template.DummyResponse()
response.delimiters = ('[[', ']]')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', context={'response': response}),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'noescape.html'),
context={'NOESCAPE': template.NOESCAPE}),
'<script></script>')
if __name__ == '__main__':
unittest.main()
+287 -53
View File
@@ -20,7 +20,7 @@ DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field
from pydal.objects import Table
from tools import Auth, Mail
from tools import Auth, Mail, Recaptcha, Recaptcha2
from gluon.globals import Request, Response, Session
from storage import Storage
from languages import translator
@@ -29,57 +29,6 @@ from gluon.http import HTTP
python_version = sys.version[:3]
IS_IMAP = "imap" in DEFAULT_URI
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
class TestAuth(unittest.TestCase):
def testRun(self):
# setup
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
current.T = T
db = DAL(DEFAULT_URI, check_reserved=['all'])
auth = Auth(db)
auth.define_tables(username=True, signature=False)
self.assertTrue('auth_user' in db)
self.assertTrue('auth_group' in db)
self.assertTrue('auth_membership' in db)
self.assertTrue('auth_permission' in db)
self.assertTrue('auth_event' in db)
db.define_table('t0', Field('tt'), auth.signature)
auth.enable_record_versioning(db)
self.assertTrue('t0_archive' in db)
for f in ['login', 'register', 'retrieve_password',
'retrieve_username']:
html_form = getattr(auth, f)().xml()
self.assertTrue('name="_formkey"' in html_form)
for f in ['logout', 'verify_email', 'reset_password',
'change_password', 'profile', 'groups']:
self.assertRaisesRegexp(HTTP, "303*", getattr(auth, f))
self.assertRaisesRegexp(HTTP, "401*", auth.impersonate)
try:
for t in ['t0_archive', 't0', 'auth_cas', 'auth_event',
'auth_membership', 'auth_permission', 'auth_group',
'auth_user']:
db[t].drop()
except SyntaxError as e:
# GAE doesn't support drop
pass
return
class TestMail(unittest.TestCase):
"""
@@ -125,7 +74,6 @@ class TestMail(unittest.TestCase):
def starttls(self):
self.tls = True
def setUp(self):
self.original_SMTP = smtplib.SMTP
self.original_SMTP_SSL = smtplib.SMTP_SSL
@@ -213,10 +161,296 @@ class TestMail(unittest.TestCase):
message='world'))
TestMail.DummySMTP.inbox.pop()
def test_attachment(self):
module_file = os.path.abspath(__file__)
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
message='world',
attachments=Mail.Attachment(module_file)))
message = TestMail.DummySMTP.inbox.pop()
import email
parsed_msg = email.message_from_string(message.payload)
attachment = parsed_msg.get_payload(1).get_payload(decode=True)
with open(module_file, 'rb') as mf:
self.assertEqual(attachment.decode('utf-8'), mf.read().decode('utf-8'))
# Test missing attachment name error
stream = open(module_file)
self.assertRaises(Exception, lambda *args, **kwargs: Mail.Attachment(*args, **kwargs), stream)
stream.close()
# Test you can define content-id and content type
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
message='world',
attachments=Mail.Attachment(module_file, content_id='trololo', content_type='tra/lala')))
message = TestMail.DummySMTP.inbox.pop()
self.assertTrue('Content-Type: tra/lala' in message.payload)
self.assertTrue('Content-Id: <trololo>' in message.payload)
# class TestRecaptcha(unittest.TestCase):
# def test_Recaptcha(self):
# from html import FORM
# form = FORM(Recaptcha(public_key='public_key', private_key='private_key'))
# self.assertEqual(form.xml(),
# '<form action="#" enctype="multipart/form-data" method="post"><div id="recaptcha"><script><!--\nvar RecaptchaOptions = {};\n//--></script><script src="http://www.google.com/recaptcha/api/challenge?k=public_key" type="text/javascript"></script><noscript><iframe frameborder="0" height="300" src="http://www.google.com/recaptcha/api/noscript?k=public_key" width="500"></iframe><br /><input name="recaptcha_response_field" type="hidden" value="manual_challenge" /></noscript></div></form>')
#
#
# class TestRecaptcha2(unittest.TestCase):
# def test_Recaptcha2(self):
# from html import FORM
# form = FORM(Recaptcha2(public_key='public_key', private_key='private_key'))
# rtn = '<form action="#" enctype="multipart/form-data" method="post"><div><script async="" defer="" src="https://www.google.com/recaptcha/api.js"></script><div class="g-recaptcha" data-sitekey="public_key"></div><noscript>\n<div style="width: 302px; height: 352px;">\n<div style="width: 302px; height: 352px; position: relative;">\n <div style="width: 302px; height: 352px; position: absolute;">\n <iframe src="https://www.google.com/recaptcha/api/fallback?k=public_key"\n frameborder="0" scrolling="no"\n style="width: 302px; height:352px; border-style: none;">\n </iframe>\n </div>\n <div style="width: 250px; height: 80px; position: absolute; border-style: none;\n bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">\n <textarea id="g-recaptcha-response" name="g-recaptcha-response"\n class="g-recaptcha-response"\n style="width: 250px; height: 80px; border: 1px solid #c1c1c1;\n margin: 0px; padding: 0px; resize: none;" value="">\n </textarea>\n </div>\n</div>\n</div></noscript></div></form>'
# self.assertEqual(form.xml(), rtn)
# TODO: class TestAuthJWT(unittest.TestCase):
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
class TestAuth(unittest.TestCase):
def setUp(self):
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
current.T = T
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
self.auth = Auth(self.db)
self.auth.define_tables(username=True, signature=False)
self.db.define_table('t0', Field('tt'), self.auth.signature)
self.auth.enable_record_versioning(self.db)
# Create a user
self.auth.get_or_create_user(dict(first_name='Bart',
last_name='Simpson',
username='bart',
email='bart@simpson.com',
password='bart_password',
registration_key='bart',
registration_id=''
))
# self.auth.settings.registration_requires_verification = False
# self.auth.settings.registration_requires_approval = False
def test_assert_setup(self):
self.assertEqual(self.db(self.db.auth_user.username == 'bart').select().first()['username'], 'bart')
self.assertTrue('auth_user' in self.db)
self.assertTrue('auth_group' in self.db)
self.assertTrue('auth_membership' in self.db)
self.assertTrue('auth_permission' in self.db)
self.assertTrue('auth_event' in self.db)
def test_enable_record_versioning(self):
self.assertTrue('t0_archive' in self.db)
def test_basic_blank_forms(self):
for f in ['login', 'retrieve_password',
'retrieve_username',
# 'register' # register complain about : client_side=self.settings.client_side
]:
html_form = getattr(self.auth, f)().xml()
self.assertTrue('name="_formkey"' in html_form)
# NOTE: Not sure it is the proper way to logout_bare() as there is not methods for that and auth.logout() failed
self.auth.logout_bare()
# self.assertTrue(self.auth.is_logged_in())
for f in ['logout', 'verify_email', 'reset_password',
'change_password', 'profile', 'groups']:
self.assertRaisesRegexp(HTTP, "303*", getattr(self.auth, f))
self.assertRaisesRegexp(HTTP, "401*", self.auth.impersonate)
try:
for t in ['t0_archive', 't0', 'auth_cas', 'auth_event',
'auth_membership', 'auth_permission', 'auth_group',
'auth_user']:
self.db[t].drop()
except SyntaxError as e:
# GAE doesn't support drop
pass
return
def test_get_or_create_user(self):
self.db.auth_user.insert(email='user1@test.com', username='user1', password='password_123')
self.db.commit()
# True case
self.assertEqual(self.auth.get_or_create_user({'email': 'user1@test.com',
'username': 'user1',
'password': 'password_123'
})['username'], 'user1')
# user2 doesn't exist yet and get created
self.assertEqual(self.auth.get_or_create_user({'email': 'user2@test.com',
'username': 'user2'})['username'], 'user2')
# user3 for corner case
self.assertEqual(self.auth.get_or_create_user({'first_name': 'Omer',
'last_name': 'Simpson',
'email': 'user3@test.com',
'registration_id': 'user3',
'username': 'user3'})['username'], 'user3')
# False case
self.assertEqual(self.auth.get_or_create_user({'email': ''}), None)
self.db.auth_user.truncate()
self.db.commit()
def test_login_bare(self):
# The following test case should succeed but failed as I never received the user record but False
self.auth.login_bare(username='bart@simpson.com', password='bart_password')
self.assertTrue(self.auth.is_logged_in())
# Failing login because bad_password
self.assertEqual(self.auth.login_bare(username='bart', password='wrong_password'), False)
self.db.auth_user.truncate()
def test_register_bare(self):
# corner case empty register call register_bare without args
self.assertRaises(ValueError, self.auth.register_bare)
# failing register_bare user already exist
self.assertEqual(self.auth.register_bare(username='bart', password='wrong_password'), False)
# successful register_bare
self.assertEqual(self.auth.register_bare(username='user2',
email='user2@test.com',
password='password_123')['username'], 'user2')
# raise ValueError
self.assertRaises(ValueError, self.auth.register_bare,
**dict(wrong_field_name='user3', password='password_123'))
# raise ValueError wrong email
self.assertRaises(ValueError, self.auth.register_bare,
**dict(email='user4@', password='password_123'))
self.db.auth_user.truncate()
self.db.commit()
def test_bulk_register(self):
self.auth.login_bare(username='bart', password='bart_password')
self.auth.settings.bulk_register_enabled = True
bulk_register_form = self.auth.bulk_register(max_emails=10).xml()
self.assertTrue('name="_formkey"' in bulk_register_form)
def test_change_password(self):
self.auth.login_bare(username='bart', password='bart_password')
change_password_form = getattr(self.auth, 'change_password')().xml()
self.assertTrue('name="_formkey"' in change_password_form)
def test_profile(self):
self.auth.login_bare(username='bart', password='bart_password')
profile_form = getattr(self.auth, 'profile')().xml()
self.assertTrue('name="_formkey"' in profile_form)
# def test_impersonate(self):
# # Create a user to be impersonated
# self.auth.get_or_create_user(dict(first_name='Omer',
# last_name='Simpson',
# username='omer',
# email='omer@test.com',
# password='password_omer',
# registration_key='',
# registration_id=''))
# # Create impersonate group, assign bart to impersonate group and add impersonate permission over auth_user
# self.auth.add_group('impersonate')
# self.auth.add_membership(user_id=1,
# group_id=self.db(self.db.auth_user.username == 'bart'
# ).select(self.db.auth_user.id).first().id)
# self.auth.add_permission(group_id=self.db(self.db.auth_group.role == 'impersonate'
# ).select(self.db.auth_group.id).first().id,
# name='impersonate',
# table_name='auth_user',
# record_id=0)
# # Bart login
# self.auth.login_bare(username='bart', password='bart_password')
# self.assertTrue(self.auth.is_logged_in())
# # Bart impersonate Omer
# omer_id = self.db(self.db.auth_user.username == 'omer').select(self.db.auth_user.id).first().id
# impersonate_form = self.auth.impersonate(user_id=omer_id)
# self.assertTrue(self.auth.is_impersonating())
# self.assertEqual(impersonate_form, 'test')
# def test_impersonate(self):
# request = Request(env={})
# request.application = 'a'
# request.controller = 'c'
# request.function = 'f'
# request.folder = 'applications/admin'
# response = Response()
# session = Session()
# T = translator('', 'en')
# session.connect(request, response)
# from gluon.globals import current
# current.request = request
# current.response = response
# current.session = session
# current.T = T
# db = DAL(DEFAULT_URI, check_reserved=['all'])
# auth = Auth(db)
# auth.define_tables(username=True, signature=False)
# db.define_table('t0', Field('tt'), auth.signature)
# auth.enable_record_versioning(db)
# # Create a user
# auth.get_or_create_user(dict(first_name='Bart',
# last_name='Simpson',
# username='bart',
# email='bart@simpson.com',
# password='bart_password',
# registration_key='bart',
# registration_id=''
# ))
# # Create a user to be impersonated
# auth.get_or_create_user(dict(first_name='Omer',
# last_name='Simpson',
# username='omer',
# email='omer@test.com',
# password='password_omer',
# registration_key='',
# registration_id=''))
# # Create impersonate group, assign bart to impersonate group and add impersonate permission over auth_user
# auth.add_group('impersonate')
# auth.add_membership(user_id=1,
# group_id=db(db.auth_user.username == 'bart'
# ).select(db.auth_user.id).first().id)
# auth.add_permission(group_id=db(db.auth_group.role == 'impersonate'
# ).select(db.auth_group.id).first().id,
# name='impersonate',
# table_name='auth_user',
# record_id=0)
# # Bart login
# auth.login_bare(username='bart', password='bart_password')
# # Bart impersonate Omer
# omer_id = db(db.auth_user.username == 'omer').select(db.auth_user.id).first().id
# impersonate_form = auth.impersonate(user_id=omer_id)
# self.assertTrue(auth.is_impersonating())
# self.assertEqual(impersonate_form, 'test')
# TODO: class TestCrud(unittest.TestCase):
# It deprecated so far from a priority
# TODO: class TestService(unittest.TestCase):
# TODO: class TestPluginManager(unittest.TestCase):
# TODO: class TestExpose(unittest.TestCase):
# TODO: class TestWiki(unittest.TestCase):
# TODO: class TestConfig(unittest.TestCase):
# TODO: class TestToolsFunctions(unittest.TestCase):
# For all the tools.py functions
if __name__ == '__main__':
unittest.main()
+38
View File
@@ -160,6 +160,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ({u'a': 100}, None))
rtn = IS_JSON()('spam1234')
self.assertEqual(rtn, ('spam1234', 'Invalid json'))
rtn = IS_JSON(native_json=True)('{"a": 100}')
self.assertEqual(rtn, ('{"a": 100}', None))
rtn = IS_JSON().formatter(None)
self.assertEqual(rtn, None)
rtn = IS_JSON().formatter({'a': 100})
self.assertEqual(rtn, '{"a": 100}')
rtn = IS_JSON(native_json=True).formatter({'a': 100})
self.assertEqual(rtn, {'a': 100})
def test_IS_IN_SET(self):
rtn = IS_IN_SET(['max', 'john'])('max')
@@ -174,6 +182,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=True)(None)
self.assertEqual(rtn, ([], None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=True)('')
self.assertEqual(rtn, ([], None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=True)('id1')
self.assertEqual(rtn, (['id1'], None))
rtn = IS_IN_SET(['id1', 'id2'], error_message='oops', multiple=(1,2))(None)
self.assertEqual(rtn, ([], 'oops'))
import itertools
rtn = IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
self.assertEqual(rtn, ('1', None))
@@ -181,6 +197,10 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('id1', None))
rtn = IS_IN_SET([('id1','first label'), ('id2','second label')]).options(zero=False)
self.assertEqual(rtn, [('id1', 'first label'), ('id2', 'second label')])
rtn = IS_IN_SET(['id1', 'id2']).options(zero=False)
self.assertEqual(rtn, [('id1', 'id1'), ('id2', 'id2')])
rtn = IS_IN_SET(['id2', 'id1'], sort=True).options(zero=False)
self.assertEqual(rtn, [('id1', 'id1'), ('id2', 'id2')])
def test_IS_IN_DB(self):
from gluon.dal import DAL, Field
@@ -190,6 +210,10 @@ class TestValidators(unittest.TestCase):
costanza_id = db.person.insert(name='costanza')
rtn = IS_IN_DB(db, 'person.id', '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None))
rtn = IS_IN_DB(db, db.person, '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None))
rtn = IS_IN_DB(db(db.person.id > 0), db.person, '%(name)s')(george_id)
self.assertEqual(rtn, (george_id, None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', error_message='oops')(george_id+costanza_id)
self.assertEqual(rtn, (george_id+costanza_id, 'oops'))
rtn = IS_IN_DB(db, db.person.id, '%(name)s')(george_id)
@@ -200,8 +224,14 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ([george_id,costanza_id], None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=True, delimiter=',')('%d,%d' % (george_id, costanza_id))
self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=(1,3), delimiter=',')('%d,%d' % (george_id, costanza_id))
self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), None))
rtn = IS_IN_DB(db, 'person.id', '%(name)s', multiple=(1,2), delimiter=',', error_message='oops')('%d,%d' % (george_id, costanza_id))
self.assertEqual(rtn, ( ('%d,%d' % (george_id, costanza_id)).split(','), 'oops'))
rtn = IS_IN_DB(db, db.person.id, '%(name)s', error_message='oops').options(zero=False)
self.assertEqual(sorted(rtn), [('%d' % george_id, 'george'), ('%d' % costanza_id, 'costanza')])
rtn = IS_IN_DB(db, db.person.id, db.person.name, error_message='oops', sort=True).options(zero=True)
self.assertEqual(rtn, [('', ''), ('%d' % costanza_id, 'costanza'), ('%d' % george_id, 'george')])
db.person.drop()
def test_IS_NOT_IN_DB(self):
@@ -211,8 +241,14 @@ class TestValidators(unittest.TestCase):
db.person.insert(name='george')
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops')('george')
self.assertEqual(rtn, ('george', 'oops'))
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops', allowed_override=['george'])('george')
self.assertEqual(rtn, ('george', None))
rtn = IS_NOT_IN_DB(db, 'person.name', error_message='oops')(' ')
self.assertEqual(rtn, (' ', 'oops'))
rtn = IS_NOT_IN_DB(db, 'person.name')('jerry')
self.assertEqual(rtn, ('jerry', None))
rtn = IS_NOT_IN_DB(db, 'person.name')(u'jerry')
self.assertEqual(rtn, ('jerry', None))
db.person.drop()
def test_IS_INT_IN_RANGE(self):
@@ -668,6 +704,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, ('a@b.co', None))
rtn = ANY_OF([IS_DATE(),IS_EMAIL()])('1982-12-14')
self.assertEqual(rtn, (datetime.date(1982, 12, 14), None))
rtn = ANY_OF([IS_DATE(format='%m/%d/%Y'),IS_EMAIL()]).formatter(datetime.date(1834, 12, 14))
self.assertEqual(rtn, '12/14/1834')
def test_IS_EMPTY_OR(self):
rtn = IS_EMPTY_OR(IS_EMAIL())('abc@def.com')
+12 -4
View File
@@ -1735,6 +1735,7 @@ class Auth(object):
host = host_names[0]
else:
host = 'localhost'
return host
def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user',
@@ -2575,7 +2576,7 @@ class Auth(object):
update_keys[key] = keys[key]
user.update_record(**update_keys)
elif checks:
if not 'first_name' in keys and 'first_name' in table_user.fields:
if 'first_name' not in keys and 'first_name' in table_user.fields:
guess = keys.get('email', 'anonymous').split('@')[0]
keys['first_name'] = keys.get('username', guess)
vars = table_user._filter_fields(keys)
@@ -2698,8 +2699,7 @@ class Auth(object):
fields[settings.passfield] = \
settings.table_user[settings.passfield].validate(fields[settings.passfield])[0]
if not fields.get(settings.userfield):
raise ValueError('register_bare: ' +
'userfield not provided or invalid')
raise ValueError('register_bare: userfield not provided or invalid')
user = self.get_or_create_user(fields, login=False, get=False,
update_fields=self.settings.update_fields)
if not user:
@@ -3251,12 +3251,16 @@ class Auth(object):
next = cas.logout_url(next)
current.session.auth = None
self.user = None
if self.settings.renew_session_onlogout:
current.session.renew(clear_session=not self.settings.keep_session_onlogout)
current.session.flash = self.messages.logged_out
if next is not None:
redirect(next)
def logout_bare(self):
self.logout(next=None, onlogout=None, log=None)
def register(self,
next=DEFAULT,
onvalidation=DEFAULT,
@@ -4124,6 +4128,7 @@ class Auth(object):
raise HTTP(401, "Not Authorized")
current_id = auth.user.id
requested_id = user_id
user = None
if user_id is DEFAULT:
user_id = current.request.post_vars.user_id
if user_id and user_id != self.user.id and user_id != '0':
@@ -4152,6 +4157,9 @@ class Auth(object):
return None
if requested_id is DEFAULT and not request.post_vars:
return SQLFORM.factory(Field('user_id', 'integer'))
elif not user:
return None
else:
return SQLFORM(table_user, user.id, readonly=True)
def update_groups(self):
@@ -4504,7 +4512,7 @@ class Auth(object):
ignore_common_filters=True).select(
limitby=(0, 1), orderby_on_limitby=False).first()
if record:
if hasattr(record, 'is_active') and not record.is_ctive:
if hasattr(record, 'is_active') and not record.is_active:
record.update_record(is_active=True)
id = record.id
else:
+6 -3
View File
@@ -471,8 +471,6 @@ class IS_IN_SET(Validator):
thestrset = [str(x) for x in self.theset]
failures = [x for x in values if not str(x) in thestrset]
if failures and self.theset:
if self.multiple and (value is None or value == ''):
return ([], None)
return (value, translate(self.error_message))
if self.multiple:
if isinstance(self.multiple, (tuple, list)) and \
@@ -1200,7 +1198,12 @@ class IS_EMAIL(Validator):
self.error_message = error_message
def __call__(self, value):
try:
match = self.regex.match(value)
except TypeError:
# Value may not be a string where we can look for matches.
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
match = None
if match:
domain = value.split('@')[1]
if (not self.banned or not self.banned.match(domain)) \
@@ -2608,7 +2611,7 @@ class ANY_OF(Validator):
# Use the formatter of the first subvalidator
# that validates the value and has a formatter
for validator in self.subs:
if hasattr(validator, 'formatter') and validator(value)[1] != None:
if hasattr(validator, 'formatter') and validator(value)[1] is None:
return validator.formatter(value)
+9 -10
View File
@@ -212,21 +212,20 @@ if [ "$nopassword" -eq 0 ]
then
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
fi
systemctl enable emperor.uwsgi.service
systemctl start emperor.uwsgi.service
/etc/init.d/nginx restart
/etc/init.d/nginx start
start uwsgi-emperor
echo <<EOF
you can reload uwsgi with
you can stop uwsgi and nginx with
systemctl restart emperor.uwsgi
sudo /etc/init.d/nginx stop
sudo stop uwsgi-emperor
and stop it with
and start it with
systemctl stop emperor.uwsgi
sudo /etc/init.d/nginx start
sudo start uwsgi-emperor
to reload web2py only (without restarting uwsgi)
touch /etc/uwsgi/web2py.ini
EOF