Skip to content

Commit cd54037

Browse files
authored
Merge pull request #135 from AvaCodeSolutions/feat/128/single-learner-api-view
feat: #128 single learner API view
2 parents c253c9d + 74651e0 commit cd54037

4 files changed

Lines changed: 80 additions & 0 deletions

File tree

django_email_learning/platform/api/serializers.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
CourseContent,
1818
Course,
1919
QuizSelectionStrategy,
20+
EnrollmentStatus,
2021
)
2122
import enum
2223

@@ -314,11 +315,25 @@ def from_seconds(cls, seconds: int) -> "WaitingPeriod":
314315
)
315316

316317

318+
class EnrollmentSummaryResponse(BaseModel):
319+
id: int
320+
course_title: str
321+
status: EnrollmentStatus
322+
323+
317324
class LearnerResponse(BaseModel):
318325
id: int
319326
email: str
320327

321328

329+
class LearnerDetailResponse(BaseModel):
330+
id: int
331+
email: str
332+
enrollments: list[EnrollmentSummaryResponse]
333+
334+
model_config = ConfigDict(from_attributes=True)
335+
336+
322337
class CreateCourseContentRequest(BaseModel):
323338
priority: int | None = Field(gt=0, examples=[1], default=None)
324339
waiting_period: WaitingPeriod

django_email_learning/platform/api/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
SingleCourseContentView,
1111
UpdateSessionView,
1212
LearnersView,
13+
SingleLearnerView,
1314
)
1415

1516
app_name = "django_email_learning"
@@ -50,6 +51,11 @@
5051
LearnersView.as_view(),
5152
name="learners_view",
5253
),
54+
path(
55+
"organizations/<int:organization_id>/learners/<int:learner_id>/",
56+
SingleLearnerView.as_view(),
57+
name="single_learner_view",
58+
),
5359
path("organizations/", OrganizationsView.as_view(), name="organizations_view"),
5460
path("session", UpdateSessionView.as_view(), name="update_session_view"),
5561
path("", page_not_found, name="root"),

django_email_learning/platform/api/views.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
is_platform_admin,
2626
)
2727
import json
28+
import logging
29+
30+
31+
logger = logging.getLogger(__name__)
2832

2933

3034
@method_decorator(ensure_csrf_cookie, name="get")
@@ -462,6 +466,34 @@ def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-unty
462466
)
463467

464468

469+
@method_decorator(accessible_for(roles={"admin", "editor", "viewer"}), name="get")
470+
class SingleLearnerView(View):
471+
def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-untyped-def]
472+
try:
473+
learner = Learner.objects.get(id=kwargs["learner_id"])
474+
enrollments = Enrollment.objects.filter(learner=learner)
475+
enroolments_list = []
476+
for enrollment in enrollments:
477+
enroolments_list.append(
478+
serializers.EnrollmentSummaryResponse(
479+
id=enrollment.id,
480+
course_title=enrollment.course.title,
481+
status=EnrollmentStatus(enrollment.status),
482+
)
483+
)
484+
return JsonResponse(
485+
serializers.LearnerDetailResponse(
486+
id=learner.id, email=learner.email, enrollments=enroolments_list
487+
).model_dump(),
488+
status=200,
489+
)
490+
except Learner.DoesNotExist:
491+
return JsonResponse({"error": "Learner not found"}, status=404)
492+
except ValidationError as e:
493+
logger.error(f"Error in SingleLearnerView: {e.json()}")
494+
return JsonResponse({"error": "An internal error occurred."}, status=500)
495+
496+
465497
class RootView(View):
466498
def get(self, request, *args, **kwargs) -> JsonResponse: # type: ignore[no-untyped-def]
467499
return JsonResponse({"message": "Email Learning API is running."}, status=200)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from django.urls import reverse
2+
3+
4+
def test_single_llearner_viewe(viewer_client, enrollment):
5+
url = reverse(
6+
"django_email_learning:api_platform:single_learner_view",
7+
kwargs={"organization_id": 1, "learner_id": enrollment.learner.id},
8+
)
9+
response = viewer_client.get(url)
10+
assert response.status_code == 200
11+
data = response.json()
12+
assert data["id"] == enrollment.learner.id
13+
assert data["email"] == enrollment.learner.email
14+
assert len(data["enrollments"]) == 1
15+
enrollment_data = data["enrollments"][0]
16+
assert enrollment_data["id"] == enrollment.id
17+
assert enrollment_data["course_title"] == enrollment.course.title
18+
assert enrollment_data["status"] == enrollment.status.value
19+
20+
21+
def test_single_learner_view_not_accessible_for_no_role(anonymous_client, enrollment):
22+
url = reverse(
23+
"django_email_learning:api_platform:single_learner_view",
24+
kwargs={"organization_id": 1, "learner_id": enrollment.learner.id},
25+
)
26+
response = anonymous_client.get(url)
27+
assert response.status_code == 401

0 commit comments

Comments
 (0)