コード例 #1
0
ファイル: views.py プロジェクト: sliva/edx-platform
class CertificatesListView(APIView):
    """REST API endpoints for listing certificates."""
    authentication_classes = (
        JwtAuthentication,
        BearerAuthenticationAllowInactiveUser,
        SessionAuthenticationAllowInactiveUser,
    )
    permission_classes = (
        C(IsAuthenticated) & (
            C(permissions.NotJwtRestrictedApplication) |
            (
                C(permissions.JwtRestrictedApplication) &
                permissions.JwtHasScope &
                permissions.JwtHasUserFilterForRequestedUser
            )
        ),
        (C(permissions.IsStaff) | IsOwnerOrPublicCertificates),
    )

    required_scopes = ['certificates:read']

    @apidocs.schema(parameters=[
        apidocs.string_parameter(
            'username',
            apidocs.ParameterLocation.PATH,
            description="The users to get certificates for",
        )
    ])
    def get(self, request, username):
        """Get a paginated list of bookmarks for a user.

        **Use Case**

        Get the list of viewable course certificates for a specific user.

        **Example Request**

        GET /api/certificates/v0/certificates/{username}

        **GET Response Values**

            If the request for information about the user's certificates is successful,
            an HTTP 200 "OK" response is returned.

            The HTTP 200 response contains a list of dicts with the following keys/values.

            * username: A string representation of an user's username passed in the request.

            * course_id: A string representation of a Course ID.

            * course_display_name: A string representation of the Course name.

            * course_organization: A string representation of the organization associated with the Course.

            * certificate_type: A string representation of the certificate type.
                Can be honor|verified|professional

            * created_date: Date/time the certificate was created, in ISO-8661 format.

            * status: A string representation of the certificate status.

            * is_passing: True if the certificate has a passing status, False if not.

            * download_url: A string representation of the certificate url.

            * grade: A string representation of a float for the user's course grade.

        **Example GET Response**

            [{
                "username": "******",
                "course_id": "edX/DemoX/Demo_Course",
                "certificate_type": "verified",
                "created_date": "2015-12-03T13:14:28+0000",
                "status": "downloadable",
                "is_passing": true,
                "download_url": "http://www.example.com/cert.pdf",
                "grade": "0.98"
            }]
        """
        user_certs = []
        if self._viewable_by_requestor(request, username):
            for user_cert in self._get_certificates_for_user(username):
                user_certs.append({
                    'username': user_cert.get('username'),
                    'course_id': six.text_type(user_cert.get('course_key')),
                    'course_display_name': user_cert.get('course_display_name'),
                    'course_organization': user_cert.get('course_organization'),
                    'certificate_type': user_cert.get('type'),
                    'created_date': user_cert.get('created'),
                    'modified_date': user_cert.get('modified'),
                    'status': user_cert.get('status'),
                    'is_passing': user_cert.get('is_passing'),
                    'download_url': user_cert.get('download_url'),
                    'grade': user_cert.get('grade'),
                })
        return Response(user_certs)

    def _viewable_by_requestor(self, request, username):
        """
        Returns whether or not the requesting user is allowed to view the given user's certificates.
        """
        try:
            user = User.objects.select_related('profile').get(username=username)
        except User.DoesNotExist:
            return False

        is_owner = request.user.username == username
        is_staff = request.user.is_staff
        certificates_viewable = 'course_certificates' in visible_fields(user.profile, user)

        return is_owner or is_staff or certificates_viewable

    def _get_certificates_for_user(self, username):
        """
        Returns a user's viewable certificates sorted by course name.
        """
        course_certificates = get_certificates_for_user(username)
        passing_certificates = {}
        for course_certificate in course_certificates:
            if course_certificate.get('is_passing', False):
                course_key = course_certificate['course_key']
                passing_certificates[course_key] = course_certificate

        viewable_certificates = []
        for course_key, course_overview in CourseOverview.get_from_ids(
            list(passing_certificates.keys())
        ).items():
            if not course_overview:
                # For deleted XML courses in which learners have a valid certificate.
                # i.e. MITx/7.00x/2013_Spring
                course_overview = self._get_pseudo_course_overview(course_key)
            if certificates_viewable_for_course(course_overview):
                course_certificate = passing_certificates[course_key]
                # add certificate into viewable certificate list only if it's a PDF certificate
                # or there is an active certificate configuration.
                if course_certificate['is_pdf_certificate'] or course_overview.has_any_active_web_certificate:
                    course_certificate['course_display_name'] = course_overview.display_name_with_default
                    course_certificate['course_organization'] = course_overview.display_org_with_default
                    viewable_certificates.append(course_certificate)

        viewable_certificates.sort(key=lambda certificate: certificate['created'])
        return viewable_certificates

    def _get_pseudo_course_overview(self, course_key):
        """
        Returns a pseudo course overview object for deleted courses.
        """
        course_run = get_course_run_details(course_key, ['title'])
        return CourseOverview(
            display_name=course_run.get('title'),
            display_org_with_default=course_key.org,
            certificates_show_before_end=True
        )
コード例 #2
0
ファイル: tabs.py プロジェクト: patilswapnilv/edx-platform
class CourseTabSettingsView(DeveloperErrorViewMixin, APIView):
    """
    API view for course tabs settings.
    """

    def handle_exception(self, exc):
        """Handle NotImplementedError and return a proper response for it."""
        if isinstance(exc, NotImplementedError):
            return self._make_error_response(400, str(exc))
        if isinstance(exc, ItemNotFoundError):
            return self._make_error_response(400, str(exc))
        return super().handle_exception(exc)

    @apidocs.schema(
        body=CourseTabUpdateSerializer(help_text=_("Change the visibility of tabs in a course.")),
        parameters=[
            apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
            apidocs.string_parameter("tab_id", apidocs.ParameterLocation.QUERY, description="Tab ID"),
            apidocs.string_parameter("tab_location", apidocs.ParameterLocation.QUERY, description="Tab usage key"),
        ],
        responses={
            204: "In case of success, a 204 is returned with no content.",
            401: "The requester is not authenticated.",
            403: "The requester cannot access the specified course.",
            404: "The requested course does not exist.",
        },
    )
    @verify_course_exists()
    def post(self, request: Request, course_id: str) -> Response:
        """
        Change visibility of tabs in a course.

        **Example Requests**

        You can provide either a tab_id or a tab_location.

        Hide a course tab using ``tab_id``:

            POST /api/contentstore/v0/tabs/{course_id}/settings/?tab_id={tab_id} {
                "is_hidden": true
            }

        Hide a course tab using ``tab_location``

            POST /api/contentstore/v0/tabs/{course_id}/settings/?tab_location={tab_location} {
                "is_hidden": true
            }

        **Response Values**

        If the request is successful, an HTTP 204 response is returned
        without any content.
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_write_access(request.user, course_key):
            self.permission_denied(request)

        tab_id_locator = TabIDLocatorSerializer(data=request.query_params)
        tab_id_locator.is_valid(raise_exception=True)

        course_module = modulestore().get_course(course_key)
        serializer = CourseTabUpdateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        edit_tab_handler(
            course_module,
            {
                "tab_id_locator": tab_id_locator.data,
                **serializer.data,
            },
            request.user,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
コード例 #3
0
ファイル: tabs.py プロジェクト: patilswapnilv/edx-platform
class CourseTabListView(DeveloperErrorViewMixin, APIView):
    """
    API view to list course tabs.
    """

    @apidocs.schema(
        parameters=[apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID")],
        responses={
            200: CourseTabSerializer,
            401: "The requester is not authenticated.",
            403: "The requester cannot access the specified course.",
            404: "The requested course does not exist.",
        },
    )
    @verify_course_exists()
    def get(self, request: Request, course_id: str) -> Response:
        """
        Get a list of all the tabs in a course including hidden tabs.

        **Example Request**

            GET /api/contentstore/v0/tabs/{course_id}

        **Response Values**

        If the request is successful, an HTTP 200 "OK" response is returned.

        The HTTP 200 response contains a list of objects that contain info
        about each tab.

        **Example Response**

        ```json
        [
            {
                "course_staff_only": false,
                "is_hidden": false,
                "is_hideable": false,
                "is_movable": false,
                "name": "Home",
                "settings": {},
                "tab_id": "info",
                "title": "Home",
                "type": "course_info"
            },
            {
                "course_staff_only": false,
                "is_hidden": false,
                "is_hideable": false,
                "is_movable": false,
                "name": "Course",
                "settings": {},
                "tab_id": "courseware",
                "title": "Course",
                "type": "courseware"
            },
            ...
        }
        ```
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_read_access(request.user, course_key):
            self.permission_denied(request)

        course_module = modulestore().get_course(course_key)
        tabs_to_render = get_course_tabs(course_module, request.user)
        return Response(CourseTabSerializer(tabs_to_render, many=True).data)
コード例 #4
0
ファイル: tabs.py プロジェクト: patilswapnilv/edx-platform
class CourseTabReorderView(DeveloperErrorViewMixin, APIView):
    """
    API view for reordering course tabs.
    """

    def handle_exception(self, exc: Exception) -> Response:
        """
        Handle NotImplementedError and return a proper response for it.
        """
        if isinstance(exc, NotImplementedError):
            return self._make_error_response(400, str(exc))
        return super().handle_exception(exc)

    @apidocs.schema(
        body=[TabIDLocatorSerializer],
        parameters=[
            apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
        ],
        responses={
            204: "In case of success, a 204 is returned with no content.",
            401: "The requester is not authenticated.",
            403: "The requester cannot access the specified course.",
            404: "The requested course does not exist.",
        },
    )
    @verify_course_exists()
    def post(self, request: Request, course_id: str) -> Response:
        """
        Reorder tabs in a course.

        **Example Requests**

        Move course tabs:

            POST /api/contentstore/v0/tabs/{course_id}/reorder [
                {
                    "tab_id": "info"
                },
                {
                    "tab_id": "courseware"
                },
                {
                    "tab_locator": "block-v1:TstX+DemoX+Demo+type@static_tab+block@d26fcb0e93824fbfa5c9e5f100e2511a"
                },
                {
                    "tab_id": "wiki"
                },
                {
                    "tab_id": "discussion"
                },
                {
                    "tab_id": "progress"
                }
            ]


        **Response Values**

        If the request is successful, an HTTP 204 response is returned
        without any content.
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_write_access(request.user, course_key):
            self.permission_denied(request)

        course_module = modulestore().get_course(course_key)
        tab_id_locators = TabIDLocatorSerializer(data=request.data, many=True)
        tab_id_locators.is_valid(raise_exception=True)
        reorder_tabs_handler(
            course_module,
            {"tabs": tab_id_locators.validated_data},
            request.user,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
コード例 #5
0
class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
    """REST endpoints for lists of bookmarks."""

    authentication_classes = (OAuth2Authentication, SessionAuthentication)
    pagination_class = BookmarksPagination
    permission_classes = (permissions.IsAuthenticated, )
    serializer_class = BookmarkSerializer

    @apidocs.schema(
        parameters=[
            apidocs.string_parameter(
                'course_id',
                apidocs.ParameterLocation.QUERY,
                description="The id of the course to limit the list",
            ),
            apidocs.string_parameter(
                'fields',
                apidocs.ParameterLocation.QUERY,
                description="The fields to return: display_name, path.",
            ),
        ], )
    def get(self, request, *args, **kwargs):
        """
        Get a paginated list of bookmarks for a user.

        The list can be filtered by passing parameter "course_id=<course_id>"
        to only include bookmarks from a particular course.

        The bookmarks are always sorted in descending order by creation date.

        Each page in the list contains 10 bookmarks by default. The page
        size can be altered by passing parameter "page_size=<page_size>".

        To include the optional fields pass the values in "fields" parameter
        as a comma separated list. Possible values are:

        * "display_name"
        * "path"

        # Example Requests

        GET /api/bookmarks/v1/bookmarks/?course_id={course_id1}&fields=display_name,path
        """
        return super(BookmarksListView, self).get(request, *args, **kwargs)

    def get_serializer_context(self):
        """
        Return the context for the serializer.
        """
        context = super(BookmarksListView, self).get_serializer_context()
        if self.request.method == 'GET':
            context['fields'] = self.fields_to_return(
                self.request.query_params)
        return context

    def get_queryset(self):
        """
        Returns queryset of bookmarks for GET requests.

        The results will only include bookmarks for the request's user.
        If the course_id is specified in the request parameters,
        the queryset will only include bookmarks from that course.
        """
        course_id = self.request.query_params.get('course_id', None)

        if course_id:
            try:
                course_key = CourseKey.from_string(course_id)
            except InvalidKeyError:
                log.error(u'Invalid course_id: %s.', course_id)
                return []
        else:
            course_key = None

        return api.get_bookmarks(user=self.request.user,
                                 course_key=course_key,
                                 fields=self.fields_to_return(
                                     self.request.query_params),
                                 serialized=False)

    def paginate_queryset(self, queryset):
        """ Override GenericAPIView.paginate_queryset for the purpose of eventing """
        page = super(BookmarksListView, self).paginate_queryset(queryset)

        course_id = self.request.query_params.get('course_id')
        if course_id:
            try:
                CourseKey.from_string(course_id)
            except InvalidKeyError:
                return page

        event_data = {
            'list_type': 'all_courses',
            'bookmarks_count': self.paginator.page.paginator.count,
            'page_size': self.paginator.page.paginator.per_page,
            'page_number': self.paginator.page.number,
        }
        if course_id is not None:
            event_data['list_type'] = 'per_course'
            event_data['course_id'] = course_id

        eventtracking.tracker.emit('edx.bookmark.listed', event_data)

        return page

    @apidocs.schema()
    def post(self, request, *unused_args, **unused_kwargs):
        """Create a new bookmark for a user.

        The POST request only needs to contain one parameter "usage_id".

        Http400 is returned if the format of the request is not correct,
        the usage_id is invalid or a block corresponding to the usage_id
        could not be found.

        # Example Requests

        POST /api/bookmarks/v1/bookmarks/
        Request data: {"usage_id": <usage-id>}
        """
        if not request.data:
            return self.error_response(ugettext_noop(u'No data provided.'),
                                       DEFAULT_USER_MESSAGE)

        usage_id = request.data.get('usage_id', None)
        if not usage_id:
            return self.error_response(
                ugettext_noop(u'Parameter usage_id not provided.'),
                DEFAULT_USER_MESSAGE)

        try:
            usage_key = UsageKey.from_string(unquote_slashes(usage_id))
        except InvalidKeyError:
            error_message = ugettext_noop(
                u'Invalid usage_id: {usage_id}.').format(usage_id=usage_id)
            log.error(error_message)
            return self.error_response(error_message, DEFAULT_USER_MESSAGE)

        try:
            bookmark = api.create_bookmark(user=self.request.user,
                                           usage_key=usage_key)
        except ItemNotFoundError:
            error_message = ugettext_noop(
                u'Block with usage_id: {usage_id} not found.').format(
                    usage_id=usage_id)
            log.error(error_message)
            return self.error_response(error_message, DEFAULT_USER_MESSAGE)
        except BookmarksLimitReachedError:
            error_message = ugettext_noop(
                u'You can create up to {max_num_bookmarks_per_course} bookmarks.'
                u' You must remove some bookmarks before you can add new ones.'
            ).format(
                max_num_bookmarks_per_course=settings.MAX_BOOKMARKS_PER_COURSE)
            log.info(u'Attempted to create more than %s bookmarks',
                     settings.MAX_BOOKMARKS_PER_COURSE)
            return self.error_response(error_message)

        return Response(bookmark, status=status.HTTP_201_CREATED)
コード例 #6
0
class AdvancedCourseSettingsView(DeveloperErrorViewMixin, APIView):
    """
    View for getting and setting the advanced settings for a course.
    """

    class FilterQuery(forms.Form):
        """
        Form for validating query parameters passed to advanced course settings view
        to filter the data it returns.
        """
        filter_fields = forms.CharField(strip=True, empty_value=None, required=False)

        def clean_filter_fields(self):
            if 'filter_fields' in self.data and self.cleaned_data['filter_fields']:
                return set(self.cleaned_data['filter_fields'].split(','))
            return None

    @apidocs.schema(
        parameters=[
            apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID"),
            apidocs.string_parameter(
                "filter_fields",
                apidocs.ParameterLocation.PATH,
                description="Comma separated list of fields to filter",
            ),
        ],
        responses={
            200: CourseAdvancedSettingsSerializer,
            401: "The requester is not authenticated.",
            403: "The requester cannot access the specified course.",
            404: "The requested course does not exist.",
        },
    )
    @verify_course_exists()
    def get(self, request: Request, course_id: str):
        """
        Get an object containing all the advanced settings in a course.

        **Example Request**

            GET /api/contentstore/v0/advanced_settings/{course_id}

        **Response Values**

        If the request is successful, an HTTP 200 "OK" response is returned.

        The HTTP 200 response contains a single dict that contains keys that
        are the course's advanced settings. For each setting a dictionary is
        returned that contains the following fields:

        * **deprecated**: This is true for settings that are deprecated.
        * **display_name**: This is a friendly name for the setting.
        * **help**: Contains help text that explains how the setting works.
        * **value**: Contains the value of the setting. The exact format
          depends on the setting and is often explained in the ``help`` field
          above.

        There may be other fields returned by the response.

        **Example Response**

        ```json
        {
            "display_name": {
                "value": "Demonstration Course",
                "display_name": "Course Display Name",
                "help": "Enter the name of the course as it should appear in the course list.",
                "deprecated": false,
                "hide_on_enabled_publisher": false
            },
            "course_edit_method": {
                "value": "Studio",
                "display_name": "Course Editor",
                "help": "Enter the method by which this course is edited (\"XML\" or \"Studio\").",
                "deprecated": true,
                "hide_on_enabled_publisher": false
            },
            "days_early_for_beta": {
                "value": null,
                "display_name": "Days Early for Beta Users",
                "help": "Enter the number of days before the start date that beta users can access the course.",
                "deprecated": false,
                "hide_on_enabled_publisher": false
            },
            ...
        }
        ```
        """
        filter_query_data = AdvancedCourseSettingsView.FilterQuery(request.query_params)
        if not filter_query_data.is_valid():
            raise ValidationError(filter_query_data.errors)
        course_key = CourseKey.from_string(course_id)
        if not has_studio_read_access(request.user, course_key):
            self.permission_denied(request)
        course_module = modulestore().get_course(course_key)
        return Response(CourseMetadata.fetch_all(
            course_module,
            filter_fields=filter_query_data.cleaned_data['filter_fields'],
        ))

    @apidocs.schema(
        body=CourseAdvancedSettingsSerializer,
        parameters=[apidocs.string_parameter("course_id", apidocs.ParameterLocation.PATH, description="Course ID")],
        responses={
            200: CourseAdvancedSettingsSerializer,
            401: "The requester is not authenticated.",
            403: "The requester cannot access the specified course.",
            404: "The requested course does not exist.",
        },
    )
    @verify_course_exists()
    def patch(self, request: Request, course_id: str):
        """
        Update a course's advanced settings.

        **Example Request**

            PATCH /api/contentstore/v0/advanced_settings/{course_id} {
                "{setting_name}": {
                    "value": {setting_value}
                }
            }

        **PATCH Parameters**

        The data sent for a patch request should follow a similar format as
        is returned by a ``GET`` request. Multiple settings can be updated in
        a single request, however only the ``value`` field can be updated
        any other fields, if included, will be ignored.

        Here is an example request that updates the ``advanced_modules``
        available for the course, and enables the calculator tool:

        ```json
        {
            "advanced_modules": {
                "value": [
                    "poll",
                    "survey",
                    "drag-and-drop-v2",
                    "lti_consumer"
                ]
            },
            "show_calculator": {
                "value": true
            }
        }
        ```

        **Response Values**

        If the request is successful, an HTTP 200 "OK" response is returned,
        along with all the course's settings similar to a ``GET`` request.
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_write_access(request.user, course_key):
            self.permission_denied(request)
        course_module = modulestore().get_course(course_key)
        updated_data = update_course_advanced_settings(course_module, request.data, request.user)
        return Response(updated_data)