Skip to content

Commit

Permalink
Add canvas compatible CSV import
Browse files Browse the repository at this point in the history
now the exported CSV can be used to upload assignment grades to canvas
  • Loading branch information
IonMich committed Feb 25, 2024
1 parent 5a752f1 commit ab017c0
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 20 deletions.
31 changes: 25 additions & 6 deletions assignments/static/assignments/detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -1037,9 +1037,9 @@ for (const btn of btnCarouselNext) {
// when export-grades-form is submitted, get the submission_pks of the cards
// and append them to the form data as submission_pks
// then submit the form with fetch
const exportGradesForm = document.getElementById('export-grades-form');
if (exportGradesForm) {
exportGradesForm.addEventListener('submit', async function(event) {
const exportGradesDetailedForm = document.getElementById('export-grades-detailed-form');
if (exportGradesDetailedForm) {
exportGradesDetailedForm.addEventListener('submit', async function(event) {
event.preventDefault();
const form = this;
const submissionPks = Array.from(document.querySelectorAll('.card')).map(card => card.getAttribute('data-pk'));
Expand All @@ -1048,19 +1048,38 @@ if (exportGradesForm) {
submissionPksInput.name = 'submission_pks';
submissionPksInput.value = JSON.stringify(submissionPks);
form.appendChild(submissionPksInput);
const csrfToken = form.querySelector('input[name="csrfmiddlewaretoken"]').value;

// const csrfToken = form.querySelector('input[name="csrfmiddlewaretoken"]').value;
form.submit();
submissionPksInput.remove();

// close the modal
const modal = document.getElementById('export-grades');
const bsModal = bootstrap.Modal.getOrCreateInstance(modal);
bsModal.hide();

});
}

const exportGradesCanvasForm = document.getElementById('export-grades-canvas-form');
if (exportGradesCanvasForm) {
exportGradesCanvasForm.addEventListener('submit', async function(event) {
event.preventDefault();
const form = this;
const submissionPks = Array.from(document.querySelectorAll('.card')).map(card => card.getAttribute('data-pk'));
const submissionPksInput = document.createElement('input');
submissionPksInput.type = 'hidden';
submissionPksInput.name = 'submission_pks';
submissionPksInput.value = JSON.stringify(submissionPks);
form.appendChild(submissionPksInput);
// const csrfToken = form.querySelector('input[name="csrfmiddlewaretoken"]').value;
form.submit();
submissionPksInput.remove();

// close the modal
const modal = document.getElementById('export-grades');
const bsModal = bootstrap.Modal.getOrCreateInstance(modal);
bsModal.hide();
});
}

// handle modal close
// when the modal is closed, resume the carousel of all cards
Expand Down
5 changes: 3 additions & 2 deletions courses/static/courses/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,13 @@ async function populateSectionsWithStudents(selectedCanvasStudents, courseId) {
canvas_id: student.id,
last_name: student.sortable_name.split(',')[0].trim(),
first_name: student.sortable_name.split(',')[1].trim(),
uni_id: student.sis_user_id || student.email || student.uuid || `canvas:${student.id}`,
email: student.email || null,
uni_id: student.sis_user_id || student.sis_login_id || student.uuid || `canvas:${student.id}`,
email: student.sis_login_id || null,
section_id: student.enrollments[0].course_section_id,
bio: student.bio || '',
avatar_url: student.avatar_url || '',
};
console.log(studentData);
studentsData.push(studentData);
}
formData.append('students', JSON.stringify(studentsData));
Expand Down
7 changes: 4 additions & 3 deletions courses/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,10 @@ def api_course_assignments_create(request, course_pk):
num_subs_with_question_grades = assignment.get_all_submissions().filter(question_grades__isnull=False).count()
if (not created) and (num_subs_with_grader > 0 or num_subs_with_question_grades > 0):
defaults.pop('max_question_scores')
print(f"Assignment has graded submissions. Not updating max_question_scores.")
print("Assignment has graded submissions. Not updating max_question_scores.")
if (not created):
print(assignment)
print(f"Assignment is updated")
print("Assignment is updated")
for key, value in defaults.items():
setattr(assignment, key, value)
assignment.save()
Expand Down Expand Up @@ -530,8 +530,9 @@ def api_canvas_sections_get_view(request, canvas_id):
corresponding_section.update(section_dict)
list_to_include = [
"enrollments", "locked", "bio",
"sis_user_id", "avatar_url"
"sis_user_id", "sis_login_id", "avatar_url"
]

canvas_users = []
try:
canvas_users = canvas_course.get_users(
Expand Down
1 change: 1 addition & 0 deletions students/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def api_course_enrollments_create(request, course_pk):
defaults.pop('uni_id')
if (not created):
for key, value in defaults.items():
print(f'setting {key} from {getattr(student, key)} to {value}')
setattr(student, key, value)
student.save()

Expand Down
17 changes: 11 additions & 6 deletions submissions/templates/submissions/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,21 @@ <h5 class="card-title text-muted"> <i>No Student</i></h5>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="export-grades-label">Export Grades</h5>
<h5 class="modal-title" id="export-grades-label">Export Grades as CSV</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Export gradebook as a CSV file</p>
<form method="POST" id="export-grades-form" action="{% url 'submissions:export-grades-csv' %}">
<p>CSV files can be used to import grades into Canvas, to keep a record of the grades, or for further analysis.</p>
<form method="POST" id="export-grades-canvas-form" action="{% url 'submissions:export-grades-csv-canvas' %}">
{% csrf_token %}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Export</button>
<div>
<button type="submit" class="btn btn-primary d-block mx-auto my-3">CSV for Canvas</button>
</div>
</form>
<form method="POST" id="export-grades-detailed-form" action="{% url 'submissions:export-grades-csv-detailed' %}">
{% csrf_token %}
<div class="mb-3 d-flex justify-content-between">
<button type="submit" class="btn btn-primary d-block mx-auto">CSV with all records</button>
</div>
</form>
</div>
Expand Down
7 changes: 4 additions & 3 deletions submissions/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
submission_classify_view, submission_comment_delete_view,
submission_comment_modify_view, submission_delete_all_view,
submission_delete_view, submission_detail_view,
submission_export_grades_csv_view, api_grade_update_view,
api_submission_patch_view,
submission_export_grades_csv_view, export_gradebook_canvas_csv_view,
api_grade_update_view, api_submission_patch_view,
api_submission_manual_version_view,
api_submissions_list_view, submission_pdf_view,
api_grades_list_view,
Expand All @@ -18,7 +18,8 @@
path('courses/<course_pk>/assignments/<assignment_pk>/submissions/', home_view, name='home'),
path('courses/<course_pk>/submissions/classify/', submission_classify_view, name='classify'),
path('courses/<course_pk>/assignments/<assignment_pk>/submissions/delete-all/', submission_delete_all_view, name='delete-all-submissions'),
path('submissions/export-csv/', submission_export_grades_csv_view, name='export-grades-csv'),
path('submissions/export-csv/', submission_export_grades_csv_view, name='export-grades-csv-detailed'),
path('submissions/export-csv-canvas/', export_gradebook_canvas_csv_view, name='export-grades-csv-canvas'),
path('courses/<course_pk>/assignments/<assignment_pk>/submissions/<submission_pk>/', submission_detail_view, name='detail'),
path('courses/<course_pk>/assignments/<assignment_pk>/submissions/<uuid:submission_pk>/previous/', redirect_to_previous, name='detail_previous'),
path('courses/<course_pk>/assignments/<assignment_pk>/submissions/<uuid:submission_pk>/next/', redirect_to_next, name='detail_next'),
Expand Down
41 changes: 41 additions & 0 deletions submissions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,47 @@ def api_grades_list_view(request, assignment_pk):
'grades': grades,
})

@login_required
def export_gradebook_canvas_csv_view(request):
"""
This view exports the grades for an assignment to a CSV file
"""
if request.method != 'POST':
return JsonResponse({
'message': 'This view only accepts POST requests',
'success': False,
})

# the sub pks are in the JSON stringified list
submission_pks = request.POST.get('submission_pks')
import json
submission_pks = json.loads(submission_pks)

# get the submissions
print(submission_pks)
submissions = PaperSubmission.objects.filter(pk__in=submission_pks)

# create a pandas dataframe
assignment_column = f"{submissions[0].assignment.name} ({submissions[0].assignment.canvas_id})"
data = []
for submission in submissions:
row = {
'Student': f"{submission.student.last_name}, {submission.student.first_name}",
'ID': submission.student.canvas_id,
'SIS User ID': submission.student.uni_id,
'SIS Login ID': "",
'Section': submission.student.get_section_in_course(submission.assignment.course).name,
assignment_column: submission.grade,
}
data.append(row)
df = pd.DataFrame(data)
# create the response
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{assignment_column}_grades.csv"'
df.to_csv(response, index=False)
return response

@login_required
def submission_export_grades_csv_view(request):
"""
Expand Down

0 comments on commit ab017c0

Please sign in to comment.