Add case inline editing functionality and search widget improvements
This commit is contained in:
+191
-1
@@ -754,6 +754,195 @@ def case_detail(request, pk):
|
||||
)
|
||||
|
||||
|
||||
CASE_INLINE_FIELD_CONFIG = {
|
||||
"title": {"label": "Title", "kind": "text"},
|
||||
"description": {"label": "Description", "kind": "textarea"},
|
||||
"history": {"label": "History", "kind": "textarea"},
|
||||
"discussion": {"label": "Discussion", "kind": "textarea"},
|
||||
"report": {"label": "Report", "kind": "textarea"},
|
||||
"diagnostic_certainty": {"label": "Diagnostic certainty", "kind": "select"},
|
||||
}
|
||||
|
||||
|
||||
def _dedupe_non_empty(values):
|
||||
out = []
|
||||
seen = set()
|
||||
for value in values:
|
||||
text = (value or "").strip()
|
||||
if not text or text in seen:
|
||||
continue
|
||||
seen.add(text)
|
||||
out.append(text)
|
||||
return out
|
||||
|
||||
|
||||
def _get_case_inline_suggestions(case, field_name):
|
||||
condition_names = [c.name for c in case.condition.all()[:3]]
|
||||
presentation_names = [p.name for p in case.presentation.all()[:3]]
|
||||
procedure_names = [p.name for p in case.procedures.all()[:3]]
|
||||
|
||||
conditions = ", ".join(condition_names)
|
||||
presentations = ", ".join(presentation_names)
|
||||
procedures = ", ".join(procedure_names)
|
||||
|
||||
first_series = None
|
||||
try:
|
||||
ordered = case.get_ordered_series()
|
||||
if ordered:
|
||||
first_series = ordered[0]
|
||||
except Exception:
|
||||
first_series = None
|
||||
|
||||
modality_text = ""
|
||||
examination_text = ""
|
||||
if first_series is not None:
|
||||
try:
|
||||
if first_series.modality is not None:
|
||||
modality_text = str(first_series.modality)
|
||||
except Exception:
|
||||
modality_text = ""
|
||||
try:
|
||||
if first_series.examination is not None:
|
||||
examination_text = str(first_series.examination)
|
||||
except Exception:
|
||||
examination_text = ""
|
||||
|
||||
if field_name == "title":
|
||||
return _dedupe_non_empty([
|
||||
conditions,
|
||||
f"{conditions} presenting with {presentations}" if conditions and presentations else "",
|
||||
presentations,
|
||||
f"{modality_text} {examination_text}" if modality_text or examination_text else "",
|
||||
])[:4]
|
||||
|
||||
if field_name == "description":
|
||||
return _dedupe_non_empty([
|
||||
f"{modality_text} {examination_text}".strip(),
|
||||
f"Case demonstrating {conditions}" if conditions else "",
|
||||
f"Key presentation: {presentations}" if presentations else "",
|
||||
])[:4]
|
||||
|
||||
if field_name == "history":
|
||||
return _dedupe_non_empty([
|
||||
f"Presentation: {presentations}." if presentations else "",
|
||||
f"Relevant background includes {procedures}." if procedures else "",
|
||||
f"Working diagnosis: {conditions}." if conditions else "",
|
||||
])[:4]
|
||||
|
||||
if field_name == "discussion":
|
||||
return _dedupe_non_empty([
|
||||
f"Imaging findings are most consistent with {conditions}." if conditions else "",
|
||||
f"Differential considerations include {conditions}." if conditions else "",
|
||||
"Correlation with clinical history and prior imaging is advised.",
|
||||
])[:4]
|
||||
|
||||
if field_name == "report":
|
||||
return _dedupe_non_empty([
|
||||
f"Findings are in keeping with {conditions}." if conditions else "",
|
||||
f"Clinical indication: {presentations}." if presentations else "",
|
||||
"No acute abnormality identified.",
|
||||
])[:4]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def _validate_case_inline_value(field_name, raw_value):
|
||||
if field_name == "diagnostic_certainty":
|
||||
try:
|
||||
parsed = int(raw_value)
|
||||
except Exception:
|
||||
return None, "Please choose a valid diagnostic certainty option."
|
||||
|
||||
allowed = {choice.value for choice in Case.CertaintyChoices}
|
||||
if parsed not in allowed:
|
||||
return None, "Please choose a valid diagnostic certainty option."
|
||||
return parsed, None
|
||||
|
||||
value = (raw_value or "").strip()
|
||||
if field_name == "title":
|
||||
if not value:
|
||||
return None, "Title cannot be empty."
|
||||
if len(value) > 255:
|
||||
return None, "Title must be 255 characters or fewer."
|
||||
|
||||
return value, None
|
||||
|
||||
|
||||
@login_required
|
||||
@user_has_case_view_access
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def case_inline_field(request, pk, field_name):
|
||||
case = get_case_for_case_detail(pk)
|
||||
|
||||
field_cfg = CASE_INLINE_FIELD_CONFIG.get(field_name)
|
||||
if field_cfg is None:
|
||||
raise Http404("Unknown inline-edit field.")
|
||||
|
||||
can_edit = case.check_user_can_edit(request.user)
|
||||
editing = request.GET.get("edit") == "1"
|
||||
show_label = request.GET.get("show_label", "1") not in ("0", "false", "False")
|
||||
error_message = ""
|
||||
|
||||
if request.method == "POST":
|
||||
if not can_edit:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
editing = True
|
||||
cleaned_value, error_message = _validate_case_inline_value(
|
||||
field_name=field_name,
|
||||
raw_value=request.POST.get("value", ""),
|
||||
)
|
||||
if error_message:
|
||||
value = request.POST.get("value", "")
|
||||
else:
|
||||
setattr(case, field_name, cleaned_value)
|
||||
case.save(update_fields=[field_name])
|
||||
editing = False
|
||||
value = getattr(case, field_name)
|
||||
else:
|
||||
value = getattr(case, field_name)
|
||||
if editing and not can_edit:
|
||||
editing = False
|
||||
|
||||
if field_name == "diagnostic_certainty":
|
||||
display_value = case.get_diagnostic_certainty_display()
|
||||
select_choices = [(str(choice.value), str(choice.label)) for choice in Case.CertaintyChoices]
|
||||
else:
|
||||
display_value = value
|
||||
select_choices = []
|
||||
|
||||
endpoint_url = reverse("atlas:case_inline_field", kwargs={"pk": case.pk, "field_name": field_name})
|
||||
if show_label:
|
||||
endpoint_url_default = endpoint_url
|
||||
endpoint_url_edit = f"{endpoint_url}?edit=1"
|
||||
else:
|
||||
endpoint_url_default = f"{endpoint_url}?show_label=0"
|
||||
endpoint_url_edit = f"{endpoint_url}?edit=1&show_label=0"
|
||||
|
||||
html = render_to_string(
|
||||
"atlas/partials/case_inline_field.html",
|
||||
{
|
||||
"case": case,
|
||||
"field_name": field_name,
|
||||
"field_label": field_cfg["label"],
|
||||
"field_kind": field_cfg["kind"],
|
||||
"value": value,
|
||||
"display_value": display_value,
|
||||
"editing": editing,
|
||||
"show_label": show_label,
|
||||
"can_edit": can_edit,
|
||||
"error_message": error_message,
|
||||
"suggestions": _get_case_inline_suggestions(case, field_name),
|
||||
"select_choices": select_choices,
|
||||
"endpoint_url_default": endpoint_url_default,
|
||||
"endpoint_url_edit": endpoint_url_edit,
|
||||
"container_id": f"case-inline-{field_name}",
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
return HttpResponse(html)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def toggle_case_normal(request, pk):
|
||||
@@ -1744,10 +1933,11 @@ def case_search(request):
|
||||
|
||||
cases = cases_qs.order_by("title")[:30]
|
||||
|
||||
multi = request.GET.get('multi') in ('1', 'true', 'True')
|
||||
return render(
|
||||
request,
|
||||
"atlas/partials/case_search_results.html",
|
||||
{"cases": cases, "collection": collection, "recent_cases": recent_cases, "q": q},
|
||||
{"cases": cases, "collection": collection, "recent_cases": recent_cases, "q": q, "multi_select": multi},
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user