more improvements
This commit is contained in:
+43
-177
@@ -1,3 +1,6 @@
|
||||
from django.db.models.fields.files import ImageField
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
from anatomy.models import Modality
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
import tagulous
|
||||
@@ -29,32 +32,6 @@ def image_directory_path(instance, filename):
|
||||
|
||||
|
||||
|
||||
class Abnormality(models.Model):
|
||||
name = models.CharField(max_length=200,
|
||||
unique=True,
|
||||
help_text="Primary abnormality on the film(s)")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', )
|
||||
|
||||
|
||||
class Region(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=200,
|
||||
unique=True,
|
||||
help_text="Region of the abnormality (not including lateralitiy)",
|
||||
blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', )
|
||||
|
||||
|
||||
class Examination(models.Model):
|
||||
examination = models.CharField(max_length=200)
|
||||
|
||||
@@ -79,39 +56,6 @@ class Condition(tagulous.models.TagModel):
|
||||
class Sign(tagulous.models.TagModel):
|
||||
pass
|
||||
|
||||
class Answer(models.Model):
|
||||
question = models.ForeignKey(
|
||||
"Long", related_name="answers", on_delete=models.CASCADE
|
||||
)
|
||||
answer = models.TextField(max_length=500)
|
||||
answer_compare = models.TextField(max_length=500)
|
||||
|
||||
class MarkOptions(models.TextChoices):
|
||||
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
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.answer
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
return super(Answer, self).save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
if self.answer:
|
||||
self.answer = self.answer.strip()
|
||||
|
||||
s = self.answer.lower()
|
||||
s = s.translate(str.maketrans('', '', string.punctuation))
|
||||
|
||||
self.answer_compare = s
|
||||
|
||||
|
||||
class Long(models.Model):
|
||||
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
||||
@@ -119,37 +63,6 @@ class Long(models.Model):
|
||||
question = models.TextField(null=True, blank=True)
|
||||
feedback = models.TextField(null=True, blank=True)
|
||||
|
||||
normal = models.BooleanField(default=False, help_text="Tick if true")
|
||||
|
||||
NONE = "NONE"
|
||||
LEFT = "LEFT"
|
||||
RIGHT = "RIGHT"
|
||||
BILATERAL = "BILAT"
|
||||
|
||||
LATERALITY_CHOICES = (
|
||||
(NONE, "None"),
|
||||
(LEFT, "Left"),
|
||||
(RIGHT, "Right"),
|
||||
(BILATERAL, "Bilateral"),
|
||||
)
|
||||
|
||||
abnormality = models.ManyToManyField(
|
||||
Abnormality,
|
||||
blank=True,
|
||||
help_text="The abnormality (laterality and region independent). Used for categorisation but does not affect the answer",
|
||||
)
|
||||
region = models.ManyToManyField(
|
||||
Region,
|
||||
blank=True,
|
||||
help_text="Region of the abnormality (laterality independent)")
|
||||
examination = models.ManyToManyField(
|
||||
Examination, help_text="Name of the (primary) examination")
|
||||
laterality = models.CharField(
|
||||
max_length=20,
|
||||
choices=LATERALITY_CHOICES,
|
||||
default=NONE,
|
||||
help_text="Applies to the answer, not the examination")
|
||||
condition = models.CharField(max_length=255, blank=True)
|
||||
#condition = tagulous.models.TagField(
|
||||
# to=Condition,
|
||||
# blank=True,
|
||||
@@ -161,6 +74,14 @@ class Long(models.Model):
|
||||
#sign = tagulous.models.TagField(
|
||||
# to=Sign, blank=True, help_text='Radiological signs in the question')
|
||||
|
||||
# Model answers
|
||||
model_observations = models.TextField(null=True, blank=True)
|
||||
model_interpretation = models.TextField(null=True, blank=True)
|
||||
model_principle_diagnosis = models.TextField(null=True, blank=True)
|
||||
model_differential_diagnosis = models.TextField(null=True, blank=True)
|
||||
model_further_management = models.TextField(null=True, blank=True)
|
||||
|
||||
|
||||
DEFAULT_SITE_ID = 1
|
||||
site = models.ManyToManyField(
|
||||
Site,
|
||||
@@ -193,91 +114,23 @@ class Long(models.Model):
|
||||
authors = ", ".join([i.username for i in self.author.all()])
|
||||
return authors
|
||||
|
||||
def get_regions(self):
|
||||
"""Returns a comma seperated text list of regions"""
|
||||
regions = ", ".join([i.name for i in self.region.all()])
|
||||
return regions
|
||||
|
||||
def get_abnormalities(self):
|
||||
"""Returns a comma seperated text list of regions"""
|
||||
abnormalities = ", ".join([i.name for i in self.abnormality.all()])
|
||||
return abnormalities
|
||||
|
||||
def get_examinations(self):
|
||||
"""Returns a comma seperated text list of regions"""
|
||||
examinations = ", ".join([i.examination for i in self.examination.all()])
|
||||
return examinations
|
||||
|
||||
def __str__(self):
|
||||
n = "Normal"
|
||||
if not self.normal:
|
||||
#n = self.answers.first()
|
||||
n = "{} / {}".format(self.abnormality.first(), self.region.first())
|
||||
exams = self.get_examinations()
|
||||
lat = ""
|
||||
if self.laterality != "NONE":
|
||||
lat = self.laterality
|
||||
return "{} / {} {}".format(exams, lat, n)
|
||||
|
||||
def GetPrimaryAnswer(self):
|
||||
if len(self.answers.all()) > 0:
|
||||
return self.answers.all().first().answer
|
||||
elif self.normal:
|
||||
return "Normal"
|
||||
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()
|
||||
|
||||
if not unmarked_answers:
|
||||
return "No answers to mark"
|
||||
return format_html(
|
||||
"<span class='warn'>{} answer unmarked:</span> {}".format(
|
||||
len(unmarked_answers), ", ".join(unmarked_answers)
|
||||
)
|
||||
)
|
||||
|
||||
def GetUnmarkedAnswers(self):
|
||||
# If normal no answers to mark
|
||||
if self.normal:
|
||||
return []
|
||||
|
||||
user_answers = set(
|
||||
[i.answer_compare for i in self.cid_user_answers.all() if i.normal == False]
|
||||
)
|
||||
unmarked_answers = user_answers - self.GetMarkedAnswers()
|
||||
|
||||
return unmarked_answers
|
||||
|
||||
def GetUnmarkedAnswerCount(self):
|
||||
return len(self.GetUnmarkedAnswers())
|
||||
|
||||
def GetMarkedAnswers(self):
|
||||
return set(
|
||||
[
|
||||
i.answer_compare
|
||||
for i in self.answers.all()
|
||||
if i.status != i.MarkOptions.UNMARKED
|
||||
]
|
||||
)
|
||||
correct_answers = set(
|
||||
[i.answer.get_compare_string() for i in self.answers.all()]
|
||||
)
|
||||
half_mark_answers = set(
|
||||
[i.answer.get_compare_string() for i in self.half_mark_answers.all()]
|
||||
)
|
||||
incorrect_answers = set(
|
||||
[i.answer.get_compare_string() for i in self.incorrect_answers.all()]
|
||||
)
|
||||
|
||||
marked_answers = correct_answers | half_mark_answers | incorrect_answers
|
||||
return marked_answers
|
||||
|
||||
def GetImages(self, feedback=False):
|
||||
qs = self.images.all()
|
||||
@@ -294,24 +147,34 @@ class Long(models.Model):
|
||||
#def GetNonFeedbackQuestionImages(self):
|
||||
#return self.GetImages()
|
||||
|
||||
|
||||
class LongImage(models.Model):
|
||||
long = models.ForeignKey(Long,
|
||||
related_name="images",
|
||||
on_delete=models.CASCADE)
|
||||
class LongSeriesImage(models.Model):
|
||||
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
||||
position = models.IntegerField(default=0)
|
||||
series = models.ForeignKey("LongSeries", related_name="series", on_delete=models.SET_NULL, null=True)
|
||||
|
||||
feedback_image = models.BooleanField(default=False)
|
||||
class LongSeries(models.Model):
|
||||
modality = models.ForeignKey(Modality, related_name="series_modality", on_delete=models.SET_NULL, null=True)
|
||||
examination = models.ForeignKey(
|
||||
Examination, help_text="Name of the examination", on_delete=models.SET_NULL, null=True)
|
||||
long = models.ForeignKey("Long", related_name="long", on_delete=models.SET_NULL, null=True)
|
||||
|
||||
def image_tag(self):
|
||||
if self.image:
|
||||
return mark_safe(
|
||||
'<img src="{}" class="admin-long-image" /><span class="admin-long-image-info">Click and hold to zoom<span>'
|
||||
.format(self.image.url))
|
||||
else:
|
||||
return ""
|
||||
|
||||
image_tag.short_description = 'Image'
|
||||
#class LongImage(models.Model):
|
||||
# long = models.ForeignKey(Long,
|
||||
# related_name="images",
|
||||
# on_delete=models.CASCADE)
|
||||
# image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
||||
#
|
||||
# feedback_image = models.BooleanField(default=False)
|
||||
#
|
||||
# def image_tag(self):
|
||||
# if self.image:
|
||||
# return mark_safe(
|
||||
# '<img src="{}" class="admin-long-image" /><span class="admin-long-image-info">Click and hold to zoom<span>'
|
||||
# .format(self.image.url))
|
||||
# else:
|
||||
# return ""
|
||||
#
|
||||
# image_tag.short_description = 'Image'
|
||||
|
||||
|
||||
class Note(models.Model):
|
||||
@@ -395,11 +258,14 @@ class CidUserAnswer(models.Model):
|
||||
Long, related_name="cid_user_answers", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
# For longs the answer can be normal in which case the below field is true
|
||||
normal = models.BooleanField(default=False)
|
||||
answer = models.TextField(max_length=500, blank=True)
|
||||
|
||||
answer_compare = models.TextField(max_length=500, blank=True)
|
||||
# Answers
|
||||
answer_observations = models.TextField(null=True, blank=True)
|
||||
answer_interpretation = models.TextField(null=True, blank=True)
|
||||
answer_principle_diagnosis = models.TextField(null=True, blank=True)
|
||||
answer_differential_diagnosis = models.TextField(null=True, blank=True)
|
||||
answer_further_management = models.TextField(null=True, blank=True)
|
||||
|
||||
|
||||
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user