Skip to content

Commit 18d6ac6

Browse files
authored
New LearningResourcePrice model (#1736)
1 parent 11cfae5 commit 18d6ac6

38 files changed

+685
-115
lines changed

fixtures/common.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,12 @@ def offeror_featured_lists(): # noqa: PT004
106106
is_course=True,
107107
)
108108
if offered_by == OfferedBy.ocw.name:
109-
LearningResourceRun.objects.filter(
110-
learning_resource=resource.id
111-
).update(prices=[])
109+
for run in LearningResourceRun.objects.filter(
110+
learning_resource__id=resource.id
111+
):
112+
run.resource_prices.set([])
113+
run.prices = []
114+
run.save()
112115
featured_path.resources.add(
113116
resource,
114117
through_defaults={

frontends/api/src/generated/v1/api.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,12 @@ export interface CourseResource {
644644
* @memberof CourseResource
645645
*/
646646
prices: Array<string>
647+
/**
648+
*
649+
* @type {Array<LearningResourcePrice>}
650+
* @memberof CourseResource
651+
*/
652+
resource_prices: Array<LearningResourcePrice>
647653
/**
648654
*
649655
* @type {Array<LearningResourceRun>}
@@ -1432,6 +1438,12 @@ export interface LearningPathResource {
14321438
* @memberof LearningPathResource
14331439
*/
14341440
prices: Array<string>
1441+
/**
1442+
*
1443+
* @type {Array<LearningResourcePrice>}
1444+
* @memberof LearningPathResource
1445+
*/
1446+
resource_prices: Array<LearningResourcePrice>
14351447
/**
14361448
*
14371449
* @type {Array<LearningResourceRun>}
@@ -2147,6 +2159,44 @@ export interface LearningResourcePlatformRequest {
21472159
*/
21482160
name?: string
21492161
}
2162+
/**
2163+
* Serializer for LearningResourcePrice model
2164+
* @export
2165+
* @interface LearningResourcePrice
2166+
*/
2167+
export interface LearningResourcePrice {
2168+
/**
2169+
*
2170+
* @type {string}
2171+
* @memberof LearningResourcePrice
2172+
*/
2173+
amount: string
2174+
/**
2175+
*
2176+
* @type {string}
2177+
* @memberof LearningResourcePrice
2178+
*/
2179+
currency: string
2180+
}
2181+
/**
2182+
* Serializer for LearningResourcePrice model
2183+
* @export
2184+
* @interface LearningResourcePriceRequest
2185+
*/
2186+
export interface LearningResourcePriceRequest {
2187+
/**
2188+
*
2189+
* @type {string}
2190+
* @memberof LearningResourcePriceRequest
2191+
*/
2192+
amount: string
2193+
/**
2194+
*
2195+
* @type {string}
2196+
* @memberof LearningResourcePriceRequest
2197+
*/
2198+
currency: string
2199+
}
21502200
/**
21512201
* CRUD serializer for LearningResourceRelationship
21522202
* @export
@@ -2252,6 +2302,12 @@ export interface LearningResourceRun {
22522302
* @memberof LearningResourceRun
22532303
*/
22542304
pace: Array<CourseResourcePaceInner>
2305+
/**
2306+
*
2307+
* @type {Array<LearningResourcePrice>}
2308+
* @memberof LearningResourceRun
2309+
*/
2310+
resource_prices: Array<LearningResourcePrice>
22552311
/**
22562312
*
22572313
* @type {string}
@@ -4201,6 +4257,12 @@ export interface PodcastEpisodeResource {
42014257
* @memberof PodcastEpisodeResource
42024258
*/
42034259
prices: Array<string>
4260+
/**
4261+
*
4262+
* @type {Array<LearningResourcePrice>}
4263+
* @memberof PodcastEpisodeResource
4264+
*/
4265+
resource_prices: Array<LearningResourcePrice>
42044266
/**
42054267
*
42064268
* @type {Array<LearningResourceRun>}
@@ -4589,6 +4651,12 @@ export interface PodcastResource {
45894651
* @memberof PodcastResource
45904652
*/
45914653
prices: Array<string>
4654+
/**
4655+
*
4656+
* @type {Array<LearningResourcePrice>}
4657+
* @memberof PodcastResource
4658+
*/
4659+
resource_prices: Array<LearningResourcePrice>
45924660
/**
45934661
*
45944662
* @type {Array<LearningResourceRun>}
@@ -5209,6 +5277,12 @@ export interface ProgramResource {
52095277
* @memberof ProgramResource
52105278
*/
52115279
prices: Array<string>
5280+
/**
5281+
*
5282+
* @type {Array<LearningResourcePrice>}
5283+
* @memberof ProgramResource
5284+
*/
5285+
resource_prices: Array<LearningResourcePrice>
52125286
/**
52135287
*
52145288
* @type {Array<LearningResourceRun>}
@@ -6076,6 +6150,12 @@ export interface VideoPlaylistResource {
60766150
* @memberof VideoPlaylistResource
60776151
*/
60786152
prices: Array<string>
6153+
/**
6154+
*
6155+
* @type {Array<LearningResourcePrice>}
6156+
* @memberof VideoPlaylistResource
6157+
*/
6158+
resource_prices: Array<LearningResourcePrice>
60796159
/**
60806160
*
60816161
* @type {Array<LearningResourceRun>}
@@ -6452,6 +6532,12 @@ export interface VideoResource {
64526532
* @memberof VideoResource
64536533
*/
64546534
prices: Array<string>
6535+
/**
6536+
*
6537+
* @type {Array<LearningResourcePrice>}
6538+
* @memberof VideoResource
6539+
*/
6540+
resource_prices: Array<LearningResourcePrice>
64556541
/**
64566542
*
64576543
* @type {Array<LearningResourceRun>}

frontends/api/src/test-utils/factories/learningResources.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
LearningResourceInstructor,
1717
LearningResourceOfferorDetail,
1818
LearningResourcePlatform,
19+
LearningResourcePrice,
1920
LearningResourceRun,
2021
LearningResourceSchool,
2122
LearningResourceTopic,
@@ -84,6 +85,17 @@ const learningResourceInstructor: Factory<LearningResourceInstructor> = (
8485
return instructor
8586
}
8687

88+
const learningResourcePrice: Factory<LearningResourcePrice> = (
89+
overrides = {},
90+
) => {
91+
const resourcePrice: LearningResourcePrice = {
92+
amount: faker.finance.amount({ min: 0, max: 200 }),
93+
currency: "USD",
94+
...overrides,
95+
}
96+
return resourcePrice
97+
}
98+
8799
const learningResourceBaseSchool: Factory<LearningResourceBaseSchool> = (
88100
overrides = {},
89101
) => {
@@ -212,6 +224,7 @@ const learningResourceRun: Factory<LearningResourceRun> = (overrides = {}) => {
212224
name: uniqueEnforcerWords.enforce(() => faker.lorem.words()),
213225
},
214226
],
227+
resource_prices: repeat(learningResourcePrice, { min: 0, max: 5 }),
215228
...overrides,
216229
}
217230
return run
@@ -264,6 +277,14 @@ const _learningResourceShared = (): Partial<
264277
platform: maybe(learningResourcePlatform) ?? null,
265278
free,
266279
prices: free ? ["0"] : [faker.finance.amount({ min: 0, max: 100 })],
280+
resource_prices: free
281+
? [{ amount: "0", currency: "USD" }]
282+
: [
283+
{
284+
amount: faker.finance.amount({ min: 0, max: 100 }).toString(),
285+
currency: "USD",
286+
},
287+
],
267288
readable_id: faker.lorem.slug(),
268289
course_feature: repeat(faker.lorem.word),
269290
runs: [],

learning_resources/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,6 @@ class Format(ExtendedEnum):
301301

302302
synchronous = "Synchronous"
303303
asynchronous = "Asynchronous"
304+
305+
306+
CURRENCY_USD = "USD"

learning_resources/etl/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from named_enum import ExtendedEnum
1111

1212
from learning_resources.constants import LearningResourceDelivery
13+
from learning_resources.models import LearningResourcePrice
1314

1415
# A custom UA so that operators of OpenEdx will know who is pinging their service
1516
COMMON_HEADERS = {
@@ -115,5 +116,6 @@ class ContentTagCategory(ExtendedEnum):
115116
class ResourceNextRunConfig:
116117
next_start_date: datetime = None
117118
prices: list[Decimal] = field(default_factory=list)
119+
resource_prices: list[LearningResourcePrice] = field(default_factory=list)
118120
availability: str = None
119121
location: str = None

learning_resources/etl/loaders.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
LearningResourceInstructor,
3333
LearningResourceOfferor,
3434
LearningResourcePlatform,
35+
LearningResourcePrice,
3536
LearningResourceRelationship,
3637
LearningResourceRun,
3738
LearningResourceTopic,
@@ -145,11 +146,17 @@ def load_run_dependent_values(
145146
if resource.certification and best_run and best_run.prices
146147
else []
147148
)
149+
resource.resource_prices.set(
150+
best_run.resource_prices.all()
151+
if resource.certification and best_run and best_run.resource_prices
152+
else []
153+
)
148154
resource.location = best_run.location
149155
resource.save()
150156
return ResourceNextRunConfig(
151157
next_start_date=resource.next_start_date,
152158
prices=resource.prices,
159+
resource_prices=resource.resource_prices.all(),
153160
availability=resource.availability,
154161
location=resource.location,
155162
)
@@ -182,6 +189,23 @@ def load_instructors(
182189
return instructors
183190

184191

192+
def load_prices(
193+
run: LearningResourceRun, prices_data: list[dict]
194+
) -> list[LearningResourcePrice]:
195+
"""Load the prices for a resource run into the database"""
196+
prices = []
197+
for price in prices_data:
198+
lr_price, _ = LearningResourcePrice.objects.get_or_create(
199+
amount=price["amount"],
200+
currency=price["currency"],
201+
)
202+
prices.append(lr_price)
203+
204+
run.resource_prices.set(prices)
205+
run.save()
206+
return prices
207+
208+
185209
def load_image(resource: LearningResource, image_data: dict) -> LearningResourceImage:
186210
"""Load the image for a resource into the database"""
187211
if image_data:
@@ -253,14 +277,13 @@ def load_run(
253277
status = run_data.pop("status", None)
254278
instructors_data = run_data.pop("instructors", [])
255279

280+
resource_prices = run_data.get("prices", [])
281+
run_data["prices"] = sorted({price["amount"] for price in resource_prices})
282+
256283
if status == RunStatus.archived.value or learning_resource.certification is False:
257284
# Archived runs or runs of resources w/out certificates should not have prices
258285
run_data["prices"] = []
259-
else:
260-
# Make sure any prices are unique and sorted in ascending order
261-
run_data["prices"] = sorted(
262-
set(run_data.get("prices", [])), key=lambda x: float(x)
263-
)
286+
resource_prices = []
264287

265288
with transaction.atomic():
266289
(
@@ -273,6 +296,7 @@ def load_run(
273296
)
274297

275298
load_instructors(learning_resource_run, instructors_data)
299+
load_prices(learning_resource_run, resource_prices)
276300
load_image(learning_resource_run, image_data)
277301
return learning_resource_run
278302

0 commit comments

Comments
 (0)