[FIX] core; base: lots of docstrings
* add configuration for `flake8[flake8-rst-docstring]` * enable docstring-related checks * fix invalid docstrings in odoo's core & `base` * fix a few more bits (mostly missing or incorrect `:param:` info fields) are out of scope for the lint but my editor catches closes odoo/odoo#74604 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
This commit is contained in:
@@ -589,22 +589,21 @@ class IrActionsServer(models.Model):
|
||||
:samp:`_run_action_{TYPE}[_multi]` method is called. This allows easy
|
||||
overriding of the server actions.
|
||||
|
||||
The `_multi` suffix means the runner can operate on multiple records,
|
||||
The ``_multi`` suffix means the runner can operate on multiple records,
|
||||
otherwise if there are multiple records the runner will be called once
|
||||
for each
|
||||
for each.
|
||||
|
||||
:param dict context: context should contain following keys
|
||||
The call context should contain the following keys:
|
||||
|
||||
- active_id: id of the current object (single mode)
|
||||
- active_model: current model that should equal the action's model
|
||||
|
||||
The following keys are optional:
|
||||
|
||||
- active_ids: ids of the current records (mass mode). If active_ids
|
||||
and active_id are present, active_ids is given precedence.
|
||||
|
||||
:return: an action_id to be executed, or False is finished correctly without
|
||||
return action
|
||||
active_id
|
||||
id of the current object (single mode)
|
||||
active_model
|
||||
current model that should equal the action's model
|
||||
active_ids (optional)
|
||||
ids of the current records (mass mode). If ``active_ids`` and
|
||||
``active_id`` are present, ``active_ids`` is given precedence.
|
||||
:return: an ``action_id`` to be executed, or ``False`` is finished
|
||||
correctly without return action
|
||||
"""
|
||||
res = False
|
||||
for action in self.sudo():
|
||||
|
||||
@@ -198,7 +198,6 @@ class IrActionsReport(models.Model):
|
||||
'''Retrieve an attachment for a specific record.
|
||||
|
||||
:param record: The record owning of the attachment.
|
||||
:param attachment_name: The optional name of the attachment.
|
||||
:return: A recordset of length <=1 or None
|
||||
'''
|
||||
attachment_name = safe_eval(self.attachment, {'object': record, 'time': time}) if self.attachment else ''
|
||||
@@ -211,13 +210,14 @@ class IrActionsReport(models.Model):
|
||||
], limit=1)
|
||||
|
||||
def _postprocess_pdf_report(self, record, buffer):
|
||||
'''Hook to handle post processing during the pdf report generation.
|
||||
The basic behavior consists to create a new attachment containing the pdf
|
||||
base64 encoded.
|
||||
'''Hook to handle post-processing during the pdf report generation.
|
||||
|
||||
:param record_id: The record that will own the attachment.
|
||||
:param pdf_content: The optional name content of the file to avoid reading both times.
|
||||
:return: A modified buffer if the previous one has been modified, None otherwise.
|
||||
The basic behavior consists of creating a new attachment containing the
|
||||
pdf base64 encoded.
|
||||
|
||||
:param record: The record that will own the attachment.
|
||||
:param io.BytesIO buffer: The PDF content as a bytes buffer.
|
||||
:return: The input buffer.
|
||||
'''
|
||||
attachment_name = safe_eval(self.attachment, {'object': record, 'time': time})
|
||||
if not attachment_name:
|
||||
|
||||
@@ -43,6 +43,9 @@ class IrDefault(models.Model):
|
||||
scope (field, user, company) will be replaced. The value is encoded
|
||||
in JSON to be stored to the database.
|
||||
|
||||
:param model_name:
|
||||
:param field_name:
|
||||
:param value:
|
||||
:param user_id: may be ``False`` for all users, ``True`` for the
|
||||
current user, or any user id
|
||||
:param company_id: may be ``False`` for all companies, ``True`` for
|
||||
@@ -93,6 +96,8 @@ class IrDefault(models.Model):
|
||||
""" Return the default value for the given field, user and company, or
|
||||
``None`` if no default is available.
|
||||
|
||||
:param model_name:
|
||||
:param field_name:
|
||||
:param user_id: may be ``False`` for all users, ``True`` for the
|
||||
current user, or any user id
|
||||
:param company_id: may be ``False`` for all companies, ``True`` for
|
||||
|
||||
@@ -84,6 +84,7 @@ class IrFieldsConverter(models.AbstractModel):
|
||||
records matching what :meth:`odoo.osv.orm.Model.write` expects.
|
||||
|
||||
:param model: :class:`odoo.osv.orm.Model` for the conversion base
|
||||
:param fromtype:
|
||||
:returns: a converter callable
|
||||
:rtype: (record: dict, logger: (field, error) -> None) -> dict
|
||||
"""
|
||||
@@ -167,11 +168,11 @@ class IrFieldsConverter(models.AbstractModel):
|
||||
it returns. The handling of a warning at the upper levels is the same
|
||||
as ``ValueError`` above.
|
||||
|
||||
:param model:
|
||||
:param field: field object to generate a value for
|
||||
:type field: :class:`odoo.fields.Field`
|
||||
:param fromtype: type to convert to something fitting for ``field``
|
||||
:type fromtype: type | str
|
||||
:param context: odoo request context
|
||||
:return: a function (fromtype -> field.write_type), if a converter is found
|
||||
:rtype: Callable | None
|
||||
"""
|
||||
@@ -345,7 +346,6 @@ class IrFieldsConverter(models.AbstractModel):
|
||||
``id`` for an external id and ``.id`` for a database
|
||||
id
|
||||
:param value: value of the reference to match to an actual record
|
||||
:param context: OpenERP request context
|
||||
:return: a pair of the matched database identifier (if any), the
|
||||
translated user-readable name for the field and the list of
|
||||
warnings
|
||||
|
||||
@@ -57,6 +57,7 @@ class IrFilters(models.Model):
|
||||
def get_filters(self, model, action_id=None):
|
||||
"""Obtain the list of filters available for the user on the given model.
|
||||
|
||||
:param int model: id of model to find filters for
|
||||
:param action_id: optional ID of action to restrict filters to this action
|
||||
plus global filters. If missing only global filters are returned.
|
||||
The action does not have to correspond to the model, it may only be
|
||||
|
||||
@@ -351,6 +351,8 @@ class IrMailServer(models.Model):
|
||||
or 'html'). Default is 'plain'.
|
||||
:param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
|
||||
containing the bytes of the attachment
|
||||
:param message_id:
|
||||
:param references:
|
||||
:param list email_cc: optional list of string values for CC header (to be joined with commas)
|
||||
:param list email_bcc: optional list of string values for BCC header (to be joined with commas)
|
||||
:param dict headers: optional map of headers to set on the outgoing mail (may override the
|
||||
|
||||
+4
-3
@@ -717,9 +717,10 @@ class Environment(Mapping):
|
||||
@contextmanager
|
||||
def protecting(self, what, records=None):
|
||||
""" Prevent the invalidation or recomputation of fields on records.
|
||||
The parameters are either:
|
||||
- ``what`` a collection of fields and ``records`` a recordset, or
|
||||
- ``what`` a collection of pairs ``(fields, records)``.
|
||||
The parameters are either:
|
||||
|
||||
- ``what`` a collection of fields and ``records`` a recordset, or
|
||||
- ``what`` a collection of pairs ``(fields, records)``.
|
||||
"""
|
||||
protected = self._protected
|
||||
try:
|
||||
|
||||
+8
-1
@@ -861,6 +861,8 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
:meth:`BaseModel.write`. If the value represents a recordset, it should
|
||||
be added for prefetching on ``record``.
|
||||
|
||||
:param value:
|
||||
:param record:
|
||||
:param bool validate: when True, field-specific validation of ``value``
|
||||
will be performed
|
||||
"""
|
||||
@@ -886,6 +888,8 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
""" Convert ``value`` from the record format to the format returned by
|
||||
method :meth:`BaseModel.read`.
|
||||
|
||||
:param value:
|
||||
:param record:
|
||||
:param bool use_name_get: when True, the value's display name will be
|
||||
computed using :meth:`BaseModel.name_get`, if relevant for the field
|
||||
"""
|
||||
@@ -903,6 +907,8 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
""" Convert ``value`` from the record format to the format returned by
|
||||
method :meth:`BaseModel.onchange`.
|
||||
|
||||
:param value:
|
||||
:param record:
|
||||
:param names: a tree of field names (for relational fields only)
|
||||
"""
|
||||
return self.convert_to_read(value, record)
|
||||
@@ -1048,6 +1054,7 @@ class Field(MetaField('DummyField', (object,), {})):
|
||||
""" Write the value of ``self`` on ``records``. This method must update
|
||||
the cache and prepare database updates.
|
||||
|
||||
:param records:
|
||||
:param value: a value in any format
|
||||
:return: the subset of `records` that have been modified
|
||||
"""
|
||||
@@ -3146,7 +3153,7 @@ class Command(enum.IntEnum):
|
||||
|
||||
|
||||
class _RelationalMulti(_Relational):
|
||||
""" Abstract class for relational fields *2many. """
|
||||
r"Abstract class for relational fields \*2many."
|
||||
write_sequence = 20
|
||||
|
||||
# Important: the cache contains the ids of all the records in the relation,
|
||||
|
||||
+1
-2
@@ -1096,8 +1096,7 @@ class OpenERPSession(sessions.Session):
|
||||
identifying that action. The method get_action() can be used to get
|
||||
back the action.
|
||||
|
||||
:param the_action: The action to save in the session.
|
||||
:type the_action: anything
|
||||
:param action: The action to save in the session.
|
||||
:return: A key identifying the saved action.
|
||||
:rtype: integer
|
||||
"""
|
||||
|
||||
+93
-64
@@ -507,7 +507,10 @@ class BaseModel(metaclass=MetaModel):
|
||||
on the records of the current model using the ``child_of`` and
|
||||
``parent_of`` domain operators.
|
||||
"""
|
||||
_active_name = None #: field to use for active records
|
||||
_active_name = None
|
||||
"""field to use for active records, automatically set to either ``"active"``
|
||||
or ``"x_active"``.
|
||||
"""
|
||||
_date_name = 'date' #: field to use for default calendar view
|
||||
_fold_name = 'fold' #: field to determine folded groups in kanban views
|
||||
|
||||
@@ -529,7 +532,9 @@ class BaseModel(metaclass=MetaModel):
|
||||
|
||||
# default values for _transient_vacuum()
|
||||
_transient_max_count = lazy_classproperty(lambda _: config.get('osv_memory_count_limit'))
|
||||
"maximum number of transient records, unlimited if ``0``"
|
||||
_transient_max_hours = lazy_classproperty(lambda _: config.get('transient_age_limit'))
|
||||
"maximum idle lifetime (in hours), unlimited if ``0``"
|
||||
|
||||
CONCURRENCY_CHECK_FIELD = '__last_update'
|
||||
|
||||
@@ -1041,7 +1046,6 @@ class BaseModel(metaclass=MetaModel):
|
||||
""" Export fields for selected objects
|
||||
|
||||
:param fields_to_export: list of fields
|
||||
:param raw_data: True to return value in native Python type
|
||||
:rtype: dictionary with a *datas* matrix
|
||||
|
||||
This method is used when exporting data via client menu
|
||||
@@ -1235,6 +1239,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
a list of sub-records
|
||||
|
||||
The following sub-fields may be set on the record (by key):
|
||||
|
||||
* None is the name_get for the record (to use with name_create/name_search)
|
||||
* "id" is the External ID for the record
|
||||
* ".id" is the Database ID for the record
|
||||
@@ -1302,10 +1307,10 @@ class BaseModel(metaclass=MetaModel):
|
||||
def _convert_records(self, records, log=lambda a: None):
|
||||
""" Converts records from the source iterable (recursive dicts of
|
||||
strings) into forms which can be written to the database (via
|
||||
self.create or (ir.model.data)._update)
|
||||
``self.create`` or ``(ir.model.data)._update``)
|
||||
|
||||
:returns: a list of triplets of (id, xid, record)
|
||||
:rtype: list((int|None, str|None, dict))
|
||||
:rtype: list[(int|None, str|None, dict)]
|
||||
"""
|
||||
field_names = {name: field.string for name, field in self._fields.items()}
|
||||
if self.env.lang:
|
||||
@@ -1614,12 +1619,19 @@ class BaseModel(metaclass=MetaModel):
|
||||
@api.model
|
||||
def load_views(self, views, options=None):
|
||||
""" Returns the fields_views of given views, along with the fields of
|
||||
the current model, and optionally its filters for the given action.
|
||||
the current model, and optionally its filters for the given action.
|
||||
|
||||
:param views: list of [view_id, view_type]
|
||||
:param options['toolbar']: True to include contextual actions when loading fields_views
|
||||
:param options['load_filters']: True to return the model's filters
|
||||
:param options['action_id']: id of the action to get the filters
|
||||
:param dict options: a dict optional boolean flags, set to enable:
|
||||
|
||||
``toolbar``
|
||||
includes contextual actions when loading fields_views
|
||||
``load_filters``
|
||||
returns the model's filters
|
||||
``action_id``
|
||||
id of the action to get the filters, otherwise loads the global
|
||||
filters or the model
|
||||
|
||||
:return: dictionary with fields_views, fields and optionally filters
|
||||
"""
|
||||
options = options or {}
|
||||
@@ -1702,9 +1714,11 @@ class BaseModel(metaclass=MetaModel):
|
||||
:return: composition of the requested view (including inherited views and extensions)
|
||||
:rtype: dict
|
||||
:raise AttributeError:
|
||||
* if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
|
||||
* if some tag other than 'position' is found in parent view
|
||||
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc defined on the structure
|
||||
|
||||
* if the inherited view has unknown position to work with other than 'before', 'after', 'inside', 'replace'
|
||||
* if some tag other than 'position' is found in parent view
|
||||
|
||||
:raise Invalid ArchitectureError: if there is view type other than form, tree, calendar, search etc... defined on the structure
|
||||
"""
|
||||
self.check_access_rights('read')
|
||||
view = self.env['ir.ui.view'].sudo().browse(view_id)
|
||||
@@ -1739,7 +1753,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
return result
|
||||
|
||||
def get_formview_id(self, access_uid=None):
|
||||
""" Return an view id to open the document ``self`` with. This method is
|
||||
""" Return a view id to open the document ``self`` with. This method is
|
||||
meant to be overridden in addons that want to give specific view ids
|
||||
for example.
|
||||
|
||||
@@ -1770,7 +1784,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Return an action to open the document. This method is meant to be
|
||||
overridden in addons that want to give specific access to the document.
|
||||
By default it opens the formview of the document.
|
||||
By default, it opens the formview of the document.
|
||||
|
||||
An optional access_uid holds the user that will access the document
|
||||
that could be different from the current user.
|
||||
@@ -1804,7 +1818,6 @@ class BaseModel(metaclass=MetaModel):
|
||||
:param str order: sort string
|
||||
:param bool count: if True, only counts and returns the number of matching records (default: False)
|
||||
:returns: at most ``limit`` records matching the search criteria
|
||||
|
||||
:raise AccessError: * if user tries to bypass access rules for read on the requested object.
|
||||
"""
|
||||
res = self._search(args, offset=offset, limit=limit, order=order, count=count)
|
||||
@@ -1842,7 +1855,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
:attr:`~.display_name` it is important that it resets to the
|
||||
"default" behaviour if the context keys are empty / missing.
|
||||
|
||||
:return: list of pairs ``(id, text_repr)`` for each records
|
||||
:return: list of pairs ``(id, text_repr)`` for each record
|
||||
:rtype: list[(int, str)]
|
||||
"""
|
||||
result = []
|
||||
@@ -1888,8 +1901,8 @@ class BaseModel(metaclass=MetaModel):
|
||||
matching the optional search domain (``args``).
|
||||
|
||||
This is used for example to provide suggestions based on a partial
|
||||
value for a relational field. Sometimes be seen as the inverse
|
||||
function of :meth:`~.name_get`, but it is not guaranteed to be.
|
||||
value for a relational field. Should usually behave as the reverse of
|
||||
:meth:`~.name_get`, but that is ont guaranteed.
|
||||
|
||||
This method is equivalent to calling :meth:`~.search` with a search
|
||||
domain based on ``display_name`` and then :meth:`~.name_get` on the
|
||||
@@ -2068,7 +2081,8 @@ class BaseModel(metaclass=MetaModel):
|
||||
|
||||
Assume we group records by month, and we only have data for June,
|
||||
September and December. By default, plotting the result gives something
|
||||
like:
|
||||
like::
|
||||
|
||||
___
|
||||
___ | |
|
||||
| | ___ | |
|
||||
@@ -2077,7 +2091,8 @@ class BaseModel(metaclass=MetaModel):
|
||||
|
||||
The problem is that December data immediately follow September data,
|
||||
which is misleading for the user. Adding explicit zeroes for missing
|
||||
data gives something like:
|
||||
data gives something like::
|
||||
|
||||
___
|
||||
___ | |
|
||||
| | ___ | |
|
||||
@@ -2098,7 +2113,8 @@ class BaseModel(metaclass=MetaModel):
|
||||
group with data.
|
||||
|
||||
If we want to fill groups only between August (fill_from)
|
||||
and October (fill_to):
|
||||
and October (fill_to)::
|
||||
|
||||
___
|
||||
___ | |
|
||||
| | ___ | |
|
||||
@@ -2106,9 +2122,9 @@ class BaseModel(metaclass=MetaModel):
|
||||
Jun Aug Sep Oct Dec
|
||||
|
||||
We still get June and December. To filter them out, we should match
|
||||
`fill_from` and `fill_to` with the domain e.g. ['&',
|
||||
('date_field', '>=', 'YYYY-08-01'),
|
||||
('date_field', '<', 'YYYY-11-01')]:
|
||||
`fill_from` and `fill_to` with the domain e.g. ``['&',
|
||||
('date_field', '>=', 'YYYY-08-01'), ('date_field', '<', 'YYYY-11-01')]``::
|
||||
|
||||
___
|
||||
___ |___| ___
|
||||
Aug Sep Oct
|
||||
@@ -2123,7 +2139,8 @@ class BaseModel(metaclass=MetaModel):
|
||||
existing group. If neither `fill_from` nor `fill_to` is specified, and
|
||||
there is no existing group, no group will be returned.
|
||||
|
||||
If we set min_groups = 4:
|
||||
If we set min_groups = 4::
|
||||
|
||||
___
|
||||
___ |___| ___ ___
|
||||
Aug Sep Oct Nov
|
||||
@@ -2210,12 +2227,15 @@ class BaseModel(metaclass=MetaModel):
|
||||
"""
|
||||
Prepares the GROUP BY and ORDER BY terms for the read_group method. Adds the missing JOIN clause
|
||||
to the query if order should be computed against m2o field.
|
||||
|
||||
:param orderby: the orderby definition in the form "%(field)s %(order)s"
|
||||
:param aggregated_fields: list of aggregated fields in the query
|
||||
:param annotated_groupbys: list of dictionaries returned by _read_group_process_groupby
|
||||
These dictionaries contains the qualified name of each groupby
|
||||
(fully qualified SQL name for the corresponding field),
|
||||
and the (non raw) field name.
|
||||
:param annotated_groupbys: list of dictionaries returned by
|
||||
:meth:`_read_group_process_groupby`
|
||||
|
||||
These dictionaries contain the qualified name of each groupby
|
||||
(fully qualified SQL name for the corresponding field),
|
||||
and the (non raw) field name.
|
||||
:param osv.Query query: the query under construction
|
||||
:return: (groupby_terms, orderby_terms)
|
||||
"""
|
||||
@@ -2436,7 +2456,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
* __range: (date/datetime only) dictionary with field names as keys mapping to
|
||||
a dictionary with keys: "from" (inclusive) and "to" (exclusive)
|
||||
mapping to a string representation of the temporal bounds of the group
|
||||
:rtype: [{'field_name_1': value, ...]
|
||||
:rtype: [{'field_name_1': value, ...}, ...]
|
||||
:raise AccessError: * if user has no read rights on the requested object
|
||||
* if user tries to bypass access rules for read on the requested object
|
||||
"""
|
||||
@@ -2878,10 +2898,8 @@ class BaseModel(metaclass=MetaModel):
|
||||
_logger.warning("parent_path field on model %r should have unaccent disabled. Add `unaccent=False` to the field definition.", self._name)
|
||||
|
||||
def _add_sql_constraints(self):
|
||||
"""
|
||||
|
||||
Modify this model's database table constraints so they match the one in
|
||||
_sql_constraints.
|
||||
""" Modify this model's database table constraints so they match the one
|
||||
in _sql_constraints.
|
||||
|
||||
"""
|
||||
cr = self._cr
|
||||
@@ -2908,7 +2926,7 @@ class BaseModel(metaclass=MetaModel):
|
||||
self._cr.execute(self._sql)
|
||||
|
||||
#
|
||||
# Update objects that uses this one to update their _inherits fields
|
||||
# Update objects that use this one to update their _inherits fields
|
||||
#
|
||||
|
||||
@api.model
|
||||
@@ -3217,6 +3235,8 @@ Fields:
|
||||
method. In Python code, prefer :meth:`~.browse`.
|
||||
|
||||
:param fields: list of field names to return (default is all fields)
|
||||
:param load: loading mode, currently the only option is to set to
|
||||
``None`` to avoid loading the ``name_get`` of m2o fields
|
||||
:return: a list of dictionaries mapping field names to their values,
|
||||
with one dictionary per record
|
||||
:raise AccessError: if user has no read rights on some of the given
|
||||
@@ -3436,7 +3456,7 @@ Fields:
|
||||
"""
|
||||
Returns rooturl for a specific given record.
|
||||
|
||||
By default, it return the ir.config.parameter of base_url
|
||||
By default, it returns the ir.config.parameter of base_url
|
||||
but it can be overridden by model.
|
||||
|
||||
:return: the base url for this record
|
||||
@@ -3558,7 +3578,7 @@ Fields:
|
||||
the current user according to ir.rules.
|
||||
|
||||
:param operation: one of ``write``, ``unlink``
|
||||
:raise UserError: * if current ir.rules do not permit this operation.
|
||||
:raise UserError: * if current ``ir.rules`` do not permit this operation.
|
||||
:return: None if the operation is allowed
|
||||
"""
|
||||
if self.env.su:
|
||||
@@ -4503,10 +4523,10 @@ Fields:
|
||||
@api.model
|
||||
def _where_calc(self, domain, active_test=True):
|
||||
"""Computes the WHERE clause needed to implement an OpenERP domain.
|
||||
:param domain: the domain to compute
|
||||
:type domain: list
|
||||
:param active_test: whether the default filtering of records with ``active``
|
||||
field set to ``False`` should be applied.
|
||||
|
||||
:param list domain: the domain to compute
|
||||
:param bool active_test: whether the default filtering of records with
|
||||
``active`` field set to ``False`` should be applied.
|
||||
:return: the query expressing the given domain as provided in domain
|
||||
:rtype: osv.query.Query
|
||||
"""
|
||||
@@ -5088,8 +5108,9 @@ Fields:
|
||||
Defaults to no limit.
|
||||
:param order: Columns to sort result, see ``order`` parameter in :meth:`search`.
|
||||
Defaults to no sort.
|
||||
:param read_kwargs: All read keywords arguments used to call read(..., **read_kwargs) method
|
||||
E.g. you can use search_read(..., load='') in order to avoid computing name_get
|
||||
:param read_kwargs: All read keywords arguments used to call
|
||||
``read(..., **read_kwargs)`` method e.g. you can use
|
||||
``search_read(..., load='')`` in order to avoid computing name_get
|
||||
:return: List of dictionaries containing the asked fields.
|
||||
:rtype: list(dict).
|
||||
"""
|
||||
@@ -5119,20 +5140,20 @@ Fields:
|
||||
return [index[record.id] for record in records if record.id in index]
|
||||
|
||||
def toggle_active(self):
|
||||
""" Inverse the value of the field ``(x_)active`` on the records in ``self``. """
|
||||
"Inverses the value of :attr:`active` on the records in ``self``."
|
||||
active_recs = self.filtered(self._active_name)
|
||||
active_recs[self._active_name] = False
|
||||
(self - active_recs)[self._active_name] = True
|
||||
|
||||
def action_archive(self):
|
||||
""" Set (x_)active=False on a recordset, by calling toggle_active to
|
||||
take the corresponding actions according to the model
|
||||
"""Sets :attr:`active` to ``False`` on a recordset, by calling
|
||||
:meth:`toggle_active` on its currently active records.
|
||||
"""
|
||||
return self.filtered(lambda record: record[self._active_name]).toggle_active()
|
||||
|
||||
def action_unarchive(self):
|
||||
""" Set (x_)active=True on a recordset, by calling toggle_active to
|
||||
take the corresponding actions according to the model
|
||||
"""Sets :attr:`active` to ``True`` on a recordset, by calling
|
||||
:meth:`toggle_active` on its currently inactive records.
|
||||
"""
|
||||
return self.filtered(lambda record: not record[self._active_name]).toggle_active()
|
||||
|
||||
@@ -5356,7 +5377,7 @@ Fields:
|
||||
return self.with_context(allowed_company_ids=allowed_company_ids)
|
||||
|
||||
def with_context(self, *args, **kwargs):
|
||||
""" with_context([context][, **overrides]) -> records
|
||||
""" with_context([context][, **overrides]) -> Model
|
||||
|
||||
Returns a new version of this recordset attached to an extended
|
||||
context.
|
||||
@@ -5374,7 +5395,7 @@ Fields:
|
||||
.. note:
|
||||
|
||||
The returned recordset has the same prefetch object as ``self``.
|
||||
"""
|
||||
""" # noqa: RST210
|
||||
if (args and 'force_company' in args[0]) or 'force_company' in kwargs:
|
||||
_logger.warning(
|
||||
"Context key 'force_company' is no longer supported. "
|
||||
@@ -5684,9 +5705,9 @@ Fields:
|
||||
""" Process all the pending computations (on all models), and flush all
|
||||
the pending updates to the database.
|
||||
|
||||
:param fnames (list<str>): list of field names to flush. If given,
|
||||
:param list[str] fnames: list of field names to flush. If given,
|
||||
limit the processing to the given fields of the current model.
|
||||
:param records (Model): if given (together with ``fnames``), limit the
|
||||
:param Model records: if given (together with ``fnames``), limit the
|
||||
processing to the given records.
|
||||
"""
|
||||
def process(model, id_vals):
|
||||
@@ -6080,7 +6101,7 @@ Fields:
|
||||
recursively_marked = self.env.not_to_compute(field, records)
|
||||
self.env.add_to_compute(field, records)
|
||||
else:
|
||||
# Dont force the recomputation of compute fields which are
|
||||
# Don't force the recomputation of compute fields which are
|
||||
# not stored as this is not really necessary.
|
||||
if field.recursive:
|
||||
recursively_marked = records & self.env.cache.get_records(records, field)
|
||||
@@ -6423,7 +6444,7 @@ Fields:
|
||||
names.append(name)
|
||||
|
||||
# prefetch x2many lines: this speeds up the initial snapshot by avoiding
|
||||
# to compute fields on new records as much as possible, as that can be
|
||||
# computing fields on new records as much as possible, as that can be
|
||||
# costly and is not necessary at all
|
||||
for name, subnames in nametree.items():
|
||||
if subnames and values.get(name):
|
||||
@@ -6742,18 +6763,26 @@ class TransientModel(Model):
|
||||
"""Clean the transient records.
|
||||
|
||||
This unlinks old records from the transient model tables whenever the
|
||||
"_transient_max_count" or "_max_age" conditions (if any) are reached.
|
||||
Actual cleaning will happen only once every "_transient_check_time" calls.
|
||||
This means this method can be called frequently called (e.g. whenever
|
||||
a new record is created).
|
||||
:attr:`_transient_max_count` or :attr:`_transient_max_hours` conditions
|
||||
(if any) are reached.
|
||||
|
||||
Actual cleaning will happen only once every 5 minutes. This means this
|
||||
method can be called frequently (e.g. whenever a new record is created).
|
||||
|
||||
Example with both max_hours and max_count active:
|
||||
Suppose max_hours = 0.2 (e.g. 12 minutes), max_count = 20, there are 55 rows in the
|
||||
table, 10 created/changed in the last 5 minutes, an additional 12 created/changed between
|
||||
5 and 10 minutes ago, the rest created/changed more then 12 minutes ago.
|
||||
- age based vacuum will leave the 22 rows created/changed in the last 12 minutes
|
||||
- count based vacuum will wipe out another 12 rows. Not just 2, otherwise each addition
|
||||
would immediately cause the maximum to be reached again.
|
||||
- the 10 rows that have been created/changed the last 5 minutes will NOT be deleted
|
||||
|
||||
Suppose max_hours = 0.2 (aka 12 minutes), max_count = 20, there are
|
||||
55 rows in the table, 10 created/changed in the last 5 minutes, an
|
||||
additional 12 created/changed between 5 and 10 minutes ago, the rest
|
||||
created/changed more than 12 minutes ago.
|
||||
|
||||
- age based vacuum will leave the 22 rows created/changed in the last 12
|
||||
minutes
|
||||
- count based vacuum will wipe out another 12 rows. Not just 2,
|
||||
otherwise each addition would immediately cause the maximum to be
|
||||
reached again.
|
||||
- the 10 rows that have been created/changed the last 5 minutes will NOT
|
||||
be deleted
|
||||
"""
|
||||
if self._transient_max_hours:
|
||||
# Age-based expiration
|
||||
|
||||
@@ -123,11 +123,15 @@ def force_demo(cr):
|
||||
def load_module_graph(cr, graph, status=None, perform_checks=True,
|
||||
skip_modules=None, report=None, models_to_check=None):
|
||||
"""Migrates+Updates or Installs all module nodes from ``graph``
|
||||
|
||||
:param cr:
|
||||
:param graph: graph of module nodes to load
|
||||
:param status: deprecated parameter, unused, left to avoid changing signature in 8.0
|
||||
:param perform_checks: whether module descriptors should be checked for validity (prints warnings
|
||||
for same cases)
|
||||
:param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
|
||||
:param report:
|
||||
:param set models_to_check:
|
||||
:return: list of modules that were installed or updated
|
||||
"""
|
||||
if models_to_check is None:
|
||||
|
||||
@@ -27,18 +27,21 @@ def load_script(path, module_name):
|
||||
|
||||
|
||||
class MigrationManager(object):
|
||||
"""
|
||||
This class manage the migration of modules
|
||||
Migrations files must be python files containing a `migrate(cr, installed_version)`
|
||||
""" Manages the migration of modules.
|
||||
|
||||
Migrations files must be python files containing a ``migrate(cr, installed_version)``
|
||||
function. These files must respect a directory tree structure: A 'migrations' folder
|
||||
which contains a folder by version. Version can be 'module' version or 'server.module'
|
||||
version (in this case, the files will only be processed by this version of the server).
|
||||
Python file names must start by `pre-` or `post-` and will be executed, respectively,
|
||||
before and after the module initialisation. `end-` scripts are run after all modules have
|
||||
Python file names must start by ``pre-`` or ``post-`` and will be executed, respectively,
|
||||
before and after the module initialisation. ``end-`` scripts are run after all modules have
|
||||
been updated.
|
||||
A special folder named `0.0.0` can contain scripts that will be run on any version change.
|
||||
In `pre` stage, `0.0.0` scripts are run first, while in `post` and `end`, they are run last.
|
||||
Example:
|
||||
|
||||
A special folder named ``0.0.0`` can contain scripts that will be run on any version change.
|
||||
In `pre` stage, ``0.0.0`` scripts are run first, while in ``post`` and ``end``, they are run last.
|
||||
|
||||
Example::
|
||||
|
||||
<moduledir>
|
||||
`-- migrations
|
||||
|-- 1.0
|
||||
|
||||
+48
-36
@@ -244,6 +244,7 @@ def combine(operator, unit, zero, domains):
|
||||
|
||||
It is guaranteed to return a normalized domain.
|
||||
|
||||
:param operator:
|
||||
:param unit: the identity element of the domains "set" with regard to the operation
|
||||
performed by ``operator``, i.e the domain component ``i`` which, when
|
||||
combined with any domain ``x`` via ``operator``, yields ``x``.
|
||||
@@ -371,12 +372,13 @@ def is_operator(element):
|
||||
|
||||
def is_leaf(element, internal=False):
|
||||
""" Test whether an object is a valid domain term:
|
||||
|
||||
- is a list or tuple
|
||||
- with 3 elements
|
||||
- second element if a valid op
|
||||
|
||||
:param tuple element: a leaf in form (left, operator, right)
|
||||
:param boolean internal: allow or not the 'inselect' internal operator
|
||||
:param bool internal: allow or not the 'inselect' internal operator
|
||||
in the term. This should be always left to False.
|
||||
|
||||
Note: OLD TODO change the share wizard to use this function.
|
||||
@@ -470,48 +472,58 @@ class expression(object):
|
||||
def parse(self):
|
||||
""" Transform the leaves of the expression
|
||||
|
||||
The principle is to pop elements from a leaf stack one at a time.
|
||||
Each leaf is processed. The processing is a if/elif list of various
|
||||
cases that appear in the leafs (many2one, function fields, ...).
|
||||
Three things can happen as a processing result:
|
||||
- the leaf is a logic operator, and updates the result stack
|
||||
accordingly;
|
||||
- the leaf has been modified and/or new leafs have to be introduced
|
||||
in the expression; they are pushed into the leaf stack, to be
|
||||
processed right after;
|
||||
- the leaf is converted to SQL and added to the result stack
|
||||
The principle is to pop elements from a leaf stack one at a time.
|
||||
Each leaf is processed. The processing is a if/elif list of various
|
||||
cases that appear in the leafs (many2one, function fields, ...).
|
||||
|
||||
Here is a suggested execution:
|
||||
Three things can happen as a processing result:
|
||||
|
||||
step stack result_stack
|
||||
- the leaf is a logic operator, and updates the result stack
|
||||
accordingly;
|
||||
- the leaf has been modified and/or new leafs have to be introduced
|
||||
in the expression; they are pushed into the leaf stack, to be
|
||||
processed right after;
|
||||
- the leaf is converted to SQL and added to the result stack
|
||||
|
||||
['&', A, B] []
|
||||
substitute B ['&', A, B1] []
|
||||
convert B1 in SQL ['&', A] ["B1"]
|
||||
substitute A ['&', '|', A1, A2] ["B1"]
|
||||
convert A2 in SQL ['&', '|', A1] ["B1", "A2"]
|
||||
convert A1 in SQL ['&', '|'] ["B1", "A2", "A1"]
|
||||
apply operator OR ['&'] ["B1", "A1 or A2"]
|
||||
apply operator AND [] ["(A1 or A2) and B1"]
|
||||
Example:
|
||||
|
||||
Some internal var explanation:
|
||||
:var list path: left operand seen as a sequence of field names
|
||||
("foo.bar" -> ["foo", "bar"])
|
||||
:var obj model: model object, model containing the field
|
||||
(the name provided in the left operand)
|
||||
:var obj field: the field corresponding to `path[0]`
|
||||
:var obj column: the column corresponding to `path[0]`
|
||||
:var obj comodel: relational model of field (field.comodel)
|
||||
(res_partner.bank_ids -> res.partner.bank)
|
||||
=================== =================== =====================
|
||||
step stack result_stack
|
||||
=================== =================== =====================
|
||||
['&', A, B] []
|
||||
substitute B ['&', A, B1] []
|
||||
convert B1 in SQL ['&', A] ["B1"]
|
||||
substitute A ['&', '|', A1, A2] ["B1"]
|
||||
convert A2 in SQL ['&', '|', A1] ["B1", "A2"]
|
||||
convert A1 in SQL ['&', '|'] ["B1", "A2", "A1"]
|
||||
apply operator OR ['&'] ["B1", "A1 or A2"]
|
||||
apply operator AND [] ["(A1 or A2) and B1"]
|
||||
=================== =================== =====================
|
||||
|
||||
Some internal var explanation:
|
||||
|
||||
:var list path: left operand seen as a sequence of field names
|
||||
("foo.bar" -> ["foo", "bar"])
|
||||
:var obj model: model object, model containing the field
|
||||
(the name provided in the left operand)
|
||||
:var obj field: the field corresponding to `path[0]`
|
||||
:var obj column: the column corresponding to `path[0]`
|
||||
:var obj comodel: relational model of field (field.comodel)
|
||||
(res_partner.bank_ids -> res.partner.bank)
|
||||
"""
|
||||
def to_ids(value, comodel, leaf):
|
||||
""" Normalize a single id or name, or a list of those, into a list of ids
|
||||
:param {int,long,basestring,list,tuple} value:
|
||||
if int, long -> return [value]
|
||||
if basestring, convert it into a list of basestrings, then
|
||||
if list of basestring ->
|
||||
perform a name_search on comodel for each name
|
||||
return the list of related ids
|
||||
|
||||
:param comodel:
|
||||
:param leaf:
|
||||
:param int|str|list|tuple value:
|
||||
|
||||
- if int, long -> return [value]
|
||||
- if basestring, convert it into a list of basestrings, then
|
||||
- if list of basestring ->
|
||||
|
||||
- perform a name_search on comodel for each name
|
||||
- return the list of related ids
|
||||
"""
|
||||
names = []
|
||||
if isinstance(value, str):
|
||||
|
||||
+5
-3
@@ -22,14 +22,16 @@ def _from_table(table, alias):
|
||||
|
||||
def _generate_table_alias(src_table_alias, link):
|
||||
""" Generate a standard table alias name. An alias is generated as following:
|
||||
|
||||
- the base is the source table name (that can already be an alias)
|
||||
- then, the joined table is added in the alias using a 'link field name'
|
||||
that is used to render unique aliases for a given path
|
||||
- the name is shortcut if it goes beyond PostgreSQL's identifier limits
|
||||
|
||||
Examples:
|
||||
- src_table_alias='res_users', link='parent_id'
|
||||
alias = 'res_users__parent_id'
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> _generate_table_alias('res_users', link='parent_id')
|
||||
'res_users__parent_id'
|
||||
|
||||
:param str src_table_alias: alias of the source table
|
||||
:param str link: field name
|
||||
|
||||
+15
-13
@@ -569,19 +569,21 @@ class TestCursor(BaseCursor):
|
||||
the transaction open across requests, and simulates committing, rolling
|
||||
back, and closing:
|
||||
|
||||
test cursor | queries on actual cursor
|
||||
------------------------+---------------------------------------
|
||||
cr = TestCursor(...) | SAVEPOINT test_cursor_N
|
||||
|
|
||||
cr.execute(query) | query
|
||||
|
|
||||
cr.commit() | RELEASE SAVEPOINT test_cursor_N
|
||||
| SAVEPOINT test_cursor_N (lazy)
|
||||
|
|
||||
cr.rollback() | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint)
|
||||
|
|
||||
cr.close() | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint)
|
||||
| RELEASE SAVEPOINT test_cursor_N (if savepoint)
|
||||
+------------------------+---------------------------------------------------+
|
||||
| test cursor | queries on actual cursor |
|
||||
+========================+===================================================+
|
||||
|``cr = TestCursor(...)``| SAVEPOINT test_cursor_N |
|
||||
+------------------------+---------------------------------------------------+
|
||||
| ``cr.execute(query)`` | query |
|
||||
+------------------------+---------------------------------------------------+
|
||||
| ``cr.commit()`` | RELEASE SAVEPOINT test_cursor_N |
|
||||
| | SAVEPOINT test_cursor_N (lazy) |
|
||||
+------------------------+---------------------------------------------------+
|
||||
| ``cr.rollback()`` | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint)|
|
||||
+------------------------+---------------------------------------------------+
|
||||
| ``cr.close()`` | ROLLBACK TO SAVEPOINT test_cursor_N (if savepoint)|
|
||||
| | RELEASE SAVEPOINT test_cursor_N (if savepoint) |
|
||||
+------------------------+---------------------------------------------------+
|
||||
"""
|
||||
_cursors_stack = []
|
||||
def __init__(self, cursor, lock):
|
||||
|
||||
+21
-13
@@ -305,11 +305,10 @@ class MetaCase(type):
|
||||
def _normalize_arch_for_assert(arch_string, parser_method="xml"):
|
||||
"""Takes some xml and normalize it to make it comparable to other xml
|
||||
in particular, blank text is removed, and the output is pretty-printed
|
||||
:param arch_string: the string representing an XML arch
|
||||
:type arch_string: str
|
||||
:param parser_method: an string representing which lxml.Parser class to use
|
||||
|
||||
:param str arch_string: the string representing an XML arch
|
||||
:param str parser_method: an string representing which lxml.Parser class to use
|
||||
when normalizing both archs. Takes either "xml" or "html"
|
||||
:type parser_method: str
|
||||
:return: the normalized arch
|
||||
:rtype str:
|
||||
"""
|
||||
@@ -671,6 +670,7 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase):
|
||||
|
||||
def _assertXMLEqual(self, original, expected, parser="xml"):
|
||||
"""Asserts that two xmls archs are equal
|
||||
|
||||
:param original: the xml arch to test
|
||||
:type original: str
|
||||
:param expected: the xml arch of reference
|
||||
@@ -981,16 +981,24 @@ class ChromeBrowser:
|
||||
self._logger.info('Chrome headless temporary user profile dir: %s', self.user_data_dir)
|
||||
|
||||
def _json_command(self, command, timeout=3, get_key=None):
|
||||
"""
|
||||
Inspect dev tools with get
|
||||
"""Queries browser state using JSON
|
||||
|
||||
Available commands:
|
||||
'' : return list of tabs with their id
|
||||
list (or json/): list tabs
|
||||
new : open a new tab
|
||||
activate/ + an id: activate a tab
|
||||
close/ + and id: close a tab
|
||||
version : get chrome and dev tools version
|
||||
protocol : get the full protocol
|
||||
|
||||
``''``
|
||||
return list of tabs with their id
|
||||
``list`` (or ``json/``)
|
||||
list tabs
|
||||
``new``
|
||||
open a new tab
|
||||
:samp:`activate/{id}`
|
||||
activate a tab
|
||||
:samp:`close/{id}`
|
||||
close a tab
|
||||
``version``
|
||||
get chrome and dev tools version
|
||||
``protocol``
|
||||
get the full protocol
|
||||
"""
|
||||
command = os.path.join('json', command).strip('/')
|
||||
url = werkzeug.urls.url_join('http://%s:%s/' % (HOST, self.devtools_port), command)
|
||||
|
||||
+96
-74
@@ -86,20 +86,26 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"multipath" is an optional parameter only applicable to *nix
|
||||
"multipath" is an optional parameter only applicable to \*nix
|
||||
which indicates that the entire list of data dirs should be
|
||||
returned. By default, the first item from XDG_DATA_DIRS is
|
||||
returned, or '/usr/local/share/<AppName>',
|
||||
if XDG_DATA_DIRS is not set
|
||||
returned, or :samp:`/usr/local/share/{AppName}`,
|
||||
if ``XDG_DATA_DIRS`` is not set
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: /Library/Application Support/<AppName>
|
||||
Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
|
||||
Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
|
||||
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||
Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
|
||||
|
||||
For Unix, this is using the $XDG_DATA_DIRS[0] default.
|
||||
Mac OS X
|
||||
:samp:`/Library/Application Support/{AppName}`
|
||||
Unix
|
||||
:samp:`/usr/local/share/{AppName}` or :samp:`/usr/share/{AppName}`
|
||||
Win XP
|
||||
:samp:`C:\Documents and Settings\All Users\Application Data\{AppAuthor}\{AppName}`
|
||||
Vista
|
||||
Fail! "C:\ProgramData" is a hidden *system* directory on Vista.
|
||||
Win 7
|
||||
:samp:`C:\ProgramData\{AppAuthor}\{AppName}` (hidden, but writeable on Win 7)
|
||||
|
||||
For Unix, this is using the ``$XDG_DATA_DIRS[0]`` default.
|
||||
|
||||
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
|
||||
"""
|
||||
@@ -136,32 +142,36 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
|
||||
|
||||
def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
r"""Return full path to the user-specific config dir for this application.
|
||||
"""Return full path to the user-specific config dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only required and used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"roaming" (boolean, default False) can be set True to use the Windows
|
||||
roaming appdata directory. That means that for users on a Windows
|
||||
network setup for roaming profiles, this user data will be
|
||||
sync'd on login. See
|
||||
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
|
||||
for a discussion of issues.
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only required and used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"roaming" (boolean, default False) can be set True to use the Windows
|
||||
roaming appdata directory. That means that for users on a Windows
|
||||
network setup for roaming profiles, this user data will be
|
||||
sync'd on login. See `managing roaming user data
|
||||
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_
|
||||
for a discussion of issues.
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: same as user_data_dir
|
||||
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
|
||||
Win *: same as user_data_dir
|
||||
|
||||
For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
|
||||
That means, by default "~/.local/share/<AppName>".
|
||||
Mac OS X
|
||||
same as user_data_dir
|
||||
Unix
|
||||
:samp:`~/.config/{AppName}` or in $XDG_CONFIG_HOME, if defined
|
||||
Win *
|
||||
same as user_data_dir
|
||||
|
||||
For Unix, we follow the XDG spec and support ``$XDG_DATA_HOME``.
|
||||
That means, by default :samp:`~/.local/share/{AppName}`.
|
||||
"""
|
||||
if sys.platform in [ "win32", "darwin" ]:
|
||||
path = user_data_dir(appname, appauthor, None, roaming)
|
||||
@@ -177,29 +187,34 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
|
||||
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
|
||||
"""Return full path to the user-shared data dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only required and used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"multipath" is an optional parameter only applicable to *nix
|
||||
which indicates that the entire list of config dirs should be
|
||||
returned. By default, the first item from XDG_CONFIG_DIRS is
|
||||
returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only required and used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"multipath" is an optional parameter only applicable to \*nix
|
||||
which indicates that the entire list of config dirs should be
|
||||
returned. By default, the first item from ``XDG_CONFIG_DIRS`` is
|
||||
returned, or :samp:`/etc/xdg/{AppName}`, if ``XDG_CONFIG_DIRS`` is not set
|
||||
|
||||
Typical user data directories are:
|
||||
Mac OS X: same as site_data_dir
|
||||
Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
|
||||
$XDG_CONFIG_DIRS
|
||||
Win *: same as site_data_dir
|
||||
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
|
||||
|
||||
For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
|
||||
Mac OS X
|
||||
same as site_data_dir
|
||||
Unix
|
||||
``/etc/xdg/<AppName>`` or ``$XDG_CONFIG_DIRS[i]/<AppName>`` for each
|
||||
value in ``$XDG_CONFIG_DIRS``
|
||||
Win *
|
||||
same as site_data_dir
|
||||
Vista
|
||||
Fail! "C:\ProgramData" is a hidden *system* directory on Vista.
|
||||
|
||||
For Unix, this is using the ``$XDG_CONFIG_DIRS[0]`` default, if ``multipath=False``
|
||||
|
||||
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
|
||||
"""
|
||||
@@ -226,34 +241,41 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False)
|
||||
def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
|
||||
r"""Return full path to the user-specific cache dir for this application.
|
||||
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only required and used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"opinion" (boolean) can be False to disable the appending of
|
||||
"Cache" to the base app data dir for Windows. See
|
||||
discussion below.
|
||||
"appname" is the name of application.
|
||||
If None, just the system directory is returned.
|
||||
"appauthor" (only required and used on Windows) is the name of the
|
||||
appauthor or distributing body for this application. Typically
|
||||
it is the owning company name. This falls back to appname.
|
||||
"version" is an optional version path element to append to the
|
||||
path. You might want to use this if you want multiple versions
|
||||
of your app to be able to run independently. If used, this
|
||||
would typically be "<major>.<minor>".
|
||||
Only applied when appname is present.
|
||||
"opinion" (boolean) can be False to disable the appending of
|
||||
"Cache" to the base app data dir for Windows. See
|
||||
discussion below.
|
||||
|
||||
Typical user cache directories are:
|
||||
Mac OS X: ~/Library/Caches/<AppName>
|
||||
Unix: ~/.cache/<AppName> (XDG default)
|
||||
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
|
||||
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
|
||||
|
||||
Mac OS X
|
||||
~/Library/Caches/<AppName>
|
||||
Unix
|
||||
~/.cache/<AppName> (XDG default)
|
||||
Win XP
|
||||
C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
|
||||
Vista
|
||||
C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
|
||||
|
||||
On Windows the only suggestion in the MSDN docs is that local settings go in
|
||||
the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
|
||||
app data dir (the default returned by `user_data_dir` above). Apps typically
|
||||
the ``CSIDL_LOCAL_APPDATA`` directory. This is identical to the non-roaming
|
||||
app data dir (the default returned by ``user_data_dir`` above). Apps typically
|
||||
put cache data somewhere *under* the given dir here. Some examples:
|
||||
...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
|
||||
...\Acme\SuperApp\Cache\1.0
|
||||
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
|
||||
This can be disabled with the `opinion=False` option.
|
||||
|
||||
- ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
|
||||
- ...\Acme\SuperApp\Cache\1.0
|
||||
|
||||
OPINION: This function appends "Cache" to the ``CSIDL_LOCAL_APPDATA`` value.
|
||||
This can be disabled with the ``opinion=False`` option.
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
if appauthor is None:
|
||||
|
||||
@@ -291,7 +291,7 @@ class configmanager(object):
|
||||
"[ipython|ptpython|bpython|python]")
|
||||
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
|
||||
help="stop the server after its initialization")
|
||||
group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False,
|
||||
group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=0,
|
||||
help="Force a limit on the maximum number of records kept in the virtual "
|
||||
"osv_memory tables. By default there is no limit.",
|
||||
type="int")
|
||||
|
||||
@@ -216,9 +216,9 @@ def json_default(obj):
|
||||
def date_range(start, end, step=relativedelta(months=1)):
|
||||
"""Date range generator with a step interval.
|
||||
|
||||
:param start datetime: beginning date of the range.
|
||||
:param end datetime: ending date of the range.
|
||||
:param step relativedelta: interval of the range.
|
||||
:param datetime start: beginning date of the range.
|
||||
:param datetime end: ending date of the range.
|
||||
:param relativedelta step: interval of the range.
|
||||
:return: a range of datetime from start to end.
|
||||
:rtype: Iterator[datetime]
|
||||
"""
|
||||
|
||||
@@ -162,13 +162,13 @@ def float_compare(value1, value2, precision_digits=None, precision_rounding=None
|
||||
|
||||
def float_repr(value, precision_digits):
|
||||
"""Returns a string representation of a float with the
|
||||
the given number of fractional digits. This should not be
|
||||
given number of fractional digits. This should not be
|
||||
used to perform a rounding operation (this is done via
|
||||
:meth:`~.float_round`), but only to produce a suitable
|
||||
:func:`~.float_round`), but only to produce a suitable
|
||||
string representation for a float.
|
||||
|
||||
:param int precision_digits: number of fractional digits to
|
||||
include in the output
|
||||
:param float value:
|
||||
:param int precision_digits: number of fractional digits to include in the output
|
||||
"""
|
||||
# Can't use str() here because it seems to have an intrinsic
|
||||
# rounding to 12 significant digits, which causes a loss of
|
||||
|
||||
+6
-5
@@ -50,7 +50,7 @@ class lazy_classproperty(lazy_property):
|
||||
def conditional(condition, decorator):
|
||||
""" Decorator for a conditionally applied decorator.
|
||||
|
||||
Example:
|
||||
Example::
|
||||
|
||||
@conditional(get_config('use_cache'), ormcache)
|
||||
def fn():
|
||||
@@ -118,12 +118,13 @@ def classproperty(func):
|
||||
|
||||
|
||||
class lazy(object):
|
||||
""" A proxy to the (memoized) result of a lazy evaluation::
|
||||
""" A proxy to the (memoized) result of a lazy evaluation:
|
||||
|
||||
foo = lazy(func, arg) # func(arg) is not called yet
|
||||
bar = foo + 1 # eval func(arg) and add 1
|
||||
baz = foo + 2 # use result of func(arg) and add 2
|
||||
.. code-block::
|
||||
|
||||
foo = lazy(func, arg) # func(arg) is not called yet
|
||||
bar = foo + 1 # eval func(arg) and add 1
|
||||
baz = foo + 2 # use result of func(arg) and add 2
|
||||
"""
|
||||
__slots__ = ['_func', '_args', '_kwargs', '_cached_value']
|
||||
|
||||
|
||||
+48
-74
@@ -53,6 +53,7 @@ class ImageProcess():
|
||||
"""Initialize the `base64_source` image for processing.
|
||||
|
||||
:param base64_source: the original image base64 encoded
|
||||
|
||||
No processing will be done if the `base64_source` is falsy or if
|
||||
the image is SVG.
|
||||
:type base64_source: string or bytes
|
||||
@@ -61,8 +62,6 @@ class ImageProcess():
|
||||
excessive before starting to process it. The max allowed resolution is
|
||||
defined by `IMAGE_MAX_RESOLUTION`.
|
||||
:type verify_resolution: bool
|
||||
|
||||
:return: self
|
||||
:rtype: ImageProcess
|
||||
|
||||
:raise: ValueError if `verify_resolution` is True and the image is too large
|
||||
@@ -99,19 +98,16 @@ class ImageProcess():
|
||||
and the `output_format` is the same as the original format and the
|
||||
quality is not specified.
|
||||
|
||||
:param quality: quality setting to apply. Default to 0.
|
||||
:param int quality: quality setting to apply. Default to 0.
|
||||
|
||||
- for JPEG: 1 is worse, 95 is best. Values above 95 should be
|
||||
avoided. Falsy values will fallback to 95, but only if the image
|
||||
was changed, otherwise the original image is returned.
|
||||
avoided. Falsy values will fallback to 95, but only if the image
|
||||
was changed, otherwise the original image is returned.
|
||||
- for PNG: set falsy to prevent conversion to a WEB palette.
|
||||
- for other formats: no effect.
|
||||
:type quality: int
|
||||
|
||||
:param output_format: the output format. Can be PNG, JPEG, GIF, or ICO.
|
||||
:param str output_format: the output format. Can be PNG, JPEG, GIF, or ICO.
|
||||
Default to the format of the original image. BMP is converted to
|
||||
PNG, other formats than those mentioned above are converted to JPEG.
|
||||
:type output_format: string
|
||||
|
||||
:return: image
|
||||
:rtype: bytes or False
|
||||
"""
|
||||
@@ -160,19 +156,16 @@ class ImageProcess():
|
||||
applied and the `output_format` is the same as the original format and
|
||||
the quality is not specified.
|
||||
|
||||
:param quality: quality setting to apply. Default to 0.
|
||||
:param int quality: quality setting to apply. Default to 0.
|
||||
|
||||
- for JPEG: 1 is worse, 95 is best. Values above 95 should be
|
||||
avoided. Falsy values will fallback to 95, but only if the image
|
||||
was changed, otherwise the original image is returned.
|
||||
avoided. Falsy values will fallback to 95, but only if the image
|
||||
was changed, otherwise the original image is returned.
|
||||
- for PNG: set falsy to prevent conversion to a WEB palette.
|
||||
- for other formats: no effect.
|
||||
:type quality: int
|
||||
|
||||
:param output_format: the output format. Can be PNG, JPEG, GIF, or ICO.
|
||||
:param str output_format: the output format. Can be PNG, JPEG, GIF, or ICO.
|
||||
Default to the format of the original image. BMP is converted to
|
||||
PNG, other formats than those mentioned above are converted to JPEG.
|
||||
:type output_format: string
|
||||
|
||||
:return: image base64 encoded or False
|
||||
:rtype: bytes or False
|
||||
"""
|
||||
@@ -200,12 +193,8 @@ class ImageProcess():
|
||||
It is currently not supported for GIF because we do not handle all the
|
||||
frames properly.
|
||||
|
||||
:param max_width: max width
|
||||
:type max_width: int
|
||||
|
||||
:param max_height: max height
|
||||
:type max_height: int
|
||||
|
||||
:param int max_width: max width
|
||||
:param int max_height: max height
|
||||
:return: self to allow chaining
|
||||
:rtype: ImageProcess
|
||||
"""
|
||||
@@ -238,20 +227,12 @@ class ImageProcess():
|
||||
It is currently not supported for GIF because we do not handle all the
|
||||
frames properly.
|
||||
|
||||
:param max_width: max width
|
||||
:type max_width: int
|
||||
|
||||
:param max_height: max height
|
||||
:type max_height: int
|
||||
|
||||
:param center_x: the center of the crop between 0 (left) and 1 (right)
|
||||
Default to 0.5 (center).
|
||||
:type center_x: float
|
||||
|
||||
:param center_y: the center of the crop between 0 (top) and 1 (bottom)
|
||||
Default to 0.5 (center).
|
||||
:type center_y: float
|
||||
|
||||
:param int max_width: max width
|
||||
:param int max_height: max height
|
||||
:param float center_x: the center of the crop between 0 (left) and 1
|
||||
(right). Defaults to 0.5 (center).
|
||||
:param float center_y: the center of the crop between 0 (top) and 1
|
||||
(bottom). Defaults to 0.5 (center).
|
||||
:return: self to allow chaining
|
||||
:rtype: ImageProcess
|
||||
"""
|
||||
@@ -332,25 +313,30 @@ def image_process(base64_source, size=(0, 0), verify_resolution=False, quality=0
|
||||
def average_dominant_color(colors, mitigate=175, max_margin=140):
|
||||
"""This function is used to calculate the dominant colors when given a list of colors
|
||||
|
||||
There are 5 steps :
|
||||
1) Select dominant colors (highest count), isolate its values and remove
|
||||
it from the current color set.
|
||||
2) Set margins according to the prevalence of the dominant color.
|
||||
3) Evaluate the colors. Similar colors are grouped in the dominant set
|
||||
while others are put in the "remaining" list.
|
||||
4) Calculate the average color for the dominant set. This is done by
|
||||
averaging each band and joining them into a tuple.
|
||||
5) Mitigate final average and convert it to hex
|
||||
There are 5 steps:
|
||||
|
||||
1) Select dominant colors (highest count), isolate its values and remove
|
||||
it from the current color set.
|
||||
2) Set margins according to the prevalence of the dominant color.
|
||||
3) Evaluate the colors. Similar colors are grouped in the dominant set
|
||||
while others are put in the "remaining" list.
|
||||
4) Calculate the average color for the dominant set. This is done by
|
||||
averaging each band and joining them into a tuple.
|
||||
5) Mitigate final average and convert it to hex
|
||||
|
||||
:param colors: list of tuples having:
|
||||
[0] color count in the image
|
||||
[1] actual color: tuple(R, G, B, A)
|
||||
-> these can be extracted from a PIL image using image.getcolors()
|
||||
|
||||
0. color count in the image
|
||||
1. actual color: tuple(R, G, B, A)
|
||||
|
||||
-> these can be extracted from a PIL image using
|
||||
:meth:`~PIL.Image.Image.getcolors`
|
||||
:param mitigate: maximum value a band can reach
|
||||
:param max_margin: maximum difference from one of the dominant values
|
||||
:returns: a tuple with two items:
|
||||
[0] the average color of the dominant set as: tuple(R, G, B)
|
||||
[1] list of remaining colors, used to evaluate subsequent dominant colors
|
||||
|
||||
0. the average color of the dominant set as: tuple(R, G, B)
|
||||
1. list of remaining colors, used to evaluate subsequent dominant colors
|
||||
"""
|
||||
dominant_color = max(colors)
|
||||
dominant_rgb = dominant_color[1][:3]
|
||||
@@ -409,11 +395,10 @@ def image_fix_orientation(image):
|
||||
save the complexity of removing it.
|
||||
|
||||
:param image: the source image
|
||||
:type image: PIL.Image
|
||||
|
||||
:type image: ~PIL.Image.Image
|
||||
:return: the resulting image, copy of the source, with orientation fixed
|
||||
or the source image if no operation was applied
|
||||
:rtype: PIL.Image
|
||||
:rtype: ~PIL.Image.Image
|
||||
"""
|
||||
getexif = getattr(image, 'getexif', None) or getattr(image, '_getexif', None) # support PIL < 6.0
|
||||
if getexif:
|
||||
@@ -431,10 +416,7 @@ def base64_to_image(base64_source):
|
||||
|
||||
:param base64_source: the image base64 encoded
|
||||
:type base64_source: string or bytes
|
||||
|
||||
:return: the PIL image
|
||||
:rtype: PIL.Image
|
||||
|
||||
:rtype: ~PIL.Image.Image
|
||||
:raise: UserError if the base64 is incorrect or the image can't be identified by PIL
|
||||
"""
|
||||
try:
|
||||
@@ -446,12 +428,9 @@ def base64_to_image(base64_source):
|
||||
def image_apply_opt(image, output_format, **params):
|
||||
"""Return the given PIL `image` using `params`.
|
||||
|
||||
:param image: the PIL image
|
||||
:type image: PIL.Image
|
||||
|
||||
:param params: params to expand when calling PIL.Image.save()
|
||||
:type params: dict
|
||||
|
||||
:type image: ~PIL.Image.Image
|
||||
:param str output_format: :meth:`~PIL.Image.Image.save`'s ``format`` parameter
|
||||
:param dict params: params to expand when calling :meth:`~PIL.Image.Image.save`
|
||||
:return: the image formatted
|
||||
:rtype: bytes
|
||||
"""
|
||||
@@ -463,12 +442,9 @@ def image_apply_opt(image, output_format, **params):
|
||||
def image_to_base64(image, output_format, **params):
|
||||
"""Return a base64_image from the given PIL `image` using `params`.
|
||||
|
||||
:param image: the PIL image
|
||||
:type image: PIL.Image
|
||||
|
||||
:param params: params to expand when calling PIL.Image.save()
|
||||
:type params: dict
|
||||
|
||||
:type image: ~PIL.Image.Image
|
||||
:param str output_format:
|
||||
:param dict params: params to expand when calling :meth:`~PIL.Image.Image.save`
|
||||
:return: the image base64 encoded
|
||||
:rtype: bytes
|
||||
"""
|
||||
@@ -495,9 +471,7 @@ def image_guess_size_from_field_name(field_name):
|
||||
|
||||
If it can't be guessed, return (0, 0) instead.
|
||||
|
||||
:param field_name: the name of a field
|
||||
:type field_name: string
|
||||
|
||||
:param str field_name: the name of a field
|
||||
:return: the guessed size
|
||||
:rtype: tuple (width, height)
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This code is what let us use ES6-style modules in odoo.
|
||||
Classic Odoo modules are composed of a top-level :samp:`odoo.define({name},{body_function})` call.
|
||||
This processor will take files starting with an `@odoo-module` annotation (in a comment) and convert them to classic modules.
|
||||
If any file has the /** odoo-module */ on top of it, it will get processed by this class.
|
||||
If any file has the ``/** odoo-module */`` on top of it, it will get processed by this class.
|
||||
It performs several operations to get from ES6 syntax to the usual odoo one with minimal changes.
|
||||
This is done on the fly, this not a pre-processing tool.
|
||||
|
||||
@@ -560,9 +560,10 @@ def remove_index(content):
|
||||
|
||||
|
||||
def relative_path_to_module_path(url, path_rel):
|
||||
"""
|
||||
Convert the relative path into a module path, which is more generic and fancy.
|
||||
"""Convert the relative path into a module path, which is more generic and
|
||||
fancy.
|
||||
|
||||
:param str url:
|
||||
:param path_rel: a relative path to the current url.
|
||||
:return: module path (@module/...)
|
||||
"""
|
||||
|
||||
+25
-15
@@ -358,16 +358,19 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
|
||||
|
||||
return html.strip()
|
||||
|
||||
def plaintext2html(text, container_tag=False):
|
||||
""" Convert plaintext into html. Content of the text is escaped to manage
|
||||
html entities, using misc.html_escape().
|
||||
- all \n,\r are replaced by <br />
|
||||
- enclose content into <p>
|
||||
- convert url into clickable link
|
||||
- 2 or more consecutive <br /> are considered as paragraph breaks
|
||||
def plaintext2html(text, container_tag=None):
|
||||
r"""Convert plaintext into html. Content of the text is escaped to manage
|
||||
html entities, using :func:`~odoo.tools.misc.html_escape`.
|
||||
|
||||
:param string container_tag: container of the html; by default the
|
||||
content is embedded into a <div>
|
||||
- all ``\n``, ``\r`` are replaced by ``<br/>``
|
||||
- enclose content into ``<p>``
|
||||
- convert url into clickable link
|
||||
- 2 or more consecutive ``<br/>`` are considered as paragraph breaks
|
||||
|
||||
:param str text: plaintext to convert
|
||||
:param str container_tag: container of the html; by default the content is
|
||||
embedded into a ``<div>``
|
||||
:rtype: markupsafe.Markup
|
||||
"""
|
||||
text = misc.html_escape(ustr(text))
|
||||
|
||||
@@ -391,15 +394,18 @@ def plaintext2html(text, container_tag=False):
|
||||
final = '<%s>%s</%s>' % (container_tag, final, container_tag)
|
||||
return markupsafe.Markup(final)
|
||||
|
||||
def append_content_to_html(html, content, plaintext=True, preserve=False, container_tag=False):
|
||||
def append_content_to_html(html, content, plaintext=True, preserve=False, container_tag=None):
|
||||
""" Append extra content at the end of an HTML snippet, trying
|
||||
to locate the end of the HTML document (</body>, </html>, or
|
||||
EOF), and converting the provided content in html unless ``plaintext``
|
||||
is False.
|
||||
|
||||
Content conversion can be done in two ways:
|
||||
- wrapping it into a pre (preserve=True)
|
||||
- use plaintext2html (preserve=False, using container_tag to wrap the
|
||||
whole content)
|
||||
|
||||
- wrapping it into a pre (``preserve=True``)
|
||||
- use plaintext2html (``preserve=False``, using ``container_tag`` to
|
||||
wrap the whole content)
|
||||
|
||||
A side-effect of this method is to coerce all HTML tags to
|
||||
lowercase in ``html``, and strip enclosing <html> or <body> tags in
|
||||
content if ``plaintext`` is False.
|
||||
@@ -410,6 +416,8 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai
|
||||
be wrapped in a <pre/> tag.
|
||||
:param bool preserve: if content is plaintext, wrap it into a <pre>
|
||||
instead of converting it into html
|
||||
:param str container_tag: tag to wrap the content into, defaults to `div`.
|
||||
:rtype: markupsafe.Markup
|
||||
"""
|
||||
html = ustr(html)
|
||||
if plaintext and preserve:
|
||||
@@ -513,7 +521,8 @@ def email_normalize(text):
|
||||
def email_domain_extract(email):
|
||||
""" Extract the company domain to be used by IAP services notably. Domain
|
||||
is extracted from email information e.g:
|
||||
- info@proximus.be -> proximus.be
|
||||
|
||||
- info@proximus.be -> proximus.be
|
||||
"""
|
||||
normalized_email = email_normalize(email)
|
||||
if normalized_email:
|
||||
@@ -530,7 +539,8 @@ def email_domain_normalize(domain):
|
||||
def url_domain_extract(url):
|
||||
""" Extract the company domain to be used by IAP services notably. Domain
|
||||
is extracted from an URL e.g:
|
||||
- www.info.proximus.be -> proximus.be
|
||||
|
||||
- www.info.proximus.be -> proximus.be
|
||||
"""
|
||||
parser_results = urlparse(url)
|
||||
company_hostname = parser_results.hostname
|
||||
|
||||
+37
-41
@@ -181,10 +181,10 @@ def file_open(name, mode="r", filter_ext=None):
|
||||
|
||||
Examples::
|
||||
|
||||
>>> file_open('hr/static/description/icon.png')
|
||||
>>> file_open('hr/static/description/icon.png', filter_ext=('.png', '.jpg'))
|
||||
>>> with file_open('/opt/odoo/addons/hr/static/description/icon.png', 'rb') as f:
|
||||
... contents = f.read()
|
||||
>>> file_open('hr/static/description/icon.png')
|
||||
>>> file_open('hr/static/description/icon.png', filter_ext=('.png', '.jpg'))
|
||||
>>> with file_open('/opt/odoo/addons/hr/static/description/icon.png', 'rb') as f:
|
||||
... contents = f.read()
|
||||
|
||||
:param name: absolute or relative path to a file located inside an addon
|
||||
:param mode: file open mode, as for `open()`
|
||||
@@ -240,24 +240,25 @@ def reverse_enumerate(l):
|
||||
"""Like enumerate but in the other direction
|
||||
|
||||
Usage::
|
||||
>>> a = ['a', 'b', 'c']
|
||||
>>> it = reverse_enumerate(a)
|
||||
>>> it.next()
|
||||
(2, 'c')
|
||||
>>> it.next()
|
||||
(1, 'b')
|
||||
>>> it.next()
|
||||
(0, 'a')
|
||||
>>> it.next()
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
StopIteration
|
||||
|
||||
>>> a = ['a', 'b', 'c']
|
||||
>>> it = reverse_enumerate(a)
|
||||
>>> it.next()
|
||||
(2, 'c')
|
||||
>>> it.next()
|
||||
(1, 'b')
|
||||
>>> it.next()
|
||||
(0, 'a')
|
||||
>>> it.next()
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
StopIteration
|
||||
"""
|
||||
return zip(range(len(l)-1, -1, -1), reversed(l))
|
||||
|
||||
def partition(pred, elems):
|
||||
""" Return a pair equivalent to:
|
||||
``filter(pred, elems), filter(lambda x: not pred(x), elems)` """
|
||||
``filter(pred, elems), filter(lambda x: not pred(x), elems)`` """
|
||||
yes, nos = [], []
|
||||
for elem in elems:
|
||||
(yes if pred(elem) else nos).append(elem)
|
||||
@@ -665,22 +666,6 @@ def split_every(n, iterable, piece_maker=tuple):
|
||||
yield piece
|
||||
piece = piece_maker(islice(iterator, n))
|
||||
|
||||
def get_and_group_by_field(cr, uid, obj, ids, field, context=None):
|
||||
""" Read the values of ``field´´ for the given ``ids´´ and group ids by value.
|
||||
|
||||
:param string field: name of the field we want to read and group by
|
||||
:return: mapping of field values to the list of ids that have it
|
||||
:rtype: dict
|
||||
"""
|
||||
res = {}
|
||||
for record in obj.read(cr, uid, ids, [field], context=context):
|
||||
key = record[field]
|
||||
res.setdefault(key[0] if isinstance(key, tuple) else key, []).append(record['id'])
|
||||
return res
|
||||
|
||||
def get_and_group_by_company(cr, uid, obj, ids, context=None):
|
||||
return get_and_group_by_field(cr, uid, obj, ids, field='company_id', context=context)
|
||||
|
||||
# port of python 2.6's attrgetter with support for dotted notation
|
||||
def resolve_attr(obj, attr):
|
||||
for name in attr.split("."):
|
||||
@@ -769,7 +754,8 @@ class UnquoteEvalContext(defaultdict):
|
||||
|
||||
class mute_logger(logging.Handler):
|
||||
"""Temporary suppress the logging.
|
||||
Can be used as context manager or decorator.
|
||||
|
||||
Can be used as context manager or decorator::
|
||||
|
||||
@mute_logger('odoo.plic.ploc')
|
||||
def do_stuff():
|
||||
@@ -777,7 +763,6 @@ class mute_logger(logging.Handler):
|
||||
|
||||
with mute_logger('odoo.foo.bar'):
|
||||
do_suff()
|
||||
|
||||
"""
|
||||
def __init__(self, *loggers):
|
||||
super().__init__()
|
||||
@@ -978,7 +963,9 @@ def freehash(arg):
|
||||
return id(arg)
|
||||
|
||||
def clean_context(context):
|
||||
""" This function take a dictionary and remove each entry with its key starting with 'default_' """
|
||||
""" This function take a dictionary and remove each entry with its key
|
||||
starting with ``default_``
|
||||
"""
|
||||
return {k: v for k, v in context.items() if not k.startswith('default_')}
|
||||
|
||||
|
||||
@@ -1118,6 +1105,8 @@ class Callbacks:
|
||||
""" A simple queue of callback functions. Upon run, every function is
|
||||
called (in addition order), and the queue is emptied.
|
||||
|
||||
::
|
||||
|
||||
callbacks = Callbacks()
|
||||
|
||||
# add foo
|
||||
@@ -1142,6 +1131,8 @@ class Callbacks:
|
||||
dictionary is automatically cleared by ``run()`` once all callback functions
|
||||
have been called.
|
||||
|
||||
::
|
||||
|
||||
# register foo to process aggregated data
|
||||
@callbacks.add
|
||||
def foo():
|
||||
@@ -1258,6 +1249,8 @@ def get_lang(env, lang_code=False):
|
||||
Retrieve the first lang object installed, by checking the parameter lang_code,
|
||||
the context and then the company. If no lang is installed from those variables,
|
||||
fallback on the first lang installed in the system.
|
||||
|
||||
:param env:
|
||||
:param str lang_code: the locale (i.e. en_US)
|
||||
:return res.lang: the first lang found that is installed on the system.
|
||||
"""
|
||||
@@ -1367,10 +1360,12 @@ def parse_date(env, value, lang_code=False):
|
||||
def format_datetime(env, value, tz=False, dt_format='medium', lang_code=False):
|
||||
""" Formats the datetime in a given format.
|
||||
|
||||
:param {str, datetime} value: naive datetime to format either in string or in datetime
|
||||
:param {str} tz: name of the timezone in which the given datetime should be localized
|
||||
:param {str} dt_format: one of “full”, “long”, “medium”, or “short”, or a custom date/time pattern compatible with `babel` lib
|
||||
:param {str} lang_code: ISO code of the language to use to render the given datetime
|
||||
:param env:
|
||||
:param str|datetime value: naive datetime to format either in string or in datetime
|
||||
:param str tz: name of the timezone in which the given datetime should be localized
|
||||
:param str dt_format: one of “full”, “long”, “medium”, or “short”, or a custom date/time pattern compatible with `babel` lib
|
||||
:param str lang_code: ISO code of the language to use to render the given datetime
|
||||
:rtype: str
|
||||
"""
|
||||
if not value:
|
||||
return ''
|
||||
@@ -1407,6 +1402,7 @@ def format_datetime(env, value, tz=False, dt_format='medium', lang_code=False):
|
||||
def format_time(env, value, tz=False, time_format='medium', lang_code=False):
|
||||
""" Format the given time (hour, minute and second) with the current user preference (language, format, ...)
|
||||
|
||||
:param env:
|
||||
:param value: the time to format
|
||||
:type value: `datetime.time` instance. Could be timezoned to display tzinfo according to format (e.i.: 'full' format)
|
||||
:param tz: name of the timezone in which the given datetime should be localized
|
||||
@@ -1604,7 +1600,7 @@ def get_diff(data_from, data_to, custom_style=False):
|
||||
|
||||
|
||||
def traverse_containers(val, type_):
|
||||
""" Yields atoms filtered by specified type_ (or type tuple), traverses
|
||||
""" Yields atoms filtered by specified ``type_`` (or type tuple), traverses
|
||||
through standard containers (non-string mappings or sequences) *unless*
|
||||
they're selected by the type filter
|
||||
"""
|
||||
|
||||
@@ -124,7 +124,7 @@ def compute(function, seed=None):
|
||||
as ``function(values, counter, random)``, where ``values`` is the other field values,
|
||||
``counter`` is an integer, and ``random`` is a pseudo-random number generator.
|
||||
|
||||
:param function function: (values, counter, random) --> field_values
|
||||
:param callable function: (values, counter, random) --> field_values
|
||||
:param seed: optional initialization of the random number generator
|
||||
:returns: function of the form (iterator, field_name, model_name) -> values
|
||||
:rtype: function (iterator, str, str) -> dict
|
||||
@@ -143,6 +143,7 @@ def randint(a, b, seed=None):
|
||||
|
||||
:param int a: minimal random value
|
||||
:param int b: maximal random value
|
||||
:param int seed:
|
||||
:returns: function of the form (iterator, field_name, model_name) -> values
|
||||
:rtype: function (iterator, str, str) -> dict
|
||||
"""
|
||||
@@ -163,12 +164,13 @@ def randdatetime(*, base_date=None, relative_before=None, relative_after=None, s
|
||||
to a random datetime between relative_before and relative_after, relatively to
|
||||
base_date
|
||||
|
||||
:param base_date (datetime): override the default base date if needed.
|
||||
:param relative_after (relativedelta, timedelta): range up which we can go after the
|
||||
:param datetime base_date: override the default base date if needed.
|
||||
:param relativedelta|timedelta relative_after: range up which we can go after the
|
||||
base date. If not set, defaults to 0, i.e. only in the past of reference.
|
||||
:param relative_before (relativedelta, timedelta): range up which we can go before the
|
||||
:param relativedelta|timedelta relative_before: range up which we can go before the
|
||||
base date. If not set, defaults to 0, i.e. only in the future of reference.
|
||||
:return (generator): iterator for random dates inside the defined range
|
||||
:param seed:
|
||||
:return: iterator for random dates inside the defined range
|
||||
"""
|
||||
base_date = base_date or datetime(2020, 1, 1)
|
||||
seconds_before = relative_before and ((base_date + relative_before) - base_date).total_seconds() or 0
|
||||
|
||||
+1
-1
@@ -254,7 +254,7 @@ def pg_varchar(size=0):
|
||||
'infinite' VARCHAR
|
||||
* Otherwise return a VARCHAR(n)
|
||||
|
||||
:type int size: varchar size, optional
|
||||
:param int size: varchar size, optional
|
||||
:rtype: str
|
||||
"""
|
||||
if size:
|
||||
|
||||
@@ -76,9 +76,12 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
|
||||
context=None, our_module=None):
|
||||
"""Take an ir.actions.act_window and follow it until a report is produced
|
||||
|
||||
:param cr:
|
||||
:param uid:
|
||||
:param action_id: the integer id of an action, or a reference to xml id
|
||||
of the act_window (can search [our_module.]+xml_id
|
||||
:param active_model, active_ids: call the action as if it had been launched
|
||||
:param active_model:
|
||||
:param active_ids: call the action as if it had been launched
|
||||
from that model+ids (tree/form view action)
|
||||
:param wiz_data: a dictionary of values to use in the wizard, if needed.
|
||||
They will override (or complete) the default values of the
|
||||
@@ -86,6 +89,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None,
|
||||
:param wiz_buttons: a list of button names, or button icon strings, which
|
||||
should be preferred to press during the wizard.
|
||||
Eg. 'OK' or 'fa-print'
|
||||
:param context:
|
||||
:param our_module: the name of the calling module (string), like 'account'
|
||||
"""
|
||||
if not our_module and isinstance(action_id, str):
|
||||
|
||||
@@ -170,6 +170,7 @@ avoid_pattern = re.compile(r"\s*<!DOCTYPE", re.IGNORECASE | re.MULTILINE | re.UN
|
||||
def translate_xml_node(node, callback, parse, serialize):
|
||||
""" Return the translation of the given XML/HTML node.
|
||||
|
||||
:param node:
|
||||
:param callback: callback(text) returns translated text or None
|
||||
:param parse: parse(text) returns a node (text is unicode)
|
||||
:param serialize: serialize(node) returns unicode text
|
||||
@@ -1135,6 +1136,7 @@ def trans_load_data(cr, fileobj, fileformat, lang,
|
||||
verbose=True, create_empty_translation=False, overwrite=False):
|
||||
"""Populates the ir_translation table.
|
||||
|
||||
:param cr:
|
||||
:param fileobj: buffer open to a translation file
|
||||
:param fileformat: format of the `fielobj` file, one of 'po' or 'csv'
|
||||
:param lang: language code of the translations contained in `fileobj`
|
||||
@@ -1237,10 +1239,12 @@ def resetlocale():
|
||||
|
||||
def load_language(cr, lang):
|
||||
""" Loads a translation terms for a language.
|
||||
|
||||
Used mainly to automate language loading at db initialization.
|
||||
|
||||
:param lang: language ISO code with optional _underscore_ and l10n flavor (ex: 'fr', 'fr_BE', but not 'fr-BE')
|
||||
:type lang: str
|
||||
:param cr:
|
||||
:param str lang: language ISO code with optional underscore (``_``) and
|
||||
l10n flavor (ex: 'fr', 'fr_BE', but not 'fr-BE')
|
||||
"""
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
installer = env['base.language.install'].create({'lang': lang})
|
||||
|
||||
+14
-11
@@ -30,10 +30,11 @@ def _check_with_xsd(tree_or_str, stream, env=None):
|
||||
|
||||
This will raise a UserError if the XML file is not valid according to the
|
||||
XSD file.
|
||||
:param tree_or_str (etree, str): representation of the tree to be checked
|
||||
:param stream (io.IOBase, str): the byte stream used to build the XSD schema.
|
||||
|
||||
:param str | etree._Element tree_or_str: representation of the tree to be checked
|
||||
:param io.IOBase | str stream: the byte stream used to build the XSD schema.
|
||||
If env is given, it can also be the name of an attachment in the filestore
|
||||
:param env (odoo.api.Environment): If it is given, it enables resolving the
|
||||
:param odoo.api.Environment env: If it is given, it enables resolving the
|
||||
imports of the schema in the filestore with ir.attachments.
|
||||
"""
|
||||
if not isinstance(tree_or_str, etree._Element):
|
||||
@@ -58,10 +59,12 @@ def create_xml_node_chain(first_parent_node, nodes_list, last_node_value=None):
|
||||
|
||||
Each new node being the child of the previous one based on the tags contained
|
||||
in `nodes_list`, under the given node `first_parent_node`.
|
||||
:param first_parent_node (etree._Element): parent of the created tree/chain
|
||||
:param nodes_list (iterable<str>): tag names to be created
|
||||
:param last_node_value (str): if specified, set the last node's text to this value
|
||||
:returns (list<etree._Element>): the list of created nodes
|
||||
|
||||
:param etree._Element first_parent_node: parent of the created tree/chain
|
||||
:param iterable[str] nodes_list: tag names to be created
|
||||
:param str last_node_value: if specified, set the last node's text to this value
|
||||
:returns: the list of created nodes
|
||||
:rtype: list[etree._Element]
|
||||
"""
|
||||
res = []
|
||||
current_node = first_parent_node
|
||||
@@ -77,9 +80,9 @@ def create_xml_node_chain(first_parent_node, nodes_list, last_node_value=None):
|
||||
def create_xml_node(parent_node, node_name, node_value=None):
|
||||
"""Create a new node.
|
||||
|
||||
:param parent_node (etree._Element): parent of the created node
|
||||
:param node_name (str): name of the created node
|
||||
:param node_value (str): value of the created node (optional)
|
||||
:returns (etree._Element):
|
||||
:param etree._Element parent_node: parent of the created node
|
||||
:param str node_name: name of the created node
|
||||
:param str node_value: value of the created node (optional)
|
||||
:rtype: etree._Element
|
||||
"""
|
||||
return create_xml_node_chain(parent_node, [node_name], node_value)[0]
|
||||
|
||||
@@ -45,3 +45,36 @@ requires =
|
||||
python3-xlwt
|
||||
python3-xlrd
|
||||
python3-zeep
|
||||
|
||||
[flake8]
|
||||
exclude =
|
||||
.git,
|
||||
.tx,
|
||||
addons,
|
||||
debian,
|
||||
doc,
|
||||
setup,
|
||||
select =
|
||||
RST
|
||||
rst-directives =
|
||||
function,
|
||||
attribute,
|
||||
seealso,
|
||||
deprecated,
|
||||
versionadded,
|
||||
versionchanged,
|
||||
todo
|
||||
rst-roles =
|
||||
ref,
|
||||
mod,
|
||||
class,
|
||||
py:meth,
|
||||
meth,
|
||||
attr,
|
||||
data,
|
||||
const,
|
||||
func,
|
||||
exc,
|
||||
term,
|
||||
samp,
|
||||
program
|
||||
|
||||
Reference in New Issue
Block a user