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

✨(backend) add offer and price fields to courseRun #2466

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Versioning](https://semver.org/spec/v2.0.0.html).

## [Unrealeased]

### Added

- Add offer and price fields to courseRun displayed at admin
view.

## [2.28.1]

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
environ_prefix=None,
)

# Course run price currency value that would be shown on course detail page
RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY = values.Value(
"EUR",
environ_name="RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY",
environ_prefix=None,
)

# Internationalization
TIME_ZONE = "Europe/Paris"
USE_I18N = True
Expand Down
7 changes: 7 additions & 0 deletions sandbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,13 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
environ_prefix=None,
)

# Course run price currency value that would be shown on course detail page
RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY = values.Value(
"EUR",
environ_name="RICHIE_DEFAULT_COURSE_RUN_PRICE_CURRENCY",
environ_prefix=None,
)

@classmethod
def _get_environment(cls):
"""Environment in which the application is launched."""
Expand Down
4 changes: 4 additions & 0 deletions src/richie/apps/courses/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class Meta:
"languages",
"enrollment_count",
"catalog_visibility",
"offer",
"price",
"sync_mode",
"display_mode",
]
Expand Down Expand Up @@ -150,6 +152,8 @@ class CourseRunAdmin(FrontendEditableAdminMixin, TranslatableAdmin):
"languages",
"enrollment_count",
"catalog_visibility",
"offer",
"price",
"sync_mode",
)
list_display = ["id"]
Expand Down
17 changes: 17 additions & 0 deletions src/richie/apps/courses/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import factory
from cms.api import add_plugin

from richie.apps.courses.models.course import CourseRunOffer
from richie.plugins.nesteditem.defaults import ACCORDION

from ..core.defaults import ALL_LANGUAGES
Expand Down Expand Up @@ -529,6 +530,22 @@ def enrollment_count(self):
"""
return random.randint(0, 10000) # nosec

@factory.lazy_attribute
def price(self):
"""
The price of a course run is a random float between 1 and 100.
"""
return random.randint(100, 10000) / 100 # nosec

@factory.lazy_attribute
def offer(self):
"""
The offer of a course run is read from Django settings.
"""
return (
CourseRunOffer.FREE if self.price == 0.0 else CourseRunOffer.PAID
) # nosec


class CategoryFactory(BLDPageExtensionDjangoModelFactory):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 4.2.13 on 2024-07-09 10:56

from django.db import migrations, models

import richie.apps.core.fields.multiselect


class Migration(migrations.Migration):
dependencies = [
("courses", "0034_auto_20230817_1736"),
]

operations = [
migrations.AddField(
model_name="courserun",
name="offer",
field=models.CharField(
choices=[
(
"free",
"free - The entire course can be completed without cost",
),
(
"partially_free",
"partially_free - More than half of the course is for free",
),
(
"subscription",
"subscription - The user must be a subscriber or paid member in order to complete the entire course",
),
(
"paid",
"paid - The user must pay to complete the course",
),
],
default="free",
max_length=20,
verbose_name="offer",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.13 on 2024-07-09 14:37

from django.db import migrations, models

import richie.apps.core.fields.multiselect


class Migration(migrations.Migration):
dependencies = [
("courses", "0035_courserun_offer_and_more"),
]

operations = [
migrations.AddField(
model_name="courserun",
name="price",
field=models.FloatField(
default=0.0, help_text="The cost of the course", verbose_name="price"
),
),
]
27 changes: 27 additions & 0 deletions src/richie/apps/courses/models/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,19 @@ class CourseRunCatalogVisibility(models.TextChoices):
HIDDEN = "hidden", _("hidden - hide on the course page and from search results")


class CourseRunOffer(models.TextChoices):
"""Course run offer choices."""

FREE = "free", _("free - The entire course can be completed without cost")
PARTIALLY_FREE = "partially_free", _(
"partially_free - More than half of the course is for free"
)
SUBSCRIPTION = "subscription", _(
"subscription - The user must be a subscriber or paid member to complete the entire course"
)
PAID = "paid", _("paid - The user must pay to complete the course")


class CourseRunDisplayMode(models.TextChoices):
"""Course run catalog display modes."""

Expand Down Expand Up @@ -771,6 +784,19 @@ class CourseRun(TranslatableModel):
blank=False,
max_length=20,
)
offer = models.CharField(
_("offer"),
choices=lazy(lambda: CourseRunOffer.choices, tuple)(),
default=CourseRunOffer.FREE,
blank=False,
max_length=20,
)
price = models.FloatField(
_("price"),
default=0.0,
blank=True,
help_text=_("The cost of the course"),
)
display_mode = models.CharField(
choices=CourseRunDisplayMode.choices,
default=CourseRunDisplayMode.DETAILED,
Expand Down Expand Up @@ -856,6 +882,7 @@ def mark_course_dirty(self):
def save(self, *args, **kwargs):
"""Enforce validation each time an instance is saved."""
self.full_clean()
self.price = float(self.price)
super().save(*args, **kwargs)

# pylint: disable=signature-differs
Expand Down
2 changes: 2 additions & 0 deletions src/richie/apps/courses/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class Meta:
"state",
"enrollment_count",
"catalog_visibility",
"offer",
"price",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

price field could be returned as a float not a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a change to this, but I'm not sure if it's in line with what you asked for.

]


Expand Down
4 changes: 4 additions & 0 deletions tests/apps/courses/test_admin_course_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ def _prepare_add_view_post(self, course, status_code):
"enrollment_end_0": "2015-01-23",
"enrollment_end_1": "09:07:11",
"catalog_visibility": "course_and_search",
"offer": "free",
"price": 0.0,
"sync_mode": "manual",
"display_mode": "detailed",
}
Expand Down Expand Up @@ -450,6 +452,8 @@ def _prepare_change_view_post(self, course_run, course, status_code, check_metho
"enrollment_end_1": "09:07:11",
"enrollment_count": "5",
"catalog_visibility": "course_and_search",
"offer": "free",
"price": 0.0,
"sync_mode": "manual",
"display_mode": "detailed",
}
Expand Down
3 changes: 3 additions & 0 deletions tests/apps/courses/test_admin_form_course_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def _get_admin_form(course, user):
"enrollment_end_0": "2015-01-23",
"enrollment_end_1": "09:07:11",
"catalog_visibility": "course_and_search",
"offer": "free",
"price": 0.0,
"sync_mode": "manual",
"display_mode": "detailed",
}
Expand Down Expand Up @@ -117,6 +119,7 @@ def test_admin_form_course_run_superuser_empty_form(self):
"display_mode": ["This field is required."],
"languages": ["This field is required."],
"catalog_visibility": ["This field is required."],
"offer": ["This field is required."],
"sync_mode": ["This field is required."],
},
)
Expand Down
4 changes: 3 additions & 1 deletion tests/apps/courses/test_models_course_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from richie.apps.core.helpers import create_i18n_page
from richie.apps.courses.factories import CourseFactory, CourseRunFactory
from richie.apps.courses.models import CourseRun, CourseRunTranslation
from richie.apps.courses.models.course import CourseRunCatalogVisibility
from richie.apps.courses.models.course import CourseRunCatalogVisibility, CourseRunOffer


# pylint: disable=too-many-public-methods
Expand Down Expand Up @@ -623,6 +623,8 @@ def test_models_course_run_mark_dirty_any_field(self):
stub = CourseRunFactory(
sync_mode="manual",
catalog_visibility=CourseRunCatalogVisibility.COURSE_ONLY,
offer=CourseRunOffer.SUBSCRIPTION,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoaoGarcao You have to use the setting RICHIE_DEFAULT_COURSE_RUN_OFFER that you have added to to the settings.py

price=3.0,
display_mode="compact",
) # New random values to update our course run

Expand Down