You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

555 lines
27 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2016 Serpent Consulting Services Pvt. Ltd. (support@serpentcs.com)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. import json
  5. from lxml import etree
  6. from odoo import tools
  7. from odoo import api, models
  8. class MassEditingWizard(models.TransientModel):
  9. _name = 'mass.editing.wizard'
  10. @api.model
  11. def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
  12. submenu=False):
  13. result =\
  14. super(MassEditingWizard, self).fields_view_get(
  15. view_id=view_id,
  16. view_type=view_type,
  17. toolbar=toolbar,
  18. submenu=submenu)
  19. context = self._context
  20. if context.get('mass_editing_object'):
  21. mass_obj = self.env['mass.object']
  22. editing_data = mass_obj.browse(context.get('mass_editing_object'))
  23. all_fields = {}
  24. xml_form = etree.Element('form', {
  25. 'string': tools.ustr(editing_data.name)
  26. })
  27. xml_group = etree.SubElement(xml_form, 'group', {
  28. 'colspan': '4',
  29. 'col': '4',
  30. })
  31. model_obj = self.env[context.get('active_model')]
  32. field_info = model_obj.fields_get()
  33. for field in editing_data.field_ids:
  34. if field.ttype == "many2many":
  35. all_fields[field.name] = field_info[field.name]
  36. all_fields["selection__" + field.name] = {
  37. 'type': 'selection',
  38. 'string': field_info[field.name]['string'],
  39. 'selection': [('set', 'Set'),
  40. ('remove_m2m', 'Remove Specific'),
  41. ('remove_m2m_all', 'Remove All'),
  42. ('add', 'Add')]
  43. }
  44. etree.SubElement(xml_group, 'separator', {
  45. 'string': field_info[field.name]['string'],
  46. 'colspan': '4',
  47. })
  48. etree.SubElement(xml_group, 'field', {
  49. 'name': "selection__" + field.name,
  50. 'colspan': '4',
  51. 'nolabel': '1'
  52. })
  53. etree.SubElement(xml_group, 'field', {
  54. 'name': field.name,
  55. 'colspan': '4',
  56. 'nolabel': '1',
  57. 'attrs': "{'invisible': [('selection__" +
  58. field.name + "', '=', 'remove_m2m')]}",
  59. })
  60. elif field.ttype == "one2many":
  61. all_fields["selection__" + field.name] = {
  62. 'type': 'selection',
  63. 'string': field_info[field.name]['string'],
  64. 'selection': [('set', 'Set'),
  65. ('remove_o2m', 'Remove')],
  66. }
  67. all_fields[field.name] = {
  68. 'type': field.ttype,
  69. 'string': field.field_description,
  70. 'relation': field.relation,
  71. }
  72. etree.SubElement(xml_group, 'separator', {
  73. 'string': field_info[field.name]['string'],
  74. 'colspan': '4',
  75. })
  76. etree.SubElement(xml_group, 'field', {
  77. 'name': "selection__" + field.name,
  78. 'colspan': '4',
  79. 'nolabel': '1'
  80. })
  81. etree.SubElement(xml_group, 'field', {
  82. 'name': field.name,
  83. 'colspan': '4',
  84. 'nolabel': '1',
  85. 'attrs': "{'invisible':[('selection__" +
  86. field.name + "', '=', 'remove_o2m')]}",
  87. })
  88. elif field.ttype == "many2one":
  89. all_fields["selection__" + field.name] = {
  90. 'type': 'selection',
  91. 'string': field_info[field.name]['string'],
  92. 'selection': [('set', 'Set'), ('remove', 'Remove')],
  93. }
  94. all_fields[field.name] = {
  95. 'type': field.ttype,
  96. 'string': field.field_description,
  97. 'relation': field.relation,
  98. }
  99. etree.SubElement(xml_group, 'field', {
  100. 'name': "selection__" + field.name,
  101. })
  102. etree.SubElement(xml_group, 'field', {
  103. 'name': field.name,
  104. 'nolabel': '1',
  105. 'colspan': '2',
  106. 'attrs': "{'invisible':[('selection__" +
  107. field.name + "', '=', 'remove')]}",
  108. })
  109. elif field.ttype == "float":
  110. all_fields["selection__" + field.name] = {
  111. 'type': 'selection',
  112. 'string': field_info[field.name]['string'],
  113. 'selection': [('set', 'Set'),
  114. ('copy', 'Copy From'),
  115. ('val_add', '+'),
  116. ('val_sub', '-'),
  117. ('val_mul', '*'),
  118. ('val_div', '/'),
  119. ('remove', 'Remove')],
  120. }
  121. all_fields["set_selection_" + field.name] = {
  122. 'type': 'selection',
  123. 'string': 'Set calculation',
  124. 'selection': [('set_fix', 'Fixed'),
  125. ('set_per', 'Percentage')],
  126. }
  127. # Create Copy field
  128. all_fields["selection__" + field.name + '_field_id'] = {
  129. 'type': 'many2one',
  130. 'string': 'Copy From',
  131. 'relation': 'ir.model.fields',
  132. }
  133. all_fields[field.name] = {
  134. 'type': field.ttype,
  135. 'string': field.field_description,
  136. 'relation': field.relation,
  137. }
  138. etree.SubElement(xml_group, 'field', {
  139. 'name': "selection__" + field.name,
  140. 'colspan': '2',
  141. })
  142. etree.SubElement(xml_group, 'field', {
  143. 'name': field.name,
  144. 'nolabel': '1',
  145. 'colspan': '1',
  146. 'attrs': "{'invisible': [('selection__" +
  147. field.name + "', 'in', ('remove', 'set')]}",
  148. })
  149. # Add Copy field in view
  150. etree.SubElement(xml_group, 'field', {
  151. 'name': "selection__" + field.name + '_field_id',
  152. 'domain': "[('model_id.model', '=', '" +
  153. model_obj._name + "'), ('ttype', 'in', ['" +
  154. field.ttype + "', 'integer'])]",
  155. 'nolabel': '1',
  156. 'colspan': '1',
  157. 'placeholder': "Copy From...",
  158. })
  159. etree.SubElement(xml_group, 'label', {
  160. 'for': "",
  161. 'colspan': '1',
  162. 'attrs': "{'invisible': [('selection__" +
  163. field.name + "', 'in', ('remove', 'set', 'copy')]}",
  164. })
  165. etree.SubElement(xml_group, 'field', {
  166. 'name': "set_selection_" + field.name,
  167. 'nolabel': '1',
  168. 'colspan': '3',
  169. 'attrs': "{'invisible': [('selection__" + field.name +
  170. "', 'in', ('remove', 'set', 'copy')]}",
  171. })
  172. elif field.ttype == "char":
  173. all_fields["selection__" + field.name] = {
  174. 'type': 'selection',
  175. 'string': field_info[field.name]['string'],
  176. 'selection': [('set', 'Set'),
  177. ('remove', 'Remove'),
  178. ('copy', 'Copy From Another Field')],
  179. }
  180. # Create Copy field
  181. all_fields["selection__" + field.name + '_field_id'] = {
  182. 'type': 'many2one',
  183. 'string': 'Copy From',
  184. 'relation': 'ir.model.fields',
  185. }
  186. all_fields[field.name] = {
  187. 'type': field.ttype,
  188. 'string': field.field_description,
  189. 'size': field.size or 256,
  190. }
  191. etree.SubElement(xml_group, 'field', {
  192. 'name': "selection__" + field.name,
  193. })
  194. # Add Copy field in view
  195. etree.SubElement(xml_group, 'field', {
  196. 'name': field.name,
  197. 'nolabel': '1',
  198. 'attrs': "{'invisible':[('selection__" +
  199. field.name + "','=','remove')]}",
  200. })
  201. etree.SubElement(xml_group, 'field', {
  202. 'name': "selection__" + field.name + '_field_id',
  203. 'domain': "[('model_id.model', '=', '" +
  204. model_obj._name + "'), ('ttype', 'in', ['" +
  205. field.ttype + "', 'selection'])]",
  206. 'nolabel': '1',
  207. 'placeholder': "Copy From...",
  208. })
  209. elif field.ttype == "integer":
  210. all_fields["selection__" + field.name] = {
  211. 'type': 'selection',
  212. 'string': field_info[field.name]['string'],
  213. 'selection': [('set', 'Set'),
  214. ('remove', 'Remove'),
  215. ('copy', 'Copy From Another Field')],
  216. }
  217. # Create Copy field
  218. all_fields["selection__" + field.name + '_field_id'] = {
  219. 'type': 'many2one',
  220. 'string': 'Copy From',
  221. 'relation': 'ir.model.fields',
  222. }
  223. all_fields[field.name] = {
  224. 'type': field.ttype,
  225. 'string': field.field_description,
  226. 'size': field.size or 256,
  227. }
  228. etree.SubElement(xml_group, 'field', {
  229. 'name': "selection__" + field.name,
  230. })
  231. # Add Copy field in view
  232. etree.SubElement(xml_group, 'field', {
  233. 'name': field.name,
  234. 'nolabel': '1',
  235. 'attrs': "{'invisible':[('selection__" +
  236. field.name + "','=','remove')]}",
  237. })
  238. etree.SubElement(xml_group, 'field', {
  239. 'name': "selection__" + field.name + '_field_id',
  240. 'domain': "[('model_id.model', '=', '" +
  241. model_obj._name + "'), ('ttype', 'in', ['" +
  242. field.ttype + "', 'selection'])]",
  243. 'nolabel': '1',
  244. 'placeholder': "Copy From...",
  245. })
  246. elif field.ttype == "boolean":
  247. all_fields["selection__" + field.name] = {
  248. 'type': 'selection',
  249. 'string': field_info[field.name]['string'],
  250. 'selection': [('set', 'Set'),
  251. ('remove', 'Remove'),
  252. ('copy', 'Copy From Another Field')],
  253. }
  254. # Create Copy field
  255. all_fields["selection__" + field.name + '_field_id'] = {
  256. 'type': 'many2one',
  257. 'string': 'Copy From',
  258. 'relation': 'ir.model.fields',
  259. }
  260. all_fields[field.name] = {
  261. 'type': field.ttype,
  262. 'string': field.field_description,
  263. 'size': field.size or 256,
  264. }
  265. etree.SubElement(xml_group, 'field', {
  266. 'name': "selection__" + field.name,
  267. })
  268. # Add Copy field in view
  269. etree.SubElement(xml_group, 'field', {
  270. 'name': field.name,
  271. 'nolabel': '1',
  272. 'attrs': "{'invisible':[('selection__" +
  273. field.name + "','=','remove')]}",
  274. })
  275. etree.SubElement(xml_group, 'field', {
  276. 'name': "selection__" + field.name + '_field_id',
  277. 'domain': "[('model_id.model', '=', '" +
  278. model_obj._name + "'), ('ttype', 'in', ['" +
  279. field.ttype + "', 'selection'])]",
  280. 'nolabel': '1',
  281. 'placeholder': "Copy From...",
  282. })
  283. elif field.ttype == 'selection':
  284. all_fields["selection__" + field.name] = {
  285. 'type': 'selection',
  286. 'string': field_info[field.name]['string'],
  287. 'selection': [('set', 'Set'),
  288. ('remove', 'Remove')]
  289. }
  290. etree.SubElement(xml_group, 'field', {
  291. 'name': "selection__" + field.name,
  292. })
  293. etree.SubElement(xml_group, 'field', {
  294. 'name': field.name,
  295. 'nolabel': '1',
  296. 'colspan': '2',
  297. 'attrs': "{'invisible':[('selection__" +
  298. field.name + "', '=', 'remove')]}",
  299. })
  300. all_fields[field.name] = {
  301. 'type': field.ttype,
  302. 'string': field.field_description,
  303. 'selection': field_info[field.name]['selection'],
  304. }
  305. else:
  306. all_fields[field.name] = {
  307. 'type': field.ttype,
  308. 'string': field.field_description,
  309. }
  310. all_fields["selection__" + field.name] = {
  311. 'type': 'selection',
  312. 'string': field_info[field.name]['string'],
  313. 'selection': [('set', 'Set'), ('remove', 'Remove')]
  314. }
  315. if field.ttype == 'text':
  316. etree.SubElement(xml_group, 'separator', {
  317. 'string': all_fields[field.name]['string'],
  318. 'colspan': '4',
  319. })
  320. etree.SubElement(xml_group, 'field', {
  321. 'name': "selection__" + field.name,
  322. 'colspan': '4',
  323. 'nolabel': '1',
  324. })
  325. etree.SubElement(xml_group, 'field', {
  326. 'name': field.name,
  327. 'colspan': '4',
  328. 'nolabel': '1',
  329. 'attrs': "{'invisible':[('selection__" +
  330. field.name + "','=','remove')]}",
  331. })
  332. else:
  333. all_fields["selection__" + field.name] = {
  334. 'type': 'selection',
  335. 'string': field_info[field.name]['string'],
  336. 'selection': [('set', 'Set'),
  337. ('remove', 'Remove')]
  338. }
  339. etree.SubElement(xml_group, 'field', {
  340. 'name': "selection__" + field.name,
  341. })
  342. etree.SubElement(xml_group, 'field', {
  343. 'name': field.name,
  344. 'nolabel': '1',
  345. 'attrs': "{'invisible':[('selection__" +
  346. field.name + "','=','remove')]}",
  347. 'colspan': '2',
  348. })
  349. # Patch fields with required extra data
  350. for field in all_fields.values():
  351. field.setdefault("views", {})
  352. etree.SubElement(xml_form, 'separator', {
  353. 'string': '',
  354. 'colspan': '3',
  355. 'col': '3',
  356. })
  357. xml_group3 = etree.SubElement(xml_form, 'footer', {})
  358. etree.SubElement(xml_group3, 'button', {
  359. 'string': 'Apply',
  360. 'class': 'btn-primary',
  361. 'type': 'object',
  362. 'name': 'action_apply',
  363. })
  364. etree.SubElement(xml_group3, 'button', {
  365. 'string': 'Close',
  366. 'class': 'btn-default',
  367. 'special': 'cancel',
  368. })
  369. root = xml_form.getroottree()
  370. result['arch'] = etree.tostring(root)
  371. result['fields'] = all_fields
  372. doc = etree.XML(result['arch'])
  373. for field in editing_data.field_ids:
  374. for node in doc.xpath(
  375. "//field[@name='set_selection_" + field.name + "']"
  376. ):
  377. modifiers = json.loads(node.get("modifiers", '{}'))
  378. modifiers.update({'invisible': [(
  379. "selection__" + field.name, 'not in',
  380. ('val_add', 'val_sub', 'val_mul', 'val_div'))],
  381. 'required': [("selection__" + field.name, 'in',
  382. ('val_add', 'val_sub', 'val_mul',
  383. 'val_div'))]}
  384. )
  385. node.set("modifiers", json.dumps(modifiers))
  386. field_name = "selection__" + field.name
  387. for node in doc.xpath("//field[@name='" + field.name + "']"):
  388. modifiers = json.loads(node.get("modifiers", '{}'))
  389. domain = [(field_name, '=', 'remove')]
  390. if field.ttype == "many2many":
  391. domain = [(field_name, '=', 'remove_m2m_all')]
  392. elif field.ttype == "one2many":
  393. domain = [(field_name, '=', 'remove_o2m')]
  394. elif field.ttype in ['char', 'float', 'integer',
  395. 'boolean']:
  396. domain = [(field_name, 'in', ['remove', 'copy'])]
  397. modifiers.update({'invisible': domain})
  398. node.set("modifiers", json.dumps(modifiers))
  399. copy_field = "selection__" + field.name + "_field_id"
  400. for node in doc.xpath("//field[@name='" + copy_field + "']"):
  401. modifiers = json.loads(node.get("modifiers", '{}'))
  402. modifiers.update({
  403. 'invisible': [(field_name, '!=', 'copy')],
  404. 'required': [(field_name, '=', 'copy')],
  405. })
  406. node.set("modifiers", json.dumps(modifiers))
  407. result['arch'] = etree.tostring(doc)
  408. return result
  409. @api.model
  410. def create(self, vals):
  411. if (self._context.get('active_model') and
  412. self._context.get('active_ids')):
  413. fields_obj = self.env['ir.model.fields']
  414. model_obj = self.env[self._context.get('active_model')]
  415. model_field_obj = self.env['ir.model.fields']
  416. translation_obj = self.env['ir.translation']
  417. model_rec = model_obj.browse(self._context.get('active_ids'))
  418. model_id = self.env['ir.model'].search([
  419. ('model', '=', self._context.get('active_model'))])
  420. values = {}
  421. for key, val in vals.items():
  422. if key.startswith('selection_'):
  423. split_key = key.split('__', 1)[1]
  424. set_val = vals.get('set_selection_' + split_key)
  425. if val == 'set':
  426. values.update({split_key: vals.get(split_key, False)})
  427. elif val == 'remove':
  428. values.update({split_key: False})
  429. # If field to remove is translatable,
  430. # its translations have to be removed
  431. model_field = model_field_obj.search([
  432. ('model', '=', self._context.get('active_model')),
  433. ('name', '=', split_key)])
  434. if model_field and model_field.translate:
  435. translation_ids = translation_obj.search([
  436. ('res_id', 'in', self._context.get(
  437. 'active_ids')),
  438. ('type', '=', 'model'),
  439. ('name', '=', u"{0},{1}".format(
  440. self._context.get('active_model'),
  441. split_key))])
  442. translation_ids.unlink()
  443. elif val in ['remove_m2m', 'remove_m2m_all']:
  444. m2m_list = []
  445. if vals.get(split_key):
  446. for m2m_id in vals.get(split_key)[0][2]:
  447. m2m_list.append((3, m2m_id))
  448. if m2m_list:
  449. values.update({split_key: m2m_list})
  450. else:
  451. values.update({split_key: [(5, 0, [])]})
  452. elif val =='remove_o2m':
  453. # model_fieds will return the particular model
  454. # in order to get the field of the model
  455. # and its relation.
  456. model_fields = self.env['ir.model.fields'].search(
  457. [('name', '=', split_key),
  458. ('model_id', '=', model_id and model_id.id)])
  459. # field_model is relation of Object Relation
  460. field_model = tools.ustr(model_fields and
  461. model_fields.relation)
  462. # field relation is relation field of O2m
  463. field_relation = tools.ustr(
  464. model_fields and
  465. model_fields.relation_field)
  466. # remove_ids is return O2m field particular ids
  467. remove_ids = self.env[field_model].search(
  468. [(field_relation, 'in',
  469. self._context.get('active_ids'))])
  470. o2m_list = [(2, rmv_id.id) for rmv_id in remove_ids]
  471. values.update({split_key: o2m_list})
  472. elif val == 'add':
  473. if vals.get(split_key, False):
  474. m2m_list = []
  475. for m2m_id in vals.get(split_key, False)[0][2]:
  476. m2m_list.append((4, m2m_id))
  477. values.update({split_key: m2m_list})
  478. elif val == 'copy':
  479. field_id = vals.get(
  480. 'selection__' + split_key + '_field_id'
  481. )
  482. if field_id:
  483. field_name = fields_obj.browse(field_id).name
  484. for data in model_rec:
  485. data.write({split_key: data[field_name]})
  486. # Mathematical operations
  487. elif val in ['val_add', 'val_sub', 'val_mul', 'val_div']:
  488. split_val = vals.get(split_key, 0.0)
  489. for data in model_rec:
  490. split_key_data = data[split_key]
  491. tot_val = 0
  492. # Addition
  493. if val == 'val_add':
  494. if set_val == 'set_fix':
  495. tot_val = split_key_data + split_val
  496. elif set_val == 'set_per':
  497. tot_val = split_key_data +\
  498. (split_key_data * split_val) / 100.0
  499. # Subtraction
  500. elif val == 'val_sub':
  501. if set_val == 'set_fix':
  502. tot_val = split_key_data - split_val
  503. elif set_val == 'set_per':
  504. tot_val = split_key_data -\
  505. (split_key_data * split_val) / 100.0
  506. # Multiplication
  507. elif val == 'val_mul':
  508. if set_val == 'set_fix':
  509. tot_val = split_key_data * split_val
  510. elif set_val == 'set_per':
  511. tot_val = split_key_data *\
  512. (split_key_data * split_val) / 100
  513. # Division
  514. elif val == 'val_div':
  515. if set_val == 'set_fix':
  516. tot_val = split_key_data / split_val
  517. elif set_val == 'set_per':
  518. tot_val = split_key_data /\
  519. (split_key_data * split_val) / 100
  520. data.write({split_key: tot_val})
  521. if values:
  522. model_rec.write(values)
  523. return super(MassEditingWizard, self).create(vals)
  524. @api.multi
  525. def action_apply(self):
  526. return {'type': 'ir.actions.act_window_close'}
  527. def read(self, fields, load='_classic_read'):
  528. """ Without this call, dynamic fields build by fields_view_get()
  529. generate a log warning, i.e.:
  530. odoo.models:mass.editing.wizard.read() with unknown field 'myfield'
  531. odoo.models:mass.editing.wizard.read()
  532. with unknown field 'selection__myfield'
  533. """
  534. real_fields = fields
  535. if fields:
  536. # We remove fields which are not in _fields
  537. real_fields = [x for x in fields if x in self._fields]
  538. result = super(MassEditingWizard, self).read(real_fields, load=load)
  539. # adding fields to result
  540. [result[0].update({x: False}) for x in fields if x not in real_fields]
  541. return result