data.py 9.67 KB
Newer Older
1 2 3 4
"""
Data Aggregation Layer of the Enrollment API. Collects all enrollment specific data into a single
source to be used throughout the API.
"""
5
import logging
6

7 8
from django.contrib.auth.models import User
from opaque_keys.edx.keys import CourseKey
9

Ned Batchelder committed
10
from enrollment.errors import (
11 12 13 14 15
    CourseEnrollmentClosedError,
    CourseEnrollmentExistsError,
    CourseEnrollmentFullError,
    InvalidEnrollmentAttribute,
    UserNotFoundError
Ned Batchelder committed
16
)
17
from enrollment.serializers import CourseEnrollmentSerializer, CourseSerializer
18
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
19
from openedx.core.lib.exceptions import CourseNotFoundError
Ned Batchelder committed
20
from student.models import (
21 22 23 24 25 26
    AlreadyEnrolledError,
    CourseEnrollment,
    CourseEnrollmentAttribute,
    CourseFullError,
    EnrollmentClosedError,
    NonExistentCourseError
Ned Batchelder committed
27
)
28 29

log = logging.getLogger(__name__)
30 31


32 33
def get_course_enrollments(user_id):
    """Retrieve a list representing all aggregated data for a user's course enrollments.
34

35
    Construct a representation of all course enrollment data for a specific user.
36 37

    Args:
38
        user_id (str): The name of the user to retrieve course enrollment information for.
39 40

    Returns:
41
        A serializable list of dictionaries of all aggregated enrollment data for a user.
42 43

    """
44
    qset = CourseEnrollment.objects.filter(
45 46
        user__username=user_id,
        is_active=True
47
    ).order_by('created')
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

    enrollments = CourseEnrollmentSerializer(qset, many=True).data

    # Find deleted courses and filter them out of the results
    deleted = []
    valid = []
    for enrollment in enrollments:
        if enrollment.get("course_details") is not None:
            valid.append(enrollment)
        else:
            deleted.append(enrollment)

    if deleted:
        log.warning(
            (
                u"Course enrollments for user %s reference "
                u"courses that do not exist (this can occur if a course is deleted)."
            ), user_id,
        )

    return valid
69 70


71 72
def get_course_enrollment(username, course_id):
    """Retrieve an object representing all aggregated data for a user's course enrollment.
73

74
    Get the course enrollment information for a specific user and course.
75 76

    Args:
77
        username (str): The name of the user to retrieve course enrollment information for.
78 79 80 81 82 83
        course_id (str): The course to retrieve course enrollment information for.

    Returns:
        A serializable dictionary representing the course enrollment.

    """
84 85 86
    course_key = CourseKey.from_string(course_id)
    try:
        enrollment = CourseEnrollment.objects.get(
87
            user__username=username, course_id=course_key
88
        )
89
        return CourseEnrollmentSerializer(enrollment).data
90 91 92 93
    except CourseEnrollment.DoesNotExist:
        return None


94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
def create_course_enrollment(username, course_id, mode, is_active):
    """Create a new course enrollment for the given user.

    Creates a new course enrollment for the specified user username.

    Args:
        username (str): The name of the user to create a new course enrollment for.
        course_id (str): The course to create the course enrollment for.
        mode (str): (Optional) The mode for the new enrollment.
        is_active (boolean): (Optional) Determines if the enrollment is active.

    Returns:
        A serializable dictionary representing the new course enrollment.

    Raises:
        CourseNotFoundError
        CourseEnrollmentFullError
        EnrollmentClosedError
        CourseEnrollmentExistsError

    """
    course_key = CourseKey.from_string(course_id)

    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        msg = u"Not user with username '{username}' found.".format(username=username)
        log.warn(msg)
        raise UserNotFoundError(msg)

    try:
        enrollment = CourseEnrollment.enroll(user, course_key, check_access=True)
        return _update_enrollment(enrollment, is_active=is_active, mode=mode)
    except NonExistentCourseError as err:
        raise CourseNotFoundError(err.message)
    except EnrollmentClosedError as err:
        raise CourseEnrollmentClosedError(err.message)
    except CourseFullError as err:
        raise CourseEnrollmentFullError(err.message)
    except AlreadyEnrolledError as err:
134 135
        enrollment = get_course_enrollment(username, course_id)
        raise CourseEnrollmentExistsError(err.message, enrollment)
136 137 138 139


def update_course_enrollment(username, course_id, mode=None, is_active=None):
    """Modify a course enrollment for a user.
140 141 142 143

    Allows updates to a specific course enrollment.

    Args:
144
        username (str): The name of the user to retrieve course enrollment information for.
145
        course_id (str): The course to retrieve course enrollment information for.
146
        mode (str): (Optional) If specified, modify the mode for this enrollment.
147 148 149 150 151 152
        is_active (boolean): (Optional) Determines if the enrollment is active.

    Returns:
        A serializable dictionary representing the modified course enrollment.

    """
153 154
    course_key = CourseKey.from_string(course_id)

155 156 157 158 159 160 161 162 163 164 165 166 167 168
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        msg = u"Not user with username '{username}' found.".format(username=username)
        log.warn(msg)
        raise UserNotFoundError(msg)

    try:
        enrollment = CourseEnrollment.objects.get(user=user, course_id=course_key)
        return _update_enrollment(enrollment, is_active=is_active, mode=mode)
    except CourseEnrollment.DoesNotExist:
        return None


169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
def add_or_update_enrollment_attr(user_id, course_id, attributes):
    """Set enrollment attributes for the enrollment of given user in the
    course provided.

    Args:
        course_id (str): The Course to set enrollment attributes for.
        user_id (str): The User to set enrollment attributes for.
        attributes (list): Attributes to be set.

    Example:
        >>>add_or_update_enrollment_attr(
            "Bob",
            "course-v1-edX-DemoX-1T2015",
            [
                {
                    "namespace": "credit",
                    "name": "provider_id",
                    "value": "hogwarts",
                },
            ]
        )
    """
    course_key = CourseKey.from_string(course_id)
    user = _get_user(user_id)
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    if not _invalid_attribute(attributes) and enrollment is not None:
        CourseEnrollmentAttribute.add_enrollment_attr(enrollment, attributes)


def get_enrollment_attributes(user_id, course_id):
    """Retrieve enrollment attributes for given user for provided course.

    Args:
        user_id: The User to get enrollment attributes for
        course_id (str): The Course to get enrollment attributes for.

    Example:
        >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015")
        [
            {
                "namespace": "credit",
                "name": "provider_id",
                "value": "hogwarts",
            },
        ]

    Returns: list
    """
    course_key = CourseKey.from_string(course_id)
    user = _get_user(user_id)
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)


def _get_user(user_id):
    """Retrieve user with provided user_id

    Args:
        user_id(str): username of the user for which object is to retrieve

    Returns: obj
    """
    try:
        return User.objects.get(username=user_id)
    except User.DoesNotExist:
        msg = u"Not user with username '{username}' found.".format(username=user_id)
        log.warn(msg)
        raise UserNotFoundError(msg)


239
def _update_enrollment(enrollment, is_active=None, mode=None):
240 241
    enrollment.update_enrollment(is_active=is_active, mode=mode)
    enrollment.save()
242
    return CourseEnrollmentSerializer(enrollment).data
243 244


245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
def _invalid_attribute(attributes):
    """Validate enrollment attribute

    Args:
        attributes(dict): dict of attribute

    Return:
        list of invalid attributes
    """
    invalid_attributes = []
    for attribute in attributes:
        if "namespace" not in attribute:
            msg = u"'namespace' not in enrollment attribute"
            log.warn(msg)
            invalid_attributes.append("namespace")
            raise InvalidEnrollmentAttribute(msg)
        if "name" not in attribute:
            msg = u"'name' not in enrollment attribute"
            log.warn(msg)
            invalid_attributes.append("name")
            raise InvalidEnrollmentAttribute(msg)
        if "value" not in attribute:
            msg = u"'value' not in enrollment attribute"
            log.warn(msg)
            invalid_attributes.append("value")
            raise InvalidEnrollmentAttribute(msg)

    return invalid_attributes


275
def get_course_enrollment_info(course_id, include_expired=False):
276
    """Returns all course enrollment information for the given course.
277

278
    Based on the course id, return all related course information.
279

280 281 282
    Args:
        course_id (str): The course to retrieve enrollment information for.

283 284 285
        include_expired (bool): Boolean denoting whether expired course modes
        should be included in the returned JSON data.

286 287 288
    Returns:
        A serializable dictionary representing the course's enrollment information.

289 290 291
    Raises:
        CourseNotFoundError

292 293
    """
    course_key = CourseKey.from_string(course_id)
294 295 296 297

    try:
        course = CourseOverview.get_from_id(course_key)
    except CourseOverview.DoesNotExist:
298 299 300
        msg = u"Requested enrollment information for unknown course {course}".format(course=course_id)
        log.warning(msg)
        raise CourseNotFoundError(msg)
301
    else:
302
        return CourseSerializer(course, include_expired=include_expired).data