Completed
Push — master ( aee7d1...f2f440 )
by Johannes
01:07
created

VATIN.from_str()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
1
import re
2
3
from django.core.exceptions import ValidationError
4
from django.utils.encoding import python_2_unicode_compatible
5
from django.utils.functional import cached_property
6
from django.utils.translation import ugettext
7
from suds import WebFault
8
from suds.client import Client
9
10
from vies import VIES_WSDL_URL, logger
11
12
13
def dk_format(v):
14
    return '%s %s %s %s' % (v[:4], v[4:6], v[6:8], v[8:10])
15
16
17
def gb_format(v):
18
    if len(v) == 11:
19
        return '%s %s %s' % (v[:5], v[5:9], v[9:11])
20
    if len(v) == 14:
21
        return '%s %s' % (gb_format(v[:11]), v[11:14])
22
    return v
23
24
25
def fr_format(v):
26
    return '%s %s' % (v[:4], v[4:])
27
28
29
VIES_OPTIONS = {
30
    'AT': ('Austria', re.compile(r'^ATU\d{8}$')),
31
    'BE': ('Belgium', re.compile(r'^BE0?\d{9}$')),
32
    'BG': ('Bulgaria', re.compile(r'^BG\d{9,10}$')),
33
    'HR': ('Croatia', re.compile(r'^HR\d{11}$')),
34
    'CY': ('Cyprus', re.compile(r'^CY\d{8}[A-Z]$')),
35
    'CZ': ('Czech Republic', re.compile(r'^CZ\d{8,10}$')),
36
    'DE': ('Germany', re.compile(r'^DE\d{9}$')),
37
    'DK': ('Denmark', re.compile(r'^DK\d{8}$'), dk_format),
38
    'EE': ('Estonia', re.compile(r'^EE\d{9}$')),
39
    'EL': ('Greece', re.compile(r'^EL\d{9}$')),
40
    'ES': ('Spain', re.compile(r'^ES[A-Z0-9]\d{7}[A-Z0-9]$')),
41
    'FI': ('Finland', re.compile(r'^FI\d{8}$')),
42
    'FR': ('France', re.compile(r'^FR[A-HJ-NP-Z0-9][A-HJ-NP-Z0-9]\d{9}$'), fr_format),
43
    'GB': ('United Kingdom', re.compile(r'^(GB(GD|HA)\d{3}|GB\d{9}|GB\d{12})$'), gb_format),
44
    'HU': ('Hungary', re.compile(r'^HU\d{8}$')),
45
    'IE': ('Ireland', re.compile(r'^IE\d[A-Z0-9\+\*]\d{5}[A-Z]{1,2}$')),
46
    'IT': ('Italy', re.compile(r'^IT\d{11}$')),
47
    'LT': ('Lithuania', re.compile(r'^LT(\d{9}|\d{12})$')),
48
    'LU': ('Luxembourg', re.compile(r'^LU\d{8}$')),
49
    'LV': ('Latvia', re.compile(r'^LV\d{11}$')),
50
    'MT': ('Malta', re.compile(r'^MT\d{8}$')),
51
    'NL': ('The Netherlands', re.compile(r'^NL\d{9}B\d{2}$')),
52
    'PL': ('Poland', re.compile(r'^PL\d{10}$')),
53
    'PT': ('Portugal', re.compile(r'^PT\d{9}$')),
54
    'RO': ('Romania', re.compile(r'^RO\d{2,10}$')),
55
    'SE': ('Sweden', re.compile(r'^SE\d{10}01$')),
56
    'SI': ('Slovenia', re.compile(r'^SI\d{8}$')),
57
    'SK': ('Slovakia', re.compile(r'^SK\d{10}$')),
58
}
59
60
VIES_COUNTRY_CHOICES = sorted(
61
    (('', '--'),) +
62
    tuple(
63
        (key, key)
64
        for key, value in VIES_OPTIONS.items())
65
)
66
67
MEMBER_COUNTRY_CODES = VIES_OPTIONS.keys()
68
69
70
@python_2_unicode_compatible
71
class VATIN(object):
72
    """Object wrapper for the european VAT Identification Number."""
73
74
    def __init__(self, country_code, number):
75
        self.country_code = country_code
76
        self.number = number
77
78
    def __str__(self):
79
        unformated_number = "{country_code}{number}".format(
80
            country_code=self.country_code,
81
            number=self.number,
82
        )
83
84
        country = VIES_OPTIONS.get(self.country_code, {})
85
        if len(country) == 3:
86
            return country[2](unformated_number)
87
        return unformated_number
88
89
    def __repr__(self):
90
        return "<VATIN {}>".format(self.__str__())
91
92
    def get_country_code(self):
93
        return self._country_code
94
95
    def set_country_code(self, value):
96
        self._country_code = value.upper()
97
98
    country_code = property(get_country_code, set_country_code)
99
100
    def get_number(self):
101
        return self._number
102
103
    def set_number(self, value):
104
        self._number = value.upper().replace(' ', '')
105
106
    number = property(get_number, set_number)
107
108
    @cached_property
109
    def data(self):
110
        """VIES API response data."""
111
        client = Client(VIES_WSDL_URL)
112
        try:
113
            return client.service.checkVat(
114
                self.country_code,
115
                self.number
116
            )
117
        except WebFault as e:
118
            logger.exception(e)
119
            raise
120
121
    def is_valid(self):
122
        try:
123
            self.verify()
124
            self.validate()
125
        except ValidationError:
126
            return False
127
        else:
128
            return True
129
130
    def verify_country_code(self):
131
        if not re.match(r'^[a-zA-Z]', self.country_code):
132
            msg = ugettext('%s is not a valid ISO_3166-1 country code.')
133
            raise ValidationError(msg % self.country_code)
134
        elif self.country_code not in MEMBER_COUNTRY_CODES:
135
            msg = ugettext('%s is not a european member state.')
136
            raise ValidationError(msg % self.country_code)
137
138
    def verify_regex(self):
139
        country = dict(map(
140
            lambda x, y: (x, y), ('country', 'validator', 'formatter'),
141
            VIES_OPTIONS[self.country_code]
142
        ))
143
        if not country['validator'].match("%s%s" % (self.country_code, self.number)):
144
            msg = ugettext("%s does not match the countries VAT ID specifications.")
145
            raise ValidationError(msg % self)
146
147
    def verify(self):
148
        self.verify_country_code()
149
        self.verify_regex()
150
151
    def validate(self):
152
        if not self.data.valid:
153
            msg = ugettext("%s is not a valid VATIN.")
154
            raise ValidationError(msg % self)
155
156
    @classmethod
157
    def from_str(cls, value):
158
        """Return a VATIN object by given string."""
159
        return cls(value[:2].strip(), value[2:].strip())
160