Skip to content

Commit

Permalink
Adds v2 of the courses API for the program page (#1983)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkachel authored Nov 8, 2023
1 parent 47b8d9d commit 254a635
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 35 deletions.
68 changes: 38 additions & 30 deletions cms/serializers.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
"""CMS app serializers"""
import bleach
from django.contrib.contenttypes.models import ContentType
from django.templatetags.static import static
from rest_framework import serializers

from cms import models
from cms.api import get_wagtail_img_src
from cms.models import FlexiblePricingRequestForm, ProgramPage
from courses.constants import DEFAULT_COURSE_IMG_PATH
from ecommerce.models import Product


class CoursePageSerializer(serializers.ModelSerializer):
class BaseCoursePageSerializer(serializers.ModelSerializer):
"""Course page model serializer"""

feature_image_src = serializers.SerializerMethodField()
page_url = serializers.SerializerMethodField()
financial_assistance_form_url = serializers.SerializerMethodField()
description = serializers.SerializerMethodField()
current_price = serializers.SerializerMethodField()
instructors = serializers.SerializerMethodField()
effort = serializers.SerializerMethodField()
length = serializers.SerializerMethodField()

Expand All @@ -34,6 +29,42 @@ def get_feature_image_src(self, instance):
def get_page_url(self, instance):
return instance.get_url()

def get_description(self, instance):
return bleach.clean(instance.description, tags=[], strip=True)

def get_effort(self, instance):
return (
bleach.clean(instance.effort, tags=[], strip=True)
if instance.effort
else None
)

def get_length(self, instance):
return (
bleach.clean(instance.length, tags=[], strip=True)
if instance.length
else None
)

class Meta:
model = models.CoursePage
fields = [
"feature_image_src",
"page_url",
"description",
"live",
"length",
"effort",
]


class CoursePageSerializer(BaseCoursePageSerializer):
"""Course page model serializer"""

financial_assistance_form_url = serializers.SerializerMethodField()
instructors = serializers.SerializerMethodField()
current_price = serializers.SerializerMethodField()

def get_financial_assistance_form_url(self, instance):
"""
Returns URL of the Financial Assistance Form.
Expand Down Expand Up @@ -84,9 +115,6 @@ def get_financial_assistance_form_url(self, instance):
else ""
)

def get_description(self, instance):
return bleach.clean(instance.description, tags=[], strip=True)

def get_current_price(self, instance):
relevant_product = (
instance.product.active_products.filter().order_by("-price").first()
Expand Down Expand Up @@ -114,32 +142,12 @@ def get_instructors(self, instance):

return returnable_members

def get_effort(self, instance):
return (
bleach.clean(instance.effort, tags=[], strip=True)
if instance.effort
else None
)

def get_length(self, instance):
return (
bleach.clean(instance.length, tags=[], strip=True)
if instance.length
else None
)

class Meta:
model = models.CoursePage
fields = [
"feature_image_src",
"page_url",
fields = BaseCoursePageSerializer.Meta.fields + [
"financial_assistance_form_url",
"description",
"current_price",
"instructors",
"live",
"length",
"effort",
]


Expand Down
171 changes: 171 additions & 0 deletions courses/serializers/v2/courses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""Courses v2 serializers"""

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

import logging

from cms.serializers import BaseCoursePageSerializer
from courses import models
from courses.api import create_run_enrollments
from courses.serializers.v1.base import (
BaseCourseSerializer,
BaseCourseRunEnrollmentSerializer,
BaseCourseRunSerializer,
ProductRelatedField,
)
from courses.serializers.v1.departments import DepartmentSerializer
from flexiblepricing.api import is_courseware_flexible_price_approved
from main import features
from openedx.constants import EDX_ENROLLMENT_AUDIT_MODE, EDX_ENROLLMENT_VERIFIED_MODE


log = logging.getLogger(__name__)


class CourseSerializer(BaseCourseSerializer):
"""Course model serializer"""

departments = DepartmentSerializer(many=True, read_only=True)
next_run_id = serializers.SerializerMethodField()
page = BaseCoursePageSerializer(read_only=True)
programs = serializers.SerializerMethodField()

def get_next_run_id(self, instance):
"""Get next run id"""
run = instance.first_unexpired_run
return run.id if run is not None else None

def get_programs(self, instance):
if self.context.get("all_runs", False):
from courses.serializers.v1.base import BaseProgramSerializer

return BaseProgramSerializer(instance.programs, many=True).data

return None

class Meta:
model = models.Course
fields = [
"id",
"title",
"readable_id",
"next_run_id",
"departments",
"page",
"programs",
]


class CourseRunSerializer(BaseCourseRunSerializer):
"""CourseRun model serializer"""

products = ProductRelatedField(many=True, read_only=True)
approved_flexible_price_exists = serializers.SerializerMethodField()

class Meta:
model = models.CourseRun
fields = BaseCourseRunSerializer.Meta.fields + [
"products",
"approved_flexible_price_exists",
]

def to_representation(self, instance):
data = super().to_representation(instance)
if self.context and self.context.get("include_enrolled_flag"):
return {
**data,
**{
"is_enrolled": getattr(instance, "user_enrollments", 0) > 0,
"is_verified": getattr(instance, "verified_enrollments", 0) > 0,
},
}
return data

def get_approved_flexible_price_exists(self, instance):
if not self.context or not self.context.get("include_approved_financial_aid"):
return False

# Get the User object if it exists.
user = self.context["request"].user if "request" in self.context else None

# Check for an approved flexible price record if the
# user exists and has an ID (not an Anonymous user).
# Otherwise return False.
flexible_price_exists = (
is_courseware_flexible_price_approved(
instance.course, self.context["request"].user
)
if user and user.id
else False
)
return flexible_price_exists


class CourseWithCourseRunsSerializer(CourseSerializer):
"""Course model serializer - also serializes child course runs"""

courseruns = serializers.SerializerMethodField(read_only=True)

def get_courseruns(self, instance):
context = {
"include_approved_financial_aid": self.context.get(
"include_approved_financial_aid", False
)
}

return CourseRunSerializer(
instance.courseruns.all(), many=True, read_only=True, context=context
).data

class Meta:
model = models.Course
fields = CourseSerializer.Meta.fields + [
"courseruns",
]


class CourseRunWithCourseSerializer(CourseRunSerializer):
"""
CourseRun model serializer - also serializes the parent Course.
"""

course = CourseSerializer(read_only=True, context={"include_page_fields": True})

class Meta:
model = models.CourseRun
fields = CourseRunSerializer.Meta.fields + [
"course",
]


class CourseRunEnrollmentSerializer(BaseCourseRunEnrollmentSerializer):
"""CourseRunEnrollment model serializer"""

run = CourseRunWithCourseSerializer(read_only=True)
run_id = serializers.IntegerField(write_only=True)
certificate = serializers.SerializerMethodField(read_only=True)
enrollment_mode = serializers.ChoiceField(
(EDX_ENROLLMENT_AUDIT_MODE, EDX_ENROLLMENT_VERIFIED_MODE), read_only=True
)
approved_flexible_price_exists = serializers.SerializerMethodField()
grades = serializers.SerializerMethodField(read_only=True)

def create(self, validated_data):
user = self.context["user"]
run_id = validated_data["run_id"]
try:
run = models.CourseRun.objects.get(id=run_id)
except models.CourseRun.DoesNotExist:
raise ValidationError({"run_id": f"Invalid course run id: {run_id}"})
successful_enrollments, edx_request_success = create_run_enrollments(
user,
[run],
keep_failed_enrollments=features.is_enabled(features.IGNORE_EDX_FAILURES),
)
return successful_enrollments

class Meta(BaseCourseRunEnrollmentSerializer.Meta):
fields = BaseCourseRunEnrollmentSerializer.Meta.fields + [
"run_id",
]
1 change: 1 addition & 0 deletions courses/urls/v2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
app_name = "courses"
router = routers.SimpleRouter()
router.register(r"programs", v2.ProgramViewSet, basename="programs_api")
router.register(r"courses", v2.CourseViewSet, basename="courses_api")

urlpatterns = router.urls
Loading

0 comments on commit 254a635

Please sign in to comment.