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: Added mobile api for course enrollment and other details. #35100

Merged
merged 6 commits into from
Jul 17, 2024
Merged
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
104 changes: 98 additions & 6 deletions lms/djangoapps/mobile_api/course_info/serializers.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
"""
Course Info serializers
"""
from rest_framework import serializers

from typing import Dict, Union

from rest_framework import serializers
from rest_framework.reverse import reverse

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.course import get_encoded_course_sharing_utm_params, get_link_for_about_page
from common.djangoapps.util.milestones_helpers import (
get_pre_requisite_courses_not_completed,
)
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.access import administrative_accesses_to_course_for_user
from common.djangoapps.util.milestones_helpers import get_pre_requisite_courses_not_completed
from lms.djangoapps.courseware.access import administrative_accesses_to_course_for_user, has_access
from lms.djangoapps.courseware.access_utils import check_course_open_for_learner
from lms.djangoapps.courseware.courses import get_assignments_completions
from lms.djangoapps.mobile_api.course_info.utils import get_user_certificate_download_url
from lms.djangoapps.mobile_api.users.serializers import ModeSerializer
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
Expand Down Expand Up @@ -136,3 +137,94 @@ def get_courseware_access(self, data: dict) -> dict:
Determine if the learner has access to the course, otherwise show error message.
"""
return has_access(data.get('user'), 'load_mobile', data.get('course')).to_json()


class CourseDetailSerializer(serializers.Serializer):
"""
Serializer for Course enrollment and overview details.
"""

id = serializers.SerializerMethodField()
course_access_details = serializers.SerializerMethodField()
certificate = serializers.SerializerMethodField()
enrollment_details = serializers.SerializerMethodField()
course_handouts = serializers.SerializerMethodField()
course_updates = serializers.SerializerMethodField()
discussion_url = serializers.SerializerMethodField()
course_info_overview = serializers.SerializerMethodField()

@staticmethod
def get_id(data):
"""
Returns course id.
"""
return str(data['course_id'])

@staticmethod
def get_course_overview(course_id):
"""
Returns course overview.
"""
return CourseOverview.get_from_id(course_id)

def get_course_info_overview(self, data):
"""
Returns course info overview.
"""
course_overview = self.get_course_overview(data['course_id'])
course_info_context = {'user': data['user']}
return CourseInfoOverviewSerializer(course_overview, context=course_info_context).data

@staticmethod
def get_discussion_url(data):
"""
Returns discussion url.
"""
course_overview = CourseOverview.get_from_id(data['course_id'])
if not course_overview.is_discussion_tab_enabled(data['user']):
return

return reverse('discussion_course', kwargs={'course_id': data['course_id']}, request=data['request'])

def get_course_access_details(self, data):
"""
Returns course access details.
"""
course_access_data = {
'course': self.get_course_overview(data['course_id']),
'course_id': data['course_id'],
'user': data['user'],
}
return CourseAccessSerializer(course_access_data).data

@staticmethod
def get_certificate(data):
"""
Returns course certificate url.
"""
return get_user_certificate_download_url(data['request'], data['user'], data['course_id'])

@staticmethod
def get_enrollment_details(data):
"""
Retrieve course enrollment details of the course.
"""
user_enrollment = CourseEnrollment.get_enrollment(user=data['user'], course_key=data['course_id'])
return MobileCourseEnrollmentSerializer(user_enrollment).data

@staticmethod
def get_course_handouts(data):
"""
Returns course_handouts.
"""

url_params = {'api_version': data['api_version'], 'course_id': data['course_id']}
return reverse('course-handouts-list', kwargs=url_params, request=data['request'])

@staticmethod
def get_course_updates(data):
"""
Returns course_updates.
"""
url_params = {'api_version': data['api_version'], 'course_id': data['course_id']}
return reverse('course-updates-list', kwargs=url_params, request=data['request'])
13 changes: 12 additions & 1 deletion lms/djangoapps/mobile_api/course_info/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
from django.conf import settings
from django.urls import path, re_path

from .views import CourseHandoutsList, CourseUpdatesList, CourseGoalsRecordUserActivity, BlocksInfoInCourseView
from .views import (
BlocksInfoInCourseView,
CourseEnrollmentDetailsView,
CourseGoalsRecordUserActivity,
CourseHandoutsList,
CourseUpdatesList
)

urlpatterns = [
re_path(
Expand All @@ -19,6 +25,11 @@
CourseUpdatesList.as_view(),
name='course-updates-list'
),
re_path(
fr'^{settings.COURSE_ID_PATTERN}/enrollment_details$',
CourseEnrollmentDetailsView.as_view(),
name='course-enrollment-details'
),
path('record_user_activity', CourseGoalsRecordUserActivity.as_view(), name='record_user_activity'),
path('blocks/', BlocksInfoInCourseView.as_view(), name="blocks_info_in_course"),
]
25 changes: 25 additions & 0 deletions lms/djangoapps/mobile_api/course_info/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Common utility methods for Course info apis.
"""

from lms.djangoapps.certificates.api import certificate_downloadable_status


def get_user_certificate_download_url(request, user, course_id):
"""
Return the information about the user's certificate in the course.

Arguments:
request (Request): The request object.
user (User): The user object.
course_id (str): The identifier of the course.
Returns:
(dict): A dict containing information about location of the user's certificate
or an empty dictionary, if there is no certificate.
"""
certificate_info = certificate_downloadable_status(user, course_id)
if certificate_info['is_downloadable']:
return {
'url': request.build_absolute_uri(certificate_info['download_url']),
}
return {}
66 changes: 39 additions & 27 deletions lms/djangoapps/mobile_api/course_info/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,26 @@
from rest_framework.reverse import reverse
from rest_framework.views import APIView

from common.djangoapps.student.models import CourseEnrollment, User as StudentUser
from common.djangoapps.static_replace import make_static_urls_absolute
from lms.djangoapps.certificates.api import certificate_downloadable_status
from lms.djangoapps.courseware.courses import get_assignments_grades, get_course_info_section_block
from lms.djangoapps.course_goals.models import UserActivity
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.models import User as StudentUser
from lms.djangoapps.course_api.blocks.views import BlocksInCourseView
from lms.djangoapps.course_goals.models import UserActivity
from lms.djangoapps.courseware.courses import get_assignments_grades, get_course_info_section_block
from lms.djangoapps.mobile_api.course_info.constants import BLOCK_STRUCTURE_CACHE_TIMEOUT
from lms.djangoapps.mobile_api.course_info.serializers import (
CourseInfoOverviewSerializer,
CourseAccessSerializer,
CourseDetailSerializer,
CourseInfoOverviewSerializer,
MobileCourseEnrollmentSerializer
)
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.view_utils import view_auth_classes
from openedx.core.lib.xblock_utils import get_course_update_items
from openedx.features.course_experience import ENABLE_COURSE_GOALS

from ..decorators import mobile_course_access, mobile_view
from .utils import get_user_certificate_download_url

User = get_user_model()
log = logging.getLogger(__name__)
Expand Down Expand Up @@ -308,27 +311,6 @@ def get_requested_user(self, user: UserType, username: Optional[str] = None) ->
log.warning('Provided username does not correspond to an existing user %s', username)
return None

def get_certificate(self, request, user, course_id):
"""
Return the information about the user's certificate in the course.

Arguments:
request (Request): The request object.
user (User): The user object.
course_id (str): The identifier of the course.
Returns:
(dict): A dict containing information about location of the user's certificate
or an empty dictionary, if there is no certificate.
"""
certificate_info = certificate_downloadable_status(user, course_id)
if certificate_info['is_downloadable']:
return {
'url': request.build_absolute_uri(
certificate_info['download_url']
),
}
return {}

def list(self, request, **kwargs): # pylint: disable=W0221
"""
REST API endpoint for listing all the blocks information in the course and
Expand Down Expand Up @@ -384,7 +366,7 @@ def list(self, request, **kwargs): # pylint: disable=W0221
'course': course_overview,
'course_id': course_key
}).data,
'certificate': self.get_certificate(request, requested_user, course_key),
'certificate': get_user_certificate_download_url(request, requested_user, course_key),
'enrollment_details': MobileCourseEnrollmentSerializer(user_enrollment).data,
})

Expand Down Expand Up @@ -425,3 +407,33 @@ def _extend_sequential_info_with_assignment_progress(
}
}
)


@mobile_view()
class CourseEnrollmentDetailsView(APIView):
"""
API that returns course details for logged-in user in the given course

**Example requests**:

This api works with all versions {api_version}, you can use: v0.5, v1, v2 or v3

GET /api/mobile/{api_version}/course_info/{course_id}}/enrollment_details

"""
@mobile_course_access()
def get(self, request, course, *args, **kwargs):
"""
Handle the GET request

Returns user enrollment and course details.
"""
data = {
'api_version': self.kwargs.get('api_version'),
'course_id': course.id,
'user': request.user,
'request': request,
}

course_detail = CourseDetailSerializer(data).data
return Response(data=course_detail, status=status.HTTP_200_OK)
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
"""
Tests for serializers for the Mobile Course Info
"""
from unittest.mock import MagicMock, Mock, patch
from typing import Dict, List, Tuple, Union
from unittest.mock import MagicMock, Mock, patch

import ddt
from django.test import TestCase

from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.mobile_api.course_info.serializers import (
CourseAccessSerializer,
CourseInfoOverviewSerializer,
)
from lms.djangoapps.mobile_api.course_info.serializers import CourseAccessSerializer, CourseInfoOverviewSerializer
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory


Expand Down
41 changes: 41 additions & 0 deletions lms/djangoapps/mobile_api/tests/test_course_info_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Tests for the Mobile Course Info utils
"""
from unittest.mock import patch

import ddt
from django.test import RequestFactory
from django.urls import reverse

from lms.djangoapps.mobile_api.course_info.utils import get_user_certificate_download_url
from lms.djangoapps.mobile_api.testutils import MobileAPITestCase


@ddt.ddt
class TestCourseInfoUtils(MobileAPITestCase):
"""
Tests for Course info utils
"""
@ddt.data(
({'is_downloadable': True, 'download_url': 'https://test_certificate_url'},
{'url': 'https://test_certificate_url'}),
({'is_downloadable': False}, {}),
)
@ddt.unpack
@patch('lms.djangoapps.mobile_api.course_info.utils.certificate_downloadable_status')
def test_get_certificate(self, certificate_status_return, expected_output, mock_certificate_status):
"""
Test get_certificate utility from the Course info utils.
Parameters:
certificate_status_return: returned value of the mocked certificate_downloadable_status function.
expected_output: return_value of the get_certificate function with specified mock return_value.
"""
mock_certificate_status.return_value = certificate_status_return
url = reverse('blocks_info_in_course', kwargs={'api_version': 'v3'})
request = RequestFactory().get(url)
request.user = self.user

certificate_info = get_user_certificate_download_url(
request, self.user, 'course-v1:Test+T101+2021_T1'
)
self.assertEqual(certificate_info, expected_output)
Loading
Loading