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.
		
		
		
		
		
			
		
			
				
					
					
						
							307 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							307 lines
						
					
					
						
							13 KiB
						
					
					
				
								# -*- coding: utf-8 -*-
							 | 
						|
								"""This model is used to detect, which all options want to hide from the
							 | 
						|
								    specified group and model"""
							 | 
						|
								#############################################################################
							 | 
						|
								#
							 | 
						|
								#    Cybrosys Technologies Pvt. Ltd.
							 | 
						|
								#
							 | 
						|
								#    Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
							 | 
						|
								#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
							 | 
						|
								#
							 | 
						|
								#    You can modify it under the terms of the GNU LESSER
							 | 
						|
								#    GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
							 | 
						|
								#
							 | 
						|
								#    This program is distributed in the hope that it will be useful,
							 | 
						|
								#    but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						|
								#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
							 | 
						|
								#    GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
							 | 
						|
								#
							 | 
						|
								#    You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
							 | 
						|
								#    (LGPL v3) along with this program.
							 | 
						|
								#    If not, see <http://www.gnu.org/licenses/>.
							 | 
						|
								#
							 | 
						|
								#############################################################################
							 | 
						|
								from collections import defaultdict
							 | 
						|
								from operator import attrgetter
							 | 
						|
								from odoo import api, _
							 | 
						|
								from odoo.exceptions import UserError
							 | 
						|
								from odoo.models import BaseModel, _unlink, LOG_ACCESS_COLUMNS, \
							 | 
						|
								    INSERT_BATCH_SIZE, SQL_DEFAULT
							 | 
						|
								from odoo.tools import OrderedSet, split_every, clean_context, SQL
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								@api.model
							 | 
						|
								def _create(self, data_list):
							 | 
						|
								    """ Create records from the stored field values in ``data_list``. """
							 | 
						|
								    assert data_list
							 | 
						|
								    cr = self.env.cr
							 | 
						|
								
							 | 
						|
								    # insert rows in batches of maximum INSERT_BATCH_SIZE
							 | 
						|
								    ids = []                                # ids of created records
							 | 
						|
								    other_fields = OrderedSet()             # non-column fields
							 | 
						|
								
							 | 
						|
								    for data_sublist in split_every(INSERT_BATCH_SIZE, data_list):
							 | 
						|
								        stored_list = [data['stored'] for data in data_sublist]
							 | 
						|
								        fnames = sorted({name for stored in stored_list for name in stored})
							 | 
						|
								
							 | 
						|
								        columns = []
							 | 
						|
								        rows = [[] for _ in stored_list]
							 | 
						|
								        for fname in fnames:
							 | 
						|
								            field = self._fields[fname]
							 | 
						|
								            if field.column_type:
							 | 
						|
								                columns.append(fname)
							 | 
						|
								                for stored, row in zip(stored_list, rows):
							 | 
						|
								                    if fname in stored:
							 | 
						|
								                        colval = field.convert_to_column(stored[fname], self, stored)
							 | 
						|
								                        if field.translate is True and colval:
							 | 
						|
								                            if 'en_US' not in colval.adapted:
							 | 
						|
								                                colval.adapted['en_US'] = next(iter(colval.adapted.values()))
							 | 
						|
								                        row.append(colval)
							 | 
						|
								                    else:
							 | 
						|
								                        row.append(SQL_DEFAULT)
							 | 
						|
								            else:
							 | 
						|
								                other_fields.add(field)
							 | 
						|
								
							 | 
						|
								            if field.type == 'properties':
							 | 
						|
								                # force calling fields.create for properties field because
							 | 
						|
								                # we might want to update the parent definition
							 | 
						|
								                other_fields.add(field)
							 | 
						|
								
							 | 
						|
								        if not columns:
							 | 
						|
								            # manage the case where we create empty records
							 | 
						|
								            columns = ['id']
							 | 
						|
								            for row in rows:
							 | 
						|
								                row.append(SQL_DEFAULT)
							 | 
						|
								
							 | 
						|
								        cr.execute(SQL(
							 | 
						|
								            'INSERT INTO %s (%s) VALUES %s RETURNING "id"',
							 | 
						|
								            SQL.identifier(self._table),
							 | 
						|
								            SQL(', ').join(map(SQL.identifier, columns)),
							 | 
						|
								            SQL(', ').join(tuple(row) for row in rows),
							 | 
						|
								        ))
							 | 
						|
								        ids.extend(id_ for id_, in cr.fetchall())
							 | 
						|
								
							 | 
						|
								    # put the new records in cache, and update inverse fields, for many2one
							 | 
						|
								    #
							 | 
						|
								    # cachetoclear is an optimization to avoid modified()'s cost until other_fields are processed
							 | 
						|
								    cachetoclear = []
							 | 
						|
								    records = self.browse(ids)
							 | 
						|
								    inverses_update = defaultdict(list)     # {(field, value): ids}
							 | 
						|
								    common_set_vals = set(LOG_ACCESS_COLUMNS + ['id', 'parent_path'])
							 | 
						|
								    for data, record in zip(data_list, records):
							 | 
						|
								        data['record'] = record
							 | 
						|
								        # DLE P104: test_inherit.py, test_50_search_one2many
							 | 
						|
								        vals = dict({k: v for d in data['inherited'].values() for k, v in d.items()}, **data['stored'])
							 | 
						|
								        set_vals = common_set_vals.union(vals)
							 | 
						|
								        for field in self._fields.values():
							 | 
						|
								            if field.type in ('one2many', 'many2many'):
							 | 
						|
								                self.env.cache.set(record, field, ())
							 | 
						|
								            elif field.related and not field.column_type:
							 | 
						|
								                self.env.cache.set(record, field, field.convert_to_cache(None, record))
							 | 
						|
								            # DLE P123: `test_adv_activity`, `test_message_assignation_inbox`, `test_message_log`, `test_create_mail_simple`, ...
							 | 
						|
								            # Set `mail.message.parent_id` to False in cache so it doesn't do the useless SELECT when computing the modified of `child_ids`
							 | 
						|
								            # in other words, if `parent_id` is not set, no other message `child_ids` are impacted.
							 | 
						|
								            # + avoid the fetch of fields which are False. e.g. if a boolean field is not passed in vals and as no default set in the field attributes,
							 | 
						|
								            # then we know it can be set to False in the cache in the case of a create.
							 | 
						|
								            elif field.store and field.name not in set_vals and not field.compute:
							 | 
						|
								                self.env.cache.set(record, field, field.convert_to_cache(None, record))
							 | 
						|
								        for fname, value in vals.items():
							 | 
						|
								            field = self._fields[fname]
							 | 
						|
								            if field.type in ('one2many', 'many2many'):
							 | 
						|
								                cachetoclear.append((record, field))
							 | 
						|
								            else:
							 | 
						|
								                cache_value = field.convert_to_cache(value, record)
							 | 
						|
								                self.env.cache.set(record, field, cache_value)
							 | 
						|
								                if field.type in ('many2one', 'many2one_reference') and self.pool.field_inverses[field]:
							 | 
						|
								                    inverses_update[(field, cache_value)].append(record.id)
							 | 
						|
								
							 | 
						|
								    for (field, value), record_ids in inverses_update.items():
							 | 
						|
								        field._update_inverses(self.browse(record_ids), value)
							 | 
						|
								
							 | 
						|
								    # update parent_path
							 | 
						|
								    records._parent_store_create()
							 | 
						|
								
							 | 
						|
								    # protect fields being written against recomputation
							 | 
						|
								    protected = [(data['protected'], data['record']) for data in data_list]
							 | 
						|
								    with self.env.protecting(protected):
							 | 
						|
								        # mark computed fields as todo
							 | 
						|
								        records.modified(self._fields, create=True)
							 | 
						|
								
							 | 
						|
								        if other_fields:
							 | 
						|
								            # discard default values from context for other fields
							 | 
						|
								            others = records.with_context(clean_context(self._context))
							 | 
						|
								            for field in sorted(other_fields, key=attrgetter('_sequence')):
							 | 
						|
								                field.create([
							 | 
						|
								                    (other, data['stored'][field.name])
							 | 
						|
								                    for other, data in zip(others, data_list)
							 | 
						|
								                    if field.name in data['stored']
							 | 
						|
								                ])
							 | 
						|
								
							 | 
						|
								            # mark fields to recompute
							 | 
						|
								            records.modified([field.name for field in other_fields], create=True)
							 | 
						|
								
							 | 
						|
								        # if value in cache has not been updated by other_fields, remove it
							 | 
						|
								        for record, field in cachetoclear:
							 | 
						|
								            if self.env.cache.contains(record, field) and not self.env.cache.get(record, field):
							 | 
						|
								                self.env.cache.remove(record, field)
							 | 
						|
								
							 | 
						|
								    # check Python constraints for stored fields
							 | 
						|
								    records._validate_fields(name for data in data_list for name in data['stored'])
							 | 
						|
								    records.check_access_rule('create')
							 | 
						|
								    # This is used to restrict the access right to create a record
							 | 
						|
								    current_model_id = self.env['ir.model'].sudo().search(
							 | 
						|
								        [('model', '=', self._name)]).id
							 | 
						|
								    access_right_rec = self.env['access.right'].sudo().search_read(
							 | 
						|
								        [('model_id', '=', current_model_id)],
							 | 
						|
								        ['model_id', 'is_create_or_update',
							 | 
						|
								         'groups_id'])
							 | 
						|
								    if access_right_rec and not self.env.is_admin():
							 | 
						|
								        for rec in access_right_rec:
							 | 
						|
								            group_name = self.env['ir.model.data'].sudo().search([
							 | 
						|
								                ('model', '=', 'res.groups'),
							 | 
						|
								                ('res_id', '=', rec['groups_id'][0])
							 | 
						|
								            ]).name
							 | 
						|
								            module_name = self.env['ir.model.data'].sudo().search([
							 | 
						|
								                ('model', '=', 'res.groups'),
							 | 
						|
								                ('res_id', '=', rec['groups_id'][0])
							 | 
						|
								            ]).module
							 | 
						|
								            group = module_name + "." + group_name
							 | 
						|
								            if self.env.user.has_group(group):
							 | 
						|
								                if rec['is_create_or_update']:
							 | 
						|
								                    raise UserError('You are restricted from performing this'
							 | 
						|
								                                      ' operation. Please contact the'
							 | 
						|
								                                      ' administrator.')
							 | 
						|
								    return records
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								@api.model
							 | 
						|
								def unlink(self):
							 | 
						|
								    """ unlink()
							 | 
						|
								
							 | 
						|
								            Deletes the records in ``self``.
							 | 
						|
								
							 | 
						|
								            :raise AccessError: if the user is not allowed to delete all the given records
							 | 
						|
								            :raise UserError: if the record is default property for other records
							 | 
						|
								            """
							 | 
						|
								    if not self:
							 | 
						|
								        return True
							 | 
						|
								
							 | 
						|
								    self.check_access_rights('unlink')
							 | 
						|
								    self.check_access_rule('unlink')
							 | 
						|
								
							 | 
						|
								    from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
							 | 
						|
								    for func in self._ondelete_methods:
							 | 
						|
								        # func._ondelete is True if it should be called during uninstallation
							 | 
						|
								        if func._ondelete or not self._context.get(MODULE_UNINSTALL_FLAG):
							 | 
						|
								            func(self)
							 | 
						|
								
							 | 
						|
								    # TOFIX: this avoids an infinite loop when trying to recompute a
							 | 
						|
								    # field, which triggers the recomputation of another field using the
							 | 
						|
								    # same compute function, which then triggers again the computation
							 | 
						|
								    # of those two fields
							 | 
						|
								    for field in self._fields.values():
							 | 
						|
								        self.env.remove_to_compute(field, self)
							 | 
						|
								
							 | 
						|
								    self.env.flush_all()
							 | 
						|
								
							 | 
						|
								    cr = self._cr
							 | 
						|
								    Data = self.env['ir.model.data'].sudo().with_context({})
							 | 
						|
								    Defaults = self.env['ir.default'].sudo()
							 | 
						|
								    Property = self.env['ir.property'].sudo()
							 | 
						|
								    Attachment = self.env['ir.attachment'].sudo()
							 | 
						|
								    ir_property_unlink = Property
							 | 
						|
								    ir_model_data_unlink = Data
							 | 
						|
								    ir_attachment_unlink = Attachment
							 | 
						|
								
							 | 
						|
								    # mark fields that depend on 'self' to recompute them after 'self' has
							 | 
						|
								    # been deleted (like updating a sum of lines after deleting one line)
							 | 
						|
								    with self.env.protecting(self._fields.values(), self):
							 | 
						|
								        self.modified(self._fields, before=True)
							 | 
						|
								
							 | 
						|
								    for sub_ids in cr.split_for_in_conditions(self.ids):
							 | 
						|
								        records = self.browse(sub_ids)
							 | 
						|
								
							 | 
						|
								        # Check if the records are used as default properties.
							 | 
						|
								        refs = [f'{self._name},{id_}' for id_ in sub_ids]
							 | 
						|
								        default_properties = Property.search(
							 | 
						|
								            [('res_id', '=', False), ('value_reference', 'in', refs)])
							 | 
						|
								        if not self._context.get(MODULE_UNINSTALL_FLAG) and default_properties:
							 | 
						|
								            raise UserError(
							 | 
						|
								                _('Unable to delete this document because it is used as a default property'))
							 | 
						|
								        else:
							 | 
						|
								            ir_property_unlink |= default_properties
							 | 
						|
								
							 | 
						|
								        # Delete the records' properties.
							 | 
						|
								        ir_property_unlink |= Property.search([('res_id', 'in', refs)])
							 | 
						|
								
							 | 
						|
								        cr.execute(SQL(
							 | 
						|
								            "DELETE FROM %s WHERE id IN %s",
							 | 
						|
								            SQL.identifier(self._table), sub_ids,
							 | 
						|
								        ))
							 | 
						|
								
							 | 
						|
								        # Removing the ir_model_data reference if the record being deleted
							 | 
						|
								        # is a record created by xml/csv file, as these are not connected
							 | 
						|
								        # with real database foreign keys, and would be dangling references.
							 | 
						|
								        #
							 | 
						|
								        # Note: the following steps are performed as superuser to avoid
							 | 
						|
								        # access rights restrictions, and with no context to avoid possible
							 | 
						|
								        # side-effects during admin calls.
							 | 
						|
								        data = Data.search(
							 | 
						|
								            [('model', '=', self._name), ('res_id', 'in', sub_ids)])
							 | 
						|
								        ir_model_data_unlink |= data
							 | 
						|
								
							 | 
						|
								        # For the same reason, remove the defaults having some of the
							 | 
						|
								        # records as value
							 | 
						|
								        Defaults.discard_records(records)
							 | 
						|
								
							 | 
						|
								        # For the same reason, remove the relevant records in ir_attachment
							 | 
						|
								        # (the search is performed with sql as the search method of
							 | 
						|
								        # ir_attachment is overridden to hide attachments of deleted
							 | 
						|
								        # records)
							 | 
						|
								        cr.execute(SQL(
							 | 
						|
								            "SELECT id FROM ir_attachment WHERE res_model=%s AND res_id IN %s",
							 | 
						|
								            self._name, sub_ids,
							 | 
						|
								        ))
							 | 
						|
								        ir_attachment_unlink |= Attachment.browse(
							 | 
						|
								            row[0] for row in cr.fetchall())
							 | 
						|
								
							 | 
						|
								    # invalidate the *whole* cache, since the orm does not handle all
							 | 
						|
								    # changes made in the database, like cascading delete!
							 | 
						|
								    self.env.invalidate_all(flush=False)
							 | 
						|
								    if ir_property_unlink:
							 | 
						|
								        ir_property_unlink.unlink()
							 | 
						|
								    if ir_model_data_unlink:
							 | 
						|
								        ir_model_data_unlink.unlink()
							 | 
						|
								    if ir_attachment_unlink:
							 | 
						|
								        ir_attachment_unlink.unlink()
							 | 
						|
								
							 | 
						|
								    # auditing: deletions are infrequent and leave no trace in the database
							 | 
						|
								    _unlink.info('User #%s deleted %s records with IDs: %r', self._uid,
							 | 
						|
								                 self._name, self.ids)
							 | 
						|
								    # This is used to restrict the access right to unlink a record
							 | 
						|
								    current_model_id = self.env['ir.model'].sudo().search(
							 | 
						|
								        [('model', '=', self._name)]).id
							 | 
						|
								    access_right_rec = self.env['access.right'].sudo().search_read(
							 | 
						|
								        [('model_id', '=', current_model_id)], ['model_id', 'is_delete',
							 | 
						|
								                                                'groups_id'])
							 | 
						|
								    if access_right_rec and not self.env.is_admin():
							 | 
						|
								        for rec in access_right_rec:
							 | 
						|
								            group_name = self.env['ir.model.data'].sudo().search([
							 | 
						|
								                ('model', '=', 'res.groups'),
							 | 
						|
								                ('res_id', '=', rec['groups_id'][0])
							 | 
						|
								            ]).name
							 | 
						|
								            module_name = self.env['ir.model.data'].sudo().search([
							 | 
						|
								                ('model', '=', 'res.groups'),
							 | 
						|
								                ('res_id', '=', rec['groups_id'][0])
							 | 
						|
								            ]).module
							 | 
						|
								            group = module_name + "." + group_name
							 | 
						|
								            if self.env.user.has_group(group):
							 | 
						|
								                if rec['is_delete']:
							 | 
						|
								                    raise UserError(_('You are restricted from performing this'
							 | 
						|
								                                      ' operation. Please contact the'
							 | 
						|
								                                      ' administrator.'))
							 | 
						|
								    return True
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								BaseModel._create = _create
							 | 
						|
								BaseModel.unlink = unlink
							 | 
						|
								
							 |