Skip to content

Commit 4cdcb3e

Browse files
committed
First version
1 parent 5706d3e commit 4cdcb3e

File tree

15 files changed

+382
-0
lines changed

15 files changed

+382
-0
lines changed

dependencies.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
elasticsearch
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
from . import components
5+
from . import models
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
{
5+
'name': 'Elasticsearch Connector Base',
6+
'version': '11.0.1.0.1',
7+
'category': 'Elasticsearch connector',
8+
'depends': [
9+
'connector',
10+
],
11+
'author': 'Creu Blanca',
12+
'license': 'AGPL-3',
13+
'summary': 'Elasticsearch connector base',
14+
'data': [
15+
'data/backend.xml',
16+
'security/ir.model.access.csv',
17+
],
18+
'external_dependencies': {
19+
'python': [
20+
'elasticsearch'
21+
],
22+
},
23+
'installable': True,
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
from . import core
5+
from . import binder
6+
from . import exporter
7+
from . import listener
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
from odoo.addons.component.core import Component
5+
6+
7+
class ElasticsearchModelBinder(Component):
8+
_name = 'elasticsearch.binder'
9+
_inherit = ['base.binder', 'base.elasticsearch.connector']
10+
_apply_on = False
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
from odoo.addons.component.core import AbstractComponent
5+
6+
7+
class BaseElasticsearchConnectorComponent(AbstractComponent):
8+
""" Base elasticsearch Connector Component
9+
All components of this connector should inherit from it.
10+
"""
11+
12+
_name = 'base.elasticsearch.connector'
13+
_inherit = 'base.connector'
14+
_collection = 'elasticsearch.backend'
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
import logging
5+
import psycopg2
6+
import simplejson
7+
import requests
8+
from datetime import datetime
9+
10+
import odoo
11+
from odoo.addons.component.core import AbstractComponent
12+
from odoo.addons.connector.exception import RetryableJobError
13+
import elasticsearch
14+
15+
ISO_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
16+
_logger = logging.getLogger(__name__)
17+
18+
19+
class ElasticsearchBaseExporter(AbstractComponent):
20+
""" Base exporter for the Elasticsearch """
21+
22+
_name = 'elasticsearch.base.exporter'
23+
_inherit = ['base.exporter', 'base.elasticsearch.connector']
24+
_usage = 'record.exporter'
25+
_exporter_failure_timeout = 2
26+
27+
def _lock(self, binding):
28+
""" Lock the binding record.
29+
Lock the binding record so we are sure that only one export
30+
job is running for this record if concurrent jobs have to export the
31+
same record.
32+
When concurrent jobs try to export the same record, the first one
33+
will lock and proceed, the others will fail to lock and will be
34+
retried later.
35+
This behavior works also when the export becomes multilevel
36+
with :meth:`_export_dependencies`. Each level will set its own lock
37+
on the binding record it has to export.
38+
"""
39+
sql = ("SELECT id FROM %s WHERE ID = %%s FOR UPDATE NOWAIT" %
40+
self.model._table)
41+
try:
42+
self.env.cr.execute(sql, (binding.id,),
43+
log_exceptions=False)
44+
except psycopg2.OperationalError:
45+
_logger.info('A concurrent job is already exporting the same '
46+
'record (%s with id %s). Job delayed later.',
47+
self.model._name, binding.id)
48+
raise RetryableJobError(
49+
'A concurrent job is already exporting the same record '
50+
'(%s with id %s). The job will be retried later.' %
51+
(self.model._name, binding.id),
52+
seconds=self._exporter_failure_timeout)
53+
54+
def create(self, binding, *args, **kwargs):
55+
self._lock(binding)
56+
index = binding.index
57+
doc_type = binding.doc_type
58+
es = elasticsearch.Elasticsearch(
59+
hosts=binding.backend_id.get_hosts())
60+
data = simplejson.dumps(kwargs['data'])
61+
es.create(index, doc_type, binding.id, body=data)
62+
binding.sync_date = kwargs['sync_date']
63+
if not odoo.tools.config['test_enable']:
64+
self.env.cr.commit() # noqa
65+
self._after_export()
66+
return True
67+
68+
def delete(self, binding, *args, **kwargs):
69+
""" Run the synchronization
70+
:param binding: binding record to export
71+
"""
72+
self._lock(binding)
73+
sync_date = datetime.strptime(kwargs['sync_date'], ISO_FORMAT)
74+
if (
75+
not binding.sync_date or
76+
sync_date >= datetime.strptime(binding.sync_date, ISO_FORMAT)
77+
):
78+
index = binding.index
79+
doc_type = binding.doc_type
80+
es = elasticsearch.Elasticsearch(
81+
hosts=binding.backend_id.get_hosts())
82+
es.delete(index, doc_type, binding.id)
83+
binding.sync_date = kwargs['sync_date']
84+
if not odoo.tools.config['test_enable']:
85+
self.env.cr.commit() # noqa
86+
binding.sync_date = kwargs['sync_date']
87+
else:
88+
_logger.info(
89+
'Record from %s with id %s has already been sended (%s), so it'
90+
' is deprecated ' % (
91+
self.model._name, binding.id, kwargs['sync_date']
92+
)
93+
)
94+
# Commit so we keep the external ID when there are several
95+
# exports (due to dependencies) and one of them fails.
96+
# The commit will also release the lock acquired on the binding
97+
# record
98+
if not odoo.tools.config['test_enable']:
99+
self.env.cr.commit() # noqa
100+
self._after_export()
101+
return True
102+
103+
def update(self, binding, *args, **kwargs):
104+
""" Run the synchronization
105+
:param binding: binding record to export
106+
"""
107+
self._lock(binding)
108+
sync_date = datetime.strptime(kwargs['sync_date'], ISO_FORMAT)
109+
if (
110+
not binding.sync_date or
111+
sync_date >= datetime.strptime(binding.sync_date, ISO_FORMAT)
112+
):
113+
index = binding.index
114+
doc_type = binding.doc_type
115+
es = elasticsearch.Elasticsearch(
116+
hosts=binding.backend_id.get_hosts())
117+
data = simplejson.dumps(kwargs['data'])
118+
es.update(index, doc_type, binding.id, body=data)
119+
binding.sync_date = kwargs['sync_date']
120+
if not odoo.tools.config['test_enable']:
121+
self.env.cr.commit() # noqa
122+
binding.sync_date = kwargs['sync_date']
123+
else:
124+
_logger.info(
125+
'Record from %s with id %s has already been sended (%s), so it'
126+
' is deprecated ' % (
127+
self.model._name, binding.id, kwargs['sync_date']
128+
)
129+
)
130+
# Commit so we keep the external ID when there are several
131+
# exports (due to dependencies) and one of them fails.
132+
# The commit will also release the lock acquired on the binding
133+
# record
134+
if not odoo.tools.config['test_enable']:
135+
self.env.cr.commit() # noqa
136+
self._after_export()
137+
return True
138+
139+
def _after_export(self):
140+
""" Can do several actions after exporting a record"""
141+
pass
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
from odoo.addons.component.core import AbstractComponent
5+
from datetime import datetime
6+
7+
8+
class AbstractElasticsearchListener(AbstractComponent):
9+
_name = 'abstract.elasticsearch.listener'
10+
_inherit = 'base.event.listener'
11+
_apply_on = False
12+
13+
def on_record_create(self, record):
14+
for rec in record:
15+
for backend in rec.get_backends():
16+
for vals in rec.get_binds(backend):
17+
binding = self.env[rec.get_binding_model()].create(vals)
18+
binding.with_delay().export_create(
19+
datetime.now().isoformat())
20+
21+
def on_record_write(self, record):
22+
for rec in record:
23+
if rec._fields.get('elasticsearch_bind_ids'):
24+
for binding in rec.elasticsearch_bind_ids:
25+
binding.with_delay().export_update(
26+
datetime.now().isoformat())
27+
28+
def on_record_unlink(self, record):
29+
for rec in record:
30+
for binding in rec.elasticsearch_bind_ids:
31+
binding.with_delay().binding.export_delete(
32+
datetime.now().isoformat())
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<odoo>
3+
<record id="elasticsearch" model="elasticsearch.backend">
4+
<field name="name">Elasticsearch</field>
5+
</record>
6+
</odoo>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright 2017 Creu Blanca
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
3+
4+
from . import backend
5+
from . import binding
6+
from . import elasticsearch_model

0 commit comments

Comments
 (0)