Compare commits

..

69 Commits

Author SHA1 Message Date
mdipierro
95709e582d R-2.17.2 2018-10-06 11:41:01 -07:00
mdipierro
755275f8ef R-2.17.2 2018-10-06 11:36:23 -07:00
mdipierro
84a33f1a50 Merge branch 'master' of github.com:web2py/web2py 2018-10-06 11:35:29 -07:00
mdipierro
ca5c79c980 R-2.17.2 2018-10-06 11:35:12 -07:00
mdipierro
09e53f495d Merge pull request #2031 from leonelcamara/patch-22
Use export columns as is supposed instead of selectable ones
2018-10-06 11:31:14 -07:00
mdipierro
4ede2de037 Merge pull request #2030 from leonelcamara/patch-21
Show readable fields with a default in create form
2018-10-06 11:30:14 -07:00
Leonel Câmara
800bd53870 Use export columns as is supposed instead of selectable ones
Fixes #2014
2018-10-04 12:43:49 +01:00
Leonel Câmara
4c87932f06 Show readable fields with a default in create form
Fixes #2006 
This is a backwards compatibility fix.
2018-10-03 12:53:58 +01:00
mdipierro
8276b30c32 Merge pull request #2026 from nicozanf/patch-1
Python 3 compatibility + fixed link for cookbook
2018-09-27 22:19:37 -07:00
mdipierro
6f87a20e26 Merge pull request #2025 from leonelcamara/patch-20
Python 3 compatibility
2018-09-27 22:19:20 -07:00
mdipierro
41162c794e Merge pull request #2022 from leonelcamara/patch-19
Fixes #2020
2018-09-27 22:18:22 -07:00
Nico Zanferrari
6034368364 Python 3 compatibility + fixed link for cookbook 2018-09-27 21:16:05 +02:00
Leonel Câmara
19c41b308d Update languages.py 2018-09-27 17:57:24 +01:00
Leonel Câmara
f5638c8f6b Python 3 has no cmp function in sorted 2018-09-27 16:22:48 +01:00
Leonel Câmara
904ca403a2 Python 3 compatibility
Fixes #2024
2018-09-27 01:53:08 +01:00
Leonel Câmara
d244c34282 Fixes #2020 2018-09-24 22:04:04 +01:00
mdipierro
1715bccac4 Merge pull request #2019 from hrother/sqlform_factory_validators
Testcase for #2007
2018-09-23 10:44:37 -07:00
mdipierro
abf3ca54bf Merge pull request #2018 from leonelcamara/fix726
Fixes #726
2018-09-22 18:48:21 -07:00
mdipierro
2440932579 Merge pull request #2017 from leonelcamara/fixfactoryvalidators
Fixes #2007
2018-09-22 18:47:51 -07:00
mdipierro
02e14d91e1 Merge pull request #2016 from inpos/master
Python 3 compat
2018-09-22 18:44:25 -07:00
mdipierro
e9547d219a Merge pull request #2008 from leonelcamara/samesite
Add SameSite support
2018-09-22 18:43:38 -07:00
Holger Rother
70bb497b96 Simplify testcase 2018-09-22 09:55:26 +02:00
Holger Rother
398fc6de37 Testcase for #2007 2018-09-21 07:58:12 +02:00
Leonel Câmara
50692a4fd3 Fixes #726 2018-09-20 18:59:41 +01:00
Leonel Câmara
11b441b777 Make SameSite=Lax the default for all web2py apps 2018-09-20 13:02:09 +01:00
Leonel Câmara
62f5372876 Fixes #2007 2018-09-20 12:47:05 +01:00
Бородин Роман
fba90d31f4 Error 'dict_items += list' when try to save code-editor settings 2018-09-19 11:06:04 +03:00
Бородин Роман
9375ea7378 TypeError when try to disable appliance 2018-09-19 10:57:57 +03:00
mdipierro
e697bdaf90 Merge branch 'master' of github.com:web2py/web2py 2018-09-17 21:36:54 -07:00
mdipierro
7bddd67a61 fixed basestring in template.py filenames, thanks appjarbiz 2018-09-17 21:32:41 -07:00
mdipierro
93c05240b7 Merge pull request #2009 from misl6/master
Fixes request_reset_password w/ custom userfield
2018-09-17 06:29:16 -07:00
mdipierro
2f1db7dfa2 Merge pull request #2005 from Faelysse/master
HTTP header date made independent of locale, fixes #1997
2018-09-17 06:24:32 -07:00
mdipierro
0389a45034 Merge pull request #2004 from DonaldMcC/issue/2003
Issue/2003
2018-09-17 06:23:34 -07:00
mdipierro
ec53580a76 Merge pull request #2002 from JusticeN/patch-2
changing the anyserver.py to make it run with py3
2018-09-17 06:22:45 -07:00
Mirko Galimberti
09c8b5eced skip requires on custom userfield 2018-09-07 10:23:07 +02:00
Leonel Câmara
928fd364cf Add SameSite support 2018-09-06 16:16:44 +01:00
Mirko Galimberti
55a2f4a6b2 Fixes request_reset_password w/ custom userfield 2018-09-06 14:58:03 +02:00
Faelysse
6e0da9cea7 HTTP header date made independent of locale, fixes #1997 2018-09-06 14:46:07 +02:00
Donald McClymont
9364aa2036 Support for compiled apps on python 3.7 and above - marshal_header_size increased to 16 for those cases - tested by compiling and running welcome app on py 2.7,3.6 and 3.7 2018-09-05 23:34:05 +01:00
Donald
48806ccd8f Merge pull request #1 from web2py/master
merge my branch forward hopefully
2018-09-05 22:52:36 +01:00
JusticeN
80582daaa0 changing the anyserver.py to make it run with py3
actually fixing the issue #1993 i have created.
i tested it successfuly with the command python3 anyserver.py -s tornado -i 127.0.0.1 -p 80 -l
2018-09-05 14:17:33 +02:00
mdipierro
33c6dd9656 fixed broken connection in scheduler, thanks Bart 2018-09-02 10:45:06 -07:00
mdipierro
1c8790271d Merge pull request #1995 from wojtek555/issue/1974
Glyphicons replaced with awasome icons in list widget, fix #1974
2018-09-02 10:27:04 -07:00
mdipierro
f8cba1e5c4 Merge pull request #1992 from jvanbraekel/master
Update the style of SQLgrid select actions
2018-09-02 10:26:40 -07:00
mdipierro
f2aacd93c8 Merge pull request #1991 from Faelysse/master
Python3 redis compatibility
2018-09-02 10:25:59 -07:00
mdipierro
02b02f73bd Merge pull request #1989 from nicozanf/patch-2
partially restore original layout for Welcome page
2018-09-02 10:25:25 -07:00
mdipierro
af69716bf0 Merge pull request #1982 from kvanzuijlen/fix_issues_for_python3
Fixed KeyError that occured when trying to retrieve column 'session_data'
2018-09-02 10:23:36 -07:00
mdipierro
433ef09d2c Merge pull request #1924 from blackthorne/master
added task broadcasting for workers within a group
2018-09-02 10:22:18 -07:00
mdipierro
2859994bbe Merge branch 'master' of github.com:web2py/web2py 2018-09-02 10:21:53 -07:00
mdipierro
5cf2c9696d Merge pull request #1994 from JusticeN/patch-1
make make_min_web2py.py run for python3
2018-09-02 10:19:46 -07:00
mdipierro
b517282238 Merge pull request #1984 from amerikan/master
Adds the auto-fill Github issues templates
2018-09-02 10:19:06 -07:00
mdipierro
842a8d613b Merge pull request #1979 from kelvinspencer/patch-1
Update web2py_bootstrap.js
2018-09-02 10:18:02 -07:00
mdipierro
2226862ea9 Merge pull request #1976 from nicozanf/patch-1
Python 3 compatibility clarification
2018-09-02 10:17:14 -07:00
mdipierro
e87ef4bc3a fixed SimpleXMLRPCDispatcher, thanks Leonel 2018-09-02 10:15:01 -07:00
Wojciech Barcik
b28cc5b5c3 Glyphicons replaced with awasome icons in list widget, fix #1974 2018-08-29 19:01:22 +02:00
JusticeN
ef4e465222 make make_min_web2py.py run for python3
i tried to minify the web2py server and this script was not able to run with python3.5.
So i change it.
2018-08-25 13:45:46 +02:00
jvanbraekel
1cdda4f7f6 Merge https://github.com/jvanbraekel/web2py 2018-08-22 17:53:27 +02:00
jvanbraekel
de7aeceac8 Merge pull request #2 from web2py/master
merge origin changes
2018-08-22 17:50:14 +02:00
jvanbraekel
e04d16bdc1 Align SQLForm.grid selec action left and insure compatibility with bootsrap 4 2018-08-22 17:21:31 +02:00
Faelysse
c5547091cf Python3 redis compatibility
Redis expects int, Python3 <int>/<int> operation returns floatgit
2018-08-17 16:35:54 +02:00
Nico Zanferrari
83abe91e3a restore original background image 2018-08-16 18:38:55 +02:00
Nico Zanferrari
4f29733fae restore dark navbar 2018-08-16 18:31:13 +02:00
Erik Montes
6b8ccff2a4 Create feature_request.md 2018-08-13 23:36:16 -07:00
Erik Montes
e7fee6a417 Create bug_report.md 2018-08-13 23:34:22 -07:00
mdipierro
63972386c2 fixed limitby in sqlhtml grid, thanks Paolo 2018-08-12 10:56:58 -07:00
Koen van Zuijlen
5c626c6d95 Fixed KeyError that occured when trying to retrieve column 'session_data' 2018-08-10 16:06:40 +02:00
Kelvin Spencer
46b8ad3fdd Update web2py_bootstrap.js
This allows dropdown menu items to be open in the target specified if it exists without changing main/top window
2018-08-08 08:58:15 -05:00
Nico Zanferrari
7111b3dcb2 Python 3 compatibility clarification 2018-08-08 00:13:23 +02:00
Francisco Ribeiro
32eb1bc27d added task broadcasting for workers within a group 2018-05-09 04:45:08 +01:00
30 changed files with 313 additions and 84 deletions

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View 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.

View File

@@ -1,4 +1,4 @@
## 2.17.1
## 2.17.1-2
- pydal 18.08
- many small bug fixes

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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');
}
});
});

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>&nbsp;<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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"""

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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')

View File

@@ -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:

View File

@@ -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'

View File

@@ -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')

View File

@@ -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)]

View File

@@ -462,3 +462,51 @@ def local_html_escape(data, quote=False):
data = data.replace(b'"', b"&quot;")
data = data.replace(b'\'', b"&#x27;")
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)

View File

@@ -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

View File

@@ -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]