Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6128d03d4b | ||
|
|
dd27abc770 | ||
|
|
9e7b2b0b0b | ||
|
|
e09655b112 | ||
|
|
399b57d13e | ||
|
|
2941aa13da | ||
|
|
c1e5571de8 | ||
|
|
a828cb2124 | ||
|
|
5a848e51a3 | ||
|
|
8477b77c56 | ||
|
|
9bee5906d9 | ||
|
|
70ccecb1ab | ||
|
|
f2b8cdf684 | ||
|
|
ef6814d452 | ||
|
|
1abdb0b5d8 | ||
|
|
ef6eb2cb04 | ||
|
|
be3343e4af | ||
|
|
1779cf1a64 | ||
|
|
df2d09706c | ||
|
|
118e57dd15 |
@@ -1,4 +1,4 @@
|
|||||||
## 2.18.1-2.18.2
|
## 2.18.1-2.18.3
|
||||||
- pydal 19.02
|
- pydal 19.02
|
||||||
- made template its own module (Yet Another Template Language)
|
- made template its own module (Yet Another Template Language)
|
||||||
- improved python 3.4-3.7 support
|
- improved python 3.4-3.7 support
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ clean:
|
|||||||
rm -f httpserver.log
|
rm -f httpserver.log
|
||||||
rm -f parameters*.py
|
rm -f parameters*.py
|
||||||
rm -f -r applications/*/compiled
|
rm -f -r applications/*/compiled
|
||||||
find ./ -name '*~' -exec rm -f {} \;
|
find . -name '*~' -exec rm -f {} \;
|
||||||
find ./ -name '*.orig' -exec rm -f {} \;
|
find . -name '*.orig' -exec rm -f {} \;
|
||||||
find ./ -name '*.rej' -exec rm -f {} \;
|
find . -name '*.rej' -exec rm -f {} \;
|
||||||
find ./ -name '#*' -exec rm -f {} \;
|
find . -name '#*' -exec rm -f {} \;
|
||||||
find ./ -name 'Thumbs.db' -exec rm -f {} \;
|
find . -name 'Thumbs.db' -exec rm -f {} \;
|
||||||
# find ./gluon/ -name '.*' -exec rm -f {} \;
|
find . -name '.tox' -exec rm -rf {} \;
|
||||||
find ./gluon/ -name '*class' -exec rm -f {} \;
|
find . -name '__pycache__' -exec rm -rf {} \;
|
||||||
find ./applications/admin/ -name '.*' -exec rm -f {} \;
|
find gluon/ -name '*class' -exec rm -f {} \;
|
||||||
find ./applications/examples/ -name '.*' -exec rm -f {} \;
|
find applications/admin/ -name '.*' -exec rm -f {} \;
|
||||||
find ./applications/welcome/ -name '.*' -exec rm -f {} \;
|
find applications/examples/ -name '.*' -exec rm -f {} \;
|
||||||
find ./ -name '*.pyc' -exec rm -f {} \;
|
find applications/welcome/ -name '.*' -exec rm -f {} \;
|
||||||
|
find . -name '*.pyc' -exec rm -f {} \;
|
||||||
tests:
|
tests:
|
||||||
python web2py.py --run_system_tests
|
python web2py.py --run_system_tests
|
||||||
coverage:
|
coverage:
|
||||||
@@ -44,9 +45,9 @@ rmfiles:
|
|||||||
rm -rf applications/examples/uploads/*
|
rm -rf applications/examples/uploads/*
|
||||||
src:
|
src:
|
||||||
### Use semantic versioning
|
### Use semantic versioning
|
||||||
echo 'Version 2.18.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
echo 'Version 2.18.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||||
### rm -f all junk files
|
### rm -f all junk files
|
||||||
#make clean
|
make clean
|
||||||
# make rmfiles
|
# make rmfiles
|
||||||
### make welcome layout and appadmin the default
|
### make welcome layout and appadmin the default
|
||||||
cp applications/welcome/views/appadmin.html applications/admin/views
|
cp applications/welcome/views/appadmin.html applications/admin/views
|
||||||
@@ -56,7 +57,7 @@ src:
|
|||||||
### build web2py_src.zip
|
### build web2py_src.zip
|
||||||
echo '' > NEWINSTALL
|
echo '' > NEWINSTALL
|
||||||
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
||||||
cd ..; zip -r --exclude=**.git** web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
cd ..; zip -r --exclude=**.git** --exclude=**.tox** --exclude=**_pycache__** web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||||
|
|
||||||
mdp:
|
mdp:
|
||||||
make src
|
make src
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.18.2-stable+timestamp.2019.02.25.21.46.30
|
Version 2.18.3-stable+timestamp.2019.03.03.15.06.20
|
||||||
|
|||||||
@@ -216,6 +216,7 @@
|
|||||||
'Enable': 'Enable',
|
'Enable': 'Enable',
|
||||||
'Enable Close-Tag': 'Enable Close-Tag',
|
'Enable Close-Tag': 'Enable Close-Tag',
|
||||||
'Enable Code Folding': 'Enable Code Folding',
|
'Enable Code Folding': 'Enable Code Folding',
|
||||||
|
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g',
|
||||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||||
'Error': 'Error',
|
'Error': 'Error',
|
||||||
'Error logs for "%(app)s"': 'Log degli errori per "%(app)s"',
|
'Error logs for "%(app)s"': 'Log degli errori per "%(app)s"',
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
'Cannot be empty': 'Non può essere vuoto',
|
'Cannot be empty': 'Non può essere vuoto',
|
||||||
'Change password': 'Cambia Password',
|
'Change password': 'Cambia Password',
|
||||||
'change password': 'Cambia password',
|
'change password': 'Cambia password',
|
||||||
|
'Change Password': 'Change Password',
|
||||||
'Check to delete': 'Seleziona per cancellare',
|
'Check to delete': 'Seleziona per cancellare',
|
||||||
'Clear': 'Resetta',
|
'Clear': 'Resetta',
|
||||||
'Clear CACHE?': 'Resetta CACHE?',
|
'Clear CACHE?': 'Resetta CACHE?',
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
'Community': 'Community',
|
'Community': 'Community',
|
||||||
'Components and Plugins': 'Componenti and Plugin',
|
'Components and Plugins': 'Componenti and Plugin',
|
||||||
'Config.ini': 'Config.ini',
|
'Config.ini': 'Config.ini',
|
||||||
|
'Confirm Password': 'Confirm Password',
|
||||||
'contains': 'contiene',
|
'contains': 'contiene',
|
||||||
'Controller': 'Controller',
|
'Controller': 'Controller',
|
||||||
'Copyright': 'Copyright',
|
'Copyright': 'Copyright',
|
||||||
@@ -155,6 +157,7 @@
|
|||||||
'Lost Password': 'Password Smarrita',
|
'Lost Password': 'Password Smarrita',
|
||||||
'Lost password?': 'Password smarrita?',
|
'Lost password?': 'Password smarrita?',
|
||||||
'lost password?': 'dimenticato la password?',
|
'lost password?': 'dimenticato la password?',
|
||||||
|
'Lost your password?': 'Lost your password?',
|
||||||
'Main Menu': 'Menu principale',
|
'Main Menu': 'Menu principale',
|
||||||
'Manage %(action)s': 'Manage %(action)s',
|
'Manage %(action)s': 'Manage %(action)s',
|
||||||
'Manage Access Control': 'Manage Access Control',
|
'Manage Access Control': 'Manage Access Control',
|
||||||
|
|||||||
+19
-29
@@ -10,39 +10,29 @@ Web2Py framework modules
|
|||||||
========================
|
========================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
|
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FILE', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
|
||||||
|
|
||||||
#: add pydal to sys.modules
|
#: add pydal to sys.modules
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
try:
|
|
||||||
pydalpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packages", "dal")
|
MESSAGE = ("web2py depends on %s, which apparently you have not installed.\n" +
|
||||||
if pydalpath not in sys.path:
|
"Probably you cloned the repository using git without '--recursive'" +
|
||||||
sys.path.append(pydalpath)
|
"\nTo fix this, please run (from inside your web2py folder):\n\n" +
|
||||||
import pydal
|
" git submodule update --init --recursive\n\n" +
|
||||||
sys.modules['pydal'] = pydal
|
"You can also download a complete copy from http://www.web2py.com.")
|
||||||
except ImportError:
|
|
||||||
raise RuntimeError(
|
def import_packages():
|
||||||
"web2py depends on pydal, which apparently you have not installed.\n" +
|
for package, location in [('pydal', 'dal'), ('yatl', 'yatl')]:
|
||||||
"Probably you cloned the repository using git without '--recursive'" +
|
try:
|
||||||
"\nTo fix this, please run (from inside your web2py folder):\n\n" +
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packages", location)
|
||||||
" git submodule update --init --recursive\n\n" +
|
if path not in sys.path:
|
||||||
"You can also download a complete copy from http://www.web2py.com."
|
sys.path.insert(0, path)
|
||||||
)
|
sys.modules[package] = __import__(package)
|
||||||
try:
|
except ImportError:
|
||||||
yatlpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packages", "yatl")
|
raise RuntimeError(MESSAGE % package)
|
||||||
if yatlpath not in sys.path:
|
|
||||||
sys.path.append(yatlpath)
|
import_packages()
|
||||||
import yatl
|
|
||||||
sys.modules['yatl'] = yatl
|
|
||||||
except ImportError:
|
|
||||||
raise RuntimeError(
|
|
||||||
"web2py depends on yatl, which apparently you have not installed.\n" +
|
|
||||||
"Probably you cloned the repository using git without '--recursive'" +
|
|
||||||
"\nTo fix this, please run (from inside your web2py folder):\n\n" +
|
|
||||||
" git submodule update --init --recursive\n\n" +
|
|
||||||
"You can also download a complete copy from http://www.web2py.com."
|
|
||||||
)
|
|
||||||
|
|
||||||
from .globals import current
|
from .globals import current
|
||||||
from .html import *
|
from .html import *
|
||||||
|
|||||||
+2
-1
@@ -436,7 +436,8 @@ def add_path_first(path):
|
|||||||
sys.path = [path] + [p for p in sys.path if (
|
sys.path = [path] + [p for p in sys.path if (
|
||||||
not p == path and not p == (path + '/'))]
|
not p == path and not p == (path + '/'))]
|
||||||
if not global_settings.web2py_runtime_gae:
|
if not global_settings.web2py_runtime_gae:
|
||||||
site.addsitedir(path)
|
if not path in sys.path:
|
||||||
|
site.addsitedir(path)
|
||||||
|
|
||||||
|
|
||||||
def try_mkdir(path):
|
def try_mkdir(path):
|
||||||
|
|||||||
+6
-7
@@ -600,15 +600,14 @@ class Cache(object):
|
|||||||
(session_, vars_, lang_, user_agent_, public_) = \
|
(session_, vars_, lang_, user_agent_, public_) = \
|
||||||
(session, vars, lang, user_agent, public)
|
(session, vars, lang, user_agent, public)
|
||||||
|
|
||||||
|
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
||||||
if time_expire:
|
if time_expire:
|
||||||
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
|
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
|
||||||
if not session_ and public_:
|
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime(
|
||||||
cache_control += ', public'
|
'%a, %d %b %Y %H:%M:%S GMT')
|
||||||
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)
|
else:
|
||||||
).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
cache_control = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
|
||||||
else:
|
cache_control += ', public' if not session_ and public_ else ', private'
|
||||||
cache_control += ', private'
|
|
||||||
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
|
||||||
|
|
||||||
if cache_model:
|
if cache_model:
|
||||||
# figure out the correct cache key
|
# figure out the correct cache key
|
||||||
|
|||||||
+1
-1
@@ -429,7 +429,7 @@ def build_environment(request, response, session, store_current=True):
|
|||||||
c = environment['cache'] = Cache(request)
|
c = environment['cache'] = Cache(request)
|
||||||
|
|
||||||
# configure the validator to use the t translator
|
# configure the validator to use the t translator
|
||||||
Validator.translator = T
|
Validator.translator = staticmethod(lambda text: None if text is None else str(T(text)))
|
||||||
|
|
||||||
if store_current:
|
if store_current:
|
||||||
current.globalenv = environment
|
current.globalenv = environment
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=Fa
|
|||||||
|
|
||||||
locker.acquire()
|
locker.acquire()
|
||||||
try:
|
try:
|
||||||
if not application:
|
if application is None:
|
||||||
application = current.request.application
|
application = current.request.application
|
||||||
instance_name = 'redis_instance_' + application
|
instance_name = 'redis_instance_' + application
|
||||||
if not hasattr(RedisCache, instance_name):
|
if not hasattr(RedisCache, instance_name):
|
||||||
@@ -110,10 +110,10 @@ class RedisClient(object):
|
|||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.with_lock = with_lock
|
self.with_lock = with_lock
|
||||||
self.fail_gracefully = fail_gracefully
|
self.fail_gracefully = fail_gracefully
|
||||||
self.application = application
|
self.application = application or current.request.application
|
||||||
self.prefix = "w2p:cache:%s:" % application
|
self.prefix = "w2p:cache:%s:" % self.application
|
||||||
if self.request:
|
if self.request:
|
||||||
app = application
|
app = self.application
|
||||||
else:
|
else:
|
||||||
app = ''
|
app = ''
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ class RedisClient(object):
|
|||||||
else:
|
else:
|
||||||
self.storage = self.meta_storage[app]
|
self.storage = self.meta_storage[app]
|
||||||
|
|
||||||
self.cache_set_key = 'w2p:%s:___cache_set' % application
|
self.cache_set_key = 'w2p:%s:___cache_set' % self.application
|
||||||
|
|
||||||
self.r_server = redis_conn
|
self.r_server = redis_conn
|
||||||
self._release_script = register_release_lock(self.r_server)
|
self._release_script = register_release_lock(self.r_server)
|
||||||
|
|||||||
@@ -29,14 +29,16 @@ except ImportError:
|
|||||||
locker = Lock()
|
locker = Lock()
|
||||||
|
|
||||||
|
|
||||||
def RConn(*args, **vars):
|
def RConn(application=None, *args, **vars):
|
||||||
"""
|
"""
|
||||||
Istantiates a StrictRedis connection with parameters, at the first time
|
Istantiates a StrictRedis connection with parameters, at the first time
|
||||||
only
|
only
|
||||||
"""
|
"""
|
||||||
locker.acquire()
|
locker.acquire()
|
||||||
try:
|
try:
|
||||||
instance_name = 'redis_conn_' + current.request.application
|
if application is None:
|
||||||
|
application = current.request.application
|
||||||
|
instance_name = 'redis_conn_' + application
|
||||||
if not hasattr(RConn, instance_name):
|
if not hasattr(RConn, instance_name):
|
||||||
setattr(RConn, instance_name, redis.StrictRedis(*args, **vars))
|
setattr(RConn, instance_name, redis.StrictRedis(*args, **vars))
|
||||||
return getattr(RConn, instance_name)
|
return getattr(RConn, instance_name)
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class WebClient(object):
|
|||||||
self.method = 'POST' if method=='auto' else method
|
self.method = 'POST' if method=='auto' else method
|
||||||
# if there is only one form, set _formname automatically
|
# if there is only one form, set _formname automatically
|
||||||
if not '_formname' in data and len(self.forms) == 1:
|
if not '_formname' in data and len(self.forms) == 1:
|
||||||
data['_formname'] = self.forms.keys()[0]
|
data['_formname'] = next(iter(self.forms.keys())) # Use the first key
|
||||||
|
|
||||||
# if there is no formkey but it is known, set it
|
# if there is no formkey but it is known, set it
|
||||||
if '_formname' in data and not '_formkey' in data and \
|
if '_formname' in data and not '_formkey' in data and \
|
||||||
|
|||||||
+1
-1
@@ -166,7 +166,7 @@ class Highlighter(object):
|
|||||||
+ r'from|True|False)(?![a-zA-Z0-9_])'),
|
+ r'from|True|False)(?![a-zA-Z0-9_])'),
|
||||||
'color:#185369; font-weight: bold'),
|
'color:#185369; font-weight: bold'),
|
||||||
('WEB2PY',
|
('WEB2PY',
|
||||||
re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|COL|COLGROUP|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])'
|
re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|COL|COLGROUP|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FILE|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])'
|
||||||
), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'),
|
), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'),
|
||||||
('MAGIC', re.compile(r'self|None'),
|
('MAGIC', re.compile(r'self|None'),
|
||||||
'color:#185369; font-weight: bold'),
|
'color:#185369; font-weight: bold'),
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import socket
|
|||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from gluon._compat import Cookie, urllib2
|
from gluon._compat import Cookie, urllib_quote
|
||||||
# from thread import allocate_lock
|
# from thread import allocate_lock
|
||||||
|
|
||||||
from gluon.fileutils import abspath, write_file
|
from gluon.fileutils import abspath, write_file
|
||||||
|
|||||||
@@ -3284,6 +3284,8 @@ class SQLFORM(FORM):
|
|||||||
if isinstance(kwargs.get(key, None), dict):
|
if isinstance(kwargs.get(key, None), dict):
|
||||||
if table._tablename in kwargs[key]:
|
if table._tablename in kwargs[key]:
|
||||||
kwargs[key] = kwargs[key][table._tablename]
|
kwargs[key] = kwargs[key][table._tablename]
|
||||||
|
elif '*' in kwargs[key]:
|
||||||
|
kwargs[key] = kwargs[key]['*']
|
||||||
else:
|
else:
|
||||||
del kwargs[key]
|
del kwargs[key]
|
||||||
check = {}
|
check = {}
|
||||||
|
|||||||
@@ -1021,6 +1021,65 @@ class TestValidators(unittest.TestCase):
|
|||||||
rtn = IS_IMAGE(error_message='oops')(img)
|
rtn = IS_IMAGE(error_message='oops')(img)
|
||||||
self.assertEqual(rtn, (img, 'oops'))
|
self.assertEqual(rtn, (img, 'oops'))
|
||||||
|
|
||||||
|
def test_IS_FILE(self):
|
||||||
|
import cgi
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
def gen_fake(filename):
|
||||||
|
formdata_file_data = """
|
||||||
|
---123
|
||||||
|
Content-Disposition: form-data; name="key2"
|
||||||
|
|
||||||
|
value2y
|
||||||
|
---123
|
||||||
|
Content-Disposition: form-data; name="file_attach"; filename="%s"
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
this is the content of the fake file
|
||||||
|
|
||||||
|
---123--
|
||||||
|
""" % filename
|
||||||
|
formdata_file_environ = {
|
||||||
|
'CONTENT_LENGTH': str(len(formdata_file_data)),
|
||||||
|
'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
|
||||||
|
'QUERY_STRING': 'key1=value1&key2=value2x',
|
||||||
|
'REQUEST_METHOD': 'POST',
|
||||||
|
}
|
||||||
|
return cgi.FieldStorage(fp=BytesIO(to_bytes(formdata_file_data)), environ=formdata_file_environ)['file_attach']
|
||||||
|
|
||||||
|
fake = gen_fake('example.pdf')
|
||||||
|
rtn = IS_FILE(extension='pdf')(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
fake = gen_fake('example.gif')
|
||||||
|
rtn = IS_FILE(extension='pdf')(fake)
|
||||||
|
self.assertEqual(rtn, (fake, 'Enter valid filename'))
|
||||||
|
fake = gen_fake('multiple.pdf')
|
||||||
|
rtn = IS_FILE(extension=['pdf', 'png'])(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
fake = gen_fake('multiple.png')
|
||||||
|
rtn = IS_FILE(extension=['pdf', 'png'])(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
fake = gen_fake('multiple.gif')
|
||||||
|
rtn = IS_FILE(extension=['pdf', 'png'])(fake)
|
||||||
|
self.assertEqual(rtn, (fake, 'Enter valid filename'))
|
||||||
|
fake = gen_fake('backup2014.tar.gz')
|
||||||
|
rtn = IS_FILE(filename=re.compile('backup.*'), extension='tar.gz', lastdot=False)(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
fake = gen_fake('README')
|
||||||
|
rtn = IS_FILE(filename='README', extension='', case=0)(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
fake = gen_fake('readme')
|
||||||
|
rtn = IS_FILE(filename='README', extension='', case=0)(fake)
|
||||||
|
self.assertEqual(rtn, (fake, 'Enter valid filename'))
|
||||||
|
fake = gen_fake('readme')
|
||||||
|
rtn = IS_FILE(filename='README', case=2)(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
fake = gen_fake('README')
|
||||||
|
rtn = IS_FILE(filename='README', case=2)(fake)
|
||||||
|
self.assertEqual(rtn, (fake, None))
|
||||||
|
rtn = IS_FILE(extension='pdf')('example.pdf')
|
||||||
|
self.assertEqual(rtn, ('example.pdf', 'Enter valid filename'))
|
||||||
|
|
||||||
def test_IS_UPLOAD_FILENAME(self):
|
def test_IS_UPLOAD_FILENAME(self):
|
||||||
import cgi
|
import cgi
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|||||||
+162
-72
@@ -48,6 +48,7 @@ __all__ = [
|
|||||||
'IS_LIST_OF_EMAILS',
|
'IS_LIST_OF_EMAILS',
|
||||||
'IS_EMPTY_OR',
|
'IS_EMPTY_OR',
|
||||||
'IS_EXPR',
|
'IS_EXPR',
|
||||||
|
'IS_FILE',
|
||||||
'IS_FLOAT_IN_RANGE',
|
'IS_FLOAT_IN_RANGE',
|
||||||
'IS_IMAGE',
|
'IS_IMAGE',
|
||||||
'IS_IN_DB',
|
'IS_IN_DB',
|
||||||
@@ -76,11 +77,8 @@ __all__ = [
|
|||||||
def options_sorter(x, y):
|
def options_sorter(x, y):
|
||||||
return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1
|
return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1
|
||||||
|
|
||||||
def translator(text):
|
def translate(text):
|
||||||
if text is None:
|
return Validator.translator(text)
|
||||||
return None
|
|
||||||
text = Validator.translator(text)
|
|
||||||
return str(text)
|
|
||||||
|
|
||||||
class ValidationError(Exception):
|
class ValidationError(Exception):
|
||||||
|
|
||||||
@@ -215,7 +213,7 @@ class IS_MATCH(Validator):
|
|||||||
match = self.regex.search(value.encode('utf8'))
|
match = self.regex.search(value.encode('utf8'))
|
||||||
if match is not None:
|
if match is not None:
|
||||||
return (self.extract and match.group() or value, None)
|
return (self.extract and match.group() or value, None)
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_EQUAL_TO(Validator):
|
class IS_EQUAL_TO(Validator):
|
||||||
@@ -244,7 +242,7 @@ class IS_EQUAL_TO(Validator):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if value == self.expression:
|
if value == self.expression:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_EXPR(Validator):
|
class IS_EXPR(Validator):
|
||||||
@@ -278,7 +276,7 @@ class IS_EXPR(Validator):
|
|||||||
exec('__ret__=' + self.expression, self.environment)
|
exec('__ret__=' + self.expression, self.environment)
|
||||||
if self.environment['__ret__']:
|
if self.environment['__ret__']:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_LENGTH(Validator):
|
class IS_LENGTH(Validator):
|
||||||
@@ -357,7 +355,7 @@ class IS_LENGTH(Validator):
|
|||||||
return (value, None)
|
return (value, None)
|
||||||
elif self.minsize <= len(str(value)) <= self.maxsize:
|
elif self.minsize <= len(str(value)) <= self.maxsize:
|
||||||
return (str(value), None)
|
return (str(value), None)
|
||||||
return (value, translator(self.error_message) %
|
return (value, self.translator(self.error_message) %
|
||||||
dict(min=self.minsize, max=self.maxsize))
|
dict(min=self.minsize, max=self.maxsize))
|
||||||
|
|
||||||
|
|
||||||
@@ -388,7 +386,7 @@ class IS_JSON(Validator):
|
|||||||
else:
|
else:
|
||||||
return (json.loads(value), None)
|
return (json.loads(value), None)
|
||||||
except JSONErrors:
|
except JSONErrors:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
def formatter(self, value):
|
def formatter(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -478,11 +476,11 @@ class IS_IN_SET(Validator):
|
|||||||
thestrset = [str(x) for x in self.theset]
|
thestrset = [str(x) for x in self.theset]
|
||||||
failures = [x for x in values if not str(x) in thestrset]
|
failures = [x for x in values if not str(x) in thestrset]
|
||||||
if failures and self.theset:
|
if failures and self.theset:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
if self.multiple:
|
if self.multiple:
|
||||||
if isinstance(self.multiple, (tuple, list)) and \
|
if isinstance(self.multiple, (tuple, list)) and \
|
||||||
not self.multiple[0] <= len(values) < self.multiple[1]:
|
not self.multiple[0] <= len(values) < self.multiple[1]:
|
||||||
return (values, translator(self.error_message))
|
return (values, self.translator(self.error_message))
|
||||||
return (values, None)
|
return (values, None)
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
@@ -640,13 +638,13 @@ class IS_IN_DB(Validator):
|
|||||||
if self.auto_add:
|
if self.auto_add:
|
||||||
value = str(self.maybe_add(table, self.fieldnames[0], value))
|
value = str(self.maybe_add(table, self.fieldnames[0], value))
|
||||||
else:
|
else:
|
||||||
return (values, translator(self.error_message))
|
return (values, self.translator(self.error_message))
|
||||||
new_values.append(value)
|
new_values.append(value)
|
||||||
values = new_values
|
values = new_values
|
||||||
|
|
||||||
if isinstance(self.multiple, (tuple, list)) and \
|
if isinstance(self.multiple, (tuple, list)) and \
|
||||||
not self.multiple[0] <= len(values) < self.multiple[1]:
|
not self.multiple[0] <= len(values) < self.multiple[1]:
|
||||||
return (values, translator(self.error_message))
|
return (values, self.translator(self.error_message))
|
||||||
if self.theset:
|
if self.theset:
|
||||||
if not [v for v in values if v not in self.theset]:
|
if not [v for v in values if v not in self.theset]:
|
||||||
return (values, None)
|
return (values, None)
|
||||||
@@ -668,12 +666,12 @@ class IS_IN_DB(Validator):
|
|||||||
elif self.auto_add:
|
elif self.auto_add:
|
||||||
value = self.maybe_add(table, self.fieldnames[0], value)
|
value = self.maybe_add(table, self.fieldnames[0], value)
|
||||||
else:
|
else:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
if self.theset:
|
if self.theset:
|
||||||
if str(value) in self.theset:
|
if str(value) in self.theset:
|
||||||
@@ -687,7 +685,7 @@ class IS_IN_DB(Validator):
|
|||||||
return self._and(value)
|
return self._and(value)
|
||||||
else:
|
else:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_NOT_IN_DB(Validator):
|
class IS_NOT_IN_DB(Validator):
|
||||||
@@ -727,7 +725,7 @@ class IS_NOT_IN_DB(Validator):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
value = to_native(str(value))
|
value = to_native(str(value))
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
if value in self.allowed_override:
|
if value in self.allowed_override:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
(tablename, fieldname) = str(self.field).split('.')
|
(tablename, fieldname) = str(self.field).split('.')
|
||||||
@@ -740,11 +738,11 @@ class IS_NOT_IN_DB(Validator):
|
|||||||
fields = [table[f] for f in id]
|
fields = [table[f] for f in id]
|
||||||
row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).first()
|
row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).first()
|
||||||
if row and any(str(row[f]) != str(id[f]) for f in id):
|
if row and any(str(row[f]) != str(id[f]) for f in id):
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
else:
|
else:
|
||||||
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
|
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
|
||||||
if row and str(row[table._id]) != str(id):
|
if row and str(row[table._id]) != str(id):
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
|
|
||||||
@@ -760,7 +758,7 @@ def range_error_message(error_message, what_to_enter, minimum, maximum):
|
|||||||
error_message += ' less than or equal to %(max)g'
|
error_message += ' less than or equal to %(max)g'
|
||||||
if type(maximum) in integer_types:
|
if type(maximum) in integer_types:
|
||||||
maximum -= 1
|
maximum -= 1
|
||||||
return translator(error_message) % dict(min=minimum, max=maximum)
|
return translate(error_message) % dict(min=minimum, max=maximum)
|
||||||
|
|
||||||
|
|
||||||
class IS_INT_IN_RANGE(Validator):
|
class IS_INT_IN_RANGE(Validator):
|
||||||
@@ -1045,7 +1043,7 @@ class IS_NOT_EMPTY(Validator):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
value, empty = is_empty(value, empty_regex=self.empty_regex)
|
value, empty = is_empty(value, empty_regex=self.empty_regex)
|
||||||
if empty:
|
if empty:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
|
|
||||||
@@ -1201,7 +1199,7 @@ class IS_EMAIL(Validator):
|
|||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if not(isinstance(value, (basestring, unicodeT))) or not value or '@' not in value:
|
if not(isinstance(value, (basestring, unicodeT))) or not value or '@' not in value:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
body, domain = value.rsplit('@', 1)
|
body, domain = value.rsplit('@', 1)
|
||||||
|
|
||||||
@@ -1224,10 +1222,10 @@ class IS_EMAIL(Validator):
|
|||||||
if (not self.banned or not self.banned.match(domain)) \
|
if (not self.banned or not self.banned.match(domain)) \
|
||||||
and (not self.forced or self.forced.match(domain)):
|
and (not self.forced or self.forced.match(domain)):
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_LIST_OF_EMAILS(object):
|
class IS_LIST_OF_EMAILS(Validator):
|
||||||
"""
|
"""
|
||||||
Example:
|
Example:
|
||||||
Used as::
|
Used as::
|
||||||
@@ -1255,7 +1253,7 @@ class IS_LIST_OF_EMAILS(object):
|
|||||||
return (value, None)
|
return (value, None)
|
||||||
else:
|
else:
|
||||||
return (value,
|
return (value,
|
||||||
translator(self.error_message) % ', '.join(bad_emails))
|
self.translator(self.error_message) % ', '.join(bad_emails))
|
||||||
|
|
||||||
def formatter(self, value, row=None):
|
def formatter(self, value, row=None):
|
||||||
return ', '.join(value or [])
|
return ', '.join(value or [])
|
||||||
@@ -1614,7 +1612,7 @@ class IS_GENERIC_URL(Validator):
|
|||||||
# if we dont have anything or the URL misuses the '%' character
|
# if we dont have anything or the URL misuses the '%' character
|
||||||
|
|
||||||
if not value or self.GENERIC_URL.search(value):
|
if not value or self.GENERIC_URL.search(value):
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
if '#' in value:
|
if '#' in value:
|
||||||
url, fragment_part = value.split('#')
|
url, fragment_part = value.split('#')
|
||||||
@@ -1626,7 +1624,7 @@ class IS_GENERIC_URL(Validator):
|
|||||||
try:
|
try:
|
||||||
components = urlparse.urlparse(urllib_unquote(value))._asdict()
|
components = urlparse.urlparse(urllib_unquote(value))._asdict()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
# Clean up the scheme before we check it
|
# Clean up the scheme before we check it
|
||||||
scheme = components['scheme']
|
scheme = components['scheme']
|
||||||
@@ -1654,7 +1652,7 @@ class IS_GENERIC_URL(Validator):
|
|||||||
else:
|
else:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
# else the URL is not valid
|
# else the URL is not valid
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
# Sources (obtained 2017-Nov-11):
|
# Sources (obtained 2017-Nov-11):
|
||||||
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||||
@@ -2090,7 +2088,7 @@ class IS_HTTP_URL(Validator):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
# else the HTTP URL is not valid
|
# else the HTTP URL is not valid
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_URL(Validator):
|
class IS_URL(Validator):
|
||||||
@@ -2240,7 +2238,7 @@ class IS_URL(Validator):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If we are not able to convert the unicode url into a
|
# If we are not able to convert the unicode url into a
|
||||||
# US-ASCII URL, then the URL is not valid
|
# US-ASCII URL, then the URL is not valid
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
methodResult = subMethod(asciiValue)
|
methodResult = subMethod(asciiValue)
|
||||||
# if the validation of the US-ASCII version of the value failed
|
# if the validation of the US-ASCII version of the value failed
|
||||||
if not methodResult[1] is None:
|
if not methodResult[1] is None:
|
||||||
@@ -2322,7 +2320,7 @@ class IS_TIME(Validator):
|
|||||||
pass
|
pass
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return (ivalue, translator(self.error_message))
|
return (ivalue, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
# A UTC class.
|
# A UTC class.
|
||||||
@@ -2353,7 +2351,7 @@ class IS_DATE(Validator):
|
|||||||
|
|
||||||
def __init__(self, format='%Y-%m-%d',
|
def __init__(self, format='%Y-%m-%d',
|
||||||
error_message='Enter date as %(format)s'):
|
error_message='Enter date as %(format)s'):
|
||||||
self.format = translator(format)
|
self.format = self.translator(format)
|
||||||
self.error_message = str(error_message)
|
self.error_message = str(error_message)
|
||||||
self.extremes = {}
|
self.extremes = {}
|
||||||
|
|
||||||
@@ -2368,7 +2366,7 @@ class IS_DATE(Validator):
|
|||||||
return (value, None)
|
return (value, None)
|
||||||
except:
|
except:
|
||||||
self.extremes.update(IS_DATETIME.nice(self.format))
|
self.extremes.update(IS_DATETIME.nice(self.format))
|
||||||
return (ovalue, translator(self.error_message) % self.extremes)
|
return (ovalue, self.translator(self.error_message) % self.extremes)
|
||||||
|
|
||||||
def formatter(self, value):
|
def formatter(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -2417,7 +2415,7 @@ class IS_DATETIME(Validator):
|
|||||||
def __init__(self, format='%Y-%m-%d %H:%M:%S',
|
def __init__(self, format='%Y-%m-%d %H:%M:%S',
|
||||||
error_message='Enter date and time as %(format)s',
|
error_message='Enter date and time as %(format)s',
|
||||||
timezone=None):
|
timezone=None):
|
||||||
self.format = translator(format)
|
self.format = self.translator(format)
|
||||||
self.error_message = str(error_message)
|
self.error_message = str(error_message)
|
||||||
self.extremes = {}
|
self.extremes = {}
|
||||||
self.timezone = timezone
|
self.timezone = timezone
|
||||||
@@ -2436,7 +2434,7 @@ class IS_DATETIME(Validator):
|
|||||||
return (value, None)
|
return (value, None)
|
||||||
except:
|
except:
|
||||||
self.extremes.update(IS_DATETIME.nice(self.format))
|
self.extremes.update(IS_DATETIME.nice(self.format))
|
||||||
return (ovalue, translator(self.error_message) % self.extremes)
|
return (ovalue, self.translator(self.error_message) % self.extremes)
|
||||||
|
|
||||||
def formatter(self, value):
|
def formatter(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -2504,9 +2502,9 @@ class IS_DATE_IN_RANGE(IS_DATE):
|
|||||||
if msg is not None:
|
if msg is not None:
|
||||||
return (value, msg)
|
return (value, msg)
|
||||||
if self.minimum and self.minimum > value:
|
if self.minimum and self.minimum > value:
|
||||||
return (ovalue, translator(self.error_message) % self.extremes)
|
return (ovalue, self.translator(self.error_message) % self.extremes)
|
||||||
if self.maximum and value > self.maximum:
|
if self.maximum and value > self.maximum:
|
||||||
return (ovalue, translator(self.error_message) % self.extremes)
|
return (ovalue, self.translator(self.error_message) % self.extremes)
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
|
|
||||||
@@ -2560,9 +2558,9 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME):
|
|||||||
if msg is not None:
|
if msg is not None:
|
||||||
return (value, msg)
|
return (value, msg)
|
||||||
if self.minimum and self.minimum > value:
|
if self.minimum and self.minimum > value:
|
||||||
return (ovalue, translator(self.error_message) % self.extremes)
|
return (ovalue, self.translator(self.error_message) % self.extremes)
|
||||||
if self.maximum and value > self.maximum:
|
if self.maximum and value > self.maximum:
|
||||||
return (ovalue, translator(self.error_message) % self.extremes)
|
return (ovalue, self.translator(self.error_message) % self.extremes)
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
|
|
||||||
@@ -2580,10 +2578,10 @@ class IS_LIST_OF(Validator):
|
|||||||
ivalue = [ivalue]
|
ivalue = [ivalue]
|
||||||
ivalue = [i for i in ivalue if str(i).strip()]
|
ivalue = [i for i in ivalue if str(i).strip()]
|
||||||
if self.minimum is not None and len(ivalue) < self.minimum:
|
if self.minimum is not None and len(ivalue) < self.minimum:
|
||||||
return (ivalue, translator(self.error_message or 'Minimum length is %(min)s') %
|
return (ivalue, self.translator(self.error_message or 'Minimum length is %(min)s') %
|
||||||
dict(min=self.minimum, max=self.maximum))
|
dict(min=self.minimum, max=self.maximum))
|
||||||
if self.maximum is not None and len(ivalue) > self.maximum:
|
if self.maximum is not None and len(ivalue) > self.maximum:
|
||||||
return (ivalue, translator(self.error_message or 'Maximum length is %(max)s') %
|
return (ivalue, self.translator(self.error_message or 'Maximum length is %(max)s') %
|
||||||
dict(min=self.minimum, max=self.maximum))
|
dict(min=self.minimum, max=self.maximum))
|
||||||
new_value = []
|
new_value = []
|
||||||
other = self.other
|
other = self.other
|
||||||
@@ -2722,7 +2720,7 @@ class IS_SLUG(Validator):
|
|||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if self.check and value != urlify(value, self.maxlen, self.keep_underscores):
|
if self.check and value != urlify(value, self.maxlen, self.keep_underscores):
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
return (urlify(value, self.maxlen, self.keep_underscores), None)
|
return (urlify(value, self.maxlen, self.keep_underscores), None)
|
||||||
|
|
||||||
|
|
||||||
@@ -2751,7 +2749,7 @@ class ANY_OF(Validator):
|
|||||||
if error is None:
|
if error is None:
|
||||||
break
|
break
|
||||||
if error is not None and self.error_message is not None:
|
if error is not None and self.error_message is not None:
|
||||||
error = translator(self.error_message)
|
error = self.translator(self.error_message)
|
||||||
return value, error
|
return value, error
|
||||||
|
|
||||||
def formatter(self, value):
|
def formatter(self, value):
|
||||||
@@ -2937,7 +2935,7 @@ class LazyCrypt(object):
|
|||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
|
||||||
class CRYPT(object):
|
class CRYPT(Validator):
|
||||||
"""
|
"""
|
||||||
Examples:
|
Examples:
|
||||||
Use as::
|
Use as::
|
||||||
@@ -3039,7 +3037,7 @@ class CRYPT(object):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
v = value and str(value)[:self.max_length]
|
v = value and str(value)[:self.max_length]
|
||||||
if not v or len(v) < self.min_length:
|
if not v or len(v) < self.min_length:
|
||||||
return ('', translator(self.error_message))
|
return ('', self.translator(self.error_message))
|
||||||
if isinstance(value, LazyCrypt):
|
if isinstance(value, LazyCrypt):
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (LazyCrypt(self, value), None)
|
return (LazyCrypt(self, value), None)
|
||||||
@@ -3084,7 +3082,7 @@ def calc_entropy(string):
|
|||||||
return round(entropy, 2)
|
return round(entropy, 2)
|
||||||
|
|
||||||
|
|
||||||
class IS_STRONG(object):
|
class IS_STRONG(Validator):
|
||||||
"""
|
"""
|
||||||
Examples:
|
Examples:
|
||||||
Use as::
|
Use as::
|
||||||
@@ -3149,49 +3147,49 @@ class IS_STRONG(object):
|
|||||||
if self.entropy is not None:
|
if self.entropy is not None:
|
||||||
entropy = calc_entropy(value)
|
entropy = calc_entropy(value)
|
||||||
if entropy < self.entropy:
|
if entropy < self.entropy:
|
||||||
failures.append(translator("Entropy (%(have)s) less than required (%(need)s)")
|
failures.append(self.translator("Entropy (%(have)s) less than required (%(need)s)")
|
||||||
% dict(have=entropy, need=self.entropy))
|
% dict(have=entropy, need=self.entropy))
|
||||||
if isinstance(self.min, int) and self.min > 0:
|
if isinstance(self.min, int) and self.min > 0:
|
||||||
if not len(value) >= self.min:
|
if not len(value) >= self.min:
|
||||||
failures.append(translator("Minimum length is %s") % self.min)
|
failures.append(self.translator("Minimum length is %s") % self.min)
|
||||||
if isinstance(self.max, int) and self.max > 0:
|
if isinstance(self.max, int) and self.max > 0:
|
||||||
if not len(value) <= self.max:
|
if not len(value) <= self.max:
|
||||||
failures.append(translator("Maximum length is %s") % self.max)
|
failures.append(self.translator("Maximum length is %s") % self.max)
|
||||||
if isinstance(self.special, int):
|
if isinstance(self.special, int):
|
||||||
all_special = [ch in value for ch in self.specials]
|
all_special = [ch in value for ch in self.specials]
|
||||||
if self.special > 0:
|
if self.special > 0:
|
||||||
if not all_special.count(True) >= self.special:
|
if not all_special.count(True) >= self.special:
|
||||||
failures.append(translator("Must include at least %s of the following: %s")
|
failures.append(self.translator("Must include at least %s of the following: %s")
|
||||||
% (self.special, self.specials))
|
% (self.special, self.specials))
|
||||||
elif self.special is 0:
|
elif self.special is 0:
|
||||||
if len(all_special) > 0:
|
if len(all_special) > 0:
|
||||||
failures.append(translator("May not contain any of the following: %s")
|
failures.append(self.translator("May not contain any of the following: %s")
|
||||||
% self.specials)
|
% self.specials)
|
||||||
if self.invalid:
|
if self.invalid:
|
||||||
all_invalid = [ch in value for ch in self.invalid]
|
all_invalid = [ch in value for ch in self.invalid]
|
||||||
if all_invalid.count(True) > 0:
|
if all_invalid.count(True) > 0:
|
||||||
failures.append(translator("May not contain any of the following: %s")
|
failures.append(self.translator("May not contain any of the following: %s")
|
||||||
% self.invalid)
|
% self.invalid)
|
||||||
if isinstance(self.upper, int):
|
if isinstance(self.upper, int):
|
||||||
all_upper = re.findall("[A-Z]", value)
|
all_upper = re.findall("[A-Z]", value)
|
||||||
if self.upper > 0:
|
if self.upper > 0:
|
||||||
if not len(all_upper) >= self.upper:
|
if not len(all_upper) >= self.upper:
|
||||||
failures.append(translator("Must include at least %s uppercase")
|
failures.append(self.translator("Must include at least %s uppercase")
|
||||||
% str(self.upper))
|
% str(self.upper))
|
||||||
elif self.upper is 0:
|
elif self.upper is 0:
|
||||||
if len(all_upper) > 0:
|
if len(all_upper) > 0:
|
||||||
failures.append(
|
failures.append(
|
||||||
translator("May not include any uppercase letters"))
|
self.translator("May not include any uppercase letters"))
|
||||||
if isinstance(self.lower, int):
|
if isinstance(self.lower, int):
|
||||||
all_lower = re.findall("[a-z]", value)
|
all_lower = re.findall("[a-z]", value)
|
||||||
if self.lower > 0:
|
if self.lower > 0:
|
||||||
if not len(all_lower) >= self.lower:
|
if not len(all_lower) >= self.lower:
|
||||||
failures.append(translator("Must include at least %s lowercase")
|
failures.append(self.translator("Must include at least %s lowercase")
|
||||||
% str(self.lower))
|
% str(self.lower))
|
||||||
elif self.lower is 0:
|
elif self.lower is 0:
|
||||||
if len(all_lower) > 0:
|
if len(all_lower) > 0:
|
||||||
failures.append(
|
failures.append(
|
||||||
translator("May not include any lowercase letters"))
|
self.translator("May not include any lowercase letters"))
|
||||||
if isinstance(self.number, int):
|
if isinstance(self.number, int):
|
||||||
all_number = re.findall("[0-9]", value)
|
all_number = re.findall("[0-9]", value)
|
||||||
if self.number > 0:
|
if self.number > 0:
|
||||||
@@ -3199,11 +3197,11 @@ class IS_STRONG(object):
|
|||||||
if self.number > 1:
|
if self.number > 1:
|
||||||
numbers = "numbers"
|
numbers = "numbers"
|
||||||
if not len(all_number) >= self.number:
|
if not len(all_number) >= self.number:
|
||||||
failures.append(translator("Must include at least %s %s")
|
failures.append(self.translator("Must include at least %s %s")
|
||||||
% (str(self.number), numbers))
|
% (str(self.number), numbers))
|
||||||
elif self.number is 0:
|
elif self.number is 0:
|
||||||
if len(all_number) > 0:
|
if len(all_number) > 0:
|
||||||
failures.append(translator("May not include any numbers"))
|
failures.append(self.translator("May not include any numbers"))
|
||||||
if len(failures) == 0:
|
if len(failures) == 0:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
if not self.error_message:
|
if not self.error_message:
|
||||||
@@ -3211,7 +3209,7 @@ class IS_STRONG(object):
|
|||||||
return (value, '|'.join(map(str, failures)))
|
return (value, '|'.join(map(str, failures)))
|
||||||
return (value, ', '.join(failures))
|
return (value, ', '.join(failures))
|
||||||
else:
|
else:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_IMAGE(Validator):
|
class IS_IMAGE(Validator):
|
||||||
@@ -3303,7 +3301,7 @@ class IS_IMAGE(Validator):
|
|||||||
value.file.seek(0)
|
value.file.seek(0)
|
||||||
return (value, None)
|
return (value, None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
def __bmp(self, stream):
|
def __bmp(self, stream):
|
||||||
if stream.read(2) == b'BM':
|
if stream.read(2) == b'BM':
|
||||||
@@ -3339,8 +3337,100 @@ class IS_IMAGE(Validator):
|
|||||||
return (-1, -1)
|
return (-1, -1)
|
||||||
|
|
||||||
|
|
||||||
|
class IS_FILE(Validator):
|
||||||
|
"""
|
||||||
|
Checks if name and extension of file uploaded through file input matches
|
||||||
|
given criteria.
|
||||||
|
|
||||||
|
Does *not* ensure the file type in any way. Returns validation failure
|
||||||
|
if no data was uploaded.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: string/compiled regex or a list of strings/regex of valid filenames
|
||||||
|
extension: string/compiled regex or a list of strings/regex of valid extensions
|
||||||
|
lastdot: which dot should be used as a filename / extension separator:
|
||||||
|
True means last dot, eg. file.jpg.png -> file.jpg / png
|
||||||
|
False means first dot, eg. file.tar.gz -> file / tar.gz
|
||||||
|
case: 0 - keep the case, 1 - transform the string into lowercase (default),
|
||||||
|
2 - transform the string into uppercase
|
||||||
|
|
||||||
|
If there is no dot present, extension checks will be done against empty
|
||||||
|
string and filename checks against whole value.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Check if file has a pdf extension (case insensitive):
|
||||||
|
|
||||||
|
INPUT(_type='file', _name='name',
|
||||||
|
requires=IS_FILE(extension='pdf'))
|
||||||
|
|
||||||
|
Check if file is called 'thumbnail' and has a jpg or png extension
|
||||||
|
(case insensitive):
|
||||||
|
|
||||||
|
INPUT(_type='file', _name='name',
|
||||||
|
requires=IS_FILE(filename='thumbnail',
|
||||||
|
extension=['jpg', 'png']))
|
||||||
|
|
||||||
|
Check if file has a tar.gz extension and name starting with backup:
|
||||||
|
|
||||||
|
INPUT(_type='file', _name='name',
|
||||||
|
requires=IS_FILE(filename=re.compile('backup.*'),
|
||||||
|
extension='tar.gz', lastdot=False))
|
||||||
|
|
||||||
|
Check if file has no extension and name matching README
|
||||||
|
(case sensitive):
|
||||||
|
|
||||||
|
INPUT(_type='file', _name='name',
|
||||||
|
requires=IS_FILE(filename='README',
|
||||||
|
extension='', case=0)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filename=None, extension=None, lastdot=True, case=1,
|
||||||
|
error_message='Enter valid filename'):
|
||||||
|
self.filename = filename
|
||||||
|
self.extension = extension
|
||||||
|
self.lastdot = lastdot
|
||||||
|
self.case = case
|
||||||
|
self.error_message = error_message
|
||||||
|
|
||||||
|
def match(self, value1, value2):
|
||||||
|
if isinstance(value1, (list, tuple)):
|
||||||
|
for v in value1:
|
||||||
|
if self.match(v, value2):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
elif isinstance(value1, type(regex_isint)):
|
||||||
|
return value1.match(value2)
|
||||||
|
elif isinstance(value1, str):
|
||||||
|
return value1 == value2
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
try:
|
||||||
|
string = value.filename
|
||||||
|
except:
|
||||||
|
return (value, self.translator(self.error_message))
|
||||||
|
if self.case == 1:
|
||||||
|
string = string.lower()
|
||||||
|
elif self.case == 2:
|
||||||
|
string = string.upper()
|
||||||
|
if self.lastdot:
|
||||||
|
dot = string.rfind('.')
|
||||||
|
else:
|
||||||
|
dot = string.find('.')
|
||||||
|
if dot == -1:
|
||||||
|
dot = len(string)
|
||||||
|
if self.filename and not self.match(self.filename, string[:dot]):
|
||||||
|
return (value, self.translator(self.error_message))
|
||||||
|
elif self.extension and not self.match(self.extension, string[dot + 1:]):
|
||||||
|
return (value, self.translator(self.error_message))
|
||||||
|
else:
|
||||||
|
return (value, None)
|
||||||
|
|
||||||
|
|
||||||
class IS_UPLOAD_FILENAME(Validator):
|
class IS_UPLOAD_FILENAME(Validator):
|
||||||
"""
|
"""
|
||||||
|
For new applications, use IS_FILE().
|
||||||
|
|
||||||
Checks if name and extension of file uploaded through file input matches
|
Checks if name and extension of file uploaded through file input matches
|
||||||
given criteria.
|
given criteria.
|
||||||
|
|
||||||
@@ -3396,7 +3486,7 @@ class IS_UPLOAD_FILENAME(Validator):
|
|||||||
try:
|
try:
|
||||||
string = value.filename
|
string = value.filename
|
||||||
except:
|
except:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
if self.case == 1:
|
if self.case == 1:
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
elif self.case == 2:
|
elif self.case == 2:
|
||||||
@@ -3408,9 +3498,9 @@ class IS_UPLOAD_FILENAME(Validator):
|
|||||||
if dot == -1:
|
if dot == -1:
|
||||||
dot = len(string)
|
dot = len(string)
|
||||||
if self.filename and not self.filename.match(string[:dot]):
|
if self.filename and not self.filename.match(string[:dot]):
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
elif self.extension and not self.extension.match(string[dot + 1:]):
|
elif self.extension and not self.extension.match(string[dot + 1:]):
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
else:
|
else:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
@@ -3580,7 +3670,7 @@ class IS_IPV4(Validator):
|
|||||||
ok = False
|
ok = False
|
||||||
if ok:
|
if ok:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_IPV6(Validator):
|
class IS_IPV6(Validator):
|
||||||
@@ -3677,7 +3767,7 @@ class IS_IPV6(Validator):
|
|||||||
ip = ipaddress.IPv6Address(to_unicode(value))
|
ip = ipaddress.IPv6Address(to_unicode(value))
|
||||||
ok = True
|
ok = True
|
||||||
except ipaddress.AddressValueError:
|
except ipaddress.AddressValueError:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
if self.subnets:
|
if self.subnets:
|
||||||
# iterate through self.subnets to see if value is a member
|
# iterate through self.subnets to see if value is a member
|
||||||
@@ -3688,7 +3778,7 @@ class IS_IPV6(Validator):
|
|||||||
try:
|
try:
|
||||||
ipnet = ipaddress.IPv6Network(to_unicode(network))
|
ipnet = ipaddress.IPv6Network(to_unicode(network))
|
||||||
except (ipaddress.NetmaskValueError, ipaddress.AddressValueError):
|
except (ipaddress.NetmaskValueError, ipaddress.AddressValueError):
|
||||||
return (value, translator('invalid subnet provided'))
|
return (value, self.translator('invalid subnet provided'))
|
||||||
if ip in ipnet:
|
if ip in ipnet:
|
||||||
ok = True
|
ok = True
|
||||||
|
|
||||||
@@ -3719,7 +3809,7 @@ class IS_IPV6(Validator):
|
|||||||
if ok:
|
if ok:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
class IS_IPADDRESS(Validator):
|
class IS_IPADDRESS(Validator):
|
||||||
@@ -3901,12 +3991,12 @@ class IS_IPADDRESS(Validator):
|
|||||||
try:
|
try:
|
||||||
ip = IPAddress(to_unicode(value))
|
ip = IPAddress(to_unicode(value))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return (value, translator(self.error_message))
|
return (value, self.translator(self.error_message))
|
||||||
|
|
||||||
if self.is_ipv4 and isinstance(ip, IPv6Address):
|
if self.is_ipv4 and isinstance(ip, IPv6Address):
|
||||||
retval = (value, translator(self.error_message))
|
retval = (value, self.translator(self.error_message))
|
||||||
elif self.is_ipv6 and isinstance(ip, IPv4Address):
|
elif self.is_ipv6 and isinstance(ip, IPv4Address):
|
||||||
retval = (value, translator(self.error_message))
|
retval = (value, self.translator(self.error_message))
|
||||||
elif self.is_ipv4 or isinstance(ip, IPv4Address):
|
elif self.is_ipv4 or isinstance(ip, IPv4Address):
|
||||||
retval = IS_IPV4(
|
retval = IS_IPV4(
|
||||||
minip=self.minip,
|
minip=self.minip,
|
||||||
@@ -3930,6 +4020,6 @@ class IS_IPADDRESS(Validator):
|
|||||||
error_message=self.error_message
|
error_message=self.error_message
|
||||||
)(value)
|
)(value)
|
||||||
else:
|
else:
|
||||||
retval = (value, translator(self.error_message))
|
retval = (value, self.translator(self.error_message))
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|||||||
Reference in New Issue
Block a user