diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6e5576ca7e..0000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 54cc7a27cb..f98fdf7bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ courseware/static/js/mathjax/* db.newaskbot db.oldaskbot flushdb.sh +build +\#*\# \ No newline at end of file diff --git a/ci/build.sh b/ci/build.sh deleted file mode 100755 index fc8c5bf7f3..0000000000 --- a/ci/build.sh +++ /dev/null @@ -1,29 +0,0 @@ -#! /bin/bash - -set -x -set -e - -#sass sass:static/css -r templates/sass/bourbon/lib/bourbon.rb --style :compressed - -if [ -z "${GIT_COMMIT}" ]; then - GIT_COMMIT=$(git rev-parse HEAD) -fi - -if [ -z "${GIT_BRANCH}" ]; then - GIT_BRANCH=$(git symbolic-ref -q HEAD) - GIT_BRANCH=${GIT_BRANCH##refs/heads/} - GIT_BRANCH=${GIT_BRANCH:-HEAD} -fi -GIT_BRANCH=${GIT_BRANCH##origin/} -GIT_BRANCH=${GIT_BRANCH//\//_} - -if [ -z "${BUILD_NUMBER}" ]; then - BUILD_NUMBER=dev -fi - -ID=mitx-${GIT_BRANCH}-${BUILD_NUMBER}-${GIT_COMMIT} -REPO_ROOT=$(dirname $0)/.. -BUILD_DIR=${REPO_ROOT}/build - -mkdir -p ${BUILD_DIR} -tar --exclude=.git --exclude=build --transform="s#^#mitx/#" -czf ${BUILD_DIR}/${ID}.tgz ${REPO_ROOT} diff --git a/djangoapps/courseware/capa/calc.py b/djangoapps/courseware/capa/calc.py index 63c5c9de01..fb64c58139 100644 --- a/djangoapps/courseware/capa/calc.py +++ b/djangoapps/courseware/capa/calc.py @@ -5,6 +5,7 @@ import operator import re import numpy +import numbers import scipy.constants from pyparsing import Word, alphas, nums, oneOf, Literal @@ -121,7 +122,7 @@ def evaluator(variables, functions, string, cs=False): 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 type(e) in [float, numpy.float64, numpy.complex]] # Ignore ^ + x = [e for e in x if isinstance(e, numbers.Number)] # Ignore ^ x.reverse() x=reduce(lambda a,b:b**a, x) return x @@ -130,7 +131,7 @@ def evaluator(variables, functions, string, cs=False): return x[0] if 0 in x: return float('nan') - x = [1./e for e in x if type(e) == float] # Ignore ^ + 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 @@ -217,4 +218,7 @@ if __name__=='__main__': 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/djangoapps/courseware/capa/inputtypes.py b/djangoapps/courseware/capa/inputtypes.py index 2ede94ad8d..789887243d 100644 --- a/djangoapps/courseware/capa/inputtypes.py +++ b/djangoapps/courseware/capa/inputtypes.py @@ -8,7 +8,8 @@ class textline(object): def render(element, value, state): eid=element.get('id') count = int(eid.split('_')[-2])-1 # HACK - context = {'id':eid, 'value':value, 'state':state, 'count':count} + size = element.get('size') + context = {'id':eid, 'value':value, 'state':state, 'count':count, 'size': size} html=render_to_string("textinput.html", context) return etree.XML(html) diff --git a/djangoapps/courseware/capa/responsetypes.py b/djangoapps/courseware/capa/responsetypes.py index 56ea51ddb9..62705d3a70 100644 --- a/djangoapps/courseware/capa/responsetypes.py +++ b/djangoapps/courseware/capa/responsetypes.py @@ -1,5 +1,6 @@ import json import math +import numbers import numpy import random import scipy @@ -37,7 +38,6 @@ class numericalresponse(object): def __init__(self, xml, context): self.xml = xml self.correct_answer = contextualize_text(xml.get('answer'), context) - self.correct_answer = float(self.correct_answer) self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default', id=xml.get('id'))[0] self.tolerance = contextualize_text(self.tolerance_xml, context) @@ -48,8 +48,11 @@ class numericalresponse(object): ''' Display HTML for a numeric response ''' student_answer = student_answers[self.answer_id] try: - correct = compare_with_tolerance (evaluator(dict(),dict(),student_answer), self.correct_answer, self.tolerance) - except: + 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: raise StudentInputError('Invalid input -- please use a number only') if correct: @@ -141,7 +144,7 @@ class formularesponse(object): except: #traceback.print_exc() raise StudentInputError("Error in formula") - if math.isnan(student_result) or math.isinf(student_result): + if numpy.isnan(student_result) or numpy.isinf(student_result): return {self.answer_id:"incorrect"} if not compare_with_tolerance(student_result, instructor_result, self.tolerance): return {self.answer_id:"incorrect"} @@ -153,9 +156,9 @@ class formularesponse(object): keys and all non-numeric values stripped out. All values also converted to float. Used so we can safely use Python contexts. ''' - d=dict([(k, float(d[k])) for k in d if type(k)==str and \ + d=dict([(k, numpy.complex(d[k])) for k in d if type(k)==str and \ k.isalnum() and \ - (type(d[k]) == float or type(d[k]) == int) ]) + isinstance(d[k], numbers.Number)]) return d def get_answers(self): diff --git a/djangoapps/courseware/grades.py b/djangoapps/courseware/grades.py index 43ed0c8155..75b24cebcc 100644 --- a/djangoapps/courseware/grades.py +++ b/djangoapps/courseware/grades.py @@ -1,23 +1,26 @@ -import logging -import urllib -from lxml import etree - import courseware.content_parser as content_parser -from models import StudentModule -from django.conf import settings import courseware.modules +import logging +import random +import urllib +from collections import namedtuple +from django.conf import settings +from lxml import etree +from models import StudentModule from student.models import UserProfile log = logging.getLogger("mitx.courseware") +Score = namedtuple("Score", "earned possible graded section") + def get_grade(user, problem, cache): ## HACK: assumes max score is fixed per problem id = problem.get('id') correct = 0 # If the ID is not in the cache, add the item - if id not in cache: + if id not in cache: module = StudentModule(module_type = 'problem', # TODO: Move into StudentModule.__init__? module_id = id, student = user, @@ -44,6 +47,17 @@ def get_grade(user, problem, cache): return (correct, total) def grade_sheet(student): + """ + This pulls a summary of all problems in the course. It returns a dictionary with two datastructures: + + - courseware_summary is a summary of all sections with problems in the course. It is organized as an array of chapters, + each containing an array of sections, each containing an array of scores. This contains information for graded and ungraded + problems, and is good for displaying a course summary with due dates, etc. + + - grade_summary is a summary of how the final grade breaks down. It is an array of "sections". Each section can either be + a conglomerate of scores (like labs or homeworks) which has subscores and a totalscore, or a section can be all from one assignment + (such as a midterm or final) and only has a totalscore. Each section has a weight that shows how it contributes to the total grade. + """ dom=content_parser.course_file(student) course = dom.xpath('//course/@name')[0] xmlChapters = dom.xpath('//course[@name=$course]/chapter', course=course) @@ -54,11 +68,14 @@ def grade_sheet(student): response_by_id[response.module_id] = response - total_scores = {} + + totaled_scores = {} chapters=[] for c in xmlChapters: sections = [] chname=c.get('name') + + for s in dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section', course=course, chname=chname): problems=dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section[@name=$section]//problem', @@ -84,22 +101,26 @@ def grade_sheet(student): else: correct = total - scores.append((int(correct),total, graded )) + scores.append( Score(int(correct),total, graded, s.get("name")) ) + + section_total = Score(sum([score.earned for score in scores]), + sum([score.possible for score in scores]), + False, + p.get("id")) - section_total = (sum([score[0] for score in scores]), - sum([score[1] for score in scores])) + graded_total = Score(sum([score.earned for score in scores if score.graded]), + sum([score.possible for score in scores if score.graded]), + True, + p.get("id")) - graded_total = (sum([score[0] for score in scores if score[2]]), - sum([score[1] for score in scores if score[2]])) - - #Add the graded total to total_scores + #Add the graded total to totaled_scores format = s.get('format') if s.get('format') else "" subtitle = s.get('subtitle') if s.get('subtitle') else format if format and graded_total[1] > 0: - format_scores = total_scores[ format ] if format in total_scores else [] - format_scores.append( graded_total + (s.get("name"),) ) - total_scores[ format ] = format_scores + format_scores = totaled_scores.get(format, []) + format_scores.append( graded_total ) + totaled_scores[ format ] = format_scores score={'section':s.get("name"), 'scores':scores, @@ -114,7 +135,20 @@ def grade_sheet(student): chapters.append({'course':course, 'chapter' : c.get("name"), 'sections' : sections,}) - + + + grade_summary = grade_summary_6002x(totaled_scores) + + return {'courseware_summary' : chapters, + 'grade_summary' : grade_summary} + + +def grade_summary_6002x(totaled_scores): + """ + This function takes the a dictionary of (graded) section scores, and applies the course grading rules to create + the grade_summary. For 6.002x this means homeworks and labs all have equal weight, with the lowest 2 of each + being dropped. There is one midterm and one final. + """ def totalWithDrops(scores, drop_count): #Note that this key will sort the list descending @@ -131,12 +165,12 @@ def grade_sheet(student): return aggregate_score, dropped_indices #Figure the homework scores - homework_scores = total_scores['Homework'] if 'Homework' in total_scores else [] + homework_scores = totaled_scores['Homework'] if 'Homework' in totaled_scores else [] homework_percentages = [] for i in range(12): if i < len(homework_scores): - percentage = homework_scores[i][0] / float(homework_scores[i][1]) - summary = "Homework {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, homework_scores[i][2] , percentage, homework_scores[i][0], homework_scores[i][1] ) + percentage = homework_scores[1].earned / float(homework_scores[i].possible) + summary = "Homework {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, homework_scores[i].section , percentage, homework_scores[i].earned, homework_scores[i].possible ) else: percentage = 0 summary = "Unreleased Homework {0} - 0% (?/?)".format(i + 1) @@ -153,13 +187,12 @@ def grade_sheet(student): homework_total, homework_dropped_indices = totalWithDrops(homework_percentages, 2) #Figure the lab scores - lab_scores = total_scores['Lab'] if 'Lab' in total_scores else [] + lab_scores = totaled_scores['Lab'] if 'Lab' in totaled_scores else [] lab_percentages = [] - log.debug("lab_scores: {0}".format(lab_scores)) for i in range(12): if i < len(lab_scores): - percentage = lab_scores[i][0] / float(lab_scores[i][1]) - summary = "Lab {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, lab_scores[i][2] , percentage, lab_scores[i][0], lab_scores[i][1] ) + percentage = lab_scores[i].earned / float(lab_scores[i].possible) + summary = "Lab {0} - {1} - {2:.0%} ({3:g}/{4:g})".format( i + 1, lab_scores[i].section , percentage, lab_scores[i].earned, lab_scores[i].possible ) else: percentage = 0 summary = "Unreleased Lab {0} - 0% (?/?)".format(i + 1) @@ -177,18 +210,18 @@ def grade_sheet(student): #TODO: Pull this data about the midterm and final from the databse. It should be exactly similar to above, but we aren't sure how exams will be done yet. - midterm_score = ('?', '?') + midterm_score = Score('?', '?', True, "?") midterm_percentage = 0 - final_score = ('?', '?') + final_score = Score('?', '?', True, "?") final_percentage = 0 if settings.GENERATE_PROFILE_SCORES: - midterm_score = (random.randrange(50, 150), 150) - midterm_percentage = midterm_score[0] / float(midterm_score[1]) + midterm_score = Score(random.randrange(50, 150), 150, True, "?") + midterm_percentage = midterm_score.earned / float(midterm_score.possible) - final_score = (random.randrange(100, 300), 300) - final_percentage = final_score[0] / float(final_score[1]) + final_score = Score(random.randrange(100, 300), 300, True, "?") + final_percentage = final_score.earned / float(final_score.possible) grade_summary = [ @@ -196,7 +229,8 @@ def grade_sheet(student): 'category': 'Homework', 'subscores' : homework_percentages, 'dropped_indices' : homework_dropped_indices, - 'totalscore' : {'score' : homework_total, 'summary' : "Homework Average - {0:.0%}".format(homework_total)}, + 'totalscore' : homework_total, + 'totalscore_summary' : "Homework Average - {0:.0%}".format(homework_total), 'totallabel' : 'HW Avg', 'weight' : 0.15, }, @@ -204,25 +238,25 @@ def grade_sheet(student): 'category': 'Labs', 'subscores' : lab_percentages, 'dropped_indices' : lab_dropped_indices, - 'totalscore' : {'score' : lab_total, 'summary' : "Lab Average - {0:.0%}".format(lab_total)}, + 'totalscore' : lab_total, + 'totalscore_summary' : "Lab Average - {0:.0%}".format(lab_total), 'totallabel' : 'Lab Avg', 'weight' : 0.15, }, { 'category': 'Midterm', - 'totalscore' : {'score' : midterm_percentage, 'summary' : "Midterm - {0:.0%} ({1}/{2})".format(midterm_percentage, midterm_score[0], midterm_score[1])}, + 'totalscore' : midterm_percentage, + 'totalscore_summary' : "Midterm - {0:.0%} ({1}/{2})".format(midterm_percentage, midterm_score.earned, midterm_score.possible), 'totallabel' : 'Midterm', 'weight' : 0.30, }, { 'category': 'Final', - 'totalscore' : {'score' : final_percentage, 'summary' : "Final - {0:.0%} ({1}/{2})".format(final_percentage, final_score[0], final_score[1])}, + 'totalscore' : final_percentage, + 'totalscore_summary' : "Final - {0:.0%} ({1}/{2})".format(final_percentage, final_score.earned, final_score.possible), 'totallabel' : 'Final', 'weight' : 0.40, } ] - return {'grade_summary' : grade_summary, - 'chapters':chapters} - - + return grade_summary diff --git a/djangoapps/courseware/modules/capa_module.py b/djangoapps/courseware/modules/capa_module.py index 17ee6264bd..7ec92ecd44 100644 --- a/djangoapps/courseware/modules/capa_module.py +++ b/djangoapps/courseware/modules/capa_module.py @@ -26,6 +26,12 @@ import courseware.content_parser as content_parser log = logging.getLogger("mitx.courseware") +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, complex): + return "{real:.7g}{imag:+.7g}*j".format(real = obj.real,imag = obj.imag) + return json.JSONEncoder.default(self, obj) + class Module(XModule): ''' Interface between capa_problem and x_module. Originally a hack meant to be refactored out, but it seems to be serving a useful @@ -240,7 +246,8 @@ class Module(XModule): if not self.answer_available(): raise Http404 else: - return json.dumps(self.lcp.get_question_answers()) + return json.dumps(self.lcp.get_question_answers(), + cls=ComplexEncoder) # Figure out if we should move these to capa_problem? diff --git a/djangoapps/courseware/tests.py b/djangoapps/courseware/tests.py index b0d7cad19f..2b2b354177 100644 --- a/djangoapps/courseware/tests.py +++ b/djangoapps/courseware/tests.py @@ -20,7 +20,7 @@ class ModelsTest(unittest.TestCase): variables={'R1':2.0, 'R3':4.0} functions={'sin':numpy.sin, 'cos':numpy.cos} - self.assertEqual(calc.evaluator(variables, functions, "10000||sin(7+5)-6k"), 4000.0) + self.assertTrue(abs(calc.evaluator(variables, functions, "10000||sin(7+5)+0.5356"))<0.01) self.assertEqual(calc.evaluator({'R1': 2.0, 'R3':4.0}, {}, "13"), 13) self.assertEqual(calc.evaluator(variables, functions, "13"), 13) self.assertEqual(calc.evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5) @@ -30,6 +30,8 @@ class ModelsTest(unittest.TestCase): self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0) self.assertTrue(abs(calc.evaluator(variables, functions, "sin(e)-0.41"))<0.01) self.assertTrue(abs(calc.evaluator(variables, functions, "k*T/q-0.025"))<0.001) + self.assertTrue(abs(calc.evaluator(variables, functions, "e^(j*pi)")+1)<0.00001) + self.assertTrue(abs(calc.evaluator(variables, functions, "j||1")-0.5-0.5j)<0.00001) exception_happened = False try: calc.evaluator({},{}, "5+7 QWSEKO") diff --git a/djangoapps/student/migrations/0005_name_change.py b/djangoapps/student/migrations/0005_name_change.py new file mode 100644 index 0000000000..77265bcddd --- /dev/null +++ b/djangoapps/student/migrations/0005_name_change.py @@ -0,0 +1,146 @@ +# encoding: utf-8 +import datetime +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)), + ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)), + ('new_email', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=255, blank=True)), + ('activation_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=32, db_index=True)), + )) + db.send_create_signal('student', ['PendingEmailChange']) + + # Adding model 'PendingNameChange' + db.create_table('student_pendingnamechange', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)), + ('new_name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('rationale', self.gf('django.db.models.fields.CharField')(max_length=1024, blank=True)), + )) + db.send_create_signal('student', ['PendingNameChange']) + + # 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') + + # Deleting model 'PendingNameChange' + db.delete_table('student_pendingnamechange') + + # 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'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'meta': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] diff --git a/djangoapps/student/models.py b/djangoapps/student/models.py index f4498c396b..a4c01d52fc 100644 --- a/djangoapps/student/models.py +++ b/djangoapps/student/models.py @@ -12,6 +12,7 @@ import uuid from django.db import models from django.contrib.auth.models import User +import json #from cache_toolbox import cache_model, cache_relation @@ -29,6 +30,18 @@ class UserProfile(models.Model): meta = models.CharField(blank=True, max_length=255) # JSON dictionary for future expansion courseware = models.CharField(blank=True, max_length=255, default='course.xml') + def get_meta(self): + js_str = self.meta + if not js_str: + js_str = dict() + else: + js_str = json.loads(self.meta) + + return js_str + + 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): @@ -58,6 +71,16 @@ 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) + #cache_relation(User.profile) #### Helper methods for use from python manage.py shell. @@ -88,6 +111,8 @@ def change_name(email, new_name): up.save() def user_count(): + print "All users", User.objects.all().count() + print "Active users", User.objects.filter(is_active = True).count() return User.objects.all().count() def active_user_count(): @@ -99,12 +124,29 @@ def create_group(name, description): utg.description = description utg.save() -def add_user_to_group(group, user): +def add_user_to_group(user, group): utg = UserTestGroup.objects.get(name = group) utg.users.add(User.objects.get(username = user)) utg.save() -def remove_user_from_group(group, user): +def remove_user_from_group(user, group): 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'} + +def add_user_to_default_group(user, group): + try: + 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.save() + diff --git a/djangoapps/student/views.py b/djangoapps/student/views.py index c012666b84..a7b02b5bb1 100644 --- a/djangoapps/student/views.py +++ b/djangoapps/student/views.py @@ -1,25 +1,33 @@ +import datetime import json import logging import random import string +import sys +import uuid from django.conf import settings from django.contrib.auth import logout, authenticate, login from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.models import User from django.core.context_processors import csrf -from django.core.validators import validate_email, validate_slug +from django.core.mail import send_mail +from django.core.validators import validate_email, validate_slug, ValidationError from django.db import connection from django.http import HttpResponse, Http404 from django.shortcuts import redirect from mitxmako.shortcuts import render_to_response, render_to_string +from mako import exceptions -from models import Registration, UserProfile from django_future.csrf import ensure_csrf_cookie +from models import Registration, UserProfile, PendingNameChange, PendingEmailChange + log = logging.getLogger("mitx.student") def csrf_token(context): + ''' A csrf token that can be included in a form. + ''' csrf_token = context.get('csrf_token', '') if csrf_token == 'NOTPROVIDED': return '' @@ -27,6 +35,8 @@ def csrf_token(context): @ensure_csrf_cookie def index(request): + ''' Redirects to main page -- info page if user authenticated, or marketing if not + ''' if settings.COURSEWARE_ENABLED and request.user.is_authenticated(): return redirect('/info') else: @@ -37,6 +47,7 @@ def index(request): # Need different levels of logging @ensure_csrf_cookie def login_user(request, error=""): + ''' AJAX request to log in the user. ''' if 'email' not in request.POST or 'password' not in request.POST: return HttpResponse(json.dumps({'success':False, 'error': 'Invalid login'})) # TODO: User error message @@ -78,20 +89,20 @@ def login_user(request, error=""): @ensure_csrf_cookie def logout_user(request): + ''' HTTP request to log in the user. Redirects to marketing page''' logout(request) -# print len(connection.queries), connection.queries return redirect('/') @ensure_csrf_cookie def change_setting(request): + ''' JSON call to change a profile setting: Right now, location and language + ''' if not request.user.is_authenticated(): return redirect('/') up = UserProfile.objects.get(user=request.user) #request.user.profile_cache if 'location' in request.POST: -# print "loc" up.location=request.POST['location'] if 'language' in request.POST: -# print "lang" up.language=request.POST['language'] up.save() @@ -101,6 +112,7 @@ def change_setting(request): @ensure_csrf_cookie def create_account(request, post_override=None): + ''' JSON call to enroll in the course. ''' js={'success':False} post_vars = post_override if post_override else request.POST @@ -111,8 +123,6 @@ def create_account(request, post_override=None): js['value']="Error (401 {field}). E-mail us.".format(field=a) return HttpResponse(json.dumps(js)) - - if post_vars['honor_code']!=u'true': js['value']="To enroll, you must follow the honor code.".format(field=a) return HttpResponse(json.dumps(js)) @@ -140,13 +150,13 @@ def create_account(request, post_override=None): try: validate_email(post_vars['email']) - except: + except ValidationError: js['value']="Valid e-mail is required.".format(field=a) return HttpResponse(json.dumps(js)) try: validate_slug(post_vars['username']) - except: + except ValidationError: js['value']="Username should only consist of A-Z and 0-9.".format(field=a) return HttpResponse(json.dumps(js)) @@ -178,25 +188,24 @@ def create_account(request, post_override=None): up.save() d={'name':post_vars['name'], - 'key':r.activation_key, - 'site':settings.SITE_NAME} + 'key':r.activation_key} - subject = render_to_string('activation_email_subject.txt',d) + subject = render_to_string('emails/activation_email_subject.txt',d) # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) - message = render_to_string('activation_email.txt',d) + message = render_to_string('emails/activation_email.txt',d) try: if not settings.GENERATE_RANDOM_USER_CREDENTIALS: res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) except: - js['value']=str(sys.exc_info()) + log.exception(sys.exc_info()) + js['value']='Could not send activation e-mail.' return HttpResponse(json.dumps(js)) js={'success':True, 'value':render_to_string('registration/reg_complete.html', {'email':post_vars['email'], 'csrf':csrf(request)['csrf_token']})} -# print len(connection.queries), connection.queries return HttpResponse(json.dumps(js), mimetype="application/json") def create_random_account(create_account_function): @@ -214,8 +223,6 @@ def create_random_account(create_account_function): 'honor_code' : u'true', 'terms_of_service' : u'true',} -# print "Creating random account: " , post_override - return create_account_function(request, post_override = post_override) return inner_create_random_account @@ -225,6 +232,8 @@ if settings.GENERATE_RANDOM_USER_CREDENTIALS: @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: if not r[0].user.is_active: @@ -252,3 +261,198 @@ def password_reset(request): else: 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') + except User.DoesNotExist: + 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, + 'error': 'User is already active'})) + + reg = Registration.objects.get(user = user) + reg.register(user) + + d={'name':UserProfile.get(user = user).name, + 'key':r.activation_key} + + subject = render_to_string('reactivation_email_subject.txt',d) + subject = ''.join(subject.splitlines()) + message = render_to_string('reactivation_email.txt',d) + + res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + + return HttpResponse(json.dumps({'success':True})) + + +@ensure_csrf_cookie +def change_email_request(request): + ''' AJAX call from the profile page. User wants a new e-mail. + ''' + ## Make sure it checks for existing e-mail conflicts + if not request.user.is_authenticated: + raise Http404 + + user = request.user + + if not user.check_password(request.POST['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.'})) + + 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.'})) + + pec_list = PendingEmailChange.objects.filter(user = request.user) + if len(pec_list) == 0: + pec = PendingEmailChange() + pec.user = user + else : + pec = pec_list[0] + + pec.new_email = request.POST['new_email'] + pec.activation_key = uuid.uuid4().hex + pec.save() + + if pec.new_email == user.email: + pec.delete() + 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} + + subject = render_to_string('emails/email_change_subject.txt',d) + subject = ''.join(subject.splitlines()) + message = render_to_string('emails/email_change.txt',d) + + res=send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [pec.new_email]) + + return HttpResponse(json.dumps({'success':True})) + +@ensure_csrf_cookie +def confirm_email_change(request, key): + ''' User requested a new e-mail. This is called when the activation + link is clicked. We confirm with the old e-mail, and update + ''' + try: + 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} + + 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 = ''.join(subject.splitlines()) + 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'] = [] + meta['old_emails'].append([user.email, datetime.datetime.now().isoformat()]) + up.set_meta(meta) + up.save() + user.email = pec.new_email + user.save() + pec.delete() + user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + + return render_to_response("email_change_successful.html", d) + +@ensure_csrf_cookie +def change_name_request(request): + ''' Log a request for a new name. ''' + if not request.user.is_authenticated: + raise Http404 + + try: + 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'})) + pnc.save() + return HttpResponse(json.dumps({'success':True})) + +@ensure_csrf_cookie +def pending_name_changes(request): + ''' Web page which allows staff to approve or reject name changes. ''' + if not request.user.is_staff: + raise Http404 + + changes = list(PendingNameChange.objects.all()) + js = {'students' : [{'new_name': c.new_name, + 'rationale':c.rationale, + 'old_name':UserProfile.objects.get(user=c.user).name, + 'email':c.user.email, + 'uid':c.user.id, + 'cid':c.id} for c in changes]} + return render_to_response('name_changes.html', js) + +@ensure_csrf_cookie +def reject_name_change(request): + ''' JSON: Name change process. Course staff clicks 'reject' on a given name change ''' + if not request.user.is_staff: + raise Http404 + + try: + pnc = PendingNameChange.objects.get(id = int(request.POST['id'])) + except PendingNameChange.DoesNotExist: + return HttpResponse(json.dumps({'success':False, 'error':'Invalid ID'})) + + pnc.delete() + return HttpResponse(json.dumps({'success':True})) + +@ensure_csrf_cookie +def accept_name_change(request): + ''' JSON: Name change process. Course staff clicks 'accept' on a given name change ''' + if not request.user.is_staff: + raise Http404 + + try: + pnc = PendingNameChange.objects.get(id = int(request.POST['id'])) + except PendingNameChange.DoesNotExist: + return HttpResponse(json.dumps({'success':False, 'error':'Invalid ID'})) + + u = pnc.user + up = UserProfile.objects.get(user=u) + + # Save old name + meta = up.get_meta() + if 'old_names' not in meta: + meta['old_names'] = [] + meta['old_names'].append([up.name, pnc.rationale, datetime.datetime.now().isoformat()]) + up.set_meta(meta) + + up.name = pnc.new_name + up.save() + pnc.delete() + + return HttpResponse(json.dumps({'success':True})) diff --git a/lib/mitxmako/middleware.py b/lib/mitxmako/middleware.py index 857ae29541..1d175abbf7 100644 --- a/lib/mitxmako/middleware.py +++ b/lib/mitxmako/middleware.py @@ -7,7 +7,7 @@ # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, +# distribuetd under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. @@ -15,6 +15,7 @@ from mako.lookup import TemplateLookup import tempfile from django.template import RequestContext +from django.conf import settings requestcontext = None lookup = {} @@ -44,4 +45,5 @@ class MakoMiddleware(object): def process_request (self, request): global requestcontext requestcontext = RequestContext(request) - + requestcontext['is_secure'] = request.is_secure() + requestcontext['site'] = settings.SITE_NAME diff --git a/lib/mitxmako/shortcuts.py b/lib/mitxmako/shortcuts.py index 862f8b96bb..ca626b5c85 100644 --- a/lib/mitxmako/shortcuts.py +++ b/lib/mitxmako/shortcuts.py @@ -18,18 +18,21 @@ from django.http import HttpResponse import mitxmako.middleware as middleware from django.conf import settings -from mitxmako.middleware import requestcontext +import mitxmako.middleware -def render_to_string(template_name, dictionary, context_instance=None, namespace='main'): - context_instance = context_instance or Context(dictionary) +def render_to_string(template_name, dictionary, context=None, namespace='main'): + context_instance = Context(dictionary) # add dictionary to context_instance context_instance.update(dictionary or {}) # collapse context_instance to a single dictionary for mako context_dictionary = {} context_instance['settings'] = settings - context_instance['request_context'] = requestcontext + for d in mitxmako.middleware.requestcontext: + context_dictionary.update(d) for d in context_instance: context_dictionary.update(d) + if context: + context_dictionary.update(context) # fetch and render template template = middleware.lookup[namespace].get_template(template_name) return template.render(**context_dictionary) diff --git a/pre-requirements.txt b/pre-requirements.txt new file mode 100644 index 0000000000..24ce15ab7e --- /dev/null +++ b/pre-requirements.txt @@ -0,0 +1 @@ +numpy diff --git a/rakefile b/rakefile new file mode 100644 index 0000000000..414bb77c5d --- /dev/null +++ b/rakefile @@ -0,0 +1,78 @@ +require 'rake/clean' +require 'tempfile' + +# Build Constants +REPO_ROOT = File.dirname(__FILE__) +BUILD_DIR = File.join(REPO_ROOT, "build") + +# Packaging constants +DEPLOY_DIR = "/opt/wwc" +PACKAGE_NAME = "mitx" +LINK_PATH = File.join(DEPLOY_DIR, PACKAGE_NAME) +VERSION = "0.1" +COMMIT = (ENV["GIT_COMMIT"] || `git rev-parse HEAD`).chomp()[0, 10] +BRANCH = (ENV["GIT_BRANCH"] || `git symbolic-ref -q HEAD`).chomp().gsub('refs/heads/', '').gsub('origin/', '').gsub('/', '_') +BUILD_NUMBER = (ENV["BUILD_NUMBER"] || "dev").chomp() + +if BRANCH == "master" + DEPLOY_NAME = "#{PACKAGE_NAME}-#{BUILD_NUMBER}-#{COMMIT}" +else + DEPLOY_NAME = "#{PACKAGE_NAME}-#{BRANCH}-#{BUILD_NUMBER}-#{COMMIT}" +end +INSTALL_DIR_PATH = File.join(DEPLOY_DIR, DEPLOY_NAME) +PACKAGE_REPO = "packages@gp.mitx.mit.edu:/opt/pkgrepo.incoming" + + +# Set up the clean and clobber tasks +CLOBBER.include('build') +CLEAN.include("#{BUILD_DIR}/*.deb", "#{BUILD_DIR}/util") + + +task :package do + FileUtils.mkdir_p(BUILD_DIR) + + Dir.chdir(BUILD_DIR) do + + postinstall = Tempfile.new('postinstall') + postinstall.write <<-POSTINSTALL.gsub(/^\s*/, '') + #! /bin/sh + set -e + set -x + + service gunicorn stop + rm -f #{LINK_PATH} + ln -s #{INSTALL_DIR_PATH} #{LINK_PATH} + service gunicorn start + POSTINSTALL + postinstall.close() + FileUtils.chmod(0755, postinstall.path) + + args = ["fakeroot", "fpm", "-s", "dir", "-t", "deb", + "--after-install=#{postinstall.path}", + "--prefix=#{INSTALL_DIR_PATH}", + "-C", "#{REPO_ROOT}", + "--depends=python-mysqldb", + "--depends=python-django", + "--depends=python-pip", + "--depends=python-flup", + "--depends=python-numpy", + "--depends=python-scipy", + "--depends=python-matplotlib", + "--depends=python-libxml2", + "--depends=python2.7-dev", + "--depends=libxml2-dev", + "--depends=libxslt-dev", + "--depends=python-markdown", + "--depends=python-pygments", + "--depends=mysql-client", + "--name=#{DEPLOY_NAME}", + "--version=#{VERSION}", + "-a", "all", + "."] + system(*args) || raise("fpm failed to build the .deb") + end +end + +task :publish => :package do + sh("scp #{BUILD_DIR}/#{DEPLOY_NAME}_#{VERSION}-1_all.deb #{PACKAGE_REPO}") +end diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..a9fc71e6c9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +django<1.4 +pip +flup +scipy +matplotlib +markdown +pygments +django-mako +django-ses +lxml +boto +mako +python-memcached +django-celery diff --git a/settings.py b/settings.py index 0e9285f296..105d64be9f 100644 --- a/settings.py +++ b/settings.py @@ -166,6 +166,15 @@ MAKO_TEMPLATES = {} LOGGING_ENV = "dev" # override this in different environments +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': '../mitx.db', + } +} + +SECRET_KEY = 'unsecure' + # Default dev cache (i.e. no caching) CACHES = { 'default': { @@ -174,7 +183,9 @@ CACHES = { } # Make sure we execute correctly regardless of where we're called from -execfile(os.path.join(BASE_DIR, "settings.py")) +override_settings = os.path.join(BASE_DIR, "settings.py") +if os.path.isfile(override_settings): + execfile(override_settings) # A sample logging configuration. The only tangible logging # performed by this configuration is to send an email to @@ -238,7 +249,7 @@ LOGGING = { }, 'loggers' : { 'django' : { - 'handlers' : handlers + ['mail_admins'], + 'handlers' : handlers, # + ['mail_admins'], 'propagate' : True, 'level' : 'INFO' }, diff --git a/static/css/application.css b/static/css/application.css index 672788420a..8cc48307f1 100644 --- a/static/css/application.css +++ b/static/css/application.css @@ -117,7 +117,7 @@ input, select { font-weight: 800; font-style: italic; } -.clearfix:after, .topbar:after, nav.sequence-nav:after, div.book-wrapper section.book nav:after, div.wiki-wrapper section.wiki-body header:after, html body section.main-content:after, html body section.outside-app:after, div.header-wrapper header:after, div.header-wrapper header hgroup:after, div.header-wrapper header nav ul:after, footer:after, li.calc-main div#calculator_wrapper form:after, div.leanModal_box#enroll ol:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls div#slider:after, section.course-content nav.sequence-bottom ul:after, div#graph-container:after, div#schematic-container:after, div.book-wrapper section.book nav ul:after, div.info-wrapper section.updates > ol > li:after, div.info-wrapper section.handouts ol li:after, div.profile-wrapper section.course-info > ol > li:after, div#wiki_panel div#wiki_create_form:after, div.wiki-wrapper section.wiki-body:after, ul.badge-list li.badge:after { +.clearfix:after, .topbar:after, nav.sequence-nav:after, div.book-wrapper section.book nav:after, div.wiki-wrapper section.wiki-body header:after, html body section.main-content:after, html body section.outside-app:after, div.header-wrapper header:after, div.header-wrapper header hgroup:after, div.header-wrapper header nav ul:after, footer:after, li.calc-main div#calculator_wrapper form:after, li.calc-main div#calculator_wrapper form div.input-wrapper:after, div.leanModal_box#enroll ol:after, div.course-wrapper section.course-content .problem-set:after, div.course-wrapper section.course-content section.problems-wrapper:after, div.course-wrapper section.course-content div#seq_content:after, div.course-wrapper section.course-content ol.vert-mod > li:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls:after, section.course-content div.video-subtitles div.video-wrapper section.video-controls div#slider:after, section.course-content nav.sequence-bottom ul:after, div#graph-container:after, div#schematic-container:after, div.book-wrapper section.book nav ul:after, div.info-wrapper section.updates > ol > li:after, div.info-wrapper section.handouts ol li:after, div.profile-wrapper section.course-info header:after, div.profile-wrapper section.course-info > ol > li:after, div#wiki_panel div#wiki_create_form:after, div.wiki-wrapper section.wiki-body:after, ul.badge-list li.badge:after { content: "."; display: block; height: 0; @@ -134,7 +134,7 @@ input, select { display: table; width: 100%; } -h1.top-header, div.course-wrapper section.course-content h1, div.info-wrapper section.updates > h1, div.profile-wrapper section.course-info > h1, div.gradebook-wrapper section.gradebook-content h1, div.question-list-header { +h1.top-header, div.course-wrapper section.course-content h1, div.info-wrapper section.updates > h1, div.profile-wrapper section.course-info header, div.gradebook-wrapper section.gradebook-content h1, div.question-list-header { background: #f3f3f3; border-bottom: 1px solid #e3e3e3; margin: -22.652px -22.652px 22.652px; @@ -222,7 +222,7 @@ h1.top-header, div.course-wrapper section.course-content h1, div.info-wrapper se .sidebar a, section.course-index a, div.book-wrapper section.book-sidebar a, div.info-wrapper section.handouts a, div.profile-wrapper section.user-info a, div#wiki_panel a, div.discussion-wrapper aside a { font-style: normal; border: none; } -.sidebar .bottom-border, section.course-index .bottom-border, div.book-wrapper section.book-sidebar .bottom-border, div.info-wrapper section.handouts .bottom-border, div.profile-wrapper section.user-info .bottom-border, div#wiki_panel .bottom-border, div.discussion-wrapper aside .bottom-border, .sidebar h3, section.course-index .sidebar h3, .sidebar section.course-index h3, div.book-wrapper section.book-sidebar .sidebar h3, .sidebar div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts .sidebar h3, .sidebar div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info .sidebar h3, .sidebar div.profile-wrapper section.user-info h3, div#wiki_panel .sidebar h3, .sidebar div#wiki_panel h3, div.discussion-wrapper aside .sidebar h3, .sidebar div.discussion-wrapper aside h3, .sidebar section.course-index h3, section.course-index .sidebar h3, section.course-index h3, div.book-wrapper section.book-sidebar section.course-index h3, section.course-index div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts section.course-index h3, section.course-index div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info section.course-index h3, section.course-index div.profile-wrapper section.user-info h3, div#wiki_panel section.course-index h3, section.course-index div#wiki_panel h3, div.discussion-wrapper aside section.course-index h3, section.course-index div.discussion-wrapper aside h3, .sidebar div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar .sidebar h3, section.course-index div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar section.course-index h3, div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info h3, div#wiki_panel div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div#wiki_panel h3, div.discussion-wrapper aside div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div.discussion-wrapper aside h3, .sidebar div.info-wrapper section.handouts h3, div.info-wrapper section.handouts .sidebar h3, section.course-index div.info-wrapper section.handouts h3, div.info-wrapper section.handouts section.course-index h3, div.book-wrapper section.book-sidebar div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div.profile-wrapper section.user-info h3, div#wiki_panel div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div#wiki_panel h3, div.discussion-wrapper aside div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div.discussion-wrapper aside h3, .sidebar div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info .sidebar h3, section.course-index div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info section.course-index h3, div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info h3, div#wiki_panel div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div#wiki_panel h3, div.discussion-wrapper aside div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div.discussion-wrapper aside h3, .sidebar div#wiki_panel h3, div#wiki_panel .sidebar h3, section.course-index div#wiki_panel h3, div#wiki_panel section.course-index h3, div.book-wrapper section.book-sidebar div#wiki_panel h3, div#wiki_panel div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div#wiki_panel h3, div#wiki_panel div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div#wiki_panel h3, div#wiki_panel div.profile-wrapper section.user-info h3, div#wiki_panel h3, div.discussion-wrapper aside div#wiki_panel h3, div#wiki_panel div.discussion-wrapper aside h3, .sidebar div.discussion-wrapper aside h3, div.discussion-wrapper aside .sidebar h3, section.course-index div.discussion-wrapper aside h3, div.discussion-wrapper aside section.course-index h3, div.book-wrapper section.book-sidebar div.discussion-wrapper aside h3, div.discussion-wrapper aside div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div.discussion-wrapper aside h3, div.discussion-wrapper aside div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div.discussion-wrapper aside h3, div.discussion-wrapper aside div.profile-wrapper section.user-info h3, div#wiki_panel div.discussion-wrapper aside h3, div.discussion-wrapper aside div#wiki_panel h3, div.discussion-wrapper aside h3, .sidebar div#wiki_panel input[type="button"], section.course-index .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], .sidebar div#wiki_panel input[type="button"], div.discussion-wrapper aside .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel .sidebar input[type="button"], section.course-index div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.profile-wrapper section.user-info input[type="button"], div#wiki_panel .sidebar input[type="button"], div.discussion-wrapper aside div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.discussion-wrapper aside input[type="button"], .sidebar section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel .sidebar input[type="button"], section.course-index div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.profile-wrapper section.user-info input[type="button"], section.course-index div#wiki_panel input[type="button"], div.discussion-wrapper aside section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index .sidebar input[type="button"], div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.profile-wrapper section.user-info input[type="button"], div#wiki_panel section.course-index input[type="button"], div.discussion-wrapper aside div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.discussion-wrapper aside input[type="button"], .sidebar div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel .sidebar input[type="button"], section.course-index div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.info-wrapper section.handouts div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.discussion-wrapper aside div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar .sidebar input[type="button"], section.course-index div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar section.course-index input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.discussion-wrapper aside div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar div.discussion-wrapper aside input[type="button"], .sidebar div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel .sidebar input[type="button"], section.course-index div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.discussion-wrapper aside div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts .sidebar input[type="button"], section.course-index div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.discussion-wrapper aside div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts div.discussion-wrapper aside input[type="button"], .sidebar div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel .sidebar input[type="button"], section.course-index div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.discussion-wrapper aside div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info .sidebar input[type="button"], section.course-index div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.discussion-wrapper aside div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel input[type="button"], div#wiki_panel .sidebar input[type="button"], section.course-index div#wiki_panel input[type="button"], div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel input[type="button"], div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel input[type="button"], div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel .sidebar input[type="button"], section.course-index div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside .sidebar input[type="button"], section.course-index div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div.info-wrapper section.handouts h1, div.info-wrapper section.handouts .sidebar h1, section.course-index div.info-wrapper section.handouts h1, div.info-wrapper section.handouts section.course-index h1, div.book-wrapper section.book-sidebar div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div.book-wrapper section.book-sidebar h1, div.info-wrapper section.handouts h1, div.profile-wrapper section.user-info div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div.profile-wrapper section.user-info h1, div#wiki_panel div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div#wiki_panel h1, div.discussion-wrapper aside div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div.discussion-wrapper aside h1, .sidebar div.profile-wrapper section.user-info h1, div.profile-wrapper section.user-info .sidebar h1, section.course-index div.profile-wrapper section.user-info h1, div.profile-wrapper section.user-info section.course-index h1, div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info h1, div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar h1, div.info-wrapper section.handouts div.profile-wrapper section.user-info h1, div.profile-wrapper section.user-info div.info-wrapper section.handouts h1, div.profile-wrapper section.user-info h1, div#wiki_panel div.profile-wrapper section.user-info h1, div.profile-wrapper section.user-info div#wiki_panel h1, div.discussion-wrapper aside div.profile-wrapper section.user-info h1, div.profile-wrapper section.user-info div.discussion-wrapper aside h1, .sidebar div#wiki_panel h2, div#wiki_panel .sidebar h2, section.course-index div#wiki_panel h2, div#wiki_panel section.course-index h2, div.book-wrapper section.book-sidebar div#wiki_panel h2, div#wiki_panel div.book-wrapper section.book-sidebar h2, div.info-wrapper section.handouts div#wiki_panel h2, div#wiki_panel div.info-wrapper section.handouts h2, div.profile-wrapper section.user-info div#wiki_panel h2, div#wiki_panel div.profile-wrapper section.user-info h2, div#wiki_panel h2, div.discussion-wrapper aside div#wiki_panel h2, div#wiki_panel div.discussion-wrapper aside h2, .sidebar div.discussion-wrapper aside h1, div.discussion-wrapper aside .sidebar h1, section.course-index div.discussion-wrapper aside h1, div.discussion-wrapper aside section.course-index h1, div.book-wrapper section.book-sidebar div.discussion-wrapper aside h1, div.discussion-wrapper aside div.book-wrapper section.book-sidebar h1, div.info-wrapper section.handouts div.discussion-wrapper aside h1, div.discussion-wrapper aside div.info-wrapper section.handouts h1, div.profile-wrapper section.user-info div.discussion-wrapper aside h1, div.discussion-wrapper aside div.profile-wrapper section.user-info h1, div#wiki_panel div.discussion-wrapper aside h1, div.discussion-wrapper aside div#wiki_panel h1, div.discussion-wrapper aside h1 { +.sidebar .bottom-border, section.course-index .bottom-border, div.book-wrapper section.book-sidebar .bottom-border, div.info-wrapper section.handouts .bottom-border, div.profile-wrapper section.user-info .bottom-border, div#wiki_panel .bottom-border, div.discussion-wrapper aside .bottom-border, .sidebar h3, section.course-index .sidebar h3, .sidebar section.course-index h3, div.book-wrapper section.book-sidebar .sidebar h3, .sidebar div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts .sidebar h3, .sidebar div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info .sidebar h3, .sidebar div.profile-wrapper section.user-info h3, div#wiki_panel .sidebar h3, .sidebar div#wiki_panel h3, div.discussion-wrapper aside .sidebar h3, .sidebar div.discussion-wrapper aside h3, .sidebar section.course-index h3, section.course-index .sidebar h3, section.course-index h3, div.book-wrapper section.book-sidebar section.course-index h3, section.course-index div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts section.course-index h3, section.course-index div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info section.course-index h3, section.course-index div.profile-wrapper section.user-info h3, div#wiki_panel section.course-index h3, section.course-index div#wiki_panel h3, div.discussion-wrapper aside section.course-index h3, section.course-index div.discussion-wrapper aside h3, .sidebar div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar .sidebar h3, section.course-index div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar section.course-index h3, div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info h3, div#wiki_panel div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div#wiki_panel h3, div.discussion-wrapper aside div.book-wrapper section.book-sidebar h3, div.book-wrapper section.book-sidebar div.discussion-wrapper aside h3, .sidebar div.info-wrapper section.handouts h3, div.info-wrapper section.handouts .sidebar h3, section.course-index div.info-wrapper section.handouts h3, div.info-wrapper section.handouts section.course-index h3, div.book-wrapper section.book-sidebar div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div.profile-wrapper section.user-info h3, div#wiki_panel div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div#wiki_panel h3, div.discussion-wrapper aside div.info-wrapper section.handouts h3, div.info-wrapper section.handouts div.discussion-wrapper aside h3, .sidebar div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info .sidebar h3, section.course-index div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info section.course-index h3, div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info h3, div#wiki_panel div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div#wiki_panel h3, div.discussion-wrapper aside div.profile-wrapper section.user-info h3, div.profile-wrapper section.user-info div.discussion-wrapper aside h3, .sidebar div#wiki_panel h3, div#wiki_panel .sidebar h3, section.course-index div#wiki_panel h3, div#wiki_panel section.course-index h3, div.book-wrapper section.book-sidebar div#wiki_panel h3, div#wiki_panel div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div#wiki_panel h3, div#wiki_panel div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div#wiki_panel h3, div#wiki_panel div.profile-wrapper section.user-info h3, div#wiki_panel h3, div.discussion-wrapper aside div#wiki_panel h3, div#wiki_panel div.discussion-wrapper aside h3, .sidebar div.discussion-wrapper aside h3, div.discussion-wrapper aside .sidebar h3, section.course-index div.discussion-wrapper aside h3, div.discussion-wrapper aside section.course-index h3, div.book-wrapper section.book-sidebar div.discussion-wrapper aside h3, div.discussion-wrapper aside div.book-wrapper section.book-sidebar h3, div.info-wrapper section.handouts div.discussion-wrapper aside h3, div.discussion-wrapper aside div.info-wrapper section.handouts h3, div.profile-wrapper section.user-info div.discussion-wrapper aside h3, div.discussion-wrapper aside div.profile-wrapper section.user-info h3, div#wiki_panel div.discussion-wrapper aside h3, div.discussion-wrapper aside div#wiki_panel h3, div.discussion-wrapper aside h3, .sidebar div#wiki_panel input[type="button"], section.course-index .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], .sidebar div#wiki_panel input[type="button"], div.discussion-wrapper aside .sidebar div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel .sidebar input[type="button"], section.course-index div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.profile-wrapper section.user-info input[type="button"], div#wiki_panel .sidebar input[type="button"], div.discussion-wrapper aside div#wiki_panel .sidebar input[type="button"], div#wiki_panel .sidebar div.discussion-wrapper aside input[type="button"], .sidebar section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel .sidebar input[type="button"], section.course-index div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.profile-wrapper section.user-info input[type="button"], section.course-index div#wiki_panel input[type="button"], div.discussion-wrapper aside section.course-index div#wiki_panel input[type="button"], section.course-index div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index .sidebar input[type="button"], div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.profile-wrapper section.user-info input[type="button"], div#wiki_panel section.course-index input[type="button"], div.discussion-wrapper aside div#wiki_panel section.course-index input[type="button"], div#wiki_panel section.course-index div.discussion-wrapper aside input[type="button"], .sidebar div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel .sidebar input[type="button"], section.course-index div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.info-wrapper section.handouts div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.discussion-wrapper aside div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar .sidebar input[type="button"], section.course-index div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar section.course-index input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.discussion-wrapper aside div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar div.discussion-wrapper aside input[type="button"], .sidebar div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel .sidebar input[type="button"], section.course-index div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.discussion-wrapper aside div.info-wrapper section.handouts div#wiki_panel input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts .sidebar input[type="button"], section.course-index div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts div.book-wrapper section.book-sidebar input[type="button"], div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.discussion-wrapper aside div#wiki_panel div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.info-wrapper section.handouts div.discussion-wrapper aside input[type="button"], .sidebar div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel .sidebar input[type="button"], section.course-index div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.discussion-wrapper aside div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info .sidebar input[type="button"], section.course-index div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info div.info-wrapper section.handouts input[type="button"], div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.discussion-wrapper aside div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.profile-wrapper section.user-info div.discussion-wrapper aside input[type="button"], .sidebar div#wiki_panel input[type="button"], div#wiki_panel .sidebar input[type="button"], section.course-index div#wiki_panel input[type="button"], div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel input[type="button"], div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel input[type="button"], div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel input[type="button"], div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel input[type="button"], div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel .sidebar input[type="button"], section.course-index div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel section.course-index input[type="button"], div.book-wrapper section.book-sidebar div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel div.profile-wrapper section.user-info input[type="button"], div.discussion-wrapper aside div#wiki_panel input[type="button"], div.discussion-wrapper aside div#wiki_panel input[type="button"], .sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside .sidebar input[type="button"], section.course-index div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside section.course-index input[type="button"], div.book-wrapper section.book-sidebar div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside div.book-wrapper section.book-sidebar input[type="button"], div.info-wrapper section.handouts div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside div.info-wrapper section.handouts input[type="button"], div.profile-wrapper section.user-info div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside div.profile-wrapper section.user-info input[type="button"], div#wiki_panel div.discussion-wrapper aside input[type="button"], div#wiki_panel div.discussion-wrapper aside input[type="button"], .sidebar div.info-wrapper section.handouts h1, div.info-wrapper section.handouts .sidebar h1, section.course-index div.info-wrapper section.handouts h1, div.info-wrapper section.handouts section.course-index h1, div.book-wrapper section.book-sidebar div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div.book-wrapper section.book-sidebar h1, div.info-wrapper section.handouts h1, div.profile-wrapper section.user-info div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div.profile-wrapper section.user-info h1, div#wiki_panel div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div#wiki_panel h1, div.discussion-wrapper aside div.info-wrapper section.handouts h1, div.info-wrapper section.handouts div.discussion-wrapper aside h1, .sidebar div.profile-wrapper section.user-info header, div.profile-wrapper section.user-info .sidebar header, section.course-index div.profile-wrapper section.user-info header, div.profile-wrapper section.user-info section.course-index header, div.book-wrapper section.book-sidebar div.profile-wrapper section.user-info header, div.profile-wrapper section.user-info div.book-wrapper section.book-sidebar header, div.info-wrapper section.handouts div.profile-wrapper section.user-info header, div.profile-wrapper section.user-info div.info-wrapper section.handouts header, div.profile-wrapper section.user-info header, div#wiki_panel div.profile-wrapper section.user-info header, div.profile-wrapper section.user-info div#wiki_panel header, div.discussion-wrapper aside div.profile-wrapper section.user-info header, div.profile-wrapper section.user-info div.discussion-wrapper aside header, .sidebar div#wiki_panel h2, div#wiki_panel .sidebar h2, section.course-index div#wiki_panel h2, div#wiki_panel section.course-index h2, div.book-wrapper section.book-sidebar div#wiki_panel h2, div#wiki_panel div.book-wrapper section.book-sidebar h2, div.info-wrapper section.handouts div#wiki_panel h2, div#wiki_panel div.info-wrapper section.handouts h2, div.profile-wrapper section.user-info div#wiki_panel h2, div#wiki_panel div.profile-wrapper section.user-info h2, div#wiki_panel h2, div.discussion-wrapper aside div#wiki_panel h2, div#wiki_panel div.discussion-wrapper aside h2, .sidebar div.discussion-wrapper aside h1, div.discussion-wrapper aside .sidebar h1, section.course-index div.discussion-wrapper aside h1, div.discussion-wrapper aside section.course-index h1, div.book-wrapper section.book-sidebar div.discussion-wrapper aside h1, div.discussion-wrapper aside div.book-wrapper section.book-sidebar h1, div.info-wrapper section.handouts div.discussion-wrapper aside h1, div.discussion-wrapper aside div.info-wrapper section.handouts h1, div.profile-wrapper section.user-info div.discussion-wrapper aside h1, div.discussion-wrapper aside div.profile-wrapper section.user-info h1, div#wiki_panel div.discussion-wrapper aside h1, div.discussion-wrapper aside div#wiki_panel h1, div.discussion-wrapper aside h1 { -webkit-box-shadow: 0 1px 0 #eeeeee; -moz-box-shadow: 0 1px 0 #eeeeee; box-shadow: 0 1px 0 #eeeeee; @@ -449,8 +449,7 @@ html body section.main-content, html body section.outside-app { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - margin-top: 3px; - overflow: hidden; } + margin-top: 3px; } @media print { html body section.main-content, html body section.outside-app { border-bottom: 0; @@ -665,7 +664,7 @@ footer nav ul.social li.linkedin a { background: url("/static/images/linkedin.png") 0 0 no-repeat; } li.calc-main { - bottom: 0; + bottom: -36px; left: 0; position: fixed; width: 100%; } @@ -692,32 +691,20 @@ li.calc-main a.calc { *vertical-align: auto; padding: 8px 12px; width: 16px; - height: 20px; } + height: 20px; + position: relative; + top: -36px; } li.calc-main a.calc:hover { opacity: .8; } li.calc-main a.calc.closed { background-image: url("/static/images/close-calc-icon.png"); } li.calc-main div#calculator_wrapper { background: rgba(17, 17, 17, 0.9); + position: relative; + top: -36px; clear: both; } li.calc-main div#calculator_wrapper form { padding: 22.652px; } -li.calc-main div#calculator_wrapper form input#calculator_input { - border: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - font-size: 16px; - padding: 10px; - width: 61.741%; - margin: 0; - float: left; } -li.calc-main div#calculator_wrapper form input#calculator_input:focus { - outline: none; - border: none; } li.calc-main div#calculator_wrapper form input#calculator_button { background: #111; border: 1px solid #000; @@ -758,14 +745,82 @@ li.calc-main div#calculator_wrapper form input#calculator_output { margin: 1px 0 0; padding: 10px; width: 31.984%; } -li.calc-main div#calculator_wrapper dl { - display: none; } -li.calc-main div#calculator_wrapper dl dt { +li.calc-main div#calculator_wrapper form div.input-wrapper { + position: relative; + width: 61.741%; + margin: 0; + float: left; } +li.calc-main div#calculator_wrapper form div.input-wrapper input#calculator_input { + border: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font-size: 16px; + padding: 10px; + width: 100%; } +li.calc-main div#calculator_wrapper form div.input-wrapper input#calculator_input:focus { + outline: none; + border: none; } +li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper { + position: absolute; + right: 8px; + top: 15px; } +li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper a { + text-indent: -9999px; + overflow: hidden; + display: block; + width: 17px; + height: 17px; + background: url("/static/images/info-icon.png") center center no-repeat; } +li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl { + background: #fff; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -ms-border-radius: 3px; + -o-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: 0 0 3px #999999; + -moz-box-shadow: 0 0 3px #999999; + box-shadow: 0 0 3px #999999; + color: #333; + opacity: 0; + padding: 10px; + position: absolute; + right: -40px; + top: -110px; + width: 500px; + -webkit-transition-property: all; + -moz-transition-property: all; + -ms-transition-property: all; + -o-transition-property: all; + transition-property: all; + -webkit-transition-duration: 0.15s; + -moz-transition-duration: 0.15s; + -ms-transition-duration: 0.15s; + -o-transition-duration: 0.15s; + transition-duration: 0.15s; + -webkit-transition-timing-function: ease-out; + -moz-transition-timing-function: ease-out; + -ms-transition-timing-function: ease-out; + -o-transition-timing-function: ease-out; + transition-timing-function: ease-out; + -webkit-transition-delay: 0; + -moz-transition-delay: 0; + -ms-transition-delay: 0; + -o-transition-delay: 0; + transition-delay: 0; } +li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl.shown { + opacity: 1; + top: -115px; } +li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl dt { clear: both; float: left; font-weight: bold; padding-right: 11.326px; } -li.calc-main div#calculator_wrapper dl dd { +li.calc-main div#calculator_wrapper form div.input-wrapper div.help-wrapper dl dd { float: left; } #lean_overlay { @@ -793,7 +848,8 @@ div.leanModal_box { -moz-box-sizing: border-box; box-sizing: border-box; display: none; - padding: 45.304px; } + padding: 45.304px; + text-align: left; } div.leanModal_box a.modal_close { color: #aaa; display: block; @@ -953,6 +1009,23 @@ div#pwd_reset p { div#pwd_reset input[type="email"] { margin-bottom: 22.652px; } +div#apply_name_change, div#change_email, div#unenroll, div#deactivate-account { + max-width: 700px; } +div#apply_name_change ul, div#change_email ul, div#unenroll ul, div#deactivate-account ul { + list-style: none; } +div#apply_name_change ul li, div#change_email ul li, div#unenroll ul li, div#deactivate-account ul li { + margin-bottom: 11.326px; } +div#apply_name_change ul li textarea, div#apply_name_change ul li input[type="email"], div#apply_name_change ul li input[type="number"], div#apply_name_change ul li input[type="password"], div#apply_name_change ul li input[type="search"], div#apply_name_change ul li input[type="tel"], div#apply_name_change ul li input[type="text"], div#apply_name_change ul li input[type="url"], div#apply_name_change ul li input[type="color"], div#apply_name_change ul li input[type="date"], div#apply_name_change ul li input[type="datetime"], div#apply_name_change ul li input[type="datetime-local"], div#apply_name_change ul li input[type="month"], div#apply_name_change ul li input[type="time"], div#apply_name_change ul li input[type="week"], div#change_email ul li textarea, div#change_email ul li input[type="email"], div#change_email ul li input[type="number"], div#change_email ul li input[type="password"], div#change_email ul li input[type="search"], div#change_email ul li input[type="tel"], div#change_email ul li input[type="text"], div#change_email ul li input[type="url"], div#change_email ul li input[type="color"], div#change_email ul li input[type="date"], div#change_email ul li input[type="datetime"], div#change_email ul li input[type="datetime-local"], div#change_email ul li input[type="month"], div#change_email ul li input[type="time"], div#change_email ul li input[type="week"], div#unenroll ul li textarea, div#unenroll ul li input[type="email"], div#unenroll ul li input[type="number"], div#unenroll ul li input[type="password"], div#unenroll ul li input[type="search"], div#unenroll ul li input[type="tel"], div#unenroll ul li input[type="text"], div#unenroll ul li input[type="url"], div#unenroll ul li input[type="color"], div#unenroll ul li input[type="date"], div#unenroll ul li input[type="datetime"], div#unenroll ul li input[type="datetime-local"], div#unenroll ul li input[type="month"], div#unenroll ul li input[type="time"], div#unenroll ul li input[type="week"], div#deactivate-account ul li textarea, div#deactivate-account ul li input[type="email"], div#deactivate-account ul li input[type="number"], div#deactivate-account ul li input[type="password"], div#deactivate-account ul li input[type="search"], div#deactivate-account ul li input[type="tel"], div#deactivate-account ul li input[type="text"], div#deactivate-account ul li input[type="url"], div#deactivate-account ul li input[type="color"], div#deactivate-account ul li input[type="date"], div#deactivate-account ul li input[type="datetime"], div#deactivate-account ul li input[type="datetime-local"], div#deactivate-account ul li input[type="month"], div#deactivate-account ul li input[type="time"], div#deactivate-account ul li input[type="week"] { + display: block; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } +div#apply_name_change ul li textarea, div#change_email ul li textarea, div#unenroll ul li textarea, div#deactivate-account ul li textarea { + height: 60px; } +div#apply_name_change ul li input[type="submit"], div#change_email ul li input[type="submit"], div#unenroll ul li input[type="submit"], div#deactivate-account ul li input[type="submit"] { + white-space: normal; } + div#feedback_div form ol li { float: none; width: 100%; } @@ -2882,14 +2955,25 @@ section.course-content div.video-subtitles.closed ol.subtitles { height: 0; } nav.sequence-nav { - margin-bottom: 22.652px; } -nav.sequence-nav ol { - display: table-row; - float: left; - width: 90.611%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 22.652px; position: relative; } +nav.sequence-nav ol { + border-bottom: 1px solid #e4d080; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: table; + padding-right: 8.696%; + width: 100%; } nav.sequence-nav ol li { - display: table-cell; } + border-left: 1px solid #e4d080; + display: table-cell; + min-width: 20px; } +nav.sequence-nav ol li:first-child { + border-left: none; } nav.sequence-nav ol li .inactive, nav.sequence-nav ol li a.seq_video_inactive, nav.sequence-nav ol li a.seq_other_inactive, nav.sequence-nav ol li a.seq_vertical_inactive, nav.sequence-nav ol li a.seq_problem_inactive { background-repeat: no-repeat; } nav.sequence-nav ol li .inactive:hover, nav.sequence-nav ol li a.seq_video_inactive:hover, nav.sequence-nav ol li a.seq_other_inactive:hover, nav.sequence-nav ol li a.seq_vertical_inactive:hover, nav.sequence-nav ol li a.seq_problem_inactive:hover { @@ -2904,25 +2988,21 @@ nav.sequence-nav ol li .visited:hover, nav.sequence-nav ol li a.seq_video_visite background-color: #f6efd4; background-position: center center; } nav.sequence-nav ol li .active, nav.sequence-nav ol div.header-wrapper header nav.courseware li.courseware a, div.header-wrapper header nav.courseware nav.sequence-nav ol li.courseware a, nav.sequence-nav ol div.header-wrapper header nav.book li.book a, div.header-wrapper header nav.book nav.sequence-nav ol li.book a, nav.sequence-nav ol div.header-wrapper header nav.info li.info a, div.header-wrapper header nav.info nav.sequence-nav ol li.info a, nav.sequence-nav ol div.header-wrapper header nav.discussion li.discussion a, div.header-wrapper header nav.discussion nav.sequence-nav ol li.discussion a, nav.sequence-nav ol div.header-wrapper header nav.wiki li.wiki a, div.header-wrapper header nav.wiki nav.sequence-nav ol li.wiki a, nav.sequence-nav ol div.header-wrapper header nav.profile li.profile a, div.header-wrapper header nav.profile nav.sequence-nav ol li.profile a, nav.sequence-nav ol li section.course-index div#accordion h3.ui-accordion-header.ui-state-active, section.course-index div#accordion nav.sequence-nav ol li h3.ui-accordion-header.ui-state-active, nav.sequence-nav ol li section.course-index div#accordion div#wiki_panel input.ui-accordion-header.ui-state-active[type="button"], section.course-index div#accordion div#wiki_panel nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"], nav.sequence-nav ol li div#wiki_panel section.course-index div#accordion input.ui-accordion-header.ui-state-active[type="button"], div#wiki_panel section.course-index div#accordion nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"], nav.sequence-nav ol li a.seq_video_active, nav.sequence-nav ol li a.seq_other_active, nav.sequence-nav ol li a.seq_vertical_active, nav.sequence-nav ol li a.seq_problem_active { + background-color: #fff; + background-repeat: no-repeat; -webkit-box-shadow: 0 1px 0 white; -moz-box-shadow: 0 1px 0 white; - box-shadow: 0 1px 0 white; - background-color: #fff; - background-repeat: no-repeat; } + box-shadow: 0 1px 0 white; } nav.sequence-nav ol li .active:hover, nav.sequence-nav ol div.header-wrapper header nav.courseware li.courseware a:hover, div.header-wrapper header nav.courseware nav.sequence-nav ol li.courseware a:hover, nav.sequence-nav ol div.header-wrapper header nav.book li.book a:hover, div.header-wrapper header nav.book nav.sequence-nav ol li.book a:hover, nav.sequence-nav ol div.header-wrapper header nav.info li.info a:hover, div.header-wrapper header nav.info nav.sequence-nav ol li.info a:hover, nav.sequence-nav ol div.header-wrapper header nav.discussion li.discussion a:hover, div.header-wrapper header nav.discussion nav.sequence-nav ol li.discussion a:hover, nav.sequence-nav ol div.header-wrapper header nav.wiki li.wiki a:hover, div.header-wrapper header nav.wiki nav.sequence-nav ol li.wiki a:hover, nav.sequence-nav ol div.header-wrapper header nav.profile li.profile a:hover, div.header-wrapper header nav.profile nav.sequence-nav ol li.profile a:hover, nav.sequence-nav ol li section.course-index div#accordion h3.ui-accordion-header.ui-state-active:hover, section.course-index div#accordion nav.sequence-nav ol li h3.ui-accordion-header.ui-state-active:hover, nav.sequence-nav ol li section.course-index div#accordion div#wiki_panel input.ui-accordion-header.ui-state-active[type="button"]:hover, section.course-index div#accordion div#wiki_panel nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"]:hover, nav.sequence-nav ol li div#wiki_panel section.course-index div#accordion input.ui-accordion-header.ui-state-active[type="button"]:hover, div#wiki_panel section.course-index div#accordion nav.sequence-nav ol li input.ui-accordion-header.ui-state-active[type="button"]:hover, nav.sequence-nav ol li a.seq_video_active:hover, nav.sequence-nav ol li a.seq_other_active:hover, nav.sequence-nav ol li a.seq_vertical_active:hover, nav.sequence-nav ol li a.seq_problem_active:hover { background-color: #fff; background-position: center; } nav.sequence-nav ol li a { - -webkit-box-shadow: 1px 0 0 white; - -moz-box-shadow: 1px 0 0 white; - box-shadow: 1px 0 0 white; background-position: center center; border: none; - border-right: 1px solid #eddfaa; cursor: pointer; - padding: 15px 4px 14px; - width: 28px; + display: block; height: 17px; + padding: 15px 0 14px; -webkit-transition-property: all; -moz-transition-property: all; -ms-transition-property: all; @@ -2942,7 +3022,8 @@ nav.sequence-nav ol li a { -moz-transition-delay: 0; -ms-transition-delay: 0; -o-transition-delay: 0; - transition-delay: 0; } + transition-delay: 0; + width: 100%; } nav.sequence-nav ol li a.seq_video_inactive { background-image: url("/static/images/sequence-nav/video-icon-normal.png"); background-position: center; } @@ -3002,8 +3083,8 @@ nav.sequence-nav ol li p { white-space: pre-wrap; z-index: 99; } nav.sequence-nav ol li p.shown { - opacity: 1; - margin-top: 4px; } + margin-top: 4px; + opacity: 1; } nav.sequence-nav ol li p:empty { background: none; } nav.sequence-nav ol li p:empty::after { @@ -3013,9 +3094,9 @@ nav.sequence-nav ol li p::after { content: " "; display: block; height: 10px; + left: 18px; position: absolute; top: -5px; - left: 18px; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -ms-transform: rotate(45deg); @@ -3023,30 +3104,33 @@ nav.sequence-nav ol li p::after { transform: rotate(45deg); width: 10px; } nav.sequence-nav ul { - float: right; margin-right: 1px; - width: 8.696%; - display: table-row; } + position: absolute; + right: 0; + top: 0; + width: 8.696%; } nav.sequence-nav ul li { - display: table-cell; } + float: left; + width: 50%; } nav.sequence-nav ul li.prev a, nav.sequence-nav ul li.next a { - -webkit-box-shadow: inset 1px 0 0 #faf7e9; - -moz-box-shadow: inset 1px 0 0 #faf7e9; - box-shadow: inset 1px 0 0 #faf7e9; background-color: #f2e7bf; background-position: center center; background-repeat: no-repeat; border-left: 1px solid #e4d080; + -webkit-box-shadow: inset 1px 0 0 #faf7e9; + -moz-box-shadow: inset 1px 0 0 #faf7e9; + box-shadow: inset 1px 0 0 #faf7e9; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; cursor: pointer; - padding: 0 4px; - text-indent: -9999px; - width: 38px; - display: block; } + display: block; + text-indent: -9999px; } nav.sequence-nav ul li.prev a:hover, nav.sequence-nav ul li.next a:hover { - text-decoration: none; + background-color: none; color: #7e691a; text-decoration: none; - background-color: none; } + text-decoration: none; } nav.sequence-nav ul li.prev a.disabled, nav.sequence-nav ul li.next a.disabled { cursor: normal; opacity: .4; } @@ -3059,16 +3143,13 @@ nav.sequence-nav ul li.next a { nav.sequence-nav ul li.next a:hover { background-color: none; } -section.course-content { - position: relative; } section.course-content div#seq_content { margin-bottom: 60px; } section.course-content nav.sequence-bottom { - position: absolute; - bottom: 0; - right: 50%; - margin-right: -53px; } + bottom: -22.652px; + position: relative; } section.course-content nav.sequence-bottom ul { + background-color: #f2e7bf; background-color: #f2e7bf; border: 1px solid #e4d080; border-bottom: 0; @@ -3077,12 +3158,12 @@ section.course-content nav.sequence-bottom ul { -ms-border-radius: 3px 3px 0 0; -o-border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0; - overflow: hidden; - width: 106px; - background-color: #f2e7bf; -webkit-box-shadow: inset 0 0 0 1px #faf7e9; -moz-box-shadow: inset 0 0 0 1px #faf7e9; - box-shadow: inset 0 0 0 1px #faf7e9; } + box-shadow: inset 0 0 0 1px #faf7e9; + margin: 0 auto; + overflow: hidden; + width: 106px; } section.course-content nav.sequence-bottom ul li { float: left; } section.course-content nav.sequence-bottom ul li.prev, section.course-content nav.sequence-bottom ul li.next { @@ -3092,10 +3173,9 @@ section.course-content nav.sequence-bottom ul li.prev a, section.course-content background-repeat: no-repeat; border-bottom: none; display: block; + display: block; padding: 16.989px 4px; text-indent: -9999px; - width: 45px; - display: block; -webkit-transition-property: all; -moz-transition-property: all; -ms-transition-property: all; @@ -3115,7 +3195,8 @@ section.course-content nav.sequence-bottom ul li.prev a, section.course-content -moz-transition-delay: 0; -ms-transition-delay: 0; -o-transition-delay: 0; - transition-delay: 0; } + transition-delay: 0; + width: 45px; } section.course-content nav.sequence-bottom ul li.prev a:hover, section.course-content nav.sequence-bottom ul li.next a:hover { background-color: #eddfaa; color: #7e691a; @@ -3269,6 +3350,11 @@ div.info-wrapper section.updates > ol > li { padding-bottom: 11.326px; margin-bottom: 11.326px; border-bottom: 1px solid #e3e3e3; } +div.info-wrapper section.updates > ol > li:first-child { + padding: 11.326px; + margin: 0 -11.326px 22.652px; + background: #f6efd4; + border-bottom: 1px solid #eddfaa; } div.info-wrapper section.updates > ol > li h2 { float: left; width: 20.109%; @@ -3352,10 +3438,22 @@ div.profile-wrapper section.user-info { border-radius: 0px 4px 4px 0; border-left: 1px solid #d3d3d3; border-right: 0; } -div.profile-wrapper section.user-info h1 { +div.profile-wrapper section.user-info header { padding: 11.326px 22.652px; - font-size: 18px; margin: 0; } +div.profile-wrapper section.user-info header h1 { + font-size: 18px; + margin: 0; + padding-right: 30px; } +div.profile-wrapper section.user-info header a { + position: absolute; + top: 13px; + right: 11.326px; + text-transform: uppercase; + font-size: 12px; + color: #999; } +div.profile-wrapper section.user-info header a:hover { + color: #555; } div.profile-wrapper section.user-info ul { list-style: none; } div.profile-wrapper section.user-info ul li { @@ -3409,18 +3507,55 @@ div.profile-wrapper section.user-info ul li div#location_sub:empty, div.profile- padding: 0; } div.profile-wrapper section.user-info ul li div#description { font-size: 12px; } -div.profile-wrapper section.user-info ul li a#change_language, div.profile-wrapper section.user-info ul li a#change_location { +div.profile-wrapper section.user-info ul li a#change_language, div.profile-wrapper section.user-info ul li a#change_location, div.profile-wrapper section.user-info ul li a.edit-email, div.profile-wrapper section.user-info ul li a.name-edit, div.profile-wrapper section.user-info ul li a.email-edit { position: absolute; top: 9px; right: 11.326px; text-transform: uppercase; font-size: 12px; color: #999; } -div.profile-wrapper section.user-info ul li a#change_language:hover, div.profile-wrapper section.user-info ul li a#change_location:hover { +div.profile-wrapper section.user-info ul li a#change_language:hover, div.profile-wrapper section.user-info ul li a#change_location:hover, div.profile-wrapper section.user-info ul li a.edit-email:hover, div.profile-wrapper section.user-info ul li a.name-edit:hover, div.profile-wrapper section.user-info ul li a.email-edit:hover { + color: #555; } +div.profile-wrapper section.user-info ul li p { + font-size: 12px; + margin-bottom: 0; + margin-top: 4px; + color: #999; } +div.profile-wrapper section.user-info ul li a.deactivate { + color: #aaa; + font-style: italic; } +div.profile-wrapper section.user-info ul li input { + background: none; + border: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + color: #999; + font-size: 12px; + font-weight: normal; + margin: 0; + padding: 0; + position: absolute; + right: 11.326px; + text-transform: uppercase; + top: 9px; } +div.profile-wrapper section.user-info ul li input:hover { color: #555; } div.profile-wrapper section.user-info div#change_password_pop { - padding: 7px 22.652px; - color: #4D4D4D; } + border-bottom: 1px solid #d3d3d3; + -webkit-box-shadow: 0 1px 0 #eeeeee; + -moz-box-shadow: 0 1px 0 #eeeeee; + box-shadow: 0 1px 0 #eeeeee; + color: #4D4D4D; + padding: 7px 22.652px; } +div.profile-wrapper section.user-info div#change_password_pop h2 { + margin-top: 0; + font-weight: bold; + text-transform: uppercase; + font-size: 14px; } +div.profile-wrapper section.course-info header h1 { + margin: 0; + float: left; } div.profile-wrapper section.course-info div#grade-detail-graph { width: 100%; min-height: 300px; } diff --git a/static/css/marketing.css b/static/css/marketing.css index f69099b379..a73f1125c8 100644 --- a/static/css/marketing.css +++ b/static/css/marketing.css @@ -754,7 +754,8 @@ div.leanModal_box { -moz-box-sizing: border-box; box-sizing: border-box; display: none; - padding: 51.776px; } + padding: 51.776px; + text-align: left; } div.leanModal_box a.modal_close { color: #aaa; display: block; @@ -914,6 +915,23 @@ div#pwd_reset p { div#pwd_reset input[type="email"] { margin-bottom: 25.888px; } +div#apply_name_change, div#change_email, div#unenroll, div#deactivate-account { + max-width: 700px; } +div#apply_name_change ul, div#change_email ul, div#unenroll ul, div#deactivate-account ul { + list-style: none; } +div#apply_name_change ul li, div#change_email ul li, div#unenroll ul li, div#deactivate-account ul li { + margin-bottom: 12.944px; } +div#apply_name_change ul li textarea, div#apply_name_change ul li input[type="email"], div#apply_name_change ul li input[type="number"], div#apply_name_change ul li input[type="password"], div#apply_name_change ul li input[type="search"], div#apply_name_change ul li input[type="tel"], div#apply_name_change ul li input[type="text"], div#apply_name_change ul li input[type="url"], div#apply_name_change ul li input[type="color"], div#apply_name_change ul li input[type="date"], div#apply_name_change ul li input[type="datetime"], div#apply_name_change ul li input[type="datetime-local"], div#apply_name_change ul li input[type="month"], div#apply_name_change ul li input[type="time"], div#apply_name_change ul li input[type="week"], div#change_email ul li textarea, div#change_email ul li input[type="email"], div#change_email ul li input[type="number"], div#change_email ul li input[type="password"], div#change_email ul li input[type="search"], div#change_email ul li input[type="tel"], div#change_email ul li input[type="text"], div#change_email ul li input[type="url"], div#change_email ul li input[type="color"], div#change_email ul li input[type="date"], div#change_email ul li input[type="datetime"], div#change_email ul li input[type="datetime-local"], div#change_email ul li input[type="month"], div#change_email ul li input[type="time"], div#change_email ul li input[type="week"], div#unenroll ul li textarea, div#unenroll ul li input[type="email"], div#unenroll ul li input[type="number"], div#unenroll ul li input[type="password"], div#unenroll ul li input[type="search"], div#unenroll ul li input[type="tel"], div#unenroll ul li input[type="text"], div#unenroll ul li input[type="url"], div#unenroll ul li input[type="color"], div#unenroll ul li input[type="date"], div#unenroll ul li input[type="datetime"], div#unenroll ul li input[type="datetime-local"], div#unenroll ul li input[type="month"], div#unenroll ul li input[type="time"], div#unenroll ul li input[type="week"], div#deactivate-account ul li textarea, div#deactivate-account ul li input[type="email"], div#deactivate-account ul li input[type="number"], div#deactivate-account ul li input[type="password"], div#deactivate-account ul li input[type="search"], div#deactivate-account ul li input[type="tel"], div#deactivate-account ul li input[type="text"], div#deactivate-account ul li input[type="url"], div#deactivate-account ul li input[type="color"], div#deactivate-account ul li input[type="date"], div#deactivate-account ul li input[type="datetime"], div#deactivate-account ul li input[type="datetime-local"], div#deactivate-account ul li input[type="month"], div#deactivate-account ul li input[type="time"], div#deactivate-account ul li input[type="week"] { + display: block; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } +div#apply_name_change ul li textarea, div#change_email ul li textarea, div#unenroll ul li textarea, div#deactivate-account ul li textarea { + height: 60px; } +div#apply_name_change ul li input[type="submit"], div#change_email ul li input[type="submit"], div#unenroll ul li input[type="submit"], div#deactivate-account ul li input[type="submit"] { + white-space: normal; } + div#feedback_div form ol li { float: none; width: 100%; } diff --git a/static/images/info-icon.png b/static/images/info-icon.png new file mode 100644 index 0000000000..736b2f2374 Binary files /dev/null and b/static/images/info-icon.png differ diff --git a/templates/activation_active.html b/templates/activation_active.html index c51cccbb70..613c20dc9f 100644 --- a/templates/activation_active.html +++ b/templates/activation_active.html @@ -6,8 +6,7 @@

Account already active!

-

This account has already been activated. We will notify you as - soon as the course starts.

-

For now you can go to the MITx homepage or the 6.002x course page.

+

This account has already been activated. You can log in at + the 6.002x course page.

diff --git a/templates/activation_complete.html b/templates/activation_complete.html index 1a46dc21a9..dc01ae54ca 100644 --- a/templates/activation_complete.html +++ b/templates/activation_complete.html @@ -4,7 +4,6 @@

Activation Complete!

-

Thanks for activating your email. We will notify you as soon as the course starts.

-

For now you can go to the MITx homepage or the 6.002x course page.

+

Thanks for activating your account. You can log in at the 6.002x course page.

diff --git a/templates/email_change_successful.html b/templates/email_change_successful.html new file mode 100644 index 0000000000..ed3b732d6e --- /dev/null +++ b/templates/email_change_successful.html @@ -0,0 +1,3 @@ +

E-mail change successful!

+ +

You should see your new name in your profile. diff --git a/templates/email_exists.html b/templates/email_exists.html new file mode 100644 index 0000000000..885fbd5520 --- /dev/null +++ b/templates/email_exists.html @@ -0,0 +1,3 @@ +

Could not change e-mail

+ +An account with the new e-mail address already exists. Sorry. diff --git a/templates/activation_email.txt b/templates/emails/activation_email.txt similarity index 80% rename from templates/activation_email.txt rename to templates/emails/activation_email.txt index 21a09335c7..7f042995ed 100644 --- a/templates/activation_email.txt +++ b/templates/emails/activation_email.txt @@ -3,7 +3,11 @@ offering of 6.002 using this email address. If it was you, and you'd like to activate and use your account, copy and paste this address into your web browser's address bar: -http://${ site }/activate/${ key } +% if is_secure: + https://${ site }/activate/${ key } +% else: + http://${ site }/activate/${ key } +% endif If you didn't request this, you don't need to do anything; you won't receive any more email from us. Please do not reply to this e-mail; if diff --git a/templates/activation_email_subject.txt b/templates/emails/activation_email_subject.txt similarity index 100% rename from templates/activation_email_subject.txt rename to templates/emails/activation_email_subject.txt diff --git a/templates/emails/confirm_email_change.txt b/templates/emails/confirm_email_change.txt new file mode 100644 index 0000000000..f6c3609295 --- /dev/null +++ b/templates/emails/confirm_email_change.txt @@ -0,0 +1,13 @@ +This is to confirm that you changed the e-mail associated with MITx +from ${old_email} to ${new_email}. If you did not make this request, +please contact the course staff immediately. Contact information is +listed at: + +% if is_secure: + https://${ site }/t/mitx_help.html +% else: + http://${ site }/t/mitx_help.html +% endif + +We keep a log of old e-mails, so if this request was unintentional, we +can investigate. diff --git a/templates/emails/email_change.txt b/templates/emails/email_change.txt new file mode 100644 index 0000000000..51be82f564 --- /dev/null +++ b/templates/emails/email_change.txt @@ -0,0 +1,13 @@ +We received a request to change the e-mail associated with your MITx +account from ${old_email} to ${new_email}. If this is correct, please +confirm your new e-mail address by visiting: + +% if is_secure: + https://${ site }/email_confirm/${ key } +% else: + http://${ site }/email_confirm/${ key } +% endif + +If you didn't request this, you don't need to do anything; you won't +receive any more email from us. Please do not reply to this e-mail; if +you require assistance, check the help section of the MITx web site. diff --git a/templates/emails/email_change_subject.txt b/templates/emails/email_change_subject.txt new file mode 100644 index 0000000000..6ab38757f6 --- /dev/null +++ b/templates/emails/email_change_subject.txt @@ -0,0 +1 @@ +Request to change MITx account e-mail diff --git a/templates/emails/reject_name_change.txt b/templates/emails/reject_name_change.txt new file mode 100644 index 0000000000..5d9edc363b --- /dev/null +++ b/templates/emails/reject_name_change.txt @@ -0,0 +1,5 @@ +(Not currently used) + +We are sorry. Our course staff did not approve your request to change +your name from ${old_name} to ${new_name}. If you need further +assistance, please e-mail the course staff at ta@mitx.mit.edu. diff --git a/templates/emails/welcome_body.txt b/templates/emails/welcome_body.txt index bc859c88cf..b08326f6fd 100644 --- a/templates/emails/welcome_body.txt +++ b/templates/emails/welcome_body.txt @@ -1,6 +1,10 @@ MITx's prototype offering, 6.002x, is now open. To log in, visit +% if is_secure: https://6002x.mitx.mit.edu +% else: + http://6002x.mitx.mit.edu +% endif where you will find a login button at the top right-hand corner of the window. diff --git a/templates/emails_change_successful.html b/templates/emails_change_successful.html new file mode 100644 index 0000000000..ed3b732d6e --- /dev/null +++ b/templates/emails_change_successful.html @@ -0,0 +1,3 @@ +

E-mail change successful!

+ +

You should see your new name in your profile. diff --git a/templates/gradebook.html b/templates/gradebook.html index 340b0da696..99d3af7515 100644 --- a/templates/gradebook.html +++ b/templates/gradebook.html @@ -1,22 +1,76 @@ <%inherit file="main.html" /> + +<%block name="headextra"> + + + + + + + + <%include file="navigation.html" args="active_page=''" />

-

Gradebook

- % for s in students: -

${s['username']}

- % for c in s['grade_info']['grade_summary']: -

${c['category']}

-

- % if 'subscores' in c: - % for ss in c['subscores']: -
${ss['summary']} - % endfor - % endif -

- % endfor - % endfor +

Gradebook

+ + %if len(students) > 0: + + <% + templateSummary = students[0]['grade_info']['grade_summary'] + %> + + + + + %for section in templateSummary: + %if 'subscores' in section: + %for subsection in section['subscores']: + + %endfor + + %else: + + %endif + %endfor + + + <%def name="percent_data(percentage)"> + <% + data_class = "grade_none" + if percentage > .87: + data_class = "grade_a" + elif percentage > .70: + data_class = "grade_b" + elif percentage > .6: + data_class = "grade_c" + %> + + + + %for student in students: + + + %for section in student['grade_info']['grade_summary']: + %if 'subscores' in section: + %for subsection in section['subscores']: + ${percent_data( subsection['percentage'] )} + %endfor + ${percent_data( section['totalscore'] )} + %else: + ${percent_data( section['totalscore'] )} + %endif + %endfor + + %endfor +
Student${subsection['label']}${section['totallabel']}${section['category']}
${ "{0:.0%}".format( percentage ) }
${student['username']}
+ %endif
-
+ \ No newline at end of file diff --git a/templates/gradebook_profilegraphs.html b/templates/gradebook_profilegraphs.html new file mode 100644 index 0000000000..22a763a410 --- /dev/null +++ b/templates/gradebook_profilegraphs.html @@ -0,0 +1,30 @@ +<%inherit file="main.html" /> +<%namespace name="profile_graphs" file="profile_graphs.js"/> + +<%block name="headextra"> + + + + % for s in students: + + %endfor + + +<%include file="navigation.html" args="active_page=''" /> +
+
+
+

Gradebook

+
    + % for s in students: +
  1. +

    ${s['username']}

    +
    +
  2. + % endfor +
+
+
+
diff --git a/templates/invalid_email_key.html b/templates/invalid_email_key.html new file mode 100644 index 0000000000..437dfa151d --- /dev/null +++ b/templates/invalid_email_key.html @@ -0,0 +1,8 @@ +

Invalid key

+ +

This e-mail key is not valid. Please check: +

diff --git a/templates/main.html b/templates/main.html index fa0fcd462e..07e1d75695 100644 --- a/templates/main.html +++ b/templates/main.html @@ -57,21 +57,27 @@
- -
-
Suffixes:
-
%kMGTcmunp
-
Operations:
-
^ * / + - ()
-
Functions:
-
sin, cos, tan, sqrt, log10, log2, ln, arccos, arcsin, arctan, abs
-
Constants
-
e, pi
+
+ - -
+ + +
+ @@ -128,11 +134,20 @@ $(function() { $("#calculator_wrapper").hide(); $(".calc").click(function(){ - $("#calculator_wrapper").slideToggle(); + $("#calculator_wrapper").slideToggle("fast"); $("#calculator_wrapper #calculator_input").focus(); $(this).toggleClass("closed"); + return false; }); + $("div.help-wrapper a").hover(function(){ + $(".help").toggleClass("shown"); + + }); + + $("div.help-wrapper a").click(function(){ + return false; + }); $("form#calculator").submit(function(e){ e.preventDefault(); $.getJSON("/calculate", {"equation":$("#calculator_input").attr("value")}, diff --git a/templates/name_changes.html b/templates/name_changes.html new file mode 100644 index 0000000000..1316084c34 --- /dev/null +++ b/templates/name_changes.html @@ -0,0 +1,44 @@ +<%inherit file="main.html" /> +<%include file="navigation.html" args="active_page=''" /> +
+ + +
+
+

Pending name changes

+ + % for s in students: + + + + + + + % endfor +
${s['old_name']}${s['new_name']|h}${s['email']|h}${s['rationale']|h}[Confirm] + [Reject]
+
+
+
diff --git a/templates/profile.html b/templates/profile.html index 7b6a596bad..6d15a14444 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -1,4 +1,6 @@ <%inherit file="main.html" /> +<%namespace name="profile_graphs" file="profile_graphs.js"/> + <%block name="title">Profile - MITx 6.002x <%! @@ -10,7 +12,7 @@ @@ -89,23 +131,25 @@ $(function() {
-

Course Progress

+
+

Course Progress

+
- +
    - %for chapter in chapters: + %for chapter in courseware_summary: %if not chapter['chapter'] == "hidden":
  1. ${ chapter['chapter'] }

    - +
      %for section in chapter['sections']:
    1. <% - earned = section['section_total'][0] - total = section['section_total'][1] + earned = section['section_total'].earned + total = section['section_total'].possible percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 else "" %> @@ -120,7 +164,7 @@ $(function() {
        ${ "Problem Scores: " if section['graded'] else "Practice Scores: "} %for score in section['scores']: -
      1. ${"{0:g}/{1:g}".format(score[0],score[1])}
      2. +
      3. ${"{0:g}/{1:g}".format(score.earned,score.possible)}
      4. %endfor
      %endif @@ -132,37 +176,135 @@ $(function() { %endif %endfor
    +
-

${name}

+
+

Student Profile

+
-
-

Password change

-

We'll e-mail a password reset link to ${email}.

- - - -
-
-
- -

Password Reset Email Sent

- An email has been sent to ${email}. Follow the link in the email to change your password. -
+
+ +

Password Reset Email Sent

+

+ An email has been sent to ${email}. Follow the link in the email to change your password. +

+
+ +
+

Apply to change your name

+
+
+
+

To uphold the credibility of MITx certificates, name changes must go through an approval process. A member of the course staff will review your request, and if approved, update your information. Please allow up to a week for your request to be processed. Thank you.

+
    +
  • + + +
  • +
  • + + +
  • +
  • + +
  • +
+
+
+
+ +
+

Change e-mail

+
+
+
+
+
    +
  • + + +
  • + +
  • + + +
  • +
  • +

    We will send a confirmation to both ${email} and your new e-mail as part of the process.

    + +
  • +
+
+
+
+ +
+

Deactivate MITx Account

+

Once you deactivate you’re MITx account you will no longer recieve updates and new class announcements from MITx.

+

If you’d like to still get updates and new class announcements you can just unenroll and keep your account active.

+ +
+
+
+
    +
  • + +
  • +
+
+
+
+ +
+

Unenroll from 6.002x

+

Please note: you will still receive updates and new class announcements from MITx. If you don’t wish to receive any more updates or announcements deactivate your account.

+ +
+
+
+
    +
  • + +
  • +
+
+
+
diff --git a/templates/profile_graphs.js b/templates/profile_graphs.js index 72a3de4471..b34a5d1636 100644 --- a/templates/profile_graphs.js +++ b/templates/profile_graphs.js @@ -1,3 +1,4 @@ +<%page args="grade_summary, graph_div_id, **kwargs"/> <%! import json %> @@ -57,21 +58,21 @@ $(function () { category_total_label = section['category'] + " Total" series.append({ 'label' : category_total_label, - 'data' : [ [tickIndex, section['totalscore']['score']] ], + 'data' : [ [tickIndex, section['totalscore']] ], 'color' : colors[sectionIndex] }) ticks.append( [tickIndex, section['totallabel']] ) - detail_tooltips[category_total_label] = [section['totalscore']['summary']] + detail_tooltips[category_total_label] = [section['totalscore_summary']] else: series.append({ 'label' : section['category'], - 'data' : [ [tickIndex, section['totalscore']['score']] ], + 'data' : [ [tickIndex, section['totalscore']] ], 'color' : colors[sectionIndex] }) ticks.append( [tickIndex, section['totallabel']] ) - detail_tooltips[section['category']] = [section['totalscore']['summary']] + detail_tooltips[section['category']] = [section['totalscore_summary']] tickIndex += 1 + sectionSpacer sectionIndex += 1 @@ -86,12 +87,12 @@ $(function () { overviewBarX = tickIndex for section in grade_summary: - weighted_score = section['totalscore']['score'] * section['weight'] + weighted_score = section['totalscore'] * section['weight'] summary_text = "{0} - {1:.1%} of a possible {2:.0%}".format(section['category'], weighted_score, section['weight']) - + weighted_category_label = section['category'] + " - Weighted" - if section['totalscore']['score'] > 0: + if section['totalscore'] > 0: series.append({ 'label' : weighted_category_label, 'data' : [ [overviewBarX, weighted_score] ], @@ -101,7 +102,7 @@ $(function () { detail_tooltips[weighted_category_label] = [ summary_text ] sectionIndex += 1 totalWeight += section['weight'] - totalScore += section['totalscore']['score'] * section['weight'] + totalScore += section['totalscore'] * section['weight'] ticks += [ [overviewBarX, "Total"] ] tickIndex += 1 + sectionSpacer @@ -128,7 +129,7 @@ $(function () { legend: {show: false}, }; - var $grade_detail_graph = $("#grade-detail-graph"); + var $grade_detail_graph = $("#${graph_div_id}"); if ($grade_detail_graph.length > 0) { var plot = $.plot($grade_detail_graph, series, options); @@ -137,7 +138,7 @@ $(function () { } var previousPoint = null; - $("#grade-detail-graph").bind("plothover", function (event, pos, item) { + $grade_detail_graph.bind("plothover", function (event, pos, item) { $("#x").text(pos.x.toFixed(2)); $("#y").text(pos.y.toFixed(2)); if (item) { diff --git a/templates/sass/_gradebook.scss b/templates/sass/_gradebook.scss index 3282d4141b..b94f5de178 100644 --- a/templates/sass/_gradebook.scss +++ b/templates/sass/_gradebook.scss @@ -8,4 +8,4 @@ div.gradebook-wrapper { @extend .top-header; } } -} +} \ No newline at end of file diff --git a/templates/sass/_info.scss b/templates/sass/_info.scss index fe0265050d..353aca75b2 100644 --- a/templates/sass/_info.scss +++ b/templates/sass/_info.scss @@ -21,12 +21,12 @@ div.info-wrapper { @extend .clearfix; border-bottom: 1px solid #e3e3e3; - // &:first-child { - // padding: lh(.5); - // margin-left: (-(lh(.5))); - // background: $cream; - // border-bottom: 1px solid darken($cream, 10%); - // } + &:first-child { + padding: lh(.5); + margin: 0 (-(lh(.5))) lh(); + background: $cream; + border-bottom: 1px solid darken($cream, 10%); + } h2 { float: left; diff --git a/templates/sass/_profile.scss b/templates/sass/_profile.scss index 3182d61b36..2410806bed 100644 --- a/templates/sass/_profile.scss +++ b/templates/sass/_profile.scss @@ -8,11 +8,29 @@ div.profile-wrapper { border-left: 1px solid #d3d3d3; border-right: 0; - h1 { + header { padding: lh(.5) lh(); - font-size: 18px; margin: 0 ; @extend .bottom-border; + + h1 { + font-size: 18px; + margin: 0; + padding-right: 30px; + } + + a { + position: absolute; + top: 13px; + right: lh(.5); + text-transform: uppercase; + font-size: 12px; + color: #999; + + &:hover { + color: #555; + } + } } ul { @@ -57,7 +75,11 @@ div.profile-wrapper { font-size: 12px; } - a#change_language, a#change_location { + a#change_language, + a#change_location, + a.edit-email, + a.name-edit, + a.email-edit { position: absolute; top: 9px; right: lh(.5); @@ -69,20 +91,66 @@ div.profile-wrapper { color: #555; } } + + p { + font-size: 12px; + margin-bottom: 0; + margin-top: 4px; + color: #999; + } + + a.deactivate { + color: #aaa; + font-style: italic; + } + + input { + background: none; + border: none; + @include box-shadow(none); + color: #999; + font-size: 12px; + font-weight: normal; + margin: 0; + padding: 0; + position: absolute; + right: lh(.5); + text-transform: uppercase; + top: 9px; + + &:hover { + color: #555; + } + } } } div#change_password_pop { - padding: 7px lh(); + border-bottom: 1px solid #d3d3d3; + @include box-shadow(0 1px 0 #eee); color: #4D4D4D; + padding: 7px lh(); + + h2 { + margin-top: 0; + font-weight: bold; + text-transform: uppercase; + font-size: $body-font-size; + } } } section.course-info { @extend .content; - > h1 { - @extend .top-header; + header { + @extend h1.top-header; + @extend .clearfix; + + h1 { + margin: 0; + float: left; + } } div#grade-detail-graph { @@ -122,29 +190,29 @@ div.profile-wrapper { padding-left: flex-gutter(9); width: flex-grid(7, 9); - > li { - padding:0 0 lh() 0; + > li { + padding:0 0 lh() 0; - &:first-child { - padding-top: 0; - } - - &:last-child { - border-bottom: 0; - } - - h3 { - color: #666; - } - - ol { - list-style: none; - - li { - display: inline-block; - padding-right: 1em; + &:first-child { + padding-top: 0; + } + + &:last-child { + border-bottom: 0; + } + + h3 { + color: #666; + } + + ol { + list-style: none; + + li { + display: inline-block; + padding-right: 1em; + } } - } } } } diff --git a/templates/sass/bourbon/css3/_border-image.scss b/templates/sass/bourbon/css3/_border-image.scss index da4f20ba49..0373980422 100644 --- a/templates/sass/bourbon/css3/_border-image.scss +++ b/templates/sass/bourbon/css3/_border-image.scss @@ -1,56 +1,7 @@ -@mixin border-image($images) { - -webkit-border-image: border-add-prefix($images, webkit); - -moz-border-image: border-add-prefix($images, moz); - -o-border-image: border-add-prefix($images, o); - border-image: border-add-prefix($images); +@mixin border-image ($image) { + -webkit-border-image: $image; + -moz-border-image: $image; + -ms-border-image: $image; + -o-border-image: $image; + border-image: $image; } - -@function border-add-prefix($images, $vendor: false) { - $border-image: (); - $images-type: type-of(nth($images, 1)); - $first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial) - - // If input is a gradient - @if $images-type == string { - @if ($first-var == "linear") or ($first-var == "radial") { - @for $i from 2 through length($images) { - $gradient-type: nth($images, 1); // Get type of gradient (linear || radial) - $gradient-args: nth($images, $i); // Get actual gradient (red, blue) - $border-image: render-gradients($gradient-args, $gradient-type, $vendor); - } - } - - // If input is a URL - @else { - $border-image: $images; - } - } - - // If input is gradient or url + additional args - @else if $images-type == list { - @for $i from 1 through length($images) { - $type: type-of(nth($images, $i)); // Get type of variable - List or String - - // If variable is a list - Gradient - @if $type == list { - $gradient-type: nth(nth($images, $i), 1); // Get type of gradient (linear || radial) - $gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue) - $border-image: render-gradients($gradient-args, $gradient-type, $vendor); - } - - // If variable is a string - Image or number - @else if ($type == string) or ($type == number) { - $border-image: append($border-image, nth($images, $i)); - } - } - } - @return $border-image; -} - -//Examples: -// @include border-image(url("image.png")); -// @include border-image(url("image.png") 20 stretch); -// @include border-image(linear-gradient(45deg, orange, yellow)); -// @include border-image(linear-gradient(45deg, orange, yellow) stretch); -// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); -// @include border-image(radial-gradient(top, cover, orange, yellow, orange)); diff --git a/templates/sass/courseware/_sequence-nav.scss b/templates/sass/courseware/_sequence-nav.scss index 056311521e..b4de051326 100644 --- a/templates/sass/courseware/_sequence-nav.scss +++ b/templates/sass/courseware/_sequence-nav.scss @@ -1,19 +1,28 @@ nav.sequence-nav { @extend .topbar; + @include box-sizing(border-box); margin-bottom: $body-line-height; + position: relative; ol { - display: table-row; - float: left; - width: flex-grid(8,9) + flex-gutter(); - position: relative; + border-bottom: 1px solid darken($cream, 20%); + @include box-sizing(border-box); + display: table; + padding-right: flex-grid(1, 9); + width: 100%; a { @extend .block-link; } li { + border-left: 1px solid darken($cream, 20%); display: table-cell; + min-width: 20px; + + &:first-child { + border-left: none; + } .inactive { background-repeat: no-repeat; @@ -35,9 +44,9 @@ nav.sequence-nav { } .active { - @include box-shadow(0 1px 0 #fff); background-color: #fff; background-repeat: no-repeat; + @include box-shadow(0 1px 0 #fff); &:hover { background-color: #fff; @@ -46,15 +55,14 @@ nav.sequence-nav { } a { - @include box-shadow(1px 0 0 #fff); background-position: center center; border: none; - border-right: 1px solid darken($cream, 10%); cursor: pointer; - padding: 15px 4px 14px; - width: 28px; + display: block; height: 17px; + padding: 15px 0 14px; @include transition(all, .4s, $ease-in-out-quad); + width: 100%; // @media screen and (max-width: 800px) { // padding: 12px 8px; @@ -134,8 +142,8 @@ nav.sequence-nav { z-index: 99; &.shown { - opacity: 1; margin-top: 4px; + opacity: 1; } &:empty { @@ -151,9 +159,9 @@ nav.sequence-nav { content: " "; display: block; height: 10px; + left: 18px; position: absolute; top: -5px; - left: 18px; @include transform(rotate(45deg)); width: 10px; } @@ -162,33 +170,34 @@ nav.sequence-nav { } ul { - float: right; margin-right: 1px; + position: absolute; + right: 0; + top: 0; width: flex-grid(1, 9); - display: table-row; li { - display: table-cell; + float: left; + width: 50%; &.prev, &.next { a { - @include box-shadow(inset 1px 0 0 lighten(#f6efd4, 5%)); background-color: darken($cream, 5%); background-position: center center; background-repeat: no-repeat; border-left: 1px solid darken(#f6efd4, 20%); + @include box-shadow(inset 1px 0 0 lighten(#f6efd4, 5%)); + @include box-sizing(border-box); cursor: pointer; - padding: 0 4px; - text-indent: -9999px; - width: 38px; display: block; + text-indent: -9999px; &:hover { - text-decoration: none; + background-color: none; color: darken($cream, 60%); text-decoration: none; - background-color: none; + text-decoration: none; } &.disabled { @@ -223,28 +232,26 @@ nav.sequence-nav { section.course-content { - position: relative; div#seq_content { margin-bottom: 60px; } nav.sequence-bottom { - position: absolute; - bottom: 0; - right: 50%; - margin-right: -53px; + bottom: (-(lh())); + position: relative; ul { @extend .clearfix; background-color: darken(#F6EFD4, 5%); + background-color: darken($cream, 5%); border: 1px solid darken(#f6efd4, 20%); border-bottom: 0; @include border-radius(3px 3px 0 0); + @include box-shadow(inset 0 0 0 1px lighten(#f6efd4, 5%)); + margin: 0 auto; overflow: hidden; width: 106px; - background-color: darken($cream, 5%); - @include box-shadow(inset 0 0 0 1px lighten(#f6efd4, 5%)); li { float: left; @@ -257,11 +264,11 @@ section.course-content { background-repeat: no-repeat; border-bottom: none; display: block; + display: block; padding: lh(.75) 4px; text-indent: -9999px; - width: 45px; - display: block; @include transition(all, .4s, $ease-in-out-quad); + width: 45px; &:hover { background-color: darken($cream, 10%); diff --git a/templates/sass/layout/_calculator.scss b/templates/sass/layout/_calculator.scss index 64901c3077..1a81ee8c94 100644 --- a/templates/sass/layout/_calculator.scss +++ b/templates/sass/layout/_calculator.scss @@ -1,5 +1,5 @@ li.calc-main { - bottom: 0; + bottom: -36px; left: 0; position: fixed; width: 100%; @@ -16,6 +16,8 @@ li.calc-main { padding: 8px 12px; width: 16px; height: 20px; + position: relative; + top: -36px; &:hover { opacity: .8; @@ -28,27 +30,14 @@ li.calc-main { div#calculator_wrapper { background: rgba(#111, .9); + position: relative; + top: -36px; clear: both; form { padding: lh(); @extend .clearfix; - input#calculator_input { - border: none; - @include box-shadow(none); - @include box-sizing(border-box); - font-size: 16px; - padding: 10px; - width: flex-grid(7.5); - margin: 0; - float: left; - - &:focus { - outline: none; - border: none; - } - } input#calculator_button { background: #111; @@ -83,20 +72,70 @@ li.calc-main { padding: 10px; width: flex-grid(4); } - } - dl { - display: none; - - dt { - clear: both; + div.input-wrapper { + position: relative; + @extend .clearfix; + width: flex-grid(7.5); + margin: 0; float: left; - font-weight: bold; - padding-right: lh(.5); + + input#calculator_input { + border: none; + @include box-shadow(none); + @include box-sizing(border-box); + font-size: 16px; + padding: 10px; + width: 100%; + + &:focus { + outline: none; + border: none; + } } - dd { - float: left; + div.help-wrapper { + position: absolute; + right: 8px; + top: 15px; + + a { + @include hide-text; + width: 17px; + height: 17px; + background: url("/static/images/info-icon.png") center center no-repeat; + } + + dl { + background: #fff; + @include border-radius(3px); + @include box-shadow(0 0 3px #999); + color: #333; + opacity: 0; + padding: 10px; + position: absolute; + right: -40px; + top: -110px; + width: 500px; + @include transition(); + + &.shown { + opacity: 1; + top: -115px; + } + + dt { + clear: both; + float: left; + font-weight: bold; + padding-right: lh(.5); + } + + dd { + float: left; + } + } + } } } } diff --git a/templates/sass/layout/_layout.scss b/templates/sass/layout/_layout.scss index a2e7a355c2..1cc9e2cdd1 100644 --- a/templates/sass/layout/_layout.scss +++ b/templates/sass/layout/_layout.scss @@ -17,7 +17,7 @@ html { @include box-shadow(0 0 4px #dfdfdf); @include box-sizing(border-box); margin-top: 3px; - overflow: hidden; + // overflow: hidden; @media print { border-bottom: 0; diff --git a/templates/sass/layout/_leanmodal.scss b/templates/sass/layout/_leanmodal.scss index 73cde4a2cb..8b1f05f37f 100644 --- a/templates/sass/layout/_leanmodal.scss +++ b/templates/sass/layout/_leanmodal.scss @@ -17,6 +17,7 @@ div.leanModal_box { @include box-sizing(border-box); display: none; padding: lh(2); + text-align: left; a.modal_close { color: #aaa; @@ -204,6 +205,35 @@ div#pwd_reset { } } +div#apply_name_change, +div#change_email, +div#unenroll, +div#deactivate-account { + max-width: 700px; + + ul { + list-style: none; + + li { + margin-bottom: lh(.5); + + textarea, #{$all-text-inputs} { + display: block; + width: 100%; + @include box-sizing(border-box); + } + + textarea { + height: 60px; + } + + input[type="submit"] { + white-space: normal; + } + } + } +} + div#feedback_div{ form{ ol { diff --git a/templates/textinput.html b/templates/textinput.html index 1473eadbf2..7c330777f3 100644 --- a/templates/textinput.html +++ b/templates/textinput.html @@ -1,5 +1,9 @@
- + diff --git a/urls.py b/urls.py index bf7b79ff64..3dbfb47f8b 100644 --- a/urls.py +++ b/urls.py @@ -10,19 +10,25 @@ import django.contrib.auth.views # admin.autodiscover() urlpatterns = ('', + url(r'^$', 'student.views.index'), # Main marketing page, or redirect to courseware + url(r'^change_email$', 'student.views.change_email_request'), + url(r'^email_confirm/(?P[^/]*)$', 'student.views.confirm_email_change'), + url(r'^change_name$', 'student.views.change_name_request'), + url(r'^accept_name_change$', 'student.views.accept_name_change'), + url(r'^reject_name_change$', 'student.views.reject_name_change'), + url(r'^pending_name_changes$', 'student.views.pending_name_changes'), url(r'^gradebook$', 'courseware.views.gradebook'), url(r'^event$', 'track.views.user_track'), url(r'^t/(?P