Skip to content
Snippets Groups Projects
course_details.py 7 KiB
Newer Older
from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import own_metadata
import json
from json.encoder import JSONEncoder
Don Mitchell's avatar
Don Mitchell committed
from contentstore.utils import get_modulestore
from models.settings import course_grading
from contentstore.utils import update_item
import logging
import datetime
class CourseDetails(object):
Don Mitchell's avatar
Don Mitchell committed
    def __init__(self, location):
        self.course_location = location  # a Location obj
        self.start_date = None  # 'start'
        self.end_date = None  # 'end'
        self.enrollment_start = None
        self.enrollment_end = None
        self.syllabus = None  # a pdf file asset
        self.overview = ""  # html to render as the overview
        self.intro_video = None  # a video pointer
        self.effort = None  # int hours/week
Don Mitchell's avatar
Don Mitchell committed

    @classmethod
    def fetch(cls, course_location):
        """
        Fetch the course details for the given course from persistence and return a CourseDetails model.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        course = cls(course_location)
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        descriptor = get_modulestore(course_location).get_item(course_location)
Calen Pennington's avatar
Calen Pennington committed

        course.start_date = descriptor.start
        course.end_date = descriptor.end
        course.enrollment_start = descriptor.enrollment_start
        course.enrollment_end = descriptor.enrollment_end
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        temploc = course_location.replace(category='about', name='syllabus')
            course.syllabus = get_modulestore(temploc).get_item(temploc).data
        except ItemNotFoundError:
            pass

Don Mitchell's avatar
Don Mitchell committed
        temploc = temploc.replace(name='overview')
            course.overview = get_modulestore(temploc).get_item(temploc).data
        except ItemNotFoundError:
            pass
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        temploc = temploc.replace(name='effort')
            course.effort = get_modulestore(temploc).get_item(temploc).data
        except ItemNotFoundError:
            pass
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        temploc = temploc.replace(name='video')
            raw_video = get_modulestore(temploc).get_item(temploc).data
Calen Pennington's avatar
Calen Pennington committed
            course.intro_video = CourseDetails.parse_video_tag(raw_video)
        except ItemNotFoundError:
            pass
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        return course
Calen Pennington's avatar
Calen Pennington committed

    @classmethod
    def update_from_json(cls, jsondict):
        """
        Decode the json into CourseDetails and save any changed attrs to the db
        """
        # TODO make it an error for this to be undefined & for it to not be retrievable from modulestore
        course_location = jsondict['course_location']
        # Will probably want to cache the inflight courses because every blur generates an update
Don Mitchell's avatar
Don Mitchell committed
        descriptor = get_modulestore(course_location).get_item(course_location)
Calen Pennington's avatar
Calen Pennington committed

        dirty = False
        # In the descriptor's setter, the date is converted to JSON using Date's to_json method.
        # Calling to_json on something that is already JSON doesn't work. Since reaching directly
        # into the model is nasty, convert the JSON Date to a Python date, which is what the
        # setter expects as input.
        date = Date()

        if 'start_date' in jsondict:
            converted = date.from_json(jsondict['start_date'])
        else:
            converted = None
        if converted != descriptor.start:
            dirty = True
            descriptor.start = converted
Calen Pennington's avatar
Calen Pennington committed

        if 'end_date' in jsondict:
            converted = date.from_json(jsondict['end_date'])
        else:
            converted = None

        if converted != descriptor.end:
            dirty = True
            descriptor.end = converted
Calen Pennington's avatar
Calen Pennington committed

        if 'enrollment_start' in jsondict:
            converted = date.from_json(jsondict['enrollment_start'])
        else:
            converted = None

        if converted != descriptor.enrollment_start:
            dirty = True
            descriptor.enrollment_start = converted
Calen Pennington's avatar
Calen Pennington committed

        if 'enrollment_end' in jsondict:
            converted = date.from_json(jsondict['enrollment_end'])
        else:
            converted = None

        if converted != descriptor.enrollment_end:
            dirty = True
            descriptor.enrollment_end = converted
Calen Pennington's avatar
Calen Pennington committed

        if dirty:
            get_modulestore(course_location).update_metadata(course_location, own_metadata(descriptor))
Calen Pennington's avatar
Calen Pennington committed

        # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed
        # to make faster, could compare against db or could have client send over a list of which fields changed.
Don Mitchell's avatar
Don Mitchell committed
        temploc = Location(course_location).replace(category='about', name='syllabus')
        update_item(temploc, jsondict['syllabus'])
Don Mitchell's avatar
Don Mitchell committed
        temploc = temploc.replace(name='overview')
        update_item(temploc, jsondict['overview'])
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        temploc = temploc.replace(name='effort')
        update_item(temploc, jsondict['effort'])
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
        temploc = temploc.replace(name='video')
        recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video'])
        update_item(temploc, recomposed_video_tag)
Calen Pennington's avatar
Calen Pennington committed

        # Could just generate and return a course obj w/o doing any db reads, but I put the reads in as a means to confirm
        # it persisted correctly
        return CourseDetails.fetch(course_location)
Calen Pennington's avatar
Calen Pennington committed

    @staticmethod
    def parse_video_tag(raw_video):
        """
        Because the client really only wants the author to specify the youtube key, that's all we send to and get from the client.
        The problem is that the db stores the html markup as well (which, of course, makes any sitewide changes to how we do videos
        next to impossible.)
        """
        if not raw_video:
            return None
Calen Pennington's avatar
Calen Pennington committed

        keystring_matcher = re.search(r'(?<=embed/)[a-zA-Z0-9_-]+', raw_video)
            keystring_matcher = re.search(r'<?=\d+:[a-zA-Z0-9_-]+', raw_video)
Calen Pennington's avatar
Calen Pennington committed

        if keystring_matcher:
            return keystring_matcher.group(0)
        else:
            logging.warn("ignoring the content because it doesn't not conform to expected pattern: " + raw_video)
Calen Pennington's avatar
Calen Pennington committed

    @staticmethod
    def recompose_video_tag(video_key):
        # TODO should this use a mako template? Of course, my hope is that this is a short-term workaround for the db not storing
        # the right thing
        result = None
        if video_key:
            result = '<iframe width="560" height="315" src="http://www.youtube.com/embed/' + \
                video_key + '?autoplay=1&rel=0" frameborder="0" allowfullscreen=""></iframe>'
Calen Pennington's avatar
Calen Pennington committed

Don Mitchell's avatar
Don Mitchell committed
# TODO move to a more general util?
Don Mitchell's avatar
Don Mitchell committed
class CourseSettingsEncoder(json.JSONEncoder):
    def default(self, obj):
Don Mitchell's avatar
Don Mitchell committed
        if isinstance(obj, (CourseDetails, course_grading.CourseGradingModel)):
            return obj.__dict__
        elif isinstance(obj, Location):
            return obj.dict()
        elif isinstance(obj, datetime.datetime):
            return Date().to_json(obj)
            return JSONEncoder.default(self, obj)