-
Notifications
You must be signed in to change notification settings - Fork 18
/
party.py
319 lines (270 loc) · 10.5 KB
/
party.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# -*- coding: utf-8 -*-
"""
party.py
"""
from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction
from trytond.pyson import Eval, Bool
from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button
__all__ = [
'Address', 'AddressValidationMsg', 'AddressValidationWizard',
'AddressValidationSuggestionView'
]
__metaclass__ = PoolMeta
class Address:
"Party"
__name__ = 'party.address'
@classmethod
def __setup__(cls):
super(Address, cls).__setup__()
cls._buttons.update({
'validate_address_button': {
'readonly': ~Bool(Eval('active')),
},
})
@classmethod
@ModelView.button_action('shipping.wizard_address_validation')
def validate_address_button(cls, addresses):
pass # pragma: no cover
def validate_address(self, carrier=None):
"""
This method provides a generic address validation API that delegates
actual calls to external services (or perhaps even database lookups)
which should be implemented by other modules.
When called without a provider, the system looks into configuration and
checks if a default validation provider exists. If one does, it uses
that.
When there is no provider specified and a default is not configured
a `UserError` is raised.
If you wish to implement an address validation API, the interface it
should follow is explained below with pseudcode. The basic idea of the
implementation is to handle three cases:
* case 1: Perfect Address. Bingo! just return a True.
* case 2: Possible matches: Return list of active records (unsaved).
* case 3: No matches, just return an empty list.
* everything else including errors should raise `UserError`
class Address:
__name__ = 'party.address'
def _providername_address_validate(self):
'''
Validate the address using the web service of providername
and return a list of active records of addresses with
any or all the information filled.
'''
# Step 1: Call my provider's API with the address
try:
response = myprovider.validate_address(
name=self.name,
street=self.street,
city=self.city,
zip=self.zip
)
except MyProviderError, err:
raise UserError(
'Address Validation Failed: %s' % err.message
)
matches = []
# Step 2: Check the response to see how the address is
if response.address_is_correct:
return True
elif response.possible_matches:
Address = Pool().get('party.address')
for suggestion in response.suggestions:
matches.append(
Address(
name=self.name,
street=self.street,
state=state_id,
city=city,
country=country_id
)
)
return matches
A good example can be found in the UPS module's implementation of this
in
:ref:`address class <trytond-ups:party.Address._ups_address_validate>`.
"""
CarrierConfig = Pool().get('party.configuration')
config = CarrierConfig(1)
carrier = carrier or config.default_validation_carrier
if not carrier:
# TODO: Make this translatable error message
self.raise_user_error(
"Validation Carrier is not selected in carrier configuration."
)
return getattr(
self, '_{0}_address_validate'.format(carrier.carrier_cost_method)
)() # pragma: no cover
def serialize(self, purpose=None):
"""
Serialize address record.
"""
if purpose == 'validation':
return {
'name': self.name or None,
'street': self.street or None,
'zip': self.zip or None,
'city': self.city or None,
'country': self.country and self.country.id or None,
'subdivision': self.subdivision and self.subdivision.id or None,
}
elif hasattr(super(Address, self), 'serialize'): # pragma: no cover
return super(Address, self).serialize(purpose=purpose)
class AddressValidationSuggestionView(ModelView):
"""
Address Validation Wizard Suggestion View
"""
__name__ = 'party.address.validation.start'
# Older Values
current_name = fields.Char('Current Name', readonly=True)
current_street = fields.Char('Current Street', readonly=True)
current_zip = fields.Char('Current Zip', readonly=True)
current_city = fields.Char('Current City', readonly=True)
current_country = fields.Many2One(
'country.country', 'Current Country', readonly=True
)
current_subdivision = fields.Many2One(
"country.subdivision", 'Current Subdivision', readonly=True
)
# Editable suggestions
street = fields.Char('Suggested Street')
zip = fields.Char('Suggested Zip')
city = fields.Char('Suggested City')
country = fields.Many2One('country.country', 'Suggested Country')
subdivision = fields.Many2One(
"country.subdivision", 'Suggested Subdivision',
domain=[('country', '=', Eval('country'))],
depends=['country']
)
class AddressValidationMsg(ModelView):
'End State for Address Validation'
__name__ = 'party.address.validation.end'
done_msg = fields.Text("Message", readonly=True)
@staticmethod
def default_done_msg(): # pragma: no cover
"""
Returns default message.
"""
return (
'Address validation is complete.'
)
class AddressValidationWizard(Wizard):
"""
Wizard to validate the given address.
"""
__name__ = 'party.address.validation'
start_state = 'init'
end_state = 'end'
init = StateTransition()
start = StateView(
'party.address.validation.start',
'shipping.address_suggestion_view_form',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button(
'Save', 'update',
'tryton-ok', default=True
)
]
)
done = StateView(
'party.address.validation.end',
'shipping.address_validation_end_view_form',
[
Button('OK', 'end', 'tryton-ok')
]
)
update = StateTransition()
@classmethod
def __setup__(cls):
super(AddressValidationWizard, cls).__setup__()
cls._error_messages.update({
'incomplete_address': (
'Please fill out the address completely before attempting '
'validation.'
),
})
def transition_init(self):
"""
Initial transition where address validation is performed.
"""
Address = Pool().get('party.address')
address = Address(Transaction().context.get('active_id'))
self.check_for_address_fields(address)
# Now perform the validation
try:
match_addresses = address.validate_address()
except Exception:
raise
if match_addresses is True: # pragma: no cover
# If match_addresses is simply True, return 'done' state.
return 'done'
if isinstance(match_addresses, list): # pragma: no cover
# Pick highest ranked suggestion.
# Save fields in self.start.
self.start.street = match_addresses[0].street
self.start.zip = match_addresses[0].zip
self.start.city = match_addresses[0].city
self.start.country = match_addresses[0].country.id
self.start.subdivision = match_addresses[0].subdivision.id
return 'start'
def default_start(self, data):
"""
Fills the values in the ModelView.
"""
Address = Pool().get('party.address')
old_address = Address(Transaction().context.get('active_id'))
# First fill in the old values
values = {
'current_' + str(key): value
for key, value in old_address.serialize(purpose='validation')
.iteritems()
}
# Update the new values.
# The `values` dict needs to contain specifically the newer address
# key-value pairs or else the fields become blank.
values.update({
'street': self.start.street,
'zip': self.start.zip,
'city': self.start.city,
'country': self.start.country.id,
'subdivision': self.start.subdivision.id,
})
return values
def transition_update(self): # pragma: no cover
"""
Updates the address and returns the next state.
"""
Address = Pool().get('party.address')
address = Address(Transaction().context.get('active_id'))
Address.write([address], {
'street': self.start.street,
'zip': self.start.zip,
'city': self.start.city,
'country': self.start.country.id,
'subdivision': self.start.subdivision.id,
})
return 'done'
def default_done(self, data):
"""
Validation completion state.
"""
Address = Pool().get('party.address')
address = Address(Transaction().context.get('active_id'))
return {
'done_msg': (
'Address validation is now complete. '
'The full address is as follows -: \n%s'
% address.full_address
),
}
def check_for_address_fields(self, address):
"""
This method checks that the address is complete before allowing any
validation.
:param address: Active record to be checked against
"""
for key, value in address.serialize(purpose='validation').iteritems():
if value is None:
self.raise_user_error('incomplete_address')