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]
135 lines
4.4 KiB
Python
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
|