.
This commit is contained in:
+8
-2
@@ -1,5 +1,6 @@
|
||||
# pull official base image
|
||||
FROM python:3.12.1-slim
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
|
||||
# set work directory
|
||||
WORKDIR /usr/src/rad
|
||||
@@ -17,9 +18,14 @@ RUN apt-get update && apt-get -y install postgresql postgresql-contrib git libca
|
||||
|
||||
|
||||
# install dependencies
|
||||
RUN pip install --upgrade pip
|
||||
#RUN pip install --upgrade pip
|
||||
COPY ./requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
RUN uv venv /opt/venv
|
||||
# Use the virtual environment automatically
|
||||
ENV VIRTUAL_ENV=/opt/venv
|
||||
# Place entry points in the environment at the front of the path
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
RUN uv pip install -r requirements.txt
|
||||
|
||||
# copy project
|
||||
COPY . .
|
||||
@@ -22,6 +22,7 @@ from collections import defaultdict
|
||||
from helpers.images import image_as_base64
|
||||
|
||||
import reversion
|
||||
import urllib.parse
|
||||
|
||||
image_storage = FileSystemStorage(
|
||||
# Physical file location ROOT
|
||||
@@ -540,6 +541,8 @@ class UserAnswer(UserAnswerBase):
|
||||
# s = self.answer.lower().strip()
|
||||
# s = s.translate(str.maketrans('', '', string.punctuation))
|
||||
# return s
|
||||
def get_answer_url_safe(self):
|
||||
return urllib.parse.quote(self.answer_compare)
|
||||
|
||||
def get_answer_string(self):
|
||||
return self.answer
|
||||
|
||||
@@ -38,22 +38,22 @@
|
||||
{% for answer in user_answers %}
|
||||
|
||||
{% if answer in answer_suggest_incorrect %}
|
||||
<li><pre><span class="answer incorrect suggest" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer %}">{{ answer }}</span></pre></li>
|
||||
<li><pre><span class="answer incorrect suggest" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer.get_answer_url_safe %}">{{ answer }}</span></pre></li>
|
||||
{% else %}
|
||||
<li><pre><span class="answer not-marked" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer %}">{{ answer }}</span></pre></li>
|
||||
<li><pre><span class="answer not-marked" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer.get_answer_url_safe %}">{{ answer }}</span></pre></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
Marked:
|
||||
<ul id="marked-answer-list" class="answer-list">
|
||||
{% for answer in correct_answers %}
|
||||
<li><pre><span class="answer correct" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer %}">{{ answer }}</span></pre></li>
|
||||
<li><pre><span class="answer correct" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer.get_answer_url_safe %}">{{ answer }}</span></pre></li>
|
||||
{% endfor %}
|
||||
{% for answer in half_mark_answers %}
|
||||
<li><pre><span class="answer half-correct" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer %}">{{ answer }}</span></pre></li>
|
||||
<li><pre><span class="answer half-correct" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer.get_answer_url_safe %}">{{ answer }}</span></pre></li>
|
||||
{% endfor %}
|
||||
{% for answer in incorrect_answers %}
|
||||
<li><pre><span class="answer incorrect" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer %}">{{ answer }}</span></pre></li>
|
||||
<li><pre><span class="answer incorrect" title="{{answer}}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer.get_answer_url_safe %}">{{ answer }}</span></pre></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="mark-buttons">
|
||||
|
||||
@@ -199,7 +199,13 @@
|
||||
|
||||
|
||||
<p class="pre-whitespace"><b>Previous case:</b> {{ case.previous_case.get_link }}</p>
|
||||
<p class="pre-whitespace"><b>Next case:</b> {{ case.next_case.get_link }}</p>
|
||||
<p class="pre-whitespace"><b>Next case:</b>
|
||||
{% if case.next_case %}
|
||||
{{ case.next_case.get_link }}
|
||||
{% else %}
|
||||
<a href="{% url 'atlas:case_clone_next' case.pk %}">Clone and add next case</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div>
|
||||
{% include 'question_notes.html' %}
|
||||
|
||||
|
||||
@@ -306,6 +306,7 @@ urlpatterns = [
|
||||
name="structure_update",
|
||||
),
|
||||
path("structure/create", views.StructureCreate.as_view(), name="structure_create"),
|
||||
path("series/<int:pk>/thumbnail/fail", views.series_thumbnail_fail, name="series_thumbnail_fail"),
|
||||
path("series/<int:pk>/thumbnail", views.series_thumbnail, name="series_thumbnail"),
|
||||
# TODO: case context series viewing (so that we can view series in the context of a case)
|
||||
path("series/<int:pk>", views.series_detail, name="series_detail"),
|
||||
@@ -372,6 +373,7 @@ urlpatterns = [
|
||||
name="case_collection_form",
|
||||
),
|
||||
path("case/<int:pk>/clone", views.AtlasClone.as_view(), name="case_clone"),
|
||||
path("case/<int:pk>/clone/next", views.AtlasCloneNext.as_view(), name="case_clone_next"),
|
||||
# path("verified/", views.verified, name="verified"),
|
||||
# path("all_questions/", views.all_questions, name="all_questions"),
|
||||
path("case/<int:pk>/scrap", views.atlas_scrap, name="case_scrap"),
|
||||
|
||||
+14
-4
@@ -197,13 +197,17 @@ def case_detail(request, pk):
|
||||
# logging.debug(atlas.subspecialty.first().name.all())
|
||||
return render(request, "atlas/case_detail.html", {"case": case})
|
||||
|
||||
@login_required
|
||||
@user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access
|
||||
def series_thumbnail_fail(request, pk):
|
||||
return series_thumbnail(request, pk=pk, fail_loudly=True)
|
||||
|
||||
@login_required
|
||||
@user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access
|
||||
def series_thumbnail(request, pk, finding_pk=None):
|
||||
def series_thumbnail(request, pk, fail_loudly=False):
|
||||
series = get_object_or_404(Series, pk=pk)
|
||||
|
||||
thumbnail, _ = series.get_thumbnail(recreate=True)
|
||||
thumbnail, _ = series.get_thumbnail(recreate=True, fail_loudly=fail_loudly)
|
||||
|
||||
return HttpResponse(thumbnail)
|
||||
|
||||
@@ -1115,7 +1119,6 @@ def add_collection_to_case_form(request, case_id):
|
||||
# pk = self.kwargs["pk"]
|
||||
# return reverse("atlas:case_detail", kwargs={"pk": pk})
|
||||
|
||||
|
||||
class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
|
||||
"""Clones a existing atlas"""
|
||||
|
||||
@@ -1138,7 +1141,14 @@ class AtlasClone(AtlasCreateBase, AuthorOrCheckerRequiredMixin, CreateView):
|
||||
else:
|
||||
pass
|
||||
|
||||
initial_data = model_to_dict(old_object, exclude=["id"])
|
||||
initial_data = model_to_dict(old_object, exclude=["id", "previous_case"])
|
||||
return initial_data
|
||||
|
||||
class AtlasCloneNext(AtlasClone):
|
||||
"""Extends AtlasClone to set the previous_case field to the case being cloned"""
|
||||
def get_initial(self):
|
||||
initial_data = super().get_initial()
|
||||
initial_data["previous_case"] = self.kwargs["pk"]
|
||||
return initial_data
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: ./rad
|
||||
|
||||
+6
-2
@@ -42,6 +42,8 @@ from django.contrib.auth.models import User
|
||||
from django.forms.models import model_to_dict
|
||||
from django.forms.utils import from_current_timezone, to_current_timezone
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def findMiddle(input_list):
|
||||
"""Returns the middle element of a list"""
|
||||
@@ -419,7 +421,7 @@ class SeriesBase(models.Model):
|
||||
else:
|
||||
return format_html('", "'.join(images))
|
||||
|
||||
def get_thumbnail(self, recreate=False):
|
||||
def get_thumbnail(self, recreate=False, fail_loudly=False):
|
||||
images = self.images.filter(removed=False)
|
||||
|
||||
if len(images) < 1:
|
||||
@@ -431,7 +433,9 @@ class SeriesBase(models.Model):
|
||||
if recreate:
|
||||
thumbnailer.delete_thumbnails()
|
||||
thumbnail = thumbnailer["exam-list"]
|
||||
except InvalidImageFormatError:
|
||||
except InvalidImageFormatError as e:
|
||||
if fail_loudly:
|
||||
raise e
|
||||
return format_html(
|
||||
'<img title="{}" src="/static/not-found-image.jpg" />', img
|
||||
), len(images)
|
||||
|
||||
@@ -33,8 +33,10 @@ from django.utils.decorators import method_decorator
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
import json
|
||||
import urllib
|
||||
import urllib.parse
|
||||
|
||||
from django.views import View
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||
@@ -2652,6 +2654,7 @@ class GenericViewBase:
|
||||
@method_decorator(login_required)
|
||||
def question_user_answers_by_compare(self, request, pk, answer_compare):
|
||||
print(answer_compare)
|
||||
answer_compare = urllib.parse.unquote(answer_compare)
|
||||
return self.question_user_answers(request, pk, answer_compare)
|
||||
|
||||
@method_decorator(login_required)
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ def pil_dicom_image(source, exif_orientation=True, **options):
|
||||
#source = BytesIO(source.read())
|
||||
|
||||
# open file with pydicom
|
||||
ds = pydicom.read_file(source)
|
||||
ds = pydicom.dcmread(source)
|
||||
|
||||
# return the image file
|
||||
return get_PIL_image(ds)
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
#Django==3.2.13
|
||||
Django==5.0.2
|
||||
Django==5.1.4
|
||||
django_debug_toolbar
|
||||
django_jquery
|
||||
django_reversion
|
||||
@@ -53,3 +53,4 @@ django_svelte_jsoneditor
|
||||
psutil
|
||||
django-psutil-dash
|
||||
django-template-partials
|
||||
easy_thumbnails
|
||||
Reference in New Issue
Block a user