Merge lp:~prestashoperpconnect-core-editors/prestashoperpconnect/prestashoperpconnect-test into lp:prestashoperpconnect

Proposed by Sébastien BEAU - http://www.akretion.com
Status: Rejected
Rejected by: Sébastien BEAU - http://www.akretion.com
Proposed branch: lp:~prestashoperpconnect-core-editors/prestashoperpconnect/prestashoperpconnect-test
Merge into: lp:prestashoperpconnect
Diff against target: 872 lines (+666/-13) (has conflicts)
11 files modified
prestashoperpconnect/prestashop_model.py (+31/-0)
prestashoperpconnect/prestashop_model_view.xml (+28/-0)
prestashoperpconnect/tests/__init__.py (+30/-0)
prestashoperpconnect/tests/common.py (+77/-0)
prestashoperpconnect/tests/test_data.py (+133/-0)
prestashoperpconnect/tests/test_synchronization.py (+129/-0)
prestashoperpconnect/unit/backend_adapter.py (+67/-12)
prestashoperpconnect/unit/import_synchronizer.py (+30/-1)
prestashoperpconnect/unit/mapper.py (+5/-0)
prestashoperpconnect_catalog_manager/tests/__init__.py (+30/-0)
prestashoperpconnect_catalog_manager/tests/test_mapper.py (+106/-0)
Text conflict in prestashoperpconnect/prestashop_model.py
Text conflict in prestashoperpconnect/prestashop_model_view.xml
Text conflict in prestashoperpconnect/unit/import_synchronizer.py
To merge this branch: bzr merge lp:~prestashoperpconnect-core-editors/prestashoperpconnect/prestashoperpconnect-test
Reviewer Review Type Date Requested Status
Sébastien BEAU - http://www.akretion.com Disapprove
Guewen Baconnier @ Camptocamp code review Needs Fixing
arthru Pending
Review via email: mp+193025@code.launchpad.net

Description of the change

Start to add test (backend, lang, product category) and fix a bug during the export (with the test).
The code for the test have been inspired from MagentoERPconnect

To post a comment you must log in.
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

l.33, l.620 s/Magento/Prestashop/

l.185-191: not sure you need to do that "double-mock" trick I used for the Magento connector. In the Magento connector, the API is a context manager, as below, and that's why I needed the second MagicMock and the __enter__.return_value.

        with magentolib.API(self.magento.location,
                            self.magento.username,
                            self.magento.password) as api:
            result = api.call(method, arguments)

For the tests using the "SingleTransactionCase", I first thought that it was better because you need to create the backend and initialize the base data only once for all the tests. Though, I encountered many issues because each test becomes dependent of the other ones. Now I prefer to use the TransactionCase even if the setUp is done once per test, that's not so long as the data is local, but I won't enforce you to choose it if you don't want to.

l.501: a method already exist for that (self.ref() if I remember well) (yes I did not noticed that when I started writing the Magento Connector tests).

l.747 s/transaltion/translation/

Can you add our copyright on the top of the files where you pasted the code from the Magento Connector? (recorder, call_to_key stuff and so on?)

review: Needs Fixing (code review)
275. By Sébastien BEAU - http://www.akretion.com

[FIX] fix test and clean code due to Guewen comment, Big thanks to him ;)

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

The project has moved to Github https://github.com/OCA/connector-prestashop.
Please resubmit your MP on github using following procedure https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub.

I put this MP in rejected in the meanwhile.

Regards

review: Disapprove

Unmerged revisions

275. By Sébastien BEAU - http://www.akretion.com

[FIX] fix test and clean code due to Guewen comment, Big thanks to him ;)

274. By Sébastien BEAU - http://www.akretion.com

[FIX] fix export of product, never send a False value. Add test

273. By Sébastien BEAU - http://www.akretion.com

[MERGE] merge with main branch

272. By Sébastien BEAU - http://www.akretion.com

[IMP] split import of product with the import of category and add test on category import

271. By Sébastien BEAU - http://www.akretion.com

[IMP] add test for prestashoperpconnect, add the recorder, add api mock...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'prestashoperpconnect/prestashop_model.py'
2--- prestashoperpconnect/prestashop_model.py 2014-02-06 13:55:14 +0000
3+++ prestashoperpconnect/prestashop_model.py 2014-03-14 15:04:18 +0000
4@@ -38,7 +38,11 @@
5 import_customers_since,
6 import_orders_since,
7 import_products,
8+<<<<<<< TREE
9 import_refunds,
10+=======
11+ import_product_categories,
12+>>>>>>> MERGE-SOURCE
13 import_carriers)
14 from .unit.direct_binder import DirectBinder
15 from .connector import get_environment
16@@ -156,6 +160,14 @@
17
18 return True
19
20+ def import_product_categories(self, cr, uid, ids, context=None):
21+ if not hasattr(ids, '__iter__'):
22+ ids = [ids]
23+ session = ConnectorSession(cr, uid, context=context)
24+ for backend_id in ids:
25+ import_product_categories.delay(session, backend_id, priority=10)
26+ return True
27+
28 def import_products(self, cr, uid, ids, context=None):
29 if not hasattr(ids, '__iter__'):
30 ids = [ids]
31@@ -253,6 +265,7 @@
32 self._scheduler_launch(cr, uid, self.import_carriers, domain=domain,
33 context=context)
34
35+<<<<<<< TREE
36 def _scheduler_import_payment_methods(self, cr, uid, domain=None, context=None):
37 self._scheduler_launch(cr, uid, self.import_payment_methods,
38 domain=domain, context=context)
39@@ -262,6 +275,24 @@
40 domain=domain, context=context)
41
42
43+=======
44+ def output_recorder(self, cr, uid, ids, context=None):
45+ """ Utility method to output a file containing all the recorded
46+ requests / responses with Prestashop. Used to generate test data.
47+ Should be called with ``erppeek`` for instance.
48+ """
49+ from .unit.backend_adapter import output_recorder
50+ import os
51+ import tempfile
52+ fmt = '%Y-%m-%d-%H-%M-%S'
53+ timestamp = datetime.now().strftime(fmt)
54+ filename = 'output_%s_%s' % (cr.dbname, timestamp)
55+ path = os.path.join(tempfile.gettempdir(), filename)
56+ output_recorder(path)
57+ return path
58+
59+
60+>>>>>>> MERGE-SOURCE
61 class prestashop_binding(orm.AbstractModel):
62 _name = 'prestashop.binding'
63 _inherit = 'external.binding'
64
65=== modified file 'prestashoperpconnect/prestashop_model_view.xml'
66--- prestashoperpconnect/prestashop_model_view.xml 2014-02-05 15:35:20 +0000
67+++ prestashoperpconnect/prestashop_model_view.xml 2014-03-14 15:04:18 +0000
68@@ -78,6 +78,7 @@
69 string="Import in background"/>
70 </group>
71 <group>
72+<<<<<<< TREE
73 <div>
74 <label string="Import product categories and products" class="oe_inline"/>
75 <field name="import_products_since"
76@@ -88,6 +89,33 @@
77 type="object"
78 class="oe_highlight"
79 string="Import in background"/>
80+=======
81+ <label string="Import product categories" class="oe_inline"/>
82+ <div>
83+ <button name="import_product_categories"
84+ type="object"
85+ class="oe_highlight"
86+ string="Import in background"/>
87+ </div>
88+ </group>
89+ <group>
90+ <label string="Import products" class="oe_inline"/>
91+ <div>
92+ <button name="import_products"
93+ type="object"
94+ class="oe_highlight"
95+ string="Import in background"/>
96+ </div>
97+ </group>
98+ <group>
99+ <label string="Import carriers" class="oe_inline"/>
100+ <div>
101+ <button name="import_carriers"
102+ type="object"
103+ class="oe_highlight"
104+ string="Import in background"/>
105+ </div>
106+>>>>>>> MERGE-SOURCE
107 </group>
108 <group>
109 <div>
110
111=== added directory 'prestashoperpconnect/tests'
112=== added file 'prestashoperpconnect/tests/__init__.py'
113--- prestashoperpconnect/tests/__init__.py 1970-01-01 00:00:00 +0000
114+++ prestashoperpconnect/tests/__init__.py 2014-03-14 15:04:18 +0000
115@@ -0,0 +1,30 @@
116+# -*- coding: utf-8 -*-
117+###############################################################################
118+#
119+# Module for OpenERP
120+# Copyright (C) 2013 Akretion (http://www.akretion.com).
121+# @author Sébastien BEAU <sebastien.beau@akretion.com>
122+#
123+# This program is free software: you can redistribute it and/or modify
124+# it under the terms of the GNU Affero General Public License as
125+# published by the Free Software Foundation, either version 3 of the
126+# License, or (at your option) any later version.
127+#
128+# This program is distributed in the hope that it will be useful,
129+# but WITHOUT ANY WARRANTY; without even the implied warranty of
130+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
131+# GNU Affero General Public License for more details.
132+#
133+# You should have received a copy of the GNU Affero General Public License
134+# along with this program. If not, see <http://www.gnu.org/licenses/>.
135+#
136+###############################################################################
137+
138+from . import test_synchronization
139+
140+fast_suite = [
141+]
142+
143+checks = [
144+ test_synchronization,
145+]
146
147=== added file 'prestashoperpconnect/tests/common.py'
148--- prestashoperpconnect/tests/common.py 1970-01-01 00:00:00 +0000
149+++ prestashoperpconnect/tests/common.py 2014-03-14 15:04:18 +0000
150@@ -0,0 +1,77 @@
151+# -*- coding: utf-8 -*-
152+###############################################################################
153+#
154+# Module for OpenERP
155+# Copyright (C) 2013 Akretion (http://www.akretion.com).
156+# Copyright 2013 Camptocamp SA
157+# @author Sébastien BEAU <sebastien.beau@akretion.com>
158+# @author: Guewen Baconnier
159+#
160+# This program is free software: you can redistribute it and/or modify
161+# it under the terms of the GNU Affero General Public License as
162+# published by the Free Software Foundation, either version 3 of the
163+# License, or (at your option) any later version.
164+#
165+# This program is distributed in the hope that it will be useful,
166+# but WITHOUT ANY WARRANTY; without even the implied warranty of
167+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
168+# GNU Affero General Public License for more details.
169+#
170+# You should have received a copy of the GNU Affero General Public License
171+# along with this program. If not, see <http://www.gnu.org/licenses/>.
172+#
173+###############################################################################
174+
175+"""
176+Helpers usable in the tests
177+"""
178+
179+import mock
180+from contextlib import contextmanager
181+from ..unit.backend_adapter import call_to_key
182+
183+
184+class TestResponder(object):
185+ """ Used to simulate the calls to Prestashop.
186+
187+ For a call (request) to Prestashop, returns a stored
188+ response.
189+ """
190+
191+ def __init__(self, responses):
192+ """
193+ The responses are stored in dict instances.
194+ The keys are normalized using the ``call_to_key``
195+ function which transform the request calls in a
196+ hashable form.
197+
198+ :param responses: responses returned by Prestashop
199+ :type responses: dict
200+ """
201+ self._responses = responses
202+
203+ def search(self, prestashop_model, filters):
204+ key = call_to_key('search', [prestashop_model, filters])
205+ assert key in self._responses, (
206+ "%s not found in prestashop responses" % str(key))
207+ return self._responses[key]
208+
209+ def get(self, prestashop_model, id, options=None):
210+ key = call_to_key('get', [prestashop_model, id, options])
211+ assert key in self._responses, (
212+ "%s not found in prestashop responses" % str(key))
213+ return self._responses[key]
214+
215+ def __call__(self, *args, **kwargs):
216+ return self
217+
218+
219+@contextmanager
220+def mock_api(responses):
221+ """
222+ :param responses: responses returned by Prestashop
223+ :type responses: dict
224+ """
225+ get_prestashop_response = TestResponder(responses)
226+ with mock.patch('prestapyt.PrestaShopWebServiceDict', get_prestashop_response) as API:
227+ yield
228
229=== added file 'prestashoperpconnect/tests/test_data.py'
230--- prestashoperpconnect/tests/test_data.py 1970-01-01 00:00:00 +0000
231+++ prestashoperpconnect/tests/test_data.py 2014-03-14 15:04:18 +0000
232@@ -0,0 +1,133 @@
233+"""
234+Prestashop responses for calls done by the connector.
235+
236+This set of responses has been recorded for the synchronizations
237+with a Prestashop 1.7 version with demo data.
238+
239+It has been recorded using ``prestashoperpconnect.unit.backend_adapter.record``
240+and ``prestashoperpconnect.unit.backend_adapter.output_recorder``
241+"""
242+import time
243+
244+
245+FMT = "%Y-%m-%d %H:%M:%S"
246+
247+prestashop_base_responses = \
248+{('get', ('shop_groups', 1, None)): {'shop_group': {'active': '1',
249+ 'deleted': '0',
250+ 'id': '1',
251+ 'name': 'Default',
252+ 'share_customer': '0',
253+ 'share_order': '0',
254+ 'share_stock': '0'}},
255+ ('get', ('shops', 1, None)): {'shop': {'active': '1',
256+ 'deleted': '0',
257+ 'id': '1',
258+ 'id_category': '2',
259+ 'id_shop_group': '1',
260+ 'id_theme': '1',
261+ 'name': 'akretion'}},
262+ ('search', ('shop_groups', frozenset([('limit', '0,1000')]))): [1],
263+ ('search', ('shops', frozenset([('limit', '0,1000')]))): [1],
264+
265+
266+ ('get', ('languages', 1, None)): {'language': {'active': '1',
267+ 'date_format_full': 'Y-m-d H:i:s',
268+ 'date_format_lite': 'Y-m-d',
269+ 'id': '1',
270+ 'is_rtl': '0',
271+ 'iso_code': 'en',
272+ 'language_code': 'en',
273+ 'name': u'English'}},
274+ ('search', ('languages', None)): [1],
275+
276+ ('get', ('categories', 1, None)): {'category': {'active': '1',
277+ 'associations': {'categories': {'attrs': {'node_type': 'category'},
278+ 'category': {'id': '2'}},
279+ 'products': {'attrs': {'node_type': 'product'},
280+ 'value': ''}},
281+ 'date_add': '2013-10-14 21:46:26',
282+ 'date_upd': '2013-10-14 21:46:26',
283+ 'description': {'language': [{'attrs': {'id': '1'},
284+ 'value': ''}]},
285+ 'id': '1',
286+ 'id_parent': '0',
287+ 'id_shop_default': '1',
288+ 'is_root_category': '0',
289+ 'level_depth': '0',
290+ 'link_rewrite': {'language': [{'attrs': {'id': '1'},
291+ 'value': 'root'}]},
292+ 'meta_description': {'language': [{'attrs': {'id': '1'},
293+ 'value': ''}]},
294+ 'meta_keywords': {'language': [{'attrs': {'id': '1'},
295+ 'value': ''}]},
296+ 'meta_title': {'language': [{'attrs': {'id': '1'},
297+ 'value': ''}]},
298+ 'name': {'language': [{'attrs': {'id': '1'},
299+ 'value': 'Root'}]},
300+ 'nb_products_recursive': {'attrs': {'not_filterable': 'true'},
301+ 'value': '7'},
302+ 'position': '1'}},
303+ ('get', ('categories', 2, None)): {'category': {'active': '1',
304+ 'associations': {'categories': {'attrs': {'node_type': 'category'},
305+ 'category': [{'id': '3'},
306+ {'id': '4'},
307+ {'id': '5'}]},
308+ 'products': {'attrs': {'node_type': 'product'},
309+ 'product': [{'id': '1'},
310+ {'id': '2'},
311+ {'id': '4'},
312+ {'id': '5'}]}},
313+ 'date_add': '2013-10-14 21:46:26',
314+ 'date_upd': '2013-10-14 21:46:26',
315+ 'description': {'language': [{'attrs': {'id': '1'},
316+ 'value': ''}]},
317+ 'id': '2',
318+ 'id_parent': '1',
319+ 'id_shop_default': '1',
320+ 'is_root_category': '1',
321+ 'level_depth': '1',
322+ 'link_rewrite': {'language': [{'attrs': {'id': '1'},
323+ 'value': 'home'}]},
324+ 'meta_description': {'language': [{'attrs': {'id': '1'},
325+ 'value': ''}]},
326+ 'meta_keywords': {'language': [{'attrs': {'id': '1'},
327+ 'value': ''}]},
328+ 'meta_title': {'language': [{'attrs': {'id': '1'},
329+ 'value': ''}]},
330+ 'name': {'language': [{'attrs': {'id': '1'},
331+ 'value': 'Accueil'}]},
332+ 'nb_products_recursive': {'attrs': {'not_filterable': 'true'},
333+ 'value': '7'},
334+ 'position': '1'}},
335+ ('get', ('categories', 3, None)): {'category': {'active': '1',
336+ 'associations': {'categories': {'attrs': {'node_type': 'category'},
337+ 'value': ''},
338+ 'products': {'attrs': {'node_type': 'product'},
339+ 'product': [{'id': '1'},
340+ {'id': '2'},
341+ {'id': '5'}]}},
342+ 'date_add': '2013-10-14 21:46:34',
343+ 'date_upd': '2013-10-14 21:46:34',
344+ 'description': {'language': [{'attrs': {'id': '1'},
345+ 'value': u'Il est temps, pour le meilleur lecteur de musique, de remonter sur sc\xe8ne pour un rappel. Avec le nouvel iPod, le monde est votre sc\xe8ne.'}]},
346+ 'id': '3',
347+ 'id_parent': '2',
348+ 'id_shop_default': '1',
349+ 'is_root_category': '0',
350+ 'level_depth': '2',
351+ 'link_rewrite': {'language': [{'attrs': {'id': '1'},
352+ 'value': 'musique-ipods'}]},
353+ 'meta_description': {'language': [{'attrs': {'id': '1'},
354+ 'value': ''}]},
355+ 'meta_keywords': {'language': [{'attrs': {'id': '1'},
356+ 'value': ''}]},
357+ 'meta_title': {'language': [{'attrs': {'id': '1'},
358+ 'value': ''}]},
359+ 'name': {'language': [{'attrs': {'id': '1'},
360+ 'value': 'iPods'}]},
361+ 'nb_products_recursive': {'attrs': {'not_filterable': 'true'},
362+ 'value': '3'},
363+ 'position': '1'}},
364+ ('search', ('categories', frozenset([('limit', '0,1000')]))): [1, 2, 3, 4, 5],
365+ }
366
367=== added file 'prestashoperpconnect/tests/test_synchronization.py'
368--- prestashoperpconnect/tests/test_synchronization.py 1970-01-01 00:00:00 +0000
369+++ prestashoperpconnect/tests/test_synchronization.py 2014-03-14 15:04:18 +0000
370@@ -0,0 +1,129 @@
371+# -*- coding: utf-8 -*-
372+###############################################################################
373+#
374+# Module for OpenERP
375+# Copyright (C) 2013 Akretion (http://www.akretion.com).
376+# Copyright 2013 Camptocamp SA
377+# @author Sébastien BEAU <sebastien.beau@akretion.com>
378+# @author: Guewen Baconnier
379+#
380+# This program is free software: you can redistribute it and/or modify
381+# it under the terms of the GNU Affero General Public License as
382+# published by the Free Software Foundation, either version 3 of the
383+# License, or (at your option) any later version.
384+#
385+# This program is distributed in the hope that it will be useful,
386+# but WITHOUT ANY WARRANTY; without even the implied warranty of
387+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
388+# GNU Affero General Public License for more details.
389+#
390+# You should have received a copy of the GNU Affero General Public License
391+# along with this program. If not, see <http://www.gnu.org/licenses/>.
392+#
393+###############################################################################
394+
395+
396+
397+import unittest2
398+
399+from openerp.addons.connector.exception import InvalidDataError
400+from openerp.addons.prestashoperpconnect.unit.import_synchronizer import (
401+ import_batch,
402+ import_record)
403+from openerp.addons.connector.session import ConnectorSession
404+import openerp.tests.common as common
405+from .common import (mock_api)
406+from .test_data import prestashop_base_responses
407+from openerp.addons.prestashoperpconnect.connector import get_environment
408+from openerp.addons.prestashoperpconnect.unit.direct_binder import DirectBinder
409+
410+DB = common.DB
411+ADMIN_USER_ID = common.ADMIN_USER_ID
412+
413+
414+
415+class test_import_prestashop(common.SingleTransactionCase):
416+ """ Test the imports from a Prestashop Mock.
417+
418+ The data returned by Prestashop are those created for the
419+ demo version of Prestashop on a standard 1.7 version.
420+ """
421+
422+ def setUp(self):
423+ super(test_import_prestashop, self).setUp()
424+ self.backend_model = self.registry('prestashop.backend')
425+ self.session = ConnectorSession(self.cr, self.uid)
426+ data_model = self.registry('ir.model.data')
427+ backend_ids = self.backend_model.search(
428+ self.cr, self.uid,
429+ [('name', '=', 'Test Prestashop')])
430+ if backend_ids:
431+ self.backend_id = backend_ids[0]
432+ else:
433+ warehouse_id = self.ref('stock.warehouse0')
434+ self.backend_id = self.backend_model.create(
435+ self.cr,
436+ self.uid,
437+ {'name': 'Test Prestashop',
438+ 'version': '1.5',
439+ 'location': 'http://anyurl',
440+ 'username': 'sebastien',
441+ 'warehouse_id': warehouse_id,
442+ 'password': '42'})
443+
444+
445+ def test_00_import_backend(self):
446+ """ Synchronize initial metadata """
447+ with mock_api(prestashop_base_responses):
448+ import_batch(self.session, 'prestashop.shop.group', self.backend_id)
449+ import_batch(self.session, 'prestashop.shop', self.backend_id)
450+
451+ shop_group_model = self.registry('prestashop.shop.group')
452+ shop_group_ids = shop_group_model.search(self.cr,
453+ self.uid,
454+ [('backend_id', '=', self.backend_id)])
455+ self.assertEqual(len(shop_group_ids), 1)
456+
457+ shop_model = self.registry('prestashop.shop')
458+ shop_ids = shop_model.search(self.cr,
459+ self.uid,
460+ [('backend_id', '=', self.backend_id)])
461+ self.assertEqual(len(shop_ids), 1)
462+
463+ def test_01_map_lang(self):
464+ """ Map language """
465+ with mock_api(prestashop_base_responses):
466+ env = get_environment(self.session, 'prestashop.res.lang', self.backend_id)
467+ directBinder = env.get_connector_unit(DirectBinder)
468+ directBinder.run()
469+ lang_model = self.registry('prestashop.res.lang')
470+ lang_ids = lang_model.search(self.cr,
471+ self.uid,
472+ [('backend_id', '=', self.backend_id)])
473+ self.assertEqual(len(lang_ids), 1)
474+
475+ def test_10_import_product_category(self):
476+ """ Import of a product category """
477+ backend_id = self.backend_id
478+ with mock_api(prestashop_base_responses):
479+ import_record(self.session, 'prestashop.product.category',
480+ backend_id, 1)
481+
482+ category_model = self.registry('prestashop.product.category')
483+ category_ids = category_model.search(
484+ self.cr, self.uid, [('backend_id', '=', backend_id)])
485+ self.assertEqual(len(category_ids), 1)
486+
487+ def test_11_import_product_category_with_gap(self):
488+ """ Import of a product category when parent categories are missing """
489+ backend_id = self.backend_id
490+ with mock_api(prestashop_base_responses):
491+ import_record(self.session, 'prestashop.product.category',
492+ backend_id, 3)
493+
494+ category_model = self.registry('prestashop.product.category')
495+ category_ids = category_model.search(
496+ self.cr, self.uid, [('backend_id', '=', backend_id)])
497+ self.assertEqual(len(category_ids), 3)
498+
499+
500
501=== modified file 'prestashoperpconnect/unit/backend_adapter.py'
502--- prestashoperpconnect/unit/backend_adapter.py 2014-02-05 15:22:22 +0000
503+++ prestashoperpconnect/unit/backend_adapter.py 2014-03-14 15:04:18 +0000
504@@ -26,14 +26,56 @@
505
506 import base64
507 import logging
508-from prestapyt import PrestaShopWebServiceDict
509+import prestapyt
510 from openerp.addons.connector.unit.backend_adapter import CRUDAdapter
511 from ..backend import prestashop
512+from openerp.tools import config
513+
514
515 _logger = logging.getLogger(__name__)
516
517-
518-class PrestaShopWebServiceImage(PrestaShopWebServiceDict):
519+#TODO Same code as in magentoerpconnect, maybe
520+#moving it in connector is a good idea
521+recorder = {}
522+
523+def call_to_key(method, arguments):
524+ """ Used to 'freeze' the method and arguments of a call to Prestashop
525+ so they can be hashable; they will be stored in a dict.
526+
527+ Used in both the recorder and the tests.
528+ """
529+ def freeze(arg):
530+ if isinstance(arg, dict):
531+ items = dict((key, freeze(value)) for key, value
532+ in arg.iteritems())
533+ return frozenset(items.iteritems())
534+ elif isinstance(arg, list):
535+ return tuple([freeze(item) for item in arg])
536+ else:
537+ return arg
538+ new_args = [freeze(arg) for arg in arguments]
539+ return (method, tuple(new_args))
540+
541+
542+def record(method, arguments, result):
543+ """ Utility function which can be used to record test data
544+ during synchronisations. Call it from MagentoCRUDAdapter._call
545+
546+ Then ``output_recorder`` can be used to write the data recorded
547+ to a file.
548+ """
549+ recorder[call_to_key(method, arguments)] = result
550+
551+
552+def output_recorder(filename):
553+ import pprint
554+ with open(filename, 'w') as f:
555+ pprint.pprint(recorder, f)
556+ _logger.debug('recorder written to file %s', filename)
557+
558+###
559+
560+class PrestaShopWebServiceImage(prestapyt.PrestaShopWebServiceDict):
561
562 def get_image(self, resource, resource_id=None, image_id=None,
563 options=None):
564@@ -113,7 +155,7 @@
565 _prestashop_model = None
566
567 def connect(self):
568- return PrestaShopWebServiceDict(self.prestashop.api_url,
569+ return prestapyt.PrestaShopWebServiceDict(self.prestashop.api_url,
570 self.prestashop.webservice_key)
571
572 def search(self, filters=None):
573@@ -123,7 +165,10 @@
574 :rtype: list
575 """
576 api = self.connect()
577- return api.search(self._prestashop_model, filters)
578+ result = api.search(self._prestashop_model, filters)
579+ if config.get('prestashop_recorder'):
580+ record('search', [self._prestashop_model, filters], result)
581+ return result
582
583 def read(self, id, attributes=None):
584 """ Returns the information of a record
585@@ -132,30 +177,40 @@
586 """
587 #TODO rename attributes in something better
588 api = self.connect()
589- res = api.get(self._prestashop_model, id, options=attributes)
590- first_key = res.keys()[0]
591- return res[first_key]
592+ result = api.get(self._prestashop_model, id, options=attributes)
593+ if config.get('prestashop_recorder'):
594+ record('get', [self._prestashop_model, id, attributes], result)
595+ first_key = result.keys()[0]
596+ return result[first_key]
597
598 def create(self, attributes=None):
599 """ Create a record on the external system """
600 api = self.connect()
601- return api.add(self._prestashop_model, {
602+ result = api.add(self._prestashop_model, {
603 self._export_node_name: attributes
604 })
605+ if config.get('prestashop_recorder'):
606+ record('add', [self._prestashop_model, {self._export_node_name: attributes}], result)
607+ return result
608
609 def write(self, id, attributes=None):
610 """ Update records on the external system """
611 api = self.connect()
612 attributes['id'] = id
613- return api.edit(self._prestashop_model, {
614+ result = api.edit(self._prestashop_model, {
615 self._export_node_name: attributes
616 })
617+ if config.get('prestashop_recorder'):
618+ record('edit', [self._prestashop_model, {self._export_node_name: attributes}], result)
619+ return result
620
621 def delete(self, ids):
622 api = self.connect()
623 """ Delete a record(s) on the external system """
624- return api.delete(self._prestashop_model, ids)
625-
626+ result = api.delete(self._prestashop_model, ids)
627+ if config.get('prestashop_recorder'):
628+ record('delete', [self._prestashop_model, ids], result)
629+ return result
630
631 @prestashop
632 class ShopGroupAdapter(GenericAdapter):
633
634=== modified file 'prestashoperpconnect/unit/import_synchronizer.py'
635--- prestashoperpconnect/unit/import_synchronizer.py 2014-02-06 13:55:14 +0000
636+++ prestashoperpconnect/unit/import_synchronizer.py 2014-03-14 15:04:18 +0000
637@@ -35,12 +35,17 @@
638 from backend_adapter import GenericAdapter
639 from .exception import OrderImportRuleRetry
640 from openerp.addons.connector.exception import FailedJobError
641+<<<<<<< TREE
642 from openerp.addons.connector.exception import NothingToDoJob
643 from backend_adapter import PrestaShopCRUDAdapter
644
645 from prestapyt import PrestaShopWebServiceError
646 from ..connector import add_checkpoint
647
648+=======
649+from openerp.osv import orm
650+from openerp.tools.translate import _
651+>>>>>>> MERGE-SOURCE
652
653 _logger = logging.getLogger(__name__)
654
655@@ -469,7 +474,11 @@
656 if not language or language['attrs']['id'] in languages:
657 continue
658 erp_lang = self._get_oerp_language(language['attrs']['id'])
659- if erp_lang is not None:
660+ if erp_lang is None:
661+ _logger.warning('The language %s is not synchronized with'
662+ 'the backend %s, the translation will be not imported',
663+ language['attrs']['id'], self.backend_record.name)
664+ else:
665 languages[language['attrs']['id']] = erp_lang['code']
666 return languages
667
668@@ -477,6 +486,18 @@
669 splitted_record = {}
670 languages = self.find_each_language(record)
671 model_name = self.environment.model_name
672+ list_languages = languages.items()
673+ if not list_languages:
674+ raise FailedJobError(
675+ _('The languages are not correctly configured. \n'
676+ 'Please configure it before trying to import a'
677+ 'translatable record.\n'
678+ 'Resolution:\n'
679+ '- Go to \'Setting > Translations > Load a Translation\' '
680+ 'and add the missing translation\n'
681+ '- Go to \'Connector > Prestashop > Backends\' '
682+ 'and Synchronize Base Data\n'
683+ ))
684 for language_id, language_code in languages.items():
685 splitted_record[language_code] = record.copy()
686 for field in self._translatable_fields[model_name]:
687@@ -846,6 +867,7 @@
688
689
690 @job
691+<<<<<<< TREE
692 def import_products(session, backend_id, since_date):
693 filters = None
694 if since_date:
695@@ -878,7 +900,14 @@
696 {'import_refunds_since': now_fmt},
697 context=session.context
698 )
699+=======
700+def import_product_categories(session, backend_id):
701+ import_batch(session, 'prestashop.product.category', backend_id, priority=13)
702
703+@job
704+def import_products(session, backend_id):
705+ import_batch(session, 'prestashop.product.product', backend_id, priority=15)
706+>>>>>>> MERGE-SOURCE
707
708 @job
709 def import_carriers(session, backend_id):
710
711=== modified file 'prestashoperpconnect/unit/mapper.py'
712--- prestashoperpconnect/unit/mapper.py 2014-03-12 18:08:30 +0000
713+++ prestashoperpconnect/unit/mapper.py 2014-03-14 15:04:18 +0000
714@@ -635,6 +635,11 @@
715 column = self.model._all_columns[from_attr].column
716 if column._type == 'boolean':
717 return res and 1 or 0
718+ if not res:
719+ if column._type in ('char', 'text', 'datetime', 'date', 'html'):
720+ return ""
721+ if column._type in ('integer', 'float'):
722+ return 0
723 return res
724
725
726
727=== added directory 'prestashoperpconnect_catalog_manager/tests'
728=== added file 'prestashoperpconnect_catalog_manager/tests/__init__.py'
729--- prestashoperpconnect_catalog_manager/tests/__init__.py 1970-01-01 00:00:00 +0000
730+++ prestashoperpconnect_catalog_manager/tests/__init__.py 2014-03-14 15:04:18 +0000
731@@ -0,0 +1,30 @@
732+# -*- coding: utf-8 -*-
733+###############################################################################
734+#
735+# Module for OpenERP
736+# Copyright (C) 2013 Akretion (http://www.akretion.com).
737+# @author Sébastien BEAU <sebastien.beau@akretion.com>
738+#
739+# This program is free software: you can redistribute it and/or modify
740+# it under the terms of the GNU Affero General Public License as
741+# published by the Free Software Foundation, either version 3 of the
742+# License, or (at your option) any later version.
743+#
744+# This program is distributed in the hope that it will be useful,
745+# but WITHOUT ANY WARRANTY; without even the implied warranty of
746+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
747+# GNU Affero General Public License for more details.
748+#
749+# You should have received a copy of the GNU Affero General Public License
750+# along with this program. If not, see <http://www.gnu.org/licenses/>.
751+#
752+###############################################################################
753+
754+from . import test_mapper
755+
756+fast_suite = [
757+]
758+
759+checks = [
760+ test_mapper,
761+]
762
763=== added file 'prestashoperpconnect_catalog_manager/tests/test_mapper.py'
764--- prestashoperpconnect_catalog_manager/tests/test_mapper.py 1970-01-01 00:00:00 +0000
765+++ prestashoperpconnect_catalog_manager/tests/test_mapper.py 2014-03-14 15:04:18 +0000
766@@ -0,0 +1,106 @@
767+# -*- coding: utf-8 -*-
768+###############################################################################
769+#
770+# Module for OpenERP
771+# Copyright (C) 2013 Akretion (http://www.akretion.com).
772+# @author Sébastien BEAU <sebastien.beau@akretion.com>
773+#
774+# This program is free software: you can redistribute it and/or modify
775+# it under the terms of the GNU Affero General Public License as
776+# published by the Free Software Foundation, either version 3 of the
777+# License, or (at your option) any later version.
778+#
779+# This program is distributed in the hope that it will be useful,
780+# but WITHOUT ANY WARRANTY; without even the implied warranty of
781+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
782+# GNU Affero General Public License for more details.
783+#
784+# You should have received a copy of the GNU Affero General Public License
785+# along with this program. If not, see <http://www.gnu.org/licenses/>.
786+#
787+###############################################################################
788+
789+
790+
791+import unittest2
792+from functools import partial
793+
794+from openerp.addons.connector.session import ConnectorSession
795+import openerp.tests.common as common
796+from openerp.addons.prestashoperpconnect.connector import get_environment
797+from openerp.addons.connector.unit.mapper import ExportMapper
798+
799+DB = common.DB
800+ADMIN_USER_ID = common.ADMIN_USER_ID
801+
802+
803+
804+class test_export_mapper(common.SingleTransactionCase):
805+ """ Test the imports from a Prestashop Mock.
806+
807+ The data returned by Prestashop are those created for the
808+ demo version of Prestashop on a standard 1.7 version.
809+ """
810+
811+ def setUp(self):
812+ super(test_export_mapper, self).setUp()
813+ self.backend_model = self.registry('prestashop.backend')
814+ self.shop_group_model = self.registry('prestashop.shop.group')
815+ self.shop_model = self.registry('prestashop.shop')
816+ self.session = ConnectorSession(self.cr, self.uid)
817+ data_model = self.registry('ir.model.data')
818+ self.get_ref = partial(data_model.get_object_reference,
819+ self.cr, self.uid)
820+ backend_ids = self.backend_model.search(
821+ self.cr, self.uid,
822+ [('name', '=', 'Test Prestashop')])
823+ if backend_ids:
824+ self.backend_id = backend_ids[0]
825+ else:
826+ __, warehouse_id = self.get_ref('stock', 'warehouse0')
827+ self.backend_id = self.backend_model.create(
828+ self.cr,
829+ self.uid,
830+ {'name': 'Test Prestashop',
831+ 'version': '1.5',
832+ 'location': 'http://anyurl',
833+ 'username': 'sebastien',
834+ 'warehouse_id': warehouse_id,
835+ 'password': '42'})
836+
837+ def test_export_direct_map(self):
838+ """ Map direct field for export """
839+ env = get_environment(self.session,
840+ 'prestashop.product.product',
841+ self.backend_id)
842+ mapper = env.get_connector_unit(ExportMapper)
843+
844+ test = [
845+ #(field_type, field_name, result_expected)
846+ ('char', 'ean13', ''),
847+ ('text', 'description', ''),
848+ ('html', 'description_html', ''),
849+ ('datetime', 'date_add', ''),
850+ ('date', 'available_date', ''),
851+ ('float', 'weight', 0),
852+ ('integer', 'minimal_quantity', 0),
853+ ('boolean', 'sale_ok', 0),
854+ ]
855+
856+ for field_type, field_name, result_expected in test:
857+ result = mapper._map_direct(
858+ {field_name: False},
859+ field_name,
860+ field_name)
861+ self.assertEqual(result, result_expected,
862+ 'Field type %s is not correctly mapped'%field_type)
863+ if result_expected == 0:
864+ self.assertEqual(str(result), str(result_expected),
865+ 'Field type %s is not correctly mapped'%field_type)
866+
867+
868+
869+
870+
871+
872+