Files
edx-platform/openedx/features/discounts/applicability.py
Calen Pennington 355826f7d8 Don't try to show anonymous users the first-purchase discount
This fixes errors like:

    Oct 10 12:46:07 ip-10-2-10-15 [service_variant=lms][django.request][env:prod-edx-edxapp] ERROR [ip-10-2-10-15  31278] [user None] [exception.py:135] - Internal Server Error: /courses/course-v1:HarvardX+1962USRx+3T2019/discussions/settings
    Traceback (most recent call last):
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
        response = get_response(request)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
        response = self._get_response(request)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
        response = self.process_exception_by_middleware(e, request)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
        response = wrapped_callback(request, *callback_args, **callback_kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/utils/decorators.py", line 185, in inner
        return func(*args, **kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/newrelic/hooks/framework_django.py", line 539, in wrapper
        return wrapped(*args, **kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/views/decorators/http.py", line 40, in inner
        return func(request, *args, **kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/utils/decorators.py", line 149, in _wrapped_view
        response = view_func(request, *args, **kwargs)
    File "/edx/app/edxapp/edx-platform/common/djangoapps/util/json_request.py", line 55, in parse_json_into_request
        return view_function(request, *args, **kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 23, in _wrapped_view
        return view_func(request, *args, **kwargs)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/views.py", line 936, in course_discussions_settings_handler
        course, discussion_settings
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/views.py", line 957, in get_divided_discussions
        all_discussions = utils.get_discussion_categories_ids(course, None, include_all=True)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/django_comment_client/utils.py", line 485, in get_discussion_categories_ids
        xblock.discussion_id for xblock in get_accessible_discussion_xblocks(course, user, include_all=include_all)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/django_comment_client/utils.py", line 146, in get_accessible_discussion_xblocks
        return get_accessible_discussion_xblocks_by_course_id(course.id, user, include_all=include_all)
    File "/edx/app/edxapp/edx-platform/openedx/core/lib/cache_utils.py", line 73, in decorator
        result = wrapped(*args, **kwargs)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/discussion/django_comment_client/utils.py", line 159, in get_accessible_discussion_xblocks_by_course_id
        if has_required_keys(xblock) and (include_all or has_access(user, 'load', xblock, course_id))
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/courseware/access.py", line 158, in has_access
        return _has_access_descriptor(user, action, obj, course_key)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/courseware/access.py", line 572, in _has_access_descriptor
        return _dispatch(checkers, action, user, descriptor)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/courseware/access.py", line 669, in _dispatch
        result = table[action]()
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/courseware/access.py", line 543, in can_load
        group_access_response = _has_group_access(descriptor, user, course_key)
    File "/edx/app/edxapp/edx-platform/lms/djangoapps/courseware/access.py", line 513, in _has_group_access
        user_fragment=partition.access_denied_fragment(descriptor, user, user_group, allowed_groups),
    File "/edx/app/edxapp/edx-platform/openedx/features/content_type_gating/partitions.py", line 98, in access_denied_fragment
        upgrade_price, _ = format_strikeout_price(user, course)
    File "/edx/app/edxapp/edx-platform/openedx/features/discounts/utils.py", line 22, in format_strikeout_price
        if can_receive_discount(user, course):
    File "/edx/app/edxapp/edx-platform/openedx/features/discounts/applicability.py", line 74, in can_receive_discount
        if CourseEnrollment.objects.filter(user=user).exclude(mode__in=CourseMode.UPSELL_TO_VERIFIED_MODES).exists():
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/manager.py", line 85, in manager_method
        return getattr(self.get_queryset(), name)(*args, **kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/query.py", line 787, in filter
        return self._filter_or_exclude(False, *args, **kwargs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/query.py", line 805, in _filter_or_exclude
        clone.query.add_q(Q(*args, **kwargs))
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1250, in add_q
        clause, _ = self._add_q(q_object, self.used_aliases)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1276, in _add_q
        allow_joins=allow_joins, split_subq=split_subq,
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1206, in build_filter
        condition = lookup_class(lhs, value)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/lookups.py", line 24, in __init__
        self.rhs = self.get_prep_lookup()
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/fields/related_lookups.py", line 112, in get_prep_lookup
        self.rhs = target_field.get_prep_value(self.rhs)
    File "/edx/app/edxapp/venvs/edxapp/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 966, in get_prep_value
        return int(value)
    TypeError: int() argument must be a string or a number, not 'AnonymousUser'

This is tracked in https://openedx.atlassian.net/browse/REV-988
[REV-988]
2019-10-10 10:53:26 -04:00

135 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
"""
Contains code related to computing discount percentage
and discount applicability.
WARNING:
Keep in mind that the code in this file only applies to discounts controlled in the lms like the first purchase offer,
not other discounts like coupons or enterprise/program offers configured in ecommerce.
"""
from __future__ import absolute_import
from datetime import datetime
from crum import get_current_request, impersonate
import pytz
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 student.models import CourseEnrollment
from track import segment
# .. feature_toggle_name: discounts.enable_discounting
# .. feature_toggle_type: flag
# .. feature_toggle_default: False
# .. feature_toggle_description: Toggle discounts always being disabled
# .. feature_toggle_category: discounts
# .. feature_toggle_use_cases: monitored_rollout
# .. feature_toggle_creation_date: 2019-4-16
# .. feature_toggle_expiration_date: None
# .. feature_toggle_warnings: None
# .. feature_toggle_tickets: REVEM-282
# .. feature_toggle_status: supported
DISCOUNT_APPLICABILITY_FLAG = WaffleFlag(
waffle_namespace=WaffleFlagNamespace(name=u'discounts'),
flag_name=u'enable_discounting',
flag_undefined_default=False
)
DISCOUNT_APPLICABILITY_HOLDBACK = 'first_purchase_discount_holdback'
def can_receive_discount(user, course): # pylint: disable=unused-argument
"""
Check all the business logic about whether this combination of user and course
can receive a discount.
"""
# Always disable discounts until we are ready to enable this feature
with impersonate(user):
if not DISCOUNT_APPLICABILITY_FLAG.is_enabled():
return False
# TODO: Add additional conditions to return False here
# Course end date needs to be in the future
if course.has_ended():
return False
# Course needs to have a non-expired verified mode
modes_dict = CourseMode.modes_for_course_dict(course=course, include_expired=False)
verified_mode = modes_dict.get('verified', None)
if not verified_mode:
return False
# Site, Partner, Course or Course Run not excluded from lms-controlled discounts
if DiscountRestrictionConfig.disabled_for_course_stacked_config(course):
return False
if user.is_anonymous:
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
# We can't import this at Django load time within the openedx tests settings context
from openedx.features.enterprise_support.utils import is_enterprise_learner
# Don't give discount to enterprise users
if is_enterprise_learner(user):
return False
# Excute holdback
if _is_in_holdback(user):
return False
return True
def _is_in_holdback(user):
"""
Return whether the specified user is in the first-purchase-discount holdback group.
"""
if datetime(2020, 8, 1, tzinfo=pytz.UTC) <= datetime.now(tz=pytz.UTC):
return False
# Holdback is 50/50
bucket = stable_bucketing_hash_group(DISCOUNT_APPLICABILITY_HOLDBACK, 2, user.username)
request = get_current_request()
if hasattr(request, 'session') and DISCOUNT_APPLICABILITY_HOLDBACK not in request.session:
properties = {
'site': request.site.domain,
'app_label': 'discounts',
'nonInteraction': 1,
'bucket': bucket,
'experiment': 'REVEM-363',
}
segment.track(
user_id=user.id,
event_name='edx.bi.experiment.user.bucketed',
properties=properties,
)
# Mark that we've recorded this bucketing, so that we don't do it again this session
request.session[DISCOUNT_APPLICABILITY_HOLDBACK] = True
return bucket == 0
def discount_percentage():
"""
Get the configured discount amount.
"""
# TODO: Add configuration information here
return 15