Hi I've created my first custom django field. Its a social security number field (Cpr) following danish syntax. As I'm new to both python and django i would love to get some feedback on the code!
import django.forms as f
from datetime import datetime
import json
from django.utils.safestring import mark_safe
from django.core.exceptions import ValidationError
# Globals
YEAR = datetime.now().year
FIELDS = (
{'name' : "cpr_date", 'size': 2, 'maxlength': 2, 'renderfunc': None,
'field': f.IntegerField(min_value=1, max_value=31)
},
{'name' : "cpr_month", 'size': 2, 'maxlength': 2, 'renderfunc': None,
'field': f.IntegerField(min_value=1, max_value=12),
},
{'name' : "cpr_year", 'size': 4, 'maxlength': 4,
'renderfunc': 'render_age',
'field': f.IntegerField(min_value=YEAR-120, max_value=YEAR),
},
{'name' : "cpr_digits", 'size': 4, 'maxlength': 4,
'renderfunc': 'render_gender',
'field': f.IntegerField(min_value=1000, max_value=9999),
},
)
class CprWidget(f.widgets.MultiWidget):
"""Widget showing date, month, year and 4-last-digit fields for a danish cpr
number.
"""
def __init__(self, attrs=None):
widgets = [f.widgets.TextInput(field) for field in FIELDS]
super(CprWidget, self).__init__(widgets, attrs)
def decompress(self, v):
"""Decompresses widget values to json before save"""
if v: return json.loads(v)
return [None, None, None, None]
def render(self, name, value, attrs=None):
"""Extends with details rendered from parts of Cpr number"""
values = self.decompress_value(value)
output = super(CprWidget, self).render(name, value, attrs)
extra_output = ""
all_is_valid = True
i = 0
for field in FIELDS:
try:
value = values[i]
if value == None:
raise ValueError
fieldinst = field['field']
fieldinst.validate(value)
fieldinst.run_validators(value)
if field['renderfunc']:
func = getattr(self, field['renderfunc'])
extra_output = extra_output + func(value)
except (IndexError, ValueError, ValidationError):
all_is_valid = False
finally:
i = i + 1
if all_is_valid:
extra_output = extra_output + self.render_cpr(values)
return output + mark_safe(extra_output)
def render_gender(self, value):
"""Renders male or female based on last digit of Cpr"""
last_digit = int(str(value)[3])
gender = (('Male','Female')[last_digit % 2 == 0])
return self.render_line("Gender", gender)
def render_age(self, value):
"""Renders age"""
return self.render_line("Age", YEAR-value)
def render_cpr(self, values):
"""Renders full Cpr number"""
cpr = "%02d%02d%s-%s" % (
int(values[0]), int(values[1]), str(values[2])[2:4], values[3])
return self.render_line("Cpr", cpr)
def render_line(self, title, value):
"""Renders a line with title and value"""
return "<br /><b>%s: </b>%s" % (title, value)
def decompress_value(self, value):
"""Decompress values from value if needed"""
if not isinstance(value, list):
value = self.decompress(value)
return value
def format_output(self, rendered_widgets):
"""Format html for widgets"""
labels = [u'Date', u'Month', u'Year', u'Digits']
labels.reverse()
html = u''
for w in rendered_widgets: html += labels.pop() + ': ' + str(w)
return html
class CprField(f.MultiValueField):
"""Field extending MultiValueField with date, month, year and 4-last-digit
fields for a danish cpr number.
"""
widget = CprWidget
def __init__(self, required=False, widget=None, label=None, initial=None):
fields = [field['field'] for field in FIELDS]
super(CprField, self).__init__(fields=fields, widget=widget, label=label
,initial=initial, required=required)
def compress(self, data_list):
"""Compress field values to json"""
return json.dumps(data_list)