[ADD] extensions: new autofield directive
This new directive is generating documentation from Odoo fields. This
can be used to build documentation about business classes.
This will help developpers import/export data and build localization
modules for instance.
X-original-commit: 16afaf6fa7
Part-of: odoo/documentation#1969
This commit is contained in:
parent
f559ec5939
commit
026d6a76be
20
conf.py
20
conf.py
@ -84,6 +84,8 @@ else:
|
|||||||
source_read_replace_vals['ODOO_RELPATH'] = '/../' + str(odoo_sources_dirs[0])
|
source_read_replace_vals['ODOO_RELPATH'] = '/../' + str(odoo_sources_dirs[0])
|
||||||
source_read_replace_vals['ODOO_ABSPATH'] = str(odoo_dir)
|
source_read_replace_vals['ODOO_ABSPATH'] = str(odoo_dir)
|
||||||
sys.path.insert(0, str(odoo_dir))
|
sys.path.insert(0, str(odoo_dir))
|
||||||
|
import odoo.addons
|
||||||
|
odoo.addons.__path__.append(str(odoo_dir) + '/addons')
|
||||||
if (3, 6) < sys.version_info < (3, 7):
|
if (3, 6) < sys.version_info < (3, 7):
|
||||||
# Running odoo needs python 3.7 min but monkey patch version_info to be compatible with 3.6
|
# Running odoo needs python 3.7 min but monkey patch version_info to be compatible with 3.6
|
||||||
sys.version_info = (3, 7, 0)
|
sys.version_info = (3, 7, 0)
|
||||||
@ -106,12 +108,17 @@ else:
|
|||||||
)
|
)
|
||||||
odoo_dir_in_path = True
|
odoo_dir_in_path = True
|
||||||
|
|
||||||
|
# Mapping between odoo models related to master data and the declaration of the
|
||||||
|
# data. This is used to point users to available xml_ids when giving values for
|
||||||
|
# a field with the autodoc_field extension.
|
||||||
|
model_references = {
|
||||||
|
'res.country': 'odoo/addons/base/data/res_country_data.xml',
|
||||||
|
'res.currency': 'odoo/addons/base/data/res_currency_data.xml',
|
||||||
|
}
|
||||||
|
|
||||||
# The Sphinx extensions to use, as module names.
|
# The Sphinx extensions to use, as module names.
|
||||||
# They can be extensions coming with Sphinx (named 'sphinx.ext.*') or custom ones.
|
# They can be extensions coming with Sphinx (named 'sphinx.ext.*') or custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
# Parse Python docstrings (autodoc, automodule, autoattribute directives)
|
|
||||||
'sphinx.ext.autodoc' if odoo_dir_in_path else 'autodoc_placeholder',
|
|
||||||
|
|
||||||
# Link sources in other projects (used to build the reference doc)
|
# Link sources in other projects (used to build the reference doc)
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
|
|
||||||
@ -141,6 +148,13 @@ if odoo_dir_in_path:
|
|||||||
extensions += [
|
extensions += [
|
||||||
'sphinx.ext.linkcode',
|
'sphinx.ext.linkcode',
|
||||||
'github_link',
|
'github_link',
|
||||||
|
# Parse Python docstrings (autodoc, automodule, autoattribute directives)
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'autodoc_field',
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
extensions += [
|
||||||
|
'autodoc_placeholder',
|
||||||
]
|
]
|
||||||
|
|
||||||
todo_include_todos = False
|
todo_include_todos = False
|
||||||
|
144
extensions/autodoc_field/__init__.py
Normal file
144
extensions/autodoc_field/__init__.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
from docutils.parsers.rst import directives
|
||||||
|
from docutils.parsers.rst.states import RSTState
|
||||||
|
from sphinx.domains.python import PyClasslike, PyAttribute
|
||||||
|
from sphinx.ext.autodoc import AttributeDocumenter, ClassDocumenter
|
||||||
|
|
||||||
|
import odoo
|
||||||
|
|
||||||
|
|
||||||
|
nested_parse = RSTState.nested_parse
|
||||||
|
def patched_nested_parse(self, block, input_offset, node, match_titles=False,
|
||||||
|
state_machine_class=None, state_machine_kwargs=None):
|
||||||
|
match_titles = True
|
||||||
|
return nested_parse(self, block, input_offset, node, match_titles, state_machine_class, state_machine_kwargs)
|
||||||
|
RSTState.nested_parse = patched_nested_parse
|
||||||
|
|
||||||
|
|
||||||
|
class OdooClassDocumenter(ClassDocumenter):
|
||||||
|
objtype = 'model'
|
||||||
|
priority = 10 + ClassDocumenter.priority
|
||||||
|
option_spec = {**ClassDocumenter.option_spec, 'main': directives.flag}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_document_member(cls, member, membername, isattr, parent):
|
||||||
|
return isinstance(member, odoo.models.MetaModel)
|
||||||
|
|
||||||
|
def add_content(self, more_content):
|
||||||
|
sourcename = self.get_sourcename()
|
||||||
|
cls = self.object
|
||||||
|
if 'main' in self.options:
|
||||||
|
self.add_line(f".. _model-{cls._name.replace('.', '-')}:", sourcename)
|
||||||
|
self.add_line('.. py:attribute:: _name', sourcename)
|
||||||
|
self.add_line(f' :value: {cls._name}', sourcename)
|
||||||
|
self.add_line('' , sourcename)
|
||||||
|
super().add_content(more_content)
|
||||||
|
|
||||||
|
def add_directive_header(self, sig: str) -> None:
|
||||||
|
"""Add the directive header and options to the generated content."""
|
||||||
|
sourcename = self.get_sourcename()
|
||||||
|
module = self.modname.split('addons.')[1].split('.')[0]
|
||||||
|
if 'main' in self.options:
|
||||||
|
title = f"Original definition from `{module}`"
|
||||||
|
else:
|
||||||
|
title = f"Additional fields with `{module}`"
|
||||||
|
|
||||||
|
self.add_line(title, sourcename)
|
||||||
|
self.add_line('=' * len(title), sourcename)
|
||||||
|
self.add_line('', sourcename)
|
||||||
|
return super().add_directive_header(sig)
|
||||||
|
|
||||||
|
|
||||||
|
class FieldDocumenter(AttributeDocumenter):
|
||||||
|
objtype = 'field'
|
||||||
|
priority = 10 + AttributeDocumenter.priority
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_document_member(cls, member, membername, isattr, parent):
|
||||||
|
return isinstance(member, odoo.fields.Field)
|
||||||
|
|
||||||
|
def update_annotations(self, parent):
|
||||||
|
super().update_annotations(parent)
|
||||||
|
annotation = parent.__annotations__
|
||||||
|
attrname = self.object.name
|
||||||
|
annotation[attrname] = dict
|
||||||
|
field = self.object
|
||||||
|
if field.type == 'many2one':
|
||||||
|
annotation[attrname] = int
|
||||||
|
elif field.type in ('one2many', 'many2many'):
|
||||||
|
annotation[attrname] = Sequence[odoo.fields.Command]
|
||||||
|
elif field.type in ('selection', 'reference', 'char', 'text', 'html'):
|
||||||
|
annotation[attrname] = str
|
||||||
|
elif field.type == 'boolean':
|
||||||
|
annotation[attrname] = bool
|
||||||
|
elif field.type in ('float', 'monetary'):
|
||||||
|
annotation[attrname] = float
|
||||||
|
elif field.type == 'integer':
|
||||||
|
annotation[attrname] = int
|
||||||
|
elif field.type == 'date':
|
||||||
|
annotation[attrname] = datetime.date
|
||||||
|
elif field.type == 'datetime':
|
||||||
|
annotation[attrname] = datetime.datetime
|
||||||
|
|
||||||
|
def add_content(self, more_content):
|
||||||
|
source_name = self.get_sourcename()
|
||||||
|
field = self.object
|
||||||
|
if field.required:
|
||||||
|
self.add_line(f":required:", source_name)
|
||||||
|
self.add_line(f":name: {field.string}", source_name)
|
||||||
|
if field.readonly:
|
||||||
|
self.add_line(f":readonly: this field is not supposed to/cannot be set manually", source_name)
|
||||||
|
if not field.store:
|
||||||
|
self.add_line(f":store: this field is there only for technical reasons", source_name)
|
||||||
|
if field.type == 'selection':
|
||||||
|
if isinstance(field.selection, (list, tuple)):
|
||||||
|
self.add_line(f":selection:", source_name)
|
||||||
|
for tech, nice in field.selection:
|
||||||
|
self.add_line(f" ``{tech}``: {nice}", source_name)
|
||||||
|
if field.type in ('many2one', 'one2many', 'many2many'):
|
||||||
|
comodel_name = field.comodel_name
|
||||||
|
string = f":comodel: :ref:`{comodel_name} <model-{comodel_name.replace('.', '-')}>`"
|
||||||
|
self.add_line(string, source_name)
|
||||||
|
reference = self.config.model_references.get(comodel_name)
|
||||||
|
if reference:
|
||||||
|
self.add_line(f":possible_values: `{reference} <{self.config.source_read_replace_vals['GITHUB_PATH']}/{reference}>`__", source_name)
|
||||||
|
if field.default:
|
||||||
|
self.add_line(f":default: {field.default(odoo.models.Model)}", source_name)
|
||||||
|
|
||||||
|
super().add_content(more_content)
|
||||||
|
if field.help:
|
||||||
|
self.add_line('', source_name)
|
||||||
|
for line in field.help.strip().split('\n'):
|
||||||
|
self.add_line(line, source_name)
|
||||||
|
self.add_line('', source_name)
|
||||||
|
|
||||||
|
def get_doc(self, encoding=None, ignore=None):
|
||||||
|
# only read docstring of field instance, do not fallback on field class
|
||||||
|
field = self.object
|
||||||
|
field.__doc__ = field.__dict__.get('__doc__', "")
|
||||||
|
res = super().get_doc(encoding, ignore)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def disable_warn_missing_reference(app, domain, node):
|
||||||
|
if not ((domain and domain.name != 'std') or node['reftype'] != 'ref'):
|
||||||
|
target = node['reftarget']
|
||||||
|
if target.startswith('model-'):
|
||||||
|
node['reftype'] = 'odoo_missing_ref'
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.add_config_value('model_references', {}, 'env')
|
||||||
|
directives.register_directive('py:model', PyClasslike)
|
||||||
|
directives.register_directive('py:field', PyAttribute)
|
||||||
|
app.add_autodocumenter(FieldDocumenter)
|
||||||
|
app.add_autodocumenter(OdooClassDocumenter)
|
||||||
|
app.connect('warn-missing-reference', disable_warn_missing_reference, priority=400)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'parallel_read_safe': True,
|
||||||
|
'parallel_write_safe': True,
|
||||||
|
}
|
@ -19,6 +19,8 @@ def setup(app):
|
|||||||
directives.register_directive('autodata', PlaceHolder)
|
directives.register_directive('autodata', PlaceHolder)
|
||||||
directives.register_directive('automethod', PlaceHolder)
|
directives.register_directive('automethod', PlaceHolder)
|
||||||
directives.register_directive('autoattribute', PlaceHolder)
|
directives.register_directive('autoattribute', PlaceHolder)
|
||||||
|
directives.register_directive('autofield', PlaceHolder)
|
||||||
|
directives.register_directive('automodel', PlaceHolder)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'parallel_read_safe': True,
|
'parallel_read_safe': True,
|
||||||
|
Loading…
Reference in New Issue
Block a user