Skip to content

Commit

Permalink
Merge pull request #45 from edx/jlajoie/AA-68
Browse files Browse the repository at this point in the history
AA-68: Adds function to find users with assignments due on a day
  • Loading branch information
Jeff LaJoie authored Apr 29, 2020
2 parents 613efb0 + eaa1aff commit 9df7424
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 2 deletions.
2 changes: 1 addition & 1 deletion edx_when/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

from __future__ import absolute_import, unicode_literals

__version__ = '1.2.1'
__version__ = '1.2.2'

default_app_config = 'edx_when.apps.EdxWhenConfig' # pylint: disable=invalid-name
50 changes: 49 additions & 1 deletion edx_when/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import ObjectDoesNotExist
from django.db.models import DateTimeField, ExpressionWrapper, F, ObjectDoesNotExist, Q
from edx_django_utils.cache.utils import DEFAULT_REQUEST_CACHE
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey

from . import models

try:
from openedx.core.djangoapps.schedules.models import Schedule
# TODO: Move schedules into edx-when
except ImportError:
Schedule = None

log = logging.getLogger(__name__)

FIELDS_TO_EXTRACT = ('due', 'start', 'end')
Expand Down Expand Up @@ -357,6 +363,48 @@ def set_date_for_block(course_id, block_id, field, date_or_timedelta, user=None,
return existing_date.id


def get_schedules_with_due_date(course_id, assignment_date):
"""
Get all Schedules with assignments due on a specific date for a Course.
Arguments:
course_id: either a CourseKey or string representation of same
assignment_date: a date object
Returns:
a QuerySet of Schedule objects for Users who have content due on the specified assignment_date
"""
user_ids = models.UserDate.objects.select_related('content_date', 'content_date__policy').annotate(
computed_date=ExpressionWrapper(
F('content_date__policy__abs_date') + F('rel_date'),
output_field=DateTimeField()
),
).filter(
Q(computed_date__date=assignment_date, abs_date__isnull=True) |
Q(content_date__policy__abs_date__date=assignment_date, rel_date__isnull=True)
).values_list('user_id', flat=True).distinct()

user_date_overridden_schedules = Schedule.objects.filter(
enrollment__course_id=course_id,
enrollment__user_id__in=user_ids,
)

# Get all relative dates for a course, we want them distinct, it doesn't matter how many of each due date there is
rel_dates = models.ContentDate.objects.filter(
course_id=course_id,
policy__rel_date__isnull=False,
).select_related('policy').values_list('policy__rel_date', flat=True).distinct()

# Using those relative dates, get all Schedules that have a "hit" by working backwards to the start_date
rel_start_dates = [assignment_date - rel_date for rel_date in rel_dates]

# Exclude any user that has an overridden date for a course on the specified day so there aren't duplicates
return Schedule.objects.filter(
enrollment__course_id=course_id,
start_date__date__in=rel_start_dates,
).exclude(enrollment__user_id__in=user_ids).select_related('enrollment') | user_date_overridden_schedules


class BaseWhenException(Exception):
pass

Expand Down
59 changes: 59 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,65 @@ def setUp(self):
cache.clear()
DEFAULT_REQUEST_CACHE.clear()

@patch('edx_when.api.Schedule', DummySchedule)
def test_get_schedules_with_due_date_for_abs_date(self):
items = make_items(with_relative=False)
api.set_dates_for_course(items[0][0].course_key, items)
assignment_date = items[0][1].get('due')
api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', assignment_date, user=self.user)
# Specify the actual assignment due date so this will return true
schedules = api.get_schedules_with_due_date(items[0][0].course_key, datetime.date(assignment_date))
for schedule in schedules:
assert schedule.enrollment.course_id == items[0][0].course_key
assert schedule.enrollment.user.id == self.user.id

@patch('edx_when.api.Schedule', DummySchedule)
def test_get_schedules_with_due_date_for_rel_date(self):
items = make_items(with_relative=True)
api.set_dates_for_course(items[0][0].course_key, items)
relative_date = timedelta(days=2)
api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', relative_date, user=self.user)
assignment_date = items[0][1].get('due') + relative_date
# Specify the actual assignment due date so this will return true
schedules = api.get_schedules_with_due_date(items[0][0].course_key, assignment_date.date())
for schedule in schedules:
assert schedule.enrollment.course_id == items[0][0].course_key
assert schedule.enrollment.user.id == self.user.id

@patch('edx_when.api.Schedule', DummySchedule)
def test_get_schedules_with_due_date_for_abs_user_dates(self):
items = make_items(with_relative=True)
api.set_dates_for_course(items[0][0].course_key, items)
assignment_date = items[0][1].get('due')
api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', assignment_date, user=self.user)
models.UserDate.objects.create(
abs_date=assignment_date,
user=self.user,
content_date=models.ContentDate.objects.first(),
)
# Specify the actual assignment due date so this will return true
schedules = api.get_schedules_with_due_date(items[0][0].course_key, assignment_date.date())
assert len(schedules) == 1 # Make sure there's only one schedule, we should not have duplicates
assert schedules[0].enrollment.course_id == items[0][0].course_key
assert schedules[0].enrollment.user.id == self.user.id

@patch('edx_when.api.Schedule', DummySchedule)
def test_get_schedules_with_due_date_for_rel_user_dates(self):
items = make_items(with_relative=True)
api.set_dates_for_course(items[0][0].course_key, items)
assignment_date = items[0][1].get('due')
api.set_date_for_block(items[0][0].course_key, items[0][0], 'due', assignment_date, user=self.user)
models.UserDate.objects.create(
rel_date=timedelta(days=2),
user=self.user,
content_date=models.ContentDate.objects.first(),
)
# Specify the actual assignment due date so this will return true
schedules = api.get_schedules_with_due_date(items[0][0].course_key, assignment_date.date())
assert len(schedules) == 1 # Make sure there's only one schedule, we should not have duplicates
assert schedules[0].enrollment.course_id == items[0][0].course_key
assert schedules[0].enrollment.user.id == self.user.id

def test_set_dates_for_course(self):
items = make_items()
api.set_dates_for_course(items[0][0].course_key, items)
Expand Down

0 comments on commit 9df7424

Please sign in to comment.