371 lines
11 KiB
Python
371 lines
11 KiB
Python
import decimal, datetime
|
|
import inspect
|
|
from six import string_types
|
|
from . import enums
|
|
|
|
def is_blank_space(val):
|
|
return len(val.strip()) == 0
|
|
|
|
class ValidationError(Exception):
|
|
def __init__(self, msg, field=None):
|
|
self.msg = msg
|
|
self.field = field
|
|
|
|
def __str__(self):
|
|
if self.field:
|
|
return "(%s.%s) %s" % (self.field.parent_name, self.field.name, self.msg)
|
|
else:
|
|
return repr(self.msg)
|
|
|
|
|
|
class Field(object):
|
|
creation_counter = 0
|
|
is_read_only = False
|
|
_value = None
|
|
|
|
def __init__(self, name=None, min_length=0, max_length=0, blank=False, required=True, uppercase=True, creation_counter=None):
|
|
self.name = name
|
|
self._value = None
|
|
self._orig_value = None
|
|
self.min_length = min_length
|
|
self.max_length = max_length
|
|
self.blank = blank
|
|
self.required = required
|
|
self.uppercase = uppercase
|
|
self.creation_counter = creation_counter or Field.creation_counter
|
|
Field.creation_counter += 1
|
|
|
|
def validate(self):
|
|
raise NotImplementedError
|
|
|
|
def get_data(self):
|
|
raise NotImplementedError
|
|
|
|
def __setvalue(self, value):
|
|
self._value = value
|
|
|
|
def __getvalue(self):
|
|
return self._value
|
|
|
|
def __len__(self):
|
|
return self.max_length
|
|
|
|
value = property(__getvalue, __setvalue)
|
|
|
|
def read(self, fp):
|
|
data = fp.read(self.max_length)
|
|
if len(data) == self.max_length:
|
|
self._orig_value = data
|
|
return self.parse(data)
|
|
raise ValueError()
|
|
|
|
def parse(self, s):
|
|
self.value = s.strip()
|
|
|
|
def toJSON(self):
|
|
data = self.get_data()
|
|
|
|
return {
|
|
'__class__': self.__class__.__name__,
|
|
'name': self.name,
|
|
'maxLength': self.max_length,
|
|
'required': self.required,
|
|
'data': data,
|
|
'value': self._value,
|
|
'dataLength': len(data)
|
|
}
|
|
|
|
def fromJSON(self, o):
|
|
import re
|
|
|
|
self.__init__(
|
|
name=o['name'],
|
|
max_length=o['maxLength'],
|
|
required=o['required'],
|
|
)
|
|
|
|
if isinstance(o['value'], str) and re.match(r'^\d*\.\d*$', o['value']):
|
|
o['value'] = decimal.Decimal(o['value'])
|
|
|
|
self.value = o['value']
|
|
return self
|
|
|
|
def debug(self, counter):
|
|
import textwrap
|
|
|
|
value = (self._orig_value or str(self.value))
|
|
|
|
wrapper = textwrap.TextWrapper(replace_whitespace=False, drop_whitespace=False)
|
|
wrapper.width = 100
|
|
value = wrapper.wrap(value)
|
|
value = list([(" " * 9) + ('"' + x + '"') for x in value])
|
|
value.append(" " * 10 + ('_' * 10) * int(wrapper.width / 10))
|
|
value.append(" " * 10 + ('0123456789') * int(wrapper.width / 10))
|
|
value.append(" " * 10 + ''.join(([str(x) + (' ' * 9) for x in range(int(wrapper.width / 10))])))
|
|
|
|
start = counter['c']
|
|
counter['c'] += len(self._orig_value or self.value)
|
|
end = counter['c']
|
|
|
|
return (
|
|
str(start) + '-' + str(end-1) +
|
|
' [' +
|
|
str(len(self._orig_value or '') or len(str(self.value))) +
|
|
'] ' + self.name +
|
|
'\n' +
|
|
'\n'.join(value)
|
|
)
|
|
|
|
|
|
class TextField(Field):
|
|
def validate(self):
|
|
if self.value is None and self.required:
|
|
raise ValidationError("value required", field=self)
|
|
data = self.get_data()
|
|
if len(data) > self.max_length:
|
|
raise ValidationError("value is too long", field=self)
|
|
stripped_data_length = len(data.strip())
|
|
if stripped_data_length < self.min_length:
|
|
raise ValidationError("value is too short", field=self)
|
|
if stripped_data_length == 0 and (not self.blank and self.required):
|
|
raise ValidationError("field cannot be blank", field=self)
|
|
|
|
def get_data(self):
|
|
value = str(self.value or '').encode('ascii') or b''
|
|
if self.uppercase:
|
|
value = value.upper()
|
|
return value.ljust(self.max_length)[:self.max_length]
|
|
|
|
def __setvalue(self, value):
|
|
# NO NEWLINES
|
|
try:
|
|
value = value.replace('\n', '').replace('\r', '')
|
|
except AttributeError:
|
|
pass
|
|
self._value = value
|
|
|
|
def __getvalue(self):
|
|
return self._value
|
|
|
|
value = property(__getvalue, __setvalue)
|
|
|
|
|
|
class StateField(TextField):
|
|
def __init__(self, name=None, required=True, use_numeric=False, max_length=2):
|
|
super(StateField, self).__init__(name=name, max_length=max_length, required=required)
|
|
self.use_numeric = use_numeric
|
|
|
|
def get_data(self):
|
|
value = str(self.value or 'XX')
|
|
if value.strip() and self.use_numeric:
|
|
postcode = enums.state_postal_numeric[value.upper()]
|
|
postcode = str(postcode).encode('ascii')
|
|
return postcode.zfill(self.max_length)
|
|
else:
|
|
formatted = value.encode('ascii').ljust(self.max_length)
|
|
return formatted[:self.max_length]
|
|
|
|
def validate(self):
|
|
super(StateField, self).validate()
|
|
if self.value and self.value.upper() not in list(enums.state_postal_numeric.keys()):
|
|
raise ValidationError("%s is not a valid state abbreviation" % self.value, field=self)
|
|
|
|
def parse(self, s):
|
|
if s.strip() and self.use_numeric:
|
|
states = dict([(v, k) for (k, v) in list(enums.state_postal_numeric.items())])
|
|
self.value = states[int(s)]
|
|
else:
|
|
self.value = s
|
|
|
|
|
|
class EmailField(TextField):
|
|
def __init__(self, name=None, required=True, max_length=None):
|
|
super(EmailField, self).__init__(name=name, max_length=max_length,
|
|
required=required, uppercase=False)
|
|
|
|
class IntegerField(TextField):
|
|
def validate(self):
|
|
super(IntegerField, self).validate()
|
|
if self.value:
|
|
try:
|
|
int(self.value)
|
|
except ValueError:
|
|
raise ValidationError("field contains non-numeric characters", field=self)
|
|
|
|
def get_data(self):
|
|
value = str(self.value).encode('ascii') if self.value else b''
|
|
return value.zfill(self.max_length)[:self.max_length]
|
|
|
|
def parse(self, s):
|
|
if not is_blank_space(s):
|
|
self.value = int(s)
|
|
else:
|
|
self.value = 0
|
|
|
|
|
|
class StaticField(TextField):
|
|
def __init__(self, name=None, required=True, value=None, uppercase=False):
|
|
super(StaticField, self).__init__(name=name,
|
|
required=required,
|
|
max_length=len(value),
|
|
uppercase=uppercase)
|
|
self._static_value = value
|
|
self._value = value
|
|
|
|
def parse(self, s):
|
|
pass
|
|
|
|
class BlankField(TextField):
|
|
is_read_only = True
|
|
|
|
def __init__(self, name=None, max_length=0, required=False):
|
|
super(BlankField, self).__init__(name=name, max_length=max_length, required=required, uppercase=False)
|
|
|
|
def get_data(self):
|
|
return b' ' * self.max_length
|
|
|
|
def parse(self, s):
|
|
pass
|
|
|
|
def validate(self):
|
|
if len(self.get_data()) != self.max_length:
|
|
raise ValidationError("blank field did not match expected length", field=self)
|
|
|
|
|
|
class ZeroField(BlankField):
|
|
is_read_only = True
|
|
|
|
def get_data(self):
|
|
return b'0' * self.max_length
|
|
|
|
|
|
class CRLFField(TextField):
|
|
is_read_only = True
|
|
|
|
def __init__(self, name=None, required=False):
|
|
super(CRLFField, self).__init__(name=name, max_length=2, required=required, uppercase=False)
|
|
|
|
def __setvalue(self, value):
|
|
self._value = value
|
|
|
|
def __getvalue(self):
|
|
return self._value
|
|
|
|
value = property(__getvalue, __setvalue)
|
|
|
|
def get_data(self):
|
|
return b'\r\n'
|
|
|
|
def parse(self, s):
|
|
self.value = s
|
|
|
|
|
|
class BooleanField(Field):
|
|
def __init__(self, name=None, required=True, value=None):
|
|
super(BooleanField, self).__init__(name=name, required=required, max_length=1)
|
|
self._value = value
|
|
|
|
def validate(self):
|
|
pass
|
|
|
|
def get_data(self):
|
|
return b'1' if self._value else b'0'
|
|
|
|
def parse(self, s):
|
|
self.value = (s == '1')
|
|
|
|
|
|
class MoneyField(Field):
|
|
def __init__(self, name=None, max_length=0, required=False):
|
|
super(MoneyField, self).__init__(name=name, uppercase=False, max_length=max_length, required=required)
|
|
|
|
def validate(self):
|
|
if self.value == None and self.required:
|
|
raise ValidationError("value required", field=self)
|
|
if len(str(int((self.value or 0)*100))) > self.max_length:
|
|
raise ValidationError("value is too long", field=self)
|
|
|
|
def get_data(self):
|
|
cents = int((self.value or 0) * 100)
|
|
formatted = str(cents).encode('ascii').zfill(self.max_length)
|
|
return formatted[:self.max_length]
|
|
|
|
def parse(self, s):
|
|
if not is_blank_space(s):
|
|
self.value = decimal.Decimal(s) * decimal.Decimal('0.01')
|
|
else:
|
|
self.value = decimal.Decimal(0.0)
|
|
|
|
def __setvalue(self, value):
|
|
new_value = value
|
|
if isinstance(new_value, string_types):
|
|
new_value = decimal.Decimal(new_value or '0')
|
|
if '.' not in value: # must be cents?
|
|
new_value *= decimal.Decimal('100.')
|
|
self._value = new_value
|
|
|
|
def __getvalue(self):
|
|
return self._value
|
|
|
|
value = property(__getvalue, __setvalue)
|
|
|
|
class DateField(TextField):
|
|
def __init__(self, name=None, required=True, value=None):
|
|
super(DateField, self).__init__(name=name, required=required, max_length=8)
|
|
if value:
|
|
self.value = value
|
|
|
|
def get_data(self):
|
|
if self._value:
|
|
return self._value.strftime('%m%d%Y').encode('ascii')
|
|
return b'0' * self.max_length
|
|
|
|
def parse(self, s):
|
|
if int(s) > 0:
|
|
self.value = datetime.date(*[int(x) for x in (s[4:8], s[0:2], s[2:4])])
|
|
else:
|
|
self.value = None
|
|
|
|
def __setvalue(self, value):
|
|
if isinstance(value, datetime.date):
|
|
self._value = value
|
|
elif value:
|
|
self._value = datetime.date(*[int(x) for x in (value[4:8], value[0:2], value[2:4])])
|
|
else:
|
|
self._value = None
|
|
|
|
def __getvalue(self):
|
|
return self._value
|
|
|
|
value = property(__getvalue, __setvalue)
|
|
|
|
|
|
class MonthYearField(TextField):
|
|
def __init__(self, name=None, required=True, value=None):
|
|
super(MonthYearField, self).__init__(name=name, required=required, max_length=6)
|
|
if value:
|
|
self.value = value
|
|
|
|
def get_data(self):
|
|
if self._value:
|
|
return str(self._value.strftime('%m%Y').encode('ascii'))
|
|
return b'0' * self.max_length
|
|
|
|
def parse(self, s):
|
|
if int(s) > 0:
|
|
self.value = datetime.date(*[int(x) for x in (s[2:6], s[0:2], 1)])
|
|
else:
|
|
self.value = None
|
|
|
|
def __setvalue(self, value):
|
|
if isinstance(value, datetime.date):
|
|
self._value = value
|
|
elif value:
|
|
self._value = datetime.date(*[int(x) for x in (value[2:6], value[0:2], 1)])
|
|
else:
|
|
self._value = None
|
|
|
|
def __getvalue(self):
|
|
return self._value
|
|
|
|
value = property(__getvalue, __setvalue)
|