Skip to content

Commit 31513df

Browse files
cp-at-mitpre-commit-ci[bot]ChristopherChudzicki
authored
Open api (#2549)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris Chudzicki <[email protected]>
1 parent ed8ce05 commit 31513df

39 files changed

+3729
-339
lines changed

.secrets.baseline

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@
193193
"filename": "main/settings.py",
194194
"hashed_secret": "09edaaba587f94f60fbb5cee2234507bcb883cc2",
195195
"is_verified": false,
196-
"line_number": 954
196+
"line_number": 959
197197
}
198198
],
199199
"pants": [
@@ -236,9 +236,9 @@
236236
"filename": "users/serializers.py",
237237
"hashed_secret": "ab90d9d736f9e0909b62d0922e0482e4b827449f",
238238
"is_verified": false,
239-
"line_number": 238
239+
"line_number": 243
240240
}
241241
]
242242
},
243-
"generated_at": "2025-01-22T20:35:24Z"
243+
"generated_at": "2025-03-04T17:10:08Z"
244244
}

authentication/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from django.contrib.auth import get_user_model
66
from django.http import HttpResponseRedirect
7+
from drf_spectacular.utils import extend_schema_field
78
from rest_framework import serializers
89
from social_core.backends.email import EmailAuth
910
from social_core.exceptions import AuthException, InvalidEmail
@@ -52,6 +53,7 @@ class SocialAuthSerializer(serializers.Serializer):
5253
redirect_url = serializers.CharField(read_only=True, default=None)
5354
extra_data = serializers.SerializerMethodField()
5455

56+
@extend_schema_field(serializers.DictField())
5557
def get_extra_data(self, instance):
5658
"""Serialize extra_data"""
5759
if instance.user is not None:

authentication/views.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from django.contrib.auth import get_user_model
88
from django.contrib.auth.views import LogoutView
99
from django.shortcuts import render, reverse
10+
from drf_spectacular.utils import extend_schema
1011
from rest_framework import status
1112
from rest_framework.decorators import api_view, permission_classes, renderer_classes
13+
from rest_framework.generics import GenericAPIView
1214
from rest_framework.permissions import IsAuthenticated
1315
from rest_framework.renderers import JSONRenderer
1416
from rest_framework.response import Response
@@ -81,6 +83,10 @@ def get_serializer_cls(self):
8183
return LoginPasswordSerializer
8284

8385

86+
@extend_schema(
87+
request=RegisterEmailSerializer,
88+
responses={200: RegisterEmailSerializer},
89+
)
8490
class RegisterEmailView(SocialAuthAPIView):
8591
"""Email register view"""
8692

@@ -105,14 +111,42 @@ def post(self, request):
105111
return super().post(request)
106112

107113

108-
class RegisterConfirmView(SocialAuthAPIView):
114+
class RegisterConfirmView(SocialAuthAPIView, GenericAPIView):
109115
"""Email registration confirmation view"""
110116

117+
serializer_class = RegisterConfirmSerializer
118+
permission_classes = []
119+
authentication_classes = []
120+
111121
def get_serializer_cls(self):
112-
"""Return the serializer cls"""
122+
"""Return the serializer class"""
113123
return RegisterConfirmSerializer
114124

125+
def post(self, request):
126+
"""
127+
Handle POST requests to confirm email registration
128+
"""
129+
if bool(request.session.get("hijack_history")):
130+
return Response(status=status.HTTP_403_FORBIDDEN)
131+
132+
serializer_cls = self.get_serializer_cls()
133+
strategy = load_drf_strategy(request)
134+
backend = load_backend(strategy, EmailAuth.name, None)
135+
serializer = serializer_cls(
136+
data=request.data,
137+
context={"request": request, "strategy": strategy, "backend": backend},
138+
)
139+
140+
if serializer.is_valid():
141+
serializer.save()
142+
return Response(serializer.data)
143+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
144+
115145

146+
@extend_schema(
147+
request=RegisterDetailsSerializer,
148+
responses={200: RegisterDetailsSerializer},
149+
)
116150
class RegisterDetailsView(SocialAuthAPIView):
117151
"""Email registration details view"""
118152

@@ -135,6 +169,10 @@ def post(self, request):
135169
return resp
136170

137171

172+
@extend_schema(
173+
request=RegisterExtraDetailsSerializer,
174+
responses={200: RegisterExtraDetailsSerializer},
175+
)
138176
class RegisterExtraDetailsView(SocialAuthAPIView):
139177
"""Email registration extra details view"""
140178

cms/serializers.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""CMS app serializers"""
22

3+
from __future__ import annotations
4+
35
import bleach
46
from django.templatetags.static import static
7+
from drf_spectacular.utils import extend_schema_field
58
from rest_framework import serializers
69

710
from cms import models
@@ -19,6 +22,7 @@ class BaseCoursePageSerializer(serializers.ModelSerializer):
1922
effort = serializers.SerializerMethodField()
2023
length = serializers.SerializerMethodField()
2124

25+
@extend_schema_field(str)
2226
def get_feature_image_src(self, instance):
2327
"""Serializes the source of the feature_image"""
2428
feature_img_src = None
@@ -27,19 +31,22 @@ def get_feature_image_src(self, instance):
2731

2832
return feature_img_src or static(DEFAULT_COURSE_IMG_PATH)
2933

34+
@extend_schema_field(serializers.URLField)
3035
def get_page_url(self, instance):
3136
return instance.get_url()
3237

38+
@extend_schema_field(str)
3339
def get_description(self, instance):
3440
return bleach.clean(instance.description, tags=[], strip=True)
3541

36-
def get_effort(self, instance):
42+
def get_effort(self, instance) -> str | None:
3743
return (
3844
bleach.clean(instance.effort, tags=[], strip=True)
3945
if instance.effort
4046
else None
4147
)
4248

49+
@extend_schema_field(str)
4350
def get_length(self, instance):
4451
return (
4552
bleach.clean(instance.length, tags=[], strip=True)
@@ -66,6 +73,7 @@ class CoursePageSerializer(BaseCoursePageSerializer):
6673
instructors = serializers.SerializerMethodField()
6774
current_price = serializers.SerializerMethodField()
6875

76+
@extend_schema_field(serializers.URLField)
6977
def get_financial_assistance_form_url(self, instance):
7078
"""
7179
Returns URL of the Financial Assistance Form.
@@ -116,14 +124,16 @@ def get_financial_assistance_form_url(self, instance):
116124
else ""
117125
)
118126

119-
def get_current_price(self, instance):
127+
@extend_schema_field(int)
128+
def get_current_price(self, instance) -> int | None:
120129
relevant_product = (
121130
instance.product.active_products.filter().order_by("-price").first()
122131
if instance.product.active_products
123132
else None
124133
)
125134
return relevant_product.price if relevant_product else None
126135

136+
@extend_schema_field(list)
127137
def get_instructors(self, instance):
128138
members = [
129139
member.linked_instructor_page
@@ -160,6 +170,7 @@ class ProgramPageSerializer(serializers.ModelSerializer):
160170
price = serializers.SerializerMethodField()
161171
financial_assistance_form_url = serializers.SerializerMethodField()
162172

173+
@extend_schema_field(str)
163174
def get_feature_image_src(self, instance):
164175
"""Serializes the source of the feature_image"""
165176
feature_img_src = None
@@ -168,12 +179,15 @@ def get_feature_image_src(self, instance):
168179

169180
return feature_img_src or static(DEFAULT_COURSE_IMG_PATH)
170181

182+
@extend_schema_field(serializers.URLField)
171183
def get_page_url(self, instance):
172184
return instance.get_url()
173185

186+
@extend_schema_field(str)
174187
def get_price(self, instance):
175188
return instance.price[0].value["text"] if len(instance.price) > 0 else None
176189

190+
@extend_schema_field(serializers.URLField)
177191
def get_financial_assistance_form_url(self, instance):
178192
"""
179193
Returns URL of the Financial Assistance Form.
@@ -235,6 +249,7 @@ class InstructorPageSerializer(serializers.ModelSerializer):
235249

236250
feature_image_src = serializers.SerializerMethodField()
237251

252+
@extend_schema_field(str)
238253
def get_feature_image_src(self, instance):
239254
"""Serializes the source of the feature_image"""
240255
feature_img_src = None

courses/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ def courses(self):
386386
return heap
387387

388388
@cached_property
389-
def required_courses(self):
389+
def required_courses(self) -> list:
390390
"""
391391
Returns just the courses under the "Required Courses" node.
392392
"""
@@ -407,7 +407,7 @@ def required_title(self):
407407
)
408408

409409
@cached_property
410-
def elective_courses(self):
410+
def elective_courses(self) -> list:
411411
"""
412412
Returns just the courses under the "Required Courses" node.
413413
"""
@@ -917,7 +917,7 @@ def get_courseware_object_readable_id(self):
917917
return self.course_run.courseware_id
918918

919919
@property
920-
def link(self):
920+
def link(self) -> str:
921921
"""
922922
Get the link at which this certificate will be served
923923
Format: /certificate/<uuid>/
@@ -1003,7 +1003,7 @@ def get_courseware_object_readable_id(self):
10031003
return self.program.readable_id
10041004

10051005
@property
1006-
def link(self):
1006+
def link(self) -> str:
10071007
"""
10081008
Get the link at which this certificate will be served
10091009
Format: /certificate/program/<uuid>/
@@ -1309,7 +1309,7 @@ def to_dict(self):
13091309
return serialize_model_object(self)
13101310

13111311
@property
1312-
def grade_percent(self):
1312+
def grade_percent(self) -> Decimal:
13131313
"""Returns the grade field value as a number out of 100 (or None if the value is None)"""
13141314
return (
13151315
Decimal(self.grade * 100).quantize(exp=Decimal(1), rounding=ROUND_HALF_EVEN)

courses/serializers/v1/base.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
from __future__ import annotations
2+
3+
from typing import Optional
4+
5+
from drf_spectacular.utils import extend_schema_field
16
from rest_framework import serializers
27

38
from cms.serializers import CoursePageSerializer
@@ -37,10 +42,31 @@ class BaseCourseRunSerializer(serializers.ModelSerializer):
3742
"""Minimal CourseRun model serializer"""
3843

3944
is_archived = serializers.SerializerMethodField()
45+
is_upgradable = serializers.SerializerMethodField()
46+
is_enrollable = serializers.SerializerMethodField()
47+
course_number = serializers.SerializerMethodField()
48+
courseware_url = serializers.SerializerMethodField()
49+
50+
def get_courseware_url(self, instance) -> Optional[str]:
51+
"""Get the courseware URL"""
52+
return instance.courseware_url
4053

41-
def get_is_archived(self, instance):
54+
def get_is_upgradable(self, instance) -> bool:
55+
"""Check if the course run is upgradable"""
56+
return instance.is_upgradable
57+
58+
def get_is_enrollable(self, instance) -> bool:
59+
"""Check if the course run is enrollable"""
60+
return instance.is_enrollable
61+
62+
def get_is_archived(self, instance) -> bool:
63+
"""Check if the course run is archived"""
4264
return instance.is_enrollable and instance.is_past
4365

66+
def get_course_number(self, instance) -> str:
67+
"""Get the course number"""
68+
return instance.course_number
69+
4470
class Meta:
4571
model = models.CourseRun
4672
fields = [
@@ -79,6 +105,14 @@ class Meta:
79105
fields = ["title", "readable_id", "id", "type"]
80106

81107

108+
class CourseRunCertificateSerializer(serializers.ModelSerializer):
109+
"""CourseRunCertificate model serializer"""
110+
111+
class Meta:
112+
model = models.CourseRunCertificate
113+
fields = ["uuid", "link"]
114+
115+
82116
class BaseCourseRunEnrollmentSerializer(serializers.ModelSerializer):
83117
certificate = serializers.SerializerMethodField(read_only=True)
84118
enrollment_mode = serializers.ChoiceField(
@@ -87,6 +121,7 @@ class BaseCourseRunEnrollmentSerializer(serializers.ModelSerializer):
87121
approved_flexible_price_exists = serializers.SerializerMethodField()
88122
grades = serializers.SerializerMethodField(read_only=True)
89123

124+
@extend_schema_field(CourseRunCertificateSerializer(allow_null=True))
90125
def get_certificate(self, enrollment):
91126
"""
92127
Resolve a certificate for this enrollment if it exists
@@ -119,6 +154,7 @@ def get_certificate(self, enrollment):
119154
except models.CourseRunCertificate.DoesNotExist:
120155
return None
121156

157+
@extend_schema_field(bool)
122158
def get_approved_flexible_price_exists(self, instance):
123159
instance_run = instance[0].run if isinstance(instance, list) else instance.run
124160
instance_user = (
@@ -129,6 +165,7 @@ def get_approved_flexible_price_exists(self, instance):
129165
)
130166
return flexible_price_exists # noqa: RET504
131167

168+
@extend_schema_field(list[dict])
132169
def get_grades(self, instance):
133170
instance_run = instance[0].run if isinstance(instance, list) else instance.run
134171
instance_user = (
@@ -155,6 +192,7 @@ class Meta:
155192
]
156193

157194

195+
@extend_schema_field(ProductFlexibilePriceSerializer)
158196
class ProductRelatedField(serializers.RelatedField):
159197
"""serializer for the Product generic field"""
160198

@@ -165,14 +203,6 @@ def to_representation(self, instance):
165203
return serializer.data
166204

167205

168-
class CourseRunCertificateSerializer(serializers.ModelSerializer):
169-
"""CourseRunCertificate model serializer"""
170-
171-
class Meta:
172-
model = models.CourseRunCertificate
173-
fields = ["uuid", "link"]
174-
175-
176206
class CourseRunGradeSerializer(serializers.ModelSerializer):
177207
"""CourseRunGrade serializer"""
178208

0 commit comments

Comments
 (0)