From 70cc7edf9b9c74ac1feb85b734aad8d795faea24 Mon Sep 17 00:00:00 2001 From: Samuel Allan Date: Tue, 2 Dec 2025 16:36:20 +1030 Subject: [PATCH] feat: add a csv report of xblocks used in courses This is added via an lms management command: ``` $ python manage.py lms xblock_list_csv --help ... usage: manage.py xblock_list_csv [-h] [--exclude-core-xblocks] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks] filename Generate a CSV file with all published components used in courses with their xblock type. positional arguments: filename Path to the CSV output file. Use '-' to print to stdout. options: -h, --help show this help message and exit --exclude-core-xblocks Exclude components using core XBlocks: (html, problem, video). Default: false ... ``` Example usage: ``` python manage.py lms xblock_list_csv --exclude-core-xblocks components.csv ``` Private-ref: https://tasks.opencraft.com/browse/BB-10225 --- .../management/commands/xblock_list_csv.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 lms/djangoapps/courseware/management/commands/xblock_list_csv.py diff --git a/lms/djangoapps/courseware/management/commands/xblock_list_csv.py b/lms/djangoapps/courseware/management/commands/xblock_list_csv.py new file mode 100644 index 000000000000..fe5eb1859a1d --- /dev/null +++ b/lms/djangoapps/courseware/management/commands/xblock_list_csv.py @@ -0,0 +1,84 @@ +""" +Script for generating CSV data on all components, +to audit the XBlocks used. +""" + +import csv +import sys + +from django.core.management.base import BaseCommand + +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from xmodule.modulestore.django import modulestore + +CORE_XBLOCKS = ["html", "problem", "video"] + + +class Command(BaseCommand): + """ + Generate a CSV file with all published components used in courses with their xblock type. + """ + + help = "Generate a CSV file with all published components used in courses with their xblock type." + + def add_arguments(self, parser): + parser.add_argument( + "filename", help="Path to the CSV output file. Use '-' to print to stdout." + ) + parser.add_argument( + "--exclude-core-xblocks", + action="store_true", + help=f"Exclude components using core XBlocks: ({', '.join(CORE_XBLOCKS)}). Default: false", + ) + + def handle(self, *args, **options): + filename = options["filename"] + exclude_core_xblocks = options["exclude_core_xblocks"] + + if filename == "-": + generate_xblocks_csv(sys.stdout, exclude_core_xblocks) + else: + with open(filename, "w") as file_handle: + generate_xblocks_csv(file_handle, exclude_core_xblocks) + + +def generate_xblocks_csv(file_handle, exclude_core_xblocks): + overviews = CourseOverview.objects.all().order_by("id") + + writer = csv.writer(file_handle) + writer.writerow( + ( + "Course ID", + "Course Name", + "Section Name", + "Subsection Name", + "Unit Name", + "Component Name", + "Xblock Type", + ) + ) + + for overview in overviews: + try: + course = modulestore().get_course(overview.id) + writer.writerows( + ( + course.id, + course.display_name, + section.display_name, + subsection.display_name, + unit.display_name, + component.display_name, + component.location.block_type, + ) + for section in course.get_children() + for subsection in section.get_children() + for unit in subsection.get_children() + for component in unit.get_children() + if not exclude_core_xblocks + or component.location.block_type not in CORE_XBLOCKS + ) + except: # pylint: disable=bare-except + self.stderr.write( + f"Failed retrieving course {overview.id} from modulestore" + )