diff --git a/conf.py b/conf.py index 1b93f43a70..74b07215f9 100644 --- a/conf.py +++ b/conf.py @@ -165,6 +165,9 @@ # Content tabs 'sphinx_tabs.tabs', + # Cards + 'cards', + # Spoilers 'spoilers', diff --git a/content/contributing/documentation/rst_cheat_sheet.rst b/content/contributing/documentation/rst_cheat_sheet.rst index 63f6d0fee5..7239d0b3d3 100644 --- a/content/contributing/documentation/rst_cheat_sheet.rst +++ b/content/contributing/documentation/rst_cheat_sheet.rst @@ -1003,6 +1003,54 @@ set, the label is used instead of the language for grouping tabs. console.log("Hello World"); +.. _contributing/cards: + +Cards +===== + +.. list-table:: + :class: o-showcase-table + + * - .. cards:: + + .. card:: Documentation + :target: ../documentation + :tag: Step-by-step guide + :large: + + Use this guide to acquire the tools and knowledge you need to write documentation. + + .. card:: Content guidelines + :target: content_guidelines + + List of guidelines and trips and tricks to make your content shine at its brightest! + + .. card:: RST guidelines + :target: rst_guidelines + + List of technical guidelines to observe when writing with reStructuredText. + + * - .. code-block:: text + + .. cards:: + + .. card:: Documentation + :target: ../documentation + :tag: Step-by-step guide + :large: + + Use this guide to acquire the tools and knowledge you need to write documentation. + + .. card:: Content guidelines + :target: content_guidelines + + List of guidelines and trips and tricks to make your content shine at its brightest! + + .. card:: RST guidelines + :target: rst_guidelines + + List of technical guidelines to observe when writing with reStructuredText. + .. _contributing/document-metadata: Document metadata diff --git a/content/developer/howtos.rst b/content/developer/howtos.rst index e3415ea7e4..8a2d04a080 100644 --- a/content/developer/howtos.rst +++ b/content/developer/howtos.rst @@ -16,95 +16,40 @@ How-to guides howtos/provide_iap_services howtos/connect_device -.. raw:: html - -
- - -
-
-

Write lean easy-to-maintain CSS

-

- Follow this guide to keep the technical debt of your CSS code under control. -

-
- -
-
- - -
-
-

Web services

-

- Learn more about Odoo's web services. -

-
- -
-
- - -
-
-

Multi-company guidelines

-

- Learn how to manage multiple companies and deal with the records-related - specificities of a multi-company environment. -

-
- -
-
- - -
-
-

Accounting localization

-

- Learn how to build a localization module, create bank operation models and - dynamic reports. -

-
- -
-
- - -
-
-

Translating modules

-

- Learn how to provide translation abilities to your module. -

-
- -
-
- - -
-
-

Provide IAP services

-

- Learn how to provide ongoing services with Odoo's In-App Purchase (IAP). -

-
- -
-
- - -
-
-

Connect with a device

-

- Learn how to enable a module to detect and communicate with an IoT device. -

-
- -
-
- -
+.. cards:: + + .. card:: Write lean easy-to-maintain CSS + :target: howtos/scss_tips + + Follow this guide to keep the technical debt of your CSS code under control. + + .. card:: Web services + :target: howtos/web_services + + Learn more about Odoo's web services. + + .. card:: Multi-company guidelines + :target: howtos/company + + Learn how to manage multiple companies and deal with the records-related specificities of a + multi-company environment. + + .. card:: Accounting localization + :target: howtos/accounting_localization + + Learn how to build a localization module, create bank operation models and dynamic reports. + + .. card:: Translating modules + :target: howtos/translations + + Learn how to provide translation abilities to your module. + + .. card:: Provide IAP services + :target: howtos/provide_iap_services + + Learn how to provide ongoing services with Odoo's In-App Purchase (IAP). + + .. card:: Connect with a device + :target: howtos/connect_device + + Learn how to enable a module to detect and communicate with an IoT device. diff --git a/content/developer/tutorials.rst b/content/developer/tutorials.rst index 2e0999ad1c..1a2d3088d0 100644 --- a/content/developer/tutorials.rst +++ b/content/developer/tutorials.rst @@ -17,124 +17,58 @@ Tutorials tutorials/pdf_reports tutorials/dashboards -.. raw:: html - - -
- - -
-
-

Getting started

-

- Develop your own module with the Odoo framework. This step-by-step tutorial - is crafted for newcomers and any other individual curious about Odoo - development. -

-
- -
-
- - -
-
-

Discover the JavaScript Framework

-

- Learn everything you need to know about the JavaScript framework of Odoo. - This tutorial will teach you how to build custom components and views, give - life to your application, and even re-introduce the kitten mode. -

-
- -
-
- - -
-
-

Define module data

-

- Define master and demo data for an Odoo module, leveraging the strengths of - the CSV and XML file formats to accommodate specific data requirements. -

-
- -
-
- - -
-
-

Restrict access to data

-

- Implement security measures to restrict access to sensitive data with the - help of groups, access rights, and record rules. -

-
- -
-
- - -
-
-

Safeguard your code with unit tests

-

- Write effective unit tests in Python to ensure the resilience of your code - and safeguard it against unexpected behaviors and regressions. -

-
- -
-
- - -
-
-

Reuse code with mixins

-

- Create mixins to code features once and reuse them in multiple models. -

-
- -
-
- - -
-
-

Build PDF reports

-

- Use QWeb, Odoo's powerful templating engine, to create custom PDF reports for - your documents. -

-
- -
-
- - -
-
-

Visualize data in dashboards

-

- Create data visualization dashboards using the enterprise edition "Dashboard" - view and so-called "SQL views". -

-
- -
-
- -
+.. cards:: + + .. card:: Getting started + :target: tutorials/getting_started + :tag: Beginner + :large: + + Develop your own module with the Odoo framework. This step-by-step tutorial is crafted for + newcomers and any other individual curious about Odoo development. + + .. card:: Discover the JavaScript Framework + :target: tutorials/discover_js_framework + :tag: Beginner + :large: + + Learn everything you need to know about the JavaScript framework of Odoo. This tutorial will + teach you how to build custom components and views, give life to your application, and even + re-introduce the kitten mode. + + .. card:: Define module data + :target: tutorials/define_module_data + :tag: Beginner + + Define master and demo data for an Odoo module, leveraging the strengths of the CSV and XML + file formats to accommodate specific data requirements. + + .. card:: Restrict access to data + :target: tutorials/restrict_data_access + :tag: Beginner + + Implement security measures to restrict access to sensitive data with the help of groups, + access rights, and record rules. + + .. card:: Safeguard your code with unit tests + :target: tutorials/unit_tests + :tag: Beginner + + Write effective unit tests in Python to ensure the resilience of your code and safeguard it + against unexpected behaviors and regressions. + + .. card:: Reuse code with mixins + :target: tutorials/mixins + + Create mixins to code features once and reuse them in multiple models. + + .. card:: Build PDF reports + :target: tutorials/pdf_reports + + Use QWeb, Odoo's powerful templating engine, to create custom PDF reports for your documents. + + .. card:: Visualize data in dashboards + :target: tutorials/dashboards + + Create data visualization dashboards using the enterprise edition "Dashboard" view and + so-called "SQL views". diff --git a/extensions/cards/__init__.py b/extensions/cards/__init__.py new file mode 100644 index 0000000000..a06f37d8f0 --- /dev/null +++ b/extensions/cards/__init__.py @@ -0,0 +1,125 @@ +from pathlib import Path + +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util.docutils import SphinxDirective + + +class Cards(SphinxDirective): + """ Implement a `cards` directive as a Bootstrap `row`. """ + has_content = True + + def run(self): + """ Process the content of the directive. + + We use custom node classes to represent HTML elements (e.g., 'div') rather than the + corresponding sphinx.nodes.* class (e.g., sphinx.nodes.container) to prevent automatically + setting the name of the node as class (e.g., "container") on the element. + """ + self.assert_has_content() + + div_row = Div(classes=[ + 'row', 'row-cols-1', 'row-cols-md-2', 'row-cols-xl-3', 'row-cols-xxl-4', 'g-4', 'mb-4' + ]) + self.state.nested_parse(self.content, self.content_offset, div_row) + return [div_row] + + +class Card(SphinxDirective): + """ Implement a `card` directive with Bootstrap's card component. """ + required_arguments = 1 + final_argument_whitespace = True + option_spec = { + 'target': directives.unchanged_required, + 'tag': directives.unchanged, + 'large': directives.flag, + } + has_content = True + + def run(self): + """ Process the content of the directive. + + We use custom node classes to represent HTML elements (e.g., 'div') rather than the + corresponding sphinx.nodes.* class (e.g., sphinx.nodes.container) to prevent automatically + setting the name of the node as class (e.g., "container") on the element. + """ + self.assert_has_content() + + current_document = f'{self.env.docname}.rst' + target_document = f'{self.options["target"]}.rst' + if target_document.startswith('/'): + raise self.warning(f"card directive's target starts with a '/'") + target_file = Path(self.env.srcdir) / Path(current_document).parent / target_document + if not target_file.exists(): + raise self.warning(f"card directive targets nonexisting document '{target_document}'") + + a_col_href = target_document.replace('.rst', '.html') + a_col_classes = ['o_toctree_card'] + if 'large' in self.options: + a_col_classes += ['col-md-12', 'col-xl-8', 'col-xxl-6'] + else: + a_col_classes += ['col'] + a_col = A(href=a_col_href, classes=a_col_classes) + + div_card = Div(classes=['card', 'h-100']) + a_col += div_card + + div_card_body = Div(classes=['card-body', 'pb-0']) + div_card += div_card_body + + h4_title = H4(classes=['card-title', 'text-primary', 'mb-1']) + h4_title += nodes.Text(self.arguments[0]) + div_card_body += h4_title + + p_card_text = nodes.paragraph(classes=['card-text', 'text-dark', 'fw-normal']) + p_card_text += nodes.Text('\n'.join(self.content)) + div_card_body += p_card_text + + div_card_footer = Div(classes=['card-footer', 'border-0']) + div_card += div_card_footer + + if 'tag' in self.options: + span_badge = Span(classes=['badge', 'rounded-pill', 'bg-dark', 'mt-auto', 'mb-2']) + div_card_footer += span_badge + span_badge += nodes.Text(self.options['tag']) + + return [a_col] + + +class Div(nodes.General, nodes.Element): + custom_tag_name = 'div' + + +class A(nodes.General, nodes.Element): + custom_tag_name = 'a' + + +class Span(nodes.General, nodes.Element): + custom_tag_name = 'span' + + +class H4(nodes.General, nodes.Element): + custom_tag_name = 'h4' + + +def visit_node(translator, node): + custom_attr = {k: v for k, v in node.attributes.items() if k not in node.known_attributes} + translator.body.append(translator.starttag(node, node.custom_tag_name, **custom_attr).rstrip()) + + +def depart_node(translator, node): + translator.body.append(f'') + + +def setup(app): + app.add_directive('cards', Cards) + app.add_directive('card', Card) + app.add_node(Div, html=(visit_node, depart_node)) + app.add_node(A, html=(visit_node, depart_node)) + app.add_node(Span, html=(visit_node, depart_node)) + app.add_node(H4, html=(visit_node, depart_node)) + + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + }