diff --git a/smash/web/forms/subject_forms.py b/smash/web/forms/subject_forms.py index b4f12f65c5eb51b4bf0f221517d04123fc49387e..cc717842d2065b99fb9e622ebc63ae0ec17fe785 100644 --- a/smash/web/forms/subject_forms.py +++ b/smash/web/forms/subject_forms.py @@ -1,3 +1,5 @@ +import logging + from django import forms from django.forms import ModelForm @@ -5,12 +7,94 @@ from web.forms.forms import DATEPICKER_DATE_ATTRS from web.models import Subject from web.models.constants import COUNTRY_OTHER_ID +logger = logging.getLogger(__name__) + def validate_subject_country(self, cleaned_data): if cleaned_data['country'].id == COUNTRY_OTHER_ID: self.add_error('country', "Select valid country") +def validate_social_security_number(self, number): + if not is_valid_social_security_number(number): + self.add_error('social_security_number', "Social security number is invalid") + + +def is_valid_social_security_number(number): + if number is not None and number != '': + if len(number) != 13: + return False + if not number.isdigit(): + return False + if not is_luhn_valid(number[:12]): + return False + if not is_valid_verhoeff(number[:11] + number[12]): + return False + + return True + + +def luhn_checksum(card_number): + def digits_of(n): + return [int(d) for d in str(n)] + + digits = digits_of(card_number) + odd_digits = digits[-1::-2] + even_digits = digits[-2::-2] + checksum = 0 + checksum += sum(odd_digits) + for d in even_digits: + checksum += sum(digits_of(d * 2)) + return checksum % 10 + + +def is_luhn_valid(card_number): + return luhn_checksum(card_number) == 0 + + +verhoeff_multiplication_table = ( + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + (1, 2, 3, 4, 0, 6, 7, 8, 9, 5), + (2, 3, 4, 0, 1, 7, 8, 9, 5, 6), + (3, 4, 0, 1, 2, 8, 9, 5, 6, 7), + (4, 0, 1, 2, 3, 9, 5, 6, 7, 8), + (5, 9, 8, 7, 6, 0, 4, 3, 2, 1), + (6, 5, 9, 8, 7, 1, 0, 4, 3, 2), + (7, 6, 5, 9, 8, 2, 1, 0, 4, 3), + (8, 7, 6, 5, 9, 3, 2, 1, 0, 4), + (9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) + +verhoeff_permutation_table = ( + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + (1, 5, 7, 6, 2, 8, 3, 0, 9, 4), + (5, 8, 0, 3, 7, 9, 6, 1, 4, 2), + (8, 9, 1, 6, 0, 4, 3, 5, 2, 7), + (9, 4, 5, 3, 1, 2, 6, 8, 7, 0), + (4, 2, 8, 6, 5, 7, 3, 9, 0, 1), + (2, 7, 9, 3, 8, 0, 6, 4, 1, 5), + (7, 0, 4, 6, 9, 1, 3, 2, 5, 8)) + + +def verhoeff_checksum(number): + """Calculate the Verhoeff checksum over the provided number. The checksum + is returned as an int. Valid numbers should have a checksum of 0.""" + # transform number list + number = tuple(int(n) for n in reversed(str(number))) + # calculate checksum + check = 0 + for i, n in enumerate(number): + check = verhoeff_multiplication_table[check][verhoeff_permutation_table[i % 8][n]] + return check + + +def is_valid_verhoeff(number): + return verhoeff_checksum(number) == 0 + + +def calculate_verhoeff_check_sum(number): + return str(verhoeff_multiplication_table[verhoeff_checksum(str(number) + '0')].index(0)) + + FIELD_ORDER = ["first_name", "last_name", "sex", "date_born", "social_security_number", "default_written_communication_language", "languages", "phone_number", "phone_number_2", "phone_number_3", "address", "city", "postal_code", "country"] @@ -32,6 +116,7 @@ class SubjectAddForm(ModelForm): def clean(self): cleaned_data = super(SubjectAddForm, self).clean() validate_subject_country(self, cleaned_data) + validate_social_security_number(self, cleaned_data["social_security_number"]) return cleaned_data @@ -53,6 +138,7 @@ class SubjectEditForm(ModelForm): def clean(self): validate_subject_country(self, self.cleaned_data) + validate_social_security_number(self, self.cleaned_data["social_security_number"]) class Meta: model = Subject diff --git a/smash/web/tests/forms/test_subject_forms.py b/smash/web/tests/forms/test_subject_forms.py new file mode 100644 index 0000000000000000000000000000000000000000..beb2bcbd1acff6c20ff7dae0ca213588d600b6bb --- /dev/null +++ b/smash/web/tests/forms/test_subject_forms.py @@ -0,0 +1,29 @@ +import logging + +from web.forms.subject_forms import is_valid_social_security_number +from web.tests import LoggedInWithWorkerTestCase +from web.tests.functions import create_subject + +logger = logging.getLogger(__name__) + + +class StudySubjectAddFormTests(LoggedInWithWorkerTestCase): + def setUp(self): + super(LoggedInWithWorkerTestCase, self).setUp() + self.subject = create_subject() + + def test_is_valid_social_security_number_too_short(self): + self.assertFalse(is_valid_social_security_number("123")) + + def test_is_valid_social_security_number_not_a_number(self): + # noinspection SpellCheckingInspection + self.assertFalse(is_valid_social_security_number("ABCDEFGHIJKLM")) + + def test_is_valid_social_security_number_invalid(self): + self.assertFalse(is_valid_social_security_number("1234567890123")) + + def test_is_valid_social_security_number(self): + self.assertTrue(is_valid_social_security_number("1893120105732")) + + def test_is_valid_social_security_number_empty(self): + self.assertTrue(is_valid_social_security_number(""))