tasks.py 4.81 KB
Newer Older
1 2 3
"""
This file contains celery tasks for contentstore views
"""
Ben McMorran committed
4
import json
5
import logging
6 7 8 9
from celery.task import task
from celery.utils.log import get_task_logger
from datetime import datetime
from pytz import UTC
Mathew Peterson committed
10

11 12
from django.contrib.auth.models import User

13
from contentstore.courseware_index import CoursewareSearchIndexer, LibrarySearchIndexer, SearchIndexingError
14
from contentstore.utils import initialize_permissions
15
from course_action_state.models import CourseRerunState
16
from opaque_keys.edx.keys import CourseKey
17 18 19
from xmodule.course_module import CourseFields
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import DuplicateCourseError, ItemNotFoundError
20

21 22
LOGGER = get_task_logger(__name__)
FULL_COURSE_REINDEX_THRESHOLD = 1
23

24 25

@task()
26
def rerun_course(source_course_key_string, destination_course_key_string, user_id, fields=None):
27 28 29
    """
    Reruns a course in a new celery task.
    """
30 31 32
    # import here, at top level this import prevents the celery workers from starting up correctly
    from edxval.api import copy_course_videos

33
    try:
Ben McMorran committed
34
        # deserialize the payload
35 36
        source_course_key = CourseKey.from_string(source_course_key_string)
        destination_course_key = CourseKey.from_string(destination_course_key_string)
Ben McMorran committed
37
        fields = deserialize_fields(fields) if fields else None
38 39 40 41 42 43

        # use the split modulestore as the store for the rerun course,
        # as the Mongo modulestore doesn't support multiple runs of the same course.
        store = modulestore()
        with store.default_store('split'):
            store.clone_course(source_course_key, destination_course_key, user_id, fields=fields)
44 45 46 47 48 49

        # set initial permissions for the user to access the course.
        initialize_permissions(destination_course_key, User.objects.get(id=user_id))

        # update state: Succeeded
        CourseRerunState.objects.succeeded(course_key=destination_course_key)
50 51 52 53

        # call edxval to attach videos to the rerun
        copy_course_videos(source_course_key, destination_course_key)

54 55 56 57
        return "succeeded"

    except DuplicateCourseError as exc:
        # do NOT delete the original course, only update the status
58 59
        CourseRerunState.objects.failed(course_key=destination_course_key)
        logging.exception(u'Course Rerun Error')
60 61
        return "duplicate course"

62 63 64
    # catch all exceptions so we can update the state and properly cleanup the course.
    except Exception as exc:  # pylint: disable=broad-except
        # update state: Failed
65 66
        CourseRerunState.objects.failed(course_key=destination_course_key)
        logging.exception(u'Course Rerun Error')
67

68 69 70 71 72 73 74 75
        try:
            # cleanup any remnants of the course
            modulestore().delete_course(destination_course_key, user_id)
        except ItemNotFoundError:
            # it's possible there was an error even before the course module was created
            pass

        return "exception: " + unicode(exc)
Ben McMorran committed
76 77 78 79 80 81 82


def deserialize_fields(json_fields):
    fields = json.loads(json_fields)
    for field_name, value in fields.iteritems():
        fields[field_name] = getattr(CourseFields, field_name).from_json(value)
    return fields
83 84


85 86 87 88 89 90 91 92 93
def _parse_time(time_isoformat):
    """ Parses time from iso format """
    return datetime.strptime(
        # remove the +00:00 from the end of the formats generated within the system
        time_isoformat.split('+')[0],
        "%Y-%m-%dT%H:%M:%S.%f"
    ).replace(tzinfo=UTC)


94 95 96 97 98
@task()
def update_search_index(course_id, triggered_time_isoformat):
    """ Updates course search index. """
    try:
        course_key = CourseKey.from_string(course_id)
99
        CoursewareSearchIndexer.index(modulestore(), course_key, triggered_at=(_parse_time(triggered_time_isoformat)))
100 101 102 103 104

    except SearchIndexingError as exc:
        LOGGER.error('Search indexing error for complete course %s - %s', course_id, unicode(exc))
    else:
        LOGGER.debug('Search indexing successful for complete course %s', course_id)
105

106

107
@task()
108
def update_library_index(library_id, triggered_time_isoformat):
109 110 111
    """ Updates course search index. """
    try:
        library_key = CourseKey.from_string(library_id)
112
        LibrarySearchIndexer.index(modulestore(), library_key, triggered_at=(_parse_time(triggered_time_isoformat)))
113 114 115 116

    except SearchIndexingError as exc:
        LOGGER.error('Search indexing error for library %s - %s', library_id, unicode(exc))
    else:
117
        LOGGER.debug('Search indexing successful for library %s', library_id)
118 119 120 121 122 123 124 125 126 127


@task()
def push_course_update_task(course_key_string, course_subscription_id, course_display_name):
    """
    Sends a push notification for a course update.
    """
    # TODO Use edx-notifications library instead (MA-638).
    from .push_notification import send_push_course_update
    send_push_course_update(course_key_string, course_subscription_id, course_display_name)