Skip to content

Commit 2860296

Browse files
committed
feat: add CSV export for course student progress
1 parent c94caf8 commit 2860296

3 files changed

Lines changed: 57 additions & 3 deletions

File tree

web/templates/courses/progress_overview.html

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
<span class="text-gray-600">Progress Overview</span>
1616
</nav>
1717
<!-- Header -->
18-
<div class="mb-8">
19-
<h1 class="text-3xl font-bold mb-2">Course Progress Overview</h1>
20-
<p class="text-gray-600 dark:text-gray-400">Monitor student progress and engagement in {{ course.title }}</p>
18+
<div class="mb-8 flex items-center justify-between">
19+
<div>
20+
<h1 class="text-3xl font-bold mb-2">Course Progress Overview</h1>
21+
<p class="text-gray-600 dark:text-gray-400">Monitor student progress and engagement in {{ course.title }}</p>
22+
</div>
23+
<a href="{% url 'export_course_progress_csv' course.slug %}"
24+
class="flex items-center bg-teal-500 hover:bg-teal-600 text-white font-semibold px-4 py-2 rounded-lg transition duration-200">
25+
<i class="fas fa-download mr-2"></i> Export CSV
26+
</a>
2127
</div>
2228
<!-- Stats Overview -->
2329
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">

web/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@
185185
views.course_progress_overview,
186186
name="course_progress_overview",
187187
),
188+
path(
189+
"courses/<slug:slug>/progress/export/",
190+
views.export_course_progress_csv,
191+
name="export_course_progress_csv",
192+
),
188193
path(
189194
"courses/<slug:slug>/materials/upload/",
190195
views.upload_material,

web/views.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import calendar
2+
import csv
23
import html
34
import ipaddress
45
import json
@@ -1904,6 +1905,48 @@ def course_progress_overview(request, slug):
19041905

19051906

19061907
@login_required
1908+
def export_course_progress_csv(request, slug):
1909+
"""Export student progress data for a course as a CSV file."""
1910+
course = get_object_or_404(Course, slug=slug)
1911+
if request.user != course.teacher:
1912+
messages.error(request, "Only the course teacher can export progress data.")
1913+
return redirect("course_detail", slug=slug)
1914+
response = HttpResponse(content_type="text/csv")
1915+
response["Content-Disposition"] = f'attachment; filename="{course.slug}-progress.csv"'
1916+
writer = csv.writer(response)
1917+
writer.writerow([
1918+
"Student Name",
1919+
"Username",
1920+
"Email",
1921+
"Enrollment Date",
1922+
"Completion %",
1923+
"Sessions Attended",
1924+
"Total Sessions",
1925+
"Attendance Rate %",
1926+
"Last Accessed",
1927+
])
1928+
enrollments = course.enrollments.filter(status="approved").select_related("student")
1929+
total_sessions = course.sessions.count()
1930+
for enrollment in enrollments:
1931+
progress, _ = CourseProgress.objects.get_or_create(enrollment=enrollment)
1932+
attended = SessionAttendance.objects.filter(
1933+
student=enrollment.student,
1934+
session__course=course,
1935+
status__in=["present", "late"],
1936+
).count()
1937+
writer.writerow([
1938+
enrollment.student.get_full_name() or enrollment.student.username,
1939+
enrollment.student.username,
1940+
enrollment.student.email,
1941+
enrollment.enrollment_date.strftime("%Y-%m-%d"),
1942+
progress.completion_percentage,
1943+
attended,
1944+
total_sessions,
1945+
progress.attendance_rate,
1946+
progress.last_accessed.strftime("%Y-%m-%d") if progress.last_accessed else "",
1947+
])
1948+
return response
1949+
19071950
def upload_material(request, slug):
19081951
course = get_object_or_404(Course, slug=slug)
19091952
if request.user != course.teacher:

0 commit comments

Comments
 (0)