Skip to content

Commit 549cab3

Browse files
Change v2 req_tree to not return root node, improve OpenAPI spec (#3010)
1 parent 7e8d433 commit 549cab3

File tree

6 files changed

+228
-19
lines changed

6 files changed

+228
-19
lines changed

courses/serializers/v1/programs_test.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import timedelta
22
from decimal import Decimal
3+
from unittest.mock import ANY
34

45
import pytest
56
from django.utils.timezone import now
@@ -108,7 +109,7 @@ def test_serialize_program(mock_context, remove_tree, program_with_empty_require
108109
)
109110

110111

111-
def test_program_requirement_tree_serializer_valid():
112+
def test_program_requirement_tree_serializer_save():
112113
"""Verify that the ProgramRequirementTreeSerializer validates data"""
113114
program = ProgramFactory.create()
114115
course1, course2, course3 = CourseFactory.create_batch(3)
@@ -142,6 +143,66 @@ def test_program_requirement_tree_serializer_valid():
142143
serializer.is_valid(raise_exception=True)
143144
serializer.save()
144145

146+
root.refresh_from_db()
147+
assert ProgramRequirementTreeSerializer(instance=root).data == [
148+
{
149+
"data": {
150+
"node_type": "program_root",
151+
"operator": None,
152+
"operator_value": None,
153+
"program": program.id,
154+
"course": None,
155+
"required_program": None,
156+
"title": "",
157+
"elective_flag": False,
158+
},
159+
"id": ANY,
160+
"children": [
161+
{
162+
"data": {
163+
"node_type": "operator",
164+
"operator": "all_of",
165+
"operator_value": None,
166+
"program": program.id,
167+
"course": None,
168+
"required_program": None,
169+
"title": "Required Courses",
170+
"elective_flag": False,
171+
},
172+
"id": ANY,
173+
"children": [
174+
{
175+
"data": {
176+
"node_type": "course",
177+
"operator": None,
178+
"operator_value": None,
179+
"program": program.id,
180+
"course": course1.id,
181+
"required_program": None,
182+
"title": None,
183+
"elective_flag": False,
184+
},
185+
"id": ANY,
186+
}
187+
],
188+
},
189+
{
190+
"data": {
191+
"node_type": "operator",
192+
"operator": "min_number_of",
193+
"operator_value": "1",
194+
"program": program.id,
195+
"course": None,
196+
"required_program": None,
197+
"title": "Elective Courses",
198+
"elective_flag": False,
199+
},
200+
"id": ANY,
201+
},
202+
],
203+
}
204+
]
205+
145206

146207
def test_program_requirement_deletion():
147208
"""Verify that saving the requirements for one program doesn't affect other programs"""

courses/serializers/v2/programs.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,18 @@ class ProgramRequirementDataSerializer(StrictFieldsSerializer):
2424

2525
node_type = serializers.ChoiceField(
2626
choices=(
27-
ProgramRequirementNodeType.OPERATOR,
2827
ProgramRequirementNodeType.COURSE,
28+
ProgramRequirementNodeType.PROGRAM,
29+
ProgramRequirementNodeType.OPERATOR,
2930
)
3031
)
31-
course = serializers.CharField(source="course_id", allow_null=True, default=None)
32-
program = serializers.CharField(source="program_id", required=False)
32+
course = serializers.IntegerField(source="course_id", allow_null=True, default=None)
33+
program = serializers.IntegerField(
34+
source="program_id", allow_null=True, default=None
35+
)
36+
required_program = serializers.IntegerField(
37+
source="required_program_id", allow_null=True, default=None
38+
)
3339
title = serializers.CharField(allow_null=True, default=None)
3440
operator = serializers.CharField(allow_null=True, default=None)
3541
operator_value = serializers.CharField(allow_null=True, default=None)
@@ -79,6 +85,14 @@ class Meta:
7985
class ProgramRequirementTreeSerializer(BaseProgramRequirementTreeSerializer):
8086
child = ProgramRequirementSerializer()
8187

88+
@property
89+
def data(self):
90+
"""Return children of root node directly, or empty array if no children"""
91+
# BaseProgramRequirementTreeSerializer overrides the data property
92+
# to bypass to_implementation, so we do also.
93+
full_data = super().data
94+
return full_data[0].get("children", []) if full_data else []
95+
8296

8397
@extend_schema_serializer(
8498
component_name="V2ProgramSerializer",

courses/serializers/v2/programs_test.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from datetime import timedelta
2+
from unittest.mock import ANY
23

34
import pytest
45
from django.utils.timezone import now
56

67
from cms.factories import CoursePageFactory
78
from cms.serializers import ProgramPageSerializer
89
from courses.factories import ( # noqa: F401
10+
CourseFactory,
911
CourseRunFactory,
1012
ProgramCollectionFactory,
1113
ProgramFactory,
@@ -144,3 +146,114 @@ def test_serialize_program(
144146
"max_price": program_with_empty_requirements.page.max_price,
145147
},
146148
)
149+
150+
151+
def test_program_requirement_tree_serializer_save():
152+
"""Verify that the ProgramRequirementTreeSerializer validates data"""
153+
program = ProgramFactory.create()
154+
course1, course2, course3 = CourseFactory.create_batch(3)
155+
root = program.requirements_root
156+
157+
serializer = ProgramRequirementTreeSerializer(
158+
instance=root,
159+
data=[
160+
{
161+
"data": {
162+
"node_type": "operator",
163+
"title": "Required Courses",
164+
"operator": "all_of",
165+
},
166+
"children": [
167+
{"id": None, "data": {"node_type": "course", "course": course1.id}}
168+
],
169+
},
170+
{
171+
"data": {
172+
"node_type": "operator",
173+
"title": "Elective Courses",
174+
"operator": "min_number_of",
175+
"operator_value": "1",
176+
},
177+
"children": [
178+
{"id": None, "data": {"node_type": "course", "course": course2.id}},
179+
{"id": None, "data": {"node_type": "course", "course": course3.id}},
180+
],
181+
},
182+
],
183+
context={"program": program},
184+
)
185+
serializer.is_valid(raise_exception=True)
186+
serializer.save()
187+
188+
root.refresh_from_db()
189+
assert ProgramRequirementTreeSerializer(instance=root).data == [
190+
{
191+
"data": {
192+
"node_type": "operator",
193+
"operator": "all_of",
194+
"operator_value": None,
195+
"program": program.id,
196+
"course": None,
197+
"required_program": None,
198+
"title": "Required Courses",
199+
"elective_flag": False,
200+
},
201+
"id": ANY,
202+
"children": [
203+
{
204+
"data": {
205+
"node_type": "course",
206+
"operator": None,
207+
"operator_value": None,
208+
"program": program.id,
209+
"course": course1.id,
210+
"required_program": None,
211+
"title": None,
212+
"elective_flag": False,
213+
},
214+
"id": ANY,
215+
}
216+
],
217+
},
218+
{
219+
"data": {
220+
"node_type": "operator",
221+
"operator": "min_number_of",
222+
"operator_value": "1",
223+
"program": program.id,
224+
"course": None,
225+
"required_program": None,
226+
"title": "Elective Courses",
227+
"elective_flag": False,
228+
},
229+
"id": ANY,
230+
"children": [
231+
{
232+
"data": {
233+
"node_type": "course",
234+
"operator": None,
235+
"operator_value": None,
236+
"program": program.id,
237+
"course": course2.id,
238+
"required_program": None,
239+
"title": None,
240+
"elective_flag": False,
241+
},
242+
"id": ANY,
243+
},
244+
{
245+
"data": {
246+
"node_type": "course",
247+
"operator": None,
248+
"operator_value": None,
249+
"program": program.id,
250+
"course": course3.id,
251+
"required_program": None,
252+
"title": None,
253+
"elective_flag": False,
254+
},
255+
"id": ANY,
256+
},
257+
],
258+
},
259+
]

openapi/specs/v0.yaml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4884,10 +4884,14 @@ components:
48844884
node_type:
48854885
$ref: '#/components/schemas/V2ProgramRequirementDataNodeTypeEnum'
48864886
course:
4887-
type: string
4887+
type: integer
48884888
nullable: true
48894889
program:
4890-
type: string
4890+
type: integer
4891+
nullable: true
4892+
required_program:
4893+
type: integer
4894+
nullable: true
48914895
title:
48924896
type: string
48934897
nullable: true
@@ -4905,15 +4909,18 @@ components:
49054909
- node_type
49064910
V2ProgramRequirementDataNodeTypeEnum:
49074911
enum:
4908-
- operator
49094912
- course
4913+
- program
4914+
- operator
49104915
type: string
49114916
description: |-
4912-
* `operator` - operator
49134917
* `course` - course
4918+
* `program` - program
4919+
* `operator` - operator
49144920
x-enum-descriptions:
4915-
- operator
49164921
- course
4922+
- program
4923+
- operator
49174924
YearsExperienceEnum:
49184925
enum:
49194926
- 2

openapi/specs/v1.yaml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4884,10 +4884,14 @@ components:
48844884
node_type:
48854885
$ref: '#/components/schemas/V2ProgramRequirementDataNodeTypeEnum'
48864886
course:
4887-
type: string
4887+
type: integer
48884888
nullable: true
48894889
program:
4890-
type: string
4890+
type: integer
4891+
nullable: true
4892+
required_program:
4893+
type: integer
4894+
nullable: true
48914895
title:
48924896
type: string
48934897
nullable: true
@@ -4905,15 +4909,18 @@ components:
49054909
- node_type
49064910
V2ProgramRequirementDataNodeTypeEnum:
49074911
enum:
4908-
- operator
49094912
- course
4913+
- program
4914+
- operator
49104915
type: string
49114916
description: |-
4912-
* `operator` - operator
49134917
* `course` - course
4918+
* `program` - program
4919+
* `operator` - operator
49144920
x-enum-descriptions:
4915-
- operator
49164921
- course
4922+
- program
4923+
- operator
49174924
YearsExperienceEnum:
49184925
enum:
49194926
- 2

openapi/specs/v2.yaml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4884,10 +4884,14 @@ components:
48844884
node_type:
48854885
$ref: '#/components/schemas/V2ProgramRequirementDataNodeTypeEnum'
48864886
course:
4887-
type: string
4887+
type: integer
48884888
nullable: true
48894889
program:
4890-
type: string
4890+
type: integer
4891+
nullable: true
4892+
required_program:
4893+
type: integer
4894+
nullable: true
48914895
title:
48924896
type: string
48934897
nullable: true
@@ -4905,15 +4909,18 @@ components:
49054909
- node_type
49064910
V2ProgramRequirementDataNodeTypeEnum:
49074911
enum:
4908-
- operator
49094912
- course
4913+
- program
4914+
- operator
49104915
type: string
49114916
description: |-
4912-
* `operator` - operator
49134917
* `course` - course
4918+
* `program` - program
4919+
* `operator` - operator
49144920
x-enum-descriptions:
4915-
- operator
49164921
- course
4922+
- program
4923+
- operator
49174924
YearsExperienceEnum:
49184925
enum:
49194926
- 2

0 commit comments

Comments
 (0)