[ADD]: Add/Remove dynamic digest field, group and misc changes

This commit is contained in:
Haresh Chavda 2018-08-24 18:06:32 +05:30
parent 44e47f76a1
commit 84ed2b2571
9 changed files with 215 additions and 74 deletions

View File

@ -19,9 +19,11 @@ Send KPI Digests periodically
'data/ir_cron_data.xml', 'data/ir_cron_data.xml',
'data/res_config_settings_data.xml', 'data/res_config_settings_data.xml',
'views/digest_views.xml', 'views/digest_views.xml',
'wizard/digest_custom_fields_view.xml',
'wizard/digest_custom_remove_view.xml',
'views/digest_views_inherit.xml',
'views/digest_templates.xml', 'views/digest_templates.xml',
'views/res_config_settings_views.xml', 'views/res_config_settings_views.xml',
'wizard/digest_custom_fields_view.xml',
], ],
'installable': True, 'installable': True,
} }

View File

@ -62,43 +62,6 @@
<group name="kpi_sales"/> <group name="kpi_sales"/>
</group> </group>
</page> </page>
<page name="how_to" string="How to customize your digest?" groups="base.group_no_one">
<div>
<button type="action" name="%(digest.digest_custom_fields_action)d" string="Customized Digest"/>
</div>
<div class="alert alert-info" role="alert">
In order to build your customized digest, follow these steps:
<ol>
<li>
You may want to add new computed fields:
<ul>
<li>
you must create 2 fields on the
<code>digest</code>
object:
</li>
<li>
first create a boolean field called
<code>x_kpi_field_name</code>
and display it in the KPI's tab;
</li>
<li>
then create a computed field called
<code>x_kpi_field_name_value</code>
that will compute your customized KPI.
</li>
</ul>
</li>
<li>Select your KPIs in the KPI's tab.</li>
<li>
Create or edit the mail template: you may get computed KPI's value using these fields:
<code>
<field name="available_fields" class="oe_inline" />
</code>
</li>
</ol>
</div>
</page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>

View File

@ -0,0 +1,50 @@
<?xml version="1.0"?>
<flectra>
<record id="digest_digest_view_form_inherit" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='kpis']" position="after">
<page name="how_to" string="How to customize your digest?" groups="base.group_no_one">
<div>
<button type="action" class="oe_highlight" name="%(digest.digest_custom_fields_action)d" string="Add Customized Digest"/>
<button type="action" name="%(digest.digest_custom_remove_action)d" string="Remove Customized Digest"/>
</div>
<div class="alert alert-info" role="alert">
In order to build your customized digest, follow these steps:
<ol>
<li>
You may want to add new computed fields:
<ul>
<li>
you must create 2 fields on the
<code>digest</code>
object:
</li>
<li>
first create a boolean field called
<code>x_kpi_field_name</code>
and display it in the KPI's tab;
</li>
<li>
then create a computed field called
<code>x_kpi_field_name_value</code>
that will compute your customized KPI.
</li>
</ul>
</li>
<li>Select your KPIs in the KPI's tab.</li>
<li>
Create or edit the mail template: you may get computed KPI's value using these fields:
<code>
<field name="available_fields" class="oe_inline" />
</code>
</li>
</ol>
</div>
</page>
</xpath>
</field>
</record>
</flectra>

View File

@ -2,3 +2,4 @@
# Part of Flectra. See LICENSE file for full copyright and licensing details. # Part of Flectra. See LICENSE file for full copyright and licensing details.
from . import digest_custom_fields from . import digest_custom_fields
from . import digest_custom_remove

View File

@ -5,6 +5,7 @@ from flectra import api, fields, models, _
from flectra.exceptions import ValidationError, UserError from flectra.exceptions import ValidationError, UserError
from lxml import etree from lxml import etree
from flectra.tools.safe_eval import test_python_expr from flectra.tools.safe_eval import test_python_expr
import xml.etree.ElementTree as ET
class DigestCustomFields(models.TransientModel): class DigestCustomFields(models.TransientModel):
@ -19,29 +20,37 @@ class DigestCustomFields(models.TransientModel):
# - time, datetime, dateutil, timezone: useful Python libraries # - time, datetime, dateutil, timezone: useful Python libraries
# - log: log(message, level='info'): logging function to record debug information in ir.logging table # - log: log(message, level='info'): logging function to record debug information in ir.logging table
# - Warning: Warning Exception to use with raise # - Warning: Warning Exception to use with raise
# To return an action, assign: action = {...}\n\n\n\n""" # To return an action, assign: action = {...}
for rec in self:
rec[''] = self.env[''].search([])\n\n\n\n"""
# field = fields.Many2one('ir.model.fields', domain="[('model_id', '=', 'digest.digest'), ('name', 'ilike', 'x_kpi'), ('depends', '=', False)]")
# compute_field = fields.Many2one('ir.model.fields', domain="[('model_id', '=', 'digest.digest'), ('name', 'ilike', 'x_kpi'), ('depends', '!=', False)]")
field_name = fields.Char('Field Name', default='x_kpi_', required=True) field_name = fields.Char('Field Name', default='x_kpi_', required=True)
label_name = fields.Char('Label Name', required=True) label_name = fields.Char('Label Name', required=True)
group_name = fields.Char('Group Name', required=True) # group_type = fields.Selection([('new', 'New Group'), ('existing', 'Existing Group')], string='Group Type', required=True)
ttype = fields.Selection([('integer', 'Integer'), ('monetary', 'Monetary')], string='Field Type', required=True) new_group_name = fields.Char('Group Name')
# compute = fields.Text(help="Code to compute the value of the field.\n" ttype = fields.Selection([('integer', 'Integer'), ('monetary', 'Monetary')], string='Field Type', required=True, default='integer')
# "Iterate on the recordset 'self' and assign the field's value:\n\n"
# " for record in self:\n"
# " record['size'] = len(record.name)\n\n"
# "Modules time, datetime, dateutil are available.")
compute = fields.Text(string='Python Code', groups='base.group_system', compute = fields.Text(string='Python Code', groups='base.group_system',
default=DEFAULT_PYTHON_CODE, default=DEFAULT_PYTHON_CODE,
help="Write Python code that the action will execute. Some variables are " help="Write Python code that the action will execute. Some variables are "
"available for use; help about pyhon expression is given in the help tab.") "available for use; help about pyhon expression is given in the help tab.")
compute_field_name = fields.Char(compute='_compute_get_field_name', string='Compute Field Name') compute_field_name = fields.Char(compute='_compute_get_field_name', string='Compute Field Name')
available_group_name = fields.Selection('_get_group_name', string='Available Group')
position = fields.Selection([('before', 'Before'), ('after', 'After'), ('inside', 'Inside')], string='Position')
def _get_group_name(self):
print("=====self=========", self.env.context)
digest_view_id = self.env.ref('digest.digest_digest_view_form').id
view_ids = self.env['ir.ui.view'].search([('inherit_id', 'child_of', digest_view_id)])
group_value = {}
for view_id in view_ids:
root = ET.fromstring(view_id.arch_base)
for group_name in root.iter('group'):
if group_name.attrib.get('name', False) and group_name.attrib.get('string', False):
group_key = str(view_id.id) + '_' + str(group_name.attrib['name'])
group_value.update({group_key : group_name.attrib['string']})
return [(x) for x in group_value.items()]
@api.constrains('compute') @api.constrains('compute')
def _check_python_code(self): def _check_python_code(self):
@ -61,6 +70,8 @@ class DigestCustomFields(models.TransientModel):
for field in self: for field in self:
if not field.field_name.startswith('x_kpi_'): if not field.field_name.startswith('x_kpi_'):
raise ValidationError(_("Custom fields must have a name that starts with 'x_kpi_'!")) raise ValidationError(_("Custom fields must have a name that starts with 'x_kpi_'!"))
# if self.position != 'inside' and not field.new_group_name.startswith('x_kpi_'):
# raise ValidationError(_("Group Name must have a name that starts with 'x_kpi_'!"))
try: try:
models.check_pg_name(field.field_name) models.check_pg_name(field.field_name)
except ValidationError: except ValidationError:
@ -90,41 +101,44 @@ class DigestCustomFields(models.TransientModel):
'depends': first_field_name, 'depends': first_field_name,
'compute': self.compute 'compute': self.compute
} }
print("====values=======", values)
ir_model_fields_obj.create(values) ir_model_fields_obj.create(values)
def field_arch(self): def field_arch(self):
xpath = etree.Element('xpath') xpath = etree.Element('xpath')
xpath_type = "group" name = self.available_group_name and self.available_group_name.split('_', 1)[1] or "kpis"
name = "kpi_general" expr = '//' + 'group' + '[@name="' + name + '"]'
position = "after"
xpath_field = self.field_name
expr = '//' + xpath_type + '[@name="' + name + '"][not(ancestor::field)]'
xpath.set('expr', expr) xpath.set('expr', expr)
xpath.set('position', position) xpath.set('position', self.position)
if position == 'after' or position == 'before' or position == 'inside':
expr = '//' + xpath_type + '[@name="' + name + '"][not(ancestor::field)]' if self.position == 'inside':
group = etree.Element('group')
group.set('string', self.group_name)
field = etree.Element('field') field = etree.Element('field')
field.set('name', xpath_field) field.set('name', self.field_name)
xpath.set('expr', expr)
xpath.append(field)
else:
group = etree.Element('group')
group.set('name', 'x_kpi_' + self.new_group_name.replace(" ", "_"))
group.set('string', self.new_group_name)
field = etree.Element('field')
field.set('name', self.field_name)
xpath.set('expr', expr) xpath.set('expr', expr)
group.append(field) group.append(field)
xpath.append(group) xpath.append(group)
return etree.tostring(xpath).decode("utf-8") return etree.tostring(xpath).decode("utf-8")
@api.multi @api.multi
def action_customize_digest(self): def action_add_customize_digest(self):
self.add_new_fields() self.add_new_fields()
arch = '<data>' + str(self.field_arch()) + '</data>' arch = '<?xml version="1.0"?>' + str(self.field_arch())
print("====arch=======", arch) view_id = self.available_group_name and self.available_group_name.split('_', 1)[0] or False
vals = { vals = {
'type': 'form', 'type': 'form',
'model': 'digest.digest', 'model': 'digest.digest',
'inherit_id': self.env.ref('digest.digest_digest_view_form').id, 'inherit_id': view_id or self.env.ref('digest.digest_digest_view_form').id,
'mode': 'extension', 'mode': 'extension',
'arch_base': arch, 'arch_base': arch,
'name': 'x_kpi_' + self.field_name + "_Customization", 'name': 'x_kpi_' + self.field_name + "_customization",
} }
ir_model = self.env['ir.model'].search([('model', '=', 'digest.digest')]) ir_model = self.env['ir.model'].search([('model', '=', 'digest.digest')])
if hasattr(ir_model, 'module_id'): if hasattr(ir_model, 'module_id'):

View File

@ -5,10 +5,12 @@
<field name="model">digest.custom.fields</field> <field name="model">digest.custom.fields</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Customized Digest"> <form string="Customized Digest">
<group class="oe_title" col="4"> <group col="4">
<field name="field_name"/> <field name="field_name"/>
<field name="label_name"/> <field name="label_name"/>
<field name="group_name"/> <field name="available_group_name" required="1"/>
<field name="position" required="1"/>
<field name="new_group_name" attrs="{'required': [('position', '!=', 'inside')], 'invisible': [('position', 'not in', ['before', 'after'])]}"/>
</group> </group>
<group string="Compute Details"> <group string="Compute Details">
<field name="compute_field_name"/> <field name="compute_field_name"/>
@ -16,7 +18,7 @@
<field name="compute" widget="ace" options="{'mode': 'python'}"/> <field name="compute" widget="ace" options="{'mode': 'python'}"/>
</group> </group>
<footer> <footer>
<button name="action_customize_digest" string="Save" type="object" class="btn btn-sm btn-primary"/> <button name="action_add_customize_digest" string="Save" type="object" class="btn btn-sm btn-primary"/>
<button string="Cancel" class="btn btn-sm btn-default" special="cancel"/> <button string="Cancel" class="btn btn-sm btn-default" special="cancel"/>
</footer> </footer>
</form> </form>

View File

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
# Part of Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models, _
from flectra.exceptions import ValidationError, UserError
from lxml import etree
from flectra.tools.safe_eval import test_python_expr
import xml.etree.ElementTree as ET
from flectra.osv import expression
class DigestCustomRemove(models.TransientModel):
_name = 'digest.custom.remove'
remove_type = fields.Selection([('group', 'Group'), ('field', 'Field')], string='Remove Type')
field_id = fields.Many2one('ir.model.fields', 'Field', domain=[('model', '=', 'digest.digest'), ('required', '=', False), ('ttype', '=', 'boolean'), ('name', 'ilike', 'x_kpi_')])
available_group_name = fields.Selection('_get_group_name', string='Available Group')
def _get_group_name(self):
digest_view_id = self.env.ref('digest.digest_digest_view_form').id
view_ids = self.env['ir.ui.view'].search([('inherit_id', 'child_of', digest_view_id)])
group_value = {}
for view_id in view_ids:
root = ET.fromstring(view_id.arch_base)
for group_name in root.iter('group'):
if group_name.attrib.get('name', False) and group_name.attrib.get('string', False) and group_name.attrib['name'].startswith('x_kpi_'):
group_key = str(view_id.id) + '_' + str(group_name.attrib['name'])
group_value.update({group_key : group_name.attrib['string']})
return [(x) for x in group_value.items()]
@api.multi
def action_customize_digest_remove(self):
ir_model_fields_obj = self.env['ir.model.fields']
ir_ui_view_obj = self.env['ir.ui.view']
if self.remove_type == 'group':
find_view_id = self.available_group_name and self.available_group_name.split('_', 1)[0] or False
print("===find_view_id==", find_view_id)
view_ids = ir_ui_view_obj.search([('inherit_id', 'child_of', int(find_view_id))], order="id desc")
field_list = []
for view_id in view_ids:
print("===view_id========", view_id)
root = ET.fromstring(view_id.arch_base)
print("==========root=====", root)
for child in root.iter('group'):
name = child.find('field')
if name.attrib and name.attrib.get('name', False):
field_list.append(name.attrib.get('name', False))
field_ids = ir_model_fields_obj.search([('name', 'in', field_list)])
print("====field_ids====", field_ids, view_ids.ids)
view_ids.unlink()
for field_id in field_ids:
ir_model_fields_obj.search([('depends', '=', field_id.name)]).unlink()
field_ids.unlink()
else:
domain = expression.OR([('arch_db', 'like', record.name)] for record in self.field_id)
print("===domain======", domain)
view_ids = ir_ui_view_obj.search(domain)
print("==========>>>>>>>>.", view_ids)
for view_id in view_ids:
# print("=====view_id.arch_base======before========", view_id.arch_base)
root = ET.fromstring(view_id.arch_base)
# result = len(root.getchildren())
# count = sum(1 for root in root.iter("field"))
# print("===============result==============>", result, count)
for child in root.iter('field'):
# print("===========>>>>",child.text, child.attrib, child.tag)
if child.attrib and child.attrib.get('name', False) == self.field_id.name:
view_id.unlink()
# root.remove(child)
# view_id.write({'arch_base': ET.tostring(root)})
ir_model_fields_obj.search([('depends', '=', self.field_id.name)]).unlink()
self.field_id.unlink()
return {
'type': 'ir.actions.client',
'tag': 'reload',
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<flectra>
<record id="digest_custom_remove_view_form" model="ir.ui.view">
<field name="name">digest.custom.remove.form</field>
<field name="model">digest.custom.remove</field>
<field name="arch" type="xml">
<form string="Customized Digest">
<group>
<field name="remove_type"/>
</group>
<group>
<field name="field_id" attrs="{'invisible': [('remove_type', '!=', 'field')]}" options="{'no_create': True}"/>
<field name="available_group_name" attrs="{'invisible': [('remove_type', '!=', 'group')]}"/>
</group>
<footer>
<button name="action_customize_digest_remove" string="Remove" type="object" class="btn btn-sm btn-primary"/>
<button string="Cancel" class="btn btn-sm btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="digest_custom_remove_action" model="ir.actions.act_window">
<field name="name">Customized Digest Remove</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">digest.custom.remove</field>
<field name="view_mode">form</field>
<field name="view_id" ref="digest_custom_remove_view_form"/>
<field name="target">new</field>
</record>
</flectra>

View File

@ -202,7 +202,9 @@ class EventEvent(models.Model):
@api.model @api.model
def _tz_get(self): def _tz_get(self):
return [(x, x) for x in pytz.all_timezones] a = [(x, x) for x in pytz.all_timezones]
print("\n\n\n=================>>>>>>.", a)
return a
@api.one @api.one
@api.depends('date_tz', 'date_begin') @api.depends('date_tz', 'date_begin')