generalise a QuestionBase
This commit is contained in:
+2
-9
@@ -16,7 +16,7 @@ from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
import string
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, ExamUserStatus, Examination, ExamBase, QuestionNote, UserAnswerBase, UserUserGroup
|
||||
from generic.models import CidUser, CidUserGroup, ExamUserStatus, Examination, ExamBase, QuestionBase, QuestionNote, UserAnswerBase, UserUserGroup
|
||||
|
||||
from collections import defaultdict
|
||||
from helpers.images import image_as_base64
|
||||
@@ -72,7 +72,7 @@ class QuestionType(models.Model):
|
||||
|
||||
|
||||
@reversion.register
|
||||
class AnatomyQuestion(models.Model):
|
||||
class AnatomyQuestion(QuestionBase):
|
||||
question_type = models.ForeignKey(
|
||||
QuestionType, on_delete=models.SET_NULL, null=True, default=1
|
||||
)
|
||||
@@ -114,11 +114,6 @@ class AnatomyQuestion(models.Model):
|
||||
structure = models.ForeignKey(
|
||||
Structure, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
@@ -127,8 +122,6 @@ class AnatomyQuestion(models.Model):
|
||||
related_name="anatomy_authored_questions",
|
||||
)
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
|
||||
class Meta:
|
||||
permissions = ()
|
||||
|
||||
|
||||
+21
-1
@@ -24,6 +24,7 @@ from django.contrib.auth.models import User
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
|
||||
class Plane(models.Model):
|
||||
plane = models.CharField(max_length=200, unique=True)
|
||||
|
||||
@@ -71,15 +72,29 @@ class QuestionBase(models.Model):
|
||||
default=False,
|
||||
)
|
||||
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
feedback = models.TextField(null=True, blank=True, help_text="Question Feedback")
|
||||
|
||||
notes = GenericRelation("generic.QuestionNote")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_unanswered_mark_and_text(self) -> tuple(int, str):
|
||||
def get_unanswered_mark_and_text(self) -> tuple[ int, str ]:
|
||||
"""
|
||||
Override in models if needed
|
||||
"""
|
||||
return (0, "Not answered")
|
||||
|
||||
def get_primary_answer(self):
|
||||
"""If this makes sense in the question/answer context override"""
|
||||
return None
|
||||
|
||||
|
||||
class ExamBase(models.Model):
|
||||
name = models.CharField(max_length=200, help_text="Name of the exam")
|
||||
@@ -436,6 +451,11 @@ class UserAnswerBase(models.Model):
|
||||
def get_absolute_url(self):
|
||||
return reverse(f"{self.app_name}:user_answer_view", kwargs={"pk": self.pk})
|
||||
|
||||
def get_answer(self):
|
||||
"""might need overriding"""
|
||||
return self.answer
|
||||
|
||||
|
||||
class NoteType(models.Model):
|
||||
note_type = models.CharField(max_length=200)
|
||||
|
||||
|
||||
+1
-4
@@ -1803,10 +1803,7 @@ class ExamViews(View, LoginRequiredMixin):
|
||||
answer_score = score
|
||||
ans = text
|
||||
else:
|
||||
if exam.app_name == "rapids" and user_answer.normal:
|
||||
ans = "Normal"
|
||||
else:
|
||||
ans = user_answer.answer
|
||||
ans = user_answer.get_answer()
|
||||
answer_score = user_answer.get_answer_score()
|
||||
|
||||
correct_answer = q.get_primary_answer()
|
||||
|
||||
+50
-16
@@ -37,6 +37,7 @@ from generic.models import (
|
||||
ExamBase,
|
||||
Plane,
|
||||
Contrast,
|
||||
QuestionBase,
|
||||
QuestionNote,
|
||||
UserAnswerBase,
|
||||
UserUserGroup,
|
||||
@@ -79,7 +80,7 @@ def findMiddle(input_list):
|
||||
|
||||
|
||||
@reversion.register
|
||||
class Long(models.Model):
|
||||
class Long(QuestionBase):
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Description of the case, for admin organisation, will not be visible when taking",
|
||||
@@ -87,8 +88,6 @@ class Long(models.Model):
|
||||
|
||||
history = models.TextField(null=True, blank=True)
|
||||
|
||||
feedback = models.TextField(null=True, blank=True)
|
||||
|
||||
# TODO: merge with atlas condition / signs / finding
|
||||
# condition = tagulous.models.TagField(
|
||||
# to=Condition,
|
||||
@@ -117,8 +116,6 @@ class Long(models.Model):
|
||||
# help_text=
|
||||
# "If we know the source of the image")
|
||||
verified = models.BooleanField(default=False)
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
published_date = models.DateTimeField(blank=True, null=True)
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
@@ -130,10 +127,6 @@ class Long(models.Model):
|
||||
default=False, help_text="Question has been scrapped and will not be shown"
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
recreate_json = models.BooleanField(
|
||||
help_text="If the json cache needs updating", default=False
|
||||
)
|
||||
@@ -146,8 +139,6 @@ class Long(models.Model):
|
||||
|
||||
series = SortedManyToManyField("LongSeries", related_name="long")
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
|
||||
# question_file = models.FileField(upload_to=question_file_directory_path, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
@@ -354,6 +345,12 @@ class Long(models.Model):
|
||||
|
||||
return url
|
||||
|
||||
def get_unanswered_mark_and_text(self) -> tuple[int, str]:
|
||||
"""
|
||||
Override in models if needed
|
||||
"""
|
||||
return (4, ("Not answered",) * 5)
|
||||
|
||||
# def GetNonFeedbackQuestionImages(self):
|
||||
# return self.get_images()
|
||||
|
||||
@@ -431,14 +428,12 @@ class LongSeries(models.Model):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
#if self.long:
|
||||
# if self.long:
|
||||
# long_id = ", ".format([long.pk for long in self.long.all()])
|
||||
# # long_id = self.long.pk
|
||||
#else:
|
||||
# else:
|
||||
# long_id = "None"
|
||||
return "{}/{} : {}".format(
|
||||
self.pk, self.get_examination(), self.description
|
||||
)
|
||||
return "{}/{} : {}".format(self.pk, self.get_examination(), self.description)
|
||||
|
||||
def get_author_objects(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
@@ -655,6 +650,35 @@ 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()
|
||||
|
||||
@@ -849,6 +873,15 @@ class UserAnswer(UserAnswerBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_answer(self):
|
||||
(
|
||||
self.answer_observations,
|
||||
self.answer_interpretation,
|
||||
self.answer_principle_diagnosis,
|
||||
self.answer_differential_diagnosis,
|
||||
self.answer_management,
|
||||
)
|
||||
|
||||
def get_answer_string(self) -> str:
|
||||
return f"""
|
||||
Observations:
|
||||
@@ -868,6 +901,7 @@ Management:
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@reversion.register
|
||||
class AnswerMarks(models.Model):
|
||||
score = models.CharField(max_length=3, choices=UserAnswer.ScoreOptions.choices)
|
||||
|
||||
+91
-91
@@ -978,97 +978,97 @@ def mark(request, exam_id, sk):
|
||||
# )
|
||||
|
||||
|
||||
def exam_scores_cid_user(request, pk, cid, passcode):
|
||||
exam = get_object_or_404(Exam, pk=pk)
|
||||
|
||||
# TODO:Need some kind of test for cid
|
||||
|
||||
if not exam.exam_mode:
|
||||
raise Http404("Packet not in exam mode")
|
||||
|
||||
if not exam.check_cid_user(cid, passcode, request):
|
||||
raise Http404("Error accessing exam")
|
||||
|
||||
|
||||
questions = exam.exam_questions.all()
|
||||
|
||||
# answers_and_marks = []
|
||||
answers_marks = []
|
||||
# answers = []
|
||||
answer_text = []
|
||||
|
||||
view_all_results = False
|
||||
if request.user.groups.filter(name="view_all_results").exists():
|
||||
view_all_results = True
|
||||
|
||||
for q in questions:
|
||||
# Get user answer
|
||||
user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
|
||||
|
||||
if not user_answer or user_answer is None:
|
||||
# skip if no answer
|
||||
# answers_marks.append("")
|
||||
# answers.append("")
|
||||
answer_score = 4
|
||||
# ans = "Not answered"
|
||||
answer_text.append(
|
||||
(
|
||||
("Not answered"),
|
||||
("Not answered"),
|
||||
("Not answered"),
|
||||
("Not answered"),
|
||||
("Not answered"),
|
||||
)
|
||||
)
|
||||
else:
|
||||
answer_score = user_answer.get_answer_score()
|
||||
|
||||
answer_text.append(
|
||||
(
|
||||
user_answer.answer_observations,
|
||||
user_answer.answer_interpretation,
|
||||
user_answer.answer_principle_diagnosis,
|
||||
user_answer.answer_differential_diagnosis,
|
||||
user_answer.answer_management,
|
||||
)
|
||||
)
|
||||
|
||||
if not exam.publish_results and not view_all_results:
|
||||
answer_score = 0
|
||||
# answers.append(ans)
|
||||
answers_marks.append(answer_score)
|
||||
# answers_and_marks.append((ans, answer_score, correct_answer))
|
||||
|
||||
answered = [i for i in answers_marks if i != ""]
|
||||
if "" in answers_marks:
|
||||
total_score = sum(answered)
|
||||
unmarked_number = len(answers_marks) - len(answered)
|
||||
total_score = "{} ({} unmarked)".format(total_score, unmarked_number)
|
||||
normalised_score = "Not available"
|
||||
else:
|
||||
total_score = sum(answered)
|
||||
normalised_score = normaliseScore(total_score)
|
||||
|
||||
max_score = len(questions) * 8
|
||||
|
||||
return render(
|
||||
request,
|
||||
"longs/exam_scores_user.html",
|
||||
{
|
||||
"exam": exam,
|
||||
"cid": cid,
|
||||
"passcode": passcode,
|
||||
"questions": questions,
|
||||
# "answers": answers,
|
||||
"answers_marks": answers_marks,
|
||||
"total_score": total_score,
|
||||
"normalised_score": normalised_score,
|
||||
"max_score": max_score,
|
||||
"answer_text": answer_text,
|
||||
# "answers_and_marks": answers_and_marks,
|
||||
"view_all_results": view_all_results,
|
||||
},
|
||||
)
|
||||
#def exam_scores_cid_user(request, pk, cid, passcode):
|
||||
# exam = get_object_or_404(Exam, pk=pk)
|
||||
#
|
||||
# # TODO:Need some kind of test for cid
|
||||
#
|
||||
# if not exam.exam_mode:
|
||||
# raise Http404("Packet not in exam mode")
|
||||
#
|
||||
# if not exam.check_cid_user(cid, passcode, request):
|
||||
# raise Http404("Error accessing exam")
|
||||
#
|
||||
#
|
||||
# questions = exam.exam_questions.all()
|
||||
#
|
||||
# # answers_and_marks = []
|
||||
# answers_marks = []
|
||||
# # answers = []
|
||||
# answer_text = []
|
||||
#
|
||||
# view_all_results = False
|
||||
# if request.user.groups.filter(name="view_all_results").exists():
|
||||
# view_all_results = True
|
||||
#
|
||||
# for q in questions:
|
||||
# # Get user answer
|
||||
# user_answer = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
|
||||
#
|
||||
# if not user_answer or user_answer is None:
|
||||
# # skip if no answer
|
||||
# # answers_marks.append("")
|
||||
# # answers.append("")
|
||||
# answer_score = 4
|
||||
# # ans = "Not answered"
|
||||
# answer_text.append(
|
||||
# (
|
||||
# ("Not answered"),
|
||||
# ("Not answered"),
|
||||
# ("Not answered"),
|
||||
# ("Not answered"),
|
||||
# ("Not answered"),
|
||||
# )
|
||||
# )
|
||||
# else:
|
||||
# answer_score = user_answer.get_answer_score()
|
||||
#
|
||||
# answer_text.append(
|
||||
# (
|
||||
# user_answer.answer_observations,
|
||||
# user_answer.answer_interpretation,
|
||||
# user_answer.answer_principle_diagnosis,
|
||||
# user_answer.answer_differential_diagnosis,
|
||||
# user_answer.answer_management,
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# if not exam.publish_results and not view_all_results:
|
||||
# answer_score = 0
|
||||
# # answers.append(ans)
|
||||
# answers_marks.append(answer_score)
|
||||
# # answers_and_marks.append((ans, answer_score, correct_answer))
|
||||
#
|
||||
# answered = [i for i in answers_marks if i != ""]
|
||||
# if "" in answers_marks:
|
||||
# total_score = sum(answered)
|
||||
# unmarked_number = len(answers_marks) - len(answered)
|
||||
# total_score = "{} ({} unmarked)".format(total_score, unmarked_number)
|
||||
# normalised_score = "Not available"
|
||||
# else:
|
||||
# total_score = sum(answered)
|
||||
# normalised_score = normaliseScore(total_score)
|
||||
#
|
||||
# max_score = len(questions) * 8
|
||||
#
|
||||
# return render(
|
||||
# request,
|
||||
# "longs/exam_scores_user.html",
|
||||
# {
|
||||
# "exam": exam,
|
||||
# "cid": cid,
|
||||
# "passcode": passcode,
|
||||
# "questions": questions,
|
||||
# # "answers": answers,
|
||||
# "answers_marks": answers_marks,
|
||||
# "total_score": total_score,
|
||||
# "normalised_score": normalised_score,
|
||||
# "max_score": max_score,
|
||||
# "answer_text": answer_text,
|
||||
# # "answers_and_marks": answers_and_marks,
|
||||
# "view_all_results": view_all_results,
|
||||
# },
|
||||
# )
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
+3
-5
@@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, ExamBase, ExamUserStatus, QuestionNote, UserAnswerBase, UserUserGroup
|
||||
from generic.models import CidUser, CidUserGroup, ExamBase, ExamUserStatus, QuestionBase, QuestionNote, UserAnswerBase, UserUserGroup
|
||||
|
||||
import reversion
|
||||
|
||||
@@ -24,7 +24,7 @@ class Category(models.Model):
|
||||
return self.category
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
class Question(QuestionBase):
|
||||
stem = models.TextField(
|
||||
blank=False,
|
||||
help_text="Stem of the question",
|
||||
@@ -82,8 +82,6 @@ class Question(models.Model):
|
||||
help_text="Feedback for answer",
|
||||
)
|
||||
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
@@ -95,7 +93,7 @@ class Question(models.Model):
|
||||
Category, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
#notes = GenericRelation(QuestionNote)
|
||||
|
||||
def __str__(self):
|
||||
return self.stem
|
||||
|
||||
+6
-10
@@ -29,6 +29,7 @@ from generic.models import (
|
||||
CidUser,
|
||||
CidUserGroup,
|
||||
ExamUserStatus,
|
||||
QuestionBase,
|
||||
Site,
|
||||
#Condition,
|
||||
#Sign,
|
||||
@@ -148,7 +149,7 @@ class Answer(models.Model):
|
||||
self.answer_compare = get_answer_compare(self.answer)
|
||||
|
||||
|
||||
class Rapid(models.Model):
|
||||
class Rapid(QuestionBase):
|
||||
"""
|
||||
Django model that defines a rapid question.
|
||||
|
||||
@@ -160,7 +161,6 @@ class Rapid(models.Model):
|
||||
|
||||
question = models.TextField(null=True, blank=True)
|
||||
history = models.TextField(null=True, blank=True)
|
||||
feedback = models.TextField(null=True, blank=True)
|
||||
|
||||
normal = models.BooleanField(default=False, help_text="Tick if true")
|
||||
|
||||
@@ -213,8 +213,7 @@ class Rapid(models.Model):
|
||||
# help_text=
|
||||
# "If we know the source of the image")
|
||||
verified = models.BooleanField(default=False)
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
published_date = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
@@ -226,12 +225,6 @@ class Rapid(models.Model):
|
||||
default=False, help_text="Question has been scrapped and will not be shown"
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If a question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("rapids:question_detail", kwargs={"pk": self.pk})
|
||||
|
||||
@@ -852,6 +845,9 @@ class UserAnswer(UserAnswerBase):
|
||||
# s = s.translate(str.maketrans('', '', string.punctuation))
|
||||
# return s
|
||||
|
||||
def get_answer(self):
|
||||
return self.get_answer_string()
|
||||
|
||||
def get_answer_string(self):
|
||||
if self.normal:
|
||||
return "Normal"
|
||||
|
||||
+3
-5
@@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
from generic.models import CidUser, CidUserGroup, ExamBase, ExamUserStatus, QuestionNote, UserAnswerBase, UserUserGroup
|
||||
from generic.models import CidUser, CidUserGroup, ExamBase, ExamUserStatus, QuestionBase, QuestionNote, UserAnswerBase, UserUserGroup
|
||||
|
||||
import reversion
|
||||
|
||||
@@ -25,7 +25,7 @@ class Category(models.Model):
|
||||
return self.category
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
class Question(QuestionBase):
|
||||
stem = models.TextField(
|
||||
blank=False,
|
||||
help_text="Stem of the question",
|
||||
@@ -82,8 +82,6 @@ class Question(models.Model):
|
||||
max_length=20, choices=BEST_ANSWER_CHOICES, help_text="The correct answer"
|
||||
)
|
||||
|
||||
created_date = models.DateTimeField(default=timezone.now)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
@@ -95,7 +93,7 @@ class Question(models.Model):
|
||||
Category, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
#notes = GenericRelation(QuestionNote)
|
||||
|
||||
def __str__(self):
|
||||
return self.stem
|
||||
|
||||
Reference in New Issue
Block a user