feat: separate fusion field service and LTC into standalone modules, update core modules
- fusion_claims: separated field service logic, updated controllers/views - fusion_tasks: updated task views and map integration - fusion_authorizer_portal: added page 11 signing, schedule booking, migrations - fusion_shipping: new standalone shipping module (Canada Post, FedEx, DHL, Purolator) - fusion_ltc_management: new standalone LTC management module
This commit is contained in:
2
fusion_shipping/api/canada_post/__init__.py
Normal file
2
fusion_shipping/api/canada_post/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import response
|
||||
from . import utils
|
||||
164
fusion_shipping/api/canada_post/response.py
Normal file
164
fusion_shipping/api/canada_post/response.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import lxml
|
||||
import datetime
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from odoo.addons.fusion_shipping.api.canada_post.utils import get_dom_tree, python_2_unicode_compatible
|
||||
import json
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ResponseDataObject():
|
||||
|
||||
def __init__(self, mydict, datetime_nodes=[]):
|
||||
self._load_dict(mydict, list(datetime_nodes))
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
return "%s" % self.__dict__
|
||||
|
||||
def has_key(self, name):
|
||||
try:
|
||||
getattr(self, name)
|
||||
return True
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def get(self, name, default=None):
|
||||
try:
|
||||
return getattr(self, name)
|
||||
except AttributeError:
|
||||
return default
|
||||
|
||||
def _setattr(self, name, value, datetime_nodes):
|
||||
if name.lower() in datetime_nodes:
|
||||
try:
|
||||
ts = "%s %s" % (value.partition('T')[0], value.partition('T')[2].partition('.')[0])
|
||||
value = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
setattr(self, name, value)
|
||||
|
||||
def _load_dict(self, mydict, datetime_nodes):
|
||||
|
||||
for a in list(mydict.items()):
|
||||
|
||||
if isinstance(a[1], dict):
|
||||
o = ResponseDataObject(a[1], datetime_nodes)
|
||||
setattr(self, a[0], o)
|
||||
|
||||
elif isinstance(a[1], list):
|
||||
objs = []
|
||||
for i in a[1]:
|
||||
if i is None or isinstance(i, str) or isinstance(i, str):
|
||||
objs.append(i)
|
||||
else:
|
||||
objs.append(ResponseDataObject(i, datetime_nodes))
|
||||
|
||||
setattr(self, a[0], objs)
|
||||
else:
|
||||
self._setattr(a[0], a[1], datetime_nodes)
|
||||
|
||||
class Response():
|
||||
|
||||
def __init__(self, obj, verb=None, parse_response=True):
|
||||
self._obj = obj
|
||||
if parse_response:
|
||||
try:
|
||||
self._dom = self._parse_xml(obj.content)
|
||||
self._dict = self._etree_to_dict(self._dom)
|
||||
|
||||
if verb and 'Envelope' in list(self._dict.keys()):
|
||||
elem = self._dom.find('Body').find('%sResponse' % verb)
|
||||
if elem is not None:
|
||||
self._dom = elem
|
||||
|
||||
self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict)
|
||||
elif verb:
|
||||
elem = self._dom.find('%sResponse' % verb)
|
||||
if elem is not None:
|
||||
self._dom = elem
|
||||
|
||||
self._dict = self._dict.get('%sResponse' % verb, self._dict)
|
||||
|
||||
self.reply = ResponseDataObject(self._dict,[])
|
||||
|
||||
except lxml.etree.XMLSyntaxError as e:
|
||||
_logger.debug('Response parse failed: %s' % e)
|
||||
self.reply = ResponseDataObject({}, [])
|
||||
else:
|
||||
self.reply = ResponseDataObject({}, [])
|
||||
|
||||
def _get_node_path(self, t):
|
||||
i = t
|
||||
path = []
|
||||
path.insert(0, i.tag)
|
||||
while 1:
|
||||
try:
|
||||
path.insert(0, i.getparent().tag)
|
||||
i = i.getparent()
|
||||
except AttributeError:
|
||||
break
|
||||
|
||||
return '.'.join(path)
|
||||
|
||||
@staticmethod
|
||||
def _pullval(v):
|
||||
if len(v) == 1:
|
||||
return v[0]
|
||||
else:
|
||||
return v
|
||||
|
||||
def _etree_to_dict(self, t):
|
||||
if type(t) == lxml.etree._Comment:
|
||||
return {}
|
||||
|
||||
# remove xmlns from nodes
|
||||
t.tag = self._get_node_tag(t)
|
||||
|
||||
d = {t.tag: {} if t.attrib else None}
|
||||
children = list(t)
|
||||
if children:
|
||||
dd = defaultdict(list)
|
||||
for dc in map(self._etree_to_dict, children):
|
||||
for k, v in list(dc.items()):
|
||||
dd[k].append(v)
|
||||
|
||||
d = {t.tag: dict((k, self._pullval(v)) for k, v in list(dd.items()))}
|
||||
|
||||
parent_path = self._get_node_path(t)
|
||||
for k in list(d[t.tag].keys()):
|
||||
path = "%s.%s" % (parent_path, k)
|
||||
|
||||
if t.attrib:
|
||||
d[t.tag].update(('_' + k, v) for k, v in list(t.attrib.items()))
|
||||
if t.text:
|
||||
text = t.text.strip()
|
||||
if children or t.attrib:
|
||||
if text:
|
||||
d[t.tag]['value'] = text
|
||||
else:
|
||||
d[t.tag] = text
|
||||
return d
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._obj, name)
|
||||
|
||||
def _parse_xml(self, xml):
|
||||
return get_dom_tree(xml)
|
||||
|
||||
def _get_node_tag(self, node):
|
||||
return node.tag.replace('{' + node.nsmap.get(node.prefix, '') + '}', '')
|
||||
|
||||
def dom(self, lxml=True):
|
||||
if not lxml:
|
||||
pass
|
||||
return self._dom
|
||||
|
||||
def dict(self):
|
||||
return self._dict
|
||||
|
||||
def json(self):
|
||||
return json.dumps(self.dict())
|
||||
215
fusion_shipping/api/canada_post/utils.py
Normal file
215
fusion_shipping/api/canada_post/utils.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import sys
|
||||
from lxml import etree as ET
|
||||
|
||||
def parse_yaml(yaml_file):
|
||||
"""
|
||||
This is simple approach to parsing a yaml config that is only
|
||||
intended for this SDK as this only supports a very minimal subset
|
||||
of yaml options.
|
||||
"""
|
||||
|
||||
with open(yaml_file) as f:
|
||||
data = {None: {}}
|
||||
current_key = None
|
||||
|
||||
for line in f.readlines():
|
||||
|
||||
# ignore comments
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
|
||||
# parse the header
|
||||
elif line[0].isalnum():
|
||||
key = line.strip().replace(':', '')
|
||||
current_key = key
|
||||
data[current_key] = {}
|
||||
|
||||
# parse the key: value line
|
||||
elif line[0].isspace():
|
||||
values = line.strip().split(':')
|
||||
|
||||
if len(values) == 2:
|
||||
cval = values[1].strip()
|
||||
|
||||
if cval == '0':
|
||||
cval = False
|
||||
elif cval == '1':
|
||||
cval = True
|
||||
|
||||
data[current_key][values[0].strip()] = cval
|
||||
return data
|
||||
|
||||
|
||||
def python_2_unicode_compatible(klass):
|
||||
"""
|
||||
A decorator that defines __unicode__ and __str__ methods under Python 2.
|
||||
Under Python 3 it does nothing.
|
||||
|
||||
To support Python 2 and 3 with a single code base, define a __str__ method
|
||||
returning text and apply this decorator to the class.
|
||||
"""
|
||||
if sys.version_info[0] < 3:
|
||||
if '__str__' not in klass.__dict__:
|
||||
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
||||
"to %s because it doesn't define __str__()." %
|
||||
klass.__name__)
|
||||
klass.__unicode__ = klass.__str__
|
||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
||||
return klass
|
||||
|
||||
|
||||
def get_dom_tree(xml):
|
||||
tree = ET.fromstring(xml)
|
||||
return tree.getroottree().getroot()
|
||||
|
||||
def attribute_check(root):
|
||||
attrs = []
|
||||
value = None
|
||||
|
||||
if isinstance(root, dict):
|
||||
if '#text' in root:
|
||||
value = root['#text']
|
||||
if '@attrs' in root:
|
||||
for ak, av in sorted(root.pop('@attrs').items()):
|
||||
attrs.append(str('{0}="{1}"').format(ak, smart_encode(av)))
|
||||
|
||||
return attrs, value
|
||||
|
||||
def smart_encode(value):
|
||||
try:
|
||||
if sys.version_info[0] < 3:
|
||||
return str(value).encode('utf-8')
|
||||
else:
|
||||
return value
|
||||
|
||||
except UnicodeDecodeError:
|
||||
return value
|
||||
|
||||
|
||||
def to_xml(root):
|
||||
return dict2xml(root)
|
||||
|
||||
def dict2xml(root):
|
||||
xml = str('')
|
||||
if root is None:
|
||||
return xml
|
||||
|
||||
if isinstance(root, dict):
|
||||
for key in sorted(root.keys()):
|
||||
|
||||
if isinstance(root[key], dict):
|
||||
attrs, value = attribute_check(root[key])
|
||||
|
||||
if not value:
|
||||
value = dict2xml(root[key])
|
||||
elif isinstance(value, dict):
|
||||
value = dict2xml(value)
|
||||
|
||||
attrs_sp = str('')
|
||||
if len(attrs) > 0:
|
||||
attrs_sp = str(' ')
|
||||
|
||||
xml = str('{xml}<{tag}{attrs_sp}{attrs}>{value}</{tag}>') \
|
||||
.format(**{'tag': key, 'xml': str(xml), 'attrs': str(' ').join(attrs),
|
||||
'value': smart_encode(value), 'attrs_sp': attrs_sp})
|
||||
|
||||
elif isinstance(root[key], list):
|
||||
|
||||
for item in root[key]:
|
||||
attrs, value = attribute_check(item)
|
||||
|
||||
if not value:
|
||||
value = dict2xml(item)
|
||||
elif isinstance(value, dict):
|
||||
value = dict2xml(value)
|
||||
|
||||
attrs_sp = ''
|
||||
if len(attrs) > 0:
|
||||
attrs_sp = ' '
|
||||
|
||||
xml = str('{xml}<{tag}{attrs_sp}{attrs}>{value}</{tag}>') \
|
||||
.format(**{'xml': str(xml), 'tag': key, 'attrs': ' '.join(attrs), 'value': smart_encode(value),
|
||||
'attrs_sp': attrs_sp})
|
||||
|
||||
else:
|
||||
value = root[key]
|
||||
xml = str('{xml}<{tag}>{value}</{tag}>') \
|
||||
.format(**{'xml': str(xml), 'tag': key, 'value': smart_encode(value)})
|
||||
|
||||
elif isinstance(root, str) or isinstance(root, int) \
|
||||
or isinstance(root, str) or isinstance(root, int) \
|
||||
or isinstance(root, float):
|
||||
xml = str('{0}{1}').format(str(xml), root)
|
||||
else:
|
||||
raise Exception('Unable to serialize node of type %s (%s)' % \
|
||||
(type(root), root))
|
||||
|
||||
return xml
|
||||
|
||||
def getValue(response_dict, *args, **kwargs):
|
||||
args_a = [w for w in args]
|
||||
first = args_a[0]
|
||||
args_a.remove(first)
|
||||
|
||||
h = kwargs.get('mydict', {})
|
||||
if h:
|
||||
h = h.get(first, {})
|
||||
else:
|
||||
h = response_dict.get(first, {})
|
||||
|
||||
if len(args) == 1:
|
||||
try:
|
||||
return h.get('value', None)
|
||||
except:
|
||||
return h
|
||||
|
||||
last = args_a.pop()
|
||||
|
||||
for a in args_a:
|
||||
h = h.get(a, {})
|
||||
|
||||
h = h.get(last, {})
|
||||
|
||||
try:
|
||||
return h.get('value', None)
|
||||
except:
|
||||
return h
|
||||
|
||||
def getNodeText(node):
|
||||
"Returns the node's text string."
|
||||
|
||||
rc = []
|
||||
|
||||
if hasattr(node, 'childNodes'):
|
||||
for cn in node.childNodes:
|
||||
if cn.nodeType == cn.TEXT_NODE:
|
||||
rc.append(cn.data)
|
||||
elif cn.nodeType == cn.CDATA_SECTION_NODE:
|
||||
rc.append(cn.data)
|
||||
|
||||
return ''.join(rc)
|
||||
|
||||
def perftest_dict2xml():
|
||||
sample_dict = {
|
||||
'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'}}},
|
||||
'paginationInput': {
|
||||
'pageNumber': '1',
|
||||
'pageSize': '25'
|
||||
},
|
||||
'itemFilter': [
|
||||
{'name': 'Condition',
|
||||
'value': 'Used'},
|
||||
{'name': 'LocatedIn',
|
||||
'value': 'GB'},
|
||||
],
|
||||
'sortOrder': 'StartTimeNewest'
|
||||
}
|
||||
xml = dict2xml(sample_dict)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import timeit
|
||||
|
||||
import doctest
|
||||
failure_count, test_count = doctest.testmod()
|
||||
sys.exit(failure_count)
|
||||
Reference in New Issue
Block a user