Skip to content

Commit

Permalink
Merge pull request #67 from IonMich/navigation
Browse files Browse the repository at this point in the history
Filtered Navigation for submissions, partial grading
  • Loading branch information
IonMich authored Dec 7, 2023
2 parents 9d4e148 + 797a1a0 commit 4479c6a
Show file tree
Hide file tree
Showing 10 changed files with 738 additions and 145 deletions.
8 changes: 8 additions & 0 deletions assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ def get_all_average_grades(self):
def get_all_grades(self):
"""Returns the grades of all submissions of the assignment."""
return [s.grade for s in self.get_all_submissions().filter(graded_by__isnull=False)]

def get_all_question_grades(self):
"""Returns the question grades of all submissions of the assignment."""
grades = []
for s in self.get_all_submissions():
if s.get_question_grades():
grades.append(s.get_question_grades())
return grades

def sync_labeled_submissions_from_canvas(self):
"""Adds the canvas_id of the corresponding canvas
Expand Down
108 changes: 93 additions & 15 deletions assignments/templates/assignments/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,100 @@


{% comment %} show the assignment grading progress and the average grade {% endcomment %}
<h1>
{{ assignment.name }}
<span class="badge bg-light text-dark {% if assignment.get_grading_progress == 100.0 %}border border-2 border-success{% endif %}"
title="Percentage of Submissions Graded">
{{ assignment.get_grading_progress }} %
</span>
{% if assignment.get_canvas_url %}
<button class="btn btn-outline-light btn-sm assignment-canvas-btn"
data-assignment-canvas-url="{{assignment.get_canvas_url}}"
title="Open assignment in Canvas"
onclick="window.open(this.dataset.assignmentCanvasUrl, '_blank');">
<img src="https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://canvas.instructure.com&size=48"
style="width: 16px; height: 16px; filter: grayscale(100%);">
</button>
<div class="d-flex justify-content-between align-items-center">
<div class="me-auto">
<h1>
{{ assignment.name }}
<span class="badge bg-light text-dark {% if assignment.get_grading_progress == 100.0 %}border border-2 border-success{% endif %}"
title="Percentage of Submissions Graded">
{{ assignment.get_grading_progress }} %
</span>
{% comment %} add settigns button {% endcomment %}

{% if assignment.get_canvas_url %}
<button class="btn btn-outline-light btn-sm assignment-canvas-btn"
data-assignment-canvas-url="{{assignment.get_canvas_url}}"
title="Open assignment in Canvas"
onclick="window.open(this.dataset.assignmentCanvasUrl, '_blank');">
<img src="https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://canvas.instructure.com&size=48"
style="width: 16px; height: 16px; filter: grayscale(100%);">
</button>
{% endif %}
</h1>
</div>
{% if qs %}
<div class="ms-auto">
<button type="button" class="btn btn-outline-light btn-sm" data-bs-toggle="modal" data-bs-target="#settingsModal">
<i class="bi bi-gear text-dark" style="width: 32px; height: 32px; filter: grayscale(100%);"></i>
</button>
</div>
{% comment %} create settings modal {% endcomment %}
<div class="modal fade" id="settingsModal" tabindex="-1" aria-labelledby="settingsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="settingsModalLabel">Select grading scheme</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="settingsModalBody update-grade-scheme py-3 w-75 m-auto">
{% comment %} if qs show warning alert {% endcomment %}
<div class="current-grade-scheme mb-2">
<p class="mb-1">Current grading scheme: {{assignment.number_of_questions}} question{{assignment.number_of_questions|pluralize}} with
maximum score{{assignment.number_of_questions|pluralize}} of {{assignment.max_question_scores}} points.
</p>
</div>
{% if assignment.get_all_question_grades%}

<div class="alert alert-secondary d-flex align-items-center" role="alert">
{% comment %} info icon {% endcomment %}
<i class="bi bi-info-circle-fill" style='font-size:26px;'></i>
<div class="ms-2">
<p>There are already graded questions for this assignment. Please delete
all grades before changing the grading scheme.</p>
</div>
</div>
{% endif %}
<form class="{% if assignment.get_all_question_grades %} d-none {% endif %}" action="" enctype="multipart/form-data" method="POST" id="gradingSchemeForm">
{% csrf_token %}
{% comment %} add slider for the number of questions. max is the total grade{% endcomment %}
<label for="num_questions">Number of questions</label>
<div class="d-flex justify-content-between align-items-center">
<input type="range" class="form-range" id="num_questions" name="num_questions" min="1" max="{{assignment.max_score}}" value="{{assignment.number_of_questions}}" oninput="this.form.num_questions_output.value = this.value">
<input type="number" class="form-control" name="num_questions_output" value="{{assignment.number_of_questions}}" min="1" max="{{assignment.max_score}}" style="width: 60px; margin-left:1rem" oninput="this.form.num_questions.value = this.value">
</div>
{% comment %} add a switch for equal or not equal grades for each question{% endcomment %}
<div class="form-check form-switch mt-4">
<input class="form-check-input" type="checkbox" role="switch" id="equal_grades" name="equal_grades"
{% if assignment.has_equal_question_scores %}
checked
{% endif %}
>
<label for="equal_grades" class="">Equal maximum scores for all questions</label>
</div>

<div class="expander" id="expander">
<div class="form-group mt-1 ms-1 me-2 mb-2" id="grades_input_group">
{% comment %} input for the maximum grade for each question{% endcomment %}
{% comment %} E.g. 3,2 for 2 questions with 3 and 2 points respectively{% endcomment %}
<label for="max_grades" class="mt-1">Maximum grades for each question:</label>
<input type="text" class="form-control" id="max_grades" name="max_grades" value="{{assignment.max_question_scores}}" placeholder="E.g. 3,2 for two questions with 3 and 2 points respectively">
</div>
</div>
{% comment %} add switch to apply the same grading scheme to all assignments in the same assignment group{% endcomment %}
<div class="form-check form-switch mt-3">
<input class="form-check-input" type="checkbox" role="switch" id="apply_to_all" name="apply_to_all">
<label for="apply_to_all" class="">Apply to assignments in "{{assignment.assignment_group_object}}"</label>
</div>
{% comment %} add a hidden input for the assignment_id {% endcomment %}
<input type="hidden" name="assignment_id" value="{{assignment.pk}}" id="assignment_id">
<button type="button" name="update_grading_scheme" id="updateGradingSchemeBtn" class="btn btn-primary mt-3" style="display:block; margin:0 auto;">Update</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
</h1>
</div>

{% if assignment.description %}
{% comment %} show description if it exists in italics {% endcomment %}
Expand Down
2 changes: 1 addition & 1 deletion assignments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def assignment_detail_view(request, course_pk, assignment_pk):
message = 'Upload to canvas completed gracefully. Details in the terminal.'
message_type = 'info'
except Exception as e:
print("An error occured while syncing submissions to canvas")
print(f"An error occured while syncing submissions to canvas: {e}")
import traceback
print(traceback.format_exc())
message = "An error occured while syncing submissions to canvas"
Expand Down
10 changes: 7 additions & 3 deletions courses/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ def api_course_assignments_create(request, course_pk):
canvas_id=data.get('canvas_id'),
defaults=defaults
)
if (not created) and assignment.get_all_submissions().filter(graded_by__isnull=False).count() > 0:
num_subs_with_grader = assignment.get_all_submissions().filter(graded_by__isnull=False).count()
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.")
if (not created):
Expand Down Expand Up @@ -363,12 +365,14 @@ def grading_scheme_update_view(request, course_pk):
for assignment in assignments_to_update:
# check if the assignment has graded submissions
# if it does, do not update the grading scheme
if assignment.get_all_submissions().filter(graded_by__isnull=False).count() == 0:
num_subs_with_grader = assignment.get_all_submissions().filter(graded_by__isnull=False).count()
num_subs_with_question_grades = assignment.get_all_submissions().filter(question_grades__isnull=False).count()
if num_subs_with_grader == 0 and num_subs_with_question_grades == 0:
print(f"Updating to grading scheme {max_grades} for assignment {assignment.name}")
assignment.max_question_scores = max_grades
assignment.save()
else:
print("Assignment has graded submissions. Not updating grading scheme.")
print(f"Assignment {assignment.name} has (partially or fully) graded submissions. Not updating grading scheme.")
status = 'warning'

response = {
Expand Down
12 changes: 7 additions & 5 deletions submissions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,20 @@ def clean(self, *args, **kwargs):
q_grades = self.get_question_grades()
for g in q_grades:
if g == "":
raise ValidationError("Question grades must be non-empty.")
pass
# raise ValidationError("Question grades must be non-empty.")
else:
try:
g = float(g)
except ValueError:
raise ValidationError("Question grades must be floats.")
q_grades = [float(g) for g in q_grades]
raise ValidationError("Question grades must be floats or empty strings.")

q_grades = [g if g == "" else float(g) for g in q_grades]

q_max_grades = self.assignment.get_max_question_scores()
q_max_grades = [float(g) for g in q_max_grades]
for q_grade, q_max_grade in zip(q_grades, q_max_grades):
if q_grade < 0 or q_grade > q_max_grade:
if (q_grade != "") and (q_grade < 0 or q_grade > q_max_grade):
raise ValidationError("Question grades must be between 0 and the maximum question grade.")
if len(q_grades) != self.assignment.number_of_questions:
raise ValidationError("Question grades must be the same length as the number of questions in the assignment.")
Expand All @@ -182,7 +184,7 @@ def short_name(self):

def save(self, *args, **kwargs):
s_grades = self.get_question_grades()
if not all(g is None for g in s_grades):
if s_grades and all(g not in [None, ""] for g in s_grades):
self.grade = sum(map(float, s_grades))
else:
self.grade = None
Expand Down
Loading

0 comments on commit 4479c6a

Please sign in to comment.