diff --git a/generic/templates/generic/exam_index.html b/generic/templates/generic/exam_index.html
index 6b94e95d..09246716 100644
--- a/generic/templates/generic/exam_index.html
+++ b/generic/templates/generic/exam_index.html
@@ -10,7 +10,7 @@
-
diff --git a/generic/views.py b/generic/views.py
index 351ee52d..39aa8d58 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -318,14 +318,15 @@ class ExamViews(View, LoginRequiredMixin):
# },
# },
# }
+
def __init__(self, data=None, *args, **kwargs):
# if filterset is bound, use initial values as defaults
if data is not None:
# get a mutable copy of the QueryDict
data = data.copy()
- #for name, f in self.base_filters.items():
- # filter param is either missing or empty, use initial as default
+ # for name, f in self.base_filters.items():
+ # filter param is either missing or empty, use initial as default
if not data.get("archive"):
data["archive"] = False
@@ -796,6 +797,8 @@ class ExamViews(View, LoginRequiredMixin):
app_exam_map = {}
app_exam_map["rapids"] = cid_user.rapid_exams
+ app_exam_map["anatomy"] = cid_user.anatomy_exams
+ app_exam_map["longs"] = cid_user.long_exams
app_exam_map["atlas"] = cid_user.casecollection_exams
if add:
@@ -815,6 +818,8 @@ class ExamViews(View, LoginRequiredMixin):
app_exam_map = {}
app_exam_map["rapids"] = user_user.user_rapid_exams
+ app_exam_map["anatomy"] = user_user.user_anatomy_exams
+ app_exam_map["longs"] = user_user.user_longs_exams
# app_exam_map["atlas"] = user_user.casecollection_exams
if add:
@@ -967,7 +972,8 @@ class ExamViews(View, LoginRequiredMixin):
# self.check_user_access(request.user, exam.pk)
if (
- (
+ request.user.is_superuser
+ or (
self.app_name == "rapids"
and request.user.groups.filter(name="rapid_checker").exists()
)
@@ -986,7 +992,11 @@ class ExamViews(View, LoginRequiredMixin):
filter = self.ExamFilter(request.GET, queryset=exams)
- return render(request, "generic/exam_index.html", {"filter": filter, "app_name": self.app_name})
+ return render(
+ request,
+ "generic/exam_index.html",
+ {"filter": filter, "app_name": self.app_name},
+ )
@method_decorator(login_required)
def exam_question_detail(self, request, pk, sk):
@@ -2064,3 +2074,26 @@ class UserUserGroupDelete(RevisionMixin, CidManagerRequiredMixin, DeleteView):
model = UserUserGroup
template_name = "confirm_delete.html"
success_url = reverse_lazy("generic:user_group_view")
+
+
+class ExamCreateBase(RevisionMixin, LoginRequiredMixin, CreateView):
+ template_name = "exam_create_form.html"
+
+ def form_valid(self, form):
+ self.object = form.save(commit=False)
+ self.object.save()
+
+ form.instance.author.add(self.request.user.id)
+ return super().form_valid(form)
+
+
+class ExamUpdateBase(RevisionMixin, LoginRequiredMixin, UpdateView):
+ template_name = "exam_update_form.html"
+
+ def form_valid(self, form):
+ self.object = form.save(commit=False)
+ self.object.save()
+
+ form.instance.author.add(self.request.user.id)
+
+ return super().form_valid(form)
diff --git a/rapids/templates/rapids/exam_form.html b/rapids/templates/rapids/exam_form.html
index 72bca3df..9901ffac 100755
--- a/rapids/templates/rapids/exam_form.html
+++ b/rapids/templates/rapids/exam_form.html
@@ -1,4 +1,4 @@
-{% extends "rapids/exams.html" %}
+{% extends "rapids/base.html" %}
{% block js %}
{{ form.media }}
@@ -6,12 +6,12 @@
{% block content %}
-
Add Exam
-
+
Add Exam
+
{% endblock %}
diff --git a/rapids/tests/test_urls.py b/rapids/tests/test_urls.py
index ffb93b99..0fe027d8 100644
--- a/rapids/tests/test_urls.py
+++ b/rapids/tests/test_urls.py
@@ -1,24 +1,122 @@
+import json
import pytest
-#from django.contrib.auth.models import User
+
+# from django.contrib.auth.models import User
from django.urls import reverse
+from rich.pretty import pprint
+
+from bs4 import BeautifulSoup
+
+from rapids.views import GenericExamViews as RapidExamViews
+from sbas.views import GenericExamViews as SbaExamViews
+from longs.views import GenericExamViews as LongExamViews
+from physics.views import GenericExamViews as PhysicExamViews
+from anatomy.views import GenericExamViews as AnatomyExamViews
+
APP_NAMES = ("rapids", "anatomy", "longs", "physics", "sbas")
+JSON_APPS = ("rapids", "anatomy", "longs")
+
+EXAM_VIEWS = (
+ RapidExamViews,
+ SbaExamViews,
+ LongExamViews,
+ PhysicExamViews,
+ AnatomyExamViews,
+)
+
+
@pytest.fixture
def create_superuser(db, django_user_model):
- return django_user_model.objects.create_superuser("admin", "ross@xkjq.uk", "adminpassword")
+ return django_user_model.objects.create_superuser(
+ "admin", "ross@xkjq.uk", "adminpassword"
+ )
-#@pytest.fixture
-#def create_exam(db):
+
+# @pytest.fixture
+# def create_exam(db):
# return Exam.objects.create(name="test exam", exam_mode=True)
# Very basic tests, make sure we can hit key urls without error
-@pytest.mark.parametrize("app_name", APP_NAMES)
-def test_index(client, create_superuser, app_name):
+
+@pytest.mark.parametrize("exam_views", EXAM_VIEWS)
+def test_index(client, create_superuser, exam_views, django_user_model):
+
+ basic_user = django_user_model.objects.create_user(
+ "user1", "user@user.net", "pass2"
+ )
+
+ e1 = exam_views.Exam.objects.create(name="test exam", exam_mode=True)
+ e = exam_views.Exam.objects.create(name="test exam 2", exam_mode=True)
+ e.author.add(basic_user)
+
client.login(username="admin", password="adminpassword")
- response = client.get(reverse(f'{app_name}:index'))
- print(reverse(f'{app_name}:index'))
- print(response.status_code)
- print(response.content)
- assert response.status_code == 200
\ No newline at end of file
+ response = client.get(reverse(f"{exam_views.app_name}:index"))
+ assert response.status_code == 200
+
+ assert (
+ BeautifulSoup(response.content, "html.parser")
+ .find("h4", {"class": "exam-number-title"})
+ .getText()
+ == "2 exams found."
+ )
+
+ if exam_views.app_name in JSON_APPS:
+ response = client.get(reverse(f"{exam_views.app_name}:active_exams"))
+
+ json_response = json.loads(response.content)["exams"]
+
+ assert len(json_response) == 2
+
+ for json_exam in json_response:
+ eid_app_type, eid = json_exam["eid"].split("/")
+ # hack for long / longs mismathc
+ assert (
+ eid_app_type == exam_views.app_name
+ or eid_app_type == exam_views.app_name[:-1]
+ )
+
+ assert exam_views.Exam.objects.get(pk=int(eid)).name == json_exam["name"]
+
+ add_user_response = client.post(
+ reverse(f"{exam_views.app_name}:exam_json_edit", args=(e1.pk,)),
+ {"edit_user_user": basic_user.pk, "add": "true"},
+ HTTP_X_REQUESTED_WITH="XMLHttpRequest",
+ )
+
+ print(f"{e1.valid_user_users.all()=}")
+
+ assert json.loads(add_user_response.content)["status"] == "success"
+
+ client.logout()
+
+ client.force_login(basic_user)
+
+ response = client.get(reverse(f"{exam_views.app_name}:index"))
+ assert response.status_code == 200
+ assert (
+ BeautifulSoup(response.content, "html.parser")
+ .find("h4", {"class": "exam-number-title"})
+ .getText()
+ == "1 exams found."
+ )
+
+ if exam_views.app_name in JSON_APPS:
+ response = client.get(reverse(f"{exam_views.app_name}:active_exams"))
+
+ json_response = json.loads(response.content)["exams"]
+
+ assert len(json_response) == 1
+
+ e1.active = True
+ e1.save()
+
+ response = client.get(reverse(f"{exam_views.app_name}:active_exams"))
+
+ json_response = json.loads(response.content)["exams"]
+
+ print(json_response)
+
+ assert len(json_response) == 2
diff --git a/rapids/views.py b/rapids/views.py
index b289b520..5f3cec54 100755
--- a/rapids/views.py
+++ b/rapids/views.py
@@ -42,7 +42,14 @@ from .models import (
CidUserAnswer,
)
-from generic.views import AuthorRequiredMixin, ExamCloneMixin, ExamViews, GenericViewBase
+from generic.views import (
+ AuthorRequiredMixin,
+ ExamCloneMixin,
+ ExamCreateBase,
+ ExamUpdateBase,
+ ExamViews,
+ GenericViewBase,
+)
from reversion.views import RevisionMixin
@@ -350,8 +357,8 @@ class RapidUpdate(
def form_valid(self, form):
# save exam orders (there must be a better way to do this)
- #exam_orders = {}
- #for exam in self.object.exams.all():
+ # exam_orders = {}
+ # for exam in self.object.exams.all():
# exam_orders[exam] = list(exam.exam_questions.all())
self.object = form.save(commit=False)
@@ -371,8 +378,8 @@ class RapidUpdate(
answer_formset.save()
# restore exam orders
- #wanted_exams = form["exams"].data
- #for exam in self.object.exams.all():
+ # wanted_exams = form["exams"].data
+ # for exam in self.object.exams.all():
# if exam in exam_orders and self.object in exam_orders[exam]:
# print(exam_orders[exam])
# exam.exam_questions.set(exam_orders[exam])
@@ -677,7 +684,6 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
)
-
def exam_scores_cid_user(request, pk, cid, passcode):
exam = get_object_or_404(Exam, pk=pk)
@@ -686,13 +692,15 @@ def exam_scores_cid_user(request, pk, cid, passcode):
# TODO:Need some kind of test for cid
- questions = exam.exam_questions.all()#.prefetch_related("cid_user_answers", "answers")
+ questions = (
+ exam.exam_questions.all()
+ ) # .prefetch_related("cid_user_answers", "answers")
- #cid_user_answers = list(CidUserAnswer.objects.filter(cid=cid, exam__id=pk).prefetch_related("question"))
+ # cid_user_answers = list(CidUserAnswer.objects.filter(cid=cid, exam__id=pk).prefetch_related("question"))
- #cid_user_answers_q_map = {}
+ # cid_user_answers_q_map = {}
- #for ans in cid_user_answers:
+ # for ans in cid_user_answers:
# cid_user_answers_q_map[ans.question] = ans
answers_and_marks = []
@@ -706,7 +714,7 @@ def exam_scores_cid_user(request, pk, cid, passcode):
for q in questions:
# Get user answer
user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
- #user_answer = cid_user_answers_q_map[q]
+ # user_answer = cid_user_answers_q_map[q]
if not user_answer or user_answer is None:
# skip if no answer
@@ -766,49 +774,36 @@ class QuestionDelete(AuthorOrCheckerRequiredMixin, DeleteView):
success_url = reverse_lazy("rapids:rapid_view")
-GenericExamViews = ExamViews(
- Exam, Rapid, Answer, CidUserAnswer, "rapids", "rapid"
-)
+GenericExamViews = ExamViews(Exam, Rapid, Answer, CidUserAnswer, "rapids", "rapid")
-class ExamCreate(RevisionMixin, LoginRequiredMixin, CreateView):
+class ExamCreate(ExamCreateBase):
model = Exam
form_class = ExamForm
- def form_valid(self, form):
- self.object = form.save(commit=False)
- self.object.save()
- form.instance.author.add(self.request.user.id)
- return super().form_valid(form)
+class ExamUpdate(ExamUpdateBase, AuthorOrCheckerRequiredMixin):
+ model = Exam
+ form_class = ExamForm
-class ExamUpdate(
- RevisionMixin, LoginRequiredMixin, AuthorOrCheckerRequiredMixin, UpdateView
+class ExamAuthorUpdate(
+ RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView
):
- model = Exam
- form_class = ExamForm
-
- def form_valid(self, form):
- self.object = form.save(commit=False)
- self.object.save()
-
- form.instance.author.add(self.request.user.id)
-
- return super().form_valid(form)
-
-class ExamAuthorUpdate(RevisionMixin, LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
model = Exam
form_class = ExamAuthorForm
+
class ExamDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
model = Exam
template_name = "exam_confirm_delete.html"
success_url = reverse_lazy("rapids:index")
+
class UserAnswerView(LoginRequiredMixin, DetailView):
model = CidUserAnswer
+
class UserAnswerTableView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = CidUserAnswer
table_class = RapidUserAnswerTable
@@ -925,6 +920,7 @@ class RapidLateralityViewSet(
GenericViews = GenericViewBase("rapids", Rapid, CidUserAnswer, Exam)
+
@user_is_author_or_rapid_checker
def question_anonymise_dicom(request, pk):
question = get_object_or_404(Rapid, pk=pk)
@@ -933,6 +929,7 @@ def question_anonymise_dicom(request, pk):
return redirect("rapids:question_detail", pk=pk)
+
@user_is_author_or_rapid_checker
def question_save_annotation(request, pk):
if request.is_ajax() and request.method == "POST":
diff --git a/requirements.txt b/requirements.txt
index e2066674..46fdc8fc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -26,4 +26,6 @@ django-querysetsequence
dicognito
django_unused_media
pytest-django
-gunicorn
\ No newline at end of file
+gunicorn
+rich
+bs4
\ No newline at end of file
diff --git a/templates/exam_create_form.html b/templates/exam_create_form.html
new file mode 100644
index 00000000..887f9ed1
--- /dev/null
+++ b/templates/exam_create_form.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+{% comment %} {% extends exam.app_name|add:"/base.html" %} {% endcomment %}
+
+{% block js %}
+ {{ form.media }}
+{% endblock %}
+
+{% block content %}
+{{form.instance}}
+
+
Add Exam
+
+{% endblock %}
+
diff --git a/templates/exam_update_form.html b/templates/exam_update_form.html
new file mode 100644
index 00000000..79aea4d2
--- /dev/null
+++ b/templates/exam_update_form.html
@@ -0,0 +1,18 @@
+{% extends exam.app_name|add:"/exams.html" %}
+
+{% block js %}
+ {{ form.media }}
+{% endblock %}
+
+{% block content %}
+
+
Update Exam
+
+{% endblock %}
+