diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 28198d2a..4a142f22 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -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): diff --git a/applications/examples/views/default/index.html b/applications/examples/views/default/index.html index 19708c79..735ee4cc 100644 --- a/applications/examples/views/default/index.html +++ b/applications/examples/views/default/index.html @@ -4,7 +4,7 @@
Web FrameworkFree open source full-stack framework for rapid development of fast, scalable, secure and portable database-driven web-based applications. Written and programmable in Python.
+Free open source full-stack framework for rapid development of fast, scalable, secure and portable database-driven web-based applications. Written and programmable in Python (version 3 and 2.7).
| @@ -18,7 +18,7 @@ |
-
+
|
diff --git a/gluon/globals.py b/gluon/globals.py
index 26fba47c..7390a3f9 100644
--- a/gluon/globals.py
+++ b/gluon/globals.py
@@ -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
diff --git a/gluon/languages.py b/gluon/languages.py
index 07dbbac9..af7bc326 100644
--- a/gluon/languages.py
+++ b/gluon/languages.py
@@ -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)
diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py
index 68d8014c..2243a7c2 100644
--- a/gluon/sqlhtml.py
+++ b/gluon/sqlhtml.py
@@ -1351,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)]
@@ -2023,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.
@@ -2036,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):
@@ -2314,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',
@@ -2692,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
diff --git a/gluon/tests/test_globals.py b/gluon/tests/test_globals.py
index 666249fa..bd78e4e1 100644
--- a/gluon/tests/test_globals.py
+++ b/gluon/tests/test_globals.py
@@ -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'
diff --git a/gluon/tests/test_sqlhtml.py b/gluon/tests/test_sqlhtml.py
index 92c2916e..52a69530 100644
--- a/gluon/tests/test_sqlhtml.py
+++ b/gluon/tests/test_sqlhtml.py
@@ -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'