diff --git a/gluon/contrib/redis_scheduler.py b/gluon/contrib/redis_scheduler.py index 6a3f00fc..bc39201d 100644 --- a/gluon/contrib/redis_scheduler.py +++ b/gluon/contrib/redis_scheduler.py @@ -487,7 +487,7 @@ class RScheduler(Scheduler): # calc next_run_time based on available slots # see #1191 next_run_time = task.start_time - secondspassed = self.total_seconds(now - next_run_time) + secondspassed = (now - next_run_time).total_seconds() steps = secondspassed // task.period + 1 next_run_time += datetime.timedelta(seconds=task.period * steps) diff --git a/gluon/tests/test_validators.py b/gluon/tests/test_validators.py index 73712664..8afccc5a 100644 --- a/gluon/tests/test_validators.py +++ b/gluon/tests/test_validators.py @@ -977,6 +977,8 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, (img, 'oops')) rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img) self.assertEqual(rtn, (img, 'oops')) + rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img) + self.assertEqual(rtn, (img, 'oops')) img = DummyImageFile('test', 'gif', 50, 100) rtn = IS_IMAGE()(img) @@ -985,6 +987,8 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, (img, 'oops')) rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img) self.assertEqual(rtn, (img, 'oops')) + rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img) + self.assertEqual(rtn, (img, 'oops')) img = DummyImageFile('test', 'jpeg', 50, 100) rtn = IS_IMAGE()(img) @@ -993,6 +997,8 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, (img, 'oops')) rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img) self.assertEqual(rtn, (img, 'oops')) + rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img) + self.assertEqual(rtn, (img, 'oops')) img = DummyImageFile('test', 'png', 50, 100) rtn = IS_IMAGE()(img) @@ -1001,6 +1007,8 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, (img, 'oops')) rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img) self.assertEqual(rtn, (img, 'oops')) + rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img) + self.assertEqual(rtn, (img, 'oops')) img = DummyImageFile('test', 'xls', 50, 100) rtn = IS_IMAGE(error_message='oops')(img) diff --git a/gluon/validators.py b/gluon/validators.py index 7e47f79c..541dddd1 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -3230,8 +3230,10 @@ class IS_IMAGE(Validator): ('jpg' extension of uploaded file counts as 'jpeg') maxsize: iterable containing maximum width and height of the image minsize: iterable containing minimum width and height of the image + aspectratio: iterable containing target aspect ratio Use (-1, -1) as minsize to pass image size check. + Use (-1, -1) as aspectratio to pass aspect ratio check. Examples: Check if uploaded file is in any of supported image formats: @@ -3247,17 +3249,24 @@ class IS_IMAGE(Validator): INPUT(_type='file', _name='name', requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200))) + + Check if uploaded file has a 16:9 aspect ratio: + + INPUT(_type='file', _name='name', + requires=IS_IMAGE(aspectratio=(16, 9))) """ def __init__(self, extensions=('bmp', 'gif', 'jpeg', 'png'), maxsize=(10000, 10000), minsize=(0, 0), + aspectratio=(-1, -1), error_message='Invalid image'): self.extensions = extensions self.maxsize = maxsize self.minsize = minsize + self.aspectratio = aspectratio self.error_message = error_message def __call__(self, value): @@ -3279,8 +3288,16 @@ class IS_IMAGE(Validator): else: width = -1 height = -1 + assert self.minsize[0] <= width <= self.maxsize[0] \ and self.minsize[1] <= height <= self.maxsize[1] + + if self.aspectratio > (-1, -1): + target_ratio = (1.0 * self.aspectratio[1]) / self.aspectratio[0] + actual_ratio = (1.0 * height) / width + + assert actual_ratio == target_ratio + value.file.seek(0) return (value, None) except Exception as e: