From 38faac60b4bf03a69b6138006fe8b3e4c6031a0f Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 15:09:50 +0200 Subject: [PATCH 01/16] docs: init djangocms-rest documentation --- docs/.gitignore | 15 + docs/Makefile | 50 ++ docs/README.md | 149 ++++ docs/changelog.rst | 145 ++++ docs/conf.py | 264 ++++++ docs/contributing.rst | 15 + docs/how-to/01-use-multi-site.rst | 162 ++++ docs/how-to/02-plugin-creation.rst | 151 ++++ docs/how-to/index.rst | 36 + docs/index.rst | 66 ++ docs/live.sh | 21 + docs/poetry.lock | 960 +++++++++++++++++++++ docs/pyproject.toml | 32 + docs/reference/breadcrumbs.rst | 211 +++++ docs/reference/index.rst | 23 + docs/reference/languages.rst | 58 ++ docs/reference/menu.rst | 233 +++++ docs/reference/pages.rst | 287 ++++++ docs/reference/placeholders.rst | 94 ++ docs/reference/plugins.rst | 69 ++ docs/reference/submenu.rst | 377 ++++++++ docs/tutorial/01-quickstart.rst | 93 ++ docs/tutorial/02-installation.rst | 404 +++++++++ docs/tutorial/03-openapi-documentation.rst | 107 +++ docs/tutorial/index.rst | 14 + 25 files changed, 4036 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/contributing.rst create mode 100644 docs/how-to/01-use-multi-site.rst create mode 100644 docs/how-to/02-plugin-creation.rst create mode 100644 docs/how-to/index.rst create mode 100644 docs/index.rst create mode 100755 docs/live.sh create mode 100644 docs/poetry.lock create mode 100644 docs/pyproject.toml create mode 100644 docs/reference/breadcrumbs.rst create mode 100644 docs/reference/index.rst create mode 100644 docs/reference/languages.rst create mode 100644 docs/reference/menu.rst create mode 100644 docs/reference/pages.rst create mode 100644 docs/reference/placeholders.rst create mode 100644 docs/reference/plugins.rst create mode 100644 docs/reference/submenu.rst create mode 100644 docs/tutorial/01-quickstart.rst create mode 100644 docs/tutorial/02-installation.rst create mode 100644 docs/tutorial/03-openapi-documentation.rst create mode 100644 docs/tutorial/index.rst diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..c798a8a --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,15 @@ +# Sphinx build directory +_build/ + +# Python cache +__pycache__/ +*.pyc +*.pyo + +# IDE files +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..67a089d --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,50 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile clean serve open dev watch live + +# Custom targets for convenience +clean: + rm -rf $(BUILDDIR)/* + +serve: + @echo "Starting documentation server at http://localhost:8000" + @python -m http.server 8000 --directory $(BUILDDIR)/html + +open: + @echo "Opening documentation in browser..." + @open $(BUILDDIR)/html/index.html + +# Development targets +dev: + @echo "Building documentation in development mode..." + @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) -W --keep-going + +watch: + @echo "Watching for changes and rebuilding..." + @while true; do \ + inotifywait -r -e modify,create,delete .; \ + $(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS); \ + done + +live: + @echo "Starting live documentation server with auto-reload..." + @echo "Server will be available at http://localhost:8000" + @echo "Press Ctrl+C to stop" + @sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)/html" --port 8000 --host 0.0.0.0 --open-browser + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..fcbd937 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,149 @@ +# djangocms-rest Documentation + +This directory contains the documentation for djangocms-rest, built with Sphinx. + +## Setup + +### Using Poetry (Recommended) + +```bash +# From project root, install all dependencies including docs +poetry install --with dev + +# Navigate to the docs directory +cd docs + +# Build the documentation +make html +``` + +### Alternative: Using pip + +```bash +# From project root, install development dependencies +pip install -e ".[dev]" + +# Navigate to the docs directory +cd docs + +# Build the documentation +make html +``` + +## Building Documentation + +### Build HTML Documentation + +```bash +# Build the documentation +make html + +# Or use sphinx-build directly +sphinx-build -b html . _build/html +``` + +### View Documentation Locally + +```bash +# Build and serve documentation +make serve + +# Or build and open in browser +make html +make open +``` + +### Development Mode with Live Reload + +```bash +# Start live documentation server with auto-reload (recommended) +sh live.sh + +# Or run sphinx-autobuild directly +poetry run sphinx-autobuild . _build/html --port 8000 --host 0.0.0.0 --open-browser + +# Build with warnings treated as errors +make dev + +# Watch for changes and rebuild automatically +make watch +``` + +## Available Make Targets + +- `make html` - Build HTML documentation +- `make clean` - Clean build directory +- `make serve` - Serve documentation on http://localhost:8000 +- `make open` - Open documentation in browser +- `make live` - Start live server with auto-reload (recommended for development) +- `make dev` - Build in development mode with warnings +- `make watch` - Watch for changes and rebuild automatically +- `make help` - Show all available targets + +## Documentation Structure + +The documentation is organized into logical sections: + +### Tutorial +- `tutorial/01-quickstart.rst` - Quick start guide +- `tutorial/02-installation.rst` - Installation guide +- `tutorial/03-openapi-documentation.rst` - OpenAPI documentation guide + +### How-to Guides +- `how-to/01-use-multi-site.rst` - Multi-site configuration guide +- `how-to/02-plugin-creation.rst` - Plugin creation guide + +### Reference +- `reference/index.rst` - API overview +- `reference/pages.rst` - Pages API reference +- `reference/languages.rst` - Languages API reference +- `reference/placeholders.rst` - Placeholders API reference +- `reference/plugins.rst` - Plugins API reference +- `reference/menu.rst` - Menu API reference +- `reference/breadcrumbs.rst` - Breadcrumbs API reference +- `reference/submenu.rst` - Submenu API reference + +### Additional +- `contributing.rst` - Contributing guide +- `changelog.rst` - Version history + +## Configuration + +The documentation is configured in `conf.py`. Key settings include: + +- **Theme**: Furo theme (modern, clean design with sidebar navigation) +- **Extensions**: autodoc, intersphinx, napoleon, sphinx-tabs, sphinx-copybutton, etc. +- **Intersphinx**: Links to Python, Django, DRF, and django CMS documentation +- **Mock imports**: Django and django CMS modules are mocked for autodoc +- **GitHub Integration**: Source repository links and GitHub icon in footer + +## Contributing to Documentation + +1. Make changes to the `.rst` files +2. Build the documentation to check for errors: `make html` +3. Test locally: `make serve` +4. Submit a pull request + +## Troubleshooting + +### Common Issues + +**ImportError: No module named 'django'** +- This is expected when building documentation without Django installed +- The `conf.py` file includes mock imports for Django modules + +**Sphinx build errors** +- Check that all dependencies are installed: `poetry install --with dev` or `pip install -e ".[dev]"` +- Ensure you're in the `docs` directory when running commands +- Check the build output for specific error messages + +**Missing intersphinx links** +- The documentation links to external documentation (Python, Django, etc.) +- These links may not work offline +- This is normal behavior + +### Getting Help + +- Check the [Sphinx documentation](https://www.sphinx-doc.org/) +- Review the [Read the Docs theme documentation](https://sphinx-rtd-theme.readthedocs.io/) +- Open an issue on the project repository \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..a92d6d7 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,145 @@ +========= +Changelog +========= + +.. changelog:: + :towncrier: ../ + +0.8.1 (2025-08-29) +================== + +Bugfixes +-------- + +* fix: Package data not included in wheel (#48) + +0.8.0 (2025-08-28) +================== + +Features +-------- + +* feat: Add comprehensive documentation for django CMS REST +* feat: Add menu endpoints (#49) +* feat: Add RESTRenderer (#42) +* feat: frontend URL handling with absolute URL generation. (#36) +* feat: extend page serializer with additional page fields (#35) +* feat: add public language support (#27) +* feat: update code to support Django CMS 5 (#29) +* feat: Refactoring for improved Open API & typing support (allows: automatic schema generation) (#20) + +Bugfixes +-------- + +* fix: Enable caching for placeholder serialization and rendering. (#31) +* fix: open api schema validation (#34) +* fix: Update test requirements (#25) +* fix: Update github actions and readme (#12) + +Documentation +------------- + +* docs: Update documentation references from "django CMS REST" to "djangocms-rest" +* docs: Update readme (#39) +* docs: update docs (#26) +* docs: initial project outline + +0.1.0 (2024-05-24) +================== + +Features +-------- + +* Initial commit with basic functionality for placeholders +* Restructure api +* Optionally include HTML in placeholder responses +* Respect page viewing permissions +* Add first test +* Code refactoring +* Fix: page detail by path +* View naming convention cms-model-list/detail +* Update language list url name +* Update rendering test + +Bugfixes +-------- + +* Fix: Language endpoint offers pages list +* Fix test actions +* Fix ruff issues +* Update action to upload codecov +* Update .coveragerc + +Documentation +------------- + +* Docs +* Update readme +* Update README.md + +Development +----------- + +* Bump codecov/codecov-action from 4.0.1 to 4.4.1 +* Bump codecov/codecov-action from 4.4.1 to 4.5.0 +* Bump codecov/codecov-action from 4.5.0 to 4.6.0 +* Bump codecov/codecov-action from 4.6.0 to 5.0.2 +* Bump codecov/codecov-action from 5.0.2 to 5.3.1 +* Bump codecov/codecov-action from 5.3.1 to 5.4.0 +* Bump codecov/codecov-action from 5.4.0 to 5.4.2 +* Bump codecov/codecov-action from 5.4.2 to 5.4.3 +* Bump codecov/codecov-action from 5.4.3 to 5.5.0 +* Bump codecov/codecov-action from 5.5.0 to 5.5.1 +* change supported version of `django-cms` (#22) +* fix: remove Node.js setup and frontend build from workflow (#28) +* chore: Add django CMS 4.1 support, simplify preview views/ruff format (#40) +* chore(deps): bump actions/checkout from 4 to 5 (#43) +* chore(deps): bump actions/setup-python from 5 to 6 (#52) +* chore(deps): bump codecov/codecov-action from 5.5.0 to 5.5.1 (#51) +* chore: Update documentation configuration and checks +* chore: Bump version from 0.1.0a to 0.8.0 (#46) +* chore: Update readme (#47) +* fix: pyproject.toml +* update pyproj.toml +* Upodate version number +* Update test.yml + +Recent Development (2025-10-18) +============================== + +Documentation +------------- + +* docs: revise contributing guidelines to emphasize community involvement and streamline setup instructions +* docs: refine multi-site guide and installation documentation, enhance language endpoint descriptions, and add planned guides section +* docs: enhance multi-site and quickstart guides with CORS clarification and updated API testing instructions +* docs: update endpoint headings for consistency and enhance multi-site installation instructions +* docs: standardize API section headings and update endpoint descriptions for clarity +* docs: update API documentation to reflect port change to 8080 for local testing +* docs: update documentation structure and content, including new quick start guide and plugin creation instructions +* docs: update multi-site guide with detailed setup instructions and Vue.js example +* docs: enhance multi-site usage guide with Vue.js example and additional resources +* docs: update API endpoint URLs in documentation to use port 8080 +* docs: imrove api references +* docs: fix typo in installation instructions +* docs: simplify installation instructions +* docs: improved installation instructions, and minor doc fixes +* docs: Enhance index documentation with motivation and key features +* docs: Update and expand API documentation for various endpoints +* docs: Add `preview` query parameter to API documentation for pages and languages +* docs: Refactor API documentation for languages and pages endpoints + +Features +-------- + +* feat: add OpenAPI support for "preview" query parameter (#53) +* feat: add OpenAPI schema decorator for `MenuView` and include `namespace` in `NavigationNodeSerializer` +* feat: add site middleware (#50) + +Bugfixes +-------- + +* fix: update `child` field in `NavigationNodeListSerializer` to accept multiple instances +* fix: wrap menu serializer data with return_key in `MenuView` response +* fix: exclude method_schema_decorator from test coverage +* fix: mark method_schema_decorator and related lines as uncovered \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..577335d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,264 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + +# -- Project information ----------------------------------------------------- + +project = "djangocms-rest" +copyright = "2024, Django CMS Association and contributors" +author = "Django CMS Association and contributors" + +# The full version, including alpha/beta/rc tags +release = "0.1.0" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", + "sphinx_copybutton", + "sphinx_tabs.tabs", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "default" +pygments_dark_style = "dracula" + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "furo" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + "source_repository": "https://github.com/django-cms/djangocms-rest", + "source_branch": "main", + "source_directory": "/docs/", + "footer_icons": [ + { + "name": "GitHub", + "url": "https://github.com/django-cms/djangocms-rest", + "html": "", + "class": "fa-brands fa-solid fa-github fa-2x", + }, + ], + "light_css_variables": { + "color-code-background": "#f8f9fa", + "color-code-foreground": "#333", + "font-stack": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif", + "font-stack--monospace": "'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', 'Droid Sans Mono', 'Consolas', 'Monaco', 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace", + }, + "dark_css_variables": { + "color-code-background": "#2d3748", + "color-code-foreground": "#e2e8f0", + }, +} + + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Add external CSS files (Font Awesome for GitHub icon) +html_css_files = [ + "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/fontawesome.min.css", + "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/solid.min.css", + "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/brands.min.css", +] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = "djangocms-rest" + +# A shorter title for the navigation bar. Default is the same as html_title. +html_short_title = "djangocms-rest" + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = '_static/favicon.ico' + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "djangocmsrestdoc" + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "djangocmsrest.tex", + "djangocms-rest Documentation", + "Django CMS Association and contributors", + "manual", + ), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "djangocmsrest", "djangocms-rest Documentation", [author], 1)] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "djangocmsrest", + "djangocms-rest Documentation", + author, + "djangocmsrest", + "API endpoints for django CMS", + "Miscellaneous", + ), +] + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ["search.html"] + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "django": ("https://docs.djangoproject.com/en/stable/", None), +} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + +# -- Options for autodoc extension ------------------------------------------- + +# Automatically extract typehints when specified and place them in +# descriptions of the relevant function/method. +autodoc_typehints = "description" + +# Don't show typehints in the signature +autodoc_typehints_format = "short" + +# Mock imports for autodoc +autodoc_mock_imports = [ + "django", + "djangorestframework", + "cms", + "djangocms_link", + "djangocms_text", +] + +# -- Options for copybutton extension ---------------------------------------- + +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True + +# -- Options for better code highlighting ------------------------------------ + +# Ensure code blocks are properly highlighted +highlight_language = "python3" diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..d3c6eca --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,15 @@ +Contributing +============ + +django CMS and djangocms-rest are open-source projects, and rely on its community of users to evolve. Every contribution, however small, is valued. + +As an open source project, anyone is welcome to contribute in whatever form they are able, which can include taking part in discussions, filing bug reports, proposing improvements, contributing code or documentation, and testing the software. + +Get involved +------------ + +*This project follows the same contributing guidelines as django CMS.* + +- Inform you about the contributing `guidelines for django CMS `_ +- Join the `django CMS community on Discord `_ +- Contribute to the project by opening a pull request or an issue on `GitHub `_ \ No newline at end of file diff --git a/docs/how-to/01-use-multi-site.rst b/docs/how-to/01-use-multi-site.rst new file mode 100644 index 0000000..580e772 --- /dev/null +++ b/docs/how-to/01-use-multi-site.rst @@ -0,0 +1,162 @@ +Multi-Site setup with single CMS instance +========================================= + +In this short guide, we will show you how to use the multi-site functionality in your frontend app. + +We will use ``vue.js`` to fetch data from a single django CMS instance with multiple sites. This implemenation +guide can easily be adapted to other frontend frameworks. + +.. warning:: + This guide assumes you have a running Django CMS project with multiple sites. + If you haven't configured django CMS for multi-site yet, please follow the `Multi-Site Support <../tutorial/02-installation.html#multi-site-support>`_ guide. + + + +Setup Django CMS for Multi-Site +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +1. Start your django CMS project + +.. code-block:: bash + + python manage.py runserver localhost:8080 + + +2. Adjust the primary site details in django admin for site A (Site ID 1) +3. Create a new site in django admin for site B (Site ID 2) +4. Create a nested pages structure for site A and site B using django CMS page tree admin. +5. Before building the frontend we want to make sure the page tree is working and returned as expected. + + +.. code-block:: bash + + # Fetch the page tree for site A + curl -v -H "X-Site-ID: 1" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + http://localhost:8080/api/en/pages-tree/ + +**Response** + +You should get your page tree for each site as a response described in the :doc:`../reference/pages` reference. + + +.. hint:: + Alternatively you can use swagger (see :doc:`../tutorial/02-installation`) to test the API endpoints or a app like `Bruno `_ + + +CMS Reference +~~~~~~~~~~~~~ + +- `Django CMS User Docs - Pages `_ + + + +Enable CORS +~~~~~~~~~~~ + +This works fine from console, but for browser requests we have to ensure that ``CORS`` (Cross-Origin Resource Sharing) is configured correctly. +See the `CORS Support <../tutorial/02-installation.html#cors-support>`_ guide for more information. + + + + + +Setup Vue.js Project +~~~~~~~~~~~~~~~~~~~~ + +Before continuing, you should set up a basic Vue.js project. + +- `Vue.js Quick Start `_ + +.. note:: + + We use TypeScript for this example. Make sure to enable it in your Vue.js project. + + ✔ Add TypeScript? … **Yes** + + +Now we will create a simple Vue.js project to fetch the page tree using the ``X-Site-ID`` request header: + +Replace the content of ``App.vue`` with the following code: + +.. code-block:: vue + + + + + + +Testing +~~~~~~~ + +Run your Vue.js project: + +.. code-block:: bash + + npm run dev + + +Visit `http://localhost:5173/ `_ in your browser, assuming you are using the default port for Vue.js. + +You can now click the ``"Fetch Page Tree"`` button to fetch the page tree for the selected site. + +.. admonition:: Success + + You should see the page tree for the selected site in the browser. + See the :doc:`../reference/pages` documentation for the expected response. + + +.. error:: + + if you get error you likely forgot to set the ``X-Site-ID`` header as allowed in the CORS settings or the domain or port is not allowed in the CORS settings. + See the :doc:`../tutorial/02-installation` guide for more information. + +Start Building +~~~~~~~~~~~~~~ + +When you are able to fetch the page tree for each site you can start building your frontend app. + +- Configure Django CMS templates with varous placeholders options +- Define and customize plugins according to your needs +- Add authentication to your frontend app, which allows content preview in the frontend app diff --git a/docs/how-to/02-plugin-creation.rst b/docs/how-to/02-plugin-creation.rst new file mode 100644 index 0000000..27b3bfd --- /dev/null +++ b/docs/how-to/02-plugin-creation.rst @@ -0,0 +1,151 @@ +Plugin Creation & Serialization +================================ + +DjangoCMS REST provides basic support for all CMS plugins. This means that all plugins are automatically serialized into JSON data, without the need for any additional configuration. + +For most simple use cases, this is all you need. However, when used in relation to other models, you might want to create a custom serializer. + +.. note:: + This guide assumes you have a running Django CMS project and a basic understanding of Django CMS plugins. If you are not familiar with them, please refer to the `Django CMS documentation `_. + +Automatic Serialization +----------------------- + +You can use the ``GenericPluginSerializer`` whenever no further application relationship is required. No additional configuration is required — simply create Django CMS plugins according to the documentation. + +**Requirements:** + +- No model fields needed +- Fields are directly available as model fields +- Fields are standard Django model fields + +**Limitations:** + +- Foreign keys are automatically resolved but not serialized. + + +Use Cases +~~~~~~~~~ + +- Layout plugins like Grid, Row, Column, etc. +- Content plugins like Text, Image, Video, etc. +- Content-Group plugins like Hero, CTAs, etc. + +You can also nest plugins inside other plugins as you would normally do in Django CMS. + +Example +~~~~~~~ + +Create your plugin as you would normally do in Django CMS. + +Even though we finally will render the plugin decoupled in the frontend, likely using json data in combination with a frontend framework (Vue.js, React, etc.). +We still define a template for the plugin. This allows us to display the plugin in the django CMS backend for editing purposes and also to pass rendered HTML to the frontend. + +Place your plugins in ``cms_plugins.py.`` For our example, include the following code: + +.. code-block:: python + + # A simple example of a plugin that renders a static template. + # No additional configuration is required for serialization. + + from cms.plugin_base import CMSPluginBase + from cms.plugin_pool import plugin_pool + from cms.models.pluginmodel import CMSPlugin + from django.utils.translation import gettext_lazy as _ + + @plugin_pool.register_plugin + class HelloPlugin(CMSPluginBase): + model = CMSPlugin + render_template = "hello_world.html" # Adjust the path to your template. +.. + +Add the following into the root template directory in a file called ``hello_world.html``: + +.. code-block:: html+django + +

Hello World!

+

This is a simple example of a plugin that renders a static template.

+.. + +Now add this plugin to a placeholder in a page. + +Depending on your projects configuration, you might have to configure placeholders in the ``CMS_PLACEHOLDER_CONF`` setting. See the `Django CMS documentation `_ for more information. + +After adding the plugin to a placeholder, you can retrieve the content of the placeholder using the :doc:`Pages <../reference/pages>` and :doc:`Placeholders <../reference/placeholders>` endpoints. + +.. note:: + We first have to retrieve the page object and then the associated placeholder objects. This is necessary because django CMS placeholders are linked to the page object, not part of the page object. This allows to use preview mode and versioning. + + +.. code-block:: bash + + # Get page details for a specific page + #replace language code and the path with your page path. + curl -X GET "http://localhost:8080/api/en/pages/my-page-with-hello-world-plugin/" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ +.. + +**Response from the pages endpoint:** + +.. code-block:: json + + "...", + "placeholders": [ + { + "content_type_id": 5, + "object_id": 9, + "slot": "content", + "details": "http://localhost:8080/api/en/placeholders/5/9/content/" + } + ], + "...", +.. + +.. code-block:: bash + + # Get placeholder content for a specific placeholder + # replace query with parameters from the response of the pages endpoint. + curl -X GET "http://localhost:8080/api/en/placeholders/5/9/content/?html=1" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ +.. + +.. note:: + ?html=1 will render the plugin as HTML. + + +**Response from the placeholders endpoint:** + +.. code-block:: json + + { + "slot": "content", + "label": "Content", + "language": "en", + "content": [ + { + "plugin_type": "HelloPlugin" + } + ], + "html": "

Hello World!

\n

This is a simple example of a plugin that renders a static template.

" + } + +.. + +As you can see, the content is serialized as a list of plugins. Each plugin has a ``plugin_type`` that helps identify and render the plugin correctly on the frontend. + +In the above example, we would simply render the HTML in the frontend. + +You can retrieve all plugin details using the :doc:`Plugins <../reference/plugins>` endpoint. + +.. hint:: + You can setup a vue.js frontend application to handle the rendering of json data. Follow the guide `Setup Vue.js Project <01-use-multi-site.html#setup-vue-js-project>`_ to get started. + + + +Custom Serialization +--------------------- + +...coming soon... + diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst new file mode 100644 index 0000000..9480e21 --- /dev/null +++ b/docs/how-to/index.rst @@ -0,0 +1,36 @@ +How-to Guides +============= + +Practical guides for implementing specific features with djangocms-rest. + + +How-to Guides +------------- + +.. toctree:: + :maxdepth: 1 + + 01-use-multi-site + 02-plugin-creation + +Planned Guides +-------------- + +All of this features can already be implemented using the API, but we plan to create dedicated guides to make the djangocms-rest more accessible for frontend development. + +- Render placeholder content in web frontend applications +- Page routing in web frontend applications +- Integrate browsable frontend pages via iFrame into django CMS +- Preview and Edit functionality for authenticated users in django CMS +- Apphook use in frontend applications + + +Contribute +---------- + +We are always looking for contributions to improve the documentation. If you are working on an implementation scenario. Plese share your experience and create a small implementation guide for others to follow. + +- **GitHub Repository**: `djangocms-rest `_ + + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..60a09ee --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,66 @@ +djangocms-rest +============================ + +.. image:: https://img.shields.io/badge/python-3.9+-blue.svg + :target: https://www.python.org/downloads/ + :alt: Python version + +.. image:: https://img.shields.io/badge/Django-4.2+-green.svg + :target: https://www.Djangoproject.com/ + :alt: Django version + +.. image:: https://img.shields.io/badge/Django--cms-5.0+-orange.svg + :target: https://www.Django-cms.org/ + :alt: Django CMS version + +.. image:: https://img.shields.io/badge/license-BSD%203--Clause-blue.svg + :target: https://opensource.org/licenses/BSD-3-Clause + :alt: License + +*Headless content delivery with Django CMS for modern, typed frontend apps.* + +djangocms-rest lets you use `Django CMS `_ as a headless backend for modern, typed frontend applications, while preserving the intuitive Django CMS editing interface. This makes it possible to build Django apps that combine CMS-driven content with decoupled frontend frameworks. + +djangocms-rest exposes Django CMS content through a read-only REST API. It is based on the `Django REST framework `_ and provides OpenAPI 3 schema generation and a browsable API via `DRF Spectacular `_. + + +Motivation +---------- +Web development is increasingly adopting decoupled front ends for greater scalability, multi-channel content and team flexibility. +**djangocms-rest** provides a solution to use Django CMS also as a headless backend. + + +👉 **You already use Django CMS? No need for yet another tech stack.** + +Key Features +------------ + +* **Easy integration** — Integrates effortlessly into existing Django CMS projects. +* **REST API** — DRF-based API exposing Django CMS content for SPAs, static sites, and mobile apps. +* **Typed Endpoints** — Auto-generate OpenAPI schemas for page data and plugin content. +* **Plugin Serialization** — Basic support for all CMS plugins, easily extendable for custom needs. +* **Multi-site Support** — Serve multiple websites from a single instance with isolated API responses. +* **Multi-language Content** — Use the robust i18n integartion of Django CMS in your frontend. +* **Preview & Draft Access** — Fetch unpublished or draft content in your frontend for editing previews. +* **Permissions & Authentication** — Uses DRF- and Django-permissions for secure access control. +* **Menus & Breadcrumbs** — Exposes the built-in navigation handlers from Django CMS. +* **Caching & Performance** — Works with Django cache backends like Redis and Memcached. + + +Getting Started +---------------- + +Follow our step-by-step tutorial to set up djangocms-rest in your project. + +- Use the :doc:`Quick Start tutorial ` to get the basics up and running. +- Check out our :doc:`Installation Guide ` for multi-site support, languages, and OpenAPI documentation. + +.. toctree:: + :maxdepth: 1 + :hidden: + + tutorial/index + how-to/index + reference/index + contributing + changelog \ No newline at end of file diff --git a/docs/live.sh b/docs/live.sh new file mode 100755 index 0000000..fbe298a --- /dev/null +++ b/docs/live.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Live documentation server script +# This script starts sphinx-autobuild for live documentation development + +echo "Starting live documentation server with auto-reload..." +echo "Server will be available at http://localhost:8000" +echo "Press Ctrl+C to stop" + +# Change to the docs directory if not already there +if [ ! -f "conf.py" ]; then + if [ -d "docs" ]; then + cd docs + else + echo "Error: conf.py not found. Please run this script from the project root or docs directory." + exit 1 + fi +fi + +# Start sphinx-autobuild +poetry run sphinx-autobuild . _build/html --port 8000 --host 0.0.0.0 --open-browser \ No newline at end of file diff --git a/docs/poetry.lock b/docs/poetry.lock new file mode 100644 index 0000000..d34723b --- /dev/null +++ b/docs/poetry.lock @@ -0,0 +1,960 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +description = "A collection of accessible pygments styles" +optional = false +python-versions = ">=3.9" +files = [ + {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, + {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, +] + +[package.dependencies] +pygments = ">=1.5" + +[package.extras] +dev = ["pillow", "pkginfo (>=1.10)", "playwright", "pre-commit", "setuptools", "twine (>=5.0)"] +tests = ["hypothesis", "pytest"] + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, + {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "25.1.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "furo" +version = "2025.7.19" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.8" +files = [ + {file = "furo-2025.7.19-py3-none-any.whl", hash = "sha256:bdea869822dfd2b494ea84c0973937e35d1575af088b6721a29c7f7878adc9e3"}, + {file = "furo-2025.7.19.tar.gz", hash = "sha256:4164b2cafcf4023a59bb3c594e935e2516f6b9d35e9a5ea83d8f6b43808fe91f"}, +] + +[package.dependencies] +accessible-pygments = ">=0.0.5" +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<9.0" +sphinx-basic-ng = ">=1.0.0.beta2" + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +files = [ + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "livereload" +version = "2.7.1" +description = "Python LiveReload is an awesome tool for web developers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "livereload-2.7.1-py3-none-any.whl", hash = "sha256:5201740078c1b9433f4b2ba22cd2729a39b9d0ec0a2cc6b4d3df257df5ad0564"}, + {file = "livereload-2.7.1.tar.gz", hash = "sha256:3d9bf7c05673df06e32bea23b494b8d36ca6d10f7d5c3c8a6989608c09c986a9"}, +] + +[package.dependencies] +tornado = "*" + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +files = [ + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rstfmt" +version = "0.0.14" +description = "A formatter for reStructuredText" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rstfmt-0.0.14-py3-none-any.whl", hash = "sha256:5488be45c457239cf512239899caebd5f67bbff562e826fe3eb2059335535b2a"}, + {file = "rstfmt-0.0.14.tar.gz", hash = "sha256:5dd5e6988386f21fd89adc015baca64588c38fa89a87ab691f81c7675162b3d2"}, +] + +[package.dependencies] +black = ">=22.1.0" +docutils = ">=0.12" +sphinx = ">=2.4.0" + +[package.extras] +d = ["aiohttp (>=3.3.2)"] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" +files = [ + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, +] + +[[package]] +name = "soupsieve" +version = "2.7" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, +] + +[package.dependencies] +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] + +[[package]] +name = "sphinx-autobuild" +version = "2021.3.14" +description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] + +[package.dependencies] +colorama = "*" +livereload = "*" +sphinx = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "2.3.0" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67"}, + {file = "sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084"}, +] + +[package.dependencies] +sphinx = ">=7.3.5" + +[package.extras] +docs = ["furo (>=2024.1.29)"] +numpy = ["nptyping (>=2.5)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.4.4)", "defusedxml (>=0.7.1)", "diff-cover (>=9)", "pytest (>=8.1.1)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.11)"] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +description = "A modern skeleton for Sphinx themes." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, +] + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-issues" +version = "3.0.1" +description = "A Sphinx extension for linking to your project's issue tracker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx-issues-3.0.1.tar.gz", hash = "sha256:b7c1dc1f4808563c454d11c1112796f8c176cdecfee95f0fd2302ef98e21e3d6"}, + {file = "sphinx_issues-3.0.1-py3-none-any.whl", hash = "sha256:8b25dc0301159375468f563b3699af7a63720fd84caf81c1442036fcd418b20c"}, +] + +[package.dependencies] +sphinx = "*" + +[package.extras] +dev = ["flake8 (==3.9.2)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.7,<3.0)", "pytest (>=6.2.0)", "tox"] +lint = ["flake8 (==3.9.2)", "flake8-bugbear (==20.11.1)", "pre-commit (>=2.7,<3.0)"] +tests = ["pytest (>=6.2.0)"] + +[[package]] +name = "sphinx-notfound-page" +version = "0.8.3" +description = "Sphinx extension to build a 404 page with absolute URLs" +optional = false +python-versions = "*" +files = [ + {file = "sphinx-notfound-page-0.8.3.tar.gz", hash = "sha256:f728403280026b84c234540bebbed7f710b9ea582e7348a35a5becefe4024332"}, + {file = "sphinx_notfound_page-0.8.3-py2.py3-none-any.whl", hash = "sha256:c4867b345afccef72de71fb410c412540dfbb5c2de0dc06bde70b331b8f30469"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +doc = ["sphinx", "sphinx-autoapi", "sphinx-notfound-page", "sphinx-prompt", "sphinx-rtd-theme", "sphinx-tabs", "sphinxemoji"] +test = ["tox"] + +[[package]] +name = "sphinx-removed-in" +version = "0.2.3" +description = "versionremoved and removed-in directives for Sphinx" +optional = false +python-versions = "*" +files = [ + {file = "sphinx-removed-in-0.2.3.tar.gz", hash = "sha256:a62dfeaa7962c5b6760b55de65ef3ed2ea83afa1a7c3416ac0bb7d3dec8fd2a6"}, + {file = "sphinx_removed_in-0.2.3-py3-none-any.whl", hash = "sha256:279dac26140b899218e37e3344ba9a7a0983694ee9a8539b446ba82bd9af69a7"}, +] + +[package.dependencies] +Sphinx = "*" + +[[package]] +name = "sphinx-rtd-theme" +version = "2.0.0" +description = "Read the Docs theme for Sphinx" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, + {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, +] + +[package.dependencies] +docutils = "<0.21" +sphinx = ">=5,<8" +sphinxcontrib-jquery = ">=4,<5" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + +[[package]] +name = "sphinx-tabs" +version = "3.4.7" +description = "Tabbed views for Sphinx" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d"}, + {file = "sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915"}, +] + +[package.dependencies] +docutils = "*" +pygments = "*" +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.13.0)"] +testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "tornado" +version = "6.5.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.9" +files = [ + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6"}, + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04"}, + {file = "tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0"}, + {file = "tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f"}, + {file = "tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af"}, + {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "zipp" +version = "3.23.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<4" +content-hash = "3d84d111b85109986a346488b9d0b7aaea802d24e7ce3f51a2919c87f301700e" diff --git a/docs/pyproject.toml b/docs/pyproject.toml new file mode 100644 index 0000000..80ae7e1 --- /dev/null +++ b/docs/pyproject.toml @@ -0,0 +1,32 @@ +[tool.poetry] +name = "djangocms-rest-docs" +version = "0.1.0" +description = "Documentation for django CMS REST" +authors = ["Django CMS Association and contributors "] +readme = "README.md" +packages = [] +package-mode = false + +[tool.poetry.dependencies] +python = ">=3.9,<4" +sphinx = "^7.2.6" +sphinx-rtd-theme = "^2.0.0" +sphinx-autodoc-typehints = "^2.0.0" +sphinx-copybutton = "^0.5.2" +sphinx-tabs = "^3.4.4" +furo = "^2025.7.19" + +[tool.poetry.group.dev.dependencies] +sphinx-autobuild = "^2021.3.14" +sphinx-issues = "^3.0.1" +sphinx-removed-in = "^0.2.1" +sphinx-notfound-page = "^0.8.3" +rstfmt = "^0.0.14" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.urls] +Homepage = "https://github.com/django-cms/djangocms-rest" +Documentation = "https://djangocms-rest.readthedocs.io/" \ No newline at end of file diff --git a/docs/reference/breadcrumbs.rst b/docs/reference/breadcrumbs.rst new file mode 100644 index 0000000..66fa62c --- /dev/null +++ b/docs/reference/breadcrumbs.rst @@ -0,0 +1,211 @@ +Breadcrumbs Endpoints +==================== + +**The Breadcrumbs endpoints provide breadcrumb navigation structures in django CMS.** + + +.. code-block:: bash + + GET /api/en/breadcrumbs/ + +* This returns breadcrumb navigation structures for the specified language +* Breadcrumb information includes page titles, URLs, visibility settings, and hierarchical relationships +* This endpoint is essential for building breadcrumb navigation, page context, and site structure indicators +* Advanced endpoints allow filtering by start level and specific paths + +CMS Reference +------------- + +- `Menu configuration `_ +- `Navigation and menus `_ + +Endpoints +--------- + +List Breadcrumbs +~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/breadcrumbs/`` + +Get the complete breadcrumb structure for a specific language. + +**Response Attributes:** + +* ``namespace``: Application namespace (nullable) +* ``title``: Menu item title +* ``url``: Complete URL to the page (nullable) +* ``api_endpoint``: API endpoint URL for the page (nullable) +* ``visible``: Whether the menu item is visible +* ``selected``: Whether the menu item is currently selected +* ``attr``: Additional attributes (nullable) +* ``level``: Menu level/depth (nullable) +* ``children``: Array of child menu items + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/breadcrumbs/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "Home", + "url": "http://localhost:8080/en/", + "api_endpoint": "http://localhost:8080/api/en/pages/", + "visible": true, + "selected": false, + "attr": null, + "level": 0, + "children": [ + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + ] + } + +List Breadcrumbs by Path +~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/breadcrumbs/{path}/`` + +Get the breadcrumb structure filtered by specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``path`` (string, required): Path as starting node for the breadcrumbs + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/breadcrumbs/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "Home", + "url": "http://localhost:8080/en/", + "api_endpoint": "http://localhost:8080/api/en/pages/", + "visible": true, + "selected": false, + "attr": null, + "level": 0, + "children": [ + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + ] + } + +List Breadcrumbs by Start Level +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/breadcrumbs/{start_level}/`` + +Get the breadcrumb structure filtered by start level. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``start_level`` (integer, required): Starting level for breadcrumb items + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/breadcrumbs/1/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + +List Breadcrumbs by Start Level and Path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/breadcrumbs/{start_level}/{path}/`` + +Get the breadcrumb structure filtered by start level and specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``start_level`` (integer, required): Starting level for breadcrumb items +* ``path`` (string, required): Path as starting node for the breadcrumbs + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/breadcrumbs/1/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 0000000..1348f27 --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,23 @@ +API Reference +============= + +The reference section provides detailed documentation insights for all djangocms-rest endpoints. + +OpenAPI Documentation +--------------------- + We recommend to use the :doc:`../tutorial/03-openapi-documentation` guide to setup a browsable OpenAPI documentation. + This will automatically generate the latest endpoints and their documentation. + Furthermore with ``swagger ui`` and ``redoc`` you can test the endpoints directly in your browser. + +Available Endpoints +------------------- +.. toctree:: + :maxdepth: 1 + + Languages Endpoints + Pages Endpoints + Placeholders Endpoints + Menu Endpoints + Submenu Endpoints + Breadcrumbs Endpoints + Plugins Endpoints \ No newline at end of file diff --git a/docs/reference/languages.rst b/docs/reference/languages.rst new file mode 100644 index 0000000..152b1e6 --- /dev/null +++ b/docs/reference/languages.rst @@ -0,0 +1,58 @@ +Languages Endpoints +=================== + +**The Languages endpoints provide language information in django CMS.** + +* This returns all the languages that are configured for the site. +* Language information includes language codes, names and configuration settings. +* This information is useful for building a language switcher and handling language fallbacks in decoupled front-end applications. + + +CMS Reference +------------- + +- `Internationalisation and Localisation `_ +- `Language configuration `_ + + +Endpoints +--------- + +List Languages +~~~~~~~~~~~~~~ + +**GET** ``/api/languages/`` + +List of languages available for the site. + +**Response Attributes:** + +* ``code``: Language code (e.g., "en", "de", "fr") +* ``name``: Human readable language name +* ``public``: Whether the language is publicly available +* ``fallbacks``: Array of fallback language codes +* ``redirect_on_fallback``: Whether to redirect when fallback is used +* ``hide_untranslated``: Whether to hide untranslated content + +**Query Parameters:** + +* ``preview`` (boolean, optional): Has currently no effect on this endpoint + +**Example Request:** + +.. code-block:: bash + + GET /api/languages/ + +**Example Response:** + +.. code-block:: json + + { + "code": "en", + "name": "English", + "public": true, + "fallbacks": ["en"], + "redirect_on_fallback": true, + "hide_untranslated": true + } \ No newline at end of file diff --git a/docs/reference/menu.rst b/docs/reference/menu.rst new file mode 100644 index 0000000..3e0e98c --- /dev/null +++ b/docs/reference/menu.rst @@ -0,0 +1,233 @@ +Menu Endpoints +============== + +**The Menu endpoints provide navigation nodes using the same structure as the django CMS menu system.** + +.. note:: + + The endpoints follow the same structure as the menu in a template. Please refer to the documentation for more details. + + ``{% show_menu 0 100 100 100 %}`` + + ``GET /api/{language}/menu/0/100/100/100/`` + + + +* Menu endpoints are essential for building navigation menus and sitemaps +* Menu information includes page meta information, state, URLs, visibility settings, and hierarchical relationships +* Retrieve menu nodes and filter by: +* * by level ranges +* * by root ID +* * by level range +* * active/inactive states, +* * specific paths + + +CMS Reference +------------- + +- `User site navigation `_ +- `Customizing the Menu `_ +- `Menu Developer Reference `_ + +Endpoints +--------- + +List Menu +~~~~~~~~~ + +**GET** ``/api/{language}/menu/`` + +Get the complete menu structure for a specific language. + +**Response Attributes:** + +* ``namespace``: Application namespace (nullable) +* ``title``: Menu item title +* ``url``: Complete URL to the page (nullable) +* ``api_endpoint``: API endpoint URL for the page (nullable) +* ``visible``: Whether the menu item is visible +* ``selected``: Whether the menu item is currently selected +* ``attr``: Additional attributes (nullable) +* ``level``: Menu level/depth (nullable) +* ``children``: Array of child menu items + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/menu/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "Home", + "url": "http://localhost:8080/en/", + "api_endpoint": "http://localhost:8080/api/en/pages/", + "visible": true, + "selected": false, + "attr": null, + "level": 0, + "children": [ + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": false, + "attr": null, + "level": 1, + "children": [] + } + ] + } + +List Menu by Level Range +~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/menu/{from_level}/{to_level}/{extra_inactive}/{extra_active}/`` + +Get the menu structure filtered by level range and active/inactive states. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``from_level`` (integer, required): Starting level for menu items +* ``to_level`` (integer, required): Ending level for menu items +* ``extra_inactive`` (integer, required): Number of extra inactive items to include +* ``extra_active`` (integer, required): Number of extra active items to include + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/menu/0/2/1/1/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "Home", + "url": "http://localhost:8080/en/", + "api_endpoint": "http://localhost:8080/api/en/pages/", + "visible": true, + "selected": false, + "attr": null, + "level": 0, + "children": [ + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": false, + "attr": null, + "level": 1, + "children": [] + } + ] + } + +List Menu by Level Range and Path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/menu/{from_level}/{to_level}/{extra_inactive}/{extra_active}/{path}/`` + +Get the menu structure filtered by level range, active/inactive states, and specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``from_level`` (integer, required): Starting level for menu items +* ``to_level`` (integer, required): Ending level for menu items +* ``extra_inactive`` (integer, required): Number of extra inactive items to include +* ``extra_active`` (integer, required): Number of extra active items to include +* ``path`` (string, required): Path as starting node for the menu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/menu/0/2/1/1/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + +List Menu by Root ID and Level Range +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/menu/{root_id}/{from_level}/{to_level}/{extra_inactive}/{extra_active}/{path}/`` + +Get the menu structure filtered by root ID, level range, active/inactive states, and specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``root_id`` (string, required): Root ID to start the menu from +* ``from_level`` (integer, required): Starting level for menu items +* ``to_level`` (integer, required): Ending level for menu items +* ``extra_inactive`` (integer, required): Number of extra inactive items to include +* ``extra_active`` (integer, required): Number of extra active items to include +* ``path`` (string, required): Path as starting node for the menu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/menu/1/0/2/1/1/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } \ No newline at end of file diff --git a/docs/reference/pages.rst b/docs/reference/pages.rst new file mode 100644 index 0000000..39f31dc --- /dev/null +++ b/docs/reference/pages.rst @@ -0,0 +1,287 @@ +Pages Endpoints +=============== + +**The Pages endpoints provide django CMS pages and their content.** + +* This returns all pages available for the specified language with their metadata and placeholder information +* Page information includes titles, URLs, navigation settings, and template configurations +* This endpoint is essential for page meta information, SEO and building page listings +* Pages can be retrieved as a nested tree structure or a list of pages with pagination support + +.. note:: + This does only return page meta informaiton. To retrieve the page content, you need to use the :doc:`Placeholders `. + +.. warning:: + Fetching a deeply nested tree of pages can be very slow for large page sets. Use the :ref:`Pages List API ` instead. + + +CMS Reference +------------- + +- `Page configuration `_ +- `Page templates `_ + +Endpoints +--------- + +List Pages +~~~~~~~~~~ + +**GET** ``/api/{language}/pages/`` + +Retrieve a list of all pages for the specified language. + +**Response Attributes:** + +* ``title``: Page title +* ``page_title``: SEO page title +* ``menu_title``: Navigation menu title +* ``meta_description``: SEO meta description +* ``redirect``: Redirect URL +* ``in_navigation``: Whether the page appears in navigation +* ``soft_root``: Whether this is a soft root page +* ``template``: Page template name +* ``xframe_options``: X-Frame-Options setting +* ``limit_visibility_in_menu``: Whether to limit visibility in menu +* ``language``: Language code +* ``path``: URL path +* ``absolute_url``: Complete URL to the page +* ``is_home``: Whether this is the home page +* ``login_required``: Whether login is required to view the page +* ``languages``: Array of available language codes +* ``is_preview``: Whether this is a preview +* ``application_namespace``: Application namespace +* ``creation_date``: Page creation date (ISO format) +* ``changed_date``: Last modification date (ISO format) +* ``details``: Page details/description +* ``placeholders``: Array of placeholder relations with content_type_id, object_id, slot, and details + +.. note:: + You can use the `application_namespace` identifer to render your django app in a decoupled frontend application. + Similar to how you would render a django app using `app_hooks`. + + + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/pages/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "title": "test", + "page_title": "test", + "menu_title": "test", + "meta_description": "", + "redirect": "", + "in_navigation": true, + "soft_root": false, + "template": "INHERIT", + "xframe_options": "", + "limit_visibility_in_menu": false, + "language": "en", + "path": "", + "absolute_url": "http://localhost:8080/", + "is_home": true, + "login_required": false, + "languages": [ + "de", + "en" + ], + "is_preview": false, + "application_namespace": "", + "creation_date": "2025-05-22T19:30:49.343177Z", + "changed_date": "2025-05-22T19:30:49.343248Z", + "details": "http://localhost:8080/api/en/pages/", + "placeholders": [ + { + "content_type_id": 5, + "object_id": 11, + "slot": "content", + "details": "http://localhost:8080/api/en/placeholders/5/11/content/" + }, + { + "content_type_id": 5, + "object_id": 11, + "slot": "cta", + "details": "http://localhost:8080/api/en/placeholders/5/11/cta/" + } + ] + } + +Retrieve Page by Path +~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/pages/{path}/`` + +Retrieve a specific page by its path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``path`` (string, required): Page path (e.g., "about", "contact") + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/pages/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "title": "About Us", + "page_title": "About Us - Our Company", + "menu_title": "About", + "meta_description": "Learn more about our company", + "redirect": "", + "in_navigation": true, + "soft_root": false, + "template": "INHERIT", + "xframe_options": "", + "limit_visibility_in_menu": false, + "language": "en", + "path": "/en/about/", + "absolute_url": "http://localhost:8080/en/about/", + "is_home": false, + "login_required": false, + "languages": [ + "de", + "en" + ], + "is_preview": false, + "application_namespace": "", + "creation_date": "2025-05-22T19:30:49.343177Z", + "changed_date": "2025-05-22T19:30:49.343248Z", + "details": "http://localhost:8080/api/en/pages/about/", + "placeholders": [ + { + "content_type_id": 5, + "object_id": 12, + "slot": "content", + "details": "http://localhost:8080/api/en/placeholders/5/12/content/" + } + ] + } + + + +.. _list-pages-paginated: + +List Pages (Paginated) +~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/pages-list/`` + +Retrieve a simplified list of pages with basic information. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") + +**Query Parameters:** + +* ``limit`` (integer, optional): Number of items to return +* ``offset`` (integer, optional): Number of items to skip +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/pages-list/?limit=10&offset=0&preview=true + +**Example Response:** + +.. code-block:: json + + { + "count": 25, + "next": "http://localhost:8080/api/en/pages-list/?limit=10&offset=10", + "previous": null, + "results": [ + { + "title": "Home", + "absolute_url": "http://localhost:8080/en/", + "path": "/en/", + "is_home": true, + "in_navigation": true + }, + { + "title": "About Us", + "absolute_url": "http://localhost:8080/en/about/", + "path": "/en/about/", + "is_home": false, + "in_navigation": true + } + ] + } + +Pages Tree +~~~~~~~~~~ + +**GET** ``/api/{language}/pages-tree/`` + +Retrieve pages in a hierarchical tree structure. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/pages-tree/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "title": "Home", + "absolute_url": "http://localhost:8080/en/", + "path": "/en/", + "is_home": true, + "in_navigation": true, + "children": [ + { + "title": "About Us", + "absolute_url": "http://localhost:8080/en/about/", + "path": "/en/about/", + "is_home": false, + "in_navigation": true, + "children": [] + }, + { + "title": "Contact", + "absolute_url": "http://localhost:8080/en/contact/", + "path": "/en/contact/", + "is_home": false, + "in_navigation": true, + "children": [] + } + ] + } \ No newline at end of file diff --git a/docs/reference/placeholders.rst b/docs/reference/placeholders.rst new file mode 100644 index 0000000..e0b1778 --- /dev/null +++ b/docs/reference/placeholders.rst @@ -0,0 +1,94 @@ +Placeholders Endpoints +====================== + +**The Placeholders endpoints provide placeholder content in django CMS.** + +* Used to retrieve content from placeholders object linked to a specific page +* This essentially returns all plugins in a placeholder as a nested JSON tree according to the defined placeholders in `CMS_PLACEHOLDER_CONF` +* The content is rendered as HTML if the ``?html=1`` parameter is added to the API URL using Django CMS template rendering +* Serialized content is either using ``ModelSerializer`` as a default or a ``CustomSerializer`` defined in your plugins configuration + +CMS Reference +------------- + +- `Howto define placeholders in CMS_PLACEHOLDER_CONF `_ +- `Howto create plugins `_ + +Endpoints +--------- + +Retrieve Placeholder +~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/placeholders/{content_type_id}/{object_id}/{slot}/`` + +Placeholder contain the dynamic content. This view retrieves the content as a structured nested object. +You can get a direct link to build the query or all attributes from the :doc:`Pages `. + +**Response Attributes:** + +* ``slot``: The slot name of the placeholder. +* ``label``: The verbose name of the placeholder. +* ``language``: The language of the returned content. +* ``content``: The content rendered as Serialized JSON. +* ``html``: Optional: The content rendered as HTML. + +.. note:: + Use ``?html=1`` to get the content rendered as HTML. + + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``content_type_id`` (integer, required): Content type ID +* ``object_id`` (integer, required): Object ID +* ``slot`` (string, required): Placeholder slot name (e.g., "content", "sidebar") + +**Query Parameters:** + +* ``html`` (integer, optional): Set to 1 to include HTML rendering in response +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +.. note:: + Use ``?preview=true`` has no effect when retrieving the content of a draft page, because we already query the draft content. + +**Example Request:** + +.. code-block:: bash + + GET /api/en/placeholders/5/9/content/?html=1 + +**Example Response:** + +.. code-block:: json + + { + "slot": "content", + "label": "Content", + "language": "en", + "content": [ + { + "plugin_type": "TextPlugin", + "body": "

Hello World!

", + "json": { + "type": "doc", + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "text": "Hello World!", + "type": "text" + } + ] + } + ] + }, + "rte": "tiptap" + } + ], + "html": "

Hello World!

" + } diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst new file mode 100644 index 0000000..5623ed9 --- /dev/null +++ b/docs/reference/plugins.rst @@ -0,0 +1,69 @@ +Plugins Endpoints +================= + +**The Plugins endpoints provide plugin definitions for all available plugins in django CMS.** + +* This returns all available plugin type definitions with their properties and schemas +* Plugin definitions include the plugin type identifier, human-readable title, and property definitions +* This endpoint is useful for understanding what plugins are available and their configuration options +* It is particularly useful for creating type-safe schemas for your frontend application +* Schema definitions are based on the ``ModelSerializer`` as a default or a ``CustomSerializer`` defined in your plugin + +.. hint:: + You can automatically generate type-safe schemas for your typescript frontend application using tools like `QuickType `_. + + +Howto +------ +- :doc:`Plugin Creation & Serialization <../how-to/011-plugin-creation>` + +CMS Reference +------------- + +- `Howto create plugins `_ + +Endpoints +--------- + +List Plugin Definitions +~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/plugins/`` + +Get all plugin type definitions available in the CMS. + +**Response Attributes:** + +* ``plugin_type``: Unique identifier for the plugin type +* ``title``: Human readable name of the plugin +* ``type``: Schema type +* ``properties``: Property definitions for the plugin + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/plugins/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "plugin_type": "TextPlugin", + "title": "Text", + "type": "object", + "properties": { + "body": { + "type": "string", + "description": "Text content" + } + } + } + +.. note:: + Use custom serializers to define the properties of your plugin in detail. This also allows a fully typed API response and drastically improves the developer experience. diff --git a/docs/reference/submenu.rst b/docs/reference/submenu.rst new file mode 100644 index 0000000..a725db4 --- /dev/null +++ b/docs/reference/submenu.rst @@ -0,0 +1,377 @@ +Submenu Endpoints +================= + +**The Submenu endpoints provide hierarchical submenu structures in django CMS.** + +.. note:: + + The endpoints follow the same structure as the submenu in a template. Please refer to the documentation for more details. + Get root level submenu: + + ``{% show_sub_menu 1 %}`` + + ``GET /api/{language}/submenu/`` + + + +* Returns hierarchical submenu structures for the specified language +* Submenu information includes page titles, URLs, visibility settings, and nested relationships +* This endpoint is essential for building dropdown menus, sidebar navigation, and contextual menus +* Advanced endpoints allow filtering by levels, root levels, nephews, and specific paths + +CMS Reference +------------- + +- `Menu configuration `_ +- `Navigation and menus `_ + +Endpoints +--------- + +List Submenu +~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/`` + +Get the complete submenu structure for a specific language. + +**Response Attributes:** + +* ``namespace``: Application namespace (nullable) +* ``title``: Menu item title +* ``url``: Complete URL to the page (nullable) +* ``api_endpoint``: API endpoint URL for the page (nullable) +* ``visible``: Whether the menu item is visible +* ``selected``: Whether the menu item is currently selected +* ``attr``: Additional attributes (nullable) +* ``level``: Menu level/depth (nullable) +* ``children``: Array of child menu items + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "Home", + "url": "http://localhost:8080/en/", + "api_endpoint": "http://localhost:8080/api/en/pages/", + "visible": true, + "selected": false, + "attr": null, + "level": 0, + "children": [ + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": false, + "attr": null, + "level": 1, + "children": [] + } + ] + } + +List Submenu by Levels +~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{levels}/`` + +Get the submenu structure filtered by number of levels. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``levels`` (integer, required): Number of levels to include in the submenu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/2/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "Home", + "url": "http://localhost:8080/en/", + "api_endpoint": "http://localhost:8080/api/en/pages/", + "visible": true, + "selected": false, + "attr": null, + "level": 0, + "children": [ + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": false, + "attr": null, + "level": 1, + "children": [] + } + ] + } + +List Submenu by Levels and Path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{levels}/{path}/`` + +Get the submenu structure filtered by number of levels and specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``levels`` (integer, required): Number of levels to include in the submenu +* ``path`` (string, required): Path as starting node for the submenu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/2/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + +List Submenu by Levels and Root Level +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{levels}/{root_level}/`` + +Get the submenu structure filtered by number of levels and root level. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``levels`` (integer, required): Number of levels to include in the submenu +* ``root_level`` (integer, required): Root level to start the submenu from + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/2/1/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": false, + "attr": null, + "level": 1, + "children": [] + } + +List Submenu by Levels, Root Level and Nephews +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{levels}/{root_level}/{nephews}/`` + +Get the submenu structure filtered by number of levels, root level, and nephews. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``levels`` (integer, required): Number of levels to include in the submenu +* ``root_level`` (integer, required): Root level to start the submenu from +* ``nephews`` (integer, required): Number of nephew items to include + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/2/1/1/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": false, + "attr": null, + "level": 1, + "children": [] + } + +List Submenu by Levels, Root Level, Nephews and Path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{levels}/{root_level}/{nephews}/{path}/`` + +Get the submenu structure filtered by number of levels, root level, nephews, and specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``levels`` (integer, required): Number of levels to include in the submenu +* ``root_level`` (integer, required): Root level to start the submenu from +* ``nephews`` (integer, required): Number of nephew items to include +* ``path`` (string, required): Path as starting node for the submenu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/2/1/1/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + +List Submenu by Levels, Root Level and Path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{levels}/{root_level}/{path}/`` + +Get the submenu structure filtered by number of levels, root level, and specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``levels`` (integer, required): Number of levels to include in the submenu +* ``root_level`` (integer, required): Root level to start the submenu from +* ``path`` (string, required): Path as starting node for the submenu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/2/1/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } + +List Submenu by Path +~~~~~~~~~~~~~~~~~~~~ + +**GET** ``/api/{language}/submenu/{path}/`` + +Get the submenu structure filtered by specific path. + +**Path Parameters:** + +* ``language`` (string, required): Language code (e.g., "en", "de") +* ``path`` (string, required): Path as starting node for the submenu + +**Query Parameters:** + +* ``preview`` (boolean, optional): Set to true to preview unpublished content (admin access required) + +**Example Request:** + +.. code-block:: bash + + GET /api/en/submenu/about/?preview=true + +**Example Response:** + +.. code-block:: json + + { + "namespace": null, + "title": "About Us", + "url": "http://localhost:8080/en/about/", + "api_endpoint": "http://localhost:8080/api/en/pages/about/", + "visible": true, + "selected": true, + "attr": null, + "level": 1, + "children": [] + } diff --git a/docs/tutorial/01-quickstart.rst b/docs/tutorial/01-quickstart.rst new file mode 100644 index 0000000..e94f06a --- /dev/null +++ b/docs/tutorial/01-quickstart.rst @@ -0,0 +1,93 @@ +Quick Start +=========== + +Get up and running with djangocms-rest in minutes. + +Prerequisites +------------- + +.. note:: + A running Django CMS project is required. Follow the `Installing Django CMS by hand `_ guide to get started. + +Installation +------------ + +.. code-block:: bash + + # Install djangocms-rest + poetry add djangocms-rest + +Configuration +------------ + +.. code-block:: python + + # Add to INSTALLED_APPS in your project's settings.py + INSTALLED_APPS = [ + # ... other apps + 'djangocms_rest', + ] + +.. code-block:: python + + # Include URLs in your project's urls.py + urlpatterns = [ + # ... other URLs + path('api/', include('djangocms_rest.urls')), + ] + +Start Server +------------ + +.. code-block:: bash + + # Start the Django development server + python manage.py runserver 8080 + +Test API +-------- + +Visit `http://localhost:8080/api/languages/ `_ to test the API endpoints. + +.. code-block:: bash + + # Test the API endpoints + curl -X 'GET' \ + 'http://localhost:8080/api/languages/' \ + -H 'accept: application/json' + +If you see a response like this, you're good to go: + +.. code-block:: json + + [ + { + "code": "en", + "name": "English", + "public": true, + "fallbacks": [ + "en" + ], + "redirect_on_fallback": true, + "hide_untranslated": false + }, + { + "code": "de", + "name": "Deutsch", + "public": false, + "fallbacks": [ + "en" + ], + "redirect_on_fallback": true, + "hide_untranslated": true + } + ] + +See the :doc:`../reference/languages` reference for more information. + +Next Steps +---------- + +- Follow the :doc:`02-installation` guide for advanced features like multi-site support, languages, and OpenAPI documentation +- Explore the :doc:`../reference/index` for detailed API documentation +- Check out :doc:`../how-to/index` for implementation guides diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst new file mode 100644 index 0000000..377f685 --- /dev/null +++ b/docs/tutorial/02-installation.rst @@ -0,0 +1,404 @@ +Installation +============ + +Requirements +------------ + +djangocms-rest requires: + +* Python 3.9 or higher +* Django 4.2 or higher +* django CMS 4.0 or higher, including latest 5.0+ versions +* Django REST Framework 3.14 or higher + +Installation +------------ +.. note:: + A running Django CMS project is required. Follow the `Installing Django CMS by hand `_ guide to get started. + +Using Poetry +~~~~~~~~~~~~ + +We recommend using ``Poetry`` to manage your dependencies. It it is used as default for this documentation. +``pip`` can be used as equivalent. + +.. code-block:: bash + + poetry add djangocms-rest + +Using pip +~~~~~~~~~ + +.. code-block:: bash + + pip install djangocms-rest + + +Latest from source +~~~~~~~~~~~~~~~~~~~~~~~~ +For the latest features, you can install a version from the GitHub repository at your own risk: + +.. code-block:: bash + + poetry add git+https://github.com/fsbraun/djangocms-rest.git + +.. code-block:: bash + + # install a specific branch + poetry add git+https://github.com/fsbraun/djangocms-rest.git@feat/extended-search + +Basic Configuration +-------------------- + +1. Add ``djangocms_rest`` to your ``INSTALLED_APPS``: + +.. code-block:: python + + INSTALLED_APPS = [ + # ... other Django apps + ..., + + # django CMS + 'cms', + + # djangocms-rest + 'djangocms_rest', + ..., + ] + +2. Include the djangocms-rest URLs in your project's main URL configuration: + +.. code-block:: python + + # demo_cms/urls.py + from django.urls import path, include + + urlpatterns = [ + # ... other URL patterns + path('api/', include('djangocms_rest.urls')), #api can be changed to your liking + ] + +2.1 Add optional API prefix + +Alternatively, you can put the API under a specific path, like ``api/cms/``. +This is handy if you want to have a separate API for different parts of your app. + +.. code-block:: python + + from django.urls import path, include + + urlpatterns = [ + # ... other URL patterns + path('api/', include('my_django_rest_app.urls')), + path('api/cms/', include('djangocms_rest.urls')), + ] + +.. note:: + When you autocreate clients and types from OpenAPI specification with tools like `heyapi.dev `_, this will also affect the naming of those components and types,eg. + ``RetrieveLanguages`` will become ``CmsRetrieveLanguages`` in the client sdk. + +CORS Support +------------ + +If you want to serve the API from a different domain, you can use the ``CorsMiddleware`` to enable CORS. +This is optional, but likely needed for security reasons with decoupled frontend apps. + +Docs +~~~~ +- `Django CORS Headers `_ + + +Configuration +~~~~~~~~~~~~~ + +.. code-block:: bash + + poetry add django-cors-headers + + +.. code-block:: python + + # settings.py + INSTALLED_APPS = [ + ..., + "corsheaders", + ..., + ] + + # add the allowed origins (your frontend apps) to the CORS settings + CORS_ALLOWED_ORIGINS = [ + ..., + "https://example.com", # set your own domain here, likely via env variable in production + "http://localhost:300", # common js frontend app port + "http://localhost:5173", # vue.js from our examples + ..., + ] + + +.. code-block:: python + + MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", + ] + + +Languages Support +----------------- + +- djnagocms-rest supports languages out of the box. +- Djnago CMS needs to be configured to use languages. +- A single language must always be set in order to use the API. + +Docs +~~~~ +- `Django CMS - Internationalisation and Localisation `_ +- `Django CMS - Language configuration `_ +- `Django CMS - Howto - Languages `_ + +Configuration +~~~~~~~~~~~~~ + +This is a simple configuration to get you started. Follow the Django CMS documentation to configure languages in-depth. + +.. code-block:: python + + # settings.py + + # Language settings + LANGUAGE_CODE = "en" + + USE_I18N = True + + LANGUAGES = ( + ("de", _("German")), + ("en", _("English")), + ) + + CMS_LANGUAGES = { + 1: [ + { + "code": "en", + "name": "English", + "public": True, + }, + { + "code": "de", + "name": _("Deutsch"), + "public": False, + "hide_untranslated": True, + }, + ], + "default": { + "fallbacks": ["en"], + "redirect_on_fallback": True, + "public": True, + "hide_untranslated": False, + }, + } + + MIDDLEWARE = [ + ..., + "cms.middleware.language.LanguageCookieMiddleware", + ..., + ] + + +.. code-block:: python + + # urls.py + # example configuration + urlpatterns += i18n_patterns( + path('admin/', include(admin.site.urls)), + path('', include('cms.urls')), + prefix_default_language=False, + ) + + +Multi-Site Support +------------------ + +djangocms-rest supports 2 ways to handle multi-site support: + +1. **Multi-Instance Setup:** Usually multiple instances of the CMS running on different domains. This is also valid option for headless mode. See the official `Django CMS - Multi-Site installation `_ documentation for more information. +2. **Single Instance Setup:** Run Django CMS in headless mode and serve multiple sites from a single instance. Using the ``SiteContextMiddleware`` from ``djangocms-rest`` to set the site context on the request. + +**Option 1:** + +1. foo.example.com/api/pages/ < REQUEST > Content foo site +2. bar.example.com/api/pages/ < REQUEST > Content bar site + +**Option 2:** + +1. cms.example.com/api/pages/ < REQUEST HEADERS X-Site-ID: 1 > Content foo site +2. cms.example.com/api/pages/ < REQUEST HEADERS X-Site-ID: 2 > Content bar site + +If you want to serve multiple sites from a single instance, you can use the ``SiteContextMiddleware`` to set the site context on the request. +This requires ``Django Sites`` framework to be installed and configured. + +Your can pass the site ID in the request headers with the ``X-Site-ID`` property set to the site ID. +The Middleware will then set the site context on the request. + +Docs +~~~~ +- `Django Sites `_ +- `Enabling Sites Framework `_ +- `Django CMS - Multi-Site installation `_ + + For Option 2, you do not need to configure the webserver to manage multiples sites as the frontend apps are decoupled and run on a different domain. + +.. note:: + + You need to have CORS configured correctly to allow the frontend app to access the API. + See `CORS Support <../tutorial/02-installation.html#cors-support>`_. + +Configuration +~~~~~~~~~~~~~ + +.. code-block:: python + + INSTALLED_APPS = [ + ... + 'django.contrib.sites', + ... + ] + + # default site id, you likely wnat to change this using env variable in production + SITE_ID = 1 + +.. code-block:: python + + CORS_ALLOW_ALL_ORIGINS=True # development seting, disable in production + CORS_ALLOWED_ORIGINS = [ + "https://frontend.com", # your production frontend domain + "http://localhost:3000", # common js frontend app port + "http://localhost:5173", # vue.js from our examples + ] + + # we need to add the X-Site-ID header to the allowed headers + # Only required for single instance setup + CORS_ALLOW_HEADERS = ( + *default_headers, + "X-Site-ID", + ) + +**Manage Sites in Django Admin** + +- Go to Django Admin → Sites +- Add/edit sites with domain and name + +Example: + +.. code-block:: json + + // Manually configured via Django Admin + // you can seed the ID in the browser url while editing the site object + [ + { + "domain": "foo.example.com", + "name": "Foo Site" + }, + { + "domain": "bar.example.com", + "name": "Bar Site" + } + ] + +.. code-block:: python + + MIDDLEWARE = [ + # Required for cross-origin requests (frontend on different domain) + "corsheaders.middleware.CorsMiddleware", + + #before other middleware that depends on the site context + "djangocms_rest.middleware.SiteContextMiddleware", + + # other django and django CMS middleware (depends on your setup) + ... + ] + +Testing +~~~~~~~ + +1. Create a test home page for each site in the Django admin. +2. Publish the pages. +3. Test the API endpoints with the ``X-Site-ID`` header set to the site ID. + + +.. code-block:: bash + + # pages endpoint without path will return the home page for the site + curl -H "X-Site-ID: 2" http://localhost:8080/api/cms/pages/ + +.. note:: + The ``X-Site-ID`` header is required to query a single CMS instance. If not set, the middleware will use the current site defined in the settings. + +Implementation Guide +~~~~~~~~~~~~~~~~~~~~ + +If the basic configuration is working you can embed it into your frontend app. + +- :doc:`../how-to/01-use-multi-site` + +Authentication +-------------- + +djangocms-rest currently uses ``Session Authentication`` as the only authentication method. +This means that users must be logged into the Django CMS admin using the standard admin login page to access protected API endpoints. +In order to access the API from the frontend app, you need to configure Django ``CORS`` and +``CSRF``. + +- Only authenticated users can access the API using the ``preview`` query parameter. + +Docs +~~~~ +- `Django CMS - Internationalisation and Localisation `_ + +.. note:: + + You need to have CORS configured correctly to allow the frontend app to access the API. + See `CORS Support <../tutorial/02-installation.html#cors-support>`_. + +Configuration +~~~~~~~~~~~~~ + +.. code-block:: python + + # Additional CORS configuration for session authentication + CORS_ALLOW_CREDENTIALS = True + + # add your frontend domain(s) here + CSRF_TRUSTED_ORIGINS = [ + "https://frontend.com", + "http://localhost:3000", + "http://localhost:5173", + ] + + # allow session and csrf cookies to be sent to frontend + # required for session authentication to work + SESSION_COOKIE_SAMESITE = "None" + CSRF_COOKIE_SAMESITE = "None" + SESSION_COOKIE_SECURE = True + CSRF_COOKIE_SECURE = True + +Testing +~~~~~~~ + +1. Login to Django admin at `http://localhost:8080/admin/ `_ +2. Change the home page name, but do not publish it. +3. Visit and api endpoint with the ``preview`` query parameter. + +.. code-block:: bash + + # Adjust language if necessary + http://localhost:8080/api/en/pages/?preview=true + + +OpenAPI Documentation +--------------------- + +For interactive API documentation and client SDK generation, follow the :doc:`03-openapi-documentation` tutorial. + +This highly recommended step enables: +- Interactive API documentation with Swagger UI +- OpenAPI schema generation for client SDKs +- Type-safe frontend development \ No newline at end of file diff --git a/docs/tutorial/03-openapi-documentation.rst b/docs/tutorial/03-openapi-documentation.rst new file mode 100644 index 0000000..eb1c8ea --- /dev/null +++ b/docs/tutorial/03-openapi-documentation.rst @@ -0,0 +1,107 @@ +OpenAPI Documentation +==================== + +Set up automatic API documentation and schema generation for your djangocms-rest API. + +.. note:: + Recommended for development. It enables interactive API documentation with typed responses. + +Prerequisites +------------- + +- Completed :doc:`02-installation` guide + +Overview +-------- + +djangocms-rest is fully typed and supports OpenAPI 3 schema generation using `drf-spectacular `_. +Swagger UI and Redoc are also supported and highly recommended for development. + +Benefits +-------- + +* **Interactive API Documentation** - Browse and test endpoints directly in your browser +* **Automatic Schema Generation** - Generate OpenAPI schemas for all endpoints and plugins +* **Type Safety** - Use schema to generate type-safe client libraries for your frontend + +Installation +----------- + +.. code-block:: bash + + poetry add drf-spectacular + +Configuration +------------- + +Add to your Django settings: + +.. code-block:: python + + INSTALLED_APPS = [ + ... + 'drf_spectacular', + ... + ] + + # Add REST Framework settings + REST_FRAMEWORK = { + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + ... # other settings + } + + # Recommended settings (optional) + SPECTACULAR_SETTINGS = { + 'TITLE': 'Your Project API', + 'DESCRIPTION': 'Your project description', + 'VERSION': '1.0.0', + 'SERVE_INCLUDE_SCHEMA': False, + # other settings + } + +URL Configuration +----------------- + +Add the OpenAPI endpoints to your URL configuration: + +.. code-block:: python + + from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularJSONAPIView, + SpectacularSwaggerView, + SpectacularRedocView, + ) + + urlpatterns = [ + ... + # OpenAPI schema and documentation + path('api/schema/', SpectacularAPIView.as_view(), name='schema'), + path("api/schema-json/", SpectacularJSONAPIView.as_view(), name="schema-json"), + path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + ... + ] + +Testing +------- + +You can now access: + +- **Interactive API Documentation**: `http://localhost:8080/api/docs/ `_ +- **OpenAPI JSON Schema**: `http://localhost:8080/api/schema-json/ `_ + +Client SDK Generation +-------------------- + +.. note:: + Using `heyapi.dev `_ you can generate a client SDK for your frontend app. + +When you autocreate clients and types from OpenAPI specification with tools like `heyapi.dev `_, this will also affect the naming of those components and types, eg. +``RetrieveLanguages`` will become ``CmsRetrieveLanguages`` in the client SDK. + +Next Steps +---------- + +- Explore the :doc:`../reference/index` for detailed API documentation +- Check out :doc:`../how-to/index` for implementation guides diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst new file mode 100644 index 0000000..2332acf --- /dev/null +++ b/docs/tutorial/index.rst @@ -0,0 +1,14 @@ +Tutorial +======== + +This section provides step-by-step tutorials to get you started with djangocms-rest. + +.. toctree:: + :maxdepth: 1 + + 01-quickstart + 02-installation + 03-openapi-documentation + +.. note:: + Start with the :doc:`Quick Start <01-quickstart>` to get up and running in minutes, then follow the :doc:`Installation Guide <02-installation>` for advanced features, and set up :doc:`OpenAPI Documentation <03-openapi-documentation>` for development. From e4e8d87ff972f4eadb455fb2735d53f3ff4df771 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 15:11:42 +0200 Subject: [PATCH 02/16] docs: Update static file handling in Sphinx configuration --- docs/_static/.gitkeep | 2 ++ docs/conf.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/_static/.gitkeep diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 0000000..baefd20 --- /dev/null +++ b/docs/_static/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the _static directory is tracked by git +# Sphinx will populate this directory with static assets during build diff --git a/docs/conf.py b/docs/conf.py index 577335d..25929c7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -110,7 +110,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = ["_static"] if os.path.exists("_static") else [] # Add external CSS files (Font Awesome for GitHub icon) html_css_files = [ From 192d674161da26537231a058ae21e7c0dd5b5c86 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:37:42 +0200 Subject: [PATCH 03/16] Update docs/tutorial/02-installation.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/tutorial/02-installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index 377f685..03dcd81 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -19,7 +19,7 @@ Installation Using Poetry ~~~~~~~~~~~~ -We recommend using ``Poetry`` to manage your dependencies. It it is used as default for this documentation. +We recommend using ``Poetry`` to manage your dependencies. It is used by default in this documentation. ``pip`` can be used as equivalent. .. code-block:: bash From 9a0c2eb58045845739a43ee47cdae286b513fbe5 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:38:14 +0200 Subject: [PATCH 04/16] Update docs/tutorial/02-installation.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/tutorial/02-installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index 03dcd81..e2bdae8 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -235,7 +235,7 @@ djangocms-rest supports 2 ways to handle multi-site support: If you want to serve multiple sites from a single instance, you can use the ``SiteContextMiddleware`` to set the site context on the request. This requires ``Django Sites`` framework to be installed and configured. -Your can pass the site ID in the request headers with the ``X-Site-ID`` property set to the site ID. +You can pass the site ID in the request headers with the ``X-Site-ID`` property set to the site ID. The Middleware will then set the site context on the request. Docs From 577008cef7bb11d4a007bda117a35eb322eaaa1b Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:38:33 +0200 Subject: [PATCH 05/16] Update docs/how-to/01-use-multi-site.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/how-to/01-use-multi-site.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/01-use-multi-site.rst b/docs/how-to/01-use-multi-site.rst index 580e772..4d9e55c 100644 --- a/docs/how-to/01-use-multi-site.rst +++ b/docs/how-to/01-use-multi-site.rst @@ -3,7 +3,7 @@ Multi-Site setup with single CMS instance In this short guide, we will show you how to use the multi-site functionality in your frontend app. -We will use ``vue.js`` to fetch data from a single django CMS instance with multiple sites. This implemenation +We will use ``vue.js`` to fetch data from a single django CMS instance with multiple sites. This implementation guide can easily be adapted to other frontend frameworks. .. warning:: From 4214fcf5a9152dd74e722adfb40ef78d18ca149d Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:38:45 +0200 Subject: [PATCH 06/16] Update docs/how-to/index.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/how-to/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 9480e21..bc15739 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -28,7 +28,7 @@ All of this features can already be implemented using the API, but we plan to cr Contribute ---------- -We are always looking for contributions to improve the documentation. If you are working on an implementation scenario. Plese share your experience and create a small implementation guide for others to follow. +We are always looking for contributions to improve the documentation. If you are working on an implementation scenario. Please share your experience and create a small implementation guide for others to follow. - **GitHub Repository**: `djangocms-rest `_ From 985eafb73ab6d509efea637ade1e1361c4328522 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:38:58 +0200 Subject: [PATCH 07/16] Update docs/tutorial/02-installation.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/tutorial/02-installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index e2bdae8..8f95cc6 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -145,7 +145,7 @@ Configuration Languages Support ----------------- -- djnagocms-rest supports languages out of the box. +- djangocms-rest supports languages out of the box. - Djnago CMS needs to be configured to use languages. - A single language must always be set in order to use the API. From 359876f166c0ba9d39565e60fb4a2b303d05070d Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:39:31 +0200 Subject: [PATCH 08/16] Update docs/tutorial/02-installation.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/tutorial/02-installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index 8f95cc6..276c2d9 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -94,7 +94,7 @@ This is handy if you want to have a separate API for different parts of your app ] .. note:: - When you autocreate clients and types from OpenAPI specification with tools like `heyapi.dev `_, this will also affect the naming of those components and types,eg. + When you autocreate clients and types from OpenAPI specification with tools like `heyapi.dev `_, this will also affect the naming of those components and types, e.g. ``RetrieveLanguages`` will become ``CmsRetrieveLanguages`` in the client sdk. CORS Support From 278c082e0cb910537a6e05dadec0e6f15f3abb8c Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:41:14 +0200 Subject: [PATCH 09/16] Update reference to Plugin Creation & Serialization in plugins.rst --- docs/reference/plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index 5623ed9..aa8262a 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -15,7 +15,7 @@ Plugins Endpoints Howto ------ -- :doc:`Plugin Creation & Serialization <../how-to/011-plugin-creation>` +- :doc:`Plugin Creation & Serialization <../how-to/02-plugin-creation>` CMS Reference ------------- From 47a995d2ceeb91866b9676f85aeab89c408f68f9 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:50:27 +0200 Subject: [PATCH 10/16] Update repository references in README and installation documentation --- README.md | 2 +- docs/index.rst | 4 ++-- docs/tutorial/02-installation.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d5eb73f..7834e84 100644 --- a/README.md +++ b/README.md @@ -411,4 +411,4 @@ like to change. ## License -[BSD-3](https://github.com/fsbraun/djangocms-rest/blob/main/LICENSE) +[BSD-3](https://github.com/django-cms/djangocms-rest/blob/main/LICENSE) diff --git a/docs/index.rst b/docs/index.rst index 60a09ee..3368b05 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,8 +39,8 @@ Key Features * **REST API** — DRF-based API exposing Django CMS content for SPAs, static sites, and mobile apps. * **Typed Endpoints** — Auto-generate OpenAPI schemas for page data and plugin content. * **Plugin Serialization** — Basic support for all CMS plugins, easily extendable for custom needs. -* **Multi-site Support** — Serve multiple websites from a single instance with isolated API responses. -* **Multi-language Content** — Use the robust i18n integartion of Django CMS in your frontend. +* **Multi-Site Support** — Serve multiple websites from a single instance with isolated API responses. +* **Multi-language Content** — Use the robust i18n integration of Django CMS in your frontend. * **Preview & Draft Access** — Fetch unpublished or draft content in your frontend for editing previews. * **Permissions & Authentication** — Uses DRF- and Django-permissions for secure access control. * **Menus & Breadcrumbs** — Exposes the built-in navigation handlers from Django CMS. diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index 276c2d9..7436032 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -40,12 +40,12 @@ For the latest features, you can install a version from the GitHub repository at .. code-block:: bash - poetry add git+https://github.com/fsbraun/djangocms-rest.git + poetry add git+https://github.com/django-cms/djangocms-rest.git .. code-block:: bash # install a specific branch - poetry add git+https://github.com/fsbraun/djangocms-rest.git@feat/extended-search + poetry add git+https://github.com/django-cms/djangocms-rest.git@feat/extended-search Basic Configuration -------------------- From 206af6998ddc825e6a898a2ea731497a6d6706e4 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:57:28 +0200 Subject: [PATCH 11/16] Update installation documentation with additional references to Django authentication, custom user requirements, and security enhancements --- docs/tutorial/02-installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index 7436032..9edb4a1 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -351,7 +351,9 @@ In order to access the API from the frontend app, you need to configure Django ` Docs ~~~~ -- `Django CMS - Internationalisation and Localisation `_ +- `Django - Default authentication `_ +- `Django CMS - Custom User Requirements `_ +- `Security Enhancements for Django CMS `_ .. note:: From c572f54040f8e2c0f1bdc6086c445e49dd5ab758 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 16:59:19 +0200 Subject: [PATCH 12/16] fixes openapi benefits listing --- docs/tutorial/02-installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/02-installation.rst b/docs/tutorial/02-installation.rst index 9edb4a1..e4114fc 100644 --- a/docs/tutorial/02-installation.rst +++ b/docs/tutorial/02-installation.rst @@ -400,7 +400,9 @@ OpenAPI Documentation For interactive API documentation and client SDK generation, follow the :doc:`03-openapi-documentation` tutorial. -This highly recommended step enables: + +**OpenAPI Documentation Benefits:** + - Interactive API documentation with Swagger UI - OpenAPI schema generation for client SDKs - Type-safe frontend development \ No newline at end of file From 4a241145075e1102191a9b7b29c5fcf252df65eb Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 17:01:28 +0200 Subject: [PATCH 13/16] Clarify note on retrieving page and placeholder objects in plugin creation documentation --- docs/how-to/02-plugin-creation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/02-plugin-creation.rst b/docs/how-to/02-plugin-creation.rst index 27b3bfd..470cc04 100644 --- a/docs/how-to/02-plugin-creation.rst +++ b/docs/how-to/02-plugin-creation.rst @@ -74,7 +74,7 @@ Depending on your projects configuration, you might have to configure placeholde After adding the plugin to a placeholder, you can retrieve the content of the placeholder using the :doc:`Pages <../reference/pages>` and :doc:`Placeholders <../reference/placeholders>` endpoints. .. note:: - We first have to retrieve the page object and then the associated placeholder objects. This is necessary because django CMS placeholders are linked to the page object, not part of the page object. This allows to use preview mode and versioning. + First, we have to retrieve the page object, and then the associated placeholder objects. This is necessary because Django CMS placeholders are linked to, but not part of, the page object. This enables us to use preview mode and versioning. .. code-block:: bash From 9eeec889777199674ee29e1272c05e03c06f3db4 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 17:45:58 +0200 Subject: [PATCH 14/16] Add basic auto-serialization example --- docs/how-to/02-plugin-creation.rst | 138 ++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/docs/how-to/02-plugin-creation.rst b/docs/how-to/02-plugin-creation.rst index 470cc04..d186f5a 100644 --- a/docs/how-to/02-plugin-creation.rst +++ b/docs/how-to/02-plugin-creation.rst @@ -33,8 +33,8 @@ Use Cases You can also nest plugins inside other plugins as you would normally do in Django CMS. -Example -~~~~~~~ +Example "Hello World" +~~~~~~~~~~~~~~~~~~~~~~ Create your plugin as you would normally do in Django CMS. @@ -112,7 +112,7 @@ After adding the plugin to a placeholder, you can retrieve the content of the pl .. .. note:: - ?html=1 will render the plugin as HTML. + ?html=1 will render plugins as HTML. **Response from the placeholders endpoint:** @@ -142,6 +142,138 @@ You can retrieve all plugin details using the :doc:`Plugins <../reference/plugin .. hint:: You can setup a vue.js frontend application to handle the rendering of json data. Follow the guide `Setup Vue.js Project <01-use-multi-site.html#setup-vue-js-project>`_ to get started. +Example "Basic Serialization" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The previous example had no data to serialize, beside the generic plugin properties. Now we will create a plugin that has some actual data to serialize. + +Create a new model ``HeroPluginModel`` in ``models.py``, which will be used to store the data for the plugin. + +In this example we will create a plugin that displays a hero image with a title, description and a link to an existing django CMS page. + +.. code-block:: python + + # models.py + from cms.models.fields import PageField + from django.db import models + from cms.models import CMSPlugin + + class HeroPluginModel(CMSPlugin): + title = models.CharField(max_length=200) + description = models.TextField() + image = models.ImageField(upload_to='hero_images') + link = PageField(blank=True, null=True) + link_text = models.CharField(max_length=200, blank=True, null=True) + + def __unicode__(self): + return self.title +.. + +We now have to run migrations when creating new models. + +.. code-block:: bash + + # using app name is optional, but recommended. + python manage.py makemigrations + python manage.py migrate +.. + +We now need to configure the plugin in ``cms_plugins.py``. + +.. code-block:: python + + # cms_plugins.py + @plugin_pool.register_plugin + class HeroPlugin(CMSPluginBase): + model = HeroPluginModel + render_template = "my_app/hero_plugin.html" + name = _("Hero Plugin") + +.. + +To be sure changes are applied, you can restart the development server. + +.. code-block:: bash + + python manage.py runserver 8080 +.. + +Add the plugin to a placeholder in a page. + +Fetch the content of the placeholder using the :doc:`Placeholders <../reference/placeholders>` endpoint. See `Example "Hello World" <02-plugin-creation.html#example-hello-world>`_. + +**Response from the placeholders endpoint:** + +.. code-block:: json + + { + "slot": "content", + "label": "Content", + "language": "en", + "content": [ + { + "plugin_type": "HeroPlugin", + "title": "A custom page hero", + "description": "We can add some important teaser content.", + "image": "http://localhost:8080/media/hero_images/demo.png", + "link_text": null, + "link": "http://localhost:8080/api/en/pages/unterseite/" + } + ], + "html": "" + } + +You have received the serialized data for the plugin. You can now render the plugin in the front end, likely using a matching component for the HeroPlugin that references the set properties. + +Type Definitions +~~~~~~~~~~~~~~~~ + +Type definitions are used to define the structure of the data that is returned by the API. They are used to validate the data in the frontend application. + +You can retrieve the type definition for the plugin using the :doc:`Plugins <../reference/plugins>` endpoint. + +.. code-block:: bash + + curl -X GET "http://localhost:8080/api/plugins/" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ +.. + +**Response from the plugins endpoint:** + +.. code-block:: json + + { + "plugin_type": "HeroPlugin", + "title": "Hero Plugin", + "type": "object", + "properties": { + "plugin_type": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "image": { + "type": "string", + "format": "uri" + }, + "link_text": { + "type": "string" + }, + "link": { + "type": "integer" + } + } + } + +.. + +.. hint:: + You can automatically generate type-safe schemas for your typescript frontend application using tools like `QuickType `_ or `heyapi.dev `_ which integrates with `Zod `_ schema validation. Custom Serialization From 5eb4c45436bc9ff5a2ec4cc6591dbd9dd4902237 Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 17:49:25 +0200 Subject: [PATCH 15/16] Add hint for generating frontend URLs in plugin creation documentation --- docs/how-to/02-plugin-creation.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/how-to/02-plugin-creation.rst b/docs/how-to/02-plugin-creation.rst index d186f5a..3df85a6 100644 --- a/docs/how-to/02-plugin-creation.rst +++ b/docs/how-to/02-plugin-creation.rst @@ -225,6 +225,9 @@ Fetch the content of the placeholder using the :doc:`Placeholders <../reference/ You have received the serialized data for the plugin. You can now render the plugin in the front end, likely using a matching component for the HeroPlugin that references the set properties. +.. hint:: + If you want to use the actual frontend URL, you can either create a custom serializer or create a utility function in your frontend to generate the required URL. + Type Definitions ~~~~~~~~~~~~~~~~ From 8787265da4a3f43459316f7790ba8ab2a13cebbc Mon Sep 17 00:00:00 2001 From: Marc Widmer Date: Sun, 19 Oct 2025 17:50:38 +0200 Subject: [PATCH 16/16] Update link_text in plugin creation documentation for clarity --- docs/how-to/02-plugin-creation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/02-plugin-creation.rst b/docs/how-to/02-plugin-creation.rst index 3df85a6..df64f3c 100644 --- a/docs/how-to/02-plugin-creation.rst +++ b/docs/how-to/02-plugin-creation.rst @@ -216,7 +216,7 @@ Fetch the content of the placeholder using the :doc:`Placeholders <../reference/ "title": "A custom page hero", "description": "We can add some important teaser content.", "image": "http://localhost:8080/media/hero_images/demo.png", - "link_text": null, + "link_text": "Read more", "link": "http://localhost:8080/api/en/pages/unterseite/" } ],