Template Views

Use normalized View objects for consistent access to endpoints, schemas, and tags

7 min read 1431 words

Bengal provides View objects that normalize access to complex data in templates. Instead of dealing with different data structures depending on configuration, Views give you consistent properties every time.

Availability: All View filters are automatically registered and available in all template engines (Jinja2, Kida, and custom engines).

Why Views?

OpenAPI endpoints can come from:

  • Individual pages (one file per endpoint)
  • Consolidated mode (all endpoints in one section)
  • DocElements with typed metadata

Without Views, your template would need conditionals everywhere:

{# Without Views - messy conditionals #}
{% if endpoint.typed_metadata %}
  {{ endpoint.typed_metadata.method }}
{% elif endpoint.metadata %}
  {{ endpoint.metadata.method }}
{% else %}
  {{ endpoint.method }}
{% end %}

With Views, the filter handles all variations automatically:

{# With Views - clean and consistent #}
{% for ep in section | endpoints %}
  {{ ep.method }} {{ ep.path }}
{% end %}

The filter automatically detects whether endpoints come from consolidated mode (DocElements) or individual pages, ensuring consistent access regardless of configuration.

Available Views

Filter Returns Use For
| endpoints EndpointView[] OpenAPI endpoints in a section
| schemas SchemaView[] OpenAPI schemas in a section
| tag_views TagView[] Taxonomy tags with counts
| tag_view TagView | None Single tag by slug (returnsNoneif not found)

EndpointView

Normalize OpenAPI endpoints for templates. Automatically handles both consolidated mode (all endpoints in one section) and individual mode (one page per endpoint).

Usage

{% let eps = section | endpoints %}

{% for ep in eps %}
<div class="endpoint {{ 'deprecated' if ep.deprecated else '' }}">
  <span class="method method--{{ ep.method | lower }}">{{ ep.method }}</span>
  <a href="{{ ep.href }}">{{ ep.path }}</a>
  <p>{{ ep.summary }}</p>
</div>
{% end %}

Properties

Property Type Description
method str HTTP method (GET,POST,PUT,DELETE, etc.)
path str URL path with parameters (/users/{id})
summary str Short description
description str Full description
deprecated bool Whether endpoint is deprecated
href str Link to endpoint (anchor#idin consolidated mode, page URL in individual mode). Always valid, neverNone.
has_page bool Whether individual page exists
operation_id str | None OpenAPI operationId
tags tuple[str, ...] Endpoint tags
typed_metadata Any FullOpenAPIEndpointMetadata(advanced)

Examples

<nav class="api-nav">
  {% for ep in section | endpoints %}
  <a href="{{ ep.href }}" class="api-nav__link">
    <span class="badge badge--{{ ep.method | lower }}">{{ ep.method }}</span>
    {{ ep.path }}
  </a>
  {% end %}
</nav>
<div class="api-cards">
  {% for ep in section | endpoints %}
  <a href="{{ ep.href }}" class="api-card{% if ep.deprecated %} api-card--deprecated{% end %}">
    <header class="api-card__header">
      <span class="method method--{{ ep.method | lower }}">{{ ep.method }}</span>
      <code>{{ ep.path }}</code>
    </header>
    {% if ep.summary %}
    <p class="api-card__summary">{{ ep.summary }}</p>
    {% end %}
    {% if ep.deprecated %}
    <span class="badge badge--warning">Deprecated</span>
    {% end %}
  </a>
  {% end %}
</div>
{# Group endpoints by their first tag #}
{% let eps = section | endpoints %}
{% let by_tag = eps | group_by('tags.0') %}

{% for tag, tag_eps in by_tag %}
<section>
  <h2>{{ tag }}</h2>
  {% for ep in tag_eps %}
    <a href="{{ ep.href }}">{{ ep.method }} {{ ep.path }}</a>
  {% end %}
</section>
{% end %}

SchemaView

Normalize OpenAPI schemas for templates. Provides consistent access to schema properties and metadata.

Usage

{% let schs = section | schemas %}

{% for schema in schs %}
<div class="schema">
  <a href="{{ schema.href }}">{{ schema.name }}</a>
  <span class="type">{{ schema.schema_type }}</span>
</div>
{% end %}

Properties

Property Type Description
name str Schema name (User,OrderRequest)
schema_type str Type (object,array,string, etc.)
description str Schema description
href str Link to schema page or anchor (#schema-{name}if no page). Always valid, neverNone.
has_page bool Whether individual page exists
properties dict[str, Any] Property definitions
required tuple[str, ...] Required property names
enum tuple[Any, ...] | None Enum values (if applicable)
example Any Example value (if provided)
typed_metadata Any FullOpenAPISchemaMetadata(advanced)

Examples

<div class="schema-list">
  {% for schema in section | schemas %}
  <a href="{{ schema.href }}" class="schema-card">
    <span class="schema-card__name">{{ schema.name }}</span>
    <span class="schema-card__type">{{ schema.schema_type }}</span>
    {% if schema.description %}
    <p class="schema-card__desc">{{ schema.description | truncate(80) }}</p>
    {% end %}
  </a>
  {% end %}
</div>
{% for schema in section | schemas %}
<article class="schema-doc">
  <h3>{{ schema.name }}</h3>
  <p>{{ schema.description }}</p>

  {% if schema.properties %}
  <table class="props-table">
    <thead>
      <tr><th>Property</th><th>Required</th></tr>
    </thead>
    <tbody>
      {% for prop_name, prop_def in schema.properties %}
      <tr>
        <td><code>{{ prop_name }}</code></td>
        <td>{{ '✓' if prop_name in schema.required else '' }}</td>
      </tr>
      {% end %}
    </tbody>
  </table>
  {% end %}
</article>
{% end %}

TagView

Normalize taxonomy tags for templates. Provides consistent access to tag data including counts, percentages, and descriptions.

Usage

{# All tags #}
{% for tag in site | tag_views %}
  <a href="{{ tag.href }}">{{ tag.name }} ({{ tag.count }})</a>
{% end %}

{# With options #}
{% for tag in site | tag_views(limit=10, sort_by='count') %}
  {{ tag.name }}
{% end %}

Properties

Property Type Description
name str Display name
slug str URL-safe slug
href str URL to tag page
count int Number of posts with this tag
description str Tag description (if defined)
percentage float Percentage of total posts (for tag clouds)

Filter Options

Option Default Description
limit None Maximum tags to return
sort_by 'count' Sort field:'count','name', or'percentage'

Examples

<div class="tag-cloud">
  {% for tag in site | tag_views %}
  {# Size based on percentage #}
  {% let size = 0.8 + (tag.percentage / 50) %}
  <a href="{{ tag.href }}"
     class="tag-cloud__tag"
     style="font-size: {{ size }}em">
    {{ tag.name }}
  </a>
  {% end %}
</div>
<aside class="sidebar-tags">
  <h3>Popular Topics</h3>
  <ul>
    {% for tag in site | tag_views(limit=10, sort_by='count') %}
    <li>
      <a href="{{ tag.href }}">
        {{ tag.name }}
        <span class="count">{{ tag.count }}</span>
      </a>
    </li>
    {% end %}
  </ul>
</aside>
{# Get a specific tag by slug #}
{# Returns None if tag doesn't exist, so always check #}
{% let python_tag = 'python' | tag_view %}

{% if python_tag %}
<div class="featured-tag">
  <a href="{{ python_tag.href }}">
    {{ python_tag.name }}{{ python_tag.count }} posts
  </a>
  {% if python_tag.description %}
  <p>{{ python_tag.description }}</p>
  {% end %}
</div>
{% end %}
<nav class="tag-index">
  {% for tag in site | tag_views(sort_by='name') %}
  <a href="{{ tag.href }}">{{ tag.name }}</a>
  {% end %}
</nav>

When to Use Views vs Raw Data

Use Views When Use Raw Data When
Building reusable theme components Custom one-off queries
Need consistent property access Need full page/section access
Working with OpenAPI autodoc Building non-autodoc features
Rendering tag clouds/lists Need raw taxonomy dict
Switching between consolidation modes Accessing page metadata directly

Key Benefit: Views eliminate the need for conditional logic when working with OpenAPI data that may come from different sources (consolidated vs. individual pages).

Accessing Raw Data

Views are filters on sections or site. You can still access raw data:

{# Raw page access #}
{% for page in section.pages %}
  {{ page.title }}
{% end %}

{# Raw taxonomy access #}
{% for tag_slug, tag_data in site.taxonomies.tags %}
  {{ tag_data.name }}: {{ tag_data.pages | length }} posts
{% end %}

Seealso