Exemplo n.º 1
0
    def update_parent_if_moved(self, original_parent_location,
                               published_version, delete_draft_only, user_id):
        """
        Update parent of an item if it has moved.

        Arguments:
            original_parent_location (BlockUsageLocator)  : Original parent block locator.
            published_version (dict)   : Published version of the block.
            delete_draft_only (function)    : A callback function to delete draft children if it was moved.
            user_id (int)   : User id
        """
        for child_location in published_version.get('definition',
                                                    {}).get('children', []):
            item_location = UsageKey.from_string(
                child_location).map_into_course(
                    original_parent_location.course_key)
            try:
                source_item = self.get_item(item_location)
            except ItemNotFoundError:
                log.error('Unable to find the item %s', unicode(item_location))
                return

            if source_item.parent and source_item.parent.block_id != original_parent_location.block_id:
                if self.update_item_parent(item_location,
                                           original_parent_location,
                                           source_item.parent, user_id):
                    delete_draft_only(Location.from_string(child_location))
Exemplo n.º 2
0
        def convert_item(item, to_be_deleted):
            """
            Convert the subtree
            """
            # collect the children's ids for future processing
            next_tier = []
            for child in item.get('definition', {}).get('children', []):
                child_loc = Location.from_string(child)
                next_tier.append(child_loc.to_deprecated_son())

            # insert a new DRAFT version of the item
            item['_id']['revision'] = MongoRevisionKey.draft
            # ensure keys are in fixed and right order before inserting
            item['_id'] = self._id_dict_to_son(item['_id'])
            bulk_record = self._get_bulk_ops_record(location.course_key)
            bulk_record.dirty = True
            try:
                self.collection.insert(item)
            except pymongo.errors.DuplicateKeyError:
                # prevent re-creation of DRAFT versions, unless explicitly requested to ignore
                if not ignore_if_draft:
                    raise DuplicateItemError(item['_id'], self, 'collection')

            # delete the old PUBLISHED version if requested
            if delete_published:
                item['_id']['revision'] = MongoRevisionKey.published
                to_be_deleted.append(item['_id'])

            return next_tier
Exemplo n.º 3
0
def get_students_problem_grades(request, csv=False):
    """
    Get a list of students and grades for a particular problem.
    If 'csv' is False, returns a dict of student's name: username: grade: percent.

    If 'csv' is True, returns a header array, and an array of arrays in the format:
    student names, usernames, grades, percents for CSV download.
    """
    module_state_key = Location.from_string(request.GET.get('module_id'))
    csv = request.GET.get('csv')

    # Query for "problem grades" students
    students = models.StudentModule.objects.select_related('student').filter(
        module_state_key=module_state_key,
        module_type__exact='problem',
        grade__isnull=False,
    ).values('student__username', 'student__profile__name', 'grade', 'max_grade').order_by('student__profile__name')

    results = []
    if not csv:
        # Restrict screen list length
        # Adding 1 so can tell if list is larger than MAX_SCREEN_LIST_LENGTH
        # without doing another select.
        for student in students[0:MAX_SCREEN_LIST_LENGTH + 1]:
            student_dict = {
                'name': student['student__profile__name'],
                'username': student['student__username'],
                'grade': student['grade'],
            }

            student_dict['percent'] = 0
            if student['max_grade'] > 0:
                student_dict['percent'] = round(student['grade'] * 100 / student['max_grade'])
            results.append(student_dict)

        max_exceeded = False
        if len(results) > MAX_SCREEN_LIST_LENGTH:
            # Remove the last item so list length is exactly MAX_SCREEN_LIST_LENGTH
            del results[-1]
            max_exceeded = True

        response_payload = {
            'results': results,
            'max_exceeded': max_exceeded,
        }
        return JsonResponse(response_payload)
    else:
        tooltip = request.GET.get('tooltip')
        filename = sanitize_filename(tooltip[:tooltip.rfind(' - ')])

        header = [_("Name"), _("Username"), _("Grade"), _("Percent")]
        for student in students:
            percent = 0
            if student['max_grade'] > 0:
                percent = round(student['grade'] * 100 / student['max_grade'])
            results.append([student['student__profile__name'], student['student__username'], student['grade'], percent])

        response = create_csv_response(filename, header, results)
        return response
Exemplo n.º 4
0
def get_students_problem_grades(request, csv=False):
    """
    Get a list of students and grades for a particular problem.
    If 'csv' is False, returns a dict of student's name: username: grade: percent.

    If 'csv' is True, returns a header array, and an array of arrays in the format:
    student names, usernames, grades, percents for CSV download.
    """
    module_state_key = Location.from_string(request.GET.get('module_id'))
    csv = request.GET.get('csv')

    # Query for "problem grades" students
    students = models.StudentModule.objects.select_related('student').filter(
        module_state_key=module_state_key,
        module_type__exact='problem',
        grade__isnull=False,
    ).values('student__username', 'student__profile__name', 'grade', 'max_grade').order_by('student__profile__name')

    results = []
    if not csv:
        # Restrict screen list length
        # Adding 1 so can tell if list is larger than MAX_SCREEN_LIST_LENGTH
        # without doing another select.
        for student in students[0:MAX_SCREEN_LIST_LENGTH + 1]:
            student_dict = {
                'name': student['student__profile__name'],
                'username': student['student__username'],
                'grade': student['grade'],
            }

            student_dict['percent'] = 0
            if student['max_grade'] > 0:
                student_dict['percent'] = round(student['grade'] * 100 / student['max_grade'])
            results.append(student_dict)

        max_exceeded = False
        if len(results) > MAX_SCREEN_LIST_LENGTH:
            # Remove the last item so list length is exactly MAX_SCREEN_LIST_LENGTH
            del results[-1]
            max_exceeded = True

        response_payload = {
            'results': results,
            'max_exceeded': max_exceeded,
        }
        return JsonResponse(response_payload)
    else:
        tooltip = request.GET.get('tooltip')
        filename = sanitize_filename(tooltip[:tooltip.rfind(' - ')])

        header = [_("Name"), _("Username"), _("Grade"), _("Percent")]
        for student in students:
            percent = 0
            if student['max_grade'] > 0:
                percent = round(student['grade'] * 100 / student['max_grade'])
            results.append([student['student__profile__name'], student['student__username'], student['grade'], percent])

        response = create_csv_response(filename, header, results)
        return response
Exemplo n.º 5
0
 def problem_location(problem_url_name, course_key=None):
     """
     Create an internal location for a test problem.
     """
     if "i4x:" in problem_url_name:
         return Location.from_string(problem_url_name)
     elif course_key:
         return course_key.make_usage_key('problem', problem_url_name)
     else:
         return TEST_COURSE_KEY.make_usage_key('problem', problem_url_name)
Exemplo n.º 6
0
 def problem_location(problem_url_name, course_key=None):
     """
     Create an internal location for a test problem.
     """
     if "i4x:" in problem_url_name:
         return Location.from_string(problem_url_name)
     elif course_key:
         return course_key.make_usage_key('problem', problem_url_name)
     else:
         return TEST_COURSE_KEY.make_usage_key('problem', problem_url_name)
Exemplo n.º 7
0
def get_students_opened_subsection(request, csv=False):
    """
    Get a list of students that opened a particular subsection.
    If 'csv' is False, returns a dict of student's name: username.

    If 'csv' is True, returns a header array, and an array of arrays in the format:
    student names, usernames for CSV download.
    """
    module_state_key = Location.from_string(request.GET.get('module_id'))
    csv = request.GET.get('csv')

    # Query for "opened a subsection" students
    students = models.StudentModule.objects.select_related('student').filter(
        module_state_key__exact=module_state_key,
        module_type__exact='sequential',
    ).values('student__username',
             'student__profile__name').order_by('student__profile__name')

    results = []
    if not csv:
        # Restrict screen list length
        # Adding 1 so can tell if list is larger than MAX_SCREEN_LIST_LENGTH
        # without doing another select.
        for student in students[0:MAX_SCREEN_LIST_LENGTH + 1]:
            results.append({
                'name': student['student__profile__name'],
                'username': student['student__username'],
            })

        max_exceeded = False
        if len(results) > MAX_SCREEN_LIST_LENGTH:
            # Remove the last item so list length is exactly MAX_SCREEN_LIST_LENGTH
            del results[-1]
            max_exceeded = True
        response_payload = {
            'results': results,
            'max_exceeded': max_exceeded,
        }
        return JsonResponse(response_payload)
    else:
        tooltip = request.GET.get('tooltip')

        # Subsection name is everything after 3rd space in tooltip
        filename = sanitize_filename(' '.join(tooltip.split(' ')[3:]))

        header = [_("Name"), _("Username")]
        for student in students:
            results.append([
                student['student__profile__name'], student['student__username']
            ])

        response = create_csv_response(filename, header, results)
        return response
Exemplo n.º 8
0
 def to_python(self, location):  # lint-amnesty, pylint: disable=arguments-differ
     """
     Deserialize to a UsageKey instance: for now it's a location missing the run
     """
     assert isinstance(location, (type(None), str, UsageKey))
     if location == '':
         return None
     if isinstance(location, str):
         location = super().to_python(location)
         return Location.from_string(location)
     else:
         return location
 def to_python(self, location):
     """
     Deserialize to a UsageKey instance: for now it's a location missing the run
     """
     assert isinstance(location, (NoneType, basestring, UsageKey))
     if location == '':
         return None
     if isinstance(location, basestring):
         location = super(UsageKeyField, self).to_python(location)
         return Location.from_string(location)
     else:
         return location
Exemplo n.º 10
0
 def to_python(self, location):
     """
     Deserialize to a UsageKey instance: for now it's a location missing the run
     """
     assert isinstance(location, (NoneType, basestring, UsageKey))
     if location == '':
         return None
     if isinstance(location, basestring):
         location = super(UsageKeyField, self).to_python(location)
         return Location.from_string(location)
     else:
         return location
Exemplo n.º 11
0
def get_course_child_key(content_id):
    """
    Returns course child key
    """
    try:
        content_id = UsageKey.from_string(content_id)
    except InvalidKeyError:
        try:
            content_id = Location.from_string(content_id)
        except (InvalidLocationError, InvalidKeyError):
            content_id = None
    return content_id
Exemplo n.º 12
0
def get_students_opened_subsection(request, csv=False):
    """
    Get a list of students that opened a particular subsection.
    If 'csv' is False, returns a dict of student's name: username.

    If 'csv' is True, returns a header array, and an array of arrays in the format:
    student names, usernames for CSV download.
    """
    module_state_key = Location.from_string(request.GET.get('module_id'))
    csv = request.GET.get('csv')

    # Query for "opened a subsection" students
    students = models.StudentModule.objects.select_related('student').filter(
        module_state_key__exact=module_state_key,
        module_type__exact='sequential',
    ).values('student__username', 'student__profile__name').order_by('student__profile__name')

    results = []
    if not csv:
        # Restrict screen list length
        # Adding 1 so can tell if list is larger than MAX_SCREEN_LIST_LENGTH
        # without doing another select.
        for student in students[0:MAX_SCREEN_LIST_LENGTH + 1]:
            results.append({
                'name': student['student__profile__name'],
                'username': student['student__username'],
            })

        max_exceeded = False
        if len(results) > MAX_SCREEN_LIST_LENGTH:
            # Remove the last item so list length is exactly MAX_SCREEN_LIST_LENGTH
            del results[-1]
            max_exceeded = True
        response_payload = {
            'results': results,
            'max_exceeded': max_exceeded,
        }
        return JsonResponse(response_payload)
    else:
        tooltip = request.GET.get('tooltip')

        # Subsection name is everything after 3rd space in tooltip
        filename = sanitize_filename(' '.join(tooltip.split(' ')[3:]))

        header = [_("Name"), _("Username")]
        for student in students:
            results.append([student['student__profile__name'], student['student__username']])

        response = create_csv_response(filename, header, results)
        return response
Exemplo n.º 13
0
 def delete_draft_only(root_location):
     """
     Helper function that calls delete on the specified location if a draft version of the item exists.
     If no draft exists, this function recursively calls itself on the children of the item.
     """
     query = root_location.to_deprecated_son(prefix='_id.')
     del query['_id.revision']
     versions_found = self.collection.find(
         query, {
             '_id': True,
             'definition.children': True
         },
         sort=[SORT_REVISION_FAVOR_DRAFT])
     # If 2 versions versions exist, we can assume one is a published version. Go ahead and do the delete
     # of the draft version.
     if versions_found.count() > 1:
         # Moving a child from published parent creates a draft of the parent and moved child.
         published_version = [
             version for version in versions_found if version.get(
                 '_id').get('revision') != MongoRevisionKey.draft
         ]
         if len(published_version) > 0:
             # This change makes sure that parents are updated too i.e. an item will have only one parent.
             self.update_parent_if_moved(root_location,
                                         published_version[0],
                                         delete_draft_only, user_id)
         self._delete_subtree(root_location, [as_draft],
                              draft_only=True)
     elif versions_found.count() == 1:
         # Since this method cannot be called on something in DIRECT_ONLY_CATEGORIES and we call
         # delete_subtree as soon as we find an item with a draft version, if there is only 1 version
         # it must be published (since adding a child to a published item creates a draft of the parent).
         item = versions_found[0]
         assert item.get('_id').get(
             'revision') != MongoRevisionKey.draft
         for child in item.get('definition', {}).get('children', []):
             child_loc = Location.from_string(child)
             delete_draft_only(child_loc)
Exemplo n.º 14
0
class TestAsideKeys(TestCase):
    """Test of Aside keys."""
    @ddt.data(*itertools.product([
        AsideUsageKeyV1,
        AsideUsageKeyV2,
    ], [
        Location.from_string('i4x://org/course/cat/name'),
        BlockUsageLocator(CourseLocator('org', 'course', 'run'), 'block_type',
                          'block_id'),
    ], ['aside', 'aside_b']))
    @ddt.unpack
    def test_usage_round_trip_deserialized(self, key_class, usage_key,
                                           aside_type):
        key = key_class(usage_key, aside_type)
        serialized = text_type(key)
        deserialized = AsideUsageKey.from_string(serialized)
        self.assertEqual(key, deserialized)
        self.assertEqual(usage_key, key.usage_key, usage_key)
        self.assertEqual(usage_key, deserialized.usage_key)
        self.assertEqual(aside_type, key.aside_type)
        self.assertEqual(aside_type, deserialized.aside_type)

    @ddt.data(
        'aside-usage-v1:i4x://org/course/cat/name::aside',
        'aside-usage-v1:block-v1:org+course+cat+type@block_type+block@name::aside',
        'aside-usage-v2:lib-block-v1$:$:+-+branch@-+version@000000000000000000000000+type@-+block@-::0',
        'aside-usage-v2:i4x$://-/-/-/$:$:-::0',
        'aside-usage-v2:i4x$://-/-/-/$:$:$:-::0',
        'aside-usage-v2:i4x$://-/-/$:$:$:$:$:/-::0',
    )
    def test_usage_round_trip_serialized(self, aside_key):
        deserialized = AsideUsageKey.from_string(aside_key)
        serialized = text_type(deserialized)
        self.assertEqual(aside_key, serialized)

    @ddt.data(*itertools.product([
        AsideDefinitionKeyV1,
        AsideDefinitionKeyV2,
    ], [
        DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234'),
    ], ['aside', 'aside_b']))
    @ddt.unpack
    def test_definition_round_trip_deserialized(self, key_class,
                                                definition_key, aside_type):
        key = key_class(definition_key, aside_type)
        serialized = text_type(key)
        deserialized = AsideDefinitionKey.from_string(serialized)
        self.assertEqual(key, deserialized)
        self.assertEqual(definition_key, key.definition_key, definition_key)
        self.assertEqual(definition_key, deserialized.definition_key)
        self.assertEqual(aside_type, key.aside_type)
        self.assertEqual(aside_type, deserialized.aside_type)

    @ddt.data(
        'aside-def-v1:def-v1:abcd1234abcd1234abcd1234+type@block_type::aside',
        'aside-def-v2:def-v1$:abcd1234abcd1234abcd1234+type@block_type::aside')
    def test_definition_round_trip_serialized(self, aside_key):
        deserialized = AsideDefinitionKey.from_string(aside_key)
        serialized = text_type(deserialized)
        self.assertEqual(aside_key, serialized)

    @ddt.data(*itertools.product(
        [
            AsideUsageKeyV1,
            AsideUsageKeyV2,
        ],
        [
            ('aside_type', 'bside'),
            ('usage_key',
             BlockUsageLocator(CourseLocator('borg', 'horse', 'gun'),
                               'lock_type', 'lock_id')),
            ('block_id', 'lock_id'),
            ('block_type', 'lock_type'),
            # BlockUsageLocator can't `replace` a definition_key, so skip for now
            # ('definition_key', DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234')),
            ('course_key', CourseLocator('borg', 'horse', 'gun')),
        ]))
    @ddt.unpack
    def test_usage_key_replace(self, key_class, attr_value):
        attr, value = attr_value
        key = key_class(
            BlockUsageLocator(CourseLocator('org', 'course', 'run'),
                              'block_type', 'block_id'), 'aside')
        new_key = key.replace(**{attr: value})
        self.assertEqual(getattr(new_key, attr), value)

    @ddt.data(*itertools.product([
        AsideDefinitionKeyV1,
        AsideDefinitionKeyV2,
    ], [
        ('aside_type', 'bside'),
        ('definition_key',
         DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234')),
        ('block_type', 'lock_type'),
    ]))
    @ddt.unpack
    def test_definition_key_replace(self, key_class, attr_value):
        attr, value = attr_value
        key = key_class(
            DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234'),
            'aside')
        new_key = key.replace(**{attr: value})
        self.assertEqual(getattr(new_key, attr), value)
Exemplo n.º 15
0
class TestAsideKeys(TestCase):
    """Test of Aside keys."""
    @ddt.data(
        (Location.from_string('i4x://org/course/cat/name'), 'aside'),
        (BlockUsageLocator(CourseLocator('org', 'course', 'run'), 'block_type',
                           'block_id'), 'aside'),
    )
    @ddt.unpack
    def test_usage_round_trip_deserialized(self, usage_key, aside_type):
        key = AsideUsageKeyV1(usage_key, aside_type)
        serialized = unicode(key)
        deserialized = AsideUsageKey.from_string(serialized)
        self.assertEquals(key, deserialized)
        self.assertEquals(usage_key, key.usage_key, usage_key)
        self.assertEquals(usage_key, deserialized.usage_key)
        self.assertEquals(aside_type, key.aside_type)
        self.assertEquals(aside_type, deserialized.aside_type)

    @ddt.data(
        'aside-usage-v1:i4x://org/course/cat/name::aside',
        'aside-usage-v1:block-v1:org+course+cat+type@block_type+block@name::aside',
    )
    def test_usage_round_trip_serialized(self, aside_key):
        deserialized = AsideUsageKey.from_string(aside_key)
        serialized = unicode(deserialized)
        self.assertEquals(aside_key, serialized)

    @ddt.data(
        (DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234'), 'aside'),
    )
    @ddt.unpack
    def test_definition_round_trip_deserialized(self, definition_key,
                                                aside_type):
        key = AsideDefinitionKeyV1(definition_key, aside_type)
        serialized = unicode(key)
        deserialized = AsideDefinitionKey.from_string(serialized)
        self.assertEquals(key, deserialized)
        self.assertEquals(definition_key, key.definition_key, definition_key)
        self.assertEquals(definition_key, deserialized.definition_key)
        self.assertEquals(aside_type, key.aside_type)
        self.assertEquals(aside_type, deserialized.aside_type)

    @ddt.data(
        'aside-def-v1:def-v1:abcd1234abcd1234abcd1234+type@block_type::aside')
    def test_definition_round_trip_serialized(self, aside_key):
        deserialized = AsideDefinitionKey.from_string(aside_key)
        serialized = unicode(deserialized)
        self.assertEquals(aside_key, serialized)

    @ddt.data(
        ('aside_type', 'bside'),
        ('usage_key',
         BlockUsageLocator(CourseLocator('borg', 'horse', 'gun'), 'lock_type',
                           'lock_id')),
        ('block_id', 'lock_id'),
        ('block_type', 'lock_type'),
        # BlockUsageLocator can't `replace` a definition_key, so skip for now
        # ('definition_key', DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234')),
        ('course_key', CourseLocator('borg', 'horse', 'gun')),
    )
    @ddt.unpack
    def test_usage_key_replace(self, attr, value):
        key = AsideUsageKeyV1(
            BlockUsageLocator(CourseLocator('org', 'course', 'run'),
                              'block_type', 'block_id'), 'aside')
        new_key = key.replace(**{attr: value})
        self.assertEquals(getattr(new_key, attr), value)

    @ddt.data(
        ('aside_type', 'bside'),
        ('definition_key',
         DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234')),
        ('block_type', 'lock_type'),
    )
    @ddt.unpack
    def test_definition_key_replace(self, attr, value):
        key = AsideDefinitionKeyV1(
            DefinitionLocator('block_type', 'abcd1234abcd1234abcd1234'),
            'aside')
        new_key = key.replace(**{attr: value})
        self.assertEquals(getattr(new_key, attr), value)