Skip to content
Snippets Groups Projects
Unverified Commit 39efc54a authored by Calen Pennington's avatar Calen Pennington Committed by GitHub
Browse files

Merge pull request #20884 from cpennington/discount-no-previous-purchase

REVEM-289: Discount no previous purchase
parents 0f045ab5 f07f2121
Branches
Tags release-2020-04-16-15.19
No related merge requests found
......@@ -516,7 +516,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
course_overview = CourseOverviewFactory(
start=self.TOMORROW, self_paced=True, enrollment_end=self.TOMORROW
)
course_enrollment = CourseEnrollmentFactory(user=self.user)
course_enrollment = CourseEnrollmentFactory(user=self.user, course_id=course_overview.id)
entitlement = CourseEntitlementFactory(user=self.user, enrollment_course_run=course_enrollment)
course_runs = [{
'key': six.text_type(course_overview.id),
......
......@@ -14,9 +14,10 @@ from django.utils.timezone import now
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from course_modes.models import format_course_price, get_cosmetic_verified_display_price
from course_modes.models import format_course_price, get_cosmetic_verified_display_price, CourseMode
from courseware.access import has_staff_access_to_preview_mode
from courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
from entitlements.models import CourseEntitlement
from lms.djangoapps.commerce.utils import EcommerceService
from openedx.core.djangoapps.catalog.utils import get_programs
from openedx.core.djangoapps.django_comment_common.models import Role
......@@ -232,14 +233,12 @@ def get_dashboard_course_info(user, dashboard_enrollments):
if DASHBOARD_INFO_FLAG.is_enabled():
# Get the enrollments here since the dashboard filters out those with completed entitlements
user_enrollments = CourseEnrollment.objects.select_related('course').filter(user_id=user.id)
audit_enrollments = user_enrollments.filter(mode='audit')
course_info = {
str(dashboard_enrollment.course): get_base_experiment_metadata_context(dashboard_enrollment.course,
user,
dashboard_enrollment,
user_enrollments,
audit_enrollments)
user_enrollments)
for dashboard_enrollment in dashboard_enrollments
}
return course_info
......@@ -257,8 +256,7 @@ def get_experiment_user_metadata_context(course, user):
has_non_audit_enrollments = False
try:
user_enrollments = CourseEnrollment.objects.select_related('course').filter(user_id=user.id)
audit_enrollments = user_enrollments.filter(mode='audit')
has_non_audit_enrollments = (len(audit_enrollments) != len(user_enrollments))
has_non_audit_enrollments = user_enrollments.exclude(mode__in=CourseMode.UPSELL_TO_VERIFIED_MODES).exists()
# TODO: clean up as part of REVO-28 (END)
enrollment = CourseEnrollment.objects.select_related(
'course'
......@@ -266,7 +264,11 @@ def get_experiment_user_metadata_context(course, user):
except CourseEnrollment.DoesNotExist:
pass # Not enrolled, use the default values
context = get_base_experiment_metadata_context(course, user, enrollment, user_enrollments, audit_enrollments)
has_entitlements = False
if user.is_authenticated():
has_entitlements = CourseEntitlement.objects.filter(user=user).exists()
context = get_base_experiment_metadata_context(course, user, enrollment, user_enrollments)
has_staff_access = has_staff_access_to_preview_mode(user, course.id)
forum_roles = []
if user.is_authenticated:
......@@ -280,7 +282,7 @@ def get_experiment_user_metadata_context(course, user):
user_partitions = {}
# TODO: clean up as part of REVO-28 (START)
context['has_non_audit_enrollments'] = has_non_audit_enrollments
context['has_non_audit_enrollments'] = has_non_audit_enrollments or has_entitlements
# TODO: clean up as part of REVO-28 (END)
context['has_staff_access'] = has_staff_access
context['forum_roles'] = forum_roles
......@@ -288,14 +290,14 @@ def get_experiment_user_metadata_context(course, user):
return context
def get_base_experiment_metadata_context(course, user, enrollment, user_enrollments, audit_enrollments):
def get_base_experiment_metadata_context(course, user, enrollment, user_enrollments):
"""
Return a context dictionary with the keys used by dashboard_metadata.html and user_metadata.html
"""
enrollment_mode = None
enrollment_time = None
# TODO: clean up as part of REVEM-199 (START)
program_key = get_program_context(course, user_enrollments, audit_enrollments)
program_key = get_program_context(course, user_enrollments)
# TODO: clean up as part of REVEM-199 (END)
if enrollment and enrollment.is_active:
enrollment_mode = enrollment.mode
......@@ -332,11 +334,13 @@ def get_audit_access_expiration(user, course):
# TODO: clean up as part of REVEM-199 (START)
def get_program_context(course, user_enrollments, audit_enrollments):
def get_program_context(course, user_enrollments):
"""
Return a context dictionary with program information.
"""
program_key = None
non_audit_enrollments = user_enrollments.exclude(mode__in=CourseMode.UPSELL_TO_VERIFIED_MODES)
if PROGRAM_INFO_FLAG.is_enabled():
programs = get_programs(course=course.id)
if programs:
......@@ -358,7 +362,6 @@ def get_program_context(course, user_enrollments, audit_enrollments):
# program has 3 courses (A, B and C), and the user previously purchased a certificate for A.
# The user is enrolled in audit mode for B. The "left to purchase price" should be the price of
# B+C.
non_audit_enrollments = [en for en in user_enrollments if en not in audit_enrollments]
courses_left_to_purchase = get_unenrolled_courses(courses, non_audit_enrollments)
if courses_left_to_purchase:
has_courses_left_to_purchase = True
......
......@@ -13,10 +13,12 @@ class CourseOverviewFactory(DjangoModelFactory):
class Meta(object):
model = CourseOverview
django_get_or_create = ('id', )
exclude = ('run', )
version = CourseOverview.VERSION
pre_requisite_courses = []
org = 'edX'
run = factory.Sequence('2012_Fall_{}'.format)
@factory.lazy_attribute
def _pre_requisite_courses_json(self):
......@@ -28,7 +30,7 @@ class CourseOverviewFactory(DjangoModelFactory):
@factory.lazy_attribute
def id(self):
return CourseLocator(self.org, 'toy', '2012_Fall')
return CourseLocator(self.org, 'toy', self.run)
@factory.lazy_attribute
def display_name(self):
......
......@@ -139,6 +139,8 @@ class ScheduleSendEmailTestMixin(FilteredQueryCountMixin):
factory_kwargs.setdefault('start', target_day)
factory_kwargs.setdefault('upgrade_deadline', upgrade_deadline)
factory_kwargs.setdefault('enrollment__course__self_paced', True)
# Make all schedules in the same course
factory_kwargs.setdefault('enrollment__course__run', '2012_Fall')
if hasattr(self, 'experience_type'):
factory_kwargs.setdefault('experience__experience_type', self.experience_type)
schedule = ScheduleFactory(**factory_kwargs)
......
......@@ -9,8 +9,10 @@ not other discounts like coupons or enterprise/program offers configured in ecom
"""
from course_modes.models import CourseMode
from entitlements.models import CourseEntitlement
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
from openedx.features.discounts.models import DiscountRestrictionConfig
from student.models import CourseEnrollment
# .. feature_toggle_name: discounts.enable_discounting
# .. feature_toggle_type: flag
......@@ -55,6 +57,15 @@ def can_receive_discount(user, course): # pylint: disable=unused-argument
if DiscountRestrictionConfig.disabled_for_course_stacked_config(course):
return False
# Don't allow users who have enrolled in any courses in non-upsellable
# modes
if CourseEnrollment.objects.filter(user=user).exclude(mode__in=CourseMode.UPSELL_TO_VERIFIED_MODES).exists():
return False
# Don't allow any users who have entitlements (past or present)
if CourseEntitlement.objects.filter(user=user).exists():
return False
return True
......
......@@ -2,19 +2,23 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
import ddt
from django.utils.timezone import now
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from entitlements.tests.factories import CourseEntitlementFactory
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from openedx.features.discounts.models import DiscountRestrictionConfig
from student.tests.factories import UserFactory
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from ..applicability import can_receive_discount, DISCOUNT_APPLICABILITY_FLAG
@ddt.ddt
class TestApplicability(ModuleStoreTestCase):
"""
Applicability determines if this combination of user and course can receive a discount. Make
......@@ -54,3 +58,40 @@ class TestApplicability(ModuleStoreTestCase):
DiscountRestrictionConfig.objects.create(disabled=True, course=disabled_course_overview)
applicability = can_receive_discount(user=self.user, course=disabled_course)
self.assertEqual(applicability, False)
@ddt.data(*(
[[]] +
[[mode] for mode in CourseMode.ALL_MODES] +
[
[mode1, mode2]
for mode1 in CourseMode.ALL_MODES
for mode2 in CourseMode.ALL_MODES
if mode1 != mode2
]
))
@override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True)
def test_can_receive_discount_previous_verified_enrollment(self, existing_enrollments):
"""
Ensure that only users who have not already purchased courses receive the discount.
"""
for mode in existing_enrollments:
CourseEnrollmentFactory.create(mode=mode, user=self.user)
applicability = can_receive_discount(user=self.user, course=self.course)
assert applicability == all(mode in CourseMode.UPSELL_TO_VERIFIED_MODES for mode in existing_enrollments)
@ddt.data(
None,
CourseMode.VERIFIED,
CourseMode.PROFESSIONAL,
)
@override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True)
def test_can_receive_discount_entitlement(self, entitlement_mode):
"""
Ensure that only users who have not already purchased courses receive the discount.
"""
if entitlement_mode is not None:
CourseEntitlementFactory.create(mode=entitlement_mode, user=self.user)
applicability = can_receive_discount(user=self.user, course=self.course)
assert applicability == (entitlement_mode is None)
......@@ -94,6 +94,7 @@ INSTALLED_APPS = (
# Django 1.11 demands to have imported models supported by installed apps.
'completion',
'entitlements',
)
LMS_ROOT_URL = "http://localhost:8000"
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment