diff --git a/conf.py b/conf.py
index c815aacaf3..d627ee42ff 100644
--- a/conf.py
+++ b/conf.py
@@ -82,6 +82,7 @@
'CURRENT_MAJOR_VERSION': current_major_version,
'GITHUB_PATH': f'https://github.com/odoo/odoo/blob/{version}',
'GITHUB_ENT_PATH': f'https://github.com/odoo/enterprise/blob/{version}',
+ 'GITHUB_TUTO_PATH': f'https://github.com/odoo/tutorials/blob/{current_major_branch}',
'OWL_PATH': f'https://github.com/odoo/owl/blob/master',
}
diff --git a/content/developer/tutorials.rst b/content/developer/tutorials.rst
index 68c685828b..9a2ea5ec16 100644
--- a/content/developer/tutorials.rst
+++ b/content/developer/tutorials.rst
@@ -16,6 +16,7 @@ Tutorials
tutorials/importable_modules
tutorials/mixins
tutorials/pdf_reports
+ tutorials/website_theme
.. tip::
If you are new to Odoo development, we recommend starting with the :doc:`setup guide
@@ -89,3 +90,9 @@ Expand your knowledge on the server framework
:target: tutorials/pdf_reports
Use QWeb, Odoo's powerful templating engine, to create custom PDF reports for your documents.
+
+ .. card:: Build a website theme
+ :target: tutorials/website_theme
+
+ Create a tailored website from scratch fully integrated with Odoo and editable via the Website
+ Builder.
diff --git a/content/developer/tutorials/web.rst b/content/developer/tutorials/web.rst
index 3688475425..ffc5cc4e33 100644
--- a/content/developer/tutorials/web.rst
+++ b/content/developer/tutorials/web.rst
@@ -13,7 +13,7 @@ Customizing the web client
This guide is about creating modules for Odoo's web client.
-To create websites with Odoo, see :doc:`website`; to add business capabilities
+To create websites with Odoo, see :doc:`website_theme`; to add business capabilities
or extend existing business systems of Odoo, see :doc:`backend`.
.. warning::
diff --git a/content/developer/tutorials/website.rst b/content/developer/tutorials/website.rst
deleted file mode 100644
index eaca1d6b04..0000000000
--- a/content/developer/tutorials/website.rst
+++ /dev/null
@@ -1,1012 +0,0 @@
-:orphan:
-
-==================
-Building a Website
-==================
-
-.. danger::
- This tutorial is outdated. We recommend reading :doc:`server_framework_101` instead.
-
-.. warning::
-
- * This guide assumes `basic knowledge of Python
- `_
- * This guide assumes :doc:`an installed Odoo `
-
-Creating a basic module
-=======================
-
-In Odoo, tasks are performed by creating modules.
-
-Modules customize the behavior of an Odoo installation, either by adding new
-behaviors or by altering existing ones (including behaviors added by other
-modules).
-
-:ref:`Odoo's scaffolding ` can setup a basic
-module. To quickly get started simply invoke:
-
-.. code-block:: console
-
- $ ./odoo-bin scaffold Academy my-modules
-
-This will automatically create a ``my-modules`` *module directory* with an
-``academy`` module inside. The directory can be an existing module directory
-if you want, but the module name must be unique within the directory.
-
-A demonstration module
-======================
-
-We have a "complete" module ready for installation.
-
-Although it does absolutely nothing we can install it:
-
-* start the Odoo server
-
- .. code-block:: console
-
- $ ./odoo-bin --addons-path addons,my-modules
-
-* go to http://localhost:8069
-* create a new database including demonstration data
-* to go :menuselection:`Settings --> Modules --> Modules`
-* in the top-right corner remove the *Installed* filter and search for
- *academy*
-* click the :guilabel:`Install` button for the *Academy* module
-
-To the browser
-==============
-
-:ref:`Controllers ` interpret browser requests and
-send data back.
-
-Add a simple controller and ensure it is imported by ``__init__.py`` (so
-Odoo can find it):
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- # -*- coding: utf-8 -*-
- from odoo import http
-
- class Academy(http.Controller):
-
- @http.route('/academy/academy/', auth='public')
- def index(self, **kw):
- return "Hello, world"
-
-Shut down your server (:kbd:`^C`) then restart it:
-
-.. code-block:: console
-
- $ ./odoo-bin --addons-path addons,my-modules
-
-and open a page to http://localhost:8069/academy/academy/, you should see your
-"page" appear:
-
-.. figure:: website/helloworld.png
-
-Templates
-=========
-
-Generating HTML in Python isn't very pleasant.
-
-The usual solution is templates_, pseudo-documents with placeholders and
-display logic. Odoo allows any Python templating system, but provides its
-own :ref:`QWeb ` templating system which integrates with other
-features.
-
-Create a template and ensure the template file is registered in the
-``__manifest__.py`` manifest, and alter the controller to use our template:
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- class Academy(http.Controller):
-
- @http.route('/academy/academy/', auth='public')
- def index(self, **kw):
- return http.request.render('academy.index', {
- 'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
- })
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-
- Academy
-
-
-
-
-
-
-
-The templates iterates (``t-foreach``) on all the teachers (passed through the
-*template context*), and prints each teacher in its own paragraph.
-
-Finally restart Odoo and update the module's data (to install the template)
-by going to :menuselection:`Settings --> Modules --> Modules -->
-Academy` and clicking :guilabel:`Upgrade`.
-
-.. tip::
-
- Alternatively, Odoo can be restarted :option:`and update modules at
- the same time`:
-
- .. code-block:: console
-
- $ odoo-bin --addons-path addons,my-modules -d academy -u academy
-
-Going to http://localhost:8069/academy/academy/ should now result in:
-
-.. image:: website/basic-list.png
-
-Storing data in Odoo
-====================
-
-:ref:`Odoo models ` map to database tables.
-
-In the previous section we just displayed a list of string entered statically
-in the Python code. This doesn't allow modifications or persistent storage
-so we'll now move our data to the database.
-
-Defining the data model
------------------------
-
-Define a teacher model, and ensure it is imported from ``__init__.py`` so it
-is correctly loaded:
-
-.. code-block:: python
- :caption: ``academy/models.py``
-
- from odoo import models, fields, api
-
- class Teachers(models.Model):
- _name = 'academy.teachers'
-
- name = fields.Char()
-
-Then setup :ref:`basic access control ` for the model
-and add them to the manifest:
-
-.. code-block:: python
- :caption: ``academy/__manifest__.py``
-
- # always loaded
- 'data': [
- 'security/ir.model.access.csv',
- 'templates.xml',
- ],
-
-.. code-block:: csv
- :caption: ``academy/security/ir.model.access.csv``
-
- id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
- access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
-
-this simply gives read access (``perm_read``) to all users (``group_id:id``
-left empty).
-
-.. note::
-
- :ref:`Data files ` (XML or CSV) must be added to the
- module manifest, Python files (models or controllers) don't but have to
- be imported from ``__init__.py`` (directly or indirectly)
-
-.. warning::
-
- the administrator user bypasses access control, they have access to all
- models even if not given access
-
-Demonstration data
-------------------
-
-The second step is to add some demonstration data to the system so it's
-possible to test it easily. This is done by adding a ``demo``
-:ref:`data file `, which must be linked from the manifest:
-
-.. code-block:: xml
- :caption: ``academy/demo.xml``
-
-
-
-
- Diana Padilla
-
-
- Jody Carroll
-
-
- Lester Vaughn
-
-
-
-
-.. tip::
-
- :ref:`Data files ` can be used for demo and non-demo data.
- Demo data are only loaded in "demonstration mode" and can be used for flow
- testing and demonstration, non-demo data are always loaded and used as
- initial system setup.
-
- In this case we're using demonstration data because an actual user of the
- system would want to input or import their own teachers list, this list
- is only useful for testing.
-
-Accessing the data
-------------------
-
-The last step is to alter model and template to use our demonstration data:
-
-#. fetch the records from the database instead of having a static list
-#. Because :meth:`~odoo.models.Model.search` returns a set of records
- matching the filter ("all records" here), alter the template to print each
- teacher's ``name``
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- class Academy(http.Controller):
-
- @http.route('/academy/academy/', auth='public')
- def index(self, **kw):
- Teachers = http.request.env['academy.teachers']
- return http.request.render('academy.index', {
- 'teachers': Teachers.search([])
- })
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-
- Academy
-
-
-
-
-
-
-
-Restart the server and update the module (in order to update the manifest
-and templates and load the demo file) then navigate to
-http://localhost:8069/academy/academy/. The page should look slightly
-different: names should simply be prefixed by a number (the database
-identifier for the teacher).
-
-Website support
-===============
-
-Odoo bundles a module dedicated to building websites.
-
-So far we've used controllers fairly directly, but Odoo 8 added deeper
-integration and a few other services (e.g. default styling, theming) via the
-``website`` module.
-
-#. first, add ``website`` as a dependency to ``academy``
-#. then add the ``website=True`` flag on the controller, this sets up a few
- new variables on :ref:`the request object ` and
- allows using the website layout in our template
-#. use the website layout in the template
-
-.. code-block:: python
- :caption: ``academy/__manifest__.py``
-
- 'version': '0.1',
-
- # any module necessary for this one to work correctly
- 'depends': ['website'],
-
- # always loaded
- 'data': [
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- class Academy(http.Controller):
-
- @http.route('/academy/academy/', auth='public', website=True)
- def index(self, **kw):
- Teachers = http.request.env['academy.teachers']
- return http.request.render('academy.index', {
- 'teachers': Teachers.search([])
- })
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-
-
- Academy
-
-
-
-
-
-
-After restarting the server while updating the module (in order to update the
-manifest and template) access http://localhost:8069/academy/academy/ should
-yield a nicer looking page with branding and a number of built-in page
-elements (top-level menu, footer, …)
-
-.. image:: website/layout.png
-
-The website layout also provides support for editing tools: click
-:guilabel:`Sign In` (in the top-right), fill the credentials in (``admin`` /
-``admin`` by default) then click :guilabel:`Log In`.
-
-You're now in Odoo "proper": the administrative interface. For now click on
-the :guilabel:`Website` menu item (top-left corner.
-
-We're back in the website but as an administrator, with access to advanced
-editing features provided by the *website* support:
-
-* a template code editor (:menuselection:`Customize --> HTML Editor`) where
- you can see and edit all templates used for the current page
-* the :guilabel:`Edit` button in the top-left switches to "editing mode" where
- blocks (snippets) and rich text editing are available
-* a number of other features such as mobile preview or :abbr:`SEO (Search
- Engine Optimization)`
-
-URLs and routing
-================
-
-Controller methods are associated with *routes* via the
-:func:`~odoo.http.route` decorator which takes a routing string and a
-number of attributes to customise its behavior or security.
-
-We've seen a "literal" routing string, which matches a URL section exactly,
-but routing strings can also use `converter patterns`_ which match bits
-of URLs and make those available as local variables. For instance we can
-create a new controller method which takes a bit of URL and prints it out:
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- # New route
- @http.route('/academy//', auth='public', website=True)
- def teacher(self, name):
- return '{}
'.format(name)
-
-restart Odoo, access http://localhost:8069/academy/Alice/ and
-http://localhost:8069/academy/Bob/ and see the difference.
-
-As the name indicates, `converter patterns`_ don't just do extraction, they
-also do *validation* and *conversion*, so we can change the new controller
-to only accept integers:
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- @http.route('/academy//', auth='public', website=True)
- def teacher(self, id):
- return '{} ({})
'.format(id, type(id).__name__)
-
-Restart Odoo, access http://localhost:8069/academy/2, note how the old value
-was a string, but the new one was converted to an integers. Try accessing
-http://localhost:8069/academy/Carol/ and note that the page was not found:
-since "Carol" is not an integer, the route was ignored and no route could be
-found.
-
-Odoo provides an additional converter called ``model`` which provides records
-directly when given their id. Let's use this to create a generic page for
-teacher biographies:
-
-.. code-block:: python
- :caption: ``academy/controllers.py``
-
- @http.route('/academy//', auth='public', website=True)
- def teacher(self, teacher):
- return http.request.render('academy.biography', {
- 'person': teacher
- })
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
- Academy
-
-
-
-
-
-
-then change the list of model to link to our new controller:
-
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
- Academy
-
-
-
-
-Restart Odoo and upgrade the module, then you can visit each teacher's page.
-As an exercise, try adding blocks to a teacher's page to write a biography,
-then go to another teacher's page and so forth. You will discover, that your
-biography is shared between all teachers, because blocks are added to the
-*template*, and the *biography* template is shared between all teachers, when
-one page is edited they're all edited at the same time.
-
-Field editing
-=============
-
-Data which is specific to a record should be saved on that record, so let us
-add a new biography field to our teachers:
-
-.. code-block:: python
- :caption: ``academy/models.py``
-
- class Teachers(models.Model):
- _name = 'academy.teachers'
-
- name = fields.Char()
- biography = fields.Html()
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
- Academy
-
-
-
-
-
-
-Restart Odoo and update the views, reload the teacher's page and… the field
-is invisible since it contains nothing.
-
-.. todo:: the view has been set to noupdate because modified previously,
- force via ``-i`` or do something else?
-
-For record fields, templates can use a special ``t-field`` directive which
-allows editing the field content from the website using field-specific
-interfaces. Change the *person* template to use ``t-field``:
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-Restart Odoo and upgrade the module, there is now a placeholder under the
-teacher's name and a new zone for blocks in :guilabel:`Edit` mode. Content
-dropped there is stored in the corresponding teacher's ``biography`` field, and
-thus specific to that teacher.
-
-The teacher's name is also editable, and when saved the change is visible on
-the index page.
-
-``t-field`` can also take formatting options which depend on the exact field.
-For instance if we display the modification date for a teacher's record:
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-it is displayed in a very "computery" manner and hard to read, but we could
-ask for a human-readable version:
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-or a relative display:
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-Administration and ERP integration
-==================================
-
-A brief and incomplete introduction to the Odoo administration
---------------------------------------------------------------
-
-The Odoo administration was briefly seen during the `website support`_ section.
-We can go back to it using :menuselection:`Administrator --> Administrator` in
-the menu (or :guilabel:`Sign In` if you're signed out).
-
-The conceptual structure of the Odoo backend is simple:
-
-#. first are menus, a tree (menus can have sub-menus) of records. Menus
- without children map to…
-#. actions. Actions have various types: links, reports, code which Odoo should
- execute or data display. Data display actions are called *window actions*,
- and tell Odoo to display a given *model* according to a set of views…
-#. a view has a type, a broad category to which it corresponds (a list,
- a graph, a calendar) and an *architecture* which customises the way the
- model is displayed inside the view.
-
-Editing in the Odoo administration
-----------------------------------
-
-By default, an Odoo model is essentially invisible to a user. To make it
-visible it must be available through an action, which itself needs to be
-reachable, generally through a menu.
-
-Let's create a menu for our model:
-
-.. code-block:: python
- :caption: ``academy/__manifest__.py``
-
- # always loaded
- 'data': [
- 'security/ir.model.access.csv',
- 'templates.xml',
- 'views.xml',
- ],
-
-.. code-block:: xml
- :caption: ``academy/views.xml``
-
-
-
- Academy teachers
- academy.teachers
-
-
-
-
-
-
-
-then accessing http://localhost:8069/web/ in the top left should be a menu
-:guilabel:`Academy`, which is selected by default, as it is the first menu,
-and having opened a listing of teachers. From the listing it is possible to
-:guilabel:`Create` new teacher records, and to switch to the "form" by-record
-view.
-
-If there is no definition of how to present records (a
-:doc:`view <../reference/user_interface/view_records>`) Odoo will automatically create a basic one
-on-the-fly. In our case it works for the "list" view for now (only displays
-the teacher's name) but in the "form" view the HTML ``biography`` field is
-displayed side-by-side with the ``name`` field and not given enough space.
-Let's define a custom form view to make viewing and editing teacher records
-a better experience:
-
-.. code-block:: xml
- :caption: ``academy/views.xml``
-
-
- Academy teachers: form
- academy.teachers
-
-
-
-
-
-Relations between models
-------------------------
-
-We have seen a pair of "basic" fields stored directly in the record. There are
-:ref:`a number of basic fields `. The second
-broad categories of fields are :ref:`relational
-` and used to link records to one another
-(within a model or across models).
-
-For demonstration, let's create a *courses* model. Each course should have a
-``teacher`` field, linking to a single teacher record, but each teacher can
-teach many courses:
-
-.. code-block:: python
- :caption: ``academy/models.py``
-
- class Courses(models.Model):
- _name = 'academy.courses'
-
- name = fields.Char()
- teacher_id = fields.Many2one('academy.teachers', string="Teacher")
-
-.. code-block:: csv
- :caption: ``academy/security/ir.model.access.csv``
-
- id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
- access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
- access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0
-
-let's also add views so we can see and edit a course's teacher:
-
-
-.. code-block:: xml
- :caption: ``academy/views.xml``
-
-
- Academy courses
- academy.courses
-
-
- Academy courses: search
- academy.courses
-
-
-
-
-
-
-
-
- Academy courses: list
- academy.courses
-
-
-
-
-
-
-
-
- Academy courses: form
- academy.courses
-
-
-
-
-
-
-
-
-
-
-It should also be possible to create new courses directly from a teacher's
-page, or to see all the courses they teach, so add
-:class:`the inverse relationship ` to the *teachers*
-model:
-
-.. code-block:: python
- :caption: ``academy/models.py``
-
- class Teachers(models.Model):
- _name = 'academy.teachers'
-
- name = fields.Char()
- biography = fields.Html()
-
- course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")
-
- class Courses(models.Model):
- _name = 'academy.courses'
-
- name = fields.Char()
- teacher_id = fields.Many2one('academy.teachers', string="Teacher")
-
-.. code-block:: xml
- :caption: ``academy/views.xml``
-
-
- Academy teachers: form
- academy.teachers
-
-
-
-
-
-Discussions and notifications
------------------------------
-
-Odoo provides technical models, which don't directly fulfill business needs
-but which add capabilities to business objects without having to build
-them by hand.
-
-One of these is the *Chatter* system, part of Odoo's email and messaging
-system, which can add notifications and discussion threads to any model.
-The model simply has to :attr:`~odoo.models.Model._inherit`
-``mail.thread``, and add the ``message_ids`` field to its form view to display
-the discussion thread. Discussion threads are per-record.
-
-For our academy, it makes sense to allow discussing courses to handle e.g.
-scheduling changes or discussions between teachers and assistants:
-
-
-.. code-block:: python
- :caption: ``academy/__manifest__.py``
-
- 'version': '0.1',
-
- # any module necessary for this one to work correctly
- 'depends': ['website', 'mail'],
-
- # always loaded
- 'data': [
-
-.. code-block:: python
- :caption: ``academy/models.py``
-
- class AcademyCourses(models.Model):
- _inherit = ['mail.thread']
-
- name = fields.Char()
- teacher_id = fields.Many2one('academy.teachers', string="Teacher")
-
-.. code-block:: xml
- :caption: ``academy/views.xml``
-
-
- Academy courses: form
- academy.courses
-
-
-
-
-
-At the bottom of each course form, there is now a discussion thread and the
-possibility for users of the system to leave messages and follow or unfollow
-discussions linked to specific courses.
-
-Selling courses
----------------
-
-Odoo also provides business models which allow using or opting in business
-needs more directly. For instance the ``website_sale`` module sets up an
-e-commerce site based on the products in the Odoo system. We can easily make
-course subscriptions sellable by making our courses specific kinds of
-products.
-
-Rather than the previous classical inheritance, this means replacing our
-*course* model by the *product* model, and extending products in-place (to
-add anything we need to it).
-
-First of all we need to add a dependency on ``website_sale`` so we get both
-products (via ``sale``) and the ecommerce interface:
-
-.. code-block:: python
- :caption: ``academy/__manifest__.py``
-
- 'version': '0.1',
-
- # any module necessary for this one to work correctly
- 'depends': ['mail', 'website_sale'],
-
- # always loaded
- 'data': [
-
-restart Odoo, update your module, there is now a :guilabel:`Shop` section in
-the website, listing a number of pre-filled (via demonstration data) products.
-
-The second step is to replace the *courses* model by ``product.template``,
-and add a new category of product for courses:
-
-.. code-block:: python
- :caption: ``academy/__manifest__.py``
-
- 'security/ir.model.access.csv',
- 'templates.xml',
- 'views.xml',
- 'data.xml',
- ],
- # only loaded in demonstration mode
- 'demo': [
-
-.. code-block:: xml
- :caption: ``academy/data.xml``
-
-
-
- Courses
-
-
-
-
-.. code-block:: xml
- :caption: ``academy/demo.xml``
-
-
- Course 0
-
-
- True
- 0
- service
-
-
- Course 1
-
-
- True
- 0
- service
-
-
- Course 2
-
-
- True
- 0
- service
-
-
-
-.. code-block:: python
- :caption: ``academy/models.py``
-
- class Courses(models.Model):
- _name = 'academy.courses'
- _inherit = ['mail.thread', 'product.template']
-
- name = fields.Char()
- teacher_id = fields.Many2one('academy.teachers', string="Teacher")
-
-With this installed, a few courses are now available in the :guilabel:`Shop`,
-though they may have to be looked for.
-
-.. note::
-
- * to extend a model in-place, it's :attr:`inherited
- ` without giving it a new
- :attr:`~odoo.models.Model._name`
- * ``product.template`` already uses the discussions system, so we can
- remove it from our extension model
- * we're creating our courses as *published* by default so they can be
- seen without having to log in
-
-Altering existing views
------------------------
-
-So far, we have briefly seen:
-
-* the creation of new models
-* the creation of new views
-* the creation of new records
-* the alteration of existing models
-
-We're left with the alteration of existing records and the alteration of
-existing views. We'll do both on the :guilabel:`Shop` pages.
-
-View alteration is done by creating *extension* views, which are applied on
-top of the original view and alter it. These alteration views can be added or
-removed without modifying the original, making it easier to try things out and
-roll changes back.
-
-Since our courses are free, there is no reason to display their price on the
-shop page, so we're going to alter the view and hide the price if it's 0. The
-first task is finding out which view displays the price, this can be done via
-:menuselection:`Customize --> HTML Editor` which lets us read the various
-templates involved in rendering a page. Going through a few of them, "Product
-item" looks a likely culprit.
-
-Altering view architectures is done in 3 steps:
-
-#. Create a new view
-#. Extend the view to modify by setting the new view's ``inherit_id`` to the
- modified view's external id
-#. In the architecture, use the ``xpath`` tag to select and alter elements
- from the modified view
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
- product.price > 0
-
-
-
-The second thing we will change is making the product categories sidebar
-visible by default: :menuselection:`Customize --> Product Categories` lets
-you toggle a tree of product categories (used to filter the main display) on
-and off.
-
-This is done via the ``customize_show`` and ``active`` fields of extension
-templates: an extension template (such as the one we've just created) can be
-*customize_show=True*. This choice will display the view in the :guilabel:`Customize`
-menu with a check box, allowing administrators to activate or disable them
-(and easily customize their website pages).
-
-We simply need to modify the *Product Categories* record and set its default
-to *active="True"*:
-
-.. code-block:: xml
- :caption: ``academy/templates.xml``
-
-
-
-
-
-With this, the *Product Categories* sidebar will automatically be enabled when
-the *Academy* module is installed.
-
-.. _templates: https://en.wikipedia.org/wiki/Web_template
-.. _postgres:
-.. _postgresql:
- https://www.postgresql.org
-.. _converter pattern:
-.. _converter patterns:
- https://werkzeug.pocoo.org/docs/routing/#rule-format
diff --git a/content/developer/tutorials/website_theme.rst b/content/developer/tutorials/website_theme.rst
new file mode 100644
index 0000000000..02e79d0cfe
--- /dev/null
+++ b/content/developer/tutorials/website_theme.rst
@@ -0,0 +1,54 @@
+:show-content:
+:show-toc:
+:hide-page-toc:
+
+=====================
+Build a website theme
+=====================
+
+| For this project, we'll collaborate on creating a custom website theme fully integrated with Odoo.
+| Our client, Airproof, has provided their latest design for their waterproof drone e-commerce,
+ which we'll replicate.
+
+To start, you must have :doc:`installed Odoo locally `.
+You will also need some knowledge in:
+
+- XML
+- JavaScript (not mandatory)
+- Bootstrap 5.1.3
+- SCSS
+- QWeb (Odoo's own templating system)
+- OWL (JavaScript framework, not mandatory)
+
+| **Goal**
+| Replicate the Airproof design.
+
+.. image:: website_theme/airproof-home.png
+ :alt: Airproof homepage.
+ :align: center
+
+| In the :file:`README.md` of the `Airproof module <{GITHUB_TUTO_PATH}/website_airproof>`_, you can
+ find the various Airproof designs that you will replicate throughout the different exercises in
+ this tutorial.
+| You can also find all the code necessary for creating the Airproof website there. You should also
+ obtain this by the end of the tutorial. It is recommended to try solving the exercices first
+ without looking at the solution!
+
+| **Don't go too fast!**
+| Follow the exercises step by step and you will reach the final design at the end of the tutorial.
+
+Throughout this tutorial, you will find "See also" sections leading to parts of the :doc:`How-to
+guide: Website themes <../howtos/website_themes>` documentation. Be sure to read this documentation
+thoroughly each time! With it, you will find the solution to every exercise.
+
+Ready? Let's get started!
+
+.. toctree::
+ :maxdepth: 2
+
+ website_theme/01_theming
+ website_theme/02_build_website
+ website_theme/03_customisation_part1
+ website_theme/04_customisation_part2
+ website_theme/05_dynamic_templates
+ website_theme/06_going_live
diff --git a/content/developer/tutorials/website_theme/01_theming.rst b/content/developer/tutorials/website_theme/01_theming.rst
new file mode 100644
index 0000000000..528569f73b
--- /dev/null
+++ b/content/developer/tutorials/website_theme/01_theming.rst
@@ -0,0 +1,207 @@
+===================
+Chapter 1 - Theming
+===================
+
+Now that you have Odoo installed and your server is running locally, it's time to create your own
+theme module for your website.
+
+.. _tutorials/website_theme/theming/setup:
+
+Setup
+=====
+
+| The first step is to ensure that Odoo is running correctly locally. To do this, use a Shell script
+ to run the server.
+| In this script, define the database name and install only the `website` module.
+
+.. seealso::
+ See reference documentation on how to :ref:`run Odoo `.
+
+.. _tutorials/website_theme/theming/module:
+
+Build your module structure
+===========================
+
+Now that we know everything is working properly, let's start building our module.
+
+Based on the following structure, start creating your module that will be used as a theme. This is
+where you are going to add your XML pages, SCSS, JS, …
+
+.. seealso::
+ See reference documentation on how to structure your :ref:`theming/module`.
+
+| Start with the basics : :file:`/data`, :file:`/img`, :file:`/scss`, :file:`/js`.
+| Don't forget to add the :file:`__init__.py` and :file:`__manifest__.py` files.
+
+In your :file:`__manifest__.py` file, you can declare your module with the following information:
+
+- name (required)
+- description
+- category
+- version
+- author
+- license
+- depends
+
+.. _tutorials/website_theme/theming/odoo_variables:
+
+Declare Odoo variables
+======================
+
+In the :file:`primary_variables.scss` file, you can override the default Odoo SCSS variables to
+match your design.
+
+Based on the Airproof design, create your :file:`primary_variables.scss` file and define the
+following elements:
+
+- Headings font family : Space Grotesk
+- Content font family : Lato
+- The color palette name and the 5 main colors that compose it: `#000000`, `#BBE1FA`, `#CEF8A1`,
+ `#FFFFFF`, `#0B8EE6`
+- Header & Footer : Use one of the default templates for the moment, we will create a custom header
+ later.
+
+.. seealso::
+ See reference documentation on how to use :ref:`primary variables `, as
+ well as a list of all `primary variables
+ <{GITHUB_PATH}/addons/website/static/src/scss/primary_variables.scss>`_ available.
+
+| Restart your script to immediately see the application of your changes.
+| Don't forget to add the path to your manifest in the script and set your module as the app
+ to install.
+
+To ensure your changes are applied correctly, log in to your website and check that your
+color palette includes your specified colors.
+
+.. tip::
+ You will need to override more variables to replicate the Airproof design. Remember to add them
+ throughout the creation of your website.
+
+.. note::
+ The font families are from `Google fonts `_.
+
+.. spoiler:: Solutions
+
+ To complete this exercise, you need to:
+
+ #. Create your :file:`primary_variables.scss` file. You can find all the necessary information in
+ the `primary_variables.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/primary_variables.scss>`_ file from our
+ example module.
+ #. Declare your file in the :file:`__manifest__.py` as indicated in the documentation.
+ #. Install your module via your script. In our example, it looks like this:
+
+ .. code-block:: xml
+
+ ./odoo-bin --addons-path=../enterprise,addons,../myprojects --db-filter=theming -d theming
+ --without-demo=all -i website_airproof --dev=xml
+
+.. _tutorials/website_theme/theming/bootstrap_variables:
+
+Declare Bootstrap variables
+===========================
+
+On top of the default Odoo variables, you can also redefine the Bootstrap variables. Bootstrap is a
+front-end framework which is included by default in Odoo.
+
+Based on the Airproof design, define the following elements:
+
+- Headings font sizes :
+
+ - h1 : 3.125rem
+ - h2 : 2.5rem
+ - h3 : 2rem
+ - h4 : 1.75rem
+ - h5 : 1.5rem
+ - h6 : 1.25rem
+
+- Inputs border radius : 10px
+- Inputs border color : black
+- Inputs border width : 1px
+- Large buttons border radius : 0px 10px 10px 10px
+
+.. seealso::
+ - See reference documentation on how to use :ref:`theming/module/bootstrap`.
+ - A list of all `Bootstrap variables
+ <{GITHUB_PATH}/addons/web/static/lib/bootstrap/scss/_variables.scss>`_ used by Odoo.
+ - And `Bootstrap framework `_
+ official documentation.
+
+.. tip::
+ - You will need to override more variables to replicate the Airproof design. Remember to add them
+ throughout the creation of your website.
+ - Make it a habit to regularly check locally that your changes have been successfully applied
+ and have not caused any errors.
+
+.. spoiler:: Solutions
+
+ To complete this exercise, you need to:
+
+ #. Create your :file:`bootstrap_overridden.scss` file. You can find all the necessary information
+ in the `bootstrap_overridden.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/bootstrap_overridden.scss>`_ file from
+ our example module.
+ #. Declare your file in the :file:`__manifest__.py` as indicated in the documentation.
+
+.. _tutorials/website_theme/theming/presets:
+
+Define presets
+==============
+
+In addition to the variables we have just covered, you can also activate specific views to modify
+the design.
+
+Add your :file:`presets.xml` file and based on the Airproof design, activate the appropriate views
+to meet the following client requests:
+
+- Deactivate the Call-to-action in the header.
+- Deactivate the wishlist feature in the shop but activate it on the product page.
+- On the shop page, activate the filtering by categories only on the left side.
+
+.. seealso::
+ | See how you can define your :ref:`presets `.
+ | To start writing your file, follow the instructions for any Odoo XML page described in
+ :doc:`/developer/howtos/website_themes/layout`.
+
+.. tip::
+ - To complete the exercise, you need to install the **eCommerce** (`website_sale`) and
+ **wishlist** (`website_sale_whishlist`) applications. **Be careful!** Referencing an
+ application in your code that hasn't been installed will result in an error.
+ - | In order to find the templates to activate or not, go to the source code:
+ `odoo/addons/website/views/**`.
+ | For example, you can find all the templates for the header in
+ `website_templates.xml <{GITHUB_PATH}/addons/website/views/website_templates.xml>`_.
+ - To see the effect of your presets, add some **products** (*Airproof Mini*, *Airproof Robin*,
+ *Warranty*, *Charger cable*) and create **eCommerce categories** (*Warranties*, *Accessories*,
+ and *Drones* with *Camera drones* and *Waterproof drones*) in the database. You will find the
+ `product images here <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content>`_.
+ - You will need to activate more views to replicate the Airproof design. Remember to add them
+ throughout the creation of your website.
+
+.. spoiler:: Solutions
+
+ To deactivate the Call-to-action:
+
+ #. The view you have to find is in :file:`odoo/addons/website/views/website_templates.xml l:2113`
+ #. Create your :file:`presets.xml` file with the right records
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/presets.xml``
+
+
+
+
+
+
+ #. In the manifest, add the 2 apps and declare your file.
+
+ .. code-block:: python
+ :caption: ``/website_airproof/__manifest__.py``
+
+ 'depends': ['website_sale', 'website_sale_wishlist'],
+ 'data': [
+ # Options
+ 'data/presets.xml',
+ ]
diff --git a/content/developer/tutorials/website_theme/02_build_website.rst b/content/developer/tutorials/website_theme/02_build_website.rst
new file mode 100644
index 0000000000..32412d4bea
--- /dev/null
+++ b/content/developer/tutorials/website_theme/02_build_website.rst
@@ -0,0 +1,172 @@
+==============================
+Chapter 2 - Build your website
+==============================
+
+.. _tutorials/website_theme/build_website/page:
+
+Create a page
+=============
+
+Now that the theme has been set up, let's move on to creating the content.
+
+First of all, start by creating your first theme page: the home page. For now, only indicate “Hello”
+as content in the page.
+
+.. tip::
+ You will need to deactivate the default homepage.
+
+.. seealso::
+ See reference documentation on how to :ref:`desactivate a default page
+ ` and how to :ref:`start a new page
+ `.
+
+.. spoiler:: Solutions
+
+ .. code-block:: python
+ :caption: ``/website_airproof/__manifest__.py``
+
+ 'data': [
+ # Pages
+ 'data/pages/home.xml',
+ ]
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/pages/home.xml``
+
+
+
+
+
+
+
+
+
+
+ Home
+
+ website_airproof.page_home
+ /
+ qweb
+
+
+
+
+ One step beyond the horizon | Airproof
+
+
+
+
+
+
+
+
+
+.. _tutorials/website_theme/build_website/media:
+
+Add a media
+===========
+
+If you want the client to be able to reuse certain pictures that you are going to add on the
+website, they must be added to the image library.
+
+To do the test, declare the drone image to add it to the library. You will find the `drone picture
+here <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/drone-robin.png>`_.
+
+.. seealso::
+ See reference documentation on how to :ref:`add a media `.
+
+Go to the :guilabel:`Website Builder`, double-click on the :guilabel:`logo`, and you will see the
+drone image in the library.
+
+.. spoiler:: Solutions
+
+ To complete this exercise, you need to:
+
+ #. Put your PNG in the right image folder.
+ #. Create your :file:`images.xml` file. You can find all the necessary information
+ in the `images.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/images.xml>`_
+ file from our example module.
+ #. Declare your file in the :file:`__manifest__.py`.
+
+.. _tutorials/website_theme/build_website/building_blocks:
+
+Add building blocks
+===================
+
+Now, let's get into the real work. Start adding content to the pages.
+
+In an Odoo website, we create the content of a page using building blocks. These can be compared to
+snippets editable by the user in the Website Builder. The standard main container for any snippet
+is a `section`.
+
+Based on the Airproof design, add the following elements to the homepage :
+
+- Create a section with the 3 boxes using the :guilabel:`Big boxes` building block.
+
+ - For this section, you don't want the future user to be able to edit it via the Website Builder.
+ - Put an opacity filter on the background image of the 3 boxes.
+
+- Create another section containing the title and icons.
+
+You can use these `images <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content>`_ and `icons
+<{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/icons>`_.
+
+.. seealso::
+ See reference documentation on how to :ref:`write standard snippets
+ `.
+
+.. image:: 02_build_website/building-blocks.png
+ :alt: Airproof building blocks.
+ :scale: 75%
+
+.. tip::
+ To determine the code needed to create your building blocks :
+
+ - | Create a test page via the website builder.
+ | Drag & drop the building block that interests you and apply the right design.
+ | Use the code generated via :guilabel:`Editor HTML/SCSS` in the menu.
+ - You can also find the original building block code in Odoo :
+ :file:`odoo/addons/website/views/snippets/**.xml`.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `home.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/pages/home.xml>`_.
+
+.. _tutorials/website_theme/build_website/navigation:
+
+Navigation
+==========
+
+For now, the client is fine with the default header but has requested some navigation adjustments.
+
+The client has requested the following changes:
+
+- Remove the link to the homepage and the shop.
+- Add a link to the future “About us” page.
+- Replace the default blog item with a dropdown to display the different blogs: “Our latest news”
+ and “Tutorials”.
+- Add a mega-menu “Waterproof drones” to display the different products.
+
+.. seealso::
+ - You can find the original mega-menu templates code in Odoo :
+ `odoo/addons/website/views/snippets/s_mega_menu_**.xml
+ <{GITHUB_PATH}/addons/website/views/snippets>`_
+ - See reference documentation on how to modifiy the
+ :doc:`/developer/howtos/website_themes/navigation`.
+
+.. image:: 02_build_website/mega-menu.png
+ :alt: Aiproof mega-menu.
+
+.. tip::
+ - Make sure the Blog app is installed and create the two different blogs in the backend.
+ - Create the different products via the backend. You can use these `product pictures
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content>`_.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `menu.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/menu.xml>`_.
diff --git a/content/developer/tutorials/website_theme/02_build_website/building-blocks.png b/content/developer/tutorials/website_theme/02_build_website/building-blocks.png
new file mode 100644
index 0000000000..93076dab99
Binary files /dev/null and b/content/developer/tutorials/website_theme/02_build_website/building-blocks.png differ
diff --git a/content/developer/tutorials/website_theme/02_build_website/mega-menu.png b/content/developer/tutorials/website_theme/02_build_website/mega-menu.png
new file mode 100644
index 0000000000..8f2aebae84
Binary files /dev/null and b/content/developer/tutorials/website_theme/02_build_website/mega-menu.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1.rst b/content/developer/tutorials/website_theme/03_customisation_part1.rst
new file mode 100644
index 0000000000..cebbf52ca4
--- /dev/null
+++ b/content/developer/tutorials/website_theme/03_customisation_part1.rst
@@ -0,0 +1,356 @@
+=================================
+Chapter 3 - Customisation, Part I
+=================================
+
+.. _tutorials/website_theme/customisation_part1/custom_scss:
+
+Add custom SCSS
+===============
+
+You've adjusted Odoo and Bootstrap variables and set presets, yet you still notice disparities
+between your website and the client's design. The only solution is to incorporate custom SCSS.
+
+In :file:`theme.scss`, reproduce the following design elements:
+
+- Add a **green underline** on active nav items.
+- Modify the **arrow** for collapsible nav items.
+- Modify the **slider's arrows** by adding a green background and changing their design.
+
+You will find the various `media here
+<{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/icons>`_.
+
+.. seealso::
+ See reference documentation on how to :ref:`add your SCSS rules `.
+
+.. image:: 03_customisation_part1/menu.png
+ :scale: 50%
+
+.. image:: 03_customisation_part1/slider.png
+
+.. note::
+ | It's always preferable to include all your SCSS rules in `#wrapwrap`. This ID is applied to the
+ div that groups the :guilabel:`header`, :guilabel:`footer`, and :guilabel:`main` content of all
+ your pages.
+ | So you will be sure that your rules will only have an impact on the website parts.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `header.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/layout/header.scss>`_
+ and `caroussel.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/snippets/caroussel.scss>`_.
+
+.. _tutorials/website_theme/customisation_part1/custom_js:
+
+Add custom JS
+=============
+
+Now, let's add a mouse follower to the website. This interactive element will enhance the browsing
+experience, making it more engaging and visually appealing.
+
+.. image:: 03_customisation_part1/mouse-follower.gif
+
+Use your JavaScript skills to implement this.
+
+.. seealso::
+ See reference documentation on how to :ref:`add Javascript code `.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `mouse_follower.js
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/js/mouse_follower.js>`_ and `mouse_follower.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/components/mouse_follower.scss>`_.
+
+.. _tutorials/website_theme/customisation_part1/custom_header:
+
+Create a custom header
+======================
+
+With variables, presets, and custom SCSS in place, it's time to refine the layout and add key
+cross-page elements, starting with the header.
+
+Based on the Airproof design, create a custom header with the following elements:
+
+- A centered logo. Ensure to declare the logo so that it appears automatically in the header.
+- A custom shopping cart icon.
+- A login/user as a button.
+- Navigation text to 14px.
+
+You can find the `logo
+<{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/branding/airproof-logo.svg>`_,
+`cart icon <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/icons/shopping.svg>`_ and
+`template illustration
+<{GITHUB_TUTO_PATH}/website_airproof/static/src/img/wbuilder/template-header-opt.svg>`_.
+
+.. seealso::
+ See reference documentation on how to:
+
+ - create :ref:`custom headers `,
+ - do a :ref:`website_themes/layout/xpath`,
+ - declare a :ref:`website_themes/media/images/use/logo`.
+
+.. image:: 03_customisation_part1/header.png
+
+.. tip::
+ - Base yourself on the code of existing header templates that you can find in
+ `odoo/addons/website/views/website_templates.xml
+ <{GITHUB_PATH}/addons/website/views/website_templates.xml>`_.
+ - A good practise should be to create different files to manage your custom views and templates.
+ For example, everything concerning the general layout (header, footer...) in
+ :file:`website_templates.xml`, everything related to blog in :file:`website_blog_templates.xml`,
+ to event in :file:`website_event_templates.xml`, etc.
+ - | To modify the cart icon, you can use an `XPath`.
+ | Since this is linked to eCommerce, place it in a new file called
+ :file:`website_sale_templates.xml`.
+ - Don't forget to continue making as many modifications as you can through the :file:`Bootstrap
+ variables` and :file:`primary variables` (font, colors, size...). You can use them to help you
+ with this exercise.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example for:
+
+ - the xml structure and to add the template to the options list on
+ `website_template.xml <{GITHUB_TUTO_PATH}/website_airproof/views/website_templates.xml>`_.
+ - disable the default header:
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/presets.xml``
+
+
+
+
+ - record the logo:
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/images.xml``
+
+
+
+
+
+
+ - declare your :file:`website_templates.xml` file along with all the new ones in your
+ :file:`manifest`.
+ - make the use of `primaries
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/primary_variables.scss>`_ like
+ `header-template`, `navbar-font`, `header-font-size`...
+ - use `bootstrap_overridden
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/bootstrap_overridden.scss>`_ like
+ `$navbar-light-color`, `$navbar-light-hover-color`, `$navbar-padding-y`...
+ - add some `scss <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/layout/header.scss>`_
+ rules.
+
+.. _tutorials/website_theme/customisation_part1/custom_footer:
+
+Create a custom footer
+======================
+
+The client is delighted with the new header, as it aligns perfectly with the provided design. Now,
+he wants a matching custom footer.
+
+Based on the Airproof design, create a custom footer with the following elements:
+
+- A section for newsletter subscription.
+- A section for the copyright and social media.
+
+You will find the `icons here <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/icons>`_.
+
+.. seealso::
+ See reference documentation on how to create a :ref:`custom footer
+ ` and adapt the :ref:`website_themes/layout/copyright`.
+
+.. image:: 03_customisation_part1/footer.png
+
+.. tip::
+ - You can enable or disable the copyright section via the presets.
+ - For the newsletter section to work, you need to install the `website_mass_mailing` application.
+
+.. spoiler:: Solutions
+
+ To complete this exercise, you need to:
+
+ - add `mass mailing` to your depends:
+
+ .. code-block:: python
+ :caption: ``/website_airproof/__manifest__.py``
+
+ 'depends': ['website_sale', 'website_sale_wishlist', 'website_blog',
+ 'website_mass_mailing'],
+
+ - find the xml structure and add the template to the options list on
+ `website_template.xml <{GITHUB_TUTO_PATH}/website_airproof/views/website_templates.xml>`_.
+ - disable the default footer and enable the copyright:
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/presets.xml``
+
+
+
+
+
+
+ - make the use of `primaries
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/primary_variables.scss>`_ like
+ `footer-template`, `footer`, `o-cc4-link`...
+ - add a little scss rule for the `newsletter
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/snippets/newsletter.scss>`_ section.
+
+.. _tutorials/website_theme/customisation_part1/custom_building_blocks:
+
+Create your custom building blocks
+==================================
+
+To allow your client to further customize his website, create tailor-made building blocks that he
+can freely drag & drop onto different pages.
+
+Based on the Airproof design, create a custom carousel snippet to showcase drones. Then, add it as
+cover section on your homepage.
+
+#. Create the snippet template and add it to the list of building blocks available in the website
+ builder. Here you will find the `images
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/snippets/s_airproof_caroussel>`_ and
+ `snippet illustration
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/wbuilder/s-airproof-snippet.svg>`_.
+
+ .. seealso::
+ See reference documentation on how to create a :ref:`custom building blocks
+ `.
+
+ .. image:: 03_customisation_part1/custom-building-block.png
+
+#. Add an option in the Website Builder to allow users to choose between a blue or green bubble
+ shadow.
+
+ .. seealso::
+ See reference documentation on how to add :ref:`snippet options
+ `.
+
+ .. image:: 03_customisation_part1/custom-building-block-option.png
+ :scale: 75%
+
+#. Add the snippet on your homepage.
+
+.. tip::
+ Don't forget to always properly declare your new files in your :file:`__manifest__.py` and follow
+ the good :ref:`folder structure ` seen previously.
+
+.. spoiler:: Solutions
+
+ To complete this exercise, you need to:
+
+ #. Create your template.
+
+ - You can find all the necessary information in `s_airproof_carousel.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/views/snippets/s_airproof_carousel.xml>`_ file and
+ `s_airproof_carousel/000.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/snippets/s_airproof_carousel/000.scss>`_
+ file from our example module.
+ - Record your images in `images.xml <{GITHUB_TUTO_PATH}/website_airproof/data/images.xml>`_.
+ - Declare your files in the `__manifest__.py
+ <{GITHUB_TUTO_PATH}/website_airproof/__manifest__.py>`_.
+ - Add it to the list of building blocks. In our example, it looks like this:
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/views/snippets/options.xml``
+
+
+
+
+
+
+
+
+
+
+ Carousel block
+
+
+
+
+
+
+
+ #. Add the option to the Website Builder. In our example, it looks like this:
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/views/snippets/s_airproof_carousel.xml``
+
+
+
+
+
+
+
+ Blue
+ Green
+
+
+
+
+
+ Additionally, the SCSS related to the bubbles in the `s_airproof_carousel/000.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/snippets/s_airproof_carousel/000.scss>`_ file.
+
+ #. Add your snippet to the homepage. You can find all the necessary information in the `home.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/pages/home.xml>`_ file from our example module.
+
+.. _tutorials/website_theme/customisation_part1/custom_dynamic_template:
+
+Create a new dynamic snippets template
+======================================
+
+| Dynamic snippets are useful building blocks. These allow you to fetch information from the backend
+ and display it on the website according to certain filters.
+| There are already several layout templates for displaying dynamic snippets. However, none of the
+ existing templates fully match your client's needs.
+
+Based on the Airproof design, create a custom template that you will apply to a product dynamic
+snippet on the homepage.
+
+#. First, create a custom template that will be added to the list of dynamic products templates. It
+ has to include the following elements:
+
+ - Add a :guilabel:`Discover more` link.
+ - Add a hover effect on cards.
+ - Move the navigation arrows.
+
+ You will find the `icons here <{GITHUB_TUTO_PATH}/website_airproof/static/src/img/content/icons>`_.
+
+ .. seealso::
+ See reference documentation on how to :ref:`create a template for dynamic snippets
+ `.
+
+ .. image:: 03_customisation_part1/custom-template.png
+
+ .. tip::
+ You can verify in the Website Builder that your template appears in the list of available
+ templates for the product dynamic snippet.
+
+#. Then, add a product dynamic snippet with the template you just created to the homepage.
+
+ .. seealso::
+ See reference documentation on how to :ref:`call a template
+ `.
+
+.. spoiler:: Solutions
+
+ To complete this exercise, you need to:
+
+ #. Create your snippet template. You can find all the necessary information in the
+ `options.xml <{GITHUB_TUTO_PATH}/website_airproof/views/snippets/options.xml>`_
+ file and `caroussel.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/snippets/caroussel.scss>`_ file from our
+ example module.
+
+ #. Apply the template to a product dynamic snippet on the homepage. You can find all the
+ necessary information in the `home.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/pages/home.xml>`_ file from our example module.
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/custom-building-block-option.png b/content/developer/tutorials/website_theme/03_customisation_part1/custom-building-block-option.png
new file mode 100644
index 0000000000..512ae796cf
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/custom-building-block-option.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/custom-building-block.png b/content/developer/tutorials/website_theme/03_customisation_part1/custom-building-block.png
new file mode 100644
index 0000000000..bcc5db33e1
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/custom-building-block.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/custom-template.png b/content/developer/tutorials/website_theme/03_customisation_part1/custom-template.png
new file mode 100644
index 0000000000..1b2579de9d
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/custom-template.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/footer.png b/content/developer/tutorials/website_theme/03_customisation_part1/footer.png
new file mode 100644
index 0000000000..7f31a7aaec
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/footer.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/header.png b/content/developer/tutorials/website_theme/03_customisation_part1/header.png
new file mode 100644
index 0000000000..bf91e9ac31
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/header.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/menu.png b/content/developer/tutorials/website_theme/03_customisation_part1/menu.png
new file mode 100644
index 0000000000..ed4a3ff115
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/menu.png differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/mouse-follower.gif b/content/developer/tutorials/website_theme/03_customisation_part1/mouse-follower.gif
new file mode 100644
index 0000000000..11d2b01886
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/mouse-follower.gif differ
diff --git a/content/developer/tutorials/website_theme/03_customisation_part1/slider.png b/content/developer/tutorials/website_theme/03_customisation_part1/slider.png
new file mode 100644
index 0000000000..6a9a969723
Binary files /dev/null and b/content/developer/tutorials/website_theme/03_customisation_part1/slider.png differ
diff --git a/content/developer/tutorials/website_theme/04_customisation_part2.rst b/content/developer/tutorials/website_theme/04_customisation_part2.rst
new file mode 100644
index 0000000000..674f15e2ca
--- /dev/null
+++ b/content/developer/tutorials/website_theme/04_customisation_part2.rst
@@ -0,0 +1,192 @@
+==================================
+Chapter 4 - Customisation, Part II
+==================================
+
+.. _tutorials/website_theme/customisation_part2/background_shape:
+
+Create a custom background shape
+================================
+
+Shapes are decorative elements that can be applied to backgrounds or images. They are SVG files
+that can be animated and customized with different colors.
+
+#. To better align with the website's desired atmosphere, create a custom background shape that the
+ client can reuse on different blocks.
+
+ Create your custom shape using the following setup:
+
+ - Declare your shape. You can find the original `SVG shape here
+ <{GITHUB_TUTO_PATH}/website_airproof/shape-waves.svg>`_.
+ - Set the base color of the shape to the theme's green, and add it to the list of available
+ shapes.
+
+.. seealso::
+ See reference documentation on how to add a :ref:`custom background shapes
+ `.
+
+.. image:: 04_customisation_part2/shape.png
+
+.. tip::
+ | **Be careful,** there is a trick!
+ | In your shape SVG file, you have to use the colors from the default Odoo palette.
+ | Here, I want it to match my primary color 3 (`#CEF8A1`). Therefore, in the SVG file, you must
+ use color 3 from Odoo's default palette (`#F6F6F6`).
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example for:
+
+ - the shape declaration on `shapes.xml <{GITHUB_TUTO_PATH}/website_airproof/data/shapes.xml>`_.
+ - adding the shape to the list thanks to
+ `primary_variable.scss
+ <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/primary_variables.scss>`_ and `option.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/views/snippets/options.xml>`_.
+
+2. Based on the Airproof design, apply the shape you just added to a `Text-Image` building block on
+ the homepage:
+
+ - Ensure the shape is in the right position.
+ - Set its color to the theme's light blue.
+
+.. seealso::
+ See reference documentation on how to use :ref:`background shapes
+ `.
+
+.. image:: 04_customisation_part2/shape-section.png
+
+.. tip::
+ Unlike a standard Odoo shapes, when applying a custom shape to a section, replace `web_editor`
+ with `illustration` in the shape class.
+
+.. spoiler:: Solutions
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/pages/home.xml``
+
+
+
+
+.. _tutorials/website_theme/customisation_part2/background_gradient:
+
+Add a background gradient
+=========================
+
+Apply a custom background gradient to your ”*Latest products*” block, transitioning from blue
+`rgb(11, 142, 230)` to dark blue `rgb(41, 128, 187)`.
+
+.. seealso::
+ See reference documentation on how to use :doc:`/developer/howtos/website_themes/gradients`.
+
+.. spoiler:: Solutions
+
+ .. code-block:: xml
+ :caption: ``/website_airproof/data/pages/home.xml``
+
+
+
+
+.. _tutorials/website_theme/customisation_part2/animations:
+
+Animations
+==========
+
+The client loves the overall design but finds the page a bit static. Enhance page interactivity with
+animations such as `fade-in`, `rotate`, `bounce`, etc. These can be applied to columns, images,
+texts, buttons…
+
+Based on the airproof design, animate the following elements:
+
+- the text of the first slide of the carousel.
+- the sticker and the photo of the drone from the first slide.
+- the 4 columns with icons.
+
+Adjust animation delays for smoother transitions.
+
+.. seealso::
+ See reference documentation on how to apply :doc:`/developer/howtos/website_themes/animations`.
+
+.. image:: 04_customisation_part2/animations.gif
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `home.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/pages/home.xml>`_.
+
+ .. code-block:: xml
+ :caption: Image animation
+
+
+
+
+
+ .. code-block:: xml
+ :caption: Text animation
+
+ One
+ step
+
+ .. code-block:: xml
+ :caption: Columns animation
+
+
+
+
+.. _tutorials/website_theme/customisation_part2/forms:
+
+Forms
+=====
+
+The forms in Odoo are very powerful. They can send emails directly to a personal inbox or integrate
+directly with other Odoo applications. This is great, as one of your client's main priorities is
+after-sales service. Therefore, the contact form must be properly configured.
+
+Based on the airproof design, create a contact page. Remember to disable the default one and add the
+new page link to the menu. The client has the following requests for their contact form:
+
+- *Name* and *email address* field.
+- *Company name* field.
+- *Conditional VAT* field displayed only if *Company name* is filled in.
+- All fields should be mandatory, except for *Company name*.
+- Form submission must trigger an email.
+- After form submission, the `thank-you message` should remain visible on the contact page.
+
+.. seealso::
+ See reference documentation on how to:
+
+ - :ref:`deactivate default pages `,
+ - :ref:`create a new page `,
+ - :ref:`add a menu item `,
+ - :doc:`create a form `.
+
+.. tip::
+ To determine the correct code for your form:
+
+ - | Create a test page via the Website Builder.
+ | Drag & drop the building block that interests you and apply the right design.
+ | Use the code generated through :guilabel:`Editor HTML/SCSS`.
+ - You can also find the original building block code in Odoo:
+ `odoo/addons/website/views/snippets/s_website_form.xml
+ <{GITHUB_PATH}/addons/website/views/snippets/s_website_form.xml>`_.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `contact.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/pages/contact.xml>`_.
diff --git a/content/developer/tutorials/website_theme/04_customisation_part2/animations.gif b/content/developer/tutorials/website_theme/04_customisation_part2/animations.gif
new file mode 100644
index 0000000000..9d08e5f357
Binary files /dev/null and b/content/developer/tutorials/website_theme/04_customisation_part2/animations.gif differ
diff --git a/content/developer/tutorials/website_theme/04_customisation_part2/shape-section.png b/content/developer/tutorials/website_theme/04_customisation_part2/shape-section.png
new file mode 100644
index 0000000000..d5cf0f3feb
Binary files /dev/null and b/content/developer/tutorials/website_theme/04_customisation_part2/shape-section.png differ
diff --git a/content/developer/tutorials/website_theme/04_customisation_part2/shape.png b/content/developer/tutorials/website_theme/04_customisation_part2/shape.png
new file mode 100644
index 0000000000..591fe0e238
Binary files /dev/null and b/content/developer/tutorials/website_theme/04_customisation_part2/shape.png differ
diff --git a/content/developer/tutorials/website_theme/05_dynamic_templates.rst b/content/developer/tutorials/website_theme/05_dynamic_templates.rst
new file mode 100644
index 0000000000..29d44592bd
--- /dev/null
+++ b/content/developer/tutorials/website_theme/05_dynamic_templates.rst
@@ -0,0 +1,86 @@
+=============================
+Chapter 5 - Dynamic templates
+=============================
+
+.. _tutorials/website_theme/dynamic_templates/shop:
+
+Adapt the shop template
+=======================
+
+Now, let's adapt the dynamic sections of the website. As you may know, some pages such as those for
+eCommerce are automatically generated. Pages like the shop, product, and checkout are automatically
+generated when the `website_sale` application is installed. These template pages pull their
+displayed information from the backend.
+
+To modify these pages, we need to edit the standard Odoo template. This can be done using SCSS,
+presets, and especially XPath. Locate the standard Odoo template you want to modify and extend it
+using `XPath`. Following the Airproof design, let's begin by modifying the shop view.
+
+#. First, locate the standard template in Odoo : :menuselection:`website_sale --> templates.xml -->
+ id="products"`.
+#. Apply all changes in your :file:`website_sale_templates.xml` file. Start by:
+
+ - Add a banner.
+ - Adapt the layout of the e-commerce category filtering on the left.
+ - Remove the search bar (you can remove it from both the shop and the product pages at the same
+ time).
+ - Move the breadcrumb.
+ - Hide the list or grid view option.
+ - Create the appropriate design and information for the product cards.
+
+.. image:: 05_dynamic_templates/airproof-shop-page.png
+ :align: center
+
+.. tip::
+ - Apply your modifications using presets, XPath and SCSS.
+ - To enable attribute/variant filtering, activate the
+ :doc:`/applications/sales/sales/products_prices/products/variants` option in the
+ website backend settings and :ref:`configure attributes and variants
+ ` for the products.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `presets.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/presets.xml>`_, `website_sale_templates.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/views/website_sale_templates.xml>`_ part *shop page*, and
+ `shop.scss <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/pages/shop.scss>`_.
+
+.. _tutorials/website_theme/dynamic_templates/product:
+
+Adapt the product page template
+===============================
+
+The client is thrilled with the shop modifications. Next, let's apply our design to the product
+pages. Based on the Airproof design below, adapt a few elements including:
+
+- Remove the search bar (if not done with the previous exercise).
+- Remove the quantity selector, Terms and Conditions, and share icons.
+- Update the :guilabel:`Add to cart` button icon.
+- Insert a title above the product specifications (this section appears only when the product
+ has one variant per attribute).
+- Design the appropriate layout for the carousel.
+- Add a title and apply the previously created product template to the `Alternative products`
+ section (ensure alternative products are assigned on the product in the backend for this section
+ to appear).
+- Implement a new drop zone below product details, visible on all products. As a use case, add an
+ `Text-Image` building block using the Website Builder (e.g.: See Airproof product page screenshot
+ with “*Six reasons to buy…*”).
+
+.. seealso::
+ See reference documentation on how to create a :ref:`website_themes/layout/dropzone`.
+
+.. image:: 05_dynamic_templates/airproof-product-page.png
+ :align: center
+
+.. tip::
+ - Make your modifications using presets, XPath, and SCSS. Be sure to comment your code properly
+ so you can find your way around.
+ - The drop zone will be visible on all products. To create a specific dropzone per product, you
+ need to add a new field to the product model.
+
+.. spoiler:: Solutions
+
+ Find the solution in our Airproof example on `presets.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/data/presets.xml>`_, `website_sale_templates.xml
+ <{GITHUB_TUTO_PATH}/website_airproof/views/website_sale_templates.xml>`_ part *product page*, and
+ `product_page.scss <{GITHUB_TUTO_PATH}/website_airproof/static/src/scss/pages/product_page.scss>`_.
diff --git a/content/developer/tutorials/website_theme/05_dynamic_templates/airproof-product-page.png b/content/developer/tutorials/website_theme/05_dynamic_templates/airproof-product-page.png
new file mode 100644
index 0000000000..39ef5d0983
Binary files /dev/null and b/content/developer/tutorials/website_theme/05_dynamic_templates/airproof-product-page.png differ
diff --git a/content/developer/tutorials/website_theme/05_dynamic_templates/airproof-shop-page.png b/content/developer/tutorials/website_theme/05_dynamic_templates/airproof-shop-page.png
new file mode 100644
index 0000000000..eb3a8654e3
Binary files /dev/null and b/content/developer/tutorials/website_theme/05_dynamic_templates/airproof-shop-page.png differ
diff --git a/content/developer/tutorials/website_theme/06_going_live.rst b/content/developer/tutorials/website_theme/06_going_live.rst
new file mode 100644
index 0000000000..e9e4bdfad3
--- /dev/null
+++ b/content/developer/tutorials/website_theme/06_going_live.rst
@@ -0,0 +1,84 @@
+======================
+Chapter 6 - Going live
+======================
+
+.. _tutorials/website_theme/going_live/translations:
+
+Translations
+============
+
+Congratulations! Your client has a beautifully designed homepage and contact page, and the eCommerce
+is fully adapted to the Airproof design. Amazing!
+
+Now, the client wants the website translated into French. To do so:
+
+#. Add French to the website in the settings and enable the language switcher in the header via
+ presets.
+#. Then for the translation itself, you have two options. We shall therefore test both:
+
+ - Translate the content of the homepage carousel through the backend.
+ - But for the menu, make the translations through the frontend.
+
+#. Export the French :file:`.po` file for your Airproof module and place it in the :file:`/i18n`
+ translations folder.
+#. If you would like, you can add more translations directly by editing the :file:`.po`
+ file. (Using Poedit software, your code editor, or another translation tool.)
+
+.. seealso::
+ See reference documentation on :ref:`website_themes/translations/backend` and
+ :ref:`website_themes/translations/frontend` translations, and how to
+ :ref:`website_themes/translations/export` them.
+
+.. note::
+ - Be careful when using Poedit, as it doesn't handle tags with styles well and generates an
+ :file:`.mo` file.
+ - To see the changes made directly via the :file:`.po` file, you will need to manually import the
+ file.
+
+.. spoiler:: Solutions
+
+ Take a look at what the file `i18n/fr_BE.po <{GITHUB_TUTO_PATH}/website_airproof/i18n/fr_BE.po>`_
+ of our Airproof example looks like.
+
+.. _tutorials/website_theme/going_live/module_import:
+
+Module import
+=============
+
+Great job! The website is now completely finished and your module is ready for installation in the
+client's SaaS database.
+
+Just before that, test the import process on a new database.
+
+.. seealso::
+ See reference documentation on how to :doc:`deploy a module
+ ` on an Odoo SaaS database.
+
+.. tip::
+ - Ensure the `base_import_module` is installed on the database before the module installation.
+ - Verify all required applications are installed.
+ - Skip the theme installation steps and start from scratch.
+ - Manually import translations after module installation, as they won't apply automatically.
+
+Conclusion
+==========
+
+Congratulations on completing the **Build a module for a website theme** tutorial!
+You've successfully navigated through every stage, from setting up your development environment to
+launching a fully customized Odoo website theme.
+
+Throughout this journey, you've mastered:
+
+| ✅ **Theme module creation** - setting up the structure, declaring Odoo and Bootstrap variables.
+| ✅ **Website building** - creating pages, adding media, and constructing dynamic building blocks.
+| ✅ **Advanced customization** - implementing custom SCSS, JavaScript, headers, footers, and unique
+ design elements.
+| ✅ **Visual enhancements** - designing background shapes, gradients, and animations for an
+ engaging user experience.
+| ✅ **eCommerce optimization** - adapting shop and product templates for a seamless shopping
+ experience.
+| ✅ **Final preparations** - managing translations and ensuring a smooth module import.
+
+| With these skills, you're now ready to design and develop professional, fully customized website
+ themes. Well done!
+| We can't wait to see the amazing themes you'll create in the future.
diff --git a/content/developer/tutorials/website_theme/airproof-home.png b/content/developer/tutorials/website_theme/airproof-home.png
new file mode 100644
index 0000000000..60cfbdc6dd
Binary files /dev/null and b/content/developer/tutorials/website_theme/airproof-home.png differ
diff --git a/content/developer/tutorials/website/basic-list.png b/content/developer/tutorials/website_theme/basic-list.png
similarity index 100%
rename from content/developer/tutorials/website/basic-list.png
rename to content/developer/tutorials/website_theme/basic-list.png
diff --git a/content/developer/tutorials/website/helloworld.png b/content/developer/tutorials/website_theme/helloworld.png
similarity index 100%
rename from content/developer/tutorials/website/helloworld.png
rename to content/developer/tutorials/website_theme/helloworld.png
diff --git a/content/developer/tutorials/website/layout.png b/content/developer/tutorials/website_theme/layout.png
similarity index 100%
rename from content/developer/tutorials/website/layout.png
rename to content/developer/tutorials/website_theme/layout.png