[ADD] extensions/cards: add an extension to implement cards

The extension adds two new directive:
- `cards` is the row container for one or more `card` directives.
- `card` is the implementation of a Bootstrap card that accepts a
  `target` argument for the href of the card, a `large` option to render
  the card on two columns, a `tag` option to display a single arbitrary
  tag on the card, and arbitrary content that is shown in the card
  body.

task-3141419

closes odoo/documentation#3685

X-original-commit: 72e636da5f
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
Antoine Vandevenne (anv) 2023-01-18 16:37:41 +00:00
parent 178bce67bf
commit 34bc63d2bc
5 changed files with 250 additions and 195 deletions

View File

@ -165,6 +165,9 @@ extensions = [
# Content tabs
'sphinx_tabs.tabs',
# Cards
'cards',
# Spoilers
'spoilers',

View File

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

View File

@ -16,95 +16,40 @@ How-to guides
howtos/provide_iap_services
howtos/connect_device
.. raw:: html
.. cards::
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 row-cols-xxl-4 g-4 mb-4">
.. card:: Write lean easy-to-maintain CSS
:target: howtos/scss_tips
<a class="o_toctree_card col" href="howtos/scss_tips.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Write lean easy-to-maintain CSS</h4>
<p class="card-text text-dark fw-normal">
Follow this guide to keep the technical debt of your CSS code under control.
</p>
</div>
<div class="card-footer border-0">
</div>
</div>
</a>
Follow this guide to keep the technical debt of your CSS code under control.
<a class="o_toctree_card col" href="howtos/web_services.html">
<div class="card h-100 pb-0">
<div class="card-body">
<h4 class="card-title text-primary mb-1">Web services</h4>
<p class="card-text text-dark fw-normal">
Learn more about Odoo's web services.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
.. card:: Web services
:target: howtos/web_services
<a class="o_toctree_card col" href="howtos/company.html">
<div class="card h-100 pb-0">
<div class="card-body">
<h4 class="card-title text-primary mb-1">Multi-company guidelines</h4>
<p class="card-text text-dark fw-normal">
Learn how to manage multiple companies and deal with the records-related
specificities of a multi-company environment.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
Learn more about Odoo's web services.
<a class="o_toctree_card col" href="howtos/accounting_localization.html">
<div class="card h-100 pb-0">
<div class="card-body">
<h4 class="card-title text-primary mb-1">Accounting localization</h4>
<p class="card-text text-dark fw-normal">
Learn how to build a localization module, create bank operation models and
dynamic reports.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
.. card:: Multi-company guidelines
:target: howtos/company
<a class="o_toctree_card col" href="howtos/translations.html">
<div class="card h-100 pb-0">
<div class="card-body">
<h4 class="card-title text-primary mb-1">Translating modules</h4>
<p class="card-text text-dark fw-normal">
Learn how to provide translation abilities to your module.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
Learn how to manage multiple companies and deal with the records-related specificities of a
multi-company environment.
<a class="o_toctree_card col" href="howtos/provide_iap_services.html">
<div class="card h-100 pb-0">
<div class="card-body">
<h4 class="card-title text-primary mb-1">Provide IAP services</h4>
<p class="card-text text-dark fw-normal">
Learn how to provide ongoing services with Odoo's In-App Purchase (IAP).
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
.. card:: Accounting localization
:target: howtos/accounting_localization
<a class="o_toctree_card col" href="howtos/connect_device.html">
<div class="card h-100 pb-0">
<div class="card-body">
<h4 class="card-title text-primary mb-1">Connect with a device</h4>
<p class="card-text text-dark fw-normal">
Learn how to enable a module to detect and communicate with an IoT device.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
Learn how to build a localization module, create bank operation models and dynamic reports.
</div>
.. 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.

View File

@ -17,124 +17,58 @@ Tutorials
tutorials/pdf_reports
tutorials/dashboards
.. raw:: html
.. cards::
<!-- 12 col on small screen, 6 on md, 3 on xl, 3 on xxl -->
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 row-cols-xxl-4 g-4 mb-4">
<!-- Big card with badge rounded-pill -->
<a class="o_toctree_card col-md-12 col-xl-8 col-xxl-6" href="tutorials/getting_started.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Getting started</h4>
<p class="card-text text-dark fw-normal">
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.
</p>
</div>
<div class="card-footer border-0">
<span class="badge rounded-pill bg-dark mt-auto mb-2">Beginner</span>
</div>
</div>
</a>
.. card:: Getting started
:target: tutorials/getting_started
:tag: Beginner
:large:
<a class="o_toctree_card col-md-12 col-xl-8 col-xxl-6" href="tutorials/discover_js_framework.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Discover the JavaScript Framework</h4>
<p class="card-text text-dark fw-normal">
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.
</p>
</div>
<div class="card-footer border-0">
<span class="badge rounded-pill bg-dark mt-auto mb-2">Beginner</span>
</div>
</div>
</a>
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.
<a class="o_toctree_card col" href="tutorials/define_module_data.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Define module data</h4>
<p class="card-text text-dark fw-normal">
Define master and demo data for an Odoo module, leveraging the strengths of
the CSV and XML file formats to accommodate specific data requirements.
</p>
</div>
<div class="card-footer border-0">
<span class="badge rounded-pill bg-dark mt-auto mb-2">Beginner</span>
</div>
</div>
</a>
.. card:: Discover the JavaScript Framework
:target: tutorials/discover_js_framework
:tag: Beginner
:large:
<a class="o_toctree_card col" href="tutorials/restrict_data_access.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Restrict access to data</h4>
<p class="card-text text-dark fw-normal">
Implement security measures to restrict access to sensitive data with the
help of groups, access rights, and record rules.
</p>
</div>
<div class="card-footer border-0">
<span class="badge rounded-pill bg-dark mt-auto mb-2">Beginner</span>
</div>
</div>
</a>
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.
<a class="o_toctree_card col" href="tutorials/unit_tests.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Safeguard your code with unit tests</h4>
<p class="card-text text-dark fw-normal">
Write effective unit tests in Python to ensure the resilience of your code
and safeguard it against unexpected behaviors and regressions.
</p>
</div>
<div class="card-footer border-0">
<span class="badge rounded-pill bg-dark mt-auto mb-2">Beginner</span>
</div>
</div>
</a>
.. card:: Define module data
:target: tutorials/define_module_data
:tag: Beginner
<a class="o_toctree_card col" href="tutorials/mixins.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Reuse code with mixins</h4>
<p class="card-text text-dark fw-normal">
Create mixins to code features once and reuse them in multiple models.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
Define master and demo data for an Odoo module, leveraging the strengths of the CSV and XML
file formats to accommodate specific data requirements.
<a class="o_toctree_card col" href="tutorials/pdf_reports.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Build PDF reports</h4>
<p class="card-text text-dark fw-normal">
Use QWeb, Odoo's powerful templating engine, to create custom PDF reports for
your documents.
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
.. card:: Restrict access to data
:target: tutorials/restrict_data_access
:tag: Beginner
<a class="o_toctree_card col" href="tutorials/dashboards.html">
<div class="card h-100">
<div class="card-body pb-0">
<h4 class="card-title text-primary mb-1">Visualize data in dashboards</h4>
<p class="card-text text-dark fw-normal">
Create data visualization dashboards using the enterprise edition "Dashboard"
view and so-called "SQL views".
</p>
</div>
<div class="card-footer border-0"></div>
</div>
</a>
Implement security measures to restrict access to sensitive data with the help of groups,
access rights, and record rules.
</div>
.. 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".

View File

@ -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'</{node.custom_tag_name}>')
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,
}