forked from OCA/product-attribute
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathproduct.py
256 lines (224 loc) · 10.1 KB
/
product.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# coding: utf-8
# © 2015 David BEAL @ Akretion
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, _
from openerp.osv import orm
from openerp.exceptions import Warning as UserError
from lxml import etree
PROFILE_MENU = (_("Sales > Configuration \n> Product Categories and Attributes"
"\n> Product Profiles"))
# Prefix name of profile fields setting a default value,
# not an immutable value according to profile
PROF_DEFAULT_STR = 'profile_default_'
LEN_DEF_STR = len(PROF_DEFAULT_STR)
def format_except_message(error, field, self):
value = self.profile_id[field]
model = type(self)._name
message = (_("Issue\n------\n"
"%s\n'%s' value can't be applied to '%s' field."
"\nThere is no matching value between 'Product Profiles' "
"\nand '%s' models for this field.\n\n"
"Resolution\n----------\n"
"Check your settings on Profile model:\n%s"
% (error, value, field, model, PROFILE_MENU)))
return message
def get_profile_fields_to_exclude():
# These fields must not be synchronized between product.profile
# and product.template/product
return models.MAGIC_COLUMNS + [
'name', 'explanation', 'sequence',
'display_name', '__last_update']
class ProductProfile(models.Model):
_name = 'product.profile'
_order = 'sequence'
name = fields.Char(
required=True,
help="Profile name displayed on product template\n"
"(not synchronized with product.template fields)")
sequence = fields.Integer(
help="Defines the order of the entries of profile_id field\n"
"(not synchronized with product.template fields)")
explanation = fields.Text(
required=True,
oldname='description',
help="An explanation on the selected profile\n"
"(not synchronized with product.template fields)")
type = fields.Selection(
selection='_get_types',
required=True,
help="See 'type' field in product.template")
def _get_types(self):
""" inherit in your custom module.
could be this one if stock module is installed
return [('product', 'Stockable Product'),
('consu', 'Consumable'),
('service', 'Service')]
"""
return [('consu', 'Consumable'), ('service', 'Service')]
@api.multi
def write(self, vals):
""" Profile update can impact products: we take care
to propagate ad hoc changes """
res = super(ProductProfile, self).write(vals)
keys2remove = []
for key in vals:
if (key[:LEN_DEF_STR] == PROF_DEFAULT_STR or
key in get_profile_fields_to_exclude()):
keys2remove.append(key)
for key in keys2remove:
# we remove keys which have no matching in product template
vals.pop(key)
if vals:
products = self.env['product.product'].search(
[('profile_id', '=', self.id)])
if products:
products.write({'profile_id': self.id})
return res
@api.model
def fields_view_get(self, view_id=None, view_type='form',
toolbar=False, submenu=False):
""" Display a warning for end user if edit record """
res = super(ProductProfile, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type == 'form':
style = 'alert alert-warning oe_text_center oe_edit_only'
alert = etree.Element('h2', {'class': style})
alert.text = (_("If you update this profile, all products "
"using this profile could also be updated. "
"Changes can take a while."))
doc = etree.XML(res['arch'])
doc[0].addprevious(alert)
res['arch'] = etree.tostring(doc, pretty_print=True)
return res
class ProductMixinProfile(models.AbstractModel):
_name = 'product.mixin.profile'
@api.model
def _get_profile_fields(self):
fields_to_exclude = set(get_profile_fields_to_exclude())
return [field for field in self.env['product.profile']._fields.keys()
if field not in fields_to_exclude]
@api.model
def _get_profile_data(self, profile_id, filled_fields=None):
# Note for migration to v9
# - rename method to a more convenient name _get_vals_from_profile()
# - remove unused filled_fields args
profile_obj = self.env['product.profile']
fields = self._get_profile_fields()
vals = profile_obj.browse(profile_id).read(fields)[0]
vals.pop('id')
for field, value in vals.items():
if value and profile_obj._fields[field].type == 'many2one':
# m2o value is a tuple
vals[field] = value[0]
if profile_obj._fields[field].type == 'many2many':
vals[field] = [(6, 0, value)]
if PROF_DEFAULT_STR == field[:LEN_DEF_STR]:
vals[field[LEN_DEF_STR:]] = vals[field]
# prefixed fields must be removed from dict
# because they are in profile not in product
vals.pop(field)
return vals
@api.onchange('profile_id')
def _onchange_from_profile(self):
""" Update product fields with product.profile corresponding fields """
self.ensure_one()
if self.profile_id:
values = self._get_profile_data(self.profile_id.id)
for field, value in values.items():
try:
self[field] = value
except Exception as e:
raise UserError(format_except_message(e, field, self))
@api.model
def create(self, vals):
if vals.get('profile_id'):
vals.update(self._get_profile_data(vals['profile_id']))
return super(ProductMixinProfile, self).create(vals)
@api.multi
def write(self, vals):
if vals.get('profile_id'):
vals.update(self._get_profile_data(vals['profile_id']))
return super(ProductMixinProfile, self).write(vals)
@api.model
def _get_default_profile_fields(self):
" Get profile fields with prefix PROF_DEFAULT_STR "
return [x for x in self.env['product.profile']._fields.keys()
if x[:LEN_DEF_STR] == PROF_DEFAULT_STR]
@api.model
def _customize_view(self, res, view_type):
profile_group = self.env.ref('product_profile.group_product_profile')
users_in_profile_group = [user.id for user in profile_group.users]
default_fields = self._get_default_profile_fields()
if view_type == 'form':
doc = etree.XML(res['arch'])
fields = self._get_profile_fields()
fields_def = self.fields_get(allfields=fields)
if self.env.uid not in users_in_profile_group:
attrs = {'invisible': [('profile_id', '!=', False)]}
else:
attrs = {'readonly': [('profile_id', '!=', False)]}
paths = ["//field[@name='%s']",
"//label[@for='%s']"]
for field in fields:
if field not in default_fields:
# default fields shouldn't be modified
for path in paths:
node = doc.xpath(path % field)
if node:
for current_node in node:
current_node.set('attrs', str(attrs))
orm.setup_modifiers(current_node,
fields_def[field])
res['arch'] = etree.tostring(doc, pretty_print=True)
elif view_type == 'search':
# Allow to dynamically create search filters for each profile
filters_to_create = self._get_profiles_to_filter()
doc = etree.XML(res['arch'])
node = doc.xpath("//filter[1]")
if node:
for my_filter in filters_to_create:
elm = etree.Element(
'filter', **self._customize_profile_filters(my_filter))
node[0].addprevious(elm)
res['arch'] = etree.tostring(doc, pretty_print=True)
return res
@api.model
def _get_profiles_to_filter(self):
""" Inherit if you want that some profiles doesn't have a filter """
return [(x.id, x.name) for x in self.env['product.profile'].search([])]
@api.model
def _customize_profile_filters(self, my_filter):
""" Inherit if you to customize search filter display"""
return {
'string': "%s" % my_filter[1],
'help': 'Filtering by Product Profile',
'domain': "[('profile_id','=', %s)]" % my_filter[0]}
class ProductTemplate(models.Model):
_inherit = ['product.template', 'product.mixin.profile']
_name = 'product.template'
profile_id = fields.Many2one(
'product.profile',
string='Profile')
profile_explanation = fields.Text(
related='profile_id.explanation',
string='Profile Explanation',
readonly=True)
@api.model
def fields_view_get(self, view_id=None, view_type='form',
toolbar=False, submenu=False):
""" fields_view_get comes from Model (not AbstractModel) """
res = super(ProductTemplate, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
return self._customize_view(res, view_type)
class ProductProduct(models.Model):
_inherit = ['product.product', 'product.mixin.profile']
_name = 'product.product'
@api.model
def fields_view_get(self, view_id=None, view_type='form',
toolbar=False, submenu=False):
res = super(ProductProduct, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
return self._customize_view(res, view_type)