Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Release Notes
=============

Version 1.142.1
---------------

- Prefer parent page for financial form URL (#3387)
- Add live filter to finance assistance forms (#3384)
- Update dependency authlib to v1.6.9 [SECURITY] (#3388)

Version 1.142.0 (Released March 17, 2026)
---------------

Expand Down
20 changes: 17 additions & 3 deletions cms/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ def _get_financial_assistance_url(self, page, slug):

def _get_course_specific_form(self, instance):
"""Get financial assistance form specific to the course."""
return FlexiblePricingRequestForm.objects.filter(
selected_course=instance.product
).first()
return (
FlexiblePricingRequestForm.objects.filter(selected_course=instance.product)
.live()
.first()
)

def _get_child_form(self, instance):
"""Get financial assistance form from child pages."""
Expand Down Expand Up @@ -308,6 +310,18 @@ def get_financial_assistance_form_url(self, instance):
.first()
)

# If a form is found via selected_program, prefer its parent page
# (e.g., a course or program page) when constructing the URL. This
# ensures that forms which are children of course pages but linked to
# a program use the correct /courses/ URL instead of the program URL.
if financial_assistance_page is not None:
parent = financial_assistance_page.get_parent()
if parent is not None:
parent_page = getattr(parent, "specific", parent)
return self._get_financial_assistance_url(
parent_page, financial_assistance_page.slug
)

# Check for child form if no direct link found
if financial_assistance_page is None:
page_children = instance.get_children()
Expand Down
75 changes: 75 additions & 0 deletions cms/serializers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,31 @@ def test_serialized_course_finaid_form_url_publishing_states(
assert serialized_output["financial_assistance_form_url"] == ""


def test_get_course_specific_form_returns_only_live_forms(fully_configured_wagtail):
"""_get_course_specific_form should ignore non-live forms and return the live one."""

course_page = CoursePageFactory()

# Non-live form for this course
FlexiblePricingFormFactory(
parent=course_page,
selected_course=course_page.product,
live=False,
)

# Live form for this course
live_form = FlexiblePricingFormFactory(
parent=course_page,
selected_course=course_page.product,
live=True,
)

serializer = CoursePageSerializer()
result = serializer._get_course_specific_form(course_page) # noqa: SLF001

assert result == live_form


def test_serialize_program_page(
mocker, fully_configured_wagtail, staff_user, mock_context
):
Expand Down Expand Up @@ -390,6 +415,56 @@ def test_serialize_program_page(
)


def test_serialize_program_page__form_child_of_course_with_program_fk(
mocker, fully_configured_wagtail, staff_user, mock_context
):
"""Program page uses course URL when form is child of course page.

This covers the case where a financial assistance form is created under a
course page in Wagtail but is linked to a program via the Selected Program
FK. The program page should link to the course-based URL for the form
rather than a /programs/ URL.
"""

fake_image_src = "http://example.com/my.img"
patched_get_wagtail_src = mocker.patch( # noqa: F841
"cms.serializers.get_wagtail_img_src", return_value=fake_image_src
)

program = ProgramFactory(page=None)
program_page = ProgramPageFactory(program=program)

course = CourseFactory(page=None)
program.add_requirement(course)
course_page = CoursePageFactory(course=course)

financial_assistance_form = FlexiblePricingFormFactory(
selected_program_id=program.id, parent=course_page
)

rf = RequestFactory()
request = rf.get("/")
request.user = staff_user

data = ProgramPageSerializer(
instance=program_page, context=program_page.get_context(request)
).data

assert_drf_json_equal(
data,
{
"feature_image_src": fake_image_src,
"page_url": program_page.url,
"financial_assistance_form_url": f"{course_page.get_url()}{financial_assistance_form.slug}/",
"description": bleach.clean(program_page.description, tags={}, strip=True),
"live": True,
"length": program_page.length,
"effort": program_page.effort,
"price": None,
},
)


def test_serialize_program_page__with_related_financial_form(
mocker, fully_configured_wagtail, staff_user, mock_context
):
Expand Down
2 changes: 1 addition & 1 deletion main/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from main.sentry import init_sentry
from openapi.settings_spectacular import open_spectacular_settings

VERSION = "1.142.0"
VERSION = "1.142.1"

log = logging.getLogger()

Expand Down
10 changes: 5 additions & 5 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading