From fa63a17a14be7ad6d4ab1673611db54ca5d2407c Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 26 Jan 2012 19:03:11 -0500 Subject: [PATCH 1/8] Added due date pre-processor --HG-- branch : profiledev --- courseware/content_parser.py | 40 ++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/courseware/content_parser.py b/courseware/content_parser.py index c5c0600f9d..494d2205b3 100644 --- a/courseware/content_parser.py +++ b/courseware/content_parser.py @@ -8,6 +8,7 @@ from lxml import etree import json import hashlib +import logging ''' This file will eventually form an abstraction layer between the course XML file and the rest of the system. @@ -15,6 +16,8 @@ course XML file and the rest of the system. TODO: Shift everything from xml.dom.minidom to XPath (or XQuery) ''' +log = logging.getLogger("mitx.courseware") + def fasthash(string): m = hashlib.new("md4") m.update(string) @@ -83,12 +86,45 @@ def id_tag(course): new_id = "".join([a for a in new_id if a.isalnum()]) elem.set('id', new_id) else: - elem.set('id', fasthash(etree.tostring(elem))) - + elem.set('id', fasthash(etree.tostring(elem))) + +def due_tag(course): + # The primary purpose of this tagging is to make sure that each problem + # inherits the due date from the section that it is in. We also make + # sure that each section has a due date. If it does not, it inherits + # the last section's due date. This is to make sure that the sections + # are in chronological order. It is an exception to have a later section + # due before an earlier one. + + # How are due dates handled for different time zones? What _time_ are things due? + + # First, we grab the first due date to occur. This is our starting date. + firstSectionDue = course.xpath("//section[@due]")[0] + # I tried adding [1] to the end of the query string to select the first, + # but it didn't work. Is this not supported in etree? + + # All new dates must be further than currentDate + currentDate = firstSectionDue.get('due') + + sections = course.xpath("//section") + for section in sections: + existingDate = section.get('due') + if existingDate: + #TODO: Make sure existing date is further into the future than currentDate + currentDate = existingDate + else: + section.set('due', currentDate) + + problems=course.xpath('//section[@name=$section]//problem', section=section.get('name')) + + for problem in problems: + problem.set('due', currentDate) + def course_file(user): # TODO: Cache. tree = etree.parse(settings.DATA_DIR+UserProfile.objects.get(user=user).courseware) id_tag(tree) + due_tag(tree) return tree def module_xml(coursefile, module, id_tag, module_id): From 4f152585978389c8a93dfdad7a692f3fa26de99d Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 27 Jan 2012 12:39:55 -0500 Subject: [PATCH 2/8] Changed behavior of due date tagger to be more like downward inheritance --HG-- branch : profiledev --- courseware/content_parser.py | 49 +++++++++++++++--------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/courseware/content_parser.py b/courseware/content_parser.py index 494d2205b3..ca70282caa 100644 --- a/courseware/content_parser.py +++ b/courseware/content_parser.py @@ -88,37 +88,28 @@ def id_tag(course): else: elem.set('id', fasthash(etree.tostring(elem))) -def due_tag(course): - # The primary purpose of this tagging is to make sure that each problem - # inherits the due date from the section that it is in. We also make - # sure that each section has a due date. If it does not, it inherits - # the last section's due date. This is to make sure that the sections - # are in chronological order. It is an exception to have a later section - # due before an earlier one. +def due_tag(element, parent_due_date=None): + ''' This call is to pass down due dates. If an element has a due date, + all of the elements children will inherit this due date (unless the element + has a due date of its own). This is called recursively''' - # How are due dates handled for different time zones? What _time_ are things due? - - # First, we grab the first due date to occur. This is our starting date. - firstSectionDue = course.xpath("//section[@due]")[0] - # I tried adding [1] to the end of the query string to select the first, - # but it didn't work. Is this not supported in etree? - - # All new dates must be further than currentDate - currentDate = firstSectionDue.get('due') - - sections = course.xpath("//section") - for section in sections: - existingDate = section.get('due') - if existingDate: - #TODO: Make sure existing date is further into the future than currentDate - currentDate = existingDate + if (parent_due_date == None): #This is the entry call. Select all due elements + all_due_elements = element.xpath("//*[@due]") + for due_element in all_due_elements: + due_date = due_element.get('due') + for child_element in due_element: + due_tag(child_element, due_date) + else: + #The hack below is because we would get _ContentOnlyELements from the + #iterator that can't have due dates set. We can't find API for it + if not element.get('due') and type(element) == etree._Element: + element.set('due', parent_due_date) + due_date = parent_due_date else: - section.set('due', currentDate) - - problems=course.xpath('//section[@name=$section]//problem', section=section.get('name')) - - for problem in problems: - problem.set('due', currentDate) + due_date = element.get('due') + + for child_element in element: + due_tag(child_element, due_date) def course_file(user): # TODO: Cache. From c3a6ad2f8c45788bc97e48b77a720b1bea282b39 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 27 Jan 2012 16:12:56 -0500 Subject: [PATCH 3/8] Module ids are unique even across module types. Fix bugs where other fields were being used for an id. --HG-- branch : profiledev --- courseware/content_parser.py | 53 +++++++++++++++++-------------- courseware/models.py | 2 +- courseware/module_render.py | 9 ++++-- courseware/modules/capa_module.py | 6 ++-- courseware/views.py | 23 +++++++++----- 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/courseware/content_parser.py b/courseware/content_parser.py index ca70282caa..e577324568 100644 --- a/courseware/content_parser.py +++ b/courseware/content_parser.py @@ -82,40 +82,47 @@ def id_tag(course): if elem.get('id'): pass elif elem.get(default_ids[elem.tag]): - new_id = elem.get(default_ids[elem.tag]) # Convert to alphanumeric - new_id = "".join([a for a in new_id if a.isalnum()]) + new_id = elem.get(default_ids[elem.tag]) + new_id = "".join([a for a in new_id if a.isalnum()]) # Convert to alphanumeric + # Without this, a conflict may occur between an hmtl or youtube id + new_id = default_ids[elem.tag] + new_id elem.set('id', new_id) else: elem.set('id', fasthash(etree.tostring(elem))) -def due_tag(element, parent_due_date=None): - ''' This call is to pass down due dates. If an element has a due date, - all of the elements children will inherit this due date (unless the element - has a due date of its own). This is called recursively''' - - if (parent_due_date == None): #This is the entry call. Select all due elements - all_due_elements = element.xpath("//*[@due]") - for due_element in all_due_elements: - due_date = due_element.get('due') - for child_element in due_element: - due_tag(child_element, due_date) +def propogate_downward_tag(element, attribute_name, parent_attribute = None): + ''' This call is to pass down an attribute to all children. If an element + has this attribute, it will be "inherited" by all of its children. If a + child (A) already has that attribute, A will keep the same attribute and + all of A's children will inherit A's attribute. This is a recursive call.''' + + if (parent_attribute == None): #This is the entry call. Select all due elements + all_attributed_elements = element.xpath("//*[@" + attribute_name +"]") + for attributed_element in all_attributed_elements: + attribute_value = attributed_element.get(attribute_name) + for child_element in attributed_element: + propogate_downward_tag(child_element, attribute_name, attribute_value) else: - #The hack below is because we would get _ContentOnlyELements from the - #iterator that can't have due dates set. We can't find API for it - if not element.get('due') and type(element) == etree._Element: - element.set('due', parent_due_date) - due_date = parent_due_date - else: - due_date = element.get('due') + '''The hack below is because we would get _ContentOnlyELements from the + iterator that can't have due dates set. We can't find API for it. If we + ever have an element which subclasses BaseElement, we will not tag it''' + if not element.get(attribute_name) and type(element) == etree._Element: + element.set(attribute_name, parent_attribute) - for child_element in element: - due_tag(child_element, due_date) + for child_element in element: + propogate_downward_tag(child_element, attribute_name, parent_attribute) + else: + #This element would have already been found by Xpath, so we return + #for now and trust that this element will get its turn to propogate + #to its children later. + return def course_file(user): # TODO: Cache. tree = etree.parse(settings.DATA_DIR+UserProfile.objects.get(user=user).courseware) id_tag(tree) - due_tag(tree) + propogate_downward_tag(tree, "due") + propogate_downward_tag(tree, "graded") return tree def module_xml(coursefile, module, id_tag, module_id): diff --git a/courseware/models.py b/courseware/models.py index cee61f336b..f334208597 100644 --- a/courseware/models.py +++ b/courseware/models.py @@ -50,7 +50,7 @@ class StudentModule(models.Model): module_id = models.CharField(max_length=255) # Filename for homeworks, etc. student = models.ForeignKey(User) class Meta: - unique_together = (('student', 'module_id', 'module_type'),) + unique_together = (('student', 'module_id'),) ## Internal state of the object state = models.TextField(null=True, blank=True) diff --git a/courseware/module_render.py b/courseware/module_render.py index 4c969447a1..b4e08ab17b 100644 --- a/courseware/module_render.py +++ b/courseware/module_render.py @@ -33,10 +33,14 @@ from django.conf import settings import courseware.content_parser as content_parser import sys +import logging from lxml import etree import uuid + +log = logging.getLogger("mitx.courseware") + ## TODO: Add registration mechanism modx_modules={'problem':courseware.modules.capa_module.LoncapaModule, 'video':courseware.modules.video_module.VideoModule, @@ -64,11 +68,10 @@ def make_track_function(request): def modx_dispatch(request, module=None, dispatch=None, id=None): ''' Generic view for extensions. ''' # Grab the student information for the module from the database - s = StudentModule.objects.filter(module_type=module, - student=request.user, + s = StudentModule.objects.filter(student=request.user, module_id=id) if len(s) == 0: - print "ls404", module, request.user, id + log.debug("Couldnt find module for user and id " + str(module) + " " + str(request.user) + " "+ str(id)) raise Http404 s=s[0] diff --git a/courseware/modules/capa_module.py b/courseware/modules/capa_module.py index 591f9c7516..51cf6f823a 100644 --- a/courseware/modules/capa_module.py +++ b/courseware/modules/capa_module.py @@ -40,13 +40,13 @@ class LoncapaModule(XModule): def get_html(self): return render_to_string('problem_ajax.html', - {'id':self.filename, + {'id':self.item_id, 'ajax_url':self.ajax_url, }) def get_init_js(self): return render_to_string('problem.js', - {'id':self.filename, + {'id':self.item_id, 'ajax_url':self.ajax_url, }) @@ -94,7 +94,7 @@ class LoncapaModule(XModule): html=render_to_string('problem.html', {'problem' : content, - 'id' : self.filename, + 'id' : self.item_id, 'check_button' : check_button, 'reset_button' : reset_button, 'save_button' : save_button, diff --git a/courseware/views.py b/courseware/views.py index 5f5680b663..ccfda0f678 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -20,6 +20,7 @@ from lxml import etree from auth.models import UserProfile from models import StudentModule from module_render import * # TODO: Clean up +from module_render import modx_dispatch import courseware.content_parser as content_parser log = logging.getLogger("mitx.courseware") @@ -45,6 +46,11 @@ def profile(request): chapters = dom.xpath('//course[@name=$course]/chapter', course=course) responses=StudentModule.objects.filter(student=request.user) + response_by_id = {} + for response in responses: + response_by_id[response.module_id] = response + + print response_by_id for c in chapters: chname=c.get('name') @@ -55,14 +61,15 @@ def profile(request): scores=[] if len(problems)>0: for p in problems: - id = p.get('filename') + id = p.get('id') correct = 0 - for response in responses: - if response.module_id == id: - if response.grade!=None: - correct=response.grade - else: - correct=0 + if id in response_by_id: + response = response_by_id[id] + if response.grade!=None: + correct=response.grade + else: + print "Couldn't find id " + id + total=courseware.modules.capa_module.LoncapaModule(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores? scores.append((int(correct),total)) score={'course':course, @@ -71,7 +78,7 @@ def profile(request): 'scores':scores, } hw.append(score) - + user_info=UserProfile.objects.get(user=request.user) context={'name':user_info.name, From 9ba59fe3a492d79104f392d7abca9e8c28002825 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 30 Jan 2012 13:30:39 -0500 Subject: [PATCH 4/8] Working on adding up graded scores for sections. --HG-- branch : profiledev --- courseware/module_render.py | 1 + courseware/views.py | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/courseware/module_render.py b/courseware/module_render.py index b31ddbc27a..f001583379 100644 --- a/courseware/module_render.py +++ b/courseware/module_render.py @@ -1,5 +1,6 @@ import StringIO import json +import logging import os import sys import sys diff --git a/courseware/views.py b/courseware/views.py index ad06e6026c..65c3711fe5 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -46,7 +46,8 @@ def profile(request): for response in responses: response_by_id[response.module_id] = response - print response_by_id + + totalScores = {} for c in chapters: chname=c.get('name') @@ -63,19 +64,34 @@ def profile(request): response = response_by_id[id] if response.grade!=None: correct=response.grade - else: - print "Couldn't find id " + id total=courseware.modules.capa_module.LoncapaModule(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores? - scores.append((int(correct),total)) + scores.append((int(correct),total, ( True if s.get('graded') == "True" else False ) )) + + + section_total = (sum([score[0] for score in scores]), + sum([score[1] for score in scores])) + + 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 totalScores + if s.get('format') and graded_total[1] > 0: + format_scores = totalScores[ s.get('format') ] if s.get('format') in totalScores else [] + format_scores.append( graded_total ) + totalScores[ s.get('format') ] = format_scores + score={'course':course, 'section':s.get("name"), 'chapter':c.get("name"), 'scores':scores, + 'section_total' : section_total, } hw.append(score) user_info=UserProfile.objects.get(user=request.user) + + print "totalScores" , totalScores context={'name':user_info.name, 'username':request.user.username, From 8712197df0740b68b6abeca4cef74bd6c42411d1 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 1 Feb 2012 14:32:43 -0500 Subject: [PATCH 5/8] Simple graphing of homework and lab scores --HG-- branch : profiledev --- courseware/views.py | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/courseware/views.py b/courseware/views.py index 65c3711fe5..b0c1862ab6 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -76,29 +76,55 @@ def profile(request): sum([score[1] for score in scores if score[2]])) #Add the graded total to totalScores - if s.get('format') and graded_total[1] > 0: - format_scores = totalScores[ s.get('format') ] if s.get('format') in totalScores else [] + format = s.get('format') if s.get('format') else "" + if format and graded_total[1] > 0: + format_scores = totalScores[ format ] if format in totalScores else [] format_scores.append( graded_total ) - totalScores[ s.get('format') ] = format_scores + totalScores[ format ] = format_scores score={'course':course, 'section':s.get("name"), 'chapter':c.get("name"), 'scores':scores, 'section_total' : section_total, + 'format' : format, } hw.append(score) - user_info=UserProfile.objects.get(user=request.user) - print "totalScores" , totalScores - + #Figure the homework scores + print totalScores + homeworkScores = totalScores['Homework'] if 'Homework' in totalScores else [] + homeworkPercentages = [] + for i in range(12): + if i < len(homeworkScores): + percentage = homeworkScores[i][0] / float(homeworkScores[i][1]) + else: + percentage = 0 + homeworkPercentages.append(percentage) + + labScores = totalScores['Lab'] if 'Lab' in totalScores else [] + labPercentages = [] + for i in range(12): + if i < len(labScores): + percentage = labScores[i][0] / float(labScores[i][1]) + else: + percentage = 0 + labPercentages.append(percentage) + + + + + + user_info=UserProfile.objects.get(user=request.user) context={'name':user_info.name, 'username':request.user.username, 'location':user_info.location, 'language':user_info.language, 'email':request.user.email, - 'homeworks':hw, + 'homeworks':hw, + 'homework_percentages' : homeworkPercentages, + 'lab_percentages' : labPercentages, 'csrf':csrf(request)['csrf_token'] } return render_to_response('profile.html', context) From 5a752dffe2a9b61b2632d6f3bd9c35766d8ef3fd Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 1 Feb 2012 21:04:04 -0500 Subject: [PATCH 6/8] Got a basic, working graph of the scores --HG-- branch : profiledev --- courseware/views.py | 81 ++++++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/courseware/views.py b/courseware/views.py index b0c1862ab6..296e41946d 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -47,7 +47,7 @@ def profile(request): response_by_id[response.module_id] = response - totalScores = {} + total_scores = {} for c in chapters: chname=c.get('name') @@ -75,12 +75,12 @@ def profile(request): 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 totalScores + #Add the graded total to total_scores format = s.get('format') if s.get('format') else "" if format and graded_total[1] > 0: - format_scores = totalScores[ format ] if format in totalScores else [] + format_scores = total_scores[ format ] if format in total_scores else [] format_scores.append( graded_total ) - totalScores[ format ] = format_scores + total_scores[ format ] = format_scores score={'course':course, 'section':s.get("name"), @@ -92,29 +92,50 @@ def profile(request): hw.append(score) - #Figure the homework scores - print totalScores - homeworkScores = totalScores['Homework'] if 'Homework' in totalScores else [] - homeworkPercentages = [] - for i in range(12): - if i < len(homeworkScores): - percentage = homeworkScores[i][0] / float(homeworkScores[i][1]) - else: - percentage = 0 - homeworkPercentages.append(percentage) - - labScores = totalScores['Lab'] if 'Lab' in totalScores else [] - labPercentages = [] - for i in range(12): - if i < len(labScores): - percentage = labScores[i][0] / float(labScores[i][1]) - else: - percentage = 0 - labPercentages.append(percentage) - - + def totalWithDrops(scores, drop_count): + sorted_scores = sorted( enumerate(scores), key=lambda x: -x[1]['percentage'] ) #Note that this key will sort the list descending + dropped_indices = [score[0] for score in sorted_scores[-drop_count:]] # A list of the indices of the dropped scores + aggregate_score = 0 + for index, score in enumerate(scores): + if index not in dropped_indices: + aggregate_score += score['percentage'] + aggregate_score /= len(scores) - drop_count + + return aggregate_score, dropped_indices + + #Figure the homework scores + homework_scores = total_scores['Homework'] if 'Homework' in total_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 = "{:.0%} ({}/{})".format( percentage, homework_scores[i][0], homework_scores[i][1] ) + else: + percentage = 0 + summary = "0% (?/?)" + summary = "Homework {} - {}".format(i + 1, summary) + + homework_percentages.append( {'percentage': percentage, 'summary': summary} ) + 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_percentages = [] + for i in range(12): + if i < len(lab_scores): + percentage = lab_scores[i][0] / float(lab_scores[i][1]) + summary = "{:.0%} ({}/{})".format( percentage, lab_scores[i][0], lab_scores[i][1] ) + else: + percentage = 0 + summary = "0% (?/?)" + summary = "Lab {} - {}".format(i + 1, summary) + lab_percentages.append( {'percentage': percentage, 'summary': summary} ) + lab_total, lab_dropped_indices = totalWithDrops(lab_percentages, 2) + + midterm_score = (130, 150) + final_score = (225, 300) user_info=UserProfile.objects.get(user=request.user) context={'name':user_info.name, @@ -123,8 +144,14 @@ def profile(request): 'language':user_info.language, 'email':request.user.email, 'homeworks':hw, - 'homework_percentages' : homeworkPercentages, - 'lab_percentages' : labPercentages, + 'homework_percentages' : homework_percentages, + 'homework_total' : homework_total, + 'homework_dropped_indices' : homework_dropped_indices, + 'lab_percentages' : lab_percentages, + 'lab_total' : lab_total, + 'lab_dropped_indices' : lab_dropped_indices, + 'midterm_score' : midterm_score, + 'final_score' : final_score, 'csrf':csrf(request)['csrf_token'] } return render_to_response('profile.html', context) From 00b1bad38f671a1c0f82ad104e09833eede3d5e1 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 2 Feb 2012 14:32:44 -0500 Subject: [PATCH 7/8] Got grade overview graph drawing --HG-- branch : profiledev --- courseware/views.py | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/courseware/views.py b/courseware/views.py index 296e41946d..d2ee114ea2 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -135,7 +135,38 @@ def profile(request): lab_total, lab_dropped_indices = totalWithDrops(lab_percentages, 2) midterm_score = (130, 150) + midterm_percentage = midterm_score[0] / float(midterm_score[1]) + final_score = (225, 300) + final_percentage = final_score[0] / float(final_score[1]) + + grade_summary = [ + { + 'category': 'Homework', + 'subscores' : homework_percentages, + 'dropped_indices' : homework_dropped_indices, + 'totalscore' : {'score' : homework_total, 'summary' : "Homework Average - {:.0%}".format(homework_total)}, + 'weight' : 0.15, + }, + { + 'category': 'Labs', + 'subscores' : lab_percentages, + 'dropped_indices' : lab_dropped_indices, + 'totalscore' : {'score' : lab_total, 'summary' : "Lab Average - {:.0%}".format(lab_total)}, + 'weight' : 0.15, + }, + { + 'category': 'Midterm', + 'totalscore' : {'score' : midterm_percentage, 'summary' : "Midterm - {:.0%} ({}/{})".format(midterm_percentage, midterm_score[0], midterm_score[1])}, + 'weight' : 0.30, + }, + { + 'category': 'Final', + 'totalscore' : {'score' : final_percentage, 'summary' : "Final - {:.0%} ({}/{})".format(final_percentage, final_score[0], final_score[1])}, + 'weight' : 0.40, + } + ] + user_info=UserProfile.objects.get(user=request.user) context={'name':user_info.name, @@ -144,14 +175,7 @@ def profile(request): 'language':user_info.language, 'email':request.user.email, 'homeworks':hw, - 'homework_percentages' : homework_percentages, - 'homework_total' : homework_total, - 'homework_dropped_indices' : homework_dropped_indices, - 'lab_percentages' : lab_percentages, - 'lab_total' : lab_total, - 'lab_dropped_indices' : lab_dropped_indices, - 'midterm_score' : midterm_score, - 'final_score' : final_score, + 'grade_summary' : grade_summary, 'csrf':csrf(request)['csrf_token'] } return render_to_response('profile.html', context) From 25f17eac211147873403434cd2f53c1e944197f8 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 2 Feb 2012 16:08:58 -0500 Subject: [PATCH 8/8] Graph tweaks. --HG-- branch : profiledev --- courseware/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courseware/views.py b/courseware/views.py index d2ee114ea2..fa705b41eb 100644 --- a/courseware/views.py +++ b/courseware/views.py @@ -134,10 +134,10 @@ def profile(request): lab_percentages.append( {'percentage': percentage, 'summary': summary} ) lab_total, lab_dropped_indices = totalWithDrops(lab_percentages, 2) - midterm_score = (130, 150) + midterm_score = (120, 150) midterm_percentage = midterm_score[0] / float(midterm_score[1]) - final_score = (225, 300) + final_score = (200, 300) final_percentage = final_score[0] / float(final_score[1]) grade_summary = [