diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index b5c79f59ae7f209b38790de921569da6ef05fa9e..ce65e281ca7bf02e549a9f37b7fffcf64956778b 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -15,8 +15,10 @@ from django.utils.translation import ugettext as _ from edxmako.shortcuts import render_to_string, render_to_response from opaque_keys.edx.keys import UsageKey from xblock.core import XBlock +import dogstats_wrapper as dog_stats_api from xmodule.modulestore.django import modulestore from xmodule.tabs import StaticTab +from xmodule.x_module import DEPRECATION_VSCOMPAT_EVENT from contentstore.utils import reverse_course_url, reverse_library_url, reverse_usage_url from models.settings.course_grading import CourseGradingModel @@ -251,6 +253,15 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if category == 'static_tab': + + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:create_xblock_static_tab", + u"course:{}".format(unicode(dest_usage_key.course_key)), + ) + ) + display_name = display_name or _("Empty") # Prevent name being None course = store.get_course(dest_usage_key.course_key) course.tabs.append( diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 0568ca747f08593f5ac097486cabd9429036fe70..1de8db34137a5d15cdd22db1c49a4d83b455a417 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -13,6 +13,7 @@ from functools import partial from static_replace import replace_static_urls from xmodule_modifiers import wrap_xblock, request_token +import dogstats_wrapper as dog_stats_api from django.conf import settings from django.core.exceptions import PermissionDenied from django.contrib.auth.decorators import login_required @@ -30,7 +31,7 @@ from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES -from xmodule.x_module import PREVIEW_VIEWS, STUDIO_VIEW, STUDENT_VIEW +from xmodule.x_module import PREVIEW_VIEWS, STUDIO_VIEW, STUDENT_VIEW, DEPRECATION_VSCOMPAT_EVENT from xmodule.course_module import DEFAULT_START_DATE from django.contrib.auth.models import User @@ -637,6 +638,15 @@ def _delete_item(usage_key, user): # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if usage_key.category == 'static_tab': + + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:_delete_item_static_tab", + u"course:{}".format(unicode(usage_key.course_key)), + ) + ) + course = store.get_course(usage_key.course_key) existing_tabs = course.tabs or [] course.tabs = [tab for tab in existing_tabs if tab.get('url_slug') != usage_key.name] diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index b748b359f1a9941cc899a2d087c922ba8e0f729a..7364730815ec2904ff5972d1eb59597214b892f6 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -6,10 +6,11 @@ from lxml import etree from pkg_resources import resource_string +import dogstats_wrapper as dog_stats_api from .capa_base import CapaMixin, CapaFields, ComplexEncoder from capa import responsetypes from .progress import Progress -from xmodule.x_module import XModule, module_attr +from xmodule.x_module import XModule, module_attr, DEPRECATION_VSCOMPAT_EVENT from xmodule.raw_module import RawDescriptor from xmodule.exceptions import NotFoundError, ProcessingError @@ -156,6 +157,10 @@ class CapaDescriptor(CapaFields, RawDescriptor): # edited in the cms @classmethod def backcompat_paths(cls, path): + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:capa_descriptor_backcompat_paths"] + ) return [ 'problems/' + path[8:], path[8:], diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 7e19b15ab251559c20d698856aa7916438ef4209..b57a6bd970d34e37c68ed41f8c7ee7ce759ce24a 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -1,24 +1,25 @@ -import copy -from fs.errors import ResourceNotFoundError -import logging import os import sys +import re +import copy +import logging +import textwrap from lxml import etree from path import path - +from fs.errors import ResourceNotFoundError from pkg_resources import resource_string -from xblock.fields import Scope, String, Boolean, List + +import dogstats_wrapper as dog_stats_api +from xmodule.annotator_mixin import html_to_text +from xmodule.contentstore.content import StaticContent from xmodule.editing_module import EditingDescriptor +from xmodule.edxnotes_utils import edxnotes from xmodule.html_checker import check_html from xmodule.stringify import stringify_children -from xmodule.x_module import XModule +from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT from xmodule.xml_module import XmlDescriptor, name_to_pathname -import textwrap -from xmodule.contentstore.content import StaticContent from xblock.core import XBlock -from xmodule.edxnotes_utils import edxnotes -from xmodule.annotator_mixin import html_to_text -import re +from xblock.fields import Scope, String, Boolean, List log = logging.getLogger("edx.courseware") @@ -103,6 +104,12 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # are being edited in the cms @classmethod def backcompat_paths(cls, path): + + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:html_descriptor_backcompat_paths"] + ) + if path.endswith('.html.xml'): path = path[:-9] + '.html' # backcompat--look for html instead of xml if path.endswith('.html.html'): @@ -192,6 +199,12 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # again in the correct format. This should go away once the CMS is # online and has imported all current (fall 2012) courses from xml if not system.resources_fs.exists(filepath): + + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:html_descriptor_load_definition"] + ) + candidates = cls.backcompat_paths(filepath) # log.debug("candidates = {0}".format(candidates)) for candidate in candidates: diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 4e7b1063c3688fdc6c459249a5f37a98c4358762..aa4769776b687c7cd81629f2449d8695d5960787 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -1,21 +1,22 @@ import pprint +import threading +from uuid import uuid4 +from decorator import contextmanager import pymongo.message -from factory import Factory, lazy_attribute_sequence, lazy_attribute +from factory import Factory, Sequence, lazy_attribute_sequence, lazy_attribute from factory.containers import CyclicDefinitionError -from uuid import uuid4 +from mock import Mock, patch +from nose.tools import assert_less_equal, assert_greater_equal +import dogstats_wrapper as dog_stats_api -from xmodule.modulestore import prefer_xmodules, ModuleStoreEnum from opaque_keys.edx.locations import Location from opaque_keys.edx.keys import UsageKey from xblock.core import XBlock from xmodule.tabs import StaticTab -from decorator import contextmanager -from mock import Mock, patch -from nose.tools import assert_less_equal, assert_greater_equal -import factory -import threading +from xmodule.modulestore import prefer_xmodules, ModuleStoreEnum from xmodule.modulestore.django import modulestore +from xmodule.x_module import DEPRECATION_VSCOMPAT_EVENT class Dummy(object): @@ -86,9 +87,9 @@ class CourseFactory(XModuleFactory): """ Factory for XModule courses. """ - org = factory.Sequence(lambda n: 'org.%d' % n) - number = factory.Sequence(lambda n: 'course_%d' % n) - display_name = factory.Sequence(lambda n: 'Run %d' % n) + org = Sequence('org.{}'.format) + number = Sequence('course_{}'.format) + display_name = Sequence('Run {}'.format) # pylint: disable=unused-argument @classmethod @@ -124,9 +125,9 @@ class LibraryFactory(XModuleFactory): """ Factory for creating a content library """ - org = factory.Sequence('org{}'.format) - library = factory.Sequence('lib{}'.format) - display_name = factory.Sequence('Test Library {}'.format) + org = Sequence('org{}'.format) + library = Sequence('lib{}'.format) + display_name = Sequence('Test Library {}'.format) # pylint: disable=unused-argument @classmethod @@ -267,6 +268,14 @@ class ItemFactory(XModuleFactory): # if we add one then we need to also add it to the policy information (i.e. metadata) # we should remove this once we can break this reference from the course to static tabs if category == 'static_tab': + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:itemfactory_create_static_tab", + u"block:{}".format(location.block_type), + ) + ) + course = store.get_course(location.course_key) course.tabs.append( StaticTab( diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index f35a3bc3950dbf4de1e8ba9dd5db27b3d0231624..9c80c95a04d04c0638eb1f627aa0ed65b24b06e0 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -18,7 +18,10 @@ from contextlib import contextmanager from xmodule.error_module import ErrorDescriptor from xmodule.errortracker import make_error_tracker, exc_info_to_str from xmodule.mako_module import MakoDescriptorSystem -from xmodule.x_module import XMLParsingSystem, policy_key, OpaqueKeyReader, AsideKeyGenerator +from xmodule.x_module import ( + XMLParsingSystem, policy_key, + OpaqueKeyReader, AsideKeyGenerator, DEPRECATION_VSCOMPAT_EVENT +) from xmodule.modulestore.xml_exporter import DEFAULT_CONTENT_FIELDS from xmodule.modulestore import ModuleStoreEnum, ModuleStoreReadBase, LIBRARY_ROOT, COURSE_ROOT from xmodule.tabs import CourseTabList @@ -27,12 +30,13 @@ from opaque_keys.edx.locator import CourseLocator, LibraryLocator from xblock.field_data import DictFieldData from xblock.runtime import DictKeyValueStore +from xblock.fields import ScopeIds +import dogstats_wrapper as dog_stats_api from .exceptions import ItemNotFoundError from .inheritance import compute_inherited_metadata, inheriting_field_data, InheritanceKeyValueStore -from xblock.fields import ScopeIds edx_xml_parser = etree.XMLParser(dtd_validation=False, load_dtd=False, remove_comments=True, remove_blank_text=True) @@ -46,8 +50,14 @@ log = logging.getLogger(__name__) # TODO (cpennington): Remove this once all fall 2012 courses have been imported # into the cms from xml def clean_out_mako_templating(xml_string): + orig_xml = xml_string xml_string = xml_string.replace('%include', 'include') xml_string = re.sub(r"(?m)^\s*%.*$", '', xml_string) + if orig_xml != xml_string: + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:xml_clean_out_mako_templating"] + ) return xml_string @@ -114,6 +124,14 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): def fallback_name(orig_name=None): """Return the fallback name for this module. This is a function instead of a variable because we want it to be lazy.""" + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:import_system_fallback_name", + u"name:{}".format(orig_name), + ) + ) + if looks_like_fallback(orig_name): # We're about to re-hash, in case something changed, so get rid of the tag_ and hash orig_name = orig_name[len(tag) + 1:-12] @@ -468,12 +486,32 @@ class XMLModuleStore(ModuleStoreReadBase): # VS[compat]: remove once courses use the policy dirs. if policy == {}: + + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:xml_load_course_policy_dir", + u"course:{}".format(course), + ) + ) + old_policy_path = self.data_dir / course_dir / 'policies' / '{0}.json'.format(url_name) policy = self.load_policy(old_policy_path, tracker) else: policy = {} # VS[compat] : 'name' is deprecated, but support it for now... if course_data.get('name'): + + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:xml_load_course_course_data_name", + u"course:{}".format(course_data.get('course')), + u"org:{}".format(course_data.get('org')), + u"name:{}".format(course_data.get('name')), + ) + ) + url_name = Location.clean(course_data.get('name')) tracker("'name' is deprecated for module xml. Please use " "display_name and url_name.") @@ -660,6 +698,14 @@ class XMLModuleStore(ModuleStoreReadBase): # Hack because we need to pull in the 'display_name' for static tabs (because we need to edit them) # from the course policy if category == "static_tab": + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:xml_load_extra_content_static_tab", + u"course_dir:{}".format(course_dir), + ) + ) + tab = CourseTabList.get_tab_by_slug(tab_list=course_descriptor.tabs, url_slug=slug) if tab: module.display_name = tab.name diff --git a/common/lib/xmodule/xmodule/template_module.py b/common/lib/xmodule/xmodule/template_module.py index 7587ae8e4354c86494aa074d2980e3549c1555cc..598b46beb0b605102bf15507fc528bd4fbefb226 100644 --- a/common/lib/xmodule/xmodule/template_module.py +++ b/common/lib/xmodule/xmodule/template_module.py @@ -1,7 +1,11 @@ -from xmodule.x_module import XModule +""" +Template module +""" +from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT from xmodule.raw_module import RawDescriptor from lxml import etree from mako.template import Template +import dogstats_wrapper as dog_stats_api class CustomTagModule(XModule): @@ -42,6 +46,11 @@ class CustomTagDescriptor(RawDescriptor): template_name = xmltree.attrib['impl'] else: # VS[compat] backwards compatibility with old nested customtag structure + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:customtag_descriptor_render_template"] + ) + child_impl = xmltree.find('impl') if child_impl is not None: template_name = child_impl.text diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 9c2d59dd2b795e1ba765b86dba75899ea353cca6..3d01be6a71ee374d09b89b149c26ce1f6972ed36 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -38,6 +38,9 @@ log = logging.getLogger(__name__) XMODULE_METRIC_NAME = 'edxapp.xmodule' +# Stats event sent to DataDog in order to determine if old XML parsing can be deprecated. +DEPRECATION_VSCOMPAT_EVENT = 'deprecation.vscompat' + # xblock view names # This is the view that will be rendered to display the XBlock in the LMS. @@ -860,6 +863,11 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock): @classmethod def _translate(cls, key): 'VS[compat]' + if key in cls.metadata_translations: + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:xmodule_descriptor_translate"] + ) return cls.metadata_translations.get(key, key) # ================================= XML PARSING ============================ diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 1f7973c188630ca3c2f243ce390fd8d4c7cd7548..6e18effd83a51c86542fe9aba555ee3862b4a71e 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -6,10 +6,12 @@ import sys from lxml import etree from xblock.fields import Dict, Scope, ScopeIds -from xmodule.x_module import XModuleDescriptor +from xblock.runtime import KvsFieldData +from xmodule.x_module import XModuleDescriptor, DEPRECATION_VSCOMPAT_EVENT from xmodule.modulestore.inheritance import own_metadata, InheritanceKeyValueStore from xmodule.modulestore import EdxJSONEncoder -from xblock.runtime import KvsFieldData + +import dogstats_wrapper as dog_stats_api log = logging.getLogger(__name__) @@ -201,6 +203,11 @@ class XmlDescriptor(XModuleDescriptor): definition_xml = copy.deepcopy(xml_object) filepath = '' else: + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:xmlparser_util_mixin_load_definition_filename"] + ) + filepath = cls._format_filepath(xml_object.tag, filename) # VS[compat] @@ -209,6 +216,11 @@ class XmlDescriptor(XModuleDescriptor): # again in the correct format. This should go away once the CMS is # online and has imported all current (fall 2012) courses from xml if not system.resources_fs.exists(filepath) and hasattr(cls, 'backcompat_paths'): + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:xmlparser_util_mixin_load_definition_backcompat"] + ) + candidates = cls.backcompat_paths(filepath) for candidate in candidates: if system.resources_fs.exists(candidate): @@ -244,6 +256,14 @@ class XmlDescriptor(XModuleDescriptor): attr = cls._translate(attr) if attr in cls.metadata_to_strip: + if attr in ('course', 'org', 'url_name', 'filename'): + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:xmlparser_util_mixin_load_metadata", + "metadata:{}".format(attr), + ) + ) # don't load these continue @@ -293,8 +313,12 @@ class XmlDescriptor(XModuleDescriptor): definition_xml = cls.load_file(filepath, system.resources_fs, def_id) system.parse_asides(definition_xml, def_id, usage_id, id_generator) else: - definition_xml = xml_object filepath = None + definition_xml = xml_object + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=["location:xmlparser_util_mixin_parse_xml"] + ) definition, children = cls.load_definition(definition_xml, system, def_id, id_generator) # note this removes metadata diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 484c53f3479041fbba4271e47e70166fc5305580..a2836dc5046f012ceb2480a04fac321f3f0e1cec 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -1,34 +1,38 @@ -"""This file contains (or should), all access control logic for the courseware. +""" +This file contains (or should), all access control logic for the courseware. Ideally, it will be the only place that needs to know about any special settings -like DISABLE_START_DATES""" +like DISABLE_START_DATES +""" import logging from datetime import datetime, timedelta import pytz from django.conf import settings from django.contrib.auth.models import AnonymousUser +from django.utils.timezone import UTC + +from opaque_keys.edx.keys import CourseKey, UsageKey +from xblock.core import XBlock from xmodule.course_module import ( CourseDescriptor, CATALOG_VISIBILITY_CATALOG_AND_ABOUT, CATALOG_VISIBILITY_ABOUT) from xmodule.error_module import ErrorDescriptor -from xmodule.x_module import XModule +from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT from xmodule.split_test_module import get_split_user_partitions - -from xblock.core import XBlock from xmodule.partitions.partitions import NoSuchUserPartitionError, NoSuchUserPartitionGroupError from external_auth.models import ExternalAuthMap from courseware.masquerade import get_masquerade_role, is_masquerading_as_student -from django.utils.timezone import UTC from student import auth from student.roles import ( GlobalStaff, CourseStaffRole, CourseInstructorRole, OrgStaffRole, OrgInstructorRole, CourseBetaTesterRole ) from student.models import CourseEnrollment, CourseEnrollmentAllowed -from opaque_keys.edx.keys import CourseKey, UsageKey from util.milestones_helpers import get_pre_requisite_courses_not_completed +import dogstats_wrapper as dog_stats_api + DEBUG_ACCESS = False log = logging.getLogger(__name__) @@ -232,6 +236,14 @@ def _has_access_course_desc(user, action, course): # properly configured enrollment_start times (if course should be # staff-only, set enrollment_start far in the future.) if settings.FEATURES.get('ACCESS_REQUIRE_STAFF_FOR_COURSE'): + dog_stats_api.increment( + DEPRECATION_VSCOMPAT_EVENT, + tags=( + "location:has_access_course_desc_see_exists", + u"course:{}".format(course), + ) + ) + # if this feature is on, only allow courses that have ispublic set to be # seen by non-staff if course.ispublic: