From e41172d55df9f1a0cb142b6a59625eef59dfa519 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sun, 20 Jan 2013 11:50:51 -0500 Subject: [PATCH 01/30] Add start of test framework for capa --- .../xmodule/xmodule/tests/test_capa_module.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 common/lib/xmodule/xmodule/tests/test_capa_module.py diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py new file mode 100644 index 0000000000..148fd893ff --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -0,0 +1,60 @@ +import json +from mock import Mock +import unittest + +from xmodule.capa_module import CapaModule +from xmodule.modulestore import Location +from lxml import etree + +from . import test_system + +class CapaFactory(object): + """ + A helper class to create problem modules with various parameters for testing. + """ + + sample_problem_xml = """ + + +

What is pi, to two decimal placs?

+
+ + + +
+""" + + num = 0 + @staticmethod + def next_num(): + CapaFactory.num += 1 + return CapaFactory.num + + @staticmethod + def create(): + definition = {'data': CapaFactory.sample_problem_xml,} + location = Location(["i4x", "edX", "capa_test", "problem", + "SampleProblem{0}".format(CapaFactory.next_num())]) + metadata = {} + descriptor = Mock(weight="1") + instance_state = None + + module = CapaModule(test_system, location, + definition, descriptor, + instance_state, None, metadata=metadata) + + return module + + + +class CapaModuleTest(unittest.TestCase): + + def test_import(self): + module = CapaFactory.create() + self.assertEqual(module.get_score()['score'], 0) + + other_module = CapaFactory.create() + self.assertEqual(module.get_score()['score'], 0) + self.assertNotEqual(module.url_name, other_module.url_name, + "Factory should be creating unique names for each problem") + From 025b074b87b5fc60c712292d541449d0d470152b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sun, 20 Jan 2013 12:17:22 -0500 Subject: [PATCH 02/30] Add simple test for showanswer, fix test_system --- common/lib/xmodule/xmodule/tests/__init__.py | 2 +- .../xmodule/xmodule/tests/test_capa_module.py | 60 ++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index a07f1ddfaf..1f323834a9 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -26,7 +26,7 @@ test_system = ModuleSystem( # "render" to just the context... render_template=lambda template, context: str(context), replace_urls=Mock(), - user=Mock(), + user=Mock(is_staff=False), filestore=Mock(), debug=True, xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10}, diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 148fd893ff..7537cb537c 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -1,7 +1,9 @@ import json from mock import Mock +from pprint import pprint import unittest + from xmodule.capa_module import CapaModule from xmodule.modulestore import Location from lxml import etree @@ -31,13 +33,59 @@ class CapaFactory(object): return CapaFactory.num @staticmethod - def create(): + def create(graceperiod=None, + due=None, + max_attempts=None, + showanswer=None, + rerandomize=None, + force_save_button=None, + attempts=None, + problem_state=None, + ): + """ + All parameters are optional, and are added to the created problem if specified. + + Arguments: + graceperiod: + due: + max_attempts: + showanswer: + force_save_button: + rerandomize: all strings, as specified in the policy for the problem + + problem_state: a dict to to be serialized into the instance_state of the + module. + + attempts: also added to instance state. Should be a number. + """ definition = {'data': CapaFactory.sample_problem_xml,} location = Location(["i4x", "edX", "capa_test", "problem", "SampleProblem{0}".format(CapaFactory.next_num())]) metadata = {} + if graceperiod is not None: + metadata['graceperiod'] = graceperiod + if due is not None: + metadata['due'] = due + if max_attempts is not None: + metadata['attempts'] = max_attempts + if showanswer is not None: + metadata['showanswer'] = showanswer + if force_save_button is not None: + metadata['force_save_button'] = force_save_button + if rerandomize is not None: + metadata['rerandomize'] = rerandomize + + descriptor = Mock(weight="1") - instance_state = None + instance_state_dict = {} + if problem_state is not None: + instance_state_dict = problem_state + if attempts is not None: + instance_state_dict['attempts'] = attempts + if len(instance_state_dict) > 0: + instance_state = json.dumps(instance_state_dict) + else: + instance_state = None module = CapaModule(test_system, location, definition, descriptor, @@ -58,3 +106,11 @@ class CapaModuleTest(unittest.TestCase): self.assertNotEqual(module.url_name, other_module.url_name, "Factory should be creating unique names for each problem") + def test_showanswer(self): + """ + Make sure the show answer logic does the right thing. + """ + # default, no due date, showanswer 'closed' + problem = CapaFactory.create() + pprint(problem.__dict__) + self.assertFalse(problem.answer_available()) From ea091a6eb83b09fbc5bafbe4f0f5011b69c8db7b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sun, 20 Jan 2013 12:49:05 -0500 Subject: [PATCH 03/30] Add tests for showanswer --- .../xmodule/xmodule/tests/test_capa_module.py | 68 +++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 7537cb537c..506c7faf9f 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -1,9 +1,9 @@ +import datetime import json from mock import Mock from pprint import pprint import unittest - from xmodule.capa_module import CapaModule from xmodule.modulestore import Location from lxml import etree @@ -56,7 +56,7 @@ class CapaFactory(object): problem_state: a dict to to be serialized into the instance_state of the module. - attempts: also added to instance state. Should be a number. + attempts: also added to instance state. Will be converted to an int. """ definition = {'data': CapaFactory.sample_problem_xml,} location = Location(["i4x", "edX", "capa_test", "problem", @@ -81,7 +81,9 @@ class CapaFactory(object): if problem_state is not None: instance_state_dict = problem_state if attempts is not None: - instance_state_dict['attempts'] = attempts + # converting to int here because I keep putting "0" and "1" in the tests + # since everything else is a string. + instance_state_dict['attempts'] = int(attempts) if len(instance_state_dict) > 0: instance_state = json.dumps(instance_state_dict) else: @@ -97,6 +99,17 @@ class CapaFactory(object): class CapaModuleTest(unittest.TestCase): + + def setUp(self): + now = datetime.datetime.now() + day_delta = datetime.timedelta(days=1) + self.yesterday_str = str(now - day_delta) + self.today_str = str(now) + self.tomorrow_str = str(now + day_delta) + + # in the capa grace period format, not in time delta format + self.two_day_delta_str = "2 days" + def test_import(self): module = CapaFactory.create() self.assertEqual(module.get_score()['score'], 0) @@ -106,11 +119,54 @@ class CapaModuleTest(unittest.TestCase): self.assertNotEqual(module.url_name, other_module.url_name, "Factory should be creating unique names for each problem") - def test_showanswer(self): + def test_showanswer_default(self): """ Make sure the show answer logic does the right thing. """ - # default, no due date, showanswer 'closed' + # default, no due date, showanswer 'closed', so problem is open, and show_answer + # not visible. problem = CapaFactory.create() - pprint(problem.__dict__) self.assertFalse(problem.answer_available()) + + + def test_showanswer_attempted(self): + problem = CapaFactory.create(showanswer='attempted') + self.assertFalse(problem.answer_available()) + problem.attempts = 1 + self.assertTrue(problem.answer_available()) + + + def test_showanswer_closed(self): + + # can see after attempts used up + used_all_attempts = CapaFactory.create(showanswer='closed', + max_attempts="1", + attempts="1") + self.assertTrue(used_all_attempts.answer_available()) + + + # can see after due date + after_due_date = CapaFactory.create(showanswer='closed', + max_attempts="1", + attempts="0", + due=self.yesterday_str) + self.assertTrue(after_due_date.answer_available()) + + # can't see because attempts left + attempts_left_open = CapaFactory.create(showanswer='closed', + max_attempts="1", + attempts="0", + due=self.tomorrow_str) + self.assertFalse(attempts_left_open.answer_available()) + + # Can't see because grace period hasn't expired + still_in_grace = CapaFactory.create(showanswer='closed', + max_attempts="1", + attempts="0", + due=self.yesterday_str, + graceperiod=self.two_day_delta_str) + self.assertFalse(still_in_grace.answer_available()) + + + + From 6088a926cc0697094c1bd6ae095581895fcc4563 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sun, 20 Jan 2013 17:35:03 -0500 Subject: [PATCH 04/30] Add showanswer="past_due" and tests --- common/lib/xmodule/xmodule/capa_module.py | 35 ++++++++------ .../xmodule/xmodule/tests/test_capa_module.py | 47 ++++++++++++++++++- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index f33da6e3a4..6d258e61ed 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -389,38 +389,43 @@ class CapaModule(XModule): }) return json.dumps(d, cls=ComplexEncoder) + def is_past_due(self): + """ + Is it now past this problem's due date, including grace period? + """ + return (self.close_date is not None and + datetime.datetime.utcnow() > self.close_date) + def closed(self): ''' Is the student still allowed to submit answers? ''' if self.attempts == self.max_attempts: return True - if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: + if self.is_past_due(): return True return False def answer_available(self): - ''' Is the user allowed to see an answer? + ''' + Is the user allowed to see an answer? ''' if self.show_answer == '': return False - - if self.show_answer == "never": + elif self.show_answer == "never": return False - - # Admins can see the answer, unless the problem explicitly prevents it - if self.system.user_is_staff: + elif self.system.user_is_staff: + # This i after the 'never' check because admins can see the answer + # unless the problem explicitly prevents it return True - - if self.show_answer == 'attempted': + elif self.show_answer == 'attempted': return self.attempts > 0 - - if self.show_answer == 'answered': + elif self.show_answer == 'answered': return self.lcp.done - - if self.show_answer == 'closed': + elif self.show_answer == 'closed': return self.closed() - - if self.show_answer == 'always': + elif self.show_answer == 'past_due': + return self.is_past_due() + elif self.show_answer == 'always': return True return False diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 506c7faf9f..e8f639e3c9 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -138,10 +138,11 @@ class CapaModuleTest(unittest.TestCase): def test_showanswer_closed(self): - # can see after attempts used up + # can see after attempts used up, even with due date in the future used_all_attempts = CapaFactory.create(showanswer='closed', max_attempts="1", - attempts="1") + attempts="1", + due=self.tomorrow_str) self.assertTrue(used_all_attempts.answer_available()) @@ -152,6 +153,7 @@ class CapaModuleTest(unittest.TestCase): due=self.yesterday_str) self.assertTrue(after_due_date.answer_available()) + # can't see because attempts left attempts_left_open = CapaFactory.create(showanswer='closed', max_attempts="1", @@ -169,4 +171,45 @@ class CapaModuleTest(unittest.TestCase): + def test_showanswer_past_due(self): + """ + With showanswer="past_due" should only show answer after the problem is closed + for everyone--e.g. after due date + grace period. + """ + + # can see after attempts used up, even with due date in the future + used_all_attempts = CapaFactory.create(showanswer='past_due', + max_attempts="1", + attempts="1", + due=self.tomorrow_str) + self.assertFalse(used_all_attempts.answer_available()) + + + # can see after due date + past_due_date = CapaFactory.create(showanswer='past_due', + max_attempts="1", + attempts="0", + due=self.yesterday_str) + self.assertTrue(past_due_date.answer_available()) + + + # can't see because attempts left + attempts_left_open = CapaFactory.create(showanswer='past_due', + max_attempts="1", + attempts="0", + due=self.tomorrow_str) + self.assertFalse(attempts_left_open.answer_available()) + + # Can't see because grace period hasn't expired, even though have no more + # attempts. + still_in_grace = CapaFactory.create(showanswer='past_due', + max_attempts="1", + attempts="1", + due=self.yesterday_str, + graceperiod=self.two_day_delta_str) + self.assertFalse(still_in_grace.answer_available()) + + + + From f6cd4c75b8ec0dc6fac8c600ae6e03a41057ed9b Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 31 Jan 2013 18:15:20 -0500 Subject: [PATCH 05/30] lms - revising xmodule HTML display styles and overwriting some to retain old syllabus table styling --- .../lib/xmodule/xmodule/css/html/display.scss | 2 +- lms/static/sass/course/_syllabus.scss | 21 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/html/display.scss b/common/lib/xmodule/xmodule/css/html/display.scss index 18794dd0b7..c031ecb141 100644 --- a/common/lib/xmodule/xmodule/css/html/display.scss +++ b/common/lib/xmodule/xmodule/css/html/display.scss @@ -117,7 +117,7 @@ th { table td, th { margin: 20px 0; padding: 10px; - border: 1px solid #ccc !important; + border: 1px solid #ccc; text-align: left; font-size: 14px; } \ No newline at end of file diff --git a/lms/static/sass/course/_syllabus.scss b/lms/static/sass/course/_syllabus.scss index 4b38d8717c..405f0412d3 100644 --- a/lms/static/sass/course/_syllabus.scss +++ b/lms/static/sass/course/_syllabus.scss @@ -13,7 +13,7 @@ div.syllabus { } table { - + table-layout: auto; text-align: left; margin: 10px 0; @@ -25,18 +25,19 @@ div.syllabus { tr.first { td { - padding-top: 15px; + padding-top: 15px !important; } - } + } td { - + border: none !important; + padding: 5px 10px !important; vertical-align: middle; - - padding: 5px 10px; + font-size: 1em !important; + line-height: auto; &.day, &.due, &.slides, &.assignment { - white-space: nowrap; + white-space: nowrap !important; } &.no_class { @@ -48,16 +49,12 @@ div.syllabus { } &.week_separator { - padding: 0px; + padding: 0px !important; hr { margin: 10px; } - } - } - } - } From 969737c58e90b288337d5bd9faf5e7fe959846d7 Mon Sep 17 00:00:00 2001 From: e0d Date: Thu, 31 Jan 2013 21:43:35 -0500 Subject: [PATCH 06/30] service variant code for loading different config --- cms/envs/aws.py | 18 ++++++++++++++++-- lms/envs/aws.py | 19 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/cms/envs/aws.py b/cms/envs/aws.py index b44baacb0b..c13e4db919 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -5,6 +5,20 @@ import json from .common import * from logsettings import get_logger_config +import os + +# specified as an environment variable. Typically this is set +# in the service's upstart script and corresponds exactly to the service name. +# Service variants apply config differences via env and auth JSON files, +# the names of which correspond to the variant. +SERVICE_VARIANT = os.environ.get('SERVICE_VARIANT', None) + +# when not variant is specified we attempt to load an unvaried +# config set. +CONFIG_PREFIX = "" + +if SERVICE_VARIANT: + CONFIG_PREFIX = SERVICE_VARIANT + "." ############################### ALWAYS THE SAME ################################ DEBUG = False @@ -16,7 +30,7 @@ DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' ########################### NON-SECURE ENV CONFIG ############################## # Things like server locations, ports, etc. -with open(ENV_ROOT / "cms.env.json") as env_file: +with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file: ENV_TOKENS = json.load(env_file) LMS_BASE = ENV_TOKENS.get('LMS_BASE') @@ -43,7 +57,7 @@ with open(ENV_ROOT / "repos.json") as repos_file: ############################## SECURE AUTH ITEMS ############################### # Secret things: passwords, access keys, etc. -with open(ENV_ROOT / "cms.auth.json") as auth_file: +with open(ENV_ROOT / CONFIG_PREFIX + "auth.json") as auth_file: AUTH_TOKENS = json.load(auth_file) AWS_ACCESS_KEY_ID = AUTH_TOKENS["AWS_ACCESS_KEY_ID"] diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 47bffac91e..2895038d40 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -10,6 +10,21 @@ import json from .common import * from logsettings import get_logger_config +import os + +# specified as an environment variable. Typically this is set +# in the service's upstart script and corresponds exactly to the service name. +# Service variants apply config differences via env and auth JSON files, +# the names of which correspond to the variant. +SERVICE_VARIANT = os.environ.get('SERVICE_VARIANT', None) + +# when not variant is specified we attempt to load an unvaried +# config set. +CONFIG_PREFIX = "" + +if SERVICE_VARIANT: + CONFIG_PREFIX = SERVICE_VARIANT + "." + ############################### ALWAYS THE SAME ################################ DEBUG = False @@ -32,7 +47,7 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') ########################### NON-SECURE ENV CONFIG ############################## # Things like server locations, ports, etc. -with open(ENV_ROOT / "env.json") as env_file: +with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file: ENV_TOKENS = json.load(env_file) SITE_NAME = ENV_TOKENS['SITE_NAME'] @@ -66,7 +81,7 @@ CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull') ############################## SECURE AUTH ITEMS ############################### # Secret things: passwords, access keys, etc. -with open(ENV_ROOT / "auth.json") as auth_file: +with open(ENV_ROOT / CONFIG_PREFIX + "auth.json") as auth_file: AUTH_TOKENS = json.load(auth_file) SECRET_KEY = AUTH_TOKENS['SECRET_KEY'] From 61de4c028a7fc8c1ca2e6c04d02b5277d31ab9d3 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 1 Feb 2013 09:41:51 -0500 Subject: [PATCH 07/30] temporary style sync up for course info lists - more work needs to be done once xmodule renders this HTML (in Studio backlog) --- cms/static/sass/_course-info.scss | 34 +++++++++++++++++++++++++++++++ lms/static/sass/course/_info.scss | 5 +++++ 2 files changed, 39 insertions(+) diff --git a/cms/static/sass/_course-info.scss b/cms/static/sass/_course-info.scss index 2ec22ebfea..f36172c4df 100644 --- a/cms/static/sass/_course-info.scss +++ b/cms/static/sass/_course-info.scss @@ -88,6 +88,40 @@ background: #f6f6f6; padding: 20px; } + + ol, ul { + margin: 1em 0; + padding: 0 0 0 1em; + color: $baseFontColor; + + li { + margin-bottom: 0.708em; + } + } + + ol { + list-style: decimal outside none; + } + + ul { + list-style: disc outside none; + } + + pre { + margin: 1em 0; + color: $baseFontColor; + font-family: monospace, serif; + font-size: 1em; + white-space: pre-wrap; + word-wrap: break-word; + } + + code { + color: $baseFontColor; + font-family: monospace, serif; + background: none; + padding: 0; + } } .new-update-button { diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 20e75dbe0d..bfd90505cf 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -65,6 +65,11 @@ div.info-wrapper { list-style-type: disc; } + > ol { + list-style: decimal outside none; + padding: 0 0 0 1em; + } + li { margin-bottom: lh(.5); } From 6b6d8a94d0763a8c8d3ed8ecb1b21a1d25712ec3 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 1 Feb 2013 10:50:01 -0500 Subject: [PATCH 08/30] Don't use PyYAML's .load() for reading YAML, use .safe_load() to avoid security problems. --- common/lib/xmodule/xmodule/x_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 84b2dd4fbb..5387a9b083 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -406,7 +406,7 @@ class ResourceTemplates(object): log.warning("Skipping unknown template file %s" % template_file) continue template_content = resource_string(__name__, os.path.join(dirname, template_file)) - template = yaml.load(template_content) + template = yaml.safe_load(template_content) templates.append(Template(**template)) return templates From 4a08bc7759d7074813e939eaae52dbe34b85e67e Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 1 Feb 2013 13:10:26 -0500 Subject: [PATCH 09/30] Adding service_variant and a little pep8 cleanup --- common/lib/logsettings.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/common/lib/logsettings.py b/common/lib/logsettings.py index 664f7e9601..b177114745 100644 --- a/common/lib/logsettings.py +++ b/common/lib/logsettings.py @@ -5,6 +5,7 @@ from logging.handlers import SysLogHandler LOG_LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] + def get_logger_config(log_dir, logging_env="no_env", tracking_filename="tracking.log", @@ -13,7 +14,8 @@ def get_logger_config(log_dir, syslog_addr=None, debug=False, local_loglevel='INFO', - console_loglevel=None): + console_loglevel=None, + service_variant=None): """ @@ -39,13 +41,15 @@ def get_logger_config(log_dir, console_loglevel = 'DEBUG' if debug else 'INFO' hostname = platform.node().split(".")[0] - syslog_format = ("[%(name)s][env:{logging_env}] %(levelname)s " + syslog_format = ("[service_variant={service_variant}]" + "[%(name)s][env:{logging_env}] %(levelname)s " "[{hostname} %(process)d] [%(filename)s:%(lineno)d] " - "- %(message)s").format( - logging_env=logging_env, hostname=hostname) + "- %(message)s").format(service_variant=service_variant, + logging_env=logging_env, + hostname=hostname) handlers = ['console', 'local'] if debug else ['console', - 'syslogger-remote', 'local'] + 'syslogger-remote', 'local'] logger_config = { 'version': 1, From 8b01298969462b4b7a3636117c5fb7b15f03908d Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 1 Feb 2013 13:11:14 -0500 Subject: [PATCH 10/30] Removing duplicate loggers --- common/lib/logsettings.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/common/lib/logsettings.py b/common/lib/logsettings.py index b177114745..43c7d5c9c9 100644 --- a/common/lib/logsettings.py +++ b/common/lib/logsettings.py @@ -82,11 +82,6 @@ def get_logger_config(log_dir, } }, 'loggers': { - 'django': { - 'handlers': handlers, - 'propagate': True, - 'level': 'INFO' - }, 'tracking': { 'handlers': ['tracking'], 'level': 'DEBUG', @@ -97,16 +92,6 @@ def get_logger_config(log_dir, 'level': 'DEBUG', 'propagate': False }, - 'mitx': { - 'handlers': handlers, - 'level': 'DEBUG', - 'propagate': False - }, - 'keyedcache': { - 'handlers': handlers, - 'level': 'DEBUG', - 'propagate': False - }, } } From f525b17b1a839540d035b2666a8bceb5e6cd76cd Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 1 Feb 2013 13:13:16 -0500 Subject: [PATCH 11/30] pep8 cleanup --- cms/envs/aws.py | 9 +++++---- lms/envs/aws.py | 22 +++++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/cms/envs/aws.py b/cms/envs/aws.py index c13e4db919..48cfa3cf9a 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -20,7 +20,7 @@ CONFIG_PREFIX = "" if SERVICE_VARIANT: CONFIG_PREFIX = SERVICE_VARIANT + "." -############################### ALWAYS THE SAME ################################ +############### ALWAYS THE SAME ################################ DEBUG = False TEMPLATE_DEBUG = False @@ -28,7 +28,7 @@ EMAIL_BACKEND = 'django_ses.SESBackend' SESSION_ENGINE = 'django.contrib.sessions.backends.cache' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' -########################### NON-SECURE ENV CONFIG ############################## +############# NON-SECURE ENV CONFIG ############################## # Things like server locations, ports, etc. with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file: ENV_TOKENS = json.load(env_file) @@ -49,13 +49,14 @@ for feature, value in ENV_TOKENS.get('MITX_FEATURES', {}).items(): LOGGING = get_logger_config(LOG_DIR, logging_env=ENV_TOKENS['LOGGING_ENV'], syslog_addr=(ENV_TOKENS['SYSLOG_SERVER'], 514), - debug=False) + debug=False, + service_variant=SERVICE_VARIANT) with open(ENV_ROOT / "repos.json") as repos_file: REPOS = json.load(repos_file) -############################## SECURE AUTH ITEMS ############################### +################ SECURE AUTH ITEMS ############################### # Secret things: passwords, access keys, etc. with open(ENV_ROOT / CONFIG_PREFIX + "auth.json") as auth_file: AUTH_TOKENS = json.load(auth_file) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 2895038d40..0779f1f684 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -26,7 +26,7 @@ if SERVICE_VARIANT: CONFIG_PREFIX = SERVICE_VARIANT + "." -############################### ALWAYS THE SAME ################################ +################### ALWAYS THE SAME ################################ DEBUG = False TEMPLATE_DEBUG = False @@ -40,11 +40,12 @@ MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True # IMPORTANT: With this enabled, the server must always be behind a proxy that # strips the header HTTP_X_FORWARDED_PROTO from client requests. Otherwise, # a user can fool our server into thinking it was an https connection. -# See https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header +# See +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header # for other warnings. SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -########################### NON-SECURE ENV CONFIG ############################## +################# NON-SECURE ENV CONFIG ############################## # Things like server locations, ports, etc. with open(ENV_ROOT / CONFIG_PREFIX + "env.json") as env_file: @@ -70,16 +71,17 @@ LOGGING = get_logger_config(LOG_DIR, logging_env=ENV_TOKENS['LOGGING_ENV'], syslog_addr=(ENV_TOKENS['SYSLOG_SERVER'], 514), local_loglevel=local_loglevel, - debug=False) + debug=False, + service_variant=SERVICE_VARIANT) COURSE_LISTINGS = ENV_TOKENS.get('COURSE_LISTINGS', {}) SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {}) VIRTUAL_UNIVERSITIES = ENV_TOKENS.get('VIRTUAL_UNIVERSITIES', []) -COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL",'') -COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY",'') +COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL", '') +COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY", '') CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull') -############################## SECURE AUTH ITEMS ############################### +############################## SECURE AUTH ITEMS ############### # Secret things: passwords, access keys, etc. with open(ENV_ROOT / CONFIG_PREFIX + "auth.json") as auth_file: AUTH_TOKENS = json.load(auth_file) @@ -99,8 +101,10 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] MODULESTORE = AUTH_TOKENS.get('MODULESTORE', MODULESTORE) CONTENTSTORE = AUTH_TOKENS.get('CONTENTSTORE', CONTENTSTORE) -STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', STAFF_GRADING_INTERFACE) -PEER_GRADING_INTERFACE = AUTH_TOKENS.get('PEER_GRADING_INTERFACE', PEER_GRADING_INTERFACE) +STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', + STAFF_GRADING_INTERFACE) +PEER_GRADING_INTERFACE = AUTH_TOKENS.get('PEER_GRADING_INTERFACE', + PEER_GRADING_INTERFACE) PEARSON_TEST_USER = "pearsontest" PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") From 1c09d9cacb10a3018862f7e4afd839e8c888b96a Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 1 Feb 2013 13:34:29 -0500 Subject: [PATCH 12/30] For prod environments only log info and up --- common/lib/logsettings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/logsettings.py b/common/lib/logsettings.py index 43c7d5c9c9..6850941db7 100644 --- a/common/lib/logsettings.py +++ b/common/lib/logsettings.py @@ -117,6 +117,9 @@ def get_logger_config(log_dir, }, }) else: + # for production environments we will only + # log INFO and up + logger_config['loggers']['']['level'] = 'INFO' logger_config['handlers'].update({ 'local': { 'level': local_loglevel, From 1595bd9b0eb82df1bc68b7813f642c809ab67844 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Fri, 1 Feb 2013 17:20:58 -0500 Subject: [PATCH 13/30] Move dnd code from base.js to a js file only loaded by overview.html --- cms/djangoapps/contentstore/views.py | 1 - cms/static/js/base.js | 188 -------------------------- cms/static/js/views/overview.js | 191 +++++++++++++++++++++++++++ cms/templates/overview.html | 1 + 4 files changed, 192 insertions(+), 189 deletions(-) create mode 100644 cms/static/js/views/overview.js diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 14f96e312a..f70164138d 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -261,7 +261,6 @@ def edit_unit(request, location): break lms_link = get_lms_link_for_item(item.location) - preview_lms_link = get_lms_link_for_item(item.location, preview=True) component_templates = defaultdict(list) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 41c1ee3cdb..7e55d2b8d8 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -80,64 +80,6 @@ $(document).ready(function() { $('.import .file-input').click(); }); - // making the unit list draggable. Note: sortable didn't work b/c it considered - // drop points which the user hovered over as destinations and proactively changed - // the dom; so, if the user subsequently dropped at an illegal spot, the reversion - // point was the last dom change. - $('.unit').draggable({ - axis: 'y', - handle: '.drag-handle', - zIndex: 999, - start: initiateHesitate, - drag: checkHoverState, - stop: removeHesitate, - revert: "invalid" - }); - - // Subsection reordering - $('.id-holder').draggable({ - axis: 'y', - handle: '.section-item .drag-handle', - zIndex: 999, - start: initiateHesitate, - drag: checkHoverState, - stop: removeHesitate, - revert: "invalid" - }); - - // Section reordering - $('.courseware-section').draggable({ - axis: 'y', - handle: 'header .drag-handle', - stack: '.courseware-section', - revert: "invalid" - }); - - - $('.sortable-unit-list').droppable({ - accept : '.unit', - greedy: true, - tolerance: "pointer", - hoverClass: "dropover", - drop: onUnitReordered - }); - $('.subsection-list > ol').droppable({ - // why don't we have a more useful class for subsections than id-holder? - accept : '.id-holder', // '.unit, .id-holder', - tolerance: "pointer", - hoverClass: "dropover", - drop: onSubsectionReordered, - greedy: true - }); - - // Section reordering - $('.courseware-overview').droppable({ - accept : '.courseware-section', - tolerance: "pointer", - drop: onSectionReordered, - greedy: true - }); - $('.new-course-button').bind('click', addNewCourse); // section name editing @@ -279,136 +221,6 @@ function removePolicyMetadata(e) { saveSubsection() } -CMS.HesitateEvent.toggleXpandHesitation = null; -function initiateHesitate(event, ui) { - CMS.HesitateEvent.toggleXpandHesitation = new CMS.HesitateEvent(expandSection, 'dragLeave', true); - $('.collapsed').on('dragEnter', CMS.HesitateEvent.toggleXpandHesitation, CMS.HesitateEvent.toggleXpandHesitation.trigger); - $('.collapsed').each(function() { - this.proportions = {width : this.offsetWidth, height : this.offsetHeight }; - // reset b/c these were holding values from aborts - this.isover = false; - }); -} -function checkHoverState(event, ui) { - // copied from jquery.ui.droppable.js $.ui.ddmanager.drag & other ui.intersect - var draggable = $(this).data("ui-draggable"), - x1 = (draggable.positionAbs || draggable.position.absolute).left + (draggable.helperProportions.width / 2), - y1 = (draggable.positionAbs || draggable.position.absolute).top + (draggable.helperProportions.height / 2); - $('.collapsed').each(function() { - // don't expand the thing being carried - if (ui.helper.is(this)) { - return; - } - - $.extend(this, {offset : $(this).offset()}); - - var droppable = this, - l = droppable.offset.left, - r = l + droppable.proportions.width, - t = droppable.offset.top, - b = t + droppable.proportions.height; - - if (l === r) { - // probably wrong values b/c invisible at the time of caching - droppable.proportions = { width : droppable.offsetWidth, height : droppable.offsetHeight }; - r = l + droppable.proportions.width; - b = t + droppable.proportions.height; - } - // equivalent to the intersects test - var intersects = (l < x1 && // Right Half - x1 < r && // Left Half - t < y1 && // Bottom Half - y1 < b ), // Top Half - - c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); - - if(!c) { - return; - } - - this[c] = true; - this[c === "isout" ? "isover" : "isout"] = false; - $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave"); - }); -} -function removeHesitate(event, ui) { - $('.collapsed').off('dragEnter', CMS.HesitateEvent.toggleXpandHesitation.trigger); - CMS.HesitateEvent.toggleXpandHesitation = null; -} - -function expandSection(event) { - $(event.delegateTarget).removeClass('collapsed', 400); - // don't descend to icon's on children (which aren't under first child) only to this element's icon - $(event.delegateTarget).children().first().find('.expand-collapse-icon').removeClass('expand', 400).addClass('collapse'); -} - -function onUnitReordered(event, ui) { - // a unit's been dropped on this subsection, - // figure out where it came from and where it slots in. - _handleReorder(event, ui, 'subsection-id', 'li:.leaf'); -} - -function onSubsectionReordered(event, ui) { - // a subsection has been dropped on this section, - // figure out where it came from and where it slots in. - _handleReorder(event, ui, 'section-id', 'li:.branch'); -} - -function onSectionReordered(event, ui) { - // a section moved w/in the overall (cannot change course via this, so no parentage change possible, just order) - _handleReorder(event, ui, 'course-id', '.courseware-section'); -} - -function _handleReorder(event, ui, parentIdField, childrenSelector) { - // figure out where it came from and where it slots in. - var subsection_id = $(event.target).data(parentIdField); - var _els = $(event.target).children(childrenSelector); - var children = _els.map(function(idx, el) { return $(el).data('id'); }).get(); - // if new to this parent, figure out which parent to remove it from and do so - if (!_.contains(children, ui.draggable.data('id'))) { - var old_parent = ui.draggable.parent(); - var old_children = old_parent.children(childrenSelector).map(function(idx, el) { return $(el).data('id'); }).get(); - old_children = _.without(old_children, ui.draggable.data('id')); - $.ajax({ - url: "/save_item", - type: "POST", - dataType: "json", - contentType: "application/json", - data:JSON.stringify({ 'id' : old_parent.data(parentIdField), 'children' : old_children}) - }); - } - else { - // staying in same parent - // remove so that the replacement in the right place doesn't double it - children = _.without(children, ui.draggable.data('id')); - } - // add to this parent (figure out where) - for (var i = 0; i < _els.length; i++) { - if (!ui.draggable.is(_els[i]) && ui.offset.top < $(_els[i]).offset().top) { - // insert at i in children and _els - ui.draggable.insertBefore($(_els[i])); - // TODO figure out correct way to have it remove the style: top:n; setting (and similar line below) - ui.draggable.attr("style", "position:relative;"); - children.splice(i, 0, ui.draggable.data('id')); - break; - } - } - // see if it goes at end (the above loop didn't insert it) - if (!_.contains(children, ui.draggable.data('id'))) { - $(event.target).append(ui.draggable); - ui.draggable.attr("style", "position:relative;"); // STYLE hack too - children.push(ui.draggable.data('id')); - } - $.ajax({ - url: "/save_item", - type: "POST", - dataType: "json", - contentType: "application/json", - data:JSON.stringify({ 'id' : subsection_id, 'children' : children}) - }); - -} - function getEdxTimeFromDateTimeVals(date_val, time_val, format) { var edxTimeStr = null; diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js new file mode 100644 index 0000000000..c007ef3efc --- /dev/null +++ b/cms/static/js/views/overview.js @@ -0,0 +1,191 @@ +$(document).ready(function() { + // making the unit list draggable. Note: sortable didn't work b/c it considered + // drop points which the user hovered over as destinations and proactively changed + // the dom; so, if the user subsequently dropped at an illegal spot, the reversion + // point was the last dom change. + $('.unit').draggable({ + axis: 'y', + handle: '.drag-handle', + zIndex: 999, + start: initiateHesitate, + drag: checkHoverState, + stop: removeHesitate, + revert: "invalid" + }); + + // Subsection reordering + $('.id-holder').draggable({ + axis: 'y', + handle: '.section-item .drag-handle', + zIndex: 999, + start: initiateHesitate, + drag: checkHoverState, + stop: removeHesitate, + revert: "invalid" + }); + + // Section reordering + $('.courseware-section').draggable({ + axis: 'y', + handle: 'header .drag-handle', + stack: '.courseware-section', + revert: "invalid" + }); + + + $('.sortable-unit-list').droppable({ + accept : '.unit', + greedy: true, + tolerance: "pointer", + hoverClass: "dropover", + drop: onUnitReordered + }); + $('.subsection-list > ol').droppable({ + // why don't we have a more useful class for subsections than id-holder? + accept : '.id-holder', // '.unit, .id-holder', + tolerance: "pointer", + hoverClass: "dropover", + drop: onSubsectionReordered, + greedy: true + }); + + // Section reordering + $('.courseware-overview').droppable({ + accept : '.courseware-section', + tolerance: "pointer", + drop: onSectionReordered, + greedy: true + }); + +}); + + +CMS.HesitateEvent.toggleXpandHesitation = null; +function initiateHesitate(event, ui) { + CMS.HesitateEvent.toggleXpandHesitation = new CMS.HesitateEvent(expandSection, 'dragLeave', true); + $('.collapsed').on('dragEnter', CMS.HesitateEvent.toggleXpandHesitation, CMS.HesitateEvent.toggleXpandHesitation.trigger); + $('.collapsed').each(function() { + this.proportions = {width : this.offsetWidth, height : this.offsetHeight }; + // reset b/c these were holding values from aborts + this.isover = false; + }); +} +function checkHoverState(event, ui) { + // copied from jquery.ui.droppable.js $.ui.ddmanager.drag & other ui.intersect + var draggable = $(this).data("ui-draggable"), + x1 = (draggable.positionAbs || draggable.position.absolute).left + (draggable.helperProportions.width / 2), + y1 = (draggable.positionAbs || draggable.position.absolute).top + (draggable.helperProportions.height / 2); + $('.collapsed').each(function() { + // don't expand the thing being carried + if (ui.helper.is(this)) { + return; + } + + $.extend(this, {offset : $(this).offset()}); + + var droppable = this, + l = droppable.offset.left, + r = l + droppable.proportions.width, + t = droppable.offset.top, + b = t + droppable.proportions.height; + + if (l === r) { + // probably wrong values b/c invisible at the time of caching + droppable.proportions = { width : droppable.offsetWidth, height : droppable.offsetHeight }; + r = l + droppable.proportions.width; + b = t + droppable.proportions.height; + } + // equivalent to the intersects test + var intersects = (l < x1 && // Right Half + x1 < r && // Left Half + t < y1 && // Bottom Half + y1 < b ), // Top Half + + c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); + + if(!c) { + return; + } + + this[c] = true; + this[c === "isout" ? "isover" : "isout"] = false; + $(this).trigger(c === "isover" ? "dragEnter" : "dragLeave"); + }); +} +function removeHesitate(event, ui) { + $('.collapsed').off('dragEnter', CMS.HesitateEvent.toggleXpandHesitation.trigger); + CMS.HesitateEvent.toggleXpandHesitation = null; +} + +function expandSection(event) { + $(event.delegateTarget).removeClass('collapsed', 400); + // don't descend to icon's on children (which aren't under first child) only to this element's icon + $(event.delegateTarget).children().first().find('.expand-collapse-icon').removeClass('expand', 400).addClass('collapse'); +} + +function onUnitReordered(event, ui) { + // a unit's been dropped on this subsection, + // figure out where it came from and where it slots in. + _handleReorder(event, ui, 'subsection-id', 'li:.leaf'); +} + +function onSubsectionReordered(event, ui) { + // a subsection has been dropped on this section, + // figure out where it came from and where it slots in. + _handleReorder(event, ui, 'section-id', 'li:.branch'); +} + +function onSectionReordered(event, ui) { + // a section moved w/in the overall (cannot change course via this, so no parentage change possible, just order) + _handleReorder(event, ui, 'course-id', '.courseware-section'); +} + +function _handleReorder(event, ui, parentIdField, childrenSelector) { + // figure out where it came from and where it slots in. + var subsection_id = $(event.target).data(parentIdField); + var _els = $(event.target).children(childrenSelector); + var children = _els.map(function(idx, el) { return $(el).data('id'); }).get(); + // if new to this parent, figure out which parent to remove it from and do so + if (!_.contains(children, ui.draggable.data('id'))) { + var old_parent = ui.draggable.parent(); + var old_children = old_parent.children(childrenSelector).map(function(idx, el) { return $(el).data('id'); }).get(); + old_children = _.without(old_children, ui.draggable.data('id')); + $.ajax({ + url: "/save_item", + type: "POST", + dataType: "json", + contentType: "application/json", + data:JSON.stringify({ 'id' : old_parent.data(parentIdField), 'children' : old_children}) + }); + } + else { + // staying in same parent + // remove so that the replacement in the right place doesn't double it + children = _.without(children, ui.draggable.data('id')); + } + // add to this parent (figure out where) + for (var i = 0; i < _els.length; i++) { + if (!ui.draggable.is(_els[i]) && ui.offset.top < $(_els[i]).offset().top) { + // insert at i in children and _els + ui.draggable.insertBefore($(_els[i])); + // TODO figure out correct way to have it remove the style: top:n; setting (and similar line below) + ui.draggable.attr("style", "position:relative;"); + children.splice(i, 0, ui.draggable.data('id')); + break; + } + } + // see if it goes at end (the above loop didn't insert it) + if (!_.contains(children, ui.draggable.data('id'))) { + $(event.target).append(ui.draggable); + ui.draggable.attr("style", "position:relative;"); // STYLE hack too + children.push(ui.draggable.data('id')); + } + $.ajax({ + url: "/save_item", + type: "POST", + dataType: "json", + contentType: "application/json", + data:JSON.stringify({ 'id' : subsection_id, 'children' : children}) + }); + +} diff --git a/cms/templates/overview.html b/cms/templates/overview.html index a20531200e..20ddcead01 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -18,6 +18,7 @@ +