improve cases series handling in collection
This commit is contained in:
+14
-1
@@ -953,7 +953,13 @@ CaseCollectionCaseFormSet = inlineformset_factory(
|
||||
CaseCollection,
|
||||
Case.casecollection_set.through,
|
||||
form=CaseCollectionCaseForm,
|
||||
exclude=["question_schema", "question_answers"],
|
||||
exclude=[
|
||||
"question_schema",
|
||||
"question_answers",
|
||||
"series_visibility_config",
|
||||
"learner_comment",
|
||||
"learner_review_comment",
|
||||
],
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
max_num=50,
|
||||
@@ -1370,6 +1376,8 @@ class CaseDetailForm(ModelForm):
|
||||
fields = [
|
||||
"redact_history",
|
||||
"override_history",
|
||||
"learner_comment",
|
||||
"learner_review_comment",
|
||||
"question_time_limit_override",
|
||||
"answer_entry_grace_period_override",
|
||||
]
|
||||
@@ -1443,6 +1451,11 @@ class CaseDetailForm(ModelForm):
|
||||
HTML(history_block),
|
||||
Field("override_history"),
|
||||
Field("redact_history"),
|
||||
Fieldset(
|
||||
"Learner Comments",
|
||||
Field("learner_comment"),
|
||||
Field("learner_review_comment"),
|
||||
),
|
||||
Fieldset(
|
||||
"Case Timing Overrides",
|
||||
Field("question_time_limit_override"),
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 6.0.1 on 2026-06-01 11:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0105_case_study_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='casedetail',
|
||||
name='learner_comment',
|
||||
field=models.TextField(blank=True, default='', help_text='Comment shown to the learner while taking the case and on review.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='casedetail',
|
||||
name='learner_review_comment',
|
||||
field=models.TextField(blank=True, default='', help_text='Comment shown to the learner only on review.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='casedetail',
|
||||
name='series_visibility_config',
|
||||
field=models.JSONField(blank=True, default=dict, help_text='Per-series visibility map for this case in this collection.'),
|
||||
),
|
||||
]
|
||||
+80
-4
@@ -2217,6 +2217,29 @@ class CaseDetail(models.Model):
|
||||
help_text="Optional per-case override for additional answer-only time in seconds after case view lock.",
|
||||
)
|
||||
|
||||
class SeriesVisibility(models.TextChoices):
|
||||
ALWAYS = "AL", _("Show all the time")
|
||||
REVIEW = "RE", _("Show on review")
|
||||
HIDDEN = "NO", _("Do not show")
|
||||
|
||||
series_visibility_config = models.JSONField(
|
||||
default=dict,
|
||||
blank=True,
|
||||
help_text="Per-series visibility map for this case in this collection.",
|
||||
)
|
||||
|
||||
learner_comment = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="Comment shown to the learner while taking the case and on review.",
|
||||
)
|
||||
|
||||
learner_review_comment = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="Comment shown to the learner only on review.",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("sort_order",)
|
||||
|
||||
@@ -2270,23 +2293,72 @@ class CaseDetail(models.Model):
|
||||
return self.override_history
|
||||
return self.case.history or "No history provided"
|
||||
|
||||
def get_case_series_nested(self, include_priors=True):
|
||||
case_series_images = self.case.get_series_images_nested(as_json=False)
|
||||
def get_series_visibility_map(self) -> dict[str, str]:
|
||||
raw_map = self.series_visibility_config or {}
|
||||
if not isinstance(raw_map, dict):
|
||||
return {}
|
||||
|
||||
valid_values = {
|
||||
self.SeriesVisibility.ALWAYS,
|
||||
self.SeriesVisibility.REVIEW,
|
||||
self.SeriesVisibility.HIDDEN,
|
||||
}
|
||||
cleaned = {}
|
||||
for key, value in raw_map.items():
|
||||
key_str = str(key)
|
||||
value_str = str(value)
|
||||
if value_str in valid_values:
|
||||
cleaned[key_str] = value_str
|
||||
return cleaned
|
||||
|
||||
def get_series_visibility_for(self, series, review_mode: bool = False) -> str:
|
||||
visibility = self.get_series_visibility_map().get(
|
||||
str(series.pk), self.SeriesVisibility.ALWAYS
|
||||
)
|
||||
if visibility == self.SeriesVisibility.REVIEW and not review_mode:
|
||||
return self.SeriesVisibility.HIDDEN
|
||||
return visibility
|
||||
|
||||
def get_visible_case_series(self, review_mode: bool = False):
|
||||
visible = []
|
||||
for series in self.case.get_ordered_series():
|
||||
visibility = self.get_series_visibility_for(series, review_mode=review_mode)
|
||||
if visibility != self.SeriesVisibility.HIDDEN:
|
||||
visible.append(series)
|
||||
return visible
|
||||
|
||||
def get_case_series_nested(self, include_priors=True, review_mode: bool = False):
|
||||
case_series_images = []
|
||||
for series in self.get_visible_case_series(review_mode=review_mode):
|
||||
case_series_images.append([
|
||||
image.image.url
|
||||
for image in series.images.filter(removed=False)
|
||||
if image.image
|
||||
])
|
||||
|
||||
if include_priors:
|
||||
logger.debug(f"Checking for prior cases for case {self.case}")
|
||||
logger.debug(f"Found {self.case.prior_case.count()} prior cases for case {self.case}")
|
||||
|
||||
for prior in self.caseprior_set.all():
|
||||
if prior.prior_visibility == CasePrior.PriorVisibility.NONE:
|
||||
continue
|
||||
if prior.prior_visibility == CasePrior.PriorVisibility.REVIEW and not review_mode:
|
||||
continue
|
||||
logger.debug(f"Adding prior case {prior.prior_case} to case {self.case}")
|
||||
case_series_images.extend(prior.prior_case.get_series_images_nested(as_json=False))
|
||||
|
||||
return json.dumps(case_series_images)
|
||||
|
||||
def get_case_named_stacks(self, include_priors=True):
|
||||
def get_case_named_stacks(self, include_priors=True, review_mode: bool = False):
|
||||
def build_stacks_for(case_obj, prefix=None):
|
||||
stacks = []
|
||||
for series in case_obj.get_ordered_series():
|
||||
if case_obj == self.case:
|
||||
series_iterable = self.get_visible_case_series(review_mode=review_mode)
|
||||
else:
|
||||
series_iterable = case_obj.get_ordered_series()
|
||||
|
||||
for series in series_iterable:
|
||||
series_images = list(series.get_images())
|
||||
series_images_with_urls = [(img, f"{REMOTE_URL}{img.image.url}") for img in series_images]
|
||||
images = [url for _, url in series_images_with_urls]
|
||||
@@ -2316,6 +2388,10 @@ class CaseDetail(models.Model):
|
||||
# include priors as separate entries
|
||||
if include_priors:
|
||||
for prior in self.caseprior_set.all():
|
||||
if prior.prior_visibility == CasePrior.PriorVisibility.NONE:
|
||||
continue
|
||||
if prior.prior_visibility == CasePrior.PriorVisibility.REVIEW and not review_mode:
|
||||
continue
|
||||
prior_case = prior.prior_case
|
||||
results.append(
|
||||
{
|
||||
|
||||
@@ -14,13 +14,54 @@
|
||||
<form method="POST" class="post-form">
|
||||
{% csrf_token %}
|
||||
{% crispy form form.helper %}
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<strong>Series Visibility</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="small text-muted mb-3">Choose how each series is shown for this case in this collection.</p>
|
||||
{% if series_visibility_rows %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Series</th>
|
||||
<th>Visibility</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in series_visibility_rows %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold">Series {{ forloop.counter }}</div>
|
||||
<div class="small text-muted">{{ row.series }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm" name="series_visibility_{{ row.series.pk }}">
|
||||
{% for value, label in visibility_choices %}
|
||||
<option value="{{ value }}" {% if row.value == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted">No series linked to this case.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" value="answer" name="submit" class="btn btn-primary">Save Changes</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
</script>
|
||||
<style></style>
|
||||
<script>
|
||||
</script>
|
||||
<style></style>
|
||||
{% endblock %}
|
||||
@@ -101,6 +101,18 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if casedetail.learner_comment %}
|
||||
<div class="alert alert-info small">
|
||||
<strong>Collection note:</strong> {{ casedetail.learner_comment|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if question_completed and casedetail.learner_review_comment %}
|
||||
<div class="alert alert-secondary small">
|
||||
<strong>Review note:</strong> {{ casedetail.learner_review_comment|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collection.show_ohif_viewer or collection.show_ohif_viewer_link or collection.show_built_in_viewer %}
|
||||
<div id="viewer-panel" class="mb-3">
|
||||
{% if collection.show_built_in_viewer %}
|
||||
@@ -118,7 +130,7 @@
|
||||
<div id="viewer-container" class="viewer-resizable-container">
|
||||
<div id="main_viewer" class="dicom-viewer-root viewer-frame-standard"
|
||||
data-auto-cache-stack="false"
|
||||
data-named-stacks='{{casedetail.get_case_named_stacks}}'
|
||||
data-named-stacks='{{ named_stacks_json }}'
|
||||
></div>
|
||||
</div>
|
||||
<div id="viewer-resize-handle" class="viewer-resize-handle" title="Drag to resize viewer">
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
{% block content %}
|
||||
|
||||
<h2>Case: {{ case.title }}</h2>
|
||||
{% if casedetail.learner_comment %}
|
||||
<div class="alert alert-info small">
|
||||
<strong>Collection note:</strong> {{ casedetail.learner_comment|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if casedetail.learner_review_comment %}
|
||||
<div class="alert alert-secondary small">
|
||||
<strong>Review note:</strong> {{ casedetail.learner_review_comment|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ casedetail.question_schema }}
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<details class="series-detail">
|
||||
<summary class="fw-semibold">Series</summary>
|
||||
<div class="mt-2 d-flex flex-column gap-2">
|
||||
{% for series in case.prefetched_series %}
|
||||
{% for series in case.visible_prefetched_series %}
|
||||
<div class="border rounded p-2 bg-body-tertiary">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div class="small">
|
||||
@@ -375,6 +375,11 @@
|
||||
$('#open-viewer-local').click(function() {
|
||||
$("#open-viewer-local").removeClass("flash-button");
|
||||
viewerConnected = false;
|
||||
try {
|
||||
localStorage.removeItem('rad_local_viewer_popout_state');
|
||||
} catch (e) {
|
||||
console.warn('Unable to clear previous viewer state', e);
|
||||
}
|
||||
const opened = openSecondaryWindow("{% url 'atlas:collection_viewer_local_progressive' %}");
|
||||
if (!opened) {
|
||||
showUserMessage('Unable to open viewer window. Please allow pop-ups for this site and try again.', 'danger');
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
</div>
|
||||
|
||||
<p class="card-text small text-muted mb-2">Relation: <span class="fw-medium">{{ relation }}</span></p>
|
||||
<p class="card-text small text-muted mb-2">
|
||||
Study date:
|
||||
{% if case.study_date %}
|
||||
<span class="fw-medium">{{ case.study_date }}</span>
|
||||
{% else %}
|
||||
<span class="fw-medium">Unknown</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<div class="mb-3 small">{{case.get_series_blocks|safe}}</div>
|
||||
|
||||
@@ -37,6 +45,12 @@
|
||||
<div class="col-12">
|
||||
<input class="form-control form-control-sm" type="text" name="relation" value="{{relation}}" placeholder="Relation to case" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="use_study_date_relation" value="1" id="prior_use_date_{{case.id}}" />
|
||||
<label class="form-check-label small" for="prior_use_date_{{case.id}}">Use study dates to generate relation text</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<select class="form-select form-select-sm" name="prior_visibility" id="prior_visibility_{{case.id}}">
|
||||
<option value="AL" {% if visibility == 'AL' %}selected{% endif %}>Always</option>
|
||||
|
||||
@@ -175,10 +175,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
var savedPayload = readPersistedViewerPayload();
|
||||
if (savedPayload) {
|
||||
applyViewerPayload(savedPayload);
|
||||
}
|
||||
// Intentionally do not auto-load persisted payload on startup.
|
||||
// The local viva launcher should open to an empty state and wait
|
||||
// for an explicit "open" message from the controller page.
|
||||
|
||||
window.addEventListener('storage', function (event) {
|
||||
if (event.key !== POPOUT_STATE_KEY || !event.newValue) {
|
||||
|
||||
+166
-57
@@ -5798,8 +5798,15 @@ def collection_viva_local(request, pk):
|
||||
for casedetail in casedetails:
|
||||
case = casedetail.case
|
||||
prefetched_series = getattr(case, "prefetched_series", [])
|
||||
visible_prefetched_series = []
|
||||
images_nested = []
|
||||
for series in prefetched_series:
|
||||
if (
|
||||
casedetail.get_series_visibility_for(series, review_mode=False)
|
||||
== CaseDetail.SeriesVisibility.HIDDEN
|
||||
):
|
||||
continue
|
||||
visible_prefetched_series.append(series)
|
||||
prefetched_images = getattr(series, "prefetched_images", [])
|
||||
images_nested.append([
|
||||
img.image.url
|
||||
@@ -5816,6 +5823,7 @@ def collection_viva_local(request, pk):
|
||||
"report": case.report,
|
||||
}
|
||||
)
|
||||
case.visible_prefetched_series = visible_prefetched_series
|
||||
case.series_images_nested_json = json.dumps(images_nested)
|
||||
|
||||
return render(
|
||||
@@ -6949,6 +6957,23 @@ def collection_case_priors(request, exam_id, case_number):
|
||||
except Exception:
|
||||
raise Http404("Case not found in collection")
|
||||
|
||||
def _render_prior_card_response(prior_case, added, relation, visibility, error=None):
|
||||
html = render_to_string(
|
||||
"atlas/partials/_prior_card.html",
|
||||
{
|
||||
"case": prior_case,
|
||||
"added": added,
|
||||
"relation": relation,
|
||||
"visibility": visibility,
|
||||
"casedetail": casedetail,
|
||||
"collection": collection,
|
||||
"case_number": case_number,
|
||||
"error": error,
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
return HttpResponse(html)
|
||||
|
||||
if request.htmx:
|
||||
# Ensure we can render the updated single-card partial and return it so HTMX
|
||||
# can swap the card on the client side.
|
||||
@@ -6962,72 +6987,79 @@ def collection_case_priors(request, exam_id, case_number):
|
||||
# Already removed; continue and render the not-added card
|
||||
pass
|
||||
|
||||
prior_case = Case.objects.get(pk=prior_pk)
|
||||
prior_case = Case.objects.filter(pk=prior_pk).first()
|
||||
if prior_case is None:
|
||||
return HttpResponse("Prior case not found", status=404)
|
||||
added = False
|
||||
relation = ""
|
||||
visibility = "AL"
|
||||
|
||||
html = render_to_string(
|
||||
"atlas/partials/_prior_card.html",
|
||||
{
|
||||
"case": prior_case,
|
||||
"added": added,
|
||||
"relation": relation,
|
||||
"visibility": visibility,
|
||||
"casedetail": casedetail,
|
||||
"collection": collection,
|
||||
},
|
||||
request=request,
|
||||
return _render_prior_card_response(
|
||||
prior_case,
|
||||
added=added,
|
||||
relation=relation,
|
||||
visibility=visibility,
|
||||
)
|
||||
return HttpResponse(html)
|
||||
elif "prior_case_id" in request.POST:
|
||||
if not request.POST.get("relation"):
|
||||
prior_case = Case.objects.filter(pk=request.POST.get("prior_case_id")).first()
|
||||
if prior_case is None:
|
||||
return HttpResponse("Prior case not found", status=404)
|
||||
|
||||
visibility = request.POST.get("prior_visibility", "AL")
|
||||
if visibility not in {
|
||||
CasePrior.PriorVisibility.ALWAYS,
|
||||
CasePrior.PriorVisibility.REVIEW,
|
||||
CasePrior.PriorVisibility.NONE,
|
||||
}:
|
||||
visibility = CasePrior.PriorVisibility.ALWAYS
|
||||
|
||||
relation = (request.POST.get("relation") or "").strip()
|
||||
use_study_date_relation = request.POST.get("use_study_date_relation") == "1"
|
||||
|
||||
if use_study_date_relation:
|
||||
current_date = casedetail.case.study_date
|
||||
prior_date = prior_case.study_date
|
||||
if current_date and prior_date:
|
||||
relative = _format_relative_date_offset((prior_date - current_date).days)
|
||||
relation = f"Study date {relative['text']} relative to the index case"
|
||||
else:
|
||||
return _render_prior_card_response(
|
||||
prior_case,
|
||||
added=False,
|
||||
relation=relation,
|
||||
visibility=visibility,
|
||||
error="Study-date relation requires both this case and the prior case to have study dates.",
|
||||
)
|
||||
|
||||
if not relation:
|
||||
# Return the card partial with an inline error so HTMX will replace
|
||||
# the card and display the validation message rather than navigating
|
||||
# to a new page or showing a plain text response.
|
||||
prior_case = Case.objects.get(pk=request.POST.get("prior_case_id"))
|
||||
added = False
|
||||
relation = request.POST.get("relation", "")
|
||||
visibility = request.POST.get("prior_visibility", "AL")
|
||||
html = render_to_string(
|
||||
"atlas/partials/_prior_card.html",
|
||||
{
|
||||
"case": prior_case,
|
||||
"added": added,
|
||||
"relation": relation,
|
||||
"visibility": visibility,
|
||||
"casedetail": casedetail,
|
||||
"collection": collection,
|
||||
"error": "You need to enter text to describe the relationship between the cases",
|
||||
},
|
||||
request=request,
|
||||
return _render_prior_card_response(
|
||||
prior_case,
|
||||
added=False,
|
||||
relation=relation,
|
||||
visibility=visibility,
|
||||
error="Enter relation text, or use study-date relation.",
|
||||
)
|
||||
return HttpResponse(html)
|
||||
prior_case = Case.objects.get(pk=request.POST["prior_case_id"])
|
||||
|
||||
p, created = CasePrior.objects.get_or_create(
|
||||
casedetail=casedetail, prior_case=prior_case
|
||||
)
|
||||
p.relation_text = request.POST.get("relation", "")
|
||||
p.prior_visibility = request.POST.get("prior_visibility", "AL")
|
||||
p.relation_text = relation
|
||||
p.prior_visibility = visibility
|
||||
p.save()
|
||||
|
||||
added = True
|
||||
relation = p.relation_text
|
||||
visibility = p.prior_visibility
|
||||
|
||||
html = render_to_string(
|
||||
"atlas/partials/_prior_card.html",
|
||||
{
|
||||
"case": prior_case,
|
||||
"added": added,
|
||||
"relation": relation,
|
||||
"visibility": visibility,
|
||||
"casedetail": casedetail,
|
||||
"collection": collection,
|
||||
},
|
||||
request=request,
|
||||
return _render_prior_card_response(
|
||||
prior_case,
|
||||
added=added,
|
||||
relation=relation,
|
||||
visibility=visibility,
|
||||
)
|
||||
return HttpResponse(html)
|
||||
else:
|
||||
return HttpResponse("False", status=400)
|
||||
|
||||
@@ -7212,10 +7244,35 @@ def collection_case_details(request, exam_id, case_number):
|
||||
except Exception:
|
||||
raise Http404("Case not found in collection")
|
||||
|
||||
ordered_series = list(casedetail.case.get_ordered_series())
|
||||
visibility_map = casedetail.get_series_visibility_map()
|
||||
visibility_choices = [
|
||||
(CaseDetail.SeriesVisibility.ALWAYS, "Show all the time"),
|
||||
(CaseDetail.SeriesVisibility.REVIEW, "Show on review"),
|
||||
(CaseDetail.SeriesVisibility.HIDDEN, "Do not show"),
|
||||
]
|
||||
|
||||
if request.method == "POST":
|
||||
form = CaseDetailForm(request.POST, instance=casedetail)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
casedetail = form.save(commit=False)
|
||||
|
||||
series_visibility_config = {}
|
||||
for series in ordered_series:
|
||||
field_name = f"series_visibility_{series.pk}"
|
||||
visibility_value = request.POST.get(
|
||||
field_name, CaseDetail.SeriesVisibility.ALWAYS
|
||||
)
|
||||
if visibility_value not in {
|
||||
CaseDetail.SeriesVisibility.ALWAYS,
|
||||
CaseDetail.SeriesVisibility.REVIEW,
|
||||
CaseDetail.SeriesVisibility.HIDDEN,
|
||||
}:
|
||||
visibility_value = CaseDetail.SeriesVisibility.ALWAYS
|
||||
series_visibility_config[str(series.pk)] = visibility_value
|
||||
|
||||
casedetail.series_visibility_config = series_visibility_config
|
||||
casedetail.save()
|
||||
if request.htmx:
|
||||
return HttpResponse("Saved")
|
||||
return redirect(request.path)
|
||||
@@ -7225,6 +7282,17 @@ def collection_case_details(request, exam_id, case_number):
|
||||
else:
|
||||
form = CaseDetailForm(instance=casedetail)
|
||||
|
||||
series_visibility_rows = []
|
||||
for series in ordered_series:
|
||||
series_visibility_rows.append(
|
||||
{
|
||||
"series": series,
|
||||
"value": visibility_map.get(
|
||||
str(series.pk), CaseDetail.SeriesVisibility.ALWAYS
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
case_number, case_count = collection.get_index_of_case(
|
||||
casedetail.case, case_count=True
|
||||
)
|
||||
@@ -7238,6 +7306,8 @@ def collection_case_details(request, exam_id, case_number):
|
||||
{
|
||||
"casedetail": casedetail,
|
||||
"form": form,
|
||||
"visibility_choices": visibility_choices,
|
||||
"series_visibility_rows": series_visibility_rows,
|
||||
"collection": collection,
|
||||
"case": casedetail.case,
|
||||
"previous": previous,
|
||||
@@ -7294,9 +7364,10 @@ def collection_mark_question(request, pk, case_number):
|
||||
previous = case_number > 0
|
||||
next = case_number < (case_count - 1)
|
||||
|
||||
casedetail = CaseDetail.objects.filter(case=case, collection=collection)
|
||||
casedetail_qs = CaseDetail.objects.filter(case=case, collection=collection)
|
||||
casedetail = casedetail_qs.first()
|
||||
|
||||
answers = CidReportAnswer.objects.filter(question__in=casedetail)
|
||||
answers = CidReportAnswer.objects.filter(question__in=casedetail_qs)
|
||||
|
||||
answer_forms = []
|
||||
if request.method == "POST":
|
||||
@@ -7339,7 +7410,7 @@ def collection_mark_question(request, pk, case_number):
|
||||
form = CidReportAnswerMarkForm(instance=answer, prefix=answer.pk)
|
||||
answer_forms.append((answer, form))
|
||||
|
||||
series_list = case.series.all().prefetch_related("images", "examination", "plane")
|
||||
series_list = casedetail.get_visible_case_series(review_mode=True) if casedetail else []
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -8212,8 +8283,6 @@ def collection_case_view_take(
|
||||
if collection.collection_type in ("REP", "QUE"):
|
||||
form = ReportAnswerForm(instance=answer, casedetail=casedetail)
|
||||
|
||||
series_list = case.series.all().prefetch_related("images", "examination", "plane")
|
||||
|
||||
previous = case_number > 0
|
||||
next = case_number < (case_count - 1)
|
||||
|
||||
@@ -8249,6 +8318,8 @@ def collection_case_view_take(
|
||||
|
||||
resources = case.caseresource_set.filter(pre_review=True)
|
||||
|
||||
series_list = casedetail.get_visible_case_series(review_mode=question_completed)
|
||||
|
||||
prior_cases = casedetail.caseprior_set.all()
|
||||
|
||||
series_to_load = []
|
||||
@@ -8293,6 +8364,9 @@ def collection_case_view_take(
|
||||
"case": case,
|
||||
"casedetail": casedetail,
|
||||
"series_list": series_list,
|
||||
"named_stacks_json": casedetail.get_case_named_stacks(
|
||||
review_mode=question_completed
|
||||
),
|
||||
"series_to_load": series_to_load,
|
||||
"has_priors": has_priors,
|
||||
"prior_count": prior_count,
|
||||
@@ -8437,12 +8511,47 @@ def collection_case_dicom_json(request, exam_id, case_number, review=False):
|
||||
except Exception:
|
||||
raise Http404("Case not found in collection")
|
||||
|
||||
if review:
|
||||
priors = casedetail.caseprior_set.exclude(prior_visibility="NO")
|
||||
else:
|
||||
priors = casedetail.caseprior_set.exclude(prior_visibility__in=["NO", "RE"])
|
||||
main_series = casedetail.get_visible_case_series(review_mode=review)
|
||||
|
||||
return JsonResponse(casedetail.case.get_case_dicom_json(priors=priors))
|
||||
studies_json = {
|
||||
"studies": [
|
||||
{
|
||||
"StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178",
|
||||
"StudyTime": "",
|
||||
"PatientName": casedetail.case.title,
|
||||
"PatientID": "LIDC-IDRI-0001",
|
||||
"AccessionNumber": "",
|
||||
"PatientAge": "",
|
||||
"PatientSex": "",
|
||||
"series": [series.get_series_dicom_json() for series in main_series],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
prior_qs = casedetail.caseprior_set.all().select_related("prior_case")
|
||||
for prior in prior_qs:
|
||||
if prior.prior_visibility == CasePrior.PriorVisibility.NONE:
|
||||
continue
|
||||
if prior.prior_visibility == CasePrior.PriorVisibility.REVIEW and not review:
|
||||
continue
|
||||
studies_json["studies"].append(
|
||||
{
|
||||
"StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630177",
|
||||
"StudyTime": "",
|
||||
"PatientName": casedetail.case.title,
|
||||
"PatientID": "LIDC-IDRI-0001",
|
||||
"AccessionNumber": "",
|
||||
"PatientAge": "",
|
||||
"PatientSex": "",
|
||||
"series": [
|
||||
series.get_series_dicom_json()
|
||||
for series in prior.prior_case.get_ordered_series()
|
||||
],
|
||||
"StudyDescription": f"Prior: {prior.relation_text}",
|
||||
}
|
||||
)
|
||||
|
||||
return JsonResponse(studies_json)
|
||||
|
||||
|
||||
def case_dicom_json(request, pk):
|
||||
|
||||
Reference in New Issue
Block a user