diff --git a/atlas/admin.py b/atlas/admin.py
index 155938a0..3b4f4e86 100755
--- a/atlas/admin.py
+++ b/atlas/admin.py
@@ -15,6 +15,7 @@ from .models import (
Structure,
PathologicalProcess,
Presentation,
+ Procedure,
UncategorisedDicom,
SeriesDetail,
Resource,
@@ -51,6 +52,7 @@ admin.site.register(UncategorisedDicom)
admin.site.register(SeriesDetail)
admin.site.register(Resource)
admin.site.register(CaseDisplaySet)
+admin.site.register(Procedure)
admin.site.register(UserReportAnswer)
admin.site.register(CidReportAnswer)
diff --git a/atlas/forms.py b/atlas/forms.py
index 830af729..f5f65a62 100755
--- a/atlas/forms.py
+++ b/atlas/forms.py
@@ -42,6 +42,7 @@ from atlas.models import (
Subspecialty,
Presentation,
PathologicalProcess,
+ Procedure,
UncategorisedDicom,
UserReportAnswer,
CaseDisplaySet,
@@ -395,6 +396,12 @@ class PathologicalProcessForm(ModelForm):
exclude = []
+class ProcedureForm(ModelForm):
+ class Meta:
+ model = Procedure
+ exclude = []
+
+
class SeriesFindingForm(ModelForm):
class Meta:
model = SeriesFinding
@@ -617,7 +624,6 @@ class CaseForm(ModelForm):
"presentation",
"discussion",
"report",
- "procedures",
"condition",
"pathological_process",
"open_access",
@@ -650,7 +656,6 @@ class CaseForm(ModelForm):
"presentation",
"discussion",
"condition",
- "procedures",
"pathological_process",
"report",
"open_access",
@@ -679,9 +684,6 @@ class CaseForm(ModelForm):
"subspecialty": CheckboxSelectMultiple(),
"pathological_process": CheckboxSelectMultiple(),
"previous_case": CaseSelect(),
- "procedures": autocomplete.ModelSelect2Multiple(
- url="atlas:procedure-autocomplete"
- ),
}
def clean_cimar_uuid(self):
diff --git a/atlas/tables.py b/atlas/tables.py
index e5f15aef..0c331b37 100755
--- a/atlas/tables.py
+++ b/atlas/tables.py
@@ -15,6 +15,7 @@ from .models import (
Finding,
Subspecialty,
Resource,
+ Procedure,
)
from django.utils.html import format_html
@@ -395,6 +396,26 @@ class PathologicalProcessTable(SelectionTable):
sequence = ("name",)
+class ProcedureTable(SelectionTable):
+ name = tables.Column(
+ linkify=("atlas:procedure_detail", {"pk": tables.A("pk")}),
+ verbose_name="Procedure",
+ )
+
+ edit = tables.LinkColumn(
+ "atlas:procedure_update", text="Edit", args=[A("pk")], orderable=False
+ )
+ delete = tables.LinkColumn(
+ "atlas:procedure_delete", text="Delete", args=[A("pk")], orderable=False
+ )
+
+ class Meta(SelectionTable.Meta):
+ model = Procedure
+ template_name = "django_tables2/bootstrap4.html"
+ fields = ("name",)
+ sequence = ("name",)
+
+
class SubspecialtyTable(SelectionTable):
name = tables.Column(
linkify=("atlas:subspecialty_detail", {"pk": tables.A("pk")}),
diff --git a/atlas/templates/atlas/_categories_search_results.html b/atlas/templates/atlas/_categories_search_results.html
index 29cb585f..2bd84d40 100644
--- a/atlas/templates/atlas/_categories_search_results.html
+++ b/atlas/templates/atlas/_categories_search_results.html
@@ -18,6 +18,8 @@
{{ item.name }}
{% elif group_name == 'presentations' %}
{{ item.name }}
+ {% elif group_name == 'procedures' %}
+ {{ item.name }}
{% else %}
{{ item.name }}
{% endif %}
@@ -27,7 +29,7 @@
{% endif %}
{% endfor %}
- {% if not results.conditions.exists and not results.findings.exists and not results.structures.exists and not results.subspecialties.exists and not results.presentations.exists %}
+ {% if not results.conditions.exists and not results.findings.exists and not results.structures.exists and not results.subspecialties.exists and not results.presentations.exists and not results.procedures.exists %}
No matches found.
{% endif %}
diff --git a/atlas/templates/atlas/base.html b/atlas/templates/atlas/base.html
index 8106488e..6c71be68 100755
--- a/atlas/templates/atlas/base.html
+++ b/atlas/templates/atlas/base.html
@@ -90,6 +90,7 @@
Subspecialties
Presentations
Pathological process
+ Procedures
{% if request.user.is_staff %}
Create
@@ -99,6 +100,7 @@
New subspecialty
New presentation
New pathological process
+ New procedure
{% endif %}
diff --git a/atlas/templates/atlas/categories_list.html b/atlas/templates/atlas/categories_list.html
index bbf5449b..216ac68a 100644
--- a/atlas/templates/atlas/categories_list.html
+++ b/atlas/templates/atlas/categories_list.html
@@ -10,6 +10,7 @@
Subspecialties
Presentations
Pathological Process
+ Procedures
@@ -94,6 +95,15 @@
hx-indicator="#category-search-loading" />
+
+
+
+
diff --git a/atlas/templates/atlas/procedure_form.html b/atlas/templates/atlas/procedure_form.html
new file mode 100644
index 00000000..e4079dbb
--- /dev/null
+++ b/atlas/templates/atlas/procedure_form.html
@@ -0,0 +1,23 @@
+{% extends "atlas/base.html" %}
+{% load crispy_forms_tags %}
+
+{% block js %}
+ {{ form.media }}
+{% endblock %}
+
+{% block content %}
+ Add / Edit Procedure
+ Use this form to create or edit a procedure.
+ Please check if it already exists before doing so!
+
+
+
+{% endblock %}
diff --git a/atlas/urls.py b/atlas/urls.py
index cda6d538..26046407 100755
--- a/atlas/urls.py
+++ b/atlas/urls.py
@@ -642,7 +642,12 @@ urlpatterns = [
path(
"presentation/", views.presentation_detail, name="presentation_detail"
),
+ path("procedure/", views.ProcedureView.as_view(), name="procedure_view"),
path("procedure/", views.procedure_detail, name="procedure_detail"),
+ path("procedure/create", views.ProcedureCreate.as_view(), name="procedure_create"),
+ path("procedure//update", views.ProcedureUpdate.as_view(), name="procedure_update"),
+ path("procedure//delete", views.ProcedureDelete.as_view(), name="procedure_delete"),
+
path("presentation/create", views.PresentationCreate.as_view(), name="presentation_create"),
path(
"process/",
diff --git a/atlas/views.py b/atlas/views.py
index 05bcf7ee..9672b930 100755
--- a/atlas/views.py
+++ b/atlas/views.py
@@ -88,6 +88,7 @@ from .forms import (
SubspecialtyForm,
PresentationForm,
PathologicalProcessForm,
+ ProcedureForm,
UserQuestionAnswerForm,
UserReportAnswerForm,
)
@@ -2748,6 +2749,65 @@ class PresentationView(LoginRequiredMixin, UserConfigurablePaginationMixin, Sing
return ctx
+class ProcedureView(LoginRequiredMixin, UserConfigurablePaginationMixin, SingleTableMixin, ListView):
+ model = Procedure
+ table_class = __import__('atlas.tables', fromlist=['ProcedureTable']).ProcedureTable
+ template_name = "atlas/view.html"
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data(**kwargs)
+ try:
+ ctx["create_url"] = reverse(f"atlas:{self.model._meta.model_name}_create")
+ except NoReverseMatch:
+ ctx["create_url"] = None
+ ctx["model_verbose_name"] = getattr(self.model._meta, "verbose_name", None)
+ ctx["model_verbose_name_plural"] = getattr(self.model._meta, "verbose_name_plural", None)
+ return ctx
+
+
+class ProcedureCreate(RevisionMixin, LoginRequiredMixin, CreateView):
+ model = Procedure
+ form_class = ProcedureForm
+ template_name = "atlas/procedure_form.html"
+ def form_valid(self, form):
+ self.object = form.save()
+ return super().form_valid(form)
+
+ def form_invalid(self, form):
+ logger.warning("ProcedureCreate form invalid: %s", form.errors)
+ return super().form_invalid(form)
+
+ def get_success_url(self):
+ try:
+ return reverse("atlas:procedure_detail", kwargs={"pk": self.object.pk})
+ except Exception:
+ return reverse("atlas:procedure_view")
+
+
+class ProcedureUpdate(RevisionMixin, LoginRequiredMixin, UpdateView):
+ model = Procedure
+ form_class = ProcedureForm
+ template_name = "atlas/procedure_form.html"
+ def form_valid(self, form):
+ self.object = form.save()
+ return super().form_valid(form)
+
+ def form_invalid(self, form):
+ logger.warning("ProcedureUpdate form invalid: %s", form.errors)
+ return super().form_invalid(form)
+
+ def get_success_url(self):
+ try:
+ return reverse("atlas:procedure_detail", kwargs={"pk": self.object.pk})
+ except Exception:
+ return reverse("atlas:procedure_view")
+
+
+class ProcedureDelete(RevisionMixin, LoginRequiredMixin, DeleteView):
+ model = Procedure
+ template_name = "confirm_delete.html"
+
+
class PathologicalProcessView(LoginRequiredMixin, UserConfigurablePaginationMixin, SingleTableMixin, FilterView):
model = PathologicalProcess
table_class = PathologicalProcessTable
@@ -3137,7 +3197,7 @@ def categories_list(request):
results = None
if query:
- from .models import Condition, Finding, Structure, Subspecialty, Presentation
+ from .models import Condition, Finding, Structure, Subspecialty, Presentation, Procedure
# Helper to conditionally populate each result list only when the
# corresponding type is requested (or when no types filter provided).
@@ -3150,6 +3210,7 @@ def categories_list(request):
"structures": Structure.objects.filter(name__icontains=query).order_by("name") if include("structures") else (),
"subspecialties": Subspecialty.objects.filter(name__icontains=query).order_by("name") if include("subspecialties") else (),
"presentations": Presentation.objects.filter(name__icontains=query).order_by("name") if include("presentations") else (),
+ "procedures": Procedure.objects.filter(name__icontains=query).order_by("name") if include("procedures") else (),
}
return render(request, "atlas/categories_list.html", {"query": query, "results": results})
@@ -3163,9 +3224,9 @@ def categories_search_partial(request):
"""
query = request.GET.get("q", "").strip()
types = request.GET.getlist("types")
- results = {"conditions": (), "findings": (), "structures": (), "subspecialties": (), "presentations": ()}
+ results = {"conditions": (), "findings": (), "structures": (), "subspecialties": (), "presentations": (), "procedures": ()}
if query:
- from .models import Condition, Finding, Structure, Subspecialty, Presentation
+ from .models import Condition, Finding, Structure, Subspecialty, Presentation, Procedure
def include(name: str) -> bool:
return not types or name in types
@@ -3176,6 +3237,7 @@ def categories_search_partial(request):
"structures": Structure.objects.filter(name__icontains=query).order_by("name") if include("structures") else (),
"subspecialties": Subspecialty.objects.filter(name__icontains=query).order_by("name") if include("subspecialties") else (),
"presentations": Presentation.objects.filter(name__icontains=query).order_by("name") if include("presentations") else (),
+ "procedures": Procedure.objects.filter(name__icontains=query).order_by("name") if include("procedures") else (),
}
return render(request, "atlas/_categories_search_results.html", {"query": query, "results": results})