diff --git a/lms/djangoapps/instructor/views/course_log.py b/lms/djangoapps/instructor/views/course_log.py new file mode 100644 index 000000000000..5b3d32764460 --- /dev/null +++ b/lms/djangoapps/instructor/views/course_log.py @@ -0,0 +1,153 @@ +import logging +import json +import pytz + +from bson.json_util import dumps +from django.contrib.auth.models import User +from django.views.decorators.csrf import csrf_exempt +from common.djangoapps.util.json_request import JsonResponse +from opaque_keys.edx.keys import CourseKey +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.django import modulestore +from django.views.decorators.clickjacking import xframe_options_exempt +from itertools import chain + + +log = logging.getLogger(__name__) + + + +def get_course_unit_log(course_id): + course_key = CourseKey.from_string(course_id) + 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 + course_definition = active_version_collection.find({"org" : course_key.org, "course" : course_key.course, "run" : course_key.run}) + + published_version = structure_collection.find_one({"_id" : course_definition[0]["versions"]["published-branch"]}) + + #fetch all previous versions + all_previous_versions = [] + document = published_version + + while True: + previous_version_id = document["previous_version"] + document = structure_collection.find_one({"_id": previous_version_id}) + + if not document: + break + + all_previous_versions.append(document) + + all_previous_versions = sorted(all_previous_versions, key=lambda x: x.get("edited_on", 0)) + + data = get_course_history(course_definition, published_version, all_previous_versions) + return data + + +def get_course_history(course_definition, published_version, all_previous_versions): + course_logs = {"last_updated_on" : conver_utc_ist(published_version["edited_on"]), + "components" : {}, "section_details" : {}, "subsection_details" : {}, "unit_details" : {}} + + course_logs["latest_published_componenets"] = [block["block_id"] for block in published_version["blocks"] if block["block_type"] not in ("course", "course_info", "about", "chapter", "vertical", "sequential")] + + for version in all_previous_versions: + course_logs = process_course_logs(version, course_logs) + + course_logs = process_course_logs(published_version, course_logs) + + return course_logs + +def process_course_logs(version, course_logs): + + components_list = [] + + for block in version["blocks"]: + + if block["block_type"] not in ("course", "course_info", "about", "chapter", "vertical", "sequential", "static_tab"): + block_id = block["block_id"] + + components_list.append(block_id) + + parents_list, parents_names = find_block_parents(version, block_id) + + edited_on = conver_utc_ist(block['edit_info']['edited_on']) + user_obj = User.objects.get(id = block["edit_info"]["edited_by"]) + + if block_id not in course_logs["components"]: + status = "Created" + course_logs["components"][block_id] = {"block_type" : block["block_type"].replace("_"," ").title(), "edited_info" : []} + + else: + + previous_block_info = course_logs["components"][block_id]["edited_info"][-1] + + if block["definition"] != previous_block_info["definition"]: + status = "Edited" + + elif block["definition"] == previous_block_info["definition"] and previous_block_info["fields"] != block["fields"]: + status = "Edited" + + elif parents_list != previous_block_info["parents_list"]: + #add another obj as Moved Out with reference to -1 obj + course_logs["components"][block_id]["edited_info"].append({ "definition" : block["definition"], "fields" : block["fields"], + "parents_list" : previous_block_info["parents_list"], + "parents_names" : previous_block_info["parents_names"], + "edited_on" : edited_on, "edited_by" : user_obj.first_name, + "status" : "Moved Out"}) + status = "Moved In" + else: + continue + + course_logs["components"][block_id]["edited_info"].append({ "definition" : block["definition"], "fields" : block["fields"], + "parents_list" : parents_list, "parents_names" : parents_names, + "edited_on" : edited_on, "edited_by" : user_obj.first_name, + "status" : status}) + + #Check for Deleted Blocks + if components_list: + deleted_blocks = list(set(course_logs["components"].keys()) - set(components_list)) + + for d_block_id in deleted_blocks: + previous_block_info = course_logs["components"][d_block_id]["edited_info"][-1] + + if previous_block_info["status"] != "Deleted" and d_block_id not in course_logs["latest_published_componenets"]: + user_obj = User.objects.get(id = version["edited_by"]) + course_logs["components"][d_block_id]["edited_info"].append({ "definition" : previous_block_info["definition"], + "fields" : previous_block_info["fields"], + "parents_list" : previous_block_info["parents_list"], + "parents_names" : previous_block_info["parents_names"], + "edited_on" : conver_utc_ist(version["edited_on"]), + "edited_by" : user_obj.first_name, "status" : "Deleted"}) + return course_logs + +def find_block_parents(version, block_id): + + for block in version["blocks"]: + if block["block_type"] == "vertical" and block_id in [i[-1] for i in block["fields"]["children"]]: + unit_name = block["fields"]["display_name"] + unit_id = block["block_id"] + break + + for block in version["blocks"]: + if block["block_type"] == "sequential" and unit_id in [i[-1] for i in block["fields"]["children"]]: + subsection_name = block["fields"]["display_name"] + subsection_id = block["block_id"] + break + + for block in version["blocks"]: + if block["block_type"] == "chapter" and subsection_id in [i[-1] for i in block["fields"]["children"]]: + section_name = block["fields"]["display_name"] + section_id = block["block_id"] + break + + return [section_id, subsection_id, unit_id], [section_name, subsection_name, unit_name] + + +def conver_utc_ist(utc_datetime): + utc_timezone = pytz.timezone('UTC') + ist_timezone = pytz.timezone('Asia/Kolkata') + edited_info_ist = utc_datetime.astimezone(ist_timezone) + formated_ist = edited_info_ist.strftime("%b %d,%Y %H:%M:%S") + return formated_ist + diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 7ee5d038c364..5ea2cfe7eeb8 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -70,6 +70,8 @@ import requests from lms.djangoapps.instructor_analytics.basic import enrolled_students_features +from lms.djangoapps.instructor.views.course_log import get_course_unit_log + log = logging.getLogger(__name__) @@ -246,7 +248,7 @@ def instructor_dashboard_2(request, course_id): # lint-amnesty, pylint: disable ) certificate_invalidations = CertificateInvalidation.get_certificate_invalidations(course_key) - + sections.append(_section_course_log(course, access)) context = { 'course': course, 'studio_url': get_studio_url(course, 'course'), @@ -806,6 +808,15 @@ def is_ecommerce_course(course_key): sku_count = len([mode.sku for mode in CourseMode.modes_for_course(course_key) if mode.sku]) return sku_count > 0 +def _section_course_log(course, access): + section_data = { + 'section_key': 'course_log', + 'section_display_name': _('Course Log'), + 'access': access, + 'course_id': str(course.id), + 'course_logs' : get_course_unit_log(str(course.id)) + } + return section_data # For enrolled students def _section_enrolled_students(course, access): diff --git a/lms/templates/instructor/instructor_dashboard_2/course_log.html b/lms/templates/instructor/instructor_dashboard_2/course_log.html new file mode 100644 index 000000000000..dcaae44232e2 --- /dev/null +++ b/lms/templates/instructor/instructor_dashboard_2/course_log.html @@ -0,0 +1,89 @@ +<%page args="section_data" expression_filter="h"/> +<%! + from pytz import timezone +%> + + + + %if section_data["course_logs"]["components"]: + +
Unit | +Type | +Edited On | +Edited By | +Status | + + + % for block_id, block_data in section_data["course_logs"]["components"].items(): + % for edited_info in block_data["edited_info"]: +
---|---|---|---|---|
${edited_info["parents_names"][0]} → ${edited_info["parents_names"][1]} → ${edited_info["parents_names"][2]} | +${block_data["block_type"]} | +${edited_info["edited_on"]} | +${edited_info["edited_by"]} | +${edited_info["status"]} | +
Content is not added..
+ %endif: +