Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d06a1f9dc6 | ||
|
|
c06fc67064 | ||
|
|
bd167aa94a | ||
|
|
c9a71a7055 | ||
|
|
02e50cadbc | ||
|
|
9f69ab9753 | ||
|
|
56d10a40c6 | ||
|
|
76b09393e4 | ||
|
|
e880da0d0e | ||
|
|
9694c66703 | ||
|
|
f22e3a7624 | ||
|
|
bd7ee209ea | ||
|
|
5e6c3dba81 | ||
|
|
135f41041d | ||
|
|
1d0f322d09 | ||
|
|
892fba9e54 | ||
|
|
6e5c8b62cc | ||
|
|
852a9e0127 | ||
|
|
485f868cd1 | ||
|
|
de8b2a477b | ||
|
|
8533fa0d00 | ||
|
|
0b41ed36f9 |
@@ -1,4 +1,5 @@
|
||||
## 2.15.1-3
|
||||
## 2.15.1-4
|
||||
- pydal 17.08
|
||||
- dropped support for python 2.6
|
||||
- dropped web shell
|
||||
- experimental python 3 support
|
||||
|
||||
2
Makefile
2
Makefile
@@ -32,7 +32,7 @@ update:
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.15.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.15.4-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.15.3-stable+timestamp.2017.08.07.07.32.04
|
||||
Version 2.15.4-stable+timestamp.2017.09.01.22.38.25
|
||||
|
||||
@@ -265,13 +265,17 @@
|
||||
}
|
||||
});
|
||||
/* help preventing double form submission for normal form (not LOADed) */
|
||||
$(doc).on('submit', 'form', function () {
|
||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
||||
web2py.disableElement(submit_button);
|
||||
$(doc).on('submit', 'form', function (e) {
|
||||
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||
submit_buttons.each(function() {
|
||||
web2py.disableElement($(this));
|
||||
})
|
||||
/* safeguard in case the form doesn't trigger a refresh,
|
||||
see https://github.com/web2py/web2py/issues/1100 */
|
||||
setTimeout(function () {
|
||||
web2py.enableElement(submit_button);
|
||||
submit_buttons.each(function() {
|
||||
web2py.enableElement($(this));
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
doc.ajaxSuccess(function (e, xhr) {
|
||||
|
||||
@@ -561,23 +561,39 @@ class AuthAPI(object):
|
||||
del self.user_groups[group_id]
|
||||
return ret
|
||||
|
||||
def has_membership(self, group_id=None, user_id=None, role=None):
|
||||
def has_membership(self, group_id=None, user_id=None, role=None, cached=False):
|
||||
"""
|
||||
Checks if user is member of group_id or role
|
||||
|
||||
NOTE: To avoid database query at each page load that use auth.has_membership, someone can use cached=True.
|
||||
If cached is set to True has_membership() check group_id or role only against auth.user_groups variable
|
||||
which is populated properly only at login time. This means that if an user membership change during a
|
||||
given session the user has to log off and log in again in order to auth.user_groups to be properly
|
||||
recreated and reflecting the user membership modification. There is one exception to this log off and
|
||||
log in process which is in case that the user change his own membership, in this case auth.user_groups
|
||||
can be properly update for the actual connected user because web2py has access to the proper session
|
||||
user_groups variable. To make use of this exception someone has to place an "auth.update_groups()"
|
||||
instruction in his app code to force auth.user_groups to be updated. As mention this will only work if it
|
||||
the user itself that change it membership not if another user, let say an administrator, change someone
|
||||
else's membership.
|
||||
"""
|
||||
group_id = group_id or self.id_group(role)
|
||||
try:
|
||||
group_id = int(group_id)
|
||||
except:
|
||||
group_id = self.id_group(group_id) # interpret group_id as a role
|
||||
if not user_id and self.user:
|
||||
user_id = self.user.id
|
||||
membership = self.table_membership()
|
||||
if group_id and user_id and self.db((membership.user_id == user_id) &
|
||||
(membership.group_id == group_id)).select():
|
||||
r = True
|
||||
if cached:
|
||||
id_role = group_id or role
|
||||
r = (user_id and id_role in self.user_groups.values()) or (user_id and id_role in self.user_groups)
|
||||
else:
|
||||
r = False
|
||||
group_id = group_id or self.id_group(role)
|
||||
try:
|
||||
group_id = int(group_id)
|
||||
except:
|
||||
group_id = self.id_group(group_id) # interpret group_id as a role
|
||||
membership = self.table_membership()
|
||||
if group_id and user_id and self.db((membership.user_id == user_id) &
|
||||
(membership.group_id == group_id)).select():
|
||||
r = True
|
||||
else:
|
||||
r = False
|
||||
self.log_event(self.messages['has_membership_log'],
|
||||
dict(user_id=user_id, group_id=group_id, check=r))
|
||||
return r
|
||||
|
||||
@@ -676,6 +676,7 @@ def run_view_in(environment):
|
||||
badv = 'invalid view (%s)' % view
|
||||
patterns = response.get('generic_patterns')
|
||||
layer = None
|
||||
scode = None
|
||||
if patterns:
|
||||
regex = re_compile('|'.join(map(fnmatch.translate, patterns)))
|
||||
short_action = '%(controller)s/%(function)s.%(extension)s' % request
|
||||
@@ -718,12 +719,14 @@ def run_view_in(environment):
|
||||
|
||||
# if the view is not compiled
|
||||
if not layer:
|
||||
# Compile the template
|
||||
ccode = parse_template(view,
|
||||
# Parse template
|
||||
scode = parse_template(view,
|
||||
pjoin(folder, 'views'),
|
||||
context=environment)
|
||||
# Compile template
|
||||
ccode = compile2(scode, filename)
|
||||
layer = filename
|
||||
restricted(ccode, environment, layer=layer)
|
||||
restricted(ccode, environment, layer=layer, scode=scode)
|
||||
# parse_template saves everything in response body
|
||||
return environment['response'].body.getvalue()
|
||||
|
||||
|
||||
@@ -673,3 +673,23 @@ def simple_detect(agent):
|
||||
if os_version:
|
||||
os = " ".join((os, os_version))
|
||||
return os, browser
|
||||
|
||||
|
||||
class mobilize(object):
|
||||
"""
|
||||
Decorator for controller functions so they use different views for mobile devices.
|
||||
|
||||
WARNING: If you update httpagentparser make sure to leave mobilize for
|
||||
backwards compatibility.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __call__(self):
|
||||
from gluon import current
|
||||
user_agent = current.request.user_agent()
|
||||
if user_agent.is_mobile:
|
||||
items = current.response.view.split('.')
|
||||
items.insert(-1, 'mobile')
|
||||
current.response.view = '.'.join(items)
|
||||
return self.func()
|
||||
|
||||
100
gluon/globals.py
100
gluon/globals.py
@@ -331,11 +331,16 @@ class Request(Storage):
|
||||
user_agent = session._user_agent
|
||||
if user_agent:
|
||||
return user_agent
|
||||
user_agent = user_agent_parser.detect(self.env.http_user_agent)
|
||||
http_user_agent = self.env.http_user_agent
|
||||
user_agent = user_agent_parser.detect(http_user_agent)
|
||||
for key, value in user_agent.items():
|
||||
if isinstance(value, dict):
|
||||
user_agent[key] = Storage(value)
|
||||
user_agent = session._user_agent = Storage(user_agent)
|
||||
user_agent = Storage(user_agent)
|
||||
user_agent.is_mobile = 'Mobile' in http_user_agent
|
||||
user_agent.is_tablet = 'Tablet' in http_user_agent
|
||||
session._user_agent = user_agent
|
||||
|
||||
return user_agent
|
||||
|
||||
def requires_https(self):
|
||||
@@ -468,45 +473,67 @@ class Response(Storage):
|
||||
response.cache_includes = (cache_method, time_expire).
|
||||
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
||||
"""
|
||||
app = current.request.application
|
||||
|
||||
# We start by building a files list in which adjacent files internal to
|
||||
# the application are placed in a list inside the files list.
|
||||
#
|
||||
# We will only minify and concat adjacent internal files as there's
|
||||
# no way to know if changing the order with which the files are apppended
|
||||
# will break things since the order matters in both CSS and JS and
|
||||
# internal files may be interleaved with external ones.
|
||||
files = []
|
||||
ext_files = []
|
||||
has_js = has_css = False
|
||||
# For the adjacent list we're going to use storage List to both distinguish
|
||||
# from the regular list and so we can add attributes
|
||||
internal = List()
|
||||
internal.has_js = False
|
||||
internal.has_css = False
|
||||
done = set() # to remove duplicates
|
||||
for item in self.files:
|
||||
if isinstance(item, (list, tuple)):
|
||||
ext_files.append(item)
|
||||
if not isinstance(item, list):
|
||||
if item in done:
|
||||
continue
|
||||
done.add(item)
|
||||
if isinstance(item, (list, tuple)) or not item.startswith('/' + app): # also consider items in other web2py applications to be external
|
||||
if internal:
|
||||
files.append(internal)
|
||||
internal = List()
|
||||
internal.has_js = False
|
||||
internal.has_css = False
|
||||
files.append(item)
|
||||
continue
|
||||
if extensions and not item.rpartition('.')[2] in extensions:
|
||||
continue
|
||||
if item in files:
|
||||
continue
|
||||
internal.append(item)
|
||||
if item.endswith('.js'):
|
||||
has_js = True
|
||||
internal.has_js = True
|
||||
if item.endswith('.css'):
|
||||
has_css = True
|
||||
files.append(item)
|
||||
internal.has_css = True
|
||||
if internal:
|
||||
files.append(internal)
|
||||
|
||||
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
|
||||
# cache for 5 minutes by default
|
||||
key = hashlib_md5(repr(files)).hexdigest()
|
||||
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
||||
|
||||
def call_minify(files=files):
|
||||
return minify.minify(files,
|
||||
URL('static', 'temp'),
|
||||
current.request.folder,
|
||||
self.optimize_css,
|
||||
self.optimize_js)
|
||||
if cache:
|
||||
cache_model, time_expire = cache
|
||||
files = cache_model('response.files.minified/' + key,
|
||||
call_minify,
|
||||
time_expire)
|
||||
else:
|
||||
files = call_minify()
|
||||
|
||||
files.extend(ext_files)
|
||||
s = []
|
||||
for item in files:
|
||||
# We're done we can now minify
|
||||
if have_minify:
|
||||
for i, f in enumerate(files):
|
||||
if isinstance(f, List) and ((self.optimize_css and f.has_css) or (self.optimize_js and f.has_js)):
|
||||
# cache for 5 minutes by default
|
||||
key = hashlib_md5(repr(f)).hexdigest()
|
||||
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
||||
def call_minify(files=f):
|
||||
return List(minify.minify(files,
|
||||
URL('static', 'temp'),
|
||||
current.request.folder,
|
||||
self.optimize_css,
|
||||
self.optimize_js))
|
||||
if cache:
|
||||
cache_model, time_expire = cache
|
||||
files[i] = cache_model('response.files.minified/' + key,
|
||||
call_minify,
|
||||
time_expire)
|
||||
else:
|
||||
files[i] = call_minify()
|
||||
|
||||
def static_map(s, item):
|
||||
if isinstance(item, str):
|
||||
f = item.lower().split('?')[0]
|
||||
ext = f.rpartition('.')[2]
|
||||
@@ -526,6 +553,13 @@ class Response(Storage):
|
||||
if tmpl:
|
||||
s.append(tmpl % item[1])
|
||||
|
||||
s = []
|
||||
for item in files:
|
||||
if isinstance(item, List):
|
||||
for f in item:
|
||||
static_map(s, f)
|
||||
else:
|
||||
static_map(s, item)
|
||||
self.write(''.join(s), escape=False)
|
||||
|
||||
def stream(self,
|
||||
|
||||
Submodule gluon/packages/dal updated: c707d55899...f9f0fdfc9a
@@ -205,7 +205,7 @@ def compile2(code, layer):
|
||||
return compile(code, layer, 'exec')
|
||||
|
||||
|
||||
def restricted(ccode, environment=None, layer='Unknown'):
|
||||
def restricted(ccode, environment=None, layer='Unknown', scode=None):
|
||||
"""
|
||||
Runs code in environment and returns the output. If an exception occurs
|
||||
in code it raises a RestrictedError containing the traceback. Layer is
|
||||
@@ -230,7 +230,9 @@ def restricted(ccode, environment=None, layer='Unknown'):
|
||||
sys.excepthook(etype, evalue, tb)
|
||||
del tb
|
||||
output = "%s %s" % (etype, evalue)
|
||||
raise RestrictedError(layer, ccode, output, environment)
|
||||
# Save source code in ticket when available
|
||||
scode = scode if scode else ccode
|
||||
raise RestrictedError(layer, scode, output, environment)
|
||||
|
||||
|
||||
def snapshot(info=None, context=5, code=None, environment=None):
|
||||
|
||||
@@ -145,7 +145,7 @@ class XssCleaner(HTMLParser):
|
||||
if url.startswith('#'):
|
||||
return True
|
||||
else:
|
||||
parsed = urlparse(url)
|
||||
parsed = urlparse.urlparse(url)
|
||||
return ((parsed[0] in self.allowed_schemes and '.' in parsed[1]) or
|
||||
(parsed[0] in self.allowed_schemes and '@' in parsed[2]) or
|
||||
(parsed[0] == '' and parsed[2].startswith('/')))
|
||||
|
||||
@@ -31,10 +31,16 @@ from gluon.globals import Request, Response, Session
|
||||
from gluon.storage import Storage, List
|
||||
from gluon.admin import w2p_unpack
|
||||
from pydal.base import BaseAdapter
|
||||
from gluon._compat import iteritems, ClassType
|
||||
from gluon._compat import iteritems, ClassType, PY2
|
||||
|
||||
logger = logging.getLogger("web2py")
|
||||
|
||||
if not PY2:
|
||||
def execfile(filename, global_vars=None, local_vars=None):
|
||||
with open(filename) as f:
|
||||
code = compile(f.read(), filename, 'exec')
|
||||
exec(code, global_vars, local_vars)
|
||||
|
||||
|
||||
def enable_autocomplete_and_history(adir, env):
|
||||
try:
|
||||
|
||||
@@ -158,10 +158,10 @@ class testResponse(unittest.TestCase):
|
||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content,
|
||||
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>' +
|
||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
|
||||
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
|
||||
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
|
||||
'<script src="/a/static/css/file.ts" type="text/typescript"></script>'
|
||||
)
|
||||
|
||||
response = Response()
|
||||
|
||||
@@ -1076,7 +1076,10 @@ def start_schedulers(options):
|
||||
return
|
||||
|
||||
# Work around OS X problem: http://bugs.python.org/issue9405
|
||||
import urllib
|
||||
if PY2:
|
||||
import urllib
|
||||
else:
|
||||
import urllib.request as urllib
|
||||
urllib.getproxies()
|
||||
|
||||
for app in apps:
|
||||
|
||||
@@ -222,7 +222,7 @@ echo <<EOF
|
||||
you can stop uwsgi and nginx with
|
||||
|
||||
sudo /etc/init.d/nginx stop
|
||||
sudo systemctl start emperor.uwsgi.service
|
||||
sudo systemctl stop emperor.uwsgi.service
|
||||
|
||||
and start it with
|
||||
|
||||
|
||||
Reference in New Issue
Block a user