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
63 changes: 62 additions & 1 deletion controllers/endpoints/assignment_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from common.maybe import maybe_bool
from controllers.auth import get_user
from controllers.helpers import (require_request_parameters, require_course_instructor, login_required,
require_course_adopter,
require_course_adopter, ajax_failure,
check_resource_exists, get_select_menu_link, get_course_id, ajax_success,
maybe_int, require_course_grader, make_log_entry)
from models import Course, Submission, AssignmentLog
Expand All @@ -16,6 +16,8 @@
from models.assignment_group import AssignmentGroup
from models.assignment_group_membership import AssignmentGroupMembership
from models.data_formats.portation import export_bundle, import_bundle, export_zip, export_pdf_zip
from models.log_tables import SubmissionLog as Log
from models.user import User

blueprint_assignment_group = Blueprint('assignment_group', __name__, url_prefix='/assignment_group')

Expand Down Expand Up @@ -377,3 +379,62 @@ def export_submissions():
filename = course.get_url_or_id() + '-' + assignment_group.get_filename(extension='.zip')
return Response(bundle, mimetype='application/zip',
headers={'Content-Disposition': 'attachment;filename={}'.format(filename)})


@blueprint_assignment_group.route('/browse_history/', methods=['GET', 'POST'])
@blueprint_assignment_group.route('/browse_history', methods=['GET', 'POST'])
@require_request_parameters('assignment_group_id', 'course_id')
def browse_history():
"""
View the history of all submissions in a single assignment group.
Returns a page with the watcher component to view submission histories.
"""
# Get parameters
assignment_group_id = maybe_int(request.values.get('assignment_group_id'))
course_id = maybe_int(request.values.get('course_id'))
user_id = maybe_int(request.values.get('user_id', None))
embed = maybe_bool(request.values.get('embed'))
user, viewer_id = get_user()

# Get resources
assignment_group = AssignmentGroup.by_id(assignment_group_id)
check_resource_exists(assignment_group, "AssignmentGroup", assignment_group_id)
course = Course.by_id(course_id)
check_resource_exists(course, "Course", course_id)

# Verify permissions - only graders can view history for an assignment group
if not user.is_grader(course_id):
return ajax_failure("Only graders can see logs for assignment groups.")

# Get all assignments in this group
assignments = assignment_group.get_assignments()
# Extract assignment IDs once for efficiency
assignment_id_list = [a.id for a in assignments] if assignments else []
# Format as comma-separated string (expected by watcher component)
assignment_ids = ','.join(str(aid) for aid in assignment_id_list)

# Get all users who have submissions in this course for these assignments
if user_id is None:
# Get all users with submissions in these assignments
if assignment_id_list:
user_ids_list = (Log.query
.filter(Log.course_id == course_id)
.filter(Log.assignment_id.in_(assignment_id_list))
.with_entities(Log.subject_id)
.distinct()
.all())
# Format as comma-separated string (expected by watcher component)
user_ids = ','.join(str(uid[0]) for uid in user_ids_list) if user_ids_list else ''
else:
user_ids = ''
else:
user_ids = str(user_id)

return render_template('assignment_groups/browse_history.html',
assignment_group=assignment_group,
course=course,
course_id=course_id,
assignment_ids=assignment_ids,
user_ids=user_ids,
user=user,
embed=embed)
49 changes: 49 additions & 0 deletions templates/assignment_groups/browse_history.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% set reduced_layout = embed or 'iframe' == request.form.get('launch_presentation_document_target') %}
{% extends 'helpers/layout.html' %}
{% import 'helpers/navigation.html' as navigation %}

{% block title %}
Browse Assignment Group History
{% endblock %}

{% block statusbar %}
{% endblock %}

{% block extrahead %}
<script type='text/javascript'>
$(function() {
let loggedInUser = {{ user.encode_json()|tojson }};
let server = new frontend.Server({{ course_id|tojson }}, {}, {
users: [loggedInUser]
});

// WatchMode enum values
const WatchMode = {
SUMMARY: 0,
FULL: 1
};

let mainModel = {
server: server,
courseId: {{ course_id|tojson }},
assignmentIds: {{ assignment_ids|tojson }},
userIds: {{ user_ids|tojson }},
defaultWatchMode: WatchMode.SUMMARY
};

ko.applyBindings(mainModel);
});
</script>
{% endblock %}

{% block body %}

<h1>Browse Assignment Group History</h1>
{{ navigation.navigate_course(course_id) }}

<p>Assignment Group: {{ assignment_group.name }}</p>
<p>Course: {{ course.name }}</p>

<watcher params="server: server, courseId: courseId, assignmentIds: assignmentIds, userIds: userIds, defaultWatchMode: defaultWatchMode"></watcher>

{% endblock %}
9 changes: 9 additions & 0 deletions templates/reports/group.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ <h1 id="overview">Overview</h1>

<assignment-group params="group: group"></assignment-group>

{% if is_grader %}
<p>
<a href="{{ url_for('assignment_group.browse_history', assignment_group_id=assignment_group.id, course_id=course_id, user_id=user_id) }}"
class="btn btn-sm btn-outline-secondary">
<i class="fas fa-history"></i> Browse All Submission History
</a>
</p>
{% endif %}

<p>
{% if reduced_layout %}
Below, you can see your current submission for this assignment.<br>
Expand Down