lots of updates

This commit is contained in:
Ross
2024-12-30 13:41:55 +00:00
parent 3c72a06ad2
commit 58301dd251
16 changed files with 136 additions and 341 deletions
-10
View File
@@ -1,10 +0,0 @@
# serializers.py
from rest_framework import serializers, permissions
from .models import Exam
class ExamSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Exam
fields = ('name', 'id', 'active', "publish_results")
-20
View File
@@ -80,10 +80,6 @@ from django.template.defaulttags import register
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
from rest_framework import viewsets, permissions
from .serializers import ExamSerializer
class AuthorOrCheckerRequiredMixin(object): class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
@@ -818,22 +814,6 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
#class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# # queryset = Exam.objects.all().order_by('name')
# serializer_class = ExamSerializer
# permission_classes = [permissions.IsAuthenticated]
#
# def get_queryset(self):
# """
# This view should return a list exams available to a user.
# """
# user = self.request.user
# if user.groups.filter(name="anatomy_checker").exists():
# return Exam.objects.all()
#
# return Exam.objects.filter(author__id=user.id)
class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView): class ExamAuthorUpdate(RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
model = Exam model = Exam
form_class = ExamAuthorForm form_class = ExamAuthorForm
+9 -3
View File
@@ -55,7 +55,7 @@ from tinymce.widgets import TinyMCE
from dal import autocomplete from dal import autocomplete
from autocomplete import widgets as htmx_widgets from autocomplete import widgets as htmx_widgets, Autocomplete, AutocompleteWidget, ModelAutocomplete, register as autocomplete_register
import logging import logging
@@ -782,11 +782,17 @@ class UncategorisedDicomForm(ModelForm):
# ) # )
# } # }
class ConditionAutocomplete(ModelAutocomplete):
model = Condition
search_attrs = ["name"]
class ConditionAutocompleteForm(Form): class ConditionAutocompleteForm(Form):
condition = ModelChoiceField(queryset=Condition.objects.all(), widget=htmx_widgets.Autocomplete( condition = ModelChoiceField(queryset=Condition.objects.all(), widget=AutocompleteWidget(
name="conditions", options=dict(model=Condition) ac_class=ConditionAutocomplete#, options=dict(model=Condition)
)) ))
autocomplete_register(ConditionAutocomplete)
class CaseCollectionAuthorForm(ExamAuthorFormMixin): class CaseCollectionAuthorForm(ExamAuthorFormMixin):
class Meta(ExamAuthorFormMixin.Meta): class Meta(ExamAuthorFormMixin.Meta):
-2
View File
@@ -1,2 +0,0 @@
# serializers.py
from rest_framework import serializers, permissions
+1 -3
View File
@@ -118,7 +118,7 @@ from .filters import (
from django_tables2 import SingleTableView, SingleTableMixin from django_tables2 import SingleTableView, SingleTableMixin
from django_filters.views import FilterView from django_filters.views import FilterView
from autocomplete import HTMXAutoComplete #from autocomplete import HTMXAutoComplete
from .decorators import ( from .decorators import (
@@ -156,8 +156,6 @@ from reversion.views import RevisionMixin
import reversion import reversion
from rest_framework import viewsets
import os import os
import difflib import difflib
@@ -24,55 +24,47 @@
{% if object.anatomy_exams.all %} {% if object.anatomy_exams.all %}
<h2>Anatomy Exams:</h2> <h2>Anatomy Exams:</h2>
<ul> {% with exams=object.anatomy_exams.all app_name="anatomy" %}
{% for exam in object.anatomy_exams.all %} {% include "exam_list.html#exam-list" %}
<li><a href="{% url 'anatomy:exam_overview' exam.pk %}">{{ exam }}</a> - <a href="{% url 'anatomy:exam_update' exam.pk %}">(Edit)</a></li> {% endwith %}
{% endfor %}
</ul>
<a href="{% url 'anatomy:exam_list_collection' object.pk %}">View exam list</a> <a href="{% url 'anatomy:exam_list_collection' object.pk %}">View exam list</a>
{% endif %} {% endif %}
{% if object.longs_exams.all %} {% if object.longs_exams.all %}
<h2>Longs Exams:</h2> <h2>Longs Exams:</h2>
<ul> {% with exams=object.longs_exams.all app_name="longs" %}
{% for exam in object.longs_exams.all %} {% include "exam_list.html#exam-list" %}
<li><a href="{% url 'longs:exam_overview' exam.pk %}">{{ exam }}</a> - <a href="{% url 'longs:exam_update' exam.pk %}">(Edit)</a></li> {% endwith %}
{% endfor %}
</ul>
<a href="{% url 'longs:exam_list_collection' object.pk %}">View exam list</a> <a href="{% url 'longs:exam_list_collection' object.pk %}">View exam list</a>
{% endif %} {% endif %}
{% if object.rapids_exams.all %} {% if object.rapids_exams.all %}
<h2>Rapids Exams:</h2> <h2>Rapids Exams:</h2>
<ul> {% with exams=object.rapids_exams.all app_name="rapids" %}
{% for exam in object.rapids_exams.all %} {% include "exam_list.html#exam-list" %}
<li><a href="{% url 'rapids:exam_overview' exam.pk %}">{{ exam }}</a> - <a href="{% url 'rapids:exam_update' exam.pk %}">(Edit)</a></li> {% endwith %}
{% endfor %}
</ul>
<a href="{% url 'rapids:exam_list_collection' object.pk %}">View exam list</a> <a href="{% url 'rapids:exam_list_collection' object.pk %}">View exam list</a>
{% endif %} {% endif %}
{% if object.physics_exams.all %} {% if object.physics_exams.all %}
<h2>Physics Exams:</h2> <h2>Physics Exams:</h2>
<ul> {% with exams=object.physics_exams.all app_name="physics" %}
{% for exam in object.physics_exams.all %} {% include "exam_list.html#exam-list" %}
<li><a href="{% url 'physics:exam_overview' exam.pk %}">{{ exam }}</a> - <a href="{% url 'physics:exam_update' exam.pk %}">(Edit)</a></li> {% endwith %}
{% endfor %}
</ul>
<a href="{% url 'physics:exam_list_collection' object.pk %}">View exam list</a> <a href="{% url 'physics:exam_list_collection' object.pk %}">View exam list</a>
{% endif %} {% endif %}
{% if object.sbas_exams.all %} {% if object.sbas_exams.all %}
<h2>SBAs Exams:</h2> <h2>SBAs Exams:</h2>
<ul> {% with exams=object.sbas_exams.all app_name="sbas" %}
{% for exam in object.sbas_exams.all %} {% include "exam_list.html#exam-list" %}
<li><a href="{% url 'sbas:exam_overview' exam.pk %}">{{ exam }}</a> - <a href="{% url 'sbas:exam_update' exam.pk %}">(Edit)</a></li> {% endwith %}
{% endfor %}
</ul>
<a href="{% url 'sbas:exam_list_collection' object.pk %}">View exam list</a> <a href="{% url 'sbas:exam_list_collection' object.pk %}">View exam list</a>
{% endif %} {% endif %}
{% include 'exam_overview_js.html' %}
{% endblock %} {% endblock %}
-11
View File
@@ -1,11 +0,0 @@
# serializers.py
from rest_framework import serializers, permissions
from .models import Exam
class ExamSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Exam
fields = ('name', 'id', 'active', "publish_results")
permission_classes = [permissions.IsAuthenticated]
-10
View File
@@ -94,10 +94,6 @@ from reversion.views import RevisionMixin
import reversion import reversion
from rest_framework import viewsets
from .serializers import ExamSerializer
from zipview.views import BaseZipView from zipview.views import BaseZipView
import os import os
@@ -1005,12 +1001,6 @@ class ExamGroupsUpdate(ExamGroupsUpdateBase):
model = Exam model = Exam
form_class = ExamGroupsForm form_class = ExamGroupsForm
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# queryset = Exam.objects.all().order_by("name")
# serializer_class = ExamSerializer
def question_json_unbased(request, pk): def question_json_unbased(request, pk):
""" """
No (file based) caching is enabled for unbased quesitons No (file based) caching is enabled for unbased quesitons
-3
View File
@@ -54,9 +54,6 @@ from generic.views import (
exam_inactive, exam_inactive,
) )
from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from .forms import ExamGroupsForm, ExamMarkerForm, UserAnswerForm, ExamAuthorForm, ExamForm from .forms import ExamGroupsForm, ExamMarkerForm, UserAnswerForm, ExamAuthorForm, ExamForm
+1 -1
View File
@@ -72,7 +72,6 @@ INSTALLED_APPS = [
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.contrib.postgres", "django.contrib.postgres",
"dbbackup", "dbbackup",
"rest_framework",
"tinymce", "tinymce",
"django_unused_media", "django_unused_media",
"django_htmx", "django_htmx",
@@ -83,6 +82,7 @@ INSTALLED_APPS = [
'django_jsonforms', 'django_jsonforms',
'django_svelte_jsoneditor', 'django_svelte_jsoneditor',
'django_psutil_dash', 'django_psutil_dash',
'template_partials',
] ]
+4 -2
View File
@@ -34,7 +34,8 @@ from generic import views as generic_views
from django.conf.urls import handler400, handler403, handler404, handler500 from django.conf.urls import handler400, handler403, handler404, handler500
from .api import api from .api import api
from autocomplete import HTMXAutoComplete from autocomplete import urls as autocomplete_urls
urlpatterns = [ urlpatterns = [
@@ -153,7 +154,8 @@ urlpatterns = [
path("privacy/", views.privacy_view, name="privacy"), path("privacy/", views.privacy_view, name="privacy"),
path("about/", views.about_view, name="about"), path("about/", views.about_view, name="about"),
# path('select2/', include('select2.urls')), # path('select2/', include('select2.urls')),
*HTMXAutoComplete.url_dispatcher('ac'),
path("ac/", autocomplete_urls),
path('psutil/', psutil_urlpatterns(), name='psutil'), path('psutil/', psutil_urlpatterns(), name='psutil'),
path("cimar/", views.cimar, name="cimar"), path("cimar/", views.cimar, name="cimar"),
-39
View File
@@ -1,39 +0,0 @@
# serializers.py
from rest_framework import serializers, permissions
from .models import Exam, Answer, Rapid, UserAnswer
class ExamSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Exam
fields = ('name', 'id', 'active', "publish_results")
class QuestionAnswerSerializer(serializers.HyperlinkedModelSerializer):
#question = serializers.HyperlinkedRelatedField(view_name='rapid-detail',
# read_only=True
# )
class Meta:
model = Answer
fields = ('id', 'question', 'answer', "answer_compare", "status")
view_name = "rapid-exam"
class UserAnswerSerializer(serializers.HyperlinkedModelSerializer):
exam = serializers.HyperlinkedRelatedField(view_name='rapid-exam-detail',
read_only=True
)
class Meta:
model = UserAnswer
fields = ('id', 'question', 'normal', "answer", "answer_compare", "cid", "user", "exam", "created", "updated")
class RapidSerializer(serializers.ModelSerializer):
class Meta:
model = Rapid
fields = ('id', 'question', 'normal', "abnormality", "region", "laterality", "examination", "exams", "author", "open_access")
view_name = "rapid-detail"
class RapidLateralitySerializer(serializers.ModelSerializer):
class Meta:
model = Rapid
fields = ('id', "laterality")
view_name = "rapid-laterality"
-98
View File
@@ -83,18 +83,6 @@ from django.forms.models import model_to_dict
from rapids.forms import RapidCreationDefaultForm from rapids.forms import RapidCreationDefaultForm
from rapids.models import RapidCreationDefault from rapids.models import RapidCreationDefault
from rest_framework import viewsets, permissions, mixins
from rest_framework.pagination import PageNumberPagination
from .serializers import (
ExamSerializer,
QuestionAnswerSerializer,
RapidLateralitySerializer,
RapidSerializer,
UserAnswerSerializer,
)
from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin from generic.mixins import CheckCanEditMixin, SuperuserRequiredMixin
from dal import autocomplete from dal import autocomplete
@@ -797,92 +785,6 @@ class UserAnswerDelete(SuperuserRequiredMixin, DeleteView):
success_url = reverse_lazy("rapids:user_answer_table_view") success_url = reverse_lazy("rapids:user_answer_table_view")
class LargeResultsSetPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = "page_size"
max_page_size = 10000
class StandardResultsSetPagination(PageNumberPagination):
page_size = 100
page_size_query_param = "page_size"
max_page_size = 1000
class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# queryset = Exam.objects.all().order_by('name')
serializer_class = ExamSerializer
def get_queryset(self):
"""
This view should return a list exams available to a user.
"""
user = self.request.user
if user.groups.filter(name="rapid_checker").exists():
return Exam.objects.filter(archive=False).order_by("name")
return Exam.objects.filter(author__id=user.id, archive=False).order_by("name")
# class QuestionAnswerViewSet(viewsets.ModelViewSet):
# queryset = Answer.objects.all() # .order_by('name')
# serializer_class = QuestionAnswerSerializer
# permission_classes = [permissions.IsAuthenticated]
#
#
# class UserAnswerViewSet(viewsets.ModelViewSet):
# queryset = UserAnswer.objects.all() # .order_by('name')
# serializer_class = UserAnswerSerializer
# permission_classes = [permissions.IsAuthenticated]
# class RapidViewSet(
# RevisionMixin,
# mixins.CreateModelMixin,
# mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.ListModelMixin,
# mixins.DestroyModelMixin,
# viewsets.GenericViewSet,
# ):
# queryset = Rapid.objects.all() # .order_by('name')
# serializer_class = RapidSerializer
# permission_classes = [permissions.IsAuthenticated]
# pagination_class = StandardResultsSetPagination
# class AddSuggestedAnswer(LoginRequiredMixin, CreateView):
# model = QuestionNote
# form_class = QuestionNoteForm
#
# #fields = ("author", "note_type", "note")
#
#
# def get_form_kwargs(self):
# kwargs = super(AddQuestionNote, self).get_form_kwargs()
# # update the kwargs for the form init method with yours
# kwargs.update(self.kwargs) # self.kwargs contains all url conf params
# return kwargs
#
# def get_initial(self):
# pk = self.kwargs["pk"]
# question_type = self.kwargs["question_type"]
# if question_type == "rapid":
# question = Rapid
# content_type = ContentType.objects.get(model="rapid")
# elif question_type == "anatomy":
# question = AnatomyQuestion
# content_type = ContentType.objects.get(model="anatomyquestion")
# elif question_type == "long":
# question = Long
# content_type = ContentType.objects.get(model="long")
# else:
# raise PermissionError()
#
# get_object_or_404(question, pk=pk)
# return { "content_type" : content_type,
# "object_id" : pk }
GenericViews = GenericViewBase("rapids", Rapid, UserAnswer, Exam) GenericViews = GenericViewBase("rapids", Rapid, UserAnswer, Exam)
+2 -1
View File
@@ -51,4 +51,5 @@ pylibjpeg-libjpeg
git+https://github.com/xkjq/django-jsonforms.git@bump-json-editor-lib git+https://github.com/xkjq/django-jsonforms.git@bump-json-editor-lib
django_svelte_jsoneditor django_svelte_jsoneditor
psutil psutil
django-psutil-dash django-psutil-dash
django-template-partials
-19
View File
@@ -54,9 +54,6 @@ from generic.views import (
exam_inactive, exam_inactive,
) )
from rest_framework import viewsets, permissions
from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from .tables import QuestionTable, UserAnswerTable from .tables import QuestionTable, UserAnswerTable
@@ -411,22 +408,6 @@ class ExamDelete(AuthorOrCheckerRequiredMixin, ExamDeleteBase):
model = Exam model = Exam
# class ExamViewSet(RevisionMixin, viewsets.ModelViewSet):
# # queryset = Exam.objects.all().order_by('name')
# serializer_class = ExamSerializer
# permission_classes = [permissions.IsAuthenticated]
#
# def get_queryset(self):
# """
# This view should return a list exams available to a user.
# """
# user = self.request.user
# if user.groups.filter(name="sbas_checker").exists():
# return Exam.objects.all()
#
# return Exam.objects.filter(author__id=user.id)
class ExamAuthorUpdate( class ExamAuthorUpdate(
RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView RevisionMixin, CheckCanEditMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
): ):
+102 -94
View File
@@ -1,102 +1,31 @@
{% extends app_name|add:'/base.html' %} {% extends app_name|add:'/base.html' %}
{% block content %} {% load partials %}
<h1>Examinations</h1>
{% if collection %} {% partialdef exam-list %}
<h2>Collection: {{collection.name}}</h2> <ul class="exam-list exam-list-active">
{% endif %} {% for exam in exams %}
<li class="exam-item">
<details class="help-text"> <a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="flex-col-2 exam-name">{{exam}}</a>
<summary><i class="bi bi-info-circle"></i> Help</summary> {% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col-half">Mark</a>{% endif %}
{% if collection %} <span class="flex-col">
<p>This page shows all the examinations within the collection "{{collection.name}}".</p> <a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}">Candidates</a> <span class="candidate-counts">[<span title="Number of active CID users">
{% else %} {{exam.valid_cid_users.count}}
<p>This page shows the currently active examinations (that you have access to manage).</p> </span> / <span title="Number of active User users">{{exam.valid_user_users.count}}]</span></span>
{% endif %} </span>
<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}" class="flex-col-half">Scores</a>
<input type="checkbox" id="active-{{exam.pk}}" class="exam-active-switch" data-posturl="{% url app_name|add:':exam_toggle_active' pk=exam.pk %}" {% if exam.active %}checked{% endif %}>
<label for="active-{{exam.pk}}" class="flex-col icon-container active-icon" title="Click to toggle active state">Exam Active</label>
<input type="checkbox" id="published-{{exam.pk}}" class="exam-publish-results-switch" data-posturl="{% url app_name|add:':exam_toggle_results_published' pk=exam.pk %}" {% if exam.publish_results %}checked{% endif %}>
<label for="published-{{exam.pk}}" class="flex-col icon-container published-icon" title="Click to toggle published state">Results Published</label>
<a href="{% url app_name|add:':exam_update' pk=exam.pk %}" class="flex-shrink exam-list-edit-button"><i class="bi bi-pencil-square"></i></a>
<input type="checkbox" id="archived-{{exam.pk}}" class="exam-archived-switch" data-posturl="{% url app_name|add:':exam_toggle_archived' pk=exam.pk %}" {% if exam.archive %}checked{% endif %}>
<label for="archived-{{exam.pk}}" class="flex-shrink icon-archive archived-icon" title="Click to toggle archived state"><i class="bi bi-archive"></i></label>
</li>
{% endfor %}
</ul>
<p>Exams that are active will be available for candidates to take.</p>
<p>Once candidates have completed the exam (or submitted some answers) they will immediately be able to view their answers on the platoform. They will not be able to see the correct answers (or their scores) until the exams results have been published.</p>
<p>A users submitted answers will remain available to the user until the exam is archived. Once an exam is archived the user will no longer be able to view their submitted answers.</p>
<p>Supervisors will have access to all exam results (including archived exams) for which they have access (when "results supervisor visible" is set to true or the user has shared their results).</p>
</details>
<div class="{{app_name}}">
Exams:<br/>
<ul class="exam-list exam-list-active">
{% for exam in exams %}
<li class="exam-item">
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="flex-col-2 exam-name">{{exam}}</a>
{% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col-half">Mark</a>{% endif %}
<span class="flex-col">
<a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}">Candidates</a> <span class="candidate-counts">[<span title="Number of active CID users">
{{exam.valid_cid_users.count}}
</span> / <span title="Number of active User users">{{exam.valid_user_users.count}}]</span></span>
</span>
<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}" class="flex-col-half">Scores</a>
<input type="checkbox" id="active-{{exam.pk}}" class="exam-active-switch" data-posturl="{% url app_name|add:':exam_toggle_active' pk=exam.pk %}" {% if exam.active %}checked{% endif %}>
<label for="active-{{exam.pk}}" class="flex-col icon-container active-icon" title="Click to toggle active state">Exam Active</label>
<input type="checkbox" id="published-{{exam.pk}}" class="exam-publish-results-switch" data-posturl="{% url app_name|add:':exam_toggle_results_published' pk=exam.pk %}" {% if exam.publish_results %}checked{% endif %}>
<label for="published-{{exam.pk}}" class="flex-col icon-container published-icon" title="Click to toggle published state">Results Published</label>
<a href="{% url app_name|add:':exam_update' pk=exam.pk %}" class="flex-shrink exam-list-edit-button"><i class="bi bi-pencil-square"></i></a>
<input type="checkbox" id="archived-{{exam.pk}}" class="exam-archived-switch" data-posturl="{% url app_name|add:':exam_toggle_archived' pk=exam.pk %}" {% if exam.archive %}checked{% endif %}>
<label for="archived-{{exam.pk}}" class="flex-shrink icon-archive archived-icon" title="Click to toggle archived state"><i class="bi bi-archive"></i></label>
</li>
{% endfor %}
</ul>
{% comment %} Inactive exams:<br/>
<ul class="exam-list exam-list-inactive">
{% for exam in exams %}
{% if not exam.active %}
<li class="exam-item">
<span class="flex-col-2">
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="exam-name">{{exam}}</a>
{% if exam.examcollection %}
<a href='{% url "generic:examcollection_detail" exam.examcollection_id %}' title="This exam is part of the collection {{exam.examcollection.name}}"><i class="bi bi-collection"></i></a>
{% endif %}
</span>
{% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col-half" title="Click to mark exam">Mark</a>{% endif %}
<span class="flex-col">
<a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}" title="Click to manage candidates">Candidates</a>
<span class="candidate-counts">
[<span title="Number of active CID users">{{exam.valid_cid_users.count}}</span>
/
<span title="Number of active User users">{{exam.valid_user_users.count}}]
</span>
</span>
</span>
<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}" class="flex-col-half">Scores</a>
<input type="checkbox" id="active-{{exam.pk}}" class="exam-active-switch" data-posturl="{% url app_name|add:':exam_toggle_active' pk=exam.pk %}" {% if exam.active %}checked{% endif %}>
<label for="active-{{exam.pk}}" class="flex-col icon-container active-icon" title="Click to toggle active state">Exam Active</label>
<input type="checkbox" id="{{exam.pk}}-pub" class="exam-publish-results-switch" data-posturl="{% url app_name|add:':exam_toggle_results_published' pk=exam.pk %}" {% if exam.publish_results %}checked{% endif %}>
<label for="{{exam.pk}}-pub" class="flex-col icon-container published-icon">Results Published</label>
<a href="{% url app_name|add:':exam_update' pk=exam.pk %}" class="flex-shrink exam-list-edit-button" title="Click to edit exam"><i class="bi bi-pencil-square"></i></a>
<input type="checkbox" id="archived-{{exam.pk}}" class="exam-archived-switch" data-posturl="{% url app_name|add:':exam_toggle_archived' pk=exam.pk %}" {% if exam.archive %}checked{% endif %}>
<label for="archived-{{exam.pk}}" class="flex-shrink icon-archive archived-icon" title="Click to toggle archived state"><i class="bi bi-archive"></i></label>
</li>
{% endif %}
{% endfor %}
</ul> {% endcomment %}
{% if collection %}
{% else %}
{% if view_all %}
<a href="{% url app_name|add:':exam_list' %}" >Hide archived</a>
{% else %}
<a href="{% url app_name|add:':exam_list_all' %}" title="Show all exams (including archived)">View all</a>
{% endif %}
{% endif %}
</div>
{% include 'exam_overview_js.html' %}
<style> <style>
.candidate-counts { .candidate-counts {
@@ -170,6 +99,85 @@
flex-shrink: 1; flex-shrink: 1;
} }
</style> </style>
{% endpartialdef %}
{% block content %}
<h1>Examinations</h1>
{% if collection %}
<h2>Collection: {{collection.name}}</h2>
{% endif %}
<details class="help-text">
<summary><i class="bi bi-info-circle"></i> Help</summary>
{% if collection %}
<p>This page shows all the examinations within the collection "{{collection.name}}".</p>
{% else %}
<p>This page shows the currently active examinations (that you have access to manage).</p>
{% endif %}
<p>Exams that are active will be available for candidates to take.</p>
<p>Once candidates have completed the exam (or submitted some answers) they will immediately be able to view their answers on the platoform. They will not be able to see the correct answers (or their scores) until the exams results have been published.</p>
<p>A users submitted answers will remain available to the user until the exam is archived. Once an exam is archived the user will no longer be able to view their submitted answers.</p>
<p>Supervisors will have access to all exam results (including archived exams) for which they have access (when "results supervisor visible" is set to true or the user has shared their results).</p>
</details>
<div class="{{app_name}}">
Exams:<br/>
{% partial exam-list %}
{% comment %} Inactive exams:<br/>
<ul class="exam-list exam-list-inactive">
{% for exam in exams %}
{% if not exam.active %}
<li class="exam-item">
<span class="flex-col-2">
<a href="{% url app_name|add:':exam_overview' pk=exam.pk %}" class="exam-name">{{exam}}</a>
{% if exam.examcollection %}
<a href='{% url "generic:examcollection_detail" exam.examcollection_id %}' title="This exam is part of the collection {{exam.examcollection.name}}"><i class="bi bi-collection"></i></a>
{% endif %}
</span>
{% if marking %}<a href="{% url app_name|add:':mark_overview' pk=exam.pk %}" class="flex-col-half" title="Click to mark exam">Mark</a>{% endif %}
<span class="flex-col">
<a href="{% url app_name|add:':exam_cids' exam_id=exam.pk %}" title="Click to manage candidates">Candidates</a>
<span class="candidate-counts">
[<span title="Number of active CID users">{{exam.valid_cid_users.count}}</span>
/
<span title="Number of active User users">{{exam.valid_user_users.count}}]
</span>
</span>
</span>
<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}" class="flex-col-half">Scores</a>
<input type="checkbox" id="active-{{exam.pk}}" class="exam-active-switch" data-posturl="{% url app_name|add:':exam_toggle_active' pk=exam.pk %}" {% if exam.active %}checked{% endif %}>
<label for="active-{{exam.pk}}" class="flex-col icon-container active-icon" title="Click to toggle active state">Exam Active</label>
<input type="checkbox" id="{{exam.pk}}-pub" class="exam-publish-results-switch" data-posturl="{% url app_name|add:':exam_toggle_results_published' pk=exam.pk %}" {% if exam.publish_results %}checked{% endif %}>
<label for="{{exam.pk}}-pub" class="flex-col icon-container published-icon">Results Published</label>
<a href="{% url app_name|add:':exam_update' pk=exam.pk %}" class="flex-shrink exam-list-edit-button" title="Click to edit exam"><i class="bi bi-pencil-square"></i></a>
<input type="checkbox" id="archived-{{exam.pk}}" class="exam-archived-switch" data-posturl="{% url app_name|add:':exam_toggle_archived' pk=exam.pk %}" {% if exam.archive %}checked{% endif %}>
<label for="archived-{{exam.pk}}" class="flex-shrink icon-archive archived-icon" title="Click to toggle archived state"><i class="bi bi-archive"></i></label>
</li>
{% endif %}
{% endfor %}
</ul> {% endcomment %}
{% if collection %}
{% else %}
{% if view_all %}
<a href="{% url app_name|add:':exam_list' %}" >Hide archived</a>
{% else %}
<a href="{% url app_name|add:':exam_list_all' %}" title="Show all exams (including archived)">View all</a>
{% endif %}
{% endif %}
</div>
{% include 'exam_overview_js.html' %}
{% endblock %} {% endblock %}