Ejemplo n.º 1
0
class CrowdsourceHinterFields(object):
    """Defines fields for the crowdsource hinter module."""
    has_children = True

    moderate = String(help='String "True"/"False" - activates moderation',
                      scope=Scope.content,
                      default='False')
    debug = String(help='String "True"/"False" - allows multiple voting',
                   scope=Scope.content,
                   default='False')
    # Usage: hints[answer] = {str(pk): [hint_text, #votes]}
    # hints is a dictionary that takes answer keys.
    # Each value is itself a dictionary, accepting hint_pk strings as keys,
    # and returning [hint text, #votes] pairs as values
    hints = Dict(help='A dictionary containing all the active hints.',
                 scope=Scope.content,
                 default={})
    mod_queue = Dict(
        help='A dictionary containing hints still awaiting approval',
        scope=Scope.content,
        default={})
    hint_pk = Integer(help='Used to index hints.',
                      scope=Scope.content,
                      default=0)
    # A list of previous answers this student made to this problem.
    # Of the form [answer, [hint_pk_1, hint_pk_2, hint_pk_3]] for each problem.  hint_pk's are
    # None if the hint was not given.
    previous_answers = List(help='A list of previous submissions.',
                            scope=Scope.user_state,
                            default=[])
    user_voted = Boolean(
        help='Specifies if the user has voted on this problem or not.',
        scope=Scope.user_state,
        default=False)
Ejemplo n.º 2
0
class WordCloudFields(object):
    """XFields for word cloud."""
    num_inputs = Integer(
        display_name="Inputs",
        help=
        "Number of text boxes available for students to input words/sentences.",
        scope=Scope.settings,
        default=5,
        values={"min": 1})
    num_top_words = Integer(
        display_name="Maximum Words",
        help="Maximum number of words to be displayed in generated word cloud.",
        scope=Scope.settings,
        default=250,
        values={"min": 1})
    display_student_percents = Boolean(
        display_name="Show Percents",
        help="Statistics are shown for entered words near that word.",
        scope=Scope.settings,
        default=True)

    # Fields for descriptor.
    submitted = Boolean(
        help="Whether this student has posted words to the cloud.",
        scope=Scope.user_state,
        default=False)
    student_words = List(help="Student answer.",
                         scope=Scope.user_state,
                         default=[])
    all_words = Dict(help="All possible words from all students.",
                     scope=Scope.content)
    top_words = Dict(help="Top num_top_words words for word cloud.",
                     scope=Scope.content)
Ejemplo n.º 3
0
class CrowdsourceHinterFields(object):
    """Defines fields for the crowdsource hinter module."""
    has_children = True

    moderate = String(help='String "True"/"False" - activates moderation', scope=Scope.content,
                      default='False')
    debug = String(help='String "True"/"False" - allows multiple voting', scope=Scope.content,
                   default='False')
    # Usage: hints[answer] = {str(pk): [hint_text, #votes]}
    # hints is a dictionary that takes answer keys.
    # Each value is itself a dictionary, accepting hint_pk strings as keys,
    # and returning [hint text, #votes] pairs as values
    hints = Dict(help='A dictionary containing all the active hints.', scope=Scope.content, default={})
    mod_queue = Dict(help='A dictionary containing hints still awaiting approval', scope=Scope.content,
                     default={})
    hint_pk = Integer(help='Used to index hints.', scope=Scope.content, default=0)

    # A list of previous hints that a student viewed.
    # Of the form [answer, [hint_pk_1, ...]] for each problem.
    # Sorry about the variable name - I know it's confusing.
    previous_answers = List(help='A list of hints viewed.', scope=Scope.user_state, default=[])

    # user_submissions actually contains a list of previous answers submitted.
    # (Originally, preivous_answers did this job, hence the name confusion.)
    user_submissions = List(help='A list of previous submissions', scope=Scope.user_state, default=[])
    user_voted = Boolean(help='Specifies if the user has voted on this problem or not.',
                         scope=Scope.user_state, default=False)
Ejemplo n.º 4
0
class CourseFields(object):
    textbooks = TextbookList(help="List of pairs of (title, url) for textbooks used in this course", scope=Scope.content)
    wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content)
    enrollment_start = Date(help="Date that enrollment for this class is opened", scope=Scope.settings)
    enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings)
    start = Date(help="Start time when this module is visible", scope=Scope.settings)
    end = Date(help="Date that this class ends", scope=Scope.settings)
    advertised_start = String(help="Date that this course is advertised to start", scope=Scope.settings)
    grading_policy = Dict(help="Grading policy definition for this class", scope=Scope.content)
    show_calculator = Boolean(help="Whether to show the calculator in this course", default=False, scope=Scope.settings)
    display_name = String(help="Display name for this module", scope=Scope.settings)
    tabs = List(help="List of tabs to enable in this course", scope=Scope.settings)
    end_of_course_survey_url = String(help="Url for the end-of-course survey", scope=Scope.settings)
    discussion_blackouts = List(help="List of pairs of start/end dates for discussion blackouts", scope=Scope.settings)
    discussion_topics = Dict(
        help="Map of topics names to ids",
        scope=Scope.settings
        )
    testcenter_info = Dict(help="Dictionary of Test Center info", scope=Scope.settings)
    announcement = Date(help="Date this course is announced", scope=Scope.settings)
    cohort_config = Dict(help="Dictionary defining cohort configuration", scope=Scope.settings)
    is_new = Boolean(help="Whether this course should be flagged as new", scope=Scope.settings)
    no_grade = Boolean(help="True if this course isn't graded", default=False, scope=Scope.settings)
    disable_progress_graph = Boolean(help="True if this course shouldn't display the progress graph", default=False, scope=Scope.settings)
    pdf_textbooks = List(help="List of dictionaries containing pdf_textbook configuration", scope=Scope.settings)
    html_textbooks = List(help="List of dictionaries containing html_textbook configuration", scope=Scope.settings)
    remote_gradebook = Dict(scope=Scope.settings)
    allow_anonymous = Boolean(scope=Scope.settings, default=True)
    allow_anonymous_to_peers = Boolean(scope=Scope.settings, default=False)
    advanced_modules = List(help="Beta modules used in your course", scope=Scope.settings)
    has_children = True
    checklists = List(scope=Scope.settings)
    info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
    show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True)
    enrollment_domain = String(help="External login method associated with user accounts allowed to register in course",
                        scope=Scope.settings)

    # An extra property is used rather than the wiki_slug/number because
    # there are courses that change the number for different runs. This allows
    # courses to share the same css_class across runs even if they have
    # different numbers.
    #
    # TODO get rid of this as soon as possible or potentially build in a robust
    # way to add in course-specific styling. There needs to be a discussion
    # about the right way to do this, but arjun will address this ASAP. Also
    # note that the courseware template needs to change when this is removed.
    css_class = String(help="DO NOT USE THIS", scope=Scope.settings)

    # TODO: This is a quick kludge to allow CS50 (and other courses) to
    # specify their own discussion forums as external links by specifying a
    # "discussion_link" in their policy JSON file. This should later get
    # folded in with Syllabus, Course Info, and additional Custom tabs in a
    # more sensible framework later.
    discussion_link = String(help="DO NOT USE THIS", scope=Scope.settings)

    # TODO: same as above, intended to let internal CS50 hide the progress tab
    # until we get grade integration set up.
    # Explicit comparison to True because we always want to return a bool.
    hide_progress_tab = Boolean(help="DO NOT USE THIS", scope=Scope.settings)
Ejemplo n.º 5
0
class PeerGradingFields(object):
    use_for_single_location = Boolean(
        display_name="Show Single Problem",
        help='When True, only the single problem specified by "Link to Problem Location" is shown. '
             'When False, a panel is displayed with all problems available for peer grading.',
        default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings
    )
    link_to_location = String(
        display_name="Link to Problem Location",
        help='The location of the problem being graded. Only used when "Show Single Problem" is True.',
        default=LINK_TO_LOCATION, scope=Scope.settings
    )
    is_graded = Boolean(
        display_name="Graded",
        help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.',
        default=IS_GRADED, scope=Scope.settings
    )
    due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings)
    grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
    max_grade = Integer(
        help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE,
        scope=Scope.settings, values={"min": 0}
    )
    student_data_for_location = Dict(
        help="Student data for a given peer grading problem.",
        scope=Scope.user_state
    )
    weight = Float(
        display_name="Problem Weight",
        help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
        scope=Scope.settings, values={"min": 0, "step": ".1"}
    )
Ejemplo n.º 6
0
class PeerGradingFields(object):
    use_for_single_location = Boolean(
        display_name="Show Single Problem",
        help=
        'When True, only the single problem specified by "Link to Problem Location" is shown. '
        'When False, a panel is displayed with all problems available for peer grading.',
        default=False,
        scope=Scope.settings)
    link_to_location = String(
        display_name="Link to Problem Location",
        help=
        'The location of the problem being graded. Only used when "Show Single Problem" is True.',
        default="",
        scope=Scope.settings)
    graded = Boolean(
        display_name="Graded",
        help=
        'Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.',
        default=False,
        scope=Scope.settings)
    due = Date(help="Due date that should be displayed.",
               default=None,
               scope=Scope.settings)
    grace_period_string = String(
        help="Amount of grace to give on the due date.",
        default=None,
        scope=Scope.settings)
    student_data_for_location = Dict(
        help="Student data for a given peer grading problem.",
        scope=Scope.user_state)
    weight = Float(
        display_name="Problem Weight",
        help=
        "Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
        scope=Scope.settings,
        values={
            "min": 0,
            "step": ".1"
        },
        default=1)
    display_name = String(display_name="Display Name",
                          help="Display name for this module",
                          scope=Scope.settings,
                          default="Peer Grading Interface")
    data = String(help="Html contents to display for this module",
                  default='<peergrading></peergrading>',
                  scope=Scope.content)
Ejemplo n.º 7
0
class PollFields(object):
    # Name of poll to use in links to this poll
    display_name = String(help="Display name for this module",
                          scope=Scope.settings)

    voted = Boolean(help="Whether this student has voted on the poll",
                    scope=Scope.user_state,
                    default=False)
    poll_answer = String(help="Student answer",
                         scope=Scope.user_state,
                         default='')
    poll_answers = Dict(
        help="All possible answers for the poll fro other students",
        scope=Scope.content)

    answers = List(help="Poll answers from xml",
                   scope=Scope.content,
                   default=[])
    question = String(help="Poll question", scope=Scope.content, default='')
Ejemplo n.º 8
0
class TestFields(object):
    # Will be returned by editable_metadata_fields.
    max_attempts = Integer(scope=Scope.settings,
                           default=1000,
                           values={
                               'min': 1,
                               'max': 10
                           })
    # Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
    due = Date(scope=Scope.settings)
    # Will not be returned by editable_metadata_fields because is not Scope.settings.
    student_answers = Dict(scope=Scope.user_state)
    # Will be returned, and can override the inherited value from XModule.
    display_name = String(scope=Scope.settings,
                          default='local default',
                          display_name='Local Display Name',
                          help='local help')
    # Used for testing select type, effect of to_json method
    string_select = CrazyJsonString(scope=Scope.settings,
                                    default='default value',
                                    values=[{
                                        'display_name': 'first',
                                        'value': 'value a'
                                    }, {
                                        'display_name': 'second',
                                        'value': 'value b'
                                    }])
    # Used for testing select type
    float_select = Float(scope=Scope.settings,
                         default=.999,
                         values=[1.23, 0.98])
    # Used for testing float type
    float_non_select = Float(scope=Scope.settings,
                             default=.999,
                             values={
                                 'min': 0,
                                 'step': .3
                             })
    # Used for testing that Booleans get mapped to select type
    boolean_select = Boolean(scope=Scope.settings)
    # Used for testing Lists
    list_field = List(scope=Scope.settings, default=[])
Ejemplo n.º 9
0
class CourseFields(object):
    textbooks = TextbookList(
        help="List of pairs of (title, url) for textbooks used in this course",
        default=[],
        scope=Scope.content)
    wiki_slug = String(help="Slug that points to the wiki for this course",
                       scope=Scope.content)
    enrollment_start = Date(
        help="Date that enrollment for this class is opened",
        scope=Scope.settings)
    enrollment_end = Date(help="Date that enrollment for this class is closed",
                          scope=Scope.settings)
    start = Date(
        help="Start time when this module is visible",
        # using now(UTC()) resulted in fractional seconds which screwed up comparisons and anyway w/b the
        # time of first invocation of this stmt on the server
        default=datetime.fromtimestamp(0, UTC()),
        scope=Scope.settings)
    end = Date(help="Date that this class ends", scope=Scope.settings)
    advertised_start = String(
        help="Date that this course is advertised to start",
        scope=Scope.settings)
    grading_policy = Dict(help="Grading policy definition for this class",
                          default={
                              "GRADER": [{
                                  "type": "Homework",
                                  "min_count": 12,
                                  "drop_count": 2,
                                  "short_label": "HW",
                                  "weight": 0.15
                              }, {
                                  "type": "Lab",
                                  "min_count": 12,
                                  "drop_count": 2,
                                  "weight": 0.15
                              }, {
                                  "type": "Midterm Exam",
                                  "short_label": "Midterm",
                                  "min_count": 1,
                                  "drop_count": 0,
                                  "weight": 0.3
                              }, {
                                  "type": "Final Exam",
                                  "short_label": "Final",
                                  "min_count": 1,
                                  "drop_count": 0,
                                  "weight": 0.4
                              }],
                              "GRADE_CUTOFFS": {
                                  "Pass": 0.5
                              }
                          },
                          scope=Scope.content)
    show_calculator = Boolean(
        help="Whether to show the calculator in this course",
        default=False,
        scope=Scope.settings)
    display_name = String(help="Display name for this module",
                          default="Empty",
                          display_name="Display Name",
                          scope=Scope.settings)
    show_chat = Boolean(help="Whether to show the chat widget in this course",
                        default=False,
                        scope=Scope.settings)
    tabs = List(help="List of tabs to enable in this course",
                scope=Scope.settings)
    end_of_course_survey_url = String(help="Url for the end-of-course survey",
                                      scope=Scope.settings)
    discussion_blackouts = List(
        help="List of pairs of start/end dates for discussion blackouts",
        scope=Scope.settings)
    discussion_topics = Dict(help="Map of topics names to ids",
                             scope=Scope.settings)
    testcenter_info = Dict(help="Dictionary of Test Center info",
                           scope=Scope.settings)
    announcement = Date(help="Date this course is announced",
                        scope=Scope.settings)
    cohort_config = Dict(help="Dictionary defining cohort configuration",
                         scope=Scope.settings)
    is_new = Boolean(help="Whether this course should be flagged as new",
                     scope=Scope.settings)
    no_grade = Boolean(help="True if this course isn't graded",
                       default=False,
                       scope=Scope.settings)
    disable_progress_graph = Boolean(
        help="True if this course shouldn't display the progress graph",
        default=False,
        scope=Scope.settings)
    pdf_textbooks = List(
        help="List of dictionaries containing pdf_textbook configuration",
        scope=Scope.settings)
    html_textbooks = List(
        help="List of dictionaries containing html_textbook configuration",
        scope=Scope.settings)
    remote_gradebook = Dict(scope=Scope.settings)
    allow_anonymous = Boolean(scope=Scope.settings, default=True)
    allow_anonymous_to_peers = Boolean(scope=Scope.settings, default=False)
    advanced_modules = List(help="Beta modules used in your course",
                            scope=Scope.settings)
    has_children = True
    checklists = List(
        scope=Scope.settings,
        default=[{
            "short_description":
            "Getting Started With Sokratik",
            "items": [{
                "short_description": "Add Course Team Members",
                "long_description":
                "Grant your collaborators permission to edit your course so you can work together.",
                "is_checked": False,
                "action_url": "ManageUsers",
                "action_text": "Edit Course Team",
                "action_external": False
            }, {
                "short_description": "Set Important Dates for Your Course",
                "long_description":
                "Establish your course's student enrollment and launch dates on the Schedule and Details page.",
                "is_checked": False,
                "action_url": "SettingsDetails",
                "action_text": "Edit Course Details &amp; Schedule",
                "action_external": False
            }, {
                "short_description": "Draft Your Course's Grading Policy",
                "long_description":
                "Set up your assignment types and grading policy even if you haven't created all your assignments.",
                "is_checked": False,
                "action_url": "SettingsGrading",
                "action_text": "Edit Grading Settings",
                "action_external": False
            }, {
                "short_description": "Explore the Other Sokratik Checklists",
                "long_description":
                "Discover other available course authoring tools, and find help when you need it.",
                "is_checked": False,
                "action_url": "",
                "action_text": "",
                "action_external": False
            }]
        }, {
            "short_description":
            "Draft a Rough Course Outline",
            "items": [{
                "short_description":
                "Create Your First Section and Subsection",
                "long_description":
                "Use your course outline to build your first Section and Subsection.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }, {
                "short_description": "Set Section Release Dates",
                "long_description":
                "Specify the release dates for each Section in your course. Sections become visible to students on their release dates.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }, {
                "short_description": "Designate a Subsection as Graded",
                "long_description":
                "Set a Subsection to be graded as a specific assignment type. Assignments within graded Subsections count toward a student's final grade.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }, {
                "short_description": "Reordering Course Content",
                "long_description":
                "Use drag and drop to reorder the content in your course.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }, {
                "short_description": "Renaming Sections",
                "long_description":
                "Rename Sections by clicking the Section name from the Course Outline.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }, {
                "short_description": "Deleting Course Content",
                "long_description":
                "Delete Sections, Subsections, or Units you don't need anymore. Be careful, as there is no Undo function.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }, {
                "short_description":
                "Add an Instructor-Only Section to Your Outline",
                "long_description":
                "Some course authors find using a section for unsorted, in-progress work useful. To do this, create a section and set the release date to the distant future.",
                "is_checked": False,
                "action_url": "CourseOutline",
                "action_text": "Edit Course Outline",
                "action_external": False
            }]
        }, {
            "short_description":
            "Explore Sokratik's Support Tools",
            "items": [{
                "short_description": "Explore the Help Forum",
                "long_description":
                "Access the Help forum from the menu that appears when you click your user name in the top right corner of Studio.",
                "is_checked": False,
                "action_url": "http://help.edge.edx.org/",
                "action_text": "Visit Help",
                "action_external": True
            }, {
                "short_description": "Enroll in edX 101",
                "long_description":
                "Register for edX 101, edX's primer for course creation.",
                "is_checked": False,
                "action_url":
                "https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about",
                "action_text": "Register for edX 101",
                "action_external": True
            }, {
                "short_description": "Download the Documentation",
                "long_description":
                "Download the searchable reference documentation in PDF form.",
                "is_checked": False,
                "action_url":
                "http://files.edx.org/Getting_Started_with_Studio.pdf",
                "action_text": "Download Documentation",
                "action_external": True
            }]
        }, {
            "short_description":
            "Draft Your Course About Page",
            "items": [{
                "short_description": "Draft a Course Description",
                "long_description":
                "Courses on Sokratik have an About page that includes a course video, description, and more. Draft the text students will read before deciding to enroll in your course.",
                "is_checked": False,
                "action_url": "SettingsDetails",
                "action_text": "Edit Course Schedule &amp; Details",
                "action_external": False
            }, {
                "short_description": "Add Staff Bios",
                "long_description":
                "Showing prospective students who their instructor will be is helpful. Include staff bios on the course About page.",
                "is_checked": False,
                "action_url": "SettingsDetails",
                "action_text": "Edit Course Schedule &amp; Details",
                "action_external": False
            }, {
                "short_description": "Add Course FAQs",
                "long_description":
                "Include a short list of frequently asked questions about your course.",
                "is_checked": False,
                "action_url": "SettingsDetails",
                "action_text": "Edit Course Schedule &amp; Details",
                "action_external": False
            }, {
                "short_description": "Add Course Prerequisites",
                "long_description":
                "Let students know what knowledge and/or skills they should have before they enroll in your course.",
                "is_checked": False,
                "action_url": "SettingsDetails",
                "action_text": "Edit Course Schedule &amp; Details",
                "action_external": False
            }]
        }])
    info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
    show_timezone = Boolean(
        help="True if timezones should be shown on dates in the courseware",
        scope=Scope.settings,
        default=True)
    enrollment_domain = String(
        help=
        "External login method associated with user accounts allowed to register in course",
        scope=Scope.settings)
    course_image = String(
        help="Filename of the course image",
        scope=Scope.settings,
        # Ensure that courses imported from XML keep their image
        default="images_course_image.jpg")

    # An extra property is used rather than the wiki_slug/number because
    # there are courses that change the number for different runs. This allows
    # courses to share the same css_class across runs even if they have
    # different numbers.
    #
    # TODO get rid of this as soon as possible or potentially build in a robust
    # way to add in course-specific styling. There needs to be a discussion
    # about the right way to do this, but arjun will address this ASAP. Also
    # note that the courseware template needs to change when this is removed.
    css_class = String(help="DO NOT USE THIS", scope=Scope.settings)

    # TODO: This is a quick kludge to allow CS50 (and other courses) to
    # specify their own discussion forums as external links by specifying a
    # "discussion_link" in their policy JSON file. This should later get
    # folded in with Syllabus, Course Info, and additional Custom tabs in a
    # more sensible framework later.
    discussion_link = String(help="DO NOT USE THIS", scope=Scope.settings)

    # TODO: same as above, intended to let internal CS50 hide the progress tab
    # until we get grade integration set up.
    # Explicit comparison to True because we always want to return a bool.
    hide_progress_tab = Boolean(help="DO NOT USE THIS", scope=Scope.settings)

    display_organization = String(
        help=
        "An optional display string for the course organization that will get rendered in the LMS",
        scope=Scope.settings)

    display_coursenumber = String(
        help=
        "An optional display string for the course number that will get rendered in the LMS",
        scope=Scope.settings)
class ABTestFields(object):
    group_portions = Dict(help="What proportions of students should go in each group", default={DEFAULT: 1}, scope=Scope.content)
    group_assignments = Dict(help="What group this user belongs to", scope=Scope.preferences, default={})
    group_content = Dict(help="What content to display to each group", scope=Scope.content, default={DEFAULT: []})
    experiment = String(help="Experiment that this A/B test belongs to", scope=Scope.content)
    has_children = True
Ejemplo n.º 11
0
class XmlDescriptor(XModuleDescriptor):
    """
    Mixin class for standardized parsing of from xml
    """

    xml_attributes = Dict(
        help=
        "Map of unhandled xml attributes, used only for storage between import and export",
        default={},
        scope=Scope.settings)

    # Extension to append to filename paths
    filename_extension = 'xml'

    # The attributes will be removed from the definition xml passed
    # to definition_from_xml, and from the xml returned by definition_to_xml

    # Note -- url_name isn't in this list because it's handled specially on
    # import and export.

    # TODO (vshnayder): Do we need a list of metadata we actually
    # understand?  And if we do, is this the place?
    # Related: What's the right behavior for clean_metadata?
    metadata_attributes = (
        'format',
        'graceperiod',
        'showanswer',
        'rerandomize',
        'start',
        'due',
        'graded',
        'display_name',
        'url_name',
        'hide_from_toc',
        'ispublic',  # if True, then course is listed for all users; see
        'xqa_key',  # for xqaa server access
        'giturl',  # url of git server for origin of file
        # information about testcenter exams is a dict (of dicts), not a string,
        # so it cannot be easily exportable as a course element's attribute.
        'testcenter_info',
        # VS[compat] Remove once unused.
        'name',
        'slug')

    metadata_to_strip = (
        'data_dir',
        'tabs',
        'grading_policy',
        'published_by',
        'published_date',
        'discussion_blackouts',
        'testcenter_info',
        # VS[compat] -- remove the below attrs once everything is in the CMS
        'course',
        'org',
        'url_name',
        'filename',
        # Used for storing xml attributes between import and export, for roundtrips
        'xml_attributes')

    metadata_to_export_to_policy = ('discussion_topics')

    @classmethod
    def get_map_for_field(cls, attr):
        """
        Returns a serialize/deserialize AttrMap for the given field of a class.

        Searches through fields defined by cls to find one named attr.
        """
        for field in set(cls.fields + cls.lms.fields):
            if field.name == attr:
                from_xml = lambda val: deserialize_field(field, val)
                to_xml = lambda val: serialize_field(val)
                return AttrMap(from_xml, to_xml)

        return AttrMap()

    @classmethod
    def definition_from_xml(cls, xml_object, system):
        """
        Return the definition to be passed to the newly created descriptor
        during from_xml

        xml_object: An etree Element
        """
        raise NotImplementedError("%s does not implement definition_from_xml" %
                                  cls.__name__)

    @classmethod
    def clean_metadata_from_xml(cls, xml_object):
        """
        Remove any attribute named in cls.metadata_attributes from the supplied
        xml_object
        """
        for attr in cls.metadata_attributes:
            if xml_object.get(attr) is not None:
                del xml_object.attrib[attr]

    @classmethod
    def file_to_xml(cls, file_object):
        """
        Used when this module wants to parse a file object to xml
        that will be converted to the definition.

        Returns an lxml Element
        """
        return etree.parse(file_object, parser=edx_xml_parser).getroot()

    @classmethod
    def load_file(cls, filepath, fs, location):
        '''
        Open the specified file in fs, and call cls.file_to_xml on it,
        returning the lxml object.

        Add details and reraise on error.
        '''
        try:
            with fs.open(filepath) as file:
                return cls.file_to_xml(file)
        except Exception as err:
            # Add info about where we are, but keep the traceback
            msg = 'Unable to load file contents at path %s for item %s: %s ' % (
                filepath, location.url(), str(err))
            raise Exception, msg, sys.exc_info()[2]

    @classmethod
    def load_definition(cls, xml_object, system, location):
        '''Load a descriptor definition from the specified xml_object.
        Subclasses should not need to override this except in special
        cases (e.g. html module)'''

        # VS[compat] -- the filename attr should go away once everything is
        # converted.  (note: make sure html files still work once this goes away)
        filename = xml_object.get('filename')
        if filename is None:
            definition_xml = copy.deepcopy(xml_object)
            filepath = ''
        else:
            filepath = cls._format_filepath(xml_object.tag, filename)

            # VS[compat]
            # TODO (cpennington): If the file doesn't exist at the right path,
            # give the class a chance to fix it up. The file will be written out
            # again in the correct format.  This should go away once the CMS is
            # online and has imported all current (fall 2012) courses from xml
            if not system.resources_fs.exists(filepath) and hasattr(
                    cls, 'backcompat_paths'):
                candidates = cls.backcompat_paths(filepath)
                for candidate in candidates:
                    if system.resources_fs.exists(candidate):
                        filepath = candidate
                        break

            definition_xml = cls.load_file(filepath, system.resources_fs,
                                           location)

        definition_metadata = get_metadata_from_xml(definition_xml)
        cls.clean_metadata_from_xml(definition_xml)
        definition, children = cls.definition_from_xml(definition_xml, system)
        if definition_metadata:
            definition['definition_metadata'] = definition_metadata
        definition['filename'] = [filepath, filename]

        return definition, children

    @classmethod
    def load_metadata(cls, xml_object):
        """
        Read the metadata attributes from this xml_object.

        Returns a dictionary {key: value}.
        """
        metadata = {}
        for attr in xml_object.attrib:
            val = xml_object.get(attr)
            if val is not None:
                # VS[compat].  Remove after all key translations done
                attr = cls._translate(attr)

                if attr in cls.metadata_to_strip:
                    # don't load these
                    continue

                attr_map = cls.get_map_for_field(attr)
                metadata[attr] = attr_map.from_xml(val)
        return metadata

    @classmethod
    def apply_policy(cls, metadata, policy):
        """
        Add the keys in policy to metadata, after processing them
        through the attrmap.  Updates the metadata dict in place.
        """
        for attr in policy:
            attr_map = cls.get_map_for_field(attr)
            metadata[cls._translate(attr)] = attr_map.from_xml(policy[attr])

    @classmethod
    def from_xml(cls, xml_data, system, org=None, course=None):
        """
        Creates an instance of this descriptor from the supplied xml_data.
        This may be overridden by subclasses

        xml_data: A string of xml that will be translated into data and children for
            this module
        system: A DescriptorSystem for interacting with external resources
        org and course are optional strings that will be used in the generated modules
            url identifiers
        """

        xml_object = etree.fromstring(xml_data)
        # VS[compat] -- just have the url_name lookup, once translation is done
        url_name = xml_object.get('url_name', xml_object.get('slug'))
        location = Location('i4x', org, course, xml_object.tag, url_name)

        # VS[compat] -- detect new-style each-in-a-file mode
        if is_pointer_tag(xml_object):
            # new style:
            # read the actual definition file--named using url_name.replace(':','/')
            filepath = cls._format_filepath(xml_object.tag,
                                            name_to_pathname(url_name))
            definition_xml = cls.load_file(filepath, system.resources_fs,
                                           location)
        else:
            definition_xml = xml_object
            filepath = None

        definition, children = cls.load_definition(
            definition_xml, system, location)  # note this removes metadata

        # VS[compat] -- make Ike's github preview links work in both old and
        # new file layouts
        if is_pointer_tag(xml_object):
            # new style -- contents actually at filepath
            definition['filename'] = [filepath, filepath]

        metadata = cls.load_metadata(definition_xml)

        # move definition metadata into dict
        dmdata = definition.get('definition_metadata', '')
        if dmdata:
            metadata['definition_metadata_raw'] = dmdata
            try:
                metadata.update(json.loads(dmdata))
            except Exception as err:
                log.debug('Error %s in loading metadata %s' % (err, dmdata))
                metadata['definition_metadata_err'] = str(err)

        # Set/override any metadata specified by policy
        k = policy_key(location)
        if k in system.policy:
            cls.apply_policy(metadata, system.policy[k])

        model_data = {}
        model_data.update(metadata)
        model_data.update(definition)
        model_data['children'] = children

        model_data['xml_attributes'] = {}
        model_data['xml_attributes']['filename'] = definition.get(
            'filename', ['', None])  # for git link
        for key, value in metadata.items():
            if key not in set(f.name for f in cls.fields + cls.lms.fields):
                model_data['xml_attributes'][key] = value
        model_data['location'] = location
        model_data['category'] = xml_object.tag

        return cls(
            system,
            model_data,
        )

    @classmethod
    def _format_filepath(cls, category, name):
        return u'{category}/{name}.{ext}'.format(category=category,
                                                 name=name,
                                                 ext=cls.filename_extension)

    def export_to_file(self):
        """If this returns True, write the definition of this descriptor to a separate
        file.

        NOTE: Do not override this without a good reason.  It is here
        specifically for customtag...
        """
        return True

    def export_to_xml(self, resource_fs):
        """
        Returns an xml string representing this module, and all modules
        underneath it.  May also write required resources out to resource_fs

        Assumes that modules have single parentage (that no module appears twice
        in the same course), and that it is thus safe to nest modules as xml
        children as appropriate.

        The returned XML should be able to be parsed back into an identical
        XModuleDescriptor using the from_xml method with the same system, org,
        and course

        resource_fs is a pyfilesystem object (from the fs package)
        """

        # Get the definition
        xml_object = self.definition_to_xml(resource_fs)
        self.__class__.clean_metadata_from_xml(xml_object)

        # Set the tag so we get the file path right
        xml_object.tag = self.category

        def val_for_xml(attr):
            """Get the value for this attribute that we want to store.
            (Possible format conversion through an AttrMap).
             """
            attr_map = self.get_map_for_field(attr)
            return attr_map.to_xml(self._model_data[attr])

        # Add the non-inherited metadata
        for attr in sorted(own_metadata(self)):
            # don't want e.g. data_dir
            if attr not in self.metadata_to_strip and attr not in self.metadata_to_export_to_policy:
                val = val_for_xml(attr)
                try:
                    xml_object.set(attr, val)
                except Exception, e:
                    logging.exception(
                        'Failed to serialize metadata attribute {0} with value {1}. This could mean data loss!!!  Exception: {2}'
                        .format(attr, val, e))
                    pass

        for key, value in self.xml_attributes.items():
            if key not in self.metadata_to_strip:
                xml_object.set(key, value)

        if self.export_to_file():
            # Write the definition to a file
            url_path = name_to_pathname(self.url_name)
            filepath = self.__class__._format_filepath(self.category, url_path)
            resource_fs.makedir(os.path.dirname(filepath),
                                recursive=True,
                                allow_recreate=True)
            with resource_fs.open(filepath, 'w') as file:
                file.write(
                    etree.tostring(xml_object,
                                   pretty_print=True,
                                   encoding='utf-8'))

            # And return just a pointer with the category and filename.
            record_object = etree.Element(self.category)
        else:
            record_object = xml_object

        record_object.set('url_name', self.url_name)

        # Special case for course pointers:
        if self.category == 'course':
            # add org and course attributes on the pointer tag
            record_object.set('org', self.location.org)
            record_object.set('course', self.location.course)

        return etree.tostring(record_object,
                              pretty_print=True,
                              encoding='utf-8')
Ejemplo n.º 12
0
class CapaFields(object):
    """
    Define the possible fields for a Capa problem
    """
    display_name = String(
        display_name="Display Name",
        help=
        "This name appears in the horizontal navigation at the top of the page.",
        scope=Scope.settings,
        # it'd be nice to have a useful default but it screws up other things; so,
        # use display_name_with_default for those
        default="Blank Advanced Problem")
    attempts = Integer(
        help="Number of attempts taken by the student on this problem",
        default=0,
        scope=Scope.user_state)
    max_attempts = Integer(
        display_name="Maximum Attempts",
        help=
        ("Defines the number of times a student can try to answer this problem. "
         "If the value is not set, infinite attempts are allowed."),
        values={"min": 0},
        scope=Scope.settings)
    due = Date(help="Date that this problem is due by", scope=Scope.settings)
    graceperiod = Timedelta(
        help=
        "Amount of time after the due date that submissions will be accepted",
        scope=Scope.settings)
    showanswer = String(
        display_name="Show Answer",
        help=("Defines when to show the answer to the problem. "
              "A default value can be set in Advanced Settings."),
        scope=Scope.settings,
        default="finished",
        values=[{
            "display_name": "Always",
            "value": "always"
        }, {
            "display_name": "Answered",
            "value": "answered"
        }, {
            "display_name": "Attempted",
            "value": "attempted"
        }, {
            "display_name": "Closed",
            "value": "closed"
        }, {
            "display_name": "Finished",
            "value": "finished"
        }, {
            "display_name": "Past Due",
            "value": "past_due"
        }, {
            "display_name": "Never",
            "value": "never"
        }])
    force_save_button = Boolean(
        help="Whether to force the save button to appear on the page",
        scope=Scope.settings,
        default=False)
    rerandomize = Randomization(
        display_name="Randomization",
        help=
        "Defines how often inputs are randomized when a student loads the problem. "
        "This setting only applies to problems that can have randomly generated numeric values. "
        "A default value can be set in Advanced Settings.",
        default="never",
        scope=Scope.settings,
        values=[{
            "display_name": "Always",
            "value": "always"
        }, {
            "display_name": "On Reset",
            "value": "onreset"
        }, {
            "display_name": "Never",
            "value": "never"
        }, {
            "display_name": "Per Student",
            "value": "per_student"
        }])
    data = String(help="XML data for the problem",
                  scope=Scope.content,
                  default="<problem></problem>")
    correct_map = Dict(
        help="Dictionary with the correctness of current student answers",
        scope=Scope.user_state,
        default={})
    input_state = Dict(
        help="Dictionary for maintaining the state of inputtypes",
        scope=Scope.user_state)
    student_answers = Dict(
        help="Dictionary with the current student responses",
        scope=Scope.user_state)
    done = Boolean(help="Whether the student has answered the problem",
                   scope=Scope.user_state)
    seed = Integer(help="Random seed for this student", scope=Scope.user_state)
    weight = Float(
        display_name="Problem Weight",
        help=
        ("Defines the number of points each problem is worth. "
         "If the value is not set, each response field in the problem is worth one point."
         ),
        values={
            "min": 0,
            "step": .1
        },
        scope=Scope.settings)
    markdown = String(help="Markdown source of this module",
                      default=None,
                      scope=Scope.settings)
    source_code = String(
        help=
        "Source code for LaTeX and Word problems. This feature is not well-supported.",
        scope=Scope.settings)