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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repos:
- id: check-toml
- id: debug-statements
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.13.0-1
rev: v3.13.1-1
hooks:
- id: shfmt
- repo: https://github.com/adrienverge/yamllint.git
Expand Down Expand Up @@ -47,7 +47,7 @@ repos:
- "config/keycloak/*"
additional_dependencies: ["gibberish-detector"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.15.9"
rev: "v0.15.10"
hooks:
- id: ruff-format
- id: ruff
Expand Down
8 changes: 8 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Release Notes
=============

Version 1.146.5
---------------

- Add fields for configuring the How You'll Learn cards on product pages (#3487)
- feat: add webhook endpoint for Open edX course enrollment (#3372)
- Make enrollment_mode field non-nullable (#3494)
- [pre-commit.ci] pre-commit autoupdate (#3486)

Version 1.146.4 (Released April 16, 2026)
---------------

Expand Down
40 changes: 40 additions & 0 deletions cms/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,43 @@
ONE_MINUTE = 60

FEATURED_ITEMS_CACHE_KEY = "CMS_homepage_featured_courses"

HYL_CHOICE_REALWORLD_LEARNING = {
"icon": "IconConnectedPeople",
"title": "Real-World Learning",
"text": "Learn from MIT faculty and experts who ground their teaching in real-world cases rather than mathematical models, making the material approachable for all.",
}
HYL_CHOICE_LEARN_BY_DOING = {
"icon": "IconBrains",
"title": "Practical Application",
"text": "Apply your new knowledge with hands-on, practical exercises drawn from healthcare, sports, finance, sustainability, and more.",
}
HYL_CHOICE_LEARN_FROM_OTHERS = {
"icon": "IconBrains",
"title": "Learn From Others",
"text": "Connect with an international community of professionals working on real-world projects.",
}
HYL_CHOICE_LEARN_ON_DEMAND = {
"icon": "IconBrains",
"title": "Learn On Demand",
"text": "Access all course content online with complete flexibility to study at your own pace.",
}
HYL_CHOICE_AI_ENABLED_SUPPORT = {
"icon": "IconComputerBulb",
"title": "AI-Enabled Support",
"text": "Deepen your understanding of the course material and get help on assignments from AskTIM, the AI assistant built by MIT researchers.",
}
HYL_CHOICE_STACKABLE_CREDENTIALS = {
"icon": "IconCertificate",
"title": "Stackable Credentials",
"text": "Earn an MIT Open Learning certificate at each milestone—module, course, and program—demonstrating your AI expertise. Available in paid courses only.",
}

HYL_CHOICES = {
"realworld_learning": HYL_CHOICE_REALWORLD_LEARNING,
"learn_by_doing": HYL_CHOICE_LEARN_BY_DOING,
"learn_from_others": HYL_CHOICE_LEARN_FROM_OTHERS,
"learn_on_demand": HYL_CHOICE_LEARN_ON_DEMAND,
"ai_enabled_support": HYL_CHOICE_AI_ENABLED_SUPPORT,
"stackable_credentials": HYL_CHOICE_STACKABLE_CREDENTIALS,
}
132 changes: 132 additions & 0 deletions cms/migrations/0060_add_hyl_choice_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Generated by Django 5.1.15 on 2026-04-14 15:49

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("cms", "0059_alter_certificatepage_verifiable_credential_criteria"),
]

operations = [
migrations.AddField(
model_name="coursepage",
name="hyl_choice_ai_enabled_support",
field=models.BooleanField(
blank=True,
default=False,
help_text="Get personalized help on assignments from AskTIM, powered by advanced AI.",
null=True,
),
),
migrations.AddField(
model_name="coursepage",
name="hyl_choice_learn_by_doing",
field=models.BooleanField(
blank=True,
default=False,
help_text="Practice core competencies through case studies, simulations, and hands-on tools.",
null=True,
),
),
migrations.AddField(
model_name="coursepage",
name="hyl_choice_learn_from_others",
field=models.BooleanField(
blank=True,
default=False,
help_text="Connect with an international community of professionals working on real-world projects.",
null=True,
),
),
migrations.AddField(
model_name="coursepage",
name="hyl_choice_learn_on_demand",
field=models.BooleanField(
blank=True,
default=False,
help_text="Access all course content online with complete flexibility to study at your own pace.",
null=True,
),
),
migrations.AddField(
model_name="coursepage",
name="hyl_choice_realworld_learning",
field=models.BooleanField(
blank=True,
default=False,
help_text="Learn from faculty experts who emphasize practical application over theory.",
null=True,
),
),
migrations.AddField(
model_name="coursepage",
name="hyl_choice_stackable_credentials",
field=models.BooleanField(
blank=True,
default=False,
help_text="Earn certificates at key milestones—module, course, and program—building a portfolio of expertise.",
null=True,
),
),
migrations.AddField(
model_name="programpage",
name="hyl_choice_ai_enabled_support",
field=models.BooleanField(
blank=True,
default=False,
help_text="Get personalized help on assignments from AskTIM, powered by advanced AI.",
null=True,
),
),
migrations.AddField(
model_name="programpage",
name="hyl_choice_learn_by_doing",
field=models.BooleanField(
blank=True,
default=False,
help_text="Practice core competencies through case studies, simulations, and hands-on tools.",
null=True,
),
),
migrations.AddField(
model_name="programpage",
name="hyl_choice_learn_from_others",
field=models.BooleanField(
blank=True,
default=False,
help_text="Connect with an international community of professionals working on real-world projects.",
null=True,
),
),
migrations.AddField(
model_name="programpage",
name="hyl_choice_learn_on_demand",
field=models.BooleanField(
blank=True,
default=False,
help_text="Access all course content online with complete flexibility to study at your own pace.",
null=True,
),
),
migrations.AddField(
model_name="programpage",
name="hyl_choice_realworld_learning",
field=models.BooleanField(
blank=True,
default=False,
help_text="Learn from faculty experts who emphasize practical application over theory.",
null=True,
),
),
migrations.AddField(
model_name="programpage",
name="hyl_choice_stackable_credentials",
field=models.BooleanField(
blank=True,
default=False,
help_text="Earn certificates at key milestones—module, course, and program—building a portfolio of expertise.",
null=True,
),
),
]
90 changes: 89 additions & 1 deletion cms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from django.core.exceptions import ValidationError
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.forms import ChoiceField, IntegerField, Textarea
from django.forms import CheckboxInput, ChoiceField, IntegerField, Textarea
from django.http import Http404
from django.template.response import TemplateResponse
from django.urls import reverse
Expand All @@ -27,6 +27,7 @@
from wagtail.admin.panels import (
FieldPanel,
InlinePanel,
MultiFieldPanel,
PageChooserPanel,
)
from wagtail.api import APIField
Expand Down Expand Up @@ -57,6 +58,7 @@
from cms.constants import (
CERTIFICATE_INDEX_SLUG,
COURSE_INDEX_SLUG,
HYL_CHOICES,
INSTRUCTOR_INDEX_SLUG,
PROGRAM_COLLECTION_INDEX_SLUG,
PROGRAM_INDEX_SLUG,
Expand Down Expand Up @@ -1159,6 +1161,46 @@ class Meta:
help_text="*Required for Verifiable Credential generation. What you will learn from this course.",
)

# How You'll Learn choice fields - these toggle on or off components on the
# Learn product pages.
hyl_choice_realworld_learning = models.BooleanField(
help_text="Learn from faculty experts who emphasize practical application over theory.",
null=True,
blank=True,
default=False,
)
hyl_choice_learn_by_doing = models.BooleanField(
help_text="Practice core competencies through case studies, simulations, and hands-on tools.",
null=True,
blank=True,
default=False,
)
hyl_choice_learn_from_others = models.BooleanField(
help_text="Connect with an international community of professionals working on real-world projects.",
null=True,
blank=True,
default=False,
)
hyl_choice_learn_on_demand = models.BooleanField(
help_text="Access all course content online with complete flexibility to study at your own pace.",
null=True,
blank=True,
default=False,
)
hyl_choice_ai_enabled_support = models.BooleanField(
help_text="Get personalized help on assignments from AskTIM, powered by advanced AI.",
null=True,
blank=True,
default=False,
)
hyl_choice_stackable_credentials = models.BooleanField(
help_text="Earn certificates at key milestones—module, course, and program—building a portfolio of expertise.",
null=True,
blank=True,
default=False,
)
# end How You'll Learn choice fields

feature_image = models.ForeignKey(
Image,
null=True,
Expand Down Expand Up @@ -1191,6 +1233,41 @@ class Meta:
FieldPanel("faq_url"),
FieldPanel("about"),
FieldPanel("what_you_learn"),
MultiFieldPanel(
children=(
FieldPanel(
"hyl_choice_realworld_learning",
widget=CheckboxInput,
heading="Real-world Learning",
),
FieldPanel(
"hyl_choice_learn_by_doing",
widget=CheckboxInput,
heading="Learn By Doing",
),
FieldPanel(
"hyl_choice_learn_from_others",
widget=CheckboxInput,
heading="Learn From Others",
),
FieldPanel(
"hyl_choice_learn_on_demand",
widget=CheckboxInput,
heading="Learn On Demand",
),
FieldPanel(
"hyl_choice_ai_enabled_support",
widget=CheckboxInput,
heading="AI-Enabled Support",
),
FieldPanel(
"hyl_choice_stackable_credentials",
widget=CheckboxInput,
heading="Stackable Credentials",
),
),
heading="How You'll Learn",
),
FieldPanel("feature_image"),
FieldPanel("video_url"),
FieldPanel("faculty_section_title"),
Expand Down Expand Up @@ -1219,6 +1296,7 @@ class Meta:
APIField("faculty_section_title"),
APIField("faculty"),
APIField("certificate_page", serializer=ProductChildPageSerializer()),
APIField("how_youll_learn"),
]

subpage_types = ["FlexiblePricingRequestForm", "CertificatePage"]
Expand Down Expand Up @@ -1279,6 +1357,16 @@ def product(self):
"""Returns the courseware object (Course, Program) associated with this page"""
raise NotImplementedError

@property
def how_youll_learn(self):
"""Returns the selected choices for the How You'll Learn section."""

return [
{"key": key, **HYL_CHOICES[key]}
for key in HYL_CHOICES
if getattr(self, f"hyl_choice_{key}", False)
]

def get_url_parts(self, request=None):
"""
Overrides base method for returning the parts of the URL for pages of this class
Expand Down
13 changes: 13 additions & 0 deletions cms/wagtail_api/schema/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ class OverrideSerializer(serializers.Serializer):
id = serializers.CharField()


class HowYoullLearnSerializer(serializers.Serializer):
"""Serializer for the How You'll Learn generated property"""

key = serializers.CharField()
icon = serializers.CharField()
title = serializers.CharField()
text = serializers.CharField()


class PageMetaSerializer(serializers.Serializer):
"""
Serializer for page metadata used in various Wagtail pages.
Expand Down Expand Up @@ -189,6 +198,7 @@ class Meta:
"topic_list",
"include_in_learn_catalog",
"ingest_content_files_for_ai",
"how_youll_learn",
]

# NOTE: We use this serializer for schema generation only,
Expand All @@ -202,6 +212,7 @@ class Meta:
certificate_page = CertificatePageSerializer(allow_null=True)
course_details = CourseSerializer()
topic_list = TopicSerializer(many=True)
how_youll_learn = HowYoullLearnSerializer(many=True)


class CoursePageListSerializer(serializers.Serializer):
Expand Down Expand Up @@ -246,6 +257,7 @@ class Meta:
"faculty",
"certificate_page",
"program_details",
"how_youll_learn",
]

# NOTE: We use this serializer for schema generation only,
Expand All @@ -265,6 +277,7 @@ def get_description(self, instance):
faculty = FacultySerializer(many=True)
certificate_page = CertificatePageSerializer()
program_details = ProgramSerializer()
how_youll_learn = HowYoullLearnSerializer(many=True)


class ProgramPageListSerializer(serializers.Serializer):
Expand Down
Loading
Loading