* feat: linting before touching all these files All these files are old enough, relative either to our current linting rules or our current linter automation, that modifying anything in them either makes the linter cranky or wants to reformat the entire file. Rather than mixing cleanup with code changes, this commit just lints this set of files to our current standards.
180 lines
6.1 KiB
Python
180 lines
6.1 KiB
Python
"""Helper functions for working with Credentials."""
|
|
|
|
import logging
|
|
from typing import Dict, List
|
|
from urllib.parse import urljoin
|
|
|
|
import requests
|
|
from django.conf import settings
|
|
from django.contrib.auth import get_user_model
|
|
from edx_rest_api_client.auth import SuppliedJwtAuth
|
|
|
|
from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
|
|
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
|
|
from openedx.core.lib.edx_api_utils import get_api_data
|
|
|
|
log = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
|
|
def get_credentials_records_url(program_uuid=None):
|
|
"""
|
|
Returns a URL for a given records page (or general records list if given no UUID).
|
|
May return None if this feature is disabled.
|
|
|
|
Arguments:
|
|
program_uuid (str): Optional program uuid to link for a program records URL
|
|
"""
|
|
base_url = CredentialsApiConfig.current().public_records_url
|
|
if base_url is None:
|
|
return None
|
|
|
|
if program_uuid:
|
|
# Credentials expects Program UUIDs without dashes so we remove them here
|
|
stripped_program_uuid = program_uuid.replace("-", "")
|
|
return urljoin(base_url, f"programs/{stripped_program_uuid}")
|
|
|
|
return base_url
|
|
|
|
|
|
def get_credentials_api_client(user):
|
|
"""
|
|
Returns an authenticated Credentials API client.
|
|
|
|
Arguments:
|
|
user (User): The user to authenticate as when requesting credentials.
|
|
"""
|
|
scopes = ["email", "profile", "user_id"]
|
|
jwt = create_jwt_for_user(user, scopes=scopes)
|
|
|
|
client = requests.Session()
|
|
client.auth = SuppliedJwtAuth(jwt)
|
|
return client
|
|
|
|
|
|
def get_credentials_api_base_url(org=None):
|
|
"""
|
|
Returns a credentials API base URL.
|
|
|
|
Arguments:
|
|
org (str): Optional organization to look up the site config for, rather than the current request
|
|
"""
|
|
if org is None:
|
|
url = CredentialsApiConfig.current().internal_api_url # by current request
|
|
else:
|
|
url = CredentialsApiConfig.get_internal_api_url_for_org(org) # by org
|
|
|
|
return url
|
|
|
|
|
|
def get_credentials(
|
|
user: User,
|
|
program_uuid: str = None,
|
|
credential_type: str = None,
|
|
raise_on_error: bool = False,
|
|
) -> List[Dict]:
|
|
"""
|
|
Given a user, get credentials earned from the credentials service.
|
|
|
|
Arguments:
|
|
user (User): The user to authenticate as when requesting credentials.
|
|
|
|
Keyword Arguments:
|
|
program_uuid (str): UUID of the program whose credential to retrieve.
|
|
credential_type (str): Which type of credentials to return (course-run or program)
|
|
raise_on_error (bool): Reraise errors back to the caller, instead if returning empty results.
|
|
|
|
Returns:
|
|
list of dict, representing credentials returned by the Credentials
|
|
service.
|
|
"""
|
|
credential_configuration = CredentialsApiConfig.current()
|
|
|
|
querystring = {
|
|
"username": user.username,
|
|
"status": "awarded",
|
|
"only_visible": "True",
|
|
}
|
|
|
|
if program_uuid:
|
|
querystring["program_uuid"] = program_uuid
|
|
|
|
if credential_type:
|
|
querystring["type"] = credential_type
|
|
|
|
# Bypass caching for staff users, who may be generating credentials and
|
|
# want to see them displayed immediately.
|
|
use_cache = credential_configuration.is_cache_enabled and not user.is_staff
|
|
cache_key = f"{credential_configuration.CACHE_KEY}.{user.username}" if use_cache else None
|
|
if cache_key and program_uuid:
|
|
cache_key = f"{cache_key}.{program_uuid}"
|
|
|
|
api_client = get_credentials_api_client(user)
|
|
base_api_url = get_credentials_api_base_url()
|
|
|
|
return get_api_data(
|
|
credential_configuration,
|
|
"credentials",
|
|
api_client=api_client,
|
|
base_api_url=base_api_url,
|
|
querystring=querystring,
|
|
cache_key=cache_key,
|
|
raise_on_error=raise_on_error,
|
|
)
|
|
|
|
|
|
def get_courses_completion_status(username, course_run_ids):
|
|
"""
|
|
Given the username and course run ids, checks for course completion status
|
|
Arguments:
|
|
username (User): Username of the user whose credentials are being requested.
|
|
course_run_ids(List): list of course run ids for which we need to check the completion status
|
|
Returns:
|
|
list of course_run_ids for which user has completed the course
|
|
Boolean: True if an exception occurred while calling the api, False otherwise
|
|
"""
|
|
credential_configuration = CredentialsApiConfig.current()
|
|
if not credential_configuration.enabled:
|
|
log.warning("%s configuration is disabled.", credential_configuration.API_NAME)
|
|
return [], False
|
|
|
|
completion_status_url = f"{settings.CREDENTIALS_INTERNAL_SERVICE_URL}/api" "/credentials/v1/learner_cert_status/"
|
|
try:
|
|
api_client = get_credentials_api_client(User.objects.get(username=settings.CREDENTIALS_SERVICE_USERNAME))
|
|
api_response = api_client.post(
|
|
completion_status_url,
|
|
json={
|
|
"username": username,
|
|
"course_runs": course_run_ids,
|
|
},
|
|
)
|
|
api_response.raise_for_status()
|
|
course_completion_response = api_response.json()
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
log.exception(
|
|
"An unexpected error occurred while reqeusting course completion statuses "
|
|
"for user [%s] for course_run_ids [%s] with exc [%s]:",
|
|
username,
|
|
course_run_ids,
|
|
exc,
|
|
)
|
|
return [], True
|
|
log.info(
|
|
"Course completion status response for user [%s] for course_run_ids [%s] is [%s]",
|
|
username,
|
|
course_run_ids,
|
|
course_completion_response,
|
|
)
|
|
# Yes, This is course_credentials_data. The key is named status but
|
|
# it contains all the courses data from credentials.
|
|
course_credentials_data = course_completion_response.get("status", [])
|
|
if course_credentials_data is not None:
|
|
filtered_records = [
|
|
course_data["course_run"]["key"]
|
|
for course_data in course_credentials_data
|
|
if course_data["course_run"]["key"] in course_run_ids
|
|
and course_data["status"] == settings.CREDENTIALS_COURSE_COMPLETION_STATE
|
|
]
|
|
return filtered_records, False
|
|
return [], False
|