diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 72ddccf..f7a1b36 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -622,6 +622,8 @@ team-tagged instead of categorised, with a two-step release flow rather than a b ``` news.News(ClubScopedModel) # -> carries `club` title, slug (SlugField, auto from title), body (TextField) + title_en CharField (blank) -- optional English translation of `title` + body_en TextField (blank) -- optional English translation of `body` teams M2M teams.Team (blank -- empty means club-wide) visibility CharField (TextChoices: internal | external | both) status CharField (TextChoices: draft | published) @@ -665,9 +667,23 @@ home.HomeConfig(ClubScopedModel) # one row PER CLUB: featured articles/teams, club person, not a raw login; `SET_NULL` so deleting a member doesn't erase their posts. - `slug`s back clean public URLs and feed `search`; they are **unique per club** (§2.4), so two clubs can both have `/news/season-kickoff`. Resolve within the request's club. -- `visibility` (internal/external/both) is stored and enforced nowhere yet — no - member-facing reading page or public API exists. Both are later work; the field is - there so they don't need a backfill when they land. +- `visibility` (internal/external/both) is enforced by the public read-only API + (`news/api.py`, mounted under `api/`) — only `external`/`both` items, published and + past their release date, are ever returned. No member-facing internal reading page + exists yet; that's later work. +- **`title`/`body` are Dutch (the club's own language, and the only one required); + `title_en`/`body_en` are an optional English translation**, both left blank by + default. Nothing computes or stores a fallback — `News.effective_title_en` / + `effective_body_en` resolve it on read (`title_en or title`), so translating a Dutch + edit later never leaves a stale English copy behind, and every existing row gets + correct fallback behaviour with no backfill. The public API always returns both + languages in one call (`title_nl`/`body_nl`/`excerpt_nl` alongside + `title_en`/`body_en`/`excerpt_en`, the latter three via the `effective_*` properties + so they're never blank) — no `?lang=` param, the consumer picks what it needs. The + control panel's news form lays the two languages out in side-by-side columns + (`management/templates/management/news_form.html`); the detail page only shows an + "English" section when a translation was actually added, not the fallback-filled + text under a second heading. - `NewsPhoto.image` / hero images use `ImageField` → **media storage must be configured** (§8). If page/news trees grow, consider a tree library later — start flat. diff --git a/api/tests.py b/api/tests.py index b24b9e4..c831caf 100644 --- a/api/tests.py +++ b/api/tests.py @@ -103,7 +103,7 @@ class NewsApiTests(ApiTestBase): def test_excerpt_is_a_truncated_prefix_of_the_body(self): self.make_news(body=" ".join(f"word{i}" for i in range(80))) - excerpt = self.api_get("/news/").json()["results"][0]["excerpt"] + excerpt = self.api_get("/news/").json()["results"][0]["excerpt_nl"] self.assertTrue(excerpt.startswith("word0 word1")) self.assertTrue(excerpt.endswith("…")) @@ -112,7 +112,7 @@ class NewsApiTests(ApiTestBase): def test_excerpt_is_unchanged_when_the_body_is_already_short(self): item = self.make_news(body="Short body.") - excerpt = self.api_get("/news/").json()["results"][0]["excerpt"] + excerpt = self.api_get("/news/").json()["results"][0]["excerpt_nl"] self.assertEqual(excerpt, item.body) @@ -144,7 +144,7 @@ class NewsApiTests(ApiTestBase): def test_body_markdown_is_rendered_to_html(self): self.make_news(body="## Big win\n\nWe beat **Rivals FC** 4-2. [Full report](https://example.com).") - body = self.api_get("/news/").json()["results"][0]["body"] + body = self.api_get("/news/").json()["results"][0]["body_nl"] self.assertIn("