Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spike, no merge #302

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion enterprise_access/apps/content_assignments/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class LearnerContentAssignmentAdmin(DjangoQLSearchMixin, SimpleHistoryAdmin):
'last_notification_at',
'created',
'modified',
'lms_user_id',
#'lms_user_id',
'get_enterprise_customer_uuid',
)
autocomplete_fields = ['assignment_configuration']
Expand Down
19 changes: 16 additions & 3 deletions enterprise_access/apps/content_assignments/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

from django.db.models import Sum

from enterprise_access.apps.core.models import User

from .constants import LearnerContentAssignmentStateChoices
from .models import AssignmentConfiguration, LearnerContentAssignment
from .tasks import create_pending_enterprise_learner_for_assignment_task

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -136,21 +139,27 @@ def allocate_assignments(assignment_configuration, learner_emails, content_key,
learner_emails_with_existing_assignments = set()

# Split up the existing assignment records by state
updated_fields = {'content_quantity', 'state'}
for assignment in existing_assignments:
learner_emails_with_existing_assignments.add(assignment.learner_email)
if assignment.state in LearnerContentAssignmentStateChoices.REALLOCATE_STATES:
assignment.content_quantity = content_quantity
assignment.state = LearnerContentAssignmentStateChoices.ALLOCATED
if not assignment.lms_user_id:
first_user = User.objects.filter(email=assignment.learner_email).first()
if first_user:
assignment.lms_user_id = first_user.lms_user_id
updated_fields.add('lms_user_id')
assignment.full_clean()
cancelled_or_errored_to_update.append(assignment)
else:
already_allocated_or_accepted.append(assignment)

# Bulk update and get a list of refreshed objects
updated_assignments = _update_and_refresh_assignments(
updated_assignments = list(_update_and_refresh_assignments(
cancelled_or_errored_to_update,
['content_quantity', 'state']
)
list(updated_fields),
))

# Narrow down creation list of learner emails
learner_emails_for_assignment_creation = set(learner_emails) - learner_emails_with_existing_assignments
Expand All @@ -163,6 +172,10 @@ def allocate_assignments(assignment_configuration, learner_emails, content_key,
content_quantity,
)

# Link all non-cancelled assignment learners to the enterprise customer record.
for assignment in updated_assignments + created_assignments:
create_pending_enterprise_learner_for_assignment_task.delay(assignment.uuid)

# Return a mapping of the action we took to lists of relevant assignment records.
return {
'updated': updated_assignments,
Expand Down
17 changes: 17 additions & 0 deletions enterprise_access/apps/content_assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
from django.db.models import Case, Exists, F, Max, OuterRef, Q, Value, When
from django.db.models.fields import BooleanField, CharField, DateTimeField, IntegerField
from django.db.models.functions import Coalesce
from django.dispatch import receiver
from django.utils import timezone
from django_extensions.db.models import TimeStampedModel
from simple_history.models import HistoricalRecords
from simple_history.utils import bulk_create_with_history, bulk_update_with_history

from enterprise_access.apps.core.models import User

from .constants import (
AssignmentActionErrors,
AssignmentActions,
Expand Down Expand Up @@ -448,3 +451,17 @@ def __str__(self):
return (
f'uuid={self.uuid}, action_type={self.action_type}, error_reason={self.error_reason}'
)


@receiver(models.signals.post_save, sender=User)
def update_assignment_lms_user_id_from_user_email(sender, **kwargs): # pylint: disable=unused-argument
""" Post save hook to update assignment lms_user_id from core user records."""

user = kwargs['instance']
if user.lms_user_id:
LearnerContentAssignment.objects.filter(
learner_email=user.email,
lms_user_id=None,
).update(
lms_user_id=user.lms_user_id,
)
34 changes: 31 additions & 3 deletions enterprise_access/apps/subsidy_access_policy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from enterprise_access.apps.content_assignments import api as assignments_api
from enterprise_access.utils import is_none, is_not_none

from ..content_assignments.constants import LearnerContentAssignmentStateChoices
from ..content_assignments.models import AssignmentConfiguration
from .constants import (
CREDIT_POLICY_TYPE_PRIORITY,
Expand Down Expand Up @@ -645,7 +646,7 @@ def redeem(self, lms_user_id, content_key, all_transactions, metadata=None):
Returns:
A ledger transaction, or None if the subsidy was not redeemed.
"""
if self.access_method == AccessMethods.DIRECT:
if self.access_method in (AccessMethods.DIRECT, AccessMethods.ASSIGNED):
idempotency_key = create_idempotency_key_for_transaction(
subsidy_uuid=str(self.subsidy_uuid),
lms_user_id=lms_user_id,
Expand Down Expand Up @@ -1011,10 +1012,37 @@ def spend_available(self):
return max(0, super().spend_available + self.total_allocated)

def can_redeem(self, lms_user_id, content_key, skip_customer_user_check=False):
raise NotImplementedError
redeemable, reason, existing_transactions = super().can_redeem(
lms_user_id, content_key, skip_customer_user_check
)
if not redeemable:
return (redeemable, reason, existing_transactions)

course_for_key = 'course-v1:edX+DemoX+Demo_Course'
has_allocated_assignment_for_course = self.assignment_configuration.assignments.filter(
lms_user_id=lms_user_id,
content_key=course_for_key,
state=LearnerContentAssignmentStateChoices.ALLOCATED,
).exists()
if has_allocated_assignment_for_course:
return (True, None, existing_transactions)
return (False, 'REASON_NO_ALLOCATION', existing_transactions)

def redeem(self, lms_user_id, content_key, all_transactions, metadata=None):
raise NotImplementedError
course_for_key = 'course-v1:edX+DemoX+Demo_Course'
allocated_assignment = self.assignment_configuration.assignments.filter(
lms_user_id=lms_user_id,
content_key=course_for_key,
state=LearnerContentAssignmentStateChoices.ALLOCATED,
).first()
if not allocated_assignment:
raise Exception('no allocation')

transaction = super().redeem(lms_user_id, content_key, all_transactions, metadata=metadata)
allocated_assignment.state = LearnerContentAssignmentStateChoices.ACCEPTED
allocated_assignment.transaction_uuid = transaction['uuid']
allocated_assignment.save()
return transaction

def can_allocate(self, number_of_learners, content_key, content_price_cents):
"""
Expand Down