diff --git a/cms/djangoapps/contentstore/management/commands/course_id_clash.py b/cms/djangoapps/contentstore/management/commands/course_id_clash.py new file mode 100644 index 0000000000..0f0f76aae5 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/course_id_clash.py @@ -0,0 +1,51 @@ +""" +Script for finding all courses whose org/name pairs == other courses when ignoring case +""" +from django.core.management.base import BaseCommand +from xmodule.modulestore.django import modulestore + + +# +# To run from command line: ./manage.py cms --settings dev course_id_clash +# +class Command(BaseCommand): + """ + Script for finding all courses whose org/name pairs == other courses when ignoring case + """ + help = 'List all courses ids which may collide when ignoring case' + + def handle(self, *args, **options): + mstore = modulestore() + if hasattr(mstore, 'collection'): + map_fn = ''' + function () { + emit(this._id.org.toLowerCase()+this._id.course.toLowerCase(), {target: this._id}); + } + ''' + reduce_fn = ''' + function (idpair, matches) { + var result = {target: []}; + matches.forEach(function (match) { + result.target.push(match.target); + }); + return result; + } + ''' + finalize = ''' + function(key, reduced) { + if (Array.isArray(reduced.target)) { + return reduced; + } + else {return null;} + } + ''' + results = mstore.collection.map_reduce( + map_fn, reduce_fn, {'inline': True}, query={'_id.category': 'course'}, finalize=finalize + ) + results = results.get('results') + for entry in results: + if entry.get('value') is not None: + print '{:-^40}'.format(entry.get('_id')) + for course_id in entry.get('value').get('target'): + print ' {}/{}/{}'.format(course_id.get('org'), course_id.get('course'), course_id.get('name')) + diff --git a/cms/djangoapps/contentstore/management/commands/tests/__init__.py b/cms/djangoapps/contentstore/management/commands/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_course_id_clash.py b/cms/djangoapps/contentstore/management/commands/tests/test_course_id_clash.py new file mode 100644 index 0000000000..5ef5756ad6 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/tests/test_course_id_clash.py @@ -0,0 +1,40 @@ +import sys +from StringIO import StringIO +from django.test import TestCase +from django.core.management import call_command +from xmodule.modulestore.tests.factories import CourseFactory + +class ClashIdTestCase(TestCase): + """ + Test for course_id_clash. + """ + def test_course_clash(self): + """ + Test for course_id_clash. + """ + expected = [] + # clashing courses + course = CourseFactory.create(org="test", course="courseid", display_name="run1") + expected.append(course.location.course_id) + course = CourseFactory.create(org="TEST", course="courseid", display_name="RUN12") + expected.append(course.location.course_id) + course = CourseFactory.create(org="test", course="CourseId", display_name="aRUN123") + expected.append(course.location.course_id) + # not clashing courses + not_expected = [] + course = CourseFactory.create(org="test", course="course2", display_name="run1") + not_expected.append(course.location.course_id) + course = CourseFactory.create(org="test1", course="courseid", display_name="run1") + not_expected.append(course.location.course_id) + course = CourseFactory.create(org="test", course="courseid0", display_name="run1") + not_expected.append(course.location.course_id) + + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + call_command('course_id_clash', stdout=mystdout) + sys.stdout = old_stdout + result = mystdout.getvalue() + for courseid in expected: + self.assertIn(courseid, result) + for courseid in not_expected: + self.assertNotIn(courseid, result)