Skip to content

Commit 2f2f788

Browse files
vanyakosmosauvipy
authored andcommitted
Add fields validation for CrontabSchedule (#154)
* ignore pyenv .python-version file * add crontab validators * add validators unittests * py2.7 compatibility * add python-crontab to default requirements * use latest version of python-crontab in 2.3+ all bugs were fixed but support for py3.3-3.4 was droped (judging by setup.py tags) * 📝 rewrite crontab validation * 📚 add validators reference file to docs * 📚 include validators reference into index toctree * 📝 add migration * please linters
1 parent 1d6b758 commit 2f2f788

File tree

8 files changed

+571
-8
lines changed

8 files changed

+571
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ cover/
3030
htmlcov/
3131
coverage.xml
3232
.eggs/
33+
.python-version
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 2.0.3 on 2018-09-14 19:22
3+
from __future__ import absolute_import, unicode_literals
4+
5+
from django.db import migrations, models
6+
from django_celery_beat import validators
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('django_celery_beat', '0007_auto_20180521_0826'),
13+
]
14+
15+
operations = [
16+
migrations.AlterField(
17+
model_name='crontabschedule',
18+
name='day_of_month',
19+
field=models.CharField(
20+
default='*', max_length=124,
21+
validators=[validators.day_of_month_validator],
22+
verbose_name='day of month'
23+
),
24+
),
25+
migrations.AlterField(
26+
model_name='crontabschedule',
27+
name='day_of_week',
28+
field=models.CharField(
29+
default='*', max_length=64,
30+
validators=[validators.day_of_week_validator],
31+
verbose_name='day of week'
32+
),
33+
),
34+
migrations.AlterField(
35+
model_name='crontabschedule',
36+
name='hour',
37+
field=models.CharField(
38+
default='*', max_length=96,
39+
validators=[validators.hour_validator],
40+
verbose_name='hour'
41+
),
42+
),
43+
migrations.AlterField(
44+
model_name='crontabschedule',
45+
name='minute',
46+
field=models.CharField(
47+
default='*', max_length=240,
48+
validators=[validators.minute_validator],
49+
verbose_name='minute'
50+
),
51+
),
52+
migrations.AlterField(
53+
model_name='crontabschedule',
54+
name='month_of_year',
55+
field=models.CharField(
56+
default='*', max_length=64,
57+
validators=[validators.month_of_year_validator],
58+
verbose_name='month of year'
59+
),
60+
),
61+
]

django_celery_beat/models.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@
33

44
from datetime import timedelta
55

6+
import timezone_field
7+
from celery import schedules
8+
from celery.five import python_2_unicode_compatible
69
from django.core.exceptions import MultipleObjectsReturned, ValidationError
710
from django.db import models
811
from django.db.models import signals
912
from django.utils.translation import ugettext_lazy as _
1013

11-
from celery import schedules
12-
from celery.five import python_2_unicode_compatible
13-
14-
from . import managers
14+
from . import managers, validators
1515
from .tzcrontab import TzAwareCrontab
16-
from .utils import now, make_aware
16+
from .utils import make_aware, now
1717

18-
import timezone_field
1918

2019
DAYS = 'days'
2120
HOURS = 'hours'
@@ -152,16 +151,25 @@ class CrontabSchedule(models.Model):
152151
# 4 chars for each value (what we save on 0-9 accomodates the []).
153152
# We leave the other fields at their historical length.
154153
#
155-
minute = models.CharField(_('minute'), max_length=60 * 4, default='*')
156-
hour = models.CharField(_('hour'), max_length=24 * 4, default='*')
154+
minute = models.CharField(
155+
_('minute'), max_length=60 * 4, default='*',
156+
validators=[validators.minute_validator],
157+
)
158+
hour = models.CharField(
159+
_('hour'), max_length=24 * 4, default='*',
160+
validators=[validators.hour_validator],
161+
)
157162
day_of_week = models.CharField(
158163
_('day of week'), max_length=64, default='*',
164+
validators=[validators.day_of_week_validator],
159165
)
160166
day_of_month = models.CharField(
161167
_('day of month'), max_length=31 * 4, default='*',
168+
validators=[validators.day_of_month_validator],
162169
)
163170
month_of_year = models.CharField(
164171
_('month of year'), max_length=64, default='*',
172+
validators=[validators.month_of_year_validator],
165173
)
166174

167175
timezone = timezone_field.TimeZoneField(default='UTC')

django_celery_beat/validators.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""Validators."""
2+
from __future__ import absolute_import, unicode_literals
3+
4+
import crontab
5+
from django.core.exceptions import ValidationError
6+
7+
8+
class _CronSlices(crontab.CronSlices):
9+
"""Cron slices with customized validation."""
10+
11+
def __init__(self, *args):
12+
super(crontab.CronSlices, self).__init__(
13+
[_CronSlice(info) for info in crontab.S_INFO]
14+
)
15+
self.special = None
16+
self.setall(*args)
17+
self.is_valid = self.is_self_valid
18+
19+
@classmethod
20+
def validate(cls, *args):
21+
try:
22+
cls(*args)
23+
except Exception as e:
24+
raise ValueError(e)
25+
26+
27+
class _CronSlice(crontab.CronSlice):
28+
"""Cron slice with custom range parser."""
29+
30+
def get_range(self, *vrange):
31+
ret = _CronRange(self, *vrange)
32+
if ret.dangling is not None:
33+
return [ret.dangling, ret]
34+
return [ret]
35+
36+
37+
class _CronRange(crontab.CronRange):
38+
"""Cron range parser class."""
39+
40+
# rewrite whole method to raise error on bad range
41+
def parse(self, value):
42+
if value.count('/') == 1:
43+
value, seq = value.split('/')
44+
try:
45+
self.seq = self.slice.parse_value(seq)
46+
except crontab.SundayError:
47+
self.seq = 1
48+
value = "0-0"
49+
if self.seq < 1 or self.seq > self.slice.max:
50+
raise ValueError("Sequence can not be divided by zero or max")
51+
if value.count('-') == 1:
52+
vfrom, vto = value.split('-')
53+
self.vfrom = self.slice.parse_value(vfrom, sunday=0)
54+
try:
55+
self.vto = self.slice.parse_value(vto)
56+
except crontab.SundayError:
57+
if self.vfrom == 1:
58+
self.vfrom = 0
59+
else:
60+
self.dangling = 0
61+
self.vto = self.slice.parse_value(vto, sunday=6)
62+
if self.vto < self.vfrom:
63+
raise ValueError("Bad range '{0.vfrom}-{0.vto}'".format(self))
64+
elif value == '*':
65+
self.all()
66+
else:
67+
raise ValueError('Unknown cron range value "%s"' % value)
68+
69+
70+
def crontab_validator(value):
71+
"""Validate crontab."""
72+
try:
73+
_CronSlices.validate(value)
74+
except ValueError as e:
75+
raise ValidationError(e)
76+
77+
78+
def minute_validator(value):
79+
"""Validate minutes crontab value."""
80+
_validate_crontab(value, 0)
81+
82+
83+
def hour_validator(value):
84+
"""Validate hours crontab value."""
85+
_validate_crontab(value, 1)
86+
87+
88+
def day_of_month_validator(value):
89+
"""Validate day of month crontab value."""
90+
_validate_crontab(value, 2)
91+
92+
93+
def month_of_year_validator(value):
94+
"""Validate month crontab value."""
95+
_validate_crontab(value, 3)
96+
97+
98+
def day_of_week_validator(value):
99+
"""Validate day of week crontab value."""
100+
_validate_crontab(value, 4)
101+
102+
103+
def _validate_crontab(value, index):
104+
tab = ['*'] * 5
105+
tab[index] = value
106+
tab = ' '.join(tab)
107+
crontab_validator(tab)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=====================================================
2+
``django_celery_beat.validators``
3+
=====================================================
4+
5+
.. contents::
6+
:local:
7+
.. currentmodule:: django_celery_beat.validators
8+
9+
.. automodule:: django_celery_beat.validators
10+
:members:
11+
:undoc-members:

docs/reference/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
django-celery-beat.schedulers
1818
django-celery-beat.admin
1919
django-celery-beat.utils
20+
django-celery-beat.validators

requirements/default.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
celery>=4.0,<5.0
22
django-timezone-field>=2.0
3+
python-crontab>=2.3.4

0 commit comments

Comments
 (0)