Commit dffbc69a by Calen Pennington

Merge pull request #11329 from cpennington/fix-cert-type-names

Fix the help text for cert_name_short and cert_name_long
parents 97955251 4191ea00
"""
A single-use management command that provides an interactive way to remove
erroneous certificate names.
"""
from collections import namedtuple
from django.core.management.base import BaseCommand
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
Result = namedtuple("Result", ["course_key", "cert_name_short", "cert_name_long", "should_clean"])
class Command(BaseCommand):
"""
A management command that provides an interactive way to remove erroneous cert_name_long and
cert_name_short course attributes across both the Split and Mongo modulestores.
"""
help = 'Allows manual clean-up of invalid cert_name_short and cert_name_long entries on CourseModules'
def _mongo_results(self):
"""
Return Result objects for any mongo-modulestore backend course that has
cert_name_short or cert_name_long set.
"""
# N.B. This code breaks many abstraction barriers. That's ok, because
# it's a one-time cleanup command.
# pylint: disable=protected-access
mongo_modulestore = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo)
old_mongo_courses = mongo_modulestore.collection.find({
"_id.category": "course",
"$or": [
{"metadata.cert_name_short": {"$exists": 1}},
{"metadata.cert_name_long": {"$exists": 1}},
]
}, {
"_id": True,
"metadata.cert_name_short": True,
"metadata.cert_name_long": True,
})
return [
Result(
mongo_modulestore.make_course_key(
course['_id']['org'],
course['_id']['course'],
course['_id']['name'],
),
course['metadata'].get('cert_name_short'),
course['metadata'].get('cert_name_long'),
True
) for course in old_mongo_courses
]
def _split_results(self):
"""
Return Result objects for any split-modulestore backend course that has
cert_name_short or cert_name_long set.
"""
# N.B. This code breaks many abstraction barriers. That's ok, because
# it's a one-time cleanup command.
# pylint: disable=protected-access
split_modulestore = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.split)
active_version_collection = split_modulestore.db_connection.course_index
structure_collection = split_modulestore.db_connection.structures
branches = active_version_collection.aggregate([{
'$group': {
'_id': 1,
'draft': {'$push': '$versions.draft-branch'},
'published': {'$push': '$versions.published-branch'}
}
}, {
'$project': {
'_id': 1,
'branches': {'$setUnion': ['$draft', '$published']}
}
}])['result'][0]['branches']
structures = list(
structure_collection.find({
'_id': {'$in': branches},
'blocks': {'$elemMatch': {
'$and': [
{"block_type": "course"},
{'$or': [
{'fields.cert_name_long': {'$exists': True}},
{'fields.cert_name_short': {'$exists': True}}
]}
]
}}
}, {
'_id': True,
'blocks.fields.cert_name_long': True,
'blocks.fields.cert_name_short': True,
})
)
structure_map = {struct['_id']: struct for struct in structures}
structure_ids = [struct['_id'] for struct in structures]
split_mongo_courses = list(active_version_collection.find({
'$or': [
{"versions.draft-branch": {'$in': structure_ids}},
{"versions.published": {'$in': structure_ids}},
]
}, {
'org': True,
'course': True,
'run': True,
'versions': True,
}))
for course in split_mongo_courses:
draft = course['versions'].get('draft-branch')
if draft in structure_map:
draft_fields = structure_map[draft]['blocks'][0].get('fields', {})
else:
draft_fields = {}
published = course['versions'].get('published')
if published in structure_map:
published_fields = structure_map[published]['blocks'][0].get('fields', {})
else:
published_fields = {}
for fields in (draft_fields, published_fields):
for field in ('cert_name_short', 'cert_name_long'):
if field in fields:
course[field] = fields[field]
return [
Result(
split_modulestore.make_course_key(
course['org'],
course['course'],
course['run'],
),
course.get('cert_name_short'),
course.get('cert_name_long'),
True
) for course in split_mongo_courses
]
def _display(self, results):
"""
Render a list of Result objects as a nicely formatted table.
"""
headers = ["Course Key", "cert_name_short", "cert_name_short", "Should clean?"]
col_widths = [
max(len(unicode(result[col])) for result in results + [headers])
for col in range(len(results[0]))
]
id_format = "{{:>{}}} |".format(len(unicode(len(results))))
col_format = "| {{:>{}}} |"
self.stdout.write(id_format.format(""), ending='')
for header, width in zip(headers, col_widths):
self.stdout.write(col_format.format(width).format(header), ending='')
self.stdout.write('')
for idx, result in enumerate(results):
self.stdout.write(id_format.format(idx), ending='')
for col, width in zip(result, col_widths):
self.stdout.write(col_format.format(width).format(unicode(col)), ending='')
self.stdout.write("")
def _commit(self, results):
"""
For each Result in ``results``, if ``should_clean`` is True, remove cert_name_long
and cert_name_short from the course and save in the backing modulestore.
"""
for result in results:
if not result.should_clean:
continue
course = modulestore().get_course(result.course_key)
del course.cert_name_short
del course.cert_name_long
modulestore().update_item(course, ModuleStoreEnum.UserID.mgmt_command)
def handle(self, *args, **options):
results = self._mongo_results() + self._split_results()
self.stdout.write("Type the index of a row to toggle whether it will be cleaned, "
"'commit' to remove all cert_name_short and cert_name_long values "
"from any rows marked for cleaning, or 'quit' to quit.")
while True:
self._display(results)
command = raw_input("<index>|commit|quit: ").strip()
if command == 'quit':
return
elif command == 'commit':
self._commit(results)
return
elif command == '':
continue
else:
index = int(command)
results[index] = results[index]._replace(should_clean=not results[index].should_clean)
......@@ -495,9 +495,9 @@ class CourseFields(object):
## Course level Certificate Name overrides.
cert_name_short = String(
help=_(
"Use this setting only when generating PDF certificates. "
"Between quotation marks, enter the short name of the course to use on the certificate that "
"students receive when they complete the course."
'Use this setting only when generating PDF certificates. '
'Between quotation marks, enter the short name of the type of certificate that '
'students receive when they complete the course. For instance, "Certificate".'
),
display_name=_("Certificate Name (Short)"),
scope=Scope.settings,
......@@ -505,9 +505,9 @@ class CourseFields(object):
)
cert_name_long = String(
help=_(
"Use this setting only when generating PDF certificates. "
"Between quotation marks, enter the long name of the course to use on the certificate that students "
"receive when they complete the course."
'Use this setting only when generating PDF certificates. '
'Between quotation marks, enter the long name of the type of certificate that students '
'receive when they complete the course. For instance, "Certificate of Achievement".'
),
display_name=_("Certificate Name (Long)"),
scope=Scope.settings,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment