diff --git a/content/developer/tutorials/website.rst b/content/developer/tutorials/website.rst deleted file mode 100644 index eaca1d6b0..000000000 --- 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`` - - - - - - - -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`` - - - - - - - -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`` - - - - - - - -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`` - - - -then change the list of model to link to our new controller: - - -.. code-block:: xml - :caption: ``academy/templates.xml`` - - - -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`` - - - -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`` - -
-
-

-

Last modified:

-
-
-

- -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`` - -
-
-

-

Last modified:

-
-
-

- -or a relative display: - -.. code-block:: xml - :caption: ``academy/templates.xml`` - -
-
-

-

Last modified:

-
-
-

- -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`` - - - -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/basic-list.png b/content/developer/tutorials/website/basic-list.png deleted file mode 100644 index 3eb76ddf7..000000000 Binary files a/content/developer/tutorials/website/basic-list.png and /dev/null differ diff --git a/content/developer/tutorials/website/helloworld.png b/content/developer/tutorials/website/helloworld.png deleted file mode 100644 index fe76ad88b..000000000 Binary files a/content/developer/tutorials/website/helloworld.png and /dev/null differ diff --git a/content/developer/tutorials/website/layout.png b/content/developer/tutorials/website/layout.png deleted file mode 100644 index f3fcaf3ac..000000000 Binary files a/content/developer/tutorials/website/layout.png and /dev/null differ