Skip to content

Commit

Permalink
Added NOBankAccountNumber field for Norway.
Browse files Browse the repository at this point in the history
Includes tests, update to docs and uses to_python, validate and
prepare_value only.
  • Loading branch information
thor committed Jan 14, 2017
1 parent 6cefb13 commit 42386bd
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/authors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Authors
* Stefan Kjartansson
* tadeo
* Thiago Avelino
* Thor K. Høgås
* Tino de Bruijn
* Trey Hunner
* Tyler Ball
Expand Down
3 changes: 2 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ New flavors:

New fields for existing flavors:

- None
- Added NOBankAccountNumber form field.
(`gh-275 <https://github.com/django/django-localflavor/pull/275>`_)

Modifications to existing flavors:

Expand Down
65 changes: 63 additions & 2 deletions localflavor/no/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@

from django.core.validators import EMPTY_VALUES
from django.forms import ValidationError
from django.forms.fields import Field, RegexField, Select
from django.forms.fields import CharField, Field, RegexField, Select
from django.utils.translation import ugettext_lazy as _

from localflavor.generic.forms import DeprecatedPhoneNumberFormFieldMixin

from .no_municipalities import MUNICIPALITY_CHOICES


Expand Down Expand Up @@ -98,6 +97,68 @@ def _get_birthday(self, value):
return birthday


class NOBankAccountNumber(CharField):
"""
A form field for Norwegian bank account numbers.
Performs MOD11 with the custom weights for the Norwegian bank account numbers,
including a check for a remainder of 0, in which event the checksum is also 0.
Usually their string representation is along the lines of ZZZZ.YY.XXXXX, where the last X is the check digit.
They're always a total of 11 digits long, with 10 out of these 11 being the actual account number itself.
* Accepts, and strips, account numbers with extra spaces.
* Accepts, and strips, account numbers provided in form of XXXX.YY.XXXXX.
.. note:: No consideration is taking for banking clearing numbers as of yet, seeing as these are only used between
banks themselves.
.. versionadded:: 1.5
"""

default_error_messages = {
'invalid': _('Enter a valid Norwegian bank account number.'),
'invalid_checksum': _('Invalid control digit. Enter a valid Norwegian bank account number.'),
'invalid_length': _('Invalid length. Norwegian bank account numbers are 11 digits long.'),
}

def validate(self, value):
super(NOBankAccountNumber, self).validate(value)

if value is '':
# It's alright to be empty.
return
elif not value.isdigit():
# You must only contain decimals.
raise ValidationError(self.error_messages['invalid'])
elif len(value) is not 11:
# They only have one length: the number is 10!
# That being said, you always store them with the check digit included, so 11.
raise ValidationError(self.error_messages['invalid_length'])

# The control/check digit is the last digit
check_digit = int(value[-1])
bank_number = value[:-1]

# These are the weights by which we multiply to get our checksum digit
weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
result = sum(w * (int(x)) for w, x in zip(weights, bank_number))
remainder = result % 11
# The checksum is 0 in the event there's no remainder, seeing as we cannot have a checksum of 11
# when 11 is one digit longer than we've got room for
checksum = 0 if remainder is 0 else 11 - remainder

if checksum != check_digit:
raise ValidationError(self.error_messages['invalid_checksum'])

def to_python(self, value):
value = super(NOBankAccountNumber, self).to_python(value)
return value.replace(".", "").replace(" ", "")

def prepare_value(self, value):
return "{}.{}.{}".format(value[0:4], value[4:6], value[6:11])


class NOPhoneNumberField(RegexField, DeprecatedPhoneNumberFormFieldMixin):
"""
Field with phonenumber validation.
Expand Down
28 changes: 27 additions & 1 deletion tests/test_no.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import override

from localflavor.no.forms import NOMunicipalitySelect, NOPhoneNumberField, NOSocialSecurityNumber, NOZipCodeField
from localflavor.no.forms import (NOBankAccountNumber, NOMunicipalitySelect, NOPhoneNumberField, NOSocialSecurityNumber,
NOZipCodeField)


class NOLocalFlavorTests(SimpleTestCase):
Expand Down Expand Up @@ -38,6 +39,31 @@ def test_NOPhoneNumberField(self):
}
self.assertFieldOutput(NOPhoneNumberField, valid, invalid)

def test_NOBankAccountNumber(self):
error_format = [_('Enter a valid Norwegian bank account number.')]
error_checksum = [_('Invalid control digit. Enter a valid Norwegian bank account number.')]
error_length = [_('Invalid length. Norwegian bank account numbers are 11 digits long.')]

# A good source of loads of highly-likely-to-be-valid examples are available at
# http://www.skatteetaten.no/no/Person/Skatteoppgjor/Restskatt/Kontonummer-til-skatteoppkreverkontorene/
valid = {
'7694 05 12057': '76940512057',
'7694.05.12057': '76940512057',
'7694.05.12057 ': '76940512057',
'1111.00.22222': '11110022222',
'5555.88.43216': '55558843216',
'63450618537': '63450618537',
' 6345.06.20027 ': '63450620027',
}
invalid = {
'76940512056': error_checksum, # invalid check digit
'1111.00.22228': error_checksum, # invalid check digit
'abcdefgh': error_format, # illegal characters, though it'll fail to create the checksum
'1111a00b22222': error_format, # illegal characters
'769405120569': error_length, # invalid length (and control number for that matter)
}
self.assertFieldOutput(NOBankAccountNumber, valid, invalid)

def test_NOSocialSecurityNumber(self):
error_format = [_('Enter a valid Norwegian social security number.')]

Expand Down

0 comments on commit 42386bd

Please sign in to comment.