Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95709e582d | ||
|
|
755275f8ef | ||
|
|
84a33f1a50 | ||
|
|
ca5c79c980 | ||
|
|
09e53f495d | ||
|
|
4ede2de037 | ||
|
|
800bd53870 | ||
|
|
4c87932f06 | ||
|
|
8276b30c32 | ||
|
|
6f87a20e26 | ||
|
|
41162c794e | ||
|
|
6034368364 | ||
|
|
19c41b308d | ||
|
|
f5638c8f6b | ||
|
|
904ca403a2 | ||
|
|
d244c34282 | ||
|
|
1715bccac4 | ||
|
|
abf3ca54bf | ||
|
|
2440932579 | ||
|
|
02e14d91e1 | ||
|
|
e9547d219a | ||
|
|
70bb497b96 | ||
|
|
398fc6de37 | ||
|
|
50692a4fd3 | ||
|
|
11b441b777 | ||
|
|
62f5372876 | ||
|
|
fba90d31f4 | ||
|
|
9375ea7378 | ||
|
|
e697bdaf90 | ||
|
|
7bddd67a61 | ||
|
|
93c05240b7 | ||
|
|
2f1db7dfa2 | ||
|
|
0389a45034 | ||
|
|
ec53580a76 | ||
|
|
09c8b5eced | ||
|
|
928fd364cf | ||
|
|
55a2f4a6b2 | ||
|
|
6e0da9cea7 | ||
|
|
9364aa2036 | ||
|
|
48806ccd8f | ||
|
|
80582daaa0 | ||
|
|
33c6dd9656 | ||
|
|
1c8790271d | ||
|
|
f8cba1e5c4 | ||
|
|
f2aacd93c8 | ||
|
|
02b02f73bd | ||
|
|
af69716bf0 | ||
|
|
433ef09d2c | ||
|
|
2859994bbe | ||
|
|
5cf2c9696d | ||
|
|
b517282238 | ||
|
|
842a8d613b | ||
|
|
2226862ea9 | ||
|
|
e87ef4bc3a | ||
|
|
b28cc5b5c3 | ||
|
|
ef4e465222 | ||
|
|
1cdda4f7f6 | ||
|
|
de7aeceac8 | ||
|
|
e04d16bdc1 | ||
|
|
c5547091cf | ||
|
|
83abe91e3a | ||
|
|
4f29733fae | ||
|
|
6b8ccff2a4 | ||
|
|
e7fee6a417 | ||
|
|
63972386c2 | ||
|
|
5c626c6d95 | ||
|
|
46b8ad3fdd | ||
|
|
7111b3dcb2 | ||
|
|
32eb1bc27d |
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
2
Makefile
2
Makefile
@@ -44,7 +44,7 @@ rmfiles:
|
||||
rm -rf applications/examples/uploads/*
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.17.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.17.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
#make clean
|
||||
# make rmfiles
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.17.1-stable+timestamp.2018.08.05.17.57.00
|
||||
Version 2.17.2-stable+timestamp.2018.10.06.11.34.06
|
||||
|
||||
16
anyserver.py
16
anyserver.py
@@ -212,16 +212,16 @@ def mongrel2_handler(application, conn, debug=False):
|
||||
|
||||
while True:
|
||||
if debug:
|
||||
print "WAITING FOR REQUEST"
|
||||
print("WAITING FOR REQUEST")
|
||||
|
||||
# receive a request
|
||||
req = conn.recv()
|
||||
if debug:
|
||||
print "REQUEST BODY: %r\n" % req.body
|
||||
print("REQUEST BODY: %r\n" % req.body)
|
||||
|
||||
if req.is_disconnect():
|
||||
if debug:
|
||||
print "DISCONNECT"
|
||||
print("DISCONNECT")
|
||||
continue # effectively ignore the disconnect from the client
|
||||
|
||||
# Set a couple of environment attributes a.k.a. header attributes
|
||||
@@ -247,7 +247,7 @@ def mongrel2_handler(application, conn, debug=False):
|
||||
environ['wsgi.input'] = req.body
|
||||
|
||||
if debug:
|
||||
print "ENVIRON: %r\n" % environ
|
||||
print("ENVIRON: %r\n" % environ)
|
||||
|
||||
# SimpleHandler needs file-like stream objects for
|
||||
# requests, errors and responses
|
||||
@@ -282,10 +282,10 @@ def mongrel2_handler(application, conn, debug=False):
|
||||
|
||||
# return the response
|
||||
if debug:
|
||||
print "RESPONSE: %r\n" % response
|
||||
print("RESPONSE: %r\n" % response)
|
||||
if errors:
|
||||
if debug:
|
||||
print "ERRORS: %r" % errors
|
||||
print("ERRORS: %r" % errors)
|
||||
data = "%s\r\n\r\n%s" % (data, errors)
|
||||
conn.reply_http(
|
||||
req, data, code=code, status=status, headers=headers)
|
||||
@@ -355,8 +355,8 @@ def main():
|
||||
dest='workers',
|
||||
help='number of workers number')
|
||||
(options, args) = parser.parse_args()
|
||||
print 'starting %s on %s:%s...' % (
|
||||
options.server, options.ip, options.port)
|
||||
print('starting %s on %s:%s...' % (
|
||||
options.server, options.ip, options.port))
|
||||
run(options.server, options.ip, options.port,
|
||||
logging=options.logging, profiler=options.profiler_dir,
|
||||
options=options)
|
||||
|
||||
@@ -562,7 +562,11 @@ def enable():
|
||||
os.unlink(filename)
|
||||
return SPAN(T('Disable'), _style='color:green')
|
||||
else:
|
||||
safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now)
|
||||
if PY2:
|
||||
safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now)
|
||||
else:
|
||||
str_ = 'disabled: True\ntime-disabled: %s' % request.now
|
||||
safe_open(filename, 'wb').write(str_.encode('utf-8'))
|
||||
return SPAN(T('Enable'), _style='color:red')
|
||||
|
||||
|
||||
@@ -642,7 +646,10 @@ def edit():
|
||||
# show settings tab and save prefernces
|
||||
if 'settings' in request.vars:
|
||||
if request.post_vars: # save new preferences
|
||||
post_vars = request.post_vars.items()
|
||||
if PY2:
|
||||
post_vars = request.post_vars.items()
|
||||
else:
|
||||
post_vars = list(request.post_vars.items())
|
||||
# Since unchecked checkbox are not serialized, we must set them as false by hand to store the correct preference in the settings
|
||||
post_vars += [(opt, 'false') for opt in preferences if opt not in request.post_vars]
|
||||
if config.save(post_vars):
|
||||
|
||||
@@ -29,5 +29,14 @@ jQuery(function(){
|
||||
}
|
||||
hoverMenu(); // first page load
|
||||
jQuery(window).resize(hoverMenu); // on resize event
|
||||
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
|
||||
jQuery('ul.nav li.dropdown a').click(function(){
|
||||
if(jQuery(this).attr("target")){
|
||||
window.open(
|
||||
jQuery(this).attr('href'),
|
||||
jQuery(this).attr('target') // <- This is what makes it open in a new window.
|
||||
);
|
||||
} else {
|
||||
window.location=jQuery(this).attr('href');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,16 +62,16 @@
|
||||
</center>
|
||||
|
||||
<p style="text-align:left;">
|
||||
The source code version works on all supported platforms, including Linux, but it requires Python 2.6, or 2.7 (recommended).
|
||||
It runs on Windows and most Unix systems, including <b>Linux</b> and <b>BSD</b>.
|
||||
The source code version works on Windows and most Unix systems, including <b>Linux</b>, <b>BSD</b> and <b>Mac</b> . It requires Python 2.6 (no more supported), Python 2.7 (stable) or Python 3.5+ (recommended for new projects) already installed on your system.
|
||||
There are also binary packages for Windows and Mac OS X. They include the Python 2.7 interpreter so you do not need to have it pre-installed.
|
||||
</p>
|
||||
|
||||
<h3>Instructions</h3>
|
||||
<p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx).
|
||||
To run from source, type:</p>
|
||||
{{=CODE("python2.7 web2py.py", language=None, counter='>', _class='boxCode')}}
|
||||
<p>With the binary packages, after download, just unzip it and then click on web2py.exe (windows) or web2py.app (osx).
|
||||
If you prefer to run it from source with your own Python interpreter alreay installed, type:</p>
|
||||
{{=CODE("python web2py.py", language=None, counter='>', _class='boxCode')}}
|
||||
<p>or for more info type:</p>
|
||||
{{=CODE("python2.7 web2py.py -h", language=None, counter='>', _class='boxCode')}}
|
||||
{{=CODE("python web2py.py -h", language=None, counter='>', _class='boxCode')}}
|
||||
|
||||
|
||||
<h3>Caveats</h3>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="twothirds">
|
||||
<div class="padded">
|
||||
<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>
|
||||
<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> (version 3 and 2.7).</p>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
@@ -18,7 +18,7 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="noeffect" href="http://link.packtpub.com/SUlnrN">
|
||||
<a class="noeffect" href="https://www.packtpub.com/web-development/web2py-application-development-cookbook">
|
||||
<img src="{{=URL('static','images/book-recipes.png')}}" />
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -348,3 +348,13 @@ td.w2p_fc,
|
||||
.icon.pen:before { content: "\f040";}
|
||||
.icon.arrowright:before { content: "\f061";}
|
||||
.icon.magnifier:before { content: "\f002";}
|
||||
|
||||
.web2py_table_selectable_actions {
|
||||
padding-top: 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.web2py_table_selectable_actions input {
|
||||
padding: 5px 7px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
});
|
||||
}
|
||||
var ul = this;
|
||||
$(ul).find(":text").addClass('form-control').wrap("<div class='input-group'></div>").after('<div class="input-group-addon"><i class="glyphicon glyphicon-plus"></i></div><div class="input-group-addon"><i class="glyphicon glyphicon-minus"></i></div>').keypress(function(e) {
|
||||
$(ul).find(":text").addClass('form-control').wrap("<div class='input-group'></div>").after('<div class="input-group-append"><i class="fa fa-plus-circle"></i></div> <div class="input-group-append"><i class="fa fa-minus-circle"></i></div>').keypress(function(e) {
|
||||
return (e.which == 13) ? pe(ul, e) : true;
|
||||
}).next().click(function(e) {
|
||||
pe(ul, e);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{extend 'layout.html'}}
|
||||
|
||||
{{block header}}
|
||||
<div class="jumbotron jumbotron-fluid" style="background-color: #333; color:white; padding:30px;word-wrap:break-word;">
|
||||
<div class="jumbotron jumbotron-fluid background" style="background-color: #333; color:white; padding:30px;word-wrap:break-word;">
|
||||
<div class="container center">
|
||||
<h1 class="display-5">/{{=request.application}}/{{=request.controller}}/{{=request.function}}</h1>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<body>
|
||||
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
|
||||
<!-- Navbar ======================================= -->
|
||||
<nav class="navbar navbar-light navbar-expand-md bg-faded justify-content-center">
|
||||
<nav class="navbar navbar-light navbar-expand-md bg-faded bg-dark navbar-dark justify-content-center">
|
||||
<a href="http://web2py.com" class="navbar-brand d-flex w-50 mr-auto">web2py</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
|
||||
@@ -15,7 +15,7 @@ Note:
|
||||
|
||||
import re
|
||||
import fnmatch
|
||||
import os
|
||||
import os, sys
|
||||
import copy
|
||||
import random
|
||||
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long, reload
|
||||
@@ -52,7 +52,10 @@ is_gae = settings.global_settings.web2py_runtime_gae
|
||||
is_jython = settings.global_settings.is_jython
|
||||
pjoin = os.path.join
|
||||
|
||||
marshal_header_size = 8 if PY2 else 12
|
||||
if PY2:
|
||||
marshal_header_size = 8
|
||||
else:
|
||||
marshal_header_size = 16 if sys.version_info[1] >= 7 else 12
|
||||
|
||||
TEST_CODE = \
|
||||
r"""
|
||||
|
||||
@@ -180,7 +180,7 @@ class RedisClient(object):
|
||||
self.r_server.incr('web2py_cache_statistics:misses')
|
||||
cache_set_key = self.cache_set_key
|
||||
expire_at = int(time.time() + time_expire) + 120
|
||||
bucket_key = "%s:%s" % (cache_set_key, expire_at / 60)
|
||||
bucket_key = "%s:%s" % (cache_set_key, expire_at // 60)
|
||||
value = f()
|
||||
value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
|
||||
if time_expire == 0:
|
||||
@@ -196,7 +196,7 @@ class RedisClient(object):
|
||||
# add the key to the bucket
|
||||
p.sadd(bucket_key, key)
|
||||
# expire the bucket properly
|
||||
p.expireat(bucket_key, ((expire_at / 60) + 1) * 60)
|
||||
p.expireat(bucket_key, ((expire_at // 60) + 1) * 60)
|
||||
p.execute()
|
||||
return value
|
||||
|
||||
|
||||
@@ -969,7 +969,7 @@ class Session(Storage):
|
||||
if row:
|
||||
# rows[0].update_record(locked=True)
|
||||
# Unpickle the data
|
||||
session_data = pickle.loads(row[b'session_data'])
|
||||
session_data = pickle.loads(row['session_data'])
|
||||
self.update(session_data)
|
||||
response.session_new = False
|
||||
else:
|
||||
@@ -1075,6 +1075,16 @@ class Session(Storage):
|
||||
scookies['HttpOnly'] = True
|
||||
if self._secure:
|
||||
scookies['secure'] = True
|
||||
if self._same_site is None:
|
||||
# Using SameSite Lax Mode is the default
|
||||
# You actually have to call session.samesite(False) if you really
|
||||
# dont want the extra protection provided by the SameSite header
|
||||
self._same_site = 'Lax'
|
||||
if self._same_site:
|
||||
if 'samesite' not in Cookie.Morsel._reserved:
|
||||
# Python version 3.7 and lower needs this
|
||||
Cookie.Morsel._reserved['samesite'] = 'SameSite'
|
||||
scookies['samesite'] = self._same_site
|
||||
|
||||
def clear_session_cookies(self):
|
||||
request = current.request
|
||||
@@ -1153,6 +1163,9 @@ class Session(Storage):
|
||||
def secure(self):
|
||||
self._secure = True
|
||||
|
||||
def samesite(self, mode='Lax'):
|
||||
self._same_site = mode
|
||||
|
||||
def forget(self, response=None):
|
||||
self._close(response)
|
||||
self._forget = True
|
||||
@@ -1180,7 +1193,7 @@ class Session(Storage):
|
||||
|
||||
def _unchanged(self, response):
|
||||
if response.session_new:
|
||||
internal = ['_last_timestamp', '_secure', '_start_timestamp']
|
||||
internal = ['_last_timestamp', '_secure', '_start_timestamp', '_same_site']
|
||||
for item in self.keys():
|
||||
if item not in internal:
|
||||
return False
|
||||
|
||||
@@ -311,7 +311,7 @@ def write_plural_dict(filename, contents):
|
||||
try:
|
||||
fp = LockedFile(filename, 'w')
|
||||
fp.write('#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n{\n# "singular form (0)": ["first plural form (1)", "second plural form (2)", ...],\n')
|
||||
for key in sorted(contents, sort_function):
|
||||
for key in sorted(contents, key=sort_function):
|
||||
forms = '[' + ','.join([repr(Utf8(form))
|
||||
for form in contents[key]]) + ']'
|
||||
fp.write('%s: %s,\n' % (repr(Utf8(key)), forms))
|
||||
@@ -325,8 +325,8 @@ def write_plural_dict(filename, contents):
|
||||
fp.close()
|
||||
|
||||
|
||||
def sort_function(x, y):
|
||||
return cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())
|
||||
def sort_function(x):
|
||||
return unicode(x, 'utf-8').lower()
|
||||
|
||||
|
||||
def write_dict(filename, contents):
|
||||
@@ -936,8 +936,8 @@ class translator(object):
|
||||
word = w[1:]
|
||||
fun = cap_fun
|
||||
if i is not None:
|
||||
return fun(self.plural(word, symbols[int(i)]))
|
||||
return fun(word)
|
||||
return to_native(fun(self.plural(word, symbols[int(i)])))
|
||||
return to_native(fun(word))
|
||||
|
||||
def sub_dict(m):
|
||||
""" word(key or num)
|
||||
|
||||
@@ -31,7 +31,7 @@ from gluon._compat import Cookie, urllib2
|
||||
|
||||
from gluon.fileutils import abspath, write_file
|
||||
from gluon.settings import global_settings
|
||||
from gluon.utils import web2py_uuid
|
||||
from gluon.utils import web2py_uuid, unlocalised_http_header_date
|
||||
from gluon.admin import add_path_first, create_missing_folders, create_missing_app_folders
|
||||
from gluon.globals import current
|
||||
|
||||
@@ -199,8 +199,7 @@ def serve_controller(request, response, session):
|
||||
('Content-Type', contenttype('.' + request.extension)),
|
||||
('Cache-Control',
|
||||
'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'),
|
||||
('Expires', time.strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
time.gmtime())),
|
||||
('Expires', unlocalised_http_header_date(time.gmtime())),
|
||||
('Pragma', 'no-cache')]
|
||||
for key, value in default_headers:
|
||||
response.headers.setdefault(key, value)
|
||||
|
||||
Submodule gluon/packages/dal updated: 70929a6dc0...b4114ea55d
@@ -804,6 +804,7 @@ class Scheduler(MetaScheduler):
|
||||
Field('group_name', default='main'),
|
||||
Field('status', requires=IS_IN_SET(TASK_STATUS),
|
||||
default=QUEUED, writable=False),
|
||||
Field('broadcast', 'boolean', default=False),
|
||||
Field('function_name',
|
||||
requires=IS_IN_SET(sorted(self.tasks.keys()))
|
||||
if self.tasks else DEFAULT),
|
||||
@@ -1155,6 +1156,15 @@ class Scheduler(MetaScheduler):
|
||||
- does "housecleaning" for dead workers
|
||||
- triggers tasks assignment to workers
|
||||
"""
|
||||
if self.db_thread:
|
||||
# BKR 20180612 check if connection still works
|
||||
try:
|
||||
query = self.db_thread.scheduler_worker.worker_name == self.worker_name
|
||||
self.db_thread(query).count()
|
||||
except self.db_thread._adapter.connection.OperationalError:
|
||||
# if not -> throw away self.db_thread and force reconnect
|
||||
self.db_thread = None
|
||||
|
||||
if not self.db_thread:
|
||||
logger.debug('thread building own DAL object')
|
||||
self.db_thread = DAL(
|
||||
@@ -1358,23 +1368,48 @@ class Scheduler(MetaScheduler):
|
||||
gname = task.group_name
|
||||
ws = wkgroups.get(gname)
|
||||
if ws:
|
||||
counter = 0
|
||||
myw = 0
|
||||
for i, w in enumerate(ws['workers']):
|
||||
if w['c'] < counter:
|
||||
myw = i
|
||||
counter = w['c']
|
||||
assigned_wn = wkgroups[gname]['workers'][myw]['name']
|
||||
d = dict(
|
||||
status=ASSIGNED,
|
||||
assigned_worker_name=assigned_wn
|
||||
)
|
||||
db(
|
||||
(st.id == task.id) &
|
||||
(st.status.belongs((QUEUED, ASSIGNED)))
|
||||
).update(**d)
|
||||
wkgroups[gname]['workers'][myw]['c'] += 1
|
||||
db.commit()
|
||||
if task.broadcast:
|
||||
for worker in ws['workers']:
|
||||
new_task = db.scheduler_task.insert(
|
||||
application_name = task.application_name,
|
||||
task_name = task.task_name,
|
||||
group_name = task.group_name,
|
||||
status = ASSIGNED,
|
||||
broadcast = False,
|
||||
function_name = task.function_name,
|
||||
args = task.args,
|
||||
start_time = now,
|
||||
repeats = 1,
|
||||
retry_failed = task.retry_failed,
|
||||
sync_output = task.sync_output,
|
||||
assigned_worker_name = worker['name'])
|
||||
if task.period:
|
||||
next_run_time = now+datetime.timedelta(seconds=task.period)
|
||||
else:
|
||||
# must be cronline
|
||||
raise NotImplementedError
|
||||
db(st.id == task.id).update(times_run=task.times_run+1,
|
||||
next_run_time=next_run_time,
|
||||
last_run_time=now)
|
||||
db.commit()
|
||||
else:
|
||||
counter = 0
|
||||
myw = 0
|
||||
for i, w in enumerate(ws['workers']):
|
||||
if w['c'] < counter:
|
||||
myw = i
|
||||
counter = w['c']
|
||||
assigned_wn = wkgroups[gname]['workers'][myw]['name']
|
||||
d = dict(
|
||||
status=ASSIGNED,
|
||||
assigned_worker_name=assigned_wn
|
||||
)
|
||||
db(
|
||||
(st.id == task.id) &
|
||||
(st.status.belongs((QUEUED, ASSIGNED)))
|
||||
).update(**d)
|
||||
wkgroups[gname]['workers'][myw]['c'] += 1
|
||||
db.commit()
|
||||
# I didn't report tasks but I'm working nonetheless!!!!
|
||||
if x > 0:
|
||||
self.w_stats.empty_runs = 0
|
||||
|
||||
@@ -699,18 +699,20 @@ class AutocompleteWidget(object):
|
||||
if isinstance(field, Field.Virtual):
|
||||
records = []
|
||||
table_rows = self.db(self.db[field.tablename]).select(orderby=self.orderby)
|
||||
count = 0
|
||||
count = self.limitby[1] if self.limitby else -1
|
||||
for row in table_rows:
|
||||
if self.at_beginning:
|
||||
if row[field.name].lower().startswith(kword):
|
||||
count += 1
|
||||
count -= 1
|
||||
records.append(row)
|
||||
else:
|
||||
if kword in row[field.name].lower():
|
||||
count += 1
|
||||
count -= 1
|
||||
records.append(row)
|
||||
if count == 10:
|
||||
if count == 0:
|
||||
break
|
||||
if self.limitby and self.limitby[0]:
|
||||
records = records[self.limitby[0]:]
|
||||
rows = Rows(self.db, records, table_rows.colnames,
|
||||
compact=table_rows.compact)
|
||||
elif settings and settings.global_settings.web2py_runtime_gae:
|
||||
@@ -1090,7 +1092,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
|
||||
|
||||
# bootstrap 4
|
||||
def formstyle_bootstrap4_stacked(form, fields):
|
||||
""" bootstrap 3 format form layout
|
||||
""" bootstrap 4 format form layout
|
||||
|
||||
Note:
|
||||
Experimental!
|
||||
@@ -1139,7 +1141,7 @@ def formstyle_bootstrap4_stacked(form, fields):
|
||||
|
||||
|
||||
def formstyle_bootstrap4_inline_factory(col_label_size=3):
|
||||
""" bootstrap 3 horizontal form layout
|
||||
""" bootstrap 4 horizontal form layout
|
||||
|
||||
Note:
|
||||
Experimental!
|
||||
@@ -1349,7 +1351,7 @@ class SQLFORM(FORM):
|
||||
if not readonly:
|
||||
if not record:
|
||||
# create form should only show writable fields
|
||||
fields = [f.name for f in table if (ignore_rw or f.writable) and not f.compute]
|
||||
fields = [f.name for f in table if (ignore_rw or f.writable or (f.readable and f.default)) and not f.compute]
|
||||
else:
|
||||
# update form should also show readable fields and computed fields (but in reaodnly mode)
|
||||
fields = [f.name for f in table if (ignore_rw or f.writable or f.readable)]
|
||||
@@ -2021,7 +2023,7 @@ class SQLFORM(FORM):
|
||||
to hold the fields.
|
||||
"""
|
||||
# this is here to avoid circular references
|
||||
from gluon.dal import DAL
|
||||
from gluon.dal import DAL, _default_validators
|
||||
# Define a table name, this way it can be logical to our CSS.
|
||||
# And if you switch from using SQLFORM to SQLFORM.factory
|
||||
# your same css definitions will still apply.
|
||||
@@ -2034,8 +2036,9 @@ class SQLFORM(FORM):
|
||||
|
||||
# Clone fields, while passing tables straight through
|
||||
fields_with_clones = [f.clone() if isinstance(f, Field) else f for f in fields]
|
||||
|
||||
return SQLFORM(DAL(None).define_table(table_name, *fields_with_clones), **attributes)
|
||||
dummy_dal = DAL(None)
|
||||
dummy_dal.validators_method = lambda f: _default_validators(dummy_dal, f) # See https://github.com/web2py/web2py/issues/2007
|
||||
return SQLFORM(dummy_dal.define_table(table_name, *fields_with_clones), **attributes)
|
||||
|
||||
@staticmethod
|
||||
def build_query(fields, keywords):
|
||||
@@ -2312,7 +2315,7 @@ class SQLFORM(FORM):
|
||||
button='button btn btn-default btn-secondary',
|
||||
buttontext='buttontext button',
|
||||
buttonadd='icon plus icon-plus glyphicon glyphicon-plus',
|
||||
buttonback='icon leftarrow icon-arrow-left glyphicon glyphicon-arrow-left',
|
||||
buttonback='icon arrowleft icon-arrow-left glyphicon glyphicon-arrow-left',
|
||||
buttonexport='icon downarrow icon-download glyphicon glyphicon-download',
|
||||
buttondelete='icon trash icon-trash glyphicon glyphicon-trash',
|
||||
buttonedit='icon pen icon-pencil glyphicon glyphicon-pencil',
|
||||
@@ -2690,13 +2693,13 @@ class SQLFORM(FORM):
|
||||
dbset = dbset(SQLFORM.build_query(
|
||||
sfields, keywords))
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
cacheable=True, *selectable_columns)
|
||||
cacheable=True, *expcolumns)
|
||||
except Exception as e:
|
||||
response.flash = T('Internal Error')
|
||||
rows = []
|
||||
else:
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
cacheable=True, *selectable_columns)
|
||||
cacheable=True, *expcolumns)
|
||||
|
||||
value = exportManager[export_type]
|
||||
clazz = value[0] if hasattr(value, '__getitem__') else value
|
||||
@@ -2855,9 +2858,9 @@ class SQLFORM(FORM):
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
cursor = request.vars.cursor or True
|
||||
limitby = (0, paginate)
|
||||
page = safe_int(request.vars.page, 1) - 1
|
||||
page = safe_int(request.vars.page or 1, 1) - 1
|
||||
elif paginate and paginate < nrows:
|
||||
page = safe_int(request.vars.page, 1) - 1
|
||||
page = safe_int(request.vars.page or 1, 1) - 1
|
||||
limitby = (paginate * page, paginate * (page + 1))
|
||||
else:
|
||||
limitby = None
|
||||
@@ -2899,7 +2902,7 @@ class SQLFORM(FORM):
|
||||
paginator = UL()
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
# this means we may have a large table with an unknown number of rows.
|
||||
page = safe_int(request.vars.page, 1) - 1
|
||||
page = safe_int(request.vars.page or 1, 1) - 1
|
||||
paginator.append(LI('page %s' % (page + 1)))
|
||||
if next_cursor:
|
||||
d = dict(page=page + 2, cursor=next_cursor)
|
||||
@@ -2918,7 +2921,7 @@ class SQLFORM(FORM):
|
||||
npages, reminder = divmod(nrows, paginate)
|
||||
if reminder:
|
||||
npages += 1
|
||||
page = safe_int(request.vars.page, 1) - 1
|
||||
page = safe_int(request.vars.page or 1, 1) - 1
|
||||
|
||||
def self_link(name, p):
|
||||
d = dict(page=p + 1)
|
||||
@@ -3089,8 +3092,9 @@ class SQLFORM(FORM):
|
||||
|
||||
if formstyle == 'bootstrap':
|
||||
# add space between buttons
|
||||
# inputs = sum([[inp, ' '] for inp in inputs], [])[:-1]
|
||||
htmltable = FORM(htmltable, DIV(_class='form-actions', *inputs))
|
||||
elif 'bootstrap' in formstyle : # Same for bootstrap 3 & 4
|
||||
htmltable = FORM(htmltable, DIV(_class='form-group web2py_table_selectable_actions', *inputs))
|
||||
else:
|
||||
htmltable = FORM(htmltable, *inputs)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import time
|
||||
import re
|
||||
import errno
|
||||
from gluon.http import HTTP
|
||||
from gluon.utils import unlocalised_http_header_date
|
||||
from gluon.contenttype import contenttype
|
||||
from gluon._compat import PY2
|
||||
|
||||
@@ -74,7 +75,7 @@ def stream_file_or_304_or_206(
|
||||
stat_file = os.stat(static_file)
|
||||
fsize = stat_file[stat.ST_SIZE]
|
||||
modified = stat_file[stat.ST_MTIME]
|
||||
mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified))
|
||||
mtime = unlocalised_http_header_date(time.gmtime(modified))
|
||||
headers.setdefault('Content-Type', contenttype(static_file))
|
||||
headers.setdefault('Last-Modified', mtime)
|
||||
headers.setdefault('Pragma', 'cache')
|
||||
|
||||
@@ -18,7 +18,7 @@ import os
|
||||
import cgi
|
||||
import logging
|
||||
from re import compile, sub, escape, DOTALL
|
||||
from gluon._compat import StringIO, unicodeT, to_unicode, to_bytes, to_native
|
||||
from gluon._compat import StringIO, unicodeT, to_unicode, to_bytes, to_native, basestring
|
||||
|
||||
try:
|
||||
# have web2py
|
||||
@@ -778,7 +778,7 @@ def parse_template(filename,
|
||||
"""
|
||||
|
||||
# First, if we have a str try to open the file
|
||||
if isinstance(filename, str):
|
||||
if isinstance(filename, basestring):
|
||||
fname = os.path.join(path, filename)
|
||||
try:
|
||||
with open(fname, 'rb') as fp:
|
||||
|
||||
@@ -231,6 +231,25 @@ class testResponse(unittest.TestCase):
|
||||
cookie = str(current.response.cookies)
|
||||
self.assertTrue('httponly' not in cookie.lower())
|
||||
|
||||
def test_cookies_samesite(self):
|
||||
# Test Lax is the default mode
|
||||
current = setup_clean_session()
|
||||
current.session._fixup_before_save()
|
||||
cookie = str(current.response.cookies)
|
||||
self.assertTrue('samesite=lax' in cookie.lower())
|
||||
# Test you can disable samesite
|
||||
current = setup_clean_session()
|
||||
current.session.samesite(False)
|
||||
current.session._fixup_before_save()
|
||||
cookie = str(current.response.cookies)
|
||||
self.assertTrue('samesite' not in cookie.lower())
|
||||
# Test you can change mode
|
||||
current = setup_clean_session()
|
||||
current.session.samesite('Strict')
|
||||
current.session._fixup_before_save()
|
||||
cookie = str(current.response.cookies)
|
||||
self.assertTrue('samesite=strict' in cookie.lower())
|
||||
|
||||
def test_include_meta(self):
|
||||
response = Response()
|
||||
response.meta[u'web2py'] = 'web2py'
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"""
|
||||
Unit tests for gluon.sqlhtml
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
@@ -312,12 +313,31 @@ class TestSQLFORM(unittest.TestCase):
|
||||
Field('field_two', 'string'))
|
||||
self.assertEqual(factory_form.xml()[:5], b'<form')
|
||||
|
||||
def test_factory_applies_default_validators(self):
|
||||
from gluon import current
|
||||
|
||||
factory_form = SQLFORM.factory(
|
||||
Field('a_date', type='date'),
|
||||
)
|
||||
# Fake user input
|
||||
current.request.post_vars.update({
|
||||
'_formname': 'no_table/create',
|
||||
'a_date': '2018-09-14',
|
||||
'_formkey': '123',
|
||||
|
||||
})
|
||||
# Fake the formkey
|
||||
current.session['_formkey[no_table/create]'] = ['123']
|
||||
|
||||
self.assertTrue(factory_form.process().accepted)
|
||||
self.assertIsInstance(factory_form.vars.a_date, datetime.date)
|
||||
|
||||
# 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()[:4], b'<div')
|
||||
|
||||
@@ -1378,6 +1378,7 @@ class Auth(AuthAPI):
|
||||
login_after_password_change=True,
|
||||
login_after_registration=False,
|
||||
login_captcha=None,
|
||||
login_specify_error=False,
|
||||
long_expiration=3600 * 30 * 24, # one month
|
||||
mailer=None,
|
||||
manager_actions={},
|
||||
@@ -2567,6 +2568,8 @@ class Auth(AuthAPI):
|
||||
settings.formstyle, 'captcha__row')
|
||||
accepted_form = False
|
||||
|
||||
specific_error = self.messages.invalid_user
|
||||
|
||||
if form.accepts(request, session if self.csrf_prevention else None,
|
||||
formname='login', dbio=False,
|
||||
onvalidation=onvalidation,
|
||||
@@ -2582,6 +2585,7 @@ class Auth(AuthAPI):
|
||||
user = table_user(**{username: entered_username})
|
||||
if user:
|
||||
# user in db, check if registration pending or disabled
|
||||
specific_error = self.messages.invalid_password
|
||||
temp_user = user
|
||||
if (temp_user.registration_key or '').startswith('pending'):
|
||||
response.flash = self.messages.registration_pending
|
||||
@@ -2631,7 +2635,7 @@ class Auth(AuthAPI):
|
||||
self.log_event(self.messages['login_failed_log'],
|
||||
request.post_vars)
|
||||
# invalid login
|
||||
session.flash = self.messages.invalid_login
|
||||
session.flash = specific_error if self.settings.login_specify_error else self.messages.invalid_login
|
||||
callback(onfail, None)
|
||||
redirect(
|
||||
self.url(args=request.args, vars=request.get_vars),
|
||||
@@ -3447,7 +3451,8 @@ class Auth(AuthAPI):
|
||||
if log is DEFAULT:
|
||||
log = self.messages['reset_password_log']
|
||||
userfield = self.settings.login_userfield or 'username' \
|
||||
if 'username' in table_user.fields else 'email'
|
||||
if self.settings.login_userfield or 'username' \
|
||||
in table_user.fields else 'email'
|
||||
if userfield == 'email':
|
||||
table_user.email.requires = [
|
||||
IS_EMAIL(error_message=self.messages.invalid_email),
|
||||
@@ -3455,7 +3460,7 @@ class Auth(AuthAPI):
|
||||
error_message=self.messages.invalid_email)]
|
||||
if not self.settings.email_case_sensitive:
|
||||
table_user.email.requires.insert(0, IS_LOWER())
|
||||
else:
|
||||
elif userfield == 'username':
|
||||
table_user.username.requires = [
|
||||
IS_IN_DB(self.db, table_user.username,
|
||||
error_message=self.messages.invalid_username)]
|
||||
|
||||
@@ -462,3 +462,51 @@ def local_html_escape(data, quote=False):
|
||||
data = data.replace(b'"', b""")
|
||||
data = data.replace(b'\'', b"'")
|
||||
return data
|
||||
|
||||
|
||||
def unlocalised_http_header_date(data):
|
||||
"""
|
||||
Converts input datetime to format defined by RFC 7231, section 7.1.1.1
|
||||
|
||||
Previously, %a and %b formats were used for weekday and month names, but
|
||||
those are not locale-safe. uWSGI requires latin1-encodable headers and
|
||||
for example in cs_CS locale, fourth day in week is not encodable in latin1,
|
||||
as it's "Čt".
|
||||
|
||||
Example output: Sun, 06 Nov 1994 08:49:37 GMT
|
||||
"""
|
||||
|
||||
short_weekday = {
|
||||
"0": "Sun",
|
||||
"1": "Mon",
|
||||
"2": "Tue",
|
||||
"3": "Wed",
|
||||
"4": "Thu",
|
||||
"5": "Fri",
|
||||
"6": "Sat",
|
||||
}.get(time.strftime("%w", data))
|
||||
|
||||
day_of_month = time.strftime("%d", data)
|
||||
|
||||
short_month = {
|
||||
"01": "Jan",
|
||||
"02": "Feb",
|
||||
"03": "Mar",
|
||||
"04": "Apr",
|
||||
"05": "May",
|
||||
"06": "Jun",
|
||||
"07": "Jul",
|
||||
"08": "Aug",
|
||||
"09": "Sep",
|
||||
"10": "Oct",
|
||||
"11": "Nov",
|
||||
"12": "Dec",
|
||||
}.get(time.strftime("%m", data))
|
||||
|
||||
year_and_time = time.strftime("%Y %H:%M:%S GMT")
|
||||
|
||||
return "{}, {} {} {}".format(
|
||||
short_weekday,
|
||||
day_of_month,
|
||||
short_month,
|
||||
year_and_time)
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
"""
|
||||
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
|
||||
from gluon._compat import PY2
|
||||
|
||||
if PY2:
|
||||
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
|
||||
else:
|
||||
from xmlrpc.server import SimpleXMLRPCDispatcher
|
||||
|
||||
def handler(request, response, methods):
|
||||
response.session_id = None # no sessions for xmlrpc
|
||||
|
||||
@@ -48,7 +48,7 @@ def main():
|
||||
global REQUIRED, IGNORED
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print USAGE
|
||||
print(USAGE)
|
||||
|
||||
# make target folder
|
||||
target = sys.argv[1]
|
||||
|
||||
Reference in New Issue
Block a user