Ocyan plugin RoadRunner Bootstrap 5
===================================

About RoadRunner Bootstrap 5
----------------------------
``ocyan.plugin.roadrunner_bs5`` is the Bootstrap 5 integration layer for the
RoadRunner page builder in Ocyan projects.

It does not replace the editor core. The core editor lives in
``wagtail-roadrunner`` under the ``rr`` app and provides the grid-based
``RoadRunnerField`` editor, preview rendering, telepath adapters, and the base
streamfield block runtime.

This package adds the Ocyan-facing Bootstrap 5 page model, block registration,
preview templates, and optional integrations used by Ocyan/Wagtail projects.

Forward path
------------
Bootstrap 5 is the forward path for RoadRunner in MandelBlogStack/Ocyan.

``ocyan.plugin.roadrunner_bs4`` and ``roadrunner.bs4`` should be treated as
legacy/support-only packages for existing projects and migration work. New work
should target:

* ``wagtail-roadrunner`` as the editor/runtime core
* ``ocyan.plugin.roadrunner_bs5`` as the canonical Ocyan integration layer

Architecture and ownership
--------------------------
Current ownership is split intentionally:

* ``wagtail-roadrunner``
  Core page-builder/editor runtime (``rr`` app), telepath UI, base streamfield
  blocks, preview pipeline, editor JS, and RoadRunnerField implementation.

* ``ocyan.plugin.roadrunner_bs5``
  Ocyan plugin wrapper for Bootstrap 5 projects. It contributes:

  * ``RoadrunnerPage`` page model
  * block registration via ``ROADRUNNER_REGISTRY_FUNCTION``
  * BS5-specific block templates and previews
  * optional Wagtail/Oscar chooser integration hooks
  * BS4-to-BS5 conversion utilities

Asset/runtime ownership
-----------------------
The current runtime surface is split:

* ``wagtail-roadrunner`` provides the main editor assets under ``rr/static/roadrunner``
* ``ocyan.plugin.roadrunner_bs5`` provides:

  * front-end page template:
    ``ocyan/plugin/roadrunner_bs5/templates/roadrunner/roadrunner_page.html``
  * plugin block templates under ``templates/roadrunner_bs5/blocks``
  * form/preview templates for plugin blocks
  * Wagtail admin JS bundle under ``static/wagtailadmin/js/roadrunner_bs5.js``
  * plugin SCSS source under ``static/roadrunner_bs5/scss/style.scss``

Known limitations
-----------------
This package is usable, but it is not yet a fully cleaned-up canonical layer.

Current limitations include:

* partial coexistence with legacy BS4 packages in the wider ecosystem
* thin automated test coverage in this package
* asset ownership is documented but not yet simplified
* some optional webshop/Oscar integrations still depend on legacy BS4 template
  references elsewhere in the ecosystem
* the editor runtime still depends on jQuery in ``wagtail-roadrunner``
* this package should not be treated as a complete replacement for every
  historical BS4 custom block without migration review

Remaining BS4 dependency edges
------------------------------
This package is the forward path, but the broader stack still contains BS4
dependency edges that must be accounted for during migrations:

* ``ocyan.plugin.wagtail_oscar_integration`` still references several
  ``roadrunner_bs4`` block templates in existing code paths
* BS4-to-BS5 conversion utilities in this package intentionally depend on the
  legacy schema/runtime during migration
* some projects may still subclass BS4 RoadRunner page models and require
  explicit conversion work before removing BS4
* legacy custom blocks registered with BS4 conventions do not automatically
  work in BS5 and must be re-registered for ``rr``/BS5

This means BS4 can be support-only, but not yet deleted blindly from live
projects.

Installation
------------
Add `ocyan.plugin.roadrunner_bs5` to your projects dependencies.

Adding custom blocks in a project
---------------------------------

To add a custom block to a project there are 3 things you need to do.

1. Write a block
2. Write the preview for the block
3. Register the block with roadrunner bs5

Writing blocks is documented in the wagtail docs: https://docs.wagtail.org/en/latest/advanced_topics/customisation/streamfield_blocks.html
Writing previews for blocks is documented in the wagtail-roadrunner docs: https://git.mandelblog.com/mandel-plugins/wagtail-roadrunner

Registering a block with roadrunner bs5 can be done by implementing your own
``ROADRUNNER_REGISTRY_FUNCTION``.
This function is passed the default blocks that come with wagtail-roadrunner, so
if you want you can drop some functionality you don't need. Here is an example:

```
# a file named myproject/customblocks.py
from ocyan.plugin.roadrunner_bs5.utils import get_roadrunner_block_choices
from .blocks import (
    Parallax_header,
    Custom_content_block,
    Custom_content_block2,
    ImageRowSlider,
)


def get_custom_block_choices(initial_blocks):
    return get_roadrunner_block_choices(
        initial_blocks  # or maybe filter the initial block to remove some?
        + [
            ("Parallax_header", Parallax_header()),
            ("Custom_content_block", Custom_content_block()),
            ("Custom_content_block2", Custom_content_block2()),
            ("ImageRowSlider", ImageRowSlider()),
        ]
    )
```
You then have to register this function with wagtail-roadrunner as django setting
like this:
```
ROADRUNNER_REGISTRY_FUNCTION = "myproject.customblocks.get_custom_block_choices"
```

Now you have added the 4 custom blocks to wagtail-roadrunner bs5.

Focused verification
--------------------
Recommended focused checks for this package:

.. code-block:: bash

   git diff --check
   ruff check ocyan/plugin/roadrunner_bs5
   black --check ocyan/plugin/roadrunner_bs5
   bin/manage check
   bin/manage makemigrations --check --dry-run roadrunner_bs5

If you want a narrow smoke test for the package itself, run:

.. code-block:: bash

   bin/manage test ocyan.plugin.roadrunner_bs5

Convert a bs4 project to bs5
----------------------------

Converting bs4 to bs5 projects is an iterative process, you will get a little
bit further each time, but you might need to restart a couple of times, so make
sure you save a copy of the original database before beginning. When doing this
iterative migration process, you might have migrations that change models and
remove fields. You MUST NOT COMMIT THESE MIGRATIONS TO YOUR REPOSITORY YET.
YOU HAVE TO RUN bs4_to_bs5 BEFORE DOING ANY DATABASE MIGRATIONS THAT CHANGE MODELS.


To start the conversion the following stuff must be changed and committed to your
repository:

0. Add ocyan.plugin.roadrunner_bs5 to the repository.
1. BS5 has no snippets. Any snippets used in the project will be rendered to html
   and the rendered result stuck into HTML blocks. If the snippets are used heavily
   and must remain they need to be converted to blocks before starting the
   migration.
2. Templates will be converted to bs5 and copied from the roadrunner_templates
   to the rr_templates table
3. Roadrunner Page content will be converted to bs5 but id's and slugs remain the same.
4. Not all blocks from bs4 exist in bs5, replacements will be generated and
   marked with a css class indicating the old block. Some css to keep layout the
   same might be required!
5. Offsets don't exist in bs5. All offsets configured in bs4 columns will be
   converted to css classes and added as classes in tthe style pane in bs5.
6. Custom blocks need to be registered with bs5. ``register_block`` DOES NOTHING
   in bs5 and your blocks will not exist. You need to define a custom ROADRUNNER_REGISTRY_FUNCTION
   and register all you blocks. This function MUST calls
   ``ocyan.plugin.roadrunner_bs5.utils.get_roadrunner_block_choices``
   and NOT ``from ocyan.plugin.roadrunner_bs4.utils import get_roadrunner_block_choices
   because during the transition we need know which blocks are missing from
   registration and might need replacements.
7. Replacements for blocks can be registered with
   ``ocyan.plugin.roadrunner_bs5.convert.map.blockmap.register``. You must
   provide the block name and the transformation function. This function will
   get the original streamfield data passed as a dict and you need to transform
   it into something bs5 understands. For examples look in: ``ocyan.plugin.roadrunner_bs5.convert.blocks``
9. When trying to convert the conversion will stop and report any unknown block types.
   It could be you forgot to register these block types, which you should correct.
   It is also possible there is stale data in the database from types that nolonger
   exist in the project. Wagtail will ignore this data, but bs4_to_bs5 will not!
   You have to register a transformation function with ``ocyan.plugin.roadrunner_bs5.convert.map.blockmap.register``
   for the conversion to continue. It is sufficient to register ``ocyan.core.utils.identity``, eg:
   ``blockmap.register("contactform1", identity)``. That will just accept the data unchanged.
10. If there are custom pages with RoadRunnerFields you can add the to the conversion
    with the pagetype flag: ``--pagetype pages.ContentPage``
11. If there are richtext fields that remain unconverted, you must specify their
    field names to add the to the conversion process: ``--richtext-key=answer``
12. If there are images that are not working after the conversion you must specify
    their field names with the ``--image-key`` flag: ``--image-key=logo``

If there are changes to models and blocks and such and you have migrations and
code changes, MAKE SURE TO NOT ADD THEM YET YOU MUST CONVERT BEFORE RUNNING MIGRATIONS
OK???

Example of Makefile with conversion command:

```
convert:
	manage.py bs4_to_bs5 --pagetype pages.ContentPage --pagetype pages.HomePage --pagetype pages.FreePage --pagetype pages.TeamPage --pagetype pages.ContactPage --pagetype pages.BlogPageList --pagetype pages.BlogPage --pagetype pages.ProductListPage --pagetype pages.ProductPage --richtext-key=quote  --richtext-key=answer
```


Convert bs4 subclassed page to bs5 subclassed page
--------------------------------------------------

1. Add `ocyan.plugin.roadrunner_bs4` to the test dependencies in the setup.py. We need this for the migrations to work while converting and migration on productin.

2. Create conversion command like in schiphorstbv: https://git.mandelblog.com/mandel/schiphorstbv/src/branch/master/schiphorstbv/management/commands/convert_chooserpages.py. 
   This converts the content and creates a record in the bs5 page with the correct ptr_id

3. Change the baseclass on the model from bs4 to bs5

4. Create a custom migration for changing the bases of the model in the state and change the primary key to the new bs5 key

```python
from django.db import models
import django.db.models.deletion

from ocyan.plugin.roadrunner_bs5.convert.migrations import Bs4ToBs5BasePageMigration


class Migration(Bs4ToBs5BasePageMigration):
    new_bases = ('roadrunner_bs5.roadrunnerpage',)
    model_name = "ChooserPage"
    old_fk = "roadrunnerbootstrapfourpage_ptr"
    new_fk = "roadrunnerpage_ptr"
    new_field = models.OneToOneField(auto_created=True, default=1, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='roadrunner_bs5.roadrunnerpage')
    
    dependencies = [
        ('schiphorstbv', '0013_maincategory_standardcategory_subcategory'),
    ]
```

5. 
   - Run: `manage.py squashmigrations appname 0001 00xx_finalmigration`
   - After that, move your `migrations` folder to `_migrations`
   - Run: `manage.py makemigrations appname`
   - Create `0001_squashed.py` in _migrations folder and paste the content of `0001_initial.py` in `0001_squashed.py`
   - Remove initial=True and add the squashed = from the squashed migration created in the first step.
   - Remove the `migrations` folder
   - Rename `_migrations` to `migrations`
   - Test migrations from scratch (new database) and test them from a not migrated database. They should both still work.

6. When the site has been upgraded on live we can remove the old migrations and only leave our `0001_squashed` in the repo
   Also remove the `ocyan.plugin.roadrunner_bs4` dependencies from the setup.py and uninstall `ocyan.plugin.roadrunner_bs4` from staging and production.

