some refactoring and atlas improvements

This commit is contained in:
Ross
2023-07-24 11:56:12 +01:00
parent e6469e70f0
commit dd300afd63
26 changed files with 521 additions and 170 deletions
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0003_alter_anatomyquestion_modality_delete_modality'),
]
operations = [
migrations.AddField(
model_name='exam',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
@@ -0,0 +1,36 @@
# Generated by Django 4.1.4 on 2023-07-24 09:06
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0006_alter_ciduserexam_completed'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('atlas', '0004_casecollection_self_review'),
]
operations = [
migrations.AddField(
model_name='casecollection',
name='user_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='casecollection_user_user_groups', to='generic.userusergroup'),
),
migrations.AddField(
model_name='casecollection',
name='valid_user_users',
field=models.ManyToManyField(blank=True, related_name='user_casecollection_exams', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='casecollection',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author of the collection', related_name='casecollection_authored_cases', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='casecollection',
name='cid_user_groups',
field=models.ManyToManyField(blank=True, help_text='These groups define which candidates are able to be added to the exams/collection.', related_name='casecollection_cid_user_groups', to='generic.cidusergroup'),
),
]
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0005_casecollection_user_user_groups_and_more'),
]
operations = [
migrations.AddField(
model_name='casecollection',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='casecollection',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='casecollection',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='casecollection',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='casecollection',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='casecollection',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
+15 -42
View File
@@ -2,7 +2,7 @@ import json
import os
import pathlib
from django.http import Http404
from django.http import Http404, HttpRequest
from rad.settings import REMOTE_URL
from django.db.models.fields.files import ImageField
from django.db.models.fields.related import ForeignKey
@@ -46,6 +46,7 @@ from generic.models import (
SeriesBase,
SeriesImageBase,
Modality,
UserUserGroup,
)
# from generic.models import Examination, Site, Condition, Sign
@@ -413,18 +414,8 @@ class Series(SeriesBase):
class CaseCollection(ExamCollectionGenericBase):
app_name = "atlas"
name = models.CharField(max_length=255, unique=True)
cases = models.ManyToManyField(Case, through="CaseDetail")
publish_results = models.BooleanField(
help_text="If a collection should published", default=False
)
active = models.BooleanField(
help_text="If a collection should be available", default=True
)
show_title_pre = models.BooleanField(
default=False, help_text="Show the title of the cases (pre exam)"
)
@@ -461,20 +452,31 @@ class CaseCollection(ExamCollectionGenericBase):
settings.AUTH_USER_MODEL,
blank=True,
help_text="Author of the collection",
# related_name="atlas_authored_cases",
related_name="casecollection_authored_cases",
)
valid_cid_users = models.ManyToManyField(
CidUser, blank=True, related_name="casecollection_exams"
)
valid_user_users = models.ManyToManyField(
settings.AUTH_USER_MODEL, blank=True, related_name="user_casecollection_exams"
)
cid_user_groups = models.ManyToManyField(
CidUserGroup,
blank=True,
help_text="These groups define which candidates are able to be added to the exams/collection.",
related_name="casecollection_cid_user_groups",
)
user_user_groups = models.ManyToManyField(
UserUserGroup,
blank=True,
help_text="These groups define which candidates are able to be added to the exams/collection.",
related_name="casecollection_user_user_groups",
)
archive = models.BooleanField(default=False)
exam_mode = models.BooleanField(default=False)
# This should override the publish setting
@@ -483,11 +485,6 @@ class CaseCollection(ExamCollectionGenericBase):
help_text="If true allows users self complete and review cases in a self directed way.",
)
open_access = models.BooleanField(
help_text="If the exam is freely accessible (to view and edit on the test system)",
default=False,
)
class COLLECTION_TYPE_CHOICES(models.TextChoices):
REVIEW = (
"REV",
@@ -525,30 +522,6 @@ class CaseCollection(ExamCollectionGenericBase):
return False
def check_user_can_take(self, cid, passcode, request):
"""
Helper to check if a user is allowed to access a collection
Args:
cid (_type_): _description_
passcode (_type_): _description_
request (_type_): _description_
Raises:
Http404: If user does not have access
"""
if not self.active:
raise Http404("Exam not found")
#if self.collection_is_review():
# return True
#if not self.self_review:
if not self.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
class CaseDetail(models.Model):
case = models.ForeignKey(Case, on_delete=models.CASCADE)
collection = models.ForeignKey(CaseCollection, on_delete=models.CASCADE)
+2 -2
View File
@@ -273,7 +273,7 @@ class SubspecialtyTable(tables.Table):
class CaseCollectionTable(tables.Table):
edit = tables.LinkColumn(
"atlas:collection_update", text="Edit", args=[A("pk")], orderable=False
"atlas:exam_update", text="Edit", args=[A("pk")], orderable=False
)
view = tables.LinkColumn(
"atlas:collection_detail", text="View", args=[A("pk")], orderable=False
@@ -282,7 +282,7 @@ class CaseCollectionTable(tables.Table):
# "atlas:case_clone", text="Clone", args=[A("pk")], orderable=False
#)
delete = tables.LinkColumn(
"atlas:collection_delete", text="Delete", args=[A("pk")], orderable=False
"atlas:exam_deleted", text="Delete", args=[A("pk")], orderable=False
)
#differential = tables.ManyToManyColumn(verbose_name="Differential")
+3 -10
View File
@@ -4,16 +4,6 @@
Atlas
{% endblock %}
{% block css %}
{% endblock %}
{% block js %}
{% endblock %}
{% block content %}
{% endblock %}
{% block navigation %}
Atlas:
{% if request.user.is_authenticated %}
@@ -28,3 +18,6 @@
Questions by:
<span id="authors-link"><a href="{% url 'atlas:author_list' %}">author</a></span> {% endcomment %}
{% endblock %}
{% block content %}
{% endblock %}
+1 -1
View File
@@ -53,7 +53,7 @@
Use this form to create a atlas case. Existing associated image sets can be added using this form.
{% if form.collection %}
<div class="alert alert-info" role="alert">Creating a case in the collection: <a href="{% url 'atlas:collection_detail' pk=form.collection.pk %}">{{form.collection.name}}</a></div>
<div class="alert alert-info" role="alert">Creating a case in the collection: <a href="{% url 'atlas:collection_detail' pk=form.exam.pk %}">{{form.collection.name}}</a></div>
{% endif %}
<form action="" method="post" enctype="multipart/form-data" id="atlas-form">
{% csrf_token %}
@@ -9,7 +9,7 @@
{% if collection.publish_results or cid_user_exam.completed %}
review
<span class="stamp-white">REVIEW</span>
{% endif %}
@@ -6,8 +6,8 @@
<a href="{% url 'atlas:exam_cids' collection.pk %}">Candidates</a> /
<a href="{% url 'atlas:atlas_create_exam' pk=collection.pk %}">Add New Case</a>
<div class="floating-header">
<a href="{% url 'atlas:collection_update' collection.id %}" title="Edit the Collection">Edit</a>
\ <a href="{% url 'atlas:collection_delete' collection.id %}" title="Delete the Collection">Delete</a>
<a href="{% url 'atlas:exam_update' collection.id %}" title="Edit the Collection">Edit</a>
\ <a href="{% url 'atlas:exam_deleted' collection.id %}" title="Delete the Collection">Delete</a>
{% comment %} \ <a href="{% url 'atlas:collection_clone' collection.id %}" title="Clone the Collection">Clone</a> {% endcomment %}
{% if request.user.is_superuser %}
\ <a href="{% url 'admin:atlas_casecollection_change' collection.id %}" title="Edit the Collection using the admin interface">Admin Edit</a>
@@ -1,7 +1,7 @@
{% extends 'atlas/base.html' %}
{% block content %}
<a href="{% url 'atlas:collection_create' %}">Create collection</a>
<a href="{% url 'atlas:exam_create' %}">Create collection</a>
<ul>
{% for collection in collections %}
<li><a href='{{collection.get_absolute_url}}'>{{collection.name}}</a></li>
@@ -8,16 +8,15 @@
<h2>Collection: {{collection.name}}
{% if collection.publish_results or cid_user_exam.completed %}
review
<span class="stamp-white">REVIEW</span>
{% endif %}
</h2>
<div><p>Questions</p></div>
{{answer_count}} out of {{collection_length}} cases answered. Click to go to case.
<div class="sba-finish-list">
<ul>
{% for question, answer in question_answer_tuples %}
<li>
<li> <span {% if not answer %}class="unanswered"{% endif %}>
<a href="
{% if cid is not None %}
{% url 'atlas:collection_case_view_take' pk=collection.id case_number=forloop.counter0 cid=cid passcode=passcode %}
@@ -25,10 +24,15 @@
{% url 'atlas:collection_case_view_take_user' pk=collection.id case_number=forloop.counter0 %}
{% endif %}
">
<span {% if not answer %}class="unanswered"{% endif %}>Case: {{forloop.counter}}</span>
Case: {{forloop.counter}}
</a>
<br/>
{{answer.answer}}
{% if not answer %}
No answer
{% else %}
{{answer.answer}}
{% endif %}
</span>
</li>
{% endfor %}
</ul>
@@ -1,18 +1,26 @@
{% extends 'atlas/base.html' %}
{% block content %}
<h2>Start: {{collection.name}}</h2>
<h2>Start: {{collection.name}}{% if cid_exam.completed %} <span class="stamp-white">REVIEW</span>{% endif %}</h2>
{% if request.user.is_authenticated and valid_user %}
User: {{request.user}}<br/>
{% if cid_exam %}
Started: {{cid_exam.start_time}} <br/>
{% if cid_exam.completed %}
Ended: {{cid_exam.end_time}} <br/>
{% endif %}
{% endif %}
<a href="{% url 'atlas:collection_case_view_take_user' pk=collection.pk case_number=0 %}">
{% if cid_exam.completed %}
<button>Review</button>
{% else %}
<button>Start</button>
{% endif %}
</a>
{% else %}
Enter your CID and passcode in the below boxes.<br />
@@ -0,0 +1,17 @@
{% extends "atlas/exams.html" %}
{% block js %}
{{ form.media }}
{% endblock %}
{% block content %}
<h2>Update Exam</h2>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
{% endblock %}
+2 -2
View File
@@ -9,8 +9,8 @@
<a href="{% url 'atlas:collection_scores_cid' exam.pk %}">Scores</a> /
<a href="{% url 'atlas:exam_cids' exam.pk %}">Candidates</a>
<div class="floating-header">
<a href="{% url 'atlas:collection_update' exam.id %}" title="Edit the Collection">Edit</a>
\ <a href="{% url 'atlas:collection_delete' exam.id %}" title="Delete the Collection">Delete</a>
<a href="{% url 'atlas:exam_update' exam.id %}" title="Edit the Collection">Edit</a>
\ <a href="{% url 'atlas:exam_deleted' exam.id %}" title="Delete the Collection">Delete</a>
{% if request.user.is_superuser %}
\ <a href="{% url 'admin:atlas_casecollection_change' exam.id %}" title="Edit the Collection using the admin interface">Admin Edit</a>
{% endif %}
+18 -3
View File
@@ -21,17 +21,17 @@ urlpatterns = [
path(
"collection/create",
views.CaseCollectionCreate.as_view(),
name="collection_create",
name="exam_create",
),
path(
"collection/<int:pk>/delete",
views.CaseCollectionDelete.as_view(),
name="collection_delete",
name="exam_deleted",
),
path(
"collection/<int:pk>/update",
views.CaseCollectionUpdate.as_view(),
name="collection_update",
name="exam_update",
),
path("collection/<int:pk>", views.collection_detail, name="collection_detail"),
path(
@@ -44,6 +44,16 @@ urlpatterns = [
views.GenericExamViews.exam_cids,
name="exam_cids",
),
path(
"exam/<int:exam_id>/cids/edit",
views.GenericExamViews.exam_cids_edit,
name="exam_cids_edit",
),
path(
"exam/<int:exam_id>/users/edit",
views.GenericExamViews.exam_users_edit,
name="exam_users_edit",
),
path(
"collection/<int:exam_id>/cids/<int:cid>/delete_answers",
views.delete_collection_cid_answers,
@@ -59,6 +69,11 @@ urlpatterns = [
views.GenericExamViews.exam_cids_edit,
name="exam_cids_edit",
),
path(
"collection/<int:exam_id>/users/edit",
views.GenericExamViews.exam_users_edit,
name="exam_users_edit",
),
path(
"collection/<int:pk>/mark",
views.collection_mark_overview,
+3
View File
@@ -442,6 +442,7 @@ class SeriesCreate(RevisionMixin, LoginRequiredMixin, CreateView):
class CaseCollectionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateView):
model = CaseCollection
form_class = CaseCollectionForm
template_name = "atlas/collection_update_form.html"
# fields = '__all__'
# #fields = [ 'condition' ]
@@ -467,6 +468,8 @@ class CaseCollectionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateVi
context["case_formset"] = CaseCollectionCaseFormSet(
instance=self.object, form_kwargs={"user": self.request.user}
)
context["collection"] = context["casecollection"]
return context
def form_valid(self, form):
+62 -26
View File
@@ -4,7 +4,7 @@ import os
from typing import Self, Tuple
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models
from django.http import HttpRequest
from django.http import Http404, HttpRequest
from django.utils import timezone
from smtplib import SMTPException
@@ -393,6 +393,30 @@ class ExamCollectionGenericBase(models.Model):
# Is this actually used?
cid_users = GenericRelation("generic.CidUserExam")
name = models.CharField(max_length=200, help_text="Name of the exam/collection")
active = models.BooleanField(
help_text="If an exam/collection should be available to take", default=False
)
publish_results = models.BooleanField(
help_text="If an exam/collections results should be available", default=False
)
archive = models.BooleanField(
help_text="Archived exams/collections will remain on the test system but will not be displayed by default",
default=False,
)
open_access = models.BooleanField(
help_text="If the exam/collection is freely accessible (to view and edit on the test system)",
default=False,
)
candidates_only = models.BooleanField(
help_text="If the exam/collection is only available to candidates who have been added",
default=True,
)
class Meta:
abstract = True
@@ -412,6 +436,42 @@ class ExamCollectionGenericBase(models.Model):
"""Helper to check if the logged in user can access the exam"""
return self.check_cid_user(request=request, user_id=request.user.id)
def check_user_can_review(
self, cid, passcode, request: HttpRequest, active_only=False
):
return self.check_user_can_take(cid, passcode, request, active_only)
def check_user_can_take(
self, cid, passcode, request: HttpRequest, active_only=True
):
"""
Helper to check if a user is allowed to access an exam/collection
Args:
cid (_type_): _description_
passcode (_type_): _description_
request (_type_): _description_
Raises:
Http404: If user does not have access
"""
# If it is not active no one can take
print(0)
if active_only and not self.active:
raise Http404("Exam not found")
print(1)
# If candidates only check they have access
if self.candidates_only:
if not self.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
return
print(2)
# Otherwise check that they are a valid user.
if not request.user.is_active:
raise Http404("Error accessing exam")
def check_cid_user(
self,
cid: int | None = None,
@@ -524,13 +584,6 @@ class ExamCollectionGenericBase(models.Model):
class ExamBase(ExamCollectionGenericBase):
name = models.CharField(max_length=200, help_text="Name of the exam")
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
active = models.BooleanField(
help_text="If an exam should be available to take", default=False
)
exam_mode = models.BooleanField(
help_text="If an exam should be taken in exam mode (users results will be submited to the server for marking)",
default=False,
@@ -541,10 +594,6 @@ class ExamBase(ExamCollectionGenericBase):
default=False,
)
publish_results = models.BooleanField(
help_text="If an exams results should be available", default=False
)
recreate_json = models.BooleanField(
help_text="If the json cache needs updating", default=False
)
@@ -554,16 +603,6 @@ class ExamBase(ExamCollectionGenericBase):
default=1, help_text="auto incrementing field when json recreated"
)
archive = models.BooleanField(
help_text="Archived exams will remain on the test system but will not be displayed by default",
default=False,
)
open_access = models.BooleanField(
help_text="If the exam is freely accessible (to view and edit on the test system)",
default=False,
)
authors_only = models.BooleanField(
help_text="If true only exam authors will be able to view.",
default=False,
@@ -1090,10 +1129,7 @@ class CidUserExam(models.Model):
else:
end_time = f"{ self.end_time:%Y-%m-%d %H:%M }"
return (
f"{user}: {start_time} {end_time}"
)
return f"{user}: {start_time} {end_time}"
CID_GROUP_EXAMS = (
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('longs', '0002_alter_longseries_modality'),
]
operations = [
migrations.AddField(
model_name='exam',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('physics', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='exam',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
+3 -15
View File
@@ -103,12 +103,7 @@ def active_exams(request):
def exam_scores_cid_user(request, pk, cid=None, passcode=None):
exam = get_object_or_404(Exam, pk=pk)
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
print(cid, passcode, request.user)
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
exam.check_user_can_review(cid, passcode, request)
questions = exam.exam_questions.all()
@@ -208,11 +203,7 @@ def exam_start(request, pk):
def exam_take_overview(request, pk, cid=None, passcode=None):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
return exam_inactive(request, context={"exam": exam})
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
exam.check_user_can_take(cid, passcode, request)
questions = exam.exam_questions.all()
@@ -258,10 +249,7 @@ def exam_take(request, pk: int, sk: int, cid: str| None = None, passcode: str| N
if not exam.active:
return exam_inactive(request, context={"exam": exam})
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
print(cid, passcode, request.user)
exam.check_user_can_take(cid, passcode, request)
cid_user_exam = exam.get_or_create_cid_user_exam(cid=cid, user_user=request.user)
+28 -3
View File
@@ -750,10 +750,19 @@ input {
min-width: 10px;
}
*/
.sba-finish-list a {
text-decoration: none;
color: unset;
}
.sba-finish-list span {
color: lightblue;
}
.sba-finish-list span.unanswered {
color: gray;
.sba-finish-list a button.unanswered {
color: red;
border-style: dashed;
}
.no-select {
@@ -975,3 +984,19 @@ h1, h2, h3, h4, h5, h6 {
.cid-candidate-list div {
}
.stamp-white {
display: inline-block;
z-index:1;
font-family:Arial,sans-serif;
transform: rotate(-15deg);
font-size:20px;
color:white;
border:solid 2px white;
padding:5px;
border-radius:5px;
zoom:1;
filter:alpha(opacity=20);
opacity:0.9;
text-shadow: 0 0 2px white;
box-shadow: 0 0 2px white;
}
+1 -1
View File
@@ -9,7 +9,7 @@ from generic.models import CidUser, CidUserGroup, UserUserGroup, ExamBase
from generic.views import ExamViews
from django.contrib.auth.models import User
from django.urls import reverse
from rich.pretty import pprint
def AssertNotFound(client, url):
"""Helper to quickly test for urls that should return a 404 (NOT FOUND) error"""
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rapids', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='exam',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
@@ -1,5 +1,5 @@
<div class="rapid {% if question.scrapped %}rapid-scrapped{% endif %}">
<button id="edit-button">Inline Edit</button>
{% comment %} <button id="edit-button">Inline Edit</button> {% endcomment %}
<div class="date">
{{ question.created_date|date:"d/m/Y" }}
</div>
@@ -9,7 +9,7 @@
<p class="pre-whitespace"><b>Rapid:</b> {{ question }}</p>
<p class="pre-whitespace"><b>Normal:</b> {{ question.normal }} <button id="toggle-normal-button"
class="toggle-button">toggle</button></p>
class="toggle-button">toggle</button></p>
<p class="pre-whitespace"><b>Region:</b> {{ question.get_regions }}</p>
<p class="pre-whitespace"><b>Examination:</b> {{ question.get_examinations }}</p>
<p class="pre-whitespace"><b>Laterality:</b> {{ question.laterality }}
@@ -24,7 +24,7 @@
<span class="image-block">
Image {{ forloop.counter }}{% if image.description %} ({{image.description}}){% endif %}{% if image.feedback_image %} [feedback image]{% endif %}:
<div class="dicom-image rapid-img {% if image.feedback_image %}feedback-img{% endif %}"
data-url="https://www.penracourses.org.uk{{ image.image.url}}"></div>
data-url="https://www.penracourses.org.uk{{ image.image.url}}"></div>
</span>
{% endfor %}
</div>
@@ -33,11 +33,11 @@
<a href="{% url 'rapids:exam_overview' pk=exam.pk %}">{{ exam.name }}</a>,
{% endfor %}
<button id="add-to-exam" data-exam_json_edit_url="{% url 'generic:generic_exam_json_edit' %}"
{% comment %} <button id="add-to-exam" data-exam_json_edit_url="{% url 'generic:generic_exam_json_edit' %}"
data-exam_list_url="{% url 'api-1:rapid_user_exams' %}" data-type="rapids" data-csrf="{{ csrf_token}}"
data-qid="{{question.pk}}">Add to exam</button>
<button id="cancel-add-to-exam" style="display:none">Cancel</button>
<span id="exam-options"></span>
<span id="exam-options"></span> {% endcomment %}
</div>
<p class="pre-whitespace"><b>Open Access:</b> {{ question.open_access }}</p>
<p class="pre-whitespace"><b>Feedback:</b> {{ question.feedback }}</p>
@@ -59,7 +59,7 @@
<tr>
<td>
<span {% if answer.proposed %}class="proposed-answer" data-aid="{{answer.pk}}" data-question-type="rapid"
{% endif %}>
{% endif %}>
{{ answer }}
</span>
<td>
@@ -94,7 +94,7 @@
Image viewer
</summary>
<div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ question.get_image_url_array }}"
data-annotations='{{question.get_image_annotations}}'>
data-annotations='{{question.get_image_annotations}}'>
</div>
<button id="save-annotations" class="save-rapid-annotations">Save Annotations</button>
</details>
@@ -110,7 +110,7 @@
{% endfor %}
</details>
<a href="{% url 'rapids:question_anonymise_dicom' pk=question.pk %}"
title="Anonymise dicom images">Anonymise dicoms</a><br />
title="Anonymise dicom images">Anonymise dicoms</a><br />
<script>
$(document).ready(function () {
$("#edit-button").click(() => {
@@ -156,21 +156,21 @@
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
console.log(data);
// show some message according to the response.
// For eg. A message box showing that the status has been changed
if (data.status == "success") {
toastr.info('Annotations saved')
} else {
toastr.warning('Error saving annotations')
}
})
if (data.status == "success") {
toastr.info('Annotations saved')
} else {
toastr.warning('Error saving annotations')
}
})
.always(function () {
console.log('[Done]');
})
console.log('[Done]');
})
})
$("#cancel-add-to-exam").click(function (evt) {
{% comment %} $("#cancel-add-to-exam").click(function (evt) {
$("#add-to-exam").toggle();
$("#cancel-add-to-exam").toggle();
$("#exam-options").empty();
@@ -296,7 +296,8 @@
alert('Error updating this model instance.');
});
});
});
});
{% endcomment %}
$(".suggested_answers li").each((n, el) => {
$(el).append($("<span class='correct'>[Add Correct]</span>").on("click", function () {
@@ -315,18 +316,18 @@
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".correct").remove()
}
if (data.success) {
toastr.info('Answer saved')
$(el).find(".correct").remove()
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
})
.always(function () {
console.log('[Done]');
})
console.log('[Done]');
})
}))
});
@@ -350,19 +351,19 @@
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
})
.always(function () {
console.log('[Done]');
})
console.log('[Done]');
})
}))
// Add button to confirm answer is incorrect
@@ -381,19 +382,19 @@
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
console.log(data);
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
if (data.success) {
toastr.info('Answer saved')
$(el).find(".confirm").remove()
$(el).removeClass("proposed-answer")
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
})
.always(function () {
console.log('[Done]');
})
console.log('[Done]');
})
}))
});
});
@@ -0,0 +1,43 @@
# Generated by Django 4.1.4 on 2023-07-24 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sbas', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='exam',
name='candidates_only',
field=models.BooleanField(default=True, help_text='If the exam/collection is only available to candidates who have been added'),
),
migrations.AlterField(
model_name='exam',
name='active',
field=models.BooleanField(default=False, help_text='If an exam/collection should be available to take'),
),
migrations.AlterField(
model_name='exam',
name='archive',
field=models.BooleanField(default=False, help_text='Archived exams/collections will remain on the test system but will not be displayed by default'),
),
migrations.AlterField(
model_name='exam',
name='name',
field=models.CharField(help_text='Name of the exam/collection', max_length=200),
),
migrations.AlterField(
model_name='exam',
name='open_access',
field=models.BooleanField(default=False, help_text='If the exam/collection is freely accessible (to view and edit on the test system)'),
),
migrations.AlterField(
model_name='exam',
name='publish_results',
field=models.BooleanField(default=False, help_text='If an exam/collections results should be available'),
),
]
+3 -9
View File
@@ -102,8 +102,7 @@ def active_exams(request):
def exam_scores_cid_user(request, pk, cid, passcode):
exam = get_object_or_404(Exam, pk=pk)
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
exam.check_user_can_review(cid, passcode, request)
questions = exam.exam_questions.all()
@@ -179,8 +178,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None):
if not exam.active:
return exam_inactive(request, context={"exam": exam})
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
exam.check_user_can_take(cid, passcode, request)
questions = exam.exam_questions.all()
@@ -222,11 +220,7 @@ def exam_take_overview(request, pk, cid=None, passcode=None):
def exam_take(request, pk: int, sk: int, cid: int = None, passcode: str = None):
exam = get_object_or_404(Exam, pk=pk)
if not exam.active:
raise Http404("Exam not found")
if not exam.check_cid_user(cid, passcode, request):
raise Http404("Error accessing exam")
exam.check_user_can_take(cid, passcode, request)
cid_user_exam = exam.get_or_create_cid_user_exam(cid = cid, user_user=request.user)