more Long improvements
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Current target
|
# Current target
|
||||||
- User management
|
- User management
|
||||||
|
- SQUASH MIGRATIONS
|
||||||
|
|
||||||
|
|
||||||
# Ongoing
|
# Ongoing
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ class QuestionBase(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
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):
|
class ExamBase(models.Model):
|
||||||
name = models.CharField(max_length=200, help_text="Name of the exam")
|
name = models.CharField(max_length=200, help_text="Name of the exam")
|
||||||
|
|||||||
+7
-4
@@ -1687,7 +1687,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
else:
|
else:
|
||||||
cid_user = None
|
cid_user = None
|
||||||
exam.exam_user_status.create(
|
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)
|
return JsonResponse(exam_json)
|
||||||
@@ -1725,6 +1725,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
question = get_object_or_404(self.Question, pk=sk)
|
question = get_object_or_404(self.Question, pk=sk)
|
||||||
exam = get_object_or_404(self.Exam, pk=pk)
|
exam = get_object_or_404(self.Exam, pk=pk)
|
||||||
|
|
||||||
|
|
||||||
if not exam.active and not self.check_user_access(request.user, pk):
|
if not exam.active and not self.check_user_access(request.user, pk):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
@@ -1733,6 +1734,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
user_id = None
|
user_id = None
|
||||||
else:
|
else:
|
||||||
user_id = request.user.pk
|
user_id = request.user.pk
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, passcode, request, user_id):
|
if not exam.check_cid_user(cid, passcode, request, user_id):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
@@ -1795,10 +1797,11 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
|
|
||||||
if not user_answer or user_answer is None:
|
if not user_answer or user_answer is None:
|
||||||
# skip if no answer
|
# skip if no answer
|
||||||
answers_marks.append(0)
|
score, text = q.get_unanswered_mark_and_text()
|
||||||
|
answers_marks.append(score)
|
||||||
# answers.append("")
|
# answers.append("")
|
||||||
answer_score = 0
|
answer_score = score
|
||||||
ans = "Not answered"
|
ans = text
|
||||||
else:
|
else:
|
||||||
if exam.app_name == "rapids" and user_answer.normal:
|
if exam.app_name == "rapids" and user_answer.normal:
|
||||||
ans = "Normal"
|
ans = "Normal"
|
||||||
|
|||||||
@@ -655,35 +655,6 @@ class Exam(ExamBase):
|
|||||||
|
|
||||||
exam_user_status = GenericRelation(ExamUserStatus)
|
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):
|
def get_exam_json(self, based=True):
|
||||||
questions = self.exam_questions.all()
|
questions = self.exam_questions.all()
|
||||||
|
|
||||||
|
|||||||
+58
-16
@@ -4,6 +4,7 @@ from django_tables2.utils import A
|
|||||||
from .models import Long, LongSeries, UserAnswer
|
from .models import Long, LongSeries, UserAnswer
|
||||||
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
|
from django.db.models import Prefetch
|
||||||
|
|
||||||
from easy_thumbnails.files import get_thumbnailer
|
from easy_thumbnails.files import get_thumbnailer
|
||||||
|
|
||||||
@@ -12,10 +13,12 @@ from easy_thumbnails.exceptions import InvalidImageFormatError
|
|||||||
|
|
||||||
class LongImageColumn(tables.Column):
|
class LongImageColumn(tables.Column):
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
blocks =[]
|
blocks = []
|
||||||
for obj in value.all():
|
for obj in value.all():
|
||||||
blocks.append(obj.get_block())
|
blocks.append(obj.get_block())
|
||||||
return format_html("<span class='multi-image-block'>{}</span>".format("".join(blocks)))
|
return format_html(
|
||||||
|
"<span class='multi-image-block'>{}</span>".format("".join(blocks))
|
||||||
|
)
|
||||||
|
|
||||||
obj = value.first()
|
obj = value.first()
|
||||||
|
|
||||||
@@ -42,9 +45,17 @@ class LongSeriesImageColumn(tables.Column):
|
|||||||
image_object = obj[0].image
|
image_object = obj[0].image
|
||||||
try:
|
try:
|
||||||
thumbnailer = get_thumbnailer(image_object)
|
thumbnailer = get_thumbnailer(image_object)
|
||||||
return format_html('<img src="/media/{}" /><br/>[{}]', thumbnailer["exam-list"], value.count())
|
return format_html(
|
||||||
|
'<img src="/media/{}" /><br/>[{}]',
|
||||||
|
thumbnailer["exam-list"],
|
||||||
|
value.count(),
|
||||||
|
)
|
||||||
except InvalidImageFormatError:
|
except InvalidImageFormatError:
|
||||||
return format_html('<span title="{}">Invalid image url<span><br/>[{}]', image_object, value.count())
|
return format_html(
|
||||||
|
'<span title="{}">Invalid image url<span><br/>[{}]',
|
||||||
|
image_object,
|
||||||
|
value.count(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LongTable(tables.Table):
|
class LongTable(tables.Table):
|
||||||
@@ -62,7 +73,7 @@ class LongTable(tables.Table):
|
|||||||
)
|
)
|
||||||
series = LongImageColumn("Images", orderable=False)
|
series = LongImageColumn("Images", orderable=False)
|
||||||
|
|
||||||
#series = tables.ManyToManyColumn(verbose_name="Exams")
|
# series = tables.ManyToManyColumn(verbose_name="Exams")
|
||||||
exams = tables.ManyToManyColumn(verbose_name="Exams")
|
exams = tables.ManyToManyColumn(verbose_name="Exams")
|
||||||
|
|
||||||
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
|
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
|
||||||
@@ -76,12 +87,19 @@ class LongTable(tables.Table):
|
|||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
data.prefetch_related(
|
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,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PopupLinkColumn(tables.Column):
|
class PopupLinkColumn(tables.Column):
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
return format_html("<span>test: {}<span>", value)
|
return format_html("<span>test: {}<span>", value)
|
||||||
@@ -120,7 +138,14 @@ class LongSeriesTable(tables.Table):
|
|||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
data.prefetch_related(
|
data.prefetch_related(
|
||||||
"long", "long__series", "images", "author", "examination", "plane", "contrast", "modality"
|
"long",
|
||||||
|
"long__series",
|
||||||
|
"images",
|
||||||
|
"author",
|
||||||
|
"examination",
|
||||||
|
"plane",
|
||||||
|
"contrast",
|
||||||
|
"modality",
|
||||||
),
|
),
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -128,17 +153,21 @@ class LongSeriesTable(tables.Table):
|
|||||||
|
|
||||||
def render_popup(self, value, record):
|
def render_popup(self, value, record):
|
||||||
print(self)
|
print(self)
|
||||||
return format_html("""<a href="#" onclick="return window.create_popup_window('/longs/series/{}', 'Series')" >Popup</a>""", record.pk)
|
return format_html(
|
||||||
|
"""<a href="#" onclick="return window.create_popup_window('/longs/series/{}', 'Series')" >Popup</a>""",
|
||||||
|
record.pk,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserAnswerTable(tables.Table):
|
class UserAnswerTable(tables.Table):
|
||||||
select = tables.CheckBoxColumn(accessor=("pk"))
|
select = tables.CheckBoxColumn(accessor=("pk"))
|
||||||
delete = tables.LinkColumn(
|
delete = tables.LinkColumn(
|
||||||
"longs:user_answer_delete", text="Delete", args=[A("pk")], orderable=False
|
"longs:user_answer_delete", text="Delete", args=[A("pk")], orderable=False
|
||||||
)
|
)
|
||||||
view = tables.LinkColumn('longs:user_answer_view',
|
view = tables.LinkColumn(
|
||||||
text='View',
|
"longs:user_answer_view", text="View", args=[A("pk")], orderable=False
|
||||||
args=[A('pk')],
|
)
|
||||||
orderable=False)
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserAnswer
|
model = UserAnswer
|
||||||
template_name = "django_tables2/bootstrap4.html"
|
template_name = "django_tables2/bootstrap4.html"
|
||||||
@@ -146,11 +175,24 @@ class UserAnswerTable(tables.Table):
|
|||||||
"cid",
|
"cid",
|
||||||
"question",
|
"question",
|
||||||
"score",
|
"score",
|
||||||
#"normal",
|
# "normal",
|
||||||
#"answer",
|
# "answer",
|
||||||
#"answer_compare",
|
# "answer_compare",
|
||||||
"exam",
|
"exam",
|
||||||
"created",
|
"created",
|
||||||
"updated",
|
"updated",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -203,7 +203,21 @@ def test_exams(db, client):
|
|||||||
res = client.get(exam_metadata["url"])
|
res = client.get(exam_metadata["url"])
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
exam_json = json.loads(res.content)
|
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"]:
|
for q in exam_json["question_requests"]:
|
||||||
print("check question:", q)
|
print("check question:", q)
|
||||||
@@ -212,37 +226,48 @@ def test_exams(db, client):
|
|||||||
question_json_res = client.get(
|
question_json_res = client.get(
|
||||||
reverse(
|
reverse(
|
||||||
f"longs:exam_question_json_cid",
|
f"longs:exam_question_json_cid",
|
||||||
kwargs={"pk": exam.pk, "sk": q, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
kwargs={
|
||||||
), follow=True
|
"pk": exam.pk,
|
||||||
|
"sk": q,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
follow=True,
|
||||||
)
|
)
|
||||||
#assert question_json_res.status_code == 200
|
# assert question_json_res.status_code == 200
|
||||||
print(question_json_res.redirect_chain)
|
|
||||||
print(question_json_res.content)
|
|
||||||
assert len(question_json_res.redirect_chain) == 2
|
assert len(question_json_res.redirect_chain) == 2
|
||||||
assert question_json_res.redirect_chain[0][1] == 302
|
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][1] == 302
|
||||||
|
|
||||||
assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json")
|
assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json")
|
||||||
|
|
||||||
|
|
||||||
question_json_unbased_res = client.get(
|
question_json_unbased_res = client.get(
|
||||||
reverse(
|
reverse(
|
||||||
f"longs:exam_question_json_unbased_cid",
|
f"longs:exam_question_json_unbased_cid",
|
||||||
kwargs={"pk": exam.pk, "sk": q, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
kwargs={
|
||||||
), follow=True
|
"pk": exam.pk,
|
||||||
|
"sk": q,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
follow=True,
|
||||||
)
|
)
|
||||||
assert question_json_unbased_res.status_code == 200
|
assert question_json_unbased_res.status_code == 200
|
||||||
question_json = json.loads(question_json_unbased_res.content)
|
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 (
|
# As we store references to the questions we have to check individually
|
||||||
question_json
|
assert q in exam_json["questions"].keys()
|
||||||
in exam_json["questions"].values()
|
|
||||||
|
q_json = Question.objects.get(pk=q).get_question_json(
|
||||||
|
based=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert question_json == q_json
|
||||||
|
|
||||||
assert len(exam_json["questions"]) == 3
|
assert len(exam_json["questions"]) == 3
|
||||||
|
|
||||||
exam.active = False
|
exam.active = False
|
||||||
@@ -303,26 +328,26 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
for questions in exam_json["questions"]:
|
for questions in exam_json["questions"]:
|
||||||
for qid in questions:
|
for qid in questions:
|
||||||
post_data = {
|
# Long answers submit 5 sections
|
||||||
"eid": exam_json["eid"],
|
sections = (
|
||||||
"cid": post_cid,
|
("1", "answer observations"),
|
||||||
"qid": qid,
|
("2", "answer interpretation"),
|
||||||
"qidn": "1",
|
("3", "answer principle diagnosis"),
|
||||||
"ans": f"Abnormal",
|
("4", "answer differential diagnosis"),
|
||||||
}
|
("5", "answer management"),
|
||||||
post_data2 = {
|
)
|
||||||
"eid": exam_json["eid"],
|
for n, name in sections:
|
||||||
"cid": post_cid,
|
post_data = {
|
||||||
"qid": qid,
|
"eid": exam_json["eid"],
|
||||||
"qidn": "2",
|
"cid": post_cid,
|
||||||
"ans": f"answer {qid}",
|
"qid": qid,
|
||||||
}
|
"qidn": n,
|
||||||
if testing_cid_user:
|
"ans": f"{name} - {q}",
|
||||||
post_data["passcode"] = cid_user.passcode
|
}
|
||||||
post_data2["passcode"] = cid_user.passcode
|
if testing_cid_user:
|
||||||
|
post_data["passcode"] = cid_user.passcode
|
||||||
|
|
||||||
valid_answers_extra.append(post_data)
|
valid_answers_extra.append(post_data)
|
||||||
valid_answers.append(post_data2)
|
|
||||||
|
|
||||||
post_json = {
|
post_json = {
|
||||||
"eid": exam_json["eid"],
|
"eid": exam_json["eid"],
|
||||||
@@ -337,8 +362,8 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
json_res = json.loads(res.content)
|
json_res = json.loads(res.content)
|
||||||
|
|
||||||
assert json_res["success"] == True
|
assert json_res["success"] is True
|
||||||
assert json_res["question_count"] == 6
|
assert json_res["question_count"] == 5 * 3
|
||||||
|
|
||||||
# Check the answers now exist in the database
|
# Check the answers now exist in the database
|
||||||
if testing_cid_user:
|
if testing_cid_user:
|
||||||
|
|||||||
@@ -1153,6 +1153,7 @@ def question_json_unbased(request, pk):
|
|||||||
"""
|
"""
|
||||||
No (file based) caching is enabled for unbased quesitons
|
No (file based) caching is enabled for unbased quesitons
|
||||||
"""
|
"""
|
||||||
|
print("UNBASED")
|
||||||
question = get_object_or_404(Long, pk=pk)
|
question = get_object_or_404(Long, pk=pk)
|
||||||
|
|
||||||
question_json = question.get_question_json(based=False)
|
question_json = question.get_question_json(based=False)
|
||||||
|
|||||||
@@ -290,8 +290,8 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
json_res = json.loads(res.content)
|
json_res = json.loads(res.content)
|
||||||
|
|
||||||
assert json_res["success"] == True
|
assert json_res["success"] is True
|
||||||
assert json_res["question_count"] == 6
|
assert json_res["question_count"] == 2 * 3
|
||||||
|
|
||||||
# Check the answers now exist in the database
|
# Check the answers now exist in the database
|
||||||
if testing_cid_user:
|
if testing_cid_user:
|
||||||
|
|||||||
Reference in New Issue
Block a user