diff --git a/generic/templates/generic/trainees.html b/generic/templates/generic/trainees.html
new file mode 100644
index 00000000..714e5929
--- /dev/null
+++ b/generic/templates/generic/trainees.html
@@ -0,0 +1,25 @@
+{% extends 'generic/base.html' %}
+
+{% load crispy_forms_tags %}
+{% block css %}
+{% endblock %}
+
+{% block content %}
+
+
+{% if grade %}
+{{grade}}
+{% endif %}Trainees
+
+
+
+
+{% for trainee in trainees %}
+| {{trainee.user.first_name}} {{trainee.user.last_name}} | {{trainee.grade}} | {{trainee.user.email}} | {{trainee.supervisor}} |
+{% endfor %}
+
+
+
+
+
+{% endblock %}
diff --git a/generic/tests/test_trainees.py b/generic/tests/test_trainees.py
new file mode 100644
index 00000000..cf22c0f9
--- /dev/null
+++ b/generic/tests/test_trainees.py
@@ -0,0 +1,68 @@
+from bs4 import BeautifulSoup
+from django.urls import reverse
+from generic.models import UserProfile, UserGrades
+from django.contrib.auth.models import Group
+
+import pytest
+
+
+@pytest.fixture
+def create_cid_manager(db, django_user_model):
+ user = django_user_model.objects.create_user(
+ "cidmanager", "test@test.com", "password"
+ )
+ g = Group.objects.create(name="cid_user_manager")
+ g.save()
+ user.groups.add(g)
+ return
+
+
+@pytest.fixture
+def create_user_grades(db):
+ for i in range(1, 6):
+ UserGrades.objects.create(name=f"ST{i}")
+
+
+@pytest.fixture
+def create_users(db, django_user_model, create_user_grades):
+ users = [
+ ("user1", "ST1", "email@email.com"),
+ ("user2", "ST1", "email@email.com"),
+ ("user3", "ST2", "email@email.com"),
+ ("user4", "ST3", "email@email.com"),
+ ]
+
+ for name, grade, email in users:
+ user = django_user_model.objects.create_user(name, email, "password")
+
+ user.userprofile.grade = UserGrades.objects.get(name=grade)
+ user.userprofile.peninsula_trainee = True
+ user.userprofile.save()
+
+
+def test_trainee_stuff(client, create_cid_manager, create_users):
+ print(UserProfile.objects.all().count())
+
+ assert UserProfile.objects.all().count() == 5
+ assert UserProfile.objects.filter(peninsula_trainee=True).count() == 4
+
+ response = client.get(reverse(f"trainees"))
+ assert response.status_code == 403
+
+ client.login(username="user1", password="password")
+
+ response = client.get(reverse(f"trainees"))
+ assert response.status_code == 403
+
+ client.login(username="cidmanager", password="password")
+
+ response = client.get(reverse(f"trainees"))
+ assert response.status_code == 200
+
+ trainee_rows = BeautifulSoup(response.content, "html.parser").find(
+ "tr", {"class": "trainee"}
+ )
+
+ assert (
+ len(trainee_rows) == UserProfile.objects.filter(peninsula_trainee=True).count()
+ )
diff --git a/generic/views.py b/generic/views.py
index 46ac399b..b18a48c7 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -7,6 +7,7 @@ from dal import autocomplete
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.forms.models import model_to_dict
+from django.db.models.functions import Coalesce
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, user_passes_test
@@ -3030,6 +3031,20 @@ def exam_inactive(request, context):
#
# success_url = reverse_lazy("accounts_list")
+@user_is_cid_user_manager
+def trainees(request, grade: None|str=None):
+
+ trainees = UserProfile.objects.filter(peninsula_trainee=True).order_by(Coalesce("grade__name", "user__last_name").asc())
+
+ if grade is not None:
+ trainees = trainees.filter(grade__name=grade)
+
+ context = {
+ "trainees" : trainees,
+ "grade": grade
+ }
+
+ return render(request, "generic/trainees.html", context)
def create_user(request, context=None):
# if this is a POST request we need to process the form data
diff --git a/templates/people.html b/templates/people.html
index c4e34310..288f534c 100644
--- a/templates/people.html
+++ b/templates/people.html
@@ -6,6 +6,13 @@
{% block content %}
+Trainees can be viewed here (
+
+ {% for i in "123456"|make_list %}
+ ST{{i}}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+ )
+
People
The system contains a number of different people / groups. This affects how they are managed and what they are able to do.
They can be managed using the navigation links above