start anatomy creation forms
This commit is contained in:
+134
-79
@@ -45,6 +45,13 @@ class Structure(models.Model):
|
||||
return self.structure
|
||||
|
||||
|
||||
class Region(models.Model):
|
||||
region = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return self.region
|
||||
|
||||
|
||||
class Modality(models.Model):
|
||||
modality = models.CharField(max_length=200)
|
||||
|
||||
@@ -63,46 +70,62 @@ class AnatomyQuestion(models.Model):
|
||||
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
||||
# image = models.ImageField()
|
||||
# feedback = models.TextField(null=True)
|
||||
question_type = models.ForeignKey(QuestionType,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True)
|
||||
question_type = models.ForeignKey(
|
||||
QuestionType, on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
|
||||
image = models.ImageField(upload_to=image_directory_path,
|
||||
storage=image_storage)
|
||||
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
||||
|
||||
image_annotations = models.TextField(blank=True, help_text="Stores a JSON representation of annotations to be applied by cornerstonetools")
|
||||
image_annotations = models.TextField(
|
||||
blank=True,
|
||||
help_text="Stores a JSON representation of annotations to be applied by cornerstonetools",
|
||||
)
|
||||
|
||||
description = models.CharField(
|
||||
max_length=400,
|
||||
help_text=
|
||||
"Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title."
|
||||
help_text="Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title.",
|
||||
)
|
||||
|
||||
examination = models.ForeignKey(Examination,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True)
|
||||
modality = models.ForeignKey(Modality,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
help_text="Modality of the image")
|
||||
body_part = models.ForeignKey(BodyPart,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True)
|
||||
structure = models.ForeignKey(Structure,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True)
|
||||
examination = models.ForeignKey(
|
||||
Examination, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
modality = models.ForeignKey(
|
||||
Modality,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
help_text="Modality of the image",
|
||||
)
|
||||
region = models.ForeignKey(
|
||||
Region,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Region the image covers",
|
||||
)
|
||||
body_part = models.ForeignKey(
|
||||
BodyPart, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
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 an question should be freely available to browse",
|
||||
default=True)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
help_text="If an question should be freely available to browse", default=True
|
||||
)
|
||||
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text="Author(s) of question",
|
||||
related_name="anatomy_authored_questions",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
# Get first answer
|
||||
return "{} [{}, {}]".format(self.GetPrimaryAnswer(), self.modality,
|
||||
self.structure)
|
||||
return "{} [{}, {}]".format(
|
||||
self.GetPrimaryAnswer(), self.modality, self.structure
|
||||
)
|
||||
# Get associated exams
|
||||
e = self.exams.all().values_list("name", flat=True)
|
||||
exams = ", ".join(e)
|
||||
@@ -110,18 +133,24 @@ class AnatomyQuestion(models.Model):
|
||||
if a is None:
|
||||
return "({})".format(exams)
|
||||
else:
|
||||
return "{} ({}) [{}, {}]".format(a.answer, exams, self.modality,
|
||||
self.structure)
|
||||
return "{} ({}) [{}, {}]".format(
|
||||
a.answer, exams, self.modality, self.structure
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def GetPrimaryAnswer(self):
|
||||
return self.answers.all().first().answer
|
||||
if len(self.answers.all()) > 0:
|
||||
return self.answers.all().first().answer
|
||||
else:
|
||||
return "None yet..."
|
||||
|
||||
def GetExams(self):
|
||||
e = self.exams.all().values_list("name", flat=True)
|
||||
exams = ", ".join(e)
|
||||
return exams
|
||||
|
||||
|
||||
def GetUnmarkedAnswersString(self):
|
||||
unmarked_answers = self.GetUnmarkedAnswers()
|
||||
|
||||
@@ -129,11 +158,14 @@ class AnatomyQuestion(models.Model):
|
||||
return "No answers to mark"
|
||||
return format_html(
|
||||
"<span class='warn'>{} answer unmarked:</span> {}".format(
|
||||
len(unmarked_answers), ", ".join(unmarked_answers)))
|
||||
len(unmarked_answers), ", ".join(unmarked_answers)
|
||||
)
|
||||
)
|
||||
|
||||
def GetUnmarkedAnswers(self):
|
||||
user_answers = set(
|
||||
[i.get_compare_string() for i in self.cid_user_answers.all()])
|
||||
[i.get_compare_string() for i in self.cid_user_answers.all()]
|
||||
)
|
||||
unmarked_answers = user_answers - self.GetMarkedAnswers()
|
||||
|
||||
return unmarked_answers
|
||||
@@ -142,30 +174,40 @@ class AnatomyQuestion(models.Model):
|
||||
return len(self.GetUnmarkedAnswers())
|
||||
|
||||
def GetMarkedAnswers(self):
|
||||
return set([i.get_compare_string() for i in self.answers.all() if i.status != i.MarkOptions.UNMARKED])
|
||||
return set(
|
||||
[
|
||||
i.get_compare_string()
|
||||
for i in self.answers.all()
|
||||
if i.status != i.MarkOptions.UNMARKED
|
||||
]
|
||||
)
|
||||
correct_answers = set([i.answer.lower() for i in self.answers.all()])
|
||||
half_mark_answers = set(
|
||||
[i.answer.lower() for i in self.half_mark_answers.all()])
|
||||
[i.answer.lower() for i in self.half_mark_answers.all()]
|
||||
)
|
||||
incorrect_answers = set(
|
||||
[i.answer.lower() for i in self.incorrect_answers.all()])
|
||||
[i.answer.lower() for i in self.incorrect_answers.all()]
|
||||
)
|
||||
|
||||
marked_answers = correct_answers | half_mark_answers | incorrect_answers
|
||||
return marked_answers
|
||||
|
||||
|
||||
class Answer(models.Model):
|
||||
question = models.ForeignKey(AnatomyQuestion,
|
||||
related_name="answers",
|
||||
on_delete=models.CASCADE)
|
||||
question = models.ForeignKey(
|
||||
AnatomyQuestion, related_name="answers", on_delete=models.CASCADE
|
||||
)
|
||||
answer = models.CharField(max_length=500)
|
||||
|
||||
class MarkOptions(models.TextChoices):
|
||||
UNMARKED = '', _('Unmarked')
|
||||
INCORRECT = '0', _('Incorrect')
|
||||
HALF_MARK = '1', _('Half mark')
|
||||
CORRECT = '2', _('Correct')
|
||||
UNMARKED = "", _("Unmarked")
|
||||
INCORRECT = "0", _("Incorrect")
|
||||
HALF_MARK = "1", _("Half mark")
|
||||
CORRECT = "2", _("Correct")
|
||||
|
||||
status = models.CharField(max_length=1, choices=MarkOptions.choices, default=MarkOptions.UNMARKED)
|
||||
status = models.CharField(
|
||||
max_length=1, choices=MarkOptions.choices, default=MarkOptions.UNMARKED
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.answer
|
||||
@@ -178,7 +220,7 @@ class Answer(models.Model):
|
||||
return self.answer.lower()
|
||||
|
||||
|
||||
#class HalfMarkAnswers(models.Model):
|
||||
# class HalfMarkAnswers(models.Model):
|
||||
# question = models.ForeignKey(AnatomyQuestion,
|
||||
# related_name="half_mark_answers",
|
||||
# on_delete=models.CASCADE)
|
||||
@@ -192,7 +234,7 @@ class Answer(models.Model):
|
||||
# self.answer.strip()
|
||||
#
|
||||
#
|
||||
#class IncorrectAnswers(models.Model):
|
||||
# class IncorrectAnswers(models.Model):
|
||||
# question = models.ForeignKey(AnatomyQuestion,
|
||||
# related_name="incorrect_answers",
|
||||
# on_delete=models.CASCADE)
|
||||
@@ -208,14 +250,17 @@ class Answer(models.Model):
|
||||
|
||||
class Exam(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
exam_questions = SortedManyToManyField(AnatomyQuestion,
|
||||
related_name="exams", blank="true")
|
||||
exam_questions = SortedManyToManyField(
|
||||
AnatomyQuestion, related_name="exams", blank="true"
|
||||
)
|
||||
|
||||
active = models.BooleanField(help_text="If an exam should be available",
|
||||
default=True)
|
||||
active = models.BooleanField(
|
||||
help_text="If an exam should be available", default=True
|
||||
)
|
||||
|
||||
recreate_json = models.BooleanField(help_text="If the json cache needs updating",
|
||||
default=False)
|
||||
recreate_json = models.BooleanField(
|
||||
help_text="If the json cache needs updating", default=False
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -224,26 +269,24 @@ class Exam(models.Model):
|
||||
return self.name
|
||||
|
||||
def get_json_url(self):
|
||||
return reverse('anatomy:exam_json', args=(self.pk, ))
|
||||
return reverse("anatomy:exam_json", args=(self.pk,))
|
||||
|
||||
|
||||
class UserAnswer(models.Model):
|
||||
question = models.ForeignKey(AnatomyQuestion,
|
||||
related_name="user_answers",
|
||||
on_delete=models.CASCADE)
|
||||
question = models.ForeignKey(
|
||||
AnatomyQuestion, related_name="user_answers", on_delete=models.CASCADE
|
||||
)
|
||||
answer = models.CharField(max_length=500, blank=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
cid = models.IntegerField(blank=True, null=True)
|
||||
|
||||
# Each user answer is associated with a particular exam
|
||||
exam = models.ForeignKey(Exam,
|
||||
related_name="exam",
|
||||
on_delete=models.CASCADE,
|
||||
null=True)
|
||||
exam = models.ForeignKey(
|
||||
Exam, related_name="exam", on_delete=models.CASCADE, null=True
|
||||
)
|
||||
|
||||
flagged = models.BooleanField(default=False)
|
||||
|
||||
@@ -255,8 +298,9 @@ class UserAnswer(models.Model):
|
||||
exam = self.exam
|
||||
except (Exam.DoesNotExist, KeyError) as e:
|
||||
exam = "None"
|
||||
return "{}/{}/{}: {}".format(exam, self.cid, self.question.GetPrimaryAnswer(),
|
||||
self.answer)
|
||||
return "{}/{}/{}: {}".format(
|
||||
exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if self.answer:
|
||||
@@ -265,18 +309,18 @@ class UserAnswer(models.Model):
|
||||
|
||||
class CidUserAnswer(models.Model):
|
||||
"""User answers by candidate"""
|
||||
question = models.ForeignKey(AnatomyQuestion,
|
||||
related_name="cid_user_answers",
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
question = models.ForeignKey(
|
||||
AnatomyQuestion, related_name="cid_user_answers", on_delete=models.CASCADE
|
||||
)
|
||||
answer = models.CharField(max_length=500, blank=True)
|
||||
|
||||
cid = models.IntegerField(blank=True, null=True)
|
||||
|
||||
# Each user answer is associated with a particular exam
|
||||
exam = models.ForeignKey(Exam,
|
||||
related_name="cid_user_answers",
|
||||
on_delete=models.CASCADE,
|
||||
null=True)
|
||||
exam = models.ForeignKey(
|
||||
Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True
|
||||
)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
@@ -286,8 +330,9 @@ class CidUserAnswer(models.Model):
|
||||
exam = self.exam
|
||||
except (Exam.DoesNotExist, KeyError) as e:
|
||||
exam = "None"
|
||||
return "{}/{}/{}: {}".format(exam, self.cid, self.question.GetPrimaryAnswer(),
|
||||
self.answer)
|
||||
return "{}/{}/{}: {}".format(
|
||||
exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if self.answer:
|
||||
@@ -299,9 +344,19 @@ class CidUserAnswer(models.Model):
|
||||
def get_answer_score(self):
|
||||
q = self.question
|
||||
ans = self.answer
|
||||
if q.answers.filter(answer__iexact=ans, status=Answer.MarkOptions.CORRECT).first() is not None:
|
||||
if (
|
||||
q.answers.filter(
|
||||
answer__iexact=ans, status=Answer.MarkOptions.CORRECT
|
||||
).first()
|
||||
is not None
|
||||
):
|
||||
mark = 2
|
||||
elif q.answers.filter(answer__iexact=ans, status=Answer.MarkOptions.HALF_MARK).first() is not None:
|
||||
elif (
|
||||
q.answers.filter(
|
||||
answer__iexact=ans, status=Answer.MarkOptions.HALF_MARK
|
||||
).first()
|
||||
is not None
|
||||
):
|
||||
mark = 1
|
||||
else:
|
||||
mark = 0
|
||||
|
||||
Reference in New Issue
Block a user