Skip to content

Commit 6e9c011

Browse files
first draft of adding graduates data endpoint (#80)
* first draft of adding graduates data endpoint * worked on first batch of review comments * minor fixes * fixed coureses variable fetching logic and minor fixes * removed unused import * WIP: First draft of graduates data testcase * fixed typo * Add graduate data view test --------- Co-authored-by: Alexey Grigorev <alexey.s.grigoriev@gmail.com>
1 parent 2d7c139 commit 6e9c011

4 files changed

Lines changed: 138 additions & 1 deletion

File tree

courses/models/course.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ class Course(models.Model):
4242
help_text="The URL of the FAQ document for the course.",
4343
)
4444

45+
min_projects_to_pass = models.IntegerField(
46+
default=1,
47+
blank=False,
48+
help_text="The minimum number of projects to pass the course.",
49+
)
50+
4551
homework_problems_comments_field = models.BooleanField(
4652
default=False,
4753
help_text="Include field for problems and comments in homework",

courses/tests/test_data.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,4 +377,79 @@ def test_project_submission_with_404_url(self):
377377
commit_id="abcd1234",
378378
)
379379
with self.assertRaises(ValidationError):
380-
self.project_submission.full_clean()
380+
self.project_submission.full_clean()
381+
382+
def test_graduate_data_view(self):
383+
"""Test that only students who passed enough projects are returned."""
384+
385+
self.course.min_projects_to_pass = 2
386+
self.course.save()
387+
388+
self.user.email = "student1@example.com"
389+
self.user.save()
390+
self.enrollment.certificate_name = "Student One"
391+
self.enrollment.save()
392+
393+
other_user = CustomUser.objects.create(
394+
username="student2", email="student2@example.com", password="pass"
395+
)
396+
other_enrollment = Enrollment.objects.create(
397+
student=other_user,
398+
course=self.course,
399+
certificate_name="Student Two",
400+
)
401+
402+
project1 = Project.objects.create(
403+
course=self.course,
404+
slug="project1",
405+
title="Project 1",
406+
description="Description",
407+
submission_due_date=timezone.now() + timezone.timedelta(days=7),
408+
peer_review_due_date=timezone.now() + timezone.timedelta(days=14),
409+
)
410+
project2 = Project.objects.create(
411+
course=self.course,
412+
slug="project2",
413+
title="Project 2",
414+
description="Description",
415+
submission_due_date=timezone.now() + timezone.timedelta(days=7),
416+
peer_review_due_date=timezone.now() + timezone.timedelta(days=14),
417+
)
418+
419+
ProjectSubmission.objects.create(
420+
project=project1,
421+
student=self.user,
422+
enrollment=self.enrollment,
423+
github_link="https://httpbin.org/status/200",
424+
commit_id="1111",
425+
passed=True,
426+
)
427+
ProjectSubmission.objects.create(
428+
project=project2,
429+
student=self.user,
430+
enrollment=self.enrollment,
431+
github_link="https://httpbin.org/status/200",
432+
commit_id="2222",
433+
passed=True,
434+
)
435+
ProjectSubmission.objects.create(
436+
project=project1,
437+
student=other_user,
438+
enrollment=other_enrollment,
439+
github_link="https://httpbin.org/status/200",
440+
commit_id="3333",
441+
passed=True,
442+
)
443+
444+
url = reverse(
445+
"data_graduates",
446+
kwargs={"course_slug": self.course.slug},
447+
)
448+
response = self.client.get(url)
449+
450+
self.assertEqual(response.status_code, 200)
451+
actual_result = response.json()
452+
453+
self.assertEqual(len(actual_result), 1)
454+
self.assertEqual(actual_result[0]["email"], self.user.email)
455+
self.assertEqual(actual_result[0]["name"], self.enrollment.certificate_name)

courses/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,9 @@
9494
data.project_data_view,
9595
name="data_project",
9696
),
97+
path(
98+
"data/<slug:course_slug>/graduates",
99+
data.graduates_data_view,
100+
name="data_graduates",
101+
),
97102
]

courses/views/data.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.shortcuts import get_object_or_404
44
from django.db.models import Prefetch
55

6+
from collections import Counter
7+
68
from courses.models import (
79
Answer,
810
Course,
@@ -115,3 +117,52 @@ def project_data_view(request, course_slug: str, project_slug: str):
115117
}
116118

117119
return JsonResponse(result)
120+
121+
@token_required
122+
def graduates_data_view(request, course_slug: str):
123+
124+
# Fetch course
125+
try:
126+
course = get_object_or_404(Course, slug=course_slug)
127+
#course = Course.objects.get(id=course_id)
128+
except Course.DoesNotExist:
129+
return JsonResponse({"error": "Course not found"}, status=404)
130+
131+
# Get passed students
132+
submissions = ProjectSubmission.objects \
133+
.filter(project__course=course, passed=True) \
134+
.prefetch_related("enrollment")
135+
136+
# Count projects per student
137+
cnt = Counter()
138+
ids_mapping = {}
139+
140+
for s in submissions:
141+
e = s.enrollment
142+
eid = e.id
143+
cnt[eid] += 1
144+
ids_mapping[eid] = e
145+
146+
passed = []
147+
148+
# Get mimimum number of projects to pass
149+
min_projects = course.min_projects_to_pass
150+
151+
for eid, c in cnt.items():
152+
if c >= min_projects:
153+
passed.append(ids_mapping[eid])
154+
155+
# Prepare results
156+
results = []
157+
for enrollment in passed:
158+
student = enrollment.student
159+
email = student.email
160+
name = enrollment.certificate_name or enrollment.display_name
161+
162+
results.append({
163+
"email": email,
164+
"name": name,
165+
})
166+
167+
168+
return JsonResponse(results, safe=False)

0 commit comments

Comments
 (0)