.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.8 on 2021-12-11 22:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0015_ciduser_ciduserexam'),
|
||||
('anatomy', '0053_anatomyquestion_structure'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='valid_users',
|
||||
field=models.ManyToManyField(blank=True, related_name='anatomy_exams', to='generic.CidUser'),
|
||||
),
|
||||
]
|
||||
+3
-1
@@ -15,7 +15,7 @@ from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
import string
|
||||
|
||||
from generic.models import Examination, ExamBase, QuestionNote
|
||||
from generic.models import CidUser, Examination, ExamBase, QuestionNote
|
||||
|
||||
from collections import defaultdict
|
||||
from helpers.images import image_as_base64
|
||||
@@ -354,6 +354,8 @@ class Exam(ExamBase):
|
||||
related_name="anatomy_exam_author",
|
||||
)
|
||||
|
||||
valid_users = models.ManyToManyField(CidUser, blank=True, related_name="anatomy_exams")
|
||||
|
||||
def get_exam_json(self, based=True):
|
||||
questions = self.exam_questions.all()
|
||||
|
||||
|
||||
@@ -26,4 +26,5 @@ urlpatterns = [
|
||||
),
|
||||
name="examination-autocomplete",
|
||||
),
|
||||
path("cids/manage/", views.create_examination, name="create_examination"),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.8 on 2021-12-11 22:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0015_ciduser_ciduserexam'),
|
||||
('longs', '0054_alter_exam_open_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='valid_users',
|
||||
field=models.ManyToManyField(blank=True, related_name='longs_exams', to='generic.CidUser'),
|
||||
),
|
||||
]
|
||||
+3
-1
@@ -28,7 +28,7 @@ from helpers.images import image_as_base64, pretty_print_dicom
|
||||
|
||||
from anatomy.models import Modality
|
||||
|
||||
from generic.models import Examination, Condition, Sign, ExamBase, Plane, Contrast, QuestionNote
|
||||
from generic.models import CidUser, Examination, Condition, Sign, ExamBase, Plane, Contrast, QuestionNote
|
||||
|
||||
# from generic.models import Examination, Site, Condition, Sign
|
||||
|
||||
@@ -597,6 +597,8 @@ class Exam(ExamBase):
|
||||
|
||||
double_mark = models.BooleanField(default=True, help_text="Defines if an exam is expected to be double marked")
|
||||
|
||||
valid_users = models.ManyToManyField(CidUser, blank=True, related_name="longs_exams")
|
||||
|
||||
def get_exam_question_json(self, question_id):
|
||||
q = get_object_or_404(Long, pk=question_id)
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.8 on 2021-12-11 22:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0015_ciduser_ciduserexam'),
|
||||
('rapids', '0038_alter_rapidimage_image_md5_hash'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='valid_users',
|
||||
field=models.ManyToManyField(blank=True, related_name='rapids_exams', to='generic.CidUser'),
|
||||
),
|
||||
]
|
||||
+136
-107
@@ -24,7 +24,7 @@ from helpers.images import image_as_base64
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
||||
from generic.models import Site, Condition, Sign, ExamBase, QuestionNote
|
||||
from generic.models import CidUser, Site, Condition, Sign, ExamBase, QuestionNote
|
||||
|
||||
import reversion
|
||||
|
||||
@@ -37,31 +37,33 @@ import pydicom.errors
|
||||
|
||||
image_storage = FileSystemStorage(
|
||||
# Physical file location ROOT
|
||||
location=u"{0}".format(settings.MEDIA_ROOT),
|
||||
location="{0}".format(settings.MEDIA_ROOT),
|
||||
# Url for file
|
||||
base_url=u"{0}".format(settings.MEDIA_URL),
|
||||
base_url="{0}".format(settings.MEDIA_URL),
|
||||
)
|
||||
|
||||
|
||||
def image_directory_path(instance, filename):
|
||||
#return u"{0}".format(filename)
|
||||
return u"rapids/picture/{0}".format(filename)
|
||||
# return u"{0}".format(filename)
|
||||
return "rapids/picture/{0}".format(filename)
|
||||
|
||||
|
||||
def get_answer_compare(s):
|
||||
s = s.strip().lower()
|
||||
s = s.translate(str.maketrans('', '', string.punctuation.replace("#", "")))
|
||||
s = s.translate(str.maketrans("", "", string.punctuation.replace("#", "")))
|
||||
return s
|
||||
|
||||
|
||||
class Abnormality(models.Model):
|
||||
name = models.CharField(max_length=200,
|
||||
unique=True,
|
||||
help_text="Primary abnormality on the film(s)")
|
||||
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', )
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
class Region(models.Model):
|
||||
@@ -69,13 +71,14 @@ class Region(models.Model):
|
||||
max_length=200,
|
||||
unique=True,
|
||||
help_text="Region of the abnormality (not including lateralitiy)",
|
||||
blank=True)
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', )
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
class Examination(models.Model):
|
||||
@@ -85,7 +88,8 @@ class Examination(models.Model):
|
||||
return self.examination
|
||||
|
||||
class Meta:
|
||||
ordering = ('examination', )
|
||||
ordering = ("examination",)
|
||||
|
||||
|
||||
class Answer(models.Model):
|
||||
question = models.ForeignKey(
|
||||
@@ -119,8 +123,8 @@ class Answer(models.Model):
|
||||
|
||||
|
||||
class Rapid(models.Model):
|
||||
#author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
||||
#image = models.ImageField()
|
||||
# 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)
|
||||
|
||||
@@ -146,25 +150,28 @@ class Rapid(models.Model):
|
||||
region = models.ManyToManyField(
|
||||
Region,
|
||||
blank=True,
|
||||
help_text="Region of the abnormality (laterality independent)")
|
||||
help_text="Region of the abnormality (laterality independent)",
|
||||
)
|
||||
examination = models.ManyToManyField(
|
||||
Examination, help_text="Name of the (primary) examination")
|
||||
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 = tagulous.models.TagField(
|
||||
help_text="Applies to the answer, not the examination",
|
||||
)
|
||||
# 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 = tagulous.models.TagField(
|
||||
# )
|
||||
# sign = tagulous.models.TagField(
|
||||
# to=Sign, blank=True, help_text='Radiological signs in the question')
|
||||
|
||||
DEFAULT_SITE_ID = 1
|
||||
#site = models.ManyToManyField(
|
||||
# site = models.ManyToManyField(
|
||||
# Site,
|
||||
# related_name="site_rapid",
|
||||
# blank=True,
|
||||
@@ -174,14 +181,16 @@ class Rapid(models.Model):
|
||||
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="rapid_authored_questions")
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text="Author of question",
|
||||
related_name="rapid_authored_questions",
|
||||
)
|
||||
|
||||
scrapped = models.BooleanField(
|
||||
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
|
||||
@@ -189,9 +198,8 @@ class Rapid(models.Model):
|
||||
|
||||
notes = GenericRelation(QuestionNote)
|
||||
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('rapids:question_detail', kwargs={'pk': self.pk})
|
||||
return reverse("rapids:question_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_authors(self):
|
||||
"""Returns a comma seperated text list of authors"""
|
||||
@@ -216,8 +224,10 @@ class Rapid(models.Model):
|
||||
def __str__(self):
|
||||
n = "Normal"
|
||||
if not self.normal:
|
||||
#n = self.answers.first()
|
||||
n = "{}/{} : {}".format(self.pk, self.abnormality.first(), self.region.first())
|
||||
# n = self.answers.first()
|
||||
n = "{}/{} : {}".format(
|
||||
self.pk, self.abnormality.first(), self.region.first()
|
||||
)
|
||||
exams = self.get_examinations()
|
||||
lat = ""
|
||||
if self.laterality != "NONE":
|
||||
@@ -227,8 +237,17 @@ class Rapid(models.Model):
|
||||
def get_primary_answer(self):
|
||||
if self.normal:
|
||||
return "Normal"
|
||||
elif self.answers.filter(proposed=False, status=Answer.MarkOptions.CORRECT).count() > 0:
|
||||
return self.answers.filter(proposed=False, status=Answer.MarkOptions.CORRECT).first().answer
|
||||
elif (
|
||||
self.answers.filter(
|
||||
proposed=False, status=Answer.MarkOptions.CORRECT
|
||||
).count()
|
||||
> 0
|
||||
):
|
||||
return (
|
||||
self.answers.filter(proposed=False, status=Answer.MarkOptions.CORRECT)
|
||||
.first()
|
||||
.answer
|
||||
)
|
||||
else:
|
||||
return "None yet..."
|
||||
|
||||
@@ -261,11 +280,15 @@ class Rapid(models.Model):
|
||||
marked_answers = self.get_marked_answers()
|
||||
|
||||
unmarked_answers = set(
|
||||
[i for i in user_answer_queryset if i.normal == False and i.answer_compare not in marked_answers]
|
||||
[
|
||||
i
|
||||
for i in user_answer_queryset
|
||||
if i.normal == False and i.answer_compare not in marked_answers
|
||||
]
|
||||
)
|
||||
|
||||
return [i.answer_compare for i in unmarked_answers]
|
||||
|
||||
|
||||
# Marker code below is not used
|
||||
if marker is None:
|
||||
return [i.answer_compare for i in unmarked_answers]
|
||||
@@ -275,7 +298,7 @@ class Rapid(models.Model):
|
||||
for answer in unmarked_answers:
|
||||
if answer.mark.filter(marker=marker).count() < 1:
|
||||
marker_unmarked.append(answer.answer_compare)
|
||||
|
||||
|
||||
return marker_unmarked
|
||||
|
||||
def get_unmarked_user_answer_count(self, exam_pk=None, marker=None):
|
||||
@@ -293,19 +316,12 @@ class Rapid(models.Model):
|
||||
if not include_normal:
|
||||
queryset = queryset.filter(normal=False)
|
||||
|
||||
user_answers = set(
|
||||
[i.answer_compare for i in queryset]
|
||||
)
|
||||
user_answers = set([i.answer_compare for i in queryset])
|
||||
|
||||
return user_answers
|
||||
|
||||
def get_compare_answers(self):
|
||||
return set(
|
||||
[
|
||||
i.answer_compare
|
||||
for i in self.answers.filter()
|
||||
]
|
||||
)
|
||||
return set([i.answer_compare for i in self.answers.filter()])
|
||||
|
||||
def get_marked_answers(self):
|
||||
return set(
|
||||
@@ -352,11 +368,14 @@ class Rapid(models.Model):
|
||||
|
||||
def get_image_url_array(self):
|
||||
return json.dumps([f"{REMOTE_URL}{i.url}" for i in self.get_images()])
|
||||
#def GetNonFeedbackQuestionImages(self):
|
||||
#return self.get_images()
|
||||
|
||||
# def GetNonFeedbackQuestionImages(self):
|
||||
# return self.get_images()
|
||||
|
||||
def get_image_annotations(self):
|
||||
return json.dumps([i.image_annotations for i in self.images.all() if not i.feedback_image])
|
||||
return json.dumps(
|
||||
[i.image_annotations for i in self.images.all() if not i.feedback_image]
|
||||
)
|
||||
|
||||
def get_laterality_string(self):
|
||||
if self.laterality == self.NONE:
|
||||
@@ -382,10 +401,12 @@ class Rapid(models.Model):
|
||||
|
||||
compare_answers = self.get_compare_answers()
|
||||
|
||||
new_answers = [a for a in answers if get_answer_compare(a) not in compare_answers]
|
||||
new_answers = [
|
||||
a for a in answers if get_answer_compare(a) not in compare_answers
|
||||
]
|
||||
|
||||
return new_answers
|
||||
|
||||
|
||||
def get_question_json(self, based=True):
|
||||
"""Returns json"""
|
||||
|
||||
@@ -399,7 +420,9 @@ class Rapid(models.Model):
|
||||
if based:
|
||||
feedback_images.append(image_as_base64(i.image))
|
||||
else:
|
||||
feedback_images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
feedback_images.append(
|
||||
"{}/{}".format(settings.REMOTE_URL, i.image.url)
|
||||
)
|
||||
feedback_images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
else:
|
||||
if based:
|
||||
@@ -407,11 +430,9 @@ class Rapid(models.Model):
|
||||
else:
|
||||
images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
|
||||
|
||||
|
||||
json = {
|
||||
"images": images,
|
||||
#"feedback_image": [],
|
||||
# "feedback_image": [],
|
||||
"annotations": annotations,
|
||||
"type": "rapid",
|
||||
}
|
||||
@@ -423,16 +444,15 @@ class Rapid(models.Model):
|
||||
return json
|
||||
|
||||
|
||||
#reversion.register(Rapid, follow=["images"])
|
||||
#@reversion.register
|
||||
# reversion.register(Rapid, follow=["images"])
|
||||
# @reversion.register
|
||||
class RapidImage(models.Model):
|
||||
rapid = models.ForeignKey(Rapid,
|
||||
related_name="images",
|
||||
on_delete=models.CASCADE)
|
||||
rapid = models.ForeignKey(Rapid, related_name="images", on_delete=models.CASCADE)
|
||||
image = models.FileField(upload_to=image_directory_path)
|
||||
|
||||
image_annotations = models.TextField(
|
||||
blank=True, null=True,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Stores a JSON representation of annotations to be applied by cornerstonetools",
|
||||
)
|
||||
|
||||
@@ -445,12 +465,14 @@ class RapidImage(models.Model):
|
||||
def image_tag(self):
|
||||
if self.image:
|
||||
return mark_safe(
|
||||
'<img src="{}" class="admin-rapid-image" /><span class="admin-rapid-image-info">Click and hold to zoom<span>'
|
||||
.format(self.image.url))
|
||||
'<img src="{}" class="admin-rapid-image" /><span class="admin-rapid-image-info">Click and hold to zoom<span>'.format(
|
||||
self.image.url
|
||||
)
|
||||
)
|
||||
else:
|
||||
return ""
|
||||
|
||||
image_tag.short_description = 'Image'
|
||||
image_tag.short_description = "Image"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Override save method to add image hash"""
|
||||
@@ -461,25 +483,24 @@ class RapidImage(models.Model):
|
||||
# and generate a hash from the pixel data
|
||||
# TODO: improve?
|
||||
dataset = pydicom.dcmread(self.image)
|
||||
#flatten = dataset.pixel_array.astype(str).flatten()
|
||||
#print("flatteded")
|
||||
#pre_join = ",".join(flatten)
|
||||
#print(pre_join)
|
||||
#hash = hashlib.md5(pre_join.encode()).hexdigest()
|
||||
#----
|
||||
# flatten = dataset.pixel_array.astype(str).flatten()
|
||||
# print("flatteded")
|
||||
# pre_join = ",".join(flatten)
|
||||
# print(pre_join)
|
||||
# hash = hashlib.md5(pre_join.encode()).hexdigest()
|
||||
# ----
|
||||
md5 = hashlib.md5()
|
||||
first = True
|
||||
for i in dataset.pixel_array.astype(str).flatten():
|
||||
if first:
|
||||
first=False
|
||||
first = False
|
||||
md5.update(f"{i}".encode())
|
||||
else:
|
||||
else:
|
||||
md5.update(f",{i}".encode())
|
||||
|
||||
hash = md5.hexdigest()
|
||||
#----
|
||||
# ----
|
||||
|
||||
|
||||
self.is_dicom = True
|
||||
except pydicom.errors.InvalidDicomError:
|
||||
self.image.file.open()
|
||||
@@ -490,13 +511,12 @@ class RapidImage(models.Model):
|
||||
|
||||
|
||||
class RapidCreationDefault(models.Model):
|
||||
#author = models.OneToOneField(User,
|
||||
# author = models.OneToOneField(User,
|
||||
author = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
related_name="rapid_default",
|
||||
on_delete=models.CASCADE)
|
||||
settings.AUTH_USER_MODEL, related_name="rapid_default", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
#site = models.ManyToManyField(
|
||||
# site = models.ManyToManyField(
|
||||
# Site,
|
||||
# related_name="site_rapid_creation_default",
|
||||
# blank=True,
|
||||
@@ -504,24 +524,30 @@ class RapidCreationDefault(models.Model):
|
||||
# help_text="Default site to use when creating a new rapid")
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('rapids:rapid_create')
|
||||
return reverse("rapids:rapid_create")
|
||||
|
||||
#@reversion.register
|
||||
|
||||
# @reversion.register
|
||||
class Exam(ExamBase):
|
||||
app_name = "rapids"
|
||||
|
||||
exam_questions = SortedManyToManyField(
|
||||
Rapid, related_name="exams", blank="true"
|
||||
)
|
||||
|
||||
exam_questions = SortedManyToManyField(Rapid, related_name="exams", blank="true")
|
||||
|
||||
time_limit = models.IntegerField(
|
||||
help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)", default=2100
|
||||
help_text="Exam time limit (in seconds). Default is 2100 secondse (35 minutes)",
|
||||
default=2100,
|
||||
)
|
||||
|
||||
author = models.ManyToManyField(settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text='Author of exam',
|
||||
related_name="rapid_exam_author")
|
||||
author = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text="Author of exam",
|
||||
related_name="rapid_exam_author",
|
||||
)
|
||||
|
||||
valid_users = models.ManyToManyField(
|
||||
CidUser, blank=True, related_name="rapids_exams"
|
||||
)
|
||||
|
||||
def get_normal_abnormal_breakdown(self):
|
||||
# Inefficient but more extendible
|
||||
@@ -534,9 +560,8 @@ class Exam(ExamBase):
|
||||
normal.append(q)
|
||||
else:
|
||||
abnormal.append(q)
|
||||
|
||||
return len(normal)
|
||||
|
||||
return len(normal)
|
||||
|
||||
def get_exam_json(self, based=True):
|
||||
questions = self.exam_questions.all()
|
||||
@@ -556,32 +581,32 @@ class Exam(ExamBase):
|
||||
if based:
|
||||
feedback_images.append(image_as_base64(i.image))
|
||||
else:
|
||||
feedback_images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
feedback_images.append(
|
||||
"{}/{}".format(settings.REMOTE_URL, i.image.url)
|
||||
)
|
||||
else:
|
||||
if based:
|
||||
images.append(image_as_base64(i.image))
|
||||
else:
|
||||
images.append("{}/{}".format(settings.REMOTE_URL, i.image.url))
|
||||
|
||||
|
||||
|
||||
exam_questions[q.id] = {
|
||||
"images": images,
|
||||
#"feedback_image": [],
|
||||
#"annotations": [str(q.image_annotations)],
|
||||
# "feedback_image": [],
|
||||
# "annotations": [str(q.image_annotations)],
|
||||
"type": "rapid",
|
||||
}
|
||||
|
||||
if not self.exam_mode:
|
||||
exam_questions[q.id]["normal"] = q.normal
|
||||
exam_questions[q.id]["feedback_images"] = feedback_images
|
||||
exam_questions[q.id]["answers"] = list(q.get_correct_unstripped_answers())
|
||||
exam_questions[q.id]["answers"] = list(
|
||||
q.get_correct_unstripped_answers()
|
||||
)
|
||||
|
||||
|
||||
#if feedback_images:
|
||||
# if feedback_images:
|
||||
# exam_questions[q.id]["feedback_image"] = feedback_images
|
||||
|
||||
|
||||
exam_json = {
|
||||
"eid": "rapid/{}".format(self.id),
|
||||
"cached": False,
|
||||
@@ -598,7 +623,7 @@ class Exam(ExamBase):
|
||||
return exam_json
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('rapids:exam_overview', kwargs={'pk': self.pk})
|
||||
return reverse("rapids:exam_overview", kwargs={"pk": self.pk})
|
||||
|
||||
|
||||
@reversion.register
|
||||
@@ -615,7 +640,11 @@ class CidUserAnswer(models.Model):
|
||||
|
||||
answer_compare = models.TextField(max_length=500, blank=True)
|
||||
|
||||
cid = models.BigIntegerField(blank=True, null=True, help_text="Candidate ID (limitied by BigIntegerField size)")
|
||||
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(
|
||||
@@ -643,7 +672,7 @@ class CidUserAnswer(models.Model):
|
||||
self.answer = self.answer.strip()
|
||||
|
||||
s = self.answer.lower()
|
||||
s = s.translate(str.maketrans('', '', string.punctuation.replace("#", "")))
|
||||
s = s.translate(str.maketrans("", "", string.punctuation.replace("#", "")))
|
||||
|
||||
self.answer_compare = s
|
||||
|
||||
@@ -671,9 +700,9 @@ class CidUserAnswer(models.Model):
|
||||
ans = self.answer_compare
|
||||
|
||||
# TODO: this should be cleaned up as we don't want duplicates...
|
||||
#try:
|
||||
# try:
|
||||
# marked_ans = q.answers.get(answer_compare__iexact=ans)
|
||||
#except Answer.DoesNotExist:
|
||||
# except Answer.DoesNotExist:
|
||||
# marked_ans = None
|
||||
marked_ans = q.answers.filter(answer_compare__iexact=ans).first()
|
||||
|
||||
@@ -708,4 +737,4 @@ class CidUserAnswer(models.Model):
|
||||
mark = 0
|
||||
else:
|
||||
mark = "unmarked"
|
||||
return mark
|
||||
return mark
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.8 on 2021-12-11 22:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('generic', '0015_ciduser_ciduserexam'),
|
||||
('sbas', '0006_alter_exam_open_access'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='exam',
|
||||
name='valid_users',
|
||||
field=models.ManyToManyField(blank=True, related_name='sba_exams', to='generic.CidUser'),
|
||||
),
|
||||
]
|
||||
+2
-1
@@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
from generic.models import ExamBase, QuestionNote
|
||||
from generic.models import CidUser, ExamBase, QuestionNote
|
||||
|
||||
import reversion
|
||||
|
||||
@@ -159,6 +159,7 @@ class Exam(ExamBase):
|
||||
help_text='Author of exam',
|
||||
related_name="sba_exam_author")
|
||||
|
||||
valid_users = models.ManyToManyField(CidUser, blank=True, related_name="sba_exams")
|
||||
|
||||
|
||||
@reversion.register
|
||||
|
||||
Reference in New Issue
Block a user