Skip to content

Commit 428efc2

Browse files
committed
WIP
1 parent 6512b75 commit 428efc2

4 files changed

Lines changed: 253 additions & 0 deletions

File tree

django_email_learning/platform/api/serializers.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,3 +1195,27 @@ def serialize_waiting_period(self, waiting_period: int) -> dict:
11951195

11961196
class ReorderCourseContentsRequest(BaseModel):
11971197
ordered_content_ids: list[int] = Field(min_length=2, examples=[[3, 1, 2]])
1198+
1199+
1200+
class AssignmentSubmissionSummaryResponse(BaseModel):
1201+
id: int
1202+
assignment_title: str
1203+
submitted_at: datetime
1204+
status: AssignmentSubmission.SubmissionStatus
1205+
reviewed_at: Optional[datetime] = None
1206+
reviewed_by: Optional[str] = None
1207+
1208+
@staticmethod
1209+
def from_django_model(
1210+
submission: AssignmentSubmission,
1211+
) -> "AssignmentSubmissionSummaryResponse":
1212+
return AssignmentSubmissionSummaryResponse(
1213+
id=submission.id,
1214+
assignment_title=submission.delivery.course_content.assignment.title, # type: ignore[union-attr]
1215+
submitted_at=submission.submitted_at,
1216+
status=AssignmentSubmission.SubmissionStatus(submission.status),
1217+
reviewed_at=submission.reviewed_at,
1218+
reviewed_by=submission.reviewer.display_name
1219+
if submission.reviewer
1220+
else None, # type: ignore[union-attr]
1221+
)

django_email_learning/platform/api/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
CourseContentView,
2020
ReorderCourseContentView,
2121
SingleCourseContentView,
22+
SubmittedAssignmentsView,
2223
UpdateSessionView,
2324
LearnersView,
2425
OauthSessionView,
@@ -127,6 +128,11 @@
127128
OauthGroupEnrollment.as_view(),
128129
name="oauth_group_enrollment_view",
129130
),
131+
path(
132+
"organizations/<int:organization_id>/courses/<int:course_id>/submitted_assignments/",
133+
SubmittedAssignmentsView.as_view(),
134+
name="submitted_assignments_view",
135+
),
130136
path("status/jobs/", JobsStatus.as_view(), name="jobs_status_view"),
131137
path("api_keys/", ApiKeyView.as_view(), name="api_key_view"),
132138
path(

django_email_learning/platform/api/views.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from django_email_learning.platform.api.pagniated_api_mixin import PaginatedApiMixin
3636
from django_email_learning.models import (
3737
ApiKey,
38+
AssignmentSubmission,
3839
Certificate,
3940
Course,
4041
CourseContent,
@@ -657,6 +658,32 @@ def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unty
657658
return JsonResponse({"organization_users": response_list}, status=200)
658659

659660

661+
@method_decorator(accessible_for(roles={"admin", "instructor"}), name="get")
662+
class SubmittedAssignmentsView(View):
663+
def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-untyped-def]
664+
submissions = AssignmentSubmission.objects.filter(
665+
delivery__course_content__course_id=kwargs["course_id"]
666+
).select_related(
667+
"delivery__course_content__assignment", "delivery__enrollment__learner"
668+
)
669+
if "status" in request.GET:
670+
status = request.GET["status"]
671+
if status in AssignmentSubmission.SubmissionStatus.values:
672+
submissions = submissions.filter(status=status)
673+
if "learner_id" in request.GET:
674+
submissions = submissions.filter(
675+
delivery__enrollment__learner_id=request.GET["learner_id"]
676+
)
677+
response_list = []
678+
for submission in submissions:
679+
response_list.append(
680+
serializers.AssignmentSubmissionSummaryResponse.from_django_model(
681+
submission
682+
).model_dump()
683+
)
684+
return JsonResponse({"submissions": response_list}, status=200)
685+
686+
660687
@method_decorator(accessible_for(roles={"admin"}), name="delete")
661688
class SingleOrganizationUserView(View):
662689
def delete(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
from django.urls import reverse
2+
from django_email_learning.models import (
3+
AssignmentSubmission,
4+
ContentDelivery,
5+
Enrollment,
6+
Learner,
7+
)
8+
import pytest
9+
import uuid
10+
11+
12+
def get_url(organization_id: int, course_id: int) -> str:
13+
return reverse(
14+
"django_email_learning:api_platform:submitted_assignments_view",
15+
kwargs={"organization_id": organization_id, "course_id": course_id},
16+
)
17+
18+
19+
@pytest.mark.parametrize(
20+
"client,expected_status",
21+
[
22+
("org_admin", 200),
23+
("instructor", 200),
24+
("editor", 403),
25+
("viewer", 403),
26+
("anonymous", 401),
27+
],
28+
indirect=["client"],
29+
)
30+
def test_submitted_assignments_view_get_role_access(
31+
client, expected_status, course_assignment_content, enrollment
32+
):
33+
delivery = ContentDelivery.objects.create(
34+
enrollment=enrollment,
35+
course_content=course_assignment_content,
36+
)
37+
AssignmentSubmission.objects.create(
38+
delivery=delivery,
39+
text_submission="My answer",
40+
)
41+
42+
response = client.get(
43+
get_url(
44+
organization_id=course_assignment_content.course.organization_id,
45+
course_id=course_assignment_content.course_id,
46+
)
47+
)
48+
49+
assert response.status_code == expected_status
50+
51+
52+
def test_submitted_assignments_view_returns_expected_list_fields(
53+
org_admin_client, course_assignment_content, enrollment
54+
):
55+
delivery = ContentDelivery.objects.create(
56+
enrollment=enrollment,
57+
course_content=course_assignment_content,
58+
)
59+
submission = AssignmentSubmission.objects.create(
60+
delivery=delivery,
61+
text_submission="My answer",
62+
)
63+
64+
response = org_admin_client.get(
65+
get_url(
66+
organization_id=course_assignment_content.course.organization_id,
67+
course_id=course_assignment_content.course_id,
68+
)
69+
)
70+
71+
assert response.status_code == 200
72+
payload = response.json()
73+
assert "submissions" in payload
74+
assert len(payload["submissions"]) == 1
75+
76+
item = payload["submissions"][0]
77+
assert set(item.keys()) == {
78+
"id",
79+
"assignment_title",
80+
"submitted_at",
81+
"status",
82+
"reviewed_at",
83+
"reviewed_by",
84+
}
85+
assert item["id"] == submission.id
86+
assert item["assignment_title"] == course_assignment_content.assignment.title
87+
assert item["status"] == AssignmentSubmission.SubmissionStatus.PENDING_REVIEW
88+
assert item["reviewed_at"] is None
89+
assert item["reviewed_by"] is None
90+
91+
92+
def test_submitted_assignments_view_filters_by_status(
93+
org_admin_client, course_assignment_content, enrollment
94+
):
95+
delivery_approved = ContentDelivery.objects.create(
96+
enrollment=enrollment,
97+
course_content=course_assignment_content,
98+
)
99+
approved_submission = AssignmentSubmission.objects.create(
100+
delivery=delivery_approved,
101+
text_submission="Approved answer",
102+
status=AssignmentSubmission.SubmissionStatus.APPROVED,
103+
)
104+
105+
second_learner = Learner.objects.create(
106+
email=f"{uuid.uuid4().hex}@example.com",
107+
organization_id=course_assignment_content.course.organization_id,
108+
)
109+
second_enrollment = Enrollment.objects.create(
110+
learner=second_learner,
111+
course=course_assignment_content.course,
112+
)
113+
delivery_pending = ContentDelivery.objects.create(
114+
enrollment=second_enrollment,
115+
course_content=course_assignment_content,
116+
)
117+
AssignmentSubmission.objects.create(
118+
delivery=delivery_pending,
119+
text_submission="Pending answer",
120+
status=AssignmentSubmission.SubmissionStatus.PENDING_REVIEW,
121+
)
122+
123+
response_without_status = org_admin_client.get(
124+
get_url(
125+
organization_id=course_assignment_content.course.organization_id,
126+
course_id=course_assignment_content.course_id,
127+
)
128+
)
129+
assert response_without_status.status_code == 200
130+
assert len(response_without_status.json()["submissions"]) == 2
131+
132+
response = org_admin_client.get(
133+
get_url(
134+
organization_id=course_assignment_content.course.organization_id,
135+
course_id=course_assignment_content.course_id,
136+
),
137+
{"status": AssignmentSubmission.SubmissionStatus.APPROVED},
138+
)
139+
140+
assert response.status_code == 200
141+
submissions = response.json()["submissions"]
142+
assert len(submissions) == 1
143+
assert submissions[0]["id"] == approved_submission.id
144+
assert submissions[0]["status"] == AssignmentSubmission.SubmissionStatus.APPROVED
145+
146+
147+
def test_submitted_assignments_view_filters_by_learner_id(
148+
org_admin_client, course_assignment_content, enrollment
149+
):
150+
delivery_first = ContentDelivery.objects.create(
151+
enrollment=enrollment,
152+
course_content=course_assignment_content,
153+
)
154+
first_submission = AssignmentSubmission.objects.create(
155+
delivery=delivery_first,
156+
text_submission="First learner answer",
157+
)
158+
159+
second_learner = Learner.objects.create(
160+
email=f"{uuid.uuid4().hex}@example.com",
161+
organization_id=course_assignment_content.course.organization_id,
162+
)
163+
second_enrollment = Enrollment.objects.create(
164+
learner=second_learner,
165+
course=course_assignment_content.course,
166+
)
167+
delivery_second = ContentDelivery.objects.create(
168+
enrollment=second_enrollment,
169+
course_content=course_assignment_content,
170+
)
171+
AssignmentSubmission.objects.create(
172+
delivery=delivery_second,
173+
text_submission="Second learner answer",
174+
)
175+
176+
response_without_learner_id = org_admin_client.get(
177+
get_url(
178+
organization_id=course_assignment_content.course.organization_id,
179+
course_id=course_assignment_content.course_id,
180+
)
181+
)
182+
assert response_without_learner_id.status_code == 200
183+
assert len(response_without_learner_id.json()["submissions"]) == 2
184+
185+
response = org_admin_client.get(
186+
get_url(
187+
organization_id=course_assignment_content.course.organization_id,
188+
course_id=course_assignment_content.course_id,
189+
),
190+
{"learner_id": enrollment.learner.id},
191+
)
192+
193+
assert response.status_code == 200
194+
submissions = response.json()["submissions"]
195+
assert len(submissions) == 1
196+
assert submissions[0]["id"] == first_submission.id

0 commit comments

Comments
 (0)