Esempio n. 1
0
    def _gc_file_store(self):
        """ Perform the garbage collection of the filestore. """
        if self._storage() != 'file':
            return

        # Continue in a new transaction. The LOCK statement below must be the
        # first one in the current transaction, otherwise the database snapshot
        # used by it may not contain the most recent changes made to the table
        # ir_attachment! Indeed, if concurrent transactions create attachments,
        # the LOCK statement will wait until those concurrent transactions end.
        # But this transaction will not see the new attachements if it has done
        # other requests before the LOCK (like the method _storage() above).
        cr = self._cr
        cr.commit()

        # prevent all concurrent updates on ir_attachment while collecting,
        # but only attempt to grab the lock for a little bit, otherwise it'd
        # start blocking other transactions. (will be retried later anyway)
        cr.execute("SET LOCAL lock_timeout TO '10s'")
        cr.execute("LOCK ir_attachment IN SHARE MODE")

        # retrieve the file names from the checklist
        checklist = {}
        for dirpath, _, filenames in os.walk(self._full_path('checklist')):
            dirname = os.path.basename(dirpath)
            for filename in filenames:
                fname = "%s/%s" % (dirname, filename)
                checklist[fname] = os.path.join(dirpath, filename)

        # Clean up the checklist. The checklist is split in chunks and files are garbage-collected
        # for each chunk.
        removed = 0
        for names in cr.split_for_in_conditions(checklist):
            # determine which files to keep among the checklist
            cr.execute(
                "SELECT store_fname FROM ir_attachment WHERE store_fname IN %s",
                [names])
            whitelist = set(row[0] for row in cr.fetchall())

            # remove garbage files, and clean up checklist
            for fname in names:
                filepath = checklist[fname]
                if fname not in whitelist:
                    try:
                        os.unlink(self._full_path(fname))
                        _logger.debug("_file_gc unlinked %s",
                                      self._full_path(fname))
                        removed += 1
                    except (OSError, IOError):
                        _logger.info("_file_gc could not unlink %s",
                                     self._full_path(fname),
                                     exc_info=True)
                with tools.ignore(OSError):
                    os.unlink(filepath)

        # commit to release the lock
        cr.commit()
        _logger.info("filestore gc %d checked, %d removed", len(checklist),
                     removed)
Esempio n. 2
0
 def _value_unpickle(self):
     for record in self:
         value = record.value
         if record.key == 'default' and value:
             # default values are pickled on the fly
             with tools.ignore(Exception):
                 value = str(pickle.loads(value))
         record.value_unpickle = value
Esempio n. 3
0
 def _value_unpickle(self):
     for record in self:
         value = record.value
         if record.key == 'default' and value:
             # default values are pickled on the fly
             with tools.ignore(Exception):
                 value = str(pickle.loads(value))
         record.value_unpickle = value
Esempio n. 4
0
 def transitive_dependencies(field, seen=[]):
     if field in seen:
         return
     for seq1 in dependencies[field]:
         yield seq1
         exceptions = (Exception,) if field.base_field.manual else ()
         with ignore(*exceptions):
             for seq2 in transitive_dependencies(seq1[-1], seen + [field]):
                 yield concat(seq1[:-1], seq2)
Esempio n. 5
0
 def _mark_for_gc(self, fname):
     """ Add ``fname`` in a checklist for the filestore garbage collection. """
     # we use a spooldir: add an empty file in the subdirectory 'checklist'
     full_path = os.path.join(self._full_path('checklist'), fname)
     if not os.path.exists(full_path):
         dirname = os.path.dirname(full_path)
         if not os.path.isdir(dirname):
             with tools.ignore(OSError):
                 os.makedirs(dirname)
         open(full_path, 'ab').close()
Esempio n. 6
0
 def _mark_for_gc(self, fname):
     """ Add ``fname`` in a checklist for the filestore garbage collection. """
     # we use a spooldir: add an empty file in the subdirectory 'checklist'
     full_path = os.path.join(self._full_path('checklist'), fname)
     if not os.path.exists(full_path):
         dirname = os.path.dirname(full_path)
         if not os.path.isdir(dirname):
             with tools.ignore(OSError):
                 os.makedirs(dirname)
         open(full_path, 'ab').close()
Esempio n. 7
0
    def get_needaction_data(self):
        """ Return for each menu entry in ``self``:
            - whether it uses the needaction mechanism (needaction_enabled)
            - the needaction counter of the related action, taking into account
              the action domain
        """
        menu_ids = set()
        for menu in self:
            menu_ids.add(menu.id)
            ctx = {}
            if menu.action and menu.action.type in (
                    'ir.actions.act_window',
                    'ir.actions.client') and menu.action.context:
                with tools.ignore(Exception):
                    # use magical UnquoteEvalContext to ignore undefined client-side variables such as `active_id`
                    eval_ctx = tools.UnquoteEvalContext(self._context)
                    ctx = eval(menu.action.context,
                               locals_dict=eval_ctx,
                               nocopy=True) or {}
            menu_refs = ctx.get('needaction_menu_ref')
            if menu_refs:
                if not isinstance(menu_refs, list):
                    menu_refs = [menu_refs]
                for menu_ref in menu_refs:
                    record = self.env.ref(menu_ref, False)
                    if record and record._name == 'ir.ui.menu':
                        menu_ids.add(record.id)

        res = {}
        for menu in self.browse(menu_ids):
            res[menu.id] = {
                'needaction_enabled': False,
                'needaction_counter': False,
            }
            if menu.action and menu.action.type in (
                    'ir.actions.act_window',
                    'ir.actions.client') and menu.action.res_model:
                if menu.action.res_model in self.env:
                    model = self.env[menu.action.res_model]
                    if model._needaction:
                        if menu.action.type == 'ir.actions.act_window':
                            eval_context = self.env[
                                'ir.actions.act_window']._get_eval_context()
                            dom = eval(menu.action.domain or '[]',
                                       eval_context)
                        else:
                            dom = eval(menu.action.params_store or '{}', {
                                'uid': self._uid
                            }).get('domain')
                        res[menu.id]['needaction_enabled'] = model._needaction
                        res[menu.id][
                            'needaction_counter'] = model._needaction_count(
                                dom)
        return res
Esempio n. 8
0
    def _validate_numerical_box(self, answer):
        try:
            floatanswer = float(answer)
        except ValueError:
            return {self.id: _('This is not a number')}

        if self.validation_required:
            # Answer is not in the right range
            with tools.ignore(Exception):
                if not (self.validation_min_float_value <= floatanswer <= self.validation_max_float_value):
                    return {self.id: self.validation_error_msg}
        return {}
Esempio n. 9
0
    def _file_gc(self):
        """ Perform the garbage collection of the filestore. """
        if self._storage() != 'file':
            return

        # Continue in a new transaction. The LOCK statement below must be the
        # first one in the current transaction, otherwise the database snapshot
        # used by it may not contain the most recent changes made to the table
        # ir_attachment! Indeed, if concurrent transactions create attachments,
        # the LOCK statement will wait until those concurrent transactions end.
        # But this transaction will not see the new attachements if it has done
        # other requests before the LOCK (like the method _storage() above).
        cr = self._cr
        cr.commit()

        # prevent all concurrent updates on ir_attachment while collecting!
        cr.execute("LOCK ir_attachment IN SHARE MODE")

        # retrieve the file names from the checklist
        checklist = {}
        for dirpath, _, filenames in os.walk(self._full_path('checklist')):
            dirname = os.path.basename(dirpath)
            for filename in filenames:
                fname = "%s/%s" % (dirname, filename)
                checklist[fname] = os.path.join(dirpath, filename)

        # determine which files to keep among the checklist
        whitelist = set()
        for names in cr.split_for_in_conditions(checklist):
            cr.execute("SELECT store_fname FROM ir_attachment WHERE store_fname IN %s", [names])
            whitelist.update(row[0] for row in cr.fetchall())

        # remove garbage files, and clean up checklist
        removed = 0
        for fname, filepath in checklist.items():
            if fname not in whitelist:
                try:
                    os.unlink(self._full_path(fname))
                    removed += 1
                except (OSError, IOError):
                    _logger.info("_file_gc could not unlink %s", self._full_path(fname), exc_info=True)
            with tools.ignore(OSError):
                os.unlink(filepath)

        # commit to release the lock
        cr.commit()
        _logger.info("filestore gc %d checked, %d removed", len(checklist), removed)
Esempio n. 10
0
    def get_needaction_data(self):
        """ Return for each menu entry in ``self``:
            - whether it uses the needaction mechanism (needaction_enabled)
            - the needaction counter of the related action, taking into account
              the action domain
        """
        menu_ids = set()
        for menu in self:
            menu_ids.add(menu.id)
            ctx = {}
            if (
                menu.action
                and menu.action.type in ("ir.actions.act_window", "ir.actions.client")
                and menu.action.context
            ):
                with tools.ignore(Exception):
                    # use magical UnquoteEvalContext to ignore undefined client-side variables such as `active_id`
                    eval_ctx = tools.UnquoteEvalContext(self._context)
                    ctx = safe_eval(menu.action.context, locals_dict=eval_ctx, nocopy=True) or {}
            menu_refs = ctx.get("needaction_menu_ref")
            if menu_refs:
                if not isinstance(menu_refs, list):
                    menu_refs = [menu_refs]
                for menu_ref in menu_refs:
                    record = self.env.ref(menu_ref, False)
                    if record and record._name == "ir.ui.menu":
                        menu_ids.add(record.id)

        res = {}
        for menu in self.browse(menu_ids):
            res[menu.id] = {"needaction_enabled": False, "needaction_counter": False}
            if (
                menu.action
                and menu.action.type in ("ir.actions.act_window", "ir.actions.client")
                and menu.action.res_model
            ):
                if menu.action.res_model in self.env:
                    model = self.env[menu.action.res_model]
                    if model._needaction:
                        if menu.action.type == "ir.actions.act_window":
                            eval_context = self.env["ir.actions.act_window"]._get_eval_context()
                            dom = safe_eval(menu.action.domain or "[]", eval_context)
                        else:
                            dom = safe_eval(menu.action.params_store or "{}", {"uid": self._uid}).get("domain")
                        res[menu.id]["needaction_enabled"] = model._needaction
                        res[menu.id]["needaction_counter"] = model._needaction_count(dom)
        return res
Esempio n. 11
0
def clean_store(dbname, env):
    tables = defaultdict(set)
    for model_name in env.registry.models:
        model = env[model_name]
        if not model._abstract:
            for name, field in model._fields.items():
                if field.type == 'file':
                    tables[model._table].add(name)
    checklist = set()
    filestore = get_store_path(dbname)
    path = os.path.join(filestore, 'checklist')
    for root, dirs, files in os.walk(path):
        for file in files:
            checkpath = os.path.join(root, file)
            relpath = os.path.relpath(checkpath, path)
            checklist.add(os.path.join(filestore, relpath))
    env.cr.commit()
    whitelist = set()
    for table, fields in tables.items():
        select_fields = list(fields)
        env.cr.execute("LOCK %s IN SHARE MODE" % table)
        select_query = "SELECT {0}".format(', '.join(select_fields))
        where_query = "WHERE {0} IN %(paths)s".format(select_fields[0])
        if len(select_fields) > 1:
            for field in select_fields[:1]:
                where_query += "OR {0} IN %s".format(field)
        sql_query = "{0} FROM {1} {2};".format(select_query, table,
                                               where_query)
        for paths in env.cr.split_for_in_conditions(checklist):
            env.cr.execute(sql_query, {'paths': paths})
            for row in env.cr.fetchall():
                for column in row:
                    whitelist.add(column)
    remove = checklist - whitelist
    for file in remove:
        try:
            os.unlink(file)
        except (OSError, IOError):
            _logger.warn("Deleting file from %s failed!", file, exc_info=True)
    with tools.ignore(OSError):
        shutil.rmtree(path)
    env.cr.commit()
    _logger.info("Cleaned files [ %d checked | %d removed ]", len(checklist),
                 len(remove))
Esempio n. 12
0
    def field_triggers(self):
        # determine field dependencies
        dependencies = {}
        for Model in self.models.values():
            if Model._abstract:
                continue
            for field in Model._fields.values():
                # dependencies of custom fields may not exist; ignore that case
                exceptions = (Exception, ) if field.base_field.manual else ()
                with ignore(*exceptions):
                    dependencies[field] = set(field.resolve_depends(self))

        # determine transitive dependencies
        def transitive_dependencies(field, seen=[]):
            if field in seen:
                return
            for seq1 in dependencies[field]:
                yield seq1
                exceptions = (Exception, ) if field.base_field.manual else ()
                with ignore(*exceptions):
                    for seq2 in transitive_dependencies(
                            seq1[-1], seen + [field]):
                        yield concat(seq1[:-1], seq2)

        def concat(seq1, seq2):
            if seq1 and seq2:
                f1, f2 = seq1[-1], seq2[0]
                if f1.type == 'one2many' and f2.type == 'many2one' and \
                        f1.model_name == f2.comodel_name and f1.inverse_name == f2.name:
                    return concat(seq1[:-1], seq2[1:])
            return seq1 + seq2

        # determine triggers based on transitive dependencies
        triggers = {}
        for field in dependencies:
            for path in transitive_dependencies(field):
                if path:
                    tree = triggers
                    for label in reversed(path):
                        tree = tree.setdefault(label, {})
                    tree.setdefault(None, set()).add(field)

        return triggers
Esempio n. 13
0
    def get_needaction_data(self):
        """ Return for each menu entry in ``self``:
            - whether it uses the needaction mechanism (needaction_enabled)
            - the needaction counter of the related action, taking into account
              the action domain
        """
        menu_ids = set()
        for menu in self:
            menu_ids.add(menu.id)
            ctx = {}
            if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.context:
                with tools.ignore(Exception):
                    # use magical UnquoteEvalContext to ignore undefined client-side variables such as `active_id`
                    eval_ctx = tools.UnquoteEvalContext(self._context)
                    ctx = eval(menu.action.context, locals_dict=eval_ctx, nocopy=True) or {}
            menu_refs = ctx.get('needaction_menu_ref')
            if menu_refs:
                if not isinstance(menu_refs, list):
                    menu_refs = [menu_refs]
                for menu_ref in menu_refs:
                    record = self.env.ref(menu_ref, False)
                    if record and record._name == 'ir.ui.menu':
                        menu_ids.add(record.id)

        res = {}
        for menu in self.browse(menu_ids):
            res[menu.id] = {
                'needaction_enabled': False,
                'needaction_counter': False,
            }
            if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
                if menu.action.res_model in self.env:
                    model = self.env[menu.action.res_model]
                    if model._needaction:
                        if menu.action.type == 'ir.actions.act_window':
                            eval_context = self.env['ir.actions.act_window']._get_eval_context()
                            dom = eval(menu.action.domain or '[]', eval_context)
                        else:
                            dom = eval(menu.action.params_store or '{}', {'uid': self._uid}).get('domain')
                        res[menu.id]['needaction_enabled'] = model._needaction
                        res[menu.id]['needaction_counter'] = model._needaction_count(dom)
        return res
Esempio n. 14
0
    def _file_gc(self):
        """ Perform the garbage collection of the filestore. """
        if self._storage() != 'file':
            return

        # prevent all concurrent updates on ir_attachment while collecting!
        cr = self._cr
        cr.execute("LOCK ir_attachment IN SHARE MODE")

        # retrieve the file names from the checklist
        checklist = {}
        for dirpath, _, filenames in os.walk(self._full_path('checklist')):
            dirname = os.path.basename(dirpath)
            for filename in filenames:
                fname = "%s/%s" % (dirname, filename)
                checklist[fname] = os.path.join(dirpath, filename)

        # determine which files to keep among the checklist
        whitelist = set()
        for names in cr.split_for_in_conditions(checklist):
            cr.execute(
                "SELECT store_fname FROM ir_attachment WHERE store_fname IN %s",
                [names])
            whitelist.update(row[0] for row in cr.fetchall())

        # remove garbage files, and clean up checklist
        removed = 0
        for fname, filepath in checklist.iteritems():
            if fname not in whitelist:
                try:
                    os.unlink(self._full_path(fname))
                    removed += 1
                except (OSError, IOError):
                    _logger.info("_file_gc could not unlink %s",
                                 self._full_path(fname),
                                 exc_info=True)
            with tools.ignore(OSError):
                os.unlink(filepath)

        _logger.info("filestore gc %d checked, %d removed", len(checklist),
                     removed)
Esempio n. 15
0
 def validate_numerical_box(self, post, answer_tag):
     self.ensure_one()
     errors = {}
     answer = post[answer_tag].strip()
     # Empty answer to mandatory question
     if self.constr_mandatory and not answer:
         errors.update({answer_tag: self.constr_error_msg})
     # Checks if user input is a number
     if answer:
         try:
             floatanswer = float(answer)
         except ValueError:
             errors.update({answer_tag: _('This is not a number')})
     # Answer validation (if properly defined)
     if answer and self.validation_required:
         # Answer is not in the right range
         with tools.ignore(Exception):
             floatanswer = float(answer)  # check that it is a float has been done hereunder
             if not (self.validation_min_float_value <= floatanswer <= self.validation_max_float_value):
                 errors.update({answer_tag: self.validation_error_msg})
     return errors
Esempio n. 16
0
File: survey.py Progetto: barsi/odoo
 def validate_numerical_box(self, post, answer_tag):
     self.ensure_one()
     errors = {}
     answer = post[answer_tag].strip()
     # Empty answer to mandatory question
     if self.constr_mandatory and not answer:
         errors.update({answer_tag: self.constr_error_msg})
     # Checks if user input is a number
     if answer:
         try:
             floatanswer = float(answer)
         except ValueError:
             errors.update({answer_tag: _('This is not a number')})
     # Answer validation (if properly defined)
     if answer and self.validation_required:
         # Answer is not in the right range
         with tools.ignore(Exception):
             floatanswer = float(answer)  # check that it is a float has been done hereunder
             if not (self.validation_min_float_value <= floatanswer <= self.validation_max_float_value):
                 errors.update({answer_tag: self.validation_error_msg})
     return errors
Esempio n. 17
0
    def _file_gc(self):
        """ Perform the garbage collection of the filestore. """
        if self._storage() != 'file':
            return

        # prevent all concurrent updates on ir_attachment while collecting!
        cr = self._cr
        cr.execute("LOCK ir_attachment IN SHARE MODE")

        # retrieve the file names from the checklist
        checklist = {}
        for dirpath, _, filenames in os.walk(self._full_path('checklist')):
            dirname = os.path.basename(dirpath)
            for filename in filenames:
                fname = "%s/%s" % (dirname, filename)
                checklist[fname] = os.path.join(dirpath, filename)

        # determine which files to keep among the checklist
        whitelist = set()
        for names in cr.split_for_in_conditions(checklist):
            cr.execute("SELECT store_fname FROM ir_attachment WHERE store_fname IN %s", [names])
            whitelist.update(row[0] for row in cr.fetchall())

        # remove garbage files, and clean up checklist
        removed = 0
        for fname, filepath in checklist.iteritems():
            if fname not in whitelist:
                try:
                    os.unlink(self._full_path(fname))
                    removed += 1
                except (OSError, IOError):
                    _logger.info("_file_gc could not unlink %s", self._full_path(fname), exc_info=True)
            with tools.ignore(OSError):
                os.unlink(filepath)

        _logger.info("filestore gc %d checked, %d removed", len(checklist), removed)
Esempio n. 18
0
 def validate_star_rate(self, post, answer_tag):
     self.ensure_one()
     errors = {}
     answer = post[answer_tag].strip()
     # Empty answer to mandatory question
     if self.constr_mandatory and not answer:
         errors.update({answer_tag: self.constr_error_msg})
     # Checks if user input is a number
     if answer:
         try:
             floatanswer = float(answer)
         except ValueError:
             errors.update({answer_tag: "This is not a number"})
             return errors
         # Answer is not in the right range
         with tools.ignore(Exception):
             # 0 answer to mandatory question
             if self.constr_mandatory:
                 if floatanswer == 0:
                     errors.update({answer_tag: self.constr_error_msg})
             if not (0 <= floatanswer <= 5):
                 errors.update(
                     {answer_tag: "Answer is not in the right range"})
     return errors
Esempio n. 19
0
    def setup_models(self, cr):
        """ Complete the setup of models.
            This must be called after loading modules and before using the ORM.
        """
        lazy_property.reset_all(self)
        env = odoo.api.Environment(cr, SUPERUSER_ID, {})

        # add manual models
        if self._init_modules:
            env['ir.model']._add_manual_models()

        # prepare the setup on all models
        models = list(env.values())
        for model in models:
            model._prepare_setup()

        # do the actual setup from a clean state
        self._m2m = defaultdict(list)
        for model in models:
            model._setup_base()

        for model in models:
            model._setup_fields()

        # determine field dependencies
        dependencies = {}
        for model in models:
            if model._abstract:
                continue
            for field in model._fields.values():
                # dependencies of custom fields may not exist; ignore that case
                exceptions = (Exception,) if field.manual else ()
                with ignore(*exceptions):
                    dependencies[field] = set(field.resolve_depends(model))

        # determine transitive dependencies
        def transitive_dependencies(field, seen=[]):
            if field in seen:
                return
            for seq1 in dependencies[field]:
                yield seq1
                for seq2 in transitive_dependencies(seq1[-1], seen + [field]):
                    yield concat(seq1[:-1], seq2)

        def concat(seq1, seq2):
            if seq1 and seq2:
                f1, f2 = seq1[-1], seq2[0]
                if f1.type == 'one2many' and f2.type == 'many2one' and \
                        f1.model_name == f2.comodel_name and f1.inverse_name == f2.name:
                    return concat(seq1[:-1], seq2[1:])
            return seq1 + seq2

        # determine triggers based on transitive dependencies
        triggers = {}
        for field in dependencies:
            for path in transitive_dependencies(field):
                if path:
                    tree = triggers
                    for label in reversed(path):
                        tree = tree.setdefault(label, {})
                    tree.setdefault(None, set()).add(field)

        self.field_triggers = triggers

        for model in models:
            model._setup_complete()

        self.registry_invalidated = True
Esempio n. 20
0
                    _logger.warning("%s: inconsistent 'compute_sudo' for computed fields: %s",
                                    self._name, ", ".join(field.name for field in fields))
        return computed

    @lazy_property
    def field_triggers(self):
        # determine field dependencies
        dependencies = {}
<<<<<<< HEAD
        for model in models:
            if model._abstract:
                continue
            for field in model._fields.values():
                # dependencies of custom fields may not exist; ignore that case
                exceptions = (Exception,) if field.base_field.manual else ()
                with ignore(*exceptions):
                    dependencies[field] = set(field.resolve_depends(model))
=======
        for Model in self.models.values():
            if Model._abstract:
                continue
            for field in Model._fields.values():
                # dependencies of custom fields may not exist; ignore that case
                exceptions = (Exception,) if field.base_field.manual else ()
                with ignore(*exceptions):
                    dependencies[field] = set(field.resolve_depends(self))
>>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8

        # determine transitive dependencies
        def transitive_dependencies(field, seen=[]):
            if field in seen:
Esempio n. 21
0
    def setup_models(self, cr):
        """ Complete the setup of models.
            This must be called after loading modules and before using the ORM.
        """
        env = odoo.api.Environment(cr, SUPERUSER_ID, {})

        # Uninstall registry hooks. Because of the condition, this only happens
        # on a fully loaded registry, and not on a registry being loaded.
        if self.ready:
            for model in env.values():
                model._unregister_hook()

        lazy_property.reset_all(self)

        if env.all.tocompute:
            _logger.error(
                "Remaining fields to compute before setting up registry: %s",
                env.all.tocompute,
                stack_info=True,
            )

        # add manual models
        if self._init_modules:
            env['ir.model']._add_manual_models()

        # prepare the setup on all models
        models = list(env.values())
        for model in models:
            model._prepare_setup()

        # do the actual setup from a clean state
        self._m2m = defaultdict(list)
        for model in models:
            model._setup_base()

        for model in models:
            model._setup_fields()

        # determine field dependencies
        dependencies = {}
        for model in models:
            if model._abstract:
                continue
            for field in model._fields.values():
                # dependencies of custom fields may not exist; ignore that case
                exceptions = (Exception, ) if field.base_field.manual else ()
                with ignore(*exceptions):
                    dependencies[field] = set(field.resolve_depends(model))

        # determine transitive dependencies
        def transitive_dependencies(field, seen=[]):
            if field in seen:
                return
            for seq1 in dependencies.get(field, ()):
                yield seq1
                for seq2 in transitive_dependencies(seq1[-1], seen + [field]):
                    yield concat(seq1[:-1], seq2)

        def concat(seq1, seq2):
            if seq1 and seq2:
                f1, f2 = seq1[-1], seq2[0]
                if f1.type == 'one2many' and f2.type == 'many2one' and \
                        f1.model_name == f2.comodel_name and f1.inverse_name == f2.name:
                    return concat(seq1[:-1], seq2[1:])
            return seq1 + seq2

        # determine triggers based on transitive dependencies
        triggers = {}
        for field in dependencies:
            for path in transitive_dependencies(field):
                if path:
                    tree = triggers
                    for label in reversed(path):
                        tree = tree.setdefault(label, {})
                    tree.setdefault(None, set()).add(field)

        self.field_triggers = triggers

        for model in models:
            model._setup_complete()

        self.registry_invalidated = True

        # Reinstall registry hooks. Because of the condition, this only happens
        # on a fully loaded registry, and not on a registry being loaded.
        if self.ready:
            for model in env.values():
                model._register_hook()
            env['base'].flush()