generalise a QuestionBase

This commit is contained in:
Ross
2023-06-05 09:10:54 +01:00
parent ac558b9ea2
commit e0723d4d3a
8 changed files with 177 additions and 141 deletions
+2 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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