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 import tagulous.models from django.core.files.storage import FileSystemStorage from django.conf import settings from django.utils.html import format_html from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from django.utils.html import mark_safe from sortedm2m.fields import SortedManyToManyField import string image_storage = FileSystemStorage( # Physical file location ROOT location=u"{0}/longs/".format(settings.MEDIA_ROOT), # Url for file base_url=u"{0}longs/".format(settings.MEDIA_URL), ) def image_directory_path(instance, filename): return u"picture/{0}".format(filename) class Examination(models.Model): examination = models.CharField(max_length=200) def __str__(self): return self.examination class Meta: ordering = ('examination', ) class Site(models.Model): site = models.CharField(max_length=200) initials = models.CharField(max_length=200) def __str__(self): return self.site class Condition(tagulous.models.TagModel): pass class Sign(tagulous.models.TagModel): pass class Long(models.Model): #author = models.ForeignKey('auth.User', on_delete=models.CASCADE) #image = models.ImageField() question = models.TextField(null=True, blank=True) feedback = models.TextField(null=True, blank=True) #condition = tagulous.models.TagField( # to=Condition, # blank=True, # help_text= # "Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes \"...\"" #) sign = models.CharField(max_length=255, blank=True) #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, blank=True, default=DEFAULT_SITE_ID, 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, help_text='Author of question', related_name="long_authored_questions") scrapped = models.BooleanField( default=False, help_text='Question has been scrapped and will not be shown') open_access = models.BooleanField( help_text="If an question should be freely available to browse", default=True ) def get_absolute_url(self): return reverse('longs:long_detail', kwargs={'pk': self.pk}) def get_authors(self): """Returns a comma seperated text list of authors""" authors = ", ".join([i.username for i in self.author.all()]) return authors 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): exams = self.get_examinations() lat = "" if self.laterality != "NONE": lat = self.laterality return "{} / {} {}".format(exams, lat, n) def GetExams(self): e = self.exams.all().values_list("name", flat=True) exams = ", ".join(e) return exams def GetImages(self, feedback=False): qs = self.images.all() if not feedback: images = [i.image for i in qs if not i.feedback_image] else: images = [i.image for i in qs] return images def GetImageUrls(self): return ",".join(["http://penracourses.org.uk{}".format(i.url) for i in self.GetImages()]) #def GetNonFeedbackQuestionImages(self): #return self.GetImages() 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) 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) #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( # 'Click and hold to zoom' # .format(self.image.url)) # else: # return "" # # image_tag.short_description = 'Image' class Note(models.Model): long = models.ForeignKey(Long, related_name="long_notes", on_delete=models.CASCADE, null=True) #author = models.ForeignKey(User, author = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="long_notes", on_delete=models.CASCADE) note = models.TextField() created_on = models.DateTimeField(auto_now_add=True) def get_absolute_url(self): self.pk = self.long_id return reverse('longs:long_detail', kwargs={'pk': self.pk}) def __str__(self): return "{} [{}] {}".format(self.long, self.author, self.created_on) class LongCreationDefault(models.Model): #author = models.OneToOneField(User, author = models.OneToOneField( settings.AUTH_USER_MODEL, related_name="long_default", on_delete=models.CASCADE) site = models.ManyToManyField( Site, blank=True, default=1, help_text="Default site to use when creating a new long") def get_absolute_url(self): return reverse('longs:long_create') class Exam(models.Model): name = models.CharField(max_length=200) exam_questions = SortedManyToManyField( Long, related_name="exams", blank="true" ) active = models.BooleanField( help_text="If an exam should be available", default=True ) publish_results = models.BooleanField( help_text="If an exams results should be available", default=True ) recreate_json = models.BooleanField( help_text="If the json cache needs updating", default=False ) time_limit = models.IntegerField( help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)", default=2100 ) def __str__(self): return self.name def get_absolute_url(self): return reverse("longs:exam_overview", kwargs={"pk": self.pk}) def get_exam_name(self): return self.name def get_json_url(self): return reverse("longs:exam_json", args=(self.pk,)) def get_question_index(self, question): return list(self.exam_questions.all()).index(question) class CidUserAnswer(models.Model): """User answers by candidate""" question = models.ForeignKey( Long, related_name="cid_user_answers", on_delete=models.CASCADE ) # 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)") # Each user answer is associated with a particular exam 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) def __str__(self): try: exam = self.exam except (Exam.DoesNotExist, KeyError) as e: exam = "None" return "{}/{}/{}: {}".format( exam, self.cid, self.question.GetPrimaryAnswer(), self.answer ) def save(self, *args, **kwargs): self.clean() return super(CidUserAnswer, 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 # def get_compare_string(self): # # strip here should be unneccasry (providing clean is now working) # s = self.answer.lower().strip() # s = s.translate(str.maketrans('', '', string.punctuation)) # return s def get_answer_score(self): q = self.question # First step we check that the normal/abnormal states match if q.normal != self.normal: # If they don't match the answer is wrong (score is 0) return 0 # If both are normal full marks elif q.normal and self.normal: return 2 # Then compare answer strings (as per anatomy questions) ans = self.answer_compare if ( q.answers.filter( answer_compare__iexact=ans, status=Answer.MarkOptions.CORRECT ).first() is not None ): mark = 2 elif ( q.answers.filter( answer_compare__iexact=ans, status=Answer.MarkOptions.HALF_MARK ).first() is not None ): mark = 1 elif q.answers.filter( answer_compare__iexact=ans, status=Answer.MarkOptions.INCORRECT ).first(): mark = 0 else: mark = "unmarked" return mark