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

feat: respect user roles in dashboard APIs #36

Merged
merged 4 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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 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.4.6"
__version__ = "0.5.2"
3 changes: 3 additions & 0 deletions futurex_openedx_extensions/dashboard/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ class DashboardConfig(AppConfig):
"""Configuration for the dashboard Django application"""

name = 'futurex_openedx_extensions.dashboard'
label = 'fx_dashboard'

# pylint: disable=duplicate-code
plugin_app = {
'settings_config': {
'lms.djangoapp': {
Expand All @@ -27,3 +29,4 @@ class DashboardConfig(AppConfig):
},
},
}
# pylint: enable=duplicate-code
26 changes: 11 additions & 15 deletions futurex_openedx_extensions/dashboard/details/courses.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Courses details collectors"""
from __future__ import annotations

from typing import List

from common.djangoapps.student.models import CourseAccessRole
from completion.models import BlockCompletion
from django.db.models import (
Expand All @@ -26,17 +24,19 @@
from lms.djangoapps.certificates.models import GeneratedCertificate

from futurex_openedx_extensions.helpers.querysets import get_base_queryset_courses
from futurex_openedx_extensions.helpers.tenants import get_course_org_filter_list


def get_courses_queryset(
tenant_ids: List, search_text: str = None, visible_filter: bool | None = True, active_filter: bool | None = None
fx_permission_info: dict,
search_text: str = None,
visible_filter: bool | None = True,
active_filter: bool | None = None
) -> QuerySet:
"""
Get the courses queryset for the given tenant IDs and search text.

:param tenant_ids: List of tenant IDs to get the courses for
:type tenant_ids: List
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param search_text: Search text to filter the courses by
:type search_text: str
:param visible_filter: Value to filter courses on catalog visibility. None means no filter
Expand All @@ -46,10 +46,8 @@ def get_courses_queryset(
:return: QuerySet of courses
:rtype: QuerySet
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']

queryset = get_base_queryset_courses(
course_org_filter_list, visible_filter=visible_filter, active_filter=active_filter,
fx_permission_info, visible_filter=visible_filter, active_filter=active_filter,
)

search_text = (search_text or '').strip()
Expand Down Expand Up @@ -121,13 +119,13 @@ def get_courses_queryset(


def get_learner_courses_info_queryset(
tenant_ids: List, user_id: int, visible_filter: bool | None = True, active_filter: bool | None = None
fx_permission_info: dict, user_id: int, visible_filter: bool | None = True, active_filter: bool | None = None
) -> QuerySet:
"""
Get the learner's courses queryset for the given user ID. This method assumes a valid user ID.

:param tenant_ids: List of tenant IDs to get the learner for
:type tenant_ids: List
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param user_id: The user ID to get the learner for
:type user_id: int
:param visible_filter: Value to filter courses on catalog visibility. None means no filter
Expand All @@ -137,10 +135,8 @@ def get_learner_courses_info_queryset(
:return: QuerySet of learners
:rtype: QuerySet
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']

queryset = get_base_queryset_courses(
course_org_filter_list, visible_filter=visible_filter, active_filter=active_filter,
fx_permission_info, visible_filter=visible_filter, active_filter=active_filter,
).filter(
courseenrollment__user_id=user_id,
courseenrollment__is_active=True,
Expand Down
49 changes: 24 additions & 25 deletions futurex_openedx_extensions/dashboard/details/learners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from __future__ import annotations

from datetime import timedelta
from typing import List

from common.djangoapps.student.models import CourseAccessRole
from django.contrib.auth import get_user_model
Expand All @@ -14,19 +13,19 @@
from lms.djangoapps.grades.models import PersistentCourseGrade

from futurex_openedx_extensions.helpers.querysets import get_base_queryset_courses, get_has_site_login_queryset
from futurex_openedx_extensions.helpers.tenants import get_course_org_filter_list, get_tenants_sites
from futurex_openedx_extensions.helpers.tenants import get_tenants_sites


def get_courses_count_for_learner_queryset(
course_org_filter_list: List[str],
fx_permission_info: dict,
visible_courses_filter: bool = True,
active_courses_filter: bool = None,
) -> QuerySet:
"""
Get the courses count for the given learner.
Annotate the given queryset with the courses count for the learner.

:param course_org_filter_list: List of course organizations to filter by
:type course_org_filter_list: List[str]
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param visible_courses_filter: Value to filter courses on catalog visibility. None means no filter.
:type visible_courses_filter: bool
:param active_courses_filter: Value to filter courses on active status. None means no filter.
Expand All @@ -38,7 +37,7 @@ def get_courses_count_for_learner_queryset(
'courseenrollment',
filter=(
Q(courseenrollment__course_id__in=get_base_queryset_courses(
course_org_filter_list,
fx_permission_info,
visible_filter=visible_courses_filter,
active_filter=active_courses_filter,
)) &
Expand All @@ -54,15 +53,15 @@ def get_courses_count_for_learner_queryset(


def get_certificates_count_for_learner_queryset(
course_org_filter_list: List[str],
fx_permission_info: dict,
visible_courses_filter: bool = True,
active_courses_filter: bool = None,
) -> QuerySet:
"""
Annotate the given queryset with the certificate counts.

:param course_org_filter_list: List of course organizations to filter by
:type course_org_filter_list: List[str]
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param visible_courses_filter: Value to filter courses on catalog visibility. None means no filter.
:type visible_courses_filter: bool
:param active_courses_filter: Value to filter courses on active status. None means no filter.
Expand All @@ -75,7 +74,7 @@ def get_certificates_count_for_learner_queryset(
filter=(
Q(generatedcertificate__course_id__in=Subquery(
get_base_queryset_courses(
course_org_filter_list,
fx_permission_info,
visible_filter=visible_courses_filter,
active_filter=active_courses_filter
).values_list('id', flat=True)
Expand Down Expand Up @@ -127,13 +126,16 @@ def get_learners_search_queryset(


def get_learners_queryset(
tenant_ids: List, search_text: str = None, visible_courses_filter: bool = True, active_courses_filter: bool = None
fx_permission_info: dict,
search_text: str = None,
visible_courses_filter: bool = True,
active_courses_filter: bool = None
) -> QuerySet:
"""
Get the learners queryset for the given tenant IDs and search text.

:param tenant_ids: List of tenant IDs to get the learners for
:type tenant_ids: List
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param search_text: Search text to filter the learners by
:type search_text: str
:param visible_courses_filter: Value to filter courses on catalog visibility. None means no filter
Expand All @@ -143,20 +145,19 @@ def get_learners_queryset(
:return: QuerySet of learners
:rtype: QuerySet
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']
tenant_sites = get_tenants_sites(tenant_ids)
tenant_sites = get_tenants_sites(fx_permission_info['permitted_tenant_ids'])

queryset = get_learners_search_queryset(search_text)

queryset = queryset.annotate(
courses_count=get_courses_count_for_learner_queryset(
course_org_filter_list,
fx_permission_info,
visible_courses_filter=visible_courses_filter,
active_courses_filter=active_courses_filter,
)
).annotate(
certificates_count=get_certificates_count_for_learner_queryset(
course_org_filter_list,
fx_permission_info,
visible_courses_filter=visible_courses_filter,
active_courses_filter=active_courses_filter,
)
Expand Down Expand Up @@ -226,13 +227,13 @@ def get_learners_by_course_queryset(course_id: str, search_text: str = None) ->


def get_learner_info_queryset(
tenant_ids: List, user_id: int, visible_courses_filter: bool = True, active_courses_filter: bool = None
fx_permission_info: dict, user_id: int, visible_courses_filter: bool = True, active_courses_filter: bool = None
) -> QuerySet:
"""
Get the learner queryset for the given user ID. This method assumes a valid user ID.

:param tenant_ids: List of tenant IDs to get the learner for
:type tenant_ids: List
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param user_id: The user ID to get the learner for
:type user_id: int
:param visible_courses_filter: Value to filter courses on catalog visibility. None means no filter
Expand All @@ -242,17 +243,15 @@ def get_learner_info_queryset(
:return: QuerySet of learners
:rtype: QuerySet
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']

queryset = get_user_model().objects.filter(id=user_id).annotate(
courses_count=get_courses_count_for_learner_queryset(
course_org_filter_list,
fx_permission_info,
visible_courses_filter=visible_courses_filter,
active_courses_filter=active_courses_filter,
)
).annotate(
certificates_count=get_certificates_count_for_learner_queryset(
course_org_filter_list,
fx_permission_info,
visible_courses_filter=visible_courses_filter,
active_courses_filter=active_courses_filter,
)
Expand Down
21 changes: 2 additions & 19 deletions futurex_openedx_extensions/dashboard/settings/common_production.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
"""Common Settings"""


def plugin_settings(settings):
"""
plugin settings
"""
# Cache timeout for tenants info
settings.FX_CACHE_TIMEOUT_TENANTS_INFO = getattr(
settings,
"FX_CACHE_TIMEOUT_TENANTS_INFO",
60 * 60 * 2, # 2 hours
)

settings.FX_RATE_LIMIT_ANONYMOUS_DATA_RETRIEVE = getattr(
settings,
"FX_RATE_LIMIT_ANONYMOUS_DATA_RETRIEVE",
"1/minute",
)

if settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"].get("fx_anonymous_data_retrieve") is None:
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["fx_anonymous_data_retrieve"] = "5/hour"
def plugin_settings(settings): # pylint: disable=unused-argument
"""plugin settings"""
13 changes: 5 additions & 8 deletions futurex_openedx_extensions/dashboard/statistics/certificates.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
"""functions for getting statistics about certificates"""
from __future__ import annotations

from typing import Dict, List
from typing import Dict

from django.db.models import Count, OuterRef, Subquery
from lms.djangoapps.certificates.models import GeneratedCertificate
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview

from futurex_openedx_extensions.helpers.querysets import get_base_queryset_courses
from futurex_openedx_extensions.helpers.tenants import get_course_org_filter_list


def get_certificates_count(
tenant_ids: List[int], visible_courses_filter: bool = True, active_courses_filter: bool = None
fx_permission_info: dict, visible_courses_filter: bool = True, active_courses_filter: bool = None
) -> Dict[str, int]:
"""
Get the count of issued certificates in the given tenants. The count is grouped by organization. Certificates
for admins, staff, and superusers are also included.

:param tenant_ids: List of tenant IDs to get the count for
:type tenant_ids: List[int]
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param visible_courses_filter: Value to filter courses on catalog visibility. None means no filter.
:type visible_courses_filter: bool
:param active_courses_filter: Value to filter courses on active status. None means no filter.
:type active_courses_filter: bool
:return: Count of certificates per organization
:rtype: Dict[str, int]
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']

result = list(GeneratedCertificate.objects.filter(
status='downloadable',
course_id__in=get_base_queryset_courses(
course_org_filter_list,
fx_permission_info,
visible_filter=visible_courses_filter,
active_filter=active_courses_filter,
),
Expand Down
23 changes: 8 additions & 15 deletions futurex_openedx_extensions/dashboard/statistics/courses.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
"""functions for getting statistics about courses"""
from __future__ import annotations

from typing import List

from django.db.models import Case, CharField, Count, Q, Value, When
from django.db.models.query import QuerySet
from django.utils.timezone import now

from futurex_openedx_extensions.helpers.constants import COURSE_STATUSES
from futurex_openedx_extensions.helpers.querysets import get_base_queryset_courses
from futurex_openedx_extensions.helpers.tenants import get_course_org_filter_list


def get_courses_count(tenant_ids: List[int], visible_filter: bool = True, active_filter: bool = None) -> QuerySet:
def get_courses_count(fx_permission_info: dict, visible_filter: bool = True, active_filter: bool = None) -> QuerySet:
"""
Get the count of courses in the given tenants

:param tenant_ids: List of tenant IDs to get the count for
:type tenant_ids: List[int]
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param visible_filter: Value to filter courses on catalog visibility. None means no filter.
:type visible_filter: bool
:param active_filter: Value to filter courses on active status. None means no filter.
:type active_filter: bool
:return: QuerySet of courses count per organization
:rtype: QuerySet
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']

q_set = get_base_queryset_courses(
course_org_filter_list, visible_filter=visible_filter, active_filter=active_filter
fx_permission_info, visible_filter=visible_filter, active_filter=active_filter
)

return q_set.values('org').annotate(
Expand All @@ -37,24 +32,22 @@ def get_courses_count(tenant_ids: List[int], visible_filter: bool = True, active


def get_courses_count_by_status(
tenant_ids: List[int], visible_filter: bool = True, active_filter: bool = None
fx_permission_info: dict, visible_filter: bool = True, active_filter: bool = None
) -> QuerySet:
"""
Get the count of courses in the given tenants by status

:param tenant_ids: List of tenant IDs to get the count for
:type tenant_ids: List[int]
:param fx_permission_info: Dictionary containing permission information
:type fx_permission_info: dict
:param visible_filter: Value to filter courses on catalog visibility. None means no filter
:type visible_filter: bool
:param active_filter: Value to filter courses on active status. None means no filter (according to dates)
:type active_filter: bool
:return: QuerySet of courses count per organization and status
:rtype: QuerySet
"""
course_org_filter_list = get_course_org_filter_list(tenant_ids)['course_org_filter_list']

q_set = get_base_queryset_courses(
course_org_filter_list, visible_filter=visible_filter, active_filter=active_filter
fx_permission_info, visible_filter=visible_filter, active_filter=active_filter
)

q_set = q_set.annotate(
Expand Down
Loading