Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.12.2
current_version = 0.12.3
commit = True
tag = True

Expand Down
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
History
-------

0.12.3 (2021-02-26)
+++++++++++++++++++++++

* (PR #193, 2021-02-16) requirements: Update dependency graph of base requirements
* (PR #197, 2021-02-26) extras: add 'RutField' for Django forms

0.12.2 (2021-02-16)
+++++++++++++++++++++++

Expand Down
2 changes: 1 addition & 1 deletion cl_sii/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
"""


__version__ = '0.12.2'
__version__ = '0.12.3'
73 changes: 73 additions & 0 deletions cl_sii/extras/dj_form_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
cl_sii "extras" / Django form fields.

"""
try:
import django
except ImportError as exc: # pragma: no cover
raise ImportError("Package 'Django' is required to use this module.") from exc

from typing import Any, Optional

import django.core.exceptions
import django.forms
from django.utils.translation import gettext_lazy as _

from cl_sii.rut import Rut


class RutField(django.forms.CharField):

"""
Django form field for RUT.

* Python data type: :class:`cl_sii.rut.Rut`

.. seealso::
:class:`.dj_model_fields.RutField`

"""

default_error_messages = {
'invalid': _('Enter a valid RUT.'),
'invalid_dv': _('RUT\'s "digito verificador" is incorrect.'),
}

def __init__(self, *, validate_dv: bool = False, **kwargs: Any) -> None:
"""
:param validate_dv: Boolean that specifies whether to validate that
the RUT's "digito verificador" is correct. False by default.
"""

self.validate_dv = validate_dv
super().__init__(strip=True, **kwargs)

def to_python(self, value: Optional[object]) -> Optional[Rut]:
"""
Validate that the input can be converted to a Python object (:class:`Rut`).

:raises django.core.exceptions.ValidationError:
if the input can't be converted
"""

if value in self.empty_values:
converted_value = None
elif isinstance(value, Rut):
converted_value = value
else:
try:
converted_value = Rut(value) # type: ignore[arg-type]
except (AttributeError, TypeError, ValueError):
raise django.core.exceptions.ValidationError(
self.error_messages['invalid'],
code='invalid',
)

if (converted_value is not None and self.validate_dv
and Rut.calc_dv(converted_value.digits) != converted_value.dv):
raise django.core.exceptions.ValidationError(
self.error_messages['invalid_dv'],
code='invalid_dv',
)

return converted_value
10 changes: 6 additions & 4 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,23 @@ signxml==2.8.1
# - cffi:
# - pycparser
# - six
# - jsonschema
# - jsonschema:
# - attrs
# - importlib-metadata (python_version<'3.8')
# - zipp
# - pyrsistent
# - setuptools
# - six
# - pyOpenSSL:
# - cryptography
# - six
# - signxml:
# - certifi
# - cryptography
# - eight
# - eight:
# - future
# - lxml
# - pyOpenSSL
# - six
attrs==20.3.0
certifi==2020.12.5
cffi==1.14.0
Expand All @@ -42,4 +44,4 @@ pycparser==2.20
pyrsistent==0.17.3
# setuptools
six==1.15.0
zipp==3.4.0
zipp==3.4.0 ; python_version < "3.8"
71 changes: 71 additions & 0 deletions tests/test_extras_dj_form_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import unittest

import django.core.exceptions

from cl_sii.extras.dj_form_fields import Rut, RutField


class RutFieldTest(unittest.TestCase):
valid_rut_canonical: str
valid_rut_instance: Rut
valid_rut_verbose_leading_zero_lowercase: str

@classmethod
def setUpClass(cls) -> None:
cls.invalid_rut_canonical = '60803000K'
cls.valid_rut_canonical = '60803000-K'
cls.valid_rut_instance = Rut(cls.valid_rut_canonical)
cls.valid_rut_canonical_with_invalid_dv = '60803000-0'
cls.valid_rut_canonical_instance_with_invalid_dv = Rut(
cls.valid_rut_canonical_with_invalid_dv
)
cls.valid_rut_verbose_leading_zero_lowercase = '060.803.000-k'

def test_clean_value_of_invalid_canonical_str(self) -> None:
rut_field = RutField()
with self.assertRaises(django.core.exceptions.ValidationError) as cm:
rut_field.clean(self.invalid_rut_canonical)
self.assertEqual(cm.exception.code, 'invalid')

def test_clean_value_of_canonical_str(self) -> None:
rut_field = RutField()
cleaned_value = rut_field.clean(self.valid_rut_canonical)
self.assertIsInstance(cleaned_value, Rut)
self.assertEqual(cleaned_value.canonical, self.valid_rut_canonical)

def test_clean_value_of_non_canonical_str(self) -> None:
rut_field = RutField()
cleaned_value = rut_field.clean(self.valid_rut_verbose_leading_zero_lowercase)
self.assertIsInstance(cleaned_value, Rut)
self.assertEqual(cleaned_value.canonical, self.valid_rut_canonical)

def test_clean_value_of_Rut(self) -> None:
rut_field = RutField()
cleaned_value = rut_field.clean(self.valid_rut_instance)
self.assertIsInstance(cleaned_value, Rut)
self.assertEqual(cleaned_value.canonical, self.valid_rut_canonical)

def test_clean_value_of_rut_str_with_invalid_dv_if_validated(self) -> None:
rut_field = RutField(validate_dv=True)
with self.assertRaises(django.core.exceptions.ValidationError) as cm:
rut_field.clean(self.valid_rut_canonical_with_invalid_dv)
self.assertEqual(cm.exception.code, 'invalid_dv')

def test_clean_value_of_rut_str_with_invalid_dv_if_not_validated(self) -> None:
rut_field = RutField(validate_dv=False)
cleaned_value = rut_field.clean(self.valid_rut_canonical_with_invalid_dv)
self.assertIsInstance(cleaned_value, Rut)
self.assertEqual(cleaned_value.canonical, self.valid_rut_canonical_with_invalid_dv)

def test_clean_of_empty_value_if_not_required(self) -> None:
rut_field = RutField(required=False)
for value in RutField.empty_values:
cleaned_value = rut_field.clean(value)
self.assertIsNone(cleaned_value)

def test_clean_of_empty_value_if_required(self) -> None:
rut_field = RutField()
for value in RutField.empty_values:
with self.assertRaises(django.core.exceptions.ValidationError) as cm:
rut_field.clean(value)
self.assertEqual(cm.exception.code, 'required')