Skip to content
Merged
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
2 changes: 1 addition & 1 deletion logify-backend/apps/accounts/test_user_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_academic_supervisor_can_view_assigned_student(self):
address="123 Main St",
industry="Tech",
city="Kampala",
contact_phone="123456789"
contact_phone="123456789",
)
InternshipPlacements.objects.create(
intern=self.student,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from django.db import migrations


def create_global_rubric(apps, schema_editor):
EvaluationRubrics = apps.get_model('evaluations', 'EvaluationRubrics')
EvaluationCriteria = apps.get_model('evaluations', 'EvaluationCriteria')
EvaluationRubrics = apps.get_model("evaluations", "EvaluationRubrics")
EvaluationCriteria = apps.get_model("evaluations", "EvaluationCriteria")

# Create global rubric
rubric, created = EvaluationRubrics.objects.get_or_create(
Expand All @@ -16,7 +17,7 @@ def create_global_rubric(apps, schema_editor):
"version": "1.0",
"is_current": True,
"is_active": True,
}
},
)

# Create default criteria
Expand Down Expand Up @@ -56,15 +57,15 @@ def create_global_rubric(apps, schema_editor):
]

for c in criteria:
EvaluationCriteria.objects.get_or_create(
rubric=rubric,
name=c["name"],
defaults=c
)
EvaluationCriteria.objects.get_or_create(rubric=rubric, name=c["name"], defaults=c)


def remove_global_rubric(apps, schema_editor):
EvaluationRubrics = apps.get_model('evaluations', 'EvaluationRubrics')
EvaluationRubrics.objects.filter(institution=None, programme=None, name="Default Final Evaluation Rubric").delete()
EvaluationRubrics = apps.get_model("evaluations", "EvaluationRubrics")
EvaluationRubrics.objects.filter(
institution=None, programme=None, name="Default Final Evaluation Rubric"
).delete()


class Migration(migrations.Migration):

Expand Down
232 changes: 232 additions & 0 deletions logify-backend/apps/evaluations/test_evaluation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import date

from apps.academics.models import Colleges, Departments, Institutions, Programmes
from apps.accounts.models import StaffProfiles
from apps.evaluations.models import (
EvaluationCriteria,
EvaluationRubrics,
Expand Down Expand Up @@ -252,6 +253,237 @@ def test_create_evaluation_uses_authenticated_user_as_evaluator(self):
self.assertEqual(response.data["evaluator_type"], self.user.role) # type: ignore


class TestEvaluationCollegeScope(APITestCase):
def setUp(self):
User = get_user_model()
self.institution = Institutions.objects.create(
name="Scoped University", email_domain="scoped.edu"
)
self.college_a = Colleges.objects.create(institution=self.institution, name="Engineering")
self.college_b = Colleges.objects.create(institution=self.institution, name="Business")
self.department_a = Departments.objects.create(
college=self.college_a, name="Computer Science"
)
self.department_b = Departments.objects.create(college=self.college_b, name="Accounting")
self.programme_a = Programmes.objects.create(
department=self.department_a,
name="Software Engineering",
level="BSc",
duration_years=4,
)
self.programme_b = Programmes.objects.create(
department=self.department_b,
name="Finance",
level="BSc",
duration_years=3,
)
self.organization = Organizations.objects.create(
name="Scoped Org",
industry="Tech",
city="Test City",
address="123 Test St",
contact_email="org@example.com",
contact_phone="1234567890",
)
self.admin_a = User.objects.create_user(
email="admin.a@scoped.edu",
password="testpassword",
first_name="Admin",
last_name="A",
role="internship_admin",
institution_id=str(self.institution.id),
)
self.admin_b = User.objects.create_user(
email="admin.b@scoped.edu",
password="testpassword",
first_name="Admin",
last_name="B",
role="internship_admin",
institution_id=str(self.institution.id),
)
StaffProfiles.objects.create(
user=self.admin_a,
staff_number="ADM-A",
department=self.department_a,
title="College Admin",
)
StaffProfiles.objects.create(
user=self.admin_b,
staff_number="ADM-B",
department=self.department_b,
title="College Admin",
)
self.student_a = User.objects.create_user(
email="student.a@scoped.edu",
password="testpassword",
first_name="Student",
last_name="A",
role="student",
institution_id=str(self.institution.id),
programme_id=str(self.programme_a.id),
)
self.student_b = User.objects.create_user(
email="student.b@scoped.edu",
password="testpassword",
first_name="Student",
last_name="B",
role="student",
institution_id=str(self.institution.id),
programme_id=str(self.programme_b.id),
)
self.supervisor = User.objects.create_user(
email="supervisor@scoped.edu",
password="testpassword",
first_name="Academic",
last_name="Supervisor",
role="academic_supervisor",
institution_id=str(self.institution.id),
)
self.rubric_a = EvaluationRubrics.objects.create(
institution=self.institution,
programme=self.programme_a,
name="Engineering Rubric",
is_current=True,
)
self.rubric_b = EvaluationRubrics.objects.create(
institution=self.institution,
programme=self.programme_b,
name="Business Rubric",
is_current=True,
)
self.criteria_a = EvaluationCriteria.objects.create(
rubric=self.rubric_a,
name="Engineering Criteria",
description="Engineering criteria",
max_score=10,
weight_percent=100.0,
evaluator_type="academic_supervisor",
)
self.criteria_b = EvaluationCriteria.objects.create(
rubric=self.rubric_b,
name="Business Criteria",
description="Business criteria",
max_score=10,
weight_percent=100.0,
evaluator_type="academic_supervisor",
)
self.placement_a = InternshipPlacements.objects.create(
intern=self.student_a,
institution=self.institution,
programme=self.programme_a,
organization=self.organization,
academic_supervisor=self.supervisor,
start_date=date(2024, 1, 1),
end_date=date(2024, 1, 14),
work_mode="On-site",
internship_title="Engineering Intern",
department_at_company="IT",
status="active",
)
self.placement_b = InternshipPlacements.objects.create(
intern=self.student_b,
institution=self.institution,
programme=self.programme_b,
organization=self.organization,
academic_supervisor=self.supervisor,
start_date=date(2024, 1, 1),
end_date=date(2024, 1, 14),
work_mode="On-site",
internship_title="Business Intern",
department_at_company="Finance",
status="active",
)
self.evaluation_a = Evaluations.objects.create(
placement=self.placement_a,
rubric=self.rubric_a,
evaluator=self.supervisor,
evaluator_type="academic_supervisor",
status="reviewed",
total_score=80.0,
)
self.evaluation_b = Evaluations.objects.create(
placement=self.placement_b,
rubric=self.rubric_b,
evaluator=self.supervisor,
evaluator_type="academic_supervisor",
status="reviewed",
total_score=90.0,
)
self.score_a = EvaluationScores.objects.create(
evaluation=self.evaluation_a,
criterion=self.criteria_a,
score=8,
)
self.score_b = EvaluationScores.objects.create(
evaluation=self.evaluation_b,
criterion=self.criteria_b,
score=9,
)
self.result_a = FinalResults.objects.create(
placement=self.placement_a,
rubric=self.rubric_a,
logbook_score=70.0,
academic_score=80.0,
final_score=77.0,
final_grade="B",
)
self.result_b = FinalResults.objects.create(
placement=self.placement_b,
rubric=self.rubric_b,
logbook_score=80.0,
academic_score=90.0,
final_score=87.0,
final_grade="A",
)

def test_internship_admin_lists_only_evaluations_in_own_college(self):
self.client.force_authenticate(user=self.admin_a)

response = self.client.get("/api/v1/evaluations/evaluations/")

self.assertEqual(response.status_code, 200)
self.assertEqual([item["id"] for item in response.data], [self.evaluation_a.id]) # type: ignore

def test_internship_admin_cannot_retrieve_evaluation_from_other_college(self):
self.client.force_authenticate(user=self.admin_a)

response = self.client.get(f"/api/v1/evaluations/evaluations/{self.evaluation_b.id}/")

self.assertEqual(response.status_code, 404)

def test_internship_admin_lists_only_scores_in_own_college(self):
self.client.force_authenticate(user=self.admin_a)

response = self.client.get("/api/v1/evaluations/scores/")

self.assertEqual(response.status_code, 200)
self.assertEqual([item["id"] for item in response.data], [self.score_a.id]) # type: ignore

def test_internship_admin_cannot_filter_scores_from_other_college(self):
self.client.force_authenticate(user=self.admin_a)

response = self.client.get(f"/api/v1/evaluations/scores/?evaluation={self.evaluation_b.id}")

self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, []) # type: ignore

def test_internship_admin_lists_only_final_results_in_own_college(self):
self.client.force_authenticate(user=self.admin_a)

response = self.client.get("/api/v1/evaluations/results/")

self.assertEqual(response.status_code, 200)
self.assertEqual([item["id"] for item in response.data], [self.result_a.id]) # type: ignore

def test_other_college_admin_sees_their_own_college_evaluations(self):
self.client.force_authenticate(user=self.admin_b)

response = self.client.get("/api/v1/evaluations/evaluations/")

self.assertEqual(response.status_code, 200)
self.assertEqual([item["id"] for item in response.data], [self.evaluation_b.id]) # type: ignore


class TestEvaluationCriteriaViewSet(APITestCase):
def setUp(self):
User = get_user_model()
Expand Down
47 changes: 43 additions & 4 deletions logify-backend/apps/evaluations/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from apps.accounts.access import (
get_programme_ids_for_college,
get_user_college_id,
get_user_institution_id,
)
from apps.accounts.models import User
from apps.accounts.permissions import (
IsAcademicSupervisor,
Expand Down Expand Up @@ -51,6 +56,22 @@ def has_permission(self, request, view):
) or IsInternshipAdmin().has_permission(request, view)


def get_admin_college_placement_filter(user):
institution_id = get_user_institution_id(user)
admin_college_id = get_user_college_id(user)
if institution_id is None or admin_college_id is None:
return None

programme_ids = get_programme_ids_for_college(admin_college_id)
if not programme_ids:
return None

return {
"placement__institution_id": institution_id,
"placement__programme_id__in": programme_ids,
}


class EvaluationRubricsViewSet(viewsets.ModelViewSet):
queryset = EvaluationRubrics.objects.all()
serializer_class = EvaluationRubricsSerializer
Expand Down Expand Up @@ -92,14 +113,19 @@ class EvaluationsViewSet(viewsets.ModelViewSet):
def get_queryset(self):
user = self.request.user
if user.is_authenticated:
if user.is_superuser:
return Evaluations.objects.all()
if user.role == User.STUDENT: # type: ignore
return Evaluations.objects.filter(placement__intern=user)
elif user.role == User.ACADEMIC_SUPERVISOR: # type: ignore
return Evaluations.objects.filter(placement__academic_supervisor=user)
elif user.role == User.WORKPLACE_SUPERVISOR: # type: ignore
return Evaluations.objects.filter(placement__workplace_supervisor=user)
elif user.role == User.INTERNSHIP_ADMIN: # type: ignore
return Evaluations.objects.all()
placement_filter = get_admin_college_placement_filter(user)
if placement_filter is None:
return Evaluations.objects.none()
return Evaluations.objects.filter(**placement_filter)
return Evaluations.objects.none()


Expand All @@ -113,13 +139,21 @@ def get_queryset(self):
queryset = self.queryset
if not user or not user.is_authenticated:
return EvaluationScores.objects.none()
if user.role == User.STUDENT:
if user.is_superuser:
queryset = EvaluationScores.objects.all()
elif user.role == User.STUDENT:
queryset = queryset.filter(evaluation__placement__intern=user)
elif user.role == User.ACADEMIC_SUPERVISOR:
queryset = queryset.filter(evaluation__placement__academic_supervisor=user)
elif user.role == User.WORKPLACE_SUPERVISOR:
queryset = queryset.filter(evaluation__placement__workplace_supervisor=user)
elif user.role != User.INTERNSHIP_ADMIN:
elif user.role == User.INTERNSHIP_ADMIN:
placement_filter = get_admin_college_placement_filter(user)
if placement_filter is None:
return EvaluationScores.objects.none()
score_filter = {f"evaluation__{key}": value for key, value in placement_filter.items()}
queryset = queryset.filter(**score_filter)
else:
return EvaluationScores.objects.none()

evaluation_id = self.request.query_params.get("evaluation")
Expand All @@ -137,6 +171,8 @@ def get_queryset(self):
user = self.request.user
if not user or not user.is_authenticated:
return FinalResults.objects.none()
if user.is_superuser:
return FinalResults.objects.all()
user_role = getattr(user, "role", None)
if user_role == User.STUDENT:
return FinalResults.objects.filter(placement__intern=user)
Expand All @@ -145,5 +181,8 @@ def get_queryset(self):
elif user_role == User.WORKPLACE_SUPERVISOR:
return FinalResults.objects.filter(placement__workplace_supervisor=user)
elif user_role == User.INTERNSHIP_ADMIN:
return FinalResults.objects.all()
placement_filter = get_admin_college_placement_filter(user)
if placement_filter is None:
return FinalResults.objects.none()
return FinalResults.objects.filter(**placement_filter)
return FinalResults.objects.none()
Loading
Loading