154 lines
6.1 KiB
Python
154 lines
6.1 KiB
Python
"""
|
|
This file contains celery tasks for entitlements-related functionality.
|
|
"""
|
|
import logging
|
|
|
|
from celery import shared_task
|
|
from celery.utils.log import get_task_logger
|
|
from django.conf import settings # lint-amnesty, pylint: disable=unused-import
|
|
from django.contrib.auth import get_user_model
|
|
from edx_django_utils.monitoring import set_code_owner_attribute
|
|
|
|
from common.djangoapps.entitlements.models import CourseEntitlement, CourseEntitlementSupportDetail
|
|
|
|
LOGGER = get_task_logger(__name__)
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Maximum number of retries before giving up on awarding credentials.
|
|
# For reference, 11 retries with exponential backoff yields a maximum waiting
|
|
# time of 2047 seconds (about 30 minutes). Setting this to None could yield
|
|
# unwanted behavior: infinite retries.
|
|
MAX_RETRIES = 11
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
@shared_task(bind=True, ignore_result=True)
|
|
@set_code_owner_attribute
|
|
def expire_old_entitlements(self, start, end, logid='...'):
|
|
"""
|
|
This task is designed to be called to process a bundle of entitlements
|
|
that might be expired and confirm if we can do so. This is useful when
|
|
checking if an entitlement has just been abandoned by the learner and needs
|
|
to be expired. (In the normal course of a learner using the platform, the
|
|
entitlement will expire itself. But if a learner doesn't log in... So we
|
|
run this task every now and then to clear the backlog.)
|
|
|
|
Args:
|
|
start (int): The beginning id in the database to examine
|
|
end (int): The id in the database to stop examining at (i.e. range is exclusive)
|
|
logid (str): A string to identify this task in the logs
|
|
|
|
Returns:
|
|
None
|
|
|
|
"""
|
|
LOGGER.info('Running task expire_old_entitlements %d:%d [%s]', start, end, logid)
|
|
|
|
# This query could be optimized to return a more narrow set, but at a
|
|
# complexity cost. See bug LEARNER-3451 about improving it.
|
|
entitlements = CourseEntitlement.objects.filter(expired_at__isnull=True, id__gte=start, id__lte=end)
|
|
|
|
countdown = 2 ** self.request.retries
|
|
|
|
try:
|
|
for entitlement in entitlements:
|
|
# This property request will update the expiration if necessary as
|
|
# a side effect. We could manually call update_expired_at(), but
|
|
# let's use the same API the rest of the LMS does, to mimic normal
|
|
# usage and allow the update call to be an internal detail.
|
|
if entitlement.expired_at_datetime:
|
|
LOGGER.info('Expired entitlement with id %d [%s]', entitlement.id, logid)
|
|
|
|
except Exception as exc:
|
|
LOGGER.exception('Failed to expire entitlements [%s]', logid)
|
|
# The call above is idempotent, so retry at will
|
|
raise self.retry(exc=exc, countdown=countdown, max_retries=MAX_RETRIES)
|
|
|
|
LOGGER.info('Successfully completed the task expire_old_entitlements after examining %d entries [%s]', entitlements.count(), logid) # lint-amnesty, pylint: disable=line-too-long
|
|
|
|
|
|
@shared_task(bind=True)
|
|
@set_code_owner_attribute
|
|
def expire_and_create_entitlements(self, entitlement_ids, support_username):
|
|
"""
|
|
Expire entitlements older than one year.
|
|
|
|
Exception: if the entitlement is for a course in a list of exceptional courses,
|
|
expire those entitlements if they're older than 18 months instead.
|
|
|
|
Then create a copy of the expired entitlement to renew it for another year
|
|
/ 18 months.
|
|
|
|
Args:
|
|
entitlement_ids (List<int>): A list of entitlement ids to expire.
|
|
support_username (str): The username to attribute the entitlement
|
|
expiration and recreation to.
|
|
|
|
Returns:
|
|
None
|
|
|
|
"""
|
|
support_user = User.objects.get(username=support_username)
|
|
|
|
first_entitlement_id = entitlement_ids[0]
|
|
last_entitlement_id = entitlement_ids[-1]
|
|
|
|
log.info(
|
|
'Running task expire_and_create_entitlements over %d entitlements from id %d to id %d, task id :%s',
|
|
len(entitlement_ids),
|
|
first_entitlement_id,
|
|
last_entitlement_id,
|
|
self.request.id
|
|
)
|
|
|
|
try:
|
|
for entitlement_id in entitlement_ids:
|
|
entitlement = CourseEntitlement.objects.get(id=entitlement_id)
|
|
log.info('Started expiring entitlement with id %d, task id :%s',
|
|
entitlement.id,
|
|
self.request.id)
|
|
entitlement.expire_entitlement()
|
|
log.info('Expired entitlement with id %d as expiration period has reached, task id :%s',
|
|
entitlement.id,
|
|
self.request.id)
|
|
support_detail = {
|
|
'action': 'EXPIRE',
|
|
'comments': 'REV-3574',
|
|
'entitlement': entitlement,
|
|
'support_user': support_user,
|
|
}
|
|
CourseEntitlementSupportDetail.objects.create(**support_detail)
|
|
|
|
# Creating new entitlement and support details
|
|
new_entitlement_detail = {
|
|
'course_uuid': entitlement.course_uuid,
|
|
'user': entitlement.user,
|
|
'mode': entitlement.mode,
|
|
'refund_locked': True,
|
|
}
|
|
new_entitlement = CourseEntitlement.objects.create(**new_entitlement_detail)
|
|
support_detail = {
|
|
'action': 'CREATE',
|
|
'comments': 'REV-3574',
|
|
'entitlement': new_entitlement,
|
|
'support_user': support_user,
|
|
}
|
|
CourseEntitlementSupportDetail.objects.create(**support_detail)
|
|
log.info(
|
|
'created new entitlement with id %d corresponding to above expired entitlement'
|
|
'with id %d, task id :%s ',
|
|
new_entitlement.id,
|
|
entitlement.id,
|
|
self.request.id
|
|
)
|
|
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
log.exception('Failed to expire entitlements that reached their expiration period, task id :%s',
|
|
self.request.id)
|
|
|
|
log.info('Successfully completed the task expire_and_create_entitlements after examining'
|
|
'%d entries, task id :%s',
|
|
len(entitlement_ids),
|
|
self.request.id)
|