diff --git a/atlas/filters.py b/atlas/filters.py index 9daa9e95..7fc6bd54 100755 --- a/atlas/filters.py +++ b/atlas/filters.py @@ -1,4 +1,6 @@ import django_filters +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout, Div from .models import ( Case, @@ -364,9 +366,10 @@ class NormalCaseFilter(django_filters.FilterSet): # Use the global Examination and Modality querysets from the Series model from generic.models import Examination, Modality - # Set up the actual querysets for the filters - self.base_filters.get("examination").queryset = Examination.objects.all() - self.base_filters.get("modality").queryset = Modality.objects.all() + # We'll set the actual querysets for the filters after applying + # any NormalCase queryset restrictions for the current user so + # that the choices only include examinations/modalities that + # actually have NormalCase records available. # If not an atlas editor restrict to cases authored by the user or open access cases if request is not None and not request.user.groups.filter(name="atlas_editor").exists(): @@ -376,8 +379,47 @@ class NormalCaseFilter(django_filters.FilterSet): queryset = NormalCase.objects.select_related("case") queryset = queryset.filter((Q(case__author__id=request.user.id) | Q(case__open_access=True))) + # Determine the NormalCase queryset we'll use to build choices + nc_qs = queryset if queryset is not None else NormalCase.objects.all() + + # Limit examination and modality choices to those present on the + # available NormalCase records (respecting user restrictions). + try: + exam_ids = nc_qs.values_list("examination_id", flat=True) + mod_ids = nc_qs.values_list("modality_id", flat=True) + self.base_filters.get("examination").queryset = Examination.objects.filter(id__in=exam_ids).distinct() + self.base_filters.get("modality").queryset = Modality.objects.filter(id__in=mod_ids).distinct() + except Exception: + # Fallback to all if something goes wrong + self.base_filters.get("examination").queryset = Examination.objects.all() + self.base_filters.get("modality").queryset = Modality.objects.all() + super(NormalCaseFilter, self).__init__(data=data, queryset=queryset, prefix=prefix, request=request) + # Attach a Crispy FormHelper to render the filter form as a + # horizontal Bootstrap grid. We set `form_tag=False` because the + # template already provides the
element. + try: + helper = FormHelper() + helper.form_method = "get" + helper.form_tag = False + helper.form_class = "row g-2 align-items-end" + helper.layout = Layout( + Div( + Div("examination", css_class="col-md-4"), + Div("modality", css_class="col-md-4"), + Div("age_days", css_class="col-md-2"), + Div("min_age", css_class="col-md-2"), + Div("max_age", css_class="col-md-2"), + css_class="row g-2 align-items-end", + ) + ) + # Assign helper to the generated form instance + if hasattr(self, 'form') and self.form is not None: + self.form.helper = helper + except Exception: + pass + def filter_min_age(self, queryset, name, value): try: days = int(float(value) * 365.25) diff --git a/atlas/templates/atlas/normals_list.html b/atlas/templates/atlas/normals_list.html index 37b6eb51..ed51f5f7 100644 --- a/atlas/templates/atlas/normals_list.html +++ b/atlas/templates/atlas/normals_list.html @@ -9,12 +9,7 @@
- {% for field in filter.form.visible_fields %} -
- {{ field.label_tag }} - {{ field }} -
- {% endfor %} + {% crispy filter.form %}
Clear