diff --git a/TODO.md b/TODO.md
index f451b742..0b08a2ca 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,5 +1,6 @@
# Current target
- User management
+- SQUASH MIGRATIONS
# Ongoing
diff --git a/generic/models.py b/generic/models.py
index 1cf4611d..bc81e96c 100644
--- a/generic/models.py
+++ b/generic/models.py
@@ -74,6 +74,12 @@ class QuestionBase(models.Model):
class Meta:
abstract = True
+ def get_unanswered_mark_and_text(self) -> tuple(int, str):
+ """
+ Override in models if needed
+ """
+ return (0, "Not answered")
+
class ExamBase(models.Model):
name = models.CharField(max_length=200, help_text="Name of the exam")
diff --git a/generic/views.py b/generic/views.py
index 962783cc..90414fd0 100644
--- a/generic/views.py
+++ b/generic/views.py
@@ -1687,7 +1687,7 @@ class ExamViews(View, LoginRequiredMixin):
else:
cid_user = None
exam.exam_user_status.create(
- cid_user=cid, user_user=user, status="downloaded", extra="unbased"
+ cid_user=cid_user, user_user=user, status="downloaded", extra="unbased"
)
return JsonResponse(exam_json)
@@ -1725,6 +1725,7 @@ class ExamViews(View, LoginRequiredMixin):
question = get_object_or_404(self.Question, pk=sk)
exam = get_object_or_404(self.Exam, pk=pk)
+
if not exam.active and not self.check_user_access(request.user, pk):
raise Http404("No available exam")
@@ -1733,6 +1734,7 @@ class ExamViews(View, LoginRequiredMixin):
user_id = None
else:
user_id = request.user.pk
+
if not exam.check_cid_user(cid, passcode, request, user_id):
raise Http404("No available exam")
@@ -1795,10 +1797,11 @@ class ExamViews(View, LoginRequiredMixin):
if not user_answer or user_answer is None:
# skip if no answer
- answers_marks.append(0)
+ score, text = q.get_unanswered_mark_and_text()
+ answers_marks.append(score)
# answers.append("")
- answer_score = 0
- ans = "Not answered"
+ answer_score = score
+ ans = text
else:
if exam.app_name == "rapids" and user_answer.normal:
ans = "Normal"
diff --git a/longs/models.py b/longs/models.py
index dee8bb37..555a43b1 100644
--- a/longs/models.py
+++ b/longs/models.py
@@ -655,35 +655,6 @@ class Exam(ExamBase):
exam_user_status = GenericRelation(ExamUserStatus)
- def get_exam_question_json(self, question_id):
- q = get_object_or_404(Long, pk=question_id)
-
- # exam_order.append(q.id)
-
- # Loop through longimage associations
- images = []
- image_titles = []
- for series in q.series.all():
- # image_array = []
- # for i in series.images.all():
- # image_array.append(image_as_base64(i.image))
- # #image_array.append(i.image.url)
- image_array = [image_as_base64(i.image) for i in series.images.all()]
- images.append(image_array)
- image_titles.append(series.get_examination())
-
- exam_question = {
- "title": q.history,
- "images": images,
- "image_titles": image_titles,
- # "feedback_image": [],
- # "annotations": [str(q.image_annotations)],
- "type": "long",
- "images_json": True,
- }
-
- return exam_question
-
def get_exam_json(self, based=True):
questions = self.exam_questions.all()
diff --git a/longs/tables.py b/longs/tables.py
index df8c0d95..1aaca779 100755
--- a/longs/tables.py
+++ b/longs/tables.py
@@ -4,6 +4,7 @@ from django_tables2.utils import A
from .models import Long, LongSeries, UserAnswer
from django.utils.html import format_html
+from django.db.models import Prefetch
from easy_thumbnails.files import get_thumbnailer
@@ -12,10 +13,12 @@ from easy_thumbnails.exceptions import InvalidImageFormatError
class LongImageColumn(tables.Column):
def render(self, value):
- blocks =[]
+ blocks = []
for obj in value.all():
blocks.append(obj.get_block())
- return format_html("{}".format("".join(blocks)))
+ return format_html(
+ "{}".format("".join(blocks))
+ )
obj = value.first()
@@ -42,9 +45,17 @@ class LongSeriesImageColumn(tables.Column):
image_object = obj[0].image
try:
thumbnailer = get_thumbnailer(image_object)
- return format_html('
[{}]', thumbnailer["exam-list"], value.count())
+ return format_html(
+ '
[{}]',
+ thumbnailer["exam-list"],
+ value.count(),
+ )
except InvalidImageFormatError:
- return format_html('Invalid image url
[{}]', image_object, value.count())
+ return format_html(
+ 'Invalid image url
[{}]',
+ image_object,
+ value.count(),
+ )
class LongTable(tables.Table):
@@ -62,7 +73,7 @@ class LongTable(tables.Table):
)
series = LongImageColumn("Images", orderable=False)
- #series = tables.ManyToManyColumn(verbose_name="Exams")
+ # series = tables.ManyToManyColumn(verbose_name="Exams")
exams = tables.ManyToManyColumn(verbose_name="Exams")
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
@@ -76,12 +87,19 @@ class LongTable(tables.Table):
def __init__(self, data=None, *args, **kwargs):
super().__init__(
data.prefetch_related(
- "series", "author", "exams", "series__images", "series__examination", "series__plane", "series__contrast"
+ "series",
+ "author",
+ "exams",
+ "series__images",
+ "series__examination",
+ "series__plane",
+ "series__contrast",
),
*args,
**kwargs,
)
+
class PopupLinkColumn(tables.Column):
def render(self, value):
return format_html("test: {}", value)
@@ -120,7 +138,14 @@ class LongSeriesTable(tables.Table):
def __init__(self, data=None, *args, **kwargs):
super().__init__(
data.prefetch_related(
- "long", "long__series", "images", "author", "examination", "plane", "contrast", "modality"
+ "long",
+ "long__series",
+ "images",
+ "author",
+ "examination",
+ "plane",
+ "contrast",
+ "modality",
),
*args,
**kwargs,
@@ -128,17 +153,21 @@ class LongSeriesTable(tables.Table):
def render_popup(self, value, record):
print(self)
- return format_html("""Popup""", record.pk)
+ return format_html(
+ """Popup""",
+ record.pk,
+ )
+
class UserAnswerTable(tables.Table):
select = tables.CheckBoxColumn(accessor=("pk"))
delete = tables.LinkColumn(
"longs:user_answer_delete", text="Delete", args=[A("pk")], orderable=False
)
- view = tables.LinkColumn('longs:user_answer_view',
- text='View',
- args=[A('pk')],
- orderable=False)
+ view = tables.LinkColumn(
+ "longs:user_answer_view", text="View", args=[A("pk")], orderable=False
+ )
+
class Meta:
model = UserAnswer
template_name = "django_tables2/bootstrap4.html"
@@ -146,11 +175,24 @@ class UserAnswerTable(tables.Table):
"cid",
"question",
"score",
- #"normal",
- #"answer",
- #"answer_compare",
+ # "normal",
+ # "answer",
+ # "answer_compare",
"exam",
"created",
"updated",
)
-
\ No newline at end of file
+
+ def __init__(self, data=None, *args, **kwargs):
+ super().__init__(
+ data.prefetch_related(
+ Prefetch(
+ "question", # "question__exams"#, "author", "exams", "series__images", "series__examination", "series__plane", "series__contrast"
+ #queryset=Long.objects.select_related().prefetch_related(
+ # Prefetch("exams")
+ #),
+ )
+ ),
+ *args,
+ **kwargs,
+ )
diff --git a/longs/tests/test_longs_exams.py b/longs/tests/test_longs_exams.py
index 553b5335..8899d211 100644
--- a/longs/tests/test_longs_exams.py
+++ b/longs/tests/test_longs_exams.py
@@ -203,7 +203,21 @@ def test_exams(db, client):
res = client.get(exam_metadata["url"])
assert res.status_code == 200
exam_json = json.loads(res.content)
- print(exam_json)
+
+ # We can also check the unbased questions
+ res = client.get(exam_metadata["url"] + "/unbased")
+ assert res.status_code == 200
+
+ exam_json_unbased = json.loads(res.content)
+
+ # This show we have different types (int vs string)
+ assert (
+ set([str(i) for i in exam_json["exam_order"]])
+ == set(exam_json["questions"].keys())
+ == set(exam_json["question_requests"].keys())
+ == set([str(i) for i in exam_json_unbased["exam_order"]])
+ == set(exam_json_unbased["questions"].keys())
+ )
for q in exam_json["question_requests"]:
print("check question:", q)
@@ -212,37 +226,48 @@ def test_exams(db, client):
question_json_res = client.get(
reverse(
f"longs:exam_question_json_cid",
- kwargs={"pk": exam.pk, "sk": q, "cid": cid_user.cid, "passcode": cid_user.passcode},
- ), follow=True
+ kwargs={
+ "pk": exam.pk,
+ "sk": q,
+ "cid": cid_user.cid,
+ "passcode": cid_user.passcode,
+ },
+ ),
+ follow=True,
)
- #assert question_json_res.status_code == 200
- print(question_json_res.redirect_chain)
- print(question_json_res.content)
+ # assert question_json_res.status_code == 200
assert len(question_json_res.redirect_chain) == 2
assert question_json_res.redirect_chain[0][1] == 302
assert question_json_res.redirect_chain[1][1] == 302
assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json")
-
question_json_unbased_res = client.get(
reverse(
f"longs:exam_question_json_unbased_cid",
- kwargs={"pk": exam.pk, "sk": q, "cid": cid_user.cid, "passcode": cid_user.passcode},
- ), follow=True
+ kwargs={
+ "pk": exam.pk,
+ "sk": q,
+ "cid": cid_user.cid,
+ "passcode": cid_user.passcode,
+ },
+ ),
+ follow=True,
)
assert question_json_unbased_res.status_code == 200
question_json = json.loads(question_json_unbased_res.content)
-
- #question_json_res = client.get(question1.get_question_json(answers=False, based=True))
- #print(question_json)
+ # question_json_res = client.get(question1.get_question_json(answers=False, based=True))
- assert (
- question_json
- in exam_json["questions"].values()
+ # As we store references to the questions we have to check individually
+ assert q in exam_json["questions"].keys()
+
+ q_json = Question.objects.get(pk=q).get_question_json(
+ based=False
)
+ assert question_json == q_json
+
assert len(exam_json["questions"]) == 3
exam.active = False
@@ -303,26 +328,26 @@ def test_exams(db, client):
for questions in exam_json["questions"]:
for qid in questions:
- post_data = {
- "eid": exam_json["eid"],
- "cid": post_cid,
- "qid": qid,
- "qidn": "1",
- "ans": f"Abnormal",
- }
- post_data2 = {
- "eid": exam_json["eid"],
- "cid": post_cid,
- "qid": qid,
- "qidn": "2",
- "ans": f"answer {qid}",
- }
- if testing_cid_user:
- post_data["passcode"] = cid_user.passcode
- post_data2["passcode"] = cid_user.passcode
+ # Long answers submit 5 sections
+ sections = (
+ ("1", "answer observations"),
+ ("2", "answer interpretation"),
+ ("3", "answer principle diagnosis"),
+ ("4", "answer differential diagnosis"),
+ ("5", "answer management"),
+ )
+ for n, name in sections:
+ post_data = {
+ "eid": exam_json["eid"],
+ "cid": post_cid,
+ "qid": qid,
+ "qidn": n,
+ "ans": f"{name} - {q}",
+ }
+ if testing_cid_user:
+ post_data["passcode"] = cid_user.passcode
- valid_answers_extra.append(post_data)
- valid_answers.append(post_data2)
+ valid_answers_extra.append(post_data)
post_json = {
"eid": exam_json["eid"],
@@ -337,8 +362,8 @@ def test_exams(db, client):
json_res = json.loads(res.content)
- assert json_res["success"] == True
- assert json_res["question_count"] == 6
+ assert json_res["success"] is True
+ assert json_res["question_count"] == 5 * 3
# Check the answers now exist in the database
if testing_cid_user:
diff --git a/longs/views.py b/longs/views.py
index a4868864..af014048 100755
--- a/longs/views.py
+++ b/longs/views.py
@@ -1153,6 +1153,7 @@ def question_json_unbased(request, pk):
"""
No (file based) caching is enabled for unbased quesitons
"""
+ print("UNBASED")
question = get_object_or_404(Long, pk=pk)
question_json = question.get_question_json(based=False)
diff --git a/rapids/tests/test_rapids_exams.py b/rapids/tests/test_rapids_exams.py
index b334bf7a..06203a0c 100644
--- a/rapids/tests/test_rapids_exams.py
+++ b/rapids/tests/test_rapids_exams.py
@@ -290,8 +290,8 @@ def test_exams(db, client):
json_res = json.loads(res.content)
- assert json_res["success"] == True
- assert json_res["question_count"] == 6
+ assert json_res["success"] is True
+ assert json_res["question_count"] == 2 * 3
# Check the answers now exist in the database
if testing_cid_user: