1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator, RegexValidator
from django.utils.deconstruct import deconstructible
from io import BytesIO
import re
import requests
from PIL import Image, ImageFile
def validate_lowercase(value):
if value != value.lower():
raise ValidationError("This field must be lowercase only")
_urlname_re = re.compile(r'^\w+\Z')
validate_urlname = RegexValidator(
_urlname_re,
"Enter a valid urlname consisting of letters, numbers or underscore.",
'invalid'
)
class BeforeValidator(object):
def __init__(self, beforedate):
self.beforedate = beforedate
def __call__(self, value):
if value >= self.beforedate:
raise ValidationError("Ensure this date is before {0}".format(self.beforedate))
class AfterValidator(object):
def __init__(self, afterdate):
self.afterdate = afterdate
def __call__(self, value):
if value <= self.afterdate:
raise ValidationError("Ensure this date is after {0}".format(self.afterdate))
def Http200Validator(value):
try:
r = requests.get(value, timeout=5, headers={
'User-Agent': 'pgeusys-link-validator/1.0',
})
if r.status_code != 200:
raise ValidationError("URL must return 200 OK, not {0}".format(r.status_code))
except requests.exceptions.InvalidURL as e:
raise ValidationError("{}".format(e))
except requests.ConnectionError:
raise ValidationError("Connection to server failed")
except requests.exceptions.ReadTimeout:
raise ValidationError("URL timed out")
except requests.exceptions.InvalidSchema:
raise ValidationError("Invalid schema in URL, please use http or https")
def TwitterValidator(value):
if value.startswith('@'):
value = value[1:]
if value == '':
# This can only happen if it was '@' initially
raise ValidationError("Enter twitter name or leave field empty")
# Twitter have broken our way of checking the username, by always returning 200 OK
# with an embedded (and not easy to find) error message. We need to re-do this with
# an authenticated API, but for now we just turn it into a simple regexp validaotor.
if not re.match('^[a-zA-Z0-9_]+$', value):
raise ValidationError('Valid Twitter names must contain only the characters a-z, the numbers 0-9, or underscore')
# Else we skip the check and just say it's fine
return value
try:
r = requests.get('https://twitter.com/{0}'.format(value),
headers={'User-agent': 'Firefox/60'},
timeout=5)
except requests.exceptions.ReadTimeout:
raise ValidationError("Could not verify twitter name - timeout")
if r.status_code != 200:
raise ValidationError("Could not verify twitter name: {0}".format(r.status_code))
# All is well! :)
return value
def ListOfEmailAddressValidator(value):
for p in value.split(','):
EmailValidator("Enter a comma separated list of valid email addresses")(p.strip())
def validate_json_structure(config, structure):
def _validate_json_level(config, structure, path):
missing = set(structure.keys()).difference(set(config.keys()))
if missing:
raise ValidationError("Keys {0} are missing".format(", ".join(["->".join(path + [m]) for m in missing])))
extra = set(config.keys()).difference(set(structure.keys()))
if extra:
raise ValidationError("Keys {0} are not allowed".format(", ".join(["->".join(path + [m]) for m in extra])))
# Keys are correct, validate datatypes
for k, v in list(config.items()):
fullkey = "->".join(path + [k])
# Dicts don't have __name__
if type(structure[k]) == dict:
structtype = dict
else:
structtype = structure[k]
structname = structtype.__name__
valname = type(v).__name__
if type(v) != structtype:
raise ValidationError("Value for {0} should be of type {1}, not {2}".format(fullkey, structname, valname))
if isinstance(v, dict):
# Recursively check substructure
_validate_json_level(v, structure[k], path + [k])
_validate_json_level(config, structure, [])
@deconstructible
class PictureUrlValidator(object):
def __init__(self, aspect=None):
self.aspect = aspect
def __call__(self, value):
try:
r = requests.get(value,
headers={'User-agent': 'Firefox/60'},
timeout=5)
except Exception as e:
raise ValidationError("Could not download promotion picture")
if r.status_code != 200:
raise ValidationError("Downloading promo picture returned status %s" % r.status_code)
try:
img = Image.open(BytesIO(r.content))
w, h = img.size
if self.aspect:
newaspect = round(float(w) / float(h), 2)
if newaspect != self.aspect:
raise ValidationError("Image has aspect ratio %s, must have %s" % (newaspect, self.aspect))
except ValidationError:
raise
except Exception as e:
raise ValidationError("Failed to parse image: %s" % e)
def __eq__(self, other):
return self.aspect == other.aspect
@deconstructible
class ImageValidator(object):
def __init__(self, formats=['JPEG', ], maxsize=None):
self.formats = formats
self.maxsize = maxsize
def __call__(self, value):
if value.size is None:
# This happens when no new file is uploaded, so assume things are fine
return
try:
p = ImageFile.Parser()
p.feed(value.read())
p.close()
img = p.image
except Exception as e:
raise ValidationError("Could not parse image: %s" % e)
if img.format.upper() not in self.formats:
raise ValidationError("Files of format {0} are not accepted, only {1}".format(img.format, ", ".join(self.formats)))
if self.maxsize:
if img.size[0] > self.maxsize[0] or img.size[1] > self.maxsize[1]:
raise ValidationError("Maximum image size is {}x{}".format(*self.maxsize))
def color_validator(value):
if not value.startswith('#'):
raise ValidationError('Color values must start with #')
if len(value) != 7:
raise ValidationError('Color values must be # + 7 characters')
for n in range(0, 3):
try:
int(value[n * 2 + 1:n * 2 + 2 + 1], 16)
except ValueError:
raise ValidationError('Invalid value in color specification')
|