Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion aplus/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
INTERNAL_IPS = ['127.0.0.1']
# Configure Debug Toolbar to work under Docker by auto-detecting the gateway IP
# See: https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#docker
DEBUG_TOOLBAR_CONFIG = globals().get('DEBUG_TOOLBAR_CONFIG', {})
if 'DEBUG_TOOLBAR_CONFIG' not in globals():
DEBUG_TOOLBAR_CONFIG = {}
# Always use the Docker-aware callback so toolbar shows when accessed via localhost
DEBUG_TOOLBAR_CONFIG.setdefault('SHOW_TOOLBAR_CALLBACK', 'debug_toolbar.middleware.show_toolbar_with_docker')
2 changes: 1 addition & 1 deletion assets/css/main.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets_src/bootstrap5/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion assets_src/bootstrap5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Bootstrap 5 for A+",
"repository": ".",
"license": "MIT",
"version": "1.0.0",
"version": "1.0.1",
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8"
Expand Down
3 changes: 3 additions & 0 deletions course/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,9 @@ def __recurse_exercises(
'hierarchical_name': child.hierarchical_name,
'difficulty': child.difficulty,
'has_submittable_files': child.has_submittable_files,
'category': child.category,
'requires_confirmation': child.confirm_the_level,
'parent_id': child.parent.id if child.parent else None,
}
exercises.append(exercise_dictionary)

Expand Down
120 changes: 81 additions & 39 deletions exercise/api/csv/aggregate_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,39 @@
from exercise.cache.content import LearningObjectContent

# Generate students' results from this course instance
# Only exercises in which student has submitted answers will be returned
# to save bandwidth. Exercise points are returned in the form:
# xx Count: yy, xx Total: zz
# where xx is the exercise id, yy the submission count and zz the exercise points.
# For convenience, we also return the total submission count and points for student
# Results are returned in a compact nested format with zeros omitted:
#
# JSON format example:
# {
# "UserID": 13,
# "exercises": {
# "22": {"c": 3, "tb": 10, "tl": 8},
# "48": {"c": 2, "tb": 2, "tl": 1, "uc": 1, "utb": 5, "utl": 3}
# },
# "totals": {"c": 12, "tb": 117, "tl": 100, "uc": 1, "utb": 5, "utl": 4}
# }
# Keys: c=official_count, tb=official_total_best, tl=official_total_last,
# uc=unofficial_count, utb=unofficial_total_best, utl=unofficial_total_last

# pylint: disable-next=too-many-locals
def aggregate_points(profiles, taggings, exercises: List[LearningObjectContent], aggregate):
DEFAULT_FIELDS = [
'UserID', 'StudentID', 'Email', 'Name', 'Tags', 'Organization', 'Count', 'Total',
'UserID', 'StudentID', 'Email', 'Name', 'Tags', 'Organization',
]
OBJECT_FIELDS = [
'{} Count', '{} Total',
]

exercise_fields = []

for e in exercises:
for n in OBJECT_FIELDS:
exercise_fields.append(n.format(e.id))

agg = {}
# Gather exercise points per student
# Gather exercise points per student (now with official/all counts and best/last grades)
for row in aggregate:
ex = row['exercise_id']

values = [row['count'],row['total']]
user_row = agg.get(row['submitters__user_id'], {})
user_row[ex] = values
user_row[ex] = {
'official_count': row['official_count'],
'official_best': row['official_best'],
'official_last': row['official_last'],
'all_count': row['all_count'],
'all_best': row['all_best'],
'all_last': row['all_last'],
}
agg[row['submitters__user_id']] = user_row

# Prefetch all tag_id - user_id pairs at once from DB to avoid multiple queries
Expand Down Expand Up @@ -65,27 +69,65 @@ def aggregate_points(profiles, taggings, exercises: List[LearningObjectContent],
('Organization', profile.organization),
])

# Add submitted exercise count and points of the user as labeled dictionary items
# so for example if agg[uid] is {14: [1,10]}, it is turned into:
# "14 Count": 1
# "14 Total": 10
#
# Add exercise data in compact nested format with zeros omitted
exercises_nested = {}
if uid in agg:
student_totalsubs = 0
student_totalscore = 0
try:
for e in agg[uid]:
row[str(e) + ' Count'] = agg[uid][e][0]
student_totalsubs += agg[uid][e][0]
row[str(e) + ' Total'] = agg[uid][e][1]
student_totalscore += agg[uid][e][1]
except KeyError:
pass

# Add totals per student
row['Count'] = student_totalsubs
row['Total'] = student_totalscore
student_official_count = 0
student_official_best = 0
student_official_last = 0
student_all_count = 0
student_all_best = 0
student_all_last = 0

for ex_id, ex_data in agg[uid].items():
student_official_count += ex_data['official_count']
student_official_best += ex_data['official_best']
student_official_last += ex_data['official_last']
student_all_count += ex_data['all_count']
student_all_best += ex_data['all_best']
student_all_last += ex_data['all_last']

# Compact nested format: include both best and last grades
ex_nested = {
'c': ex_data['official_count'],
'tb': ex_data['official_best'],
'tl': ex_data['official_last']
}

# Only add unofficial fields if they differ from official (omit zeros)
unofficial_count = ex_data['all_count'] - ex_data['official_count']
unofficial_best = ex_data['all_best'] - ex_data['official_best']
unofficial_last = ex_data['all_last'] - ex_data['official_last']
if unofficial_count > 0:
ex_nested['uc'] = unofficial_count
if unofficial_best > 0:
ex_nested['utb'] = unofficial_best
if unofficial_last > 0:
ex_nested['utl'] = unofficial_last

exercises_nested[str(ex_id)] = ex_nested

# Add nested exercises object
row['exercises'] = exercises_nested

# Add totals in nested format with both best and last
totals_nested = {
'c': student_official_count,
'tb': student_official_best,
'tl': student_official_last
}
unofficial_total_count = student_all_count - student_official_count
unofficial_total_best = student_all_best - student_official_best
unofficial_total_last = student_all_last - student_official_last
if unofficial_total_count > 0:
totals_nested['uc'] = unofficial_total_count
if unofficial_total_best > 0:
totals_nested['utb'] = unofficial_total_best
if unofficial_total_last > 0:
totals_nested['utl'] = unofficial_total_last

row['totals'] = totals_nested

sheet.append(row)

return sheet, DEFAULT_FIELDS + exercise_fields
return sheet, DEFAULT_FIELDS
Loading
Loading