From c002e2eb379e8a3db90b45269f8ff2b7e246c8b2 Mon Sep 17 00:00:00 2001
From: Adrian Torres <adt@odoo.com>
Date: Wed, 11 Jul 2018 13:06:00 +0200
Subject: [PATCH] [IMP] allow installing demo data post db init

Intended for demo / throwaway databases only

Task-ID: 1856493
---
 addons/sale/data/sale_demo.xml                |   1 -
 .../controllers/main.py                       |   7 +
 .../static/src/js/dashboard.js                |  21 ++-
 .../static/src/xml/dashboard.xml              |   3 +
 odoo/addons/base/__manifest__.py              |   1 +
 odoo/addons/base/data/ir_demo_data.xml        |  23 ++++
 odoo/addons/base/models/__init__.py           |   3 +-
 odoo/addons/base/models/ir_demo.py            |  15 +++
 odoo/modules/loading.py                       | 121 +++++++++++-------
 9 files changed, 147 insertions(+), 48 deletions(-)
 create mode 100644 odoo/addons/base/data/ir_demo_data.xml
 create mode 100644 odoo/addons/base/models/ir_demo.py

diff --git a/addons/sale/data/sale_demo.xml b/addons/sale/data/sale_demo.xml
index a81e797d6825..b203cab9bf53 100644
--- a/addons/sale/data/sale_demo.xml
+++ b/addons/sale/data/sale_demo.xml
@@ -198,7 +198,6 @@
             <field name="user_id" ref="base.user_root"/>
             <field name="pricelist_id" ref="product.list0"/>
             <field name="team_id" ref="sales_team.team_sales_department"/>
-            <field name="state">sale</field>
         </record>
 
         <record id="sale_order_line_16" model="sale.order.line">
diff --git a/addons/web_settings_dashboard/controllers/main.py b/addons/web_settings_dashboard/controllers/main.py
index 21423ec902d3..d2a23fc7c65a 100644
--- a/addons/web_settings_dashboard/controllers/main.py
+++ b/addons/web_settings_dashboard/controllers/main.py
@@ -53,6 +53,12 @@ class WebSettingsDashboard(http.Controller):
         enterprise_users = request.env['res.users'].search_count([("login_date", ">=", fields.Datetime.to_string(limit_date)), ('share', '=', False)])
 
         expiration_date = request.env['ir.config_parameter'].sudo().get_param('database.expiration_date')
+
+        # We assume that if there's at least one module with demo data active, then the db was
+        # initialized with demo=True or it has been force-activated by the `Load demo data` button
+        # in the settings dashboard.
+        demo_active = bool(request.env['ir.module.module'].search_count([('demo', '=', True)]))
+
         return {
             'apps': {
                 'installed_apps': installed_apps,
@@ -68,6 +74,7 @@ class WebSettingsDashboard(http.Controller):
                 'server_version': release.version,
                 'expiration_date': expiration_date,
                 'debug': request.debug,
+                'demo_active': demo_active,
             },
             'company': {
                 'company_id': request.env.user.company_id.id,
diff --git a/addons/web_settings_dashboard/static/src/js/dashboard.js b/addons/web_settings_dashboard/static/src/js/dashboard.js
index 9f0ed3d75523..4d58da380414 100644
--- a/addons/web_settings_dashboard/static/src/js/dashboard.js
+++ b/addons/web_settings_dashboard/static/src/js/dashboard.js
@@ -2,6 +2,7 @@ odoo.define('web_settings_dashboard', function (require) {
 "use strict";
 
 var AbstractAction = require('web.AbstractAction');
+var config = require('web.config');
 var core = require('web.core');
 var framework = require('web.framework');
 var Widget = require('web.Widget');
@@ -279,6 +280,7 @@ var DashboardShare = Widget.extend({
         'click .tw_share': 'share_twitter',
         'click .fb_share': 'share_facebook',
         'click .li_share': 'share_linkedin',
+        'click .o_web_settings_dashboard_force_demo': '_onClickForceDemo',
     },
 
     init: function (parent, data) {
@@ -286,6 +288,7 @@ var DashboardShare = Widget.extend({
         this.parent = parent;
         this.share_url = 'https://www.odoo.com';
         this.share_text = encodeURIComponent("I am using #Odoo - Awesome open source business apps.");
+        return this._super.apply(this, arguments);
     },
 
     /**
@@ -319,7 +322,23 @@ var DashboardShare = Widget.extend({
             popup_url,
             'Share Dialog',
             'width=600,height=400'); // We have to add a size otherwise the window pops in a new tab
-    }
+    },
+
+    //--------------------------------------------------------------------------
+    // Handlers
+    //--------------------------------------------------------------------------
+
+    /**
+     * Forces demo data to be installed in a database without demo data installed.
+     *
+     * @private
+     * @param {MouseEvent} ev
+     */
+    _onClickForceDemo: function (ev) {
+        ev.preventDefault();
+        this.do_action('base.demo_force_install_action');
+        config.debug = false;
+    },
 });
 
 var DashboardTranslations = Widget.extend({
diff --git a/addons/web_settings_dashboard/static/src/xml/dashboard.xml b/addons/web_settings_dashboard/static/src/xml/dashboard.xml
index 5a505bb02e46..a98a557d66d3 100644
--- a/addons/web_settings_dashboard/static/src/xml/dashboard.xml
+++ b/addons/web_settings_dashboard/static/src/xml/dashboard.xml
@@ -109,6 +109,7 @@
             <hr/>
             <t t-set="server_version" t-value="widget.data.server_version"/>
             <t t-set="debug" t-value="widget.data.debug"/>
+            <t t-set="demo_active" t-value="widget.data.demo_active"/>
             <div class="row">
                 <div class="text-center">
                     <div class="user-heading">
@@ -134,6 +135,8 @@
                     <a t-if="debug != 'assets'" class="oe_activate_debug_mode pull-right" href="?debug=assets" >Activate the developer mode (with assets)</a>
                     <br t-if="debug != 'assets'"/>
                     <a t-if="debug != false" class="oe_activate_debug_mode pull-right" href="/web" >Deactivate the developer mode</a>
+                    <br t-if="debug != false"/>
+                    <a t-if="(debug) and !(demo_active)" class="oe_activate_debug_mode pull-right o_web_settings_dashboard_force_demo" href="#">Load demo data</a>
                 </div>
             </div>
         </div>
diff --git a/odoo/addons/base/__manifest__.py b/odoo/addons/base/__manifest__.py
index 9caa89917c18..99f634350e2a 100644
--- a/odoo/addons/base/__manifest__.py
+++ b/odoo/addons/base/__manifest__.py
@@ -20,6 +20,7 @@ The kernel of Odoo, needed for all installation.
         'data/report_paperformat_data.xml',
         'data/res_currency_data.xml',
         'data/res_country_data.xml',
+        'data/ir_demo_data.xml',
         'security/base_groups.xml',
         'security/base_security.xml',
         'views/base_menus.xml',
diff --git a/odoo/addons/base/data/ir_demo_data.xml b/odoo/addons/base/data/ir_demo_data.xml
new file mode 100644
index 000000000000..e108590147a5
--- /dev/null
+++ b/odoo/addons/base/data/ir_demo_data.xml
@@ -0,0 +1,23 @@
+<odoo>
+    <record model="ir.actions.act_window" id="demo_force_install_action">
+        <field name="name">Load demo data</field>
+        <field name="res_model">ir.demo</field>
+        <field name="view_mode">form</field>
+        <field name="target">new</field>
+    </record>
+
+    <record model="ir.ui.view" id="demo_force_install_form">
+        <field name="name">ir.demo.form</field>
+        <field name="model">ir.demo</field>
+        <field name="arch" type="xml">
+            <form>
+                <p>Demo data should only be used on test databases. Once they are loaded, they cannot be removed.</p>
+                <p>Are you sure you want to load demo data?</p>
+                <footer>
+                    <button name="install_demo" string="Load demo data" type="object" class="btn-primary"/>
+                    <button special="cancel" string="Discard" class="btn-default"/>
+                </footer>
+            </form>
+        </field>
+    </record>
+</odoo>
diff --git a/odoo/addons/base/models/__init__.py b/odoo/addons/base/models/__init__.py
index 59d125ab30e8..4506929a5c56 100644
--- a/odoo/addons/base/models/__init__.py
+++ b/odoo/addons/base/models/__init__.py
@@ -27,6 +27,7 @@ from . import ir_http
 from . import ir_logging
 from . import ir_property
 from . import ir_module
+from . import ir_demo
 from . import report_paperformat
 from . import res_country
 from . import res_lang
@@ -35,4 +36,4 @@ from . import res_bank
 from . import res_config
 from . import res_currency
 from . import res_company
-from . import res_users
\ No newline at end of file
+from . import res_users
diff --git a/odoo/addons/base/models/ir_demo.py b/odoo/addons/base/models/ir_demo.py
new file mode 100644
index 000000000000..34e4292be284
--- /dev/null
+++ b/odoo/addons/base/models/ir_demo.py
@@ -0,0 +1,15 @@
+from odoo import models
+from odoo.modules.loading import force_demo
+
+
+class IrDemo(models.TransientModel):
+
+    _name = 'ir.demo'
+
+    def install_demo(self):
+        force_demo(self.env.cr)
+        return {
+            'type': 'ir.actions.act_url',
+            'target': 'self',
+            'url': '/web',
+        }
diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py
index 6e123324635f..d8ff08128661 100644
--- a/odoo/modules/loading.py
+++ b/odoo/modules/loading.py
@@ -25,30 +25,15 @@ _logger = logging.getLogger(__name__)
 _test_logger = logging.getLogger('odoo.tests')
 
 
-def load_module_graph(cr, graph, status=None, perform_checks=True,
-                      skip_modules=None, report=None, models_to_check=None):
-    """Migrates+Updates or Installs all module nodes from ``graph``
-       :param graph: graph of module nodes to load
-       :param status: deprecated parameter, unused, left to avoid changing signature in 8.0
-       :param perform_checks: whether module descriptors should be checked for validity (prints warnings
-                              for same cases)
-       :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
-       :return: list of modules that were installed or updated
+def load_data(cr, idref, mode, kind, package, report):
     """
-    def load_test(module_name, idref, mode):
-        cr.commit()
-        try:
-            _load_data(cr, module_name, idref, mode, 'test')
-            return True
-        except Exception:
-            _test_logger.exception(
-                'module %s: an exception occurred in a test', module_name)
-            return False
-        finally:
-            cr.rollback()
-            # avoid keeping stale xml_id, etc. in cache
-            odoo.registry(cr.dbname).clear_caches()
 
+    kind: data, demo, test, init_xml, update_xml, demo_xml.
+
+    noupdate is False, unless it is demo data or it is csv data in
+    init mode.
+
+    """
 
     def _get_files_of_kind(kind):
         if kind == 'demo':
@@ -72,27 +57,74 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
                     )
         return files
 
-    def _load_data(cr, module_name, idref, mode, kind):
-        """
+    try:
+        if kind in ('demo', 'test'):
+            threading.currentThread().testing = True
+        for filename in _get_files_of_kind(kind):
+            _logger.info("loading %s/%s", package.name, filename)
+            noupdate = False
+            if kind in ('demo', 'demo_xml') or (filename.endswith('.csv') and kind in ('init', 'init_xml')):
+                noupdate = True
+            tools.convert_file(cr, package.name, filename, idref, mode, noupdate, kind, report)
+    finally:
+        if kind in ('demo', 'test'):
+            threading.currentThread().testing = False
+
 
-        kind: data, demo, test, init_xml, update_xml, demo_xml.
+def load_demo(cr, package, idref, mode, report=None):
+    """
+    Loads demo data for the specified package.
+    """
+    has_demo = hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed')
+    if has_demo:
+        load_data(cr, idref, mode, kind='demo', package=package, report=report)
+    return has_demo
+
+
+def force_demo(cr):
+    """
+    Forces the `demo` flag on all modules, and installs demo data for all installed modules.
+    """
+    graph = odoo.modules.graph.Graph()
+    cr.execute(
+        "SELECT name FROM ir_module_module WHERE state IN ('installed', 'to upgrade', 'to remove')"
+    )
+    module_list = [name for (name,) in cr.fetchall()]
+    graph.add_modules(cr, module_list, ['demo'])
+
+    for package in graph:
+        load_demo(cr, package, {}, 'init')
+
+    cr.execute('update ir_module_module set demo=%s', (True,))
+    env = api.Environment(cr, SUPERUSER_ID, {})
+    env['ir.module.module'].invalidate_cache(['demo'])
+    cr.commit()
 
-        noupdate is False, unless it is demo data or it is csv data in
-        init mode.
 
-        """
+def load_module_graph(cr, graph, status=None, perform_checks=True,
+                      skip_modules=None, report=None, models_to_check=None):
+    """Migrates+Updates or Installs all module nodes from ``graph``
+       :param graph: graph of module nodes to load
+       :param status: deprecated parameter, unused, left to avoid changing signature in 8.0
+       :param perform_checks: whether module descriptors should be checked for validity (prints warnings
+                              for same cases)
+       :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
+       :return: list of modules that were installed or updated
+    """
+    def load_test(idref, mode):
+        nonlocal package
+        cr.commit()
         try:
-            if kind in ('demo', 'test'):
-                threading.currentThread().testing = True
-            for filename in _get_files_of_kind(kind):
-                _logger.info("loading %s/%s", module_name, filename)
-                noupdate = False
-                if kind in ('demo', 'demo_xml') or (filename.endswith('.csv') and kind in ('init', 'init_xml')):
-                    noupdate = True
-                tools.convert_file(cr, module_name, filename, idref, mode, noupdate, kind, report)
+            load_data(cr, idref, mode, 'test', package, report)
+            return True
+        except Exception:
+            _test_logger.exception(
+                'module %s: an exception occurred in a test', package.name)
+            return False
         finally:
-            if kind in ('demo', 'test'):
-                threading.currentThread().testing = False
+            cr.rollback()
+            # avoid keeping stale xml_id, etc. in cache
+            odoo.registry(cr.dbname).clear_caches()
 
     if models_to_check is None:
         models_to_check = set()
@@ -172,13 +204,12 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
             if perform_checks:
                 module._check()
 
-            if package.state=='to upgrade':
+            if package.state == 'to upgrade':
                 # upgrading the module information
                 module.write(module.get_values_from_terp(package.data))
-            _load_data(cr, module_name, idref, mode, kind='data')
-            has_demo = hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed')
-            if has_demo:
-                _load_data(cr, module_name, idref, mode, kind='demo')
+            load_data(cr, idref, mode, kind='data', package=package, report=report)
+            demo_loaded = load_demo(cr, package, idref, mode, report)
+            if demo_loaded:
                 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
                 module.invalidate_cache(['demo'])
 
@@ -199,11 +230,11 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
             # validate all the views at a whole
             env['ir.ui.view']._validate_module_views(module_name)
 
-            if has_demo:
+            if demo_loaded:
                 # launch tests only in demo mode, allowing tests to use demo data.
                 if tools.config.options['test_enable']:
                     # Yamel test
-                    report.record_result(load_test(module_name, idref, mode))
+                    report.record_result(load_test(idref, mode))
                     # Python tests
                     env['ir.http']._clear_routing_map()     # force routing map to be rebuilt
                     report.record_result(odoo.modules.module.run_unit_tests(module_name, cr.dbname))
-- 
GitLab