Python opaque_keys.InvalidKeyError() Examples

The following are 30 code examples of opaque_keys.InvalidKeyError(). You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may also want to check out all available functions/classes of the module opaque_keys , or try the search function .
Example #1
Source File: views.py    From figures with MIT License 6 votes vote down vote up
def site_course_helper(self, pk):
        """Hep

        Improvements:
        * make this a decorator
        * Test this with both course id strings and CourseKey objects
        """
        course_id = pk.replace(' ', '+')
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            raise NotFound()

        site = django.contrib.sites.shortcuts.get_current_site(self.request)
        if figures.helpers.is_multisite():
            if site != figures.sites.get_site_for_course(course_id):
                raise NotFound()
        else:
            get_object_or_404(CourseOverview,
                              pk=course_key)
        return site, course_id 
Example #2
Source File: views.py    From jupyter-edx-grader-xblock with BSD 3-Clause "New" or "Revised" License 6 votes vote down vote up
def validate_user(self, request, course_id):
        """Validate that course exists and user is enrolled in course"""
        try:
            course_key = CourseKey.from_string(course_id)
            c = CourseEnrollment.objects.get(user_id=request.user.id, course_id=course_key)
            if not c.is_active:
                msg = "Access Denied. {} is not currently enrolled in {}"\
                        .format(request.user.username, course_id)
                raise ValidationError(msg, 403)

        # Something wrong with CourseKey
        except InvalidKeyError as e:
            msg = "Course: {} not found".format(course_id)
            raise ValidationError(msg, 404)

        # Enrollment does not exist for user
        except CourseEnrollment.DoesNotExist:
            log.error("User: {} tried to access student notebook in: {}"\
                .format(request.user.username, course_id))
            msg = "Access Denied. Either course {} does not exist or user {} is not currently enrolled"\
                    .format(course_id, request.user.username)
            raise ValidationError(msg, 403) 
Example #3
Source File: utils.py    From micromasters with BSD 3-Clause "New" or "Revised" License 6 votes vote down vote up
def parse_edx_key(edx_course_key):
    """
    Attempts to parse a CourseRun's edx_course_key as an edX opaque key and return the portion
    of the key that indicates the course's semester/year

    Args:
        edx_course_key (str): An edX course key (CourseRun.edx_course_key)

    Returns:
        str: The 'run' portion of a parsed CourseKey (opaque_keys.edx.keys.CourseKey#run),
        eg: '1T2016'. Return None if the parse fails.
    """
    try:
        course_key = CourseKey.from_string(edx_course_key)
    except InvalidKeyError:
        return None
    return course_key.run if course_key else None 
Example #4
Source File: test_contentstore.py    From ANALYSE with GNU Affero General Public License v3.0 6 votes vote down vote up
def assert_course_creation_failed(self, error_message):
        """
        Checks that the course did not get created
        """
        test_enrollment = False
        try:
            course_id = _get_course_id(self.course_data)
            initially_enrolled = CourseEnrollment.is_enrolled(self.user, course_id)
            test_enrollment = True
        except InvalidKeyError:
            # b/c the intent of the test with bad chars isn't to test auth but to test the handler, ignore
            pass
        resp = self.client.ajax_post('/course/', self.course_data)
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertRegexpMatches(data['ErrMsg'], error_message)
        if test_enrollment:
            # One test case involves trying to create the same course twice. Hence for that course,
            # the user will be enrolled. In the other cases, initially_enrolled will be False.
            self.assertEqual(initially_enrolled, CourseEnrollment.is_enrolled(self.user, course_id)) 
Example #5
Source File: views.py    From edx-enterprise with GNU Affero General Public License v3.0 6 votes vote down vote up
def is_course_run_id(self, course_id):
        """
        Returns True if the course_id is in the correct format of a course_run_id, false otherwise.

        Arguments:
            course_id (str): The course_key or course run id

        Returns:
            (Boolean): True or False
        """
        try:
            # Check if we have a course ID or a course run ID
            CourseKey.from_string(course_id)
        except InvalidKeyError:
            # The ID we have is for a course instead of a course run
            return False
        # If here, the course_id is a course_run_id
        return True 
Example #6
Source File: opaque_key_util.py    From edx-analytics-pipeline with GNU Affero General Public License v3.0 6 votes vote down vote up
def get_filename_safe_course_id(course_id, replacement_char='_'):
    """
    Create a representation of a course_id that can be used safely in a filepath.
    """
    try:
        course_key = CourseKey.from_string(course_id)
        # Ignore the namespace of the course_id altogether, for backwards compatibility.
        filename = course_key._to_string()  # pylint: disable=protected-access
    except InvalidKeyError:
        # If the course_id doesn't parse, we will still return a value here.
        filename = course_id

    # The safest characters are A-Z, a-z, 0-9, <underscore>, <period> and <hyphen>.
    # We represent the first four with \w.
    # TODO: Once we support courses with unicode characters, we will need to revisit this.
    return re.sub(r'[^\w\.\-]', unicode(replacement_char), filename) 
Example #7
Source File: delete_course.py    From ANALYSE with GNU Affero General Public License v3.0 6 votes vote down vote up
def handle(self, *args, **options):
        if len(args) != 1 and len(args) != 2:
            raise CommandError("delete_course requires one or more arguments: <course_id> |commit|")

        try:
            course_key = CourseKey.from_string(args[0])
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(args[0])

        commit = False
        if len(args) == 2:
            commit = args[1] == 'commit'

        if commit:
            print('Actually going to delete the course from DB....')

            if query_yes_no("Deleting course {0}. Confirm?".format(course_key), default="no"):
                if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
                    delete_course_and_groups(course_key, ModuleStoreEnum.UserID.mgmt_command) 
Example #8
Source File: empty_asset_trashcan.py    From ANALYSE with GNU Affero General Public License v3.0 6 votes vote down vote up
def handle(self, *args, **options):
        if len(args) != 1 and len(args) != 0:
            raise CommandError("empty_asset_trashcan requires one or no arguments: |<course_id>|")

        if len(args) == 1:
            try:
                course_key = CourseKey.from_string(args[0])
            except InvalidKeyError:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(args[0])

            course_ids = [course_key]
        else:
            course_ids = [course.id for course in modulestore().get_courses()]

        if query_yes_no("Emptying trashcan. Confirm?", default="no"):
            empty_asset_trashcan(course_ids) 
Example #9
Source File: views.py    From ecommerce with GNU Affero General Public License v3.0 6 votes vote down vote up
def _get_courses(self):
        course_keys = []
        course_ids = self.request.GET.getlist('course_id')
        for course_id in course_ids:
            try:
                course_run_key = CourseKey.from_string(course_id)
            except InvalidKeyError:
                # An `InvalidKeyError` is thrown because this course key not a course run key
                # We will get the title from the discovery.
                try:
                    course = get_course_detail(self.request.site, course_id)
                    course_keys.append(course.get('title'))
                except (ReqConnectionError, SlumberHttpBaseException, Timeout) as exc:
                    logger.exception(
                        '[Account activation failure] User tried to excess the course from discovery and failed.'
                        'User: %s, course: %s, Message: %s',
                        self.request.user.id,
                        course_id,
                        exc
                    )
                    raise Http404
            else:
                course_run = get_object_or_404(Course, id=course_run_key)
                course_keys.append(course_run.name)
        return course_keys 
Example #10
Source File: opaque_key_util.py    From edx-analytics-pipeline with GNU Affero General Public License v3.0 6 votes vote down vote up
def get_course_key_from_url(url):
    """
    Extracts the course from the given `url`, if possible.

    """
    url = url or ''

    match = COURSE_REGEX.match(url)
    course_key = None
    if match:
        course_id_string = match.group('course_id')
        try:
            course_key = CourseKey.from_string(course_id_string)
        except InvalidKeyError:
            pass

    return course_key 
Example #11
Source File: opaque_key_util.py    From edx-analytics-pipeline with GNU Affero General Public License v3.0 5 votes vote down vote up
def is_valid_course_id(course_id):
    """
    Determines if a course_id from an event log is possibly legitimate.
    """
    if course_id and course_id[-1] == '\n':
        log.error("Found course_id that ends with a newline character '%s'", course_id)
        return False

    try:
        _course_key = CourseKey.from_string(course_id)
        return True
    except InvalidKeyError as exc:
        log.error("Unable to parse course_id '%s' : error = %s", course_id, exc)
        return False 
Example #12
Source File: utils.py    From edx-analytics-data-api with GNU Affero General Public License v3.0 5 votes vote down vote up
def validate_course_id(course_id):
    """Raises CourseKeyMalformedError if course ID is invalid."""
    try:
        CourseKey.from_string(course_id)
    except InvalidKeyError:
        raise CourseKeyMalformedError(course_id=course_id) 
Example #13
Source File: opaque_key_util.py    From edx-analytics-pipeline with GNU Affero General Public License v3.0 5 votes vote down vote up
def is_valid_org_id(org_id):
    """
    Determines if a course_id from an event log is possibly legitimate.
    """
    try:
        _course_key = CourseLocator(org=org_id, course="course", run="run")
        return True
    except InvalidKeyError as exc:
        log.error("Unable to parse org_id '%s' : error = %s", org_id, exc)
        return False 
Example #14
Source File: opaque_key_util.py    From edx-analytics-pipeline with GNU Affero General Public License v3.0 5 votes vote down vote up
def get_org_id_for_course(course_id):
    """
    Args:
        course_id(unicode): The identifier for the course.

    Returns:
        The org_id extracted from the course_id, or None if none is found.
    """

    try:
        course_key = CourseKey.from_string(course_id)
        return course_key.org
    except InvalidKeyError:
        return None 
Example #15
Source File: models.py    From edx-enterprise with GNU Affero General Public License v3.0 5 votes vote down vote up
def proxied_get(self, *args, **kwargs):
        """
        Perform the query and returns a single object matching the given keyword arguments.

        This customizes the queryset to return an instance of ``ProxyDataSharingConsent`` when
        the searched-for ``DataSharingConsent`` instance does not exist.
        """
        # TODO: ENT-2010
        original_kwargs = kwargs.copy()
        if 'course_id' in kwargs:
            try:
                # Check if we have a course ID or a course run ID
                course_run_key = str(CourseKey.from_string(kwargs['course_id']))
            except InvalidKeyError:
                # The ID we have is for a course instead of a course run; fall through
                # to the second check.
                pass
            else:
                try:
                    # Try to get the record for the course run specifically
                    return self.get(*args, **kwargs)
                except DataSharingConsent.DoesNotExist:
                    # A record for the course run didn't exist, so modify the query
                    # parameters to look for just a course record on the second pass.

                    site = None
                    if 'enterprise_customer' in kwargs:
                        site = kwargs['enterprise_customer'].site

                    try:
                        course_id = get_course_catalog_api_service_client(site=site).get_course_id(
                            course_identifier=course_run_key
                        )
                        kwargs['course_id'] = course_id
                    except ImproperlyConfigured:
                        LOGGER.warning('CourseCatalogApiServiceClient is improperly configured.')

        try:
            return self.get(*args, **kwargs)
        except DataSharingConsent.DoesNotExist:
            return ProxyDataSharingConsent(**original_kwargs) 
Example #16
Source File: discovery.py    From edx-enterprise with GNU Affero General Public License v3.0 5 votes vote down vote up
def get_course_id(self, course_identifier):
        """
        Return the course id for the given course identifier.  The `course_identifier` may be a course id or a course
        run id; in either case the course id will be returned.

        The 'course id' is the identifier for a course (ex. edX+DemoX)
        The 'course run id' is the identifier for a run of a course (ex. edX+DemoX+demo_run)

        Arguments:
            course_identifier (str): The course id or course run id

        Returns:
            (str): course id
        """

        try:
            CourseKey.from_string(course_identifier)
        except InvalidKeyError:
            # An `InvalidKeyError` is thrown if `course_identifier` is not in the proper format for a course run id.
            # Since `course_identifier` is not a course run id we assume `course_identifier` is the  course id.
            return course_identifier

        # If here, `course_identifier` must be a course run id.
        # We cannot use `CourseKey.from_string` to find the course id because that method assumes the course id is
        # always a substring of the course run id and this is not always the case.  The only reliable way to determine
        # which courses are associated with a given course run id is by by calling the discovery service.
        course_run_data = self.get_course_run(course_identifier)
        if 'course' in course_run_data:
            return course_run_data['course']

        LOGGER.info(
            "Could not find course_key for course identifier [%s].", course_identifier
        )
        return None 
Example #17
Source File: clients.py    From edx-analytics-data-api with GNU Affero General Public License v3.0 5 votes vote down vote up
def all_videos(self, course_id):
        try:
            logger.debug('Retrieving course video blocks for course_id: %s', course_id)
            response = self.blocks.get(course_id=course_id, all_blocks=True, depth='all', block_types_filter='video')
            logger.info("Successfully authenticated with the Course Blocks API.")
        except HttpClientError as e:
            if e.response.status_code == 401:
                logger.warning("Course Blocks API failed to return video ids (%s). "
                               "See README for instructions on how to authenticate the API with your local LMS.",
                               e.response.status_code)
            elif e.response.status_code == 404:
                logger.warning("Course Blocks API failed to return video ids (%s). "
                               "Does the course exist in the LMS?",
                               e.response.status_code)
            else:
                logger.warning("Course Blocks API failed to return video ids (%s).", e.response.status_code)
            return None
        except RequestException as e:
            logger.warning("Course Blocks API request failed. Is the LMS running?: %s", str(e))
            return None

        # Setup a terrible hack to silence mysterious flood of ImportErrors from stevedore inside edx-opaque-keys.
        # (The UsageKey utility still works despite the import errors, so I think the errors are not important).
        with temp_log_level('stevedore', log_level=logging.CRITICAL):
            videos = []
            for video in response['blocks'].values():
                try:
                    encoded_id = UsageKey.from_string(video['id']).html_id()
                except InvalidKeyError:
                    encoded_id = video['id']  # just pass through any wonky ids we don't understand
                videos.append({'video_id': course_id + '|' + encoded_id,
                               'video_module_id': encoded_id})

        return videos 
Example #18
Source File: utils.py    From edx-analytics-data-api with GNU Affero General Public License v3.0 5 votes vote down vote up
def get_filename_safe_course_id(course_id, replacement_char='_'):
    """
    Create a representation of a course_id that can be used safely in a filepath.
    """
    try:
        course_key = CourseKey.from_string(course_id)
        filename = six.text_type(replacement_char).join([course_key.org, course_key.course, course_key.run])
    except InvalidKeyError:
        # If the course_id doesn't parse, we will still return a value here.
        filename = course_id

    # The safest characters are A-Z, a-z, 0-9, <underscore>, <period> and <hyphen>.
    # We represent the first four with \w.
    # TODO: Once we support courses with unicode characters, we will need to revisit this.
    return re.sub(r'[^\w\.\-]', six.text_type(replacement_char), filename) 
Example #19
Source File: test_helpers.py    From figures with MIT License 5 votes vote down vote up
def test_from_invalid_string(self):
        with pytest.raises(InvalidKeyError):
            as_course_key('some invalid string') 
Example #20
Source File: utils.py    From edx-proctoring with GNU Affero General Public License v3.0 5 votes vote down vote up
def _emit_event(name, context, data):
    """
    Do the actual integration into the event-tracker
    """

    try:
        if context:
            # try to parse out the org_id from the course_id
            if 'course_id' in context:
                try:
                    course_key = CourseKey.from_string(context['course_id'])
                    context['org_id'] = course_key.org
                except InvalidKeyError:
                    # leave org_id blank
                    pass

            with tracker.get_tracker().context(name, context):
                tracker.emit(name, data)
        else:
            # if None is passed in then we don't construct the 'with' context stack
            tracker.emit(name, data)
    except KeyError:
        # This happens when a default tracker has not been registered by the host application
        # aka LMS. This is normal when running unit tests in isolation.
        log.warning(
            u'Analytics tracker not properly configured. '
            u'If this message appears in a production environment, please investigate'
        ) 
Example #21
Source File: middleware.py    From edx-analytics-dashboard with GNU Affero General Public License v3.0 5 votes vote down vote up
def process_view(self, request, _view_func, _view_args, view_kwargs):
        request.course_key = None
        request.course_id = None

        course_id = view_kwargs.get('course_id', None)

        if course_id:
            try:
                request.course_key = CourseKey.from_string(course_id)
            except InvalidKeyError:
                # Raising an InvalidKeyError here causes a 500-level error which alerts devops. This should really be a
                # 404 error because though the course requested cannot be found, the server is operating correctly.
                raise Http404
            request.course_id = six.text_type(request.course_key) 
Example #22
Source File: models.py    From credentials with GNU Affero General Public License v3.0 5 votes vote down vote up
def validate_course_key(course_key):
    """
    Validate the course_key is correct.
    """
    try:
        CourseKey.from_string(course_key)
    except InvalidKeyError:
        raise ValidationError(_("Invalid course key.")) 
Example #23
Source File: migrate_to_split.py    From ANALYSE with GNU Affero General Public License v3.0 5 votes vote down vote up
def parse_args(self, *args):
        """
        Return a 5-tuple of passed in values for (course_key, user, org, course, run).
        """
        if len(args) < 2:
            raise CommandError(
                "migrate_to_split requires at least two arguments: "
                "a course_key and a user identifier (email or ID)"
            )

        try:
            course_key = CourseKey.from_string(args[0])
        except InvalidKeyError:
            raise CommandError("Invalid location string")

        try:
            user = user_from_str(args[1])
        except User.DoesNotExist:
            raise CommandError("No user found identified by {}".format(args[1]))

        org = course = run = None
        try:
            org = args[2]
            course = args[3]
            run = args[4]
        except IndexError:
            pass

        return course_key, user.id, org, course, run 
Example #24
Source File: edit_course_tabs.py    From ANALYSE with GNU Affero General Public License v3.0 5 votes vote down vote up
def handle(self, *args, **options):
        if not options['course']:
            raise CommandError(Command.course_option.help)

        try:
            course_key = CourseKey.from_string(options['course'])
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course'])

        course = get_course_by_id(course_key)

        print 'Warning: this command directly edits the list of course tabs in mongo.'
        print 'Tabs before any changes:'
        print_course(course)

        try:
            if options['delete']:
                if len(args) != 1:
                    raise CommandError(Command.delete_option.help)
                num = int(args[0])
                if query_yes_no('Deleting tab {0} Confirm?'.format(num), default='no'):
                    tabs.primitive_delete(course, num - 1)  # -1 for 0-based indexing
            elif options['insert']:
                if len(args) != 3:
                    raise CommandError(Command.insert_option.help)
                num = int(args[0])
                tab_type = args[1]
                name = args[2]
                if query_yes_no('Inserting tab {0} "{1}" "{2}" Confirm?'.format(num, tab_type, name), default='no'):
                    tabs.primitive_insert(course, num - 1, tab_type, name)  # -1 as above
        except ValueError as e:
            # Cute: translate to CommandError so the CLI error prints nicely.
            raise CommandError(e) 
Example #25
Source File: clone_course.py    From ANALYSE with GNU Affero General Public License v3.0 5 votes vote down vote up
def course_key_from_arg(self, arg):
        """
        Convert the command line arg into a course key
        """
        try:
            return CourseKey.from_string(arg)
        except InvalidKeyError:
            return SlashSeparatedCourseKey.from_deprecated_string(arg) 
Example #26
Source File: transcripts_ajax.py    From ANALYSE with GNU Affero General Public License v3.0 5 votes vote down vote up
def save_transcripts(request):
    """
    Saves video module with updated values of fields.

    Returns: status `Success` or status `Error` and HTTP 400.
    """
    response = {'status': 'Error'}

    data = json.loads(request.GET.get('data', '{}'))
    if not data:
        return error_response(response, 'Incoming video data is empty.')

    try:
        item = _get_item(request, data)
    except (InvalidKeyError, ItemNotFoundError):
        return error_response(response, "Can't find item by locator.")

    metadata = data.get('metadata')
    if metadata is not None:
        new_sub = metadata.get('sub')

        for metadata_key, value in metadata.items():
            setattr(item, metadata_key, value)

        item.save_with_metadata(request.user)  # item becomes updated with new values

        if new_sub:
            manage_video_subtitles_save(item, request.user)
        else:
            # If `new_sub` is empty, it means that user explicitly does not want to use
            # transcripts for current video ids and we remove all transcripts from storage.
            current_subs = data.get('current_subs')
            if current_subs is not None:
                for sub in current_subs:
                    remove_subs_from_store(sub, item)

        response['status'] = 'Success'

    return JsonResponse(response) 
Example #27
Source File: transcripts_ajax.py    From ANALYSE with GNU Affero General Public License v3.0 5 votes vote down vote up
def _validate_transcripts_data(request):
    """
    Validates, that request contains all proper data for transcripts processing.

    Returns tuple of 3 elements::

        data: dict, loaded json from request,
        videos: parsed `data` to useful format,
        item:  video item from storage

    Raises `TranscriptsRequestValidationException` if validation is unsuccessful
    or `PermissionDenied` if user has no access.
    """
    data = json.loads(request.GET.get('data', '{}'))
    if not data:
        raise TranscriptsRequestValidationException(_('Incoming video data is empty.'))

    try:
        item = _get_item(request, data)
    except (InvalidKeyError, ItemNotFoundError):
        raise TranscriptsRequestValidationException(_("Can't find item by locator."))

    if item.category != 'video':
        raise TranscriptsRequestValidationException(_('Transcripts are supported only for "video" modules.'))

    # parse data form request.GET.['data']['video'] to useful format
    videos = {'youtube': '', 'html5': {}}
    for video_data in data.get('videos'):
        if video_data['type'] == 'youtube':
            videos['youtube'] = video_data['video']
        else:  # do not add same html5 videos
            if videos['html5'].get('video') != video_data['video']:
                videos['html5'][video_data['video']] = video_data['mode']

    return data, videos, item 
Example #28
Source File: transcripts_ajax.py    From ANALYSE with GNU Affero General Public License v3.0 5 votes vote down vote up
def download_transcripts(request):
    """
    Passes to user requested transcripts file.

    Raises Http404 if unsuccessful.
    """
    locator = request.GET.get('locator')
    if not locator:
        log.debug('GET data without "locator" property.')
        raise Http404

    try:
        item = _get_item(request, request.GET)
    except (InvalidKeyError, ItemNotFoundError):
        log.debug("Can't find item by locator.")
        raise Http404

    subs_id = request.GET.get('subs_id')
    if not subs_id:
        log.debug('GET data without "subs_id" property.')
        raise Http404

    if item.category != 'video':
        log.debug('transcripts are supported only for video" modules.')
        raise Http404

    filename = 'subs_{0}.srt.sjson'.format(subs_id)
    content_location = StaticContent.compute_location(item.location.course_key, filename)
    try:
        sjson_transcripts = contentstore().find(content_location)
        log.debug("Downloading subs for %s id", subs_id)
        str_subs = generate_srt_from_sjson(json.loads(sjson_transcripts.data), speed=1.0)
        if not str_subs:
            log.debug('generate_srt_from_sjson produces no subtitles')
            raise Http404
        response = HttpResponse(str_subs, content_type='application/x-subrip')
        response['Content-Disposition'] = 'attachment; filename="{0}.srt"'.format(subs_id)
        return response
    except NotFoundError:
        log.debug("Can't find content in storage for %s subs", subs_id)
        raise Http404 
Example #29
Source File: views.py    From jupyter-edx-grader-xblock with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def validate_user(self, request, course_id):
        """Validate user has studio access to this course"""
        try:
            course_key = CourseKey.from_string(course_id)
            if not has_studio_write_access(request.user, course_key):
                msg = "Access Denied. User: {} does not have instructor rights"\
                " in this course"\
                    .format(request.user.username)
                raise ValidationError(msg, 403)

        # Something wrong with CourseKey
        except InvalidKeyError as e:
            msg = "Course: {} not found".format(course_id)
            raise ValidationError(msg, 404) 
Example #30
Source File: check_course.py    From ANALYSE with GNU Affero General Public License v3.0 4 votes vote down vote up
def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("check_course requires one argument: <course_id>")

        try:
            course_key = CourseKey.from_string(args[0])
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(args[0])

        store = modulestore()

        course = store.get_course(course_key, depth=3)

        err_cnt = 0

        def _xlint_metadata(module):
            err_cnt = check_module_metadata_editability(module)
            for child in module.get_children():
                err_cnt = err_cnt + _xlint_metadata(child)
            return err_cnt

        err_cnt = err_cnt + _xlint_metadata(course)

        # we've had a bug where the xml_attributes field can we rewritten as a string rather than a dict
        def _check_xml_attributes_field(module):
            err_cnt = 0
            if hasattr(module, 'xml_attributes') and isinstance(module.xml_attributes, basestring):
                print 'module = {0} has xml_attributes as a string. It should be a dict'.format(module.location)
                err_cnt = err_cnt + 1
            for child in module.get_children():
                err_cnt = err_cnt + _check_xml_attributes_field(child)
            return err_cnt

        err_cnt = err_cnt + _check_xml_attributes_field(course)

        # check for dangling discussion items, this can cause errors in the forums
        def _get_discussion_items(module):
            discussion_items = []
            if module.location.category == 'discussion':
                discussion_items = discussion_items + [module.location]

            for child in module.get_children():
                discussion_items = discussion_items + _get_discussion_items(child)

            return discussion_items

        discussion_items = _get_discussion_items(course)

        # now query all discussion items via get_items() and compare with the tree-traversal
        queried_discussion_items = store.get_items(course_key=course_key, qualifiers={'category': 'discussion'})

        for item in queried_discussion_items:
            if item.location not in discussion_items:
                print 'Found dangling discussion module = {0}'.format(item.location)