fix a few things
This commit is contained in:
@@ -502,54 +502,6 @@ class CaseCollection(ExamCollectionGenericBase):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_author_objects(self):
|
|
||||||
"""Returns a comma seperated text list of authors"""
|
|
||||||
authors = [i for i in self.author.all()]
|
|
||||||
return authors
|
|
||||||
|
|
||||||
def check_cid_user(self, cid, passcode, request=None):
|
|
||||||
if request is not None and request.user.is_superuser:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self.valid_cid_users.exists():
|
|
||||||
user = self.valid_cid_users.filter(cid=cid).first()
|
|
||||||
|
|
||||||
if not user or user.passcode != passcode:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_or_create_cid_user_exam(self, cid_user, start_time=None):
|
|
||||||
content_type = ContentType.objects.get_for_model(self)
|
|
||||||
c = CidUserExam.objects.filter(
|
|
||||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
|
||||||
).first()
|
|
||||||
if c:
|
|
||||||
return c
|
|
||||||
|
|
||||||
if start_time is None:
|
|
||||||
start_time = timezone.now()
|
|
||||||
new = CidUserExam(
|
|
||||||
content_type=content_type,
|
|
||||||
object_id=self.pk,
|
|
||||||
cid_user=cid_user,
|
|
||||||
start_time=start_time,
|
|
||||||
)
|
|
||||||
new.save()
|
|
||||||
return new
|
|
||||||
|
|
||||||
def get_cid_user_exams(self, cid_user=None):
|
|
||||||
content_type = ContentType.objects.get_for_model(self)
|
|
||||||
if cid_user is None:
|
|
||||||
return CidUserExam.objects.filter(
|
|
||||||
content_type=content_type,
|
|
||||||
object_id=self.pk,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return CidUserExam.objects.filter(
|
|
||||||
content_type=content_type, object_id=self.pk, cid_user=cid_user
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CaseDetail(models.Model):
|
class CaseDetail(models.Model):
|
||||||
case = models.ForeignKey(Case, on_delete=models.CASCADE)
|
case = models.ForeignKey(Case, on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -28,13 +28,15 @@
|
|||||||
{% for series in case.series.all %}
|
{% for series in case.series.all %}
|
||||||
<span class="series-block">
|
<span class="series-block">
|
||||||
<span>
|
<span>
|
||||||
<span class="series-block-series-number">Series {{ forloop.counter }}:</span>
|
<span class="series-block-series-number">Series {{ forloop.counter }}:</span><br>
|
||||||
<a href="{% url 'atlas:series_detail' pk=series.pk %}">
|
<a href="{% url 'atlas:series_detail' pk=series.pk %}">
|
||||||
{{series.get_block}}
|
{{series.get_block}}
|
||||||
</a>
|
</a>
|
||||||
|
<br>
|
||||||
|
<span class="series-block-popup-link">
|
||||||
<a href="#"
|
<a href="#"
|
||||||
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
|
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
{% for finding in series.findings.all %}
|
{% for finding in series.findings.all %}
|
||||||
<div class="finding-box">
|
<div class="finding-box">
|
||||||
<button class="view-finding-button" data-annotationjson={{finding.annotation_json}}
|
<button class="view-finding-button" data-annotationjson={{finding.annotation_json}}
|
||||||
data-viewportjson={{finding.viewport_json}} data-findingid={{finding.id}}>Click to view</button>
|
data-viewportjson={{finding.viewport_json}} data-findingid={{finding.id}}>Click to view</button>
|
||||||
<span class="view-finding-details">
|
<span class="view-finding-details">
|
||||||
Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
|
Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
|
||||||
Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
|
Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
|
||||||
@@ -58,18 +58,24 @@
|
|||||||
<summary>Image info</summary>
|
<summary>Image info</summary>
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'atlas:series_anonymise_dicom' pk=series.pk %}"
|
<a href="{% url 'atlas:series_anonymise_dicom' pk=series.pk %}"
|
||||||
title="Anonymise dicom images">Anonymise dicoms</a><br />
|
title="Anonymise dicom images">Anonymise dicoms</a><br />
|
||||||
<a href="{% url 'atlas:series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms
|
<a href="{% url 'atlas:series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms
|
||||||
by slice location</a>
|
by slice location</a>
|
||||||
<a href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}"
|
<a href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}"
|
||||||
title="orders dicom by instance number">Order dicoms by instance number</a>
|
title="orders dicom by instance number">Order dicoms by instance number</a>
|
||||||
<a href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}"
|
<a href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}"
|
||||||
title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
|
title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
|
||||||
<a href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}"
|
<a href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}"
|
||||||
title="orders dicom by uploaded filename">Order by uploaded filename</a>
|
title="orders dicom by uploaded filename">Order by uploaded filename</a>
|
||||||
</div>
|
</div>
|
||||||
{% for image in series.images.all %}
|
{% for image in series.images.all %}
|
||||||
{{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} [{{image.image.size|filesizeformat}}]<br />
|
{{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}}
|
||||||
|
|
||||||
|
|
||||||
|
{% if image.image %}
|
||||||
|
[{{image.image.size|filesizeformat}}]
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
|
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -159,13 +165,13 @@
|
|||||||
viewport_json: JSON.stringify(c.viewport),
|
viewport_json: JSON.stringify(c.viewport),
|
||||||
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
|
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
|
||||||
.map((i, el) => {
|
.map((i, el) => {
|
||||||
return $(el).val()
|
return $(el).val()
|
||||||
}).toArray()),
|
}).toArray()),
|
||||||
structures: JSON.stringify($('#finding-form select[name="structures"]').find(
|
structures: JSON.stringify($('#finding-form select[name="structures"]').find(
|
||||||
":selected")
|
":selected")
|
||||||
.map((i, el) => {
|
.map((i, el) => {
|
||||||
return $(el).val()
|
return $(el).val()
|
||||||
}).toArray()),
|
}).toArray()),
|
||||||
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
|
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
|
||||||
action: 'post'
|
action: 'post'
|
||||||
},
|
},
|
||||||
|
|||||||
+19
-8
@@ -116,6 +116,7 @@ class QuestionBase(models.Model):
|
|||||||
"""If this makes sense in the question/answer context override"""
|
"""If this makes sense in the question/answer context override"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SeriesImageBase(models.Model):
|
class SeriesImageBase(models.Model):
|
||||||
position = models.IntegerField(default=0)
|
position = models.IntegerField(default=0)
|
||||||
upload_filename = models.CharField(max_length=255, blank=True)
|
upload_filename = models.CharField(max_length=255, blank=True)
|
||||||
@@ -206,9 +207,9 @@ class SeriesBase(models.Model):
|
|||||||
thumbnailer = get_thumbnailer(img)
|
thumbnailer = get_thumbnailer(img)
|
||||||
thumbnail = thumbnailer["exam-list"]
|
thumbnail = thumbnailer["exam-list"]
|
||||||
except InvalidImageFormatError:
|
except InvalidImageFormatError:
|
||||||
return format_html('<img title="{}" src="/static/not-found-image.jpg" />', img), len(
|
return format_html(
|
||||||
images
|
'<img title="{}" src="/static/not-found-image.jpg" />', img
|
||||||
)
|
), len(images)
|
||||||
return format_html('<img src="/media/{}" />', thumbnail), len(images)
|
return format_html('<img src="/media/{}" />', thumbnail), len(images)
|
||||||
|
|
||||||
def get_thumbnail_link(self):
|
def get_thumbnail_link(self):
|
||||||
@@ -216,21 +217,30 @@ class SeriesBase(models.Model):
|
|||||||
"<a href='{}'>{}<a/>", self.get_absolute_url(), self.get_thumbnail()[0]
|
"<a href='{}'>{}<a/>", self.get_absolute_url(), self.get_thumbnail()[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_block(self):
|
def get_block(self, series_number: None | int = None):
|
||||||
examination = self.get_examination_full()
|
examination = self.get_examination_full()
|
||||||
thumb, image_number = self.get_thumbnail()
|
thumb, image_number = self.get_thumbnail()
|
||||||
|
|
||||||
|
series_html = ""
|
||||||
|
if series_number is not None:
|
||||||
|
series_html = format_html(
|
||||||
|
"<span class='series-block-series-number'>Series {}</span><br>",
|
||||||
|
series_number,
|
||||||
|
)
|
||||||
|
|
||||||
return format_html(
|
return format_html(
|
||||||
"""<span>
|
"""{}
|
||||||
|
<span>
|
||||||
<span class='series-block-examination'>{}</span><br/>
|
<span class='series-block-examination'>{}</span><br/>
|
||||||
<span class='series-block-thumbnail'>{}</span><br/>
|
<span class='series-block-thumbnail'>{}</span><br/>
|
||||||
<span class='series-block-image-number'>Images: <span class='series-block-image-number-count'>{}</span></span>
|
<span class='series-block-image-number'>Images: <span class='series-block-image-number-count'>{}</span></span>
|
||||||
</span>""",
|
</span>""",
|
||||||
|
series_html,
|
||||||
examination,
|
examination,
|
||||||
thumb,
|
thumb,
|
||||||
image_number,
|
image_number,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def order_by_upload_filename(self):
|
def order_by_upload_filename(self):
|
||||||
images = self.images.all()
|
images = self.images.all()
|
||||||
|
|
||||||
@@ -311,12 +321,14 @@ class SeriesBase(models.Model):
|
|||||||
except pydicom.errors.InvalidDicomError:
|
except pydicom.errors.InvalidDicomError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ExamCollectionGenericBase(models.Model):
|
class ExamCollectionGenericBase(models.Model):
|
||||||
"""Holds functions that relate to both case and other exams
|
"""Holds functions that relate to both case and other exams
|
||||||
|
|
||||||
e.g. user management
|
e.g. user management
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Is this actually used?
|
# Is this actually used?
|
||||||
cid_users = GenericRelation("generic.CidUserExam")
|
cid_users = GenericRelation("generic.CidUserExam")
|
||||||
|
|
||||||
@@ -429,6 +441,7 @@ class ExamCollectionGenericBase(models.Model):
|
|||||||
content_type=content_type, object_id=self.pk, user_user=user_user
|
content_type=content_type, object_id=self.pk, user_user=user_user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExamBase(ExamCollectionGenericBase):
|
class ExamBase(ExamCollectionGenericBase):
|
||||||
name = models.CharField(max_length=200, help_text="Name of the exam")
|
name = models.CharField(max_length=200, help_text="Name of the exam")
|
||||||
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
# exam_questions = SortedManyToManyField(Long, related_name="exams", blank="true")
|
||||||
@@ -490,7 +503,6 @@ class ExamBase(ExamCollectionGenericBase):
|
|||||||
|
|
||||||
user_scores = models.JSONField(default=dict)
|
user_scores = models.JSONField(default=dict)
|
||||||
|
|
||||||
|
|
||||||
exam_results_emailed = models.DateTimeField(default=None, null=True)
|
exam_results_emailed = models.DateTimeField(default=None, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -547,7 +559,6 @@ class ExamBase(ExamCollectionGenericBase):
|
|||||||
def get_question_index(self, question):
|
def get_question_index(self, question):
|
||||||
return list(self.exam_questions.all()).index(question)
|
return list(self.exam_questions.all()).index(question)
|
||||||
|
|
||||||
|
|
||||||
def get_cid_user_score(self, cid_user):
|
def get_cid_user_score(self, cid_user):
|
||||||
c = "c/" + str(cid_user)
|
c = "c/" + str(cid_user)
|
||||||
if c in self.user_scores:
|
if c in self.user_scores:
|
||||||
|
|||||||
+3
-2
@@ -87,6 +87,7 @@ from django.db.models import Case, When
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from django.utils import timezone
|
||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
@@ -1626,7 +1627,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
return redirect(url)
|
return redirect(url)
|
||||||
return JsonResponse(exam_json_cache)
|
return JsonResponse(exam_json_cache)
|
||||||
|
|
||||||
time = datetime.now()
|
time = timezone.now()
|
||||||
|
|
||||||
exam.exam_json_id += 1
|
exam.exam_json_id += 1
|
||||||
|
|
||||||
@@ -1672,7 +1673,7 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
if not exam.check_cid_user(cid, passcode, request, user_id):
|
if not exam.check_cid_user(cid, passcode, request, user_id):
|
||||||
raise Http404("No available exam")
|
raise Http404("No available exam")
|
||||||
|
|
||||||
time = datetime.now()
|
time = timezone.now()
|
||||||
|
|
||||||
exam_json = exam.get_exam_json(based=False)
|
exam_json = exam.get_exam_json(based=False)
|
||||||
exam_json["generated"] = time.isoformat()
|
exam_json["generated"] = time.isoformat()
|
||||||
|
|||||||
@@ -571,12 +571,21 @@ td.user-answer-score-2.rapid-ans::after {
|
|||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.series-block a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.series-block-image-number-count {
|
.series-block-image-number-count {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.series-block-popup-link {
|
.series-block-popup-link {
|
||||||
font-size: small;
|
font-size: smaller;
|
||||||
color: darkblue
|
}
|
||||||
|
.series-block-popup-link a {
|
||||||
|
color: rgb(0, 153, 255);
|
||||||
|
opacity: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.series-block img {
|
.series-block img {
|
||||||
@@ -820,6 +829,10 @@ input {
|
|||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-results__option {
|
||||||
|
background-color: #05668d;
|
||||||
|
}
|
||||||
|
|
||||||
.select2-search__field {
|
.select2-search__field {
|
||||||
background-color: #05668d;
|
background-color: #05668d;
|
||||||
color: darkblue;
|
color: darkblue;
|
||||||
|
|||||||
Reference in New Issue
Block a user