[IMP]: dynamic set domain, field and model for new digest field

This commit is contained in:
Haresh Chavda 2018-08-31 17:39:21 +05:30
parent 84ed2b2571
commit c2fd95a87b
10 changed files with 122 additions and 88 deletions

View File

@ -15,6 +15,7 @@ Send KPI Digests periodically
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/digest_template_data.xml', 'data/digest_template_data.xml',
'views/digest_templates.xml',
'data/digest_data.xml', 'data/digest_data.xml',
'data/ir_cron_data.xml', 'data/ir_cron_data.xml',
'data/res_config_settings_data.xml', 'data/res_config_settings_data.xml',
@ -22,7 +23,6 @@ Send KPI Digests periodically
'wizard/digest_custom_fields_view.xml', 'wizard/digest_custom_fields_view.xml',
'wizard/digest_custom_remove_view.xml', 'wizard/digest_custom_remove_view.xml',
'views/digest_views_inherit.xml', 'views/digest_views_inherit.xml',
'views/digest_templates.xml',
'views/res_config_settings_views.xml', 'views/res_config_settings_views.xml',
], ],
'installable': True, 'installable': True,

View File

@ -10,8 +10,8 @@
<table style="width: 100%; border-spacing: 0; font-family: Helvetica,Arial,Verdana,sans-serif;"> <table style="width: 100%; border-spacing: 0; font-family: Helvetica,Arial,Verdana,sans-serif;">
<tr> <tr>
<td align="center" valign="top" style="border-collapse: collapse; padding: 0"> <td align="center" valign="top" style="border-collapse: collapse; padding: 0">
% set company, user = ctx['company'], ctx['user'] % set company, user = user.company_id, user
% set data = object.compute_kpis(company, user) % set data = object.compute_kpis(user.company, user)
% set tips = object.compute_tips(company, user) % set tips = object.compute_tips(company, user)
% set kpi_actions = object.compute_kpis_actions(company, user) % set kpi_actions = object.compute_kpis_actions(company, user)
% set kpis = data.yesterday.keys() % set kpis = data.yesterday.keys()
@ -120,32 +120,6 @@
</tr> </tr>
</table> </table>
% endif % endif
<table style="width: 100%; max-width: 600px; margin-top: 5px; border: 1px solid #e7e7e7;">
<tr>
<td style="border-collapse: collapse; background-color: #ffffff; line-height: 21px; padding: 0 20px 10px 20px; text-align: center;"><br/>
<div style="color: #3d466e; font-size: 16px; font-weight: 600; line-height: 23px;">Run your bussiness from anywhere with Flectra Mobile.</div>
</td>
</tr>
<tr>
<td>
<div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=com.flectrahq.mobile" target="_blank"><img src="/digest/static/src/img/google_play.png" style="display: inline-block; height: 30px; margin-left: auto; margin-right: 12px;"/></a><a href="https://itunes.apple.com/us/app/flectrahq/id1272543640" target="_blank"><img src="/digest/static/src/img/app_store.png" style="display: inline-block; height: 30px; margin-left: 12px; margin-right: auto;"/></a>
</div>
</td>
</tr>
</table>
<table style="margin-top: 5px; border: 1px solid #e7e7e7; font-size: 15px; width: 100%; max-width: 600px;">
<tr>
<td style="border-collapse: collapse; margin: 0; padding: 10px 20px;">
% if ctx['user'].has_group('base.group_system'):
<div style="margin-top: 20px;">
Want to customize the email?
<a href="/web#view_type=form&amp;model=digest.digest&amp;id=${object.id}" target="_blank" style="color: #875A7B;">Choose the metrics you care about</a>
</div>
<br />
% endif
</td>
</tr>
</table>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -44,7 +44,6 @@ class Digest(models.Model):
kpi_mail_message_total = fields.Boolean('Messages') kpi_mail_message_total = fields.Boolean('Messages')
kpi_mail_message_total_value = fields.Integer(compute='_compute_kpi_mail_message_total_value') kpi_mail_message_total_value = fields.Integer(compute='_compute_kpi_mail_message_total_value')
def _compute_is_subscribed(self): def _compute_is_subscribed(self):
for digest in self: for digest in self:
digest.is_subscribed = self.env.user in digest.user_ids digest.is_subscribed = self.env.user in digest.user_ids
@ -108,6 +107,10 @@ class Digest(models.Model):
def compute_kpis(self, company, user): def compute_kpis(self, company, user):
self.ensure_one() self.ensure_one()
if not company:
company = self.env.user.company_id
if not user:
user = self.env.user
res = {} res = {}
for tf_name, tf in self._compute_timeframes(company).items(): for tf_name, tf in self._compute_timeframes(company).items():
digest = self.with_context(start_date=tf[0][0], end_date=tf[0][1], company=company).sudo(user.id) digest = self.with_context(start_date=tf[0][0], end_date=tf[0][1], company=company).sudo(user.id)
@ -115,7 +118,6 @@ class Digest(models.Model):
kpis = {} kpis = {}
for field_name, field in self._fields.items(): for field_name, field in self._fields.items():
if field.type == 'boolean' and (field_name.startswith('kpi_') or field_name.startswith('x_kpi_')) and self[field_name]: if field.type == 'boolean' and (field_name.startswith('kpi_') or field_name.startswith('x_kpi_')) and self[field_name]:
try: try:
compute_value = digest[field_name + '_value'] compute_value = digest[field_name + '_value']
previous_value = previous_digest[field_name + '_value'] previous_value = previous_digest[field_name + '_value']

View File

@ -10,8 +10,9 @@ class ResUsers(models.Model):
def create(self, vals): def create(self, vals):
""" Automatically subscribe employee users to default digest if activated """ """ Automatically subscribe employee users to default digest if activated """
user = super(ResUsers, self).create(vals) user = super(ResUsers, self).create(vals)
default_digest_emails = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_emails') config_obj = self.env['ir.config_parameter'].sudo()
default_digest_id = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_id') default_digest_emails = config_obj.get_param('digest.default_digest_emails')
default_digest_id = config_obj.get_param('digest.default_digest_id')
if user.has_group('base.group_user') and default_digest_emails and default_digest_id: if user.has_group('base.group_user') and default_digest_emails and default_digest_id:
digest = self.env['digest.digest'].sudo().browse(int(default_digest_id)) digest = self.env['digest.digest'].sudo().browse(int(default_digest_id))
digest.user_ids |= user digest.user_ids |= user

View File

@ -13,4 +13,33 @@
</div> </div>
</t> </t>
</template> </template>
<record model="ir.ui.view" id="email_template_preview_digest_form">
<field name="name">email_template.preview.digest.form</field>
<field name="model">email_template.preview</field>
<field name="arch" type="xml">
<form string="Preview Of Digest">
Choose an example <field name="model_id" class="oe_inline" readonly="1"/> record:
<field name="res_id" class="oe_inline" style="margin-left: 8px;"/>
<field name="body_html" widget="html" readonly="1"
nolabel="1" options='{"safe": True}'/>
<footer>
<button string="Discard" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="wizard_email_template_preview_digest" model="ir.actions.act_window">
<field name="name">Preview of Digest</field>
<field name="res_model">email_template.preview</field>
<field name="src_model">digest.digest</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="email_template_preview_digest_form"/>
<field name="target">new</field>
<field name="context">{'template_id':template_id, 'default_res_id': active_id}</field>
</record>
</flectra> </flectra>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<flectra> <flectra>
<record id="digest_digest_view_tree" model="ir.ui.view"> <record id="digest_digest_view_tree" model="ir.ui.view">
<field name="name">digest.digest.view.tree</field> <field name="name">digest.digest.view.tree</field>
<field name="model">digest.digest</field> <field name="model">digest.digest</field>
@ -36,6 +37,9 @@
<field name="state" widget="statusbar"/> <field name="state" widget="statusbar"/>
</header> </header>
<sheet> <sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="%(digest.wizard_email_template_preview_digest)d" icon="fa-search-plus" string="Preview" type="action" target="new" context="{'template_id': template_id}" attrs="{'invisible': [('template_id', '=', False)]}"/>
</div>
<div class="oe_title"> <div class="oe_title">
<label for="name" class="oe_edit_only"/> <label for="name" class="oe_edit_only"/>
<h1> <h1>
@ -92,4 +96,5 @@
parent="base.menu_email" parent="base.menu_email"
groups="base.group_erp_manager" groups="base.group_erp_manager"
sequence="93"/> sequence="93"/>
</flectra> </flectra>

View File

@ -11,36 +11,45 @@ import xml.etree.ElementTree as ET
class DigestCustomFields(models.TransientModel): class DigestCustomFields(models.TransientModel):
_name = 'digest.custom.fields' _name = 'digest.custom.fields'
DEFAULT_PYTHON_CODE = """# Available variables:
# - env: Flectra Environment on which the action is triggered
# - model: Flectra Model of the record on which the action is triggered; is a void recordset
# - record: record on which the action is triggered; may be be void
# - records: recordset of all records on which the action is triggered in multi-mode; may be void
# - time, datetime, dateutil, timezone: useful Python libraries
# - log: log(message, level='info'): logging function to record debug information in ir.logging table
# - Warning: Warning Exception to use with raise
# To return an action, assign: action = {...}
for rec in self:
rec[''] = self.env[''].search([])\n\n\n\n"""
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_type = fields.Selection([('new', 'New Group'), ('existing', 'Existing Group')], string='Group Type', required=True)
new_group_name = fields.Char('Group Name') new_group_name = fields.Char('Group Name')
ttype = fields.Selection([('integer', 'Integer'), ('monetary', 'Monetary')], string='Field Type', required=True, default='integer') ttype = fields.Selection([('integer', 'Integer'), ('monetary', 'Monetary')], string='Field Type', required=True, default='integer')
compute = fields.Text(string='Python Code', groups='base.group_system', compute = fields.Text(string='Compute', groups='base.group_system')
default=DEFAULT_PYTHON_CODE,
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.")
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') available_group_name = fields.Selection('_get_group_name', string='Available Group')
position = fields.Selection([('before', 'Before'), ('after', 'After'), ('inside', 'Inside')], string='Position') position = fields.Selection([('before', 'Before'), ('after', 'After'), ('inside', 'Inside')], string='Position')
model_id = fields.Many2one(
'ir.model', string='Model')
model_name = fields.Char(related='model_id.model', string='Model Name')
model_domain = fields.Char(string='Domain', oldname='domain', default=[])
model_real = fields.Char(compute='_compute_model', string='Real Model')
related_date_field_id = fields.Many2one('ir.model.fields', 'Date')
@api.onchange('compute_field_name', 'model_id', 'model_domain', 'date_domain', 'related_date_field_id')
def onchange_compute_field_name(self):
data = """for record in self:
start, end, company = record._get_kpi_compute_parameters()
"""
domain = self.model_domain
date = self.related_date_field_id
domain_values = """[["%s", ">=", start], ["%s", "<", end]]""" % (date.name or '', date.name or '')
if domain != '[]':
domain += domain_values
domain = domain.replace("][", ",")
else:
domain = domain_values
data += "record['%s'] = self.env['%s'].search_count(%s)" % (self.compute_field_name, self.model_real or '', domain) + "\n\n"
self.compute = data
@api.depends('model_id')
def _compute_model(self):
for record in self:
if record.model_id:
record.model_real = record.model_name or 'res.users'
def _get_group_name(self): def _get_group_name(self):
print("=====self=========", self.env.context)
digest_view_id = self.env.ref('digest.digest_digest_view_form').id 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)]) view_ids = self.env['ir.ui.view'].search([('inherit_id', 'child_of', digest_view_id)])
group_value = {} group_value = {}
@ -70,8 +79,6 @@ for rec in self:
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,9 +97,8 @@ for rec in self:
'field_description': self.label_name, 'field_description': self.label_name,
'model': 'digest.digest' 'model': 'digest.digest'
} }
ir_model_fields_obj.create(values)
values = { compute_values = {
'model_id': model_id.id, 'model_id': model_id.id,
'ttype': self.ttype, 'ttype': self.ttype,
'name': self.field_name + '_value', 'name': self.field_name + '_value',
@ -101,7 +107,12 @@ for rec in self:
'depends': first_field_name, 'depends': first_field_name,
'compute': self.compute 'compute': self.compute
} }
ir_model_fields_obj.create(values)
try:
ir_model_fields_obj.create(values)
ir_model_fields_obj.create(compute_values)
except Exception as e:
raise ValidationError(e)
def field_arch(self): def field_arch(self):
xpath = etree.Element('xpath') xpath = etree.Element('xpath')
@ -109,19 +120,16 @@ for rec in self:
expr = '//' + 'group' + '[@name="' + name + '"]' expr = '//' + 'group' + '[@name="' + name + '"]'
xpath.set('expr', expr) xpath.set('expr', expr)
xpath.set('position', self.position) xpath.set('position', self.position)
field = etree.Element('field')
field.set('name', self.field_name)
xpath.set('expr', expr)
if self.position == 'inside': if self.position == 'inside':
field = etree.Element('field')
field.set('name', self.field_name)
xpath.set('expr', expr)
xpath.append(field) xpath.append(field)
else: else:
group = etree.Element('group') group = etree.Element('group')
group.set('name', 'x_kpi_' + self.new_group_name.replace(" ", "_")) group.set('name', 'x_kpi_' + self.new_group_name.replace(" ", "_"))
group.set('string', self.new_group_name) group.set('string', self.new_group_name)
field = etree.Element('field')
field.set('name', self.field_name)
xpath.set('expr', expr)
group.append(field) group.append(field)
xpath.append(group) xpath.append(group)

View File

@ -12,11 +12,38 @@
<field name="position" required="1"/> <field name="position" required="1"/>
<field name="new_group_name" attrs="{'required': [('position', '!=', 'inside')], 'invisible': [('position', 'not in', ['before', 'after'])]}"/> <field name="new_group_name" attrs="{'required': [('position', '!=', 'inside')], 'invisible': [('position', 'not in', ['before', 'after'])]}"/>
</group> </group>
<group string="Compute Details"> <group>
<field name="compute_field_name"/> <label for="model_id"/>
<field name="ttype"/> <div>
<field name="compute" widget="ace" options="{'mode': 'python'}"/> <field name="model_id" options="{'no_create': True}" required="1"/>
<field name="model_name" invisible="1"/>
<field name="model_real" invisible="1"/>
<field name="model_domain" widget="domain"
options="{'model': 'model_real'}"/>
</div>
</group> </group>
<group>
<field name="related_date_field_id" options="{'no_create': True}" domain="[('model', '=', model_real), ('ttype', 'in', ['date', 'datetime'])]" required="1"/>
</group>
<notebook>
<page string="Compute Details">
<group>
<field name="compute_field_name"/>
<field name="ttype"/>
<field name="compute" widget="ace" options="{'mode': 'python'}"/>
</group>
</page>
<page string="Help">
<h3>How to define a computed field</h3>
<div class="alert alert-info" role="alert">
The field <code>Compute</code> is the Python code to compute the value of the field on a set of records.
The value of the field must be assigned to each record with a dictionary-like assignment.
<pre>
for record in self:
record['size'] = len(record.name)</pre>
</div>
</page>
</notebook>
<footer> <footer>
<button name="action_add_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"/>

View File

@ -34,40 +34,27 @@ class DigestCustomRemove(models.TransientModel):
ir_ui_view_obj = self.env['ir.ui.view'] ir_ui_view_obj = self.env['ir.ui.view']
if self.remove_type == 'group': if self.remove_type == 'group':
find_view_id = self.available_group_name and self.available_group_name.split('_', 1)[0] or False 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") view_ids = ir_ui_view_obj.search([('inherit_id', 'child_of', int(find_view_id))], order="id desc")
field_list = [] field_list = []
for view_id in view_ids: for view_id in view_ids:
print("===view_id========", view_id)
root = ET.fromstring(view_id.arch_base) root = ET.fromstring(view_id.arch_base)
print("==========root=====", root)
for child in root.iter('group'): for child in root.iter('group'):
name = child.find('field') name = child.find('field')
if name.attrib and name.attrib.get('name', False): if name.attrib and name.attrib.get('name', False):
field_list.append(name.attrib.get('name', False)) field_list.append(name.attrib.get('name', False))
field_ids = ir_model_fields_obj.search([('name', 'in', field_list)]) field_ids = ir_model_fields_obj.search([('name', 'in', field_list)])
print("====field_ids====", field_ids, view_ids.ids)
view_ids.unlink() view_ids.unlink()
for field_id in field_ids: for field_id in field_ids:
ir_model_fields_obj.search([('depends', '=', field_id.name)]).unlink() ir_model_fields_obj.search([('depends', '=', field_id.name)]).unlink()
field_ids.unlink() field_ids.unlink()
else: else:
domain = expression.OR([('arch_db', 'like', record.name)] for record in self.field_id) 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) view_ids = ir_ui_view_obj.search(domain)
print("==========>>>>>>>>.", view_ids)
for view_id in 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) 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'): 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: if child.attrib and child.attrib.get('name', False) == self.field_id.name:
view_id.unlink() 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() ir_model_fields_obj.search([('depends', '=', self.field_id.name)]).unlink()
self.field_id.unlink() self.field_id.unlink()
return { return {

View File

@ -6,14 +6,15 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Customized Digest"> <form string="Customized Digest">
<group> <group>
<field name="remove_type"/> <group>
</group> <field name="remove_type"/>
<group> <field name="field_id" attrs="{'invisible': [('remove_type', '!=', 'field')], 'required': [('remove_type', '=', 'field')]}" options="{'no_create': True}"/>
<field name="field_id" attrs="{'invisible': [('remove_type', '!=', 'field')]}" options="{'no_create': True}"/> <field name="available_group_name" attrs="{'invisible': [('remove_type', '!=', 'group')], 'required': [('remove_type', '=', 'group')]}"/>
<field name="available_group_name" attrs="{'invisible': [('remove_type', '!=', 'group')]}"/> </group>
</group> </group>
<footer> <footer>
<button name="action_customize_digest_remove" string="Remove" type="object" class="btn btn-sm btn-primary"/> <button name="action_customize_digest_remove" string="Remove Field" attrs="{'invisible': [('remove_type', '!=', 'field')]}" type="object" class="btn btn-sm btn-primary"/>
<button name="action_customize_digest_remove" string="Remove Group" attrs="{'invisible': [('remove_type', '!=', 'group')]}" type="object" class="btn btn-sm btn-primary" confirm="Do you want remove all child related to this group?"/>
<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>