Skip to content

Latest commit

 

History

History
176 lines (131 loc) · 5.37 KB

File metadata and controls

176 lines (131 loc) · 5.37 KB
icon lucide/layout-template

Blueprints and Views

CKAN extensions expose HTTP interfaces, endpoints, and web pages using Flask Blueprints. View functions are placed inside a views.py file (or views/ submodule) and registered automatically via blueprints blanket.


Defining Blueprints

Define a Blueprint instance and map your views. To ensure consistency in error states, you can register custom error handlers to render clean error templates:

from __future__ import annotations

import logging
from flask import Blueprint, jsonify
import ckan.plugins.toolkit as tk

log = logging.getLogger(__name__)

# 1. Define the blueprint
bp = Blueprint("myextension", __name__)

# Optional: export the blueprint explicitly
__all__ = ["bp"]


# 2. Register global error handlers for this blueprint
def not_found_handler(error: tk.ObjectNotFound) -> tuple[str, int]:
    """Render custom 404 page for ObjectNotFound exceptions."""
    return (
        tk.render(
            "error_document_template.html",
            {
                "code": 404,
                "content": f"Object not found: {error.message}",
                "name": "Not Found",
            },
        ),
        404,
    )

bp.register_error_handler(tk.ObjectNotFound, not_found_handler)

Implementing View Routes

Implement standard Flask routes. Use CKAN's toolkit functions to check permissions, render Jinja2 templates, and read request params.

@bp.route("/items")
def index() -> str:
    """Render items list page."""
    try:
        items = tk.get_action("myextension_item_list")({}, {}) # (1)!
    except tk.NotAuthorized:
        return tk.abort(403, "Not authorized to view items.")

    # Render templates placed inside the 'templates/' folder
    return tk.render("myextension/index.html", {"items": items})


@bp.route("/api/items/<item_id>") # (2)!
def api_show(item_id: str):
    """API endpoint returning item details."""
    try:
        # If you did not add global 404 error handler,
        # add explicit ObjectNotFound handler
        item = tk.get_action("myextension_item_show")({}, {"id": item_id})
    except tk.NotAuthorized:
        return tk.abort(403, "Not authorized to view the item.")

    return jsonify(item)
  1. You can pass empty context into auth functions and actions, when they are called from views. CKAN will add missing user and session properties automatically.
  2. Here you can use id as a name of the parameter, but the recommendation is to include entity type into parameter name. In this way you avoid shadowing of global function id() and can extend route in future with additional IDs. Think of confusing resource.show endpoint, which has id pointing at the package and resource_id pointint at the resource: it would be much better if parameters were named package_id and resource_id instead of id and resource_id.

Context Autopopulation in Views

When calling actions (tk.get_action) or checking permissions (tk.check_access) inside a Flask blueprint view function, you do not need to manually populate the context dictionary with the current logged-in user or the database session.

CKAN's internal Flask context wrappers automatically intercept logic layer calls originating from view routes. Under the hood, CKAN enriches the context by injecting the active database session and the currently authenticated session user.

For standard actions, passing an empty context dictionary {} is the recommended practice.

/// admonition type: example

@bp.route("/item/<item_id>/edit")
def edit_item(item_id: str):
    # CKAN automatically populates {} with the logged-in user and active DB session
    try:
        tk.check_access("myextension_item_update", {}, {"id": item_id})
        item = tk.get_action("myextension_item_show")({}, {"id": item_id})
    except tk.NotAuthorized:
        return tk.abort(403)
    except tk.ObjectNotFound:
        return tk.abort(404)

    return tk.render("myextension/edit.html", {"item": item})

///

You should pass explicit context parameters (such as a hardcoded username or a specific database state) only if the view explicitly needs to execute logic on behalf of a different user (for example, an administrator performing a resource transfer on behalf of another user, or executing system task tasks via an internal daemon context):

/// admonition type: example

@bp.route("/admin/transfer/<item_id>")
def admin_transfer(item_id: str):
    # Only admins can access this route
    tk.check_access("sysadmin", {}, {})

    # Explicitly run the action on behalf of the target user, not the admin
    context = {"user": "target_user_name"}
    tk.get_action("myextension_item_update")(context, {"id": item_id, "owner_id": "new_owner"})

    return "Transferred successfully"

///


Auto-Registration

Apply the @tk.blanket.blueprints decorator to your plugin class in plugin.py. This scans the views.py file for Flask blueprints and automatically hooks them into the CKAN routing framework.

import ckan.plugins as p
import ckan.plugins.toolkit as tk

@tk.blanket.blueprints  # (1)
class MyExtensionPlugin(p.SingletonPlugin):
    pass
  1. Automatic blueprint discovery removes the need to manually implement the IBlueprint interface or write route-registration boilerplate methods.