From 6bf0e093bc5a4520f48bebab14ad8c39ece51f6e Mon Sep 17 00:00:00 2001 From: Calen Pennington <calen.pennington@gmail.com> Date: Mon, 16 Jul 2012 15:55:10 -0400 Subject: [PATCH] Add tests for the edit page for the toy course, and make sure that exporting to github sets up the git repo properly --- cms/djangoapps/contentstore/tests/tests.py | 123 +++++++++++------- cms/djangoapps/contentstore/views.py | 1 - cms/djangoapps/github_sync/__init__.py | 40 ++++-- cms/djangoapps/github_sync/tests/__init__.py | 43 +++--- cms/envs/common.py | 4 +- cms/envs/dev.py | 11 +- cms/envs/test.py | 2 + .../xmodule/xmodule/modulestore/__init__.py | 6 +- .../lib/xmodule/xmodule/modulestore/mongo.py | 24 +++- common/test/data/toy/course.xml | 10 +- common/test/data/toy/html/Lab2A.html | 105 +++++++++++++++ .../data/{custom_tags/.git-keep => .gitkeep} | 0 test_root/data/course_settings.py | 28 ---- 13 files changed, 277 insertions(+), 120 deletions(-) create mode 100644 common/test/data/toy/html/Lab2A.html rename test_root/data/{custom_tags/.git-keep => .gitkeep} (100%) delete mode 100644 test_root/data/course_settings.py diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index ade7c4e75d7..6b346046fcd 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -1,13 +1,19 @@ import json -from django.test.client import Client from django.test import TestCase +from django.test.client import Client from mock import patch, Mock from override_settings import override_settings from django.conf import settings from django.core.urlresolvers import reverse +from path import path from student.models import Registration from django.contrib.auth.models import User +from xmodule.modulestore.django import modulestore +import xmodule.modulestore.django +from xmodule.modulestore import Location +from contentstore import import_from_xml +import copy def parse_json(response): @@ -25,36 +31,21 @@ def registration(email): return Registration.objects.get(user__email=email) -class AuthTestCase(TestCase): - """Check that various permissions-related things work""" - - def setUp(self): - self.email = 'a@b.com' - self.pw = 'xyz' - self.username = 'testuser' - self.client = Client() - - def check_page_get(self, url, expected): - resp = self.client.get(url) - self.assertEqual(resp.status_code, expected) +class ContentStoreTestCase(TestCase): + def _login(self, email, pw): + '''Login. View should always return 200. The success/fail is in the + returned json''' + resp = self.client.post(reverse('login_post'), + {'email': email, 'password': pw}) + self.assertEqual(resp.status_code, 200) return resp - def test_public_pages_load(self): - """Make sure pages that don't require login load without error.""" - pages = ( - reverse('login'), - reverse('signup'), - ) - for page in pages: - print "Checking '{0}'".format(page) - self.check_page_get(page, 200) - - def test_create_account_errors(self): - # No post data -- should fail - resp = self.client.post('/create_account', {}) - self.assertEqual(resp.status_code, 200) + def login(self, email, pw): + '''Login, check that it worked.''' + resp = self._login(email, pw) data = parse_json(resp) - self.assertEqual(data['success'], False) + self.assertTrue(data['success']) + return resp def _create_account(self, username, email, pw): '''Try to create an account. No error checking''' @@ -78,7 +69,7 @@ class AuthTestCase(TestCase): self.assertEqual(data['success'], True) # Check both that the user is created, and inactive - self.assertFalse(user(self.email).is_active) + self.assertFalse(user(email).is_active) return resp @@ -95,26 +86,43 @@ class AuthTestCase(TestCase): resp = self._activate_user(email) self.assertEqual(resp.status_code, 200) # Now make sure that the user is now actually activated - self.assertTrue(user(self.email).is_active) + self.assertTrue(user(email).is_active) - def test_create_account(self): - self.create_account(self.username, self.email, self.pw) - self.activate_user(self.email) - def _login(self, email, pw): - '''Login. View should always return 200. The success/fail is in the - returned json''' - resp = self.client.post(reverse('login_post'), - {'email': email, 'password': pw}) - self.assertEqual(resp.status_code, 200) +class AuthTestCase(ContentStoreTestCase): + """Check that various permissions-related things work""" + + def setUp(self): + self.email = 'a@b.com' + self.pw = 'xyz' + self.username = 'testuser' + self.client = Client() + + def check_page_get(self, url, expected): + resp = self.client.get(url) + self.assertEqual(resp.status_code, expected) return resp + + def test_public_pages_load(self): + """Make sure pages that don't require login load without error.""" + pages = ( + reverse('login'), + reverse('signup'), + ) + for page in pages: + print "Checking '{0}'".format(page) + self.check_page_get(page, 200) - def login(self, email, pw): - '''Login, check that it worked.''' - resp = self._login(self.email, self.pw) + def test_create_account_errors(self): + # No post data -- should fail + resp = self.client.post('/create_account', {}) + self.assertEqual(resp.status_code, 200) data = parse_json(resp) - self.assertTrue(data['success']) - return resp + self.assertEqual(data['success'], False) + + def test_create_account(self): + self.create_account(self.username, self.email, self.pw) + self.activate_user(self.email) def test_login(self): self.create_account(self.username, self.email, self.pw) @@ -170,3 +178,30 @@ class AuthTestCase(TestCase): self.assertEqual(resp.status_code, 302) # Logged in should work. + +TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) +TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') + +@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) +class EditTestCase(ContentStoreTestCase): + """Check that editing functionality works on example courses""" + + def setUp(self): + email = 'edit@test.com' + password = 'foo' + self.create_account('edittest', email, password) + self.activate_user(email) + self.login(email, password) + xmodule.modulestore.django._MODULESTORES = {} + + def check_edit_item(self, test_course_name): + import_from_xml('common/test/data/', test_course_name) + + for descriptor in modulestore().get_items(Location(None, None, None, None, None)): + print "Checking ", descriptor.location.url() + print descriptor.__class__, descriptor.location + resp = self.client.get(reverse('edit_item'), {'id': descriptor.location.url()}) + self.assertEqual(resp.status_code, 200) + + def test_edit_item_toy(self): + self.check_edit_item('toy') diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 2bec0272fa1..a79aa0e18f5 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -92,7 +92,6 @@ def edit_item(request): """ # TODO (vshnayder): change name from id to location in coffee+html as well. item_location = request.GET['id'] - print item_location, request.GET if not has_access(request.user, item_location): raise Http404 # TODO (vshnayder): better error diff --git a/cms/djangoapps/github_sync/__init__.py b/cms/djangoapps/github_sync/__init__.py index 108fd5629fa..149b92670ac 100644 --- a/cms/djangoapps/github_sync/__init__.py +++ b/cms/djangoapps/github_sync/__init__.py @@ -13,16 +13,17 @@ from .exceptions import GithubSyncError log = logging.getLogger(__name__) -def import_from_github(repo_settings): +def setup_repo(repo_settings): """ - Imports data into the modulestore based on the XML stored on github + Reset the local github repo specified by repo_settings repo_settings is a dictionary with the following keys: path: file system path to the local git repo branch: name of the branch to track on github + origin: git url for the repository to track """ - repo_path = repo_settings['path'] - data_dir, course_dir = os.path.split(repo_path) + course_dir = repo_settings['path'] + repo_path = settings.GITHUB_REPO_ROOT / course_dir if not os.path.isdir(repo_path): Repo.clone_from(repo_settings['origin'], repo_path) @@ -33,8 +34,29 @@ def import_from_github(repo_settings): # Do a hard reset to the remote branch so that we have a clean import git_repo.git.checkout(repo_settings['branch']) + + return git_repo + + +def load_repo_settings(course_dir): + """ + Returns the repo_settings for the course stored in course_dir + """ + for repo_settings in settings.REPOS.values(): + if repo_settings['path'] == course_dir: + return repo_settings + raise InvalidRepo(course_dir) + + +def import_from_github(repo_settings): + """ + Imports data into the modulestore based on the XML stored on github + """ + course_dir = repo_settings['path'] + git_repo = setup_repo(repo_settings) git_repo.head.reset('origin/%s' % repo_settings['branch'], index=True, working_tree=True) - module_store = import_from_xml(data_dir, course_dirs=[course_dir]) + + module_store = import_from_xml(settings.GITHUB_REPO_ROOT, course_dirs=[course_dir]) return git_repo.head.commit.hexsha, module_store.courses[course_dir] @@ -44,14 +66,16 @@ def export_to_github(course, commit_message, author_str=None): and push to github (if MITX_FEATURES['GITHUB_PUSH'] is True). If author_str is specified, uses it in the commit. ''' - repo_path = settings.DATA_DIR / course.metadata.get('data_dir', course.location.course) - fs = OSFS(repo_path) + course_dir = course.metadata.get('data_dir', course.location.course) + repo_settings = load_repo_settings(course_dir) + git_repo = setup_repo(repo_settings) + + fs = OSFS(git_repo.working_dir) xml = course.export_to_xml(fs) with fs.open('course.xml', 'w') as course_xml: course_xml.write(xml) - git_repo = Repo(repo_path) if git_repo.is_dirty(): git_repo.git.add(A=True) if author_str is not None: diff --git a/cms/djangoapps/github_sync/tests/__init__.py b/cms/djangoapps/github_sync/tests/__init__.py index 452904ffffc..c95d5380309 100644 --- a/cms/djangoapps/github_sync/tests/__init__.py +++ b/cms/djangoapps/github_sync/tests/__init__.py @@ -10,36 +10,37 @@ from xmodule.modulestore import Location from override_settings import override_settings from github_sync.exceptions import GithubSyncError - -@override_settings(DATA_DIR=path('test_root')) +REPO_DIR = settings.GITHUB_REPO_ROOT / 'local_repo' +WORKING_DIR = path(settings.TEST_ROOT) +REMOTE_DIR = WORKING_DIR / 'remote_repo' + + +@override_settings(REPOS={ + 'local': { + 'path': 'local_repo', + 'origin': REMOTE_DIR, + 'branch': 'master', + } +}) class GithubSyncTestCase(TestCase): def cleanup(self): - shutil.rmtree(self.repo_dir, ignore_errors=True) - shutil.rmtree(self.remote_dir, ignore_errors=True) + shutil.rmtree(REPO_DIR, ignore_errors=True) + shutil.rmtree(REMOTE_DIR, ignore_errors=True) + modulestore().collection.drop() def setUp(self): - self.working_dir = path(settings.TEST_ROOT) - self.repo_dir = self.working_dir / 'local_repo' - self.remote_dir = self.working_dir / 'remote_repo' - # make sure there's no stale data lying around self.cleanup() - shutil.copytree('common/test/data/toy', self.remote_dir) + shutil.copytree('common/test/data/toy', REMOTE_DIR) - remote = Repo.init(self.remote_dir) + remote = Repo.init(REMOTE_DIR) remote.git.add(A=True) remote.git.commit(m='Initial commit') remote.git.config("receive.denyCurrentBranch", "ignore") - modulestore().collection.drop() - - self.import_revision, self.import_course = import_from_github({ - 'path': self.repo_dir, - 'origin': self.remote_dir, - 'branch': 'master', - }) + self.import_revision, self.import_course = import_from_github(settings.REPOS['local']) def tearDown(self): self.cleanup() @@ -48,7 +49,7 @@ class GithubSyncTestCase(TestCase): """ Test that importing from github will create a repo if the repo doesn't already exist """ - self.assertEquals(1, len(Repo(self.repo_dir).head.reference.log())) + self.assertEquals(1, len(Repo(REPO_DIR).head.reference.log())) def test_import_contents(self): """ @@ -66,7 +67,7 @@ class GithubSyncTestCase(TestCase): Test that with the GITHUB_PUSH feature disabled, no content is pushed to the remote """ export_to_github(self.import_course, 'Test no-push') - self.assertEquals(1, Repo(self.remote_dir).head.commit.count()) + self.assertEquals(1, Repo(REMOTE_DIR).head.commit.count()) @override_settings(MITX_FEATURES={'GITHUB_PUSH': True}) def test_export_push(self): @@ -75,7 +76,7 @@ class GithubSyncTestCase(TestCase): """ self.import_course.metadata['display_name'] = 'Changed display name' export_to_github(self.import_course, 'Test push') - self.assertEquals(2, Repo(self.remote_dir).head.commit.count()) + self.assertEquals(2, Repo(REMOTE_DIR).head.commit.count()) @override_settings(MITX_FEATURES={'GITHUB_PUSH': True}) def test_export_conflict(self): @@ -84,7 +85,7 @@ class GithubSyncTestCase(TestCase): """ self.import_course.metadata['display_name'] = 'Changed display name' - remote = Repo(self.remote_dir) + remote = Repo(REMOTE_DIR) remote.git.commit(allow_empty=True, m="Testing conflict commit") self.assertRaises(GithubSyncError, export_to_github, self.import_course, 'Test push') diff --git a/cms/envs/common.py b/cms/envs/common.py index 5fb0c82bb72..7b1f6e3fcda 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -44,10 +44,8 @@ PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms REPO_ROOT = PROJECT_ROOT.dirname() COMMON_ROOT = REPO_ROOT / "common" ENV_ROOT = REPO_ROOT.dirname() # virtualenv dir /mitx is in -COURSES_ROOT = ENV_ROOT / "data" -# FIXME: To support multiple courses, we should walk the courses dir at startup -DATA_DIR = COURSES_ROOT +GITHUB_REPO_ROOT = ENV_ROOT / "data" sys.path.append(REPO_ROOT) sys.path.append(PROJECT_ROOT / 'djangoapps') diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 40139a8a22b..dd12ce5770d 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -18,6 +18,7 @@ MODULESTORE = { 'host': 'localhost', 'db': 'xmodule', 'collection': 'modulestore', + 'fs_root': GITHUB_REPO_ROOT, } } } @@ -31,35 +32,35 @@ DATABASES = { REPOS = { 'edx4edx': { - 'path': DATA_DIR / "edx4edx", + 'path': "edx4edx", 'org': 'edx', 'course': 'edx4edx', 'branch': 'for_cms', 'origin': 'git@github.com:MITx/edx4edx.git', }, '6002x-fall-2012': { - 'path': DATA_DIR / '6002x-fall-2012', + 'path': '6002x-fall-2012', 'org': 'mit.edu', 'course': '6.002x', 'branch': 'for_cms', 'origin': 'git@github.com:MITx/6002x-fall-2012.git', }, '6.00x': { - 'path': DATA_DIR / '6.00x', + 'path': '6.00x', 'org': 'mit.edu', 'course': '6.00x', 'branch': 'for_cms', 'origin': 'git@github.com:MITx/6.00x.git', }, '7.00x': { - 'path': DATA_DIR / '7.00x', + 'path': '7.00x', 'org': 'mit.edu', 'course': '7.00x', 'branch': 'for_cms', 'origin': 'git@github.com:MITx/7.00x.git', }, '3.091x': { - 'path': DATA_DIR / '3.091x', + 'path': '3.091x', 'org': 'mit.edu', 'course': '3.091x', 'branch': 'for_cms', diff --git a/cms/envs/test.py b/cms/envs/test.py index 2a867af91fc..28dce366825 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -24,6 +24,7 @@ TEST_ROOT = path('test_root') # Want static files in the same dir for running on jenkins. STATIC_ROOT = TEST_ROOT / "staticfiles" +GITHUB_REPO_ROOT = TEST_ROOT / "data" MODULESTORE = { 'default': { @@ -33,6 +34,7 @@ MODULESTORE = { 'host': 'localhost', 'db': 'test_xmodule', 'collection': 'modulestore', + 'fs_root': GITHUB_REPO_ROOT, } } } diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index 30010a09a78..0390c314ab7 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -45,7 +45,7 @@ class Location(_LocationBase): """ return re.sub('_+', '_', INVALID_CHARS.sub('_', value)) - def __new__(_cls, loc_or_tag, org=None, course=None, category=None, name=None, revision=None): + def __new__(_cls, loc_or_tag=None, org=None, course=None, category=None, name=None, revision=None): """ Create a new location that is a clone of the specifed one. @@ -70,11 +70,15 @@ class Location(_LocationBase): wildcard selection """ + if org is None and course is None and category is None and name is None and revision is None: location = loc_or_tag else: location = (loc_or_tag, org, course, category, name, revision) + if location is None: + return _LocationBase.__new__(_cls, *([None] * 6)) + def check_dict(dict_): check_list(dict_.itervalues()) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 7ebee98c162..63c955f6da6 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -6,6 +6,7 @@ from xmodule.mako_module import MakoDescriptorSystem from mitxmako.shortcuts import render_to_string from bson.son import SON from itertools import repeat +from fs.osfs import OSFS from . import ModuleStore, Location from .exceptions import ItemNotFoundError, InsufficientSpecificationError @@ -61,7 +62,9 @@ class MongoModuleStore(ModuleStore): """ A Mongodb backed ModuleStore """ - def __init__(self, host, db, collection, port=27017, default_class=None): + + # TODO (cpennington): Enable non-filesystem filestores + def __init__(self, host, db, collection, fs_root, port=27017, default_class=None): self.collection = pymongo.connection.Connection( host=host, port=port @@ -77,6 +80,7 @@ class MongoModuleStore(ModuleStore): module_path, _, class_name = default_class.rpartition('.') class_ = getattr(import_module(module_path), class_name) self.default_class = class_ + self.fs_root = fs_root def _clean_item_data(self, item): """ @@ -113,13 +117,27 @@ class MongoModuleStore(ModuleStore): return data + def _load_item(self, item, data_cache): + """ + Load an XModuleDescriptor from item, using the children stored in data_cache + """ + resource_fs = OSFS(self.fs_root / item.get('data_dir', item['location']['course'])) + system = CachingDescriptorSystem( + self, + data_cache, + self.default_class, + resource_fs, + render_to_string + ) + return system.load_item(item['location']) + def _load_items(self, items, depth=0): """ Load a list of xmodules from the data in items, with children cached up to specified depth """ data_cache = self._cache_children(items, depth) - system = CachingDescriptorSystem(self, data_cache, self.default_class, None, render_to_string) - return [system.load_item(item['location']) for item in items] + + return [self._load_item(item, data_cache) for item in items] def get_item(self, location, depth=0): """ diff --git a/common/test/data/toy/course.xml b/common/test/data/toy/course.xml index ecac9a47768..645fbd7af8d 100644 --- a/common/test/data/toy/course.xml +++ b/common/test/data/toy/course.xml @@ -1,11 +1,9 @@ <course name="Toy Course" graceperiod="1 day 5 hours 59 minutes 59 seconds" showanswer="always" rerandomize="never"> <chapter name="Overview"> - <section format="Video" name="Welcome"> - <video youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/> - </section> - <section format="Lecture Sequence" name="System Usage Sequence"> - <html id="Lab2A" filename="Lab2A.html"/> + <video name="Welcome" youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/> + <videosequence format="Lecture Sequence" name="System Usage Sequence"> + <html id="Lab2A" filename="Lab2A"/> <video name="S0V1: Video Resources" youtube="0.75:EuzkdzfR0i8,1.0:1bK-WdDi6Qw,1.25:0v1VzoDVUTM,1.50:Bxk_-ZJb240"/> - </section> + </videosequence> </chapter> </course> diff --git a/common/test/data/toy/html/Lab2A.html b/common/test/data/toy/html/Lab2A.html new file mode 100644 index 00000000000..7fe52cc1bed --- /dev/null +++ b/common/test/data/toy/html/Lab2A.html @@ -0,0 +1,105 @@ +<script type="text/javascript"> +$(document).ready(function() { +$("#r1_slider").slider({ +value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider, +schematic: "ctrls", component: "R1", property: "r", analysis: "dc", +}) + +$("#r2_slider").slider({ +value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider, +schematic: "ctrls", component: "R2", property: "r", analysis: "dc", +}) + +$("#r3_slider").slider({ +value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider, +schematic: "ctrls", component: "R3", property: "r", analysis: "dc", +}) + +$("#r4_slider").slider({ +value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider, +schematic: "ctrls", component: "R4", property: "r", analysis: "dc", +}) + +$("#slider").slider(); }); +</script> + +<b>Lab 2A: Superposition Experiment</b> + +<br><br><i>Note: This part of the lab is just to develop your intuition about +superposition. There are no responses that need to be checked.</i> + +<br/><br/>Circuits with multiple sources can be hard to analyze as-is. For example, what is the voltage +between the two terminals on the right of Figure 1? + +<center> +<input width="425" type="hidden" height="150" id="schematic1" parts="" analyses="" class="schematic ctrls" name="test2" value="[["w",[160,64,184,64]],["w",[160,16,184,16]],["w",[64,16,112,16]],["w",[112,64,88,64]],["w",[64,64,88,64]],["g",[88,64,0],{},["0"]],["w",[112,64,160,64]],["w",[16,64,64,64]],["r",[160,16,0],{"name":"R4","r":"1"},["1","0"]],["r",[160,16,1],{"name":"R3","r":"1"},["1","2"]],["i",[112,64,6],{"name":"","value":"6A"},["0","2"]],["r",[64,16,0],{"name":"R2","r":"1"},["2","0"]],["r",[64,16,1],{"name":"R1","r":"1"},["2","3"]],["v",[16,16,0],{"name":"","value":"8V"},["3","0"]],["view",-24,0,2]]"/> +Figure 1. Example multi-source circuit +</center> + +<br/><br/>We can use superposition to make the analysis much easier. +The circuit in Figure 1 can be decomposed into two separate +subcircuits: one involving only the voltage source and one involving only the +current source. We'll analyze each circuit separately and combine the +results using superposition. Recall that to decompose a circuit for +analysis, we'll pick each source in turn and set all the other sources +to zero (i.e., voltage sources become short circuits and current +sources become open circuits). The circuit above has two sources, so +the decomposition produces two subcircuits, as shown in Figure 2. + +<center> +<table><tr><td> +<input style="display:inline;" width="425" type="hidden" height="150" id="schematic2" parts="" analyses="" class="schematic ctrls" name="test2" value="[["w",[160,64,184,64]],["w",[160,16,184,16]],["w",[64,16,112,16]],["w",[112,64,88,64]],["w",[64,64,88,64]],["g",[88,64,0],{},["0"]],["w",[112,64,160,64]],["w",[16,64,64,64]],["r",[160,16,0],{"name":"R4","r":"1"},["1","0"]],["r",[160,16,1],{"name":"R3","r":"1"},["1","2"]],["r",[64,16,0],{"name":"R2","r":"1"},["2","0"]],["r",[64,16,1],{"name":"R1","r":"1"},["2","3"]],["v",[16,16,0],{"name":"","value":"8V"},["3","0"]],["view",-24,0,2]]"/> +(a) Subcircuit for analyzing contribution of voltage source +</td><td> +<input width="425" type="hidden" height="150" id="schematic3" parts="" analyses="" class="schematic ctrls" name="test2" value="[["w",[16,16,16,64]],["w",[160,64,184,64]],["w",[160,16,184,16]],["w",[64,16,112,16]],["w",[112,64,88,64]],["w",[64,64,88,64]],["g",[88,64,0],{},["0"]],["w",[112,64,160,64]],["w",[16,64,64,64]],["r",[160,16,0],{"name":"R4","r":"1"},["1","0"]],["r",[160,16,1],{"name":"R3","r":"1"},["1","2"]],["i",[112,64,6],{"name":"","value":"6A"},["0","2"]],["r",[64,16,0],{"name":"R2","r":"1"},["2","0"]],["r",[64,16,1],{"name":"R1","r":"1"},["2","3"]],["view",-24,0,2]]"/> +(b) Subcircuit for analyzing contribution of current source +</td></tr></table> +<br>Figure 2. Decomposition of Figure 1 into subcircuits +</center> + +<br/>Let's use the DC analysis capability of the schematic tool to see superposition +in action. The sliders below control the resistances of R1, R2, R3 and R4 in all +the diagrams. As you move the sliders, the schematic tool will adjust the appropriate +resistance, perform a DC analysis and display the node voltages on the diagrams. Here's +what you want to observe as you play with the sliders: + +<ul style="margin-left:2em;margin-top:1em;margin-right:2em;margin-bottom:1em;"> +<i>The voltage for a node in Figure 1 is the sum of the voltages for +that node in Figures 2(a) and 2(b), just as predicted by +superposition. (Note that due to round-off in the display of the +voltages, the sum of the displayed voltages in Figure 2 may only be within +.01 of the voltages displayed in Figure 1.)</i> +</ul> + +<br> +<center> +<table><tr valign="top"> +<td> + <table> + <tr valign="top"> + <td>R1</td> + <td> + <div id="r1_slider" style="width:200px; height:10px; margin-left:15px"></div> + </td> + </tr> + <tr valign="top"> + <td>R2</td> + <td> + <div id="r2_slider" style="width:200px; height:10px; margin-left:15px; margin-top:10px;"></div> + </td> + </tr> + <tr valign="top"> + <td>R3</td> + <td> + <div id="r3_slider" style="width:200px; height:10px; margin-left:15px; margin-top:10px;"></div> + </td> + </tr> + <tr valign="top"> + <td>R4</td> + <td> + <div id="r4_slider" style="width:200px; height:10px; margin-left:15px; margin-top:10px;"></div> + </td> + </tr> + </table> +</td></tr></table> +</center> diff --git a/test_root/data/custom_tags/.git-keep b/test_root/data/.gitkeep similarity index 100% rename from test_root/data/custom_tags/.git-keep rename to test_root/data/.gitkeep diff --git a/test_root/data/course_settings.py b/test_root/data/course_settings.py deleted file mode 100644 index f4e9696d1d2..00000000000 --- a/test_root/data/course_settings.py +++ /dev/null @@ -1,28 +0,0 @@ -GRADER = [ - { - 'type' : "Homework", - 'min_count' : 12, - 'drop_count' : 2, - 'short_label' : "HW", - 'weight' : 0.15, - }, - { - 'type' : "Lab", - 'min_count' : 12, - 'drop_count' : 2, - 'category' : "Labs", - 'weight' : 0.15 - }, - { - 'type' : "Midterm", - 'name' : "Midterm Exam", - 'short_label' : "Midterm", - 'weight' : 0.3, - }, - { - 'type' : "Final", - 'name' : "Final Exam", - 'short_label' : "Final", - 'weight' : 0.4, - } -] -- GitLab