Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: standardize Catalog & API courseware #3146

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
90 changes: 73 additions & 17 deletions cms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,84 @@
from datetime import MAXYEAR, UTC, datetime

from django.contrib.contenttypes.models import ContentType
from django.db.models import Prefetch, Q
from wagtail.models import Page, Site

from cms import models as cms_models
from cms.constants import CERTIFICATE_INDEX_SLUG, ENTERPRISE_PAGE_SLUG
from mitxpro.utils import now_in_utc

log = logging.getLogger(__name__)
DEFAULT_HOMEPAGE_PROPS = dict(title="Home Page", subhead="This is the home page") # noqa: C408
DEFAULT_SITE_PROPS = dict(hostname="localhost", port=80) # noqa: C408


def filter_and_sort_catalog_pages(
program_pages, course_pages, external_course_pages, external_program_pages
):
def filter_program_pages(is_external=False): # noqa: FBT002
"""Filter the internal and external program objects"""
now = now_in_utc()
program_page_cls = cms_models.ProgramPage
prefetch_type = "coursepage"
if is_external:
program_page_cls = cms_models.ExternalProgramPage
prefetch_type = "externalcoursepage"

return (
program_page_cls.objects.live()
.filter(
(
Q(
program__courses__courseruns__start_date__isnull=False,
program__courses__courseruns__start_date__gte=now,
)
| Q(
program__courses__courseruns__enrollment_end__isnull=False,
program__courses__courseruns__enrollment_end__gte=now,
)
),
program__live=True,
)
.order_by("id")
.select_related("program")
.prefetch_related(
Prefetch(
"program__courses",
cms_models.Course.objects.order_by(
"position_in_program"
).select_related(prefetch_type),
),
)
)


def filter_course_pages(is_external=False): # noqa: FBT002
"""Filter the internal and external course pages"""
now = now_in_utc()
course_page_cls = (
cms_models.CoursePage if is_external else cms_models.ExternalCoursePage
)

return (
course_page_cls.objects.live()
.filter(
(
Q(
course__courseruns__start_date__isnull=False,
course__courseruns__start_date__gte=now,
)
| Q(
course__courseruns__enrollment_end__isnull=False,
course__courseruns__enrollment_end__gte=now,
)
),
course__live=True,
)
.order_by("id")
.select_related("course")
.distinct()
)


def filter_and_sort_catalog_pages(program_pages, course_pages):
"""
Filters program and course pages to only include those that should be visible in the catalog, then returns a tuple
of sorted lists of pages
Expand All @@ -32,27 +97,18 @@ def filter_and_sort_catalog_pages(
tuple of (list of Pages): A tuple containing a list of combined ProgramPages, CoursePages, ExternalCoursePages and ExternalProgramPages, a list of
ProgramPages and ExternalProgramPages, and a list of CoursePages and ExternalCoursePages, all sorted by the next course/program run date and title
"""
all_program_pages = program_pages + external_program_pages
all_course_pages = course_pages + external_course_pages

valid_program_pages = [
page for page in all_program_pages if page.product.is_catalog_visible
]
valid_course_pages = [
page for page in all_course_pages if page.product.is_catalog_visible
]

page_run_dates = {
page: page.product.next_run_date
or datetime(year=MAXYEAR, month=1, day=1, tzinfo=UTC)
for page in itertools.chain(
valid_program_pages,
valid_course_pages,
program_pages,
course_pages,
)
}
return (
sorted(
valid_program_pages + valid_course_pages,
program_pages + course_pages,
# ProgramPages with the same next run date as a CoursePage should be sorted first
key=lambda page: (
page_run_dates[page],
Expand All @@ -61,11 +117,11 @@ def filter_and_sort_catalog_pages(
),
),
sorted(
valid_program_pages,
program_pages,
key=lambda page: (page_run_dates[page], page.title),
),
sorted(
valid_course_pages,
course_pages,
key=lambda page: (page_run_dates[page], page.title),
),
)
Expand Down
53 changes: 14 additions & 39 deletions cms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@
from wagtailmetadata.models import MetadataPageMixin

from blog.api import fetch_blog
from cms.api import filter_and_sort_catalog_pages
from cms.api import (
filter_and_sort_catalog_pages,
filter_course_pages,
filter_program_pages,
)
from cms.blocks import (
BannerHeadingBlock,
CourseRunCertificateOverrides,
Expand Down Expand Up @@ -511,31 +515,11 @@ def get_context(self, request, *args, **kwargs): # noqa: ARG002
Populate the context with live programs, courses and programs + courses
"""
topic_filter = request.GET.get("topic", ALL_TOPICS)
program_page_qset = (
ProgramPage.objects.live()
.filter(program__live=True)
.order_by("id")
.select_related("program")
.prefetch_related(
Prefetch(
"program__courses",
Course.objects.order_by("position_in_program").select_related(
"coursepage"
),
),
)
)
external_program_qset = ExternalProgramPage.objects.live().order_by("title")
program_page_qset = filter_program_pages()
external_program_qset = filter_program_pages(is_external=True)

course_page_qset = (
CoursePage.objects.live()
.filter(course__live=True)
.order_by("id")
.select_related("course")
)
external_course_qset = (
ExternalCoursePage.objects.live().select_related("course").order_by("title")
)
course_page_qset = filter_course_pages()
external_course_qset = filter_course_pages(is_external=True)

if topic_filter != ALL_TOPICS:
program_page_qset = program_page_qset.related_pages(topic_filter)
Expand All @@ -544,27 +528,21 @@ def get_context(self, request, *args, **kwargs): # noqa: ARG002
course_page_qset = course_page_qset.related_pages(topic_filter)
external_course_qset = external_course_qset.related_pages(topic_filter)

program_page_qset = list(program_page_qset)
external_program_qset = list(external_program_qset)
course_page_qset = list(course_page_qset)
external_course_qset = list(external_course_qset)
program_page_qset = list(program_page_qset) + list(external_program_qset)
course_page_qset = list(course_page_qset) + list(external_course_qset)

# prefetch thumbnail images for all the pages in one query
prefetch_related_objects(
[
*program_page_qset,
*course_page_qset,
*external_course_qset,
*external_program_qset,
],
"thumbnail_image",
)

programs = [
page.program for page in [*program_page_qset, *external_program_qset]
]
programs = [page.program for page in [*program_page_qset]]
courses = [
*[page.course for page in [*course_page_qset, *external_course_qset]],
*[page.course for page in [*course_page_qset]],
*[course for program in programs for course in program.courses.all()],
]

Expand Down Expand Up @@ -596,10 +574,7 @@ def get_context(self, request, *args, **kwargs): # noqa: ARG002
)

all_pages, program_pages, course_pages = filter_and_sort_catalog_pages(
program_page_qset,
course_page_qset,
external_course_qset,
external_program_qset,
program_page_qset, course_page_qset
)
return dict(
**super().get_context(request),
Expand Down
7 changes: 1 addition & 6 deletions cms/templates/partials/card_details_top.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ <h3 class="title">
{{ courseware_page.duration }}
</li>
{% endif %}
{% if courseware_page.is_external_page and courseware_page.next_run_date %}
<li>
<strong>Next Start Date:</strong>
{{ courseware_page.next_run_date|date:"F j, Y" }}
</li>
{% elif courseware_page.product.next_run_date %}
{% if courseware_page.product.next_run_date %}
<li>
<strong>Next Start Date:</strong>
{{ courseware_page.product.next_run_date|date:"F j, Y" }}
Expand Down
31 changes: 29 additions & 2 deletions courses/views/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
ProgramSerializer,
)
from ecommerce.models import Product
from mitxpro.utils import now_in_utc


class ProgramViewSet(viewsets.ReadOnlyModelViewSet):
"""API view set for Programs"""

now = now_in_utc()
products_prefetch = Prefetch("products", Product.objects.with_ordered_versions())
course_runs_prefetch = Prefetch(
"courseruns", CourseRun.objects.prefetch_related(products_prefetch)
Expand All @@ -49,7 +51,19 @@ class ProgramViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = []
serializer_class = ProgramSerializer
queryset = (
Program.objects.filter(live=True)
Program.objects.filter(
(
Q(
courses__courseruns__start_date__isnull=False,
courses__courseruns__start_date__gte=now,
)
| Q(
courses__courseruns__enrollment_end__isnull=False,
courses__courseruns__enrollment_end__gte=now,
)
),
live=True,
)
.exclude(products=None)
.select_related("programpage", "externalprogrampage", "platform")
.prefetch_related(courses_prefetch, products_prefetch)
Expand All @@ -69,8 +83,21 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CourseSerializer

def get_queryset(self):
now = now_in_utc()
queryset = (
Course.objects.filter(live=True)
Course.objects.filter(
(
Q(
courseruns__start_date__isnull=False,
courseruns__start_date__gte=now,
)
| Q(
courseruns__enrollment_end__isnull=False,
courseruns__enrollment_end__gte=now,
)
),
live=True,
)
.select_related("coursepage", "externalcoursepage", "platform")
.prefetch_related(
"coursepage__topics",
Expand Down
Loading