Skip to content

Commit 12e6814

Browse files
Paginate CMS listings past 100 rows; lint formatting
listCMSContent pages through the server's 100-row cap so /places, /portals, and the homepage PlaceMap don't silently truncate once slug x language rows exceed one page. Remaining files: ruff/prettier auto-fixes from pre-commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fd1bdec commit 12e6814

9 files changed

Lines changed: 291 additions & 39 deletions

File tree

app/src/app/utils/api/cmsContent.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,23 @@ export const getGallery = (slug: string, accessToken?: string): Promise<CMSGalle
186186
* only pages live in that language; when omitted, the CMS returns pages across
187187
* ALL languages (consumers dedupe by slug where needed).
188188
*/
189-
export const listCMSContent = (
189+
export const listCMSContent = async (
190190
type: CmsContentTypes,
191-
params: {language?: string; offset?: number; limit?: number} = {}
191+
params: {language?: string} = {}
192192
): Promise<CMSContentListItem[] | null> => {
193-
const searchParams = new URLSearchParams();
194-
if (params.language) searchParams.set('language', params.language);
195-
if (params.offset !== undefined) searchParams.set('offset', String(params.offset));
196-
if (params.limit !== undefined) searchParams.set('limit', String(params.limit));
197-
return cmsFetch<CMSContentListItem[]>(`/api/content/${type}/list?${searchParams.toString()}`);
193+
// The server caps each page at 100 rows spanning ALL languages; page
194+
// through so listings don't silently truncate once slug × language rows
195+
// exceed one page.
196+
const PAGE = 100;
197+
const all: CMSContentListItem[] = [];
198+
for (let offset = 0; ; offset += PAGE) {
199+
const searchParams = new URLSearchParams({offset: String(offset), limit: String(PAGE)});
200+
if (params.language) searchParams.set('language', params.language);
201+
const rows = await cmsFetch<CMSContentListItem[]>(
202+
`/api/content/${type}/list?${searchParams.toString()}`
203+
);
204+
if (rows === null) return offset === 0 ? null : all;
205+
all.push(...rows);
206+
if (rows.length < PAGE) return all;
207+
}
198208
};

backend/app/alembic/versions/ba6fafe520dc_merge_dev_and_wagtail_cutover_heads.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
Create Date: 2026-08-04 19:06:23.398536
66
77
"""
8-
from typing import Sequence, Union
98

10-
from alembic import op
11-
import sqlalchemy as sa
9+
from typing import Sequence, Union
1210

1311

1412
# revision identifiers, used by Alembic.
15-
revision: str = 'ba6fafe520dc'
16-
down_revision: Union[str, None] = ('a1c4d2e9b7f3', 'a30db9686b7c')
13+
revision: str = "ba6fafe520dc"
14+
down_revision: Union[str, None] = ("a1c4d2e9b7f3", "a30db9686b7c")
1715
branch_labels: Union[str, Sequence[str], None] = None
1816
depends_on: Union[str, Sequence[str], None] = None
1917

cms/authapi/migrations/0006_remove_team_slug.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55

66
class Migration(migrations.Migration):
7-
87
dependencies = [
9-
('authapi', '0005_grant_admin_team_permissions'),
8+
("authapi", "0005_grant_admin_team_permissions"),
109
]
1110

1211
operations = [
1312
migrations.RemoveField(
14-
model_name='team',
15-
name='slug',
13+
model_name="team",
14+
name="slug",
1615
),
1716
]

cms/content/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from django.views.decorators.http import require_GET
3030

3131
from content.models import PlacePage, StaticPage, TagPage
32-
from core.api import MAX_PAGE_SIZE, _json, pagination
32+
from core.api import _json, pagination
3333

3434
CONTENT_TYPE_PAGES = {
3535
"tags": TagPage,

cms/content/migrations/0005_static_pages.py

Lines changed: 261 additions & 13 deletions
Large diffs are not rendered by default.

cms/content/tests.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -669,9 +669,7 @@ def test_static_detail_has_no_map_fields(self):
669669

670670
def test_static_list(self):
671671
rows = self.client.get("/api/content/static/list").json()
672-
self.assertEqual(
673-
rows, [{"slug": "rules", "title": "Rules", "language": "en"}]
674-
)
672+
self.assertEqual(rows, [{"slug": "rules", "title": "Rules", "language": "en"}])
675673

676674
def test_list_negative_pagination_clamped(self):
677675
# Negative offset/limit must clamp to 0, not 500 on a negative slice.

cms/content/wagtail_hooks.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ def scope_content_pages_in_explorer(parent_page, pages, request):
5454
if not user_is_team_scoped(request.user):
5555
return pages
5656
scoped = list(districtr_map_slugs_for_user(request.user))
57-
out_of_scope = TagPage.objects.exclude(
58-
districtr_map_slug__in=scoped
59-
).values_list("pk", flat=True)
57+
out_of_scope = TagPage.objects.exclude(districtr_map_slug__in=scoped).values_list(
58+
"pk", flat=True
59+
)
6060
out_of_scope_places = PlacePage.objects.exclude(
6161
districtr_map_slugs__overlap=scoped
6262
).values_list("pk", flat=True)
@@ -84,7 +84,6 @@ def deny_out_of_team_bulk_action(request, action_type, objects, action):
8484
# never the per-page ones. Fires for snippet bulk actions too, so guard on
8585
# Page — snippets are covered by their own hooks.
8686
if any(
87-
isinstance(obj, Page) and _is_out_of_scope_page(request, obj)
88-
for obj in objects
87+
isinstance(obj, Page) and _is_out_of_scope_page(request, obj) for obj in objects
8988
):
9089
return permission_denied(request)

cms/datastore/wagtail_hooks.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
from django import forms
2121
from django.conf import settings
22-
from django.http import Http404
2322
from django.urls import path, reverse
2423
from wagtail import hooks
2524
from wagtail.admin.menu import MenuItem

cms/galleries/wagtail_hooks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class GalleryViewSet(TeamScopedViewSetMixin, SnippetViewSet):
130130
InlinePanel("entries", heading="Plans", label="Plan"),
131131
]
132132

133+
133134
register_snippet(GalleryViewSet)
134135

135136

0 commit comments

Comments
 (0)