diff --git a/TODO.md b/TODO.md
index d6e4e4f3..4cc1856c 100644
--- a/TODO.md
+++ b/TODO.md
@@ -6,3 +6,7 @@
- Improve documentation
- Test coverage
+
+
+# Future
+- Clean up user results / score pages
\ No newline at end of file
diff --git a/anatomy/static/css/anatomy.css b/anatomy/static/css/anatomy.css
index 5dabd15e..c6ab1581 100644
--- a/anatomy/static/css/anatomy.css
+++ b/anatomy/static/css/anatomy.css
@@ -453,6 +453,15 @@ td.user-answer-score-2.rapid-ans::after {
padding-top: 10px;
}
+.submitted-user-answer:hover::after {
+ content: " Submitted answer";
+ opacity: 50%;
+}
+
+.question-stem-text {
+ font-weight: bolder;
+}
+
.parent-help:hover .help-text {
opacity: 50%;
}
@@ -870,4 +879,27 @@ tr:has(.errorlist){
.errorlist + input {
background-color: red;
color: darkblue;
+}
+
+/* Form styling */
+
+.control-group {
+ padding-top: 10px;
+}
+
+.help-block {
+ float: right;
+ opacity: 25%;
+}
+
+.control-group:hover .help-block{
+ opacity: 100%;
+}
+
+.control-group {
+ padding-top: 10px;
+}
+
+.inline-form {
+ display: inline-grid;
}
\ No newline at end of file
diff --git a/anatomy/templates/anatomy/exam_scores_user.html b/anatomy/templates/anatomy/exam_scores_user.html
index 1dfb9382..274c85d0 100644
--- a/anatomy/templates/anatomy/exam_scores_user.html
+++ b/anatomy/templates/anatomy/exam_scores_user.html
@@ -13,9 +13,7 @@
-
Exam: {{ exam.name }}
-
Candidate: {{ cid|default_if_none:request.user.username }}
- Answers:
+ {% include 'user_score_header.html' %}
{% for ans, score, correct_answer in answers_and_marks %}
- Question {{forloop.counter}} - Correct answer: {{correct_answer }}
@@ -24,12 +22,7 @@
View
{% endfor %}
-
Total mark: {{ total_score }} / {{max_score}}
-
+ {% include 'user_scores_footer.html' %}
{% endblock %}
{% block js %}
diff --git a/generic/forms.py b/generic/forms.py
index 6c107833..71ed4d1b 100755
--- a/generic/forms.py
+++ b/generic/forms.py
@@ -7,14 +7,22 @@ from django.forms import (
CharField,
HiddenInput,
EmailField,
- IntegerField
-
+ IntegerField,
)
from django.forms import inlineformset_factory
from atlas.models import CaseCollection
from django.contrib.auth.models import User
-from generic.models import CidUser, CidUserGroup, Examination, QuestionNote, Supervisor, UserGrades, UserProfile, UserUserGroup
+from generic.models import (
+ CidUser,
+ CidUserGroup,
+ Examination,
+ QuestionNote,
+ Supervisor,
+ UserGrades,
+ UserProfile,
+ UserUserGroup,
+)
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea
@@ -50,6 +58,7 @@ from sbas.models import CidUserAnswer as SbasCidUserAnswer
from sbas.models import Exam as SbasExam
from sbas.models import Question as SbasQuestion
+
class ExamFormMixin:
class Meta:
fields = [
@@ -64,7 +73,7 @@ class ExamFormMixin:
"archive",
"cid_user_groups",
"user_user_groups",
- #"author",
+ # "author",
]
def save(self, commit=True):
@@ -75,6 +84,7 @@ class ExamFormMixin:
return instance
+
class ExamAuthorFormMixin(ModelForm):
class Meta:
fields = ["author"]
@@ -133,7 +143,6 @@ class QuestionNoteForm(ModelForm):
class CidUserForm(ModelForm):
-
def __init__(self, *args, **kwargs):
if kwargs.get("instance"):
# We get the 'initial' keyword argument or initialize it
@@ -141,12 +150,22 @@ class CidUserForm(ModelForm):
initial = kwargs.setdefault("initial", {})
# The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data.
- initial["physics_exams"] = [t.pk for t in kwargs["instance"].physics_exams.all()]
- initial["anatomy_exams"] = [t.pk for t in kwargs["instance"].anatomy_exams.all()]
+ initial["physics_exams"] = [
+ t.pk for t in kwargs["instance"].physics_exams.all()
+ ]
+ initial["anatomy_exams"] = [
+ t.pk for t in kwargs["instance"].anatomy_exams.all()
+ ]
initial["sba_exams"] = [t.pk for t in kwargs["instance"].sba_exams.all()]
- initial["rapid_exams"] = [t.pk for t in kwargs["instance"].rapid_exams.all()]
- initial["longs_exams"] = [t.pk for t in kwargs["instance"].longs_exams.all()]
- initial["casecollection_exams"] = [t.pk for t in kwargs["instance"].casecollection_exams.all()]
+ initial["rapid_exams"] = [
+ t.pk for t in kwargs["instance"].rapid_exams.all()
+ ]
+ initial["longs_exams"] = [
+ t.pk for t in kwargs["instance"].longs_exams.all()
+ ]
+ initial["casecollection_exams"] = [
+ t.pk for t in kwargs["instance"].casecollection_exams.all()
+ ]
ModelForm.__init__(self, *args, **kwargs)
@@ -155,12 +174,16 @@ class CidUserForm(ModelForm):
self.fields["physics_exams"] = ModelMultipleChoiceField(
required=False,
queryset=PhysicsExam.objects.filter(archive=False),
- widget=FilteredSelectMultiple(verbose_name="Physics Exams", is_stacked=False),
+ widget=FilteredSelectMultiple(
+ verbose_name="Physics Exams", is_stacked=False
+ ),
)
self.fields["anatomy_exams"] = ModelMultipleChoiceField(
required=False,
queryset=AnatomyExam.objects.filter(archive=False),
- widget=FilteredSelectMultiple(verbose_name="Anatomy Exams", is_stacked=False),
+ widget=FilteredSelectMultiple(
+ verbose_name="Anatomy Exams", is_stacked=False
+ ),
)
self.fields["sba_exams"] = ModelMultipleChoiceField(
required=False,
@@ -180,7 +203,9 @@ class CidUserForm(ModelForm):
self.fields["casecollection_exams"] = ModelMultipleChoiceField(
required=False,
queryset=CaseCollection.objects.filter(archive=False),
- widget=FilteredSelectMultiple(verbose_name="Case Collection", is_stacked=False),
+ widget=FilteredSelectMultiple(
+ verbose_name="Case Collection", is_stacked=False
+ ),
)
def save(self, commit=True):
@@ -219,7 +244,7 @@ class CidUserForm(ModelForm):
self.save_m2m()
return instance
-
+
class Meta:
model = CidUser
fields = [
@@ -234,31 +259,42 @@ class CidUserForm(ModelForm):
"group",
]
+
+class CidUserGroupModelChoiceField(ModelMultipleChoiceField):
+ def label_from_instance(self, obj):
+ return f"{obj.cid} [{obj.email}]"
+
+
class CidUserGroupForm(ModelForm):
+
class Meta:
model = CidUserGroup
fields = ["name", "archive"]
+
class UserUserGroupModelChoiceField(ModelMultipleChoiceField):
def label_from_instance(self, obj):
return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]"
+
class UserUserGroupForm(ModelForm):
users = UserUserGroupModelChoiceField(
required=False,
queryset=User.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
)
+
class Meta:
model = UserUserGroup
fields = ["name", "archive", "users"]
-#class UserForm(ModelForm):
+
+# class UserForm(ModelForm):
# class Meta:
# model = User
# fields = ["first_name", "last_name", "email"]
#
-#class UserProfileForm(ModelForm):
+# class UserProfileForm(ModelForm):
# class Meta:
# model = UserProfile
# fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"]
@@ -271,18 +307,25 @@ GRADE_CHOICES = (
("ST5", "ST5"),
)
+
class UserUserForm(Form):
- username = EmailField(required=True, help_text="Username / email should be the same. Ideally this should be an nhs email.")
+ username = EmailField(
+ required=True,
+ help_text="Username / email should be the same. Ideally this should be an nhs email.",
+ )
first_name = CharField(max_length=255, required=True)
last_name = CharField(max_length=255, required=True)
grade = ModelChoiceField(UserGrades.objects.all(), required=False)
- supervisor = ModelChoiceField(Supervisor.objects.all(), required=False) # Needs to be a user/object ref
+ supervisor = ModelChoiceField(
+ Supervisor.objects.all(), required=False
+ ) # Needs to be a user/object ref
#
# class Meta:
#
# fields = ("username", "first_name", "last_name")
+
class SupervisorForm(ModelForm):
class Meta:
model = Supervisor
- exclude = ("",)
\ No newline at end of file
+ exclude = ("",)
diff --git a/generic/migrations/0047_auto_20221121_1637.py b/generic/migrations/0047_auto_20221121_1637.py
new file mode 100644
index 00000000..108252d3
--- /dev/null
+++ b/generic/migrations/0047_auto_20221121_1637.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.13 on 2022-11-21 16:37
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('generic', '0046_auto_20221121_0955'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='supervisor',
+ name='email',
+ field=models.EmailField(help_text='The (nhs.net) email address of the supervisor', max_length=254, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='supervisor',
+ name='name',
+ field=models.CharField(help_text='Name of the supervisor', max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='supervisor',
+ name='site',
+ field=models.ForeignKey(blank=True, help_text='Hospital site at which the supervisor is based', null=True, on_delete=django.db.models.deletion.SET_NULL, to='generic.site'),
+ ),
+ ]
diff --git a/generic/models.py b/generic/models.py
index 1c1d6000..b9bab9ee 100644
--- a/generic/models.py
+++ b/generic/models.py
@@ -638,13 +638,13 @@ class CidUserGroup(models.Model):
class UserUserGroup(models.Model):
- name = models.CharField(blank=True, max_length=50)
- archive = models.BooleanField(default=False)
+ name = models.CharField(blank=True, max_length=50, help_text="Name of the User Group")
+ archive = models.BooleanField(default=False, help_text="Archived groups remain on the test system but are not displayed by default")
users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
- related_name="user_groups"
+ related_name="user_groups",
)
def __str__(self) -> str:
@@ -686,10 +686,10 @@ class Supervisor(models.Model):
This can be linked with a user account.
"""
- email = models.EmailField(blank=True, unique=True, help_text="The (nhs.net) email address of the supervisor")
- name = models.CharField(max_length=255, blank=True, help_text="Name of the supervisor")
+ email = models.EmailField(unique=True, help_text="The (nhs.net) email address of the supervisor")
+ name = models.CharField(max_length=255, help_text="Name of the supervisor")
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True, help_text="If the supervisor has an account on the test system that can be associated here")
- site = models.ForeignKey("Site", on_delete=models.SET_NULL, null=True, blank=True)
+ site = models.ForeignKey("Site", on_delete=models.SET_NULL, null=True, blank=True, help_text="Hospital site at which the supervisor is based")
active = models.BooleanField(default=True)
def __str__(self) -> str:
diff --git a/generic/tables.py b/generic/tables.py
index ef69aa58..3e300ca0 100644
--- a/generic/tables.py
+++ b/generic/tables.py
@@ -33,6 +33,7 @@ class CidUserTable(tables.Table):
#sba_exams = tables.ManyToManyColumn(verbose_name="SBA Exams")
#longs_exams = tables.ManyToManyColumn(verbose_name="Long Exams")
#anatomy_exams = tables.ManyToManyColumn(verbose_name="Anatomy Exams")
+ group = tables.Column(linkify=True)
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
@@ -143,6 +144,8 @@ class UserUserTable(tables.Table):
empty_values=(), linkify={"viewname": "account_update", "args":[A("username")]}, orderable=True, verbose_name="Edit",
)
+ user_groups = tables.ManyToManyColumn(verbose_name="Groups", linkify_item=True)
+
class Meta:
model = User
template_name = "django_tables2/bootstrap4.html"
@@ -150,17 +153,19 @@ class UserUserTable(tables.Table):
# "cid",
#"userprofile",
"userprofile.grade",
+ #"user_groups",
#"userprofile.supervisor",
# "email",
)
- sequence = ("user", "userprofile.grade", "supervisor", "edit")
+ sequence = ("user", "userprofile.grade", "supervisor", "user_groups", "edit")
def __init__(self, data=None, *args, **kwargs):
super().__init__(
data.prefetch_related(
- "supervisor",
"userprofile",
- #"userprofile.grade",
+ "user_groups",
+ "userprofile__grade",
+ "supervisor",
),
*args,
**kwargs,
diff --git a/generic/templates/generic/cid_group_view.html b/generic/templates/generic/cid_group_view.html
index 60d5b7ea..27fccdf3 100755
--- a/generic/templates/generic/cid_group_view.html
+++ b/generic/templates/generic/cid_group_view.html
@@ -6,7 +6,7 @@
Create new group
{% for group in groups %}
- {{group.name}}
+
{{group.name}}