Skip to content

feat(product-pages): expose CMS-managed testimonials on course and program pages - #3987

Merged
zamanafzal merged 1 commit into
mainfrom
zafzal/13286-testimonials-product-pages
Sep 23, 2026
Merged

zamanafzal merged 1 commit into
mainfrom
zafzal/13286-testimonials-product-pages

Conversation

@zamanafzal

@zamanafzal zamanafzal commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

https://github.com/mitodl/hq/issues/13286

Related ticket and PRs:

Description (What does it do?)

Adds an optional Testimonials section that CMS authors manage per Course/Program page, exposed through the Wagtail v2 API — the same pattern as the FAQ feature (mitxonline#3961). Backend only; the MIT Learn frontend rendering is a separate change.

  • New ProductPageTestimonial(Orderable) model: quote and name required, title and image optional. ParentalKey targets Page (like faqs_list), so only Course/Program pages surface it via their InlinePanel + APIField("testimonials").
  • ProductPageTestimonialSerializer exposes id, quote, name, title, and image_src (resolved via get_wagtail_img_src, null when no image).
  • The API view prefetches testimonials_list for testimonials/* field requests to avoid an N+1 on the listing route.
  • Ordering follows authoring order (Orderable.sort_order). Adds the TestimonialItem schema serializer and regenerates the OpenAPI specs. No feature flag — release is managed via the CMS.

Screenshots (if appropriate):

Screenshot 2026-09-21 at 3 58 31 PM Screenshot 2026-09-21 at 3 58 02 PM

How can this be tested?

Apply the migration and start the stack:

docker compose run --rm web python manage.py migrate
docker compose up -d
  1. Open the Wagtail admin at http://mitxonline.odl.local:8013/cms/ and edit any Course or Program page. Note its numeric page id from the admin URL (…/pages/<PAGE_ID>/edit/).

  2. Scroll to the new Testimonials section → Add testimonial. Add two rows, each with a quote and name (set title + image on one, leave both blank on the other), reorder them via the drag handles, then Publish. → Both rows save and the drag order is preserved.

  3. Open the page in the Wagtail v2 API in your browser (it renders the JSON). The type param is required — the detail route only allows anonymous access for known page types; without it you get a 403 (use type=cms.programpage for a Program page):

    http://mitxonline.odl.local:8013/api/v2/pages/<PAGE_ID>/?type=cms.coursepage&fields=testimonials

    → Returns the testimonials in the order you arranged them, each with id, quote, name, title, and image_src (a URL for the row with an image, null for the one without).

  4. Confirm the empty state on a page with no testimonials:

    http://mitxonline.odl.local:8013/api/v2/pages/<OTHER_PAGE_ID>/?type=cms.coursepage&fields=testimonials

    → Returns "testimonials": [].

Additional Context

Backend only — the MIT Learn frontend that renders this section is a separate, dependent PR (same pattern as the FAQ rollout).

@github-actions

Copy link
Copy Markdown

OpenAPI Changes

Show/hide changes
## Changes for v0.yaml:
4 changes: 0 error, 0 warning, 4 info
info	[response-required-property-added] at head/openapi/specs/v0.yaml
	in API GET /api/v2/pages/?fields=*&type=cms.coursepage
		added the required property `items/items/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v0.yaml
	in API GET /api/v2/pages/?fields=*&type=cms.programpage
		added the required property `items/items/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v0.yaml
	in API GET /api/v2/pages/{id}/
		added the required property `oneOf[#/components/schemas/CoursePageItem]/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v0.yaml
	in API GET /api/v2/pages/{id}/
		added the required property `oneOf[#/components/schemas/ProgramPageItem]/testimonials` to the response with the `200` status



## Changes for v1.yaml:
4 changes: 0 error, 0 warning, 4 info
info	[response-required-property-added] at head/openapi/specs/v1.yaml
	in API GET /api/v2/pages/?fields=*&type=cms.coursepage
		added the required property `items/items/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v1.yaml
	in API GET /api/v2/pages/?fields=*&type=cms.programpage
		added the required property `items/items/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v1.yaml
	in API GET /api/v2/pages/{id}/
		added the required property `oneOf[#/components/schemas/CoursePageItem]/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v1.yaml
	in API GET /api/v2/pages/{id}/
		added the required property `oneOf[#/components/schemas/ProgramPageItem]/testimonials` to the response with the `200` status



## Changes for v2.yaml:
4 changes: 0 error, 0 warning, 4 info
info	[response-required-property-added] at head/openapi/specs/v2.yaml
	in API GET /api/v2/pages/?fields=*&type=cms.coursepage
		added the required property `items/items/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v2.yaml
	in API GET /api/v2/pages/?fields=*&type=cms.programpage
		added the required property `items/items/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v2.yaml
	in API GET /api/v2/pages/{id}/
		added the required property `oneOf[#/components/schemas/CoursePageItem]/testimonials` to the response with the `200` status

info	[response-required-property-added] at head/openapi/specs/v2.yaml
	in API GET /api/v2/pages/{id}/
		added the required property `oneOf[#/components/schemas/ProgramPageItem]/testimonials` to the response with the `200` status



Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The implemented 600-character editorial guidance conflicts with the PR’s documented 500-character guidance.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Adds CMS-managed testimonials to course and program pages and exposes them through the Wagtail API.

Changes:

  • Adds the testimonial model, editor panel, serialization, and migration.
  • Prefetches testimonial images and tests ordering, empty states, and query behavior.
  • Updates generated OpenAPI schemas.
File Description
cms/​models.py Defines testimonials and exposes them on product pages.
cms/​serializers.py Serializes testimonial API data.
cms/​migrations/​0069_add_product_page_testimonial.py Creates the testimonial table.
cms/​wagtail_api/​views.py Prefetches testimonials and images.
cms/​wagtail_api/​views_test.py Tests testimonial API behavior and performance.
cms/​wagtail_api/​schema/​serializers.py Adds testimonial schema definitions.
openapi/​specs/​v0.yaml Updates v0 API schema.
openapi/​specs/​v1.yaml Updates v1 API schema.
openapi/​specs/​v2.yaml Updates v2 API schema.
drf_lint_baseline.json Adjusts shifted lint-baseline location.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cms/models.py
@zamanafzal
zamanafzal force-pushed the zafzal/13286-testimonials-product-pages branch from ce41cae to 6ebad5a Compare September 21, 2026 11:02
@zamanafzal
zamanafzal force-pushed the zafzal/13286-testimonials-product-pages branch from 6ebad5a to 17fea28 Compare September 22, 2026 06:39
…ogram pages

Add an optional Testimonials section that CMS authors manage per
Course/Program page, exposed through the Wagtail v2 API — same pattern as
the FAQ feature (mitxonline#3961). Backend only.

- New ProductPageTestimonial(Orderable) model: quote and name required,
  title and image optional. ParentalKey targets Page (like faqs_list), so
  only Course/Program pages surface it via InlinePanel + APIField.
- ProductPageTestimonialSerializer exposes id, quote, name, title, and
  image_src (resolved via get_wagtail_img_src, null when no image).
- Prefetch testimonials_list__image for testimonials/* field requests to
  avoid an N+1 on the listing route.
- Help-text length guidance (quote 600, name 40, title 80) with no hard
  schema limits.
- Add the TestimonialItem schema serializer and regenerate the OpenAPI
  specs.

drf_lint_baseline.json: repoint the cms/serializers.py InstructorPage
feature_image line for this change, and realign the flexiblepricing entries
that drifted on main (0f1a2c2 changed the file without regenerating the
baseline) so the pinned drf-lint hook passes.

mitodl/hq#13286

@annagav annagav left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zamanafzal
zamanafzal merged commit 489b082 into main Sep 23, 2026
14 checks passed
@zamanafzal
zamanafzal deleted the zafzal/13286-testimonials-product-pages branch September 23, 2026 15:12
@odlbot odlbot mentioned this pull request Sep 23, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants