# Ocyan plugin Wagtail Blog

`ocyan.plugin.wagtail_blog` provides a small, extendable Wagtail blog layer for
the MandelBlogStack/Ocyan stack. It is intentionally lightweight: the plugin
owns the page models, default templates, and a narrow set of query helpers, and
expects projects/themes to override templates or helper classes when they need
different presentation or editorial behavior.

## What it provides

- `BlogPageList`: blog overview/list page
- `BlogPage`: standard blog article page
- `PageMakerBlogPage`: RoadRunner-based article page for advanced layouts
- optional Coyote stream block extension for blog highlights
- an opt-in premium template pack for list/detail presentation

## Installation

Add `ocyan.plugin.wagtail_blog` to your project dependencies and enable it in
your project plugin configuration.

## Optional integrations

- `wagtail-roadrunner`: required only for `PageMakerBlogPage`
- `ocyan.plugin.coyote`: optional, only needed for the blog highlight stream
  block extension

The core list/detail blog pages remain usable even if a project does not use
Coyote stream blocks.

## Configuration

The plugin works without custom settings. One optional setting is available in
`config.json`:

- `items_per_page` (number, default `10`): number of blog items per overview
  page

## Extension points

The plugin keeps its query and pagination behavior override-friendly.

Utility override hooks exposed through Ocyan class loading:

- `ocyan_plugin_wagtail_blog.utils.parse_items_per_page`
- `ocyan_plugin_wagtail_blog.utils.paginate_queryset`
- `ocyan_plugin_wagtail_blog.utils.recent_from_queryset`

Model override seams:

- `BlogPageList.get_blog_queryset()`
- `BlogPageList.get_items_per_page()`
- `BlogPageList.get_paginated_blogs(request)`
- `BlogPage.get_sibling_blog_queryset()`
- `BlogPage.get_recent_blog_queryset()`
- `BlogPage.get_recent_blogs()`
- `CoyoteBlogsBlock.get_blog_items_queryset(value)`

Templates are also intended to be project/theme overridable:

- `blog/blog_list.html`
- `blog/blog_item.html`
- `blog/pagemaker_blog_item.html`
- `blog/blog_image.html`
- `blog/packs/premium/blog_list.html`
- `blog/packs/premium/blog_item.html`
- `blog/packs/premium/pagemaker_blog_item.html`

Premium presentation partials are also plugin-owned and safe to override
individually:

- `blog/partials/blog_overview_intro.html`
- `blog/partials/blog_overview_card.html`
- `blog/partials/blog_meta.html`
- `blog/partials/blog_detail_main.html`
- `blog/partials/blog_detail_sidebar.html`
- `blog/partials/blog_canonical_links.html`
- `blog/partials/blog_structured_data.html`
- `blog/packs/premium/partials/blog_overview_intro.html`
- `blog/packs/premium/partials/blog_overview_card.html`
- `blog/packs/premium/partials/blog_detail_main.html`
- `blog/packs/premium/partials/blog_detail_sidebar.html`

This keeps the default design reusable while allowing projects and themes to
replace only the pieces they need.

## Template styles

`BlogPageList` now owns the visual template pack selection through the
`template_style` field:

- `default`: preserves the original list/detail templates
- `premium`: uses the premium BS5-oriented template pack

The style selector is stored only on `BlogPageList`. Detail pages inherit from
their parent overview page:

- `BlogPageList.get_list_template_name()`
- `BlogPageList.get_detail_template_name()`
- `BlogPageList.get_pagemaker_detail_template_name()`
- `BlogPage.get_template()`
- `PageMakerBlogPage.get_template()`

This keeps the contract extendable: projects can override either the default
templates, the premium pack, or the helper methods that resolve template names.

Static CSS remains compiled and shipped:

- `wagtail_blog/css/blog.css`
- `wagtail_blog/css/blog-premium.css`

There is no runtime SCSS loading in either template pack.

## Template contract

The default templates assume these context values:

- blog overview:
  - `page`
  - `blogs` (paginated items)
  - `paginator`
  - `current_page`
- blog detail:
  - `page`
  - `recent_blogs`

Projects can safely override presentation templates without changing the model
API as long as they keep using those context values.

## SEO and structured data

The plugin now exposes lightweight SEO helpers without adding new content-model
fields:

- `BlogPage.get_meta_description()`
- `BlogPageList.get_meta_description()`
- `BlogPage.get_structured_data()`
- `BlogPageList.get_structured_data()`
- `BlogPage.get_structured_data_json()`
- `BlogPageList.get_structured_data_json()`

Meta description fallback for blog articles is:

1. `search_description`
2. stripped `intro`
3. `None`

Structured data is rendered through an overridable partial:

- `blog/partials/blog_structured_data.html`

Canonical and paginated SEO link tags are rendered through a separate
overridable partial:

- `blog/partials/blog_canonical_links.html`

The default canonical behavior is conservative and explicit:

- the first overview page canonical points to the base overview URL
- paginated overview pages canonical to their own `?p=<number>` URL
- paginated overview pages emit `rel="prev"` / `rel="next"` when applicable
- article pages canonical to their detail URL

`PageMakerBlogPage` uses the blog-owned template override
`blog/pagemaker_blog_item.html`, which extends the generic Roadrunner BS5 page
template and injects the same blog canonical / structured-data hooks as the
standard detail page.

The default schema output is intentionally conservative:

- blog overview pages emit `CollectionPage`
- blog article pages emit `BlogPosting`

Optional fields such as description, image, and modified date are only included
when the relevant data is available.

## Legacy field note

`BlogPage.magento_id` remains present for backward compatibility with older
project integrations. It should be treated as a legacy field and not as a
required part of the generic blog contract.

## Testing

Focused checks for this plugin:

```bash
/Users/motolaniolaiya/kitchensink/.venv/bin/ruff check ocyan/plugin/wagtail_blog
/Users/motolaniolaiya/kitchensink/.venv/bin/black --check --exclude 'migrations/*' ocyan/plugin/wagtail_blog
```

Plugin tests can then be run through the host project or a minimal Ocyan/Wagtail
test host, depending on the environment available.
