From 86bb0cfa2c8fd162e288dda0c2139ae5ae08d29b Mon Sep 17 00:00:00 2001 From: Matthew Piatetsky <mpiatetsky@edx.org> Date: Tue, 22 Oct 2019 16:40:04 -0400 Subject: [PATCH] add discount percentage configuration --- common/djangoapps/course_modes/views.py | 2 +- openedx/features/discounts/admin.py | 25 +++++++++- openedx/features/discounts/applicability.py | 8 ++-- .../migrations/0002_auto_20191022_1720.py | 46 +++++++++++++++++++ openedx/features/discounts/models.py | 15 ++++++ openedx/features/discounts/utils.py | 2 +- openedx/features/discounts/views.py | 4 +- 7 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 openedx/features/discounts/migrations/0002_auto_20191022_1720.py diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 77d12334be4..586fe8315c8 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -216,7 +216,7 @@ class ChooseModeView(View): ) if offer_banner_fragment: context['offer_banner_fragment'] = offer_banner_fragment - discounted_price = "{:0.2f}".format(price_before_discount * ((100.0 - discount_percentage()) / 100)) + discounted_price = "{:0.2f}".format(price_before_discount * ((100.0 - discount_percentage(course)) / 100)) context["min_price"] = discounted_price context["price_before_discount"] = price_before_discount diff --git a/openedx/features/discounts/admin.py b/openedx/features/discounts/admin.py index 7efc67cc848..e3a65692635 100644 --- a/openedx/features/discounts/admin.py +++ b/openedx/features/discounts/admin.py @@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from openedx.core.djangoapps.config_model_utils.admin import StackedConfigModelAdmin -from .models import DiscountRestrictionConfig +from .models import DiscountPercentageConfig, DiscountRestrictionConfig class DiscountRestrictionConfigAdmin(StackedConfigModelAdmin): @@ -43,3 +43,26 @@ class DiscountRestrictionConfigAdmin(StackedConfigModelAdmin): raw_id_fields = ('course',) admin.site.register(DiscountRestrictionConfig, DiscountRestrictionConfigAdmin) + + +class DiscountPercentageConfigAdmin(StackedConfigModelAdmin): + """ + Admin to configure discount percentage + """ + fieldsets = ( + ('Context', { + 'fields': DiscountRestrictionConfig.KEY_FIELDS, + 'description': _( + 'These define the context to configure the percentage for the first purchase discount.' + 'If multiple contexts apply to a course (for example, if configuration ' + 'is specified for the course specifically, and for the org that the course ' + 'is in, then the more specific context overrides the more general context.' + ), + }), + ('Configuration', { + 'fields': ('percentage',), + }) + ) + raw_id_fields = ('course',) + +admin.site.register(DiscountPercentageConfig, DiscountPercentageConfigAdmin) diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index 4a5eac5cd9e..f967feba08d 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -20,7 +20,7 @@ from course_modes.models import CourseMode from entitlements.models import CourseEntitlement from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace -from openedx.features.discounts.models import DiscountRestrictionConfig +from openedx.features.discounts.models import DiscountPercentageConfig, DiscountRestrictionConfig from student.models import CourseEnrollment from track import segment @@ -181,9 +181,11 @@ def _is_in_holdback(user): return bucket == 0 -def discount_percentage(): +def discount_percentage(course): """ Get the configured discount amount. """ - # TODO: Add configuration information here + configured_percentage = DiscountPercentageConfig.current(course_key=course.id).percentage + if configured_percentage: + return configured_percentage return 15 diff --git a/openedx/features/discounts/migrations/0002_auto_20191022_1720.py b/openedx/features/discounts/migrations/0002_auto_20191022_1720.py new file mode 100644 index 00000000000..8de17c57175 --- /dev/null +++ b/openedx/features/discounts/migrations/0002_auto_20191022_1720.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.25 on 2019-10-22 17:20 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import openedx.core.djangoapps.config_model_utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('course_overviews', '0017_auto_20191002_0823'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('sites', '0002_alter_domain_unique'), + ('discounts', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='DiscountPercentageConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), + ('enabled', models.NullBooleanField(default=None, verbose_name='Enabled')), + ('org', models.CharField(blank=True, db_index=True, help_text='Configure values for all course runs associated with this Organization. This is the organization string (i.e. edX, MITx).', max_length=255, null=True)), + ('org_course', models.CharField(blank=True, db_index=True, help_text="Configure values for all course runs associated with this course. This is should be formatted as 'org+course' (i.e. MITx+6.002x, HarvardX+CS50).", max_length=255, null=True, validators=[openedx.core.djangoapps.config_model_utils.models.validate_course_in_org], verbose_name='Course in Org')), + ('percentage', models.PositiveIntegerField()), + ('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')), + ('course', models.ForeignKey(blank=True, help_text='Configure values for this course run. This should be formatted as the CourseKey (i.e. course-v1://MITx+6.002x+2019_Q1)', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='course_overviews.CourseOverview', verbose_name='Course Run')), + ('site', models.ForeignKey(blank=True, help_text='Configure values for all course runs associated with this site.', null=True, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddIndex( + model_name='discountpercentageconfig', + index=models.Index(fields=['site', 'org', 'course'], name='discounts_d_site_id_f87020_idx'), + ), + migrations.AddIndex( + model_name='discountpercentageconfig', + index=models.Index(fields=['site', 'org', 'org_course', 'course'], name='discounts_d_site_id_9fe8d6_idx'), + ), + ] diff --git a/openedx/features/discounts/models.py b/openedx/features/discounts/models.py index 0023320224c..bd2ff87c896 100644 --- a/openedx/features/discounts/models.py +++ b/openedx/features/discounts/models.py @@ -39,3 +39,18 @@ class DiscountRestrictionConfig(StackedConfigurationModel): return "DiscountRestrictionConfig(disabled={!r})".format( self.disabled ) + + +@python_2_unicode_compatible +class DiscountPercentageConfig(StackedConfigurationModel): + """ + A ConfigurationModel to configure the discount percentage for the first purchase discount + """ + STACKABLE_FIELDS = ('percentage',) + percentage = models.PositiveIntegerField() + + def __str__(self): + return "DiscountPercentageConfig(enabled={!r},percentage={!r})".format( + self.enabled, + self.percentage + ) diff --git a/openedx/features/discounts/utils.py b/openedx/features/discounts/utils.py index dcc70b66191..1e19621f0f8 100644 --- a/openedx/features/discounts/utils.py +++ b/openedx/features/discounts/utils.py @@ -52,7 +52,7 @@ def format_strikeout_price(user, course, base_price=None, check_for_discount=Tru original_price = format_course_price(base_price) if not check_for_discount or can_receive_discount(user, course): - discount_price = base_price * ((100.0 - discount_percentage()) / 100) + discount_price = base_price * ((100.0 - discount_percentage(course)) / 100) if discount_price == int(discount_price): discount_price = format_course_price("{:0.0f}".format(discount_price)) else: diff --git a/openedx/features/discounts/views.py b/openedx/features/discounts/views.py index 548b283838f..5229b199ba6 100644 --- a/openedx/features/discounts/views.py +++ b/openedx/features/discounts/views.py @@ -73,7 +73,7 @@ class CourseUserDiscount(DeveloperErrorViewMixin, APIView): course_key = CourseKey.from_string(course_key_string) course = CourseOverview.get_from_id(course_key) discount_applicable = can_receive_discount(user=request.user, course=course) - discount_percent = discount_percentage() + discount_percent = discount_percentage(course) payload = {'discount_applicable': discount_applicable, 'discount_percent': discount_percent} return Response({ 'discount_applicable': discount_applicable, @@ -136,7 +136,7 @@ class CourseUserDiscountWithUserParam(DeveloperErrorViewMixin, APIView): course = CourseOverview.get_from_id(course_key) user = User.objects.get(id=user_id) discount_applicable = can_receive_discount(user=user, course=course) - discount_percent = discount_percentage() + discount_percent = discount_percentage(course) payload = {'discount_applicable': discount_applicable, 'discount_percent': discount_percent} return Response({ 'discount_applicable': discount_applicable, -- GitLab