Compare commits

..
15 Commits
Author SHA1 Message Date
Ross 19eb10c089 Add user content management views and templates for content review 2026-02-13 22:26:41 +00:00
Ross 2ab5a8e3b5 Add author parameter to My Questions link in frcr_index.html 2026-02-10 22:19:22 +00:00
Ross c2f12f384a Add questions section with navigation links to frcr_index.html 2026-02-10 22:05:06 +00:00
Ross 33f32dab8b Create frcr_index.html template and update ExamViews to render it 2026-02-10 21:48:11 +00:00
Ross a676d437bb Add badges to FRCR and Atlas sections for better categorization 2026-02-10 21:13:44 +00:00
Ross ba5b11cce2 Update call-to-action section in index.html for improved user engagement 2026-02-10 21:12:49 +00:00
Ross a6c5b99620 Update label for AccordionGroup in AnatomyQuestionForm to improve clarity 2026-02-10 20:53:34 +00:00
Ross 666f49f2c2 Refactor AnatomyQuestionForm layout to arrange modality and related fields in a horizontal flex row 2026-02-10 20:51:49 +00:00
Ross 46608a0758 Update index.html text for clarity in user instructions 2026-02-10 20:48:12 +00:00
Ross cfdf33b9ce Add open access field to AnatomyQuestionForm and update index template layout 2026-02-10 20:42:42 +00:00
Ross bdc97922af Refactor AnatomyQuestionForm layout and update template to use crispy forms 2026-02-10 20:33:18 +00:00
Ross 3626f3918e Enhance image annotation help text and layout in question detail view 2026-02-09 22:50:40 +00:00
Ross 49fbff76e9 Replace FilteredSelectMultiple with custom ExamSearchWidget in AnatomyQuestionForm 2026-02-09 22:39:50 +00:00
Ross 7ec81133d8 Allow any authenticated user to create new Structure entries in autocomplete 2026-02-09 22:33:30 +00:00
Ross c67db4a39e Update hidden input type in open access form to use exam app name 2026-02-09 22:25:43 +00:00
11 changed files with 423 additions and 129 deletions
+35 -2
View File
@@ -34,7 +34,9 @@ from dal import autocomplete
from tinymce.widgets import TinyMCE
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from crispy_forms.layout import Submit, Layout, Div
from crispy_forms.bootstrap import Accordion, AccordionGroup
from generic.widgets import ExamSearchWidget
class AnatomyAnswerForm(ModelForm):
class Meta:
@@ -127,7 +129,38 @@ class AnatomyQuestionForm(ModelForm):
self.fields["exams"] = ModelMultipleChoiceField(
required=False,
queryset=exam_queryset,
widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False),
widget=ExamSearchWidget(exam_model=Exam),
)
# Now that all fields have been created, set the helper layout so
# Crispy can find the referenced field names (including
# `answer_suggest_incorrect`). Placing the layout here ensures the
# collapse/accordion renders server-side.
self.helper.layout = Layout(
"question_type",
"image",
"description",
# Place modality → body_part fields in a horizontal flex row
Div(
Div("modality", css_class="flex-fill me-2"),
Div("region", css_class="flex-fill me-2"),
Div("structure", css_class="flex-fill me-2"),
Div("examination", css_class="flex-fill me-2"),
Div("body_part", css_class="flex-fill"),
css_class="d-flex flex-wrap align-items-start mb-2",
),
"answer_help",
Accordion(
AccordionGroup(
"Suggest incorrect answers",
"answer_suggest_incorrect",
active=False,
)
),
"feedback",
"open_access",
"exams",
Div(Submit("submit", "Submit"), css_class="form-group"),
)
def save(self, commit=True):
@@ -233,6 +233,10 @@
<p>Question type can be selected from a predefined list. If you require a more please contact ross.kruger@nhs.net.</p>
<p>Correct and incorrect answers can be defined once the question has been created.</p>
<p>Images can be selected either directly via the browse button or by dragging and dropping them into the designated area below.</p>
<p>Supported formats include JPEG, PNG, and GIF. It is also possible to use DICOM images.</p>
{% endhelp %}
<form action="" method="post" enctype="multipart/form-data" id="anatomyquestion-form">
{% csrf_token %}
@@ -241,10 +245,9 @@
<a href="/anatomy/body_part/create" id="add_body_part" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static 'img/icon-addlink.svg' %}"></a>
{{ form|crispy }}
{% crispy form %}
<div id="drop-container" class="drop-target">Drop image here
<div id="drop-filenames"></div>
</div>
<input type="submit" class="submit-button" value="Submit" name="submit">
</form>
{% endblock %}
@@ -10,10 +10,10 @@
<div class="card-body">
<div id="anatomy-dicom-image" class="dicom-image-legacy w-100" data-url="{{ question.get_image_url }}" data-annotations='{{question.image_annotations}}' data-edit_annotation=true style="min-height:300px">
</div>
<div class="mt-3 d-flex justify-content-between align-items-center">
<details class="mb-0">
<summary class="small text-muted"><i class="bi bi-info-circle"></i> Image help</summary>
<div class="small text-muted mt-2">Annotate the image using the right mouse button to click and drag an arrow. To remove an arrow drag it out of the image boundaries. Click <strong>Save Annotations</strong> to persist.</div>
<div class="mt-3 d-flex justify-content-between align-items-start">
<details class="mb-0 flex-grow-1 me-2">
<summary class="small text-muted"><i class="bi bi-info-circle"></i> Image annotation help</summary>
<div class="small text-muted mt-2">Annotate the image using the right mouse button to click and drag an arrow. To remove an arrow drag it out of the image boundaries. Click <strong>Save Annotations</strong> to persist. If required use the middle button to zoom and the left button to pan.</div>
</details>
<button id="save-annotations" class="btn btn-sm btn-primary">Save Annotations</button>
+15
View File
@@ -3005,6 +3005,21 @@ class StructureAutocomplete(autocomplete.Select2QuerySetView):
return Structure.objects.none()
return qs
def has_add_permission(self, request):
"""Allow any authenticated user to create new Structure entries.
By default DAL requires the user to have the model add permission.
For Anatomy question creation we want any registered user to be
able to create structures, so relax that check to only require
authentication.
"""
try:
auth = request.user.is_authenticated
except Exception:
auth = False
return bool(auth)
class SubspecialtyAutocomplete(autocomplete.Select2QuerySetView):
@@ -131,7 +131,7 @@
<div class="card-body p-2">
<div class="collapse" id="open-access-bulk">
<form hx-post="{% url 'generic:generic_exam_set_open_access' %}" hx-target="#action-result" hx-swap="innerHTML" class="row g-2 align-items-center">
<input type="hidden" name="type" value="sbas" />
<input type="hidden" name="type" value="{{exam.app_name}}" />
<input type="hidden" name="exam_id" value="{{ exam.pk }}" />
<div class="col-12 d-flex flex-column flex-md-row gap-2 justify-content-md-end">
<button class="btn btn-sm btn-outline-success" type="submit" name="set_open_access" value="true" hx-include="input[name='selection']:checked">Set Open Access = True</button>
@@ -1,6 +1,13 @@
{% extends app_name|add:'/base.html' %}
{% block content %}
<h3>Questions </h3>
<a href="{% url app_name|add:':question_view' %}?author={{ request.user.id }}" class="btn btn-sm btn-outline-secondary">My questions</a>
<div class="d-flex justify-content-end mb-3">
<a href="{% url app_name|add:':question_create' %}" class="btn btn-sm btn-primary">Create question</a>
</div>
<h3>Exams</h3>
<div id="exam-list-wrapper">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
@@ -0,0 +1,62 @@
{% load static %}
{% if user_obj %}
<div class="card p-2">
<h5>Content by {{ user_obj.get_full_name }} ({{ user_obj.username }})</h5>
{% for app_key, info in content.items %}
<div class="mb-2">
<strong>{{ info.label }}</strong>
<div class="small">
Questions: {{ info.questions|length }} &nbsp; Exams: {{ info.exams|length }}
</div>
<div class="mt-1">
{% if info.questions %}
<ul class="list-unstyled ms-3">
{% for q in info.questions %}
<li>
{% comment %} Try to link to app question detail if available {% endcomment %}
<a href="{% url app_key|add:':question_detail' q.pk %}" target="_blank">{{ q }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% if info.exams %}
<ul class="list-unstyled ms-3">
{% for ex in info.exams %}
<li>{{ ex }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
<div class="mb-2">
<strong>Atlas Cases</strong>
<div class="small">Cases: {{ atlas_cases|length }}</div>
{% if atlas_cases %}
<ul class="list-unstyled ms-3">
{% for case in atlas_cases %}
<li><a href="{% url 'atlas:case_detail' case.pk %}" target="_blank">{{ case }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="mb-2">
<strong>Atlas Collections</strong>
<div class="small">Collections: {{ atlas_collections|length }}</div>
{% if atlas_collections %}
<ul class="list-unstyled ms-3">
{% for coll in atlas_collections %}
<li><a href="{% url 'atlas:collection_detail' coll.pk %}" target="_blank">{{ coll }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% else %}
<div>No user found</div>
{% endif %}
@@ -0,0 +1,35 @@
{% extends 'generic/base.html' %}
{% block content %}
<h2>User content</h2>
<p>Click "Show content" to load items created by a user.</p>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Username</th>
<th>Content count</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.first_name }} {{ user.last_name }}</td>
<td><a href="{% url 'account_profile' user.username %}">{{ user.username }}</a></td>
<td>{{ user.content_count }}</td>
<td>
<button class="btn btn-sm btn-outline-primary"
hx-get="{% url 'generic:user_content_review' user.pk %}"
hx-target="closest tr"
hx-swap="afterend">Show content</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
+3
View File
@@ -8,6 +8,9 @@ from generic.views import ExamViews as GenericExamViews, GenericViewBase
app_name = "generic"
urlpatterns = [
# User content review views
path("user-content/", views.user_content_list, name="user_content_list"),
path("user-content/<int:user_id>/", views.user_content_review, name="user_content_review"),
path("examination/", views.ExaminationView.as_view(), name="examination_view"),
path("examination/<int:pk>", views.examination_detail, name="examination_detail"),
path(
+85 -1
View File
@@ -2989,7 +2989,7 @@ class ExamViews(View, LoginRequiredMixin):
return render(
request,
"generic/exam_index.html",
"generic/frcr_index.html",
{"filter": filter, "app_name": self.app_name},
)
@@ -6877,3 +6877,87 @@ def supervisor_search(request):
return render(request, "generic/partials/supervisor_search_results.html", {"results": results, "row": row})
@login_required
@user_passes_test(lambda u: u.is_superuser or u.groups.filter(name__in=["content_moderator", "atlas_editor"]).exists())
def user_content_list(request):
"""
Shows a list of all users with the ability to load the content they have created via htmx requests
"""
users = User.objects.annotate(
content_count=Count("user_anatomy_exams", filter=Q(user_anatomy_exams__archive=False))
+ Count("user_longs_exams", filter=Q(user_longs_exams__archive=False))
+ Count("user_physics_exams", filter=Q(user_physics_exams__archive=False))
+ Count("user_rapid_exams", filter=Q(user_rapid_exams__archive=False))
+ Count("user_sba_exams", filter=Q(user_sba_exams__archive=False))
+ Count("user_shorts_exams", filter=Q(user_shorts_exams__archive=False))
).filter(content_count__gt=0)
return render(request, "generic/user_content_list.html", {"users": users})
@login_required
@user_passes_test(lambda u: u.is_superuser or u.groups.filter(name__in=["content_moderator", "atlas_editor"]).exists())
def user_content_review(request, user_id):
"""
HTMX partial to show content authored by a given user.
Includes authored questions and authored exams for each app,
plus atlas cases and case collections.
"""
user_obj = get_object_or_404(User, pk=user_id)
# Mapping of app -> (question_related_name, exam_author_related_name, display_label)
APP_MAP = {
"anatomy": ("anatomy_authored_questions", "anatomy_exam_author", "Anatomy"),
"physics": ("physics_authored_questions", "physics_exam_author", "Physics"),
"rapids": ("rapid_authored_questions", "rapid_exam_author", "Rapids"),
"shorts": ("shorts_authored_questions", "shorts_exam_author", "Shorts"),
"longs": ("long_authored_questions", "long_exam_author", "Longs"),
"sbas": ("sbas_authored_questions", "sba_exam_author", "SBAs"),
}
content = {}
for app_key, (q_rel, e_rel, label) in APP_MAP.items():
questions = []
exams = []
try:
questions = list(getattr(user_obj, q_rel).all().order_by("-pk")[:50])
except Exception:
questions = []
try:
exams = list(getattr(user_obj, e_rel).all().filter(archive=False).order_by("-pk")[:50])
except Exception:
exams = []
content[app_key] = {
"label": label,
"questions": questions,
"exams": exams,
}
# Atlas authored content
atlas_cases = []
atlas_collections = []
try:
atlas_cases = list(user_obj.atlas_authored_cases.all().order_by("-pk")[:50])
except Exception:
atlas_cases = []
try:
atlas_collections = list(user_obj.casecollection_authored_cases.all().order_by("-pk")[:50])
except Exception:
atlas_collections = []
context = {
"user_obj": user_obj,
"content": content,
"atlas_cases": atlas_cases,
"atlas_collections": atlas_collections,
}
# Render HTMX partial (or full page if not HTMX)
template = "generic/partials/user_content_review.html"
return render(request, template, context)
+171 -119
View File
@@ -2,128 +2,180 @@
{% load auth_extras %}
{% load help_tags %}
{% block content %}
<div class="py-4">
<div class="container">
<div class="row mb-4">
<div class="col-12">
<div class="p-4 bg-dark rounded-3 border">
<div class="d-flex justify-content-between align-items-center">
<h1 class="mb-0">PENRA Courses</h1>
{% if not request.user.is_authenticated %}
{% comment %} <div>
<a class="btn btn-outline-primary me-2" href="{% url 'login' %}">Log in</a>
<a class="btn btn-outline-secondary" href="{% url 'password_reset' %}">Reset password</a>
</div> {% endcomment %}
{% else %}
<a class="btn btn-outline-light" href="{% url 'profile' %}">Your profile</a>
{% endif %}
</div>
<p class="text-muted mt-2">Access exams, courses and resources for trainees and trainers.</p>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
{% if request.user.is_authenticated %}
{% if request.user.userprofile.peninsula_trainee %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Profile</h5>
<p class="mb-1"><strong>Name:</strong> {{ request.user.first_name }} {{ request.user.last_name }}</p>
<p class="mb-1"><strong>Email:</strong> {{ request.user.email }}</p>
<p class="mb-1"><strong>Grade:</strong> {{ request.user.userprofile.grade }}</p>
<p class="mb-0"><strong>Supervisor:</strong> {{ request.user.userprofile.supervisor }}</p>
<div role="alert" class="alert alert-warning mt-3">
If any of this information is incorrect, please update it via your <a href="{% url 'profile' %}">profile page</a>
</div>
</div>
</div>
<div class="py-4">
<div class="container">
<div class="row mb-4">
<div class="col-12">
<div class="p-4 bg-dark rounded-3 border">
<div class="d-flex justify-content-between align-items-center">
<h1 class="mb-0">PENRA Courses</h1>
{% if not request.user.is_authenticated %}
{% comment %} <div>
<a class="btn btn-outline-primary me-2" href="{% url 'login' %}">Log in</a>
<a class="btn btn-outline-secondary" href="{% url 'password_reset' %}">Reset password</a>
</div> {% endcomment %}
{% else %}
<a class="btn btn-outline-light" href="{% url 'profile' %}">Your profile</a>
{% endif %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Exams / Courses</h5>
<p class="card-text">View your courses and exam scores.</p>
<a class="btn btn-outline-primary" href="{% url 'user_scores' %}">View my courses</a>
</div>
</div>
{% else %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Peninsula Trainees</h5>
<p class="card-text">Please log in to access your exams and courses.</p>
<a class="btn btn-outline-primary me-2" href="{% url 'login' %}">Log in</a>
<a class="btn btn-outline-secondary" href="{% url 'password_reset' %}">Reset password</a>
</div>
</div>
<div class="alert alert-info" role="alert">
<h5 class="alert-heading mb-1">Not a Peninsula trainee?</h5>
<p class="mb-0">If you are an external user, please <a href="{% url 'cid_selector' %}">log in via CID</a>.</p>
{% help "Help" %}
You should have received your candidate ID and login details by email or directly from your course organiser. If you have not, please contact them for assistance.
{% endhelp %}
</div>
{% endif %}
{% if collections %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Exam collections</h5>
<div class="list-group list-group-flush">
{% for collection in collections %}
<a class="list-group-item list-group-item-action" href="{% url 'generic:examcollection_detail' collection.pk %}">{{ collection.name }}</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if rcr_assessor %}
<div class="alert alert-info" role="alert">
<h5 class="alert-heading mb-1">RCR platform migration</h5>
<p class="mb-0"><a href="{% url 'rcr:radiology_index_view' %}">Open RCR migration tools</a></p>
</div>
{% endif %}
</div>
<div class="col-lg-4">
<div class="card mb-3">
<div class="card-body">
<h6 class="card-title">Quick links</h6>
<ul class="list-unstyled mb-0">
<li><a href="http://www.penracourses.org.uk/rts" class="link-primary">RTS platform</a></li>
{% if request.user.is_authenticated %}
<li><a href="{% url 'user_marking' %}" class="link-primary">Marking overview</a></li>
{% endif %}
{% if not request.user.is_authenticated %}<li><a href="{% url 'cid_selector' %}" class="link-primary">CID login</a></li>{% endif %}
{% if request.user.supervisor %}<li><a href='{% url "generic:supervisor_overview" request.user.supervisor.pk %}' class="link-primary">Supervisor overview</a></li>{% endif %}
{% if request.user|has_group:"cid_user_manager" %}
<li>Manage <a href="{% url 'trainees' %}">trainees</a> · <a href="{% url 'accounts_list' %}">users</a> · <a href="{% url 'generic:manage_cids' %}">candidates</a></li>
{% endif %}
{% if request.user.is_superuser %}
<li><a href="{% url 'django_psutil_dash:dashboard' %}" class="link-primary">psutil</a></li>
{% endif %}
</ul>
</div>
</div>
<div class="card text-muted small">
<div class="card-body">
<strong>Need help?</strong>
<p class="mb-0">See <a href="{% url 'about' %}">About</a> or contact your admin.</p>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-center">
{% if request.user.is_authenticated %}
{% if request.user|has_group:"cid_user_manager" %}<a class="btn btn-sm btn-outline-secondary me-2" href="{% url 'admin:index' %}">Admin</a>{% endif %}
{% endif %}
<p class="text-muted mt-2">Exams, courses and resources for trainees and trainers.</p>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
{% if request.user.is_authenticated %}
{% if request.user.userprofile.peninsula_trainee %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Profile</h5>
<p class="mb-1"><strong>Name:</strong> {{ request.user.first_name }} {{ request.user.last_name
}}</p>
<p class="mb-1"><strong>Email:</strong> {{ request.user.email }}</p>
<p class="mb-1"><strong>Grade:</strong> {{ request.user.userprofile.grade }}</p>
<p class="mb-0"><strong>Supervisor:</strong> {{ request.user.userprofile.supervisor }}</p>
<div role="alert" class="alert alert-warning mt-3">
If any of this information is incorrect, please update it via your <a
href="{% url 'profile' %}">profile page</a>
</div>
</div>
</div>
{% endif %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Exams / Courses</h5>
<p class="card-text">View your courses and exam scores.</p>
<a class="btn btn-outline-primary" href="{% url 'user_scores' %}">View my courses</a>
</div>
</div>
{% if request.user.is_authenticated %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">We need your help!</h5>
<p class="card-text small text-muted">Help us grow the question banks and atlas.</p>
<div class="row g-3">
<div class="col-md-6">
<div class="border rounded p-3 h-100 bg-dark-subtle">
<div class="d-flex align-items-center justify-content-between mb-2">
<h6 class="mb-0">FRCR</h6>
<span class="badge text-bg-secondary">Exams</span>
</div>
<div class="d-grid gap-2">
<a class="btn btn-sm btn-outline-primary"
href="{% url 'anatomy:question_create' %}">Add Anatomy question</a>
<a class="btn btn-sm btn-outline-primary"
href="{% url 'sbas:question_create' %}">Add SBA question</a>
<a class="btn btn-sm btn-outline-secondary"
href="{% url 'sbas:question_review_start' %}">Review SBA question</a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="border rounded p-3 h-100 bg-dark-subtle">
<div class="d-flex align-items-center justify-content-between mb-2">
<h6 class="mb-0">Atlas</h6>
<span class="badge text-bg-secondary">Cases</span>
</div>
<div class="d-grid gap-2">
<a class="btn btn-sm btn-outline-primary"
href="{% url 'atlas:case_create' %}">Add case to Atlas</a>
<a class="btn btn-sm btn-outline-primary"
href="{% url 'atlas:exam_create' %}">Create collection</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% else %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Peninsula Trainees</h5>
<p class="card-text">Please log in to access your exams and courses.</p>
<a class="btn btn-outline-primary me-2" href="{% url 'login' %}">Log in</a>
<a class="btn btn-outline-secondary" href="{% url 'password_reset' %}">Reset password</a>
</div>
</div>
<div class="alert alert-info" role="alert">
<h5 class="alert-heading mb-1">Not a Peninsula trainee?</h5>
<p class="mb-0">If you are an external user, please <a href="{% url 'cid_selector' %}">log in via
CID</a>.</p>
{% help "Help" %}
You should have received your candidate ID and login details by email or directly from your course
organiser. If you have not, please contact them for assistance.
{% endhelp %}
</div>
{% endif %}
{% if collections %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Exam collections</h5>
<div class="list-group list-group-flush">
{% for collection in collections %}
<a class="list-group-item list-group-item-action"
href="{% url 'generic:examcollection_detail' collection.pk %}">{{ collection.name }}</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if rcr_assessor %}
<div class="alert alert-info" role="alert">
<h5 class="alert-heading mb-1">RCR platform migration</h5>
<p class="mb-0"><a href="{% url 'rcr:radiology_index_view' %}">Open RCR migration tools</a></p>
</div>
{% endif %}
</div>
<div class="col-lg-4">
<div class="card mb-3">
<div class="card-body">
<h6 class="card-title">Quick links</h6>
<ul class="list-unstyled mb-0">
<li><a href="http://www.penracourses.org.uk/rts" class="link-primary">RTS platform</a></li>
{% if request.user.is_authenticated %}
<li><a href="{% url 'user_marking' %}" class="link-primary">Marking overview</a></li>
{% endif %}
{% if not request.user.is_authenticated %}<li><a href="{% url 'cid_selector' %}"
class="link-primary">CID login</a></li>{% endif %}
{% if request.user.supervisor %}<li><a
href='{% url "generic:supervisor_overview" request.user.supervisor.pk %}'
class="link-primary">Supervisor overview</a></li>{% endif %}
{% if request.user|has_group:"cid_user_manager" %}
<li>Manage <a href="{% url 'trainees' %}">trainees</a> · <a
href="{% url 'accounts_list' %}">users</a> · <a
href="{% url 'generic:manage_cids' %}">candidates</a></li>
{% endif %}
{% if request.user.is_superuser %}
<li><a href="{% url 'django_psutil_dash:dashboard' %}" class="link-primary">psutil</a></li>
{% endif %}
</ul>
</div>
</div>
<div class="card text-muted small">
<div class="card-body">
<strong>Need help?</strong>
<p class="mb-0">See <a href="{% url 'about' %}">About</a> or contact your admin.</p>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-center">
{% if request.user.is_authenticated %}
{% if request.user|has_group:"cid_user_manager" %}<a class="btn btn-sm btn-outline-secondary me-2"
href="{% url 'admin:index' %}">Admin</a>{% endif %}
{% endif %}
</div>
</div>
</div>
{% endblock %}
</div>
{% endblock %}