Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81d0291ce2 | ||
|
|
894ff3c140 | ||
|
|
216ce5507c | ||
|
|
068aecff93 | ||
|
|
59cbe99347 | ||
|
|
bdbc053285 | ||
|
|
d746d43be5 | ||
|
|
9a3e73031b | ||
|
|
0468c16bc2 | ||
|
|
8c5858b6b7 | ||
|
|
6400e28a85 | ||
|
|
3e46d50aa1 | ||
|
|
cb94fde80b | ||
|
|
98593eefce | ||
|
|
4bbfe70927 | ||
|
|
02a0d1c9b0 | ||
|
|
2bf0ad9268 | ||
|
|
2e63b7637a | ||
|
|
a9c5cf3072 |
14
.travis.yml
14
.travis.yml
@@ -12,8 +12,22 @@ python:
|
||||
- 'pypy'
|
||||
|
||||
install:
|
||||
- |
|
||||
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
|
||||
pushd "$PYENV_ROOT" && git pull && popd
|
||||
else
|
||||
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
|
||||
fi
|
||||
export PYPY_VERSION="5.0.1"
|
||||
"$PYENV_ROOT/bin/pyenv" install --skip-existing "pypy-$PYPY_VERSION"
|
||||
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
|
||||
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
|
||||
fi
|
||||
- pip install -e .
|
||||
|
||||
|
||||
before_script:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 2.14.1-4
|
||||
## 2.14.1-5
|
||||
|
||||
- fixed two major security issues that caused the examples app to leak information
|
||||
- new Auth(…,host_names=[…]) to prevent host header injection
|
||||
|
||||
2
Makefile
2
Makefile
@@ -32,7 +32,7 @@ update:
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.14.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.14.5-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.14.4-stable+timestamp.2016.04.12.15.44.54
|
||||
Version 2.14.5-stable+timestamp.2016.04.13.22.22.13
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
************/
|
||||
|
||||
/*** 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 {box-sizing:border-box;}
|
||||
*, *:after, *:before {border:0; margin:0; padding:0; box-sizing:inherit;}
|
||||
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}
|
||||
@@ -27,7 +28,7 @@ thead tr {background-color:#f1f1f1}
|
||||
tbody tr {border-bottom:2px solid #f1f1f1}
|
||||
td, th {padding: 5px; text-align: left; vertical-align:top}
|
||||
thead th {vertical-align:bottom}
|
||||
header, footer {with:100%}
|
||||
header, main, footer {display:block; with:100%} /* IE fix */
|
||||
|
||||
@media all and (max-width:599px) {
|
||||
h1{font-size:2em}
|
||||
|
||||
10
gluon/dal.py
10
gluon/dal.py
@@ -74,12 +74,12 @@ def _default_validators(db, field):
|
||||
return requires
|
||||
# does not get here for reference and list:reference
|
||||
if field.unique:
|
||||
requires.insert(0,validators.IS_NOT_IN_DB(db, field))
|
||||
excluded_fields = ['string','upload','text','password','boolean']
|
||||
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
|
||||
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
|
||||
if (field.notnull or field.unique) and not field_type in excluded_fields:
|
||||
requires.insert(0,validators.IS_NOT_EMPTY())
|
||||
requires.insert(0, validators.IS_NOT_EMPTY())
|
||||
elif not field.notnull and not field.unique and requires:
|
||||
requires[0] = validators.IS_EMPTY_OR(requires[0])
|
||||
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field in ('string', 'text', 'password') else None)
|
||||
return requires
|
||||
|
||||
from gluon.serializers import custom_json, xml
|
||||
@@ -93,7 +93,7 @@ DAL.uuid = lambda x: web2py_uuid()
|
||||
DAL.representers = {
|
||||
'rows_render': sqlhtml.represent,
|
||||
'rows_xml': sqlhtml.SQLTABLE
|
||||
}
|
||||
}
|
||||
DAL.Field = Field
|
||||
DAL.Table = Table
|
||||
|
||||
|
||||
@@ -173,6 +173,9 @@ class TestBareHelpers(unittest.TestCase):
|
||||
# bug check for the sanitizer for closing no-close tags
|
||||
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
|
||||
XML('<p>Test</p><br /><p>Test</p><br />'))
|
||||
# basic flatten test
|
||||
self.assertEqual(XML('<p>Test</p>').flatten(), '<p>Test</p>')
|
||||
self.assertEqual(XML('<p>Test</p>').flatten(render=lambda text, tag, attr: text), '<p>Test</p>')
|
||||
|
||||
def test_XML_pickle_unpickle(self):
|
||||
# weird test
|
||||
@@ -236,6 +239,9 @@ class TestBareHelpers(unittest.TestCase):
|
||||
# DIV(BR('<>')).xml()
|
||||
# self.assertEqual(cm.exception[0], '<br/> tags cannot have components')
|
||||
|
||||
# test .get('attrib')
|
||||
self.assertEqual(DIV('<p>Test</p>', _class="class_test").get('_class'), 'class_test')
|
||||
|
||||
def test_CAT(self):
|
||||
# Empty CAT()
|
||||
self.assertEqual(CAT().xml(), '')
|
||||
|
||||
@@ -36,10 +36,19 @@ class TestMail(unittest.TestCase):
|
||||
"""
|
||||
|
||||
class Message(object):
|
||||
|
||||
def __init__(self, sender, to, payload):
|
||||
self.sender = sender
|
||||
self.to = to
|
||||
self.payload = payload
|
||||
self._parsed_payload = None
|
||||
|
||||
@property
|
||||
def parsed_payload(self):
|
||||
if self._parsed_payload is None:
|
||||
import email
|
||||
self._parsed_payload = email.message_from_string(self.payload)
|
||||
return self._parsed_payload
|
||||
|
||||
class DummySMTP(object):
|
||||
"""
|
||||
@@ -137,6 +146,19 @@ class TestMail(unittest.TestCase):
|
||||
message = TestMail.DummySMTP.inbox.pop()
|
||||
self.assertTrue('Content-Type: text/html' in message.payload)
|
||||
|
||||
def test_alternative(self):
|
||||
mail = Mail()
|
||||
mail.settings.server = 'smtp.example.com:25'
|
||||
mail.settings.sender = 'you@example.com'
|
||||
self.assertTrue(mail.send(to=['somebody@example.com'],
|
||||
message=('Text only', '<html><pre>HTML Only</pre></html>')))
|
||||
message = TestMail.DummySMTP.inbox.pop()
|
||||
self.assertTrue(message.parsed_payload.is_multipart())
|
||||
self.assertTrue(message.parsed_payload.get_content_type() == 'multipart/alternative')
|
||||
parts = message.parsed_payload.get_payload()
|
||||
self.assertTrue('Text only' in parts[0].as_string())
|
||||
self.assertTrue('<html><pre>HTML Only</pre></html>' in parts[1].as_string())
|
||||
|
||||
def test_ssl(self):
|
||||
mail = Mail()
|
||||
mail.settings.server = 'smtp.example.com:25'
|
||||
@@ -171,9 +193,7 @@ class TestMail(unittest.TestCase):
|
||||
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)
|
||||
attachment = message.parsed_payload.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
|
||||
|
||||
@@ -232,7 +232,19 @@ class TestValidators(unittest.TestCase):
|
||||
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')])
|
||||
# Test it works with self reference
|
||||
db.define_table('category',
|
||||
Field('parent_id', 'reference category', requires=IS_EMPTY_OR(IS_IN_DB(db, 'category.id', '%(name)s'))),
|
||||
Field('name')
|
||||
)
|
||||
ret = db.category.validate_and_insert(name='seinfeld')
|
||||
self.assertFalse(list(ret.errors))
|
||||
ret = db.category.validate_and_insert(name='characters', parent_id=ret.id)
|
||||
self.assertFalse(list(ret.errors))
|
||||
rtn = IS_IN_DB(db, 'category.id', '%(name)s')(ret.id)
|
||||
self.assertEqual(rtn, (ret.id, None))
|
||||
db.person.drop()
|
||||
db.category.drop()
|
||||
|
||||
def test_IS_NOT_IN_DB(self):
|
||||
from gluon.dal import DAL, Field
|
||||
|
||||
@@ -376,8 +376,8 @@ class IS_JSON(Validator):
|
||||
def __call__(self, value):
|
||||
try:
|
||||
if self.native_json:
|
||||
simplejson.loads(value) # raises error in case of malformed json
|
||||
return (value, None) # the serialized value is not passed
|
||||
simplejson.loads(value) # raises error in case of malformed json
|
||||
return (value, None) # the serialized value is not passed
|
||||
else:
|
||||
return (simplejson.loads(value), None)
|
||||
except JSONErrors:
|
||||
@@ -459,7 +459,7 @@ class IS_IN_SET(Validator):
|
||||
|
||||
def __call__(self, value):
|
||||
if self.multiple:
|
||||
### if below was values = re.compile("[\w\-:]+").findall(str(value))
|
||||
# if below was values = re.compile("[\w\-:]+").findall(str(value))
|
||||
if not value:
|
||||
values = []
|
||||
elif isinstance(value, (tuple, list)):
|
||||
@@ -523,8 +523,8 @@ class IS_IN_DB(Validator):
|
||||
field = field._id
|
||||
elif isinstance(field, str):
|
||||
items = field.split('.')
|
||||
if len(items)==1: items+=['id']
|
||||
field = self.dbset.db[items[0]][items[1]]
|
||||
if len(items) == 1:
|
||||
field = items[0] + '.id'
|
||||
|
||||
(ktable, kfield) = str(field).split('.')
|
||||
if not label:
|
||||
@@ -534,16 +534,16 @@ class IS_IN_DB(Validator):
|
||||
label = '%%(%s)s' % str(label).split('.')[-1]
|
||||
fieldnames = regex2.findall(label)
|
||||
if kfield not in fieldnames:
|
||||
fieldnames.append(kfield) # kfield must be last
|
||||
fieldnames.append(kfield) # kfield must be last
|
||||
elif isinstance(label, Field):
|
||||
fieldnames = [label.name, kfield] # kfield must be last
|
||||
fieldnames = [label.name, kfield] # kfield must be last
|
||||
label = '%%(%s)s' % label.name
|
||||
elif callable(label):
|
||||
fieldnames = '*'
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.field = field # the lookup field
|
||||
self.fieldnames = fieldnames # fields requires to build the formatting
|
||||
|
||||
self.fieldnames = fieldnames # fields requires to build the formatting
|
||||
self.label = label
|
||||
self.ktable = ktable
|
||||
self.kfield = kfield
|
||||
@@ -621,16 +621,16 @@ class IS_IN_DB(Validator):
|
||||
if isinstance(value, list):
|
||||
values = value
|
||||
elif self.delimiter:
|
||||
values = value.split(self.delimiter) # because of autocomplete
|
||||
values = value.split(self.delimiter) # because of autocomplete
|
||||
elif value:
|
||||
values = [value]
|
||||
else:
|
||||
values = []
|
||||
|
||||
if self.field.type in ('id','integer'):
|
||||
if field.type in ('id', 'integer'):
|
||||
new_values = []
|
||||
for value in values:
|
||||
if not (isinstance(value,(int,long)) or value.isdigit()):
|
||||
if not (isinstance(value, (int, long)) or value.isdigit()):
|
||||
if self.auto_add:
|
||||
value = str(self.maybe_add(table, self.fieldnames[0], value))
|
||||
else:
|
||||
@@ -657,8 +657,8 @@ class IS_IN_DB(Validator):
|
||||
elif count(values) == len(values):
|
||||
return (values, None)
|
||||
else:
|
||||
if self.field.type in ('id','integer'):
|
||||
if isinstance(value,(int,long)) or value.isdigit():
|
||||
if field.type in ('id', 'integer'):
|
||||
if isinstance(value, (int, long)) or value.isdigit():
|
||||
value = int(value)
|
||||
elif self.auto_add:
|
||||
value = self.maybe_add(table, self.fieldnames[0], value)
|
||||
@@ -818,7 +818,7 @@ class IS_INT_IN_RANGE(Validator):
|
||||
if regex_isint.match(str(value)):
|
||||
v = int(value)
|
||||
if ((self.minimum is None or v >= self.minimum) and
|
||||
(self.maximum is None or v < self.maximum)):
|
||||
(self.maximum is None or v < self.maximum)):
|
||||
return (v, None)
|
||||
return (value, self.error_message)
|
||||
|
||||
@@ -892,7 +892,7 @@ class IS_FLOAT_IN_RANGE(Validator):
|
||||
else:
|
||||
v = float(str(value).replace(self.dot, '.'))
|
||||
if ((self.minimum is None or v >= self.minimum) and
|
||||
(self.maximum is None or v <= self.maximum)):
|
||||
(self.maximum is None or v <= self.maximum)):
|
||||
return (v, None)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
@@ -978,7 +978,7 @@ class IS_DECIMAL_IN_RANGE(Validator):
|
||||
else:
|
||||
v = decimal.Decimal(str(value).replace(self.dot, '.'))
|
||||
if ((self.minimum is None or v >= self.minimum) and
|
||||
(self.maximum is None or v <= self.maximum)):
|
||||
(self.maximum is None or v <= self.maximum)):
|
||||
return (v, None)
|
||||
except (ValueError, TypeError, decimal.InvalidOperation):
|
||||
pass
|
||||
@@ -2248,7 +2248,7 @@ class IS_DATE(Validator):
|
||||
y = '%.4i' % year
|
||||
format = format.replace('%y', y[-2:])
|
||||
format = format.replace('%Y', y)
|
||||
if year < 1900:
|
||||
if year < 1900:
|
||||
year = 2000
|
||||
d = datetime.date(year, value.month, value.day)
|
||||
return d.strftime(format)
|
||||
@@ -2347,6 +2347,7 @@ class IS_DATE_IN_RANGE(IS_DATE):
|
||||
(datetime.date(2010, 3, 3), 'oops')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
minimum=None,
|
||||
maximum=None,
|
||||
@@ -2400,6 +2401,7 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME):
|
||||
(datetime.datetime(2010, 3, 3, 0, 0), 'oops')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
minimum=None,
|
||||
maximum=None,
|
||||
@@ -2513,7 +2515,7 @@ def urlify(s, maxlen=80, keep_underscores=False):
|
||||
if keep_underscores:
|
||||
s = re.sub('\s+', '-', s) # whitespace to hyphens
|
||||
s = re.sub('[^\w\-]', '', s)
|
||||
# strip all but alphanumeric/underscore/hyphen
|
||||
# strip all but alphanumeric/underscore/hyphen
|
||||
else:
|
||||
s = re.sub('[\s_]+', '-', s) # whitespace & underscores to hyphens
|
||||
s = re.sub('[^a-z0-9\-]', '', s) # strip all but alphanumeric/hyphen
|
||||
@@ -2705,6 +2707,7 @@ class LazyCrypt(object):
|
||||
"""
|
||||
Stores a lazy password hash
|
||||
"""
|
||||
|
||||
def __init__(self, crypt, password):
|
||||
"""
|
||||
crypt is an instance of the CRYPT validator,
|
||||
@@ -2760,8 +2763,8 @@ class LazyCrypt(object):
|
||||
# LazyCrypt objects comparison
|
||||
if isinstance(stored_password, self.__class__):
|
||||
return ((self is stored_password) or
|
||||
((self.crypt.key == stored_password.crypt.key) and
|
||||
(self.password == stored_password.password)))
|
||||
((self.crypt.key == stored_password.crypt.key) and
|
||||
(self.password == stored_password.password)))
|
||||
|
||||
if self.crypt.key:
|
||||
if ':' in self.crypt.key:
|
||||
@@ -3404,14 +3407,14 @@ class IS_IPV4(Validator):
|
||||
ok = True
|
||||
if not (self.is_localhost is None or self.is_localhost ==
|
||||
(number == self.localhost)):
|
||||
ok = False
|
||||
ok = False
|
||||
if not (self.is_private is None or self.is_private ==
|
||||
(sum([private_number[0] <= number <= private_number[1]
|
||||
for private_number in self.private]) > 0)):
|
||||
ok = False
|
||||
ok = False
|
||||
if not (self.is_automatic is None or self.is_automatic ==
|
||||
(self.automatic[0] <= number <= self.automatic[1])):
|
||||
ok = False
|
||||
ok = False
|
||||
if ok:
|
||||
return (value, None)
|
||||
return (value, translate(self.error_message))
|
||||
@@ -3695,6 +3698,7 @@ class IS_IPADDRESS(Validator):
|
||||
>>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
|
||||
('2001::8ffa:fe22:b3af', 'invalid subnet provided')
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
minip='0.0.0.0',
|
||||
@@ -3757,7 +3761,7 @@ class IS_IPADDRESS(Validator):
|
||||
is_private=self.is_private,
|
||||
is_automatic=self.is_automatic,
|
||||
error_message=self.error_message
|
||||
)(value)
|
||||
)(value)
|
||||
elif self.is_ipv6 or isinstance(ip, IPv6Address):
|
||||
retval = IS_IPV6(
|
||||
is_private=self.is_private,
|
||||
@@ -3769,7 +3773,7 @@ class IS_IPADDRESS(Validator):
|
||||
is_teredo=self.is_teredo,
|
||||
subnets=self.subnets,
|
||||
error_message=self.error_message
|
||||
)(value)
|
||||
)(value)
|
||||
else:
|
||||
retval = (value, translate(self.error_message))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user