Skip to content

Commit

Permalink
feat: apply removable annotations on applicable querysets
Browse files Browse the repository at this point in the history
  • Loading branch information
shadinaif committed Nov 17, 2024
1 parent e3244f4 commit 468f0f9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 50 deletions.
2 changes: 1 addition & 1 deletion futurex_openedx_extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""One-line description for README and other doc files."""

__version__ = '0.9.13'
__version__ = '0.9.14'
56 changes: 35 additions & 21 deletions futurex_openedx_extensions/dashboard/details/courses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Courses details collectors"""
from __future__ import annotations

from common.djangoapps.student.models import CourseEnrollment
from completion.models import BlockCompletion
from django.contrib.auth import get_user_model
from django.db.models import (
Expand Down Expand Up @@ -29,6 +30,7 @@
check_staff_exist_queryset,
get_base_queryset_courses,
get_one_user_queryset,
update_removable_annotations,
)


Expand Down Expand Up @@ -62,6 +64,8 @@ def annotate_courses_rating_queryset(
), 0),
)

update_removable_annotations(queryset, removable=['rating_count', 'rating_total'])

return queryset


Expand Down Expand Up @@ -104,30 +108,34 @@ def get_courses_queryset(
if include_staff:
is_staff_queryset = Q(Value(False, output_field=BooleanField()))
else:
is_staff_queryset = check_staff_exist_queryset('courseenrollment__user_id', 'org', 'id')
is_staff_queryset = check_staff_exist_queryset(ref_user_id='user_id', ref_org='course__org', ref_course_id='id')

queryset = queryset.annotate(
enrolled_count=Count(
'courseenrollment',
filter=(
Q(courseenrollment__is_active=True) &
Q(courseenrollment__user__is_active=True) &
Q(courseenrollment__user__is_staff=False) &
Q(courseenrollment__user__is_superuser=False) &
~is_staff_queryset
),
)
enrolled_count=Coalesce(Subquery(
CourseEnrollment.objects.filter(
course_id=OuterRef('id'),
is_active=True,
user__is_active=True,
user__is_staff=False,
user__is_superuser=False,
# ).filter(
# ~is_staff_queryset,
).values('user_id').annotate(count=Count('id')).values('count'),
output_field=IntegerField(),
), 0)
).annotate(
active_count=Count(
'courseenrollment',
filter=(
Q(courseenrollment__is_active=True) &
Q(courseenrollment__user__is_active=True) &
Q(courseenrollment__user__is_staff=False) &
Q(courseenrollment__user__is_superuser=False) &
~is_staff_queryset
),
)
active_count=Coalesce(Subquery(
CourseEnrollment.objects.filter(
course_id=OuterRef('id'),
is_active=True,
user__is_active=True,
user__is_staff=False,
user__is_superuser=False,
# ).filter(
# ~is_staff_queryset,
).values('course_id').annotate(count=Count('id')).values('count'),
output_field=IntegerField(),
), 0)
).annotate(
certificates_count=Coalesce(Subquery(
GeneratedCertificate.objects.filter(
Expand All @@ -144,6 +152,10 @@ def get_courses_queryset(
)
)

update_removable_annotations(queryset, removable=[
'enrolled_count', 'active_count', 'certificates_count', 'completion_rate',
])

return queryset


Expand Down Expand Up @@ -216,4 +228,6 @@ def get_learner_courses_info_queryset(
)
)

update_removable_annotations(queryset, removable=['related_user_id', 'enrollment_date', 'last_activity'])

return queryset
64 changes: 37 additions & 27 deletions futurex_openedx_extensions/dashboard/details/learners.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

from datetime import timedelta

from common.djangoapps.student.models import CourseEnrollment
from django.contrib.auth import get_user_model
from django.db.models import BooleanField, Case, Count, Exists, OuterRef, Q, Subquery, Value, When
from django.db.models import BooleanField, Case, Count, Exists, IntegerField, OuterRef, Q, Subquery, Value, When
from django.db.models.functions import Coalesce
from django.db.models.query import QuerySet
from django.utils import timezone
from lms.djangoapps.certificates.models import GeneratedCertificate
Expand All @@ -17,6 +19,7 @@
get_learners_search_queryset,
get_one_user_queryset,
get_permitted_learners_queryset,
update_removable_annotations,
)


Expand All @@ -25,7 +28,7 @@ def get_courses_count_for_learner_queryset(
visible_courses_filter: bool | None = True,
active_courses_filter: bool | None = None,
include_staff: bool = False,
) -> Count:
) -> Coalesce:
"""
Annotate the given queryset with the courses count for the learner.
Expand All @@ -37,36 +40,37 @@ def get_courses_count_for_learner_queryset(
:type active_courses_filter: bool | None
:param include_staff: flag to include staff users
:type include_staff: bool
:return: Count of learners
:rtype: Count
:return: Count of enrolled courses
:rtype: Coalesce
"""
if not include_staff:
is_staff_queryset = check_staff_exist_queryset(
ref_user_id='id', ref_org='courseenrollment__course__org', ref_course_id='courseenrollment__course_id',
ref_user_id='user_id', ref_org='course__org', ref_course_id='course_id',
)
else:
is_staff_queryset = Q(Value(False, output_field=BooleanField()))

return Count(
'courseenrollment',
filter=(
Q(courseenrollment__course_id__in=get_base_queryset_courses(
return Coalesce(Subquery(
CourseEnrollment.objects.filter(
user_id=OuterRef('id'),
course_id__in=get_base_queryset_courses(
fx_permission_info,
visible_filter=visible_courses_filter,
active_filter=active_courses_filter,
)) &
Q(courseenrollment__is_active=True) &
~is_staff_queryset
),
distinct=True
)
),
is_active=True,
).filter(
~is_staff_queryset,
).values('user_id').annotate(count=Count('id')).values('count'),
output_field=IntegerField(),
), 0)


def get_certificates_count_for_learner_queryset(
fx_permission_info: dict,
visible_courses_filter: bool | None = True,
active_courses_filter: bool | None = None,
) -> Count:
) -> Coalesce:
"""
Annotate the given queryset with the certificate counts.
Expand All @@ -76,23 +80,23 @@ def get_certificates_count_for_learner_queryset(
:type visible_courses_filter: bool | None
:param active_courses_filter: Value to filter courses on active status. None means no filter.
:type active_courses_filter: bool | None
:return: QuerySet of learners
:rtype: QuerySet
:return: Count of certificates
:rtype: Coalesce
"""
return Count(
'generatedcertificate',
filter=(
Q(generatedcertificate__course_id__in=Subquery(
return Coalesce(Subquery(
GeneratedCertificate.objects.filter(
user_id=OuterRef('id'),
course_id__in=Subquery(
get_base_queryset_courses(
fx_permission_info,
visible_filter=visible_courses_filter,
active_filter=active_courses_filter
).values_list('id', flat=True)
)) &
Q(generatedcertificate__status='downloadable')
),
distinct=True
)
),
status='downloadable',
).values('user_id').annotate(count=Count('id')).values('count'),
output_field=IntegerField(),
), 0)


def get_learners_queryset(
Expand Down Expand Up @@ -141,6 +145,8 @@ def get_learners_queryset(
)
).select_related('profile', 'extrainfo').order_by('id')

update_removable_annotations(queryset, removable=['courses_count', 'certificates_count'])

return queryset


Expand Down Expand Up @@ -202,6 +208,8 @@ def get_learners_by_course_queryset(
)
).select_related('profile').order_by('id')

update_removable_annotations(queryset, removable=['certificate_available', 'course_score', 'active_in_course'])

return queryset


Expand Down Expand Up @@ -249,4 +257,6 @@ def get_learner_info_queryset(
)
).select_related('profile')

update_removable_annotations(queryset, removable=['courses_count', 'certificates_count'])

return queryset
4 changes: 4 additions & 0 deletions futurex_openedx_extensions/helpers/querysets.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,15 @@ def get_base_queryset_courses(
),
)

update_removable_annotations(q_set, removable=['course_is_active', 'course_is_visible'])

if active_filter is not None:
q_set = q_set.filter(course_is_active=active_filter)
update_removable_annotations(q_set, not_removable=['course_is_active'])

if visible_filter is not None:
q_set = q_set.filter(course_is_visible=visible_filter)
update_removable_annotations(q_set, not_removable=['course_is_visible'])

return q_set

Expand Down
6 changes: 5 additions & 1 deletion tests/test_dashboard/test_details/test_details_courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ def test_get_courses_queryset_result_for_completion_rate(
course_id = 'course-v1:ORG1+2+2'

# Initial state: no enrollments, expect completion rate to be 0
print(CourseEnrollment.objects.filter(course_id=course_id).count())
for q in CourseEnrollment.objects.filter(course_id=course_id):
print(q.user_id)
course = get_courses_queryset(fx_permission_info).get(id=course_id)
assert course.enrolled_count == 0
assert course.enrolled_count == 1
assert course.certificates_count == 0
assert course.completion_rate == 0.0

Expand All @@ -87,6 +90,7 @@ def test_get_courses_queryset_result_for_completion_rate(

# Assert values after 2 enrollments but 0 certificates
course = get_courses_queryset(fx_permission_info).get(id=course_id)
print(str(get_courses_queryset(fx_permission_info).query))
assert course.enrolled_count == 2
assert course.certificates_count == 0
assert course.completion_rate == 0.0
Expand Down

0 comments on commit 468f0f9

Please sign in to comment.