From 1d1a9173a412a51ab78cee8d3feffb4344561e5d Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 23 Jul 2012 14:44:40 -0400
Subject: [PATCH] Cleaning up pep8 violations
---
cms/djangoapps/contentstore/tests/__init__.py | 1 -
cms/djangoapps/contentstore/tests/tests.py | 17 +-
cms/djangoapps/github_sync/__init__.py | 2 +-
.../github_sync/tests/test_views.py | 1 -
common/djangoapps/cache_toolbox/core.py | 3 +
common/djangoapps/cache_toolbox/middleware.py | 1 +
common/djangoapps/cache_toolbox/model.py | 1 +
common/djangoapps/cache_toolbox/relation.py | 1 +
.../templatetags/cache_toolbox.py | 4 +
common/djangoapps/django_future/csrf.py | 2 +
common/djangoapps/pipeline_mako/__init__.py | 6 +
common/djangoapps/student/admin.py | 1 -
.../management/commands/6002exportusers.py | 20 +-
.../management/commands/6002importusers.py | 22 +-
.../management/commands/assigngroups.py | 35 +-
.../student/management/commands/emaillist.py | 2 +
.../student/management/commands/massemail.py | 8 +-
.../management/commands/massemailtxt.py | 28 +-
.../student/management/commands/userinfo.py | 24 +-
.../student/migrations/0001_initial.py | 7 +-
.../0002_text_to_varchar_and_indexes.py | 7 +-
.../0003_auto__add_usertestgroup.py | 7 +-
.../migrations/0004_add_email_index.py | 3 +-
.../student/migrations/0005_name_change.py | 7 +-
.../migrations/0006_expand_meta_field.py | 7 +-
.../migrations/0007_convert_to_utf8.py | 3 +-
.../0008__auto__add_courseregistration.py | 4 +-
...ourseregistration__add_courseenrollment.py | 4 +-
...o__chg_field_courseenrollment_course_id.py | 2 +-
...t_user__del_unique_courseenrollment_use.py | 7 +-
...der__add_field_userprofile_date_of_birt.py | 4 +-
.../0013_auto__chg_field_userprofile_meta.py | 2 +-
.../0014_auto__del_courseenrollment.py | 4 +-
..._unique_courseenrollment_user_course_id.py | 4 +-
...ent_date__chg_field_userprofile_country.py | 4 +-
.../migrations/0017_rename_date_to_created.py | 3 +-
.../student/migrations/0018_auto.py | 4 +-
...e_approved_demographic_fields_fall_2012.py | 4 +-
common/djangoapps/student/models.py | 68 +--
common/djangoapps/student/views.py | 214 ++++-----
common/djangoapps/track/middleware.py | 33 +-
common/djangoapps/track/views.py | 57 +--
common/djangoapps/util/cache.py | 23 +-
common/djangoapps/util/json_request.py | 1 +
common/djangoapps/util/memcache.py | 2 +
common/djangoapps/util/middleware.py | 4 +-
common/djangoapps/util/views.py | 57 +--
common/lib/capa/capa/__init__.py | 1 -
common/lib/capa/capa/calc.py | 205 +++++----
common/lib/capa/capa/capa_problem.py | 61 +--
common/lib/capa/capa/checker.py | 20 +-
common/lib/capa/capa/correctmap.py | 61 +--
common/lib/capa/capa/eia.py | 13 +-
common/lib/capa/capa/inputtypes.py | 195 ++++----
common/lib/capa/capa/responsetypes.py | 407 +++++++++--------
common/lib/capa/capa/util.py | 20 +-
common/lib/mitxmako/mitxmako/__init__.py | 1 -
common/lib/mitxmako/mitxmako/shortcuts.py | 2 +
common/lib/xmodule/progress.py | 19 +-
common/lib/xmodule/tests/__init__.py | 428 +++++++++---------
common/lib/xmodule/xmodule/capa_module.py | 12 +-
common/lib/xmodule/xmodule/course_module.py | 15 +-
common/lib/xmodule/xmodule/graders.py | 164 +++----
.../xmodule/xmodule/modulestore/__init__.py | 4 +-
.../xmodule/xmodule/modulestore/exceptions.py | 1 +
.../lib/xmodule/xmodule/modulestore/mongo.py | 1 +
.../modulestore/tests/test_location.py | 1 +
common/lib/xmodule/xmodule/modulestore/xml.py | 2 +-
common/lib/xmodule/xmodule/progress.py | 19 +-
.../lib/xmodule/xmodule/schematic_module.py | 2 +
common/lib/xmodule/xmodule/seq_module.py | 6 +-
common/lib/xmodule/xmodule/video_module.py | 2 +-
common/lib/xmodule/xmodule/x_module.py | 11 +-
.../management/commands/ungenerated_certs.py | 14 +-
.../0001_added_generatedcertificates.py | 2 +-
...field_generatedcertificate_download_url.py | 2 +-
..._add_field_generatedcertificate_enabled.py | 2 +-
...icate_graded_certificate_id__add_field_.py | 7 +-
...to__add_field_generatedcertificate_name.py | 7 +-
...eld_generatedcertificate_certificate_id.py | 7 +-
.../0007_auto__add_revokedcertificate.py | 7 +-
lms/djangoapps/certificates/models.py | 86 ++--
lms/djangoapps/certificates/views.py | 83 ++--
lms/djangoapps/circuit/models.py | 5 +-
lms/djangoapps/circuit/views.py | 14 +-
lms/djangoapps/courseware/course_settings.py | 88 ++--
lms/djangoapps/courseware/courses.py | 22 +-
.../courseware/migrations/0001_initial.py | 7 +-
.../courseware/migrations/0002_add_indexes.py | 7 +-
.../migrations/0003_done_grade_cache.py | 7 +-
lms/djangoapps/courseware/models.py | 3 +-
lms/djangoapps/courseware/module_render.py | 20 +-
lms/djangoapps/courseware/progress.py | 22 +-
lms/djangoapps/courseware/views.py | 15 +-
lms/djangoapps/heartbeat/views.py | 1 +
.../multicourse/multicourse_settings.py | 22 +-
lms/djangoapps/multicourse/views.py | 4 +-
lms/djangoapps/simplewiki/__init__.py | 2 +-
lms/djangoapps/simplewiki/admin.py | 12 +-
lms/djangoapps/simplewiki/mdx_circuit.py | 24 +-
lms/djangoapps/simplewiki/mdx_image.py | 26 +-
lms/djangoapps/simplewiki/mdx_mathjax.py | 4 +-
lms/djangoapps/simplewiki/mdx_video.py | 13 +-
lms/djangoapps/simplewiki/mdx_wikipath.py | 33 +-
.../simplewiki/migrations/0001_initial.py | 4 +-
.../migrations/0002_unique_slugs.py | 7 +-
...article_parent__add_field_article_names.py | 4 +-
.../0004_multicourse_data_migration.py | 5 +-
.../0005_auto__add_unique_namespace_name.py | 4 +-
.../simplewiki/migrations/0006_auto.py | 4 +-
.../simplewiki/migrations/0007_auto.py | 4 +-
lms/djangoapps/simplewiki/models.py | 136 +++---
.../templatetags/simplewiki_utils.py | 2 +
lms/djangoapps/simplewiki/tests.py | 2 +-
lms/djangoapps/simplewiki/urls.py | 4 +-
lms/djangoapps/simplewiki/views.py | 309 +++++++------
lms/djangoapps/simplewiki/wiki_settings.py | 22 +-
lms/djangoapps/ssl_auth/ssl_auth.py | 60 +--
lms/djangoapps/static_template_view/views.py | 22 +-
lms/djangoapps/staticbook/views.py | 8 +-
lms/lib/dogfood/check.py | 40 +-
lms/lib/dogfood/views.py | 152 ++++---
lms/lib/loncapa/loncapa_check.py | 21 +-
lms/lib/newrelic_logging/__init__.py | 1 +
lms/lib/perfstats/middleware.py | 15 +-
lms/lib/perfstats/views.py | 1 +
lms/lib/symmath/formula.py | 247 +++++-----
lms/lib/symmath/symmath_check.py | 166 +++----
128 files changed, 2200 insertions(+), 2006 deletions(-)
diff --git a/cms/djangoapps/contentstore/tests/__init__.py b/cms/djangoapps/contentstore/tests/__init__.py
index 8b13789179..e69de29bb2 100644
--- a/cms/djangoapps/contentstore/tests/__init__.py
+++ b/cms/djangoapps/contentstore/tests/__init__.py
@@ -1 +0,0 @@
-
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 4ab152adb1..ade7c4e75d 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -19,10 +19,12 @@ def user(email):
'''look up a user by email'''
return User.objects.get(email=email)
+
def registration(email):
'''look up registration object by email'''
return Registration.objects.get(user__email=email)
+
class AuthTestCase(TestCase):
"""Check that various permissions-related things work"""
@@ -36,7 +38,7 @@ class AuthTestCase(TestCase):
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 = (
@@ -60,11 +62,11 @@ class AuthTestCase(TestCase):
'username': username,
'email': email,
'password': pw,
- 'location' : 'home',
- 'language' : 'Franglish',
- 'name' : 'Fred Weasley',
- 'terms_of_service' : 'true',
- 'honor_code' : 'true',
+ 'location': 'home',
+ 'language': 'Franglish',
+ 'name': 'Fred Weasley',
+ 'terms_of_service': 'true',
+ 'honor_code': 'true',
})
return resp
@@ -99,7 +101,6 @@ class AuthTestCase(TestCase):
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'''
@@ -108,7 +109,6 @@ class AuthTestCase(TestCase):
self.assertEqual(resp.status_code, 200)
return resp
-
def login(self, email, pw):
'''Login, check that it worked.'''
resp = self._login(self.email, self.pw)
@@ -162,7 +162,6 @@ class AuthTestCase(TestCase):
for page in simple_auth_pages:
print "Checking '{0}'".format(page)
self.check_page_get(page, expected=200)
-
def test_index_auth(self):
diff --git a/cms/djangoapps/github_sync/__init__.py b/cms/djangoapps/github_sync/__init__.py
index 9f490119af..c142ca2897 100644
--- a/cms/djangoapps/github_sync/__init__.py
+++ b/cms/djangoapps/github_sync/__init__.py
@@ -58,7 +58,7 @@ def export_to_github(course, commit_message, author_str=None):
git_repo.git.commit(m=commit_message, author=author_str)
else:
git_repo.git.commit(m=commit_message)
-
+
origin = git_repo.remotes.origin
if settings.MITX_FEATURES['GITHUB_PUSH']:
push_infos = origin.push()
diff --git a/cms/djangoapps/github_sync/tests/test_views.py b/cms/djangoapps/github_sync/tests/test_views.py
index 9e8095a67b..f46e7f7db3 100644
--- a/cms/djangoapps/github_sync/tests/test_views.py
+++ b/cms/djangoapps/github_sync/tests/test_views.py
@@ -50,4 +50,3 @@ class PostReceiveTestCase(TestCase):
import_from_github.assert_called_with(settings.REPOS['repo'])
mock_revision, mock_course = import_from_github.return_value
export_to_github.assert_called_with(mock_course, 'path', "Changes from cms import of revision %s" % mock_revision)
-
diff --git a/common/djangoapps/cache_toolbox/core.py b/common/djangoapps/cache_toolbox/core.py
index b2802e7fec..208be34a73 100644
--- a/common/djangoapps/cache_toolbox/core.py
+++ b/common/djangoapps/cache_toolbox/core.py
@@ -13,6 +13,7 @@ from django.db import DEFAULT_DB_ALIAS
from . import app_settings
+
def get_instance(model, instance_or_pk, timeout=None, using=None):
"""
Returns the ``model`` instance with a primary key of ``instance_or_pk``.
@@ -87,6 +88,7 @@ def get_instance(model, instance_or_pk, timeout=None, using=None):
return instance
+
def delete_instance(model, *instance_or_pk):
"""
Purges the cache keys for the instances of this model.
@@ -94,6 +96,7 @@ def delete_instance(model, *instance_or_pk):
cache.delete_many([instance_key(model, x) for x in instance_or_pk])
+
def instance_key(model, instance_or_pk):
"""
Returns the cache key for this (model, instance) pair.
diff --git a/common/djangoapps/cache_toolbox/middleware.py b/common/djangoapps/cache_toolbox/middleware.py
index 97f0bdb2af..fd24bbf18c 100644
--- a/common/djangoapps/cache_toolbox/middleware.py
+++ b/common/djangoapps/cache_toolbox/middleware.py
@@ -84,6 +84,7 @@ from django.contrib.auth.middleware import AuthenticationMiddleware
from .model import cache_model
+
class CacheBackedAuthenticationMiddleware(AuthenticationMiddleware):
def __init__(self):
cache_model(User)
diff --git a/common/djangoapps/cache_toolbox/model.py b/common/djangoapps/cache_toolbox/model.py
index 8ac8f0d249..688b054bd5 100644
--- a/common/djangoapps/cache_toolbox/model.py
+++ b/common/djangoapps/cache_toolbox/model.py
@@ -58,6 +58,7 @@ from django.db.models.signals import post_save, post_delete
from .core import get_instance, delete_instance
+
def cache_model(model, timeout=None):
if hasattr(model, 'get_cached'):
# Already patched
diff --git a/common/djangoapps/cache_toolbox/relation.py b/common/djangoapps/cache_toolbox/relation.py
index 38d985aa94..da41c574db 100644
--- a/common/djangoapps/cache_toolbox/relation.py
+++ b/common/djangoapps/cache_toolbox/relation.py
@@ -74,6 +74,7 @@ from django.db.models.signals import post_save, post_delete
from .core import get_instance, delete_instance
+
def cache_relation(descriptor, timeout=None):
rel = descriptor.related
related_name = '%s_cache' % rel.field.related_query_name()
diff --git a/common/djangoapps/cache_toolbox/templatetags/cache_toolbox.py b/common/djangoapps/cache_toolbox/templatetags/cache_toolbox.py
index feea2af1c8..0f746aecfb 100644
--- a/common/djangoapps/cache_toolbox/templatetags/cache_toolbox.py
+++ b/common/djangoapps/cache_toolbox/templatetags/cache_toolbox.py
@@ -5,6 +5,7 @@ from django.template import resolve_variable
register = template.Library()
+
class CacheNode(Node):
def __init__(self, nodelist, expire_time, key):
self.nodelist = nodelist
@@ -21,6 +22,7 @@ class CacheNode(Node):
cache.set(key, value, expire_time)
return value
+
@register.tag
def cachedeterministic(parser, token):
"""
@@ -42,6 +44,7 @@ def cachedeterministic(parser, token):
raise TemplateSyntaxError(u"'%r' tag requires 2 arguments." % tokens[0])
return CacheNode(nodelist, tokens[1], tokens[2])
+
class ShowIfCachedNode(Node):
def __init__(self, key):
self.key = key
@@ -50,6 +53,7 @@ class ShowIfCachedNode(Node):
key = resolve_variable(self.key, context)
return cache.get(key) or ''
+
@register.tag
def showifcached(parser, token):
"""
diff --git a/common/djangoapps/django_future/csrf.py b/common/djangoapps/django_future/csrf.py
index ba5c3a7791..151d7f6013 100644
--- a/common/djangoapps/django_future/csrf.py
+++ b/common/djangoapps/django_future/csrf.py
@@ -60,6 +60,7 @@ def csrf_response_exempt(view_func):
PendingDeprecationWarning)
return view_func
+
def csrf_view_exempt(view_func):
"""
Marks a view function as being exempt from CSRF view protection.
@@ -68,6 +69,7 @@ def csrf_view_exempt(view_func):
PendingDeprecationWarning)
return csrf_exempt(view_func)
+
def csrf_exempt(view_func):
"""
Marks a view function as being exempt from the CSRF view protection.
diff --git a/common/djangoapps/pipeline_mako/__init__.py b/common/djangoapps/pipeline_mako/__init__.py
index f100d95916..4703b53e52 100644
--- a/common/djangoapps/pipeline_mako/__init__.py
+++ b/common/djangoapps/pipeline_mako/__init__.py
@@ -6,6 +6,7 @@ from pipeline.conf import settings
from pipeline.packager import Packager
from pipeline.utils import guess_type
+
def compressed_css(package_name):
package = settings.PIPELINE_CSS.get(package_name, {})
if package:
@@ -20,6 +21,7 @@ def compressed_css(package_name):
paths = packager.compile(package.paths)
return render_individual_css(package, paths)
+
def render_css(package, path):
template_name = package.template_name or "mako/css.html"
context = package.extra_context
@@ -29,6 +31,7 @@ def render_css(package, path):
})
return render_to_string(template_name, context)
+
def render_individual_css(package, paths):
tags = [render_css(package, path) for path in paths]
return '\n'.join(tags)
@@ -49,6 +52,7 @@ def compressed_js(package_name):
templates = packager.pack_templates(package)
return render_individual_js(package, paths, templates)
+
def render_js(package, path):
template_name = package.template_name or "mako/js.html"
context = package.extra_context
@@ -58,6 +62,7 @@ def render_js(package, path):
})
return render_to_string(template_name, context)
+
def render_inline_js(package, js):
context = package.extra_context
context.update({
@@ -65,6 +70,7 @@ def render_inline_js(package, js):
})
return render_to_string("mako/inline_js.html", context)
+
def render_individual_js(package, paths, templates=None):
tags = [render_js(package, js) for js in paths]
if templates:
diff --git a/common/djangoapps/student/admin.py b/common/djangoapps/student/admin.py
index 1391f26482..ec3b708ca7 100644
--- a/common/djangoapps/student/admin.py
+++ b/common/djangoapps/student/admin.py
@@ -15,4 +15,3 @@ admin.site.register(CourseEnrollment)
admin.site.register(Registration)
admin.site.register(PendingNameChange)
-
diff --git a/common/djangoapps/student/management/commands/6002exportusers.py b/common/djangoapps/student/management/commands/6002exportusers.py
index 0ea458408c..fcf565fb83 100644
--- a/common/djangoapps/student/management/commands/6002exportusers.py
+++ b/common/djangoapps/student/management/commands/6002exportusers.py
@@ -25,27 +25,29 @@ import mitxmako.middleware as middleware
middleware.MakoMiddleware()
+
class Command(BaseCommand):
help = \
-'''Exports all users and user profiles.
+'''Exports all users and user profiles.
Caveat: Should be looked over before any run
-for schema changes.
+for schema changes.
-Current version grabs user_keys from
+Current version grabs user_keys from
django.contrib.auth.models.User and up_keys
from student.userprofile. '''
+
def handle(self, *args, **options):
users = list(User.objects.all())
user_profiles = list(UserProfile.objects.all())
user_profile_dict = dict([(up.user_id, up) for up in user_profiles])
-
+
user_tuples = [(user_profile_dict[u.id], u) for u in users if u.id in user_profile_dict]
-
- user_keys = ['id', 'username', 'email', 'password', 'is_staff',
- 'is_active', 'is_superuser', 'last_login', 'date_joined',
+
+ user_keys = ['id', 'username', 'email', 'password', 'is_staff',
+ 'is_active', 'is_superuser', 'last_login', 'date_joined',
'password']
- up_keys = ['language', 'location','meta','name', 'id','user_id']
-
+ up_keys = ['language', 'location', 'meta', 'name', 'id', 'user_id']
+
def extract_dict(keys, object):
d = {}
for key in keys:
diff --git a/common/djangoapps/student/management/commands/6002importusers.py b/common/djangoapps/student/management/commands/6002importusers.py
index e6d801edb5..64be84d910 100644
--- a/common/djangoapps/student/management/commands/6002importusers.py
+++ b/common/djangoapps/student/management/commands/6002importusers.py
@@ -22,6 +22,7 @@ import mitxmako.middleware as middleware
middleware.MakoMiddleware()
+
def import_user(u):
user_info = u['u']
up_info = u['up']
@@ -30,11 +31,10 @@ def import_user(u):
user_info['last_login'] = dateutil.parser.parse(user_info['last_login'])
user_info['date_joined'] = dateutil.parser.parse(user_info['date_joined'])
- user_keys = ['id', 'username', 'email', 'password', 'is_staff',
- 'is_active', 'is_superuser', 'last_login', 'date_joined',
+ user_keys = ['id', 'username', 'email', 'password', 'is_staff',
+ 'is_active', 'is_superuser', 'last_login', 'date_joined',
'password']
- up_keys = ['language', 'location','meta','name', 'id','user_id']
-
+ up_keys = ['language', 'location', 'meta', 'name', 'id', 'user_id']
u = User()
for key in user_keys:
@@ -47,20 +47,22 @@ def import_user(u):
up.__setattr__(key, up_info[key])
up.save()
+
class Command(BaseCommand):
help = \
-'''Exports all users and user profiles.
+'''Exports all users and user profiles.
Caveat: Should be looked over before any run
-for schema changes.
+for schema changes.
-Current version grabs user_keys from
+Current version grabs user_keys from
django.contrib.auth.models.User and up_keys
from student.userprofile. '''
+
def handle(self, *args, **options):
extracted = json.load(open('transfer_users.txt'))
- n=0
+ n = 0
for u in extracted:
import_user(u)
- if n%100 == 0:
+ if n % 100 == 0:
print n
- n = n+1
+ n = n + 1
diff --git a/common/djangoapps/student/management/commands/assigngroups.py b/common/djangoapps/student/management/commands/assigngroups.py
index 87c15bb1ab..fb7bfc85cd 100644
--- a/common/djangoapps/student/management/commands/assigngroups.py
+++ b/common/djangoapps/student/management/commands/assigngroups.py
@@ -17,29 +17,32 @@ import json
middleware.MakoMiddleware()
+
def group_from_value(groups, v):
''' Given group: (('a',0.3),('b',0.4),('c',0.3)) And random value
in [0,1], return the associated group (in the above case, return
'a' if v<0.3, 'b' if 0.3<=v<0.7, and 'c' if v>0.7
'''
sum = 0
- for (g,p) in groups:
+ for (g, p) in groups:
sum = sum + p
if sum > v:
return g
- return g # For round-off errors
+ return g # For round-off errors
+
class Command(BaseCommand):
help = \
''' Assign users to test groups. Takes a list
-of groups:
+of groups:
a:0.3,b:0.4,c:0.3 file.txt "Testing something"
Will assign each user to group a, b, or c with
-probability 0.3, 0.4, 0.3. Probabilities must
-add up to 1.
+probability 0.3, 0.4, 0.3. Probabilities must
+add up to 1.
Will log what happened to file.txt.
'''
+
def handle(self, *args, **options):
if len(args) != 3:
print "Invalid number of options"
@@ -47,13 +50,13 @@ Will log what happened to file.txt.
# Extract groups from string
group_strs = [x.split(':') for x in args[0].split(',')]
- groups = [(group,float(value)) for group,value in group_strs]
+ groups = [(group, float(value)) for group, value in group_strs]
print "Groups", groups
## Confirm group probabilities add up to 1
total = sum(zip(*groups)[1])
print "Total:", total
- if abs(total-1)>0.01:
+ if abs(total - 1) > 0.01:
print "Total not 1"
sys.exit(-1)
@@ -65,15 +68,15 @@ Will log what happened to file.txt.
group_objects = {}
- f = open(args[1],"a+")
+ f = open(args[1], "a+")
## Create groups
for group in dict(groups):
utg = UserTestGroup()
- utg.name=group
- utg.description = json.dumps({"description":args[2]},
- {"time":datetime.datetime.utcnow().isoformat()})
- group_objects[group]=utg
+ utg.name = group
+ utg.description = json.dumps({"description": args[2]},
+ {"time": datetime.datetime.utcnow().isoformat()})
+ group_objects[group] = utg
group_objects[group].save()
## Assign groups
@@ -83,11 +86,11 @@ Will log what happened to file.txt.
if count % 1000 == 0:
print count
count = count + 1
- v = random.uniform(0,1)
- group = group_from_value(groups,v)
+ v = random.uniform(0, 1)
+ group = group_from_value(groups, v)
group_objects[group].users.add(user)
- f.write("Assigned user {name} ({id}) to {group}\n".format(name=user.username,
- id=user.id,
+ f.write("Assigned user {name} ({id}) to {group}\n".format(name=user.username,
+ id=user.id,
group=group))
## Save groups
diff --git a/common/djangoapps/student/management/commands/emaillist.py b/common/djangoapps/student/management/commands/emaillist.py
index 019e98aac5..4011c41bd2 100644
--- a/common/djangoapps/student/management/commands/emaillist.py
+++ b/common/djangoapps/student/management/commands/emaillist.py
@@ -10,9 +10,11 @@ import mitxmako.middleware as middleware
middleware.MakoMiddleware()
+
class Command(BaseCommand):
help = \
''' Extract an e-mail list of all active students. '''
+
def handle(self, *args, **options):
#text = open(args[0]).read()
#subject = open(args[1]).read()
diff --git a/common/djangoapps/student/management/commands/massemail.py b/common/djangoapps/student/management/commands/massemail.py
index 306ae51c0e..c6f6e5f6d4 100644
--- a/common/djangoapps/student/management/commands/massemail.py
+++ b/common/djangoapps/student/management/commands/massemail.py
@@ -10,18 +10,20 @@ import mitxmako.middleware as middleware
middleware.MakoMiddleware()
+
class Command(BaseCommand):
help = \
-'''Sends an e-mail to all users. Takes a single
+'''Sends an e-mail to all users. Takes a single
parameter -- name of e-mail template -- located
in templates/email. Adds a .txt for the message
body, and an _subject.txt for the subject. '''
+
def handle(self, *args, **options):
#text = open(args[0]).read()
#subject = open(args[1]).read()
users = User.objects.all()
- text = middleware.lookup['main'].get_template('email/'+args[0]+".txt").render()
- subject = middleware.lookup['main'].get_template('email/'+args[0]+"_subject.txt").render().strip()
+ text = middleware.lookup['main'].get_template('email/' + args[0] + ".txt").render()
+ subject = middleware.lookup['main'].get_template('email/' + args[0] + "_subject.txt").render().strip()
for user in users:
if user.is_active:
user.email_user(subject, text)
diff --git a/common/djangoapps/student/management/commands/massemailtxt.py b/common/djangoapps/student/management/commands/massemailtxt.py
index 661c4e4fb7..4ea75f972b 100644
--- a/common/djangoapps/student/management/commands/massemailtxt.py
+++ b/common/djangoapps/student/management/commands/massemailtxt.py
@@ -16,16 +16,18 @@ import datetime
middleware.MakoMiddleware()
+
def chunks(l, n):
""" Yield successive n-sized chunks from l.
"""
for i in xrange(0, len(l), n):
- yield l[i:i+n]
+ yield l[i:i + n]
+
class Command(BaseCommand):
help = \
-'''Sends an e-mail to all users in a text file.
-E.g.
+'''Sends an e-mail to all users in a text file.
+E.g.
manage.py userlist.txt message logfile.txt rate
userlist.txt -- list of all users
message -- prefix for template with message
@@ -35,28 +37,28 @@ rate -- messages per second
log_file = None
def hard_log(self, text):
- self.log_file.write(datetime.datetime.utcnow().isoformat()+' -- '+text+'\n')
+ self.log_file.write(datetime.datetime.utcnow().isoformat() + ' -- ' + text + '\n')
def handle(self, *args, **options):
global log_file
(user_file, message_base, logfilename, ratestr) = args
-
+
users = [u.strip() for u in open(user_file).readlines()]
- message = middleware.lookup['main'].get_template('emails/'+message_base+"_body.txt").render()
- subject = middleware.lookup['main'].get_template('emails/'+message_base+"_subject.txt").render().strip()
+ message = middleware.lookup['main'].get_template('emails/' + message_base + "_body.txt").render()
+ subject = middleware.lookup['main'].get_template('emails/' + message_base + "_subject.txt").render().strip()
rate = int(ratestr)
-
- self.log_file = open(logfilename, "a+", buffering = 0)
- i=0
+ self.log_file = open(logfilename, "a+", buffering=0)
+
+ i = 0
for users in chunks(users, rate):
- emails = [ (subject, message, settings.DEFAULT_FROM_EMAIL, [u]) for u in users ]
+ emails = [(subject, message, settings.DEFAULT_FROM_EMAIL, [u]) for u in users]
self.hard_log(" ".join(users))
- send_mass_mail( emails, fail_silently = False )
+ send_mass_mail(emails, fail_silently=False)
time.sleep(1)
print datetime.datetime.utcnow().isoformat(), i
- i = i+len(users)
+ i = i + len(users)
# Emergency interruptor
if os.path.exists("/tmp/stopemails.txt"):
self.log_file.close()
diff --git a/common/djangoapps/student/management/commands/userinfo.py b/common/djangoapps/student/management/commands/userinfo.py
index 72b4d2ebd6..e458995284 100644
--- a/common/djangoapps/student/management/commands/userinfo.py
+++ b/common/djangoapps/student/management/commands/userinfo.py
@@ -13,26 +13,28 @@ from student.models import UserProfile
middleware.MakoMiddleware()
+
class Command(BaseCommand):
help = \
-''' Extract full user information into a JSON file.
+''' Extract full user information into a JSON file.
Pass a single filename.'''
+
def handle(self, *args, **options):
- f = open(args[0],'w')
+ f = open(args[0], 'w')
#text = open(args[0]).read()
#subject = open(args[1]).read()
users = User.objects.all()
l = []
for user in users:
- up = UserProfile.objects.get(user = user)
- d = { 'username':user.username,
- 'email':user.email,
- 'is_active':user.is_active,
- 'joined':user.date_joined.isoformat(),
- 'name':up.name,
- 'language':up.language,
- 'location':up.location}
+ up = UserProfile.objects.get(user=user)
+ d = {'username': user.username,
+ 'email': user.email,
+ 'is_active': user.is_active,
+ 'joined': user.date_joined.isoformat(),
+ 'name': up.name,
+ 'language': up.language,
+ 'location': up.location}
l.append(d)
- json.dump(l,f)
+ json.dump(l, f)
f.close()
diff --git a/common/djangoapps/student/migrations/0001_initial.py b/common/djangoapps/student/migrations/0001_initial.py
index cab5690ea7..d5766ca823 100644
--- a/common/djangoapps/student/migrations/0001_initial.py
+++ b/common/djangoapps/student/migrations/0001_initial.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding model 'UserProfile'
db.create_table('auth_userprofile', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
@@ -28,16 +29,14 @@ class Migration(SchemaMigration):
))
db.send_create_signal('student', ['Registration'])
-
def backwards(self, orm):
-
+
# Deleting model 'UserProfile'
db.delete_table('auth_userprofile')
# Deleting model 'Registration'
db.delete_table('auth_registration')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0002_text_to_varchar_and_indexes.py b/common/djangoapps/student/migrations/0002_text_to_varchar_and_indexes.py
index a75f164f3a..27aea40a5c 100644
--- a/common/djangoapps/student/migrations/0002_text_to_varchar_and_indexes.py
+++ b/common/djangoapps/student/migrations/0002_text_to_varchar_and_indexes.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Changing field 'UserProfile.name'
db.alter_column('auth_userprofile', 'name', self.gf('django.db.models.fields.CharField')(max_length=255))
@@ -32,9 +33,8 @@ class Migration(SchemaMigration):
# Adding index on 'UserProfile', fields ['location']
db.create_index('auth_userprofile', ['location'])
-
def backwards(self, orm):
-
+
# Removing index on 'UserProfile', fields ['location']
db.delete_index('auth_userprofile', ['location'])
@@ -59,7 +59,6 @@ class Migration(SchemaMigration):
# Changing field 'UserProfile.location'
db.alter_column('auth_userprofile', 'location', self.gf('django.db.models.fields.TextField')())
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0003_auto__add_usertestgroup.py b/common/djangoapps/student/migrations/0003_auto__add_usertestgroup.py
index 4765d63578..a63abf9469 100644
--- a/common/djangoapps/student/migrations/0003_auto__add_usertestgroup.py
+++ b/common/djangoapps/student/migrations/0003_auto__add_usertestgroup.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding model 'UserTestGroup'
db.create_table('student_usertestgroup', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
@@ -24,16 +25,14 @@ class Migration(SchemaMigration):
))
db.create_unique('student_usertestgroup_users', ['usertestgroup_id', 'user_id'])
-
def backwards(self, orm):
-
+
# Deleting model 'UserTestGroup'
db.delete_table('student_usertestgroup')
# Removing M2M table for field users on 'UserTestGroup'
db.delete_table('student_usertestgroup_users')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0004_add_email_index.py b/common/djangoapps/student/migrations/0004_add_email_index.py
index c95e36ae96..60af424f65 100644
--- a/common/djangoapps/student/migrations/0004_add_email_index.py
+++ b/common/djangoapps/student/migrations/0004_add_email_index.py
@@ -4,18 +4,17 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
db.execute("create unique index email on auth_user (email)")
pass
-
def backwards(self, orm):
db.execute("drop index email on auth_user")
pass
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0005_name_change.py b/common/djangoapps/student/migrations/0005_name_change.py
index 77265bcddd..24c393ccce 100644
--- a/common/djangoapps/student/migrations/0005_name_change.py
+++ b/common/djangoapps/student/migrations/0005_name_change.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding model 'PendingEmailChange'
db.create_table('student_pendingemailchange', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
@@ -29,9 +30,8 @@ class Migration(SchemaMigration):
# Changing field 'UserProfile.user'
db.alter_column('auth_userprofile', 'user_id', self.gf('django.db.models.fields.related.OneToOneField')(unique=True, to=orm['auth.User']))
-
def backwards(self, orm):
-
+
# Deleting model 'PendingEmailChange'
db.delete_table('student_pendingemailchange')
@@ -41,7 +41,6 @@ class Migration(SchemaMigration):
# Changing field 'UserProfile.user'
db.alter_column('auth_userprofile', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True))
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0006_expand_meta_field.py b/common/djangoapps/student/migrations/0006_expand_meta_field.py
index 19fd402cef..7fc3094ccc 100644
--- a/common/djangoapps/student/migrations/0006_expand_meta_field.py
+++ b/common/djangoapps/student/migrations/0006_expand_meta_field.py
@@ -4,20 +4,19 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Changing field 'UserProfile.meta'
db.alter_column('auth_userprofile', 'meta', self.gf('django.db.models.fields.TextField')())
-
def backwards(self, orm):
-
+
# Changing field 'UserProfile.meta'
db.alter_column('auth_userprofile', 'meta', self.gf('django.db.models.fields.CharField')(max_length=255))
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0007_convert_to_utf8.py b/common/djangoapps/student/migrations/0007_convert_to_utf8.py
index 84a3299c87..7a96496ad0 100644
--- a/common/djangoapps/student/migrations/0007_convert_to_utf8.py
+++ b/common/djangoapps/student/migrations/0007_convert_to_utf8.py
@@ -4,6 +4,7 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
@@ -16,12 +17,10 @@ class Migration(SchemaMigration):
ALTER TABLE student_usertestgroup_users CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
""")
-
def backwards(self, orm):
# Although this migration can't be undone, it is okay for it to be run backwards because it doesn't add/remove any fields
pass
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/common/djangoapps/student/migrations/0008__auto__add_courseregistration.py b/common/djangoapps/student/migrations/0008__auto__add_courseregistration.py
index 7b10821f4e..22ef55913a 100644
--- a/common/djangoapps/student/migrations/0008__auto__add_courseregistration.py
+++ b/common/djangoapps/student/migrations/0008__auto__add_courseregistration.py
@@ -16,12 +16,10 @@ class Migration(SchemaMigration):
))
db.send_create_signal('student', ['CourseRegistration'])
-
def backwards(self, orm):
# Deleting model 'CourseRegistration'
db.delete_table('student_courseregistration')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -129,4 +127,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0009_auto__del_courseregistration__add_courseenrollment.py b/common/djangoapps/student/migrations/0009_auto__del_courseregistration__add_courseenrollment.py
index 5dcdd509de..c5af13d34c 100644
--- a/common/djangoapps/student/migrations/0009_auto__del_courseregistration__add_courseenrollment.py
+++ b/common/djangoapps/student/migrations/0009_auto__del_courseregistration__add_courseenrollment.py
@@ -19,7 +19,6 @@ class Migration(SchemaMigration):
))
db.send_create_signal('student', ['CourseEnrollment'])
-
def backwards(self, orm):
# Adding model 'CourseRegistration'
db.create_table('student_courseregistration', (
@@ -32,7 +31,6 @@ class Migration(SchemaMigration):
# Deleting model 'CourseEnrollment'
db.delete_table('student_courseenrollment')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -140,4 +138,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0010_auto__chg_field_courseenrollment_course_id.py b/common/djangoapps/student/migrations/0010_auto__chg_field_courseenrollment_course_id.py
index 47a79e43cd..153a166b5e 100644
--- a/common/djangoapps/student/migrations/0010_auto__chg_field_courseenrollment_course_id.py
+++ b/common/djangoapps/student/migrations/0010_auto__chg_field_courseenrollment_course_id.py
@@ -124,4 +124,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0011_auto__chg_field_courseenrollment_user__del_unique_courseenrollment_use.py b/common/djangoapps/student/migrations/0011_auto__chg_field_courseenrollment_user__del_unique_courseenrollment_use.py
index bb74eed0b4..e8417f53ef 100644
--- a/common/djangoapps/student/migrations/0011_auto__chg_field_courseenrollment_user__del_unique_courseenrollment_use.py
+++ b/common/djangoapps/student/migrations/0011_auto__chg_field_courseenrollment_user__del_unique_courseenrollment_use.py
@@ -13,8 +13,8 @@ class Migration(SchemaMigration):
pass
# # Removing unique constraint on 'CourseEnrollment', fields ['user']
# db.delete_unique('student_courseenrollment', ['user_id'])
- #
- #
+ #
+ #
# # Changing field 'CourseEnrollment.user'
# db.alter_column('student_courseenrollment', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User']))
@@ -25,7 +25,6 @@ class Migration(SchemaMigration):
# # Adding unique constraint on 'CourseEnrollment', fields ['user']
# db.create_unique('student_courseenrollment', ['user_id'])
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -133,4 +132,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0012_auto__add_field_userprofile_gender__add_field_userprofile_date_of_birt.py b/common/djangoapps/student/migrations/0012_auto__add_field_userprofile_gender__add_field_userprofile_date_of_birt.py
index e77d47f2b1..ce46bf50fa 100644
--- a/common/djangoapps/student/migrations/0012_auto__add_field_userprofile_gender__add_field_userprofile_date_of_birt.py
+++ b/common/djangoapps/student/migrations/0012_auto__add_field_userprofile_gender__add_field_userprofile_date_of_birt.py
@@ -38,7 +38,6 @@ class Migration(SchemaMigration):
self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
keep_default=False)
-
def backwards(self, orm):
# Deleting field 'UserProfile.gender'
db.delete_column('auth_userprofile', 'gender')
@@ -58,7 +57,6 @@ class Migration(SchemaMigration):
# Deleting field 'UserProfile.occupation'
db.delete_column('auth_userprofile', 'occupation')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -172,4 +170,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0013_auto__chg_field_userprofile_meta.py b/common/djangoapps/student/migrations/0013_auto__chg_field_userprofile_meta.py
index 9c2e6620b7..de81c6bf53 100644
--- a/common/djangoapps/student/migrations/0013_auto__chg_field_userprofile_meta.py
+++ b/common/djangoapps/student/migrations/0013_auto__chg_field_userprofile_meta.py
@@ -130,4 +130,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0014_auto__del_courseenrollment.py b/common/djangoapps/student/migrations/0014_auto__del_courseenrollment.py
index 964ef1cf72..926c38c55f 100644
--- a/common/djangoapps/student/migrations/0014_auto__del_courseenrollment.py
+++ b/common/djangoapps/student/migrations/0014_auto__del_courseenrollment.py
@@ -11,7 +11,6 @@ class Migration(SchemaMigration):
# Deleting model 'CourseEnrollment'
db.delete_table('student_courseenrollment')
-
def backwards(self, orm):
# Adding model 'CourseEnrollment'
db.create_table('student_courseenrollment', (
@@ -21,7 +20,6 @@ class Migration(SchemaMigration):
))
db.send_create_signal('student', ['CourseEnrollment'])
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -129,4 +127,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0015_auto__add_courseenrollment__add_unique_courseenrollment_user_course_id.py b/common/djangoapps/student/migrations/0015_auto__add_courseenrollment__add_unique_courseenrollment_user_course_id.py
index 5b4882a614..3cde0c755c 100644
--- a/common/djangoapps/student/migrations/0015_auto__add_courseenrollment__add_unique_courseenrollment_user_course_id.py
+++ b/common/djangoapps/student/migrations/0015_auto__add_courseenrollment__add_unique_courseenrollment_user_course_id.py
@@ -19,7 +19,6 @@ class Migration(SchemaMigration):
# Adding unique constraint on 'CourseEnrollment', fields ['user', 'course_id']
db.create_unique('student_courseenrollment', ['user_id', 'course_id'])
-
def backwards(self, orm):
# Removing unique constraint on 'CourseEnrollment', fields ['user', 'course_id']
db.delete_unique('student_courseenrollment', ['user_id', 'course_id'])
@@ -27,7 +26,6 @@ class Migration(SchemaMigration):
# Deleting model 'CourseEnrollment'
db.delete_table('student_courseenrollment')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -141,4 +139,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py b/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py
index 38e25db095..58f013742e 100644
--- a/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py
+++ b/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py
@@ -13,7 +13,6 @@ class Migration(SchemaMigration):
self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, blank=True),
keep_default=False)
-
# Changing field 'UserProfile.country'
db.alter_column('auth_userprofile', 'country', self.gf('django_countries.fields.CountryField')(max_length=2, null=True))
@@ -21,7 +20,6 @@ class Migration(SchemaMigration):
# Deleting field 'CourseEnrollment.date'
db.delete_column('student_courseenrollment', 'date')
-
# Changing field 'UserProfile.country'
db.alter_column('auth_userprofile', 'country', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
@@ -139,4 +137,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0017_rename_date_to_created.py b/common/djangoapps/student/migrations/0017_rename_date_to_created.py
index 9b387ed2e9..6bde313d8b 100644
--- a/common/djangoapps/student/migrations/0017_rename_date_to_created.py
+++ b/common/djangoapps/student/migrations/0017_rename_date_to_created.py
@@ -15,7 +15,6 @@ class Migration(SchemaMigration):
# Rename 'created' field to 'date'
db.rename_column('student_courseenrollment', 'created', 'date')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -130,4 +129,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0018_auto.py b/common/djangoapps/student/migrations/0018_auto.py
index 1f91bf9d4a..df0cc3fbdb 100644
--- a/common/djangoapps/student/migrations/0018_auto.py
+++ b/common/djangoapps/student/migrations/0018_auto.py
@@ -11,12 +11,10 @@ class Migration(SchemaMigration):
# Adding index on 'CourseEnrollment', fields ['created']
db.create_index('student_courseenrollment', ['created'])
-
def backwards(self, orm):
# Removing index on 'CourseEnrollment', fields ['created']
db.delete_index('student_courseenrollment', ['created'])
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -131,4 +129,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/migrations/0019_create_approved_demographic_fields_fall_2012.py b/common/djangoapps/student/migrations/0019_create_approved_demographic_fields_fall_2012.py
index d260e263f7..7c0207f4f4 100644
--- a/common/djangoapps/student/migrations/0019_create_approved_demographic_fields_fall_2012.py
+++ b/common/djangoapps/student/migrations/0019_create_approved_demographic_fields_fall_2012.py
@@ -38,7 +38,6 @@ class Migration(SchemaMigration):
# Adding index on 'UserProfile', fields ['gender']
db.create_index('auth_userprofile', ['gender'])
-
def backwards(self, orm):
# Removing index on 'UserProfile', fields ['gender']
db.delete_index('auth_userprofile', ['gender'])
@@ -72,7 +71,6 @@ class Migration(SchemaMigration):
# Deleting field 'UserProfile.goals'
db.delete_column('auth_userprofile', 'goals')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
@@ -186,4 +184,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['student']
\ No newline at end of file
+ complete_apps = ['student']
diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index ca5081254e..49d3381303 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -18,6 +18,7 @@ from django_countries import CountryField
#from cache_toolbox import cache_model, cache_relation
+
class UserProfile(models.Model):
class Meta:
db_table = "auth_userprofile"
@@ -28,7 +29,7 @@ class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile')
name = models.CharField(blank=True, max_length=255, db_index=True)
- meta = models.TextField(blank=True) # JSON dictionary for future expansion
+ meta = models.TextField(blank=True) # JSON dictionary for future expansion
courseware = models.CharField(blank=True, max_length=255, default='course.xml')
# Location is no longer used, but is held here for backwards compatibility
@@ -59,7 +60,6 @@ class UserProfile(models.Model):
mailing_address = models.TextField(blank=True, null=True)
goals = models.TextField(blank=True, null=True)
-
def get_meta(self):
js_str = self.meta
if not js_str:
@@ -69,9 +69,10 @@ class UserProfile(models.Model):
return js_str
- def set_meta(self,js):
+ def set_meta(self, js):
self.meta = json.dumps(js)
+
## TODO: Should be renamed to generic UserGroup, and possibly
# Given an optional field for type of group
class UserTestGroup(models.Model):
@@ -79,6 +80,7 @@ class UserTestGroup(models.Model):
name = models.CharField(blank=False, max_length=32, db_index=True)
description = models.TextField(blank=True)
+
class Registration(models.Model):
''' Allows us to wait for e-mail before user is registered. A
registration profile is created when the user creates an
@@ -92,8 +94,8 @@ class Registration(models.Model):
def register(self, user):
# MINOR TODO: Switch to crypto-secure key
- self.activation_key=uuid.uuid4().hex
- self.user=user
+ self.activation_key = uuid.uuid4().hex
+ self.user = user
self.save()
def activate(self):
@@ -101,22 +103,25 @@ class Registration(models.Model):
self.user.save()
#self.delete()
+
class PendingNameChange(models.Model):
user = models.OneToOneField(User, unique=True, db_index=True)
new_name = models.CharField(blank=True, max_length=255)
rationale = models.CharField(blank=True, max_length=1024)
+
class PendingEmailChange(models.Model):
user = models.OneToOneField(User, unique=True, db_index=True)
new_email = models.CharField(blank=True, max_length=255, db_index=True)
activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True)
+
class CourseEnrollment(models.Model):
user = models.ForeignKey(User)
course_id = models.CharField(max_length=255, db_index=True)
-
+
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
-
+
class Meta:
unique_together = (('user', 'course_id'), )
@@ -124,38 +129,45 @@ class CourseEnrollment(models.Model):
#### Helper methods for use from python manage.py shell.
+
def get_user(email):
- u = User.objects.get(email = email)
- up = UserProfile.objects.get(user = u)
- return u,up
+ u = User.objects.get(email=email)
+ up = UserProfile.objects.get(user=u)
+ return u, up
+
def user_info(email):
- u,up = get_user(email)
+ u, up = get_user(email)
print "User id", u.id
print "Username", u.username
print "E-mail", u.email
print "Name", up.name
print "Location", up.location
print "Language", up.language
- return u,up
+ return u, up
+
def change_email(old_email, new_email):
- u = User.objects.get(email = old_email)
+ u = User.objects.get(email=old_email)
u.email = new_email
u.save()
+
def change_name(email, new_name):
- u,up = get_user(email)
+ u, up = get_user(email)
up.name = new_name
up.save()
+
def user_count():
print "All users", User.objects.all().count()
- print "Active users", User.objects.filter(is_active = True).count()
+ print "Active users", User.objects.filter(is_active=True).count()
return User.objects.all().count()
+
def active_user_count():
- return User.objects.filter(is_active = True).count()
+ return User.objects.filter(is_active=True).count()
+
def create_group(name, description):
utg = UserTestGroup()
@@ -163,29 +175,31 @@ def create_group(name, description):
utg.description = description
utg.save()
+
def add_user_to_group(user, group):
- utg = UserTestGroup.objects.get(name = group)
- utg.users.add(User.objects.get(username = user))
+ utg = UserTestGroup.objects.get(name=group)
+ utg.users.add(User.objects.get(username=user))
utg.save()
+
def remove_user_from_group(user, group):
- utg = UserTestGroup.objects.get(name = group)
- utg.users.remove(User.objects.get(username = user))
+ utg = UserTestGroup.objects.get(name=group)
+ utg.users.remove(User.objects.get(username=user))
utg.save()
-default_groups = {'email_future_courses' : 'Receive e-mails about future MITx courses',
- 'email_helpers' : 'Receive e-mails about how to help with MITx',
- 'mitx_unenroll' : 'Fully unenrolled -- no further communications',
- '6002x_unenroll' : 'Took and dropped 6002x'}
+default_groups = {'email_future_courses': 'Receive e-mails about future MITx courses',
+ 'email_helpers': 'Receive e-mails about how to help with MITx',
+ 'mitx_unenroll': 'Fully unenrolled -- no further communications',
+ '6002x_unenroll': 'Took and dropped 6002x'}
+
def add_user_to_default_group(user, group):
try:
- utg = UserTestGroup.objects.get(name = group)
+ utg = UserTestGroup.objects.get(name=group)
except UserTestGroup.DoesNotExist:
utg = UserTestGroup()
utg.name = group
utg.description = default_groups[group]
utg.save()
- utg.users.add(User.objects.get(username = user))
+ utg.users.add(User.objects.get(username=user))
utg.save()
-
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index 143a22683e..449ea1d02d 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -28,7 +28,7 @@ from django.core.cache import cache
from django_future.csrf import ensure_csrf_cookie
from student.models import Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment
-from util.cache import cache_if_anonymous
+from util.cache import cache_if_anonymous
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -80,10 +80,12 @@ def index(request):
return render_to_response('index.html', {'universities': universities, 'entries': entries})
+
def course_from_id(id):
course_loc = CourseDescriptor.id_to_location(id)
return modulestore().get_item(course_loc)
+
@login_required
@ensure_csrf_cookie
def dashboard(request):
@@ -100,19 +102,18 @@ def dashboard(request):
except ItemNotFoundError:
log.error("User {0} enrolled in non-existant course {1}"
.format(user.username, enrollment.course_id))
-
-
+
message = ""
if not user.is_active:
message = render_to_string('registration/activate_account_notice.html', {'email': user.email})
- context = {'courses': courses, 'message' : message}
+ context = {'courses': courses, 'message': message}
return render_to_response('dashboard.html', context)
def try_change_enrollment(request):
"""
- This method calls change_enrollment if the necessary POST
+ This method calls change_enrollment if the necessary POST
parameters are present, but does not return anything. It
simply logs the result or exception. This is usually
called after a registration or login, as secondary action.
@@ -126,22 +127,23 @@ def try_change_enrollment(request):
log.info("Attempted to automatically enroll after login. Results: {0}".format(enrollment_output))
except Exception, e:
log.exception("Exception automatically enrolling after login: {0}".format(str(e)))
-
+
@login_required
def change_enrollment_view(request):
return HttpResponse(json.dumps(change_enrollment(request)))
+
def change_enrollment(request):
if request.method != "POST":
raise Http404
-
- action = request.POST.get("enrollment_action" , "")
+
+ action = request.POST.get("enrollment_action", "")
user = request.user
course_id = request.POST.get("course_id", None)
if course_id == None:
return HttpResponse(json.dumps({'success': False, 'error': 'There was an error receiving the course id.'}))
-
+
if action == "enroll":
# Make sure the course exists
# We don't do this check on unenroll, or a bad course id can't be unenrolled from
@@ -151,22 +153,23 @@ def change_enrollment(request):
log.error("User {0} tried to enroll in non-existant course {1}"
.format(user.username, enrollment.course_id))
return {'success': False, 'error': 'The course requested does not exist.'}
-
+
enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id)
return {'success': True}
-
+
elif action == "unenroll":
try:
- enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id)
+ enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id)
enrollment.delete()
return {'success': True}
except CourseEnrollment.DoesNotExist:
return {'success': False, 'error': 'You are not enrolled for this course.'}
else:
return {'success': False, 'error': 'Invalid enrollment_action.'}
-
+
return {'success': False, 'error': 'We weren\'t able to unenroll you. Please try again.'}
+
# Need different levels of logging
@ensure_csrf_cookie
def login_user(request, error=""):
@@ -195,7 +198,7 @@ def login_user(request, error=""):
try:
login(request, user)
if request.POST.get('remember') == 'true':
- request.session.set_expiry(None) # or change to 604800 for 7 days
+ request.session.set_expiry(None) # or change to 604800 for 7 days
log.debug("Setting user session to never expire")
else:
request.session.set_expiry(0)
@@ -204,38 +207,41 @@ def login_user(request, error=""):
log.exception(e)
log.info("Login success - {0} ({1})".format(username, email))
-
+
try_change_enrollment(request)
-
- return HttpResponse(json.dumps({'success':True}))
+
+ return HttpResponse(json.dumps({'success': True}))
log.warning("Login failed - Account not active for user {0}".format(username))
- return HttpResponse(json.dumps({'success':False,
+ return HttpResponse(json.dumps({'success': False,
'value': 'This account has not been activated. Please check your e-mail for the activation instructions.'}))
+
@ensure_csrf_cookie
def logout_user(request):
''' HTTP request to log out the user. Redirects to marketing page'''
logout(request)
return redirect('/')
+
@login_required
@ensure_csrf_cookie
def change_setting(request):
''' JSON call to change a profile setting: Right now, location
'''
- up = UserProfile.objects.get(user=request.user) #request.user.profile_cache
+ up = UserProfile.objects.get(user=request.user) # request.user.profile_cache
if 'location' in request.POST:
- up.location=request.POST['location']
+ up.location = request.POST['location']
up.save()
- return HttpResponse(json.dumps({'success':True,
- 'location':up.location,}))
+ return HttpResponse(json.dumps({'success': True,
+ 'location': up.location, }))
+
@ensure_csrf_cookie
def create_account(request, post_override=None):
''' JSON call to enroll in the course. '''
- js={'success':False}
+ js = {'success': False}
post_vars = post_override if post_override else request.POST
@@ -246,12 +252,11 @@ def create_account(request, post_override=None):
return HttpResponse(json.dumps(js))
if post_vars.get('honor_code', 'false') != u'true':
- js['value']="To enroll, you must follow the honor code.".format(field=a)
+ js['value'] = "To enroll, you must follow the honor code.".format(field=a)
return HttpResponse(json.dumps(js))
-
if post_vars.get('terms_of_service', 'false') != u'true':
- js['value']="You must accept the terms of service.".format(field=a)
+ js['value'] = "You must accept the terms of service.".format(field=a)
return HttpResponse(json.dumps(js))
# Confirm appropriate fields are there.
@@ -261,25 +266,25 @@ def create_account(request, post_override=None):
# TODO: Check password is sane
for a in ['username', 'email', 'name', 'password', 'terms_of_service', 'honor_code']:
if len(post_vars[a]) < 2:
- error_str = {'username' : 'Username of length 2 or greater',
- 'email' : 'Properly formatted e-mail',
- 'name' : 'Your legal name ',
+ error_str = {'username': 'Username of length 2 or greater',
+ 'email': 'Properly formatted e-mail',
+ 'name': 'Your legal name ',
'password': 'Valid password ',
'terms_of_service': 'Accepting Terms of Service',
'honor_code': 'Agreeing to the Honor Code'}
- js['value']="{field} is required.".format(field=error_str[a])
+ js['value'] = "{field} is required.".format(field=error_str[a])
return HttpResponse(json.dumps(js))
try:
validate_email(post_vars['email'])
except ValidationError:
- js['value']="Valid e-mail is required.".format(field=a)
+ js['value'] = "Valid e-mail is required.".format(field=a)
return HttpResponse(json.dumps(js))
try:
validate_slug(post_vars['username'])
except ValidationError:
- js['value']="Username should only consist of A-Z and 0-9.".format(field=a)
+ js['value'] = "Username should only consist of A-Z and 0-9.".format(field=a)
return HttpResponse(json.dumps(js))
u = User(username=post_vars['username'],
@@ -311,17 +316,17 @@ def create_account(request, post_override=None):
up.gender = post_vars.get('gender')
up.mailing_address = post_vars.get('mailing_address')
up.goals = post_vars.get('goals')
-
+
try:
up.year_of_birth = int(post_vars['year_of_birth'])
except (ValueError, KeyError):
- up.year_of_birth = None # If they give us garbage, just ignore it instead
+ up.year_of_birth = None # If they give us garbage, just ignore it instead
# of asking them to put an integer.
try:
up.save()
except Exception:
log.exception("UserProfile creation failed for user {0}.".format(u.id))
-
+
d = {'name': post_vars['name'],
'key': r.activation_key,
}
@@ -334,7 +339,7 @@ def create_account(request, post_override=None):
try:
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
- message = "Activation for %s (%s): %s\n" % (u,u.email,up.name) + '-' * 80 + '\n\n' + message
+ message = "Activation for %s (%s): %s\n" % (u, u.email, up.name) + '-' * 80 + '\n\n' + message
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
elif not settings.GENERATE_RANDOM_USER_CREDENTIALS:
res = u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
@@ -342,57 +347,60 @@ def create_account(request, post_override=None):
log.exception(sys.exc_info())
js['value'] = 'Could not send activation e-mail.'
return HttpResponse(json.dumps(js))
-
+
# Immediately after a user creates an account, we log them in. They are only
# logged in until they close the browser. They can't log in again until they click
# the activation link from the email.
- login_user = authenticate(username=post_vars['username'], password = post_vars['password'] )
+ login_user = authenticate(username=post_vars['username'], password=post_vars['password'])
login(request, login_user)
- request.session.set_expiry(0)
-
+ request.session.set_expiry(0)
+
try_change_enrollment(request)
-
- js={'success': True}
+
+ js = {'success': True}
return HttpResponse(json.dumps(js), mimetype="application/json")
+
def create_random_account(create_account_function):
def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def inner_create_random_account(request):
- post_override= {'username' : "random_" + id_generator(),
- 'email' : id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu",
- 'password' : id_generator(),
- 'location' : id_generator(size=5, chars=string.ascii_uppercase),
- 'name' : id_generator(size=5, chars=string.ascii_lowercase) + " " + id_generator(size=7, chars=string.ascii_lowercase),
- 'honor_code' : u'true',
- 'terms_of_service' : u'true',}
+ post_override = {'username': "random_" + id_generator(),
+ 'email': id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu",
+ 'password': id_generator(),
+ 'location': id_generator(size=5, chars=string.ascii_uppercase),
+ 'name': id_generator(size=5, chars=string.ascii_lowercase) + " " + id_generator(size=7, chars=string.ascii_lowercase),
+ 'honor_code': u'true',
+ 'terms_of_service': u'true', }
- return create_account_function(request, post_override = post_override)
+ return create_account_function(request, post_override=post_override)
return inner_create_random_account
if settings.GENERATE_RANDOM_USER_CREDENTIALS:
create_account = create_random_account(create_account)
+
@ensure_csrf_cookie
def activate_account(request, key):
''' When link in activation e-mail is clicked
'''
- r=Registration.objects.filter(activation_key=key)
- if len(r)==1:
+ r = Registration.objects.filter(activation_key=key)
+ if len(r) == 1:
user_logged_in = request.user.is_authenticated()
already_active = True
if not r[0].user.is_active:
r[0].activate()
already_active = False
- resp = render_to_response("registration/activation_complete.html",{'user_logged_in':user_logged_in, 'already_active' : already_active})
+ resp = render_to_response("registration/activation_complete.html", {'user_logged_in': user_logged_in, 'already_active': already_active})
return resp
- if len(r)==0:
- return render_to_response("registration/activation_invalid.html",{'csrf':csrf(request)['csrf_token']})
+ if len(r) == 0:
+ return render_to_response("registration/activation_invalid.html", {'csrf': csrf(request)['csrf_token']})
return HttpResponse("Unknown error. Please e-mail us to let us know how it happened.")
+
@ensure_csrf_cookie
def password_reset(request):
''' Attempts to send a password reset e-mail. '''
@@ -400,43 +408,44 @@ def password_reset(request):
raise Http404
form = PasswordResetForm(request.POST)
if form.is_valid():
- form.save( use_https = request.is_secure(),
- from_email = settings.DEFAULT_FROM_EMAIL,
- request = request )
- return HttpResponse(json.dumps({'success':True,
+ form.save(use_https=request.is_secure(),
+ from_email=settings.DEFAULT_FROM_EMAIL,
+ request=request)
+ return HttpResponse(json.dumps({'success': True,
'value': render_to_string('registration/password_reset_done.html', {})}))
else:
- return HttpResponse(json.dumps({'success':False,
+ return HttpResponse(json.dumps({'success': False,
'error': 'Invalid e-mail'}))
+
@ensure_csrf_cookie
def reactivation_email(request):
''' Send an e-mail to reactivate a deactivated account, or to
resend an activation e-mail. Untested. '''
email = request.POST['email']
try:
- user = User.objects.get(email = 'email')
+ user = User.objects.get(email='email')
except User.DoesNotExist:
- return HttpResponse(json.dumps({'success':False,
+ return HttpResponse(json.dumps({'success': False,
'error': 'No inactive user with this e-mail exists'}))
if user.is_active:
- return HttpResponse(json.dumps({'success':False,
+ return HttpResponse(json.dumps({'success': False,
'error': 'User is already active'}))
- reg = Registration.objects.get(user = user)
+ reg = Registration.objects.get(user=user)
reg.register(user)
- d={'name':UserProfile.get(user = user).name,
- 'key':r.activation_key}
+ d = {'name': UserProfile.get(user=user).name,
+ 'key': r.activation_key}
- subject = render_to_string('reactivation_email_subject.txt',d)
+ subject = render_to_string('reactivation_email_subject.txt', d)
subject = ''.join(subject.splitlines())
- message = render_to_string('reactivation_email.txt',d)
+ message = render_to_string('reactivation_email.txt', d)
- res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
+ res = u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
- return HttpResponse(json.dumps({'success':True}))
+ return HttpResponse(json.dumps({'success': True}))
@ensure_csrf_cookie
@@ -450,26 +459,26 @@ def change_email_request(request):
user = request.user
if not user.check_password(request.POST['password']):
- return HttpResponse(json.dumps({'success':False,
- 'error':'Invalid password'}))
+ return HttpResponse(json.dumps({'success': False,
+ 'error': 'Invalid password'}))
new_email = request.POST['new_email']
try:
validate_email(new_email)
except ValidationError:
- return HttpResponse(json.dumps({'success':False,
- 'error':'Valid e-mail address required.'}))
+ return HttpResponse(json.dumps({'success': False,
+ 'error': 'Valid e-mail address required.'}))
- if len(User.objects.filter(email = new_email)) != 0:
+ if len(User.objects.filter(email=new_email)) != 0:
## CRITICAL TODO: Handle case sensitivity for e-mails
- return HttpResponse(json.dumps({'success':False,
- 'error':'An account with this e-mail already exists.'}))
+ return HttpResponse(json.dumps({'success': False,
+ 'error': 'An account with this e-mail already exists.'}))
- pec_list = PendingEmailChange.objects.filter(user = request.user)
+ pec_list = PendingEmailChange.objects.filter(user=request.user)
if len(pec_list) == 0:
pec = PendingEmailChange()
pec.user = user
- else :
+ else:
pec = pec_list[0]
pec.new_email = request.POST['new_email']
@@ -478,20 +487,21 @@ def change_email_request(request):
if pec.new_email == user.email:
pec.delete()
- return HttpResponse(json.dumps({'success':False,
- 'error':'Old email is the same as the new email.'}))
+ return HttpResponse(json.dumps({'success': False,
+ 'error': 'Old email is the same as the new email.'}))
- d = {'key':pec.activation_key,
- 'old_email' : user.email,
- 'new_email' : pec.new_email}
+ d = {'key': pec.activation_key,
+ 'old_email': user.email,
+ 'new_email': pec.new_email}
- subject = render_to_string('emails/email_change_subject.txt',d)
+ subject = render_to_string('emails/email_change_subject.txt', d)
subject = ''.join(subject.splitlines())
- message = render_to_string('emails/email_change.txt',d)
+ message = render_to_string('emails/email_change.txt', d)
- res=send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [pec.new_email])
+ res = send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [pec.new_email])
+
+ return HttpResponse(json.dumps({'success': True}))
- return HttpResponse(json.dumps({'success':True}))
@ensure_csrf_cookie
def confirm_email_change(request, key):
@@ -499,22 +509,21 @@ def confirm_email_change(request, key):
link is clicked. We confirm with the old e-mail, and update
'''
try:
- pec=PendingEmailChange.objects.get(activation_key=key)
+ pec = PendingEmailChange.objects.get(activation_key=key)
except PendingEmailChange.DoesNotExist:
return render_to_response("invalid_email_key.html", {})
user = pec.user
- d = {'old_email' : user.email,
- 'new_email' : pec.new_email}
+ d = {'old_email': user.email,
+ 'new_email': pec.new_email}
- if len(User.objects.filter(email = pec.new_email)) != 0:
+ if len(User.objects.filter(email=pec.new_email)) != 0:
return render_to_response("email_exists.html", d)
-
- subject = render_to_string('emails/email_change_subject.txt',d)
+ subject = render_to_string('emails/email_change_subject.txt', d)
subject = ''.join(subject.splitlines())
- message = render_to_string('emails/confirm_email_change.txt',d)
- up = UserProfile.objects.get( user = user )
+ message = render_to_string('emails/confirm_email_change.txt', d)
+ up = UserProfile.objects.get(user=user)
meta = up.get_meta()
if 'old_emails' not in meta:
meta['old_emails'] = []
@@ -528,6 +537,7 @@ def confirm_email_change(request, key):
return render_to_response("email_change_successful.html", d)
+
@ensure_csrf_cookie
def change_name_request(request):
''' Log a request for a new name. '''
@@ -535,18 +545,18 @@ def change_name_request(request):
raise Http404
try:
- pnc = PendingNameChange.objects.get(user = request.user)
+ pnc = PendingNameChange.objects.get(user=request.user)
except PendingNameChange.DoesNotExist:
pnc = PendingNameChange()
pnc.user = request.user
pnc.new_name = request.POST['new_name']
pnc.rationale = request.POST['rationale']
- if len(pnc.new_name)<2:
- return HttpResponse(json.dumps({'success':False,'error':'Name required'}))
- if len(pnc.rationale)<2:
- return HttpResponse(json.dumps({'success':False,'error':'Rationale required'}))
+ if len(pnc.new_name) < 2:
+ return HttpResponse(json.dumps({'success': False, 'error': 'Name required'}))
+ if len(pnc.rationale) < 2:
+ return HttpResponse(json.dumps({'success': False, 'error': 'Rationale required'}))
pnc.save()
- return HttpResponse(json.dumps({'success':True}))
+ return HttpResponse(json.dumps({'success': True}))
@ensure_csrf_cookie
diff --git a/common/djangoapps/track/middleware.py b/common/djangoapps/track/middleware.py
index 3beabeb690..52d914aeef 100644
--- a/common/djangoapps/track/middleware.py
+++ b/common/djangoapps/track/middleware.py
@@ -4,6 +4,7 @@ from django.conf import settings
import views
+
class TrackMiddleware:
def process_request(self, request):
try:
@@ -11,34 +12,34 @@ class TrackMiddleware:
# names/passwords.
if request.META['PATH_INFO'] in ['/event', '/login']:
return
-
+
# Removes passwords from the tracking logs
# WARNING: This list needs to be changed whenever we change
- # password handling functionality.
+ # password handling functionality.
#
# As of the time of this comment, only 'password' is used
- # The rest are there for future extension.
+ # The rest are there for future extension.
#
- # Passwords should never be sent as GET requests, but
+ # Passwords should never be sent as GET requests, but
# this can happen due to older browser bugs. We censor
- # this too.
- #
+ # this too.
+ #
# We should manually confirm no passwords make it into log
- # files when we change this.
+ # files when we change this.
- censored_strings = ['password', 'newpassword', 'new_password',
+ censored_strings = ['password', 'newpassword', 'new_password',
'oldpassword', 'old_password']
post_dict = dict(request.POST)
get_dict = dict(request.GET)
- for string in censored_strings:
- if string in post_dict:
- post_dict[string] = '*'*8
- if string in get_dict:
- get_dict[string] = '*'*8
+ for string in censored_strings:
+ if string in post_dict:
+ post_dict[string] = '*' * 8
+ if string in get_dict:
+ get_dict[string] = '*' * 8
+
+ event = {'GET': dict(get_dict),
+ 'POST': dict(post_dict)}
- event = { 'GET' : dict(get_dict),
- 'POST' : dict(post_dict)}
-
# TODO: Confirm no large file uploads
event = json.dumps(event)
event = event[:512]
diff --git a/common/djangoapps/track/views.py b/common/djangoapps/track/views.py
index 6f2e0e5155..a60d8bef28 100644
--- a/common/djangoapps/track/views.py
+++ b/common/djangoapps/track/views.py
@@ -10,61 +10,64 @@ from django.conf import settings
log = logging.getLogger("tracking")
+
def log_event(event):
event_str = json.dumps(event)
log.info(event_str[:settings.TRACK_MAX_EVENT])
+
def user_track(request):
- try: # TODO: Do the same for many of the optional META parameters
+ try: # TODO: Do the same for many of the optional META parameters
username = request.user.username
- except:
+ except:
username = "anonymous"
- try:
- scookie = request.META['HTTP_COOKIE'] # Get cookies
- scookie = ";".join([c.split('=')[1] for c in scookie.split(";") if "sessionid" in c]).strip() # Extract session ID
- except:
+ try:
+ scookie = request.META['HTTP_COOKIE'] # Get cookies
+ scookie = ";".join([c.split('=')[1] for c in scookie.split(";") if "sessionid" in c]).strip() # Extract session ID
+ except:
scookie = ""
- try:
+ try:
agent = request.META['HTTP_USER_AGENT']
- except:
+ except:
agent = ''
# TODO: Move a bunch of this into log_event
event = {
- "username" : username,
- "session" : scookie,
- "ip" : request.META['REMOTE_ADDR'],
- "event_source" : "browser",
- "event_type" : request.GET['event_type'],
- "event" : request.GET['event'],
- "agent" : agent,
- "page" : request.GET['page'],
+ "username": username,
+ "session": scookie,
+ "ip": request.META['REMOTE_ADDR'],
+ "event_source": "browser",
+ "event_type": request.GET['event_type'],
+ "event": request.GET['event'],
+ "agent": agent,
+ "page": request.GET['page'],
"time": datetime.datetime.utcnow().isoformat(),
}
log_event(event)
return HttpResponse('success')
+
def server_track(request, event_type, event, page=None):
- try:
+ try:
username = request.user.username
- except:
+ except:
username = "anonymous"
- try:
+ try:
agent = request.META['HTTP_USER_AGENT']
- except:
+ except:
agent = ''
event = {
- "username" : username,
- "ip" : request.META['REMOTE_ADDR'],
- "event_source" : "server",
- "event_type" : event_type,
- "event" : event,
- "agent" : agent,
- "page" : page,
+ "username": username,
+ "ip": request.META['REMOTE_ADDR'],
+ "event_source": "server",
+ "event_type": event_type,
+ "event": event,
+ "agent": agent,
+ "page": page,
"time": datetime.datetime.utcnow().isoformat(),
}
log_event(event)
diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py
index e253b5b633..85b8ed3369 100644
--- a/common/djangoapps/util/cache.py
+++ b/common/djangoapps/util/cache.py
@@ -2,7 +2,7 @@
This module aims to give a little more fine-tuned control of caching and cache
invalidation. Import these instead of django.core.cache.
-Note that 'default' is being preserved for user session caching, which we're
+Note that 'default' is being preserved for user session caching, which we're
not migrating so as not to inconvenience users by logging them all out.
"""
from functools import wraps
@@ -16,26 +16,27 @@ try:
except Exception:
cache = cache.cache
+
def cache_if_anonymous(view_func):
"""
Many of the pages in edX are identical when the user is not logged
- in, but should not be cached when the user is logged in (because
- of the navigation bar at the top with the username).
-
+ in, but should not be cached when the user is logged in (because
+ of the navigation bar at the top with the username).
+
The django middleware cache does not handle this correctly, because
we access the session to put the csrf token in the header. This adds
the cookie to the vary header, and so every page is cached seperately
for each user (because each user has a different csrf token).
-
+
Note that this decorator should only be used on views that do not
contain the csrftoken within the html. The csrf token can be included
in the header by ordering the decorators as such:
-
+
@ensure_csrftoken
@cache_if_anonymous
def myView(request):
"""
-
+
@wraps(view_func)
def _decorated(request, *args, **kwargs):
if not request.user.is_authenticated():
@@ -45,12 +46,12 @@ def cache_if_anonymous(view_func):
if not response:
response = view_func(request, *args, **kwargs)
cache.set(cache_key, response, 60 * 3)
-
+
return response
-
+
else:
#Don't use the cache
return view_func(request, *args, **kwargs)
-
+
return _decorated
-
\ No newline at end of file
+
diff --git a/common/djangoapps/util/json_request.py b/common/djangoapps/util/json_request.py
index 4066cfc889..c2fad16d70 100644
--- a/common/djangoapps/util/json_request.py
+++ b/common/djangoapps/util/json_request.py
@@ -2,6 +2,7 @@ from functools import wraps
import copy
import json
+
def expect_json(view_function):
@wraps(view_function)
def expect_json_with_cloned_request(request, *args, **kwargs):
diff --git a/common/djangoapps/util/memcache.py b/common/djangoapps/util/memcache.py
index 3da65a1b51..540cf96539 100644
--- a/common/djangoapps/util/memcache.py
+++ b/common/djangoapps/util/memcache.py
@@ -6,11 +6,13 @@ from django.utils.encoding import smart_str
import hashlib
import urllib
+
def fasthash(string):
m = hashlib.new("md4")
m.update(string)
return m.hexdigest()
+
def safe_key(key, key_prefix, version):
safe_key = urllib.quote_plus(smart_str(key))
diff --git a/common/djangoapps/util/middleware.py b/common/djangoapps/util/middleware.py
index eeffa2668c..ce7b961766 100644
--- a/common/djangoapps/util/middleware.py
+++ b/common/djangoapps/util/middleware.py
@@ -5,12 +5,12 @@ from django.http import HttpResponseServerError
log = logging.getLogger("mitx")
+
class ExceptionLoggingMiddleware(object):
- """Just here to log unchecked exceptions that go all the way up the Django
+ """Just here to log unchecked exceptions that go all the way up the Django
stack"""
if not settings.TEMPLATE_DEBUG:
def process_exception(self, request, exception):
log.exception(exception)
return HttpResponseServerError("Server Error - Please try again later.")
-
diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py
index c1f2bb39ea..e38056934f 100644
--- a/common/djangoapps/util/views.py
+++ b/common/djangoapps/util/views.py
@@ -14,53 +14,57 @@ from mitxmako.shortcuts import render_to_response, render_to_string
import capa.calc
import track.views
+
def calculate(request):
''' Calculator in footer of every page. '''
equation = request.GET['equation']
- try:
+ try:
result = capa.calc.evaluator({}, {}, equation)
except:
- event = {'error':map(str,sys.exc_info()),
- 'equation':equation}
+ event = {'error': map(str, sys.exc_info()),
+ 'equation': equation}
track.views.server_track(request, 'error:calc', event, page='calc')
- return HttpResponse(json.dumps({'result':'Invalid syntax'}))
- return HttpResponse(json.dumps({'result':str(result)}))
+ return HttpResponse(json.dumps({'result': 'Invalid syntax'}))
+ return HttpResponse(json.dumps({'result': str(result)}))
+
def send_feedback(request):
''' Feeback mechanism in footer of every page. '''
- try:
+ try:
username = request.user.username
email = request.user.email
- except:
+ except:
username = "anonymous"
email = "anonymous"
-
- try:
- browser = request.META['HTTP_USER_AGENT']
- except:
- browser = "Unknown"
-
- feedback = render_to_string("feedback_email.txt",
- {"subject":request.POST['subject'],
- "url": request.POST['url'],
- "time": datetime.datetime.now().isoformat(),
- "feedback": request.POST['message'],
- "email":email,
- "browser":browser,
- "user":username})
- send_mail("MITx Feedback / " +request.POST['subject'],
- feedback,
+ try:
+ browser = request.META['HTTP_USER_AGENT']
+ except:
+ browser = "Unknown"
+
+ feedback = render_to_string("feedback_email.txt",
+ {"subject": request.POST['subject'],
+ "url": request.POST['url'],
+ "time": datetime.datetime.now().isoformat(),
+ "feedback": request.POST['message'],
+ "email": email,
+ "browser": browser,
+ "user": username})
+
+ send_mail("MITx Feedback / " + request.POST['subject'],
+ feedback,
settings.DEFAULT_FROM_EMAIL,
- [ settings.DEFAULT_FEEDBACK_EMAIL ],
- fail_silently = False
+ [settings.DEFAULT_FEEDBACK_EMAIL],
+ fail_silently=False
)
- return HttpResponse(json.dumps({'success':True}))
+ return HttpResponse(json.dumps({'success': True}))
+
def info(request):
''' Info page (link from main header) '''
return render_to_response("info.html", {})
+
# From http://djangosnippets.org/snippets/1042/
def parse_accept_header(accept):
"""Parse the Accept header *accept*, returning a list with pairs of
@@ -82,6 +86,7 @@ def parse_accept_header(accept):
result.sort(lambda x, y: -cmp(x[2], y[2]))
return result
+
def accepts(request, media_type):
"""Return whether this request has an Accept header that matches type"""
accept = parse_accept_header(request.META.get("HTTP_ACCEPT", ""))
diff --git a/common/lib/capa/capa/__init__.py b/common/lib/capa/capa/__init__.py
index 8b13789179..e69de29bb2 100644
--- a/common/lib/capa/capa/__init__.py
+++ b/common/lib/capa/capa/__init__.py
@@ -1 +0,0 @@
-
diff --git a/common/lib/capa/capa/calc.py b/common/lib/capa/capa/calc.py
index 42bb5c3112..7979a33d84 100644
--- a/common/lib/capa/capa/calc.py
+++ b/common/lib/capa/capa/calc.py
@@ -14,29 +14,30 @@ from pyparsing import StringEnd, Optional, Forward
from pyparsing import CaselessLiteral, Group, StringEnd
from pyparsing import NoMatch, stringEnd, alphanums
-default_functions = {'sin' : numpy.sin,
- 'cos' : numpy.cos,
- 'tan' : numpy.tan,
+default_functions = {'sin': numpy.sin,
+ 'cos': numpy.cos,
+ 'tan': numpy.tan,
'sqrt': numpy.sqrt,
- 'log10':numpy.log10,
- 'log2':numpy.log2,
+ 'log10': numpy.log10,
+ 'log2': numpy.log2,
'ln': numpy.log,
- 'arccos':numpy.arccos,
- 'arcsin':numpy.arcsin,
- 'arctan':numpy.arctan,
- 'abs':numpy.abs
+ 'arccos': numpy.arccos,
+ 'arcsin': numpy.arcsin,
+ 'arctan': numpy.arctan,
+ 'abs': numpy.abs
}
-default_variables = {'j':numpy.complex(0,1),
- 'e':numpy.e,
- 'pi':numpy.pi,
- 'k':scipy.constants.k,
- 'c':scipy.constants.c,
- 'T':298.15,
- 'q':scipy.constants.e
+default_variables = {'j': numpy.complex(0, 1),
+ 'e': numpy.e,
+ 'pi': numpy.pi,
+ 'k': scipy.constants.k,
+ 'c': scipy.constants.c,
+ 'T': 298.15,
+ 'q': scipy.constants.e
}
log = logging.getLogger("mitx.courseware.capa")
+
class UndefinedVariable(Exception):
def raiseself(self):
''' Helper so we can use inside of a lambda '''
@@ -44,28 +45,31 @@ class UndefinedVariable(Exception):
general_whitespace = re.compile('[^\w]+')
+
+
def check_variables(string, variables):
- ''' Confirm the only variables in string are defined.
+ ''' Confirm the only variables in string are defined.
Pyparsing uses a left-to-right parser, which makes the more
- elegant approach pretty hopeless.
+ elegant approach pretty hopeless.
achar = reduce(lambda a,b:a|b ,map(Literal,alphas)) # Any alphabetic character
undefined_variable = achar + Word(alphanums)
undefined_variable.setParseAction(lambda x:UndefinedVariable("".join(x)).raiseself())
varnames = varnames | undefined_variable'''
- possible_variables = re.split(general_whitespace, string) # List of all alnums in string
+ possible_variables = re.split(general_whitespace, string) # List of all alnums in string
bad_variables = list()
for v in possible_variables:
if len(v) == 0:
continue
- if v[0] <= '9' and '0' <= 'v': # Skip things that begin with numbers
+ if v[0] <= '9' and '0' <= 'v': # Skip things that begin with numbers
continue
- if v not in variables:
+ if v not in variables:
bad_variables.append(v)
- if len(bad_variables)>0:
+ if len(bad_variables) > 0:
raise UndefinedVariable(' '.join(bad_variables))
+
def evaluator(variables, functions, string, cs=False):
''' Evaluate an expression. Variables are passed as a dictionary
from string to value. Unary functions are passed as a dictionary
@@ -84,147 +88,152 @@ def evaluator(variables, functions, string, cs=False):
all_variables = copy.copy(default_variables)
all_functions = copy.copy(default_functions)
- if not cs:
+ if not cs:
all_variables = lower_dict(all_variables)
all_functions = lower_dict(all_functions)
- all_variables.update(variables)
+ all_variables.update(variables)
all_functions.update(functions)
-
- if not cs:
+
+ if not cs:
string_cs = string.lower()
all_functions = lower_dict(all_functions)
all_variables = lower_dict(all_variables)
- CasedLiteral = CaselessLiteral
+ CasedLiteral = CaselessLiteral
else:
string_cs = string
CasedLiteral = Literal
- check_variables(string_cs, set(all_variables.keys()+all_functions.keys()))
+ check_variables(string_cs, set(all_variables.keys() + all_functions.keys()))
if string.strip() == "":
return float('nan')
- ops = { "^" : operator.pow,
- "*" : operator.mul,
- "/" : operator.truediv,
- "+" : operator.add,
- "-" : operator.sub,
+ ops = {"^": operator.pow,
+ "*": operator.mul,
+ "/": operator.truediv,
+ "+": operator.add,
+ "-": operator.sub,
}
# We eliminated extreme ones, since they're rarely used, and potentially
- # confusing. They may also conflict with variables if we ever allow e.g.
+ # confusing. They may also conflict with variables if we ever allow e.g.
# 5R instead of 5*R
- suffixes={'%':0.01,'k':1e3,'M':1e6,'G':1e9,
- 'T':1e12,#'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
- 'c':1e-2,'m':1e-3,'u':1e-6,
- 'n':1e-9,'p':1e-12}#,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
-
+ suffixes = {'%': 0.01, 'k': 1e3, 'M': 1e6, 'G': 1e9,
+ 'T': 1e12,# 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
+ 'c': 1e-2, 'm': 1e-3, 'u': 1e-6,
+ 'n': 1e-9, 'p': 1e-12}# ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
+
def super_float(text):
''' Like float, but with si extensions. 1k goes to 1000'''
if text[-1] in suffixes:
- return float(text[:-1])*suffixes[text[-1]]
+ return float(text[:-1]) * suffixes[text[-1]]
else:
return float(text)
- def number_parse_action(x): # [ '7' ] -> [ 7 ]
+ def number_parse_action(x): # [ '7' ] -> [ 7 ]
return [super_float("".join(x))]
- def exp_parse_action(x): # [ 2 ^ 3 ^ 2 ] -> 512
- x = [e for e in x if isinstance(e, numbers.Number)] # Ignore ^
+
+ def exp_parse_action(x): # [ 2 ^ 3 ^ 2 ] -> 512
+ x = [e for e in x if isinstance(e, numbers.Number)] # Ignore ^
x.reverse()
- x=reduce(lambda a,b:b**a, x)
+ x = reduce(lambda a, b: b ** a, x)
return x
- def parallel(x): # Parallel resistors [ 1 2 ] => 2/3
+
+ def parallel(x): # Parallel resistors [ 1 2 ] => 2/3
if len(x) == 1:
return x[0]
if 0 in x:
return float('nan')
- x = [1./e for e in x if isinstance(e, numbers.Number)] # Ignore ||
- return 1./sum(x)
- def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
+ x = [1. / e for e in x if isinstance(e, numbers.Number)] # Ignore ||
+ return 1. / sum(x)
+
+ def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
total = 0.0
op = ops['+']
for e in x:
if e in set('+-'):
op = ops[e]
else:
- total=op(total, e)
+ total = op(total, e)
return total
- def prod_parse_action(x): # [ 1 * 2 / 3 ] => 0.66
+
+ def prod_parse_action(x): # [ 1 * 2 / 3 ] => 0.66
prod = 1.0
op = ops['*']
for e in x:
if e in set('*/'):
op = ops[e]
else:
- prod=op(prod, e)
+ prod = op(prod, e)
return prod
+
def func_parse_action(x):
return [all_functions[x[0]](x[1])]
- number_suffix=reduce(lambda a,b:a|b, map(Literal,suffixes.keys()), NoMatch()) # SI suffixes and percent
- (dot,minus,plus,times,div,lpar,rpar,exp)=map(Literal,".-+*/()^")
-
- number_part=Word(nums)
- inner_number = ( number_part+Optional("."+number_part) ) | ("."+number_part) # 0.33 or 7 or .34
- number=Optional(minus | plus)+ inner_number + \
- Optional(CaselessLiteral("E")+Optional("-")+number_part)+ \
- Optional(number_suffix) # 0.33k or -17
- number=number.setParseAction( number_parse_action ) # Convert to number
-
+ number_suffix = reduce(lambda a, b: a | b, map(Literal, suffixes.keys()), NoMatch()) # SI suffixes and percent
+ (dot, minus, plus, times, div, lpar, rpar, exp) = map(Literal, ".-+*/()^")
+
+ number_part = Word(nums)
+ inner_number = (number_part + Optional("." + number_part)) | ("." + number_part) # 0.33 or 7 or .34
+ number = Optional(minus | plus) + inner_number + \
+ Optional(CaselessLiteral("E") + Optional("-") + number_part) + \
+ Optional(number_suffix) # 0.33k or -17
+ number = number.setParseAction(number_parse_action) # Convert to number
+
# Predefine recursive variables
- expr = Forward()
+ expr = Forward()
factor = Forward()
-
+
def sreduce(f, l):
''' Same as reduce, but handle len 1 and len 0 lists sensibly '''
- if len(l)==0:
+ if len(l) == 0:
return NoMatch()
- if len(l)==1:
+ if len(l) == 1:
return l[0]
return reduce(f, l)
- # Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution.
+ # Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution.
# Special case for no variables because of how we understand PyParsing is put together
- if len(all_variables)>0:
- # We sort the list so that var names (like "e2") match before
+ if len(all_variables) > 0:
+ # We sort the list so that var names (like "e2") match before
# mathematical constants (like "e"). This is kind of a hack.
all_variables_keys = sorted(all_variables.keys(), key=len, reverse=True)
- varnames = sreduce(lambda x,y:x|y, map(lambda x: CasedLiteral(x), all_variables_keys))
- varnames.setParseAction(lambda x:map(lambda y:all_variables[y], x))
+ varnames = sreduce(lambda x, y: x | y, map(lambda x: CasedLiteral(x), all_variables_keys))
+ varnames.setParseAction(lambda x: map(lambda y: all_variables[y], x))
else:
- varnames=NoMatch()
- # Same thing for functions.
- if len(all_functions)>0:
- funcnames = sreduce(lambda x,y:x|y, map(lambda x: CasedLiteral(x), all_functions.keys()))
- function = funcnames+lpar.suppress()+expr+rpar.suppress()
+ varnames = NoMatch()
+ # Same thing for functions.
+ if len(all_functions) > 0:
+ funcnames = sreduce(lambda x, y: x | y, map(lambda x: CasedLiteral(x), all_functions.keys()))
+ function = funcnames + lpar.suppress() + expr + rpar.suppress()
function.setParseAction(func_parse_action)
else:
function = NoMatch()
- atom = number | function | varnames | lpar+expr+rpar
- factor << (atom + ZeroOrMore(exp+atom)).setParseAction(exp_parse_action) # 7^6
- paritem = factor + ZeroOrMore(Literal('||')+factor) # 5k || 4k
- paritem=paritem.setParseAction(parallel)
- term = paritem + ZeroOrMore((times|div)+paritem) # 7 * 5 / 4 - 3
+ atom = number | function | varnames | lpar + expr + rpar
+ factor << (atom + ZeroOrMore(exp + atom)).setParseAction(exp_parse_action) # 7^6
+ paritem = factor + ZeroOrMore(Literal('||') + factor) # 5k || 4k
+ paritem = paritem.setParseAction(parallel)
+ term = paritem + ZeroOrMore((times | div) + paritem) # 7 * 5 / 4 - 3
term = term.setParseAction(prod_parse_action)
- expr << Optional((plus|minus)) + term + ZeroOrMore((plus|minus)+term) # -5 + 4 - 3
- expr=expr.setParseAction(sum_parse_action)
- return (expr+stringEnd).parseString(string)[0]
+ expr << Optional((plus | minus)) + term + ZeroOrMore((plus | minus) + term) # -5 + 4 - 3
+ expr = expr.setParseAction(sum_parse_action)
+ return (expr + stringEnd).parseString(string)[0]
-if __name__=='__main__':
- variables={'R1':2.0, 'R3':4.0}
- functions={'sin':numpy.sin, 'cos':numpy.cos}
- print "X",evaluator(variables, functions, "10000||sin(7+5)-6k")
- print "X",evaluator(variables, functions, "13")
- print evaluator({'R1': 2.0, 'R3':4.0}, {}, "13")
+if __name__ == '__main__':
+ variables = {'R1': 2.0, 'R3': 4.0}
+ functions = {'sin': numpy.sin, 'cos': numpy.cos}
+ print "X", evaluator(variables, functions, "10000||sin(7+5)-6k")
+ print "X", evaluator(variables, functions, "13")
+ print evaluator({'R1': 2.0, 'R3': 4.0}, {}, "13")
- print evaluator({'e1':1,'e2':1.0,'R3':7,'V0':5,'R5':15,'I1':1,'R4':6}, {},"e2")
+ print evaluator({'e1': 1, 'e2': 1.0, 'R3': 7, 'V0': 5, 'R5': 15, 'I1': 1, 'R4': 6}, {}, "e2")
print evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5")
- print evaluator({},{}, "-1")
- print evaluator({},{}, "-(7+5)")
- print evaluator({},{}, "-0.33")
- print evaluator({},{}, "-.33")
- print evaluator({},{}, "5+1*j")
- print evaluator({},{}, "j||1")
- print evaluator({},{}, "e^(j*pi)")
- print evaluator({},{}, "5+7 QWSEKO")
+ print evaluator({}, {}, "-1")
+ print evaluator({}, {}, "-(7+5)")
+ print evaluator({}, {}, "-0.33")
+ print evaluator({}, {}, "-.33")
+ print evaluator({}, {}, "5+1*j")
+ print evaluator({}, {}, "j||1")
+ print evaluator({}, {}, "e^(j*pi)")
+ print evaluator({}, {}, "5+7 QWSEKO")
diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py
index 5ccb98f090..13ab9d9eb1 100644
--- a/common/lib/capa/capa/capa_problem.py
+++ b/common/lib/capa/capa/capa_problem.py
@@ -37,7 +37,7 @@ from util import contextualize_text
import responsetypes
# dict of tagname, Response Class -- this should come from auto-registering
-response_tag_dict = dict([(x.response_tag,x) for x in responsetypes.__all__])
+response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__])
entry_types = ['textline', 'schematic', 'textbox', 'imageinput', 'optioninput', 'choicegroup', 'radiogroup', 'checkboxgroup']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
@@ -57,13 +57,14 @@ global_context = {'random': random,
'eia': eia}
# These should be removed from HTML output, including all subelements
-html_problem_semantics = ["responseparam", "answer", "script","hintgroup"]
+html_problem_semantics = ["responseparam", "answer", "script", "hintgroup"]
log = logging.getLogger('mitx.' + __name__)
#-----------------------------------------------------------------------------
# main class for this module
+
class LoncapaProblem(object):
'''
Main class for capa Problems.
@@ -79,7 +80,7 @@ class LoncapaProblem(object):
- id (string): identifier for this problem; often a filename (no spaces)
- state (dict): student state
- seed (int): random number generator seed (int)
- - system (I4xSystem): I4xSystem instance which provides OS, rendering, and user context
+ - system (I4xSystem): I4xSystem instance which provides OS, rendering, and user context
'''
@@ -118,7 +119,7 @@ class LoncapaProblem(object):
# the dict has keys = xml subtree of Response, values = Response instance
self._preprocess_problem(self.tree)
- if not self.student_answers: # True when student_answers is an empty dict
+ if not self.student_answers: # True when student_answers is an empty dict
self.set_initial_display()
def do_reset(self):
@@ -132,7 +133,7 @@ class LoncapaProblem(object):
def set_initial_display(self):
initial_answers = dict()
for responder in self.responders.values():
- if hasattr(responder,'get_initial_display'):
+ if hasattr(responder, 'get_initial_display'):
initial_answers.update(responder.get_initial_display())
self.student_answers = initial_answers
@@ -160,11 +161,11 @@ class LoncapaProblem(object):
'''
maxscore = 0
for response, responder in self.responders.iteritems():
- if hasattr(responder,'get_max_score'):
+ if hasattr(responder, 'get_max_score'):
try:
maxscore += responder.get_max_score()
except Exception:
- log.debug('responder %s failed to properly return from get_max_score()' % responder) # FIXME
+ log.debug('responder %s failed to properly return from get_max_score()' % responder) # FIXME
raise
else:
maxscore += len(self.responder_answers[response])
@@ -181,7 +182,7 @@ class LoncapaProblem(object):
try:
correct += self.correct_map.get_npoints(key)
except Exception:
- log.error('key=%s, correct_map = %s' % (key,self.correct_map))
+ log.error('key=%s, correct_map = %s' % (key, self.correct_map))
raise
if (not self.student_answers) or len(self.student_answers) == 0:
@@ -193,19 +194,19 @@ class LoncapaProblem(object):
def update_score(self, score_msg, queuekey):
'''
- Deliver grading response (e.g. from async code checking) to
- the specific ResponseType that requested grading
-
+ Deliver grading response (e.g. from async code checking) to
+ the specific ResponseType that requested grading
+
Returns an updated CorrectMap
'''
cmap = CorrectMap()
cmap.update(self.correct_map)
for responder in self.responders.values():
- if hasattr(responder,'update_score'):
+ if hasattr(responder, 'update_score'):
# Each LoncapaResponse will update the specific entries of 'cmap' that it's responsible for
cmap = responder.update_score(score_msg, cmap, queuekey)
self.correct_map.set_dict(cmap.get_dict())
- return cmap
+ return cmap
def is_queued(self):
'''
@@ -232,7 +233,7 @@ class LoncapaProblem(object):
newcmap = CorrectMap() # start new with empty CorrectMap
# log.debug('Responders: %s' % self.responders)
for responder in self.responders.values():
- results = responder.evaluate_answers(answers,oldcmap) # call the responsetype instance to do the actual grading
+ results = responder.evaluate_answers(answers, oldcmap) # call the responsetype instance to do the actual grading
newcmap.update(results)
self.correct_map = newcmap
# log.debug('%s: in grade_answers, answers=%s, cmap=%s' % (self,answers,newcmap))
@@ -285,23 +286,23 @@ class LoncapaProblem(object):
file = inc.get('file')
if file is not None:
try:
- ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore
+ ifp = self.system.filestore.open(file) # open using I4xSystem OSFS filestore
except Exception as err:
- log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
- log.error('Cannot find file %s in %s' % (file,self.system.filestore))
+ log.error('Error %s in problem xml include: %s' % (err, etree.tostring(inc, pretty_print=True)))
+ log.error('Cannot find file %s in %s' % (file, self.system.filestore))
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
raise
else: continue
try:
incxml = etree.XML(ifp.read()) # read in and convert to XML
except Exception as err:
- log.error('Error %s in problem xml include: %s' % (err,etree.tostring(inc,pretty_print=True)))
+ log.error('Error %s in problem xml include: %s' % (err, etree.tostring(inc, pretty_print=True)))
log.error('Cannot parse XML in %s' % (file))
if not self.system.get('DEBUG'): # if debugging, don't fail - just log error
raise
else: continue
parent = inc.getparent() # insert new XML into tree in place of inlcude
- parent.insert(parent.index(inc),incxml)
+ parent.insert(parent.index(inc), incxml)
parent.remove(inc)
log.debug('Included %s into %s' % (file, self.problem_id))
@@ -330,9 +331,9 @@ class LoncapaProblem(object):
abs_dir = os.path.normpath(dir)
log.debug("appending to path: %s" % abs_dir)
path.append(abs_dir)
-
+
return path
-
+
def _extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private
'''
Extract content of from the problem.xml file, and exec it in the
@@ -353,7 +354,7 @@ class LoncapaProblem(object):
return context
def _execute_scripts(self, scripts, context):
- '''
+ '''
Executes scripts in the given context.
'''
original_path = sys.path
@@ -391,7 +392,7 @@ class LoncapaProblem(object):
Used by get_html.
'''
- if problemtree.tag=='script' and problemtree.get('type') and 'javascript' in problemtree.get('type'):
+ if problemtree.tag == 'script' and problemtree.get('type') and 'javascript' in problemtree.get('type'):
# leave javascript intact.
return problemtree
@@ -424,8 +425,8 @@ class LoncapaProblem(object):
'status': status,
'id': problemtree.get('id'),
'feedback': {'message': msg,
- 'hint' : hint,
- 'hintmode' : hintmode,
+ 'hint': hint,
+ 'hintmode': hintmode,
}
},
use='capa_input')
@@ -443,7 +444,7 @@ class LoncapaProblem(object):
if tree.tag in html_transforms:
tree.tag = html_transforms[problemtree.tag]['tag']
else:
- for (key, value) in problemtree.items(): # copy attributes over if not innocufying
+ for (key, value) in problemtree.items(): # copy attributes over if not innocufying
tree.set(key, value)
tree.text = problemtree.text
@@ -466,7 +467,7 @@ class LoncapaProblem(object):
self.responders = {}
for response in tree.xpath('//' + "|//".join(response_tag_dict)):
response_id_str = self.problem_id + "_" + str(response_id)
- response.set('id',response_id_str) # create and save ID for this response
+ response.set('id', response_id_str) # create and save ID for this response
response_id += 1
answer_id = 1
@@ -478,7 +479,7 @@ class LoncapaProblem(object):
entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id)
answer_id = answer_id + 1
- responder = response_tag_dict[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response
+ responder = response_tag_dict[response.tag](response, inputfields, self.context, self.system) # instantiate capa Response
self.responders[response] = responder # save in list in self
# get responder answers (do this only once, since there may be a performance cost, eg with externalresponse)
@@ -487,9 +488,9 @@ class LoncapaProblem(object):
try:
self.responder_answers[response] = self.responders[response].get_answers()
except:
- log.debug('responder %s failed to properly return get_answers()' % self.responders[response]) # FIXME
+ log.debug('responder %s failed to properly return get_answers()' % self.responders[response]) # FIXME
raise
-
+
# ... may not be associated with any specific response; give IDs for those separately
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
solution_id = 1
diff --git a/common/lib/capa/capa/checker.py b/common/lib/capa/capa/checker.py
index 2139035d2a..f583a5ea7d 100755
--- a/common/lib/capa/capa/checker.py
+++ b/common/lib/capa/capa/checker.py
@@ -34,9 +34,10 @@ class DemoSystem(object):
context_dict.update(context)
return self.lookup.get_template(template_filename).render(**context_dict)
+
def main():
parser = argparse.ArgumentParser(description='Check Problem Files')
- parser.add_argument("command", choices=['test', 'show']) # Watch? Render? Open?
+ parser.add_argument("command", choices=['test', 'show']) # Watch? Render? Open?
parser.add_argument("files", nargs="+", type=argparse.FileType('r'))
parser.add_argument("--seed", required=False, type=int)
parser.add_argument("--log-level", required=False, default="INFO",
@@ -67,13 +68,14 @@ def main():
# In case we want to do anything else here.
+
def command_show(problem):
"""Display the text for this problem"""
print problem.get_html()
-
+
def command_test(problem):
- # We're going to trap stdout/stderr from the problems (yes, some print)
+ # We're going to trap stdout/stderr from the problems (yes, some print)
old_stdout, old_stderr = sys.stdout, sys.stderr
try:
sys.stdout = StringIO()
@@ -82,7 +84,7 @@ def command_test(problem):
check_that_suggested_answers_work(problem)
check_that_blanks_fail(problem)
- log_captured_output(sys.stdout,
+ log_captured_output(sys.stdout,
"captured stdout from {0}".format(problem))
log_captured_output(sys.stderr,
"captured stderr from {0}".format(problem))
@@ -91,9 +93,10 @@ def command_test(problem):
finally:
sys.stdout, sys.stderr = old_stdout, old_stderr
+
def check_that_blanks_fail(problem):
"""Leaving it blank should never work. Neither should a space."""
- blank_answers = dict((answer_id, u"")
+ blank_answers = dict((answer_id, u"")
for answer_id in problem.get_question_answers())
grading_results = problem.grade_answers(blank_answers)
try:
@@ -113,7 +116,7 @@ def check_that_suggested_answers_work(problem):
* Displayed answers use units but acceptable ones do not.
- L1e0.xml
- Presents itself as UndefinedVariable (when it tries to pass to calc)
- * "a or d" is what's displayed, but only "a" or "d" is accepted, not the
+ * "a or d" is what's displayed, but only "a" or "d" is accepted, not the
string "a or d".
- L1-e00.xml
"""
@@ -129,14 +132,14 @@ def check_that_suggested_answers_work(problem):
log.debug("Real answers: {0}".format(real_answers))
if real_answers:
try:
- real_results = dict((answer_id, result) for answer_id, result
+ real_results = dict((answer_id, result) for answer_id, result
in problem.grade_answers(all_answers).items()
if answer_id in real_answers)
log.debug(real_results)
assert(all(result == 'correct'
for answer_id, result in real_results.items()))
except UndefinedVariable as uv_exc:
- log.error("The variable \"{0}\" specified in the ".format(uv_exc) +
+ log.error("The variable \"{0}\" specified in the ".format(uv_exc) +
"solution isn't recognized (is it a units measure?).")
except AssertionError:
log.error("The following generated answers were not accepted for {0}:"
@@ -148,6 +151,7 @@ def check_that_suggested_answers_work(problem):
log.error("Uncaught error in {0}".format(problem))
log.exception(ex)
+
def log_captured_output(output_stream, stream_name):
output_stream.seek(0)
output_text = output_stream.read()
diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py
index 11c5bb75f1..c727626a33 100644
--- a/common/lib/capa/capa/correctmap.py
+++ b/common/lib/capa/capa/correctmap.py
@@ -3,6 +3,7 @@
#
# Used by responsetypes and capa_problem
+
class CorrectMap(object):
'''
Stores map between answer_id and response evaluation result for each question
@@ -18,11 +19,11 @@ class CorrectMap(object):
Behaves as a dict.
'''
- def __init__(self,*args,**kwargs):
+ def __init__(self, *args, **kwargs):
self.cmap = dict() # start with empty dict
self.items = self.cmap.items
self.keys = self.cmap.keys
- self.set(*args,**kwargs)
+ self.set(*args, **kwargs)
def __getitem__(self, *args, **kwargs):
return self.cmap.__getitem__(*args, **kwargs)
@@ -35,9 +36,9 @@ class CorrectMap(object):
self.cmap[answer_id] = {'correctness': correctness,
'npoints': npoints,
'msg': msg,
- 'hint' : hint,
- 'hintmode' : hintmode,
- 'queuekey' : queuekey,
+ 'hint': hint,
+ 'hintmode': hintmode,
+ 'queuekey': queuekey,
}
def __repr__(self):
@@ -49,69 +50,69 @@ class CorrectMap(object):
'''
return self.cmap
- def set_dict(self,correct_map):
+ def set_dict(self, correct_map):
'''
set internal dict to provided correct_map dict
for graceful migration, if correct_map is a one-level dict, then convert it to the new
dict of dicts format.
'''
- if correct_map and not (type(correct_map[correct_map.keys()[0]])==dict):
+ if correct_map and not (type(correct_map[correct_map.keys()[0]]) == dict):
self.__init__() # empty current dict
- for k in correct_map: self.set(k,correct_map[k]) # create new dict entries
+ for k in correct_map: self.set(k, correct_map[k]) # create new dict entries
else:
self.cmap = correct_map
- def is_correct(self,answer_id):
+ def is_correct(self, answer_id):
if answer_id in self.cmap: return self.cmap[answer_id]['correctness'] == 'correct'
return None
- def is_queued(self,answer_id):
+ def is_queued(self, answer_id):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] is not None
def is_right_queuekey(self, answer_id, test_key):
return answer_id in self.cmap and self.cmap[answer_id]['queuekey'] == test_key
- def get_npoints(self,answer_id):
+ def get_npoints(self, answer_id):
if self.is_correct(answer_id):
- npoints = self.cmap[answer_id].get('npoints',1) # default to 1 point if correct
+ npoints = self.cmap[answer_id].get('npoints', 1) # default to 1 point if correct
return npoints or 1
return 0 # if not correct, return 0
- def set_property(self,answer_id,property,value):
+ def set_property(self, answer_id, property, value):
if answer_id in self.cmap: self.cmap[answer_id][property] = value
- else: self.cmap[answer_id] = {property:value}
+ else: self.cmap[answer_id] = {property: value}
- def get_property(self,answer_id,property,default=None):
- if answer_id in self.cmap: return self.cmap[answer_id].get(property,default)
+ def get_property(self, answer_id, property, default=None):
+ if answer_id in self.cmap: return self.cmap[answer_id].get(property, default)
return default
- def get_correctness(self,answer_id):
- return self.get_property(answer_id,'correctness')
+ def get_correctness(self, answer_id):
+ return self.get_property(answer_id, 'correctness')
- def get_msg(self,answer_id):
- return self.get_property(answer_id,'msg','')
+ def get_msg(self, answer_id):
+ return self.get_property(answer_id, 'msg', '')
- def get_hint(self,answer_id):
- return self.get_property(answer_id,'hint','')
+ def get_hint(self, answer_id):
+ return self.get_property(answer_id, 'hint', '')
- def get_hintmode(self,answer_id):
- return self.get_property(answer_id,'hintmode',None)
+ def get_hintmode(self, answer_id):
+ return self.get_property(answer_id, 'hintmode', None)
- def set_hint_and_mode(self,answer_id,hint,hintmode):
+ def set_hint_and_mode(self, answer_id, hint, hintmode):
'''
- hint : (string) HTML text for hint
- hintmode : (string) mode for hint display ('always' or 'on_request')
'''
- self.set_property(answer_id,'hint',hint)
- self.set_property(answer_id,'hintmode',hintmode)
+ self.set_property(answer_id, 'hint', hint)
+ self.set_property(answer_id, 'hintmode', hintmode)
- def update(self,other_cmap):
+ def update(self, other_cmap):
'''
Update this CorrectMap with the contents of another CorrectMap
'''
- if not isinstance(other_cmap,CorrectMap):
+ if not isinstance(other_cmap, CorrectMap):
raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap)
self.cmap.update(other_cmap.get_dict())
-
+
diff --git a/common/lib/capa/capa/eia.py b/common/lib/capa/capa/eia.py
index 362dc33a2d..b41f205576 100644
--- a/common/lib/capa/capa/eia.py
+++ b/common/lib/capa/capa/eia.py
@@ -1,10 +1,9 @@
""" Standard resistor codes.
http://en.wikipedia.org/wiki/Electronic_color_code
"""
-E6=[10,15,22,33,47,68]
-E12=[10,12,15,18,22,27,33,39,47,56,68,82]
-E24=[10,12,15,18,22,27,33,39,47,56,68,82,11,13,16,20,24,30,36,43,51,62,75,91]
-E48=[100,121,147,178,215,261,316,383,464,562,681,825,105,127,154,187,226,274,332,402,487,590,715,866,110,133,162,196,237,287,348,422,511,619,750,909,115,140,169,205,249,301,365,442,536,649,787,953]
-E96=[100,121,147,178,215,261,316,383,464,562,681,825,102,124,150,182,221,267,324,392,475,576,698,845,105,127,154,187,226,274,332,402,487,590,715,866,107,130,158,191,232,280,340,412,499,604,732,887,110,133,162,196,237,287,348,422,511,619,750,909,113,137,165,200,243,294,357,432,523,634,768,931,115,140,169,205,249,301,365,442,536,649,787,953,118,143,174,210,255,309,374,453,549,665,806,976]
-E192=[100,121,147,178,215,261,316,383,464,562,681,825,101,123,149,180,218,264,320,388,470,569,690,835,102,124,150,182,221,267,324,392,475,576,698,845,104,126,152,184,223,271,328,397,481,583,706,856,105,127,154,187,226,274,332,402,487,590,715,866,106,129,156,189,229,277,336,407,493,597,723,876,107,130,158,191,232,280,340,412,499,604,732,887,109,132,160,193,234,284,344,417,505,612,741,898,110,133,162,196,237,287,348,422,511,619,750,909,111,135,164,198,240,291,352,427,517,626,759,920,113,137,165,200,243,294,357,432,523,634,768,931,114,138,167,203,246,298,361,437,530,642,777,942,115,140,169,205,249,301,365,442,536,649,787,953,117,142,172,208,252,305,370,448,542,657,796,965,118,143,174,210,255,309,374,453,549,665,806,976,120,145,176,213,258,312,379,459,556,673,816,988]
-
+E6 = [10, 15, 22, 33, 47, 68]
+E12 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82]
+E24 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82, 11, 13, 16, 20, 24, 30, 36, 43, 51, 62, 75, 91]
+E48 = [100, 121, 147, 178, 215, 261, 316, 383, 464, 562, 681, 825, 105, 127, 154, 187, 226, 274, 332, 402, 487, 590, 715, 866, 110, 133, 162, 196, 237, 287, 348, 422, 511, 619, 750, 909, 115, 140, 169, 205, 249, 301, 365, 442, 536, 649, 787, 953]
+E96 = [100, 121, 147, 178, 215, 261, 316, 383, 464, 562, 681, 825, 102, 124, 150, 182, 221, 267, 324, 392, 475, 576, 698, 845, 105, 127, 154, 187, 226, 274, 332, 402, 487, 590, 715, 866, 107, 130, 158, 191, 232, 280, 340, 412, 499, 604, 732, 887, 110, 133, 162, 196, 237, 287, 348, 422, 511, 619, 750, 909, 113, 137, 165, 200, 243, 294, 357, 432, 523, 634, 768, 931, 115, 140, 169, 205, 249, 301, 365, 442, 536, 649, 787, 953, 118, 143, 174, 210, 255, 309, 374, 453, 549, 665, 806, 976]
+E192 = [100, 121, 147, 178, 215, 261, 316, 383, 464, 562, 681, 825, 101, 123, 149, 180, 218, 264, 320, 388, 470, 569, 690, 835, 102, 124, 150, 182, 221, 267, 324, 392, 475, 576, 698, 845, 104, 126, 152, 184, 223, 271, 328, 397, 481, 583, 706, 856, 105, 127, 154, 187, 226, 274, 332, 402, 487, 590, 715, 866, 106, 129, 156, 189, 229, 277, 336, 407, 493, 597, 723, 876, 107, 130, 158, 191, 232, 280, 340, 412, 499, 604, 732, 887, 109, 132, 160, 193, 234, 284, 344, 417, 505, 612, 741, 898, 110, 133, 162, 196, 237, 287, 348, 422, 511, 619, 750, 909, 111, 135, 164, 198, 240, 291, 352, 427, 517, 626, 759, 920, 113, 137, 165, 200, 243, 294, 357, 432, 523, 634, 768, 931, 114, 138, 167, 203, 246, 298, 361, 437, 530, 642, 777, 942, 115, 140, 169, 205, 249, 301, 365, 442, 536, 649, 787, 953, 117, 142, 172, 208, 252, 305, 370, 448, 542, 657, 796, 965, 118, 143, 174, 210, 255, 309, 374, 453, 549, 665, 806, 976, 120, 145, 176, 213, 258, 312, 379, 459, 556, 673, 816, 988]
diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index d809f98ed2..3acd4e7858 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -26,37 +26,39 @@ Each input type takes the xml tree as 'element', the previous answer as 'value',
import logging
import re
-import shlex # for splitting quoted strings
+import shlex # for splitting quoted strings
from lxml import etree
import xml.sax.saxutils as saxutils
log = logging.getLogger('mitx.' + __name__)
+
def get_input_xml_tags():
''' Eventually, this will be for all registered input types '''
return SimpleInput.get_xml_tags()
-class SimpleInput():# XModule
+
+class SimpleInput():# XModule
'''
Type for simple inputs -- plain HTML with a form element
'''
- xml_tags = {} ## Maps tags to functions
+ xml_tags = {} # # Maps tags to functions
- def __init__(self, system, xml, item_id = None, track_url=None, state=None, use = 'capa_input'):
+ def __init__(self, system, xml, item_id=None, track_url=None, state=None, use='capa_input'):
'''
Instantiate a SimpleInput class. Arguments:
- - system : I4xSystem instance which provides OS, rendering, and user context
+ - system : I4xSystem instance which provides OS, rendering, and user context
- xml : Element tree of this Input element
- item_id : id for this input element (assigned by capa_problem.LoncapProblem) - string
- track_url : URL used for tracking - string
- - state : a dictionary with optional keys:
+ - state : a dictionary with optional keys:
* Value
* ID
* Status (answered, unanswered, unsubmitted)
- * Feedback (dictionary containing keys for hints, errors, or other
+ * Feedback (dictionary containing keys for hints, errors, or other
feedback from previous attempt)
- use :
'''
@@ -66,11 +68,11 @@ class SimpleInput():# XModule
self.system = system
if not state: state = {}
- ## ID should only come from one place.
+ ## ID should only come from one place.
## If it comes from multiple, we use state first, XML second, and parameter
- ## third. Since we don't make this guarantee, we can swap this around in
- ## the future if there's a more logical order.
- if item_id: self.id = item_id
+ ## third. Since we don't make this guarantee, we can swap this around in
+ ## the future if there's a more logical order.
+ if item_id: self.id = item_id
if xml.get('id'): self.id = xml.get('id')
if 'id' in state: self.id = state['id']
@@ -81,14 +83,14 @@ class SimpleInput():# XModule
self.msg = ''
feedback = state.get('feedback')
if feedback is not None:
- self.msg = feedback.get('message','')
- self.hint = feedback.get('hint','')
- self.hintmode = feedback.get('hintmode',None)
-
+ self.msg = feedback.get('message', '')
+ self.hint = feedback.get('hint', '')
+ self.hintmode = feedback.get('hintmode', None)
+
# put hint above msg if to be displayed
if self.hintmode == 'always':
self.msg = self.hint + ('
' if self.msg else '') + self.msg
-
+
self.status = 'unanswered'
if 'status' in state:
self.status = state['status']
@@ -104,17 +106,20 @@ class SimpleInput():# XModule
def get_html(self):
return self.xml_tags[self.tag](self.xml, self.value, self.status, self.system.render_template, self.msg)
+
def register_render_function(fn, names=None, cls=SimpleInput):
if names is None:
SimpleInput.xml_tags[fn.__name__] = fn
else:
raise NotImplementedError
+
def wrapped():
return fn
return wrapped
#-----------------------------------------------------------------------------
+
@register_render_function
def optioninput(element, value, status, render_template, msg=''):
'''
@@ -124,7 +129,7 @@ def optioninput(element, value, status, render_template, msg=''):
The location of the sky
'''
- eid=element.get('id')
+ eid = element.get('id')
options = element.get('options')
if not options:
raise Exception("[courseware.capa.inputtypes.optioninput] Missing options specification in " + etree.tostring(element))
@@ -134,14 +139,14 @@ def optioninput(element, value, status, render_template, msg=''):
oset = [x[1:-1] for x in list(oset)]
# osetdict = dict([('option_%s_%s' % (eid,x),oset[x]) for x in range(len(oset)) ]) # make dict with IDs
- osetdict = [(oset[x],oset[x]) for x in range(len(oset)) ] # make ordered list with (key,value) same
+ osetdict = [(oset[x], oset[x]) for x in range(len(oset))] # make ordered list with (key,value) same
# TODO: allow ordering to be randomized
-
- context={'id':eid,
- 'value':value,
- 'state':status,
- 'msg':msg,
- 'options':osetdict,
+
+ context = {'id': eid,
+ 'value': value,
+ 'state': status,
+ 'msg': msg,
+ 'options': osetdict,
}
html = render_template("optioninput.html", context)
@@ -149,6 +154,7 @@ def optioninput(element, value, status, render_template, msg=''):
#-----------------------------------------------------------------------------
+
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
@@ -159,23 +165,23 @@ def choicegroup(element, value, status, render_template, msg=''):
TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute,
ie random, top, bottom.
'''
- eid=element.get('id')
+ eid = element.get('id')
if element.get('type') == "MultipleChoice":
- type="radio"
+ type = "radio"
elif element.get('type') == "TrueFalse":
- type="checkbox"
+ type = "checkbox"
else:
- type="radio"
- choices=[]
+ type = "radio"
+ choices = []
for choice in element:
- if not choice.tag=='choice':
+ if not choice.tag == 'choice':
raise Exception("[courseware.capa.inputtypes.choicegroup] Error only tags should be immediate children of a , found %s instead" % choice.tag)
ctext = ""
- ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it?
+ ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it?
if choice.text is not None:
ctext += choice.text # TODO: fix order?
- choices.append((choice.get("name"),ctext))
- context={'id':eid, 'value':value, 'state':status, 'input_type':type, 'choices':choices, 'inline':True, 'name_array_suffix':''}
+ choices.append((choice.get("name"), ctext))
+ context = {'id': eid, 'value': value, 'state': status, 'input_type': type, 'choices': choices, 'inline': True, 'name_array_suffix': ''}
html = render_template("choicegroup.html", context)
return etree.XML(html)
@@ -193,9 +199,9 @@ def extract_choices(element):
choices = []
for choice in element:
- if not choice.tag=='choice':
+ if not choice.tag == 'choice':
raise Exception("[courseware.capa.inputtypes.extract_choices] \
- Expected a tag; got %s instead"
+ Expected a tag; got %s instead"
% choice.tag)
choice_text = ''.join([etree.tostring(x) for x in choice])
@@ -203,6 +209,7 @@ def extract_choices(element):
return choices
+
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
@@ -211,15 +218,16 @@ def radiogroup(element, value, status, render_template, msg=''):
Radio button inputs: (multiple choice)
'''
- eid=element.get('id')
+ eid = element.get('id')
choices = extract_choices(element)
- context = { 'id':eid, 'value':value, 'state':status, 'input_type': 'radio', 'choices':choices, 'inline': False, 'name_array_suffix': '[]' }
+ context = {'id': eid, 'value': value, 'state': status, 'input_type': 'radio', 'choices': choices, 'inline': False, 'name_array_suffix': '[]'}
html = render_template("choicegroup.html", context)
return etree.XML(html)
+
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
@@ -228,44 +236,46 @@ def checkboxgroup(element, value, status, render_template, msg=''):
Checkbox inputs: (select one or more choices)
'''
- eid=element.get('id')
+ eid = element.get('id')
choices = extract_choices(element)
- context = { 'id':eid, 'value':value, 'state':status, 'input_type': 'checkbox', 'choices':choices, 'inline': False, 'name_array_suffix': '[]' }
+ context = {'id': eid, 'value': value, 'state': status, 'input_type': 'checkbox', 'choices': choices, 'inline': False, 'name_array_suffix': '[]'}
html = render_template("choicegroup.html", context)
return etree.XML(html)
+
@register_render_function
def textline(element, value, status, render_template, msg=""):
'''
Simple text line input, with optional size specification.
'''
if element.get('math') or element.get('dojs'): # 'dojs' flag is temporary, for backwards compatibility with 8.02x
- return SimpleInput.xml_tags['textline_dynamath'](element,value,status,render_template,msg)
- eid=element.get('id')
+ return SimpleInput.xml_tags['textline_dynamath'](element, value, status, render_template, msg)
+ eid = element.get('id')
if eid is None:
msg = 'textline has no id: it probably appears outside of a known response type'
- msg += "\nSee problem XML source line %s" % getattr(element,'sourceline','')
+ msg += "\nSee problem XML source line %s" % getattr(element, 'sourceline', '')
raise Exception(msg)
- count = int(eid.split('_')[-2])-1 # HACK
+ count = int(eid.split('_')[-2]) - 1 # HACK
size = element.get('size')
- hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
+ hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden
escapedict = {'"': '"'}
- value = saxutils.escape(value,escapedict) # otherwise, answers with quotes in them crashes the system!
- context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg': msg, 'hidden': hidden}
+ value = saxutils.escape(value, escapedict) # otherwise, answers with quotes in them crashes the system!
+ context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size, 'msg': msg, 'hidden': hidden}
html = render_template("textinput.html", context)
try:
xhtml = etree.XML(html)
except Exception as err:
- if True: # TODO needs to be self.system.DEBUG - but can't access system
+ if True: # TODO needs to be self.system.DEBUG - but can't access system
log.debug('[inputtypes.textline] failed to parse XML for:\n%s' % html)
raise
return xhtml
#-----------------------------------------------------------------------------
+
@register_render_function
def textline_dynamath(element, value, status, render_template, msg=''):
'''
@@ -278,16 +288,17 @@ def textline_dynamath(element, value, status, render_template, msg=''):
uses a `{::}`
and a hidden textarea with id=input_eid_fromjs for the mathjax rendering and return.
'''
- eid=element.get('id')
- count = int(eid.split('_')[-2])-1 # HACK
+ eid = element.get('id')
+ count = int(eid.split('_')[-2]) - 1 # HACK
size = element.get('size')
- hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
- context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size,
- 'msg':msg, 'hidden' : hidden,
+ hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden
+ context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size,
+ 'msg': msg, 'hidden': hidden,
}
html = render_template("textinput_dynamath.html", context)
return etree.XML(html)
+
#-----------------------------------------------------------------------------
## TODO: Make a wrapper for
@register_render_function
@@ -297,31 +308,32 @@ def textbox(element, value, status, render_template, msg=''):
evaluating the code, eg error messages, and output from the code tests.
'''
- eid=element.get('id')
- count = int(eid.split('_')[-2])-1 # HACK
+ eid = element.get('id')
+ count = int(eid.split('_')[-2]) - 1 # HACK
size = element.get('size')
rows = element.get('rows') or '30'
cols = element.get('cols') or '80'
mode = element.get('mode') or 'python' # mode for CodeMirror, eg "python" or "xml"
- hidden = element.get('hidden','') # if specified, then textline is hidden and id is stored in div of name given by hidden
- linenumbers = element.get('linenumbers') # for CodeMirror
- if not value: value = element.text # if no student input yet, then use the default input given by the problem
- context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, 'msg':msg,
- 'mode':mode, 'linenumbers':linenumbers,
- 'rows':rows, 'cols':cols,
- 'hidden' : hidden,
+ hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden
+ linenumbers = element.get('linenumbers') # for CodeMirror
+ if not value: value = element.text # if no student input yet, then use the default input given by the problem
+ context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size, 'msg': msg,
+ 'mode': mode, 'linenumbers': linenumbers,
+ 'rows': rows, 'cols': cols,
+ 'hidden': hidden,
}
html = render_template("textbox.html", context)
try:
xhtml = etree.XML(html)
except Exception as err:
- newmsg = 'error %s in rendering message' % (str(err).replace('<','<'))
- newmsg += '
Original message: %s' % msg.replace('<','<')
+ newmsg = 'error %s in rendering message' % (str(err).replace('<', '<'))
+ newmsg += '
Original message: %s' % msg.replace('<', '<')
context['msg'] = newmsg
html = render_template("textbox.html", context)
xhtml = etree.XML(html)
return xhtml
+
#-----------------------------------------------------------------------------
@register_render_function
def schematic(element, value, status, render_template, msg=''):
@@ -333,19 +345,20 @@ def schematic(element, value, status, render_template, msg=''):
initial_value = element.get('initial_value')
submit_analyses = element.get('submit_analyses')
context = {
- 'id':eid,
- 'value':value,
- 'initial_value':initial_value,
- 'state':status,
- 'width':width,
- 'height':height,
- 'parts':parts,
- 'analyses':analyses,
- 'submit_analyses':submit_analyses,
+ 'id': eid,
+ 'value': value,
+ 'initial_value': initial_value,
+ 'state': status,
+ 'width': width,
+ 'height': height,
+ 'parts': parts,
+ 'analyses': analyses,
+ 'submit_analyses': submit_analyses,
}
html = render_template("schematicinput.html", context)
return etree.XML(html)
+
#-----------------------------------------------------------------------------
### TODO: Move out of inputtypes
@register_render_function
@@ -357,17 +370,17 @@ def math(element, value, status, render_template, msg=''):
Examples:
$\displaystyle U(r)=4 U_0
- $r_0$
+ $r_0$
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
TODO: use shorter tags (but this will require converting problem XML files!)
'''
- mathstr = re.sub('\$(.*)\$','[mathjaxinline]\\1[/mathjaxinline]',element.text)
+ mathstr = re.sub('\$(.*)\$', '[mathjaxinline]\\1[/mathjaxinline]', element.text)
mtag = 'mathjax'
if not '\\displaystyle' in mathstr: mtag += 'inline'
- else: mathstr = mathstr.replace('\\displaystyle','')
- mathstr = mathstr.replace('mathjaxinline]','%s]'%mtag)
+ else: mathstr = mathstr.replace('\\displaystyle', '')
+ mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag)
#if '\\displaystyle' in mathstr:
# isinline = False
@@ -376,13 +389,13 @@ def math(element, value, status, render_template, msg=''):
# isinline = True
# html = render_template("mathstring.html",{'mathstr':mathstr,'isinline':isinline,'tail':element.tail})
- html = '%s%s' % (mathstr,saxutils.escape(element.tail))
+ html = '%s%s' % (mathstr, saxutils.escape(element.tail))
try:
xhtml = etree.XML(html)
except Exception as err:
- if False: # TODO needs to be self.system.DEBUG - but can't access system
- msg = "Error %s
" % str(err).replace('<','<')
- msg += 'Failed to construct math expression from
%s
' % html.replace('<','<')
+ if False: # TODO needs to be self.system.DEBUG - but can't access system
+ msg = "Error %s
" % str(err).replace('<', '<')
+ msg += 'Failed to construct math expression from
%s
' % html.replace('<', '<')
msg += ""
log.error(msg)
return etree.XML(msg)
@@ -393,6 +406,7 @@ def math(element, value, status, render_template, msg=''):
#-----------------------------------------------------------------------------
+
@register_render_function
def solution(element, value, status, render_template, msg=''):
'''
@@ -401,19 +415,20 @@ def solution(element, value, status, render_template, msg=''):
is pressed. Note that the solution content is NOT sent with the HTML. It is obtained
by a JSON call.
'''
- eid=element.get('id')
+ eid = element.get('id')
size = element.get('size')
- context = {'id':eid,
- 'value':value,
- 'state':status,
+ context = {'id': eid,
+ 'value': value,
+ 'state': status,
'size': size,
- 'msg':msg,
+ 'msg': msg,
}
html = render_template("solutionspan.html", context)
return etree.XML(html)
#-----------------------------------------------------------------------------
+
@register_render_function
def imageinput(element, value, status, render_template, msg=''):
'''
@@ -429,12 +444,12 @@ def imageinput(element, value, status, render_template, msg=''):
width = element.get('width')
# if value is of the form [x,y] then parse it and send along coordinates of previous answer
- m = re.match('\[([0-9]+),([0-9]+)]',value.strip().replace(' ',''))
+ m = re.match('\[([0-9]+),([0-9]+)]', value.strip().replace(' ', ''))
if m:
- (gx,gy) = [int(x)-15 for x in m.groups()]
+ (gx, gy) = [int(x) - 15 for x in m.groups()]
else:
- (gx,gy) = (0,0)
-
+ (gx, gy) = (0, 0)
+
context = {
'id': eid,
'value': value,
@@ -443,7 +458,7 @@ def imageinput(element, value, status, render_template, msg=''):
'src': src,
'gx': gx,
'gy': gy,
- 'state': status, # to change
+ 'state': status, # to change
'msg': msg, # to change
}
html = render_template("imageinput.html", context)
diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py
index 21a3083c13..b232664cd5 100644
--- a/common/lib/capa/capa/responsetypes.py
+++ b/common/lib/capa/capa/responsetypes.py
@@ -26,25 +26,28 @@ from calc import evaluator, UndefinedVariable
from correctmap import CorrectMap
from util import *
from lxml import etree
-from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
+from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
log = logging.getLogger('mitx.' + __name__)
#-----------------------------------------------------------------------------
# Exceptions
+
class LoncapaProblemError(Exception):
'''
Error in specification of a problem
'''
pass
+
class ResponseError(Exception):
'''
Error for failure in processing a response
'''
pass
+
class StudentInputError(Exception):
pass
@@ -52,6 +55,7 @@ class StudentInputError(Exception):
#
# Main base class for CAPA responsetypes
+
class LoncapaResponse(object):
'''
Base class for CAPA responsetypes. Each response type (ie a capa question,
@@ -60,7 +64,7 @@ class LoncapaResponse(object):
- get_score : evaluate the given student answers, and return a CorrectMap
- get_answers : provide a dict of the expected answers for this problem
-
+
Each subclass must also define the following attributes:
- response_tag : xhtml tag identifying this response (used in auto-registering)
@@ -81,7 +85,7 @@ class LoncapaResponse(object):
- hint_tag : xhtml tag identifying hint associated with this response inside hintgroup
'''
- __metaclass__=abc.ABCMeta # abc = Abstract Base Class
+ __metaclass__ = abc.ABCMeta # abc = Abstract Base Class
response_tag = None
hint_tag = None
@@ -89,7 +93,7 @@ class LoncapaResponse(object):
max_inputfields = None
allowed_inputfields = []
required_attributes = []
-
+
def __init__(self, xml, inputfields, context, system=None):
'''
Init is passed the following arguments:
@@ -97,7 +101,7 @@ class LoncapaResponse(object):
- xml : ElementTree of this Response
- inputfields : ordered list of ElementTrees for each input entry field in this Response
- context : script processor context
- - system : I4xSystem instance which provides OS, rendering, and user context
+ - system : I4xSystem instance which provides OS, rendering, and user context
'''
self.xml = xml
@@ -107,35 +111,35 @@ class LoncapaResponse(object):
for abox in inputfields:
if abox.tag not in self.allowed_inputfields:
- msg = "%s: cannot have input field %s" % (unicode(self),abox.tag)
- msg += "\nSee XML source line %s" % getattr(xml,'sourceline','')
+ msg = "%s: cannot have input field %s" % (unicode(self), abox.tag)
+ msg += "\nSee XML source line %s" % getattr(xml, 'sourceline', '')
raise LoncapaProblemError(msg)
- if self.max_inputfields and len(inputfields)>self.max_inputfields:
- msg = "%s: cannot have more than %s input fields" % (unicode(self),self.max_inputfields)
- msg += "\nSee XML source line %s" % getattr(xml,'sourceline','')
+ if self.max_inputfields and len(inputfields) > self.max_inputfields:
+ msg = "%s: cannot have more than %s input fields" % (unicode(self), self.max_inputfields)
+ msg += "\nSee XML source line %s" % getattr(xml, 'sourceline', '')
raise LoncapaProblemError(msg)
for prop in self.required_attributes:
if not xml.get(prop):
- msg = "Error in problem specification: %s missing required attribute %s" % (unicode(self),prop)
- msg += "\nSee XML source line %s" % getattr(xml,'sourceline','')
+ msg = "Error in problem specification: %s missing required attribute %s" % (unicode(self), prop)
+ msg += "\nSee XML source line %s" % getattr(xml, 'sourceline', '')
raise LoncapaProblemError(msg)
- self.answer_ids = [x.get('id') for x in self.inputfields] # ordered list of answer_id values for this response
- if self.max_inputfields==1:
+ self.answer_ids = [x.get('id') for x in self.inputfields] # ordered list of answer_id values for this response
+ if self.max_inputfields == 1:
self.answer_id = self.answer_ids[0] # for convenience
self.default_answer_map = {} # dict for default answer map (provided in input elements)
for entry in self.inputfields:
- answer = entry.get('correct_answer')
+ answer = entry.get('correct_answer')
if answer:
self.default_answer_map[entry.get('id')] = contextualize_text(answer, self.context)
- if hasattr(self,'setup_response'):
+ if hasattr(self, 'setup_response'):
self.setup_response()
- def render_html(self,renderer):
+ def render_html(self, renderer):
'''
Return XHTML Element tree representation of this Response.
@@ -150,7 +154,7 @@ class LoncapaResponse(object):
tree.tail = self.xml.tail
return tree
- def evaluate_answers(self,student_answers,old_cmap):
+ def evaluate_answers(self, student_answers, old_cmap):
'''
Called by capa_problem.LoncapaProblem to evaluate student answers, and to
generate hints (if any).
@@ -190,14 +194,14 @@ class LoncapaResponse(object):
'''
if not hintfn in self.context:
msg = 'missing specified hint function %s in script context' % hintfn
- msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','')
+ msg += "\nSee XML source line %s" % getattr(self.xml, 'sourceline', '')
raise LoncapaProblemError(msg)
try:
self.context[hintfn](self.answer_ids, student_answers, new_cmap, old_cmap)
except Exception as err:
- msg = 'Error %s in evaluating hint function %s' % (err,hintfn)
- msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','')
+ msg = 'Error %s in evaluating hint function %s' % (err, hintfn)
+ msg += "\nSee XML source line %s" % getattr(self.xml, 'sourceline', '')
raise ResponseError(msg)
return
@@ -214,18 +218,18 @@ class LoncapaResponse(object):
# You have inverted the slope in the question. The slope is
# (y2-y1)/(x2 - x1) you have the slope as (x2-x1)/(y2-y1).
#
- #
+ #
#
- if self.hint_tag is not None and hintgroup.find(self.hint_tag) is not None and hasattr(self,'check_hint_condition'):
+ if self.hint_tag is not None and hintgroup.find(self.hint_tag) is not None and hasattr(self, 'check_hint_condition'):
rephints = hintgroup.findall(self.hint_tag)
- hints_to_show = self.check_hint_condition(rephints,student_answers)
- hintmode = hintgroup.get('mode','always') # can be 'on_request' or 'always' (default)
+ hints_to_show = self.check_hint_condition(rephints, student_answers)
+ hintmode = hintgroup.get('mode', 'always') # can be 'on_request' or 'always' (default)
for hintpart in hintgroup.findall('hintpart'):
if hintpart.get('on') in hints_to_show:
hint_text = hintpart.find('text').text
aid = self.answer_ids[-1] # make the hint appear after the last answer box in this response
- new_cmap.set_hint_and_mode(aid,hint_text,hintmode)
+ new_cmap.set_hint_and_mode(aid, hint_text, hintmode)
log.debug('after hint: new_cmap = %s' % new_cmap)
@abc.abstractmethod
@@ -249,7 +253,7 @@ class LoncapaResponse(object):
'''
pass
- def check_hint_condition(self,hxml_set,student_answers):
+ def check_hint_condition(self, hxml_set, student_answers):
'''
Return a list of hints to show.
@@ -266,6 +270,7 @@ class LoncapaResponse(object):
def __unicode__(self):
return u'LoncapaProblem Response %s' % self.xml.tag
+
#-----------------------------------------------------------------------------
class ChoiceResponse(LoncapaResponse):
'''
@@ -310,8 +315,8 @@ class ChoiceResponse(LoncapaResponse):
'''
- response_tag = 'choiceresponse'
- max_inputfields = 1
+ response_tag = 'choiceresponse'
+ max_inputfields = 1
allowed_inputfields = ['checkboxgroup', 'radiogroup']
def setup_response(self):
@@ -328,17 +333,17 @@ class ChoiceResponse(LoncapaResponse):
Initialize name attributes in tags for this response.
'''
- for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice',
+ for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice',
id=self.xml.get('id'))):
- choice.set("name", "choice_"+str(index))
+ choice.set("name", "choice_" + str(index))
def get_score(self, student_answers):
student_answer = student_answers.get(self.answer_id, [])
-
+
if not isinstance(student_answer, list):
student_answer = [student_answer]
-
+
student_answer = set(student_answer)
required_selected = len(self.correct_choices - student_answer) == 0
@@ -347,15 +352,16 @@ class ChoiceResponse(LoncapaResponse):
correct = required_selected & no_extra_selected
if correct:
- return CorrectMap(self.answer_id,'correct')
+ return CorrectMap(self.answer_id, 'correct')
else:
- return CorrectMap(self.answer_id,'incorrect')
+ return CorrectMap(self.answer_id, 'incorrect')
def get_answers(self):
- return { self.answer_id : self.correct_choices }
+ return {self.answer_id: self.correct_choices}
#-----------------------------------------------------------------------------
+
class MultipleChoiceResponse(LoncapaResponse):
# TODO: handle direction and randomize
snippets = [{'snippet': '''
@@ -373,28 +379,28 @@ class MultipleChoiceResponse(LoncapaResponse):
allowed_inputfields = ['choicegroup']
def setup_response(self):
- self.mc_setup_response() # call secondary setup for MultipleChoice questions, to set name attributes
+ self.mc_setup_response() # call secondary setup for MultipleChoice questions, to set name attributes
# define correct choices (after calling secondary setup)
xml = self.xml
- cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]',id=xml.get('id'))
+ cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]', id=xml.get('id'))
self.correct_choices = [choice.get('name') for choice in cxml]
def mc_setup_response(self):
'''
Initialize name attributes in stanzas in the in this response.
'''
- i=0
+ i = 0
for response in self.xml.xpath("choicegroup"):
rtype = response.get('type')
if rtype not in ["MultipleChoice"]:
response.set("type", "MultipleChoice") # force choicegroup to be MultipleChoice if not valid
for choice in list(response):
if choice.get("name") is None:
- choice.set("name", "choice_"+str(i))
- i+=1
+ choice.set("name", "choice_" + str(i))
+ i += 1
else:
- choice.set("name", "choice_"+choice.get("name"))
+ choice.set("name", "choice_" + choice.get("name"))
def get_score(self, student_answers):
'''
@@ -402,39 +408,41 @@ class MultipleChoiceResponse(LoncapaResponse):
'''
# log.debug('%s: student_answers=%s, correct_choices=%s' % (unicode(self),student_answers,self.correct_choices))
if self.answer_id in student_answers and student_answers[self.answer_id] in self.correct_choices:
- return CorrectMap(self.answer_id,'correct')
+ return CorrectMap(self.answer_id, 'correct')
else:
- return CorrectMap(self.answer_id,'incorrect')
+ return CorrectMap(self.answer_id, 'incorrect')
def get_answers(self):
- return {self.answer_id:self.correct_choices}
+ return {self.answer_id: self.correct_choices}
+
class TrueFalseResponse(MultipleChoiceResponse):
response_tag = 'truefalseresponse'
def mc_setup_response(self):
- i=0
+ i = 0
for response in self.xml.xpath("choicegroup"):
response.set("type", "TrueFalse")
for choice in list(response):
if choice.get("name") is None:
- choice.set("name", "choice_"+str(i))
- i+=1
+ choice.set("name", "choice_" + str(i))
+ i += 1
else:
- choice.set("name", "choice_"+choice.get("name"))
-
+ choice.set("name", "choice_" + choice.get("name"))
+
def get_score(self, student_answers):
correct = set(self.correct_choices)
answers = set(student_answers.get(self.answer_id, []))
-
+
if correct == answers:
- return CorrectMap( self.answer_id , 'correct')
-
- return CorrectMap(self.answer_id ,'incorrect')
+ return CorrectMap(self.answer_id, 'correct')
+
+ return CorrectMap(self.answer_id, 'incorrect')
#-----------------------------------------------------------------------------
+
class OptionResponse(LoncapaResponse):
'''
TODO: handle direction and randomize
@@ -456,19 +464,20 @@ class OptionResponse(LoncapaResponse):
cmap = CorrectMap()
amap = self.get_answers()
for aid in amap:
- if aid in student_answers and student_answers[aid]==amap[aid]:
- cmap.set(aid,'correct')
+ if aid in student_answers and student_answers[aid] == amap[aid]:
+ cmap.set(aid, 'correct')
else:
- cmap.set(aid,'incorrect')
+ cmap.set(aid, 'incorrect')
return cmap
def get_answers(self):
- amap = dict([(af.get('id'),af.get('correct')) for af in self.answer_fields])
+ amap = dict([(af.get('id'), af.get('correct')) for af in self.answer_fields])
# log.debug('%s: expected answers=%s' % (unicode(self),amap))
return amap
#-----------------------------------------------------------------------------
+
class NumericalResponse(LoncapaResponse):
response_tag = 'numericalresponse'
@@ -497,25 +506,26 @@ class NumericalResponse(LoncapaResponse):
'''Grade a numeric response '''
student_answer = student_answers[self.answer_id]
try:
- correct = compare_with_tolerance (evaluator(dict(),dict(),student_answer), complex(self.correct_answer), self.tolerance)
- # We should catch this explicitly.
+ correct = compare_with_tolerance(evaluator(dict(), dict(), student_answer), complex(self.correct_answer), self.tolerance)
+ # We should catch this explicitly.
# I think this is just pyparsing.ParseException, calc.UndefinedVariable:
# But we'd need to confirm
- except:
+ except:
raise StudentInputError('Invalid input -- please use a number only')
if correct:
- return CorrectMap(self.answer_id,'correct')
+ return CorrectMap(self.answer_id, 'correct')
else:
- return CorrectMap(self.answer_id,'incorrect')
+ return CorrectMap(self.answer_id, 'incorrect')
# TODO: add check_hint_condition(self,hxml_set,student_answers)
def get_answers(self):
- return {self.answer_id:self.correct_answer}
+ return {self.answer_id: self.correct_answer}
#-----------------------------------------------------------------------------
+
class StringResponse(LoncapaResponse):
response_tag = 'stringresponse'
@@ -530,28 +540,29 @@ class StringResponse(LoncapaResponse):
def get_score(self, student_answers):
'''Grade a string response '''
student_answer = student_answers[self.answer_id].strip()
- correct = self.check_string(self.correct_answer,student_answer)
- return CorrectMap(self.answer_id,'correct' if correct else 'incorrect')
+ correct = self.check_string(self.correct_answer, student_answer)
+ return CorrectMap(self.answer_id, 'correct' if correct else 'incorrect')
- def check_string(self,expected,given):
- if self.xml.get('type')=='ci': return given.lower() == expected.lower()
+ def check_string(self, expected, given):
+ if self.xml.get('type') == 'ci': return given.lower() == expected.lower()
return given == expected
- def check_hint_condition(self,hxml_set,student_answers):
+ def check_hint_condition(self, hxml_set, student_answers):
given = student_answers[self.answer_id].strip()
hints_to_show = []
for hxml in hxml_set:
name = hxml.get('name')
- correct_answer = contextualize_text(hxml.get('answer'),self.context).strip()
- if self.check_string(correct_answer,given): hints_to_show.append(name)
+ correct_answer = contextualize_text(hxml.get('answer'), self.context).strip()
+ if self.check_string(correct_answer, given): hints_to_show.append(name)
log.debug('hints_to_show = %s' % hints_to_show)
return hints_to_show
def get_answers(self):
- return {self.answer_id:self.correct_answer}
+ return {self.answer_id: self.correct_answer}
#-----------------------------------------------------------------------------
+
class CustomResponse(LoncapaResponse):
'''
Custom response. The python code to be run should be in ...
@@ -592,7 +603,7 @@ def sympy_check2():
'''}]
response_tag = 'customresponse'
- allowed_inputfields = ['textline','textbox']
+ allowed_inputfields = ['textline', 'textbox']
def setup_response(self):
xml = self.xml
@@ -607,7 +618,7 @@ def sympy_check2():
self.code = None
answer = None
try:
- answer = xml.xpath('//*[@id=$id]//answer',id=xml.get('id'))[0]
+ answer = xml.xpath('//*[@id=$id]//answer', id=xml.get('id'))[0]
except IndexError:
# print "xml = ",etree.tostring(xml,pretty_print=True)
@@ -619,8 +630,8 @@ def sympy_check2():
if cfn in self.context:
self.code = self.context[cfn]
else:
- msg = "%s: can't find cfn %s in context" % (unicode(self),cfn)
- msg += "\nSee XML source line %s" % getattr(self.xml,'sourceline','')
+ msg = "%s: can't find cfn %s in context" % (unicode(self), cfn)
+ msg += "\nSee XML source line %s" % getattr(self.xml, 'sourceline', '')
raise LoncapaProblemError(msg)
if not self.code:
@@ -631,7 +642,7 @@ def sympy_check2():
else:
answer_src = answer.get('src')
if answer_src is not None:
- self.code = self.system.filesystem.open('src/'+answer_src).read()
+ self.code = self.system.filesystem.open('src/' + answer_src).read()
else:
self.code = answer.text
@@ -641,52 +652,52 @@ def sympy_check2():
of each key removed (the string before the first "_").
'''
- log.debug('%s: student_answers=%s' % (unicode(self),student_answers))
+ log.debug('%s: student_answers=%s' % (unicode(self), student_answers))
idset = sorted(self.answer_ids) # ordered list of answer id's
try:
- submission = [student_answers[k] for k in idset] # ordered list of answers
+ submission = [student_answers[k] for k in idset] # ordered list of answers
except Exception as err:
msg = '[courseware.capa.responsetypes.customresponse] error getting student answer from %s' % student_answers
- msg += '\n idset = %s, error = %s' % (idset,err)
+ msg += '\n idset = %s, error = %s' % (idset, err)
log.error(msg)
raise Exception(msg)
# global variable in context which holds the Presentation MathML from dynamic math input
- dynamath = [ student_answers.get(k+'_dynamath',None) for k in idset ] # ordered list of dynamath responses
+ dynamath = [student_answers.get(k + '_dynamath', None) for k in idset] # ordered list of dynamath responses
# if there is only one box, and it's empty, then don't evaluate
- if len(idset)==1 and not submission[0]:
- return CorrectMap(idset[0],'incorrect',msg='No answer entered!')
+ if len(idset) == 1 and not submission[0]:
+ return CorrectMap(idset[0], 'incorrect', msg='No answer entered!')
correct = ['unknown'] * len(idset)
messages = [''] * len(idset)
# put these in the context of the check function evaluator
# note that this doesn't help the "cfn" version - only the exec version
- self.context.update({'xml' : self.xml, # our subtree
- 'response_id' : self.myid, # my ID
+ self.context.update({'xml': self.xml, # our subtree
+ 'response_id': self.myid, # my ID
'expect': self.expect, # expected answer (if given as attribute)
- 'submission':submission, # ordered list of student answers from entry boxes in our subtree
- 'idset':idset, # ordered list of ID's of all entry boxes in our subtree
- 'dynamath':dynamath, # ordered list of all javascript inputs in our subtree
- 'answers':student_answers, # dict of student's responses, with keys being entry box IDs
- 'correct':correct, # the list to be filled in by the check function
- 'messages':messages, # the list of messages to be filled in by the check function
- 'options':self.xml.get('options'), # any options to be passed to the cfn
- 'testdat':'hello world',
+ 'submission': submission, # ordered list of student answers from entry boxes in our subtree
+ 'idset': idset, # ordered list of ID's of all entry boxes in our subtree
+ 'dynamath': dynamath, # ordered list of all javascript inputs in our subtree
+ 'answers': student_answers, # dict of student's responses, with keys being entry box IDs
+ 'correct': correct, # the list to be filled in by the check function
+ 'messages': messages, # the list of messages to be filled in by the check function
+ 'options': self.xml.get('options'), # any options to be passed to the cfn
+ 'testdat': 'hello world',
})
- # pass self.system.debug to cfn
+ # pass self.system.debug to cfn
self.context['debug'] = self.system.DEBUG
# exec the check function
- if type(self.code)==str:
+ if type(self.code) == str:
try:
exec self.code in self.context['global_context'], self.context
except Exception as err:
print "oops in customresponse (code) error %s" % err
- print "context = ",self.context
+ print "context = ", self.context
print traceback.format_exc()
else: # self.code is not a string; assume its a function
@@ -695,44 +706,44 @@ def sympy_check2():
ret = None
log.debug(" submission = %s" % submission)
try:
- answer_given = submission[0] if (len(idset)==1) else submission
+ answer_given = submission[0] if (len(idset) == 1) else submission
# handle variable number of arguments in check function, for backwards compatibility
# with various Tutor2 check functions
- args = [self.expect,answer_given,student_answers,self.answer_ids[0]]
+ args = [self.expect, answer_given, student_answers, self.answer_ids[0]]
argspec = inspect.getargspec(fn)
- nargs = len(argspec.args)-len(argspec.defaults or [])
+ nargs = len(argspec.args) - len(argspec.defaults or [])
kwargs = {}
for argname in argspec.args[nargs:]:
kwargs[argname] = self.context[argname] if argname in self.context else None
log.debug('[customresponse] answer_given=%s' % answer_given)
- log.debug('nargs=%d, args=%s, kwargs=%s' % (nargs,args,kwargs))
+ log.debug('nargs=%d, args=%s, kwargs=%s' % (nargs, args, kwargs))
- ret = fn(*args[:nargs],**kwargs)
+ ret = fn(*args[:nargs], **kwargs)
except Exception as err:
log.error("oops in customresponse (cfn) error %s" % err)
# print "context = ",self.context
log.error(traceback.format_exc())
raise Exception("oops in customresponse (cfn) error %s" % err)
log.debug("[courseware.capa.responsetypes.customresponse.get_score] ret = %s" % ret)
- if type(ret)==dict:
- correct = ['correct']*len(idset) if ret['ok'] else ['incorrect']*len(idset)
+ if type(ret) == dict:
+ correct = ['correct'] * len(idset) if ret['ok'] else ['incorrect'] * len(idset)
msg = ret['msg']
if 1:
# try to clean up message html
- msg = ''+msg+''
- msg = msg.replace('<','<')
+ msg = '' + msg + ''
+ msg = msg.replace('<', '<')
#msg = msg.replace('<','<')
- msg = etree.tostring(fromstring_bs(msg,convertEntities=None),pretty_print=True)
+ msg = etree.tostring(fromstring_bs(msg, convertEntities=None), pretty_print=True)
#msg = etree.tostring(fromstring_bs(msg),pretty_print=True)
- msg = msg.replace('
','')
+ msg = msg.replace('
', '')
#msg = re.sub('(.*)','\\1',msg,flags=re.M|re.DOTALL) # python 2.7
- msg = re.sub('(?ms)(.*)','\\1',msg)
+ msg = re.sub('(?ms)(.*)', '\\1', msg)
messages[0] = msg
else:
- correct = ['correct']*len(idset) if ret else ['incorrect']*len(idset)
+ correct = ['correct'] * len(idset) if ret else ['incorrect'] * len(idset)
# build map giving "correct"ness of the answer(s)
correct_map = CorrectMap()
@@ -750,14 +761,15 @@ def sympy_check2():
but for simplicity, if an "expect" attribute was given by the content author
ie then that.
'''
- if len(self.answer_ids)>1:
+ if len(self.answer_ids) > 1:
return self.default_answer_map
if self.expect:
- return {self.answer_ids[0] : self.expect}
+ return {self.answer_ids[0]: self.expect}
return self.default_answer_map
#-----------------------------------------------------------------------------
+
class SymbolicResponse(CustomResponse):
"""
Symbolic math response checking, using symmath library.
@@ -776,15 +788,16 @@ class SymbolicResponse(CustomResponse):
response_tag = 'symbolicresponse'
def setup_response(self):
- self.xml.set('cfn','symmath_check')
+ self.xml.set('cfn', 'symmath_check')
code = "from symmath import *"
- exec code in self.context,self.context
+ exec code in self.context, self.context
CustomResponse.setup_response(self)
#-----------------------------------------------------------------------------
+
class CodeResponse(LoncapaResponse):
- '''
+ '''
Grade student code using an external server, called 'xqueue'
In contrast to ExternalResponse, CodeResponse has following behavior:
1) Goes through a queueing system
@@ -797,20 +810,20 @@ class CodeResponse(LoncapaResponse):
def setup_response(self):
xml = self.xml
- self.url = xml.get('url', "http://ec2-50-16-59-149.compute-1.amazonaws.com/xqueue/submit/") # FIXME -- hardcoded url
+ self.url = xml.get('url', "http://ec2-50-16-59-149.compute-1.amazonaws.com/xqueue/submit/") # FIXME -- hardcoded url
answer = xml.find('answer')
if answer is not None:
answer_src = answer.get('src')
if answer_src is not None:
- self.code = self.system.filesystem.open('src/'+answer_src).read()
+ self.code = self.system.filesystem.open('src/' + answer_src).read()
else:
self.code = answer.text
- else: # no stanza; get code from