forms.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # -*- coding: utf-8 -*-
  2. """
  3. BR-specific Form helpers
  4. """
  5. from __future__ import absolute_import, unicode_literals
  6. import re
  7. from django.contrib.localflavor.br.br_states import STATE_CHOICES
  8. from django.core.validators import EMPTY_VALUES
  9. from django.forms import ValidationError
  10. from django.forms.fields import Field, RegexField, CharField, Select
  11. from django.utils.encoding import smart_unicode
  12. from django.utils.translation import ugettext_lazy as _
  13. phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$')
  14. class BRZipCodeField(RegexField):
  15. default_error_messages = {
  16. 'invalid': _('Enter a zip code in the format XXXXX-XXX.'),
  17. }
  18. def __init__(self, max_length=None, min_length=None, *args, **kwargs):
  19. super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$',
  20. max_length, min_length, *args, **kwargs)
  21. class BRPhoneNumberField(Field):
  22. default_error_messages = {
  23. 'invalid': _('Phone numbers must be in XX-XXXX-XXXX format.'),
  24. }
  25. def clean(self, value):
  26. super(BRPhoneNumberField, self).clean(value)
  27. if value in EMPTY_VALUES:
  28. return ''
  29. value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
  30. m = phone_digits_re.search(value)
  31. if m:
  32. return '%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
  33. raise ValidationError(self.error_messages['invalid'])
  34. class BRStateSelect(Select):
  35. """
  36. A Select widget that uses a list of Brazilian states/territories
  37. as its choices.
  38. """
  39. def __init__(self, attrs=None):
  40. super(BRStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
  41. class BRStateChoiceField(Field):
  42. """
  43. A choice field that uses a list of Brazilian states as its choices.
  44. """
  45. widget = Select
  46. default_error_messages = {
  47. 'invalid': _('Select a valid brazilian state. That state is not one of the available states.'),
  48. }
  49. def __init__(self, required=True, widget=None, label=None,
  50. initial=None, help_text=None):
  51. super(BRStateChoiceField, self).__init__(required, widget, label,
  52. initial, help_text)
  53. self.widget.choices = STATE_CHOICES
  54. def clean(self, value):
  55. value = super(BRStateChoiceField, self).clean(value)
  56. if value in EMPTY_VALUES:
  57. value = ''
  58. value = smart_unicode(value)
  59. if value == '':
  60. return value
  61. valid_values = set([smart_unicode(k) for k, v in self.widget.choices])
  62. if value not in valid_values:
  63. raise ValidationError(self.error_messages['invalid'])
  64. return value
  65. def DV_maker(v):
  66. if v >= 2:
  67. return 11 - v
  68. return 0
  69. class BRCPFField(CharField):
  70. """
  71. This field validate a CPF number or a CPF string. A CPF number is
  72. compounded by XXX.XXX.XXX-VD. The two last digits are check digits.
  73. More information:
  74. http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas
  75. """
  76. default_error_messages = {
  77. 'invalid': _("Invalid CPF number."),
  78. 'max_digits': _("This field requires at most 11 digits or 14 characters."),
  79. 'digits_only': _("This field requires only numbers."),
  80. }
  81. def __init__(self, max_length=14, min_length=11, *args, **kwargs):
  82. super(BRCPFField, self).__init__(max_length, min_length, *args, **kwargs)
  83. def clean(self, value):
  84. """
  85. Value can be either a string in the format XXX.XXX.XXX-XX or an
  86. 11-digit number.
  87. """
  88. value = super(BRCPFField, self).clean(value)
  89. if value in EMPTY_VALUES:
  90. return ''
  91. orig_value = value[:]
  92. if not value.isdigit():
  93. value = re.sub("[-\.]", "", value)
  94. try:
  95. int(value)
  96. except ValueError:
  97. raise ValidationError(self.error_messages['digits_only'])
  98. if len(value) != 11:
  99. raise ValidationError(self.error_messages['max_digits'])
  100. orig_dv = value[-2:]
  101. new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))])
  102. new_1dv = DV_maker(new_1dv % 11)
  103. value = value[:-2] + str(new_1dv) + value[-1]
  104. new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(11, 1, -1))])
  105. new_2dv = DV_maker(new_2dv % 11)
  106. value = value[:-1] + str(new_2dv)
  107. if value[-2:] != orig_dv:
  108. raise ValidationError(self.error_messages['invalid'])
  109. return orig_value
  110. class BRCNPJField(Field):
  111. default_error_messages = {
  112. 'invalid': _("Invalid CNPJ number."),
  113. 'digits_only': _("This field requires only numbers."),
  114. 'max_digits': _("This field requires at least 14 digits"),
  115. }
  116. def clean(self, value):
  117. """
  118. Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a
  119. group of 14 characters.
  120. """
  121. value = super(BRCNPJField, self).clean(value)
  122. if value in EMPTY_VALUES:
  123. return ''
  124. orig_value = value[:]
  125. if not value.isdigit():
  126. value = re.sub("[-/\.]", "", value)
  127. try:
  128. int(value)
  129. except ValueError:
  130. raise ValidationError(self.error_messages['digits_only'])
  131. if len(value) != 14:
  132. raise ValidationError(self.error_messages['max_digits'])
  133. orig_dv = value[-2:]
  134. new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))])
  135. new_1dv = DV_maker(new_1dv % 11)
  136. value = value[:-2] + str(new_1dv) + value[-1]
  137. new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(6, 1, -1) + range(9, 1, -1))])
  138. new_2dv = DV_maker(new_2dv % 11)
  139. value = value[:-1] + str(new_2dv)
  140. if value[-2:] != orig_dv:
  141. raise ValidationError(self.error_messages['invalid'])
  142. return orig_value