From 65b1d2da3c97fd148510a1edb95bd4cd61284e34 Mon Sep 17 00:00:00 2001
From: Ross
Date: Mon, 28 Nov 2022 15:48:50 +0000
Subject: [PATCH] Further candidate management improvements
---
anatomy/forms.py | 3 +-
anatomy/static/css/anatomy.css | 4 ++
generic/forms.py | 17 ++++++
generic/models.py | 56 +++++++++++++++----
generic/templates/generic/ciduser_form.html | 51 ++++++++++-------
.../templates/generic/user_group_view.html | 2 +-
.../generic/user_group_view_detail.html | 43 ++++++++++++++
generic/urls.py | 1 +
generic/views.py | 21 +++++++
templates/privacy.html | 48 +++++++++-------
templates/profile.html | 32 ++++++++---
11 files changed, 219 insertions(+), 59 deletions(-)
create mode 100755 generic/templates/generic/user_group_view_detail.html
diff --git a/anatomy/forms.py b/anatomy/forms.py
index fc4540a2..8bc274e2 100644
--- a/anatomy/forms.py
+++ b/anatomy/forms.py
@@ -22,7 +22,7 @@ from .models import (
QuestionType,
Exam,
)
-from generic.models import Examination
+from generic.models import CidUserGroup, Examination
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea
@@ -204,6 +204,7 @@ class BodyPartForm(ModelForm):
class ExamForm(ExamFormMixin, ModelForm):
+
class Meta(ExamFormMixin.Meta):
model = Exam
diff --git a/anatomy/static/css/anatomy.css b/anatomy/static/css/anatomy.css
index 80e5ef6d..d2eeec72 100644
--- a/anatomy/static/css/anatomy.css
+++ b/anatomy/static/css/anatomy.css
@@ -907,4 +907,8 @@ tr:has(.errorlist){
form .submit-button {
margin-top: 10px;
margin-bottom: 10px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin-top: 10px;
}
\ No newline at end of file
diff --git a/generic/forms.py b/generic/forms.py
index 2404c47a..dddcd9f7 100755
--- a/generic/forms.py
+++ b/generic/forms.py
@@ -60,6 +60,23 @@ from sbas.models import Question as SbasQuestion
class ExamFormMixin:
+ def __init__(self, *args, user, **kwargs) -> None:
+ super(ModelForm, self).__init__(*args, **kwargs)
+
+ if user.is_superuser or user.groups.filter(name="cid_manager").exists():
+ cid_user_group_queryset = CidUserGroup.objects.all()
+ user_user_group_queryset = UserUserGroup.objects.all()
+ else:
+ cid_user_group_queryset = CidUserGroup.objects.none()
+ user_user_group_queryset = UserUserGroup.objects.none()
+ self.fields["cid_user_groups"] = ModelMultipleChoiceField(
+ required=False,
+ queryset=cid_user_group_queryset,
+ )
+ self.fields["user_user_groups"] = ModelMultipleChoiceField(
+ required=False,
+ queryset=user_user_group_queryset,
+ )
class Meta:
fields = [
"name",
diff --git a/generic/models.py b/generic/models.py
index d2f2759e..1dd88985 100644
--- a/generic/models.py
+++ b/generic/models.py
@@ -422,16 +422,16 @@ class QuestionNote(models.Model):
def get_short_str(self):
return f"{self.note_type} - {self.note}"
+EXAM_TYPES = (
+ ("Physics", "physics_exams"),
+ ("Rapids", "rapid_exams"),
+ ("SBAs", "sba_exams"),
+ ("Anatomy", "anatomy_exams"),
+ ("Longs", "longs_exams"),
+ ("CaseCollection", "casecollection_exams"),
+)
class CidUser(models.Model):
- EXAM_TYPES = (
- ("Physics", "physics_exams"),
- ("Rapids", "rapid_exams"),
- ("SBAs", "sba_exams"),
- ("Anatomy", "anatomy_exams"),
- ("Longs", "longs_exams"),
- ("CaseCollection", "casecollection_exams"),
- )
cid = models.IntegerField(blank=True, null=True, unique=True)
passcode = models.CharField(blank=True, max_length=50)
@@ -463,13 +463,13 @@ class CidUser(models.Model):
def get_cid_exams(self):
available_exams = []
- for n, t in self.EXAM_TYPES:
+ for n, t in EXAM_TYPES:
exam_rel = getattr(self, t)
if exam_rel.exists():
exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
available_exams.append((n, exams))
- return available_exams
+ return dict(available_exams)
def generate_exam_report(self):
exam_list = self.get_cid_exams()
@@ -655,6 +655,13 @@ class CidUserGroup(models.Model):
return reverse("generic:cid_group_update", kwargs={"pk": self.pk})
+USER_GROUP_EXAMS = (
+ ("SBAs", "sba_user_user_groups"),
+ ("Physics", "physics_user_user_groups"),
+ ("Anatomy", "anatomy_user_user_groups"),
+ ("Rapids", "rapid_user_user_groups"),
+ ("Longs", "longs_user_user_groups"),
+)
class UserUserGroup(models.Model):
name = models.CharField(blank=True, max_length=50, help_text="Name of the User Group")
@@ -675,6 +682,15 @@ class UserUserGroup(models.Model):
s = ", ".join([f"{user.id} - {user.username} ({user.email})" for user in users])
return s
+ def GetGroupExams(self):
+ exams = defaultdict(list)
+ for t, rel in USER_GROUP_EXAMS:
+ exam_rel = getattr(self, rel)
+ if exam_rel.exists():
+ exams[t].extend(exam_rel.all())
+
+ return dict(exams)
+
def get_absolute_url(self):
return reverse("generic:user_group_update", kwargs={"pk": self.pk})
@@ -684,6 +700,15 @@ class UserGrades(models.Model):
def __str__(self):
return self.name
+USER_EXAM_TYPES = (
+ ("Physics", "user_physics_exams"),
+ ("Rapids", "user_rapid_exams"),
+ ("SBAs", "user_sba_exams"),
+ ("Anatomy", "user_anatomy_exams"),
+ ("Longs", "user_longs_exams"),
+ #("CaseCollection", "user_casecollection_exams"),
+)
+
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
supervisor = models.ForeignKey("Supervisor", on_delete=models.SET_NULL, blank=True, null=True, related_name="trainee")
@@ -700,6 +725,17 @@ class UserProfile(models.Model):
username = property(getusername)
+ def get_exams(self):
+ available_exams = defaultdict(list)
+
+ for n, t in USER_EXAM_TYPES:
+ exam_rel = getattr(self.user, t)
+ if exam_rel.exists():
+ exams = exam_rel.filter(exam_mode=True, archive=False).order_by("name")
+ available_exams[n].extend(exams)
+
+ return dict(available_exams)
+
class Supervisor(models.Model):
"""Model to hold individual supervisor details (email and name)
diff --git a/generic/templates/generic/ciduser_form.html b/generic/templates/generic/ciduser_form.html
index b41fd6f7..088e5ec8 100755
--- a/generic/templates/generic/ciduser_form.html
+++ b/generic/templates/generic/ciduser_form.html
@@ -6,33 +6,46 @@
{% block css %}
{% endblock %}
{% block js %}
-
-{{form.media}}
+
+ {{form.media}}
-
+
-
+
{% endblock %}
{% block content %}
-Edit CID User / {{ciduser.cid}}
-Use this form to edit a CID user.
-
+
+
-Exams
-The user is currently part of the following exams
+ Exams
+ The user is currently part of the following exams
-
-{{ object.get_cid_exams }}
-
+
+ {% with object.get_cid_exams as exam_map %}
+ {% for exam_type, exams in exam_map.items %}
+
{{exam_type}}
+
+ {% for exam in exams %}
+
+ {{exam}}
+
+ {% endfor %}
+
+ {% empty %}
+ No exams.
+ {% endfor %}
+ {% endwith %}
+
-
-These should be edited via the exam candidate page. If you need to manually toggle you can do so here (you shouldn't)
-
+
+ These should be edited via the exam candidate page. If you need to manually toggle you can do so here (you shouldn't)
+
{% endblock %}
\ No newline at end of file
diff --git a/generic/templates/generic/user_group_view.html b/generic/templates/generic/user_group_view.html
index 0d24c303..d2a0915d 100755
--- a/generic/templates/generic/user_group_view.html
+++ b/generic/templates/generic/user_group_view.html
@@ -6,7 +6,7 @@
Create new group
{% for group in groups %}
- {{group.name}}
+
{{group.name}}
{% comment %} Email candidate details
{{exam}}
+
+ {% endfor %}
+
+ {% endfor %}
+ {% endwith %}
+
+{% endblock %}
+
+{% block js %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/generic/urls.py b/generic/urls.py
index 6215ae6e..12e91638 100755
--- a/generic/urls.py
+++ b/generic/urls.py
@@ -78,6 +78,7 @@ urlpatterns = [
),
path("user/group/", views.user_group_view, name="user_group_view"),
path("user/group/all", views.user_group_view_all, name="user_group_view_all"),
+ path("user/group//", views.user_group_view_detail, name="user_group_detail"),
path(
"user/group//update",
views.UserUserGroupUpdate.as_view(),
diff --git a/generic/views.py b/generic/views.py
index 5ca313fc..46c4c938 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -2141,6 +2141,17 @@ def cid_group_view_all(request):
{"groups": groups, "view_all": True},
)
+@user_is_cid_user_manager
+def user_group_view_detail(request, group_id):
+ group = get_object_or_404(UserUserGroup, pk=group_id)
+
+ users = group.users.filter() # Filter inactive users?
+
+ return render(
+ request,
+ "generic/user_group_view_detail.html",
+ {"group": group, "users": users},
+ )
@user_is_cid_user_manager
def user_group_view(request):
@@ -2493,6 +2504,11 @@ class ExamCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
print(context["view"].model.app_name)
return context
+ def get_form_kwargs(self):
+ kwargs = super(ExamCreateBase, self).get_form_kwargs()
+ kwargs.update({"user": self.request.user})
+ return kwargs
+
class ExamUpdateBase(RevisionMixin, LoginRequiredMixin, UpdateView):
template_name = "exam_update_form.html"
@@ -2505,6 +2521,11 @@ class ExamUpdateBase(RevisionMixin, LoginRequiredMixin, UpdateView):
return super().form_valid(form)
+ def get_form_kwargs(self):
+ kwargs = super(ExamUpdateBase, self).get_form_kwargs()
+ kwargs.update({"user": self.request.user})
+ return kwargs
+
class ExamDeleteBase(RevisionMixin, DeleteView):
template_name = "exam_confirm_delete.html"
diff --git a/templates/privacy.html b/templates/privacy.html
index 4a3fafd0..3047a7ca 100644
--- a/templates/privacy.html
+++ b/templates/privacy.html
@@ -1,39 +1,45 @@
{% extends 'base.html' %}
{% block content %}
-
+
Privacy Policy
+
+
User information
We aim to store the minimum information required to enable delivery of the required service on the platform. What is stored depends on the service being provided.
CID candidates
Candidates who receive a CID number may have the following information stored on the server.
-
-
These details will be linked (via a CID) with any answers given in the course of using the platform.
+
+
These details will be linked (via a CID) with any answers given in the course of using the platform.
User accounts
User accounts may have the following information stored on the server.
-
- Name
- Email address
- Supervisor
- Professional registration number
- Grade
-
-
These details will be linked (via a the user account) with any answers given in the course of using the platform.
+
+ Name
+ Email address
+ Supervisor
+ Professional registration number
+ Grade
+
+
These details will be linked (via a the user account) with any answers given in the course of using the platform.
Supervisors
A supervisor may have a user account in which case all of the above (under the "User accounts" section) may apply
In addition the following information may be stored
-
- Name
- Email address
- Trainee(s)
- Site
-
+
+ Name
+ Email address
+ Trainee(s)
+ Site
+
-
+
Patient details
+
No patient identifiable information is stored on the system. If you believe you this is incorrect please flag the approapriate item via the built in system.
+
+
+
{% endblock %}
\ No newline at end of file
diff --git a/templates/profile.html b/templates/profile.html
index 346a9613..16977936 100644
--- a/templates/profile.html
+++ b/templates/profile.html
@@ -24,15 +24,33 @@
- Groups
+ Groups
-
- {% for group in user.user_groups.all %}
- {{group}}
- {% empty %}
+
+ {% for group in user.user_groups.all %}
+ {{group}}
+ {% empty %}
No groups.
- {% endfor %}
-
+ {% endfor %}
+
+
+ Exams
+
+ {% with user.userprofile.get_exams as exam_map %}
+ {% for exam_type, exams in exam_map.items %}
+
{{exam_type}}
+
+ {% for exam in exams %}
+
+ {{exam}}
+
+ {% endfor %}
+
+ {% empty %}
+ No exams.
+ {% endfor %}
+ {% endwith %}
+
Change password