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