示例#1
0
    def on_xml_record(self, xml_record):
        model = xml_record.record_node.get("model")
        if model != "ir.ui.view":
            return

        arch = get_view_arch(xml_record.record_node)
        if arch is None:
            return

        for attr, version in (("string", 8), ("colors", 9), ("fonts", 9)):
            if xml_record.addon.version < version:
                continue

            for search in arch.xpath(f".//tree[@{attr}]"):
                yield Issue(
                    "deprecated_tree_attribute",
                    f"`<tree>` `{attr}` attribute is deprecated since version "
                    f"{version}.0",
                    xml_record.addon.manifest_path,
                    [Location(xml_record.path, [search.sourceline])],
                    categories=["deprecated"],
                )
            for xpath in arch.xpath('.//xpath[@position="attributes"]'):
                nodename = get_xpath_expr_target_element(xpath.get("expr"))
                if nodename != "tree":
                    continue
                for attr_el in xpath.xpath(f'.//attribute[@name="{attr}"]'):
                    yield Issue(
                        "deprecated_tree_attribute",
                        f"`<tree>` `{attr}` attribute is deprecated since version "
                        f"{version}.0",
                        xml_record.addon.manifest_path,
                        [Location(xml_record.path, [attr_el.sourceline])],
                        categories=["deprecated"],
                    )
示例#2
0
    def on_python_module(self, python_module):
        for node in walk(python_module.module):
            if node.type != "decorator":
                continue

            name_parts = extract_func_name(node)
            if (len(name_parts) == 1 and name_parts[0] != "route") or (
                    len(name_parts) == 2 and
                (name_parts[0] != "http" or name_parts[1] != "route")):
                continue

            kwargs = {
                c.children[0].value: (
                    c.children[0].line,
                    column_index_1(c.children[0].start_pos),
                )
                for c in walk(node) if c.type == "argument"
            }

            for kw in kwargs.keys() - ROUTE_KWARGS[
                    python_module.addon.version]:
                yield Issue(
                    "unknown_route_kwarg",
                    f'Unknown `http.route()` keyword argument "{kw}"',
                    python_module.addon.manifest_path,
                    [Location(python_module.path, [kwargs[kw][0]])],
                    categories=["correctness"],
                )
示例#3
0
文件: noupdate.py 项目: srekal/odd
    def on_xml_record(self, xml_record):
        record = xml_record.record_node
        record_id = record.get("id")
        if (
            not record_id
            or xml_record.noupdate
            or xml_record.path not in xml_record.addon.data_files
        ):
            return

        addon_name, _ = split_external_id(record_id)
        if addon_name and addon_name != xml_record.addon.name:
            return

        model = record.attrib["model"]
        if model in MODELS:
            yield Issue(
                "expected_noupdate_flag",
                f'`{model}` model records should be declared in a `noupdate="1"` '
                f"XML data element to allow user modifications",
                xml_record.addon.manifest_path,
                [
                    Location(
                        xml_record.path,
                        [record.sourceline, record.getparent().sourceline],
                    )
                ],
                categories=["correctness"],
            )
示例#4
0
    def _extract_xml_record(self, addon, filename, tree):
        # <record> operation.
        for record in tree.xpath("//record"):
            record_model, record_id = record.attrib["model"], record.get("id")
            if record_model:
                yield _model_ref(addon, filename, record.sourceline,
                                 record_model)
            if record_id:
                yield _ref_or_def(addon, filename, record.sourceline,
                                  record_id, record_model)

            # <field> operation.
            for field in record.iterchildren(tag="field"):
                field_name = field.attrib["name"]

                field_external_id = _field_record_id(record_model, field_name)
                yield ExternalIDReference(
                    addon,
                    UNKNOWN,
                    field_external_id,
                    "ir.model.fields",
                    Location(filename, field.sourceline),
                )

                ref = field.get("ref")
                if ref:
                    ref_model = KNOWN_FIELD_MODELS.get(record_model, {}).get(
                        field_name, UNKNOWN)
                    yield _ref(addon, filename, field.sourceline, ref,
                               ref_model)

                for attr_name in ("eval", "search"):
                    yield from self._get_ref_from_eval(addon, filename,
                                                       field.sourceline,
                                                       field.get(attr_name))

            # View-specific tags.
            if record_model != "ir.ui.view":
                continue

            arch = get_view_arch(record)
            if arch is None:
                continue

            for button in arch.xpath(".//button[@type='action' and @name]"):
                button_name = button.get("name")
                if button_name:
                    yield _ref(
                        addon,
                        filename,
                        button.sourceline,
                        remove_old_style_format(button_name),
                        "ir.ui.view",
                    )

            for el in arch.xpath(".//*[@groups]"):
                groups = el.get("groups")
                for group in split_groups(groups):
                    yield _ref(addon, filename, el.sourceline, group,
                               "res.groups")
示例#5
0
    def on_addon(self, addon):
        extensions = {
            f".{ext}"
            for ext in lookup_version_list(EXT_VERSION_MAP, addon.version)
        }
        combined_data_files = {
            *addon.data_files, *addon.demo_files, *addon.qweb_files
        }
        exclude_dirs = {
            addon.path / dir
            for dir in ("tests", "static/src/xml")
        }

        for file_path in list_files(addon.path,
                                    list_dirs=False,
                                    exclude_dirs=exclude_dirs):
            if file_path.suffix not in extensions:
                continue
            if file_path not in combined_data_files:
                yield Issue(
                    "data_file_missing_in_manifest",
                    "Data file is not included in `demo` or `data` "
                    "sections in the manifest file",
                    addon.manifest_path,
                    [Location(file_path)],
                    categories=["correctness"],
                )
示例#6
0
def _ref_or_def(addon, filename, position, external_id,
                model) -> typing.Union[ExternalID, ExternalIDReference]:
    addon_name, record_id = split_external_id(external_id)
    cls_ = ExternalIDReference
    if not addon_name or addon_name == addon.name:
        cls_ = ExternalID
    return cls_(addon, addon_name, record_id, model,
                Location(filename, [position]))
示例#7
0
    def on_xml_record(self, xml_record):
        record = xml_record.record_node
        if record.attrib["model"] != "ir.ui.view":
            return
        view_xml_id = record.get("id")

        # Skip inherited views.
        inherit_id = record.xpath("./field[@name='inherit_id']")
        if inherit_id and inherit_id[0].attrib.get("ref"):
            return

        # Skip `arch` override in an extending addon.
        if view_xml_id:
            addon_name, _ = split_external_id(view_xml_id)
            if addon_name and addon_name != xml_record.addon.name:
                return

        arch = get_view_arch(record)
        if arch is None:
            _LOG.warning(
                "`ir.ui.view` record has no `arch` field "
                "in file: %s at line %d",
                xml_record.path,
                record.sourceline,
            )
            return

        children = [
            c for c in arch.getchildren() if c.tag is not etree.Comment
        ]
        if len(children) != 1:
            _LOG.warning(
                "Unexpected number of children in `ir.ui.view` "
                "`arch` in file: %s at line %d",
                xml_record.path,
                arch.sourceline,
            )
            return

        fields = collections.defaultdict(list)
        for path, line_no in find_fields(arch, ()):
            fields[path].append(line_no)

        for path, line_nos in fields.items():
            if len(line_nos) > 1:
                field_name = path.split("/")[-1]
                yield Issue(
                    "duplicate_view_field",
                    f'"{view_xml_id}" `ir.ui.view` has duplicate field '
                    f'"{field_name}"' if view_xml_id else
                    (f'`ir.ui.view` has duplicate field "{field_name}"'),
                    xml_record.addon.manifest_path,
                    [
                        Location(xml_record.path, [line_no])
                        for line_no in line_nos
                    ],
                    categories=["correctness"],
                )
示例#8
0
    def on_field_definition(self, field):
        addon, path = field.model.addon, field.model.path
        known_fields = FIELD_TYPE_VERSION_MAP.get(addon.version, set())
        get_odoo_string = get_odoo_string_compute_func(addon.version)
        sources = ([ODOO_SOURCE_URL_VERSION_MAP[addon.version]]
                   if addon.version in ODOO_SOURCE_URL_VERSION_MAP else [])
        if field.class_name not in known_fields:
            _LOG.warning("Unknown field type: %s", field.class_name)
            return

        string_kwarg = None
        for kwarg in field.kwargs:
            if kwarg.name == "string":
                string_kwarg = kwarg
                break
        if string_kwarg and string_kwarg.value == get_odoo_string(field.name):
            yield Issue(
                "redundant_field_attribute",
                f'Redundant field attribute `string="{string_kwarg.value}"` '
                f'for field "{field.name}". The same value will be computed '
                f"by Odoo automatically.",
                addon.manifest_path,
                [Location(path, [column_index_1(string_kwarg.start_pos)])],
                categories=["redundancy"],
                sources=sources,
            )
            return

        if field.args:
            arg_index = FIELD_TYPE_STRING_INDEX_MAP.get(field.class_name, 0)
            if len(field.args) < arg_index + 1:
                return
            string_arg = field.args[arg_index]

            if string_arg.value == get_odoo_string(field.name):
                yield Issue(
                    "redundant_field_attribute",
                    f"Redundant implied field attribute `string` "
                    f'"{string_arg.value}"` for field "{field.name}". '
                    f"The same value will be computed by Odoo automatically.",
                    addon.manifest_path,
                    [Location(path, [column_index_1(string_arg.start_pos)])],
                    categories=["redundancy"],
                    sources=sources,
                )
示例#9
0
 def _get_issue_from_element(self, addon, filename, element, attr_names):
     yield Issue(
         "search_view_element_takes_no_attributes",
         f"`<search>` view element takes no attributes, "
         f"has: {', '.join(attr_names)}",
         addon.manifest_path,
         [Location(filename, [element.sourceline])],
         categories=["maintainability"],
     )
示例#10
0
 def on_xml_tree(self, xml_tree):
     for el in xml_tree.tree_node.xpath(XPATH_EXPR):
         yield Issue(
             "xml_operation_without_id",
             f"XML operation `<{el.tag}>` has no `id` attribute",
             xml_tree.addon.manifest_path,
             [Location(xml_tree.path, [el.sourceline])],
             categories=["maintainability"],
         )
示例#11
0
    def on_xml_record(self, xml_record):
        if xml_record.record_node.get("model") != "ir.ui.view":
            return

        # Only works on non-inherited views.
        if xml_record.record_node.xpath('./field[@name="inherit_id"]'):
            return

        # Does not work on QWeb templates.
        if xml_record.record_node.xpath(
                './field[@name="type" and text() = "qweb"]'):
            return

        arch = get_view_arch(xml_record.record_node)
        if arch is None:
            return

        view_el = next(c for c in arch.iterchildren()
                       if c.tag is not ET.Comment)

        # RelaxNG based validation in <= v10 is only possible for < 7.0 version views.
        if xml_record.addon.version < 11:
            if float(view_el.get("version", "7.0")) >= 7.0:
                return

        # `<form>` and `<kanban>` seem to not be supported in v11+.
        if view_el.tag in ("form",
                           "kanban") and xml_record.addon.version >= 11:
            return

        # `<gantt>` was moved somewhere in v13 (https://git.io/fjxGB).
        if view_el.tag == "gantt" and xml_record.addon.version >= 13:
            return

        # In case it is e.g. `xpath` element in view `arch` override.
        if view_el.tag not in VIEW_ELEMENT_VERSION_MAP[
                xml_record.addon.version]:
            return

        relaxng = _load_validator(view_el.tag, xml_record.addon.version)
        try:
            relaxng.assert_(view_el)
        except AssertionError:
            last_error = relaxng.error_log.last_error
            yield Issue(
                "view_relaxng_error",
                f'"{view_el.tag}" view does not match the RelaxNG schema: '
                f"{last_error.message}",
                xml_record.addon.manifest_path,
                [
                    Location(xml_record.path,
                             [(last_error.line, last_error.column + 1)])
                ],
                categories=["correctness"],
            )
示例#12
0
 def on_after(self, addon):
     for model_name, (filename, start_pos) in self._models.items():
         model_external_id = f"model_{model_name.replace('.', '_')}"
         if model_external_id not in self._access_rules:
             yield Issue(
                 "no_ir_model_access_record",
                 f'Model "{model_name}" has no `ir.model.access` records',
                 addon.manifest_path,
                 [Location(filename, [column_index_1(start_pos)])],
                 categories=["correctness", "security"],
             )
示例#13
0
 def on_addon(self, addon):
     if addon.version >= 10 and addon.manifest_path.path.name != "__manifest__.py":
         yield Issue(
             "deprecated_manifest_filename",
             "Starting with Odoo 10, addon manifest files should be named "
             '"__manifest__.py"',
             addon.manifest_path,
             [Location(addon.manifest_path.path)],
             categories=["deprecated"],
             sources=[odoo_commit_url("4339196e5231aa734a0154e2f4e88b2e54f27d48")],
         )
示例#14
0
    def on_xml_record(self, xml_record):
        if (xml_record.addon.version < 11
                or xml_record.record_node.get("model") != "ir.cron"):
            return

        record = xml_record.record_node
        data = collect_fields(record)
        code, code_line_no = data.get("code", (None, None))
        state, state_line_no = data.get("state", (None, None))
        if code and not state:
            yield Issue(
                "incorrect_cron_record",
                f"`ir.cron` record \"{record.attrib['id']}\" has `code` set, "
                f"but no `state` set - code will not be executed when the "
                f"cron job runs",
                xml_record.addon.manifest_path,
                [Location(xml_record.path, [code_line_no])],
                categories=["correctness"],
            )
        elif code and state != "code":
            yield Issue(
                "incorrect_cron_record",
                f"`ir.cron` record \"{record.attrib['id']}\" has `code` set, "
                f'but `state` is not "code" - code will not be executed '
                f"when the cron job runs",
                xml_record.addon.manifest_path,
                [
                    Location(xml_record.path, [code_line_no]),
                    Location(xml_record.path, [state_line_no]),
                ],
                categories=["correctness"],
            )
        elif not code and state == "code":
            yield Issue(
                "incorrect_cron_record",
                f"`ir.cron` record \"{record.attrib['id']}\" has `state` set "
                f'to "code", but `code` is not set',
                xml_record.addon.manifest_path,
                [Location(xml_record.path, [state_line_no])],
                categories=["correctness"],
            )
示例#15
0
    def on_csv_row(self, csv_row):
        model = csv_row.path.stem
        addon, path, row = csv_row.addon, csv_row.path, csv_row.row

        yield ExternalIDReference(addon, UNKNOWN, _model_record_id(model),
                                  "ir.model", Location(path))

        if path not in self._csv_external_id_fields:
            for field_name in row:
                if (field_name.split(":")[-1] == "id"
                        or field_name.split("/")[-1] == "id"):
                    self._csv_external_id_fields[path].add(field_name)

                # Add references to fields from the CSV file header.
                field_external_id = _field_record_id(
                    model,
                    (field_name[:-3] if field_name.endswith(
                        (":id", "/id")) else field_name),
                )
                yield ExternalIDReference(
                    addon,
                    UNKNOWN,
                    field_external_id,
                    "ir.model.fields",
                    Location(path, [1]),
                )

        for field_name in self._csv_external_id_fields[path]:
            # TODO: Add support for KNOWN_FIELD_MODELS.
            external_id = csv_row.row[field_name]
            if not external_id:
                continue
            addon_name, record_id = split_external_id(external_id)
            cls_ = ExternalID if field_name == "id" else ExternalIDReference
            yield cls_(
                csv_row.addon,
                addon_name,
                record_id,
                model if field_name == "id" else UNKNOWN,
                Location(csv_row.path, [csv_row.line_no]),
            )
示例#16
0
 def issue(module_import, import_name):
     yield Issue(
         "legacy_import",
         f"Legacy import `{import_name}`",
         python_module.addon.manifest_path,
         [
             Location(
                 python_module.path, [column_index_1(module_import.start_pos)]
             )
         ],
         categories=["deprecated"],
     )
示例#17
0
文件: permissions.py 项目: srekal/odd
 def on_addon_path(self, addon_path):
     path = addon_path.path
     if path.is_dir():
         current_permissions = path.stat().st_mode & 0o777
         if current_permissions != RECOMMENDED_DIRECTORY_PERMISSIONS:
             yield Issue(
                 "directory_permissions",
                 f"Directories should have {RECOMMENDED_DIRECTORY_PERMISSIONS:o} "
                 f"permissions (current: {current_permissions:o})",
                 addon_path.addon.manifest_path,
                 [Location(path)],
                 categories=["correctness"],
             )
示例#18
0
    def on_xml_tree(self, xml_tree):
        root_tags = ROOT_TAG_VERSION_MAP[xml_tree.addon.version]
        xpath_expr = "//data[%s]" % " and ".join(f"not(parent::{root_tag})"
                                                 for root_tag in root_tags)

        for data in xml_tree.tree_node.xpath(xpath_expr):
            yield Issue(
                "invalid_data_element_parent",
                f"Expected `<data>` element to be a direct child of one of "
                f"these elements: {', '.join(root_tags)}",
                xml_tree.addon.manifest_path,
                [Location(xml_tree.path, [data.sourceline])],
                categories=["correctness"],
            )
示例#19
0
def _ref(addon,
         filename,
         position,
         external_id,
         model,
         unknown_addon=False) -> ExternalIDReference:
    addon_name, record_id = split_external_id(external_id)
    return ExternalIDReference(
        addon,
        UNKNOWN if unknown_addon else addon_name,
        record_id,
        model,
        Location(filename, [position]),
    )
示例#20
0
    def on_xml_tree(self, xml_tree):
        relaxng = VERSION_RNG_MAP[xml_tree.addon.version]

        try:
            relaxng.assert_(xml_tree.tree_node)
        except AssertionError:
            last_error = relaxng.error_log.last_error
            yield Issue(
                "relaxng_error",
                f"XML file does not match Odoo RelaxNG schema: {last_error.message}",
                xml_tree.addon.manifest_path,
                [Location(xml_tree.path, [(last_error.line, last_error.column + 1)])],
                categories=["correctness"],
            )
示例#21
0
文件: common.py 项目: srekal/odd
def run_check_test(
    data_dir: pathlib.Path,
    check_name,
    manifest_path_parts,
    version,
    issues,
    extra_checks=(
        "addon_path_emitter",
        "addon_file_emitter",
        "xml_tree_emitter",
        "python_emitter",
        "external_id_emitter",
        "csv_row_emitter",
        "model_definition_emitter",
        "field_definition_emitter",
    ),
):
    manifest_path = ManifestPath(
        data_dir.joinpath(check_name, *manifest_path_parts))
    expected_issues = []
    for issue in issues:
        locations = []
        for path_parts, line_nos in issue.pop("locations", []):
            locations.append(
                Location(manifest_path.addon_path.joinpath(*path_parts),
                         line_nos))

        sources = []
        for source in issue.pop("sources", []):
            sources.append(yarl.URL(source))

        expected_issues.append(
            Issue(
                **{
                    "manifest_path": manifest_path,
                    "locations": locations,
                    "sources": sources,
                    **issue,
                }))

    checks_to_load = {check_name}
    if extra_checks:
        checks_to_load.update(extra_checks)

    actual_issues = list(
        check_addon(manifest_path, get_checks(checks_to_load),
                    version=version))

    assert expected_issues == actual_issues
示例#22
0
 def on_xml_tree(self, xml_tree):
     if xml_tree.addon.version < 9:
         return
     attributes = ", ".join(f'"{attr}"' for attr in ["class"])
     xpath = "|".join(f"/{main_tag}//attribute[@name=({attributes})]"
                      for main_tag in ("openerp", "odoo"))
     for el in xml_tree.tree_node.xpath(xpath):
         is_override = not any(a in el.attrib for a in ("add", "remove"))
         if is_override:
             yield Issue(
                 "attribute_override",
                 f"`<attribute>` overrides the `{el.get('name')}` attribute value, "
                 f'consider using `add="..."` or `remove="..."` instead of '
                 f"overriding",
                 xml_tree.addon.manifest_path,
                 [Location(xml_tree.path, [el.sourceline])],
                 categories=["correctness", "maintainability"],
             )
示例#23
0
 def on_python_module(self, python_module):
     if python_module.path.name not in MANIFEST_FILENAMES:
         return
     key_locations = None
     for check in ("active", "deprecated_xml", "unknown_keys"):
         for key, issue in getattr(self, f"_check_{check}")(
                 python_module.addon.manifest):
             if key_locations is None:
                 key_locations = self._get_key_locations(
                     python_module.module)
             yield Issue(
                 **{
                     "locations":
                     [Location(python_module.path, key_locations[key])],
                     "manifest_path":
                     python_module.addon.manifest_path,
                     **issue,
                 })
示例#24
0
    def on_python_module(self, python_module):
        if python_module.addon.version < 12 or not python_module.path.name.startswith(
                "test_"):
            return

        imports = set()
        for imp in get_imports(python_module.module):
            from_part = ".".join(imp.from_names)
            if from_part:
                imports.update((from_part, n) for n in imp.names)
            else:
                imports.update(imp.names)

        for classdef in python_module.module.iter_classdefs():
            bases = get_bases(classdef.get_super_arglist())
            is_test_case = (("unittest", "TestCase") in bases
                            and "unittest" in imports) or (
                                ("TestCase", ) in bases and
                                ("unittest", "TestCase") in imports)
            if not is_test_case:
                continue

            tagged = False
            for decorator in classdef.get_decorators():
                if extract_func_name(decorator)[-1] == "tagged":
                    tagged = True
                    break
            if not tagged:
                yield Issue(
                    "unittest_testcase_not_tagged",
                    f"`unittest.TestCase` subclass `{classdef.name.value}` is not "
                    f"decorated with `@tagged()`, it will not be picked up by Odoo "
                    f"test runner",
                    python_module.addon.manifest_path,
                    [
                        Location(python_module.path,
                                 [column_index_1(classdef.start_pos)])
                    ],
                    categories=["correctness"],
                    sources=[
                        odoo_commit_url(
                            "b356b190338e3ee032b9e3a7f670f76468965006")
                    ],
                )
示例#25
0
    def on_xml_tree(self, xml_tree):
        for template in xml_tree.tree_node.xpath("//template"):
            for el in template.xpath(
                    ".//*/@*[starts-with(name(), 't-attf-')]/.."):
                for name, value in el.attrib.items():
                    if not name.startswith("t-attf-"):
                        continue

                    is_format_string = ("{{" in value and "}}" in value) or (
                        "#{" in value and "}" in value)
                    if not is_format_string:
                        yield Issue(
                            "redundant_t_attf",
                            f"Element `<{el.tag}>` has a redundant `t-attf-$name` "
                            f"attribute `{name}`: {value}",
                            xml_tree.addon.manifest_path,
                            [Location(xml_tree.path, [el.sourceline])],
                            categories=["correctness", "performance"],
                        )
示例#26
0
 def on_xml_record(self, xml_record):
     record = xml_record.record_node
     record_fields = get_fields(record)
     for field_name, line_nos in record_fields.items():
         model, record_id = record.attrib["model"], record.get("id")
         if len(line_nos) > 1:
             yield Issue(
                 "duplicate_record_field",
                 f'`{model}` record "{record_id}" has duplicated values '
                 f'for field "{field_name}"' if record_id else
                 (f"`{model}` record has duplicated values "
                  f'for field "{field_name}"'),
                 xml_record.addon.manifest_path,
                 [
                     Location(xml_record.path, [line_no])
                     for line_no in line_nos
                 ],
                 categories=["correctness", "maintainability"],
             )
示例#27
0
    def on_csv_row(self, csv_row):
        if csv_row.path.name.lower() != "ir.model.access.csv" or csv_row.row.get(
            "group_id:id"
        ):
            return

        permissions = [
            perm
            for perm in ("create", "read", "write", "unlink")
            if csv_row.row.get(f"perm_{perm}") == "1"
        ]
        yield Issue(
            "ir_model_access_without_group",
            f"`ir.model.access` record ({csv_row.row['id']}) allows the "
            f"following operations to users without group: "
            f"{', '.join(permissions)}",
            csv_row.addon.manifest_path,
            [Location(csv_row.path, [csv_row.line_no])],
            categories=["security", "correctness"],
        )
示例#28
0
def _get_issues(xml_record, element, classes):
    classes = set((classes or "").split())
    for old_class, new_class in CLASS_MAP.items():
        if old_class in classes:
            yield Issue(
                "deprecated_button_class",
                f"`{old_class}` button class is deprecated since v12.0 "
                f"in favor of `{new_class}`",
                xml_record.addon.manifest_path,
                [Location(xml_record.path, [element.sourceline])],
                categories=["maintainability", "deprecated"],
                sources=[
                    odoo_source_url(
                        "1e5fbb8e5bf0e0458d83a399b2b59d03a601e86a",
                        "addons/web/static/src/js/core/dom.js",
                        start=340,
                        end=345,
                    )
                ],
            )
示例#29
0
    def on_field_definition(self, field: FieldDefinition):
        addon = field.model.addon
        if addon.version < 12:
            return

        for kwarg in field.kwargs:
            if kwarg.name == "track_visibility" and kwarg.value == "always":
                yield Issue(
                    "track_visibility_always_deprecated",
                    'Field `track_visibility` attribute value "always" is '
                    "deprecated since version 12.0",
                    addon.manifest_path,
                    [
                        Location(field.model.path,
                                 [column_index_1(kwarg.start_pos)])
                    ],
                    categories=["deprecated"],
                    sources=[
                        odoo_commit_url(
                            "c99de4551583e801ecc6669ac456c4f7e2eef1da")
                    ],
                )
示例#30
0
    def on_model_definition(self, model):
        if get_model_type(model.node) == UNKNOWN:
            return
        model_name = model.params.get("_name")
        if model.params.get("_inherit") or not model_name:
            return

        if not model.params.get("_description"):
            yield Issue(
                "no_model_description",
                f'Model "{model_name}" has no `_description` set',
                model.addon.manifest_path,
                [Location(model.path, [column_index_1(model.node.start_pos)])],
                categories=(["future-warning"]
                            if model.addon.version < 12 else ["correctness"]),
                sources=[
                    odoo_source_url(
                        "ff9ddfdacc9581361b555fd5f69e2da61800acad",
                        "odoo/models.py",
                        start=529,
                    )
                ],
            )