Complete supervisor model and start improving forms

This commit is contained in:
Ross
2022-11-28 09:50:07 +00:00
parent 653baa85ff
commit 747bf93373
27 changed files with 309 additions and 183 deletions
+4
View File
@@ -6,3 +6,7 @@
- Improve documentation - Improve documentation
- Test coverage - Test coverage
# Future
- Clean up user results / score pages
+32
View File
@@ -453,6 +453,15 @@ td.user-answer-score-2.rapid-ans::after {
padding-top: 10px; padding-top: 10px;
} }
.submitted-user-answer:hover::after {
content: " Submitted answer";
opacity: 50%;
}
.question-stem-text {
font-weight: bolder;
}
.parent-help:hover .help-text { .parent-help:hover .help-text {
opacity: 50%; opacity: 50%;
} }
@@ -871,3 +880,26 @@ tr:has(.errorlist){
background-color: red; background-color: red;
color: darkblue; 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;
}
@@ -13,9 +13,7 @@
</div> </div>
</div> </div>
<div class="anatomy"> <div class="anatomy">
<h2>Exam: {{ exam.name }}</h2> {% include 'user_score_header.html' %}
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
Answers:
<ul class="score-answer-list">{% for ans, score, correct_answer in answers_and_marks %} <ul class="score-answer-list">{% for ans, score, correct_answer in answers_and_marks %}
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{correct_answer }}</span></li> <li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{correct_answer }}</span></li>
<span class="user-answer-score user-answer-score-{{score}} physics-ans"> <span class="user-answer-score user-answer-score-{{score}} physics-ans">
@@ -24,12 +22,7 @@
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span> <span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
{% endfor %} {% endfor %}
</ul> </ul>
<br /> Total mark: {{ total_score }} / {{max_score}} {% include 'user_scores_footer.html' %}
<div>
{% if cid %}
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
+60 -17
View File
@@ -7,14 +7,22 @@ from django.forms import (
CharField, CharField,
HiddenInput, HiddenInput,
EmailField, EmailField,
IntegerField IntegerField,
) )
from django.forms import inlineformset_factory from django.forms import inlineformset_factory
from atlas.models import CaseCollection from atlas.models import CaseCollection
from django.contrib.auth.models import User 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.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput, Textarea 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 Exam as SbasExam
from sbas.models import Question as SbasQuestion from sbas.models import Question as SbasQuestion
class ExamFormMixin: class ExamFormMixin:
class Meta: class Meta:
fields = [ fields = [
@@ -64,7 +73,7 @@ class ExamFormMixin:
"archive", "archive",
"cid_user_groups", "cid_user_groups",
"user_user_groups", "user_user_groups",
#"author", # "author",
] ]
def save(self, commit=True): def save(self, commit=True):
@@ -75,6 +84,7 @@ class ExamFormMixin:
return instance return instance
class ExamAuthorFormMixin(ModelForm): class ExamAuthorFormMixin(ModelForm):
class Meta: class Meta:
fields = ["author"] fields = ["author"]
@@ -133,7 +143,6 @@ class QuestionNoteForm(ModelForm):
class CidUserForm(ModelForm): class CidUserForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if kwargs.get("instance"): if kwargs.get("instance"):
# We get the 'initial' keyword argument or initialize it # We get the 'initial' keyword argument or initialize it
@@ -141,12 +150,22 @@ class CidUserForm(ModelForm):
initial = kwargs.setdefault("initial", {}) initial = kwargs.setdefault("initial", {})
# The widget for a ModelMultipleChoiceField expects # The widget for a ModelMultipleChoiceField expects
# a list of primary key for the selected data. # a list of primary key for the selected data.
initial["physics_exams"] = [t.pk for t in kwargs["instance"].physics_exams.all()] initial["physics_exams"] = [
initial["anatomy_exams"] = [t.pk for t in kwargs["instance"].anatomy_exams.all()] 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["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["rapid_exams"] = [
initial["longs_exams"] = [t.pk for t in kwargs["instance"].longs_exams.all()] t.pk for t in kwargs["instance"].rapid_exams.all()
initial["casecollection_exams"] = [t.pk for t in kwargs["instance"].casecollection_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) ModelForm.__init__(self, *args, **kwargs)
@@ -155,12 +174,16 @@ class CidUserForm(ModelForm):
self.fields["physics_exams"] = ModelMultipleChoiceField( self.fields["physics_exams"] = ModelMultipleChoiceField(
required=False, required=False,
queryset=PhysicsExam.objects.filter(archive=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( self.fields["anatomy_exams"] = ModelMultipleChoiceField(
required=False, required=False,
queryset=AnatomyExam.objects.filter(archive=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( self.fields["sba_exams"] = ModelMultipleChoiceField(
required=False, required=False,
@@ -180,7 +203,9 @@ class CidUserForm(ModelForm):
self.fields["casecollection_exams"] = ModelMultipleChoiceField( self.fields["casecollection_exams"] = ModelMultipleChoiceField(
required=False, required=False,
queryset=CaseCollection.objects.filter(archive=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): def save(self, commit=True):
@@ -234,31 +259,42 @@ class CidUserForm(ModelForm):
"group", "group",
] ]
class CidUserGroupModelChoiceField(ModelMultipleChoiceField):
def label_from_instance(self, obj):
return f"{obj.cid} [{obj.email}]"
class CidUserGroupForm(ModelForm): class CidUserGroupForm(ModelForm):
class Meta: class Meta:
model = CidUserGroup model = CidUserGroup
fields = ["name", "archive"] fields = ["name", "archive"]
class UserUserGroupModelChoiceField(ModelMultipleChoiceField): class UserUserGroupModelChoiceField(ModelMultipleChoiceField):
def label_from_instance(self, obj): def label_from_instance(self, obj):
return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]" return f"{obj.username} ({obj.userprofile.grade}) [{obj.email}]"
class UserUserGroupForm(ModelForm): class UserUserGroupForm(ModelForm):
users = UserUserGroupModelChoiceField( users = UserUserGroupModelChoiceField(
required=False, required=False,
queryset=User.objects.all(), queryset=User.objects.all(),
widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False), widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
) )
class Meta: class Meta:
model = UserUserGroup model = UserUserGroup
fields = ["name", "archive", "users"] fields = ["name", "archive", "users"]
#class UserForm(ModelForm):
# class UserForm(ModelForm):
# class Meta: # class Meta:
# model = User # model = User
# fields = ["first_name", "last_name", "email"] # fields = ["first_name", "last_name", "email"]
# #
#class UserProfileForm(ModelForm): # class UserProfileForm(ModelForm):
# class Meta: # class Meta:
# model = UserProfile # model = UserProfile
# fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"] # fields = ["supervisor_name", "supervisor_email", "registration_number", "peninsula_trainee"]
@@ -271,17 +307,24 @@ GRADE_CHOICES = (
("ST5", "ST5"), ("ST5", "ST5"),
) )
class UserUserForm(Form): 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) first_name = CharField(max_length=255, required=True)
last_name = CharField(max_length=255, required=True) last_name = CharField(max_length=255, required=True)
grade = ModelChoiceField(UserGrades.objects.all(), required=False) 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: # class Meta:
# #
# fields = ("username", "first_name", "last_name") # fields = ("username", "first_name", "last_name")
class SupervisorForm(ModelForm): class SupervisorForm(ModelForm):
class Meta: class Meta:
model = Supervisor model = Supervisor
@@ -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'),
),
]
+6 -6
View File
@@ -638,13 +638,13 @@ class CidUserGroup(models.Model):
class UserUserGroup(models.Model): class UserUserGroup(models.Model):
name = models.CharField(blank=True, max_length=50) name = models.CharField(blank=True, max_length=50, help_text="Name of the User Group")
archive = models.BooleanField(default=False) archive = models.BooleanField(default=False, help_text="Archived groups remain on the test system but are not displayed by default")
users = models.ManyToManyField( users = models.ManyToManyField(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
blank=True, blank=True,
related_name="user_groups" related_name="user_groups",
) )
def __str__(self) -> str: def __str__(self) -> str:
@@ -686,10 +686,10 @@ class Supervisor(models.Model):
This can be linked with a user account. 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") email = models.EmailField(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") 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") 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) active = models.BooleanField(default=True)
def __str__(self) -> str: def __str__(self) -> str:
+8 -3
View File
@@ -33,6 +33,7 @@ class CidUserTable(tables.Table):
#sba_exams = tables.ManyToManyColumn(verbose_name="SBA Exams") #sba_exams = tables.ManyToManyColumn(verbose_name="SBA Exams")
#longs_exams = tables.ManyToManyColumn(verbose_name="Long Exams") #longs_exams = tables.ManyToManyColumn(verbose_name="Long Exams")
#anatomy_exams = tables.ManyToManyColumn(verbose_name="Anatomy Exams") #anatomy_exams = tables.ManyToManyColumn(verbose_name="Anatomy Exams")
group = tables.Column(linkify=True)
selection = tables.CheckBoxColumn(accessor="pk", orderable=False) 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", 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: class Meta:
model = User model = User
template_name = "django_tables2/bootstrap4.html" template_name = "django_tables2/bootstrap4.html"
@@ -150,17 +153,19 @@ class UserUserTable(tables.Table):
# "cid", # "cid",
#"userprofile", #"userprofile",
"userprofile.grade", "userprofile.grade",
#"user_groups",
#"userprofile.supervisor", #"userprofile.supervisor",
# "email", # "email",
) )
sequence = ("user", "userprofile.grade", "supervisor", "edit") sequence = ("user", "userprofile.grade", "supervisor", "user_groups", "edit")
def __init__(self, data=None, *args, **kwargs): def __init__(self, data=None, *args, **kwargs):
super().__init__( super().__init__(
data.prefetch_related( data.prefetch_related(
"supervisor",
"userprofile", "userprofile",
#"userprofile.grade", "user_groups",
"userprofile__grade",
"supervisor",
), ),
*args, *args,
**kwargs, **kwargs,
@@ -6,7 +6,7 @@
<a href="{% url 'generic:cid_group_create' %}">Create new group</a> <a href="{% url 'generic:cid_group_create' %}">Create new group</a>
</p> </p>
{% for group in groups %} {% for group in groups %}
<p><a href="{% url 'generic:cid_group_detail' group.pk %}">{{group.name}}</a> <p><a href="{% url 'generic:cid_group_detail' group.pk %}"><h4>{{group.name}}</h4></a>
<button class="small-url-button" data-url="{% url 'generic:group_email' pk=group.pk %} <button class="small-url-button" data-url="{% url 'generic:group_email' pk=group.pk %}
">Email candidate details</button> ">Email candidate details</button>
<button class="small-url-button" data-url="{% url 'generic:group_email_resend' pk=group.pk %} <button class="small-url-button" data-url="{% url 'generic:group_email_resend' pk=group.pk %}
+2 -1
View File
@@ -1,5 +1,6 @@
{% extends 'generic/base.html' %} {% extends 'generic/base.html' %}
{% load crispy_forms_tags %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% block css %} {% block css %}
{% endblock %} {% endblock %}
@@ -12,7 +13,7 @@
<h3>Filter CID Users </h3> <h3>Filter CID Users </h3>
</summary> </summary>
<form action="" method="get"> <form action="" method="get">
{{ filter.form }} {{ filter.form |crispy}}
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" /> <input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
</form> </form>
</details> </details>
@@ -28,13 +28,19 @@
</table> </table>
<input type="submit" class="submit-button" value="Submit" name="submit"> <input type="submit" class="submit-button" value="Submit" name="submit">
<p>Users can be added to the group <a href="{% url 'generic:manage_cids' %}">here</a></p>
<h3>Users</h3> <h3>Users</h3>
{% if cidusergroup.GetGroupUsers %} <ul>
{{cidusergroup.GetGroupUsers}} {% for user in cidusergroup.ciduser_set.all %}
{% else %} <li><a href="{% url 'generic:update_cid' user.pk %}">{{user.cid}}</a>: {{user.name}} [{{user.email}} {{user.last_name}}] {{user.userprofile.grade}}</li>
No users currently added to group.
{% endif %} {% empty %}
<li>Group currently has no users.</li>
{% endfor %}
</ul>
Count: {{cidusergroup.ciduser_set.count}}
</form> </form>
@@ -20,7 +20,7 @@
{% endfor %} {% endfor %}
</ol> </ol>
{% else %} {% else %}
This exam is not associated with any user groups. Edit and add a group to enable user management. This exam is not associated with any user groups. <a href="{% url exam.app_name|add:':exam_update' exam.pk %}">Edit and add a group</a> to enable user management.
{% endif %} {% endif %}
<script> <script>
@@ -1,5 +1,6 @@
{% extends "generic/supervisor_base.html" %} {% extends "generic/supervisor_base.html" %}
<!-- {% load static from static %} --> <!-- {% load static from static %} -->
{% load crispy_forms_tags %}
{% block css %} {% block css %}
<style> <style>
@@ -25,7 +26,7 @@ Use this form to create / edit a supervisor
{% csrf_token %} {% csrf_token %}
<table> <table>
{{ form.as_table }} {{ form|crispy }}
</table> </table>
<input type="submit" class="submit-button" value="Submit" name="submit"> <input type="submit" class="submit-button" value="Submit" name="submit">
</form> </form>
@@ -5,8 +5,8 @@
<ul> <ul>
{% for supervisor in object_list %} {% for supervisor in object_list %}
<li><a href="{% url 'generic:supervisor_detail' pk=supervisor.pk %}">Supervisor: {{supervisor.name}}</a> <li><a href="{% url 'generic:supervisor_detail' pk=supervisor.pk %}">{{supervisor.name}}</a>
<br/>{{supervisor.email}} <br/>{{supervisor.email}} [{{supervisor.site}}]
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -1,5 +1,6 @@
{% extends "generic/base.html" %} {% extends "generic/base.html" %}
<!-- {% load static from static %} --> <!-- {% load static from static %} -->
{% load crispy_forms_tags %}
{% block css %} {% block css %}
{% endblock %} {% endblock %}
@@ -18,29 +19,32 @@
{% else %} {% else %}
<h2>Add New Group</h2> <h2>Add New Group</h2>
{% endif %} {% endif %}
Use this form to create / edit a CID user group. Use this form to create / edit a User group.<br/>
<form action="" method="post" enctype="multipart/form-data" id="condition-form"> <form action="" method="post" enctype="multipart/form-data" id="condition-form" class="inline-form">
{% csrf_token %} {% csrf_token %}
<table> {{ form|crispy }}
{{ form.as_table }}
</table>
<input type="submit" class="submit-button" value="Submit" name="submit"> <input type="submit" class="submit-button" value="Submit" name="submit">
</form>
<div>
<h3>Users</h3> <h3>Users</h3>
<ul> <ul>
{% for user in userusergroup.users.all %} {% for user in userusergroup.users.all %}
<li>{{user}}</li>{% empty %} <li><a href="{% url 'account_profile' user.username %}">{{user.username}}</a> [{{user.first_name}} {{user.last_name}}] {{user.userprofile.grade}}</li>
{% empty %}
<li>Group currently has no users.</li> <li>Group currently has no users.</li>
{% endfor %} {% endfor %}
</ul> </ul>
Count: {{userusergroup.users.count}}
</div>
{% comment %} {% if userusergroup.users.all %} {% comment %} {% if userusergroup.users.all %}
{{userusergroup.GetGroupUsers}} {{userusergroup.GetGroupUsers}}
{% else %} {% else %}
No users currently added to group. No users currently added to group.
{% endif %} {% endcomment %} {% endif %} {% endcomment %}
</form>
{% endblock %} {% endblock %}
+9 -14
View File
@@ -1,7 +1,7 @@
{% extends 'longs/base.html' %} {% extends 'longs/base.html' %}
{% block content %} {% block content %}
<div class="question-display-block"> <div class="question-display-block">
<div class="hide-show">HIDE/SHOW</div> <div class="hide-show">HIDE/SHOW</div>
<div class="inner-display-block"> <div class="inner-display-block">
Question <span class="question-number">1</span><br /> Question <span class="question-number">1</span><br />
@@ -10,20 +10,17 @@
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''> <div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''>
</div> </div>
</div> </div>
</div> </div>
<div class="longs"> <div class="longs">
<h2>Exam: {{ exam.name }}</h2> {% include 'user_score_header.html' %}
<h3>Candidate: {{ cid }}</h3> <ul>
Scores: {% for score in answers_marks %}
<ul>{% for score in answers_marks %}
<li class="user-answer-li">Question {{forloop.counter}}</li> <li class="user-answer-li">Question {{forloop.counter}}</li>
<span class="user-answer-score user-answer-score-{{score}}"> <span class="user-answer-score user-answer-score-{{score}}">
<pre>{{ans}}</pre> ({{score}}) <pre>{{ans}}</pre> ({{score}})
</span> </span>
{% endfor %} {% endfor %}
</ul> </ul>
<br /> Total mark: {{ total_score }} / {{max_score}}
<br /> Normalised score: {{normalised_score}}
<div> <div>
<h4>Answers</h4> <h4>Answers</h4>
<ul class="long-answer">{% for a,b,c,d,e in answer_text %} <ul class="long-answer">{% for a,b,c,d,e in answer_text %}
@@ -51,13 +48,11 @@
</ul> </ul>
</div> </div>
<div> {% include 'user_scores_footer.html' %}
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$(".hide-show").click((evt, el) => { $(".hide-show").click((evt, el) => {
$(".inner-display-block").toggle(); $(".inner-display-block").toggle();
@@ -119,5 +114,5 @@
$(".view-question-link").first().click(); $(".view-question-link").first().click();
}); });
</script> </script>
{% endblock %} {% endblock %}
@@ -1,26 +1,19 @@
{% extends 'physics/base.html' %} {% extends 'physics/base.html' %}
{% block content %} {% block content %}
<div class="physics"> <div class="physics">
{% include 'generic/user_scores_header.html' %} {% include 'user_score_header.html' %}
Answers:
<ul> <ul>
{% for question, ans in answers_and_marks %} {% for question, ans in answers_and_marks %}
<li class="user-answer-li">Question {{forloop.counter}} - {{ question.stem|safe }}</li> <li class="user-answer-li"><span class="question-stem-text">Question {{forloop.counter}} - {{ question.stem|safe }}</span></li>
<ol type="a"> <ol type="a">
{% for q, a, score, correct_answer in ans %} {% for q, a, score, correct_answer in ans %}
<li>{{q|safe}}: Correct answer: {{correct_answer}} <br />{{a}} <span class="answer-{{score}}">(Score: <li><span class="question-text">{{q|safe}}:</span> Correct answer: {{correct_answer}} <br /><span class="submitted-user-answer">{{a}} <span class="answer-{{score}}">(Score:
{{score}})</span></li> {{score}})</span></span></li>
{% endfor %} {% endfor %}
</ol> </ol>
{% endfor %} {% endfor %}
</ul> </ul>
<br /> Total mark: {{ total_score }} / {{max_score}} {% include 'user_scores_footer.html' %}
<div>
{% if cid %}
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
{% endif %}
</div> </div>
</div>
{% endblock %} {% endblock %}
+3
View File
@@ -73,6 +73,7 @@ INSTALLED_APPS = [
"tinymce", "tinymce",
"django_unused_media", "django_unused_media",
"django_htmx", "django_htmx",
"crispy_forms",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -298,6 +299,8 @@ EMAIL_USE_SSL = True
ADMINS = [("Ross","ross@xkjq.uk")] ADMINS = [("Ross","ross@xkjq.uk")]
CRISPY_TEMPLATE_PACK = 'bootstrap'
DEBUG_CONTAINER = False DEBUG_CONTAINER = False
try: try:
+2 -10
View File
@@ -16,9 +16,7 @@
</div> </div>
</div> </div>
<div class="rapids"> <div class="rapids">
<h2>Exam: {{ exam.name }}</h2> {% include 'user_score_header.html' %}
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
Answers:
<ul class="score-answer-list"> <ul class="score-answer-list">
{% for ans, score, correct_answer in answers_and_marks %} {% for ans, score, correct_answer in answers_and_marks %}
<li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{ correct_answer }}</span></li> <li class="user-answer-li">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{ correct_answer }}</span></li>
@@ -28,13 +26,7 @@
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span> <span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
{% endfor %} {% endfor %}
</ul> </ul>
<br /> Total mark: {{ total_score }} / {{max_score}} {% include 'user_scores_footer.html' %}
<br /> Normalised score: {{ normalised_score }}
<div>
{% if cid %}
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
+1
View File
@@ -32,3 +32,4 @@ bs4
django-htmx django-htmx
debugpy debugpy
django-cookie-consent django-cookie-consent
django-crispy-forms
+2 -10
View File
@@ -2,12 +2,7 @@
{% block content %} {% block content %}
<div class="sbas"> <div class="sbas">
<h2>Exam: {{ exam.name }}</h2> {% include 'user_score_header.html' %}
<h3>Candidate: {{ cid }}</h3>
{% if not exam.publish_results %}
<div class="alert alert-info" role="alert"><h4>Exam results not published</h4>Submitted answers are visible below.</div>
{% endif %}
Answers:
<ul> <ul>
{% for question, a, score, correct_answer, chosen_answer in answers_and_marks %} {% for question, a, score, correct_answer, chosen_answer in answers_and_marks %}
<li class="user-answer-li"><a href="{% url 'sbas:exam_take' exam.pk forloop.counter0 cid passcode %}">Question <li class="user-answer-li"><a href="{% url 'sbas:exam_take' exam.pk forloop.counter0 cid passcode %}">Question
@@ -20,9 +15,6 @@
</span> </span>
{% endfor %} {% endfor %}
</ul> </ul>
<br /> Total mark: {{ total_score }} / {{max_score}} {% include 'user_scores_footer.html' %}
<div>
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
</div>
</div> </div>
{% endblock %} {% endblock %}
+1
View File
@@ -1,6 +1,7 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
{% load static %} {% load static %}
{% load django_htmx %} {% load django_htmx %}
{% load crispy_forms_tags %}
{% load auth_extras %} {% load auth_extras %}
<html> <html>
+1
View File
@@ -27,6 +27,7 @@
<p><a href="http://www.penracourses.org.uk/rts">RTS is available here</a></p> <p><a href="http://www.penracourses.org.uk/rts">RTS is available here</a></p>
<p><a href="{% url 'cid_selector' %}">CID users can log in here</a></p>
<div> <div>
{% if request.user.is_staff %} {% if request.user.is_staff %}
Manage users <a href="{% url 'generic:manage_cids'%}">here</a> Manage users <a href="{% url 'generic:manage_cids'%}">here</a>
+14 -2
View File
@@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div class="anatomy"> <div class="anatomy">
<h1>Profile</h1> <h1>Profile</h1>
<div> <div>
Username: {{ user.username }} Username: {{ user.username }}
@@ -23,8 +23,20 @@
Registration number: {{ user.userprofile.registration_number }} Registration number: {{ user.userprofile.registration_number }}
</div> </div>
<h3>Groups</h3>
<ul>
{% for group in user.user_groups.all %}
<li>{{group}}</li>
{% empty %}
<li>No groups.</li>
{% endfor %}
</ul>
<a href="{% url 'password_change'%}">Change password</a> <a href="{% url 'password_change'%}">Change password</a>
<a href="{% url 'account_update' user.username %}">Update user details</a> <a href="{% url 'account_update' user.username %}">Update user details</a>
<a href="{% url 'account_profile_update' user.username %}">Update user profile</a> <a href="{% url 'account_profile_update' user.username %}">Update user profile</a>
</div> </div>
{% endblock %} {% endblock %}
@@ -1,7 +1,13 @@
<h2>Exam: {{ exam.name }}</h2>
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
{% if view_all_results %} {% if view_all_results %}
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">
Exam state: Active [{{exam.active}}] / Published [{{exam.publish_results}}] Exam state: Active [{{exam.active}}] / Published [{{exam.publish_results}}]
</div> </div>
{% else %}
{% if not exam.publish_results %}
<div class="alert alert-info" role="alert">Results are not currently published. You can see your own answers below.</a></div>
{% endif %}
{% endif %} {% endif %}
<h2>Exam: {{ exam.name }}</h2>
<h3>Candidate: {{ cid|default_if_none:request.user.username }}</h3>
Answers:
+11
View File
@@ -0,0 +1,11 @@
{% if view_all_results or exam.publish_results %}
<br /> Total mark: {{ total_score }} / {{max_score}}
{% if normalised_score %}
<br /> Normalised score: {{ normalised_score }}
{% endif %}
{% endif %}
<div>
{% if cid %}
<a href="{% url 'cid_scores' cid passcode %}">Other exams</a>
{% endif %}
</div>
+2 -2
View File
@@ -1,10 +1,10 @@
{% extends 'generic/base.html' %} {% extends 'generic/base.html' %}
{% load crispy_forms_tags %}
{% block content %} {% block content %}
<h2>Editing user: {{object.username}}</h2> <h2>Editing user: {{object.username}}</h2>
This form allows you to edit the users name and email address. More details (such as grade / supervisor / etc...) can be changed <a href="{% url 'account_profile_update' object.username %}">here</a> This form allows you to edit the users name and email address. More details (such as grade / supervisor / etc...) can be changed <a href="{% url 'account_profile_update' object.username %}">here</a>
<form method="post">{% csrf_token %} <form method="post">{% csrf_token %}
{{ form.as_p }} {{ form|crispy }}
<input type="submit" value="Update"> <input type="submit" value="Update">
</form> </form>
{% endblock %} {% endblock %}
+2 -1
View File
@@ -1,10 +1,11 @@
{% extends 'generic/base.html' %} {% extends 'generic/base.html' %}
{% load crispy_forms_tags %}
{% block content %} {% block content %}
<h2>Editing user: {{object.username}}</h2> <h2>Editing user: {{object.username}}</h2>
This form allows you to edit additional user details. Name and emails can be edited <a href="{% url 'account_update' object.username %}">here</a> This form allows you to edit additional user details. Name and emails can be edited <a href="{% url 'account_update' object.username %}">here</a>
<form method="post">{% csrf_token %} <form method="post">{% csrf_token %}
{{ form.as_p }} {{ form|crispy }}
<input type="submit" value="Update"> <input type="submit" value="Update">
</form> </form>
{% endblock %} {% endblock %}