Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openapi_doc_viewer/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
waiting the bot :)
1 change: 1 addition & 0 deletions openapi_doc_viewer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import controllers
15 changes: 15 additions & 0 deletions openapi_doc_viewer/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2026 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{
"name": "OpenAPI docs viewer",
"summary": "Browse all the OpenAPI docs you can see in one page.",
"version": "18.0.1.0.0",
"license": "LGPL-3",
"development_status": "Alpha",
"author": "Camptocamp,Odoo Community Association (OCA)",
"maintainers": ["simahawk"],
"website": "https://github.com/OCA/web-api",
"depends": ["openapi_doc_base"],
"data": ["views/menus.xml"],
}
1 change: 1 addition & 0 deletions openapi_doc_viewer/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
36 changes: 36 additions & 0 deletions openapi_doc_viewer/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2026 Camptocamp SA
# @author: Simone Orsi <simone.orsi@camptocamp.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import http
from odoo.http import request

from odoo.addons.openapi_doc_base.http import OpenApiDocsControllerMixin

DOCS_ROUTE = "/openapi/docs"


class OpenApiDocsController(http.Controller, OpenApiDocsControllerMixin):
"""One page for all the docs sources the user can see."""

def _openapi_all_sources(self):
"""All the registered sources: the mixin keeps only the visible ones."""
return request.env["ir.http"].sudo()._openapi_doc_sources_for()

@http.route(DOCS_ROUTE, type="http", auth="user", methods=["GET"])
def openapi_docs(self, category=None, app=None):
"""Swagger UI w/ category and app selectors."""
return self._openapi_index_response(
self._openapi_all_sources(),
DOCS_ROUTE,
lambda source: f"{DOCS_ROUTE}/{source.key}.json",
category=category,
app=app,
)

@http.route(
f"{DOCS_ROUTE}/<string:key>.json", type="http", auth="user", methods=["GET"]
)
def openapi_doc(self, key):
"""OpenAPI document of one source."""
return self._openapi_document_response(self._openapi_all_sources(), key)
3 changes: 3 additions & 0 deletions openapi_doc_viewer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
1 change: 1 addition & 0 deletions openapi_doc_viewer/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Simone Orsi \<<simone.orsi@camptocamp.com>\>
9 changes: 9 additions & 0 deletions openapi_doc_viewer/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Viewer for OpenAPI docs: one page to browse all the docs the current user can see:
`/openapi/docs` (also in *Settings > Technical > API docs*).

The page lists the sources registered via `openapi_doc_base`
(documented controllers, configurable endpoints, apps...)
with links to filter them by category and app.

Each source is shown only to the users in its groups
(and in the groups of its category): see `openapi_doc_base`.
10 changes: 10 additions & 0 deletions openapi_doc_viewer/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Give the *API docs: viewer* group to the users that need to read the docs
(admins have it by default). Sources or categories protected by other groups
are shown only to their members.

Open `/openapi/docs` and use the links at the top to filter by category and app.
Each document is also available as JSON at `/openapi/docs/<source key>.json`.

You don't need this module to expose docs at your own URL
(eg: public docs for an app): define your own controller
w/ `OpenApiDocsControllerMixin` from `openapi_doc_base`.
Binary file added openapi_doc_viewer/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions openapi_doc_viewer/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_http
88 changes: 88 additions & 0 deletions openapi_doc_viewer/tests/test_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2026 Camptocamp SA
# @author: Simone Orsi <simone.orsi@camptocamp.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from unittest import mock

from odoo import http
from odoo.tests.common import HttpCase, new_test_user, tagged

from odoo.addons.openapi_doc_base.sources import DocSource


def _doc(title):
return lambda env: {"openapi": "3.0.3", "info": {"title": title}, "paths": {}}


EXTRA_SOURCES = [
DocSource("test.a1", "A1", _doc("A1"), category="cat_a", app="app_1"),
DocSource(
"test.s1", "S1", _doc("S1"), category="cat_a", groups=("base.group_system",)
),
]


class OpenApiDocTestController(http.Controller):
@http.route(
"/openapi_doc_test/documented",
type="http",
auth="public",
methods=["GET"],
api_doc={"group": "test_openapi_doc", "summary": "Documented controller"},
)
def documented(self):
return ""


@tagged("-at_install", "post_install")
class TestHttp(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.registry.clear_cache("routing")
new_test_user(
cls.env,
login="doc_viewer",
password="doc_viewer",
groups="base.group_user,openapi_doc_base.group_openapi_doc_viewer",
)
ir_http_cls = type(cls.env["ir.http"])
orig = ir_http_cls._openapi_doc_sources

def _sources(self):
return orig(self) + EXTRA_SOURCES

patcher = mock.patch.object(ir_http_cls, "_openapi_doc_sources", _sources)
patcher.start()
cls.addClassCleanup(patcher.stop)

def test_page(self):
"""The page lists the sources the user may see, filterable by category."""
self.authenticate("doc_viewer", "doc_viewer")
response = self.url_open("/openapi/docs")
self.assertEqual(response.status_code, 200)
# Documented controllers are collected automatically
self.assertIn("/openapi/docs/routes.test_openapi_doc.json", response.text)
self.assertIn("/openapi/docs/test.a1.json", response.text)
self.assertNotIn("test.s1.json", response.text)
self.assertIn("category=cat_a", response.text)
response = self.url_open("/openapi/docs?category=cat_a")
self.assertNotIn("routes.test_openapi_doc.json", response.text)

def test_document(self):
"""A source's document is served only to users allowed to see it."""
self.authenticate("doc_viewer", "doc_viewer")
response = self.url_open("/openapi/docs/routes.test_openapi_doc.json")
self.assertIn("/openapi_doc_test/documented", response.json()["paths"])
response = self.url_open("/openapi/docs/test.s1.json")
self.assertEqual(response.status_code, 404)
self.authenticate("admin", "admin")
response = self.url_open("/openapi/docs/test.s1.json")
self.assertEqual(response.json()["info"]["title"], "S1")

def test_menu(self):
"""The docs menu is restricted to the viewer group."""
menu = self.env.ref("openapi_doc_viewer.menu_openapi_docs")
self.assertEqual(
menu.groups_id, self.env.ref("openapi_doc_base.group_openapi_doc_viewer")
)
18 changes: 18 additions & 0 deletions openapi_doc_viewer/views/menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2026 Camptocamp SA
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<odoo>
<record id="action_openapi_docs" model="ir.actions.act_url">
<field name="name">API docs</field>
<field name="url">/openapi/docs</field>
<field name="target">new</field>
</record>
<menuitem
id="menu_openapi_docs"
name="API docs"
parent="base.menu_custom"
action="action_openapi_docs"
groups="openapi_doc_base.group_openapi_doc_viewer"
sequence="100"
/>
</odoo>
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
odoo-addon-openapi_doc_base @ git+https://github.com/OCA/web-api.git@refs/pull/181/head#subdirectory=openapi_doc_base
Loading