Merge lp:~akretion-team/openerp-connector-magento/openerp-connector-magento-catalog into lp:~openerp-connector-core-editors/openerp-connector-magento/7.0

Proposed by Sébastien BEAU - http://www.akretion.com
Status: Rejected
Rejected by: Sébastien BEAU - http://www.akretion.com
Proposed branch: lp:~akretion-team/openerp-connector-magento/openerp-connector-magento-catalog
Merge into: lp:~openerp-connector-core-editors/openerp-connector-magento/7.0
Diff against target: 2313 lines (+2228/-0) (has conflicts)
16 files modified
magentoerpconnect_catalog/__init__.py (+29/-0)
magentoerpconnect_catalog/__openerp__.py (+67/-0)
magentoerpconnect_catalog/connector.py (+26/-0)
magentoerpconnect_catalog/consumer.py (+118/-0)
magentoerpconnect_catalog/magento_model.py (+50/-0)
magentoerpconnect_catalog/magento_model_view.xml (+26/-0)
magentoerpconnect_catalog/product.py (+185/-0)
magentoerpconnect_catalog/product_attribute.py (+527/-0)
magentoerpconnect_catalog/product_attribute_view.xml (+231/-0)
magentoerpconnect_catalog/product_category.py (+239/-0)
magentoerpconnect_catalog/product_image.py (+336/-0)
magentoerpconnect_catalog/product_image_view.xml (+79/-0)
magentoerpconnect_catalog/product_view.xml (+78/-0)
magentoerpconnect_catalog/tests/__init__.py (+29/-0)
magentoerpconnect_catalog/tests/test_attribute_synchronisation.py (+109/-0)
magentoerpconnect_catalog/tests/test_data.py (+99/-0)
Conflict adding file magentoerpconnect_catalog.  Moved existing file to magentoerpconnect_catalog.moved.
To merge this branch: bzr merge lp:~akretion-team/openerp-connector-magento/openerp-connector-magento-catalog
Reviewer Review Type Date Requested Status
OpenERP Connector Core Editors Pending
Review via email: mp+223605@code.launchpad.net

Description of the change

Add product catalog export

To post a comment you must log in.
44. By Sébastien BEAU - http://www.akretion.com

[REF] comment product image view for now, need to be fixed

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

[REF] attribute set export skeletonSetId should be map only at creation

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

[FIX] fix backend view, move attribute_set_tpl_id just after default category

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

[FIX] replace fields by vals (connector 2.2 update)

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

[IMP] add test for attribute set synchronisation

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

This merge have been moved on github https://github.com/OCA/connector-magento/pull/4

Unmerged revisions

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

[IMP] add test for attribute set synchronisation

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

[FIX] replace fields by vals (connector 2.2 update)

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

[FIX] fix backend view, move attribute_set_tpl_id just after default category

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

[REF] attribute set export skeletonSetId should be map only at creation

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

[REF] comment product image view for now, need to be fixed

43. By Chafique DELLI

[ADD]:add module that manages the product variant's price and fix export product attributes and fix export products
(../connector-magentoconnect-catalog rev 736)

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

[IMP] add multi-lang support for exporting product and add a TODO FIXME
(../connector-magentoconnect-catalog rev 735)

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

[FIX] fix export of attrbute option, now support multi-lang, TODO fix the option sequence or add comment to understand the +1
(../connector-magentoconnect-catalog rev 734)

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

[IMP] improve view for attribute set
(../connector-magentoconnect-catalog rev 733)

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

[IMP] add the export of the translation for the category
(../connector-magentoconnect-catalog rev 731)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'magentoerpconnect_catalog'
2=== renamed directory 'magentoerpconnect_catalog' => 'magentoerpconnect_catalog.moved'
3=== added file 'magentoerpconnect_catalog/__init__.py'
4--- magentoerpconnect_catalog/__init__.py 1970-01-01 00:00:00 +0000
5+++ magentoerpconnect_catalog/__init__.py 2014-07-01 22:23:08 +0000
6@@ -0,0 +1,29 @@
7+# -*- coding: utf-8 -*-
8+##############################################################################
9+#
10+# Copyright 2013
11+# Author: Guewen Baconnier - Camptocamp SA
12+# Augustin Cisterne-Kaasv - Elico-corp
13+# David Béal - Akretion
14+# This program is free software: you can redistribute it and/or modify
15+# it under the terms of the GNU Affero General Public License as
16+# published by the Free Software Foundation, either version 3 of the
17+# License, or (at your option) any later version.
18+#
19+# This program is distributed in the hope that it will be useful,
20+# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+# GNU Affero General Public License for more details.
23+#
24+# You should have received a copy of the GNU Affero General Public License
25+# along with this program. If not, see <http://www.gnu.org/licenses/>.
26+#
27+##############################################################################
28+
29+import connector
30+import consumer
31+import magento_model
32+import product
33+import product_category
34+import product_attribute
35+import product_image
36
37=== added file 'magentoerpconnect_catalog/__openerp__.py'
38--- magentoerpconnect_catalog/__openerp__.py 1970-01-01 00:00:00 +0000
39+++ magentoerpconnect_catalog/__openerp__.py 2014-07-01 22:23:08 +0000
40@@ -0,0 +1,67 @@
41+# -*- coding: utf-8 -*-
42+##############################################################################
43+#
44+# Author: Guewen Baconnier
45+# Copyright 2013 Camptocamp SA
46+# Copyright 2013 Akretion
47+#
48+# This program is free software: you can redistribute it and/or modify
49+# it under the terms of the GNU Affero General Public License as
50+# published by the Free Software Foundation, either version 3 of the
51+# License, or (at your option) any later version.
52+#
53+# This program is distributed in the hope that it will be useful,
54+# but WITHOUT ANY WARRANTY; without even the implied warranty of
55+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56+# GNU Affero General Public License for more details.
57+#
58+# You should have received a copy of the GNU Affero General Public License
59+# along with this program. If not, see <http://www.gnu.org/licenses/>.
60+#
61+##############################################################################
62+
63+
64+{'name': 'Magento Connector - Catalog',
65+ 'version': '2.0.0',
66+ 'category': 'Connector',
67+ 'depends': ['magentoerpconnect',
68+ 'product_custom_attributes',
69+ #'product_links',
70+ 'product_image',
71+ ],
72+ 'author': 'MagentoERPconnect Core Editors',
73+ 'license': 'AGPL-3',
74+ 'website': 'https://launchpad.net/magentoerpconnect',
75+ 'description': """
76+Magento Connector - Catalog
77+===========================
78+
79+Extension for **Magento Connector**, add management of the product's catalog:
80+
81+Export
82+* products
83+* categories
84+* attributes (only export up to now): attribute set, attributes and attribute options :
85+
86+ - to be used, you need to manually create an attribute set which match with magento 'Default' attribute set (generally magento_id '4')
87+
88+* product image: dependency
89+
90+ - dev branch: lp:~akretion-team/openerp-product-attributes/openerp-product-attributes-image
91+ - future production branch: lp:openerp-product-attributes/openerp-product-attributes
92+
93+TODO:
94+* import/export product links
95+* import attributes
96+""",
97+ 'images': [],
98+ 'demo': [],
99+ 'data': [
100+ 'magento_model_view.xml',
101+ 'product_view.xml',
102+ 'product_attribute_view.xml',
103+ 'product_image_view.xml',
104+ ],
105+ 'installable': True,
106+ 'application': False,
107+}
108\ No newline at end of file
109
110=== added file 'magentoerpconnect_catalog/connector.py'
111--- magentoerpconnect_catalog/connector.py 1970-01-01 00:00:00 +0000
112+++ magentoerpconnect_catalog/connector.py 2014-07-01 22:23:08 +0000
113@@ -0,0 +1,26 @@
114+# -*- coding: utf-8 -*-
115+##############################################################################
116+#
117+# Copyright 2013
118+# Author: Guewen Baconnier - Camptocamp SA
119+# Augustin Cisterne-Kaasv - Elico-corp
120+# David Béal - Akretion
121+# This program is free software: you can redistribute it and/or modify
122+# it under the terms of the GNU Affero General Public License as
123+# published by the Free Software Foundation, either version 3 of the
124+# License, or (at your option) any later version.
125+#
126+# This program is distributed in the hope that it will be useful,
127+# but WITHOUT ANY WARRANTY; without even the implied warranty of
128+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
129+# GNU Affero General Public License for more details.
130+#
131+# You should have received a copy of the GNU Affero General Public License
132+# along with this program. If not, see <http://www.gnu.org/licenses/>.
133+#
134+##############################################################################
135+
136+from openerp.addons.connector.connector import install_in_connector
137+
138+
139+install_in_connector()
140
141=== added file 'magentoerpconnect_catalog/consumer.py'
142--- magentoerpconnect_catalog/consumer.py 1970-01-01 00:00:00 +0000
143+++ magentoerpconnect_catalog/consumer.py 2014-07-01 22:23:08 +0000
144@@ -0,0 +1,118 @@
145+# -*- coding: utf-8 -*-
146+##############################################################################
147+#
148+# Copyright 2013
149+# Author: Guewen Baconnier - Camptocamp SA
150+# Augustin Cisterne-Kaasv - Elico-corp
151+# David Béal - Akretion
152+# This program is free software: you can redistribute it and/or modify
153+# it under the terms of the GNU Affero General Public License as
154+# published by the Free Software Foundation, either version 3 of the
155+# License, or (at your option) any later version.
156+#
157+# This program is distributed in the hope that it will be useful,
158+# but WITHOUT ANY WARRANTY; without even the implied warranty of
159+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
160+# GNU Affero General Public License for more details.
161+#
162+# You should have received a copy of the GNU Affero General Public License
163+# along with this program. If not, see <http://www.gnu.org/licenses/>.
164+#
165+##############################################################################
166+
167+from openerp.addons.connector.event import (on_record_write,
168+ on_record_create,
169+ on_record_unlink
170+ )
171+
172+import openerp.addons.magentoerpconnect.consumer as magentoerpconnect
173+
174+from openerp.addons.connector.connector import Binder
175+from openerp.addons.magentoerpconnect.connector import get_environment
176+from openerp.addons.magentoerpconnect.unit.delete_synchronizer import (
177+ export_delete_record)
178+
179+
180+EXCLUDED_FIELDS_WRITING = {
181+ 'product.product': ['magento_bind_ids', 'image_ids'],
182+ 'product.category': ['magento_bind_ids',],
183+ 'magento.product.category': ['magento_bind_ids',],
184+}
185+
186+def exclude_fields_from_synchro(model_name, fields):
187+ if fields and EXCLUDED_FIELDS_WRITING.get(model_name):
188+ fields = list(set(fields).difference(EXCLUDED_FIELDS_WRITING))
189+ return fields
190+
191+@on_record_create(model_names=[
192+ 'magento.product.category',
193+ 'magento.product.product',
194+ 'magento.product.attribute',
195+ 'magento.attribute.set',
196+ 'magento.attribute.option',
197+ 'magento.product.image',
198+ ])
199+@on_record_write(model_names=[
200+ 'magento.product.category',
201+ 'magento.product.product',
202+ 'magento.product.attribute',
203+ 'magento.attribute.option',
204+ 'magento.product.image',
205+ #'magento.product.storeview',
206+ ])
207+def delay_export(session, model_name, record_id, vals=None):
208+ #fields = exclude_fields_from_synchro(model_name, fields)
209+ magentoerpconnect.delay_export(session, model_name,
210+ record_id, vals=vals)
211+
212+
213+@on_record_write(model_names=[
214+ 'product.product',
215+ 'product.category',
216+ ])
217+def delay_export_all_bindings(session, model_name, record_id, vals=None):
218+ #fields = exclude_fields_from_synchro(model_name, fields)
219+ magentoerpconnect.delay_export_all_bindings(session, model_name,
220+ record_id, vals=vals)
221+
222+#
223+#@on_record_unlink(model_names=[
224+# 'product.category',
225+# ])
226+#def delay_unlink_all_bindings(session, model_name, record_id):
227+# #magentoerpconnect.delay_unlink_all_bindings(session, model_name, record_id)
228+# import pdb;pdb.set_trace()
229+# magentoerpconnect.delay_unlink(session, model_name, record_id)
230+
231+
232+@on_record_unlink(model_names=[
233+ 'magento.product.category',
234+ 'magento.product.product'
235+ 'magento.product.attribute',
236+ 'magento.attribute.set',
237+ 'magento.attribute.option',
238+ ])
239+def delay_unlink(session, model_name, record_id):
240+ magentoerpconnect.delay_unlink(session, model_name, record_id)
241+
242+
243+@on_record_unlink(model_names=['magento.product.image'])
244+def delay_image_unlink(session, model_name, record_id):
245+ model = session.pool.get('magento.product.image')
246+ record = model.browse(session.cr, session.uid,
247+ record_id, context=session.context)
248+ magento_keys = []
249+ env = get_environment(session, 'magento.product.image',
250+ record.backend_id.id)
251+ binder = env.get_connector_unit(Binder)
252+ magento_keys.append(binder.to_backend(record_id))
253+ # in addition to magento 'image id' needs 'product id' to remove images
254+ # see http://www.magentocommerce.com/api/soap/catalog/...
255+ # catalogProductAttributeMedia/catalog_product_attribute_media.remove.html
256+ env = get_environment(session, 'magento.product.product',
257+ record.backend_id.id)
258+ binder = env.get_connector_unit(Binder)
259+ magento_keys.append(binder.to_backend(record.openerp_id.product_id.id, wrap=True))
260+ if magento_keys:
261+ export_delete_record.delay(session, 'magento.product.image',
262+ record.backend_id.id, magento_keys)
263
264=== added directory 'magentoerpconnect_catalog/i18n'
265=== added file 'magentoerpconnect_catalog/magento_model.py'
266--- magentoerpconnect_catalog/magento_model.py 1970-01-01 00:00:00 +0000
267+++ magentoerpconnect_catalog/magento_model.py 2014-07-01 22:23:08 +0000
268@@ -0,0 +1,50 @@
269+# -*- coding: utf-8 -*-
270+##############################################################################
271+#
272+# Copyright 2013
273+# Author: Guewen Baconnier - Camptocamp
274+# David Béal - Akretion
275+# Sébastien Beau - Akretion
276+# This program is free software: you can redistribute it and/or modify
277+# it under the terms of the GNU Affero General Public License as
278+# published by the Free Software Foundation, either version 3 of the
279+# License, or (at your option) any later version.
280+#
281+# This program is distributed in the hope that it will be useful,
282+# but WITHOUT ANY WARRANTY; without even the implied warranty of
283+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
284+# GNU Affero General Public License for more details.
285+#
286+# You should have received a copy of the GNU Affero General Public License
287+# along with this program. If not, see <http://www.gnu.org/licenses/>.
288+#
289+##############################################################################
290+
291+import logging
292+from openerp.osv import orm, fields
293+from openerp.addons.connector.session import ConnectorSession
294+from openerp.addons.magentoerpconnect.unit.import_synchronizer import import_batch
295+
296+
297+_logger = logging.getLogger(__name__)
298+
299+
300+class magento_backend(orm.Model):
301+ _inherit = 'magento.backend'
302+
303+ def import_attribute_sets(self, cr, uid, ids, context=None):
304+ if not hasattr(ids, '__iter__'):
305+ ids = [ids]
306+ self.check_magento_structure(cr, uid, ids, context=context)
307+ session = ConnectorSession(cr, uid, context=context)
308+ for backend_id in ids:
309+ import_batch.delay(session, 'magento.attribute.set', backend_id)
310+ return True
311+
312+ _columns = {
313+ 'attribute_set_tpl_id': fields.many2one(
314+ 'magento.attribute.set',
315+ 'Attribute set template',
316+ help="Attribute set ID basing on which the new attribute set "
317+ "will be created."),
318+ }
319
320=== added file 'magentoerpconnect_catalog/magento_model_view.xml'
321--- magentoerpconnect_catalog/magento_model_view.xml 1970-01-01 00:00:00 +0000
322+++ magentoerpconnect_catalog/magento_model_view.xml 2014-07-01 22:23:08 +0000
323@@ -0,0 +1,26 @@
324+<?xml version="1.0" encoding="utf-8"?>
325+<openerp>
326+ <data>
327+
328+<record id="view_magento_backend_form" model="ir.ui.view">
329+ <field name="model">magento.backend</field>
330+ <field name="inherit_id" ref="magentoerpconnect.view_magento_backend_form"/>
331+ <field name="arch" type="xml">
332+ <xpath expr="//notebook/page[@name='import']/group[5]" position="after">
333+ <group>
334+ <label string="Import attribute sets"
335+ class="oe_inline"/>
336+ <button name="import_attribute_sets"
337+ type="object"
338+ class="oe_highlight"
339+ string="Import in background"/>
340+ </group>
341+ </xpath>
342+ <xpath expr="//notebook/page[@name='advanced_configuration']/group/field[@name='default_category_id']" position="after">
343+ <field name="attribute_set_tpl_id"/>
344+ </xpath>
345+ </field>
346+</record>
347+
348+ </data>
349+</openerp>
350
351=== added file 'magentoerpconnect_catalog/product.py'
352--- magentoerpconnect_catalog/product.py 1970-01-01 00:00:00 +0000
353+++ magentoerpconnect_catalog/product.py 2014-07-01 22:23:08 +0000
354@@ -0,0 +1,185 @@
355+# -*- coding: utf-8 -*-
356+##############################################################################
357+#
358+# Copyright 2013
359+# Author: Guewen Baconnier - Camptocamp SA
360+# Augustin Cisterne-Kaasv - Elico-corp
361+# David Béal - Akretion
362+# This program is free software: you can redistribute it and/or modify
363+# it under the terms of the GNU Affero General Public License as
364+# published by the Free Software Foundation, either version 3 of the
365+# License, or (at your option) any later version.
366+#
367+# This program is distributed in the hope that it will be useful,
368+# but WITHOUT ANY WARRANTY; without even the implied warranty of
369+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
370+# GNU Affero General Public License for more details.
371+#
372+# You should have received a copy of the GNU Affero General Public License
373+# along with this program. If not, see <http://www.gnu.org/licenses/>.
374+#
375+##############################################################################
376+
377+from openerp.osv import fields, orm, osv
378+from openerp.addons.connector.queue.job import job
379+from openerp.addons.connector.unit.mapper import (mapping,
380+ changed_by,
381+ ExportMapper)
382+from openerp.addons.magentoerpconnect.unit.delete_synchronizer import (
383+ MagentoDeleteSynchronizer)
384+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
385+ MagentoTranslationExporter)
386+from openerp.addons.magentoerpconnect.backend import magento
387+from openerp.addons.magentoerpconnect.product import ProductProductAdapter
388+from openerp.addons.connector.exception import MappingError
389+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
390+ export_record
391+)
392+
393+
394+@magento
395+class ProductProductDeleteSynchronizer(MagentoDeleteSynchronizer):
396+ """ Partner deleter for Magento """
397+ _model_name = ['magento.product.product']
398+
399+
400+@magento
401+class ProductProductExport(MagentoTranslationExporter):
402+ _model_name = ['magento.product.product']
403+
404+ def _export_dependencies(self):
405+ """ Export the dependencies for the product"""
406+ #TODO add export of category
407+ attribute_binder = self.get_binder_for_model('magento.product.attribute')
408+ option_binder = self.get_binder_for_model('magento.attribute.option')
409+ record = self.binding_record
410+ for group in record.attribute_group_ids:
411+ for attribute in group.attribute_ids:
412+ attribute_ext_id = attribute_binder.to_backend(attribute.attribute_id.id, wrap=True)
413+ if attribute_ext_id:
414+ options = []
415+ if attribute.ttype == 'many2one' and record[attribute.name]:
416+ options = [record[attribute.name]]
417+ elif attribute.ttype == 'many2many':
418+ options = record[attribute.name]
419+ for option in options:
420+ if not option_binder.to_backend(option.id, wrap=True):
421+ ctx = self.session.context.copy()
422+ ctx['connector_no_export'] = True
423+ binding_id = self.session.pool['magento.attribute.option'].create(
424+ self.session.cr, self.session.uid,{
425+ 'backend_id': self.backend_record.id,
426+ 'openerp_id': option.id,
427+ 'name': option.name,
428+ }, context=ctx)
429+ export_record(self.session, 'magento.attribute.option', binding_id)
430+
431+
432+@magento
433+class ProductProductExportMapper(ExportMapper):
434+ _model_name = 'magento.product.product'
435+
436+ #TODO FIXME
437+ # direct = [('name', 'name'),
438+ # ('description', 'description'),
439+ # ('weight', 'weight'),
440+ # ('list_price', 'price'),
441+ # ('description_sale', 'short_description'),
442+ # ('default_code', 'sku'),
443+ # ('product_type', 'type'),
444+ # ('created_at', 'created_at'),
445+ # ('updated_at', 'updated_at'),
446+ # ('status', 'status'),
447+ # ('visibility', 'visibility'),
448+ # ('product_type', 'product_type')
449+ # ]
450+ @mapping
451+ def all(self, record):
452+ return {'name': record.name,
453+ 'description': record.description,
454+ 'weight': record.weight,
455+ 'price': record.list_price,
456+ 'short_description': record.description_sale,
457+ 'type': record.product_type,
458+ 'created_at': record.created_at,
459+ #'updated_at': record.updated_at,
460+ 'status': record.status,
461+ 'visibility': record.visibility,
462+ 'product_type': record.product_type }
463+
464+ @mapping
465+ def sku(self, record):
466+ sku = record.default_code
467+ if not sku:
468+ raise MappingError("The product attribute default code cannot be empty.")
469+ return {'sku': sku}
470+
471+ @mapping
472+ def set(self, record):
473+ binder = self.get_binder_for_model('magento.attribute.set')
474+ set_id = binder.to_backend(record.attribute_set_id.id, wrap=True)
475+ return {'attrset': set_id}
476+
477+ @mapping
478+ def updated_at(self, record):
479+ updated_at = record.updated_at
480+ if not updated_at:
481+ updated_at = '1970-01-01'
482+ return {'updated_at': updated_at}
483+
484+ @mapping
485+ def website_ids(self, record):
486+ website_ids = []
487+ for website_id in record.website_ids:
488+ magento_id = website_id.magento_id
489+ website_ids.append(magento_id)
490+ return {'website_ids': website_ids}
491+
492+ @mapping
493+ def category(self, record):
494+ categ_ids = []
495+ if record.categ_id:
496+ for m_categ in record.categ_id.magento_bind_ids:
497+ if m_categ.backend_id.id == self.backend_record.id:
498+ categ_ids.append(m_categ.magento_id)
499+
500+ for categ in record.categ_ids:
501+ for m_categ in categ.magento_bind_ids:
502+ if m_categ.backend_id.id == self.backend_record.id:
503+ categ_ids.append(m_categ.magento_id)
504+ return {'categories': categ_ids}
505+
506+ @mapping
507+ def get_product_attribute_option(self, record):
508+ result = {}
509+ attribute_binder = self.get_binder_for_model('magento.product.attribute')
510+ option_binder = self.get_binder_for_model('magento.attribute.option')
511+ for group in record.attribute_group_ids:
512+ for attribute in group.attribute_ids:
513+ magento_attribute = None
514+ #TODO maybe adding a get_bind function can be better
515+ for bind in attribute.magento_bind_ids:
516+ if bind.backend_id.id == self.backend_record.id:
517+ magento_attribute = bind
518+
519+ if not magento_attribute:
520+ continue
521+
522+ if attribute.ttype == 'many2one':
523+ option = record[attribute.name]
524+ if option:
525+ result[magento_attribute.attribute_code] = \
526+ option_binder.to_backend(option.id, wrap=True)
527+ else:
528+ continue
529+ elif attribute.ttype == 'many2many':
530+ options = record[attribute.name]
531+ if options:
532+ result[magento_attribute.attribute_code] = \
533+ [option_binder.to_backend(option.id, wrap=True) for option in options]
534+ else:
535+ continue
536+ else:
537+ #TODO add support of lang
538+ result[magento_attribute.attribute_code] = record[attribute.name]
539+ return result
540
541=== added file 'magentoerpconnect_catalog/product_attribute.py'
542--- magentoerpconnect_catalog/product_attribute.py 1970-01-01 00:00:00 +0000
543+++ magentoerpconnect_catalog/product_attribute.py 2014-07-01 22:23:08 +0000
544@@ -0,0 +1,527 @@
545+# -*- coding: utf-8 -*-
546+##############################################################################
547+#
548+# Copyright 2013
549+# Author: Guewen Baconnier - Camptocamp
550+# Augustin Cisterne-Kaas - Elico-corp
551+# David Béal - Akretion
552+# Sébastien Beau - Akretion
553+# Chafique Delli - Akretion
554+# This program is free software: you can redistribute it and/or modify
555+# it under the terms of the GNU Affero General Public License as
556+# published by the Free Software Foundation, either version 3 of the
557+# License, or (at your option) any later version.
558+#
559+# This program is distributed in the hope that it will be useful,
560+# but WITHOUT ANY WARRANTY; without even the implied warranty of
561+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
562+# GNU Affero General Public License for more details.
563+#
564+# You should have received a copy of the GNU Affero General Public License
565+# along with this program. If not, see <http://www.gnu.org/licenses/>.
566+#
567+##############################################################################
568+
569+
570+from openerp.osv import fields, orm
571+#from openerp.tools.translate import _
572+from openerp.osv.osv import except_osv
573+from openerp.addons.connector.unit.mapper import (
574+ mapping,
575+ #changed_by,
576+ only_create,
577+ ImportMapper,
578+ ExportMapper,)
579+#from openerp.addons.connector.exception import MappingError
580+from openerp.addons.magentoerpconnect.backend import magento
581+from openerp.addons.magentoerpconnect.unit.backend_adapter import GenericAdapter
582+from openerp.addons.magentoerpconnect.unit.binder import MagentoModelBinder
583+from openerp.addons.magentoerpconnect.unit.delete_synchronizer import (
584+ MagentoDeleteSynchronizer)
585+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
586+ MagentoExporter)
587+from openerp.addons.magentoerpconnect.unit.import_synchronizer import (
588+ DelayedBatchImport,
589+ MagentoImportSynchronizer,)
590+from openerp.addons.connector.exception import FailedJobError
591+
592+
593+@magento(replacing=MagentoModelBinder)
594+class MagentoAttributeBinder(MagentoModelBinder):
595+ _model_name = [
596+ 'magento.product.attribute',
597+ 'magento.attribute.option',
598+ 'magento.attribute.set',
599+ ]
600+
601+
602+# Attribute Set
603+class AttributeSet(orm.Model):
604+ _inherit = 'attribute.set'
605+
606+ _columns = {
607+ 'magento_bind_ids': fields.one2many(
608+ 'magento.attribute.set',
609+ 'openerp_id',
610+ string='Magento Bindings',),
611+ }
612+
613+
614+class MagentoAttributeSet(orm.Model):
615+ _name = 'magento.attribute.set'
616+ _description = "Magento attribute set"
617+ _inherit = 'magento.binding'
618+ _rec_name = 'attribute_set_name'
619+
620+ _columns = {
621+ 'openerp_id': fields.many2one(
622+ 'attribute.set',
623+ string='Attribute set',
624+ ondelete='cascade'),
625+ 'attribute_set_name': fields.char(
626+ 'Name',
627+ size=64,
628+ required=True),
629+ 'sort_order': fields.integer(
630+ 'Sort order',
631+ readonly=True),
632+ }
633+
634+ def name_get(self, cr, uid, ids, context=None):
635+ res = []
636+ for elm in self.read(cr, uid, ids, ['attribute_set_name'],
637+ context=context):
638+ res.append((elm['id'], elm['attribute_set_name']))
639+ return res
640+
641+ _sql_constraints = [
642+ ('magento_uniq', 'unique(backend_id, openerp_id)',
643+ "An 'Attribute set' with the same ID on this Magento backend "
644+ "already exists")
645+ ]
646+
647+
648+@magento
649+class AttributeSetAdapter(GenericAdapter):
650+ _model_name = 'magento.attribute.set'
651+ _magento_default_model = 'product_attribute_set'
652+ _magento_model = 'ol_catalog_product_attributeset'
653+
654+ def create(self, data):
655+ """ Create a record on the external system """
656+ return self._call('%s.create' % self._magento_default_model,
657+ [data['attribute_set_name'], data['skeletonSetId']])
658+
659+ def delete(self, id):
660+ """ Delete a record on the external system """
661+ return self._call('%s.remove' % self._magento_default_model, [str(id)])
662+
663+ def search(self, filters=None):
664+ """ Search records according and returns a list of ids
665+ :rtype: list
666+ """
667+ return self._call('%s.search' % self._magento_model, [])
668+
669+ def read(self, id, storeview_id=None, attributes=None):
670+ """ Returns the information of a record
671+ :rtype: dict
672+ """
673+ return self._call('%s.info' % self._magento_model, [int(id)])
674+
675+ def update(self, id, attribute_id):
676+ """ Add an existing attribute to an attribute set on the external system
677+ :rtype: boolean
678+ """
679+ return self._call('%s.attributeAdd' % self._magento_default_model,
680+ [str(attribute_id),str(id)])
681+
682+
683+@magento
684+class AttributeSetDelayedBatchImport(DelayedBatchImport):
685+ _model_name = ['magento.attribute.set']
686+
687+
688+@magento
689+class AttributeSetImport(MagentoImportSynchronizer):
690+ _model_name = ['magento.attribute.set']
691+
692+
693+@magento
694+class AttributeSetImportMapper(ImportMapper):
695+ _model_name = 'magento.attribute.set'
696+
697+ direct = [
698+ ('attribute_set_name', 'attribute_set_name'),
699+ ('attribute_set_id', 'magento_id'),
700+ ('sort_order', 'sort_order'), ]
701+
702+ @mapping
703+ def backend_id(self, record):
704+ return {'backend_id': self.backend_record.id}
705+
706+
707+@magento
708+class AttributeSetDeleteSynchronizer(MagentoDeleteSynchronizer):
709+ _model_name = ['magento.attribute.set']
710+
711+
712+@magento
713+class AttributeSetExport(MagentoExporter):
714+ _model_name = ['magento.attribute.set']
715+
716+
717+@magento
718+class AttributeSetExportMapper(ExportMapper):
719+ _model_name = 'magento.attribute.set'
720+
721+ direct = [
722+ ('attribute_set_name', 'attribute_set_name'),
723+ ('sort_order', 'sort_order'),
724+ ]
725+
726+ @only_create
727+ @mapping
728+ def skeletonSetId(self, record):
729+ tmpl_set_id = self.backend_record.attribute_set_tpl_id.id
730+ if tmpl_set_id:
731+ binder = self.get_binder_for_model('magento.attribute.set')
732+ magento_tpl_set_id = binder.to_backend(tmpl_set_id)
733+ else:
734+ raise FailedJobError((
735+ "'Attribute set template' field must be define on "
736+ "the backend.\n\n"
737+ "Resolution: \n"
738+ "- Go to Connectors > Magento > Backends > '%s'\n"
739+ "- Fill the field Attribte set Tempalte\n"
740+ )% self.backend_record.name)
741+ return {'skeletonSetId': magento_tpl_set_id}
742+
743+
744+# Attribute
745+class AttributeAttribute(orm.Model):
746+ _inherit = 'attribute.attribute'
747+
748+ def _get_model_product(self, cr, uid, ids, idcontext=None):
749+ model, res_id = self.pool['ir.model.data'].get_object_reference(
750+ cr, uid, 'product', 'model_product_product')
751+ return res_id
752+
753+ _columns = {
754+ 'magento_bind_ids': fields.one2many(
755+ 'magento.product.attribute',
756+ 'openerp_id',
757+ string='Magento Bindings',),
758+ }
759+
760+ _defaults = {
761+ 'model_id': _get_model_product,
762+ }
763+
764+
765+class MagentoProductAttribute(orm.Model):
766+ _name = 'magento.product.attribute'
767+ _description = "Magento Product Attribute"
768+ _inherit = 'magento.binding'
769+ _rec_name = 'attribute_code'
770+ MAGENTO_HELP = "This field is a technical / configuration field for " \
771+ "the attribute on Magento. \nPlease refer to the Magento " \
772+ "documentation for details. "
773+
774+ def copy(self, cr, uid, id, default=None, context=None):
775+ if default is None:
776+ default = {}
777+ default['attribute_code'] = default.get('attribute_code', '') + 'Copy '
778+ return super(MagentoProductAttribute, self).copy(
779+ cr, uid, id, default, context=context)
780+
781+ def _frontend_input(self, cr, uid, ids, field_names, arg, context=None):
782+ res={}
783+ for elm in self.browse(cr, uid, ids):
784+ field_type = elm.openerp_id.attribute_type
785+ map_type = {
786+ 'char': 'text',
787+ 'text': 'textarea',
788+ 'float': 'price',
789+ 'datetime': 'date',
790+ 'binary': 'media_image',
791+ }
792+ res[elm.id] = map_type.get(field_type, field_type)
793+ return res
794+
795+ _columns = {
796+ 'openerp_id': fields.many2one(
797+ 'attribute.attribute',
798+ string='Attribute',
799+ required=True,
800+ ondelete='cascade'),
801+ 'attribute_code': fields.char(
802+ 'Code',
803+ required=True,
804+ size=200,),
805+ 'scope': fields.selection(
806+ [('store', 'store'), ('website', 'website'), ('global', 'global')],
807+ 'Scope',
808+ required=True,
809+ help=MAGENTO_HELP),
810+ 'apply_to': fields.selection([
811+ ('simple', 'simple'),
812+ ],
813+ 'Apply to',
814+ required=True,
815+ help=MAGENTO_HELP),
816+ 'frontend_input': fields.function(
817+ _frontend_input,
818+ method=True,
819+ string='Frontend input',
820+ type='char',
821+ store=False,
822+ help="This field depends on OpenERP attribute 'type' field "
823+ "but used on Magento"),
824+ 'frontend_label': fields.char(
825+ 'Label', required=True, size=100, help=MAGENTO_HELP),
826+ 'position': fields.integer('Position', help=MAGENTO_HELP),
827+ 'group_id': fields.integer('Group', help=MAGENTO_HELP) ,
828+ 'default_value': fields.char(
829+ 'Default Value',
830+ size=10,
831+ help=MAGENTO_HELP),
832+ 'note': fields.char(
833+ 'Note', size=200, help=MAGENTO_HELP),
834+ 'entity_type_id': fields.integer(
835+ 'Entity Type', help=MAGENTO_HELP),
836+ # boolean fields
837+ 'is_visible_in_advanced_search': fields.boolean(
838+ 'Visible in advanced search?', help=MAGENTO_HELP),
839+ 'is_visible': fields.boolean('Visible?', help=MAGENTO_HELP),
840+ 'is_visible_on_front': fields.boolean(
841+ 'Visible (front)?', help=MAGENTO_HELP),
842+ 'is_html_allowed_on_front': fields.boolean(
843+ 'Html (front)?', help=MAGENTO_HELP),
844+ 'is_wysiwyg_enabled': fields.boolean(
845+ 'Wysiwyg enabled?', help=MAGENTO_HELP),
846+ 'is_global': fields.boolean('Global?', help=MAGENTO_HELP),
847+ 'is_unique': fields.boolean('Unique?', help=MAGENTO_HELP),
848+ 'is_required': fields.boolean('Required?', help=MAGENTO_HELP),
849+ 'is_filterable': fields.boolean('Filterable?', help=MAGENTO_HELP),
850+ 'is_comparable': fields.boolean('Comparable?', help=MAGENTO_HELP),
851+ 'is_searchable': fields.boolean('Searchable ?', help=MAGENTO_HELP),
852+ 'is_configurable': fields.boolean('Configurable?', help=MAGENTO_HELP),
853+ 'is_user_defined': fields.boolean('User defined?', help=MAGENTO_HELP),
854+ 'used_for_sort_by': fields.boolean('Use for sort?', help=MAGENTO_HELP),
855+ 'is_used_for_price_rules': fields.boolean(
856+ 'Used for pricing rules?', help=MAGENTO_HELP),
857+ 'is_used_for_promo_rules': fields.boolean(
858+ 'Use for promo?', help=MAGENTO_HELP),
859+ 'used_in_product_listing': fields.boolean(
860+ 'In product listing?', help=MAGENTO_HELP),
861+ }
862+
863+ _defaults = {
864+ 'scope': 'global',
865+ 'apply_to': 'simple',
866+ 'is_visible': True,
867+ 'is_visible_on_front': True,
868+ 'is_visible_in_advanced_search': True,
869+ 'is_filterable': True,
870+ 'is_searchable': True,
871+ 'is_comparable': True,
872+ }
873+
874+ _sql_constraints = [
875+ ('magento_uniq', 'unique(attribute_code)',
876+ "Attribute with the same code already exists : must be unique"),
877+ ('openerp_uniq', 'unique(backend_id, openerp_id)',
878+ 'An attribute can not be bound to several records on the same backend.'),
879+ ]
880+
881+
882+@magento
883+class ProductAttributeAdapter(GenericAdapter):
884+ _model_name = 'magento.product.attribute'
885+ _magento_model = 'product_attribute'
886+
887+ def delete(self, id):
888+ return self._call('%s.remove'% self._magento_model,[int(id)])
889+
890+
891+@magento
892+class ProductAttributeDeleteSynchronizer(MagentoDeleteSynchronizer):
893+ _model_name = ['magento.product.attribute']
894+
895+
896+@magento
897+class ProductAttributeExport(MagentoExporter):
898+ _model_name = ['magento.product.attribute']
899+
900+ def _should_import(self):
901+ "Attributes in magento doesn't retrieve infos on dates"
902+ return False
903+
904+ def _after_export(self):
905+ """ Run the after export"""
906+ sess = self.session
907+ attribute_location_obj = sess.pool.get('attribute.location')
908+ magento_attribute_obj = sess.pool.get('magento.product.attribute')
909+ magento_attribute_set_obj = sess.pool.get('magento.attribute.set')
910+ attribute_set_adapter = self.get_connector_unit_for_model(
911+ GenericAdapter, 'magento.attribute.set')
912+ attribute_id = self.binding_record.openerp_id.id
913+ magento_attribute_id = magento_attribute_obj.browse(
914+ sess.cr, sess.uid,
915+ self.binding_record.id,context=sess.context).magento_id
916+ attribute_location_ids = attribute_location_obj.search(
917+ sess.cr, sess.uid,
918+ [['attribute_id','=',attribute_id]], context=sess.context)
919+ for attribute_location in attribute_location_ids:
920+ attribute_set_id = attribute_location_obj.browse(
921+ sess.cr, sess.uid,
922+ attribute_location, context=sess.context).attribute_set_id.id
923+ magento_attribute_set_ids = magento_attribute_set_obj.search(
924+ sess.cr, sess.uid,
925+ [['openerp_id','=',attribute_set_id]],
926+ context=sess.context)
927+ for magento_attribute_set in magento_attribute_set_ids:
928+ magento_attribute_set_id = magento_attribute_set_obj.browse(
929+ sess.cr, sess.uid,
930+ magento_attribute_set,context=sess.context).magento_id
931+ attribute_set_adapter.update(
932+ magento_attribute_set_id, magento_attribute_id)
933+
934+
935+
936+@magento
937+class ProductAttributeExportMapper(ExportMapper):
938+ _model_name = 'magento.product.attribute'
939+
940+ direct = [
941+ ('attribute_code', 'attribute_code'), # required
942+ ('frontend_input', 'frontend_input'),
943+ ('scope', 'scope'),
944+ ('is_global', 'is_global'),
945+ ('is_filterable', 'is_filterable'),
946+ ('is_comparable', 'is_comparable'),
947+ ('is_visible', 'is_visible'),
948+ ('is_searchable', 'is_searchable'),
949+ ('is_user_defined', 'is_user_defined'),
950+ ('is_configurable', 'is_configurable'),
951+ ('is_visible_on_front', 'is_visible_on_front'),
952+ ('is_used_for_price_rules', 'is_used_for_price_rules'),
953+ ('is_unique', 'is_unique'),
954+ ('is_required', 'is_required'),
955+ ('position', 'position'),
956+ ('group_id', 'group_id'),
957+ ('default_value', 'default_value'),
958+ ('is_visible_in_advanced_search', 'is_visible_in_advanced_search'),
959+ ('note', 'note'),
960+ ('entity_type_id', 'entity_type_id'),
961+ ]
962+
963+ @mapping
964+ def frontend_label(self, record):
965+ #required
966+ return {'frontend_label': [{
967+ 'store_id': 0,
968+ 'label': record.frontend_label,
969+ }]}
970+
971+
972+# Attribute option
973+class AttributeOption(orm.Model):
974+ _inherit = 'attribute.option'
975+
976+ _columns = {
977+ 'magento_bind_ids': fields.one2many(
978+ 'magento.attribute.option',
979+ 'openerp_id',
980+ string='Magento Bindings',),
981+ }
982+
983+
984+class MagentoAttributeOption(orm.Model):
985+ _name = 'magento.attribute.option'
986+ _description = ""
987+ _inherit = 'magento.binding'
988+
989+ _columns = {
990+ 'openerp_id': fields.many2one(
991+ 'attribute.option',
992+ string='Attribute option',
993+ required=True,
994+ ondelete='cascade'),
995+ 'name': fields.char(
996+ 'Name',
997+ size=64,
998+ required=True),
999+ 'is_default': fields.boolean('Is default'),
1000+ }
1001+
1002+ _defaults = {
1003+ 'is_default': True,
1004+ }
1005+
1006+ _sql_constraints = [
1007+ ('magento_uniq', 'unique(backend_id, magento_id)',
1008+ 'An attribute option with the same ID on Magento already exists.'),
1009+ ('openerp_uniq', 'unique(backend_id, openerp_id)',
1010+ 'An attribute option can not be bound to several records on the same backend.'),
1011+ ]
1012+
1013+
1014+
1015+@magento
1016+class AttributeOptionAdapter(GenericAdapter):
1017+ _model_name = 'magento.attribute.option'
1018+ _magento_model = 'oerp_product_attribute'
1019+
1020+ def create(self, data):
1021+ return self._call('%s.addOption'% self._magento_model,
1022+ [data.pop('attribute'), data])
1023+
1024+
1025+@magento
1026+class AttributeOptionDeleteSynchronizer(MagentoDeleteSynchronizer):
1027+ _model_name = ['magento.attribute.option']
1028+
1029+
1030+@magento
1031+class AttributeOptionExport(MagentoExporter):
1032+ _model_name = ['magento.attribute.option']
1033+
1034+
1035+@magento
1036+class AttributeOptionExportMapper(ExportMapper):
1037+ _model_name = 'magento.attribute.option'
1038+
1039+ direct = []
1040+
1041+ @mapping
1042+ def label(self, record):
1043+ storeview_ids = self.session.search(
1044+ 'magento.storeview',
1045+ [('backend_id', '=', self.backend_record.id)])
1046+ storeviews = self.session.browse('magento.storeview', storeview_ids)
1047+ label = []
1048+ for storeview in storeviews:
1049+ name = record.openerp_id.read(['name'], context={
1050+ 'lang': storeview.lang_id.code,
1051+ })[0]['name']
1052+ label.append({
1053+ 'store_id': [storeview.magento_id],
1054+ 'value': name
1055+ })
1056+ return {'label': label}
1057+
1058+ @mapping
1059+ def attribute(self, record):
1060+ binder = self.get_binder_for_model('magento.product.attribute')
1061+ magento_attribute_id = binder.to_backend(record.openerp_id.attribute_id.id, wrap=True)
1062+ return {'attribute': magento_attribute_id}
1063+
1064+ @mapping
1065+ def order(self, record):
1066+ #TODO FIXME
1067+ return {'order': record.openerp_id.sequence + 1 }
1068+
1069+ @mapping
1070+ def is_default(self, record):
1071+ return {'is_default': int(record.is_default)}
1072
1073=== added file 'magentoerpconnect_catalog/product_attribute_view.xml'
1074--- magentoerpconnect_catalog/product_attribute_view.xml 1970-01-01 00:00:00 +0000
1075+++ magentoerpconnect_catalog/product_attribute_view.xml 2014-07-01 22:23:08 +0000
1076@@ -0,0 +1,231 @@
1077+<?xml version="1.0" encoding="UTF-8"?>
1078+<openerp>
1079+ <data>
1080+
1081+<!-- Attribute-->
1082+<record id="attribute_attribute_form_view" model="ir.ui.view">
1083+ <field name="model">attribute.attribute</field>
1084+ <field name="inherit_id"
1085+ ref="base_custom_attributes.attribute_attribute_form_view"/>
1086+ <field name="arch" type="xml">
1087+ <xpath expr="//group" position="after">
1088+ <group colspan="4">
1089+ <separator string="Magento attributes"/>
1090+ <field name="magento_bind_ids" nolabel="1" colspan="6"
1091+ context="{
1092+ 'hide_openerp_id': True,
1093+ 'default_frontend_label': field_description,
1094+ 'default_attribute_code': name}">
1095+ <tree name="magento">
1096+ <field name="attribute_code"/>
1097+ <field name="backend_id"/>
1098+ <field name="is_required"/>
1099+ <field name="default_value" string="Default"/>
1100+ <field name="scope"/>
1101+ </tree>
1102+ </field>
1103+ </group>
1104+ </xpath>
1105+ </field>
1106+</record>
1107+
1108+<record id="attribute_set_form_view" model="ir.ui.view">
1109+ <field name="model">attribute.set</field>
1110+ <field name="inherit_id"
1111+ ref="base_custom_attributes.attribute_set_form_view"/>
1112+ <field name="arch" type="xml">
1113+ <form string="Attribute Set" position="inside">
1114+ <separator string="Magento attribute sets" colspan="6"/>
1115+ <field name="magento_bind_ids" nolabel="1" colspan="6"
1116+ context="{'default_attribute_set_name': name}">
1117+ <tree string="Attribute set" editable="top">
1118+ <field name="backend_id"/>
1119+ <field name="attribute_set_name"/>
1120+ <field name="sort_order"/>
1121+ <field name="magento_id" readonly="1"/>
1122+ </tree>
1123+ </field>
1124+ </form>
1125+ </field>
1126+</record>
1127+
1128+
1129+<menuitem id="menu_magento_product_attribute_main"
1130+ name="Attributes"
1131+ parent="magentoerpconnect.menu_magento_root"
1132+ sequence="40" />
1133+
1134+
1135+
1136+<!--Magento attribute-->
1137+<record id="magento_product_attribute_tree_view" model="ir.ui.view">
1138+ <field name="model">magento.product.attribute</field>
1139+ <field name="arch" type="xml">
1140+ <tree string="Attribute">
1141+ <field name="openerp_id"/>
1142+ <field name="attribute_code"/>
1143+ <field name="backend_id"/>
1144+ </tree>
1145+ </field>
1146+</record>
1147+
1148+<record id="magento_product_attribute_action" model="ir.actions.act_window">
1149+ <field name="type">ir.actions.act_window</field>
1150+ <field name="res_model">magento.product.attribute</field>
1151+ <field name="view_type">form</field>
1152+ <field name="name">Magento attribute</field>
1153+ <field name="view_id" ref="magento_product_attribute_tree_view"/>
1154+ <field name="help" type="html">
1155+ <p class="oe_view_nocontent_create">
1156+ Click to add an attribute.
1157+ </p>
1158+ </field>
1159+</record>
1160+
1161+<menuitem id="menu_magento_product_attribute"
1162+ name="Attributes"
1163+ action="magento_product_attribute_action"
1164+ parent="menu_magento_product_attribute_main"
1165+ sequence="10"/>
1166+
1167+
1168+<record id="magento_product_attribute_form_view" model="ir.ui.view">
1169+ <field name="model">magento.product.attribute</field>
1170+ <field name="arch" type="xml">
1171+ <form string="main" version="7.0">
1172+ <group name="main" col="4">
1173+ <field name="frontend_label"/>
1174+ <field name="attribute_code"
1175+ attributes="{'readonly': [('magento_id', '!=', False)]}"/>
1176+ <field name="backend_id" string="Backend"/>
1177+ <field name="scope"/>
1178+ <field name="frontend_input"/>
1179+ <!-- TODO FIXME <field name="openerp_id"
1180+ invisible="context.get('hide_openerp_id')"/>-->
1181+ </group>
1182+ <group name="other" col="4">
1183+ <field name="default_value"
1184+ attrs="{'invisible':[('frontend_input', 'in',
1185+ ['media_image', 'multiselect'])]}"/>
1186+ <field name="entity_type_id"/>
1187+ <field name="group_id"/>
1188+ <field name="apply_to"/>
1189+ <field name="position"/>
1190+ <field name="note"/>
1191+ </group>
1192+ <separator/>
1193+ <group name="boolean" col="6"
1194+ attrs="{'invisible':[
1195+ ('frontend_input', 'in', ['media_image'])]}">
1196+ <field name="is_required"/>
1197+ <field name="is_filterable"/>
1198+ <field name="is_visible"/>
1199+ <field name="is_global"/>
1200+ <field name="is_searchable"/>
1201+ <field name="is_visible_on_front"/>
1202+ <field name="is_unique"/>
1203+ <field name="is_configurable"/>
1204+ <field name="is_visible_in_advanced_search"/>
1205+ <field name="is_used_for_price_rules"/>
1206+ <field name="is_comparable"/>
1207+ <field name="is_user_defined"/>
1208+ <field name="is_used_for_promo_rules"/>
1209+ <field name="used_for_sort_by"/>
1210+ <field name="is_wysiwyg_enabled"/>
1211+ <field name="used_in_product_listing"/>
1212+ <span colspan="2"/>
1213+ <field name="is_html_allowed_on_front"/>
1214+ </group>
1215+ <group name="system" col="4">
1216+ <field name="sync_date" readonly="1"/>
1217+ <field name="magento_id" readonly="1"/>
1218+ </group>
1219+ </form>
1220+ </field>
1221+</record>
1222+
1223+
1224+<!--Magento attribute set-->
1225+<record id="magento_attribute_set_tree_view" model="ir.ui.view">
1226+ <field name="model">magento.attribute.set</field>
1227+ <field name="arch" type="xml">
1228+ <tree string="Attribute set" editable="top">
1229+ <field name="backend_id"/>
1230+ <field name="attribute_set_name"/>
1231+ <field name="openerp_id"/>
1232+ <field name="sync_date" readonly="1"/>
1233+ <field name="sort_order"/>
1234+ <field name="magento_id" readonly="1"/>
1235+ </tree>
1236+ </field>
1237+</record>
1238+
1239+
1240+<record id="magento_attribute_set_form_view" model="ir.ui.view">
1241+ <field name="model">magento.attribute.set</field>
1242+ <field name="arch" type="xml">
1243+ <form string="main" version="7.0">
1244+ <group col="4">
1245+ <field name="backend_id"/>
1246+ <field name="attribute_set_name"/>
1247+ <field name="sync_date"/>
1248+ <field name="openerp_id"/>
1249+ <field name="sort_order"/>
1250+ <field name="magento_id"/>
1251+ </group>
1252+ </form>
1253+ </field>
1254+</record>
1255+
1256+
1257+<record id="magento_attribute_set_action" model="ir.actions.act_window">
1258+ <field name="type">ir.actions.act_window</field>
1259+ <field name="res_model">magento.attribute.set</field>
1260+ <field name="view_type">form</field>
1261+ <field name="name">Mag. attr. set</field>
1262+ <!--<field name="view_id" ref="magento_attribute_set_tree_view"/>-->
1263+ <field name="help" type="html">
1264+ <p class="oe_view_nocontent_create">
1265+ Click to add an attribute set.
1266+ </p>
1267+ </field>
1268+</record>
1269+
1270+<menuitem id="menu_magento_attribute_set"
1271+ name="Sets"
1272+ action="magento_attribute_set_action"
1273+ parent="menu_magento_product_attribute_main"
1274+ sequence="5"/>
1275+
1276+
1277+<!--Magento attribute option-->
1278+<record id="magento_attribute_option_tree_view" model="ir.ui.view">
1279+ <field name="model">magento.attribute.option</field>
1280+ <field name="arch" type="xml">
1281+ <tree string="Attribute option">
1282+ <field name="openerp_id"/>
1283+ </tree>
1284+ </field>
1285+</record>
1286+
1287+<record id="magento_attribute_option_action" model="ir.actions.act_window">
1288+ <field name="type">ir.actions.act_window</field>
1289+ <field name="res_model">magento.attribute.option</field>
1290+ <field name="view_type">form</field>
1291+ <field name="name">Mag. attr. option</field>
1292+ <field name="view_id" ref="magento_attribute_option_tree_view"/>
1293+ <field name="help" type="html">
1294+ <p class="oe_view_nocontent_create">
1295+ Click to add an attribute option.
1296+ </p>
1297+ </field>
1298+</record>
1299+
1300+<menuitem id="menu_magento_attribute_option"
1301+ name="Options"
1302+ action="magento_attribute_option_action"
1303+ parent="menu_magento_product_attribute_main"
1304+ sequence="15"/>
1305+
1306+ </data>
1307+</openerp>
1308
1309=== added file 'magentoerpconnect_catalog/product_category.py'
1310--- magentoerpconnect_catalog/product_category.py 1970-01-01 00:00:00 +0000
1311+++ magentoerpconnect_catalog/product_category.py 2014-07-01 22:23:08 +0000
1312@@ -0,0 +1,239 @@
1313+# -*- coding: utf-8 -*-
1314+##############################################################################
1315+#
1316+# Copyright 2013
1317+# Author: Guewen Baconnier - Camptocamp SA
1318+# Augustin Cisterne-Kaasv - Elico-corp
1319+# David Béal - Akretion
1320+# This program is free software: you can redistribute it and/or modify
1321+# it under the terms of the GNU Affero General Public License as
1322+# published by the Free Software Foundation, either version 3 of the
1323+# License, or (at your option) any later version.
1324+#
1325+# This program is distributed in the hope that it will be useful,
1326+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1327+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1328+# GNU Affero General Public License for more details.
1329+#
1330+# You should have received a copy of the GNU Affero General Public License
1331+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1332+#
1333+##############################################################################
1334+
1335+from openerp.osv import fields, orm
1336+from openerp.addons.connector.unit.mapper import (mapping,
1337+ #changed_by,
1338+ ExportMapper)
1339+from openerp.addons.magentoerpconnect.unit.delete_synchronizer import (
1340+ MagentoDeleteSynchronizer)
1341+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
1342+ MagentoTranslationExporter)
1343+from openerp.addons.magentoerpconnect.backend import magento
1344+from openerp.addons.magentoerpconnect import product_category
1345+
1346+
1347+class MagentoProductCategory(orm.Model):
1348+ _inherit = 'magento.product.category'
1349+ MAGENTO_HELP = "This field is a technical / configuration field for " \
1350+ "the category on Magento. \nPlease refer to the Magento " \
1351+ "documentation for details. "
1352+
1353+ def get_custom_design(self, cr, uid, context=None):
1354+ return [
1355+ ('base/default', 'base default'),
1356+ ('default/modern', 'default modern'),
1357+ ('default/iphone', 'default iphone'),
1358+ ('default/default', 'default default'),
1359+ ('default/blank', 'default blank'),
1360+ ]
1361+
1362+ def _get_custom_design(self, cr, uid, context=None):
1363+ return self.get_custom_design(cr, uid, context=context)
1364+
1365+ def get_page_layout(self, cr, uid, context=None):
1366+ return [
1367+ ('empty', 'Empty'),
1368+ ('one_column', '1 colmun'),
1369+ ('two_columns_left', '2 columns with left bar'),
1370+ ('two_columns_right', '2 columns with right bar'),
1371+ ('three_columns', '3 columns'),
1372+ ]
1373+
1374+ def _get_page_layout(self, cr, uid, context=None):
1375+ return self.get_page_layout(cr, uid, context=context)
1376+
1377+ _columns = {
1378+ #==== General Information ====
1379+ 'thumbnail_like_image': fields.boolean('Thumbnail like main image'),
1380+ 'thumbnail_binary': fields.binary('Thumbnail'),
1381+ 'thumbnail': fields.char(
1382+ 'Thumbnail name',
1383+ size=100, help=MAGENTO_HELP),
1384+ 'image_binary': fields.binary('Image'),
1385+ 'image': fields.char(
1386+ 'Image name',
1387+ size=100, help=MAGENTO_HELP),
1388+ 'meta_title': fields.char('Title (Meta)', size=75, help=MAGENTO_HELP),
1389+ 'meta_keywords': fields.text('Meta Keywords', help=MAGENTO_HELP),
1390+ 'meta_description': fields.text('Meta Description', help=MAGENTO_HELP),
1391+ 'url_key': fields.char('URL-key', size=100, readonly="True"),
1392+ #==== Display Settings ====
1393+ 'display_mode': fields.selection([
1394+ ('PRODUCTS', 'Products Only'),
1395+ ('PAGE', 'Static Block Only'),
1396+ ('PRODUCTS_AND_PAGE', 'Static Block & Products')],
1397+ 'Display Mode', required=True, help=MAGENTO_HELP),
1398+ 'is_anchor': fields.boolean('Anchor?', help=MAGENTO_HELP),
1399+ 'use_default_available_sort_by': fields.boolean(
1400+ 'Default Config For Available Sort By', help=MAGENTO_HELP),
1401+
1402+ #TODO use custom attribut for category
1403+
1404+ #'available_sort_by': fields.sparse(
1405+ # type='many2many',
1406+ # relation='magerp.product_category_attribute_options',
1407+ # string='Available Product Listing (Sort By)',
1408+ # serialization_field='magerp_fields',
1409+ # domain="[('attribute_name', '=', 'sort_by'), ('value', '!=','None')]",
1410+ # help=MAGENTO_HELP),
1411+ #filter_price_range landing_page ?????????????
1412+ 'default_sort_by': fields.selection([
1413+ ('_', 'Config settings'), #?????????????
1414+ ('position', 'Best Value'),
1415+ ('name', 'Name'),
1416+ ('price', 'Price')],
1417+ 'Default sort by', required=True, help=MAGENTO_HELP),
1418+
1419+ #==== Custom Design ====
1420+ 'custom_apply_to_products': fields.boolean(
1421+ 'Apply to products', help=MAGENTO_HELP),
1422+ 'custom_design': fields.selection(
1423+ _get_custom_design,
1424+ string='Custom design',
1425+ help=MAGENTO_HELP),
1426+ 'custom_design_from': fields.date(
1427+ 'Active from', help=MAGENTO_HELP),
1428+ 'custom_design_to': fields.date(
1429+ 'Active to', help=MAGENTO_HELP),
1430+ 'custom_layout_update': fields.text(
1431+ 'Layout update', help=MAGENTO_HELP),
1432+ 'page_layout': fields.selection(
1433+ _get_page_layout,
1434+ 'Page layout', help=MAGENTO_HELP),
1435+ }
1436+
1437+ _defaults = {
1438+ 'thumbnail_like_image': True,
1439+ 'display_mode': 'PRODUCTS',
1440+ 'use_default_available_sort_by': True,
1441+ 'default_sort_by': '_',
1442+ 'is_anchor': True,
1443+ 'include_in_menu': True,
1444+ }
1445+
1446+ _sql_constraints = [
1447+ ('magento_img_uniq', 'unique(backend_id, image)',
1448+ "'Image file name' already exists : must be unique"),
1449+ ('magento_thumb_uniq', 'unique(backend_id, thumbnail)',
1450+ "'thumbnail name' already exists : must be unique"),
1451+ ]
1452+
1453+
1454+@magento
1455+class ProductCategoryDeleteSynchronizer(MagentoDeleteSynchronizer):
1456+ """ Product category deleter for Magento """
1457+ _model_name = ['magento.product.category']
1458+
1459+
1460+@magento
1461+class ProductCategoryExport(MagentoTranslationExporter):
1462+ _model_name = ['magento.product.category']
1463+
1464+ def _export_dependencies(self):
1465+ """Export parent of the category"""
1466+ #TODO FIXME
1467+ return True
1468+ env = self.environment
1469+ record = self.binding_record
1470+ binder = self.get_binder_for_model()
1471+ if record.magento_parent_id:
1472+ mag_parent_id = record.magento_parent_id.id
1473+ if binder.to_backend(mag_parent_id) is None:
1474+ exporter = env.get_connector_unit(ProductCategoryExporter)
1475+ exporter.run(mag_parent_id)
1476+ elif record.openerp_id.parent_id:
1477+ parent = record.openerp_id.parent_id
1478+ if binder.to_backend(parent.id, wrap=True) is None:
1479+ exporter = env.get_connector_unit(ProductCategoryExporter)
1480+ exporter.run(parent.magento_parent_id.id)
1481+
1482+@magento
1483+class ProductCategoryExportMapper(ExportMapper):
1484+ _model_name = 'magento.product.category'
1485+
1486+ direct = [('description', 'description'),
1487+ #change that to mapping top level category has no name
1488+ ('name', 'name'),
1489+ ('meta_title', 'meta_title'),
1490+ ('meta_keywords', 'meta_keywords'),
1491+ ('meta_description', 'meta_description'),
1492+ ('display_mode', 'display_mode'),
1493+ ('is_anchor', 'is_anchor'),
1494+ ('use_default_available_sort_by', 'use_default_available_sort_by'),
1495+ ('custom_design', 'custom_design'),
1496+ ('custom_design_from', 'custom_design_from'),
1497+ ('custom_design_to', 'custom_design_to'),
1498+ ('custom_layout_update', 'custom_layout_update'),
1499+ ('page_layout', 'page_layout'),
1500+ ]
1501+
1502+ @mapping
1503+ def sort(self, record):
1504+ return {'default_sort_by':'price', 'available_sort_by': 'price'}
1505+
1506+ @mapping
1507+ def parent(self, record):
1508+ """ Magento root category's Id equals 1 """
1509+ if not record.magento_parent_id:
1510+ openerp_parent = record.parent_id
1511+ binder = self.get_binder_for_model('magento.product.category')
1512+ parent_id = binder.to_backend(openerp_parent.id, wrap=True)
1513+ else:
1514+ parent_id = record.magento_parent_id.magento_id
1515+ if not parent_id:
1516+ parent_id = 1
1517+ return {'parent_id':parent_id}
1518+
1519+ @mapping
1520+ def active(self, record):
1521+ is_active = record['is_active']
1522+ if not is_active:
1523+ is_active = 0
1524+ return {'is_active':is_active}
1525+
1526+ @mapping
1527+ def menu(self, record):
1528+ include_in_menu = record['include_in_menu']
1529+ if not include_in_menu:
1530+ include_in_menu = 0
1531+ return {'include_in_menu':include_in_menu}
1532+
1533+ @mapping
1534+ def image(self, record):
1535+ res = {}
1536+ if record.image_binary:
1537+ res.update({'image': record.image,
1538+ 'image_binary': record.image_binary})
1539+ if record.thumbnail_like_image == True :
1540+ res.update({'thumbnail': record.image,})
1541+ elif record.thumbnail:
1542+ res.update({'thumbnail': record.thumbnail,
1543+ 'thumbnail_binary': record.thumbnail_binary})
1544+ return res
1545+
1546+@magento(replacing=product_category.ProductCategoryImportMapper)
1547+class ProductCategoryImportMapper(product_category.ProductCategoryImportMapper):
1548+ _model_name = 'magento.product.category'
1549+ direct = product_category.ProductCategoryImportMapper.direct + []
1550+
1551+ #TODO add mapping for default_sort_by and available_sort_by
1552
1553=== added file 'magentoerpconnect_catalog/product_image.py'
1554--- magentoerpconnect_catalog/product_image.py 1970-01-01 00:00:00 +0000
1555+++ magentoerpconnect_catalog/product_image.py 2014-07-01 22:23:08 +0000
1556@@ -0,0 +1,336 @@
1557+# -*- coding: utf-8 -*-
1558+##############################################################################
1559+#
1560+# Copyright 2013
1561+# Author: Guewen Baconnier - Camptocamp
1562+# David Béal - Akretion
1563+# Sébastien Beau - Akretion
1564+# This program is free software: you can redistribute it and/or modify
1565+# it under the terms of the GNU Affero General Public License as
1566+# published by the Free Software Foundation, either version 3 of the
1567+# License, or (at your option) any later version.
1568+#
1569+# This program is distributed in the hope that it will be useful,
1570+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1571+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1572+# GNU Affero General Public License for more details.
1573+#
1574+# You should have received a copy of the GNU Affero General Public License
1575+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1576+#
1577+##############################################################################
1578+
1579+import mimetypes
1580+
1581+from openerp.osv import fields, orm
1582+#from openerp.tools.translate import _
1583+#from openerp.osv.osv import except_osv
1584+from openerp.addons.connector.unit.mapper import (mapping,
1585+ ExportMapper)
1586+from openerp.addons.magentoerpconnect.unit.binder import MagentoModelBinder
1587+from openerp.addons.magentoerpconnect.unit.delete_synchronizer import (
1588+ MagentoDeleteSynchronizer)
1589+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
1590+ MagentoExporter)
1591+from openerp.addons.magentoerpconnect.backend import magento
1592+from openerp.addons.magentoerpconnect.unit.backend_adapter import GenericAdapter
1593+
1594+
1595+MAGENTO_HELP = "This field is a technical / configuration field for " \
1596+ "the attribute on Magento. \nPlease refer to the Magento " \
1597+ "documentation for details. "
1598+
1599+
1600+@magento(replacing=MagentoModelBinder)
1601+class MagentoImageBinder(MagentoModelBinder):
1602+ _model_name = [
1603+ 'magento.product.image',
1604+ ]
1605+
1606+
1607+class MagentoProductProduct(orm.Model):
1608+ _inherit = 'magento.product.product'
1609+
1610+ def _get_images(self, cr, uid, ids, field_names, arg, context=None):
1611+ res={}
1612+ for prd in self.browse(cr, uid, ids, context=context):
1613+ img_ids = self.pool['magento.product.image'].search(
1614+ cr, uid, [
1615+ ('product_id', '=', prd.openerp_id.id),
1616+ ('backend_id', '=', prd.backend_id.id), ], context=context)
1617+ res[prd.id] = img_ids
1618+ return res
1619+
1620+ def copy(self, cr, uid, id, default=None, context=None):
1621+ #take care about duplicate on one2many and function fields
1622+ #https://bugs.launchpad.net/openobject-server/+bug/705364
1623+ if default is None:
1624+ default = {}
1625+ default['magento_product_image_ids'] = None
1626+ return super(MagentoProductProduct, self).copy(cr, uid, id,
1627+ default=default,
1628+ context=context)
1629+ _columns = {
1630+ 'magento_product_image_ids': fields.function(
1631+ _get_images,
1632+ type='one2many',
1633+ relation='magento.product.image',
1634+ string='Magento product images'),
1635+ 'magento_product_storeview_ids': fields.one2many(
1636+ 'magento.product.storeview',
1637+ 'magento_product_id',
1638+ string='Magento storeview',),
1639+ }
1640+
1641+ def open_images(self, cr, uid, ids, context=None):
1642+ view_id = self.pool['ir.model.data'].get_object_reference(
1643+ cr, uid, 'magentoerpconnect_catalog',
1644+ 'magento_product_img_form_view')[1]
1645+ return {
1646+ 'name': 'Product images',
1647+ 'view_type': 'form',
1648+ 'view_mode': 'form',
1649+ 'view_id': view_id,
1650+ 'res_model': self._name,
1651+ 'context': context,
1652+ 'type': 'ir.actions.act_window',
1653+ 'res_id': ids and ids[0] or False,
1654+ }
1655+
1656+
1657+class ProductImage(orm.Model):
1658+ _inherit = 'product.image'
1659+
1660+ _columns = {
1661+ 'magento_bind_ids': fields.one2many(
1662+ 'magento.product.image',
1663+ 'openerp_id',
1664+ string='Magento bindings',),
1665+ }
1666+
1667+
1668+class MagentoProductImage(orm.Model):
1669+ _name = 'magento.product.image'
1670+ _description = "Magento product image"
1671+ _inherit = 'magento.binding'
1672+ _inherits = {'product.image': 'openerp_id'}
1673+
1674+ _columns = {
1675+ 'openerp_id': fields.many2one(
1676+ 'product.image',
1677+ required=True,
1678+ ondelete="cascade",
1679+ string='Image'),
1680+ }
1681+
1682+ def _get_backend(self, cr, uid, context=None):
1683+ backend_id = False
1684+ backend_m = self.pool['magento.backend']
1685+ back_ids = backend_m.search(cr, uid, [], context=context)
1686+ if back_ids:
1687+ backend_id = backend_m.browse(
1688+ cr, uid, back_ids, context=context)[0].id
1689+ return backend_id
1690+
1691+ _defaults = {
1692+ 'backend_id': _get_backend,
1693+ }
1694+
1695+ _sql_constraints = [
1696+ ('magento_uniq', 'unique(backend_id, magento_id)',
1697+ "An image with the same ID on Magento already exists")
1698+ ]
1699+
1700+@magento
1701+class ProductImageDeleteSynchronizer(MagentoDeleteSynchronizer):
1702+ _model_name = ['magento.product.image']
1703+
1704+
1705+@magento
1706+class ProductImageExport(MagentoExporter):
1707+ _model_name = ['magento.product.image']
1708+
1709+ def _should_import(self):
1710+ "Images in magento doesn't retrieve infos on dates"
1711+ return False
1712+
1713+
1714+@magento
1715+class ProductImageExportMapper(ExportMapper):
1716+ _model_name = 'magento.product.image'
1717+
1718+ direct = [
1719+ ('label', 'label'),
1720+ ('sequence', 'position'),
1721+ ]
1722+
1723+ @mapping
1724+ def product(self, record):
1725+ binder = self.get_binder_for_model('magento.product.product')
1726+ external_product_id = binder.to_backend(
1727+ record.openerp_id.product_id.id, True)
1728+ return {'product': str(external_product_id)}
1729+
1730+ @mapping
1731+ def identifierType(self, record):
1732+ return {'identifierType': 'ID'}
1733+
1734+ @mapping
1735+ def types(self, record):
1736+ return {'types': ['image', 'small_image', 'thumbnail']}
1737+ # return {'types':
1738+ # [x for x in ['image', 'small_image', 'thumbnail'] if record[x]]
1739+ # }
1740+
1741+ @mapping
1742+ def file(self, record):
1743+ return {'file': {
1744+ 'mime': mimetypes.guess_type(record.name + record.extension)[0],
1745+ 'name': record.label,
1746+ 'content': self.session.pool['image.image'].get_image(
1747+ self.session.cr, self.session.uid,
1748+ record.openerp_id.image_id.id,
1749+ context=self.session.context),
1750+ }
1751+ }
1752+
1753+
1754+@magento
1755+class ProductImageAdapter(GenericAdapter):
1756+ _model_name = 'magento.product.image'
1757+ _magento_model = 'catalog_product_attribute_media'
1758+
1759+ def create(self, data, storeview_id=None):
1760+ #import pdb;pdb.set_trace()
1761+ print data
1762+ return self._call('%s.create' % self._magento_model,
1763+ [data.pop('product'), data, storeview_id])
1764+
1765+ def write(self, id, data):
1766+ """ Update records on the external system
1767+ changes with GenericAdapter : prevent 'int(id)' """
1768+ return self._call('%s.update' % self._magento_model,
1769+ [data.pop('product'), id, data])
1770+
1771+ def delete(self, id):
1772+ """ Delete a record on the external system """
1773+ image_id, external_product_id = id
1774+ return self._call('%s.remove' % self._magento_model,
1775+ [external_product_id, image_id])
1776+
1777+
1778+class MagentoProductStoreview(orm.Model):
1779+ _name = 'magento.product.storeview'
1780+ _description = "Magento product storeview"
1781+ _inherits = {'magento.product.product': 'magento_product_id'}
1782+
1783+ _columns = {
1784+ 'magento_product_id' : fields.many2one(
1785+ 'magento.product.product',
1786+ required=True,
1787+ ondelete="cascade",
1788+ string='Image'),
1789+ 'storeview_id': fields.many2one(
1790+ 'magento.storeview',
1791+ required=True,
1792+ string='Storeview'),
1793+ 'image': fields.many2one(
1794+ 'magento.product.image',
1795+ 'Base image',
1796+ help=MAGENTO_HELP),
1797+ 'small_image': fields.many2one(
1798+ 'magento.product.image',
1799+ 'Small image',
1800+ help=MAGENTO_HELP),
1801+ 'thumbnail': fields.many2one(
1802+ 'magento.product.image',
1803+ 'Thumbnail',
1804+ domain="[('backend_id', '=', 'backend_id')]",
1805+ help=MAGENTO_HELP),
1806+ 'exclude_ids': fields.many2many(
1807+ 'magento.product.image', 'product_id',
1808+ string='Exclude',
1809+ help=MAGENTO_HELP),
1810+ }
1811+
1812+
1813+@magento
1814+class ProductStoreviewExport(MagentoExporter):
1815+ _model_name = ['magento.product.storeview']
1816+
1817+# TODO
1818+# def _export_dependencies(self):
1819+
1820+ def _should_import(self):
1821+ "Images in magento doesn't retrieve infos on dates"
1822+ return False
1823+ #
1824+ #def _run(self, fields=None):
1825+ # """ Flow of the synchronization, implemented in inherited classes"""
1826+ # assert self.binding_id
1827+ # assert self.binding_record
1828+ #
1829+ #
1830+ # if not self.magento_id:
1831+ # fields = None # should be created with all the fields
1832+ #
1833+ # if self._has_to_skip():
1834+ # return
1835+ #
1836+ # export the missing linked resources
1837+ # self._export_dependencies()
1838+ #
1839+ # self._map_data(fields=fields)
1840+ #
1841+ # if self.magento_id:
1842+ # record = self.mapper.data
1843+ # if not record:
1844+ # return _('Nothing to export.')
1845+ # special check on data before export
1846+ # self._validate_data(record)
1847+ # self._update(record)
1848+ # else:
1849+ # record = self.mapper.data_for_create
1850+ # if not record:
1851+ # return _('Nothing to export.')
1852+ # special check on data before export
1853+ # self._validate_data(record)
1854+ # self.magento_id = self._create(record)
1855+ # return _('Record exported with ID %s on Magento.') % self.magento_id
1856+
1857+
1858+@magento
1859+class ProductStoreviewExportMapper(ExportMapper):
1860+ _model_name = 'magento.product.storeview'
1861+
1862+ direct = [
1863+ ('label', 'label'),
1864+ ('sequence', 'position'), ]
1865+
1866+ @mapping
1867+ def product(self, record):
1868+ return {'product': ''}
1869+
1870+
1871+#
1872+#@magento
1873+#class ProductStoreviewAdapter(GenericAdapter):
1874+# _model_name = 'magento.product.storeview'
1875+#
1876+# def update_image(self, product_id, data, storeview_id=None):
1877+# #data = {'small', image_id, 'medium':image_id,...}
1878+#
1879+# return self._call('catalog_product_attribute_media.update',
1880+# [product_id, image_id, data, storeview_id])
1881+#
1882+
1883+
1884+
1885+#
1886+#@job
1887+#def export_record(session, model_name, binding_id, fields=None):
1888+# """ Export a record on Magento """
1889+# record = session.browse(model_name, binding_id)
1890+# env = get_environment(session, model_name, record.backend_id.id)
1891+# exporter = env.get_connector_unit(MagentoExporter)
1892+# return exporter.run(binding_id, fields=fields)
1893
1894=== added file 'magentoerpconnect_catalog/product_image_view.xml'
1895--- magentoerpconnect_catalog/product_image_view.xml 1970-01-01 00:00:00 +0000
1896+++ magentoerpconnect_catalog/product_image_view.xml 2014-07-01 22:23:08 +0000
1897@@ -0,0 +1,79 @@
1898+<?xml version="1.0" encoding="UTF-8"?>
1899+<openerp>
1900+ <data>
1901+
1902+ <!--
1903+<record id="view_product_image_form" model="ir.ui.view">
1904+ <field name="model">product.image</field>
1905+ <field name="inherit_id"
1906+ ref="product_image.view_product_image_form" />
1907+ <field name="arch" type="xml" >
1908+ <xpath expr="//group[@name='link']" position="after">
1909+ <field name="product_id" invisible="1"/>
1910+ <group name="magento">
1911+ <field name="magento_bind_ids" nolabel="1">
1912+ <tree name="magento_img_storeview"
1913+ editable="bottom">
1914+ <field name="backend_id" string="Backend"/>
1915+ </tree>
1916+ </field>
1917+ </group>
1918+ </xpath>
1919+ </field>
1920+</record>
1921+
1922+<record id="magento_product_img_form_view" model="ir.ui.view">
1923+ <field name="model">magento.product.product</field>
1924+ <field name="priority">30</field>
1925+ <field name="arch" type="xml">
1926+ <form version="7.0" string="_">
1927+ <separator string="Magento images"/>
1928+ <field name="backend_id" invisible="True"/>
1929+ <field name="openerp_id" invisible="True"/>
1930+ <field name="magento_product_image_ids">
1931+ <tree editable="bottom">
1932+ <field name="product_id"/>
1933+ <field name="label"/>
1934+ <field name="sequence"/>
1935+ <field name="name"/>
1936+ <field name="extension" string="ext."/>
1937+ <field name="magento_id"/>
1938+ <field name="file" widget="image"/>
1939+ </tree>
1940+ </field>
1941+ <separator string="Magento storeviews"/>
1942+ <field name="magento_product_storeview_ids">
1943+ <tree editable="bottom">
1944+ <field name="image"
1945+ domain="[('backend_id', '=', parent.backend_id), ('product_id', '=', parent.openerp_id)]"/>
1946+ <field name="small_image"
1947+ domain="[('backend_id', '=', parent.backend_id), ('product_id', '=', parent.openerp_id)]"/>
1948+ <field name="thumbnail"
1949+ domain="[('backend_id', '=', parent.backend_id), ('product_id', '=', parent.openerp_id)]"/>
1950+ <field name="storeview_id"
1951+ domain="[('backend_id', '=', parent.backend_id)]"/>
1952+ <field name="exclude_ids"
1953+ widget="many2many_tags"
1954+ domain="[('backend_id', '=', parent.backend_id), ('product_id', '=', parent.openerp_id)]"/>
1955+ </tree>
1956+ </field>
1957+ </form>
1958+ </field>
1959+</record>
1960+
1961+<record id="product_normal_form_view" model="ir.ui.view">
1962+ <field name="model">product.product</field>
1963+ <field name="inherit_id"
1964+ ref="magentoerpconnect.product_normal_form_view"/>
1965+ <field name="arch" type="xml">
1966+ <xpath expr="//field[@name='magento_bind_ids']/tree/field[@name='backend_id']"
1967+ position="before">
1968+ <button string="Images management" type="object"
1969+ name="open_images" icon="stock_gantt"/>
1970+ </xpath>
1971+ </field>
1972+</record>
1973+
1974+-->
1975+ </data>
1976+</openerp>
1977
1978=== added file 'magentoerpconnect_catalog/product_view.xml'
1979--- magentoerpconnect_catalog/product_view.xml 1970-01-01 00:00:00 +0000
1980+++ magentoerpconnect_catalog/product_view.xml 2014-07-01 22:23:08 +0000
1981@@ -0,0 +1,78 @@
1982+<?xml version="1.0" encoding="UTF-8"?>
1983+<openerp>
1984+ <data>
1985+
1986+<record id="view_magento_product_category_form" model="ir.ui.view">
1987+ <field name="name">magento.product.category.form</field>
1988+ <field name="model">magento.product.category</field>
1989+ <field name="inherit_id" ref="magentoerpconnect.view_magento_product_category_form" />
1990+ <field name="arch" type="xml" >
1991+ <xpath expr="/form/group[1]" position="replace">
1992+ <group col="4">
1993+ <field name="backend_id" colspan="2"/>
1994+ <field name="magento_id" readonly="1" colspan="2"/>
1995+ </group>
1996+ </xpath>
1997+ <xpath expr="/form" position="inside">
1998+ <group string="General informations" col="4">
1999+ <group colspan="2">
2000+ <field name="is_active"/>
2001+ <field name="include_in_menu"/>
2002+ <field name="magento_parent_id"/>
2003+ </group>
2004+ <group colspan="2">
2005+ <label for="magento_child_ids" class="oe_inline"/>
2006+ <field name="magento_child_ids" colspan="2" nolabel="1"/>
2007+ </group>
2008+ <field name="description" colspan="4"/>
2009+ <group colspan="2">
2010+ <field name="image"/>
2011+ <field name="meta_title"/>
2012+ <field name="meta_description"/>
2013+ <field name="meta_keywords"/>
2014+ <field name="thumbnail_like_image"/>
2015+ <group name="thumbnail_grp" colspan="2"
2016+ attrs="{'invisible': [('thumbnail_like_image', '=', True)]}">
2017+
2018+ <field name="thumbnail"/>
2019+ <field name="thumbnail_binary" widget="image"
2020+ filename="thumbnail" nolabel="1"/>
2021+ </group>
2022+ </group>
2023+ <group colspan="2">
2024+ <field name="image_binary" widget="image"
2025+ filename="image" nolabel="1"/>
2026+ </group>
2027+ </group>
2028+ <group string="Display settings" col="4">
2029+ <field name="display_mode"/>
2030+ <field name="is_anchor"/>
2031+ <field name="use_default_available_sort_by"/>
2032+ </group>
2033+ <group string="Custom design" col="4">
2034+ <field name="custom_design"/>
2035+ <field name="custom_design_from"/>
2036+ <field name="custom_apply_to_products"/>
2037+ <field name="custom_design_to"/>
2038+ <field name="page_layout"/><br/><br/>
2039+ <field name="custom_layout_update" colspan="4"/>
2040+ </group>
2041+ </xpath>
2042+ </field>
2043+</record>
2044+
2045+
2046+<record id="product_product_form_view_set_button" model="ir.ui.view">
2047+ <field name="model">product.product</field>
2048+ <field name="inherit_id"
2049+ ref="product_custom_attributes.product_product_form_view_set_button"/>
2050+ <field name="arch" type="xml">
2051+ <field name="attribute_set_id" position="attributes">
2052+ <attribute name="attrs">{'required': [('magento_bind_ids','!=',[])]}</attribute>
2053+ </field>
2054+ </field>
2055+</record>
2056+
2057+
2058+ </data>
2059+</openerp>
2060\ No newline at end of file
2061
2062=== added directory 'magentoerpconnect_catalog/tests'
2063=== added file 'magentoerpconnect_catalog/tests/__init__.py'
2064--- magentoerpconnect_catalog/tests/__init__.py 1970-01-01 00:00:00 +0000
2065+++ magentoerpconnect_catalog/tests/__init__.py 2014-07-01 22:23:08 +0000
2066@@ -0,0 +1,29 @@
2067+# -*- coding: utf-8 -*-
2068+##############################################################################
2069+#
2070+# Author: Guewen Baconnier
2071+# Copyright 2012 Camptocamp SA
2072+#
2073+# This program is free software: you can redistribute it and/or modify
2074+# it under the terms of the GNU Affero General Public License as
2075+# published by the Free Software Foundation, either version 3 of the
2076+# License, or (at your option) any later version.
2077+#
2078+# This program is distributed in the hope that it will be useful,
2079+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2080+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2081+# GNU Affero General Public License for more details.
2082+#
2083+# You should have received a copy of the GNU Affero General Public License
2084+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2085+#
2086+##############################################################################
2087+
2088+import test_attribute_synchronisation
2089+
2090+fast_suite = [
2091+]
2092+
2093+checks = [
2094+ test_attribute_synchronisation,
2095+]
2096
2097=== added file 'magentoerpconnect_catalog/tests/test_attribute_synchronisation.py'
2098--- magentoerpconnect_catalog/tests/test_attribute_synchronisation.py 1970-01-01 00:00:00 +0000
2099+++ magentoerpconnect_catalog/tests/test_attribute_synchronisation.py 2014-07-01 22:23:08 +0000
2100@@ -0,0 +1,109 @@
2101+# -*- coding: utf-8 -*-
2102+###############################################################################
2103+#
2104+# Module for OpenERP
2105+# Copyright (C) 2014 Akretion (http://www.akretion.com).
2106+# @author Sébastien BEAU <sebastien.beau@akretion.com>
2107+#
2108+# This program is free software: you can redistribute it and/or modify
2109+# it under the terms of the GNU Affero General Public License as
2110+# published by the Free Software Foundation, either version 3 of the
2111+# License, or (at your option) any later version.
2112+#
2113+# This program is distributed in the hope that it will be useful,
2114+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2115+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2116+# GNU Affero General Public License for more details.
2117+#
2118+# You should have received a copy of the GNU Affero General Public License
2119+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2120+#
2121+###############################################################################
2122+
2123+from openerp.addons.magentoerpconnect.tests.test_synchronization import (
2124+ SetUpMagentoSynchronized)
2125+from openerp.addons.magentoerpconnect.unit.import_synchronizer import (
2126+ import_record)
2127+from openerp.addons.magentoerpconnect.tests.common import (
2128+ mock_api,
2129+ mock_urlopen_image)
2130+from openerp.addons.magentoerpconnect.unit.export_synchronizer import (
2131+ export_record)
2132+from .test_data import magento_attribute_responses
2133+
2134+
2135+class TestImportAttribute(SetUpMagentoSynchronized):
2136+ """ Test the import from a Magento Mock.
2137+
2138+ The data returned by Magento are those created for the
2139+ demo version of Magento on a standard 1.7 version.
2140+ """
2141+
2142+ def test_10_import_attribute_set(self):
2143+ """ Import the default attribute set"""
2144+ with mock_api(magento_attribute_responses):
2145+ import_record(self.session, 'magento.attribute.set',
2146+ self.backend_id, '9')
2147+
2148+ mag_attr_obj = self.registry('magento.attribute.set')
2149+ cr, uid = self.cr, self.uid
2150+ mag_attr_set_ids = mag_attr_obj.search(cr, uid, [
2151+ ('magento_id', '=', '9'),
2152+ ('backend_id', '=', self.backend_id),
2153+ ])
2154+ self.assertEqual(len(mag_attr_set_ids), 1)
2155+ mag_attr_set = mag_attr_obj.browse(cr, uid, mag_attr_set_ids[0])
2156+ self.assertEqual(mag_attr_set.attribute_set_name, 'Default')
2157+
2158+
2159+class TestExportAttribute(SetUpMagentoSynchronized):
2160+ """ Test the export from a Magento Mock.
2161+
2162+ The data returned by Magento are those created for the
2163+ demo version of Magento on a standard 1.7 version.
2164+ """
2165+ def setUp(self):
2166+ super(TestExportAttribute, self).setUp()
2167+ with mock_api(magento_attribute_responses):
2168+ import_record(self.session, 'magento.attribute.set',
2169+ self.backend_id, '9')
2170+
2171+ mag_attr_model = self.registry('magento.attribute.set')
2172+ cr, uid = self.cr, self.uid
2173+ mag_attr_set_ids = mag_attr_model.search(cr, uid, [
2174+ ('magento_id', '=', '9'),
2175+ ('backend_id', '=', self.backend_id),
2176+ ])
2177+ self.registry('magento.backend').write(cr, uid, self.backend_id, {
2178+ 'attribute_set_tpl_id': mag_attr_set_ids[0]
2179+ })
2180+
2181+ def test_20_export_attribute_set(self):
2182+ """ Test export of attribute set"""
2183+ response = {
2184+ 'product_attribute_set.create': 69,
2185+ }
2186+ cr = self.cr
2187+ uid = self.uid
2188+ with mock_api(response, key_func=lambda m, a: m) as calls_done:
2189+ mag_attr_set_model = self.registry('magento.attribute.set')
2190+ attr_set_model = self.registry('attribute.set')
2191+
2192+ attr_set_id = attr_set_model.create(cr, uid, {
2193+ 'name': 'Test Export Attribute',
2194+ }, {'force_model': 'product.template'})
2195+ mag_attr_set_id = mag_attr_set_model.create(cr, uid, {
2196+ 'attribute_set_name': 'Test Export Attribute',
2197+ 'openerp_id': attr_set_id,
2198+ 'backend_id': self.backend_id,
2199+ })
2200+
2201+ export_record(self.session, 'magento.attribute.set',
2202+ mag_attr_set_id)
2203+
2204+ self.assertEqual(len(calls_done), 1)
2205+
2206+ method, (data, skeleton_id) = calls_done[0]
2207+ print data
2208+ self.assertEqual(method, 'product_attribute_set.create')
2209+ self.assertEqual(skeleton_id, '9')
2210
2211=== added file 'magentoerpconnect_catalog/tests/test_data.py'
2212--- magentoerpconnect_catalog/tests/test_data.py 1970-01-01 00:00:00 +0000
2213+++ magentoerpconnect_catalog/tests/test_data.py 2014-07-01 22:23:08 +0000
2214@@ -0,0 +1,99 @@
2215+# -*- coding: utf-8 -*-
2216+###############################################################################
2217+#
2218+# Module for OpenERP
2219+# Copyright (C) 2014 Akretion (http://www.akretion.com).
2220+# @author Sébastien BEAU <sebastien.beau@akretion.com>
2221+#
2222+# This program is free software: you can redistribute it and/or modify
2223+# it under the terms of the GNU Affero General Public License as
2224+# published by the Free Software Foundation, either version 3 of the
2225+# License, or (at your option) any later version.
2226+#
2227+# This program is distributed in the hope that it will be useful,
2228+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2229+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2230+# GNU Affero General Public License for more details.
2231+#
2232+# You should have received a copy of the GNU Affero General Public License
2233+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2234+#
2235+###############################################################################
2236+
2237+
2238+magento_attribute_responses = \
2239+{('ol_catalog_product_attributeset.info', (9,)): {'attribute_set_id': '9',
2240+ 'attribute_set_name': 'Default',
2241+ 'entity_type_id': '10',
2242+ 'sort_order': '16'},
2243+ ('ol_catalog_product_attributeset.info', (38,)): {'attribute_set_id': '38',
2244+ 'attribute_set_name': 'Cell Phones',
2245+ 'entity_type_id': '10',
2246+ 'sort_order': '0'},
2247+ ('ol_catalog_product_attributeset.info', (39,)): {'attribute_set_id': '39',
2248+ 'attribute_set_name': 'Computer',
2249+ 'entity_type_id': '10',
2250+ 'sort_order': '0'},
2251+ ('ol_catalog_product_attributeset.info', (40,)): {'attribute_set_id': '40',
2252+ 'attribute_set_name': 'Shoes',
2253+ 'entity_type_id': '10',
2254+ 'sort_order': '0'},
2255+ ('ol_catalog_product_attributeset.info', (41,)): {'attribute_set_id': '41',
2256+ 'attribute_set_name': 'Shirts T',
2257+ 'entity_type_id': '10',
2258+ 'sort_order': '0'},
2259+ ('ol_catalog_product_attributeset.info', (42,)): {'attribute_set_id': '42',
2260+ 'attribute_set_name': 'Furniture',
2261+ 'entity_type_id': '10',
2262+ 'sort_order': '0'},
2263+ ('ol_catalog_product_attributeset.info', (44,)): {'attribute_set_id': '44',
2264+ 'attribute_set_name': 'Cameras',
2265+ 'entity_type_id': '10',
2266+ 'sort_order': '0'},
2267+ ('ol_catalog_product_attributeset.info', (45,)): {'attribute_set_id': '45',
2268+ 'attribute_set_name': 'Shirts Other',
2269+ 'entity_type_id': '10',
2270+ 'sort_order': '0'},
2271+ ('ol_catalog_product_attributeset.info', (46,)): {'attribute_set_id': '46',
2272+ 'attribute_set_name': 'Shirts (General)',
2273+ 'entity_type_id': '10',
2274+ 'sort_order': '0'},
2275+ ('ol_catalog_product_attributeset.info', (58,)): {'attribute_set_id': '58',
2276+ 'attribute_set_name': 'RAM',
2277+ 'entity_type_id': '10',
2278+ 'sort_order': '0'},
2279+ ('ol_catalog_product_attributeset.info', (59,)): {'attribute_set_id': '59',
2280+ 'attribute_set_name': 'Warranties',
2281+ 'entity_type_id': '10',
2282+ 'sort_order': '0'},
2283+ ('ol_catalog_product_attributeset.info', (60,)): {'attribute_set_id': '60',
2284+ 'attribute_set_name': 'CPU',
2285+ 'entity_type_id': '10',
2286+ 'sort_order': '0'},
2287+ ('ol_catalog_product_attributeset.info', (61,)): {'attribute_set_id': '61',
2288+ 'attribute_set_name': 'Monitors',
2289+ 'entity_type_id': '10',
2290+ 'sort_order': '0'},
2291+ ('ol_catalog_product_attributeset.info', (62,)): {'attribute_set_id': '62',
2292+ 'attribute_set_name': 'Hard Drive',
2293+ 'entity_type_id': '10',
2294+ 'sort_order': '0'},
2295+ ('ol_catalog_product_attributeset.info', (63,)): {'attribute_set_id': '63',
2296+ 'attribute_set_name': 't-shirt-open',
2297+ 'entity_type_id': '10',
2298+ 'sort_order': '0'},
2299+ ('ol_catalog_product_attributeset.search', ()): ['44',
2300+ '38',
2301+ '39',
2302+ '60',
2303+ '9',
2304+ '42',
2305+ '62',
2306+ '61',
2307+ '58',
2308+ '46',
2309+ '45',
2310+ '41',
2311+ '40',
2312+ '63',
2313+ '59']}