Skip to content

Commit

Permalink
Merge 19db4c7 into 1e1bab9
Browse files Browse the repository at this point in the history
  • Loading branch information
Ted S committed Dec 16, 2016
2 parents 1e1bab9 + 19db4c7 commit 4d4e738
Show file tree
Hide file tree
Showing 20 changed files with 485 additions and 50 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This project provides an EasyPost connection in Odoo, allowing for features such
- Rate Quotes
- Purchase Shipping Labels
- Address Verification
- EasyPost WebHook Tracking Event handling


[//]: # (addons)
Expand All @@ -18,6 +19,7 @@ Available addons
addon | version | summary
--- | --- | ---
[connector_easypost](connector_easypost/) | 9.0.1.0.0 | EasyPost connector core
[connector_easypost_tracker](connector_easypost/) | 9.0.1.0.0 | EasyPost connector tracking WebHooks module


Unported addons
Expand All @@ -35,10 +37,11 @@ Contributors
------------

* Dave Lasley <dave@laslabs.com>
* Ted Salmon <tsalmon@laslabs.com>

Maintainer
----------

This module is maintained by [LasLabs Inc.](https://laslabs.com)

* https://github.com/laslabs/odoo-connector-carepoint/
* https://github.com/laslabs/odoo-connector-easypost/
12 changes: 2 additions & 10 deletions connector_easypost/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from contextlib import contextmanager
import openerp.tests.common as common
from openerp.addons.connector.session import ConnectorSession
from ..unit.object_dict import ObjectDict


backend_adapter = 'openerp.addons.connector_easypost.unit.backend_adapter'
Expand Down Expand Up @@ -75,15 +76,6 @@ def __init__(self, cr, registry, model_name):
self.model = registry(model_name)


class ObjDict(dict):

def __getattr__(self, key):
try:
return super(ObjDict, self).__getattr__(key)
except AttributeError:
return self[key]


class SetUpEasypostBase(common.TransactionCase):
""" Base class - Test the imports from a Easypost Mock.
The data returned by Easypost are those created for the
Expand Down Expand Up @@ -165,7 +157,7 @@ def setUp(self, ship=False):
}]]
}
self.rates = [
ObjDict(**{
ObjectDict(**{
"carrier": "USPS",
"carrier_account_id":
"ca_ac8c059614f5495295d1161dfa1f0290",
Expand Down
1 change: 1 addition & 0 deletions connector_easypost/unit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from . import import_synchronizer
from . import export_synchronizer
from . import mapper
from . import object_dict
45 changes: 19 additions & 26 deletions connector_easypost/unit/import_synchronizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
import dateutil.parser
import pytz
from hashlib import md5
from json import loads
from openerp import fields, _
from openerp.addons.connector.queue.job import job
from openerp.addons.connector.connector import ConnectorUnit
from openerp.addons.connector.exception import InvalidDataError
from openerp.addons.connector.session import ConnectorSession
from openerp.addons.connector.unit.synchronizer import Importer
from .object_dict import ObjectDict
from ..backend import easypost
from ..connector import get_environment, add_checkpoint

Expand Down Expand Up @@ -197,8 +199,8 @@ def run(self, easypost_id=None, force=False):
:returns object: The binding record we created/updated
"""
if not easypost_id and not self.easypost_record:
raise InvalidDataError('EasyPost ID must be supplied or \
easypost_record object must be filled')
raise InvalidDataError('EasyPost ID must be supplied or '
'easypost_record object must be filled')
self.easypost_id = easypost_id
if not self.easypost_record:
self.easypost_record = self._get_easypost_data()
Expand Down Expand Up @@ -314,21 +316,18 @@ def run(self, odoo_binding_id):
self.backend_record.id)


def _get_env_return_importer(env, model_name, backend_id):
def create_connector_session(env, model_name, backend_id):
""" Create the session from the given environment and return an
`EasypostImporter` instance """
session = ConnectorSession(env.cr, env, context=env.context)
env = get_environment(session, model_name, backend_id)
importer = env.get_connector_unit(EasypostImporter)
return importer
`ConnectorSession` instance """
return ConnectorSession(env.cr, env.uid, context=env.context)


@job(default_channel='root.easypost')
def import_batch(session, model_name, backend_id, filters=None):
""" Prepare a batch import of records from Easypost """
env = get_environment(session, model_name, backend_id)
importer = env.get_connector_unit(BatchImporter)
importer.run(filters=filters)
return importer.run(filters=filters)


@job(default_channel='root.easypost')
Expand All @@ -338,23 +337,17 @@ def import_record(session, model_name, backend_id, easypost_id, force=False):
importer = env.get_connector_unit(EasypostImporter)
_logger.debug('Importing EasyPost Record %s from %s',
easypost_id, model_name)
importer.run(easypost_id, force=force)
return importer.run(easypost_id, force=force)


@job(default_channel='root.easypost')
def easy_import_record(model_name, easypost_id, env, backend_id, force=False):
""" Import a record from Easypost while also creating the session """
importer = _get_env_return_importer(env, model_name, backend_id)
_logger.debug('Importing EasyPost Record %s from %s',
easypost_id, model_name)
importer.run(easypost_id, force=force)


@job(default_channel='root.easypost')
def easy_import_data(model_name, record, env, backend_id, force=False):
""" Import a record from Easypost while also creating the session """
importer = _get_env_return_importer(env, model_name, backend_id)
_logger.debug('Importing EasyPost Data %s into record of type %s',
record, model_name)
importer.easypost_data = record
importer.run(getattr(record, 'id', None), force=force)
def import_data(session, model_name, backend_id, easypost_data, force=False):
""" Import EasyPost data directly from a JSON string """
env = get_environment(session, model_name, backend_id)
importer = env.get_connector_unit(EasypostImporter)
_logger.debug('Importing EasyPost Data %s from %s',
easypost_data, model_name)
# Load the record as an object instead of a dict
record = loads(easypost_data, object_hook=lambda d: ObjectDict(**d))
importer.easypost_record = record
return importer.run(getattr(record, 'id', None), force=force)
21 changes: 21 additions & 0 deletions connector_easypost/unit/object_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

class ObjectDict(object):
""" This class allows you to create objects with
attributes and values direct from a `dict`. Useful
when you have a dict that needs to be accessed like
an object for compatibility """

def __init__(self, **data):
self.__dict__ = data

def __getitem__(self, key):
return getattr(self, key)

def __setitem__(self, key, val):
return setattr(self, key, val)

def __repr__(self):
return str(self.__dict__)
6 changes: 6 additions & 0 deletions connector_easypost_tracker/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ EasyPost Connector Tracker
This module provides web hooks to allow EasyPost to update package tracking
status in Odoo.

.. image:: static/description/screenshot_1.png?raw=true
:alt: Tracking Data in Picking

.. image:: static/description/screenshot_2.png?raw=true
:alt: Tracking Events and Locations


Installation
============
Expand Down
1 change: 1 addition & 0 deletions connector_easypost_tracker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import controllers
from . import models
from . import unit
5 changes: 5 additions & 0 deletions connector_easypost_tracker/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import main
38 changes: 38 additions & 0 deletions connector_easypost_tracker/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from json import loads, dumps
from openerp import http, SUPERUSER_ID
from openerp.http import request
from openerp.addons.connector_easypost.unit.import_synchronizer import (
create_connector_session,
import_data,
)


class EasypostWebhookController(http.Controller):

EVENTS = {'tracker.updated': 'easypost.stock.picking.tracking.group'}

def _get_backend(self, env):
return env['easypost.backend'].sudo().search([
('is_default', '=', True),
])

@http.route([
'/connector_easypost_tracker/webhook',
], type='http', auth='none', csrf=False)
def easypost_webhook(self):
""" Handle requests from the EasyPost Webhook """
req = loads(request.httprequest.data)
model = self.EVENTS.get(req.get('description'))
if model:
# We need to escalate to SUPERUSER in order to
# access protected models. Open to suggestions here
request.env.uid = SUPERUSER_ID
backend = self._get_backend(request.env)
session = create_connector_session(request.env, model, backend.id)
import_data.delay(session, model, backend.id,
dumps(req.get('result')))
return 'ok'
2 changes: 2 additions & 0 deletions connector_easypost_tracker/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import stock_picking_tracking_event
from . import stock_picking_tracking_group
from . import stock_picking_tracking_location
112 changes: 112 additions & 0 deletions connector_easypost_tracker/models/stock_picking_tracking_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import logging
from openerp import fields, models
from openerp.addons.connector.unit.mapper import (
mapping,
only_create,
)
from openerp.addons.connector_easypost.backend import easypost
from openerp.addons.connector_easypost.unit.backend_adapter import (
EasypostCRUDAdapter,
)
from openerp.addons.connector_easypost.unit.import_synchronizer import (
EasypostImporter,
)
from openerp.addons.connector_easypost.unit.mapper import (
EasypostImportMapper,
eval_false,
)
from .stock_picking_tracking_location import (
StockPickingTrackingLocationImporter,
)

_logger = logging.getLogger(__name__)


class EasypostStockPickingTrackingEvent(models.Model):
""" Binding Model for the Easypost StockPickingTrackingEvent"""
_name = 'easypost.stock.picking.tracking.event'
_inherit = 'easypost.binding'
_inherits = {'stock.picking.tracking.event': 'odoo_id'}
_description = 'Easypost StockPickingTrackingEvent'
_easypost_model = 'Tracker'

odoo_id = fields.Many2one(
comodel_name='stock.picking.tracking.event',
string='Stock Picking Tracking Event',
required=True,
ondelete='cascade',
)

_sql_constraints = [
('odoo_uniq', 'unique(backend_id, odoo_id)',
'A Easypost binding for this record already exists.'),
]


class StockPickingTrackingEvent(models.Model):
""" Adds the ``one2many`` relation to the Easypost bindings
(``easypost_bind_ids``)
"""
_inherit = 'stock.picking.tracking.event'

easypost_bind_ids = fields.One2many(
comodel_name='easypost.stock.picking.tracking.event',
inverse_name='odoo_id',
string='Easypost Bindings',
)


@easypost
class EasypostStockPickingTrackingEventAdapter(EasypostCRUDAdapter):
""" Backend Adapter for the Easypost EasypostStockPicking """
_model_name = 'easypost.stock.picking.tracking.event'


@easypost
class StockPickingTrackingEventImportMapper(EasypostImportMapper):
_model_name = 'easypost.stock.picking.tracking.event'

direct = [
(eval_false('message'), 'message'),
(eval_false('status'), 'state'),
(eval_false('source'), 'source'),
(eval_false('datetime'), 'date_created'),
]

@mapping
@only_create
def group_id(self, record):
""" `group_id` is not present in the event object and should be
injected by the caller """
return {'group_id': record.group.id}

@mapping
@only_create
def location_id(self, record):
return {'location_id': record.location_id}


@easypost
class StockPickingTrackingEventImporter(EasypostImporter):
_model_name = ['easypost.stock.picking.tracking.event']
_base_mapper = StockPickingTrackingEventImportMapper
_id_prefix = 'det'
_hashable_attrs = ('message', 'status', 'source', 'datetime')

def _before_import(self):
""" Prior to import, export the TrackingLocation and add the resulting
`location_id` to our easypost_record for mapping """
importer = self.unit_for(
StockPickingTrackingLocationImporter,
model='easypost.stock.picking.tracking.location'
)
importer.easypost_record = self.easypost_record.tracking_location
importer.default_easypost_values(
self.easypost_record.group.picking_id.company_id
)
ret = importer.run()
self.easypost_record.location_id = ret.odoo_id.id
Loading

0 comments on commit 4d4e738

Please sign in to comment.