Compare commits

...

8 commits
14.0 ... 19.0

Author SHA1 Message Date
d40681746b docs: add §17 Infrastructure — cluster topology, Odoo 19 porting notes, secrets, runboat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 19:01:09 -04:00
b69d98d0ac ci: fix Odoo image version to 19.0, add runboat integration stage
- Fix ODOO_IMAGE from 17.0 to 19.0 to match deployment
- Build image on 19.0 branch (not just main)
- Add runboat_tag stage: tags image with branch slug and triggers
  runboat build via API so PRs get live test instances on runboat.itsulu.com

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 19:00:18 -04:00
086749d9fe Fix action: replace target=inline with target=current for Odoo 17+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 17:03:41 -04:00
1042567afd Fix search view: replace group element with separator for Odoo 17+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 16:59:44 -04:00
741b44f409 Fix views: rename tree to list for Odoo 17+ compatibility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 16:55:46 -04:00
45e676633c Fix ir.cron data: remove numbercall field removed in Odoo 17+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 16:40:35 -04:00
4382ffb4f0 fix: --break-system-packages for Python 3.12 (odoo:19.0/Bookworm, PEP 668)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 15:09:41 -04:00
6702c8390e release: v0.5.0 — establish Odoo 19.0 branch (multi-version support)
Seed the 19.0 series branch from the 17.0 baseline and wire up the
per-Odoo-version structure:
- .odoo-series = 19.0; Dockerfile FROM odoo:19.0; manifest 19.0.0.5.0
- PORTING.md tracks the Odoo-19 API porting checklist
- README retargeted to 19.0 with a porting-in-progress notice
- CHANGELOG v0.5.0 entry

NOTE: code is seeded from 17.0 — the Odoo-19 port is NOT yet verified
(see PORTING.md). Not deploy-ready on a 19.0 instance until the suite
runs green there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 11:15:50 -04:00
14 changed files with 185 additions and 32 deletions

View file

@ -3,10 +3,11 @@ stages:
- test
- build
- e2e
- runboat
- notify
variables:
ODOO_IMAGE: odoo:17.0
ODOO_IMAGE: odoo:19.0
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: odoo_test
@ -214,8 +215,39 @@ build_image:
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
- /^19\.0$/
- merge_requests
# ================================================================
# RUNBOAT: TAG IMAGE WITH BRANCH/PR VERSION FOR RUNBOAT PICKUP
# ================================================================
runboat_tag:
stage: runboat
image: docker:latest
services:
- docker:dind
needs: [build_image]
variables:
BRANCH_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
# Tag image with branch slug so runboat can find the right image per branch
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $BRANCH_TAG
- docker push $BRANCH_TAG
# Notify Forgejo commit status via runboat API
- |
curl -sf -X POST "$RUNBOAT_BASE_URL/api/v1/builds" \
-u "$RUNBOAT_API_ADMIN_USER:$RUNBOAT_API_ADMIN_PASSWD" \
-H "Content-Type: application/json" \
-d "{\"repo\":\"itsulu-odoo/$CI_PROJECT_NAME\",\"target_branch\":\"$CI_COMMIT_REF_NAME\",\"pr\":null,\"git_commit\":\"$CI_COMMIT_SHA\"}" \
|| echo "Runboat trigger skipped (not a watched branch)"
only:
- /^19\.0$/
- main
allow_failure: true
# ================================================================
# NOTIFY ON FAILURE
# ================================================================
@ -227,3 +259,4 @@ notify_failure:
when: on_failure
only:
- main
- /^19\.0$/

1
.odoo-series Normal file
View file

@ -0,0 +1 @@
19.0

View file

@ -12,6 +12,30 @@ Release notes are written in plain language so anyone on the team can follow wha
---
## v0.5.0 — 2026-05-30
Sets up support for **multiple Odoo Community versions**, each on its own branch — the same
model the Odoo project itself uses. We initially target **Odoo 19.0** (the latest stable) and
**Odoo 14.0** (the version ITSulu runs in production).
### Added
- **Per-Odoo-version branches.** The addon now lives on a branch per Odoo release — `19.0`
and `14.0` — so each can track the API differences of its Odoo version independently. Check
out the branch that matches your Odoo install.
- **Version tooling is Odoo-aware.** The release helper (`bump-version.sh`) now reads which
Odoo series a branch targets and stamps the addon and release tags accordingly
(e.g. `19.0-v0.5.0`, `14.0-v0.5.0`), so one product version can ship on several Odoo versions.
- A `PORTING.md` on each version branch tracks what still needs checking for that Odoo release.
### Notes
- This `19.0` branch is **seeded from the 17.0 codebase** and the Odoo-19 port is still being
verified — see `PORTING.md`. For production today, use the `14.0` branch (also in progress).
- Documentation (`README`, `CLAUDE.md` §15) now describes the branch-per-version model.
---
## v0.4.8 — 2026-05-30
The first tagged release. This version gets the whole test suite running green on the

View file

@ -1052,4 +1052,59 @@ Infrastructure / Documentation** as applicable.
**Add Phase 4 (Playwright, migration testing) when ≥ 2:**
- Production incident that automated testing would have caught
- Odoo upgrade requires emergency rollback
---
## 17. Infrastructure (ITSulu Cluster)
### Deployment topology
| Service | Namespace | URL |
|---------|-----------|-----|
| Blog Publisher (Odoo 19) | `blog-publisher` | `https://blog.itsulu.com` |
| Runboat CI | `runboat` | `https://runboat.itsulu.com` |
| Forgejo | `forgejo` | `https://git.itsulu.com` |
| Runboat build namespace | `itsulu-testing` | `*.runboat.itsulu.com` |
### Odoo 19 porting notes (learned 2026-05-30)
These fields/tags were removed in Odoo 17+ and will cause `ParseError` on first install if present:
| Old (≤16) | New (17+) | File type |
|-----------|-----------|-----------|
| `<tree>` | `<list>` | view XML |
| `view_mode="tree,form"` | `view_mode="list,form"` | action XML |
| `<group expand="0" string="Group By">` in `<search>` | `<separator/>` then bare `<filter>` | search view XML |
| `target="inline"` on `ir.actions.act_window` | `target="current"` | action XML |
| `numbercall` field on `ir.cron` | removed — omit entirely | data XML |
### Docker image build
The `19.0` Docker image is built manually from this repo root and pushed directly:
```bash
docker build -t registry.gitlab.com/itsulu-odoo/itsulu-blog-publisher:19.0 .
docker push registry.gitlab.com/itsulu-odoo/itsulu-blog-publisher:19.0
```
CI `build_image` job only runs on `main` and `merge_requests`. Branch `19.0` is built locally until a `main` branch is established or CI is updated.
### Secrets management
All secrets are in Vault (`itsulu/*` path), synced by ESO `ClusterSecretStore/css-vault`.
Namespaces must be listed in `kubernetes/eso/css.yaml` `spec.conditions[0].namespaces` to use ESO.
The `gitlab/dockerconfigjson` Vault key must store `dockerconfigjson` as the property name,
and the ESO ExternalSecret must use `secretKey: .dockerconfigjson` + `property: dockerconfigjson`
(NOT the `template.data` approach which adds a broken `{"dockerconfigjson":"..."}` wrapper).
### Runboat integration
The `runboat_tag` CI stage (`.gitlab-ci.yml`) fires on `19.0` and `main` after `build_image`:
1. Tags the commit-SHA image with the branch slug (`registry…:19-0`)
2. POSTs to `$RUNBOAT_BASE_URL/api/v1/builds` to trigger a live test instance
Runboat is a fork of `sbidoul/runboat` at `gitlab.com/itsulu-odoo/runboat` with two added
env vars: `RUNBOAT_FORGE_API_BASE_URL` and `RUNBOAT_FORGE_WEB_BASE_URL` (defaults to GitHub;
set to `https://git.itsulu.com/api/v1` and `https://git.itsulu.com` for Forgejo).
The upstream PR branch is `forgejo-configurable-base-url` on `gitlab.com/itsulu-odoo/runboat`.
- Team says "I don't trust the test suite"

View file

@ -1,7 +1,10 @@
FROM odoo:17.0
FROM odoo:19.0
# Install Python testing dependencies using the system Python
RUN python3 -m pip install --no-cache-dir \
# Install Python testing dependencies.
# odoo:19.0 ships Python 3.12 (Debian 12 Bookworm) which marks the system Python
# as externally-managed (PEP 668). Use --break-system-packages for the test tools
# — this is a single-purpose container, not a shared Python install.
RUN python3 -m pip install --no-cache-dir --break-system-packages \
pytest \
pytest-odoo \
pytest-bdd \

35
PORTING.md Normal file
View file

@ -0,0 +1,35 @@
# Porting status — Odoo 19.0 branch
This branch targets **Odoo Community 19.0**. It was **seeded from the 17.0 baseline**
(the `main` line) and the Odoo-19-specific porting is tracked here. Until every item below
is verified, treat this branch as **work in progress** — do not deploy to a 19.0 instance
expecting it to work unverified.
> The product feature set is the same across all Odoo branches; only the Odoo-API-specific
> code differs. See `CLAUDE.md` §15 for the branch model and §11.5 / §12 for known
> version-specific gotchas.
## Series markers (done)
- [x] `.odoo-series` = `19.0`
- [x] `Dockerfile` base image = `odoo:19.0`
- [x] manifest version prefix = `19.0.` (via `bump-version.sh`)
## API porting checklist (verify on a real Odoo 19.0 instance)
- [ ] Module installs cleanly: `odoo -i itsulu_blog_publisher` on 19.0
- [ ] `blog.post` body field name (was `content` in 17.0 — confirm for 19.0)
- [ ] `mail.template` rendering (subject inline_template, body qweb `type="html"`)
- [ ] `mail.template._render_field` signature/return shape
- [ ] `website_blog` dependency + view inheritance still valid
- [ ] Wizard / settings views pass RELAXNG validation on 19.0
- [ ] `ir.cron` data format
- [ ] Python version compatibility (19.0 ships on a newer Python)
- [ ] Full test suite green on a 19.0 template DB (K8s job, §8)
## How to work this branch
1. Stand up a 19.0 template DB (mirror §8, base image `odoo:19.0`).
2. Run the suite, fix failures one Odoo-API difference at a time.
3. Record each gotcha in `CLAUDE.md` §12 tagged with the series.
4. When green, cut a release on this branch (`bump-version.sh` → tag `19.0-vX.Y.Z`).

View file

@ -1,8 +1,12 @@
# ITSulu Blog Publisher — Odoo 17 Addon
# ITSulu Blog Publisher — Odoo 19 Addon
**Version:** 0.4.8
**Version:** 0.5.0 · **Odoo series:** 19.0
Automated blog post generation and publishing for Odoo 17 Community Edition, powered by generative AI (Anthropic Claude, OpenAI, Google Gemini, or Ollama).
Automated blog post generation and publishing for Odoo 19.0 Community Edition, powered by generative AI (Anthropic Claude, OpenAI, Google Gemini, or Ollama).
> ⚠️ **Porting in progress.** This `19.0` branch is seeded from the 17.0 baseline; the
> Odoo-19 API port is tracked in [PORTING.md](PORTING.md). Use the `14.0` branch for the
> ITSulu production instance until 19.0 is verified.
> **Versioning**`MAJOR.MINOR.PATCH` (each 0999). MAJOR = major release for sale / significant change; MINOR = features or performance improvements; PATCH = a single group of commits. See [CHANGELOG.md](CHANGELOG.md) for release notes and [CLAUDE.md](CLAUDE.md) §15 for the full scheme.
@ -63,8 +67,8 @@ primed PostgreSQL template database. See [CLAUDE.md](CLAUDE.md) §8 for the K8s
### Prerequisites
- Odoo 17.0 Community
- Python 3.10+
- Odoo 19.0 Community
- Python 3.11+
- PostgreSQL 13+
- pip packages: `requests`, `pytest-odoo`, `pytest-bdd`

View file

@ -1 +1 @@
0.4.8
0.5.0

View file

@ -3,7 +3,7 @@
'name': 'ITSulu Blog Publisher',
# Odoo manifest version = <odoo_series>.<product_version>. Product version
# is tracked in the repo-root VERSION file (currently 0.4.8). See CLAUDE.md §15.
'version': '17.0.0.4.8',
'version': '19.0.0.5.0',
'summary': 'AI-powered blog post generation with multi-LLM support, scheduling, and social media copy',
'description': """
ITSulu Blog Publisher

View file

@ -14,7 +14,6 @@
<field name="code">model._cron_run_all_active_slots()</field>
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="active">False</field>
<!-- Active=False by default — admin enables in Settings after
configuring API keys and schedule slots. -->

View file

@ -9,7 +9,7 @@
<field name="name">itsulu.blog.generation.log.tree</field>
<field name="model">itsulu.blog.generation.log</field>
<field name="arch" type="xml">
<tree string="Generation Log"
<list string="Generation Log"
decoration-danger="state=='error'"
decoration-success="state=='success'"
decoration-info="state=='running'">
@ -23,7 +23,7 @@
<field name="tokens_used"/>
<field name="duration_seconds" string="Dur (s)" optional="show"/>
<field name="blog_post_id"/>
</tree>
</list>
</field>
</record>
@ -81,7 +81,7 @@
<record id="action_blog_generation_log_list" model="ir.actions.act_window">
<field name="name">Generation Log</field>
<field name="res_model">itsulu.blog.generation.log</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_state_group': 1}</field>
</record>

View file

@ -9,13 +9,13 @@
<field name="name">itsulu.blog.post.social.tree</field>
<field name="model">itsulu.blog.post.social</field>
<field name="arch" type="xml">
<tree string="Social Media Copy">
<list string="Social Media Copy">
<field name="blog_post_id"/>
<field name="twitter_enabled" widget="boolean_toggle" optional="show"/>
<field name="bluesky_enabled" widget="boolean_toggle" optional="show"/>
<field name="mastodon_enabled" widget="boolean_toggle" optional="show"/>
<field name="linkedin_enabled" widget="boolean_toggle" optional="show"/>
</tree>
</list>
</field>
</record>
@ -78,7 +78,7 @@
<record id="action_blog_post_social_list" model="ir.actions.act_window">
<field name="name">Social Media Copy</field>
<field name="res_model">itsulu.blog.post.social</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
</record>
</odoo>

View file

@ -9,7 +9,7 @@
<field name="name">itsulu.blog.schedule.tree</field>
<field name="model">itsulu.blog.schedule</field>
<field name="arch" type="xml">
<tree string="Schedule Slots">
<list string="Schedule Slots">
<field name="name"/>
<field name="slot"/>
<field name="trigger_time"/>
@ -23,7 +23,7 @@
<button name="%(action_blog_generate_wizard)d" type="action"
string="▶ Run Now" class="btn-sm btn-primary"
context="{'default_blog_id': blog_id, 'default_llm_provider': llm_provider, 'default_llm_model': llm_model}"/>
</tree>
</list>
</field>
</record>
@ -85,14 +85,14 @@
</page>
<page string="Generation Log">
<field name="log_ids" readonly="1">
<tree decoration-danger="state=='error'" decoration-success="state=='success'">
<list decoration-danger="state=='error'" decoration-success="state=='success'">
<field name="create_date"/>
<field name="state" widget="badge"/>
<field name="topic_used"/>
<field name="tokens_used"/>
<field name="duration_seconds" string="Duration (s)"/>
<field name="blog_post_id"/>
</tree>
</list>
</field>
</page>
</notebook>
@ -104,7 +104,7 @@
<record id="action_blog_schedule_list" model="ir.actions.act_window">
<field name="name">Schedule Slots</field>
<field name="res_model">itsulu.blog.schedule</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
</record>
</odoo>

View file

@ -9,7 +9,7 @@
<field name="name">itsulu.blog.topic.tree</field>
<field name="model">itsulu.blog.topic</field>
<field name="arch" type="xml">
<tree string="Topic Queue" decoration-muted="state=='used'" decoration-warning="priority=='urgent'">
<list string="Topic Queue" decoration-muted="state=='used'" decoration-warning="priority=='urgent'">
<field name="sequence" widget="handle"/>
<field name="priority" widget="priority"/>
<field name="name"/>
@ -25,7 +25,7 @@
<button name="action_mark_skipped" type="object" string="Skip"
invisible="state != 'pending'"
class="btn-sm btn-warning"/>
</tree>
</list>
</field>
</record>
@ -71,7 +71,7 @@
<record id="action_blog_topic_list" model="ir.actions.act_window">
<field name="name">Topic Queue</field>
<field name="res_model">itsulu.blog.topic</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_state_pending': 1}</field>
</record>
@ -84,11 +84,10 @@
<filter name="state_pending" string="Pending" domain="[('state','=','pending')]"/>
<filter name="state_used" string="Used" domain="[('state','=','used')]"/>
<filter name="priority_urgent" string="Urgent" domain="[('priority','=','urgent')]"/>
<group expand="0" string="Group By">
<separator/>
<filter name="group_state" string="State" context="{'group_by': 'state'}"/>
<filter name="group_priority" string="Priority" context="{'group_by': 'priority'}"/>
<filter name="group_blog" string="Blog" context="{'group_by': 'blog_id'}"/>
</group>
</search>
</field>
</record>
@ -101,7 +100,7 @@
<field name="name">Blog Publisher Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="target">current</field>
<field name="context">{'module': 'itsulu_blog_publisher'}</field>
</record>