diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web/models/models.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web/models/models.py')
| -rw-r--r-- | addons/web/models/models.py | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/addons/web/models/models.py b/addons/web/models/models.py new file mode 100644 index 00000000..a882df71 --- /dev/null +++ b/addons/web/models/models.py @@ -0,0 +1,818 @@ +# -*- coding: utf-8 -*- +import babel.dates +import pytz +from lxml import etree +import base64 +import json + +from odoo import _, _lt, api, fields, models +from odoo.osv.expression import AND, TRUE_DOMAIN, normalize_domain +from odoo.tools import date_utils, lazy +from odoo.tools.misc import get_lang +from odoo.exceptions import UserError +from collections import defaultdict + +SEARCH_PANEL_ERROR_MESSAGE = _lt("Too many items to display.") + +def is_true_domain(domain): + return normalize_domain(domain) == TRUE_DOMAIN + + +class lazymapping(defaultdict): + def __missing__(self, key): + value = self.default_factory(key) + self[key] = value + return value + +DISPLAY_DATE_FORMATS = { + 'day': 'dd MMM yyyy', + 'week': "'W'w YYYY", + 'month': 'MMMM yyyy', + 'quarter': 'QQQ yyyy', + 'year': 'yyyy', +} + + +class IrActionsActWindowView(models.Model): + _inherit = 'ir.actions.act_window.view' + + view_mode = fields.Selection(selection_add=[ + ('qweb', 'QWeb') + ], ondelete={'qweb': 'cascade'}) + + +class Base(models.AbstractModel): + _inherit = 'base' + + @api.model + def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None): + """ + Performs a search_read and a search_count. + + :param domain: search domain + :param fields: list of fields to read + :param limit: maximum number of records to read + :param offset: number of records to skip + :param order: columns to sort results + :return: { + 'records': array of read records (result of a call to 'search_read') + 'length': number of records matching the domain (result of a call to 'search_count') + } + """ + records = self.search_read(domain, fields, offset=offset, limit=limit, order=order) + if not records: + return { + 'length': 0, + 'records': [] + } + if limit and (len(records) == limit or self.env.context.get('force_search_count')): + length = self.search_count(domain) + else: + length = len(records) + offset + return { + 'length': length, + 'records': records + } + + @api.model + def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False, + lazy=True, expand=False, expand_limit=None, expand_orderby=False): + """ + Returns the result of a read_group (and optionally search for and read records inside each + group), and the total number of groups matching the search domain. + + :param domain: search domain + :param fields: list of fields to read (see ``fields``` param of ``read_group``) + :param groupby: list of fields to group on (see ``groupby``` param of ``read_group``) + :param limit: see ``limit`` param of ``read_group`` + :param offset: see ``offset`` param of ``read_group`` + :param orderby: see ``orderby`` param of ``read_group`` + :param lazy: see ``lazy`` param of ``read_group`` + :param expand: if true, and groupby only contains one field, read records inside each group + :param expand_limit: maximum number of records to read in each group + :param expand_orderby: order to apply when reading records in each group + :return: { + 'groups': array of read groups + 'length': total number of groups + } + """ + groups = self._web_read_group(domain, fields, groupby, limit, offset, orderby, lazy, expand, + expand_limit, expand_orderby) + + if not groups: + length = 0 + elif limit and len(groups) == limit: + # We need to fetch all groups to know the total number + # this cannot be done all at once to avoid MemoryError + length = limit + chunk_size = 100000 + while True: + more = len(self.read_group(domain, ['display_name'], groupby, offset=length, limit=chunk_size, lazy=True)) + length += more + if more < chunk_size: + break + else: + length = len(groups) + offset + return { + 'groups': groups, + 'length': length + } + + @api.model + def _web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False, + lazy=True, expand=False, expand_limit=None, expand_orderby=False): + """ + Performs a read_group and optionally a web_search_read for each group. + See ``web_read_group`` for params description. + + :returns: array of groups + """ + groups = self.read_group(domain, fields, groupby, offset=offset, limit=limit, + orderby=orderby, lazy=lazy) + + if expand and len(groupby) == 1: + for group in groups: + group['__data'] = self.web_search_read(domain=group['__domain'], fields=fields, + offset=0, limit=expand_limit, + order=expand_orderby) + + return groups + + @api.model + def read_progress_bar(self, domain, group_by, progress_bar): + """ + Gets the data needed for all the kanban column progressbars. + These are fetched alongside read_group operation. + + :param domain - the domain used in the kanban view to filter records + :param group_by - the name of the field used to group records into + kanban columns + :param progress_bar - the <progressbar/> declaration attributes + (field, colors, sum) + :return a dictionnary mapping group_by values to dictionnaries mapping + progress bar field values to the related number of records + """ + group_by_fname = group_by.partition(':')[0] + field_type = self._fields[group_by_fname].type + if field_type == 'selection': + selection_labels = dict(self.fields_get()[group_by]['selection']) + + def adapt(value): + if field_type == 'selection': + value = selection_labels.get(value, False) + if type(value) == tuple: + value = value[1] # FIXME should use technical value (0) + return value + + result = {} + for group in self._read_progress_bar(domain, group_by, progress_bar): + group_by_value = str(adapt(group[group_by])) + field_value = group[progress_bar['field']] + if group_by_value not in result: + result[group_by_value] = dict.fromkeys(progress_bar['colors'], 0) + if field_value in result[group_by_value]: + result[group_by_value][field_value] += group['__count'] + return result + + def _read_progress_bar(self, domain, group_by, progress_bar): + """ Implementation of read_progress_bar() that returns results in the + format of read_group(). + """ + try: + fname = progress_bar['field'] + return self.read_group(domain, [fname], [group_by, fname], lazy=False) + except UserError: + # possibly failed because of grouping on or aggregating non-stored + # field; fallback on alternative implementation + pass + + # Workaround to match read_group's infrastructure + # TO DO in master: harmonize this function and readgroup to allow factorization + group_by_name = group_by.partition(':')[0] + group_by_modifier = group_by.partition(':')[2] or 'month' + + records_values = self.search_read(domain or [], [progress_bar['field'], group_by_name]) + field_type = self._fields[group_by_name].type + + for record_values in records_values: + group_by_value = record_values.pop(group_by_name) + + # Again, imitating what _read_group_format_result and _read_group_prepare_data do + if group_by_value and field_type in ['date', 'datetime']: + locale = get_lang(self.env).code + group_by_value = date_utils.start_of(fields.Datetime.to_datetime(group_by_value), group_by_modifier) + group_by_value = pytz.timezone('UTC').localize(group_by_value) + tz_info = None + if field_type == 'datetime' and self._context.get('tz') in pytz.all_timezones: + tz_info = self._context.get('tz') + group_by_value = babel.dates.format_datetime( + group_by_value, format=DISPLAY_DATE_FORMATS[group_by_modifier], + tzinfo=tz_info, locale=locale) + else: + group_by_value = babel.dates.format_date( + group_by_value, format=DISPLAY_DATE_FORMATS[group_by_modifier], + locale=locale) + + record_values[group_by] = group_by_value + record_values['__count'] = 1 + + return records_values + + ##### qweb view hooks ##### + @api.model + def qweb_render_view(self, view_id, domain): + assert view_id + return self.env['ir.qweb']._render( + view_id, { + **self.env['ir.ui.view']._prepare_qcontext(), + **self._qweb_prepare_qcontext(view_id, domain), + }) + + def _qweb_prepare_qcontext(self, view_id, domain): + """ + Base qcontext for rendering qweb views bound to this model + """ + return { + 'model': self, + 'domain': domain, + # not necessarily necessary as env is already part of the + # non-minimal qcontext + 'context': self.env.context, + 'records': lazy(self.search, domain), + } + + @api.model + def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): + r = super().fields_view_get(view_id, view_type, toolbar, submenu) + # avoid leaking the raw (un-rendered) template, also avoids bloating + # the response payload for no reason. Only send the root node, + # to send attributes such as `js_class`. + if r['type'] == 'qweb': + root = etree.fromstring(r['arch']) + r['arch'] = etree.tostring(etree.Element('qweb', root.attrib)) + return r + + @api.model + def _search_panel_field_image(self, field_name, **kwargs): + """ + Return the values in the image of the provided domain by field_name. + + :param model_domain: domain whose image is returned + :param extra_domain: extra domain to use when counting records associated with field values + :param field_name: the name of a field (type many2one or selection) + :param enable_counters: whether to set the key '__count' in image values + :param only_counters: whether to retrieve information on the model_domain image or only + counts based on model_domain and extra_domain. In the later case, + the counts are set whatever is enable_counters. + :param limit: integer, maximal number of values to fetch + :param set_limit: boolean, whether to use the provided limit (if any) + :return: a dict of the form + { + id: { 'id': id, 'display_name': display_name, ('__count': c,) }, + ... + } + """ + + enable_counters = kwargs.get('enable_counters') + only_counters = kwargs.get('only_counters') + extra_domain = kwargs.get('extra_domain', []) + no_extra = is_true_domain(extra_domain) + model_domain = kwargs.get('model_domain', []) + count_domain = AND([model_domain, extra_domain]) + + limit = kwargs.get('limit') + set_limit = kwargs.get('set_limit') + + if only_counters: + return self._search_panel_domain_image(field_name, count_domain, True) + + model_domain_image = self._search_panel_domain_image(field_name, model_domain, + enable_counters and no_extra, + set_limit and limit, + ) + if enable_counters and not no_extra: + count_domain_image = self._search_panel_domain_image(field_name, count_domain, True) + for id, values in model_domain_image.items(): + element = count_domain_image.get(id) + values['__count'] = element['__count'] if element else 0 + + return model_domain_image + + @api.model + def _search_panel_domain_image(self, field_name, domain, set_count=False, limit=False): + """ + Return the values in the image of the provided domain by field_name. + + :param domain: domain whose image is returned + :param field_name: the name of a field (type many2one or selection) + :param set_count: whether to set the key '__count' in image values. Default is False. + :param limit: integer, maximal number of values to fetch. Default is False. + :return: a dict of the form + { + id: { 'id': id, 'display_name': display_name, ('__count': c,) }, + ... + } + """ + field = self._fields[field_name] + if field.type == 'many2one': + def group_id_name(value): + return value + + else: + # field type is selection: see doc above + desc = self.fields_get([field_name])[field_name] + field_name_selection = dict(desc['selection']) + + def group_id_name(value): + return value, field_name_selection[value] + + domain = AND([ + domain, + [(field_name, '!=', False)], + ]) + groups = self.read_group(domain, [field_name], [field_name], limit=limit) + + domain_image = {} + for group in groups: + id, display_name = group_id_name(group[field_name]) + values = { + 'id': id, + 'display_name': display_name, + } + if set_count: + values['__count'] = group[field_name + '_count'] + domain_image[id] = values + + return domain_image + + + @api.model + def _search_panel_global_counters(self, values_range, parent_name): + """ + Modify in place values_range to transform the (local) counts + into global counts (local count + children local counts) + in case a parent field parent_name has been set on the range values. + Note that we save the initial (local) counts into an auxiliary dict + before they could be changed in the for loop below. + + :param values_range: dict of the form + { + id: { 'id': id, '__count': c, parent_name: parent_id, ... } + ... + } + :param parent_name: string, indicates which key determines the parent + """ + local_counters = lazymapping(lambda id: values_range[id]['__count']) + + for id in values_range: + values = values_range[id] + # here count is the initial value = local count set on values + count = local_counters[id] + if count: + parent_id = values[parent_name] + while parent_id: + values = values_range[parent_id] + local_counters[parent_id] + values['__count'] += count + parent_id = values[parent_name] + + @api.model + def _search_panel_sanitized_parent_hierarchy(self, records, parent_name, ids): + """ + Filter the provided list of records to ensure the following properties of + the resulting sublist: + 1) it is closed for the parent relation + 2) every record in it is an ancestor of a record with id in ids + (if ids = records.ids, that condition is automatically satisfied) + 3) it is maximal among other sublists with properties 1 and 2. + + :param records, the list of records to filter, the records must have the form + { 'id': id, parent_name: False or (id, display_name),... } + :param parent_name, string, indicates which key determines the parent + :param ids: list of record ids + :return: the sublist of records with the above properties + } + """ + def get_parent_id(record): + value = record[parent_name] + return value and value[0] + + allowed_records = { record['id']: record for record in records } + records_to_keep = {} + for id in ids: + record_id = id + ancestor_chain = {} + chain_is_fully_included = True + while chain_is_fully_included and record_id: + known_status = records_to_keep.get(record_id) + if known_status != None: + # the record and its known ancestors have already been considered + chain_is_fully_included = known_status + break + record = allowed_records.get(record_id) + if record: + ancestor_chain[record_id] = record + record_id = get_parent_id(record) + else: + chain_is_fully_included = False + + for id, record in ancestor_chain.items(): + records_to_keep[id] = chain_is_fully_included + + # we keep initial order + return [rec for rec in records if records_to_keep.get(rec['id'])] + + + @api.model + def _search_panel_selection_range(self, field_name, **kwargs): + """ + Return the values of a field of type selection possibly enriched + with counts of associated records in domain. + + :param enable_counters: whether to set the key '__count' on values returned. + Default is False. + :param expand: whether to return the full range of values for the selection + field or only the field image values. Default is False. + :param field_name: the name of a field of type selection + :param model_domain: domain used to determine the field image values and counts. + Default is []. + :return: a list of dicts of the form + { 'id': id, 'display_name': display_name, ('__count': c,) } + with key '__count' set if enable_counters is True + """ + + + enable_counters = kwargs.get('enable_counters') + expand = kwargs.get('expand') + + if enable_counters or not expand: + domain_image = self._search_panel_field_image(field_name, only_counters=expand, **kwargs) + + if not expand: + return list(domain_image.values()) + + selection = self.fields_get([field_name])[field_name]['selection'] + + selection_range = [] + for value, label in selection: + values = { + 'id': value, + 'display_name': label, + } + if enable_counters: + image_element = domain_image.get(value) + values['__count'] = image_element['__count'] if image_element else 0 + selection_range.append(values) + + return selection_range + + + @api.model + def search_panel_select_range(self, field_name, **kwargs): + """ + Return possible values of the field field_name (case select="one"), + possibly with counters, and the parent field (if any and required) + used to hierarchize them. + + :param field_name: the name of a field; + of type many2one or selection. + :param category_domain: domain generated by categories. Default is []. + :param comodel_domain: domain of field values (if relational). Default is []. + :param enable_counters: whether to count records by value. Default is False. + :param expand: whether to return the full range of field values in comodel_domain + or only the field image values (possibly filtered and/or completed + with parents if hierarchize is set). Default is False. + :param filter_domain: domain generated by filters. Default is []. + :param hierarchize: determines if the categories must be displayed hierarchically + (if possible). If set to true and _parent_name is set on the + comodel field, the information necessary for the hierarchization will + be returned. Default is True. + :param limit: integer, maximal number of values to fetch. Default is None. + :param search_domain: base domain of search. Default is []. + with parents if hierarchize is set) + :return: { + 'parent_field': parent field on the comodel of field, or False + 'values': array of dictionaries containing some info on the records + available on the comodel of the field 'field_name'. + The display name, the __count (how many records with that value) + and possibly parent_field are fetched. + } + or an object with an error message when limit is defined and is reached. + """ + field = self._fields[field_name] + supported_types = ['many2one', 'selection'] + if field.type not in supported_types: + types = dict(self.env["ir.model.fields"]._fields["ttype"]._description_selection(self.env)) + raise UserError(_( + 'Only types %(supported_types)s are supported for category (found type %(field_type)s)', + supported_types=", ".join(types[t] for t in supported_types), + field_type=types[field.type], + )) + + model_domain = kwargs.get('search_domain', []) + extra_domain = AND([ + kwargs.get('category_domain', []), + kwargs.get('filter_domain', []), + ]) + + if field.type == 'selection': + return { + 'parent_field': False, + 'values': self._search_panel_selection_range(field_name, model_domain=model_domain, + extra_domain=extra_domain, **kwargs + ), + } + + Comodel = self.env[field.comodel_name].with_context(hierarchical_naming=False) + field_names = ['display_name'] + hierarchize = kwargs.get('hierarchize', True) + parent_name = False + if hierarchize and Comodel._parent_name in Comodel._fields: + parent_name = Comodel._parent_name + field_names.append(parent_name) + + def get_parent_id(record): + value = record[parent_name] + return value and value[0] + else: + hierarchize = False + + comodel_domain = kwargs.get('comodel_domain', []) + enable_counters = kwargs.get('enable_counters') + expand = kwargs.get('expand') + limit = kwargs.get('limit') + + if enable_counters or not expand: + domain_image = self._search_panel_field_image(field_name, + model_domain=model_domain, extra_domain=extra_domain, + only_counters=expand, + set_limit= limit and not (expand or hierarchize or comodel_domain), **kwargs + ) + + if not (expand or hierarchize or comodel_domain): + values = list(domain_image.values()) + if limit and len(values) == limit: + return {'error_msg': str(SEARCH_PANEL_ERROR_MESSAGE)} + return { + 'parent_field': parent_name, + 'values': values, + } + + if not expand: + image_element_ids = list(domain_image.keys()) + if hierarchize: + condition = [('id', 'parent_of', image_element_ids)] + else: + condition = [('id', 'in', image_element_ids)] + comodel_domain = AND([comodel_domain, condition]) + comodel_records = Comodel.search_read(comodel_domain, field_names, limit=limit) + + if hierarchize: + ids = [rec['id'] for rec in comodel_records] if expand else image_element_ids + comodel_records = self._search_panel_sanitized_parent_hierarchy(comodel_records, parent_name, ids) + + if limit and len(comodel_records) == limit: + return {'error_msg': str(SEARCH_PANEL_ERROR_MESSAGE)} + + field_range = {} + for record in comodel_records: + record_id = record['id'] + values = { + 'id': record_id, + 'display_name': record['display_name'], + } + if hierarchize: + values[parent_name] = get_parent_id(record) + if enable_counters: + image_element = domain_image.get(record_id) + values['__count'] = image_element['__count'] if image_element else 0 + field_range[record_id] = values + + if hierarchize and enable_counters: + self._search_panel_global_counters(field_range, parent_name) + + return { + 'parent_field': parent_name, + 'values': list(field_range.values()), + } + + + @api.model + def search_panel_select_multi_range(self, field_name, **kwargs): + """ + Return possible values of the field field_name (case select="multi"), + possibly with counters and groups. + + :param field_name: the name of a filter field; + possible types are many2one, many2many, selection. + :param category_domain: domain generated by categories. Default is []. + :param comodel_domain: domain of field values (if relational) + (this parameter is used in _search_panel_range). Default is []. + :param enable_counters: whether to count records by value. Default is False. + :param expand: whether to return the full range of field values in comodel_domain + or only the field image values. Default is False. + :param filter_domain: domain generated by filters. Default is []. + :param group_by: extra field to read on comodel, to group comodel records + :param group_domain: dict, one domain for each activated group + for the group_by (if any). Those domains are + used to fech accurate counters for values in each group. + Default is [] (many2one case) or None. + :param limit: integer, maximal number of values to fetch. Default is None. + :param search_domain: base domain of search. Default is []. + :return: { + 'values': a list of possible values, each being a dict with keys + 'id' (value), + 'name' (value label), + '__count' (how many records with that value), + 'group_id' (value of group), set if a group_by has been provided, + 'group_name' (label of group), set if a group_by has been provided + } + or an object with an error message when limit is defined and reached. + """ + field = self._fields[field_name] + supported_types = ['many2one', 'many2many', 'selection'] + if field.type not in supported_types: + raise UserError(_('Only types %(supported_types)s are supported for filter (found type %(field_type)s)', + supported_types=supported_types, field_type=field.type)) + + model_domain = kwargs.get('search_domain', []) + extra_domain = AND([ + kwargs.get('category_domain', []), + kwargs.get('filter_domain', []), + ]) + + if field.type == 'selection': + return { + 'values': self._search_panel_selection_range(field_name, model_domain=model_domain, + extra_domain=extra_domain, **kwargs + ) + } + + Comodel = self.env.get(field.comodel_name).with_context(hierarchical_naming=False) + field_names = ['display_name'] + group_by = kwargs.get('group_by') + limit = kwargs.get('limit') + if group_by: + group_by_field = Comodel._fields[group_by] + + field_names.append(group_by) + + if group_by_field.type == 'many2one': + def group_id_name(value): + return value or (False, _("Not Set")) + + elif group_by_field.type == 'selection': + desc = Comodel.fields_get([group_by])[group_by] + group_by_selection = dict(desc['selection']) + group_by_selection[False] = _("Not Set") + + def group_id_name(value): + return value, group_by_selection[value] + + else: + def group_id_name(value): + return (value, value) if value else (False, _("Not Set")) + + comodel_domain = kwargs.get('comodel_domain', []) + enable_counters = kwargs.get('enable_counters') + expand = kwargs.get('expand') + + if field.type == 'many2many': + comodel_records = Comodel.search_read(comodel_domain, field_names, limit=limit) + if expand and limit and len(comodel_records) == limit: + return {'error_msg': str(SEARCH_PANEL_ERROR_MESSAGE)} + + group_domain = kwargs.get('group_domain') + field_range = [] + for record in comodel_records: + record_id = record['id'] + values= { + 'id': record_id, + 'display_name': record['display_name'], + } + if group_by: + group_id, group_name = group_id_name(record[group_by]) + values['group_id'] = group_id + values['group_name'] = group_name + + if enable_counters or not expand: + search_domain = AND([ + model_domain, + [(field_name, 'in', record_id)], + ]) + local_extra_domain = extra_domain + if group_by and group_domain: + local_extra_domain = AND([ + local_extra_domain, + group_domain.get(json.dumps(group_id), []), + ]) + search_count_domain = AND([ + search_domain, + local_extra_domain + ]) + if enable_counters: + count = self.search_count(search_count_domain) + if not expand: + if enable_counters and is_true_domain(local_extra_domain): + inImage = count + else: + inImage = self.search(search_domain, limit=1) + + if expand or inImage: + if enable_counters: + values['__count'] = count + field_range.append(values) + + if not expand and limit and len(field_range) == limit: + return {'error_msg': str(SEARCH_PANEL_ERROR_MESSAGE)} + + return { 'values': field_range, } + + if field.type == 'many2one': + if enable_counters or not expand: + extra_domain = AND([ + extra_domain, + kwargs.get('group_domain', []), + ]) + domain_image = self._search_panel_field_image(field_name, + model_domain=model_domain, extra_domain=extra_domain, + only_counters=expand, + set_limit=limit and not (expand or group_by or comodel_domain), **kwargs + ) + + if not (expand or group_by or comodel_domain): + values = list(domain_image.values()) + if limit and len(values) == limit: + return {'error_msg': str(SEARCH_PANEL_ERROR_MESSAGE)} + return {'values': values, } + + if not expand: + image_element_ids = list(domain_image.keys()) + comodel_domain = AND([ + comodel_domain, + [('id', 'in', image_element_ids)], + ]) + comodel_records = Comodel.search_read(comodel_domain, field_names, limit=limit) + if limit and len(comodel_records) == limit: + return {'error_msg': str(SEARCH_PANEL_ERROR_MESSAGE)} + + field_range = [] + for record in comodel_records: + record_id = record['id'] + values= { + 'id': record_id, + 'display_name': record['display_name'], + } + + if group_by: + group_id, group_name = group_id_name(record[group_by]) + values['group_id'] = group_id + values['group_name'] = group_name + + if enable_counters: + image_element = domain_image.get(record_id) + values['__count'] = image_element['__count'] if image_element else 0 + + field_range.append(values) + + return { 'values': field_range, } + + +class ResCompany(models.Model): + _inherit = 'res.company' + + @api.model + def create(self, values): + res = super().create(values) + style_fields = {'external_report_layout_id', 'font', 'primary_color', 'secondary_color'} + if not style_fields.isdisjoint(values): + self._update_asset_style() + return res + + def write(self, values): + res = super().write(values) + style_fields = {'external_report_layout_id', 'font', 'primary_color', 'secondary_color'} + if not style_fields.isdisjoint(values): + self._update_asset_style() + return res + + def _get_asset_style_b64(self): + template_style = self.env.ref('web.styles_company_report', raise_if_not_found=False) + if not template_style: + return b'' + # One bundle for everyone, so this method + # necessarily updates the style for every company at once + company_ids = self.sudo().search([]) + company_styles = template_style._render({ + 'company_ids': company_ids, + }) + return base64.b64encode((company_styles)) + + def _update_asset_style(self): + asset_attachment = self.env.ref('web.asset_styles_company_report', raise_if_not_found=False) + if not asset_attachment: + return + asset_attachment = asset_attachment.sudo() + b64_val = self._get_asset_style_b64() + if b64_val != asset_attachment.datas: + asset_attachment.write({'datas': b64_val}) |
