From fedfc0b29a054a6dbc59b320ecc439e040dd67c4 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Sat, 24 Sep 2022 23:49:56 -0400 Subject: [PATCH 01/18] begin prefilters --- hawc/apps/summary/filters.py | 89 +++++++++++++++++++ .../templates/summary/prefilter_temp.html | 18 ++++ hawc/apps/summary/urls.py | 5 ++ hawc/apps/summary/views.py | 18 ++++ 4 files changed, 130 insertions(+) create mode 100644 hawc/apps/summary/filters.py create mode 100644 hawc/apps/summary/templates/summary/prefilter_temp.html diff --git a/hawc/apps/summary/filters.py b/hawc/apps/summary/filters.py new file mode 100644 index 0000000000..27418ccacd --- /dev/null +++ b/hawc/apps/summary/filters.py @@ -0,0 +1,89 @@ +from django import forms +import django_filters as df +from django.db.models import QuerySet +from django.db.models.functions import Lower + +from ..assessment.models import DoseUnits +from ..animal.models import Experiment, Endpoint +from ..animal.constants import ExperimentType +from ..study.models import Study + + +def is_boolean_true(queryset: QuerySet, name: str, value: bool) -> QuerySet: + if value: + return queryset.filter(**{name: True}) + return queryset + + +def distinct_term_choices(assessment_id: int, field: str) -> QuerySet: + return ( + Endpoint.objects.filter(assessment_id=assessment_id) + .order_by(field) + .distinct(field) + .values_list(field, field) + ) + + +class EndpointFilter(df.FilterSet): + def __init__(self, *args, **kw): + self.assessment_id = kw.pop("assessment_id") + super().__init__(*args, **kw) + + def limit_filters(self): + form = self.form + form.fields["animal_group__experiment__type"].choices = [ + (el, ExperimentType(el).label) + for el in Experiment.objects.filter(study__assessment_id=self.assessment_id) + .values_list("type", flat=True) + .distinct() + ] + + form.fields["animal_group__experiment__study"].queryset = ( + Study.objects.assessment_qs(self.assessment_id) + .filter(bioassay=True) + .order_by(Lower("short_citation")) + ) + form.fields[ + "animal_group__dosed_animals__doses__dose_units" + ].queryset = DoseUnits.objects.get_animal_units(self.assessment_id) + form.fields["system"].choices = distinct_term_choices(self.assessment_id, "system") + form.fields["organ"].choices = distinct_term_choices(self.assessment_id, "organ") + form.fields["effect"].choices = distinct_term_choices(self.assessment_id, "effect") + form.fields["effect_subtype"].choices = distinct_term_choices( + self.assessment_id, "effect_subtype" + ) + + animal_group__experiment__type = df.MultipleChoiceFilter( + label="Experiment type", lookup_expr="exact", choices=ExperimentType.choices + ) + animal_group__dosed_animals__doses__dose_units = df.ModelMultipleChoiceFilter( + label="Dose Units", lookup_expr="exact", queryset=Study.objects.none() + ) + animal_group__experiment__study = df.ModelMultipleChoiceFilter( + label="Included studies", lookup_expr="exact", queryset=Study.objects.none() + ) + system = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) + organ = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) + effect = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) + effect_subtype = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) + effects = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) + published_only = df.BooleanFilter( + field_name="animal_group__experiment__study__published", + label="Published only", + widget=forms.CheckboxInput, + method=is_boolean_true, + ) + + class Meta: + model = Endpoint + fields = [ + "animal_group__experiment__type", + "animal_group__dosed_animals__doses__dose_units", + "animal_group__experiment__study", + "system", + "organ", + "effect", + "effect_subtype", + "effects", + "published_only", + ] diff --git a/hawc/apps/summary/templates/summary/prefilter_temp.html b/hawc/apps/summary/templates/summary/prefilter_temp.html new file mode 100644 index 0000000000..40657c0da1 --- /dev/null +++ b/hawc/apps/summary/templates/summary/prefilter_temp.html @@ -0,0 +1,18 @@ +{% extends 'crumbless.html' %} +{% load crispy_forms_tags %} + +{% block content %} +
+ {{ form }} + +
+ +

Total found: {{object_list.count}}

+ +{% endblock %} diff --git a/hawc/apps/summary/urls.py b/hawc/apps/summary/urls.py index 701c376b6d..8f9ad167f5 100644 --- a/hawc/apps/summary/urls.py +++ b/hawc/apps/summary/urls.py @@ -57,6 +57,11 @@ views.SummaryTableDelete.as_view(), name="tables_delete", ), + path( + "prefilters//temp/", + views.PrefilterTemp.as_view(), + name="prefilters_temp", + ), # VISUALIZATIONS path( "assessment//visuals/", diff --git a/hawc/apps/summary/views.py b/hawc/apps/summary/views.py index d813fb9bbd..b3ada98305 100644 --- a/hawc/apps/summary/views.py +++ b/hawc/apps/summary/views.py @@ -721,3 +721,21 @@ def get_context_data(self, **kwargs): class DatasetInteractivity(TemplateView): template_name = "summary/dataset_interactivity.html" + + +from typing import Any +from ..animal.models import Endpoint +from .filters import EndpointFilter + + +class PrefilterTemp(TemplateView): + template_name = "summary/prefilter_temp.html" + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + assessment_id = self.kwargs["pk"] + qs = Endpoint.objects.filter(assessment_id=assessment_id) + f = EndpointFilter(self.request.GET, queryset=qs, assessment_id=assessment_id) + f.limit_filters() + context = super().get_context_data(**kwargs) + context.update(object_list=f.qs, form=f.form) + return context From 74ce132c7e88cc924c0bac448579804acceffc26 Mon Sep 17 00:00:00 2001 From: Daniel Rabstejnek Date: Wed, 19 Oct 2022 00:22:05 -0400 Subject: [PATCH 02/18] Replaced endpoint list views with django-filter implementations --- hawc/apps/animal/filterset.py | 221 +++++++++++++++ hawc/apps/animal/forms.py | 266 +----------------- .../templates/animal/endpoint_list.html | 83 ++++-- hawc/apps/animal/urls.py | 2 +- hawc/apps/animal/views.py | 98 +++---- hawc/apps/common/filterset.py | 110 ++++++++ .../common/templates/common/filter_list.html | 14 + hawc/apps/common/views.py | 59 +--- hawc/apps/epi/filterset.py | 185 ++++++++++++ hawc/apps/epi/forms.py | 211 +------------- hawc/apps/epi/templates/epi/outcome_list.html | 91 ++++-- hawc/apps/epi/urls.py | 2 +- hawc/apps/epi/views.py | 16 +- hawc/apps/epimeta/filterset.py | 95 +++++++ hawc/apps/epimeta/forms.py | 116 +------- .../templates/epimeta/metaresult_list.html | 93 ++++-- hawc/apps/epimeta/urls.py | 2 +- hawc/apps/epimeta/views.py | 34 +-- hawc/apps/invitro/filterset.py | 150 ++++++++++ hawc/apps/invitro/forms.py | 170 +---------- .../templates/invitro/ivendpoint_list.html | 100 +++++-- hawc/apps/invitro/urls.py | 2 +- hawc/apps/invitro/views.py | 20 +- hawc/apps/summary/filters.py | 89 ------ .../templates/summary/prefilter_temp.html | 18 -- hawc/apps/summary/urls.py | 5 - hawc/apps/summary/views.py | 18 -- .../includes/paginator_with_total.html | 2 + 28 files changed, 1131 insertions(+), 1141 deletions(-) create mode 100644 hawc/apps/animal/filterset.py create mode 100644 hawc/apps/common/filterset.py create mode 100644 hawc/apps/common/templates/common/filter_list.html create mode 100644 hawc/apps/epi/filterset.py create mode 100644 hawc/apps/epimeta/filterset.py create mode 100644 hawc/apps/invitro/filterset.py delete mode 100644 hawc/apps/summary/filters.py delete mode 100644 hawc/apps/summary/templates/summary/prefilter_temp.html create mode 100644 hawc/templates/includes/paginator_with_total.html diff --git a/hawc/apps/animal/filterset.py b/hawc/apps/animal/filterset.py new file mode 100644 index 0000000000..ae52098e40 --- /dev/null +++ b/hawc/apps/animal/filterset.py @@ -0,0 +1,221 @@ +import django_filters as df +from django import forms + +from ..assessment.autocomplete import EffectTagAutocomplete, SpeciesAutocomplete, StrainAutocomplete +from ..assessment.models import DoseUnits +from ..common.autocomplete import AutocompleteTextWidget +from ..common.filterset import ( + AutocompleteModelChoiceFilter, + AutocompleteModelMultipleChoiceFilter, + BaseFilterSet, +) +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, constants, models + + +class EndpointFilterSet(BaseFilterSet): + studies = AutocompleteModelMultipleChoiceFilter( + field_name="animal_group__experiment__study", + autocomplete_class=StudyAutocomplete, + label="Study reference", + help_text="ex: Smith et al. 2010", + ) + chemical = df.CharFilter( + field_name="animal_group__experiment__chemical", + lookup_expr="icontains", + label="Chemical name", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.ExperimentAutocomplete, field="chemical" + ), + help_text="ex: sodium", + ) + cas = df.CharFilter( + field_name="animal_group__experiment__cas", + lookup_expr="icontains", + label="CAS", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.ExperimentAutocomplete, field="cas" + ), + help_text="ex: 107-02-8", + ) + lifestage_exposed = df.CharFilter( + field_name="animal_group__lifestage_exposed", + lookup_expr="icontains", + label="Lifestage exposed", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.AnimalGroupAutocomplete, field="lifestage_exposed" + ), + help_text="ex: pup", + ) + lifestage_assessed = df.CharFilter( + field_name="animal_group__lifestage_assessed", + lookup_expr="icontains", + label="Lifestage assessed", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.AnimalGroupAutocomplete, field="lifestage_assessed" + ), + help_text="ex: adult", + ) + species = AutocompleteModelChoiceFilter( + field_name="animal_group__species", + autocomplete_class=SpeciesAutocomplete, + label="Species", + help_text="ex: Mouse", + ) + strain = AutocompleteModelChoiceFilter( + field_name="animal_group__strain", + autocomplete_class=StrainAutocomplete, + label="Strain", + help_text="ex: B6C3F1", + ) + sex = df.MultipleChoiceFilter( + field_name="animal_group__sex", + label="Sex", + choices=constants.Sex.choices, + widget=forms.CheckboxSelectMultiple, + initial=constants.Sex.values, + ) + data_extracted = df.ChoiceFilter( + choices=( + (True, "Yes"), + (False, "No"), + ), + empty_label="All data", + ) + name = df.CharFilter( + lookup_expr="icontains", + label="Endpoint name", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.EndpointAutocomplete, field="name" + ), + help_text="ex: heart weight", + ) + system = df.CharFilter( + lookup_expr="icontains", + label="System", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.EndpointAutocomplete, field="system" + ), + help_text="ex: endocrine", + ) + organ = df.CharFilter( + lookup_expr="icontains", + label="Organ", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.EndpointAutocomplete, field="organ" + ), + help_text="ex: pituitary", + ) + effect = df.CharFilter( + lookup_expr="icontains", + label="Effect", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.EndpointAutocomplete, field="effect" + ), + help_text="ex: alanine aminotransferase (ALT)", + ) + effect_subtype = df.CharFilter( + lookup_expr="icontains", + label="Effect Subtype", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.EndpointAutocomplete, field="effect_subtype" + ), + help_text="ex: ", + ) + tags = df.CharFilter( + field_name="effects__name", + lookup_expr="icontains", + label="Tags", + widget=AutocompleteTextWidget(autocomplete_class=EffectTagAutocomplete, field="name"), + help_text="ex: antibody response", + ) + dose_units = df.ModelChoiceFilter( + field_name="animal_group__dosing_regime__doses__dose_units", + label="Dose units", + queryset=DoseUnits.objects.all(), + ) + order_by = df.OrderingFilter( + fields=( + ("animal_group__experiment__study__short_citation", "study"), + ("animal_group__experiment__name", "experiment name"), + ("animal_group__name", "animal group"), + ("name", "endpoint name"), + ("animal_group__dosing_regime__doses__dose_units_id", "dose units"), + ("system", "system"), + ("organ", "organ"), + ("effect", "effect"), + ("effect_subtype", "effect subtype"), + ("animal_group__experiment__chemical", "chemical"), + ), + choices=( + ("study", "study"), + ("experiment name", "experiment name"), + ("animal group", "animal group"), + ("endpoint name", "endpoint name"), + ("dose units", "dose units"), + ("system", "system"), + ("organ", "organ"), + ("effect", "effect"), + ("effect subtype", "effect subtype"), + ("chemical", "chemical"), + ), + ) + + class Meta: + model = models.Endpoint + fields = [ + "studies", + "chemical", + "cas", + "lifestage_exposed", + "lifestage_assessed", + "species", + "strain", + "sex", + "data_extracted", + "name", + "system", + "organ", + "effect", + "effect_subtype", + "tags", + "dose_units", + "order_by", + "paginate_by", + ] + grid_layout = [ + [3, 3, 3, 3], + [3, 3, 3, 3], + [3, 3, 3, 3], + [3, 3, 3, 3], + [3, 3], + ] + + def prefilter_queryset(self, queryset): + queryset = queryset.filter(assessment=self.assessment) + if not self.perms["edit"]: + queryset = queryset.filter(animal_group__experiment__study__published=True) + return queryset + + def change_form(self, form): + form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "bioassay": True}) + form.fields["species"].set_filters( + {"animalgroup__experiment__study__assessment_id": self.assessment.id} + ) + form.fields["strain"].set_filters( + {"animalgroup__experiment__study__assessment_id": self.assessment.id} + ) + + for field in form.fields: + widget = form.fields[field].widget + if field in ("chemical", "cas"): + widget.update_filters({"study__assessment_id": self.assessment.id}) + elif field in ("lifestage_exposed", "lifestage_assessed"): + widget.update_filters({"experiment__study__assessment_id": self.assessment.id}) + elif field in ("name", "system", "organ", "effect", "effect_subtype"): + widget.update_filters( + {"animal_group__experiment__study__assessment_id": self.assessment.id} + ) + + form.fields["dose_units"].queryset = DoseUnits.objects.get_animal_units(self.assessment.id) + return form diff --git a/hawc/apps/animal/forms.py b/hawc/apps/animal/forms.py index 2b89009972..583341eeae 100644 --- a/hawc/apps/animal/forms.py +++ b/hawc/apps/animal/forms.py @@ -4,32 +4,17 @@ from crispy_forms import layout as cfl from django import forms -from django.db.models import Q from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory from django.urls import reverse -from ..assessment.autocomplete import ( - DSSToxAutocomplete, - EffectTagAutocomplete, - SpeciesAutocomplete, - StrainAutocomplete, -) -from ..assessment.models import DoseUnits +from ..assessment.autocomplete import DSSToxAutocomplete, EffectTagAutocomplete from ..common.autocomplete import ( - AutocompleteChoiceField, - AutocompleteMultipleChoiceField, AutocompleteSelectMultipleWidget, AutocompleteSelectWidget, AutocompleteTextWidget, ) -from ..common.forms import ( - BaseFormHelper, - CopyAsNewSelectorForm, - QuillField, - form_actions_apply_filters, -) -from ..study.autocomplete import StudyAutocomplete +from ..common.forms import BaseFormHelper, CopyAsNewSelectorForm, QuillField from ..vocab.constants import VocabularyNamespace from . import autocomplete, constants, models @@ -691,250 +676,3 @@ class EndpointSelectorForm(CopyAsNewSelectorForm): label = "Endpoint" parent_field = "animal_group__experiment__study_id" autocomplete_class = autocomplete.EndpointAutocomplete - - -class EndpointFilterForm(forms.Form): - - ORDER_BY_CHOICES = [ - ("animal_group__experiment__study__short_citation", "study"), - ("animal_group__experiment__name", "experiment name"), - ("animal_group__name", "animal group"), - ("name", "endpoint name"), - ("animal_group__dosing_regime__doses__dose_units_id", "dose units"), - ("system", "system"), - ("organ", "organ"), - ("effect", "effect"), - ["-NOEL", ""], - ["-LOEL", ""], - ("bmd", "BMD"), - ("bmdl", "BMDLS"), - ("effect_subtype", "effect subtype"), - ("animal_group__experiment__chemical", "chemical"), - ] - - studies = AutocompleteMultipleChoiceField( - autocomplete_class=StudyAutocomplete, - label="Study reference", - help_text="ex: Smith et al. 2010", - required=False, - ) - - chemical = forms.CharField( - label="Chemical name", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.ExperimentAutocomplete, field="chemical" - ), - help_text="ex: sodium", - required=False, - ) - - cas = forms.CharField( - label="CAS", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.ExperimentAutocomplete, field="cas" - ), - help_text="ex: 107-02-8", - required=False, - ) - - lifestage_exposed = forms.CharField( - label="Lifestage exposed", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.AnimalGroupAutocomplete, field="lifestage_exposed" - ), - help_text="ex: pup", - required=False, - ) - - lifestage_assessed = forms.CharField( - label="Lifestage assessed", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.AnimalGroupAutocomplete, field="lifestage_assessed" - ), - help_text="ex: adult", - required=False, - ) - - species = AutocompleteChoiceField( - autocomplete_class=SpeciesAutocomplete, - label="Species", - help_text="ex: Mouse", - required=False, - ) - - strain = AutocompleteChoiceField( - autocomplete_class=StrainAutocomplete, - label="Strain", - help_text="ex: B6C3F1", - required=False, - ) - - sex = forms.MultipleChoiceField( - choices=constants.Sex.choices, - widget=forms.CheckboxSelectMultiple, - initial=constants.Sex.values, - required=False, - ) - - data_extracted = forms.ChoiceField( - choices=((True, "Yes"), (False, "No"), (None, "All data")), - initial=None, - required=False, - ) - - name = forms.CharField( - label="Endpoint name", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.EndpointAutocomplete, field="name" - ), - help_text="ex: heart weight", - required=False, - ) - - system = forms.CharField( - label="System", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.EndpointAutocomplete, field="system" - ), - help_text="ex: endocrine", - required=False, - ) - - organ = forms.CharField( - label="Organ", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.EndpointAutocomplete, field="organ" - ), - help_text="ex: pituitary", - required=False, - ) - - effect = forms.CharField( - label="Effect", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.EndpointAutocomplete, field="effect" - ), - help_text="ex: alanine aminotransferase (ALT)", - required=False, - ) - - effect_subtype = forms.CharField( - label="Effect Subtype", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.EndpointAutocomplete, field="effect_subtype" - ), - help_text="ex: ", - required=False, - ) - - tags = forms.CharField( - label="Tags", - widget=AutocompleteTextWidget(autocomplete_class=EffectTagAutocomplete, field="name"), - help_text="ex: antibody response", - required=False, - ) - - dose_units = forms.ModelChoiceField(queryset=DoseUnits.objects.all(), required=False) - - order_by = forms.ChoiceField( - choices=ORDER_BY_CHOICES, - ) - - paginate_by = forms.IntegerField( - label="Items per page", min_value=10, initial=25, max_value=500, required=False - ) - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop("assessment") - super().__init__(*args, **kwargs) - noel_names = assessment.get_noel_names() - - self.fields["studies"].set_filters({"assessment_id": assessment.id, "bioassay": True}) - self.fields["species"].set_filters( - {"animalgroup__experiment__study__assessment_id": assessment.id} - ) - self.fields["strain"].set_filters( - {"animalgroup__experiment__study__assessment_id": assessment.id} - ) - - for field in self.fields: - widget = self.fields[field].widget - if field in ("chemical", "cas"): - widget.update_filters({"study__assessment_id": assessment.id}) - elif field in ("lifestage_exposed", "lifestage_assessed"): - widget.update_filters({"experiment__study__assessment_id": assessment.id}) - elif field in ("name", "system", "organ", "effect", "effect_subtype"): - widget.update_filters( - {"animal_group__experiment__study__assessment_id": assessment.id} - ) - - self.fields["dose_units"].queryset = DoseUnits.objects.get_animal_units(assessment.id) - - for i, (k, v) in enumerate(self.fields["order_by"].choices): - if v == "": - self.fields["order_by"].choices[i][1] = noel_names.noel - self.fields["order_by"].widget.choices[i][1] = noel_names.noel - elif v == "": - self.fields["order_by"].choices[i][1] = noel_names.loel - self.fields["order_by"].widget.choices[i][1] = noel_names.loel - - @property - def helper(self): - helper = BaseFormHelper(self, form_actions=form_actions_apply_filters()) - helper.form_method = "GET" - - helper.add_row("studies", 4, "col-md-3") - helper.add_row("lifestage_assessed", 4, "col-md-3") - helper.add_row("data_extracted", 4, "col-md-3") - helper.add_row("effect", 4, "col-md-3") - helper.add_row("order_by", 2, "col-md-3") - - return helper - - def get_query(self): - - query = Q() - if studies := self.cleaned_data.get("studies"): - query &= Q(animal_group__experiment__study__in=studies) - if chemical := self.cleaned_data.get("chemical"): - query &= Q(animal_group__experiment__chemical__icontains=chemical) - if cas := self.cleaned_data.get("cas"): - query &= Q(animal_group__experiment__cas__icontains=cas) - if lifestage_exposed := self.cleaned_data.get("lifestage_exposed"): - query &= Q(animal_group__lifestage_exposed__icontains=lifestage_exposed) - if lifestage_assessed := self.cleaned_data.get("lifestage_assessed"): - query &= Q(animal_group__lifestage_assessed__icontains=lifestage_assessed) - if species := self.cleaned_data.get("species"): - query &= Q(animal_group__species=species) - if strain := self.cleaned_data.get("strain"): - query &= Q(animal_group__strain__name__icontains=strain.name) - if sex := self.cleaned_data.get("sex"): - query &= Q(animal_group__sex__in=sex) - if data_extracted := self.cleaned_data.get("data_extracted"): - query &= Q(data_extracted=data_extracted == "True") - if name := self.cleaned_data.get("name"): - query &= Q(name__icontains=name) - if system := self.cleaned_data.get("system"): - query &= Q(system__icontains=system) - if organ := self.cleaned_data.get("organ"): - query &= Q(organ__icontains=organ) - if effect := self.cleaned_data.get("effect"): - query &= Q(effect__icontains=effect) - if effect_subtype := self.cleaned_data.get("effect_subtype"): - query &= Q(effect_subtype__icontains=effect_subtype) - if NOEL := self.cleaned_data.get("NOEL"): - query &= Q(NOEL__icontains=NOEL) - if LOEL := self.cleaned_data.get("LOEL"): - query &= Q(LOEL__icontains=LOEL) - if tags := self.cleaned_data.get("tags"): - query &= Q(effects__name__icontains=tags) - if dose_units := self.cleaned_data.get("dose_units"): - query &= Q(animal_group__dosing_regime__doses__dose_units=dose_units) - return query - - def get_order_by(self): - return self.cleaned_data.get("order_by", self.ORDER_BY_CHOICES[0][0]) - - def get_dose_units_id(self): - if hasattr(self, "cleaned_data") and self.cleaned_data.get("dose_units"): - return self.cleaned_data.get("dose_units").id diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 2eaf64ff9b..0f00d4d6ba 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -1,32 +1,63 @@ {% extends 'assessment-rooted.html' %} -{% load crispy_forms_tags %} - {% block content %} -

Assessment endpoints ({{page_obj.paginator.count}} found)

-
-
- -
-
-
- {% crispy form %} -
-
-
-
- {% include "includes/paginator.html" %} -{% endblock content %} + +

Assessment endpoints ({{page_obj.paginator.count}} found)

+ +{% include 'common/filter_list.html' with plural_object_name='endpoints' %} + + + + + + + + + + + + + + + + + + + + + + {% for object in object_list %} + + + + + + + {% endfor %} + +
StudyExperimentAnimal groupEndpoint
+ {{object.animal_group.experiment.study.short_citation}} + + {{object.animal_group.experiment.name}} + + {{object.animal_group.name}} + + + {{object.name}} + + +
+ +{% include 'includes/paginator_with_total.html' with plural_object_name='endpoints' %} + +{% endblock %} {% block extrajs %} - {{ config|json_script:"config" }} - + {% endblock %} diff --git a/hawc/apps/animal/urls.py b/hawc/apps/animal/urls.py index 028ddfbc12..815e383f43 100644 --- a/hawc/apps/animal/urls.py +++ b/hawc/apps/animal/urls.py @@ -113,7 +113,7 @@ # Endpoint path( "assessment//endpoints/", - views.EndpointList.as_view(), + views.EndpointFilterList.as_view(), name="endpoint_list", ), path( diff --git a/hawc/apps/animal/views.py b/hawc/apps/animal/views.py index ebfc487e24..ee28bf0bae 100644 --- a/hawc/apps/animal/views.py +++ b/hawc/apps/animal/views.py @@ -1,8 +1,6 @@ import json from django.db import transaction -from django.db.models import Q -from django.db.models.expressions import RawSQL from django.forms.models import modelformset_factory from django.http import HttpResponseRedirect from django.urls import reverse @@ -16,7 +14,7 @@ BaseCreateWithFormset, BaseDelete, BaseDetail, - BaseEndpointFilterList, + BaseFilterList, BaseList, BaseUpdate, BaseUpdateWithFormset, @@ -27,7 +25,7 @@ from ..mgmt.views import EnsureExtractionStartedMixin from ..study.models import Study from ..study.views import StudyRead -from . import forms, models +from . import filterset, forms, models # Heatmap views @@ -406,6 +404,36 @@ def get_context_data(self, **kwargs): return context +class EndpointFilterList(BaseFilterList): + parent_model = Assessment + model = models.Endpoint + filterset_class = filterset.EndpointFilterSet + + def get_queryset(self): + return ( + super() + .get_queryset() + .select_related( + "assessment", + "animal_group__experiment__dtxsid", + "animal_group__experiment__study", + "animal_group__species", + "animal_group__strain", + ) + .prefetch_related( + "bmd_models", + "effects", + "groups", + "animal_group__parents", + "animal_group__siblings", + "animal_group__children", + "animal_group__dosing_regime__doses__dose_units", + "animal_group__experiment__study__searches", + "animal_group__experiment__study__identifiers", + ) + ) + + @method_decorator(beta_tester_required, name="dispatch") class EndpointListV2(BaseList): parent_model = Assessment @@ -421,68 +449,10 @@ def get_app_config(self, context) -> WebappConfig: ) -class EndpointList(BaseEndpointFilterList): - # List of Endpoints associated with assessment - parent_model = Assessment - model = models.Endpoint - form_class = forms.EndpointFilterForm - - def get_query(self, perms): - query = Q(assessment=self.assessment) - if not perms["edit"]: - query &= Q(animal_group__experiment__study__published=True) - return query - - def get_queryset(self): - # TODO - revisit after upgrading to 2.1 to see if this can be handled outside of - # RawSQL query - perms = super().get_obj_perms() - order_by = None - - query = self.get_query(perms) - - if self.form.is_valid(): - query &= self.form.get_query() - order_by = self.form.get_order_by() - - qs = self.model.objects.filter(query).distinct() - - if order_by: - if order_by == "bmd": - qs = qs.order_by(RawSQL("bmd_model.output->>'BMD'", ()), "bmd_models__model") - elif order_by == "bmdl": - qs = qs.order_by(RawSQL("bmd_model.output->>'BMDL'", ()), "bmd_models__model") - else: - qs = qs.order_by(order_by) - - return qs.select_related( - "assessment", - "animal_group__experiment__dtxsid", - "animal_group__experiment__study", - "animal_group__species", - "animal_group__strain", - ).prefetch_related( - "bmd_models", - "effects", - "groups", - "animal_group__parents", - "animal_group__siblings", - "animal_group__children", - "animal_group__dosing_regime__doses__dose_units", - "animal_group__experiment__study__searches", - "animal_group__experiment__study__identifiers", - ) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["config"]["dose_units"] = self.form.get_dose_units_id() - return context - - -class EndpointTags(EndpointList): +class EndpointTags(EndpointFilterList): # List of Endpoints associated with an assessment and tag - def get_queryset(self): + def get_base_queryset(self): return self.model.objects.tag_qs(self.assessment.pk, self.kwargs["tag_slug"]) diff --git a/hawc/apps/common/filterset.py b/hawc/apps/common/filterset.py new file mode 100644 index 0000000000..19af532722 --- /dev/null +++ b/hawc/apps/common/filterset.py @@ -0,0 +1,110 @@ +import django_filters as df +from crispy_forms import layout as cfl +from django import forms + +from . import autocomplete +from .forms import BaseFormHelper, form_actions_apply_filters + + +class AutocompleteModelMultipleChoiceFilter(df.ModelMultipleChoiceFilter): + field_class = autocomplete.AutocompleteMultipleChoiceField + + +class AutocompleteModelChoiceFilter(df.ModelChoiceFilter): + field_class = autocomplete.AutocompleteChoiceField + + +class FilterForm(forms.Form): + def __init__(self, *args, **kwargs): + self.grid_layout = kwargs.pop("grid_layout", []) + super().__init__(*args, **kwargs) + + @property + def helper(self): + helper = BaseFormHelper(self, form_actions=form_actions_apply_filters()) + helper.form_method = "GET" + + for i, row in enumerate(self.grid_layout): + for j, column in enumerate(row): + helper[i + j].wrap(cfl.Column, css_class=f"col-md-{column}") + helper[i : i + len(row)].wrap_together(cfl.Row) + + return helper + + +class FilterSetOptions: + def __init__(self, options=None): + self.model = getattr(options, "model", None) + self.fields = getattr(options, "fields", None) + self.exclude = getattr(options, "exclude", None) + + self.filter_overrides = getattr(options, "filter_overrides", {}) + self.field_kwargs = getattr(options, "field_kwargs", {}) + self.grid_layout = getattr(options, "grid_layout", []) + + self.form = getattr(options, "form", FilterForm) + + +class FilterSetMetaclass(df.filterset.FilterSetMetaclass): + def __new__(cls, name, bases, attrs): + attrs["declared_filters"] = cls.get_declared_filters(bases, attrs) + + new_class = type.__new__(cls, name, bases, attrs) + new_class._meta = FilterSetOptions(getattr(new_class, "Meta", None)) + new_class.base_filters = new_class.get_filters() + + for filter_name, field_kwargs in new_class._meta.field_kwargs.items(): + new_class.base_filters[filter_name].extra.update(field_kwargs) + + return new_class + + +class FilterSet(df.filterset.BaseFilterSet, metaclass=FilterSetMetaclass): + pass + + +class BaseFilterSet(FilterSet): + paginate_by = df.ChoiceFilter( + label="Items per page", + choices=( + (25, "25"), + (50, "50"), + (100, "100"), + (250, "250"), + (500, "500"), + ), + method="filter_noop", + ) + + def __init__(self, *args, **kwargs): + self.assessment = kwargs.pop("assessment") + super().__init__(*args, **kwargs) + + def filter_noop(self, queryset, name, value): + return queryset + + @property + def perms(self): + return self.assessment.user_permissions(self.request.user) + + @property + def form(self): + if not hasattr(self, "_form"): + Form = self.get_form_class() + if self.is_bound: + form = Form(self.data, prefix=self.form_prefix, grid_layout=self._meta.grid_layout) + else: + form = Form(prefix=self.form_prefix, grid_layout=self._meta.grid_layout) + self._form = self.change_form(form) + return self._form + + def prefilter_queryset(self, queryset): + # any prefiltering or annotations necessary for ordering go here + pass + + def filter_queryset(self, queryset): + queryset = self.prefilter_queryset(queryset) + return super().filter_queryset(queryset) + + def change_form(self, form): + pass diff --git a/hawc/apps/common/templates/common/filter_list.html b/hawc/apps/common/templates/common/filter_list.html new file mode 100644 index 0000000000..df41558a65 --- /dev/null +++ b/hawc/apps/common/templates/common/filter_list.html @@ -0,0 +1,14 @@ +{% load crispy_forms_tags %} + +
+
+ +
+
+
+ {% crispy form %} +
+
+
diff --git a/hawc/apps/common/views.py b/hawc/apps/common/views.py index 17f556fed4..de2a37f9a6 100644 --- a/hawc/apps/common/views.py +++ b/hawc/apps/common/views.py @@ -791,60 +791,31 @@ def get_context_data(self, **kwargs): return super().get_context_data(**kwargs) -class BaseEndpointFilterList(BaseList): - parent_model = Assessment +class BaseFilterList(BaseList): + filterset_class = None # required def get_paginate_by(self, qs) -> int: value = self.request.GET.get("paginate_by") return tryParseInt(value, default=25, min_value=10, max_value=500) - def get(self, request, *args, **kwargs): - if len(self.request.GET) > 0: - self.form = self.form_class(self.request.GET, assessment=self.assessment) - else: - self.form = self.form_class(assessment=self.assessment) - return super().get(request, *args, **kwargs) + def get_base_queryset(self): + return self.model.objects.all() - def get_query(self, perms): - """ - query = Q(relation__to__assessment=self.assessment) - if not perms['edit']: - query &= Q(study__published=True) - return query - """ - pass + @property + def filterset(self): + if not hasattr(self, "_filterset"): + qs = self.get_base_queryset() + self._filterset = self.filterset_class( + data=self.request.GET, queryset=qs, request=self.request, assessment=self.assessment + ) + return self._filterset def get_queryset(self): - perms = super().get_obj_perms() - order_by = None + return self.filterset.qs - query = self.get_query(perms) - - if self.form.is_valid(): - query &= self.form.get_query() - order_by = self.form.get_order_by() - - ids = ( - self.model.objects.filter(query) - .order_by("id") - .distinct("id") - .values_list("id", flat=True) - ) - - qs = self.model.objects.filter(id__in=ids) - - if order_by: - qs = qs.order_by(order_by) - - return qs - - def get_context_data(self, **kwargs): - kwargs["form"] = self.form + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) - if "config" not in context: # TODO - remove this case; implement #507 - context["config"] = { - "items": self.model.get_qs_json(context["object_list"], json_encode=False) - } + context.update(form=self.filterset.form) return context diff --git a/hawc/apps/epi/filterset.py b/hawc/apps/epi/filterset.py new file mode 100644 index 0000000000..42a365409d --- /dev/null +++ b/hawc/apps/epi/filterset.py @@ -0,0 +1,185 @@ +import django_filters as df +from django import forms + +from ..assessment.models import DoseUnits +from ..common.autocomplete import AutocompleteTextWidget +from ..common.filterset import AutocompleteModelMultipleChoiceFilter, BaseFilterSet +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, constants, models + + +class OutcomeFilterSet(BaseFilterSet): + studies = AutocompleteModelMultipleChoiceFilter( + field_name="study_population__study", + autocomplete_class=StudyAutocomplete, + label="Study reference", + help_text="ex: Smith et al. 2010", + ) + name = df.CharFilter( + lookup_expr="icontains", + label="Outcome name", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="name" + ), + help_text="ex: blood, glucose", + ) + study_population = df.CharFilter( + field_name="study_population__name", + lookup_expr="icontains", + label="Study population", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="name" + ), + help_text="ex: population near a Teflon manufacturing plant", + ) + metric = df.CharFilter( + field_name="study_population__exposures__metric", + lookup_expr="icontains", + label="Measurement metric", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.ExposureAutocomplete, field="metric" + ), + help_text="ex: drinking water", + ) + age_profile = df.CharFilter( + field_name="study_population__age_profile", + lookup_expr="icontains", + label="Age profile", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="age_profile" + ), + help_text="ex: children", + ) + source = df.CharFilter( + field_name="study_population__source", + lookup_expr="icontains", + label="Study population source", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="source" + ), + help_text="ex: occupational exposure", + ) + country = df.CharFilter( + field_name="study_population__countries__name", + lookup_expr="icontains", + label="Study population country", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.CountryAutocomplete, field="name" + ), + help_text="ex: Japan", + ) + design = df.MultipleChoiceFilter( + field_name="study_population__design", + label="Study design", + choices=constants.Design.choices, + widget=forms.CheckboxSelectMultiple, + initial=constants.Design.values, + ) + system = df.CharFilter( + lookup_expr="icontains", + label="System", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="system" + ), + help_text="ex: immune and lymphatic system", + ) + effect = df.CharFilter( + lookup_expr="icontains", + label="Effect", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect" + ), + help_text="ex: Cancer", + ) + effect_subtype = df.CharFilter( + lookup_expr="icontains", + label="Effect subtype", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect_subtype" + ), + help_text="ex: Melanoma", + ) + diagnostic = df.MultipleChoiceFilter( + field_name="diagnostic", + choices=constants.Diagnostic.choices, + widget=forms.CheckboxSelectMultiple, + initial=constants.Diagnostic.values, + ) + metric_units = df.ModelChoiceFilter( + field_name="study_population__exposures__metric_units", + label="Metric units", + queryset=DoseUnits.objects.all(), + ) + order_by = df.OrderingFilter( + fields=( + ("study_population__study__short_citation", "study"), + ("study_population__name", "study population"), + ("name", "outcome name"), + ("system", "system"), + ("effect", "effect"), + ("diagnostic", "diagnostic"), + ), + choices=( + ("study", "study"), + ("study population", "study population"), + ("outcome name", "outcome name"), + ("system", "system"), + ("effect", "effect"), + ("diagnostic", "diagnostic"), + ), + ) + + class Meta: + model = models.Outcome + fields = [ + "studies", + "name", + "study_population", + "metric", + "age_profile", + "source", + "country", + "design", + "system", + "effect", + "effect_subtype", + "diagnostic", + "metric_units", + "order_by", + "paginate_by", + ] + grid_layout = [ + [3, 3, 3, 3], + [3, 3, 3, 3], + [3, 3, 3, 3], + [3, 3, 3], + ] + + def prefilter_queryset(self, queryset): + queryset = queryset.filter(assessment=self.assessment) + if not self.perms["edit"]: + queryset = queryset.filter(study_population__study__published=True) + return queryset + + def change_form(self, form): + form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "epi": True}) + form.fields["metric"].widget.update_filters( + {"study_population__study__assessment_id": self.assessment.id} + ) + form.fields["country"].widget.update_filters( + {"studypopulation__study__assessment_id": self.assessment.id} + ) + form.fields["metric_units"].queryset = DoseUnits.objects.get_epi_units(self.assessment.id) + ("study_population") + + for field in form.fields: + widget = form.fields[field].widget + # for study population autocomplete + if field in ("study_population", "age_profile", "source"): + widget.update_filters({"study__assessment_id": self.assessment.id}) + # for outcome autocomplete + elif field in ("name", "system", "effect", "effect_subtype"): + widget.update_filters( + {"study_population__study__assessment_id": self.assessment.id} + ) + return form diff --git a/hawc/apps/epi/forms.py b/hawc/apps/epi/forms.py index bdae70f5d5..9173cf6615 100644 --- a/hawc/apps/epi/forms.py +++ b/hawc/apps/epi/forms.py @@ -7,7 +7,6 @@ from django.urls import reverse from ..assessment.autocomplete import DSSToxAutocomplete, EffectTagAutocomplete -from ..assessment.models import DoseUnits from ..common.autocomplete import ( AutocompleteMultipleChoiceField, AutocompleteSelectMultipleWidget, @@ -18,12 +17,10 @@ BaseFormHelper, CopyAsNewSelectorForm, check_unique_for_assessment, - form_actions_apply_filters, form_actions_create_or_close, ) from ..common.helper import tryParseInt -from ..study.autocomplete import StudyAutocomplete -from . import autocomplete, constants, models +from . import autocomplete, models class CriteriaForm(forms.ModelForm): @@ -394,212 +391,6 @@ def helper(self): return helper -class OutcomeFilterForm(forms.Form): - - ORDER_BY_CHOICES = ( - ("study_population__study__short_citation", "study"), - ("study_population__name", "study population"), - ("name", "outcome name"), - ("system", "system"), - ("effect", "effect"), - ("diagnostic", "diagnostic"), - ) - - studies = AutocompleteMultipleChoiceField( - autocomplete_class=StudyAutocomplete, - label="Study reference", - help_text="ex: Smith et al. 2010", - required=False, - ) - - name = forms.CharField( - label="Outcome name", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.OutcomeAutocomplete, field="name" - ), - help_text="ex: blood, glucose", - required=False, - ) - - study_population = forms.CharField( - label="Study population", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="name" - ), - help_text="ex: population near a Teflon manufacturing plant", - required=False, - ) - - metric = forms.CharField( - label="Measurement metric", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.ExposureAutocomplete, field="metric" - ), - help_text="ex: drinking water", - required=False, - ) - - age_profile = forms.CharField( - label="Age profile", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="age_profile" - ), - help_text="ex: children", - required=False, - ) - - source = forms.CharField( - label="Study population source", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.StudyPopulationAutocomplete, field="source" - ), - help_text="ex: occupational exposure", - required=False, - ) - - country = forms.CharField( - label="Study population country", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.CountryAutocomplete, field="name" - ), - help_text="ex: Japan", - required=False, - ) - - design = forms.MultipleChoiceField( - label="Study design", - choices=constants.Design.choices, - widget=forms.CheckboxSelectMultiple, - initial=constants.Design.values, - required=False, - ) - - system = forms.CharField( - label="System", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.OutcomeAutocomplete, field="system" - ), - help_text="ex: immune and lymphatic system", - required=False, - ) - - effect = forms.CharField( - label="Effect", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect" - ), - help_text="ex: Cancer", - required=False, - ) - - effect_subtype = forms.CharField( - label="Effect subtype", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.OutcomeAutocomplete, field="effect_subtype" - ), - help_text="ex: Melanoma", - required=False, - ) - - diagnostic = forms.MultipleChoiceField( - choices=constants.Diagnostic.choices, - widget=forms.CheckboxSelectMultiple, - initial=constants.Diagnostic.values, - required=False, - ) - - metric_units = forms.ModelChoiceField(queryset=DoseUnits.objects.all(), required=False) - order_by = forms.ChoiceField( - choices=ORDER_BY_CHOICES, - ) - paginate_by = forms.IntegerField( - label="Items per page", min_value=10, initial=25, max_value=500, required=False - ) - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop("assessment") - super().__init__(*args, **kwargs) - self.fields["studies"].set_filters({"assessment_id": assessment.id, "epi": True}) - self.fields["metric"].widget.update_filters( - {"study_population__study__assessment_id": assessment.id} - ) - self.fields["country"].widget.update_filters( - {"studypopulation__study__assessment_id": assessment.id} - ) - self.fields["metric_units"].queryset = DoseUnits.objects.get_epi_units(assessment.id) - ("study_population") - - for field in self.fields: - widget = self.fields[field].widget - # for study population autocomplete - if field in ("study_population", "age_profile", "source"): - widget.update_filters({"study__assessment_id": assessment.id}) - # for outcome autocomplete - elif field in ("name", "system", "effect", "effect_subtype"): - widget.update_filters({"study_population__study__assessment_id": assessment.id}) - - @property - def helper(self): - helper = BaseFormHelper(self, form_actions=form_actions_apply_filters()) - - helper.form_method = "GET" - - helper.add_row("studies", 4, "col-md-3") - helper.add_row("age_profile", 4, "col-md-3") - helper.add_row("system", 4, "col-md-3") - helper.add_row("metric_units", 3, "col-md-3") - - return helper - - def get_query(self): - - studies = self.cleaned_data.get("studies") - name = self.cleaned_data.get("name") - study_population = self.cleaned_data.get("study_population") - metric = self.cleaned_data.get("metric") - age_profile = self.cleaned_data.get("age_profile") - source = self.cleaned_data.get("source") - country = self.cleaned_data.get("country") - design = self.cleaned_data.get("design") - system = self.cleaned_data.get("system") - effect = self.cleaned_data.get("effect") - effect_subtype = self.cleaned_data.get("effect_subtype") - diagnostic = self.cleaned_data.get("diagnostic") - metric_units = self.cleaned_data.get("metric_units") - - query = Q() - if studies: - query &= Q(study_population__study__in=studies) - if name: - query &= Q(name__icontains=name) - if study_population: - query &= Q(study_population__name__icontains=study_population) - if metric: - query &= Q(study_population__exposures__metric__icontains=metric) - if age_profile: - query &= Q(study_population__age_profile__icontains=age_profile) - if source: - query &= Q(study_population__source__icontains=source) - if country: - query &= Q(study_population__countries__name__icontains=country) - if design: - query &= Q(study_population__design__in=design) - if system: - query &= Q(system__icontains=system) - if effect: - query &= Q(effect__icontains=effect) - if effect_subtype: - query &= Q(effect_subtype__icontains=effect_subtype) - if diagnostic: - query &= Q(diagnostic__in=diagnostic) - if metric_units: - query &= Q(study_population__exposures__metric_units=metric_units) - return query - - def get_order_by(self): - return self.cleaned_data.get("order_by", self.ORDER_BY_CHOICES[0][0]) - - class OutcomeSelectorForm(CopyAsNewSelectorForm): label = "Outcome" parent_field = "study_population_id" diff --git a/hawc/apps/epi/templates/epi/outcome_list.html b/hawc/apps/epi/templates/epi/outcome_list.html index b4ef98a31f..a06949756b 100644 --- a/hawc/apps/epi/templates/epi/outcome_list.html +++ b/hawc/apps/epi/templates/epi/outcome_list.html @@ -1,32 +1,71 @@ {% extends 'assessment-rooted.html' %} -{% load crispy_forms_tags %} - {% block content %} -

Outcomes ({{page_obj.paginator.count}} found)

-
-
- -
-
-
- {% crispy form %} -
-
-
-
- {% include "includes/paginator.html" %} -{% endblock content %} + +

Outcomes ({{page_obj.paginator.count}} found)

+ +{% include 'common/filter_list.html' with plural_object_name='outcomes' %} + + + + + + + + + + + + + + + + + + + + + + + + + + {% for object in object_list %} + + + + + + + + + {% endfor %} + +
StudyStudy populationOutcomeSystemEffectDiagnostic
+ {{object.study_population.study.short_citation}} + + {{object.study_population.name}} + + + {{object.name}} + + + + {{object.system|default:"--"}} + + {{object.effect|default:"--"}} + + {{object.get_diagnostic_display|default:"--"}} +
+ +{% include 'includes/paginator_with_total.html' with plural_object_name='outcomes' %} + +{% endblock %} {% block extrajs %} - {{ config|json_script:"config" }} - + {% endblock %} diff --git a/hawc/apps/epi/urls.py b/hawc/apps/epi/urls.py index c07ad1eeee..90632b431c 100644 --- a/hawc/apps/epi/urls.py +++ b/hawc/apps/epi/urls.py @@ -103,7 +103,7 @@ # Outcome path( "assessment//outcomes/", - views.OutcomeList.as_view(), + views.OutcomeFilterList.as_view(), name="outcome_list", ), path( diff --git a/hawc/apps/epi/views.py b/hawc/apps/epi/views.py index 5f062be378..a39dc6d9fe 100644 --- a/hawc/apps/epi/views.py +++ b/hawc/apps/epi/views.py @@ -1,12 +1,10 @@ -from django.db.models import Q - from ..assessment.models import Assessment from ..common.views import ( BaseCreate, BaseCreateWithFormset, BaseDelete, BaseDetail, - BaseEndpointFilterList, + BaseFilterList, BaseUpdate, BaseUpdateWithFormset, CloseIfSuccessMixin, @@ -16,7 +14,7 @@ from ..mgmt.views import EnsureExtractionStartedMixin from ..study.models import Study from ..study.views import StudyRead -from . import forms, models +from . import filterset, forms, models # Heatmap views @@ -167,16 +165,10 @@ def get_success_url(self): # Outcome -class OutcomeList(BaseEndpointFilterList): +class OutcomeFilterList(BaseFilterList): parent_model = Assessment model = models.Outcome - form_class = forms.OutcomeFilterForm - - def get_query(self, perms): - query = Q(assessment=self.assessment) - if not perms["edit"]: - query &= Q(study_population__study__published=True) - return query + filterset_class = filterset.OutcomeFilterSet class OutcomeCreate(BaseCreate): diff --git a/hawc/apps/epimeta/filterset.py b/hawc/apps/epimeta/filterset.py new file mode 100644 index 0000000000..65a480c6d5 --- /dev/null +++ b/hawc/apps/epimeta/filterset.py @@ -0,0 +1,95 @@ +import django_filters as df + +from ..common.autocomplete import AutocompleteTextWidget +from ..common.filterset import AutocompleteModelMultipleChoiceFilter, BaseFilterSet +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, models + + +class MetaResultFilterSet(BaseFilterSet): + studies = AutocompleteModelMultipleChoiceFilter( + field_name="protocol__study", + autocomplete_class=StudyAutocomplete, + label="Study reference", + help_text="ex: Smith et al. 2010", + ) + label = df.CharFilter( + lookup_expr="icontains", + label="Meta result label", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="label" + ), + help_text="ex: ALL, folic acid, any time", + ) + protocol = df.CharFilter( + field_name="protocol__name", + lookup_expr="icontains", + label="Protocol", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaProtocolAutocomplete, field="name" + ), + help_text="ex: B vitamins and risk of cancer", + ) + health_outcome = df.CharFilter( + lookup_expr="icontains", + label="Health outcome", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="health_outcome" + ), + help_text="ex: Any adenoma", + ) + exposure_name = df.CharFilter( + lookup_expr="icontains", + label="Exposure name", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.MetaResultAutocomplete, field="exposure_name" + ), + help_text="ex: Folate", + ) + order_by = df.OrderingFilter( + fields=( + ("protocol__study__short_citation", "study"), + ("label", "meta result label"), + ("protocol__name", "protocol"), + ("health_outcome", "health outcome"), + ("exposure", "exposure"), + ), + choices=( + ("study", "study"), + ("meta result label", "meta result label"), + ("protocol", "protocol"), + ("health outcome", "health outcome"), + ("exposure", "exposure"), + ), + ) + + class Meta: + model = models.MetaResult + fields = [ + "studies", + "label", + "protocol", + "health_outcome", + "exposure_name", + "order_by", + "paginate_by", + ] + grid_layout = [ + [3, 3, 3, 3], + [3, 3, 3], + ] + + def prefilter_queryset(self, queryset): + queryset = queryset.filter(protocol__study__assessment=self.assessment) + if not self.perms["edit"]: + queryset = queryset.filter(protocol__study__published=True) + return queryset + + def change_form(self, form): + form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "epi_meta": True}) + form.fields["protocol"].widget.update_filters({"study__assessment_id": self.assessment.id}) + for field in form.fields: + widget = form.fields[field].widget + if field in ("label", "health_outcome", "exposure_name"): + widget.update_filters({"protocol__study__assessment_id": self.assessment.id}) + return form diff --git a/hawc/apps/epimeta/forms.py b/hawc/apps/epimeta/forms.py index 2534bf2cfb..305d833b19 100644 --- a/hawc/apps/epimeta/forms.py +++ b/hawc/apps/epimeta/forms.py @@ -1,18 +1,12 @@ from functools import partial from django import forms -from django.db.models import Q from django.forms.models import modelformset_factory from django.urls import reverse -from ..common.autocomplete import ( - AutocompleteMultipleChoiceField, - AutocompleteSelectMultipleWidget, - AutocompleteTextWidget, -) -from ..common.forms import BaseFormHelper, CopyAsNewSelectorForm, form_actions_apply_filters +from ..common.autocomplete import AutocompleteSelectMultipleWidget, AutocompleteTextWidget +from ..common.forms import BaseFormHelper, CopyAsNewSelectorForm from ..epi.autocomplete import AdjustmentFactorAutocomplete, CriteriaAutocomplete -from ..study.autocomplete import StudyAutocomplete from . import autocomplete, models @@ -172,112 +166,6 @@ def helper(self): return helper -class MetaResultFilterForm(forms.Form): - - ORDER_BY_CHOICES = ( - ("protocol__study__short_citation", "study"), - ("label", "meta result label"), - ("protocol__name", "protocol"), - ("health_outcome", "health outcome"), - ("exposure", "exposure"), - ) - - studies = AutocompleteMultipleChoiceField( - label="Study reference", - autocomplete_class=StudyAutocomplete, - help_text="ex: Smith et al. 2010", - required=False, - ) - - label = forms.CharField( - label="Meta result label", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.MetaResultAutocomplete, field="label" - ), - help_text="ex: ALL, folic acid, any time", - required=False, - ) - - protocol = forms.CharField( - label="Protocol", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.MetaProtocolAutocomplete, field="name" - ), - help_text="ex: B vitamins and risk of cancer", - required=False, - ) - - health_outcome = forms.CharField( - label="Health outcome", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.MetaResultAutocomplete, field="health_outcome" - ), - help_text="ex: Any adenoma", - required=False, - ) - - exposure_name = forms.CharField( - label="Exposure name", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.MetaResultAutocomplete, field="exposure_name" - ), - help_text="ex: Folate", - required=False, - ) - - order_by = forms.ChoiceField( - choices=ORDER_BY_CHOICES, - ) - - paginate_by = forms.IntegerField( - label="Items per page", min_value=10, initial=25, max_value=500, required=False - ) - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop("assessment") - super().__init__(*args, **kwargs) - self.fields["studies"].set_filters({"assessment_id": assessment.id, "epi_meta": True}) - self.fields["protocol"].widget.update_filters({"study__assessment_id": assessment.id}) - for field in self.fields: - widget = self.fields[field].widget - if field in ("label", "health_outcome", "exposure_name"): - widget.update_filters({"protocol__study__assessment_id": assessment.id}) - - @property - def helper(self): - helper = BaseFormHelper(self, form_actions=form_actions_apply_filters()) - helper.form_method = "GET" - - helper.add_row("studies", 4, "col-md-3") - helper.add_row("exposure_name", 3, "col-md-3") - - return helper - - def get_query(self): - - studies = self.cleaned_data.get("studies") - label = self.cleaned_data.get("label") - protocol = self.cleaned_data.get("protocol") - health_outcome = self.cleaned_data.get("health_outcome") - exposure_name = self.cleaned_data.get("exposure_name") - - query = Q() - if studies: - query &= Q(protocol__study__in=studies) - if label: - query &= Q(label__icontains=label) - if protocol: - query &= Q(protocol__name__icontains=protocol) - if health_outcome: - query &= Q(health_outcome__icontains=health_outcome) - if exposure_name: - query &= Q(exposure_name__icontains=exposure_name) - return query - - def get_order_by(self): - return self.cleaned_data.get("order_by", self.ORDER_BY_CHOICES[0][0]) - - class SingleResultForm(forms.ModelForm): class Meta: model = models.SingleResult diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html index be378d7c1d..7918524ce1 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html @@ -1,25 +1,80 @@ {% extends 'assessment-rooted.html' %} -{% load crispy_forms_tags %} - {% block content %} -

Meta-analysis/pooled results ({{page_obj.paginator.count}} found)

-
-
- -
-
-
- {% crispy form %} -
-
-
-
- {% include "includes/paginator.html" %} -{% endblock content %} + +

Meta-analysis/pooled results ({{page_obj.paginator.count}} found)

+ +{% include 'common/filter_list.html' with plural_object_name='results' %} + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for object in object_list %} + + + + + + + + + + {% endfor %} + +
StudyMeta resultProtocolHealth outcomeExposureConfidence intervalEstimate
+ {{object.protocol.study.short_citation}} + + + {{object.label}} + + + + {{object.protocol.name}} + + {{object.health_outcome|default:"--"}} + + {{object.exposure_name|default:"--"}} + + {% if object.ci_units_percentage is not None %} + {{object.ci_units_percentage}}% + {% else %} + -- + {% endif %} + + {{object.estimate_formatted|default:"--"}} +
+ +{% include 'includes/paginator_with_total.html' with plural_object_name='results' %} + +{% endblock %} {% block extrajs %} - {{ config|json_script:"config" }} + {% endblock %} diff --git a/hawc/apps/epimeta/urls.py b/hawc/apps/epimeta/urls.py index 46e50fa720..76820703ab 100644 --- a/hawc/apps/epimeta/urls.py +++ b/hawc/apps/epimeta/urls.py @@ -37,7 +37,7 @@ # result views path( "assessment//results/", - views.MetaResultList.as_view(), + views.MetaResultFilterList.as_view(), name="result_list", ), path( diff --git a/hawc/apps/epimeta/views.py b/hawc/apps/epimeta/views.py index 008c76a0fc..b2e19b5448 100644 --- a/hawc/apps/epimeta/views.py +++ b/hawc/apps/epimeta/views.py @@ -1,19 +1,20 @@ -from django.db.models import Q +from django.db.models import Case, F, When +from ..assessment.models import Assessment from ..common.helper import WebappConfig from ..common.views import ( BaseCreate, BaseCreateWithFormset, BaseDelete, BaseDetail, - BaseEndpointFilterList, + BaseFilterList, BaseUpdate, BaseUpdateWithFormset, CopyAsNewSelectorMixin, ) from ..mgmt.views import EnsureExtractionStartedMixin from ..study.models import Study -from . import forms, models +from . import filterset, forms, models def get_app_config_metaprotocol(self, context) -> WebappConfig: @@ -129,19 +130,18 @@ def get_success_url(self): return self.object.protocol.get_absolute_url() -class MetaResultList(BaseEndpointFilterList): +class MetaResultFilterList(BaseFilterList): + parent_model = Assessment model = models.MetaResult - form_class = forms.MetaResultFilterForm - - def get_query(self, perms): - query = Q(protocol__study__assessment=self.assessment) - if not perms["edit"]: - query &= Q(protocol__study__published=True) - return query - - def get_app_config(self, context) -> WebappConfig: - return WebappConfig( - app="epiMetaStartup", - page="startupMetaResultListPage", - data={"items": self.model.get_qs_json(context["object_list"], json_encode=False)}, + filterset_class = filterset.MetaResultFilterSet + + def get_queryset(self): + return ( + super() + .get_queryset() + .annotate( + ci_units_percentage=Case( + When(ci_units=None, then=None), default=F("ci_units") * 100 + ) + ) ) diff --git a/hawc/apps/invitro/filterset.py b/hawc/apps/invitro/filterset.py new file mode 100644 index 0000000000..3da714972f --- /dev/null +++ b/hawc/apps/invitro/filterset.py @@ -0,0 +1,150 @@ +import django_filters as df + +from ..assessment.models import DoseUnits +from ..common.autocomplete import AutocompleteTextWidget +from ..common.filterset import AutocompleteModelMultipleChoiceFilter, BaseFilterSet +from ..study.autocomplete import StudyAutocomplete +from . import autocomplete, models + + +class EndpointFilterSet(BaseFilterSet): + studies = AutocompleteModelMultipleChoiceFilter( + field_name="experiment__study", + autocomplete_class=StudyAutocomplete, + label="Study reference", + help_text="ex: Smith et al. 2010", + ) + name = df.CharFilter( + lookup_expr="icontains", + label="Endpoint name", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="name" + ), + help_text="ex: B cells", + ) + chemical = df.CharFilter( + field_name="chemical__name", + lookup_expr="icontains", + label="Chemical name", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVChemicalAutocomplete, field="name" + ), + help_text="ex: PFOA", + ) + cas = df.CharFilter( + field_name="chemical__cas", + lookup_expr="icontains", + label="CAS", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVChemicalAutocomplete, field="cas" + ), + help_text="ex: 107-02-8", + ) + cell_type = df.CharFilter( + field_name="experiment__cell_type__cell_type", + lookup_expr="icontains", + label="Cell type", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="cell_type" + ), + help_text="ex: HeLa", + ) + tissue = df.CharFilter( + field_name="experiment__cell_type__tissue", + lookup_expr="icontains", + label="Tissue", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="tissue" + ), + help_text="ex: adipocytes", + ) + effect = df.CharFilter( + lookup_expr="icontains", + label="Effect", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="effect" + ), + help_text="ex: gene expression", + ) + response_units = df.CharFilter( + lookup_expr="icontains", + label="Response units", + widget=AutocompleteTextWidget( + autocomplete_class=autocomplete.IVEndpointAutocomplete, field="response_units" + ), + help_text="ex: counts", + ) + dose_units = df.ModelChoiceFilter( + field_name="study_population__exposures__metric_units", + label="Dose units", + queryset=DoseUnits.objects.all(), + ) + order_by = df.OrderingFilter( + fields=( + ("experiment__study__short_citation", "study"), + ("experiment__name", "experiment name"), + ("name", "endpoint name"), + ("assay_type", "assay type"), + ("effect", "effect"), + ("chemical__name", "chemical"), + ("category__name", "category"), + ("observation_time", "observation time"), + ("experiment__dose_units_id", "dose units"), + ("response_units", "response units"), + ), + choices=( + ("study", "study"), + ("experiment name", "experiment name"), + ("endpoint name", "endpoint name"), + ("assay type", "assay type"), + ("effect", "effect"), + ("chemical", "chemical"), + ("category", "category"), + ("observation time", "observation time"), + ("dose units", "dose units"), + ("response units", "response units"), + ), + ) + + class Meta: + model = models.IVEndpoint + fields = [ + "studies", + "name", + "chemical", + "cas", + "cell_type", + "tissue", + "effect", + "response_units", + "dose_units", + "order_by", + "paginate_by", + ] + grid_layout = [ + [3, 3, 3, 3], + [3, 3, 3, 3], + [3, 3, 3], + ] + + def prefilter_queryset(self, queryset): + queryset = queryset.filter(assessment=self.assessment) + if not self.perms["edit"]: + queryset = queryset.filter(experiment__study__published=True) + return queryset + + def change_form(self, form): + form.fields["dose_units"].queryset = DoseUnits.objects.get_iv_units(self.assessment.id) + form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "in_vitro": True}) + # for endpoint autocomplete + for field in ("name", "effect", "response_units"): + form.fields[field].widget.update_filters( + {"experiment__study__assessment_id": self.assessment.id} + ) + # for chemical autocomplete + for field in ("chemical", "cas"): + form.fields[field].widget.update_filters({"study__assessment_id": self.assessment.id}) + # for cell type autocomplete + for field in ("cell_type", "tissue"): + form.fields[field].widget.update_filters({"study__assessment_id": self.assessment.id}) + return form diff --git a/hawc/apps/invitro/forms.py b/hawc/apps/invitro/forms.py index 244672f734..bba907bb0c 100644 --- a/hawc/apps/invitro/forms.py +++ b/hawc/apps/invitro/forms.py @@ -1,21 +1,17 @@ import json from django import forms -from django.db.models import Q from django.forms.models import BaseModelFormSet, inlineformset_factory, modelformset_factory from django.forms.widgets import Select from django.urls import reverse from ..assessment.autocomplete import DSSToxAutocomplete, EffectTagAutocomplete -from ..assessment.models import DoseUnits from ..common.autocomplete import ( - AutocompleteMultipleChoiceField, AutocompleteSelectMultipleWidget, AutocompleteSelectWidget, AutocompleteTextWidget, ) -from ..common.forms import BaseFormHelper, form_actions_apply_filters -from ..study.autocomplete import StudyAutocomplete +from ..common.forms import BaseFormHelper from . import autocomplete, models @@ -368,170 +364,6 @@ def helper(self): return helper -class IVEndpointFilterForm(forms.Form): - - ORDER_BY_CHOICES = ( - ("experiment__study__short_citation", "study"), - ("experiment__name", "experiment name"), - ("name", "endpoint name"), - ("assay_type", "assay type"), - ("effect", "effect"), - ("chemical__name", "chemical"), - ("category__name", "category"), - ("observation_time", "observation time"), - ("experiment__dose_units_id", "dose units"), - ("response_units", "response units"), - ) - - studies = AutocompleteMultipleChoiceField( - label="Study reference", - autocomplete_class=StudyAutocomplete, - help_text="ex: Smith et al. 2010", - required=False, - ) - - name = forms.CharField( - label="Endpoint name", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVEndpointAutocomplete, field="name" - ), - help_text="ex: B cells", - required=False, - ) - - chemical = forms.CharField( - label="Chemical name", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVChemicalAutocomplete, field="name" - ), - help_text="ex: PFOA", - required=False, - ) - - cas = forms.CharField( - label="CAS", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVChemicalAutocomplete, field="cas" - ), - help_text="ex: 107-02-8", - required=False, - ) - - cell_type = forms.CharField( - label="Cell type", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="cell_type" - ), - help_text="ex: HeLa", - required=False, - ) - - tissue = forms.CharField( - label="Tissue", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVCellTypeAutocomplete, field="tissue" - ), - help_text="ex: adipocytes", - required=False, - ) - - effect = forms.CharField( - label="Effect", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVEndpointAutocomplete, field="effect" - ), - help_text="ex: gene expression", - required=False, - ) - - response_units = forms.CharField( - label="Response units", - widget=AutocompleteTextWidget( - autocomplete_class=autocomplete.IVEndpointAutocomplete, field="response_units" - ), - help_text="ex: counts", - required=False, - ) - - dose_units = forms.ModelChoiceField(queryset=DoseUnits.objects.all(), required=False) - - order_by = forms.ChoiceField( - choices=ORDER_BY_CHOICES, - ) - - paginate_by = forms.IntegerField( - label="Items per page", min_value=10, initial=25, max_value=500, required=False - ) - - def __init__(self, *args, **kwargs): - assessment = kwargs.pop("assessment") - super().__init__(*args, **kwargs) - self.fields["dose_units"].queryset = DoseUnits.objects.get_iv_units(assessment.id) - self.fields["studies"].set_filters({"assessment_id": assessment.id, "in_vitro": True}) - # for endpoint autocomplete - for field in ("name", "effect", "response_units"): - self.fields[field].widget.update_filters( - {"experiment__study__assessment_id": assessment.id} - ) - # for chemical autocomplete - for field in ("chemical", "cas"): - self.fields[field].widget.update_filters({"study__assessment_id": assessment.id}) - # for cell type autocomplete - for field in ("cell_type", "tissue"): - self.fields[field].widget.update_filters({"study__assessment_id": assessment.id}) - - @property - def helper(self): - helper = BaseFormHelper(self, form_actions=form_actions_apply_filters()) - helper.form_method = "GET" - - helper.add_row("studies", 4, "col-md-3") - helper.add_row("cell_type", 4, "col-md-3") - helper.add_row("dose_units", 3, "col-md-3") - - return helper - - def get_query(self): - - studies = self.cleaned_data.get("studies") - name = self.cleaned_data.get("name") - chemical = self.cleaned_data.get("chemical") - cas = self.cleaned_data.get("cas") - cell_type = self.cleaned_data.get("cell_type") - tissue = self.cleaned_data.get("tissue") - effect = self.cleaned_data.get("effect") - response_units = self.cleaned_data.get("response_units") - dose_units = self.cleaned_data.get("dose_units") - - query = Q() - if studies: - query &= Q(experiment__study__in=studies) - if name: - query &= Q(name__icontains=name) - if chemical: - query &= Q(chemical__name__icontains=chemical) - if cas: - query &= Q(chemical__cas__icontains=cas) - if cell_type: - query &= Q(experiment__cell_type__cell_type__icontains=cell_type) - if tissue: - query &= Q(experiment__cell_type__tissue__icontains=tissue) - if effect: - query &= Q(effect__icontains=effect) - if response_units: - query &= Q(response_units__icontains=response_units) - if dose_units: - query &= Q(experiment__dose_units=dose_units) - return query - - def get_order_by(self): - return self.cleaned_data.get("order_by", self.ORDER_BY_CHOICES[0][0]) - - def get_dose_units_id(self): - if hasattr(self, "cleaned_data") and self.cleaned_data.get("dose_units"): - return self.cleaned_data.get("dose_units").id - - class IVEndpointGroupForm(forms.ModelForm): class Meta: model = models.IVEndpointGroup diff --git a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html index ba0b1bd9a6..d81e4b660b 100644 --- a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html +++ b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html @@ -1,32 +1,80 @@ {% extends 'assessment-rooted.html' %} -{% load crispy_forms_tags %} - {% block content %} -

In vitro endpoints ({{page_obj.paginator.count}} found)

-
-
- -
-
-
- {% crispy form %} -
-
-
-
- {% include "includes/paginator.html" %} -{% endblock content %} + +

In vitro endpoints ({{page_obj.paginator.count}} found)

+ +{% include 'common/filter_list.html' with plural_object_name='endpoints' %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for object in object_list %} + + + + + + + + + + + {% endfor %} + +
StudyExperimentChemicalEndpointEffect CategoryEffectsDose UnitsResponse Units
+ {{object.experiment.study.short_citation}} + + {{object.experiment.name}} + + {{object.chemical.name}} + + + {{object.name}} + + + + {{object.effect|default:"--"}} + + {{object.effects.all|join:", "|default:"--"}} + + {{object.experiment.dose_units.name|default:"--"}} + + {{object.response_units|default:"--"}} +
+ +{% include 'includes/paginator_with_total.html' with plural_object_name='endpoints' %} + +{% endblock %} {% block extrajs %} - {{ config|json_script:"config" }} - + {% endblock %} diff --git a/hawc/apps/invitro/urls.py b/hawc/apps/invitro/urls.py index f706476af4..6a9d19377c 100644 --- a/hawc/apps/invitro/urls.py +++ b/hawc/apps/invitro/urls.py @@ -87,7 +87,7 @@ # endpoint path( "assessment//endpoints/", - views.EndpointList.as_view(), + views.EndpointFilterList.as_view(), name="endpoint_list", ), path( diff --git a/hawc/apps/invitro/views.py b/hawc/apps/invitro/views.py index 0e8ff34cc0..2c86ebed93 100644 --- a/hawc/apps/invitro/views.py +++ b/hawc/apps/invitro/views.py @@ -1,4 +1,3 @@ -from django.db.models import Q from django.middleware.csrf import get_token from django.urls import reverse from django.views.generic import DetailView @@ -11,7 +10,7 @@ BaseCreateWithFormset, BaseDelete, BaseDetail, - BaseEndpointFilterList, + BaseFilterList, BaseUpdate, BaseUpdateWithFormset, ProjectManagerOrHigherMixin, @@ -19,7 +18,7 @@ ) from ..mgmt.views import EnsureExtractionStartedMixin from ..study.models import Study -from . import forms, models +from . import filterset, forms, models # Experiment @@ -218,18 +217,7 @@ def get_success_url(self): return self.object.experiment.get_absolute_url() -class EndpointList(BaseEndpointFilterList): +class EndpointFilterList(BaseFilterList): parent_model = Assessment model = models.IVEndpoint - form_class = forms.IVEndpointFilterForm - - def get_query(self, perms): - query = Q(assessment=self.assessment) - if not perms["edit"]: - query &= Q(experiment__study__published=True) - return query - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["config"]["dose_units"] = self.form.get_dose_units_id() - return context + filterset_class = filterset.EndpointFilterSet diff --git a/hawc/apps/summary/filters.py b/hawc/apps/summary/filters.py deleted file mode 100644 index 27418ccacd..0000000000 --- a/hawc/apps/summary/filters.py +++ /dev/null @@ -1,89 +0,0 @@ -from django import forms -import django_filters as df -from django.db.models import QuerySet -from django.db.models.functions import Lower - -from ..assessment.models import DoseUnits -from ..animal.models import Experiment, Endpoint -from ..animal.constants import ExperimentType -from ..study.models import Study - - -def is_boolean_true(queryset: QuerySet, name: str, value: bool) -> QuerySet: - if value: - return queryset.filter(**{name: True}) - return queryset - - -def distinct_term_choices(assessment_id: int, field: str) -> QuerySet: - return ( - Endpoint.objects.filter(assessment_id=assessment_id) - .order_by(field) - .distinct(field) - .values_list(field, field) - ) - - -class EndpointFilter(df.FilterSet): - def __init__(self, *args, **kw): - self.assessment_id = kw.pop("assessment_id") - super().__init__(*args, **kw) - - def limit_filters(self): - form = self.form - form.fields["animal_group__experiment__type"].choices = [ - (el, ExperimentType(el).label) - for el in Experiment.objects.filter(study__assessment_id=self.assessment_id) - .values_list("type", flat=True) - .distinct() - ] - - form.fields["animal_group__experiment__study"].queryset = ( - Study.objects.assessment_qs(self.assessment_id) - .filter(bioassay=True) - .order_by(Lower("short_citation")) - ) - form.fields[ - "animal_group__dosed_animals__doses__dose_units" - ].queryset = DoseUnits.objects.get_animal_units(self.assessment_id) - form.fields["system"].choices = distinct_term_choices(self.assessment_id, "system") - form.fields["organ"].choices = distinct_term_choices(self.assessment_id, "organ") - form.fields["effect"].choices = distinct_term_choices(self.assessment_id, "effect") - form.fields["effect_subtype"].choices = distinct_term_choices( - self.assessment_id, "effect_subtype" - ) - - animal_group__experiment__type = df.MultipleChoiceFilter( - label="Experiment type", lookup_expr="exact", choices=ExperimentType.choices - ) - animal_group__dosed_animals__doses__dose_units = df.ModelMultipleChoiceFilter( - label="Dose Units", lookup_expr="exact", queryset=Study.objects.none() - ) - animal_group__experiment__study = df.ModelMultipleChoiceFilter( - label="Included studies", lookup_expr="exact", queryset=Study.objects.none() - ) - system = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) - organ = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) - effect = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) - effect_subtype = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) - effects = df.MultipleChoiceFilter(lookup_expr="exact", choices=[]) - published_only = df.BooleanFilter( - field_name="animal_group__experiment__study__published", - label="Published only", - widget=forms.CheckboxInput, - method=is_boolean_true, - ) - - class Meta: - model = Endpoint - fields = [ - "animal_group__experiment__type", - "animal_group__dosed_animals__doses__dose_units", - "animal_group__experiment__study", - "system", - "organ", - "effect", - "effect_subtype", - "effects", - "published_only", - ] diff --git a/hawc/apps/summary/templates/summary/prefilter_temp.html b/hawc/apps/summary/templates/summary/prefilter_temp.html deleted file mode 100644 index 40657c0da1..0000000000 --- a/hawc/apps/summary/templates/summary/prefilter_temp.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'crumbless.html' %} -{% load crispy_forms_tags %} - -{% block content %} -
- {{ form }} - -
- -

Total found: {{object_list.count}}

-
    -{% for object in object_list %} -
  • {{object.id}}
  • -{% empty %} -
  • None
  • -{% endfor %} -
-{% endblock %} diff --git a/hawc/apps/summary/urls.py b/hawc/apps/summary/urls.py index 8f9ad167f5..701c376b6d 100644 --- a/hawc/apps/summary/urls.py +++ b/hawc/apps/summary/urls.py @@ -57,11 +57,6 @@ views.SummaryTableDelete.as_view(), name="tables_delete", ), - path( - "prefilters//temp/", - views.PrefilterTemp.as_view(), - name="prefilters_temp", - ), # VISUALIZATIONS path( "assessment//visuals/", diff --git a/hawc/apps/summary/views.py b/hawc/apps/summary/views.py index b3ada98305..d813fb9bbd 100644 --- a/hawc/apps/summary/views.py +++ b/hawc/apps/summary/views.py @@ -721,21 +721,3 @@ def get_context_data(self, **kwargs): class DatasetInteractivity(TemplateView): template_name = "summary/dataset_interactivity.html" - - -from typing import Any -from ..animal.models import Endpoint -from .filters import EndpointFilter - - -class PrefilterTemp(TemplateView): - template_name = "summary/prefilter_temp.html" - - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: - assessment_id = self.kwargs["pk"] - qs = Endpoint.objects.filter(assessment_id=assessment_id) - f = EndpointFilter(self.request.GET, queryset=qs, assessment_id=assessment_id) - f.limit_filters() - context = super().get_context_data(**kwargs) - context.update(object_list=f.qs, form=f.form) - return context diff --git a/hawc/templates/includes/paginator_with_total.html b/hawc/templates/includes/paginator_with_total.html new file mode 100644 index 0000000000..5a7d1c1f9b --- /dev/null +++ b/hawc/templates/includes/paginator_with_total.html @@ -0,0 +1,2 @@ +{% include "includes/paginator.html" %} +

Showing {{plural_object_name}} {{page_obj.start_index}}-{{page_obj.end_index}} of {{paginator.count}}

From b157e94ca3a9c88480d101d0fb0e74382901e67e Mon Sep 17 00:00:00 2001 From: Daniel Rabstejnek Date: Wed, 19 Oct 2022 11:03:38 -0400 Subject: [PATCH 03/18] Remove old javascript tables for endpoints --- frontend/animal/EndpointListTable.js | 77 ------------------------- frontend/animal/index.js | 2 - frontend/epi/Outcome.js | 17 ------ frontend/epi/OutcomeListTable.js | 37 ------------ frontend/epi/index.js | 2 - frontend/epimeta/MetaResult.js | 18 ------ frontend/epimeta/MetaResultListTable.js | 34 ----------- frontend/epimeta/index.js | 6 -- frontend/invitro/IVEndpoint.js | 19 ------ frontend/invitro/IVEndpointListTable.js | 50 ---------------- frontend/invitro/index.js | 2 - 11 files changed, 264 deletions(-) delete mode 100644 frontend/animal/EndpointListTable.js delete mode 100644 frontend/epi/OutcomeListTable.js delete mode 100644 frontend/epimeta/MetaResultListTable.js delete mode 100644 frontend/invitro/IVEndpointListTable.js diff --git a/frontend/animal/EndpointListTable.js b/frontend/animal/EndpointListTable.js deleted file mode 100644 index ef2a25b007..0000000000 --- a/frontend/animal/EndpointListTable.js +++ /dev/null @@ -1,77 +0,0 @@ -import _ from "lodash"; -import $ from "$"; - -import BaseTable from "shared/utils/BaseTable"; -import Endpoint from "./Endpoint"; - -const endpointRow = function(endpoint) { - const link = `${endpoint.data.name}`, - detail = $( - '' - ).click(() => endpoint.displayAsModal({complete: true})), - ep = $('').append(link, detail), - study = endpoint.data.animal_group.experiment.study, - experiment = endpoint.data.animal_group.experiment, - animalGroup = endpoint.data.animal_group, - row = [ - `${study.short_citation}`, - `${experiment.name}`, - `${animalGroup.name}`, - ep, - endpoint.doseUnits.activeUnit.name, - endpoint.get_special_dose_text("NOEL"), - endpoint.get_special_dose_text("LOEL"), - endpoint.get_special_bmd_value("BMD"), - endpoint.get_special_bmd_value("BMDL"), - ]; - return row; -}; - -class EndpointListTable { - constructor(endpoints, dose_id) { - this.endpoints = endpoints.map(d => new Endpoint(d)); - if (_.isFinite(dose_id)) { - this.endpoints.forEach(e => e.doseUnits.activate(dose_id)); - } - this.tbl = new BaseTable(); - } - - build_table() { - if (this.endpoints.length === 0) return "

No endpoints available.

"; - - var tbl = this.tbl, - headers = [ - "Study", - "Experiment", - "Animal group", - "Endpoint", - "Units", - this.endpoints[0].data.noel_names.noel, - this.endpoints[0].data.noel_names.loel, - "BMD", - "BMDLS", - ], - headersToSortKeys = tbl.makeHeaderToSortKeyMapFromOrderByDropdown( - "select#id_order_by", - { - "experiment name": "experiment", - "endpoint name": "endpoint", - "dose units": "units", - } - ); - - tbl.setColGroup([12, 16, 17, 31, 10, 7, 7]); - tbl.addHeaderRow(headers); - headersToSortKeys.noael = "-NOEL"; - headersToSortKeys.noel = "-NOEL"; - headersToSortKeys.nel = "-NOEL"; - headersToSortKeys.loael = "-LOEL"; - headersToSortKeys.loel = "-LOEL"; - headersToSortKeys.lel = "-LOEL"; - tbl.enableSortableHeaderLinks($("#initial_order_by").val(), headersToSortKeys); - this.endpoints.forEach(endpoint => tbl.addRow(endpointRow(endpoint))); - return tbl.getTbl(); - } -} - -export default EndpointListTable; diff --git a/frontend/animal/index.js b/frontend/animal/index.js index 894dee4f0e..83e58fb9ca 100644 --- a/frontend/animal/index.js +++ b/frontend/animal/index.js @@ -5,7 +5,6 @@ import EditEndpoint from "./EditEndpoint"; import Endpoint from "./Endpoint"; import startupEndpointForm from "./EndpointForm"; import startupEndpointListApp from "./EndpointListApp"; -import EndpointListTable from "./EndpointListTable"; import Experiment from "./Experiment"; import ehvBrowserStartup from "./EhvBrowser"; @@ -17,7 +16,6 @@ export default { Endpoint, startupEndpointForm, startupEndpointListApp, - EndpointListTable, Experiment, ehvBrowserStartup, }; diff --git a/frontend/epi/Outcome.js b/frontend/epi/Outcome.js index 52345ab01d..2bb85e6bdc 100644 --- a/frontend/epi/Outcome.js +++ b/frontend/epi/Outcome.js @@ -97,23 +97,6 @@ class Outcome { return $el; } - buildListRow() { - let link = `${this.data.name}`, - detail = $( - '' - ).click(() => this.displayAsModal({complete: true})), - outcome = $('').append(link, detail); - - return [ - `${this.data.study_population.study.short_citation}`, - `${this.data.study_population.name}`, - outcome, - this.data.system ? this.data.system : "--", - this.data.effect ? this.data.effect : "--", - this.data.diagnostic ? this.data.diagnostic : "--", - ]; - } - build_breadcrumbs() { var urls = [ { diff --git a/frontend/epi/OutcomeListTable.js b/frontend/epi/OutcomeListTable.js deleted file mode 100644 index 949e55dd2b..0000000000 --- a/frontend/epi/OutcomeListTable.js +++ /dev/null @@ -1,37 +0,0 @@ -import $ from "$"; -import BaseTable from "shared/utils/BaseTable"; -import Outcome from "./Outcome"; - -class OutcomeListTable { - constructor(data) { - this.outcomes = data.map(d => new Outcome(d)); - this.table = new BaseTable(); - } - - buildTable() { - if (this.outcomes.length === 0) { - return "

No endpoints available.

"; - } - - var table = this.table, - headers = ["Study", "Study population", "Outcome", "System", "Effect", "Diagnostic"]; - table.setColGroup([12, 25, 16, 17, 10, 13]); - table.addHeaderRow(headers); - - var headersToSortKeys = table.makeHeaderToSortKeyMapFromOrderByDropdown( - "select#id_order_by", - { - "outcome name": "outcome", - } - ); - - table.enableSortableHeaderLinks($("#initial_order_by").val(), headersToSortKeys); - - this.outcomes.map(outcome => { - table.addRow(outcome.buildListRow()); - }); - return table.getTbl(); - } -} - -export default OutcomeListTable; diff --git a/frontend/epi/index.js b/frontend/epi/index.js index e300a5ce5e..8f17ba1677 100644 --- a/frontend/epi/index.js +++ b/frontend/epi/index.js @@ -2,7 +2,6 @@ import ComparisonSet from "./ComparisonSet"; import Exposure from "./Exposure"; import Group from "./Group"; import Outcome from "./Outcome"; -import OutcomeListTable from "./OutcomeListTable"; import Result from "./Result"; import StudyPopulation from "./StudyPopulation"; @@ -11,7 +10,6 @@ export default { Exposure, Group, Outcome, - OutcomeListTable, Result, StudyPopulation, }; diff --git a/frontend/epimeta/MetaResult.js b/frontend/epimeta/MetaResult.js index 92effee451..49f351ff4e 100644 --- a/frontend/epimeta/MetaResult.js +++ b/frontend/epimeta/MetaResult.js @@ -90,24 +90,6 @@ class MetaResult { return HAWCUtils.build_breadcrumbs(urls); } - buildListRow() { - let link = `${this.data.label}`, - detail = $( - '' - ).click(() => this.displayAsModal({complete: true})), - endpoint = $('').append(link, detail); - - return [ - `${this.data.protocol.study.short_citation}`, - endpoint, - `${this.data.protocol.name}`, - this.data.health_outcome ? this.data.health_outcome : "--", - this.data.exposure_name ? this.data.exposure_name : "--", - this.data.ci_units ? `${this.data.ci_units * 100}%` : "--", - this.data.estimateFormatted ? this.data.estimateFormatted : "--", - ]; - } - displayAsModal() { var modal = new HAWCModal(), title = `

${this.build_breadcrumbs()}

`, diff --git a/frontend/epimeta/MetaResultListTable.js b/frontend/epimeta/MetaResultListTable.js deleted file mode 100644 index 0fafd36931..0000000000 --- a/frontend/epimeta/MetaResultListTable.js +++ /dev/null @@ -1,34 +0,0 @@ -import BaseTable from "shared/utils/BaseTable"; -import MetaResult from "./MetaResult"; - -class MetaResultListTable { - constructor(endpoint_data) { - this.endpoints = endpoint_data.map(d => new MetaResult(d)); - this.table = new BaseTable(); - } - - buildTable() { - if (this.endpoints.length === 0) { - return "

No results available.

"; - } - - var table = this.table, - headers = [ - "Study", - "Meta result", - "Protocol", - "Health outcome", - "Exposure", - "Confidence interval", - "Estimate", - ]; - table.setColGroup([10, 16, 19, 12, 11, 10, 10]); - table.addHeaderRow(headers); - this.endpoints.map(endpoint => { - table.addRow(endpoint.buildListRow()); - }); - return table.getTbl(); - } -} - -export default MetaResultListTable; diff --git a/frontend/epimeta/index.js b/frontend/epimeta/index.js index ebf046f445..93f50de1b4 100644 --- a/frontend/epimeta/index.js +++ b/frontend/epimeta/index.js @@ -2,16 +2,10 @@ import $ from "$"; import MetaProtocol from "./MetaProtocol"; import MetaResult from "./MetaResult"; -import MetaResultListTable from "./MetaResultListTable"; export default { MetaProtocol, MetaResult, - MetaResultListTable, startupMetaProtocolPage: (el, config) => MetaProtocol.displayFullPager($(el), config.id), startupMetaResultPage: (el, config) => MetaResult.displayFullPager($(el), config.id), - startupMetaResultListPage: (el, config) => { - const tbl = new MetaResultListTable(config.items); - $(el).html(tbl.buildTable()); - }, }; diff --git a/frontend/invitro/IVEndpoint.js b/frontend/invitro/IVEndpoint.js index 4c8f06323e..f4bb20e169 100644 --- a/frontend/invitro/IVEndpoint.js +++ b/frontend/invitro/IVEndpoint.js @@ -172,25 +172,6 @@ class IVEndpoint { return tbl.getTbl(); } - buildListRow() { - let link = `${this.data.name}`, - detail = $( - '' - ).click(() => this.displayAsModal({complete: true})), - endpoint = $('').append(link, detail); - - return [ - `${this.data.experiment.study.short_citation}`, - `${this.data.experiment.name}`, - `${this.chemical.data.name}`, - endpoint, - this.data.effect ? this.data.effect : "--", - this.data.effects.length > 0 ? _.map(this.data.effects, "name").join(", ") : "--", - this.data.experiment.dose_units.name ? this.data.experiment.dose_units.name : "--", - this.data.response_units ? this.data.response_units : "--", - ]; - } - displayAsModal() { var modal = new HAWCModal(), $details = $('
'), diff --git a/frontend/invitro/IVEndpointListTable.js b/frontend/invitro/IVEndpointListTable.js deleted file mode 100644 index 4750a35dbe..0000000000 --- a/frontend/invitro/IVEndpointListTable.js +++ /dev/null @@ -1,50 +0,0 @@ -import BaseTable from "shared/utils/BaseTable"; - -import IVEndpoint from "./IVEndpoint"; - -class IVEndpointListTable { - constructor(data) { - this.endpoints = data.map(d => new IVEndpoint(d)); - this.table = new BaseTable(); - } - - buildTable() { - if (this.endpoints.length === 0) { - return "

No endpoints available.

"; - } - - var table = this.table, - headers = [ - "Study", - "Experiment", - "Chemical", - "Endpoint", - "Effect Category", - "Effects", - "Dose Units", - "Response Units", - ]; - table.setColGroup([10, 16, 12, 11, 16, 20, 7, 7]); - table.addHeaderRow(headers); - - var headersToSortKeys = table.makeHeaderToSortKeyMapFromOrderByDropdown( - "select#id_order_by", - { - "experiment name": "experiment", - "endpoint name": "endpoint", - effect: "effect category", - } - ); - - table.enableSortableHeaderLinks($("#initial_order_by").val(), headersToSortKeys, { - unsortableColumns: ["effects"], - }); - - this.endpoints.map(endpoint => { - table.addRow(endpoint.buildListRow()); - }); - return table.getTbl(); - } -} - -export default IVEndpointListTable; diff --git a/frontend/invitro/index.js b/frontend/invitro/index.js index f7ba31e017..d3c70c65f2 100644 --- a/frontend/invitro/index.js +++ b/frontend/invitro/index.js @@ -1,13 +1,11 @@ import IVCellType from "./IVCellType"; import IVChemical from "./IVChemical"; import IVEndpoint from "./IVEndpoint"; -import IVEndpointListTable from "./IVEndpointListTable"; import IVExperiment from "./IVExperiment"; export default { IVCellType, IVChemical, IVEndpoint, - IVEndpointListTable, IVExperiment, }; From 078438f098d7288562c1e9c1f21f7dda641f01eb Mon Sep 17 00:00:00 2001 From: Daniel Rabstejnek Date: Wed, 19 Oct 2022 11:28:49 -0400 Subject: [PATCH 04/18] Add row in endpoint table to convey no endpoints --- hawc/apps/animal/templates/animal/endpoint_list.html | 6 ++++++ hawc/apps/epi/templates/epi/outcome_list.html | 6 ++++++ hawc/apps/epimeta/templates/epimeta/metaresult_list.html | 6 ++++++ hawc/apps/invitro/templates/invitro/ivendpoint_list.html | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 0f00d4d6ba..9745d66ded 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -46,6 +46,12 @@

Assessment endpoints ({{page_obj.paginator.count}} found)

+ {% empty %} + + + No endpoints available + + {% endfor %} diff --git a/hawc/apps/epi/templates/epi/outcome_list.html b/hawc/apps/epi/templates/epi/outcome_list.html index a06949756b..b7ce63ae19 100644 --- a/hawc/apps/epi/templates/epi/outcome_list.html +++ b/hawc/apps/epi/templates/epi/outcome_list.html @@ -54,6 +54,12 @@

Outcomes ({{page_obj.paginator.count}} found)

{{object.get_diagnostic_display|default:"--"}} + {% empty %} + + + No outcomes available + + {% endfor %} diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html index 7918524ce1..34e76ebd16 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html @@ -63,6 +63,12 @@

Meta-analysis/pooled results ({{page_obj.paginator.count}} found)

{{object.estimate_formatted|default:"--"}} + {% empty %} + + + No results available + + {% endfor %} diff --git a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html index d81e4b660b..313411e665 100644 --- a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html +++ b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html @@ -63,6 +63,12 @@

In vitro endpoints ({{page_obj.paginator.count}} found)

{{object.response_units|default:"--"}} + {% empty %} + + + No endpoints available + + {% endfor %} From 715a0c5e31b56daca5825392f3b43bb6356d6c6a Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Mon, 7 Nov 2022 17:04:33 -0500 Subject: [PATCH 05/18] change autocomplete to not require auth --- hawc/apps/common/autocomplete/__init__.py | 3 ++- hawc/apps/common/autocomplete/views.py | 9 +++++++-- hawc/apps/myuser/autocomplete.py | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/hawc/apps/common/autocomplete/__init__.py b/hawc/apps/common/autocomplete/__init__.py index a49739368a..13b922e282 100644 --- a/hawc/apps/common/autocomplete/__init__.py +++ b/hawc/apps/common/autocomplete/__init__.py @@ -6,7 +6,7 @@ AutocompleteTextWidget, ) from .registry import autodiscover, get_autocomplete, register -from .views import BaseAutocomplete, SearchLabelMixin +from .views import BaseAutocomplete, BaseAutocompleteAuthenticated, SearchLabelMixin __all__ = [ "AutocompleteChoiceField", @@ -15,6 +15,7 @@ "AutocompleteSelectWidget", "AutocompleteTextWidget", "BaseAutocomplete", + "BaseAutocompleteAuthenticated", "SearchLabelMixin", "autodiscover", "get_autocomplete", diff --git a/hawc/apps/common/autocomplete/views.py b/hawc/apps/common/autocomplete/views.py index 059b4b2da7..11ec78a997 100644 --- a/hawc/apps/common/autocomplete/views.py +++ b/hawc/apps/common/autocomplete/views.py @@ -77,8 +77,6 @@ def get_queryset(self): return qs def dispatch(self, request, *args, **kwargs): - if not request.user.is_authenticated: - return HttpResponseForbidden("Authentication required") self.field = request.GET.get("field", "") if self.field: self.search_fields = [self.field] @@ -96,6 +94,13 @@ def url(cls, **kwargs): return reverse_with_query_lazy("autocomplete", args=[cls.registry_key()], query=kwargs) +class BaseAutocompleteAuthenticated(BaseAutocomplete): + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return HttpResponseForbidden("Authentication required") + return super().dispatch(request, *args, **kwargs) + + class SearchLabelMixin: """ Constructs autocomplete labels by using values from the search_fields diff --git a/hawc/apps/myuser/autocomplete.py b/hawc/apps/myuser/autocomplete.py index 5fd0c2c91e..bbe4569be3 100644 --- a/hawc/apps/myuser/autocomplete.py +++ b/hawc/apps/myuser/autocomplete.py @@ -1,11 +1,11 @@ from django.db.models import Q -from ..common.autocomplete import BaseAutocomplete, register +from ..common.autocomplete import BaseAutocompleteAuthenticated, register from . import models @register -class UserAutocomplete(BaseAutocomplete): +class UserAutocomplete(BaseAutocompleteAuthenticated): model = models.HAWCUser search_fields = ["first_name", "last_name", "email"] From 2ea0f4aa9202e203ed994a2b47ac82c3225d8699 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Mon, 7 Nov 2022 17:09:15 -0500 Subject: [PATCH 06/18] fix paginator instead of making new one --- hawc/apps/animal/templates/animal/endpoint_list.html | 2 +- hawc/apps/assessment/templates/assessment/assessment_list.html | 3 +-- .../assessment/templates/assessment/assessment_log_list.html | 2 +- hawc/apps/epi/templates/epi/outcome_list.html | 2 +- hawc/apps/epimeta/templates/epimeta/metaresult_list.html | 2 +- hawc/apps/invitro/templates/invitro/ivendpoint_list.html | 2 +- .../riskofbias/templates/riskofbias/rob_assignment_update.html | 2 +- hawc/templates/includes/paginator.html | 3 ++- hawc/templates/includes/paginator_with_total.html | 2 -- 9 files changed, 9 insertions(+), 11 deletions(-) delete mode 100644 hawc/templates/includes/paginator_with_total.html diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 9745d66ded..114be7046e 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -56,7 +56,7 @@

Assessment endpoints ({{page_obj.paginator.count}} found)

-{% include 'includes/paginator_with_total.html' with plural_object_name='endpoints' %} +{% include "includes/paginator.html" with plural_object_name="endpoints" %} {% endblock %} diff --git a/hawc/apps/assessment/templates/assessment/assessment_list.html b/hawc/apps/assessment/templates/assessment/assessment_list.html index 7fc13e7dba..24643fe7ac 100644 --- a/hawc/apps/assessment/templates/assessment/assessment_list.html +++ b/hawc/apps/assessment/templates/assessment/assessment_list.html @@ -19,8 +19,7 @@

Assessments

{% include "assessment/_assessment_list_tbl.html" %} - {% include "includes/paginator.html" %} -

Showing assessments {{page_obj.start_index}}-{{page_obj.end_index}} of {{paginator.count}}

+ {% include "includes/paginator.html" with plural_object_name="assessments" %} {% endblock content_outer %} diff --git a/hawc/apps/assessment/templates/assessment/assessment_log_list.html b/hawc/apps/assessment/templates/assessment/assessment_log_list.html index 2f8d87e8ba..146e8c4f39 100644 --- a/hawc/apps/assessment/templates/assessment/assessment_log_list.html +++ b/hawc/apps/assessment/templates/assessment/assessment_log_list.html @@ -94,6 +94,6 @@

{{assessment}} Logs

{% endfor %} -{% include "includes/paginator.html" %} +{% include "includes/paginator.html" with plural_object_name="logs" %} {% include 'assessment/_logs_note.html' %} {% endblock content %} diff --git a/hawc/apps/epi/templates/epi/outcome_list.html b/hawc/apps/epi/templates/epi/outcome_list.html index b7ce63ae19..cc747524da 100644 --- a/hawc/apps/epi/templates/epi/outcome_list.html +++ b/hawc/apps/epi/templates/epi/outcome_list.html @@ -64,7 +64,7 @@

Outcomes ({{page_obj.paginator.count}} found)

-{% include 'includes/paginator_with_total.html' with plural_object_name='outcomes' %} +{% include "includes/paginator.html" with plural_object_name="outcomes" %} {% endblock %} diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html index 34e76ebd16..4e4085a9d1 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html @@ -73,7 +73,7 @@

Meta-analysis/pooled results ({{page_obj.paginator.count}} found)

-{% include 'includes/paginator_with_total.html' with plural_object_name='results' %} +{% include "includes/paginator.html" with plural_object_name="results" %} {% endblock %} diff --git a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html index 313411e665..f883f3bea9 100644 --- a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html +++ b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html @@ -73,7 +73,7 @@

In vitro endpoints ({{page_obj.paginator.count}} found)

-{% include 'includes/paginator_with_total.html' with plural_object_name='endpoints' %} +{% include "includes/paginator.html" with plural_object_name="endpoints" %} {% endblock %} diff --git a/hawc/apps/riskofbias/templates/riskofbias/rob_assignment_update.html b/hawc/apps/riskofbias/templates/riskofbias/rob_assignment_update.html index a3d54836b1..83968ea05b 100644 --- a/hawc/apps/riskofbias/templates/riskofbias/rob_assignment_update.html +++ b/hawc/apps/riskofbias/templates/riskofbias/rob_assignment_update.html @@ -16,7 +16,7 @@

Update {{assessment.get_rob_name_display|lower}} assignments

- {% include "includes/paginator.html" %} + {% include "includes/paginator.html" with plural_object_name="assignments" %} {% endblock %} {% block extrajs %} diff --git a/hawc/templates/includes/paginator.html b/hawc/templates/includes/paginator.html index 16efd7154e..990f49a423 100644 --- a/hawc/templates/includes/paginator.html +++ b/hawc/templates/includes/paginator.html @@ -1,7 +1,7 @@ {% load url_replace %} {% if is_paginated %} -
    +
      {% if page_obj.has_previous %}
    • @@ -38,4 +38,5 @@
    +

    Showing {{plural_object_name|default:"items"}} {{page_obj.start_index}}-{{page_obj.end_index}} of {{paginator.count}}

    {% endif %} diff --git a/hawc/templates/includes/paginator_with_total.html b/hawc/templates/includes/paginator_with_total.html deleted file mode 100644 index 5a7d1c1f9b..0000000000 --- a/hawc/templates/includes/paginator_with_total.html +++ /dev/null @@ -1,2 +0,0 @@ -{% include "includes/paginator.html" %} -

    Showing {{plural_object_name}} {{page_obj.start_index}}-{{page_obj.end_index}} of {{paginator.count}}

    From c9bca097f5e083fae9312d980763a13df0d67d30 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Mon, 7 Nov 2022 17:16:38 -0500 Subject: [PATCH 07/18] do not return form --- hawc/apps/animal/filterset.py | 1 - hawc/apps/epi/filterset.py | 1 - hawc/apps/epimeta/filterset.py | 1 - hawc/apps/invitro/filterset.py | 1 - 4 files changed, 4 deletions(-) diff --git a/hawc/apps/animal/filterset.py b/hawc/apps/animal/filterset.py index ae52098e40..3fa49c724f 100644 --- a/hawc/apps/animal/filterset.py +++ b/hawc/apps/animal/filterset.py @@ -218,4 +218,3 @@ def change_form(self, form): ) form.fields["dose_units"].queryset = DoseUnits.objects.get_animal_units(self.assessment.id) - return form diff --git a/hawc/apps/epi/filterset.py b/hawc/apps/epi/filterset.py index 42a365409d..f8e10f2d90 100644 --- a/hawc/apps/epi/filterset.py +++ b/hawc/apps/epi/filterset.py @@ -182,4 +182,3 @@ def change_form(self, form): widget.update_filters( {"study_population__study__assessment_id": self.assessment.id} ) - return form diff --git a/hawc/apps/epimeta/filterset.py b/hawc/apps/epimeta/filterset.py index 65a480c6d5..45a5008e9b 100644 --- a/hawc/apps/epimeta/filterset.py +++ b/hawc/apps/epimeta/filterset.py @@ -92,4 +92,3 @@ def change_form(self, form): widget = form.fields[field].widget if field in ("label", "health_outcome", "exposure_name"): widget.update_filters({"protocol__study__assessment_id": self.assessment.id}) - return form diff --git a/hawc/apps/invitro/filterset.py b/hawc/apps/invitro/filterset.py index 3da714972f..67c0e76e1e 100644 --- a/hawc/apps/invitro/filterset.py +++ b/hawc/apps/invitro/filterset.py @@ -147,4 +147,3 @@ def change_form(self, form): # for cell type autocomplete for field in ("cell_type", "tissue"): form.fields[field].widget.update_filters({"study__assessment_id": self.assessment.id}) - return form From b9c2f524c40e56ab6acf440e6e7a3e6b0a4c9947 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Mon, 7 Nov 2022 17:19:01 -0500 Subject: [PATCH 08/18] use standard queryset filter instead of prefilter method --- hawc/apps/animal/filterset.py | 2 +- hawc/apps/common/filterset.py | 12 +++--------- hawc/apps/epi/filterset.py | 2 +- hawc/apps/epimeta/filterset.py | 2 +- hawc/apps/invitro/filterset.py | 2 +- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/hawc/apps/animal/filterset.py b/hawc/apps/animal/filterset.py index 3fa49c724f..aee15b4c24 100644 --- a/hawc/apps/animal/filterset.py +++ b/hawc/apps/animal/filterset.py @@ -191,7 +191,7 @@ class Meta: [3, 3], ] - def prefilter_queryset(self, queryset): + def filter_queryset(self, queryset): queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(animal_group__experiment__study__published=True) diff --git a/hawc/apps/common/filterset.py b/hawc/apps/common/filterset.py index 19af532722..74d9dc3f65 100644 --- a/hawc/apps/common/filterset.py +++ b/hawc/apps/common/filterset.py @@ -1,6 +1,7 @@ import django_filters as df from crispy_forms import layout as cfl from django import forms +from django.db.models import QuerySet from . import autocomplete from .forms import BaseFormHelper, form_actions_apply_filters @@ -98,13 +99,6 @@ def form(self): self._form = self.change_form(form) return self._form - def prefilter_queryset(self, queryset): - # any prefiltering or annotations necessary for ordering go here - pass - - def filter_queryset(self, queryset): - queryset = self.prefilter_queryset(queryset) - return super().filter_queryset(queryset) - - def change_form(self, form): + def change_form(self, form: forms.Form): + """Make modifications to the generated form if needed""" pass diff --git a/hawc/apps/epi/filterset.py b/hawc/apps/epi/filterset.py index f8e10f2d90..129a48bf28 100644 --- a/hawc/apps/epi/filterset.py +++ b/hawc/apps/epi/filterset.py @@ -155,7 +155,7 @@ class Meta: [3, 3, 3], ] - def prefilter_queryset(self, queryset): + def filter_queryset(self, queryset): queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(study_population__study__published=True) diff --git a/hawc/apps/epimeta/filterset.py b/hawc/apps/epimeta/filterset.py index 45a5008e9b..381c11a206 100644 --- a/hawc/apps/epimeta/filterset.py +++ b/hawc/apps/epimeta/filterset.py @@ -79,7 +79,7 @@ class Meta: [3, 3, 3], ] - def prefilter_queryset(self, queryset): + def filter_queryset(self, queryset): queryset = queryset.filter(protocol__study__assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(protocol__study__published=True) diff --git a/hawc/apps/invitro/filterset.py b/hawc/apps/invitro/filterset.py index 67c0e76e1e..2be13739e0 100644 --- a/hawc/apps/invitro/filterset.py +++ b/hawc/apps/invitro/filterset.py @@ -127,7 +127,7 @@ class Meta: [3, 3, 3], ] - def prefilter_queryset(self, queryset): + def filter_queryset(self, queryset): queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(experiment__study__published=True) From 0efbedcb843a4d740f58626348a8413f19d5b5c7 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 11:51:05 -0500 Subject: [PATCH 09/18] rewrite form creation hook --- hawc/apps/animal/filterset.py | 4 +++- hawc/apps/common/filterset.py | 23 +++++++++++++---------- hawc/apps/epi/filterset.py | 4 +++- hawc/apps/epimeta/filterset.py | 4 +++- hawc/apps/invitro/filterset.py | 4 +++- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/hawc/apps/animal/filterset.py b/hawc/apps/animal/filterset.py index aee15b4c24..aa68b1d021 100644 --- a/hawc/apps/animal/filterset.py +++ b/hawc/apps/animal/filterset.py @@ -197,7 +197,8 @@ def filter_queryset(self, queryset): queryset = queryset.filter(animal_group__experiment__study__published=True) return queryset - def change_form(self, form): + def create_form(self): + form = super().create_form() form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "bioassay": True}) form.fields["species"].set_filters( {"animalgroup__experiment__study__assessment_id": self.assessment.id} @@ -218,3 +219,4 @@ def change_form(self, form): ) form.fields["dose_units"].queryset = DoseUnits.objects.get_animal_units(self.assessment.id) + return form diff --git a/hawc/apps/common/filterset.py b/hawc/apps/common/filterset.py index 74d9dc3f65..981da0c528 100644 --- a/hawc/apps/common/filterset.py +++ b/hawc/apps/common/filterset.py @@ -1,7 +1,6 @@ import django_filters as df from crispy_forms import layout as cfl from django import forms -from django.db.models import QuerySet from . import autocomplete from .forms import BaseFormHelper, form_actions_apply_filters @@ -91,14 +90,18 @@ def perms(self): @property def form(self): if not hasattr(self, "_form"): - Form = self.get_form_class() - if self.is_bound: - form = Form(self.data, prefix=self.form_prefix, grid_layout=self._meta.grid_layout) - else: - form = Form(prefix=self.form_prefix, grid_layout=self._meta.grid_layout) - self._form = self.change_form(form) + self._form = self.create_form() return self._form - def change_form(self, form: forms.Form): - """Make modifications to the generated form if needed""" - pass + def create_form(self) -> forms.Form: + """Create the form used for the filterset. + + Returns: + forms.Form: a django.Form instance + """ + Form = self.get_form_class() + if self.is_bound: + form = Form(self.data, prefix=self.form_prefix, grid_layout=self._meta.grid_layout) + else: + form = Form(prefix=self.form_prefix, grid_layout=self._meta.grid_layout) + return form diff --git a/hawc/apps/epi/filterset.py b/hawc/apps/epi/filterset.py index 129a48bf28..967bc42ad1 100644 --- a/hawc/apps/epi/filterset.py +++ b/hawc/apps/epi/filterset.py @@ -161,7 +161,8 @@ def filter_queryset(self, queryset): queryset = queryset.filter(study_population__study__published=True) return queryset - def change_form(self, form): + def create_form(self): + form = super().create_form() form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "epi": True}) form.fields["metric"].widget.update_filters( {"study_population__study__assessment_id": self.assessment.id} @@ -182,3 +183,4 @@ def change_form(self, form): widget.update_filters( {"study_population__study__assessment_id": self.assessment.id} ) + return form diff --git a/hawc/apps/epimeta/filterset.py b/hawc/apps/epimeta/filterset.py index 381c11a206..19a4a2017f 100644 --- a/hawc/apps/epimeta/filterset.py +++ b/hawc/apps/epimeta/filterset.py @@ -85,10 +85,12 @@ def filter_queryset(self, queryset): queryset = queryset.filter(protocol__study__published=True) return queryset - def change_form(self, form): + def create_form(self): + form = super().create_form() form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "epi_meta": True}) form.fields["protocol"].widget.update_filters({"study__assessment_id": self.assessment.id}) for field in form.fields: widget = form.fields[field].widget if field in ("label", "health_outcome", "exposure_name"): widget.update_filters({"protocol__study__assessment_id": self.assessment.id}) + return form diff --git a/hawc/apps/invitro/filterset.py b/hawc/apps/invitro/filterset.py index 2be13739e0..88fa42b881 100644 --- a/hawc/apps/invitro/filterset.py +++ b/hawc/apps/invitro/filterset.py @@ -133,7 +133,8 @@ def filter_queryset(self, queryset): queryset = queryset.filter(experiment__study__published=True) return queryset - def change_form(self, form): + def create_form(self): + form = super().create_form() form.fields["dose_units"].queryset = DoseUnits.objects.get_iv_units(self.assessment.id) form.fields["studies"].set_filters({"assessment_id": self.assessment.id, "in_vitro": True}) # for endpoint autocomplete @@ -147,3 +148,4 @@ def change_form(self, form): # for cell type autocomplete for field in ("cell_type", "tissue"): form.fields[field].widget.update_filters({"study__assessment_id": self.assessment.id}) + return form From a83d43bfb7e4a5a010252bd0302467b24897873a Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 12:53:34 -0500 Subject: [PATCH 10/18] check views individually --- hawc/apps/animal/filterset.py | 3 ++- hawc/apps/animal/views.py | 24 ------------------------ hawc/apps/epi/filterset.py | 3 ++- hawc/apps/epimeta/filterset.py | 7 ++++--- hawc/apps/invitro/filterset.py | 7 +++++-- 5 files changed, 13 insertions(+), 31 deletions(-) diff --git a/hawc/apps/animal/filterset.py b/hawc/apps/animal/filterset.py index aa68b1d021..a1536478ff 100644 --- a/hawc/apps/animal/filterset.py +++ b/hawc/apps/animal/filterset.py @@ -192,10 +192,11 @@ class Meta: ] def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(animal_group__experiment__study__published=True) - return queryset + return queryset.select_related("animal_group__experiment__study") def create_form(self): form = super().create_form() diff --git a/hawc/apps/animal/views.py b/hawc/apps/animal/views.py index ee28bf0bae..3b19d80fda 100644 --- a/hawc/apps/animal/views.py +++ b/hawc/apps/animal/views.py @@ -409,30 +409,6 @@ class EndpointFilterList(BaseFilterList): model = models.Endpoint filterset_class = filterset.EndpointFilterSet - def get_queryset(self): - return ( - super() - .get_queryset() - .select_related( - "assessment", - "animal_group__experiment__dtxsid", - "animal_group__experiment__study", - "animal_group__species", - "animal_group__strain", - ) - .prefetch_related( - "bmd_models", - "effects", - "groups", - "animal_group__parents", - "animal_group__siblings", - "animal_group__children", - "animal_group__dosing_regime__doses__dose_units", - "animal_group__experiment__study__searches", - "animal_group__experiment__study__identifiers", - ) - ) - @method_decorator(beta_tester_required, name="dispatch") class EndpointListV2(BaseList): diff --git a/hawc/apps/epi/filterset.py b/hawc/apps/epi/filterset.py index 967bc42ad1..e7d31e51bd 100644 --- a/hawc/apps/epi/filterset.py +++ b/hawc/apps/epi/filterset.py @@ -156,10 +156,11 @@ class Meta: ] def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(study_population__study__published=True) - return queryset + return queryset.select_related("study_population__study") def create_form(self): form = super().create_form() diff --git a/hawc/apps/epimeta/filterset.py b/hawc/apps/epimeta/filterset.py index 19a4a2017f..9a60d6e51d 100644 --- a/hawc/apps/epimeta/filterset.py +++ b/hawc/apps/epimeta/filterset.py @@ -52,14 +52,14 @@ class MetaResultFilterSet(BaseFilterSet): ("label", "meta result label"), ("protocol__name", "protocol"), ("health_outcome", "health outcome"), - ("exposure", "exposure"), + ("estimate", "estimate"), ), choices=( ("study", "study"), ("meta result label", "meta result label"), ("protocol", "protocol"), ("health outcome", "health outcome"), - ("exposure", "exposure"), + ("estimate", "estimate"), ), ) @@ -80,10 +80,11 @@ class Meta: ] def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) queryset = queryset.filter(protocol__study__assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(protocol__study__published=True) - return queryset + return queryset.select_related("protocol__study") def create_form(self): form = super().create_form() diff --git a/hawc/apps/invitro/filterset.py b/hawc/apps/invitro/filterset.py index 88fa42b881..a06e4b8d02 100644 --- a/hawc/apps/invitro/filterset.py +++ b/hawc/apps/invitro/filterset.py @@ -75,7 +75,7 @@ class EndpointFilterSet(BaseFilterSet): help_text="ex: counts", ) dose_units = df.ModelChoiceFilter( - field_name="study_population__exposures__metric_units", + field_name="experiment__dose_units", label="Dose units", queryset=DoseUnits.objects.all(), ) @@ -128,10 +128,13 @@ class Meta: ] def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(experiment__study__published=True) - return queryset + return queryset.select_related( + "experiment__study", "experiment__dose_units", "chemical" + ).prefetch_related("effects") def create_form(self): form = super().create_form() From 62ef78d257ac0711ec9a063ec39683f7ede5f81d Mon Sep 17 00:00:00 2001 From: munnsmunns Date: Tue, 8 Nov 2022 14:22:57 -0500 Subject: [PATCH 11/18] add columns --- .../animal/templates/animal/endpoint_list.html | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 114be7046e..6faaf01c3d 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -12,7 +12,12 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    - + + + + + + @@ -22,6 +27,11 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    Experiment Animal group Endpoint + Units + NOEL + LOEL + BMD + BMDLS @@ -45,6 +55,11 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    + + {{object.NOEL}} + {{object.LOEL}} + + {% empty %} From 9d73a52f579bba83acffcdd64469485f74fad661 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 14:44:49 -0500 Subject: [PATCH 12/18] few more updates --- hawc/apps/common/views.py | 5 +++-- hawc/apps/epi/filterset.py | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hawc/apps/common/views.py b/hawc/apps/common/views.py index de2a37f9a6..a7f4d8df03 100644 --- a/hawc/apps/common/views.py +++ b/hawc/apps/common/views.py @@ -21,6 +21,7 @@ from ..assessment.models import Assessment, BaseEndpoint, Log, TimeSpentEditing from .crumbs import Breadcrumb +from .filterset import BaseFilterSet from .helper import WebappConfig, tryParseInt logger = logging.getLogger(__name__) @@ -792,7 +793,7 @@ def get_context_data(self, **kwargs): class BaseFilterList(BaseList): - filterset_class = None # required + filterset_class: BaseFilterSet def get_paginate_by(self, qs) -> int: value = self.request.GET.get("paginate_by") @@ -805,7 +806,7 @@ def get_base_queryset(self): def filterset(self): if not hasattr(self, "_filterset"): qs = self.get_base_queryset() - self._filterset = self.filterset_class( + self._filterset: BaseFilterSet = self.filterset_class( data=self.request.GET, queryset=qs, request=self.request, assessment=self.assessment ) return self._filterset diff --git a/hawc/apps/epi/filterset.py b/hawc/apps/epi/filterset.py index e7d31e51bd..c183e314b4 100644 --- a/hawc/apps/epi/filterset.py +++ b/hawc/apps/epi/filterset.py @@ -172,8 +172,6 @@ def create_form(self): {"studypopulation__study__assessment_id": self.assessment.id} ) form.fields["metric_units"].queryset = DoseUnits.objects.get_epi_units(self.assessment.id) - ("study_population") - for field in form.fields: widget = form.fields[field].widget # for study population autocomplete From 1bba47957bcb5a2b5d08849f24c6172f167cb117 Mon Sep 17 00:00:00 2001 From: munnsmunns Date: Tue, 8 Nov 2022 15:35:35 -0500 Subject: [PATCH 13/18] WIP create a dict w/ frontend values --- frontend/animal/Endpoint.js | 10 ++++++++++ hawc/apps/animal/templates/animal/endpoint_list.html | 3 +++ 2 files changed, 13 insertions(+) diff --git a/frontend/animal/Endpoint.js b/frontend/animal/Endpoint.js index fe6231f927..f84f97faa7 100644 --- a/frontend/animal/Endpoint.js +++ b/frontend/animal/Endpoint.js @@ -145,6 +145,16 @@ class Endpoint extends Observee { }); } + static get_display_values(endpoints, dose_id) { + this.endpoints = endpoints.map(d => new Endpoint(d)); + if (_.isFinite(dose_id)) { + this.endpoints.forEach(e => e.doseUnits.activate(dose_id)); + } + var values = {} + this.endpoints.forEach(e => values[e.id] = { "Units": e.doseUnits.activeUnit.name, "NOEL": e.get_special_dose_text("NOEL"), "LOEL": e.get_special_dose_text("LOEL"), "BMD": e.get_special_bmd_value("BMD"), "BMDL": e.get_special_bmd_value("BMDl") }); + return values; + } + get_name() { return this.data.name; } diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 6faaf01c3d..6a29f1fbb6 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -79,6 +79,9 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    {% endblock %} From d049cb5ff3cbf2b4e4dc3d06871d68fe6223d223 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 21:53:14 -0500 Subject: [PATCH 14/18] update templates for header/footer --- .../templates/animal/endpoint_list.html | 25 +++------------ hawc/apps/epi/templates/epi/outcome_list.html | 29 +++-------------- .../templates/epimeta/metaresult_list.html | 30 +++-------------- .../templates/invitro/ivendpoint_list.html | 32 +++---------------- 4 files changed, 16 insertions(+), 100 deletions(-) diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 114be7046e..3105074dc3 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -1,30 +1,13 @@ {% extends 'assessment-rooted.html' %} -{% block content %} +{% load bs4 %} +{% block content %}

    Assessment endpoints ({{page_obj.paginator.count}} found)

    - {% include 'common/filter_list.html' with plural_object_name='endpoints' %} - - - - - - - - - - - - - - - - - - - + {% bs4_colgroup '15,20,15,50' %} + {% bs4_thead 'Study,Experiment,Animal group,Endpoint' %} {% for object in object_list %} diff --git a/hawc/apps/epi/templates/epi/outcome_list.html b/hawc/apps/epi/templates/epi/outcome_list.html index cc747524da..43141aa220 100644 --- a/hawc/apps/epi/templates/epi/outcome_list.html +++ b/hawc/apps/epi/templates/epi/outcome_list.html @@ -1,34 +1,13 @@ {% extends 'assessment-rooted.html' %} -{% block content %} +{% load bs4 %} +{% block content %}

    Outcomes ({{page_obj.paginator.count}} found)

    - {% include 'common/filter_list.html' with plural_object_name='outcomes' %} -
    StudyExperimentAnimal groupEndpoint
    - - - - - - - - - - - - - - - - - - - - - - + {% bs4_colgroup '17,20,17,18,14,14' %} + {% bs4_thead 'Study,Study population,Outcome,System,Effect,Diagnostic' %} {% for object in object_list %} diff --git a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html index 4e4085a9d1..b5afe9367e 100644 --- a/hawc/apps/epimeta/templates/epimeta/metaresult_list.html +++ b/hawc/apps/epimeta/templates/epimeta/metaresult_list.html @@ -1,35 +1,13 @@ {% extends 'assessment-rooted.html' %} -{% block content %} +{% load bs4 %} +{% block content %}

    Meta-analysis/pooled results ({{page_obj.paginator.count}} found)

    - {% include 'common/filter_list.html' with plural_object_name='results' %} -
    StudyStudy populationOutcomeSystemEffectDiagnostic
    - - - - - - - - - - - - - - - - - - - - - - - + {% bs4_colgroup '13,17,19,14,13,12,12' %} + {% bs4_thead 'Study,Meta result,Protocol,Health outcome,Exposure,Confidence interval,Estimate' %} {% for object in object_list %} diff --git a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html index f883f3bea9..3396254da1 100644 --- a/hawc/apps/invitro/templates/invitro/ivendpoint_list.html +++ b/hawc/apps/invitro/templates/invitro/ivendpoint_list.html @@ -1,37 +1,13 @@ {% extends 'assessment-rooted.html' %} -{% block content %} +{% load bs4 %} +{% block content %}

    In vitro endpoints ({{page_obj.paginator.count}} found)

    - {% include 'common/filter_list.html' with plural_object_name='endpoints' %} -
    StudyMeta resultProtocolHealth outcomeExposureConfidence intervalEstimate
    - - - - - - - - - - - - - - - - - - - - - - - - - + {% bs4_colgroup '10,13,12,20,16,13,8,8' %} + {% bs4_thead 'Study,Experiment,Chemical,Endpoint,Effect Category,Effects,Dose Units,Response Units' %} {% for object in object_list %} From d3b5d3d9b8250dfd47df0f3fe3bce1f82b92abd1 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 23:33:40 -0500 Subject: [PATCH 15/18] reimplement loel, noel, and bmdl fields --- hawc/apps/animal/filterset.py | 6 +++ hawc/apps/animal/managers.py | 50 +++++++++++++++++-- .../templates/animal/endpoint_list.html | 11 ++-- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/hawc/apps/animal/filterset.py b/hawc/apps/animal/filterset.py index a1536478ff..bc48c589f6 100644 --- a/hawc/apps/animal/filterset.py +++ b/hawc/apps/animal/filterset.py @@ -196,6 +196,12 @@ def filter_queryset(self, queryset): queryset = queryset.filter(assessment=self.assessment) if not self.perms["edit"]: queryset = queryset.filter(animal_group__experiment__study__published=True) + + dose_units = ( + self.form.cleaned_data["dose_units"] or self.form.fields["dose_units"].queryset.first() + ) + queryset = queryset.annotate_dose_values(dose_units) + return queryset.select_related("animal_group__experiment__study") def create_form(self): diff --git a/hawc/apps/animal/managers.py b/hawc/apps/animal/managers.py index cee4dfb9fa..d6993dc047 100644 --- a/hawc/apps/animal/managers.py +++ b/hawc/apps/animal/managers.py @@ -1,13 +1,13 @@ -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional import numpy as np import pandas as pd from django.apps import apps -from django.db import transaction -from django.db.models import Max, Min +from django.db import models, transaction +from django.db.models import Max, Min, OuterRef, QuerySet, Subquery, Value from rest_framework.serializers import ValidationError -from ..assessment.models import Assessment +from ..assessment.models import Assessment, DoseUnits from ..common.models import BaseManager, get_distinct_charfield, get_distinct_charfield_opts from ..vocab.constants import VocabularyTermType from ..vocab.models import Term @@ -104,9 +104,51 @@ def by_dose_regime(self, dose_regime): return self.filter(dose_regime=dose_regime) +class EndpointQuerySet(QuerySet): + def annotate_dose_values(self, dose_units: Optional[DoseUnits] = None) -> QuerySet: + """Annotate dose unit-specific responses from queryset, if a dose-unit is available. + + Args: + dose_units (Optional[DoseUnits]): selected dose units, if exists + """ + if dose_units is None: + return self.annotate( + units_name=Value("", output_field=models.CharField()), + noel_value=Value(None, output_field=models.FloatField(null=True)), + loel_value=Value(None, output_field=models.FloatField(null=True)), + bmd=Value(None, output_field=models.FloatField(null=True)), + bmdl=Value(None, output_field=models.FloatField(null=True)), + ) + DoseGroup = apps.get_model("animal", "DoseGroup") + noel_value_qs = DoseGroup.objects.filter( + dose_regime__animalgroup__endpoints=OuterRef("pk"), + dose_group_id=OuterRef("NOEL"), + dose_units=dose_units, + ) + loel_value_qs = DoseGroup.objects.filter( + dose_regime__animalgroup__endpoints=OuterRef("pk"), + dose_group_id=OuterRef("LOEL"), + dose_units=dose_units, + ) + Model = apps.get_model("bmd", "Model") + bmd_qs = Model.objects.filter( + selectedmodel__endpoint=OuterRef("pk"), selectedmodel__dose_units=dose_units + ) + return self.annotate( + units_name=Value(dose_units.name, output_field=models.CharField()), + noel_value=Subquery(noel_value_qs.values("dose")), + loel_value=Subquery(loel_value_qs.values("dose")), + bmd=Subquery(bmd_qs.values("output__BMD")), + bmdl=Subquery(bmd_qs.values("output__BMDL")), + ) + + class EndpointManager(BaseManager): assessment_relation = "assessment" + def get_queryset(self): + return EndpointQuerySet(self.model, using=self._db) + def published(self, assessment_id=None): return self.get_qs(assessment_id).filter(animal_group__experiment__study__published=True) diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index 3105074dc3..cb8660dc90 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -6,8 +6,8 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    {% include 'common/filter_list.html' with plural_object_name='endpoints' %}
    StudyExperimentChemicalEndpointEffect CategoryEffectsDose UnitsResponse Units
    - {% bs4_colgroup '15,20,15,50' %} - {% bs4_thead 'Study,Experiment,Animal group,Endpoint' %} + {% bs4_colgroup '12,14,15,24,7,7,7,7,7' %} + {% bs4_thead 'Study,Experiment,Animal group,Endpoint,Units,NOEL,LOEL,BMD,BMDL' %} {% for object in object_list %} @@ -28,10 +28,15 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    + + + + + {% empty %} - From e4479ce93da5b2a53c8bf0b398d8874f6cc04f86 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 23:41:15 -0500 Subject: [PATCH 16/18] use correct noel/loel names --- hawc/apps/animal/templates/animal/endpoint_list.html | 2 +- hawc/apps/animal/views.py | 6 ++++++ hawc/apps/assessment/models.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index cb8660dc90..62529bcaed 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -7,7 +7,7 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    {% include 'common/filter_list.html' with plural_object_name='endpoints' %}
    {{object.units_name|default:"-"}}{{object.noel_value|default:"-"}}{{object.loel_value|default:"-"}}{{object.bmd|default:"-"}}{{object.bmdl|default:"-"}}
    + No endpoints available
    {% bs4_colgroup '12,14,15,24,7,7,7,7,7' %} - {% bs4_thead 'Study,Experiment,Animal group,Endpoint,Units,NOEL,LOEL,BMD,BMDL' %} + {% bs4_thead header_names %} {% for object in object_list %} diff --git a/hawc/apps/animal/views.py b/hawc/apps/animal/views.py index 3b19d80fda..764504d399 100644 --- a/hawc/apps/animal/views.py +++ b/hawc/apps/animal/views.py @@ -409,6 +409,12 @@ class EndpointFilterList(BaseFilterList): model = models.Endpoint filterset_class = filterset.EndpointFilterSet + def get_context_data(self, **kwargs): + oel_names = self.assessment.get_noel_names() + header_names = f"Study,Experiment,Animal group,Endpoint,Units,{oel_names.noel},{oel_names.loel},BMD,BMDL" + kwargs.update(header_names=header_names) + return super().get_context_data(**kwargs) + @method_decorator(beta_tester_required, name="dispatch") class EndpointListV2(BaseList): diff --git a/hawc/apps/assessment/models.py b/hawc/apps/assessment/models.py index effabb9021..73ae5c3569 100644 --- a/hawc/apps/assessment/models.py +++ b/hawc/apps/assessment/models.py @@ -340,7 +340,7 @@ def get_vocabulary_display(self) -> str: else: return "" - def get_noel_names(self): + def get_noel_names(self) -> NoelNames: if self.noel_name == constants.NoelName.NEL: return NoelNames( "NEL", From 0af567231aed1abbf220128ca4abf4af6b9d727c Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 23:44:00 -0500 Subject: [PATCH 17/18] removed unused JS --- frontend/animal/Endpoint.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/animal/Endpoint.js b/frontend/animal/Endpoint.js index f84f97faa7..fe6231f927 100644 --- a/frontend/animal/Endpoint.js +++ b/frontend/animal/Endpoint.js @@ -145,16 +145,6 @@ class Endpoint extends Observee { }); } - static get_display_values(endpoints, dose_id) { - this.endpoints = endpoints.map(d => new Endpoint(d)); - if (_.isFinite(dose_id)) { - this.endpoints.forEach(e => e.doseUnits.activate(dose_id)); - } - var values = {} - this.endpoints.forEach(e => values[e.id] = { "Units": e.doseUnits.activeUnit.name, "NOEL": e.get_special_dose_text("NOEL"), "LOEL": e.get_special_dose_text("LOEL"), "BMD": e.get_special_bmd_value("BMD"), "BMDL": e.get_special_bmd_value("BMDl") }); - return values; - } - get_name() { return this.data.name; } From 33dd1213d3453d4a24e4ce93f7f517852edf4378 Mon Sep 17 00:00:00 2001 From: Andy Shapiro Date: Tue, 8 Nov 2022 23:45:22 -0500 Subject: [PATCH 18/18] remove JS --- hawc/apps/animal/templates/animal/endpoint_list.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/hawc/apps/animal/templates/animal/endpoint_list.html b/hawc/apps/animal/templates/animal/endpoint_list.html index b5859d28c0..62529bcaed 100644 --- a/hawc/apps/animal/templates/animal/endpoint_list.html +++ b/hawc/apps/animal/templates/animal/endpoint_list.html @@ -52,9 +52,6 @@

    Assessment endpoints ({{page_obj.paginator.count}} found)

    {% endblock %}