[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#3689

X-original-commit: 34bc63d2bc
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 0fb94a128e
commit 009ae8ffde
5 changed files with 250 additions and 195 deletions

View File

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

View File

@ -1003,6 +1003,54 @@ set, the label is used instead of the language for grouping tabs.
console.log("Hello World"); 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: .. _contributing/document-metadata:
Document metadata Document metadata

View File

@ -16,95 +16,40 @@ How-to guides
howtos/provide_iap_services howtos/provide_iap_services
howtos/connect_device 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"> Follow this guide to keep the technical debt of your CSS code under control.
<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>
<a class="o_toctree_card col" href="howtos/web_services.html"> .. card:: Web services
<div class="card h-100 pb-0"> :target: howtos/web_services
<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>
<a class="o_toctree_card col" href="howtos/company.html"> Learn more about Odoo's web services.
<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>
<a class="o_toctree_card col" href="howtos/accounting_localization.html"> .. card:: Multi-company guidelines
<div class="card h-100 pb-0"> :target: howtos/company
<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>
<a class="o_toctree_card col" href="howtos/translations.html"> Learn how to manage multiple companies and deal with the records-related specificities of a
<div class="card h-100 pb-0"> multi-company environment.
<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>
<a class="o_toctree_card col" href="howtos/provide_iap_services.html"> .. card:: Accounting localization
<div class="card h-100 pb-0"> :target: howtos/accounting_localization
<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>
<a class="o_toctree_card col" href="howtos/connect_device.html"> Learn how to build a localization module, create bank operation models and dynamic reports.
<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>
</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/pdf_reports
tutorials/dashboards tutorials/dashboards
.. raw:: html .. cards::
<!-- 12 col on small screen, 6 on md, 3 on xl, 3 on xxl --> .. card:: Getting started
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 row-cols-xxl-4 g-4 mb-4"> :target: tutorials/getting_started
<!-- Big card with badge rounded-pill --> :tag: Beginner
<a class="o_toctree_card col-md-12 col-xl-8 col-xxl-6" href="tutorials/getting_started.html"> :large:
<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>
<a class="o_toctree_card col-md-12 col-xl-8 col-xxl-6" href="tutorials/discover_js_framework.html"> Develop your own module with the Odoo framework. This step-by-step tutorial is crafted for
<div class="card h-100"> newcomers and any other individual curious about Odoo development.
<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>
<a class="o_toctree_card col" href="tutorials/define_module_data.html"> .. card:: Discover the JavaScript Framework
<div class="card h-100"> :target: tutorials/discover_js_framework
<div class="card-body pb-0"> :tag: Beginner
<h4 class="card-title text-primary mb-1">Define module data</h4> :large:
<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>
<a class="o_toctree_card col" href="tutorials/restrict_data_access.html"> Learn everything you need to know about the JavaScript framework of Odoo. This tutorial will
<div class="card h-100"> teach you how to build custom components and views, give life to your application, and even
<div class="card-body pb-0"> re-introduce the kitten mode.
<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>
<a class="o_toctree_card col" href="tutorials/unit_tests.html"> .. card:: Define module data
<div class="card h-100"> :target: tutorials/define_module_data
<div class="card-body pb-0"> :tag: Beginner
<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>
<a class="o_toctree_card col" href="tutorials/mixins.html"> Define master and demo data for an Odoo module, leveraging the strengths of the CSV and XML
<div class="card h-100"> file formats to accommodate specific data requirements.
<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>
<a class="o_toctree_card col" href="tutorials/pdf_reports.html"> .. card:: Restrict access to data
<div class="card h-100"> :target: tutorials/restrict_data_access
<div class="card-body pb-0"> :tag: Beginner
<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>
<a class="o_toctree_card col" href="tutorials/dashboards.html"> Implement security measures to restrict access to sensitive data with the help of groups,
<div class="card h-100"> access rights, and record rules.
<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>
</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,
}